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 '' empty_at(name=f'{is_hit}search_{iteration}({ct})', pos=square_co, collection=debug_col, size=0.001, show_name=True) # type='SPHERE' if object_hit: # and object_hit in col.all_objects[:]: context.scene.cursor.location = hit_location ## Get location coplanar with triangle hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri)) empty_at(name=f'{object_hit.name}_{i}-{iteration}', pos=hit_location, collection=debug_col, type='SPHERE', size=0.001, show_name=True) 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=True) 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)