From 992ad8e24a6fc2e1b08036a21510a233212502a9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 18 May 2025 15:32:55 +1000 Subject: [PATCH] See #4699. Continue refactoring logic from drawing decorator into DecoratorData. --- src/bonsai/bonsai/bim/module/drawing/data.py | 84 ++++++++++++++++--- .../bonsai/bim/module/drawing/decoration.py | 64 ++------------ src/bonsai/bonsai/bim/module/drawing/prop.py | 2 +- src/bonsai/bonsai/bim/module/drawing/ui.py | 2 +- .../bonsai/bim/module/drawing/workspace.py | 4 +- src/bonsai/bonsai/tool/drawing.py | 2 +- 6 files changed, 88 insertions(+), 70 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py index 0b2ae6e3df..cfe1492247 100644 --- a/src/bonsai/bonsai/bim/module/drawing/data.py +++ b/src/bonsai/bonsai/bim/module/drawing/data.py @@ -236,23 +236,39 @@ class DecoratorData: fill_cache = {} @classmethod - def load(cls): + def load(cls, handler): cls.is_loaded = True cls.cut_cache = {} cls.layerset_cache = {} text = {} dimension = {} + fall = {} + symbol = {} for obj in bpy.context.visible_objects: if not (element := tool.Ifc.get_entity(obj)): continue - if tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]): - text[obj.name] = cls.get_ifc_text_data(obj) + if tool.Drawing.is_annotation_object_type(element, ("TEXT", "TEXT_LEADER")): + text[obj.name] = cls.get_text_data(obj) + if text[obj.name]["Symbol"]: + symbol[obj.name] = cls.get_symbol_data(obj) elif tool.Drawing.is_annotation_object_type( element, ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS") ): dimension[obj.name] = cls.get_dimension_data(obj) - cls.data = {"text": text, "dimension": dimension} + elif tool.Drawing.is_annotation_object_type( + element, ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT") + ): + fall[obj.name] = cls.get_fall_data(obj) + elif tool.Drawing.is_annotation_object_type(element, ("SYMBOL",)): + symbol[obj.name] = cls.get_symbol_data(obj) + cls.data = { + "text": text, + "dimension": dimension, + "fall": fall, + "symbol": symbol, + "object_decorators": cls.object_decorators(handler), + } @classmethod def get_batting_thickness(cls, obj): @@ -311,7 +327,7 @@ class DecoratorData: return display_data @classmethod - def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict: + def get_text_data(cls, obj: bpy.types.Object) -> dict: """used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n returns font size in mm for current ifc text object""" element = tool.Ifc.get_entity(obj) @@ -354,11 +370,6 @@ class DecoratorData: return {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at} - @classmethod - def get_symbol(cls, obj: bpy.types.Object) -> Union[str, None]: - """used by IfcAnnotations with ObjectType MULTI_SYMBOL""" - return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj)) - @classmethod def get_dimension_data(cls, obj): """used by Ifc Annotations with ObjectType: @@ -394,6 +405,59 @@ class DecoratorData: "custom_unit": custom_unit, } + @classmethod + def get_fall_data(cls, obj): + object_type = None + if element := tool.Ifc.get_entity(obj): + object_type = ifcopenshell.util.element.get_predefined_type(element) + return {"object_type": object_type} + + @classmethod + def get_symbol_data(cls, obj): + return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj)) + + @classmethod + def object_decorators(cls, handler): + import bonsai.bim.module.drawing.decoration + + if not bonsai.bim.module.drawing.decoration.DecorationsHandler.installed: + return [] + + props = tool.Drawing.get_document_props() + if (drawing := props.get_active_drawing()) is None: + return [] + + camera = tool.Ifc.get_object(drawing) + assert isinstance(camera, bpy.types.Object) + collection = tool.Blender.get_object_bim_props(camera).collection + assert collection + + results = [] + viewport = tool.Blender.get_view3d_space() + + for obj in collection.all_objects: + if not obj.visible_get(viewport=viewport): + continue + element = tool.Ifc.get_entity(obj) + if not element: + continue + if not element.is_a("IfcAnnotation"): + continue + object_type: Union[str, None] = ifcopenshell.util.element.get_predefined_type(element) + if object_type == "DRAWING": + continue + if dec := handler.decorators.get(object_type, None): + results.append((obj, dec)) + elif isinstance(obj.data, bpy.types.Mesh): + if object_type == "LINEWORK" and "dashed" in str( + ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes") + ).split(" "): + results.append((obj, handler.decorators["HIDDEN_LINE"])) + else: + results.append((obj, handler.decorators["MISC"])) + + return results + class AnnotationData: data = {} diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index 2f00ad5d1d..7e4e0805cb 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -562,8 +562,7 @@ class BaseDecorator: MiscDecorator.decorate(self, context, obj) return - symbol = DecoratorData.get_symbol(obj) - if not symbol: + if not (symbol := DecoratorData.data["symbol"].get(obj.name, None)): return for vert in mesh.vertices: @@ -572,8 +571,7 @@ class BaseDecorator: return # EMPTY objects - symbol = DecoratorData.get_symbol(obj) - if not symbol: + if not (symbol := DecoratorData.data["symbol"].get(obj.name, None)): return rotation = -Vector((1, 0)).angle_signed(annotation_dir) @@ -998,8 +996,6 @@ class FallDecorator(BaseDecorator): # generate label text # same function as in svgwriter.py def get_label_text(): - element = tool.Ifc.get_entity(obj) - assert element B, A = [v.co.xyz for v in spline_points[:2]] rise = abs(A.z - B.z) O = A.copy() @@ -1011,8 +1007,8 @@ class FallDecorator(BaseDecorator): else: angle = 90 - # ues SLOPE_ANGLE as default - object_type = ifcopenshell.util.element.get_predefined_type(element) + # uses SLOPE_ANGLE as default + DecoratorData.data["fall"].get(obj, {}).get("object_type", None) if object_type in ("FALL", "SLOPE_ANGLE"): return f"{angle}°" elif object_type == "SLOPE_FRACTION": @@ -1023,6 +1019,7 @@ class FallDecorator(BaseDecorator): if angle == 90: return "-" return f"{round(angle_tg * 100)} %" + return "NO DATA" if spline_points: text = get_label_text() @@ -1921,17 +1918,18 @@ class DecorationsHandler: ] installed = None + handler = None @classmethod def install(cls, context): if cls.installed: cls.uninstall() - if not DecoratorData.is_loaded: - DecoratorData.load() handler = cls() # NOTE: we USE POST_PIXEL here so that we can use both POLYLINE_UNIFORM_COLOR # and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL") + if not DecoratorData.is_loaded: + DecoratorData.load(handler) @classmethod def uninstall(cls): @@ -1954,53 +1952,9 @@ class DecorationsHandler: for decorator in self.decorators.values(): decorator.font_id = font_id - def get_objects_and_decorators(self, collection): - # TODO: do it in data instead of the handler for performance? - results = [] - viewport = bpy.context.space_data - - for obj in collection.all_objects: - if not obj.visible_get(viewport=viewport): - continue - - element = tool.Ifc.get_entity(obj) - if not element: - continue - - if not element.is_a("IfcAnnotation"): - continue - - object_type: Union[str, None] = ifcopenshell.util.element.get_predefined_type(element) - if object_type == "DRAWING": - continue - - if dec := self.decorators.get(object_type, None): - results.append((obj, dec)) - - elif isinstance(obj.data, bpy.types.Mesh): - if object_type == "LINEWORK" and "dashed" in str( - ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes") - ).split(" "): - results.append((obj, self.decorators["HIDDEN_LINE"])) - else: - results.append((obj, self.decorators["MISC"])) - - return results - def __call__(self, context): - props = tool.Drawing.get_document_props() - drawing = props.get_active_drawing() - if drawing is None: - return - - camera = tool.Ifc.get_object(drawing) - assert isinstance(camera, bpy.types.Object) - collection = tool.Blender.get_object_bim_props(camera).collection - assert collection - if not DrawingsData.is_loaded: DrawingsData.load() - object_decorators = self.get_objects_and_decorators(collection) - for obj, decorator in object_decorators: + for obj, decorator in DecoratorData.data["object_decorators"]: decorator.decorate(context, obj) diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index c7cba52c10..49a4001cd9 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -789,7 +789,7 @@ class BIMTextProperties(PropertyGroup): def get_text_edited_data(self) -> dict[str, Any]: """should be called only if `is_editing` - otherwise should use `DecoratorData.get_ifc_text_data(obj)` instead + otherwise should use `DecoratorData.get_text_data(obj)` instead because this data could be out of date """ literals_data = [] diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index cb6162ee17..f3eb58da32 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -592,7 +592,7 @@ class BIM_PT_text(Panel): col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}') else: - text_data = DecoratorData.get_ifc_text_data(obj) + text_data = DecoratorData.get_text_data(obj) row = self.layout.row() row.operator("bim.enable_editing_text", icon="GREASEPENCIL") diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py index cbe9de1dc3..b65d68e53b 100644 --- a/src/bonsai/bonsai/bim/module/drawing/workspace.py +++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py @@ -218,7 +218,7 @@ class AnnotationToolUI: @classmethod def draw_edit_object_interface(cls, context): - if DecoratorData.get_ifc_text_data(bpy.context.active_object): + if DecoratorData.get_text_data(bpy.context.active_object): add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "") @classmethod @@ -311,7 +311,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): if not bpy.context.active_object: return - if DecoratorData.get_ifc_text_data(bpy.context.active_object): + if DecoratorData.get_text_data(bpy.context.active_object): bpy.ops.bim.edit_text_popup() def hotkey_S_G(self): diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index e6721ae53d..ff17b307d0 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -1048,7 +1048,7 @@ class Drawing(bonsai.core.tool.Drawing): from bonsai.bim.module.drawing.data import DecoratorData - text_data = DecoratorData.get_ifc_text_data(obj) + text_data = DecoratorData.get_text_data(obj) props.font_size = str(text_data["FontSize"]) props.newline_at = text_data["Newline_At"]