Bonsai: add viewport decorator base helpers

Add two helpers to tool.Blender that 17+ existing viewport decorators
re-implement byte-identically:

- ViewportDecorator.draw_batch(shader_type, content_pos, color, indices=None)
  collapses the validate + batch_for_shader + uniform_float + draw cycle
  every shader-driven decorator needs.
- Blender.transparent_color(color, alpha=0.1) is the RGBA-alpha-override
  helper duplicated across nest, project, aggregate, model, system module
  scopes plus six nested-def copies inside draw methods.

Pure additions on the tool/ layer with direct unit tests covering the
default-alpha, explicit-alpha, non-mutation, new-list-instance, and
validation-guard branches.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-06-30 13:59:15 +02:00
parent a65f291a89
commit 091fc9e7e5
2 changed files with 60 additions and 0 deletions
+20
View File
@@ -538,6 +538,19 @@ class Blender(bonsai.core.tool.Blender):
cls.handlers.clear()
cls.is_installed = False
def draw_batch(self, shader_type, content_pos, color, indices=None):
"""Submit a GPU batch through ``self.line_shader`` (for ``"LINES"``)
or ``self.shader`` (for any other primitive). Skips empty batches
via ``validate_shader_batch_data`` so Blender 4.4+ doesn't crash on
empty ``indices``. Subclasses bind both shaders in their draw method
before calling this helper."""
if not Blender.validate_shader_batch_data(content_pos, indices):
return
shader = self.line_shader if shader_type == "LINES" else self.shader
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
shader.uniform_float("color", color)
batch.draw(shader)
@staticmethod
def _lookup_active_instance(gizmo_cls: type, context: bpy.types.Context) -> Optional[Any]:
"""Return the live ``GizmoGroup`` instance registered under
@@ -2391,6 +2404,13 @@ class Blender(bonsai.core.tool.Blender):
return False
return True
@staticmethod
def transparent_color(color: Iterable[float], alpha: float = 0.1) -> list[float]:
"""Copy an RGBA color with its alpha channel overridden."""
out = [c for c in color]
out[3] = alpha
return out
@classmethod
def draw_bmesh_face_tris(
cls,
+40
View File
@@ -42,6 +42,46 @@ class TestImplementsTool(NewFile):
assert isinstance(subject(), bonsai.core.tool.Blender)
class TestTransparentColor(NewFile):
def test_default_alpha_overrides_to_zero_one(self):
assert subject.transparent_color([1.0, 0.5, 0.25, 1.0]) == [1.0, 0.5, 0.25, 0.1]
def test_explicit_alpha_is_applied(self):
assert subject.transparent_color([1.0, 0.5, 0.25, 1.0], alpha=0.5) == [1.0, 0.5, 0.25, 0.5]
def test_does_not_mutate_input(self):
original = [1.0, 0.5, 0.25, 1.0]
subject.transparent_color(original)
assert original == [1.0, 0.5, 0.25, 1.0]
def test_returns_new_list_instance(self):
original = [1.0, 0.5, 0.25, 1.0]
result = subject.transparent_color(original)
assert result is not original
class TestViewportDecoratorDrawBatch(NewFile):
def test_empty_content_pos_skips_shader_calls(self):
from unittest.mock import MagicMock
decorator = subject.ViewportDecorator()
decorator.line_shader = MagicMock()
decorator.shader = MagicMock()
decorator.draw_batch("LINES", [], (1.0, 1.0, 1.0, 1.0))
decorator.line_shader.uniform_float.assert_not_called()
decorator.shader.uniform_float.assert_not_called()
def test_empty_indices_skips_shader_calls(self):
from unittest.mock import MagicMock
decorator = subject.ViewportDecorator()
decorator.line_shader = MagicMock()
decorator.shader = MagicMock()
decorator.draw_batch("LINES", [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0)], (1.0, 1.0, 1.0, 1.0), indices=[])
decorator.line_shader.uniform_float.assert_not_called()
decorator.shader.uniform_float.assert_not_called()
class TestCopyNodeGraph(NewFile):
def test_run(self):
material_to = bpy.data.materials.new("material_to")