Add single object raycast method

This commit is contained in:
pullusb
2024-02-06 15:57:35 +01:00
parent 5590753550
commit b8180ea84f
5 changed files with 141 additions and 62 deletions
+81 -40
View File
@@ -6,12 +6,14 @@ from mathutils import Vector, Matrix
from gp_interpolate.utils import (matrix_transform,
plane_on_bone,
ray_cast_point,
obj_ray_cast,
intersect_with_tesselated_plane,
triangle_normal,
search_square,
get_gp_draw_plane,
create_plane,
following_keys,
index_list_from_bools,
attr_set)
from mathutils.geometry import (barycentric_transform,
@@ -22,6 +24,7 @@ from mathutils.geometry import (barycentric_transform,
## TODO: add bake animation to empty for later GP layer parenting
## TODO: Occlusion management
class GP_OT_interpolate_stroke(bpy.types.Operator):
bl_idname = "gp.interpolate_stroke"
@@ -63,10 +66,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
gp = context.object
# matrix = gp.matrix_world
# origin = scn.camera.matrix_world.to_translation()
matrix = np.array(gp.matrix_world, dtype='float64')
origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
# matrix = np.array(gp.matrix_world, dtype='float64')
# origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
matrix = gp.matrix_world
origin = scn.camera.matrix_world.to_translation()
col = settings.target_collection
if not col:
@@ -87,10 +90,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
return {'CANCELLED'}
included_cols = [c.name for c in gp.users_collection]
target_obj = None
start = time()
if settings.method == 'BONE':
## Follow Bone method (WIP)
if not settings.target_rig or not settings.target_bone:
self.report({'ERROR'}, 'No Bone selected')
return {'CANCELLED'}
@@ -115,13 +118,23 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
if plane.name not in toolcol.objects:
toolcol.objects.link(plane)
## TODO: Ensure the plane is not animated!
target_obj = plane
else:
# Geometry mode
elif settings.method == 'GEOMETRY':
if col != context.scene.collection:
included_cols.append(col.name)
## Maybe include a plane just behing geo ? probably bad idea
## Maybe include a plane just behind geo ? probably bad idea
elif settings.method == 'OBJECT':
if not settings.target_object:
self.report({'ERROR'}, 'No Object selected')
return {'CANCELLED'}
col = scn.collection # Reset collection filter
target_obj = settings.target_object
if target_obj.library:
## Look if an override exists in scene to use instead of default object
if (override := next((o for o in scn.objects if o.name == target_obj.name and o.override_library), None)):
target_obj = override
## Prepare context manager
store_list = [
@@ -130,8 +143,9 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
# (bpy.context.scene.render, 'simplify_subdivision', 0),
]
# TODO : Customize below filter to use in geo mode as well
# so it does not exclude collections containing rig
# TODO: for now, the collection filter is not used at all in GEOMETRY mode
# it can be used to hide collection for faster animation mode
if settings.method == 'BONE':
## TEST: Add collections containing rig (cannot be excluded)
# rig_parent_cols = [c.name for c in scn.collection.children_recursive if settings.target_rig.name in c.all_objects]
@@ -173,38 +187,26 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
stroke_data = []
for i, point in enumerate(stroke.points):
# print(si, i)
point_co_world = world_co_3d[i]
object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg)
## Try condition (not needed)
# try:
# object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg)
# except Exception as e:
# print(f'Error on first {si}:{i}')
# self.report({'ERROR'}, f'Error on first {si}:{i}')
# for p in stroke.points:
# p.select = False
# stroke.points[i].select = True
# print(e)
# return {'CANCELLED'}
if target_obj:
object_hit, hit_location, tri, tri_indices = obj_ray_cast(target_obj, Vector(point_co_world), origin, dg)
else:
# scene raycast
object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg)
## with one simple extra search
# 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
### with increasing search range
if not object_hit or object_hit not in col.all_objects[:]:
## Increasing search range
if not object_hit: # or object_hit not in col.all_objects[:]:
found = False
for iteration in range(1, 6):
for square_co in search_square(point_co_world, factor=settings.search_range * iteration):
object_hit, hit_location, tri, tri_indices = ray_cast_point(square_co, origin, dg)
if object_hit and object_hit in col.all_objects[:]:
if target_obj:
object_hit, hit_location, tri, tri_indices = obj_ray_cast(target_obj, Vector(square_co), origin, dg)
else:
# scene raycast
object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, 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))
found = True
# print(f'{si}:{i} iteration {iteration}') # Dbg
@@ -241,8 +243,8 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
for f in frames_to_jump:
wm.progress_update(f) # Pgs
scn.frame_set(f)
# origin = scn.camera.matrix_world.to_translation()
origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
origin = scn.camera.matrix_world.to_translation()
# origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
plan_co, plane_no = get_gp_draw_plane(gp)
bpy.ops.gpencil.paste()
@@ -256,7 +258,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
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):
for new_stroke, stroke_data in zip(reversed(new_strokes), reversed(strokes_data)):
world_co_3d = []
for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data:
eval_ob = object_hit.evaluated_get(dg)
@@ -288,6 +290,45 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3))
new_stroke.points.update()
## TODO: Occlusion management
## Tag occlusion on points for removal (need to create all substrokes from existing strokes)
# if settings.method == 'GEOMETRY':
# ## WIP
# occlusion_list = [False]*len(world_co_3d)
# for i, nco in enumerate(world_co_3d):
# vec_direction = nco - origin
# ## Maybe distance need to be reduced by tiny segment...
# n_hit, _hit_location, _normal, _n_face_index, n_object_hit, _matrix = scn.ray_cast(dg, origin, vec_direction, distance=vec_direction.length)
# # if n_hit and n_object_hit != object_hit: # note: Arm could still hit from torso...
# if n_hit:
# # if there is a hit, it's occluded
# # Occluded !
# occlusion_list[i] = True
# if all(occlusion_list):
# # all occluded, Just remove stroke (! Safer to reverse both list in zip iteration !)
# gp.data.layers.active.active_frame.strokes.remove(new_stroke)
# if any(occlusion_list):
# # Create substroke according to indices in original stroke
# for sublist in index_list_from_bools(occlusion_list):
# ## Clear if only one isolated point ?
# # if len(sublist) == 1:
# # continue
# ns = gp.data.layers.active.active_frame.strokes.new()
# for elem in ('hardness', 'material_index', 'line_width'):
# setattr(ns, elem, getattr(new_strokes, elem))
# ns.points.add(len(sublist))
# for i, point_index in enumerate(sublist):
# for elem in ('uv_factor', 'uv_fill', 'uv_rotation', 'pressure', 'co', 'strength', 'vertex_color'):
# setattr(ns.points[i], elem, getattr(new_strokes.points[point_index], elem))
# ## Delete original stroke
# gp.data.layers.active.active_frame.strokes.remove(new_stroke)
wm.progress_end() # Pgs
+6 -5
View File
@@ -19,6 +19,7 @@ class GP_PG_interpolate_settings(PropertyGroup):
items= (
('GEOMETRY', 'Geometry', 'Directly follow underlying geometry', 0),
('BONE', 'Bone', 'Pick an armature bone and follow it', 1),
('OBJECT', 'Object', 'Directly follow a specific object, even if occluded', 2),
),
default='GEOMETRY',
description='Select method for interpolating strokes'
@@ -34,7 +35,6 @@ class GP_PG_interpolate_settings(PropertyGroup):
\nThe value is as percentage of the camera width",
default=0.05, precision=2, step=3, options={'HIDDEN'})
mode : EnumProperty(
name='Mode',
# Combined ?markers ?
@@ -64,10 +64,11 @@ class GP_PG_interpolate_settings(PropertyGroup):
description='Rig to use as target',
type=bpy.types.Object)
# target_rig : StringProperty(
# name='Rig',
# description='Rig to use as target')
target_object : PointerProperty(
name='Object',
description='Object to interpolate on',
type=bpy.types.Object)
target_bone : StringProperty(
name='Bone',
description='Bone of the rig to follow when interpolating') # Bone