From 0608f92bc4afe80110472f544e7cc7edf6644941 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 19 Apr 2025 21:19:18 +1000 Subject: [PATCH] See #4699. Potentially fix crashes by only loading IFC data once and caching in decorator. --- src/bonsai/bonsai/bim/module/drawing/data.py | 52 ++++++++----------- .../bonsai/bim/module/drawing/decoration.py | 31 +++++++---- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py index b5103b7b8c..fb03c98576 100644 --- a/src/bonsai/bonsai/bim/module/drawing/data.py +++ b/src/bonsai/bonsai/bim/module/drawing/data.py @@ -34,9 +34,7 @@ def refresh(): DrawingsData.is_loaded = False ElementFiltersData.is_loaded = False AnnotationData.is_loaded = False - DecoratorData.data = {} - DecoratorData.cut_cache = {} - DecoratorData.layerset_cache = {} + DecoratorData.is_loaded = False class ProductAssignmentsData: @@ -239,6 +237,25 @@ class DecoratorData: slice_cache = {} fill_cache = {} + @classmethod + def load(cls): + cls.is_loaded = True + cls.cut_cache = {} + cls.layerset_cache = {} + + text = {} + dimension = {} + 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) + 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} + @classmethod def get_batting_thickness(cls, obj): """used by IfcAnnotations with ObjectType = "BATTING" """ @@ -296,17 +313,10 @@ class DecoratorData: return display_data @classmethod - def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict[str, Any]: + def get_ifc_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""" - result = cls.data.get(obj.name, None) - if result is not None: - return result - element = tool.Ifc.get_entity(obj) - if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]): - return None - props = tool.Drawing.get_text_props(obj) # getting font size pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {} @@ -344,9 +354,7 @@ class DecoratorData: literals_data.append(literal_data) - text_data = {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at} - cls.data[obj.name] = text_data - return text_data + 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]: @@ -359,19 +367,7 @@ class DecoratorData: DIMENSION / DIAMETER / SECTION_LEVEL / PLAN_LEVEL / RADIUS """ - result = cls.data.get(obj.name, None) - if result is not None: - return result - element = tool.Ifc.get_entity(obj) - supported_object_types = ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS") - if ( - not element - or not element.is_a("IfcAnnotation") - or ifcopenshell.util.element.get_predefined_type(element) not in supported_object_types - ): - return None - dimension_style = "arrow" fill_bg = False classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes") @@ -390,7 +386,7 @@ class DecoratorData: custom_unit_list = pset_data.get("CustomUnit", None) or "" custom_unit = custom_unit_list[0] if custom_unit_list else "" - dimension_data = { + return { "dimension_style": dimension_style, "show_description_only": show_description_only, "suppress_zero_inches": suppress_zero_inches, @@ -399,8 +395,6 @@ class DecoratorData: "fill_bg": fill_bg, "custom_unit": custom_unit, } - cls.data[obj.name] = dimension_data - return dimension_data class AnnotationData: diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index a169bb46af..2e546b55ac 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -599,7 +599,7 @@ class BaseDecorator: if not (pos := location_3d_to_region_2d(region, region3d, text_world_position)): return props = tool.Drawing.get_text_props(obj) - text_data = DecoratorData.get_ifc_text_data(obj) + text_data = DecoratorData.data["text"].get(obj.name, None) if props.is_editing: text_data = text_data | props.get_text_edited_data() literals_data = text_data["Literals"] @@ -656,7 +656,10 @@ class DimensionDecorator(BaseDecorator): viewportDrawingScale = self.get_viewport_drawing_scale(context) # setup geometry parameters - dimension_style = DecoratorData.get_dimension_data(obj)["dimension_style"] + dimension_data = DecoratorData.data["dimension"].get(obj.name, None) + if not dimension_data: + return + dimension_style = dimension_data["dimension_style"] if dimension_style == "oblique": size = viewportDrawingScale * 10 # OLBIQUE_SYMBOL_SIZE angle = radians(45) @@ -718,7 +721,9 @@ class DimensionDecorator(BaseDecorator): element = tool.Ifc.get_entity(obj) description = element.Description - dimension_data = DecoratorData.get_dimension_data(obj) + dimension_data = DecoratorData.data["dimension"].get(obj.name, None) + if not dimension_data: + return show_description_only = dimension_data["show_description_only"] text_prefix = dimension_data["text_prefix"] text_suffix = dimension_data["text_suffix"] @@ -952,7 +957,9 @@ class RadiusDecorator(BaseDecorator): return element = tool.Ifc.get_entity(obj) description = element.Description - dimension_data = DecoratorData.get_dimension_data(obj) + dimension_data = DecoratorData.data["dimension"].get(obj.name, None) + if not dimension_data: + return viewportDrawingScale = self.get_viewport_drawing_scale(context) text_offset = 20 * viewportDrawingScale @@ -1179,7 +1186,9 @@ class PlanLevelDecorator(BaseDecorator): element = tool.Ifc.get_entity(obj) description = element.Description - dimension_data = DecoratorData.get_dimension_data(obj) + dimension_data = DecoratorData.data["dimension"].get(obj.name, None) + if not dimension_data: + return for verts in splines: p0, p1 = [location_3d_to_region_2d(region, region3d, v) for v in verts[:2]] @@ -1262,7 +1271,9 @@ class SectionLevelDecorator(BaseDecorator): storey = tool.Drawing.get_annotation_element(element) tag = storey.Name if storey else element.Description - dimension_data = DecoratorData.get_dimension_data(obj) + dimension_data = DecoratorData.data["dimension"].get(obj.name, None) + if not dimension_data: + return for verts in splines: @@ -1721,11 +1732,11 @@ class CutDecorator: self.recalculate_fill(context, obj, element) def recalculate_cut(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None: - if not tool.Drawing.is_intersecting_camera(obj, context.scene.camera): - DecoratorData.cut_cache[element.id()] = (False, False) - else: + if tool.Drawing.is_intersecting_camera(obj, context.scene.camera): verts, edges = tool.Drawing.bisect_mesh(obj, context.scene.camera) DecoratorData.cut_cache[element.id()] = (verts, edges) + else: + DecoratorData.cut_cache[element.id()] = (False, False) def recalculate_fill(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None: element_id = element.id() @@ -1909,6 +1920,8 @@ class DecorationsHandler: 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