black formatting

This commit is contained in:
Bruno Perdigão
2026-08-19 19:59:54 -03:00
committed by Bruno Perdigão
parent f3cdf7ee4e
commit 2b429dc605
2 changed files with 83 additions and 70 deletions
+74 -64
View File
@@ -50,6 +50,7 @@ _obj_list: list[bpy.types.Object] = []
_SNAP_RADIUS_PX = 10 # half-size of the readback region around the cursor
def _create_encoding_shader() -> gpu.types.GPUShader:
"""Unlit flat-colour shader for encoding primitive IDs as RGBA."""
iface = GPUStageInterfaceInfo("iface")
@@ -64,10 +65,7 @@ def _create_encoding_shader() -> gpu.types.GPUShader:
shader_info.fragment_out(0, "VEC4", "FragColor")
shader_info.vertex_source(
"void main() {\n"
" slot_id = vert_slot;\n"
" gl_Position = MVP * vec4(pos, 1.0);\n"
"}\n"
"void main() {\n" " slot_id = vert_slot;\n" " gl_Position = MVP * vec4(pos, 1.0);\n" "}\n"
)
shader_info.fragment_source(
"vec4 encode(float f) {\n"
@@ -87,6 +85,7 @@ def _create_encoding_shader() -> gpu.types.GPUShader:
del shader_info, iface
return s
def _decode_wireframe_pixel(r: int, g: int, b: int, a: int) -> int:
"""Decode an RGBA pixel back to an integer slot ID."""
return (a << 24) | (b << 16) | (g << 8) | r
@@ -99,6 +98,7 @@ def _create_vert_format() -> GPUVertFormat:
fmt.attr_add(id="vert_slot", comp_type="F32", len=1, fetch_mode="FLOAT")
return fmt
def _find_closest_wireframe_pixel(buffer_data, cx, cy):
"""Scan *buffer_data* (list of rows) for the closest non-zero pixel
to (cx, cy). Used for points a lines detection. Returns ``(encoded_value, dx, dy)`` or None."""
@@ -119,6 +119,7 @@ def _find_closest_wireframe_pixel(buffer_data, cx, cy):
best = (val, dx, dy)
return best
def _get_solid_triangles(obj: bpy.types.Object):
"""Return the list of triangles for *obj* in **local** space.
@@ -140,11 +141,13 @@ def _get_solid_triangles(obj: bpy.types.Object):
v0 = mesh.vertices[tri.vertices[0]].co
v1 = mesh.vertices[tri.vertices[1]].co
v2 = mesh.vertices[tri.vertices[2]].co
tris.append((
(v0.x, v0.y, v0.z),
(v1.x, v1.y, v1.z),
(v2.x, v2.y, v2.z),
))
tris.append(
(
(v0.x, v0.y, v0.z),
(v1.x, v1.y, v1.z),
(v2.x, v2.y, v2.z),
)
)
eval_obj.to_mesh_clear()
return tris
@@ -185,14 +188,14 @@ def _ensure_triangle_batches(obj: bpy.types.Object) -> tuple[GPUBatch, int] | No
vbo.attr_fill(id="pos", data=coords)
vbo.attr_fill(id="vert_slot", data=slot_ids)
ibo = GPUIndexBuf(type="TRIS",
seq=[(i * 3, i * 3 + 1, i * 3 + 2) for i in range(n_tris)])
ibo = GPUIndexBuf(type="TRIS", seq=[(i * 3, i * 3 + 1, i * 3 + 2) for i in range(n_tris)])
batch = GPUBatch(type="TRIS", buf=vbo, elem=ibo)
result = (batch, n_tris)
_triangle_batch_cache[cache_key] = result
return result
def _get_boundary_features(obj: bpy.types.Object):
"""Return ``(all_vert_coords, edge_pairs)`` for *obj*.
@@ -239,10 +242,12 @@ def _get_boundary_features(obj: bpy.types.Object):
for e in bm.edges:
if not e.link_faces:
v0, v1 = e.verts
edge_pairs.append((
(v0.co.x, v0.co.y, v0.co.z),
(v1.co.x, v1.co.y, v1.co.z),
))
edge_pairs.append(
(
(v0.co.x, v0.co.y, v0.co.z),
(v1.co.x, v1.co.y, v1.co.z),
)
)
# Add unique endpoint vertices
for v in (v0, v1):
coord = (v.co.x, v.co.y, v.co.z)
@@ -305,14 +310,14 @@ def _ensure_wireframe_batches(obj) -> dict[str, tuple[GPUBatch, int, list]]:
vbo.attr_fill(id="pos", data=coords)
vbo.attr_fill(id="vert_slot", data=prim_ids)
ibo = GPUIndexBuf(type="LINES",
seq=[(i, i + 1) for i in range(0, n_line_verts, 2)])
ibo = GPUIndexBuf(type="LINES", seq=[(i, i + 1) for i in range(0, n_line_verts, 2)])
batches["LINES"] = (GPUBatch(type="LINES", buf=vbo, elem=ibo), n_lines, edge_pairs)
if batches:
_wireframe_batch_cache[cache_key] = batches
return batches
class Raycast(bonsai.core.tool.Raycast):
offset = 10
mouse_offset = (
@@ -326,6 +331,7 @@ class Raycast(bonsai.core.tool.Raycast):
(0, -offset),
(offset, -offset),
)
@classmethod
def get_visible_objects(cls, context: bpy.types.Context):
depsgraph = context.evaluated_depsgraph_get()
@@ -631,8 +637,8 @@ class Raycast(bonsai.core.tool.Raycast):
context: bpy.types.Context,
event: bpy.types.Event,
objs_to_raycast: list[bpy.types.Object],
tris: boolean = False,
) -> Any:
tris: bool = False,
) -> tuple[list[dict], bpy.types.Object | None]:
"""GPU-based solid face detection.
Renders all solid objects' triangles to an offscreen buffer
@@ -643,7 +649,7 @@ class Raycast(bonsai.core.tool.Raycast):
snap dicts (same format as the raycast-based version) and
*closest_obj* is the single closest object (or None).
"""
global _encoding_shader, _offscreen, _offscreen_change, _obj_list
global _encoding_shader, _offscreen, _obj_list
if bpy.app.background:
return [], None
@@ -661,6 +667,7 @@ class Raycast(bonsai.core.tool.Raycast):
# Build the object index -> object lookup and collect render ops
_obj_list.clear()
render_ops: list[tuple[GPUBatch, Matrix, int]] = []
obj_slots: list[tuple] = [] # [(snap_obj, pts_start, n_pts, lines_start, n_lines), ...]
if tris:
for snap_obj in objs_to_raycast:
@@ -681,12 +688,11 @@ class Raycast(bonsai.core.tool.Raycast):
slot_base = obj_index + 1 # slot 0 = background
render_ops.append((batch, snap_obj.matrix_world.copy(), slot_base))
else:
slot = 1 # slot 0 = background
obj_slots: list[tuple] = [] # [(snap_obj, pts_start, n_pts, lines_start, n_lines), ...]
slot = 1 # slot 0 = background
for snap_obj in objs_to_raycast:
# avoids creating batches for solid objects
if (hasattr(snap_obj.data, "polygons") and len(snap_obj.data.polygons) > 0):
if hasattr(snap_obj.data, "polygons") and len(snap_obj.data.polygons) > 0:
continue
batches = _ensure_wireframe_batches(snap_obj)
@@ -716,7 +722,6 @@ class Raycast(bonsai.core.tool.Raycast):
if n_pts > 0 or n_lines > 0:
obj_slots.append((snap_obj, pts_start, n_pts, lines_start, n_lines))
if not render_ops:
return [], None
@@ -743,7 +748,7 @@ class Raycast(bonsai.core.tool.Raycast):
if not tris:
gpu.state.depth_mask_set(False)
gpu.state.depth_test_set("NONE")
gpu.state.blend_set("NONE")
gpu.state.face_culling_set("NONE")
@@ -773,19 +778,16 @@ class Raycast(bonsai.core.tool.Raycast):
gpu.matrix.load_matrix(Matrix.Identity(4))
batch.draw(_encoding_shader)
if read_per_object: # gets all buffers
buf = fb.read_color(int(read_x), int(read_y),
read_size, read_size, 4, 0, "UBYTE")
if read_per_object: # gets all buffers
buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
buffers_list.append(buf)
if not read_per_object:
last_buf = fb.read_color(int(read_x), int(read_y),
read_size, read_size, 4, 0, "UBYTE")
last_buf = fb.read_color(int(read_x), int(read_y), read_size, read_size, 4, 0, "UBYTE")
# Restore state
gpu.state.depth_mask_set(True)
gpu.state.depth_test_set("LESS")
if tris:
# Decode hits
hits: set[int] = set()
@@ -803,7 +805,7 @@ class Raycast(bonsai.core.tool.Raycast):
return [], None
px = pixel_data[0][0]
val = _decode_wireframe_pixel(px[0], px[1], px[2], px[3])
if val in vals_read: # avoid getting all the tris from the same object
if val in vals_read: # avoid getting all the tris from the same object
continue
vals_read.add(val)
if val > 0:
@@ -886,13 +888,15 @@ class Raycast(bonsai.core.tool.Raycast):
# Compute proper 3D distance from vertex to view ray
proj = tool.Cad.point_on_edge(world_pos, (ray_target, loc))
distance = (world_pos - proj).length
snaps.append({
"object": snap_obj,
"type": "Vertex",
"point": world_pos,
"distance": distance,
"group": "Wireframe",
})
snaps.append(
{
"object": snap_obj,
"type": "Vertex",
"point": world_pos,
"distance": distance,
"group": "Wireframe",
}
)
break
if n_lines > 0 and lines_start <= encoded < lines_start + n_lines:
@@ -919,39 +923,45 @@ class Raycast(bonsai.core.tool.Raycast):
proj = tool.Cad.point_on_edge(edge_point, (ray_target, loc))
distance = (edge_point - proj).length
snaps.append({
"object": snap_obj,
"type": "Edge",
"point": edge_point,
"edge_verts": (v0, v1),
"distance": distance,
"group": "Wireframe",
})
snaps.append(
{
"object": snap_obj,
"type": "Edge",
"point": edge_point,
"edge_verts": (v0, v1),
"distance": distance,
"group": "Wireframe",
}
)
# Edge Center snap (midpoint)
mid = (v0 + v1) / 2
mid_proj = tool.Cad.point_on_edge(mid, (ray_target, loc))
mid_dist = (mid - mid_proj).length
snaps.append({
"object": snap_obj,
"type": "Edge Center",
"point": mid,
"distance": mid_dist,
"group": "Wireframe",
})
snaps.append(
{
"object": snap_obj,
"type": "Edge Center",
"point": mid,
"distance": mid_dist,
"group": "Wireframe",
}
)
# Also include vertex snaps for edge endpoints
for vtx in (v0, v1):
proj = tool.Cad.point_on_edge(vtx, (ray_target, loc))
vtx_dist = (vtx - proj).length
if vtx_dist < snap_threshold:
snaps.append({
"object": snap_obj,
"type": "Vertex",
"point": vtx,
"distance": vtx_dist,
"group": "Wireframe",
})
snaps.append(
{
"object": snap_obj,
"type": "Vertex",
"point": vtx,
"distance": vtx_dist,
"group": "Wireframe",
}
)
break
return snaps, None
@@ -959,13 +969,11 @@ class Raycast(bonsai.core.tool.Raycast):
@classmethod
def get_gpu_solid_snaps(cls, context, event, objs_to_raycast):
return cls.get_gpu_detection_snaps(context, event, objs_to_raycast, tris=True)
@classmethod
def get_gpu_wireframe_snaps(cls, context, event, objs_to_raycast):
return cls.get_gpu_detection_snaps(context, event, objs_to_raycast)
@classmethod
def ray_cast_by_proximity(
cls,
@@ -1190,7 +1198,9 @@ class Raycast(bonsai.core.tool.Raycast):
for obj, bbox_2d in objs_2d_bbox:
if bbox_2d:
if tool.Raycast.intersect_mouse_2d_bounding_box(mouse_pos, bbox_2d):
if tool.Raycast.object_is_visible_in_clipping_plane(obj): # TODO Make this work only if clipping plane is active
if tool.Raycast.object_is_visible_in_clipping_plane(
obj
): # TODO Make this work only if clipping plane is active
objs_to_raycast.append(obj)
return objs_to_raycast
+9 -6
View File
@@ -401,7 +401,9 @@ class Snap(bonsai.core.tool.Snap):
for snap in snap_faces:
if snap["object"] == closest_obj:
detected_snaps.append(snap)
snap_points = tool.Raycast.ray_cast_by_proximity(context, event, snap["object"], snap["object"].data.polygons[snap["face_index"]])
snap_points = tool.Raycast.ray_cast_by_proximity(
context, event, snap["object"], snap["object"].data.polygons[snap["face_index"]]
)
for point in snap_points:
point["group"] = "Object"
detected_snaps.append(point)
@@ -410,23 +412,24 @@ class Snap(bonsai.core.tool.Snap):
ray_origin = tool.Raycast.get_viewport_ray_data(context, event)[0]
occl_dist = (snap["point"] - ray_origin).length + 1e-4
visible_wireframe_snaps = [
w for w in wireframe_snaps
if (w["point"] - ray_origin).length <= occl_dist
w for w in wireframe_snaps if (w["point"] - ray_origin).length <= occl_dist
]
detected_snaps.extend(visible_wireframe_snaps)
continue
if not snap_faces:
detected_snaps.extend(wireframe_snaps)
else:
# Doesn't include face snaps, only their edges and vertices
for snap in snap_faces:
snap_points = tool.Raycast.ray_cast_by_proximity(context, event, snap["object"], snap["object"].data.polygons[snap["face_index"]])
snap_points = tool.Raycast.ray_cast_by_proximity(
context, event, snap["object"], snap["object"].data.polygons[snap["face_index"]]
)
for point in snap_points:
point["group"] = "Object"
detected_snaps.append(point)
detected_snaps.extend(wireframe_snaps)
# snap to cut geometry (e.g. in plan view)