mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-09 13:52:23 +00:00
Bonsai: migrate viewport decorators onto canonical base
Migrate 17 legacy viewport decorators (ClashDecorator, SolarDecorator, MeasureDecorator, ItemDecorator, GeoreferenceDecorator, NestDecorator, NestModeDecorator, GridDecorator, LoadsDecorator, AggregateDecorator, AggregateModeDecorator, PolylineDecorator, ProductDecorator, WallAxisDecorator, SlabDirectionDecorator, FaceAreaDecorator, BoundingBoxDecorator) from hand-rolled install/uninstall lifecycles onto the canonical tool.Blender.ViewportDecorator base. The legacy uninstall removed each handler from Blender but never cleared cls.handlers, growing a stale-reference list across enable/disable cycles. The base's uninstall clears the list correctly. State-derived install methods (ItemDecorator, ProductDecorator, LoadsDecorator, PolylineDecorator) keep an install override per the base's documented contract. Drop the now-redundant per-class draw_batch copies and the module- or method-scope transparent_color defs in favour of the base helpers introduced in the preceding commit. system/decorator.py and boundary/decorator.py keep their installed-flag lifecycle (different pattern, no leak) but consume tool.Blender.transparent_color. Add an AST forward-compat guard pinning the contract structurally: any class declaring handlers = [] (Assign or AnnAssign) must subclass tool.Blender.ViewportDecorator. Add a runtime regression on ClashDecorator's install/uninstall cycle. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -20,7 +20,6 @@ import blf
|
|||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
from bpy.types import SpaceView3D
|
|
||||||
from bpy_extras import view3d_utils
|
from bpy_extras import view3d_utils
|
||||||
from gpu_extras.batch import batch_for_shader
|
from gpu_extras.batch import batch_for_shader
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
@@ -28,12 +27,6 @@ from mathutils import Vector
|
|||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
|
|
||||||
def create_bounding_box(objs):
|
def create_bounding_box(objs):
|
||||||
# Initialize the bounding box coordinates
|
# Initialize the bounding box coordinates
|
||||||
min_x, min_y, min_z = float("inf"), float("inf"), float("inf")
|
min_x, min_y, min_z = float("inf"), float("inf"), float("inf")
|
||||||
@@ -79,26 +72,8 @@ def create_bounding_box(objs):
|
|||||||
return indices, edges
|
return indices, edges
|
||||||
|
|
||||||
|
|
||||||
class AggregateDecorator:
|
class AggregateDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_method = "draw_aggregate"
|
||||||
handlers = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_aggregate, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def dotted_line_shader(self):
|
def dotted_line_shader(self):
|
||||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||||
@@ -153,14 +128,6 @@ class AggregateDecorator:
|
|||||||
shader.uniform_float("u_Scale", 25)
|
shader.uniform_float("u_Scale", 25)
|
||||||
batch.draw(shader)
|
batch.draw(shader)
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_aggregate(self, context):
|
def draw_aggregate(self, context):
|
||||||
props = tool.Aggregate.get_aggregate_props()
|
props = tool.Aggregate.get_aggregate_props()
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
@@ -225,39 +192,11 @@ class AggregateDecorator:
|
|||||||
self.draw_custom_batch(line, decorator_color_unselected)
|
self.draw_custom_batch(line, decorator_color_unselected)
|
||||||
|
|
||||||
|
|
||||||
class AggregateModeDecorator:
|
class AggregateModeDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_aggregate_name", "POST_PIXEL"),
|
||||||
|
("draw_aggregate_empty", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(
|
|
||||||
SpaceView3D.draw_handler_add(handler.draw_aggregate_name, (context,), "WINDOW", "POST_PIXEL")
|
|
||||||
)
|
|
||||||
cls.handlers.append(
|
|
||||||
SpaceView3D.draw_handler_add(handler.draw_aggregate_empty, (context,), "WINDOW", "POST_VIEW")
|
|
||||||
)
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_aggregate_name(self, context):
|
def draw_aggregate_name(self, context):
|
||||||
if context.mode == "EDIT_MESH":
|
if context.mode == "EDIT_MESH":
|
||||||
|
|||||||
@@ -56,11 +56,6 @@ class BoundaryDecorator:
|
|||||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||||
special_elements_color = self.addon_prefs.decorator_color_special
|
special_elements_color = self.addon_prefs.decorator_color_special
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
gpu.state.point_size_set(6)
|
gpu.state.point_size_set(6)
|
||||||
gpu.state.blend_set("ALPHA")
|
gpu.state.blend_set("ALPHA")
|
||||||
|
|
||||||
@@ -109,7 +104,11 @@ class BoundaryDecorator:
|
|||||||
|
|
||||||
if unselected_edges:
|
if unselected_edges:
|
||||||
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
||||||
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
|
self.draw_batch(
|
||||||
|
"TRIS", unselected_vertices, tool.Blender.transparent_color(special_elements_color), unselected_tris
|
||||||
|
)
|
||||||
if selected_edges:
|
if selected_edges:
|
||||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
self.draw_batch(
|
||||||
|
"TRIS", selected_vertices, tool.Blender.transparent_color(selected_elements_color), selected_tris
|
||||||
|
)
|
||||||
|
|||||||
@@ -18,43 +18,17 @@
|
|||||||
|
|
||||||
import blf
|
import blf
|
||||||
import gpu
|
import gpu
|
||||||
from bpy.types import SpaceView3D
|
|
||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
class ClashDecorator:
|
class ClashDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_text", "POST_PIXEL"),
|
||||||
|
("draw_geometry", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_geometry, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_text(self, context):
|
def draw_text(self, context):
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
|
|
||||||
import blf
|
import blf
|
||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
@@ -25,15 +23,16 @@ import ifcopenshell
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from bpy.types import SpaceView3D
|
from bpy.types import SpaceView3D
|
||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
class ItemDecorator:
|
class ItemDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_text", "POST_PIXEL"),
|
||||||
|
("draw", "POST_VIEW"),
|
||||||
|
)
|
||||||
objs: dict[str, dict[str, list]]
|
objs: dict[str, dict[str, list]]
|
||||||
obj_is_selected: dict[str, bool]
|
obj_is_selected: dict[str, bool]
|
||||||
obj_is_boolean: dict[str, list[ifcopenshell.entity_instance]]
|
obj_is_boolean: dict[str, list[ifcopenshell.entity_instance]]
|
||||||
@@ -119,23 +118,6 @@ class ItemDecorator:
|
|||||||
"special_edges": special_edges,
|
"special_edges": special_edges,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_text(self, context):
|
def draw_text(self, context):
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||||
@@ -163,11 +145,6 @@ class ItemDecorator:
|
|||||||
blf.disable(font_id, blf.SHADOW)
|
blf.disable(font_id, blf.SHADOW)
|
||||||
|
|
||||||
def draw(self, context: bpy.types.Context) -> None:
|
def draw(self, context: bpy.types.Context) -> None:
|
||||||
def transparent_color(color: Sequence[float], alpha: float = 0.05) -> list[float]:
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||||
@@ -197,15 +174,33 @@ class ItemDecorator:
|
|||||||
if context.mode != "OBJECT":
|
if context.mode != "OBJECT":
|
||||||
continue
|
continue
|
||||||
self.draw_batch("LINES", data["verts"], selected_elements_color, data["edges"])
|
self.draw_batch("LINES", data["verts"], selected_elements_color, data["edges"])
|
||||||
self.draw_batch("TRIS", data["verts"], transparent_color(selected_elements_color), data["tris"])
|
self.draw_batch(
|
||||||
|
"TRIS",
|
||||||
|
data["verts"],
|
||||||
|
tool.Blender.transparent_color(selected_elements_color, alpha=0.05),
|
||||||
|
data["tris"],
|
||||||
|
)
|
||||||
self.draw_batch("LINES", data["special_verts"], selected_elements_color, data["special_edges"])
|
self.draw_batch("LINES", data["special_verts"], selected_elements_color, data["special_edges"])
|
||||||
elif self.obj_is_boolean[obj_name]:
|
elif self.obj_is_boolean[obj_name]:
|
||||||
self.draw_batch("LINES", data["verts"], special_elements_color, data["edges"])
|
self.draw_batch("LINES", data["verts"], special_elements_color, data["edges"])
|
||||||
self.draw_batch("TRIS", data["verts"], transparent_color(special_elements_color), data["tris"])
|
self.draw_batch(
|
||||||
|
"TRIS",
|
||||||
|
data["verts"],
|
||||||
|
tool.Blender.transparent_color(special_elements_color, alpha=0.05),
|
||||||
|
data["tris"],
|
||||||
|
)
|
||||||
self.draw_batch("LINES", data["special_verts"], special_elements_color, data["special_edges"])
|
self.draw_batch("LINES", data["special_verts"], special_elements_color, data["special_edges"])
|
||||||
else:
|
else:
|
||||||
self.draw_batch(
|
self.draw_batch(
|
||||||
"LINES", data["verts"], transparent_color(unselected_elements_color, alpha=0.2), data["edges"]
|
"LINES",
|
||||||
|
data["verts"],
|
||||||
|
tool.Blender.transparent_color(unselected_elements_color, alpha=0.2),
|
||||||
|
data["edges"],
|
||||||
|
)
|
||||||
|
self.draw_batch(
|
||||||
|
"TRIS",
|
||||||
|
data["verts"],
|
||||||
|
tool.Blender.transparent_color(special_elements_color, alpha=0.05),
|
||||||
|
data["tris"],
|
||||||
)
|
)
|
||||||
self.draw_batch("TRIS", data["verts"], transparent_color(special_elements_color), data["tris"])
|
|
||||||
self.draw_batch("LINES", data["special_verts"], special_elements_color, data["special_edges"])
|
self.draw_batch("LINES", data["special_verts"], special_elements_color, data["special_edges"])
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from math import radians
|
|||||||
import blf
|
import blf
|
||||||
import gpu
|
import gpu
|
||||||
import ifcopenshell.util.geolocation
|
import ifcopenshell.util.geolocation
|
||||||
from bpy.types import SpaceView3D
|
|
||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
from gpu_extras.batch import batch_for_shader
|
from gpu_extras.batch import batch_for_shader
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector
|
||||||
@@ -30,27 +29,11 @@ import bonsai.tool as tool
|
|||||||
from bonsai.bim.module.georeference.data import GeoreferenceData
|
from bonsai.bim.module.georeference.data import GeoreferenceData
|
||||||
|
|
||||||
|
|
||||||
class GeoreferenceDecorator:
|
class GeoreferenceDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_text", "POST_PIXEL"),
|
||||||
|
("draw_geometry", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_geometry, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None, should_scale=True):
|
def draw_batch(self, shader_type, content_pos, color, indices=None, should_scale=True):
|
||||||
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
|
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
|
||||||
|
|||||||
@@ -20,44 +20,18 @@
|
|||||||
import blf
|
import blf
|
||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
from bpy.types import SpaceView3D
|
|
||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Matrix, Vector
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.light.data import SolarData
|
from bonsai.bim.module.light.data import SolarData
|
||||||
|
|
||||||
|
|
||||||
class SolarDecorator:
|
class SolarDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_text", "POST_PIXEL"),
|
||||||
|
("draw_geometry", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_geometry, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_text(self, context: bpy.types.Context) -> None:
|
def draw_text(self, context: bpy.types.Context) -> None:
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
|
|||||||
@@ -53,12 +53,6 @@ from bonsai.bim.module.drawing.gizmos import (
|
|||||||
from bonsai.bim.module.drawing.helper import format_distance
|
from bonsai.bim.module.drawing.helper import format_distance
|
||||||
|
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
|
|
||||||
def highlight_color(color, alpha=0.1):
|
def highlight_color(color, alpha=0.1):
|
||||||
color = [i + (1 - i) * 0.5 for i in color]
|
color = [i + (1 - i) * 0.5 for i in color]
|
||||||
return color
|
return color
|
||||||
@@ -133,7 +127,7 @@ class ProfileDecorator:
|
|||||||
|
|
||||||
def draw_faces(self, bm, vertices_coords):
|
def draw_faces(self, bm, vertices_coords):
|
||||||
"""Submit a non-mutating beauty-triangulated TRIS batch over ``bm``'s faces."""
|
"""Submit a non-mutating beauty-triangulated TRIS batch over ``bm``'s faces."""
|
||||||
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
|
faces_color = tool.Blender.transparent_color(self.addon_prefs.decorator_color_special)
|
||||||
tool.Blender.draw_bmesh_face_tris(bm, vertices_coords, faces_color, self.draw_batch)
|
tool.Blender.draw_bmesh_face_tris(bm, vertices_coords, faces_color, self.draw_batch)
|
||||||
|
|
||||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||||
@@ -263,7 +257,7 @@ class ProfileDecorator:
|
|||||||
self.draw_batch("LINES", all_vertices, unselected_elements_color, unselected_edges)
|
self.draw_batch("LINES", all_vertices, unselected_elements_color, unselected_edges)
|
||||||
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
||||||
|
|
||||||
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
|
self.draw_batch("POINTS", unselected_vertices, tool.Blender.transparent_color(unselected_elements_color, 0.5))
|
||||||
self.draw_batch("POINTS", error_vertices, error_elements_color)
|
self.draw_batch("POINTS", error_vertices, error_elements_color)
|
||||||
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
||||||
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
||||||
@@ -354,9 +348,11 @@ class ProfileDecorator:
|
|||||||
return points, listEdg
|
return points, listEdg
|
||||||
|
|
||||||
|
|
||||||
class PolylineDecorator:
|
class PolylineDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
# draw_methods declares only the always-bound handler so the base's
|
||||||
handlers = []
|
# __init_subclass__ validation passes; the override install below
|
||||||
|
# conditionally registers up to four more handlers based on ui_only.
|
||||||
|
draw_methods = (("draw_input_ui", "POST_PIXEL"),)
|
||||||
event = None
|
event = None
|
||||||
input_type = None
|
input_type = None
|
||||||
input_ui = None
|
input_ui = None
|
||||||
@@ -392,15 +388,6 @@ class PolylineDecorator:
|
|||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW"))
|
cls.handlers.append(SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW"))
|
||||||
cls.is_installed = True
|
cls.is_installed = True
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(
|
def update(
|
||||||
cls,
|
cls,
|
||||||
@@ -459,14 +446,6 @@ class PolylineDecorator:
|
|||||||
|
|
||||||
return {"verts": verts, "edges": edges, "tris": tris}
|
return {"verts": verts, "edges": edges, "tris": tris}
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def shader_config(self, context):
|
def shader_config(self, context):
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
self.decorator_color = self.addon_prefs.decorations_colour
|
self.decorator_color = self.addon_prefs.decorations_colour
|
||||||
@@ -734,7 +713,9 @@ class PolylineDecorator:
|
|||||||
if self.polyline_data.measurement_type == "POLY_AREA" and area:
|
if self.polyline_data.measurement_type == "POLY_AREA" and area:
|
||||||
if float(area) > 0:
|
if float(area) > 0:
|
||||||
tris = self.calculate_polygon(polyline_verts)["tris"]
|
tris = self.calculate_polygon(polyline_verts)["tris"]
|
||||||
self.draw_batch("TRIS", polyline_verts, transparent_color(self.decorator_color_special), tris)
|
self.draw_batch(
|
||||||
|
"TRIS", polyline_verts, tool.Blender.transparent_color(self.decorator_color_special), tris
|
||||||
|
)
|
||||||
|
|
||||||
# Draw polyline with selected points
|
# Draw polyline with selected points
|
||||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||||
@@ -987,9 +968,8 @@ class PolylineDecorator:
|
|||||||
self.draw_batch("LINES", polyline_verts, decorator_color_unselected, polyline_edges)
|
self.draw_batch("LINES", polyline_verts, decorator_color_unselected, polyline_edges)
|
||||||
|
|
||||||
|
|
||||||
class ProductDecorator:
|
class ProductDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_method = "draw_product_preview"
|
||||||
handlers = []
|
|
||||||
preview_mode: Literal["PROFILE_VERTICAL", "PROFILE_HORIZONTAL", "LAYER2", "LAYER3", "GENERIC"]
|
preview_mode: Literal["PROFILE_VERTICAL", "PROFILE_HORIZONTAL", "LAYER2", "LAYER3", "GENERIC"]
|
||||||
relating_type = None
|
relating_type = None
|
||||||
obj_data: dict[str, list] = {}
|
obj_data: dict[str, list] = {}
|
||||||
@@ -1032,29 +1012,7 @@ class ProductDecorator:
|
|||||||
)
|
)
|
||||||
cls.is_installed = True
|
cls.is_installed = True
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_product_preview(self, context):
|
def draw_product_preview(self, context):
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||||
@@ -1086,7 +1044,7 @@ class ProductDecorator:
|
|||||||
data = self.get_generic_preview_data()
|
data = self.get_generic_preview_data()
|
||||||
if data:
|
if data:
|
||||||
self.draw_batch("LINES", data["verts"], decorator_color, data["edges"])
|
self.draw_batch("LINES", data["verts"], decorator_color, data["edges"])
|
||||||
self.draw_batch("TRIS", data["verts"], transparent_color(decorator_color), data["tris"])
|
self.draw_batch("TRIS", data["verts"], tool.Blender.transparent_color(decorator_color), data["tris"])
|
||||||
|
|
||||||
def get_wall_preview_data(self):
|
def get_wall_preview_data(self):
|
||||||
relating_type = self.relating_type
|
relating_type = self.relating_type
|
||||||
@@ -1619,34 +1577,8 @@ class ProductDecorator:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class WallAxisDecorator:
|
class WallAxisDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_method = "draw_wall_axis"
|
||||||
handlers = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_wall_axis, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_wall_axis(self, context):
|
def draw_wall_axis(self, context):
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
@@ -1685,34 +1617,8 @@ class WallAxisDecorator:
|
|||||||
self.draw_batch("LINES", arrow, unselected_elements_color, [(0, 1), (1, 2), (1, 3)])
|
self.draw_batch("LINES", arrow, unselected_elements_color, [(0, 1), (1, 2), (1, 3)])
|
||||||
|
|
||||||
|
|
||||||
class SlabDirectionDecorator:
|
class SlabDirectionDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_method = "draw_wall_axis"
|
||||||
handlers = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_wall_axis, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_wall_axis(self, context):
|
def draw_wall_axis(self, context):
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
@@ -1742,41 +1648,10 @@ class SlabDirectionDecorator:
|
|||||||
self.draw_batch("LINES", base, selected_elements_color, [(0, 1)])
|
self.draw_batch("LINES", base, selected_elements_color, [(0, 1)])
|
||||||
|
|
||||||
|
|
||||||
class FaceAreaDecorator:
|
class FaceAreaDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_method = "draw_face_area"
|
||||||
handlers = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_face_area, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_face_area(self, context):
|
def draw_face_area(self, context):
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||||
@@ -1797,12 +1672,16 @@ class FaceAreaDecorator:
|
|||||||
if data:
|
if data:
|
||||||
self.draw_batch("POINTS", data["verts"], decorator_color)
|
self.draw_batch("POINTS", data["verts"], decorator_color)
|
||||||
self.draw_batch("LINES", data["verts"], decorator_color, data["edges"])
|
self.draw_batch("LINES", data["verts"], decorator_color, data["edges"])
|
||||||
self.draw_batch("TRIS", data["verts"], transparent_color(decorator_color, alpha=0.5), data["tris"])
|
self.draw_batch(
|
||||||
|
"TRIS", data["verts"], tool.Blender.transparent_color(decorator_color, alpha=0.5), data["tris"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BoundingBoxDecorator:
|
class BoundingBoxDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_bounding_box_wire_cube", "POST_VIEW"),
|
||||||
|
("draw_dimension_text", "POST_PIXEL"),
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
context = bpy.context
|
context = bpy.context
|
||||||
@@ -1813,31 +1692,6 @@ class BoundingBoxDecorator:
|
|||||||
self.decorator_color_wire = (*theme.view_3d.bone_solid, 1)
|
self.decorator_color_wire = (*theme.view_3d.bone_solid, 1)
|
||||||
self.decorator_color_special = tool.Blender.get_addon_preferences().decorator_color_special
|
self.decorator_color_special = tool.Blender.get_addon_preferences().decorator_color_special
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(
|
|
||||||
bpy.types.SpaceView3D.draw_handler_add(
|
|
||||||
handler.draw_bounding_box_wire_cube, (context,), "WINDOW", "POST_VIEW"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
cls.handlers.append(
|
|
||||||
bpy.types.SpaceView3D.draw_handler_add(handler.draw_dimension_text, (context,), "WINDOW", "POST_PIXEL")
|
|
||||||
)
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
bpy.types.SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
cls.handlers.clear()
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_combined_bounding_box_corners(objects):
|
def get_combined_bounding_box_corners(objects):
|
||||||
|
|
||||||
@@ -1910,14 +1764,6 @@ class BoundingBoxDecorator:
|
|||||||
]
|
]
|
||||||
return trihedron[best_origin]
|
return trihedron[best_origin]
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_text_background(self, context, coords_dim, text_dim):
|
def draw_text_background(self, context, coords_dim, text_dim):
|
||||||
padding = 5
|
padding = 5
|
||||||
theme = context.preferences.themes.items()[0][1]
|
theme = context.preferences.themes.items()[0][1]
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import blf
|
|||||||
import bpy
|
import bpy
|
||||||
import gpu
|
import gpu
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
from bpy.types import SpaceView3D
|
|
||||||
from bpy_extras import view3d_utils
|
from bpy_extras import view3d_utils
|
||||||
from gpu_extras.batch import batch_for_shader
|
from gpu_extras.batch import batch_for_shader
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
@@ -28,12 +27,6 @@ from mathutils import Vector
|
|||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
|
|
||||||
def create_bounding_box(objs):
|
def create_bounding_box(objs):
|
||||||
# Initialize the bounding box coordinates
|
# Initialize the bounding box coordinates
|
||||||
min_x, min_y, min_z = float("inf"), float("inf"), float("inf")
|
min_x, min_y, min_z = float("inf"), float("inf"), float("inf")
|
||||||
@@ -79,26 +72,8 @@ def create_bounding_box(objs):
|
|||||||
return indices, edges
|
return indices, edges
|
||||||
|
|
||||||
|
|
||||||
class NestDecorator:
|
class NestDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_method = "draw_nest"
|
||||||
handlers = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_nest, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def dotted_line_shader(self):
|
def dotted_line_shader(self):
|
||||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||||
@@ -154,14 +129,6 @@ class NestDecorator:
|
|||||||
shader.uniform_float("u_Scale", 25)
|
shader.uniform_float("u_Scale", 25)
|
||||||
batch.draw(shader)
|
batch.draw(shader)
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_nest(self, context: bpy.types.Context) -> None:
|
def draw_nest(self, context: bpy.types.Context) -> None:
|
||||||
props = tool.Nest.get_nest_props()
|
props = tool.Nest.get_nest_props()
|
||||||
if props.in_nest_mode:
|
if props.in_nest_mode:
|
||||||
@@ -226,35 +193,11 @@ class NestDecorator:
|
|||||||
self.draw_custom_batch(line, decorator_color_unselected)
|
self.draw_custom_batch(line, decorator_color_unselected)
|
||||||
|
|
||||||
|
|
||||||
class NestModeDecorator:
|
class NestModeDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_nest_name", "POST_PIXEL"),
|
||||||
|
("draw_nest_empty", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_nest_name, (context,), "WINDOW", "POST_PIXEL"))
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_nest_empty, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_nest_name(self, context):
|
def draw_nest_name(self, context):
|
||||||
if context.mode == "EDIT_MESH":
|
if context.mode == "EDIT_MESH":
|
||||||
|
|||||||
@@ -42,12 +42,6 @@ def toggle_decorations_on_load(*args):
|
|||||||
# as queried object is linked from separate .blend file.
|
# as queried object is linked from separate .blend file.
|
||||||
|
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectDecorator:
|
class ProjectDecorator:
|
||||||
installed = None
|
installed = None
|
||||||
|
|
||||||
@@ -80,11 +74,6 @@ class ProjectDecorator:
|
|||||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||||
special_elements_color = self.addon_prefs.decorator_color_special
|
special_elements_color = self.addon_prefs.decorator_color_special
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
gpu.state.point_size_set(6)
|
gpu.state.point_size_set(6)
|
||||||
gpu.state.blend_set("ALPHA")
|
gpu.state.blend_set("ALPHA")
|
||||||
|
|
||||||
@@ -110,7 +99,9 @@ class ProjectDecorator:
|
|||||||
|
|
||||||
if geom.selected_edges:
|
if geom.selected_edges:
|
||||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, geom.selected_edges)
|
self.draw_batch("LINES", selected_vertices, selected_elements_color, geom.selected_edges)
|
||||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), geom.selected_tris)
|
self.draw_batch(
|
||||||
|
"TRIS", selected_vertices, tool.Blender.transparent_color(selected_elements_color), geom.selected_tris
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ClippingPlaneDecorator:
|
class ClippingPlaneDecorator:
|
||||||
@@ -145,11 +136,6 @@ class ClippingPlaneDecorator:
|
|||||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||||
special_elements_color = self.addon_prefs.decorator_color_special
|
special_elements_color = self.addon_prefs.decorator_color_special
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
gpu.state.point_size_set(6)
|
gpu.state.point_size_set(6)
|
||||||
gpu.state.blend_set("ALPHA")
|
gpu.state.blend_set("ALPHA")
|
||||||
|
|
||||||
@@ -210,37 +196,21 @@ class ClippingPlaneDecorator:
|
|||||||
|
|
||||||
if unselected_edges:
|
if unselected_edges:
|
||||||
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
||||||
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
|
self.draw_batch(
|
||||||
|
"TRIS", unselected_vertices, tool.Blender.transparent_color(special_elements_color), unselected_tris
|
||||||
|
)
|
||||||
if selected_edges:
|
if selected_edges:
|
||||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
self.draw_batch(
|
||||||
|
"TRIS", selected_vertices, tool.Blender.transparent_color(selected_elements_color), selected_tris
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MeasureDecorator:
|
class MeasureDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_measurements_text", "POST_PIXEL"),
|
||||||
|
("draw_measurements_poly", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(
|
|
||||||
SpaceView3D.draw_handler_add(handler.draw_measurements_text, (context,), "WINDOW", "POST_PIXEL")
|
|
||||||
)
|
|
||||||
cls.handlers.append(
|
|
||||||
SpaceView3D.draw_handler_add(handler.draw_measurements_poly, (context,), "WINDOW", "POST_VIEW")
|
|
||||||
)
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_measurements_text(self, context):
|
def draw_measurements_text(self, context):
|
||||||
PolylineDecorator().select_and_draw_measurements_text(context)
|
PolylineDecorator().select_and_draw_measurements_text(context)
|
||||||
|
|||||||
@@ -18,43 +18,17 @@
|
|||||||
|
|
||||||
import blf
|
import blf
|
||||||
import gpu
|
import gpu
|
||||||
from bpy.types import SpaceView3D
|
|
||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
|
||||||
class GridDecorator:
|
class GridDecorator(tool.Blender.ViewportDecorator):
|
||||||
is_installed = False
|
draw_methods = (
|
||||||
handlers = []
|
("draw_text", "POST_PIXEL"),
|
||||||
|
("draw", "POST_VIEW"),
|
||||||
@classmethod
|
)
|
||||||
def install(cls, context):
|
|
||||||
if cls.is_installed:
|
|
||||||
cls.uninstall()
|
|
||||||
handler = cls()
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
|
|
||||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw, (context,), "WINDOW", "POST_VIEW"))
|
|
||||||
cls.is_installed = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls):
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
|
||||||
if not tool.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)
|
|
||||||
|
|
||||||
def draw_text(self, context):
|
def draw_text(self, context):
|
||||||
if not tool.Blender.is_addon_enabled():
|
if not tool.Blender.is_addon_enabled():
|
||||||
|
|||||||
@@ -31,11 +31,17 @@ import bonsai.tool as tool
|
|||||||
from bonsai.bim.module.structural.load_decoration_data import ShaderInfo
|
from bonsai.bim.module.structural.load_decoration_data import ShaderInfo
|
||||||
|
|
||||||
|
|
||||||
class LoadsDecorator:
|
class LoadsDecorator(tool.Blender.ViewportDecorator):
|
||||||
"""Decorator to show structural loads in 3D"""
|
"""Decorator to show structural loads in 3D"""
|
||||||
|
|
||||||
is_installed = False
|
# draw_methods exists to satisfy ViewportDecorator.__init_subclass__'s
|
||||||
handlers = []
|
# method-existence check; the override install below is what actually
|
||||||
|
# registers handlers (the POST_VIEW binding passes no context arg, which
|
||||||
|
# the base's generic install cannot express).
|
||||||
|
draw_methods = (
|
||||||
|
("draw_load_values", "POST_PIXEL"),
|
||||||
|
("__call__", "POST_VIEW"),
|
||||||
|
)
|
||||||
decoration_data = None
|
decoration_data = None
|
||||||
text_info = []
|
text_info = []
|
||||||
shader_info = []
|
shader_info = []
|
||||||
@@ -54,15 +60,6 @@ class LoadsDecorator:
|
|||||||
cls.update()
|
cls.update()
|
||||||
cls.is_installed = True
|
cls.is_installed = True
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def uninstall(cls) -> None:
|
|
||||||
for handler in cls.handlers:
|
|
||||||
try:
|
|
||||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
cls.is_installed = False
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls) -> None:
|
def update(cls) -> None:
|
||||||
cls.decoration_data.update()
|
cls.decoration_data.update()
|
||||||
|
|||||||
@@ -31,12 +31,6 @@ ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
|
|||||||
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
||||||
|
|
||||||
|
|
||||||
def transparent_color(color, alpha=0.1):
|
|
||||||
color = [i for i in color]
|
|
||||||
color[3] = alpha
|
|
||||||
return color
|
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def toggle_decorations_on_load(*args):
|
def toggle_decorations_on_load(*args):
|
||||||
props = tool.System.get_system_props()
|
props = tool.System.get_system_props()
|
||||||
@@ -80,7 +74,7 @@ class SystemDecorator:
|
|||||||
|
|
||||||
def draw_faces(self, bm, vertices_coords):
|
def draw_faces(self, bm, vertices_coords):
|
||||||
"""Submit a non-mutating beauty-triangulated TRIS batch over ``bm``'s faces."""
|
"""Submit a non-mutating beauty-triangulated TRIS batch over ``bm``'s faces."""
|
||||||
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
|
faces_color = tool.Blender.transparent_color(self.addon_prefs.decorator_color_special)
|
||||||
tool.Blender.draw_bmesh_face_tris(bm, vertices_coords, faces_color, self.draw_batch)
|
tool.Blender.draw_bmesh_face_tris(bm, vertices_coords, faces_color, self.draw_batch)
|
||||||
|
|
||||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||||
@@ -128,13 +122,15 @@ class SystemDecorator:
|
|||||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||||
self.shader.bind()
|
self.shader.bind()
|
||||||
|
|
||||||
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
|
self.draw_batch(
|
||||||
|
"LINES", all_vertices, tool.Blender.transparent_color(unselected_elements_color), unselected_edges
|
||||||
|
)
|
||||||
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
||||||
self.draw_batch("LINES", all_vertices, UNSPECIAL_ELEMENT_COLOR, arc_edges)
|
self.draw_batch("LINES", all_vertices, UNSPECIAL_ELEMENT_COLOR, arc_edges)
|
||||||
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
|
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
|
||||||
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
|
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
|
||||||
|
|
||||||
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
|
self.draw_batch("POINTS", unselected_vertices, tool.Blender.transparent_color(unselected_elements_color, 0.5))
|
||||||
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
|
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
|
||||||
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
||||||
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ markers =
|
|||||||
boundary
|
boundary
|
||||||
brick
|
brick
|
||||||
bsdd
|
bsdd
|
||||||
|
clash
|
||||||
classification
|
classification
|
||||||
clip_box
|
clip_box
|
||||||
context
|
context
|
||||||
|
contract_guard
|
||||||
cost
|
cost
|
||||||
covering
|
covering
|
||||||
debug
|
debug
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Bonsai - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2026
|
||||||
|
#
|
||||||
|
# This file is part of Bonsai.
|
||||||
|
#
|
||||||
|
# Bonsai is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Bonsai is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Bonsai - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2026
|
||||||
|
#
|
||||||
|
# This file is part of Bonsai.
|
||||||
|
#
|
||||||
|
# Bonsai is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Bonsai is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# This file was generated with the assistance of an AI coding tool.
|
||||||
|
|
||||||
|
"""Runtime regression: viewport-decorator install / uninstall keeps the
|
||||||
|
``handlers`` list empty across repeated cycles.
|
||||||
|
|
||||||
|
ClashDecorator is the representative subclass — its lifecycle is now
|
||||||
|
inherited from ``tool.Blender.ViewportDecorator``. The contract pinned
|
||||||
|
here is the canonical one for every subclass: after each ``uninstall``,
|
||||||
|
``cls.handlers`` must be empty and ``cls.is_installed`` must be False."""
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from bonsai.bim.module.clash.decorator import ClashDecorator
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.clash
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _reset_decorator_state():
|
||||||
|
ClashDecorator.uninstall()
|
||||||
|
yield
|
||||||
|
ClashDecorator.uninstall()
|
||||||
|
|
||||||
|
|
||||||
|
def test_clash_decorator_handlers_cleared_across_install_cycles():
|
||||||
|
ctx = bpy.context
|
||||||
|
for _ in range(3):
|
||||||
|
ClashDecorator.install(ctx)
|
||||||
|
assert ClashDecorator.is_installed is True
|
||||||
|
assert len(ClashDecorator.handlers) > 0
|
||||||
|
ClashDecorator.uninstall()
|
||||||
|
assert ClashDecorator.is_installed is False
|
||||||
|
assert ClashDecorator.handlers == []
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Bonsai - OpenBIM Blender Add-on
|
||||||
|
# Copyright (C) 2026
|
||||||
|
#
|
||||||
|
# This file is part of Bonsai.
|
||||||
|
#
|
||||||
|
# Bonsai is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Bonsai is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# This file was generated with the assistance of an AI coding tool.
|
||||||
|
|
||||||
|
"""Forward-compat AST contract for viewport decorator lifecycle.
|
||||||
|
|
||||||
|
Any class that tracks Blender draw handlers via a class-level ``handlers``
|
||||||
|
list MUST subclass ``tool.Blender.ViewportDecorator``. The base sets
|
||||||
|
``handlers = []`` and ``is_installed = False`` via ``__init_subclass__`` and
|
||||||
|
provides install / uninstall with the correct ``cls.handlers.clear()``.
|
||||||
|
A class that declares its own ``handlers = []`` outside the base duplicates
|
||||||
|
the lifecycle and is at risk of regressing the handler-clear bug class."""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.contract_guard
|
||||||
|
|
||||||
|
ADDON_ROOT = Path(__file__).parent.parent.parent / "bonsai"
|
||||||
|
DECORATORS_GLOB = "bim/module/**/decorator.py"
|
||||||
|
|
||||||
|
|
||||||
|
def _is_empty_handlers_list(target: ast.expr, value: ast.expr | None) -> bool:
|
||||||
|
return isinstance(target, ast.Name) and target.id == "handlers" and isinstance(value, ast.List) and not value.elts
|
||||||
|
|
||||||
|
|
||||||
|
def _has_handlers_list_class_attr(class_node: ast.ClassDef) -> bool:
|
||||||
|
for node in class_node.body:
|
||||||
|
if isinstance(node, ast.Assign):
|
||||||
|
for target in node.targets:
|
||||||
|
if _is_empty_handlers_list(target, node.value):
|
||||||
|
return True
|
||||||
|
elif isinstance(node, ast.AnnAssign):
|
||||||
|
if _is_empty_handlers_list(node.target, node.value):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _subclasses_viewport_decorator(class_node: ast.ClassDef) -> bool:
|
||||||
|
for base in class_node.bases:
|
||||||
|
if isinstance(base, ast.Name) and base.id.endswith("ViewportDecorator"):
|
||||||
|
return True
|
||||||
|
if isinstance(base, ast.Attribute) and base.attr.endswith("ViewportDecorator"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_decorator_class_duplicates_viewport_lifecycle() -> None:
|
||||||
|
offenders: list[str] = []
|
||||||
|
for path in sorted(ADDON_ROOT.glob(DECORATORS_GLOB)):
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if not isinstance(node, ast.ClassDef):
|
||||||
|
continue
|
||||||
|
if not _has_handlers_list_class_attr(node):
|
||||||
|
continue
|
||||||
|
if _subclasses_viewport_decorator(node):
|
||||||
|
continue
|
||||||
|
rel = path.relative_to(ADDON_ROOT)
|
||||||
|
offenders.append(f"{rel.as_posix()}:{node.lineno}: class {node.name}")
|
||||||
|
if offenders:
|
||||||
|
listing = "\n ".join(offenders)
|
||||||
|
pytest.fail(
|
||||||
|
"Class(es) declare ``handlers = []`` at class scope without subclassing "
|
||||||
|
"``tool.Blender.ViewportDecorator``. Migrate to the canonical viewport-lifecycle "
|
||||||
|
"base (which sets handlers/is_installed via __init_subclass__ and provides "
|
||||||
|
"install/uninstall with the correct cls.handlers.clear()):\n " + listing
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user