Add tool.Blender.draw_quads utility

Promotes the private _fill_quads_alpha helper from
bim/module/model/decorator.py to tool.Blender.draw_quads so any feature
decorator can reuse the same TRIS-batch path.

The new utility accepts an optional outline_color so callers can draw
fill, outline, or both in a single call. Migrates the only existing
caller (WallGizmoPreviewDecorator in model/wall.py) to the public API
and removes the local helper.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-06-22 10:40:01 +02:00
parent 3c20c27794
commit 6367de6102
3 changed files with 84 additions and 42 deletions
@@ -2069,46 +2069,6 @@ class BoundingBoxDecorator:
co2.y -= y_overlap / 2 + min_spacing co2.y -= y_overlap / 2 + min_spacing
def _fill_quads_alpha(
context: bpy.types.Context,
quads: list[
tuple[
tuple[float, float, float],
tuple[float, float, float],
tuple[float, float, float],
tuple[float, float, float],
]
],
color_rgb: tuple[float, float, float],
alpha: float,
) -> None:
"""Render ``quads`` (each a 4-tuple of world-space corner verts in CCW
order) as one TRIS batch with two triangles per quad."""
if not quads:
return
verts: list[tuple[float, float, float]] = []
indices: list[tuple[int, int, int]] = []
for quad in quads:
if len(quad) != 4:
continue
base = len(verts)
verts.extend(tuple(v) for v in quad)
indices.append((base, base + 1, base + 2))
indices.append((base, base + 2, base + 3))
if not tool.Blender.validate_shader_batch_data(verts, indices):
return
region = getattr(context, "region", None)
if region is None:
return
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
shader.bind()
shader.uniform_float("color", (*color_rgb, alpha))
batch = batch_for_shader(shader, "TRIS", {"pos": verts}, indices=indices)
gpu.state.blend_set("ALPHA")
batch.draw(shader)
gpu.state.blend_set("NONE")
def compute_mep_join_location(): def compute_mep_join_location():
"""Midpoint between the closest endpoint pair of two selected MEP """Midpoint between the closest endpoint pair of two selected MEP
segments — the world location where a connecting fitting (bend / segments — the world location where a connecting fitting (bend /
+1 -2
View File
@@ -63,7 +63,6 @@ from bonsai.bim.module.model.decorator import (
_BBOX_HIGHLIGHT_LINE_WIDTH, _BBOX_HIGHLIGHT_LINE_WIDTH,
PolylineDecorator, PolylineDecorator,
ProductDecorator, ProductDecorator,
_fill_quads_alpha,
bbox_world_edges, bbox_world_edges,
draw_polyline_segments, draw_polyline_segments,
) )
@@ -4859,7 +4858,7 @@ class WallGizmoPreviewDecorator(tool.Blender.ViewportDecorator):
], ],
color_rgb: tuple[float, float, float], color_rgb: tuple[float, float, float],
) -> None: ) -> None:
_fill_quads_alpha(context, quads, color_rgb, self.QUAD_ALPHA) tool.Blender.draw_quads(context, quads, fill_color=(*color_rgb, self.QUAD_ALPHA))
@staticmethod @staticmethod
def _wall_floor_quad(mw: Matrix, x0: float, x1: float, y0: float, y1: float) -> tuple[ def _wall_floor_quad(mw: Matrix, x0: float, x1: float, y0: float, y1: float) -> tuple[
+83
View File
@@ -55,9 +55,11 @@ from typing import (
import bmesh import bmesh
import bpy import bpy
import gpu
import ifcopenshell.util.element import ifcopenshell.util.element
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
from gpu_extras.batch import batch_for_shader
from ifcopenshell import entity_instance from ifcopenshell import entity_instance
from mathutils import Matrix, Vector from mathutils import Matrix, Vector
@@ -2403,6 +2405,87 @@ class Blender(bonsai.core.tool.Blender):
tris = [[loop.vert.index for loop in tri] for tri in bm.calc_loop_triangles()] tris = [[loop.vert.index for loop in tri] for tri in bm.calc_loop_triangles()]
draw_batch("TRIS", world_vert_coords, color, tris) draw_batch("TRIS", world_vert_coords, color, tris)
@classmethod
def draw_quads(
cls,
context: bpy.types.Context,
quads: Sequence[
tuple[
tuple[float, float, float],
tuple[float, float, float],
tuple[float, float, float],
tuple[float, float, float],
]
],
*,
fill_color: Optional[tuple[float, float, float, float]] = None,
outline_color: Optional[tuple[float, float, float, float]] = None,
outline_width: float = 1.0,
) -> None:
"""Render ``quads`` (each a 4-tuple of CCW world-space corners) as
a filled TRIS batch, an outline LINES batch, or both.
Both colors are RGBA 4-tuples. Pass ``fill_color=None`` to skip
the fill pass and ``outline_color=None`` to skip the outline.
Skipping both is a no-op.
Replaces the per-decorator quad-fill helpers that used to live
inline in each feature module.
"""
if not quads or (fill_color is None and outline_color is None):
return
region = getattr(context, "region", None)
if region is None:
return
verts: list[tuple[float, float, float]] = []
tri_indices: list[tuple[int, int, int]] = []
line_indices: list[tuple[int, int]] = []
for quad in quads:
if len(quad) != 4:
continue
base = len(verts)
verts.extend(tuple(v) for v in quad)
if fill_color is not None:
tri_indices.append((base, base + 1, base + 2))
tri_indices.append((base, base + 2, base + 3))
if outline_color is not None:
line_indices.append((base, base + 1))
line_indices.append((base + 1, base + 2))
line_indices.append((base + 2, base + 3))
line_indices.append((base + 3, base))
if not cls.validate_shader_batch_data(verts, None):
return
gpu.state.blend_set("ALPHA")
try:
if fill_color is not None and tri_indices:
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
shader.bind()
shader.uniform_float("color", fill_color)
batch = batch_for_shader(
shader, "TRIS", {"pos": verts}, indices=tri_indices
)
batch.draw(shader)
if outline_color is not None and line_indices:
shader = gpu.shader.from_builtin("UNIFORM_COLOR")
shader.bind()
shader.uniform_float("color", outline_color)
# Outline width: the UNIFORM_COLOR shader respects the
# GPU's current line-width state; restore on exit.
prev_width = gpu.state.line_width_get()
gpu.state.line_width_set(outline_width)
try:
batch = batch_for_shader(
shader, "LINES", {"pos": verts}, indices=line_indices
)
batch.draw(shader)
finally:
gpu.state.line_width_set(prev_width)
finally:
gpu.state.blend_set("NONE")
@classmethod @classmethod
def build_dashed_line_segments( def build_dashed_line_segments(
cls, cls,