mega wip bone plane interpo
parent
6ec11e1170
commit
7d05e7390d
|
@ -101,6 +101,36 @@ def ray_cast_point(point, origin, depsgraph):
|
|||
return object_hit, np.array(hit_location), tri, tri_indices
|
||||
|
||||
|
||||
def plane_on_bone(bone, arm=None, cam=None):
|
||||
if cam is None:
|
||||
cam = bpy.context.scene.camera
|
||||
|
||||
if arm is None:
|
||||
arm = bone.id_data
|
||||
|
||||
plane = plane_coords()
|
||||
mat = cam.matrix_world.copy()
|
||||
## center
|
||||
mat.translation = arm @ ((bone.tail + bone.head) / 2)
|
||||
## plane is 1 unit side
|
||||
mat_scale = Matrix.Scale(10,4)
|
||||
return matrix_transform(plane, mat @ mat_scale)
|
||||
|
||||
def cast_on_plane(point, origin, face_co):
|
||||
'''
|
||||
face_co: World face coordinate
|
||||
'''
|
||||
|
||||
tri = None
|
||||
for tri_idx in tessellate_polygon([face_co]):
|
||||
tri = [face_co[i] for i in tri_idx]
|
||||
tri_indices = [i for i in tri_idx]
|
||||
hit_location = intersect_line_plane(origin, point, tri, triangle_normal(*tri))
|
||||
if intersect_point_tri(hit_location, *tri):
|
||||
break
|
||||
|
||||
return np.array(hit_location), tri, tri_indices
|
||||
|
||||
def following_key(forward=True):
|
||||
direction = 1 if forward else -1
|
||||
cur_frame = bpy.context.scene.frame_current
|
||||
|
@ -157,14 +187,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
else:
|
||||
return f"Interpolate Stroke Backward"
|
||||
|
||||
next : bpy.props.BoolProperty(name='Next', default=True, options={'SKIP_SAVE'})
|
||||
|
||||
# jump : bpy.props.EnumProperty(name='Direction', default='NEXT',
|
||||
# items=(
|
||||
# ('NEXT', 'Next', 'Next frame', 0),
|
||||
# ('PREV', 'Previous', 'Previous frame', 0)
|
||||
# ),
|
||||
# )
|
||||
next : bpy.props.BoolProperty(name='Next', default=True, options={'SKIP_SAVE'})
|
||||
|
||||
def execute(self, context):
|
||||
settings = context.scene.gp_interpo_settings
|
||||
|
@ -195,7 +218,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
|
||||
tgt_strokes = [s for s in gp.data.layers.active.active_frame.strokes if s.select]
|
||||
|
||||
## If nothing selected in sculpt/paint, Select all befaore triggering
|
||||
## If nothing selected in sculpt/paint, Select all before triggering
|
||||
if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
|
||||
for s in gp.data.layers.active.active_frame.strokes:
|
||||
s.select = True
|
||||
|
@ -205,66 +228,135 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
|||
self.report({'ERROR'}, 'No stroke selected !')
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
strokes_data = []
|
||||
for stroke in tgt_strokes:
|
||||
nb_points = len(stroke.points)
|
||||
if settings.method == 'BONE':
|
||||
self.report({'ERROR'}, 'Mega WIP')
|
||||
return {'CANCELLED'}
|
||||
|
||||
local_co = np.empty(nb_points * 3, dtype='float64')
|
||||
stroke.points.foreach_get('co', local_co)
|
||||
# local_co_3d = local_co.reshape((nb_points, 3))
|
||||
world_co_3d = matrix_transform(local_co.reshape((nb_points, 3)), matrix)
|
||||
## Follow Bone method (Full WIP)
|
||||
if not settings.target_rig or not settings.target_bone:
|
||||
self.report({'ERROR'}, 'No Bone selected')
|
||||
return {'CANCELLED'}
|
||||
|
||||
stroke_data = []
|
||||
for i, point in enumerate(stroke.points):
|
||||
point_co_world = world_co_3d[i]
|
||||
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone), arm=settings.target_rig)
|
||||
|
||||
strokes_data = []
|
||||
for stroke in tgt_strokes:
|
||||
nb_points = len(stroke.points)
|
||||
local_co = np.empty(nb_points * 3, dtype='float64')
|
||||
stroke.points.foreach_get('co', local_co)
|
||||
# local_co_3d = local_co.reshape((nb_points, 3))
|
||||
world_co_3d = matrix_transform(local_co.reshape((nb_points, 3)), matrix)
|
||||
|
||||
stroke_data = []
|
||||
|
||||
for i, point in enumerate(stroke.points):
|
||||
point_co_world = world_co_3d[i]
|
||||
hit_location = cast_on_plane(point_co_world, origin, bone_plane)
|
||||
## probably easier to just generate a single vast triangle and use it
|
||||
# hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri))
|
||||
|
||||
## Store same as other method, without object hit
|
||||
stroke_data.append((stroke, point_co_world, hit_location, tri, tri_indices))
|
||||
|
||||
object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg)
|
||||
if not object_hit or object_hit not in col.all_objects[:]:
|
||||
for square_co in search_square(point_co_world, factor=settings.search_range):
|
||||
object_hit, hit_location, tri, tri_indices = ray_cast_point(square_co, origin, dg)
|
||||
if object_hit and object_hit in col.all_objects[:]:
|
||||
|
||||
hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri))
|
||||
|
||||
break
|
||||
|
||||
stroke_data.append((stroke, point_co_world, object_hit, hit_location, tri, tri_indices))
|
||||
strokes_data.append(stroke_data)
|
||||
|
||||
bpy.ops.gpencil.copy()
|
||||
|
||||
scn.frame_set(frame_to_jump)
|
||||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
plan_co, plane_no = get_gp_draw_plane(gp)
|
||||
|
||||
strokes_data.append(stroke_data)
|
||||
bpy.ops.gpencil.paste()
|
||||
|
||||
|
||||
bpy.ops.gpencil.copy()
|
||||
matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
|
||||
new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
|
||||
|
||||
scn.frame_set(frame_to_jump)
|
||||
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone), arm=settings.target_rig)
|
||||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
for new_stroke, stroke_data in zip(new_strokes, strokes_data):
|
||||
world_co_3d = [] # np.array(len()dtype='float64')#np.
|
||||
for stroke, point_co, hit_location, tri_a, tri_indices in stroke_data:
|
||||
## MEGA WIP
|
||||
## TODO: use new bone_plane to set new coordinate
|
||||
tri_b = [bone_plane[i] for i in tri_indices]
|
||||
tri_b = matrix_transform(tri_b, eval_ob.matrix_world)
|
||||
|
||||
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
||||
world_co_3d.append(new_loc)
|
||||
|
||||
plan_co, plane_no = get_gp_draw_plane(gp)
|
||||
|
||||
bpy.ops.gpencil.paste()
|
||||
|
||||
|
||||
matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
|
||||
new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
|
||||
|
||||
for new_stroke, stroke_data in zip(new_strokes, strokes_data):
|
||||
world_co_3d = [] # np.array(len()dtype='float64')#np.
|
||||
for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data:
|
||||
eval_ob = object_hit.evaluated_get(dg)
|
||||
tri_b = [eval_ob.data.vertices[i].co for i in tri_indices]
|
||||
tri_b = matrix_transform(tri_b, eval_ob.matrix_world)
|
||||
# Reproject on plane
|
||||
new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d]
|
||||
new_local_co_3d = matrix_transform(new_world_co_3d, matrix_inv)
|
||||
|
||||
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
||||
world_co_3d.append(new_loc)
|
||||
nb_points = len(new_stroke.points)
|
||||
new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3))
|
||||
new_stroke.points.update()
|
||||
|
||||
# Reproject on plane
|
||||
new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d]
|
||||
new_local_co_3d = matrix_transform(new_world_co_3d, matrix_inv)
|
||||
|
||||
else:
|
||||
## Geometry method
|
||||
for stroke in tgt_strokes:
|
||||
nb_points = len(stroke.points)
|
||||
|
||||
local_co = np.empty(nb_points * 3, dtype='float64')
|
||||
stroke.points.foreach_get('co', local_co)
|
||||
# local_co_3d = local_co.reshape((nb_points, 3))
|
||||
world_co_3d = matrix_transform(local_co.reshape((nb_points, 3)), matrix)
|
||||
|
||||
stroke_data = []
|
||||
for i, point in enumerate(stroke.points):
|
||||
point_co_world = world_co_3d[i]
|
||||
|
||||
object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg)
|
||||
if not object_hit or object_hit not in col.all_objects[:]:
|
||||
for square_co in search_square(point_co_world, factor=settings.search_range):
|
||||
object_hit, hit_location, tri, tri_indices = ray_cast_point(square_co, origin, dg)
|
||||
if object_hit and object_hit in col.all_objects[:]:
|
||||
|
||||
hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri))
|
||||
|
||||
break
|
||||
|
||||
stroke_data.append((stroke, point_co_world, object_hit, hit_location, tri, tri_indices))
|
||||
|
||||
strokes_data.append(stroke_data)
|
||||
|
||||
|
||||
bpy.ops.gpencil.copy()
|
||||
|
||||
scn.frame_set(frame_to_jump)
|
||||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
plan_co, plane_no = get_gp_draw_plane(gp)
|
||||
|
||||
nb_points = len(new_stroke.points)
|
||||
new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3))
|
||||
new_stroke.points.update()
|
||||
bpy.ops.gpencil.paste()
|
||||
|
||||
|
||||
matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
|
||||
new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
|
||||
|
||||
for new_stroke, stroke_data in zip(new_strokes, strokes_data):
|
||||
world_co_3d = [] # np.array(len()dtype='float64')#np.
|
||||
for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data:
|
||||
eval_ob = object_hit.evaluated_get(dg)
|
||||
tri_b = [eval_ob.data.vertices[i].co for i in tri_indices]
|
||||
tri_b = matrix_transform(tri_b, eval_ob.matrix_world)
|
||||
|
||||
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
||||
world_co_3d.append(new_loc)
|
||||
|
||||
# Reproject on plane
|
||||
new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d]
|
||||
new_local_co_3d = matrix_transform(new_world_co_3d, matrix_inv)
|
||||
|
||||
nb_points = len(new_stroke.points)
|
||||
new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3))
|
||||
new_stroke.points.update()
|
||||
|
||||
context.tool_settings.use_keyframe_insert_auto = auto_key_status
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import bpy
|
||||
from bpy.props import EnumProperty, IntProperty, FloatProperty, BoolProperty, PointerProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (EnumProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
BoolProperty,
|
||||
PointerProperty,
|
||||
StringProperty)
|
||||
|
||||
class GP_PG_interpolate_settings(PropertyGroup):
|
||||
|
||||
|
@ -8,12 +13,23 @@ class GP_PG_interpolate_settings(PropertyGroup):
|
|||
# name="Dry run mode (Check only)",
|
||||
# description="Do not change anything, just print the messages",
|
||||
# default=False, options={'HIDDEN'})
|
||||
|
||||
method : EnumProperty(
|
||||
name='Method',
|
||||
items= (
|
||||
('BONE', 'Bone', 'Pick an armature bone and follow it', 0),
|
||||
('GEOMETRY', 'Geometry', 'Directly follow underlying geometry', 1) ,
|
||||
),
|
||||
default='BONE',
|
||||
description='Select method for interpolating strokes'
|
||||
)
|
||||
|
||||
search_range : FloatProperty(
|
||||
name="Search Range",
|
||||
description="Search range size when points are out of mesh",
|
||||
default=0.05, precision=2, step=3, options={'HIDDEN'})
|
||||
|
||||
|
||||
mode : EnumProperty(
|
||||
name='Mode',
|
||||
# Combined ?markers ?
|
||||
|
@ -32,11 +48,24 @@ class GP_PG_interpolate_settings(PropertyGroup):
|
|||
min=1)
|
||||
|
||||
target_collection : PointerProperty(
|
||||
name='Collection',
|
||||
type=bpy.types.Collection,
|
||||
description='Target collection to check armature keyframes from',
|
||||
# placeholder='Collection'
|
||||
)
|
||||
|
||||
target_rig : PointerProperty(
|
||||
name='Rig',
|
||||
description='Rig to use as target',
|
||||
type=bpy.types.Object)
|
||||
|
||||
# target_rig : StringProperty(
|
||||
# name='Rig',
|
||||
# description='Rig to use as target')
|
||||
|
||||
target_bone : StringProperty(
|
||||
name='Bone',
|
||||
description='Bone of the rig to follow when interpolating') # Bone
|
||||
|
||||
classes = (
|
||||
GP_PG_interpolate_settings,
|
||||
|
|
25
ui.py
25
ui.py
|
@ -8,30 +8,43 @@ class GP_PT_interpolate(bpy.types.Panel):
|
|||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'GPENCIL'
|
||||
return context.object # and context.object.type == 'GPENCIL'
|
||||
|
||||
def draw(self, context):
|
||||
settings = bpy.context.scene.gp_interpo_settings
|
||||
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
col = layout.column(align=False)
|
||||
|
||||
## interpolation buttons
|
||||
if settings.mode == 'FRAME':
|
||||
prev_icon, next_icon = 'FRAME_PREV', 'FRAME_NEXT'
|
||||
else:
|
||||
prev_icon, next_icon = 'PREV_KEYFRAME', 'NEXT_KEYFRAME'
|
||||
|
||||
|
||||
row = col.row(align=True)
|
||||
row.scale_x = 3
|
||||
row.operator("gp.interpolate_stroke", text="", icon=prev_icon).next = False
|
||||
row.operator("gp.interpolate_stroke", text="", icon=next_icon).next = True
|
||||
# col.separator()
|
||||
|
||||
col.prop(settings, 'target_collection', text='Collection')
|
||||
col.prop(settings, 'search_range')
|
||||
col = layout.column(align=True)
|
||||
|
||||
col.prop(settings, 'method', text='Method')
|
||||
|
||||
if settings.method == 'BONE':
|
||||
col = layout.column(align=True)
|
||||
col.prop_search(settings, 'target_rig', context.scene, 'objects', text='Rig')
|
||||
if settings.target_rig:
|
||||
# col.prop_search(ob.rig_picker, 'name', settings.target_rig.pose, 'bones', text='Bone')
|
||||
col.prop_search(settings, 'target_bone', settings.target_rig.pose, 'bones', text='Bone')
|
||||
|
||||
|
||||
elif settings.method == 'GEOMETRY':
|
||||
col.prop(settings, 'target_collection', text='Collection')
|
||||
col.prop(settings, 'search_range')
|
||||
|
||||
col.separator()
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(settings, 'mode', expand=True)
|
||||
|
||||
|
|
Loading…
Reference in New Issue