See #4699. Potentially fix crashes by only loading IFC data once and caching in decorator.

This commit is contained in:
Dion Moult
2025-04-19 21:19:18 +10:00
parent df18108406
commit 0608f92bc4
2 changed files with 45 additions and 38 deletions
+23 -29
View File
@@ -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:
@@ -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