From 4da4c2801d362051536519c5f1acbf51d17ec353 Mon Sep 17 00:00:00 2001 From: pullusb Date: Mon, 15 Jul 2024 15:47:24 +0200 Subject: [PATCH] Fix error when there is no hit --- __init__.py | 2 +- interpolate_strokes/__init__.py | 2 + interpolate_strokes/debug.py | 216 +++++++++++++++++++++++++++++++ interpolate_strokes/operators.py | 5 +- ui.py | 6 + utils.py | 2 +- 6 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 interpolate_strokes/debug.py diff --git a/__init__.py b/__init__.py index 201d097..a144d39 100755 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "gp interpolate", "author": "Christophe Seux, Samuel Bernou", - "version": (0, 7, 1), + "version": (0, 7, 2), "blender": (3, 6, 0), "location": "Sidebar > Gpencil Tab > Interpolate", "description": "Interpolate Grease pencil strokes over 3D", diff --git a/interpolate_strokes/__init__.py b/interpolate_strokes/__init__.py index 4ba7a54..5e51979 100644 --- a/interpolate_strokes/__init__.py +++ b/interpolate_strokes/__init__.py @@ -1,11 +1,13 @@ from gp_interpolate.interpolate_strokes import (operators, properties, + debug, #interpolate_simple ) modules = ( properties, operators, + debug #interpolate_simple, ) diff --git a/interpolate_strokes/debug.py b/interpolate_strokes/debug.py new file mode 100644 index 0000000..2388576 --- /dev/null +++ b/interpolate_strokes/debug.py @@ -0,0 +1,216 @@ +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) diff --git a/interpolate_strokes/operators.py b/interpolate_strokes/operators.py index 34bed73..df7a98b 100644 --- a/interpolate_strokes/operators.py +++ b/interpolate_strokes/operators.py @@ -281,9 +281,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): 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) + 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 object_hit: + ## Get location coplanar with triangle hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri)) found = True # print(f'{si}:{i} iteration {iteration}') # Dbg diff --git a/ui.py b/ui.py index 7754049..bef6664 100755 --- a/ui.py +++ b/ui.py @@ -71,6 +71,12 @@ class GP_PT_interpolate(bpy.types.Panel): # col.operator('gp.parent_layer', text='Direct Parent').direct_parent = True # col.operator('gp.parent_layer', text='Empty Parent').direct_parent = False + if context.scene.gp_interpo_settings.method in ('GEOMETRY', 'OBJECT'): + col.separator() + row = col.row(align=True) + row.operator('gp.debug_geometry_interpolation_targets', text='Debug points') + row.operator('gp.debug_geometry_interpolation_targets', text='', icon='X').clear = True + classes = ( GP_PT_interpolate, ) diff --git a/utils.py b/utils.py index 67493f9..b6090ca 100644 --- a/utils.py +++ b/utils.py @@ -64,7 +64,7 @@ def search_square(point, factor=0.05, cam=None): 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) + mat_scale = Matrix.Scale(tan(cam.data.angle * 0.5) * depth * factor, 4) return matrix_transform(plane, mat @ mat_scale)