snap: remove face index from the GPU detection.

Now the face index can be handled only by the object raycast.
This commit is contained in:
Bruno Perdigão
2026-08-19 11:56:04 -03:00
committed by Bruno Perdigão
parent 58565b8c21
commit f3cdf7ee4e
+17 -29
View File
@@ -42,14 +42,12 @@ import bonsai.tool as tool
_wireframe_batch_cache: dict[int, dict[str, tuple[GPUBatch, int, list]]] = {} _wireframe_batch_cache: dict[int, dict[str, tuple[GPUBatch, int, list]]] = {}
_wireframe_vert_fmt: GPUVertFormat | None = None _wireframe_vert_fmt: GPUVertFormat | None = None
_triangle_batch_cache: dict[int, tuple[GPUBatch, int, list, list]] = {} _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] = []
_TRI_OBJ_SHIFT = 20
_TRI_FACE_MASK = (1 << _TRI_OBJ_SHIFT) - 1
_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
def _create_encoding_shader() -> gpu.types.GPUShader: def _create_encoding_shader() -> gpu.types.GPUShader:
@@ -95,7 +93,7 @@ def _decode_wireframe_pixel(r: int, g: int, b: int, a: int) -> int:
def _create_vert_format() -> GPUVertFormat: def _create_vert_format() -> GPUVertFormat:
"""Attribute 0 = position (vec3), attribute 1 = face index or primitive slot (float).""" """Attribute 0 = position (vec3), attribute 1 = primitive slot (float); solid faces use 0."""
fmt = GPUVertFormat() fmt = GPUVertFormat()
fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT") fmt.attr_add(id="pos", comp_type="F32", len=3, fetch_mode="FLOAT")
fmt.attr_add(id="vert_slot", comp_type="F32", len=1, fetch_mode="FLOAT") fmt.attr_add(id="vert_slot", comp_type="F32", len=1, fetch_mode="FLOAT")
@@ -122,15 +120,10 @@ 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):
"""Return ``(tri_list, face_indices)`` 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.
tri_list : list[tuple[tuple, tuple, tuple]]
Each element is three local-space vertex positions.
face_indices : list[int]
polygon_index for each triangle.
""" """
depsgraph = bpy.context.evaluated_depsgraph_get() depsgraph = bpy.context.evaluated_depsgraph_get()
eval_obj = obj.evaluated_get(depsgraph) eval_obj = obj.evaluated_get(depsgraph)
@@ -143,7 +136,6 @@ def _get_solid_triangles(obj: bpy.types.Object):
mesh.calc_loop_triangles() mesh.calc_loop_triangles()
tris: list[tuple[tuple, tuple, tuple]] = [] tris: list[tuple[tuple, tuple, tuple]] = []
face_indices: list[int] = []
for tri in mesh.loop_triangles: for tri in mesh.loop_triangles:
v0 = mesh.vertices[tri.vertices[0]].co v0 = mesh.vertices[tri.vertices[0]].co
v1 = mesh.vertices[tri.vertices[1]].co v1 = mesh.vertices[tri.vertices[1]].co
@@ -153,20 +145,18 @@ def _get_solid_triangles(obj: bpy.types.Object):
(v1.x, v1.y, v1.z), (v1.x, v1.y, v1.z),
(v2.x, v2.y, v2.z), (v2.x, v2.y, v2.z),
)) ))
face_indices.append(tri.polygon_index)
eval_obj.to_mesh_clear() eval_obj.to_mesh_clear()
return tris, face_indices return tris
def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int, list, list] | None: def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int] | None:
"""Build (or fetch from cache) a TRIANGLES batch for *obj*. """Build (or fetch from cache) a TRIANGLES batch for *obj*.
Every face triangle is rendered, with the vertex slot encoding Every face triangle is rendered with a zeroed vertex slot since the
the face_index so the GPU can write it to the framebuffer. GPU only encodes the object index; the face is found later via ray_cast.
Returns ``(batch, n_tris, tri_list, face_indices)`` or None Returns ``(batch, n_tris)`` or None when the object has no faces.
when the object has no faces.
""" """
global _triangle_vert_fmt, _triangle_batch_cache global _triangle_vert_fmt, _triangle_batch_cache
@@ -177,18 +167,18 @@ def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int, list
if cache_key in _triangle_batch_cache: if cache_key in _triangle_batch_cache:
return _triangle_batch_cache[cache_key] return _triangle_batch_cache[cache_key]
tris, face_indices = _get_solid_triangles(obj) tris = _get_solid_triangles(obj)
n_tris = len(tris) n_tris = len(tris)
if n_tris == 0: if n_tris == 0:
return None return None
# Flatten: 3 verts per tri, each carrying the face_index as slot_id # Flatten: 3 verts per tri; the slot is unused for solid faces
coords: list[tuple[float, float, float]] = [] coords: list[tuple[float, float, float]] = []
slot_ids: list[float] = [] slot_ids: list[float] = []
for fi, tri in zip(face_indices, tris): for tri in tris:
for v in tri: for v in tri:
coords.append(v) coords.append(v)
slot_ids.append(float(fi)) slot_ids.append(0.0)
n_verts = len(coords) n_verts = len(coords)
vbo = GPUVertBuf(len=n_verts, format=_triangle_vert_fmt) vbo = GPUVertBuf(len=n_verts, format=_triangle_vert_fmt)
@@ -199,7 +189,7 @@ def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int, list
seq=[(i * 3, i * 3 + 1, i * 3 + 2) for i in range(n_tris)]) seq=[(i * 3, i * 3 + 1, i * 3 + 2) for i in range(n_tris)])
batch = GPUBatch(type="TRIS", buf=vbo, elem=ibo) batch = GPUBatch(type="TRIS", buf=vbo, elem=ibo)
result = (batch, n_tris, tris, face_indices) result = (batch, n_tris)
_triangle_batch_cache[cache_key] = result _triangle_batch_cache[cache_key] = result
return result return result
@@ -685,10 +675,10 @@ class Raycast(bonsai.core.tool.Raycast):
if batch_info is None: if batch_info is None:
continue continue
batch, n_tris, _tris, _face_indices = batch_info batch, _ = batch_info
obj_index = len(_obj_list) obj_index = len(_obj_list)
_obj_list.append(snap_obj) _obj_list.append(snap_obj)
slot_base = (obj_index << _TRI_OBJ_SHIFT) + 1 slot_base = obj_index + 1 # slot 0 = background
render_ops.append((batch, snap_obj.matrix_world.copy(), slot_base)) render_ops.append((batch, snap_obj.matrix_world.copy(), slot_base))
else: else:
slot = 1 # slot 0 = background slot = 1 # slot 0 = background
@@ -817,8 +807,7 @@ class Raycast(bonsai.core.tool.Raycast):
continue continue
vals_read.add(val) vals_read.add(val)
if val > 0: if val > 0:
val -= 1 obj_index = val - 1
obj_index = int(val) >> _TRI_OBJ_SHIFT
if obj_index < len(_obj_list): if obj_index < len(_obj_list):
hits.add(obj_index) hits.add(obj_index)
else: else:
@@ -831,8 +820,7 @@ class Raycast(bonsai.core.tool.Raycast):
px = pixel_data[centre_y][centre_x] px = pixel_data[centre_y][centre_x]
val = _decode_wireframe_pixel(px[0], px[1], px[2], px[3]) val = _decode_wireframe_pixel(px[0], px[1], px[2], px[3])
if val > 0: if val > 0:
val -= 1 obj_index = val - 1
obj_index = int(val) >> _TRI_OBJ_SHIFT
if obj_index < len(_obj_list): if obj_index < len(_obj_list):
hits.add(obj_index) hits.add(obj_index)