Compare commits
No commits in common. "58e6816e392d4a840c947541da75d7ed1c05461b" and "c8763f5ca4ea63b736635365ef96963f0d405b6d" have entirely different histories.
58e6816e39
...
c8763f5ca4
|
@ -373,7 +373,11 @@ def add_multiple_strokes(stroke_list, layer=None, use_current_frame=True, select
|
|||
|
||||
for s in stroke_list:
|
||||
add_stroke(s, target_frame, layer, obj, select=select)
|
||||
|
||||
'''
|
||||
for s in stroke_data:
|
||||
add_stroke(s, target_frame)
|
||||
'''
|
||||
|
||||
# print(len(stroke_list), 'strokes pasted')
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import numpy as np
|
|||
from time import time
|
||||
from .utils import (location_to_region, region_to_location)
|
||||
|
||||
## DISABLED (in init, also in menu append, see register below)
|
||||
|
||||
"""
|
||||
## Do not work on multiple object
|
||||
def batch_flat_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False):
|
||||
|
|
|
@ -21,7 +21,6 @@ from .utils import get_addon_prefs, is_vector_close
|
|||
# PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<tag2>[A-Z]{1,6}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
|
||||
PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
|
||||
|
||||
# TODO: allow a more flexible prefix pattern
|
||||
|
||||
def layer_name_build(layer, prefix='', desc='', suffix=''):
|
||||
'''GET a layer and argument to build and assign name
|
||||
|
@ -156,16 +155,13 @@ class GPTB_OT_layer_name_build(Operator):
|
|||
gpl = ob.data.layers
|
||||
act = gpl.active
|
||||
if not act:
|
||||
act = ob.data.layer_groups.active
|
||||
|
||||
if not act:
|
||||
self.report({'ERROR'}, 'No layer active')
|
||||
self.report({'ERROR'}, 'no layer active')
|
||||
return {"CANCELLED"}
|
||||
|
||||
layer_name_build(act, prefix=self.prefix, desc=self.desc, suffix=self.suffix)
|
||||
|
||||
## /!\ Deactivate multi-selection on layer !
|
||||
## Somethimes it affect a random layer that is still considered selected
|
||||
## Deactivate multi-selection on layer !
|
||||
## somethimes it affect a random layer that is still considered selected
|
||||
# for l in gpl:
|
||||
# if l.select or l == act:
|
||||
# layer_name_build(l, prefix=self.prefix, desc=self.desc, suffix=self.suffix)
|
||||
|
@ -173,6 +169,79 @@ class GPTB_OT_layer_name_build(Operator):
|
|||
return {"FINISHED"}
|
||||
|
||||
|
||||
def grp_toggle(l, mode='TOGGLE'):
|
||||
'''take mode in (TOGGLE, GROUP, UNGROUP) '''
|
||||
grp_item_id = ' - '
|
||||
res = re.search(r'^(\s{1,3}-\s{0,3})(.*)', l.name)
|
||||
if not res and mode in ('TOGGLE', 'GROUP'):
|
||||
# No gpr : add group prefix after stripping all space and dash
|
||||
l.name = grp_item_id + l.name.lstrip(' -')
|
||||
|
||||
elif res and mode in ('TOGGLE', 'UNGROUP'):
|
||||
# found : delete group prefix
|
||||
l.name = res.group(2)
|
||||
|
||||
|
||||
class GPTB_OT_layer_group_toggle(Operator):
|
||||
bl_idname = "gp.layer_group_toggle"
|
||||
bl_label = "Group Toggle"
|
||||
bl_description = "Group or ungroup a layer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
# group : StringProperty(default='', options={'SKIP_SAVE'})
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.object
|
||||
gpl = ob.data.layers
|
||||
act = gpl.active
|
||||
if not act:
|
||||
self.report({'ERROR'}, 'no layer active')
|
||||
return {"CANCELLED"}
|
||||
for l in gpl:
|
||||
if l.select or l == act:
|
||||
grp_toggle(l)
|
||||
return {"FINISHED"}
|
||||
|
||||
class GPTB_OT_layer_new_group(Operator):
|
||||
bl_idname = "gp.layer_new_group"
|
||||
bl_label = "New Group"
|
||||
bl_description = "Create a group from active layer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.object
|
||||
gpl = ob.data.layers
|
||||
act = gpl.active
|
||||
if not act:
|
||||
self.report({'ERROR'}, 'no layer active')
|
||||
return {"CANCELLED"}
|
||||
|
||||
res = re.search(PATTERN, act.name)
|
||||
if not res:
|
||||
self.report({'ERROR'}, 'Could not create a group name, create a layer manually')
|
||||
return {"CANCELLED"}
|
||||
|
||||
name = res.group('name').strip(' -')
|
||||
if not name:
|
||||
self.report({'ERROR'}, f'No name found in {act.name}')
|
||||
return {"CANCELLED"}
|
||||
|
||||
if name in [l.name.strip(' -') for l in gpl]:
|
||||
self.report({'WARNING'}, f'Name already exists: {act.name}')
|
||||
return {"FINISHED"}
|
||||
|
||||
grp_toggle(act, mode='GROUP')
|
||||
n = gpl.new(name, set_active=False)
|
||||
n.use_onion_skinning = n.use_lights = False
|
||||
n.hide = True
|
||||
n.opacity = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
#-## SELECTION MANAGEMENT ##-#
|
||||
|
||||
def activate_channel_group_color(context):
|
||||
|
@ -749,6 +818,8 @@ def unregister_keymaps():
|
|||
classes=(
|
||||
GPTB_OT_rename_gp_layer,
|
||||
GPTB_OT_layer_name_build,
|
||||
GPTB_OT_layer_group_toggle,
|
||||
GPTB_OT_layer_new_group,
|
||||
GPTB_OT_select_set_same_prefix,
|
||||
GPTB_OT_select_set_same_color,
|
||||
|
||||
|
|
|
@ -93,7 +93,6 @@ class GPTB_OT_move_material_to_layer(Operator) :
|
|||
# t = time.time() # Dbg
|
||||
total = 0
|
||||
oct = 0
|
||||
|
||||
for ob in pool:
|
||||
mat_index = next((i for i, ms in enumerate(ob.material_slots) if ms.material and ms.material == mat), None)
|
||||
if mat_index is None:
|
||||
|
@ -111,26 +110,26 @@ class GPTB_OT_move_material_to_layer(Operator) :
|
|||
### Move Strokes to a new key (or existing key if comming for yet another layer)
|
||||
fct = 0
|
||||
sct = 0
|
||||
for layer in gpl:
|
||||
if layer == target_layer:
|
||||
for l in gpl:
|
||||
if l == target_layer:
|
||||
## ! infinite loop if target layer is included
|
||||
continue
|
||||
for fr in layer.frames:
|
||||
for f in l.frames:
|
||||
## skip if no stroke has active material
|
||||
if not next((s for s in fr.drawing.strokes if s.material_index == mat_index), None):
|
||||
if not next((s for s in f.drawing.strokes if s.material_index == mat_index), None):
|
||||
continue
|
||||
## Get/Create a destination frame and keep a reference to it
|
||||
if not (dest_key := key_dict.get(fr.frame_number)):
|
||||
dest_key = target_layer.frames.new(fr.frame_number)
|
||||
if not (dest_key := key_dict.get(f.frame_number)):
|
||||
dest_key = target_layer.frames.new(f.frame_number)
|
||||
key_dict[dest_key.frame_number] = dest_key
|
||||
|
||||
print(f'{ob.name} : frame {fr.frame_number}')
|
||||
print(f'{ob.name} : frame {f.frame_number}')
|
||||
## Replicate strokes in dest_keys
|
||||
stroke_to_delete = []
|
||||
for s_idx, s in enumerate(fr.drawing.strokes):
|
||||
for s in f.drawing.strokes:
|
||||
if s.material_index == mat_index:
|
||||
utils.copy_stroke_to_frame(s, dest_key)
|
||||
stroke_to_delete.append(s_idx)
|
||||
stroke_to_delete.append(s)
|
||||
|
||||
## Debug
|
||||
# if time.time() - t > 10:
|
||||
|
@ -139,16 +138,17 @@ class GPTB_OT_move_material_to_layer(Operator) :
|
|||
|
||||
sct += len(stroke_to_delete)
|
||||
|
||||
## Remove from source frame (fr)
|
||||
# print('Removing frames') # Dbg
|
||||
## Remove from source frame (f)
|
||||
if not self.copy:
|
||||
# print('Removing frames') # Dbg
|
||||
if stroke_to_delete:
|
||||
fr.drawing.remove_strokes(indices=stroke_to_delete)
|
||||
for s in reversed(stroke_to_delete):
|
||||
f.drawing.strokes.remove(s)
|
||||
|
||||
## ? Remove frame if layer is empty ? -> probably not, otherwise will show previous frame
|
||||
## ? Remove frame if layer is empty ? -> probably not, will show previous frame
|
||||
|
||||
fct += 1
|
||||
|
||||
l.frames.update()
|
||||
|
||||
|
||||
if fct:
|
||||
oct += 1
|
||||
|
|
|
@ -4,7 +4,7 @@ bl_info = {
|
|||
"name": "GP toolbox",
|
||||
"description": "Tool set for Grease Pencil in animation production",
|
||||
"author": "Samuel Bernou, Christophe Seux",
|
||||
"version": (4, 0, 2),
|
||||
"version": (4, 0, 1),
|
||||
"blender": (4, 3, 0),
|
||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||
"warning": "",
|
||||
|
@ -35,7 +35,7 @@ from . import OP_brushes
|
|||
from . import OP_file_checker
|
||||
from . import OP_copy_paste
|
||||
from . import OP_realign
|
||||
# from . import OP_flat_reproject # Disabled
|
||||
from . import OP_flat_reproject
|
||||
from . import OP_depth_move
|
||||
from . import OP_key_duplicate_send
|
||||
from . import OP_layer_manager
|
||||
|
@ -793,7 +793,7 @@ addon_modules = (
|
|||
OP_brushes,
|
||||
OP_cursor_snap_canvas,
|
||||
OP_copy_paste,
|
||||
# OP_flat_reproject # Disabled,
|
||||
OP_flat_reproject,
|
||||
OP_realign,
|
||||
OP_depth_move,
|
||||
OP_key_duplicate_send,
|
||||
|
|
194
utils.py
194
utils.py
|
@ -10,80 +10,14 @@ from math import sqrt
|
|||
from mathutils import Vector
|
||||
from sys import platform
|
||||
|
||||
## constants values
|
||||
|
||||
|
||||
## Default stroke and points attributes
|
||||
stroke_attr = [
|
||||
'start_cap',
|
||||
'end_cap',
|
||||
'softness',
|
||||
'material_index',
|
||||
'fill_opacity',
|
||||
'fill_color',
|
||||
'cyclic',
|
||||
'aspect_ratio',
|
||||
'time_start',
|
||||
# 'curve_type', # read-only
|
||||
]
|
||||
|
||||
point_attr = [
|
||||
'position',
|
||||
'radius',
|
||||
'rotation',
|
||||
'opacity',
|
||||
'vertex_color',
|
||||
'delta_time',
|
||||
# 'select',
|
||||
]
|
||||
|
||||
### Attribute value, types and shape
|
||||
|
||||
attribute_value_string = {
|
||||
'FLOAT': "value",
|
||||
'INT': "value",
|
||||
'FLOAT_VECTOR': "vector",
|
||||
'FLOAT_COLOR': "color",
|
||||
'BYTE_COLOR': "color",
|
||||
'STRING': "value",
|
||||
'BOOLEAN': "value",
|
||||
'FLOAT2': "value",
|
||||
'INT8': "value",
|
||||
'INT32_2D': "value",
|
||||
'QUATERNION': "value",
|
||||
'FLOAT4X4': "value",
|
||||
}
|
||||
|
||||
attribute_value_dtype = {
|
||||
'FLOAT': np.float32,
|
||||
'INT': np.dtype('int'),
|
||||
'FLOAT_VECTOR': np.float32,
|
||||
'FLOAT_COLOR': np.float32,
|
||||
'BYTE_COLOR': np.int8,
|
||||
'STRING': np.dtype('str'),
|
||||
'BOOLEAN': np.dtype('bool'),
|
||||
'FLOAT2': np.float32,
|
||||
'INT8': np.int8,
|
||||
'INT32_2D': np.dtype('int'),
|
||||
'QUATERNION': np.float32,
|
||||
'FLOAT4X4': np.float32,
|
||||
}
|
||||
|
||||
attribute_value_shape = {
|
||||
'FLOAT': (),
|
||||
'INT': (),
|
||||
'FLOAT_VECTOR': (3,),
|
||||
'FLOAT_COLOR': (4,),
|
||||
'BYTE_COLOR': (4,),
|
||||
'STRING': (),
|
||||
'BOOLEAN': (),
|
||||
'FLOAT2':(2,),
|
||||
'INT8': (),
|
||||
'INT32_2D': (2,),
|
||||
'QUATERNION': (4,),
|
||||
'FLOAT4X4': (4,4),
|
||||
}
|
||||
|
||||
""" def get_gp_parent(layer) :
|
||||
if layer.parent_type == "BONE" and layer.parent_bone :
|
||||
return layer.parent.pose.bones.get(layer.parent_bone)
|
||||
else :
|
||||
return layer.parent
|
||||
"""
|
||||
|
||||
def translate_range(OldValue, OldMin, OldMax, NewMax, NewMin):
|
||||
return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
|
||||
|
@ -623,30 +557,64 @@ def copy_stroke_to_frame(s, frame, select=True):
|
|||
return created stroke
|
||||
'''
|
||||
|
||||
frame.drawing.add_strokes([len(s.points)])
|
||||
ns = frame.drawing.strokes[-1]
|
||||
# print(len(s.points), 'new:', len(ns.points))
|
||||
#ns.material_index
|
||||
ns = frame.drawing.strokes.new()
|
||||
|
||||
## Set strokes attr
|
||||
stroke_attr = [
|
||||
'line_width',
|
||||
'material_index',
|
||||
'draw_cyclic',
|
||||
'use_cyclic',
|
||||
'uv_scale',
|
||||
'uv_rotation',
|
||||
'hardness',
|
||||
'uv_translation',
|
||||
'vertex_color_fill',
|
||||
]
|
||||
|
||||
## replicate attributes (simple loop)
|
||||
## TODO : might need to create atribute domain if does not exists in destination
|
||||
for attr in stroke_attr:
|
||||
if not hasattr(s, attr):
|
||||
continue
|
||||
# print(f'transfer stroke {attr}') # Dbg
|
||||
setattr(ns, attr, getattr(s, attr))
|
||||
|
||||
for src_p, dest_p in zip(s.points, ns.points):
|
||||
for attr in point_attr:
|
||||
setattr(dest_p, attr, getattr(src_p, attr))
|
||||
## Define selection
|
||||
dest_p.select=select
|
||||
## create points
|
||||
point_count = len(s.points)
|
||||
ns.points.add(len(s.points))
|
||||
|
||||
## Set points attr
|
||||
# for p, np in zip(s.points, ns.points):
|
||||
flat_list = [0.0] * point_count
|
||||
flat_uv_fill_list = [0.0, 0.0] * point_count
|
||||
flat_vector_list = [0.0, 0.0, 0.0] * point_count
|
||||
flat_color_list = [0.0, 0.0, 0.0, 0.0] * point_count
|
||||
|
||||
single_attr = [
|
||||
'pressure',
|
||||
'strength',
|
||||
'uv_factor',
|
||||
'uv_rotation',
|
||||
]
|
||||
|
||||
## Direcly iterate over attribute ?
|
||||
# src_start = src_dr.curve_offsets[0].value
|
||||
# src_end = src_start + data_size
|
||||
# dst_start = dst_dr.curve_offsets[0].value
|
||||
# dst_end = dst_start + data_size
|
||||
# for src_idx, dest_idx in zip(range(src_start, src_end),range(dst_start, dst_end)):
|
||||
# setattr(dest_attr.data[dest_idx], val_type, getattr(source_attr.data[src_idx], val_type))
|
||||
for attr in single_attr:
|
||||
# print(f'transfer point {attr}') # Dbg
|
||||
s.points.foreach_get(attr, flat_list)
|
||||
ns.points.foreach_set(attr, flat_list)
|
||||
|
||||
# print(f'transfer point co') # Dbg
|
||||
s.points.foreach_get('co', flat_vector_list)
|
||||
ns.points.foreach_set('co', flat_vector_list)
|
||||
|
||||
# print(f'transfer point uv_fill') # Dbg
|
||||
s.points.foreach_get('uv_fill', flat_uv_fill_list)
|
||||
ns.points.foreach_set('uv_fill', flat_uv_fill_list)
|
||||
|
||||
# print(f'transfer point vertex_color') # Dbg
|
||||
s.points.foreach_get('vertex_color', flat_color_list)
|
||||
ns.points.foreach_set('vertex_color', flat_color_list)
|
||||
|
||||
ns.select = select
|
||||
ns.points.update()
|
||||
return ns
|
||||
|
||||
"""## Works, but do not copy all attributes type (probably ok for GP though)
|
||||
|
@ -689,6 +657,52 @@ def bulk_frame_copy_attributes(source_attr, target_attr):
|
|||
# setattr(dest_attr.data[dest_idx], val_type, getattr(source_attr.data[src_idx], val_type))
|
||||
"""
|
||||
|
||||
attribute_value_string = {
|
||||
'FLOAT': "value",
|
||||
'INT': "value",
|
||||
'FLOAT_VECTOR': "vector",
|
||||
'FLOAT_COLOR': "color",
|
||||
'BYTE_COLOR': "color",
|
||||
'STRING': "value",
|
||||
'BOOLEAN': "value",
|
||||
'FLOAT2': "value",
|
||||
'INT8': "value",
|
||||
'INT32_2D': "value",
|
||||
'QUATERNION': "value",
|
||||
'FLOAT4X4': "value",
|
||||
}
|
||||
|
||||
attribute_value_dtype = {
|
||||
'FLOAT': np.float32,
|
||||
'INT': np.dtype('int'),
|
||||
'FLOAT_VECTOR': np.float32,
|
||||
'FLOAT_COLOR': np.float32,
|
||||
'BYTE_COLOR': np.int8,
|
||||
'STRING': np.dtype('str'),
|
||||
'BOOLEAN': np.dtype('bool'),
|
||||
'FLOAT2': np.float32,
|
||||
'INT8': np.int8,
|
||||
'INT32_2D': np.dtype('int'),
|
||||
'QUATERNION': np.float32,
|
||||
'FLOAT4X4': np.float32,
|
||||
}
|
||||
|
||||
attribute_value_shape = {
|
||||
'FLOAT': (),
|
||||
'INT': (),
|
||||
'FLOAT_VECTOR': (3,),
|
||||
'FLOAT_COLOR': (4,),
|
||||
'BYTE_COLOR': (4,),
|
||||
'STRING': (),
|
||||
'BOOLEAN': (),
|
||||
'FLOAT2':(2,),
|
||||
'INT8': (),
|
||||
'INT32_2D': (2,),
|
||||
'QUATERNION': (4,),
|
||||
'FLOAT4X4': (4,4),
|
||||
}
|
||||
|
||||
|
||||
def bulk_copy_attributes(source_attr, target_attr):
|
||||
'''Get and apply data as flat numpy array based on attribute type'''
|
||||
value_string = attribute_value_string[source_attr.data_type]
|
||||
|
|
Loading…
Reference in New Issue