follow bone method
This commit is contained in:
		
							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:
 | 
				
			||||||
    mat_scale = Matrix.Scale(10,4)
 | 
					        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)
 | 
				
			||||||
    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':
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										52
									
								
								utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								utils.py
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user