refactor - separate utils functions
parent
373a36a825
commit
4c44ff96b3
|
@ -1,163 +1,23 @@
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
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 gp_interpolate.utils import (matrix_transform,
|
||||||
|
plane_on_bone,
|
||||||
|
ray_cast_point,
|
||||||
|
intersect_with_tesselated_plane,
|
||||||
|
triangle_normal,
|
||||||
|
search_square,
|
||||||
|
get_gp_draw_plane)
|
||||||
|
|
||||||
from mathutils.geometry import (barycentric_transform, intersect_point_tri,
|
from mathutils.geometry import (barycentric_transform,
|
||||||
intersect_point_line, intersect_line_plane, tessellate_polygon)
|
intersect_point_tri,
|
||||||
|
intersect_point_line,
|
||||||
|
intersect_line_plane,
|
||||||
|
tessellate_polygon)
|
||||||
|
|
||||||
|
|
||||||
def get_gp_draw_plane(obj=None):
|
|
||||||
''' return tuple with plane coordinate and normal
|
|
||||||
of the curent drawing according to geometry'''
|
|
||||||
|
|
||||||
if obj is None:
|
|
||||||
obj = bpy.context.object
|
|
||||||
|
|
||||||
settings = bpy.context.scene.tool_settings
|
|
||||||
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
|
|
||||||
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
|
|
||||||
|
|
||||||
mat = obj.matrix_world
|
|
||||||
plane_no = Vector((0.0, 0.0, 1.0))
|
|
||||||
plane_co = mat.to_translation()
|
|
||||||
|
|
||||||
# -> orientation
|
|
||||||
if orient == 'VIEW':
|
|
||||||
mat = bpy.context.scene.camera.matrix_world
|
|
||||||
# -> placement
|
|
||||||
if loc == "CURSOR":
|
|
||||||
plane_co = bpy.context.scene.cursor.location
|
|
||||||
mat = bpy.context.scene.cursor.matrix
|
|
||||||
|
|
||||||
elif orient == 'AXIS_Y':#front (X-Z)
|
|
||||||
plane_no = Vector((0,1,0))
|
|
||||||
|
|
||||||
elif orient == 'AXIS_X':#side (Y-Z)
|
|
||||||
plane_no = Vector((1,0,0))
|
|
||||||
|
|
||||||
elif orient == 'AXIS_Z':#top (X-Y)
|
|
||||||
plane_no = Vector((0,0,1))
|
|
||||||
|
|
||||||
plane_no.rotate(mat)
|
|
||||||
|
|
||||||
return plane_co, plane_no
|
|
||||||
|
|
||||||
def triangle_normal(a, b, c):
|
|
||||||
x = a[1] * b[2] - a[2] * b[1]
|
|
||||||
y = a[2] * b[0] - a[0] * b[2]
|
|
||||||
z = a[0] * b[1] - a[1] * b[0]
|
|
||||||
|
|
||||||
return np.array([x, y, z], dtype='float64')
|
|
||||||
|
|
||||||
def plane_coords(size=1):
|
|
||||||
v = size * 0.5
|
|
||||||
return np.array([(-v, v, 0), (v, v, 0), (v, -v, 0), (-v, -v, 0)], dtype='float64')
|
|
||||||
|
|
||||||
def matrix_transform(coords, matrix):
|
|
||||||
coords_4d = np.column_stack((coords, np.ones(len(coords), dtype='float64')))
|
|
||||||
return np.einsum('ij,aj->ai', matrix, coords_4d)[:, :-1]
|
|
||||||
|
|
||||||
def vector_normalized(vec):
|
|
||||||
return vec / np.sqrt(np.sum(vec**2))
|
|
||||||
|
|
||||||
def vector_magnitude(vec):
|
|
||||||
return np.sqrt(vec.dot(vec))
|
|
||||||
|
|
||||||
def search_square(point, factor=0.05, cam=None):
|
|
||||||
if cam is None:
|
|
||||||
cam = bpy.context.scene.camera
|
|
||||||
|
|
||||||
plane = plane_coords()
|
|
||||||
mat = cam.matrix_world.copy()
|
|
||||||
mat.translation = point
|
|
||||||
depth = vector_magnitude(point - cam.matrix_world.to_translation())
|
|
||||||
mat_scale = Matrix.Scale(tan(cam.data.angle*0.5)*depth*factor, 4)
|
|
||||||
|
|
||||||
return matrix_transform(plane, mat @ mat_scale)
|
|
||||||
|
|
||||||
def ray_cast_point(point, origin, depsgraph):
|
|
||||||
ray = (point - origin)#.normalized()
|
|
||||||
hit, hit_location, normal, face_index, object_hit, matrix = bpy.context.scene.ray_cast(depsgraph, origin, ray)
|
|
||||||
|
|
||||||
if not hit:
|
|
||||||
return None, None, None, None
|
|
||||||
|
|
||||||
eval_ob = object_hit.evaluated_get(depsgraph)
|
|
||||||
|
|
||||||
face = eval_ob.data.polygons[face_index]
|
|
||||||
vertices = [eval_ob.data.vertices[i] for i in face.vertices]
|
|
||||||
face_co = matrix_transform([v.co for v in vertices], eval_ob.matrix_world)
|
|
||||||
|
|
||||||
tri = None
|
|
||||||
for tri_idx in tessellate_polygon([face_co]):
|
|
||||||
tri = [face_co[i] for i in tri_idx]
|
|
||||||
tri_indices = [vertices[i].index for i in tri_idx]
|
|
||||||
if intersect_point_tri(hit_location, *tri):
|
|
||||||
break
|
|
||||||
|
|
||||||
return object_hit, np.array(hit_location), tri, tri_indices
|
|
||||||
|
|
||||||
|
|
||||||
def plane_on_bone(bone, arm=None, cam=None, set_rotation=True):
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
return matrix_transform(plane, mat @ mat_scale)
|
|
||||||
|
|
||||||
def intersect_with_tesselated_plane(point, origin, face_co):
|
|
||||||
'''
|
|
||||||
face_co: World face coordinates
|
|
||||||
'''
|
|
||||||
|
|
||||||
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, sum((Vector(v) for v in tri), Vector()) / 3, triangle_normal(*tri))
|
|
||||||
if intersect_point_tri(hit_location, *tri):
|
|
||||||
break
|
|
||||||
|
|
||||||
return np.array(hit_location), tri, tri_indices
|
|
||||||
|
|
||||||
def following_key(forward=True):
|
def following_key(forward=True):
|
||||||
direction = 1 if forward else -1
|
direction = 1 if forward else -1
|
||||||
cur_frame = bpy.context.scene.frame_current
|
cur_frame = bpy.context.scene.frame_current
|
||||||
|
|
158
utils.py
158
utils.py
|
@ -1,8 +1,127 @@
|
||||||
import bpy
|
import bpy
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from math import tan
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from bpy_extras.object_utils import world_to_camera_view
|
from bpy_extras.object_utils import world_to_camera_view
|
||||||
|
from mathutils.geometry import (barycentric_transform, intersect_point_tri,
|
||||||
|
intersect_point_line, intersect_line_plane, tessellate_polygon)
|
||||||
|
|
||||||
|
# --- Vector
|
||||||
|
|
||||||
|
def triangle_normal(a, b, c):
|
||||||
|
x = a[1] * b[2] - a[2] * b[1]
|
||||||
|
y = a[2] * b[0] - a[0] * b[2]
|
||||||
|
z = a[0] * b[1] - a[1] * b[0]
|
||||||
|
|
||||||
|
return np.array([x, y, z], dtype='float64')
|
||||||
|
|
||||||
|
def plane_coords(size=1):
|
||||||
|
v = size * 0.5
|
||||||
|
return np.array([(-v, v, 0), (v, v, 0), (v, -v, 0), (-v, -v, 0)], dtype='float64')
|
||||||
|
|
||||||
|
def matrix_transform(coords, matrix):
|
||||||
|
coords_4d = np.column_stack((coords, np.ones(len(coords), dtype='float64')))
|
||||||
|
return np.einsum('ij,aj->ai', matrix, coords_4d)[:, :-1]
|
||||||
|
|
||||||
|
def vector_normalized(vec):
|
||||||
|
return vec / np.sqrt(np.sum(vec**2))
|
||||||
|
|
||||||
|
def vector_magnitude(vec):
|
||||||
|
return np.sqrt(vec.dot(vec))
|
||||||
|
|
||||||
|
def search_square(point, factor=0.05, cam=None):
|
||||||
|
if cam is None:
|
||||||
|
cam = bpy.context.scene.camera
|
||||||
|
|
||||||
|
plane = plane_coords()
|
||||||
|
mat = cam.matrix_world.copy()
|
||||||
|
mat.translation = point
|
||||||
|
depth = vector_magnitude(point - cam.matrix_world.to_translation())
|
||||||
|
mat_scale = Matrix.Scale(tan(cam.data.angle*0.5)*depth*factor, 4)
|
||||||
|
|
||||||
|
return matrix_transform(plane, mat @ mat_scale)
|
||||||
|
|
||||||
|
def ray_cast_point(point, origin, depsgraph):
|
||||||
|
ray = (point - origin)#.normalized()
|
||||||
|
hit, hit_location, normal, face_index, object_hit, matrix = bpy.context.scene.ray_cast(depsgraph, origin, ray)
|
||||||
|
|
||||||
|
if not hit:
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
eval_ob = object_hit.evaluated_get(depsgraph)
|
||||||
|
|
||||||
|
face = eval_ob.data.polygons[face_index]
|
||||||
|
vertices = [eval_ob.data.vertices[i] for i in face.vertices]
|
||||||
|
face_co = matrix_transform([v.co for v in vertices], eval_ob.matrix_world)
|
||||||
|
|
||||||
|
tri = None
|
||||||
|
for tri_idx in tessellate_polygon([face_co]):
|
||||||
|
tri = [face_co[i] for i in tri_idx]
|
||||||
|
tri_indices = [vertices[i].index for i in tri_idx]
|
||||||
|
if intersect_point_tri(hit_location, *tri):
|
||||||
|
break
|
||||||
|
|
||||||
|
return object_hit, np.array(hit_location), tri, tri_indices
|
||||||
|
|
||||||
|
|
||||||
|
def plane_on_bone(bone, arm=None, cam=None, set_rotation=True):
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
return matrix_transform(plane, mat @ mat_scale)
|
||||||
|
|
||||||
|
def intersect_with_tesselated_plane(point, origin, face_co):
|
||||||
|
'''
|
||||||
|
face_co: World face coordinates
|
||||||
|
'''
|
||||||
|
|
||||||
|
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, sum((Vector(v) for v in tri), Vector()) / 3, triangle_normal(*tri))
|
||||||
|
if intersect_point_tri(hit_location, *tri):
|
||||||
|
break
|
||||||
|
|
||||||
|
return np.array(hit_location), tri, tri_indices
|
||||||
|
|
||||||
|
|
||||||
def get_bone_head_tail_2d(posebone, scene=None, cam=None) -> tuple[Vector, Vector]:
|
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
|
'''Get 2D vectors in camera view of bone head and tails
|
||||||
|
@ -50,3 +169,42 @@ def rotate_matrix_around_pivot(matrix, angle, pivot, axis):
|
||||||
new_matrix = translate_back @ rot_matrix @ translate_to_origin @ matrix
|
new_matrix = translate_back @ rot_matrix @ translate_to_origin @ matrix
|
||||||
|
|
||||||
return new_matrix
|
return new_matrix
|
||||||
|
|
||||||
|
|
||||||
|
# --- GREASE PENCIL
|
||||||
|
|
||||||
|
def get_gp_draw_plane(obj=None):
|
||||||
|
''' return tuple with plane coordinate and normal
|
||||||
|
of the curent drawing according to geometry'''
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
obj = bpy.context.object
|
||||||
|
|
||||||
|
settings = bpy.context.scene.tool_settings
|
||||||
|
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
|
||||||
|
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
|
||||||
|
|
||||||
|
mat = obj.matrix_world
|
||||||
|
plane_no = Vector((0.0, 0.0, 1.0))
|
||||||
|
plane_co = mat.to_translation()
|
||||||
|
|
||||||
|
# -> orientation
|
||||||
|
if orient == 'VIEW':
|
||||||
|
mat = bpy.context.scene.camera.matrix_world
|
||||||
|
# -> placement
|
||||||
|
if loc == "CURSOR":
|
||||||
|
plane_co = bpy.context.scene.cursor.location
|
||||||
|
mat = bpy.context.scene.cursor.matrix
|
||||||
|
|
||||||
|
elif orient == 'AXIS_Y':#front (X-Z)
|
||||||
|
plane_no = Vector((0,1,0))
|
||||||
|
|
||||||
|
elif orient == 'AXIS_X':#side (Y-Z)
|
||||||
|
plane_no = Vector((1,0,0))
|
||||||
|
|
||||||
|
elif orient == 'AXIS_Z':#top (X-Y)
|
||||||
|
plane_no = Vector((0,0,1))
|
||||||
|
|
||||||
|
plane_no.rotate(mat)
|
||||||
|
|
||||||
|
return plane_co, plane_no
|
Loading…
Reference in New Issue