initial parent code
parent
6197aa622b
commit
8cccc7ba8c
|
@ -20,10 +20,11 @@ module_name = Path(__file__).parent.name
|
|||
sys.modules.update({'gp_interpolate': importlib.import_module(module_name)})
|
||||
|
||||
|
||||
from gp_interpolate import interpolate_strokes, ui
|
||||
from gp_interpolate import interpolate_strokes, parent_layer, ui
|
||||
|
||||
modules = (
|
||||
interpolate_strokes,
|
||||
parent_layer,
|
||||
ui,
|
||||
)
|
||||
|
||||
|
|
|
@ -149,7 +149,6 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
|
||||
# Override collection
|
||||
col = intercol
|
||||
## TODO: Hide all other collections
|
||||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
strokes_data = []
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
import bpy
|
||||
import numpy as np
|
||||
from time import perf_counter, time
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from gp_interpolate.utils import (matrix_transform,
|
||||
plane_on_bone,
|
||||
ray_cast_point,
|
||||
intersect_with_tesselated_plane,
|
||||
triangle_normal,
|
||||
search_square,
|
||||
get_gp_draw_plane,
|
||||
create_plane,
|
||||
following_keys,
|
||||
attr_set)
|
||||
|
||||
|
||||
class GP_OT_parent_layer(bpy.types.Operator):
|
||||
bl_idname = "gp.parent_layer"
|
||||
bl_label = "Parent Layer"
|
||||
bl_description = 'Parent Layer'
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.active_object\
|
||||
and context.object.type == 'GPENCIL'\
|
||||
and context.object.layers.active:
|
||||
return True
|
||||
cls.poll_message_set("Need a Grease pencil object with an active layer")
|
||||
return False
|
||||
|
||||
# @classmethod
|
||||
# def description(cls, context, properties):
|
||||
# if properties.next:
|
||||
# return f"Interpolate Stroke Forward"
|
||||
# else:
|
||||
# return f"Interpolate Stroke Backward"
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
settings = context.scene.gp_interpo_settings
|
||||
scn = bpy.context.scene
|
||||
|
||||
gp = context.object
|
||||
lay = gp.layers.active
|
||||
# matrix = np.array(gp.matrix_world, dtype='float64')#.inverted()
|
||||
# origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
|
||||
|
||||
col = settings.target_collection
|
||||
if not col:
|
||||
col = scn.collection
|
||||
|
||||
parent_name = f'ref_{gp.name}_{lay.info}'
|
||||
parent_collec_name = 'gp_parents'
|
||||
# print('----')
|
||||
|
||||
included_cols = [c.name for c in gp.users_collection]
|
||||
start = time()
|
||||
if settings.method == 'BONE':
|
||||
if not settings.target_rig or not settings.target_bone:
|
||||
self.report({'ERROR'}, 'No Bone Selected')
|
||||
return {'CANCELLED'}
|
||||
|
||||
included_cols.append(parent_collec_name)
|
||||
|
||||
## Ensure collection and plane exists
|
||||
# Get/create collection
|
||||
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
|
||||
|
||||
# Get/create meshplane
|
||||
plane = bpy.data.objects.get(parent_name)
|
||||
if not plane:
|
||||
plane = create_plane(name=parent_name)
|
||||
plane.select_set(False)
|
||||
|
||||
if plane.name not in col.objects:
|
||||
col.objects.link(plane)
|
||||
## TODO: Ensure the plane is not animated!
|
||||
|
||||
else:
|
||||
# Geometry mode
|
||||
if col != context.scene.collection:
|
||||
included_cols.append(col.name)
|
||||
## Maybe include a plane just behing geo ? probably bad idea
|
||||
|
||||
## Prepare context manager
|
||||
store_list = [
|
||||
# (context.view_layer.objects, 'active', gp),
|
||||
(context.tool_settings, 'use_keyframe_insert_auto', True),
|
||||
# (bpy.context.scene.render, 'simplify_subdivision', 0),
|
||||
]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
# print(f'Preparation {time()-start:.4f}s')
|
||||
|
||||
with attr_set(store_list):
|
||||
if settings.method == 'BONE':
|
||||
## replace plane
|
||||
_bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone),
|
||||
arm=settings.target_rig,
|
||||
set_rotation=settings.use_bone_rotation,
|
||||
mesh=True)
|
||||
|
||||
## Set collection visibility
|
||||
intercol = bpy.data.collections.get(parent_collec_name)
|
||||
vl_col = bpy.context.view_layer.layer_collection.children.get(intercol.name)
|
||||
intercol.hide_viewport = vl_col.exclude = vl_col.hide_viewport = False
|
||||
|
||||
# Override collection
|
||||
col = intercol
|
||||
## TODO: Hide all other collections
|
||||
|
||||
print('Done')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
classes = (
|
||||
GP_OT_parent_layer,
|
||||
)
|
||||
|
||||
def register():
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
|
||||
|
||||
def unregister():
|
||||
for c in reversed(classes):
|
||||
bpy.utils.unregister_class(c)
|
|
@ -0,0 +1,151 @@
|
|||
import bpy
|
||||
import numpy as np
|
||||
from time import perf_counter, time
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from gp_interpolate.utils import (get_gp_draw_plane,
|
||||
is_animated,
|
||||
create_plane,
|
||||
following_keys,
|
||||
place_object_to_ref_facing_cam,
|
||||
attr_set)
|
||||
|
||||
|
||||
class GP_OT_parent_layer(bpy.types.Operator):
|
||||
bl_idname = "gp.parent_layer"
|
||||
bl_label = "Parent Layer"
|
||||
bl_description = 'Parent active layer to object or bone\
|
||||
\nBake intermediate parent object to compensate GP offset and moves'
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.object\
|
||||
and context.object.type == 'GPENCIL'\
|
||||
and context.object.data.layers.active:
|
||||
return True
|
||||
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'})
|
||||
|
||||
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
|
||||
if bone:
|
||||
self.lay.parent_type = 'BONE'
|
||||
self.lay.parent_bone = bone.name
|
||||
print(f'Parent to {self.lay.parent.name} > bone {bone.name}')
|
||||
|
||||
## Offset parent
|
||||
if bone:
|
||||
bone_mat = self.lay.parent.matrix_world @ bone.matrix
|
||||
self.lay.matrix_inverse = bone_mat.inverted() @ self.gp.matrix_world
|
||||
|
||||
else:
|
||||
print(f'Parent to {self.lay.parent.name}')
|
||||
self.lay.matrix_inverse = self.lay.parent.matrix_world.inverted() @ self.gp.matrix_world
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def execute(self, context):
|
||||
settings = context.scene.gp_interpo_settings
|
||||
scn = bpy.context.scene
|
||||
|
||||
self.gp = context.object
|
||||
self.lay = self.gp.data.layers.active
|
||||
|
||||
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'}
|
||||
|
||||
|
||||
if self.direct_parent:
|
||||
return self.parent_and_compensate(context)
|
||||
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
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]
|
||||
included_cols.append(parent_collec_name)
|
||||
|
||||
## Ensure collection and parent exists
|
||||
# Get/create collection
|
||||
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
|
||||
|
||||
# Get/create meshplane
|
||||
parent = bpy.data.objects.get(parent_name)
|
||||
if not parent:
|
||||
parent = create_plane(name=parent_name)
|
||||
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),
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
else:
|
||||
with attr_set(store_list):
|
||||
place_object_to_ref_facing_cam(parent,
|
||||
ref_ob=settings.target_rig,
|
||||
bone=bone,
|
||||
set_rotation=settings.use_bone_rotation
|
||||
)
|
||||
|
||||
|
||||
print('Done')
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
classes = (
|
||||
GP_OT_parent_layer,
|
||||
)
|
||||
|
||||
def register():
|
||||
for c in classes:
|
||||
bpy.utils.register_class(c)
|
||||
|
||||
|
||||
def unregister():
|
||||
for c in reversed(classes):
|
||||
bpy.utils.unregister_class(c)
|
2
ui.py
2
ui.py
|
@ -58,6 +58,8 @@ 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')
|
||||
|
||||
classes = (
|
||||
GP_PT_interpolate,
|
||||
|
|
72
utils.py
72
utils.py
|
@ -159,6 +159,72 @@ def plane_on_bone(bone, arm=None, cam=None, set_rotation=True, mesh=True):
|
|||
plane = plane_coords()
|
||||
return matrix_transform(plane, mat @ mat_scale)
|
||||
|
||||
def place_object_to_ref_facing_cam(obj, ref_ob, bone=None, cam=None, set_rotation=True):
|
||||
'''
|
||||
obj (Object): the object to place
|
||||
ref_ob (Object): the reference object or armature
|
||||
bone (posebone): reference pose bone
|
||||
arm (optional: Armature): Armature of the pose bone (if not passed found using bone.id_data)
|
||||
cam (optional: Camera) : Camera to align plane to (if not passed use scene camera)
|
||||
set_rotation (bool): rotate the plane on cam view axis according to bone direction in 2d cam space
|
||||
'''
|
||||
|
||||
if cam is None:
|
||||
cam = bpy.context.scene.camera
|
||||
|
||||
# if ref_ob is None:
|
||||
# ref_ob = bone.id_data
|
||||
|
||||
mat = cam.matrix_world.copy()
|
||||
|
||||
if set_rotation:
|
||||
if bone:
|
||||
head_world_coord = ref_ob.matrix_world @ bone.head
|
||||
mat.translation = head_world_coord
|
||||
|
||||
## Apply 2d bone rotation facing camera
|
||||
# Get 2d camera space coords (NDC: normalized device coordinate, 0,0 is bottom-left)
|
||||
head_2d, tail_2d = get_bone_head_tail_2d(bone, cam=cam)
|
||||
else:
|
||||
mat.translation = ref_ob.matrix_world
|
||||
# Get 2d camera space coords (NDC: normalized device coordinate, 0,0 is bottom-left)
|
||||
scene = bpy.context.scene
|
||||
up_vec = Vector((0,0,1))
|
||||
up_vec.rotate(ref_ob.matrix_world)
|
||||
tail_3d = ref_ob.matrix_world.to_translation() + up_vec
|
||||
head_2d = world_to_camera_view(scene, cam, ref_ob.matrix_world.to_translation())
|
||||
tail_2d = world_to_camera_view(scene, cam, tail_3d)
|
||||
ratio = scene.render.resolution_y / scene.render.resolution_x
|
||||
head_2d.y *= ratio
|
||||
tail_2d.y *= ratio
|
||||
|
||||
vec_from_corner_2d = (tail_2d - head_2d).normalized()
|
||||
up_vec_2d = Vector((0,1))
|
||||
# angle = acos(up_vec_2d.dot(vec_from_corner_2d)) ## equivalent but not signed!
|
||||
angle = up_vec_2d.angle_signed(vec_from_corner_2d)
|
||||
|
||||
## Axis camera aim (seem slightly off)
|
||||
# rot_axis = Vector((0, 0, -1))
|
||||
# rot_axis.rotate(cam.matrix_world)
|
||||
|
||||
## Axis camera origin -> pivot
|
||||
rot_axis = head_world_coord - cam.matrix_world.translation
|
||||
mat = rotate_matrix_around_pivot(mat, angle, head_world_coord, rot_axis)
|
||||
|
||||
else:
|
||||
if bone:
|
||||
## Use mid bone to better follow movement
|
||||
mat.translation = ref_ob.matrix_world @ ((bone.tail + bone.head) / 2) # Mid bone
|
||||
else:
|
||||
mat.translation = ref_ob.matrix_world
|
||||
|
||||
## change/adapt scale
|
||||
# mat_scale = Matrix.Scale(10, 4) # maybe move above mesh condition
|
||||
# mat = mat @ mat_scale
|
||||
obj.matrix_world = mat
|
||||
|
||||
|
||||
|
||||
def create_plane(name='Plane', collection=None):
|
||||
'''Create a plane using pydata
|
||||
collection: link in passed collection, else do not link in scene
|
||||
|
@ -335,3 +401,9 @@ def following_keys(forward=True, all_keys=False) -> list:# -> list[int] | list |
|
|||
if new is None:
|
||||
return []
|
||||
return [int(new)]
|
||||
|
||||
|
||||
## -- animation
|
||||
|
||||
def is_animated(obj):
|
||||
return True
|
Loading…
Reference in New Issue