follow bone method
parent
7d05e7390d
commit
373a36a825
|
@ -1,7 +1,7 @@
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "gp interpolate",
|
"name": "gp interpolate",
|
||||||
"author": "Christophe Seux, Samuel Bernou",
|
"author": "Christophe Seux, Samuel Bernou",
|
||||||
"version": (0, 1, 0),
|
"version": (0, 1, 1),
|
||||||
"blender": (3, 6, 0),
|
"blender": (3, 6, 0),
|
||||||
"location": "Sidebar > Gpencil Tab > Interpolate",
|
"location": "Sidebar > Gpencil Tab > Interpolate",
|
||||||
"description": "Interpolate Grease pencil strokes over 3D",
|
"description": "Interpolate Grease pencil strokes over 3D",
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from math import tan
|
from math import tan, acos, degrees
|
||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
|
|
||||||
|
from gp_interpolate import utils
|
||||||
|
|
||||||
from mathutils.geometry import (barycentric_transform, intersect_point_tri,
|
from mathutils.geometry import (barycentric_transform, intersect_point_tri,
|
||||||
intersect_point_line, intersect_line_plane, tessellate_polygon)
|
intersect_point_line, intersect_line_plane, tessellate_polygon)
|
||||||
|
|
||||||
|
@ -101,7 +103,7 @@ def ray_cast_point(point, origin, depsgraph):
|
||||||
return object_hit, np.array(hit_location), tri, tri_indices
|
return object_hit, np.array(hit_location), tri, tri_indices
|
||||||
|
|
||||||
|
|
||||||
def plane_on_bone(bone, arm=None, cam=None):
|
def plane_on_bone(bone, arm=None, cam=None, set_rotation=True):
|
||||||
if cam is None:
|
if cam is None:
|
||||||
cam = bpy.context.scene.camera
|
cam = bpy.context.scene.camera
|
||||||
|
|
||||||
|
@ -110,22 +112,47 @@ def plane_on_bone(bone, arm=None, cam=None):
|
||||||
|
|
||||||
plane = plane_coords()
|
plane = plane_coords()
|
||||||
mat = cam.matrix_world.copy()
|
mat = cam.matrix_world.copy()
|
||||||
## center
|
|
||||||
mat.translation = arm @ ((bone.tail + bone.head) / 2)
|
|
||||||
## plane is 1 unit side
|
if set_rotation:
|
||||||
|
head_world_coord = arm.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 = utils.get_bone_head_tail_2d(bone, cam=cam)
|
||||||
|
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 = utils.rotate_matrix_around_pivot(mat, angle, head_world_coord, rot_axis)
|
||||||
|
|
||||||
|
else:
|
||||||
|
## Use mid bone to better follow movement
|
||||||
|
mat.translation = arm.matrix_world @ ((bone.tail + bone.head) / 2) # Mid bone
|
||||||
|
|
||||||
mat_scale = Matrix.Scale(10, 4)
|
mat_scale = Matrix.Scale(10, 4)
|
||||||
return matrix_transform(plane, mat @ mat_scale)
|
return matrix_transform(plane, mat @ mat_scale)
|
||||||
|
|
||||||
def cast_on_plane(point, origin, face_co):
|
def intersect_with_tesselated_plane(point, origin, face_co):
|
||||||
'''
|
'''
|
||||||
face_co: World face coordinate
|
face_co: World face coordinates
|
||||||
'''
|
'''
|
||||||
|
|
||||||
tri = None
|
tri = None
|
||||||
for tri_idx in tessellate_polygon([face_co]):
|
for tri_idx in tessellate_polygon([face_co]):
|
||||||
tri = [face_co[i] for i in tri_idx]
|
tri = [face_co[i] for i in tri_idx]
|
||||||
tri_indices = [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))
|
hit_location = intersect_line_plane(origin, point, sum((Vector(v) for v in tri), Vector()) / 3, triangle_normal(*tri))
|
||||||
if intersect_point_tri(hit_location, *tri):
|
if intersect_point_tri(hit_location, *tri):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -230,15 +257,14 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
||||||
|
|
||||||
strokes_data = []
|
strokes_data = []
|
||||||
if settings.method == 'BONE':
|
if settings.method == 'BONE':
|
||||||
self.report({'ERROR'}, 'Mega WIP')
|
|
||||||
return {'CANCELLED'}
|
|
||||||
|
|
||||||
## Follow Bone method (Full WIP)
|
## Follow Bone method (Full WIP)
|
||||||
if not settings.target_rig or not settings.target_bone:
|
if not settings.target_rig or not settings.target_bone:
|
||||||
self.report({'ERROR'}, 'No Bone selected')
|
self.report({'ERROR'}, 'No Bone selected')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone), arm=settings.target_rig)
|
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone),
|
||||||
|
arm=settings.target_rig,
|
||||||
|
set_rotation=settings.use_bone_rotation)
|
||||||
|
|
||||||
strokes_data = []
|
strokes_data = []
|
||||||
for stroke in tgt_strokes:
|
for stroke in tgt_strokes:
|
||||||
|
@ -252,11 +278,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
||||||
|
|
||||||
for i, point in enumerate(stroke.points):
|
for i, point in enumerate(stroke.points):
|
||||||
point_co_world = world_co_3d[i]
|
point_co_world = world_co_3d[i]
|
||||||
hit_location = cast_on_plane(point_co_world, origin, bone_plane)
|
hit_location, tri, tri_indices = intersect_with_tesselated_plane(point_co_world, origin, bone_plane)
|
||||||
## probably easier to just generate a single vast triangle and use it
|
## 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
|
## Store same as other method (without object hit)
|
||||||
stroke_data.append((stroke, point_co_world, hit_location, tri, tri_indices))
|
stroke_data.append((stroke, point_co_world, hit_location, tri, tri_indices))
|
||||||
|
|
||||||
strokes_data.append(stroke_data)
|
strokes_data.append(stroke_data)
|
||||||
|
@ -275,19 +300,21 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
|
||||||
matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
|
matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
|
||||||
new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
|
new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
|
||||||
|
|
||||||
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone), arm=settings.target_rig)
|
bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone),
|
||||||
|
arm=settings.target_rig,
|
||||||
|
set_rotation=settings.use_bone_rotation)
|
||||||
|
|
||||||
for new_stroke, stroke_data in zip(new_strokes, strokes_data):
|
for new_stroke, stroke_data in zip(new_strokes, strokes_data):
|
||||||
world_co_3d = [] # np.array(len()dtype='float64')#np.
|
world_co_3d = [] # np.array(len()dtype='float64')#np.
|
||||||
for stroke, point_co, hit_location, tri_a, tri_indices in stroke_data:
|
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 = [bone_plane[i] for i in tri_indices]
|
||||||
tri_b = matrix_transform(tri_b, eval_ob.matrix_world)
|
# tri_b = matrix_transform(tri_b, settings.target_rig.matrix_world)
|
||||||
|
## rotate tri_b by bone differential angle camera's aim axis ?
|
||||||
|
|
||||||
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)
|
||||||
world_co_3d.append(new_loc)
|
world_co_3d.append(new_loc)
|
||||||
|
|
||||||
|
|
||||||
# Reproject on plane
|
# Reproject on plane
|
||||||
new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d]
|
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_local_co_3d = matrix_transform(new_world_co_3d, matrix_inv)
|
||||||
|
|
|
@ -67,6 +67,11 @@ class GP_PG_interpolate_settings(PropertyGroup):
|
||||||
name='Bone',
|
name='Bone',
|
||||||
description='Bone of the rig to follow when interpolating') # Bone
|
description='Bone of the rig to follow when interpolating') # Bone
|
||||||
|
|
||||||
|
use_bone_rotation : BoolProperty(
|
||||||
|
name='Use Bone Rotation',
|
||||||
|
default=True,
|
||||||
|
description='Apply rotation of the bone') # Bone
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GP_PG_interpolate_settings,
|
GP_PG_interpolate_settings,
|
||||||
)
|
)
|
||||||
|
|
1
ui.py
1
ui.py
|
@ -37,6 +37,7 @@ class GP_PT_interpolate(bpy.types.Panel):
|
||||||
if settings.target_rig:
|
if settings.target_rig:
|
||||||
# col.prop_search(ob.rig_picker, 'name', settings.target_rig.pose, 'bones', text='Bone')
|
# 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')
|
col.prop_search(settings, 'target_bone', settings.target_rig.pose, 'bones', text='Bone')
|
||||||
|
col.prop(settings, 'use_bone_rotation', text='Use Bone Rotation')
|
||||||
|
|
||||||
|
|
||||||
elif settings.method == 'GEOMETRY':
|
elif settings.method == 'GEOMETRY':
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import bpy
|
||||||
|
import math
|
||||||
|
|
||||||
|
from mathutils import Vector, Matrix
|
||||||
|
from bpy_extras.object_utils import world_to_camera_view
|
||||||
|
|
||||||
|
def get_bone_head_tail_2d(posebone, scene=None, cam=None) -> tuple[Vector, Vector]:
|
||||||
|
'''Get 2D vectors in camera view of bone head and tails
|
||||||
|
return tuple of 2d vectors (head_2d and tail_2d)
|
||||||
|
'''
|
||||||
|
scene = scene or bpy.context.scene
|
||||||
|
cam = cam or scene.camera
|
||||||
|
|
||||||
|
arm = posebone.id_data
|
||||||
|
|
||||||
|
# Get 3D locations of head and tail
|
||||||
|
head_3d = arm.matrix_world @ posebone.head
|
||||||
|
tail_3d = arm.matrix_world @ posebone.tail
|
||||||
|
|
||||||
|
# Convert 3D locations to 2D
|
||||||
|
head_2d = world_to_camera_view(scene, cam, head_3d)
|
||||||
|
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
|
||||||
|
|
||||||
|
return Vector((head_2d.x, head_2d.y)), Vector((tail_2d.x, tail_2d.y))
|
||||||
|
|
||||||
|
|
||||||
|
def rotate_matrix_around_pivot(matrix, angle, pivot, axis):
|
||||||
|
'''Rotate a given matrix by a CW angle around pivot on a given axis
|
||||||
|
matrix (Matrix): the matrix to rotate
|
||||||
|
angle (Float, Radians): the angle in radians
|
||||||
|
pivot (Vector3): the pivot 3D coordinate
|
||||||
|
axis (Vector3): the vector axis of rotation
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Convert angle to radians ?
|
||||||
|
# angle = math.radians(angle)
|
||||||
|
|
||||||
|
# Create a rotation matrix
|
||||||
|
rot_matrix = Matrix.Rotation(angle, 4, axis)
|
||||||
|
|
||||||
|
# Create translation matrices
|
||||||
|
translate_to_origin = Matrix.Translation(-pivot)
|
||||||
|
translate_back = Matrix.Translation(pivot)
|
||||||
|
|
||||||
|
# Combine the transformations : The order of multiplication is important
|
||||||
|
new_matrix = translate_back @ rot_matrix @ translate_to_origin @ matrix
|
||||||
|
|
||||||
|
return new_matrix
|
Loading…
Reference in New Issue