diff --git a/parent_layer/operators.py b/parent_layer/operators.py index 4540bcb..37fdc66 100644 --- a/parent_layer/operators.py +++ b/parent_layer/operators.py @@ -8,6 +8,7 @@ from gp_interpolate.utils import (get_gp_draw_plane, create_plane, following_keys, place_object_to_ref_facing_cam, + empty_at, attr_set) @@ -27,18 +28,11 @@ class GP_OT_parent_layer(bpy.types.Operator): cls.poll_message_set("Need a Grease pencil object with an active layer") return False - direct_parent : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}) + direct_parent : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'}) - def parent_and_compensate(self, context): - settings = context.scene.gp_interpo_settings - bone = None - if settings.target_rig.type == 'ARMATURE': - bone = settings.target_rig.pose.bones.get(settings.target_bone) - if not bone: - self.report({'ERROR'}, f'{settings.target_bone} not found in armature {settings.target_rig.name}') - return {'CANCELLED'} - - self.lay.parent = settings.target_rig + def parent_and_compensate(self, obj, bone=None): + '''If bone is None, parent layer only to object''' + self.lay.parent = obj if bone: self.lay.parent_type = 'BONE' self.lay.parent_bone = bone.name @@ -65,18 +59,24 @@ class GP_OT_parent_layer(bpy.types.Operator): if not settings.target_rig: self.report({'ERROR'}, 'No object Selected') return {'CANCELLED'} - - if not settings.target_bone: - self.report({'ERROR'}, 'No Bone Selected') - return {'CANCELLED'} + + self.bone = None + if settings.target_rig.type == 'ARMATURE': + if not settings.target_bone: + self.report({'ERROR'}, 'No Bone Selected') + return {'CANCELLED'} + self.bone = settings.target_rig.pose.bones.get(settings.target_bone) + if not self.bone: + self.report({'ERROR'}, f'{settings.target_bone} not found in armature {settings.target_rig.name}') + return {'CANCELLED'} if self.direct_parent: - return self.parent_and_compensate(context) + return self.parent_and_compensate(settings.target_rig, settings.target_bone) + return {'FINISHED'} - - return {'FINISHED'} - + + ## Place on a 2d intermediate parent_name = f'ref_{self.gp.name}_{self.lay.info}' parent_collec_name = 'gp_parents' included_cols = [c.name for c in self.gp.users_collection] @@ -87,20 +87,17 @@ class GP_OT_parent_layer(bpy.types.Operator): col = bpy.data.collections.get(parent_collec_name) if not col: col = bpy.data.collections.new(parent_collec_name) - if col.name not in bpy.context.scene.collection.children: bpy.context.scene.collection.children.link(col) - col.hide_viewport = True + ## hide parent collection ? + # col.hide_viewport = True - # Get/create meshplane + # Get/create empty parent parent = bpy.data.objects.get(parent_name) if not parent: - parent = create_plane(name=parent_name) + parent = empty_at(name=parent_name, size=0.1, show_name=True) parent.select_set(False) - if parent.name not in col.objects: - col.objects.link(parent) - ## Prepare context manager store_list = [ (context.tool_settings, 'use_keyframe_insert_auto', False), @@ -109,31 +106,32 @@ class GP_OT_parent_layer(bpy.types.Operator): for vlc in context.view_layer.layer_collection.children: store_list.append( (vlc, 'exclude', vlc.name not in included_cols), + # (), # (vlc, 'hide_viewport', vlc.name not in included_cols), # viewport viz ) ## Offset at curent frame to compensate for object, GP (and GP layer ?) transformations - - - - ## If GP object is animated, animate parent obj to compensate ## How to smart-test if self.gp is animated in space ? - if not is_animated(self.gp): - # direct parent with one offset - pass + # if not is_animated(self.gp): - else: - with attr_set(store_list): + with attr_set(store_list): + parent.animation_data_clear() # Kill animation data to redo it + for i in range(scn.frame_start, scn.frame_end+1): + scn.frame_set(i) place_object_to_ref_facing_cam(parent, - ref_ob=settings.target_rig, - bone=bone, - set_rotation=settings.use_bone_rotation - ) + ref_ob=settings.target_rig, + bone=self.bone, + set_rotation=settings.use_bone_rotation) + parent.keyframe_insert('location') + + parent.keyframe_insert('rotation_euler') + parent.keyframe_insert('scale') + - - print('Done') + self.parent_and_compensate(parent) + print('Parent Done') return {'FINISHED'} diff --git a/ui.py b/ui.py index 1e2c359..20d19b5 100755 --- a/ui.py +++ b/ui.py @@ -59,7 +59,10 @@ class GP_PT_interpolate(bpy.types.Panel): if settings.mode == 'FRAME': col.prop(settings, 'padding') - layout.operator('gp.parent_layer', text='Parent Layer To Target') + col = layout.column() + col.label(text='Layer Parent') + col.operator('gp.parent_layer', text='Direct Parent').direct_parent = True + col.operator('gp.parent_layer', text='Empty Parent').direct_parent = False classes = ( GP_PT_interpolate, diff --git a/utils.py b/utils.py index 7274141..b2fae40 100644 --- a/utils.py +++ b/utils.py @@ -91,6 +91,27 @@ def ray_cast_point(point, origin, depsgraph): return object_hit, np.array(hit_location), tri, tri_indices +def empty_at(name='Empty', pos=(0,0,0), collection=None, type='PLAIN_AXES', size=1, show_name=False): + ''' + Create an empty at given Vector3 position. + Optional type (default 'PLAIN_AXES') in ,'ARROWS','SINGLE_ARROW','CIRCLE','CUBE','SPHERE','CONE','IMAGE' + default size is 1.0 + ''' + + mire = bpy.data.objects.get(name) + if not mire: + mire = bpy.data.objects.new(name, None) + if collection is None: + collection = bpy.context.collection + if mire.name not in collection.all_objects: + collection.objects.link(mire) + + mire.location = pos + mire.empty_display_type = type + mire.empty_display_size = size + mire.show_name = show_name + return mire + def plane_on_bone(bone, arm=None, cam=None, set_rotation=True, mesh=True): ''' bone (posebone): reference pose bone