232 lines
9.8 KiB
Python
232 lines
9.8 KiB
Python
import bpy
|
|
import numpy as np
|
|
from time import perf_counter, time, sleep
|
|
from mathutils import Vector, Matrix
|
|
|
|
from ..utils import empty_at
|
|
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,
|
|
intersect_point_tri,
|
|
intersect_point_line,
|
|
intersect_line_plane,
|
|
tessellate_polygon)
|
|
|
|
|
|
class GP_OT_debug_geometry_interpolation_targets(bpy.types.Operator):
|
|
bl_idname = "gp.debug_geometry_interpolation_targets"
|
|
bl_label = "Debug geometry interpolation target"
|
|
bl_description = 'Identify position geometry used for interpolating select point at current frame'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if context.active_object and context.object.type == 'GPENCIL'\
|
|
and context.mode in ('EDIT_GPENCIL', 'SCULPT_GPENCIL', 'PAINT_GPENCIL'):
|
|
return True
|
|
cls.poll_message_set("Need a Grease pencil object in Edit or Sculpt mode")
|
|
return False
|
|
|
|
clear : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'})
|
|
|
|
def apply_and_store(self):
|
|
# self.store = []
|
|
# item = (prop, attr, [new_val])
|
|
for item in self.store_list:
|
|
prop, attr = item[:2]
|
|
self.store.append( (prop, attr, getattr(prop, attr)) )
|
|
if len(item) >= 3:
|
|
setattr(prop, attr, item[2])
|
|
|
|
def restore(self):
|
|
for prop, attr, old_val in self.store:
|
|
setattr(prop, attr, old_val)
|
|
|
|
def exit_ops(self, context, status='INFO', text=None):
|
|
if text:
|
|
self.report({status}, text)
|
|
|
|
def execute(self, context):
|
|
debug_col_name = '.gp_interpolation_debug'
|
|
debug_col = context.scene.collection.children.get(debug_col_name)
|
|
if not debug_col:
|
|
debug_col = bpy.data.collections.get(debug_col_name)
|
|
|
|
if self.clear:
|
|
if not debug_col:
|
|
return {'CANCELLED'}
|
|
|
|
## Clear debug collection and remove
|
|
for ob in reversed(debug_col.objects):
|
|
bpy.data.objects.remove(ob)
|
|
bpy.data.collections.remove(debug_col)
|
|
return {'FINISHED'}
|
|
|
|
if not debug_col:
|
|
debug_col = bpy.data.collections.new(debug_col_name)
|
|
if debug_col.name not in context.scene.collection.children:
|
|
context.scene.collection.children.link(debug_col)
|
|
|
|
|
|
## Clear debug collection
|
|
for ob in reversed(debug_col.objects):
|
|
bpy.data.objects.remove(ob)
|
|
|
|
# return {'FINISHED'}
|
|
|
|
scn = bpy.context.scene
|
|
|
|
self.gp = context.object
|
|
# matrix = np.array(self.gp.matrix_world, dtype='float64')
|
|
# origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
|
|
matrix = self.gp.matrix_world
|
|
origin = scn.camera.matrix_world.to_translation()
|
|
|
|
settings = context.scene.gp_interpo_settings
|
|
col = settings.target_collection
|
|
if not col:
|
|
col = scn.collection
|
|
|
|
# print('----')
|
|
if not self.gp.data.layers.active:
|
|
self.exit_ops(context, status='ERROR', text='No active layer')
|
|
return {'CANCELLED'}
|
|
if not self.gp.data.layers.active.active_frame:
|
|
self.exit_ops(context, status='ERROR', text='No active frame')
|
|
return {'CANCELLED'}
|
|
|
|
tgt_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
|
|
|
|
## 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 self.gp.data.layers.active.active_frame.strokes:
|
|
s.select = True
|
|
tgt_strokes = self.gp.data.layers.active.active_frame.strokes
|
|
|
|
if not tgt_strokes:
|
|
self.exit_ops(context, status='ERROR', text='No stroke selected !')
|
|
return {'CANCELLED'}
|
|
|
|
included_cols = [c.name for c in self.gp.users_collection]
|
|
target_obj = None
|
|
|
|
if settings.method == 'GEOMETRY':
|
|
if col != context.scene.collection:
|
|
included_cols.append(col.name)
|
|
## Maybe include a plane just behind geo ? probably bad idea
|
|
|
|
elif settings.method == 'OBJECT':
|
|
if not settings.target_object:
|
|
self.exit_ops(context, status='ERROR', text='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
|
|
|
|
## Get only active point
|
|
points = [p for p in tgt_strokes[0].points if p.select]
|
|
print('points: ', points)
|
|
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
|
|
# nb_points = len(stroke.points)
|
|
## Batch get absolute coordinate
|
|
# 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(points):
|
|
point_co_world = matrix @ point.co
|
|
|
|
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)
|
|
|
|
## Increasing search range
|
|
if not object_hit: # or object_hit not in col.all_objects[:]:
|
|
found = False
|
|
for iteration in range(1, 6):
|
|
ct = 0
|
|
for square_co in search_square(point_co_world, factor=settings.search_range * iteration):
|
|
ct += 1
|
|
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(square_co, origin, dg)
|
|
|
|
is_hit = 'HIT' if object_hit else ''
|
|
## Show all search square elements
|
|
# empty_at(name=f'{is_hit}{i}search_{iteration}({ct})', pos=square_co, collection=debug_col, size=0.001, show_name=False) # type='SPHERE'
|
|
|
|
## Show only hit location (at hit location)
|
|
if is_hit:
|
|
empty_at(name=f'{is_hit}{i}search_{iteration}({ct})', pos=hit_location, collection=debug_col, size=0.001, show_name=False) # type='SPHERE'
|
|
|
|
if object_hit: # and object_hit in col.all_objects[:]:
|
|
context.scene.cursor.location = hit_location
|
|
## On location coplanar with face triangle
|
|
# hit_location = intersect_line_plane(origin, point_co_world, hit_location, triangle_normal(*tri))
|
|
|
|
## On view plane
|
|
view_vec = context.scene.camera.matrix_world.to_quaternion() @ Vector((0,0,1))
|
|
hit_location = intersect_line_plane(origin, point_co_world, hit_location, view_vec)
|
|
|
|
## An average of the two ?
|
|
# hit_location_1 = intersect_line_plane(origin, point_co_world, hit_location, triangle_normal(*tri))
|
|
# hit_location_2 = intersect_line_plane(origin, point_co_world, hit_location, view_vec)
|
|
# hit_location = (hit_location_1 + hit_location_2) / 2
|
|
|
|
empty_at(name=f'{object_hit.name}_{i}-{iteration}', pos=hit_location, collection=debug_col, type='SPHERE', size=0.001, show_name=False)
|
|
found = True
|
|
# print(f'{si}:{i} iteration {iteration}') # Dbg
|
|
break
|
|
|
|
if found:
|
|
break
|
|
|
|
if not found:
|
|
context.scene.cursor.location = point_co_world
|
|
self.exit_ops(context, status='ERROR', text=f'point {i} could not find underlying geometry')
|
|
return {'CANCELLED'}
|
|
|
|
else:
|
|
## Hit at original position
|
|
print(object_hit.name)
|
|
empty_at(name=f'{object_hit.name}_{i}', pos=hit_location, collection=debug_col, type='SPHERE', size=0.001, show_name=False)
|
|
|
|
print('Done')
|
|
return {'FINISHED'}
|
|
|
|
|
|
classes = (
|
|
GP_OT_debug_geometry_interpolation_targets,
|
|
)
|
|
|
|
def register():
|
|
for c in classes:
|
|
bpy.utils.register_class(c)
|
|
|
|
|
|
def unregister():
|
|
for c in reversed(classes):
|
|
bpy.utils.unregister_class(c)
|