snap: typing

This commit is contained in:
Bruno Perdigão
2026-08-22 21:06:07 -03:00
parent dbe64ddbb7
commit 11866bfbe2
+18 -19
View File
@@ -49,7 +49,7 @@ _triangle_batch_cache: dict[int, tuple[GPUBatch, int]] = {}
_triangle_vert_fmt: GPUVertFormat | None = None _triangle_vert_fmt: GPUVertFormat | None = None
_encoding_shader: gpu.types.GPUShader | None = None _encoding_shader: gpu.types.GPUShader | None = None
_offscreen: GPUOffScreen | None = None _offscreen: GPUOffScreen | None = None
_obj_list: list[bpy.types.Object] = [] _obj_list: list[[bpy.types.Object, bool]] = []
_SNAP_RADIUS_PX = 10 # half-size of the readback region around the cursor _SNAP_RADIUS_PX = 10 # half-size of the readback region around the cursor
@@ -123,11 +123,14 @@ def _find_closest_wireframe_pixel(buffer_data, cx, cy):
return best return best
def _get_solid_triangles(obj: bpy.types.Object): def _get_solid_triangles(obj: bpy.types.Object) -> list[tuple[tuple, tuple, tuple]]:
"""Return the list of triangles for *obj* in **local** space. """Return the list of triangles for *obj* in **local** space.
Uses evaluated mesh so that modifiers are respected. Uses evaluated mesh so that modifiers are respected.
The world matrix is applied separately in the shader. The world matrix is applied separately in the shader.
Returns
tris: list[tuple[tuple, tuple, tuple]] = []
""" """
depsgraph = bpy.context.evaluated_depsgraph_get() depsgraph = bpy.context.evaluated_depsgraph_get()
eval_obj = obj.evaluated_get(depsgraph) eval_obj = obj.evaluated_get(depsgraph)
@@ -135,7 +138,7 @@ def _get_solid_triangles(obj: bpy.types.Object):
if not mesh or not mesh.vertices: if not mesh or not mesh.vertices:
if mesh: if mesh:
eval_obj.to_mesh_clear() eval_obj.to_mesh_clear()
return [], [] return []
mesh.calc_loop_triangles() mesh.calc_loop_triangles()
@@ -165,7 +168,7 @@ def _get_cut_object_solid_triangles(obj: bpy.types.Object) -> list[tuple[tuple,
""" """
model_props = tool.Model.get_model_props() model_props = tool.Model.get_model_props()
if not (element := tool.Ifc.get_entity(obj)): if not (element := tool.Ifc.get_entity(obj)):
return {}, {} return []
if model_props.show_cut_decorator_fill and element.id() in DecoratorData.fill_cache: if model_props.show_cut_decorator_fill and element.id() in DecoratorData.fill_cache:
tris_co: list[tuple[tuple, tuple, tuple]] = [] tris_co: list[tuple[tuple, tuple, tuple]] = []
for color, verts_and_tris in DecoratorData.fill_cache[element.id()].items(): for color, verts_and_tris in DecoratorData.fill_cache[element.id()].items():
@@ -178,7 +181,7 @@ def _get_cut_object_solid_triangles(obj: bpy.types.Object) -> list[tuple[tuple,
return tris_co return tris_co
def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int] | None: def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch | None, bool]:
"""Build (or fetch from cache) a TRIANGLES batch for *obj*. """Build (or fetch from cache) a TRIANGLES batch for *obj*.
When in drawing view, creates the triangles from cut decorator fill. When in drawing view, creates the triangles from cut decorator fill.
@@ -206,9 +209,7 @@ def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int] | No
tris = [] tris = []
if CutDecorator.installed: if CutDecorator.installed:
print(obj)
tris = _get_cut_object_solid_triangles(obj) tris = _get_cut_object_solid_triangles(obj)
print(tris)
if tris: if tris:
is_cut_face = True is_cut_face = True
if not tris and (hasattr(obj.data, "polygons") and len(obj.data.polygons) > 0): if not tris and (hasattr(obj.data, "polygons") and len(obj.data.polygons) > 0):
@@ -241,7 +242,7 @@ def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int] | No
return batch, is_cut_face return batch, is_cut_face
def _get_boundary_features(obj: bpy.types.Object): def _get_boundary_features(obj: bpy.types.Object) -> tuple[list[tuple[float, float, float]], list[tuple[tuple, tuple]]]:
""" """
Uses bmesh on the **evaluated** mesh so that modifiers are respected. Uses bmesh on the **evaluated** mesh so that modifiers are respected.
Gets only edges that are not in a face and vertices that are not on and edge Gets only edges that are not in a face and vertices that are not on and edge
@@ -305,7 +306,7 @@ def _get_boundary_features(obj: bpy.types.Object):
eval_obj.to_mesh_clear() eval_obj.to_mesh_clear()
return verts, edge_pairs return verts, edge_pairs
def _get_cut_objects_features( def _get_cut_object_features(
obj: bpy.types.Object, obj: bpy.types.Object,
) -> (list[tuple[float, float, float]], list[tuple[tuple[float, float, float], tuple[float, float, float]]]): ) -> (list[tuple[float, float, float]], list[tuple[tuple[float, float, float], tuple[float, float, float]]]):
"""Return ``(all_vert_coords, edge_pairs)`` for *obj*. """Return ``(all_vert_coords, edge_pairs)`` for *obj*.
@@ -320,7 +321,7 @@ def _get_cut_objects_features(
""" """
model_props = tool.Model.get_model_props() model_props = tool.Model.get_model_props()
if not (element := tool.Ifc.get_entity(obj)): if not (element := tool.Ifc.get_entity(obj)):
return {}, {} return [], []
if model_props.show_cut_decorator and element.id() in DecoratorData.cut_cache and (hasattr(obj.data, "polygons") and len(obj.data.polygons) > 0): if model_props.show_cut_decorator and element.id() in DecoratorData.cut_cache and (hasattr(obj.data, "polygons") and len(obj.data.polygons) > 0):
verts, edges = DecoratorData.cut_cache[element.id()] verts, edges = DecoratorData.cut_cache[element.id()]
if not verts or not edges: if not verts or not edges:
@@ -330,7 +331,7 @@ def _get_cut_objects_features(
return verts, edge_pairs return verts, edge_pairs
else: else:
return {}, {} return [], []
def _ensure_wireframe_batches(obj: bpy.types.Object) -> dict[str, tuple[GPUBatch, int, list]]: def _ensure_wireframe_batches(obj: bpy.types.Object) -> dict[str, tuple[GPUBatch, int, list]]:
@@ -349,7 +350,7 @@ def _ensure_wireframe_batches(obj: bpy.types.Object) -> dict[str, tuple[GPUBatch
if _wireframe_vert_fmt is None: if _wireframe_vert_fmt is None:
_wireframe_vert_fmt = _create_vert_format() _wireframe_vert_fmt = _create_vert_format()
cache_key = id(obj) # tied to obj lifecycle for auto-invalidation cache_key = id(obj)
# Cache hit # Cache hit
if cache_key in _wireframe_batch_cache: if cache_key in _wireframe_batch_cache:
@@ -357,8 +358,7 @@ def _ensure_wireframe_batches(obj: bpy.types.Object) -> dict[str, tuple[GPUBatch
if CutDecorator.installed: if CutDecorator.installed:
all_vert_coords, edge_pairs = [], [] all_vert_coords, edge_pairs = [], []
v, e = _get_cut_objects_features(obj) v, e = _get_cut_object_features(obj)
print(e)
if v and e: if v and e:
all_vert_coords, edge_pairs = v, e all_vert_coords, edge_pairs = v, e
@@ -410,7 +410,7 @@ def _ensure_wireframe_batches(obj: bpy.types.Object) -> dict[str, tuple[GPUBatch
_wireframe_batch_cache[cache_key] = batches _wireframe_batch_cache[cache_key] = batches
return batches return batches
def _get_tris_render_ops(objs_to_raycast): def _get_tris_render_ops(objs_to_raycast: list[bpy.types.Object]) -> list[tuple[GPUBatch, Matrix, int]]:
"""Build render ops for solid (triangle) objects to raycast. """Build render ops for solid (triangle) objects to raycast.
Each mesh contributes a single TRIANGLES batch and a slot base that Each mesh contributes a single TRIANGLES batch and a slot base that
@@ -447,7 +447,7 @@ def _get_tris_render_ops(objs_to_raycast):
render_ops.append((batch, snap_obj.matrix_world.copy(), slot_base)) render_ops.append((batch, snap_obj.matrix_world.copy(), slot_base))
return render_ops return render_ops
def _create_tris_snaps(context, event, mouse_read_rect, buffers_list, last_buf, xray_mode): def _create_tris_snaps(context: bpy.types.Context, event: bpy.types.Event, mouse_read_rect, buffers_list, last_buf, xray_mode) -> tuple[list[dict], bpy.types.Object | None]:
"""Decode the triangle readback buffer(s) into face snaps. """Decode the triangle readback buffer(s) into face snaps.
In xray mode each object is read back as a single pixel under the In xray mode each object is read back as a single pixel under the
@@ -518,7 +518,6 @@ def _create_tris_snaps(context, event, mouse_read_rect, buffers_list, last_buf,
for obj_index in hits: for obj_index in hits:
obj, is_cut_face = _obj_list[obj_index] obj, is_cut_face = _obj_list[obj_index]
hit_obj, hit, face_index = tool.Raycast.cast_rays_to_single_object(context, event, obj) hit_obj, hit, face_index = tool.Raycast.cast_rays_to_single_object(context, event, obj)
print("IS_CUT", obj, is_cut_face)
if hit: if hit:
snap: dict = { snap: dict = {
"point": hit, "point": hit,
@@ -538,7 +537,7 @@ def _create_tris_snaps(context, event, mouse_read_rect, buffers_list, last_buf,
return snaps, closest_obj return snaps, closest_obj
def _get_wireframe_render_ops(objs_to_raycast): def _get_wireframe_render_ops(objs_to_raycast: list[bpy.types.Objects]) -> tuple[list[tuple[GPUBatch, Matrix, int]], list[tuple]]:
"""Build render ops for wireframe (non-solid) objects. """Build render ops for wireframe (non-solid) objects.
Boundary points and lines of each object are assigned sequential slot Boundary points and lines of each object are assigned sequential slot
@@ -590,7 +589,7 @@ def _get_wireframe_render_ops(objs_to_raycast):
return render_ops, obj_slots return render_ops, obj_slots
def _create_wireframe_snaps(context, event, mouse_read_rect, obj_slots, last_buf): def _create_wireframe_snaps(context: bpy.types.Context, event: bpy.types.Event, mouse_read_rect, obj_slots, last_buf) -> tuple[list[dict], None]:
"""Decode the wireframe readback buffer into vertex/edge snaps. """Decode the wireframe readback buffer into vertex/edge snaps.
Finds the closest non-zero pixel to the cursor, maps its encoded slot ID Finds the closest non-zero pixel to the cursor, maps its encoded slot ID