From b62a23858c7df12e9cfbc24e27245059915dc9c6 Mon Sep 17 00:00:00 2001 From: Pullusb Date: Wed, 20 Oct 2021 16:05:56 +0200 Subject: [PATCH] improved Filtered create empty frames ops 1.7.1 - feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers --- CHANGELOG.md | 3 + GP_guided_colorize/OP_create_empty_frames.py | 146 +++++++++++++++++-- OP_key_duplicate_send.py | 4 +- __init__.py | 2 +- 4 files changed, 140 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b637f08..0248e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +1.7.1 + +- feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers 1.7.0 diff --git a/GP_guided_colorize/OP_create_empty_frames.py b/GP_guided_colorize/OP_create_empty_frames.py index d9f37ab..b8529e4 100644 --- a/GP_guided_colorize/OP_create_empty_frames.py +++ b/GP_guided_colorize/OP_create_empty_frames.py @@ -1,33 +1,156 @@ ## Create empty keyframe where keyframe exists in layers above. import bpy +from bpy.props import (FloatProperty, + BoolProperty, + EnumProperty, + StringProperty, + IntProperty) + +## copied from OP_key_duplicate_send +def get_layer_list(self, context): + '''return (identifier, name, description) of enum content''' + return [(l.info, l.info, '') for l in context.object.data.layers if l != context.object.data.layers.active] class GP_OT_create_empty_frames(bpy.types.Operator): bl_idname = "gp.create_empty_frames" - bl_label = "Create empty frames" + bl_label = "Create Empty Frames" bl_description = "Create new empty frames on active layer where there is a frame in layer above\n(usefull in color layers to match line frames)" bl_options = {'REGISTER','UNDO'} + layers_enum : EnumProperty( + name="Duplicate to layers", + description="Duplicate selected keys in active layer and send them to choosen layer", + items=get_layer_list + ) + + targeted_layers : EnumProperty( + name="Sources", # Empty keys from targets + description="Duplicate keys as empty on current layer from selected targets", + default="ALL_ABOVE", + items=( + ('ALL_ABOVE', 'All Layers Above', 'Empty frames from all layers above'), + ('ALL_BELOW', 'All Layers Below', 'Empty frames from all layers below'), + ('NUMBER', 'Number Above Or Below', 'Positive number above layers\nNegative number below layers'), + ('ABOVE', 'Layer Directly Above', 'Empty frames from layer directly above'), + ('BELOW', 'Layer Directly Below', 'Empty frames from layer directly below'), + ('ALL_VISIBLE', 'Visible', 'Empty frames from all visible layers'), + ('CHOSEN', 'Chosen layer', ''), + ) + ) + + range : EnumProperty( + name="Range", + description="Restraint empty copy from a defined range", + default="FULL", + items=( + ('FULL', 'Full range', 'Empty frames from all layers above'), + ('BEFORE', 'Before Time Cursor', 'Empty frames from all layers below'), + ('AFTER', 'After Time Cursor', 'Only After time cursor'), + ('SCENE', 'On scene range', 'Restric to Scene/Preview range'), + ) + ) + + number : IntProperty(name='Number', + default=1, + description='Number of layer to create empty key from\nabove (positive) or layer below (negative)', + options={'SKIP_SAVE'}) + @classmethod def poll(cls, context): return context.active_object is not None and context.active_object.type == 'GPENCIL' + def invoke(self, context, event): + # Possible preset with shortcut + # if event.alt: + # self.targeted_layers = 'ALL_VISIBLE' + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + layout = self.layout + # layout.label(text='Create Empty Frames From Other Layers') + # target + layout.prop(self, 'targeted_layers') + if self.targeted_layers == 'CHOSEN': + if self.layers_enum: + layout.prop(self, 'layers_enum') + else: + layout.label(text='No other layers to match keyframe') + + elif self.targeted_layers == 'NUMBER': + row = layout.row() + row.prop(self, 'number') + row.active = self.number != 0 + if self.number == 0: + layout.label(text="Can't have 0 as value") + + layout.separator() + layout.prop(self, 'range') + if self.range == 'SCENE': + if context.scene.use_preview_range: + layout.label(text='Using preview range', icon='INFO') + def execute(self, context): obj = context.object gpl = obj.data.layers - gpl.active_index + + print(self.targeted_layers) + if self.targeted_layers == 'ALL_ABOVE': + tgt_layers = [l for i, l in enumerate(gpl) if i > gpl.active_index] + + elif self.targeted_layers == 'ALL_BELOW': + tgt_layers = [l for i, l in enumerate(gpl) if i < gpl.active_index] + + elif self.targeted_layers == 'ABOVE': + tgt_layers = [l for i, l in enumerate(gpl) if i == gpl.active_index + 1] + + elif self.targeted_layers == 'BELOW': + tgt_layers = [l for i, l in enumerate(gpl) if i == gpl.active_index - 1] + + elif self.targeted_layers == 'ALL_VISIBLE': + tgt_layers = [l for l in gpl if not l.hide and l != gpl.active] + + elif self.targeted_layers == 'CHOSEN': + if not self.layers_enum: + self.report({'ERROR'}, f"No chosen layers, aborted") + return {'CANCELLED'} + tgt_layers = [l for l in gpl if l.info == self.layers_enum] + + elif self.targeted_layers == 'NUMBER': + if self.number == 0: + self.report({'ERROR'}, f"Can't have 0 as value") + return {'CANCELLED'} + + l_range = gpl.active_index + self.number + print('l_range: ', l_range) + if self.number > 0: # positive + tgt_layers = [l for i, l in enumerate(gpl) if gpl.active_index < i <= l_range] + else: + tgt_layers = [l for i, l in enumerate(gpl) if gpl.active_index > i >= l_range] - ## Only possible on 'fill' layer ?? - # if not 'fill' in gpl.active.info.lower(): - # self.report({'ERROR'}, f"There must be 'fill' text in layer name") - # return {'CANCELLED'} + if not tgt_layers: + self.report({'ERROR'}, f"No layers found with chosen Targets") + return {'CANCELLED'} frame_id_list = [] - for i, l in enumerate(gpl): - # don't list layer below - if i <= gpl.active_index: - continue - # print(l.info, "index:", i) + for l in tgt_layers: for f in l.frames: + + ## frame filter + if self.range != 'FULL': # FULl = No filter + if self.range == 'BEFORE': + if not f.frame_number <= context.scene.frame_current: + continue + elif self.range == 'AFTER': + if not f.frame_number >= context.scene.frame_current: + continue + elif self.range == 'SCENE': + if context.scene.use_preview_range: + if not context.scene.frame_preview_start <= f.frame_number <= context.scene.frame_preview_end: + continue + else: + if not context.scene.frame_start <= f.frame_number <= context.scene.frame_end: + continue + frame_id_list.append(f.frame_number) frame_id_list = list(set(frame_id_list)) @@ -50,7 +173,6 @@ class GP_OT_create_empty_frames(bpy.types.Operator): return {'FINISHED'} - def register(): bpy.utils.register_class(GP_OT_create_empty_frames) diff --git a/OP_key_duplicate_send.py b/OP_key_duplicate_send.py index 32c3c94..00f64eb 100644 --- a/OP_key_duplicate_send.py +++ b/OP_key_duplicate_send.py @@ -110,9 +110,9 @@ def register_keymaps(): addon = bpy.context.window_manager.keyconfigs.addon # km = addon.keymaps.new(name = "Screen", space_type = "EMPTY") km = addon.keymaps.new(name = "Dopesheet", space_type = "DOPESHEET_EDITOR") - kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", alt=False, ctrl=True, shift=True, any=False) + kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='D', value="PRESS", ctrl=True, shift=True) addon_keymaps.append((km,kmi)) - kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", alt=False, ctrl=True, shift=True, any=False) + kmi = km.keymap_items.new('gp.duplicate_send_to_layer', type='X', value="PRESS", ctrl=True, shift=True) kmi.properties.delete_source = True addon_keymaps.append((km,kmi)) diff --git a/__init__.py b/__init__.py index b83125b..cb9553c 100755 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ bl_info = { "name": "GP toolbox", "description": "Set of tools for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (1, 7, 0), +"version": (1, 7, 1), "blender": (2, 91, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "",