183 lines
6.8 KiB
Python
183 lines
6.8 KiB
Python
import bpy
|
|
from bpy.types import Operator
|
|
import mathutils
|
|
from mathutils import Vector, Matrix, geometry
|
|
from bpy_extras import view3d_utils
|
|
from . import utils
|
|
|
|
# def get_layer_list(self, context):
|
|
# '''return (identifier, name, description) of enum content'''
|
|
# if not context:
|
|
# return [('None', 'None','None')]
|
|
# if not context.object:
|
|
# return [('None', 'None','None')]
|
|
# return [(l.name, l.name, '') for l in context.object.data.layers] # if l != context.object.data.layers.active
|
|
|
|
## in Class
|
|
# bl_property = "layers_enum"
|
|
|
|
# layers_enum : bpy.props.EnumProperty(
|
|
# name="Send Material To Layer",
|
|
# description="Send active material to layer",
|
|
# items=get_layer_list,
|
|
# options={'HIDDEN'},
|
|
# )
|
|
|
|
class GPTB_OT_move_material_to_layer(Operator) :
|
|
bl_idname = "gp.move_material_to_layer"
|
|
bl_label = 'Move Material To Layer'
|
|
bl_description = 'Move active material to an existing or new layer'
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
layer_name : bpy.props.StringProperty(
|
|
name='Layer Name', default='', options={'SKIP_SAVE'})
|
|
|
|
copy : bpy.props.BoolProperty(
|
|
name='Copy to layer', default=False,
|
|
description='Copy strokes to layer instead of moving',
|
|
options={'SKIP_SAVE'})
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'GREASEPENCIL'
|
|
|
|
def invoke(self, context, event):
|
|
if self.layer_name:
|
|
return self.execute(context)
|
|
if not len(context.object.data.layers):
|
|
self.report({'WARNING'}, 'No layers on current GP object')
|
|
return {'CANCELLED'}
|
|
|
|
mat = context.object.data.materials[context.object.active_material_index]
|
|
self.mat_name = mat.name
|
|
|
|
# wm.invoke_search_popup(self)
|
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
# layout.operator_context = "INVOKE_DEFAULT"
|
|
layout.prop(self, 'copy', text='Copy Strokes')
|
|
action_label = 'Copy' if self.copy else 'Move'
|
|
layout.label(text=f'{action_label} material "{self.mat_name}" to layer:', icon='MATERIAL')
|
|
|
|
col = layout.column()
|
|
col.prop(self, 'layer_name', text='', icon='ADD')
|
|
# if self.layer_name:
|
|
# col.label(text='Ok/Enter to create new layer', icon='INFO')
|
|
|
|
col.separator()
|
|
for l in reversed(context.object.data.layers):
|
|
|
|
icon = 'GREASEPENCIL' if l == context.object.data.layers.active else 'BLANK1'
|
|
row = col.row()
|
|
row.alignment = 'LEFT'
|
|
op = col.operator('gp.move_material_to_layer', text=l.name, icon=icon, emboss=False)
|
|
op.layer_name = l.name
|
|
op.copy = self.copy
|
|
|
|
def execute(self, context):
|
|
if not self.layer_name:
|
|
print('Out')
|
|
return {'CANCELLED'}
|
|
|
|
## Active + selection
|
|
pool = [o for o in bpy.context.selected_objects if o.type == 'GREASEPENCIL']
|
|
if not context.object in pool:
|
|
pool.append(context.object)
|
|
|
|
mat = context.object.data.materials[context.object.active_material_index]
|
|
|
|
print(f'Moving strokes using material "{mat.name}" on {len(pool)} object(s)')
|
|
# import time
|
|
# 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:
|
|
print(f'/!\ {ob.name} has no Material {mat.name} in stack')
|
|
continue
|
|
|
|
gpl = ob.data.layers
|
|
|
|
if not (target_layer := gpl.get(self.layer_name)):
|
|
target_layer = gpl.new(self.layer_name)
|
|
|
|
## List existing frames
|
|
key_dict = {f.frame_number : f for f in target_layer.frames}
|
|
|
|
### 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:
|
|
## ! infinite loop if target layer is included
|
|
continue
|
|
for fr in layer.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):
|
|
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)
|
|
key_dict[dest_key.frame_number] = dest_key
|
|
|
|
print(f'{ob.name} : frame {fr.frame_number}')
|
|
## Replicate strokes in dest_keys
|
|
stroke_to_delete = []
|
|
for s_idx, s in enumerate(fr.drawing.strokes):
|
|
if s.material_index == mat_index:
|
|
utils.copy_stroke_to_frame(s, dest_key)
|
|
stroke_to_delete.append(s_idx)
|
|
|
|
## Debug
|
|
# if time.time() - t > 10:
|
|
# print('TIMEOUT')
|
|
# return {'CANCELLED'}
|
|
|
|
sct += len(stroke_to_delete)
|
|
|
|
## Remove from source frame (fr)
|
|
if not self.copy:
|
|
# print('Removing frames') # Dbg
|
|
if stroke_to_delete:
|
|
fr.drawing.remove_strokes(indices=stroke_to_delete)
|
|
|
|
## ? Remove frame if layer is empty ? -> probably not, otherwise will show previous frame
|
|
|
|
fct += 1
|
|
|
|
|
|
if fct:
|
|
oct += 1
|
|
print(f'{ob.name}: Moved {fct} frames -> {sct} Strokes') # Dbg
|
|
|
|
total += fct
|
|
|
|
report_type = 'INFO' if total else 'WARNING'
|
|
if self.copy:
|
|
self.report({report_type}, f'Copied {total} frames accross {oct} object(s)')
|
|
else:
|
|
self.report({report_type}, f'Moved {total} frames accross {oct} object(s)')
|
|
return {'FINISHED'}
|
|
|
|
|
|
# def menu_duplicate_and_send_to_layer(self, context):
|
|
# if context.space_data.ui_mode == 'GPENCIL':
|
|
# self.layout.operator_context = 'INVOKE_REGION_WIN'
|
|
# self.layout.operator('gp.duplicate_send_to_layer', text='Move Keys To Layer').delete_source = True
|
|
# self.layout.operator('gp.duplicate_send_to_layer', text='Copy Keys To Layer')
|
|
|
|
classes = (
|
|
GPTB_OT_move_material_to_layer,
|
|
)
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |