See #4699. Continue refactoring logic from drawing decorator into DecoratorData.

This commit is contained in:
Dion Moult
2025-05-18 15:32:55 +10:00
parent 1de2515fcd
commit 992ad8e24a
6 changed files with 88 additions and 70 deletions
+74 -10
View File
@@ -236,23 +236,39 @@ class DecoratorData:
fill_cache = {} fill_cache = {}
@classmethod @classmethod
def load(cls): def load(cls, handler):
cls.is_loaded = True cls.is_loaded = True
cls.cut_cache = {} cls.cut_cache = {}
cls.layerset_cache = {} cls.layerset_cache = {}
text = {} text = {}
dimension = {} dimension = {}
fall = {}
symbol = {}
for obj in bpy.context.visible_objects: for obj in bpy.context.visible_objects:
if not (element := tool.Ifc.get_entity(obj)): if not (element := tool.Ifc.get_entity(obj)):
continue continue
if tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]): if tool.Drawing.is_annotation_object_type(element, ("TEXT", "TEXT_LEADER")):
text[obj.name] = cls.get_ifc_text_data(obj) 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( elif tool.Drawing.is_annotation_object_type(
element, ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS") element, ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS")
): ):
dimension[obj.name] = cls.get_dimension_data(obj) 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 @classmethod
def get_batting_thickness(cls, obj): def get_batting_thickness(cls, obj):
@@ -311,7 +327,7 @@ class DecoratorData:
return display_data return display_data
@classmethod @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 """used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n
returns font size in mm for current ifc text object""" returns font size in mm for current ifc text object"""
element = tool.Ifc.get_entity(obj) 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} 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 @classmethod
def get_dimension_data(cls, obj): def get_dimension_data(cls, obj):
"""used by Ifc Annotations with ObjectType: """used by Ifc Annotations with ObjectType:
@@ -394,6 +405,59 @@ class DecoratorData:
"custom_unit": custom_unit, "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: class AnnotationData:
data = {} data = {}
@@ -562,8 +562,7 @@ class BaseDecorator:
MiscDecorator.decorate(self, context, obj) MiscDecorator.decorate(self, context, obj)
return return
symbol = DecoratorData.get_symbol(obj) if not (symbol := DecoratorData.data["symbol"].get(obj.name, None)):
if not symbol:
return return
for vert in mesh.vertices: for vert in mesh.vertices:
@@ -572,8 +571,7 @@ class BaseDecorator:
return return
# EMPTY objects # EMPTY objects
symbol = DecoratorData.get_symbol(obj) if not (symbol := DecoratorData.data["symbol"].get(obj.name, None)):
if not symbol:
return return
rotation = -Vector((1, 0)).angle_signed(annotation_dir) rotation = -Vector((1, 0)).angle_signed(annotation_dir)
@@ -998,8 +996,6 @@ class FallDecorator(BaseDecorator):
# generate label text # generate label text
# same function as in svgwriter.py # same function as in svgwriter.py
def get_label_text(): def get_label_text():
element = tool.Ifc.get_entity(obj)
assert element
B, A = [v.co.xyz for v in spline_points[:2]] B, A = [v.co.xyz for v in spline_points[:2]]
rise = abs(A.z - B.z) rise = abs(A.z - B.z)
O = A.copy() O = A.copy()
@@ -1011,8 +1007,8 @@ class FallDecorator(BaseDecorator):
else: else:
angle = 90 angle = 90
# ues SLOPE_ANGLE as default # uses SLOPE_ANGLE as default
object_type = ifcopenshell.util.element.get_predefined_type(element) DecoratorData.data["fall"].get(obj, {}).get("object_type", None)
if object_type in ("FALL", "SLOPE_ANGLE"): if object_type in ("FALL", "SLOPE_ANGLE"):
return f"{angle}°" return f"{angle}°"
elif object_type == "SLOPE_FRACTION": elif object_type == "SLOPE_FRACTION":
@@ -1023,6 +1019,7 @@ class FallDecorator(BaseDecorator):
if angle == 90: if angle == 90:
return "-" return "-"
return f"{round(angle_tg * 100)} %" return f"{round(angle_tg * 100)} %"
return "NO DATA"
if spline_points: if spline_points:
text = get_label_text() text = get_label_text()
@@ -1921,17 +1918,18 @@ class DecorationsHandler:
] ]
installed = None installed = None
handler = None
@classmethod @classmethod
def install(cls, context): def install(cls, context):
if cls.installed: if cls.installed:
cls.uninstall() cls.uninstall()
if not DecoratorData.is_loaded:
DecoratorData.load()
handler = cls() handler = cls()
# NOTE: we USE POST_PIXEL here so that we can use both POLYLINE_UNIFORM_COLOR # 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 # 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") cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
if not DecoratorData.is_loaded:
DecoratorData.load(handler)
@classmethod @classmethod
def uninstall(cls): def uninstall(cls):
@@ -1954,53 +1952,9 @@ class DecorationsHandler:
for decorator in self.decorators.values(): for decorator in self.decorators.values():
decorator.font_id = font_id 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): 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: if not DrawingsData.is_loaded:
DrawingsData.load() DrawingsData.load()
object_decorators = self.get_objects_and_decorators(collection) for obj, decorator in DecoratorData.data["object_decorators"]:
for obj, decorator in object_decorators:
decorator.decorate(context, obj) decorator.decorate(context, obj)
+1 -1
View File
@@ -789,7 +789,7 @@ class BIMTextProperties(PropertyGroup):
def get_text_edited_data(self) -> dict[str, Any]: def get_text_edited_data(self) -> dict[str, Any]:
"""should be called only if `is_editing` """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 because this data could be out of date
""" """
literals_data = [] literals_data = []
+1 -1
View File
@@ -592,7 +592,7 @@ class BIM_PT_text(Panel):
col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}') col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}')
else: else:
text_data = DecoratorData.get_ifc_text_data(obj) text_data = DecoratorData.get_text_data(obj)
row = self.layout.row() row = self.layout.row()
row.operator("bim.enable_editing_text", icon="GREASEPENCIL") row.operator("bim.enable_editing_text", icon="GREASEPENCIL")
@@ -218,7 +218,7 @@ class AnnotationToolUI:
@classmethod @classmethod
def draw_edit_object_interface(cls, context): 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", "") add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
@classmethod @classmethod
@@ -311,7 +311,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if not bpy.context.active_object: if not bpy.context.active_object:
return 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() bpy.ops.bim.edit_text_popup()
def hotkey_S_G(self): def hotkey_S_G(self):
+1 -1
View File
@@ -1048,7 +1048,7 @@ class Drawing(bonsai.core.tool.Drawing):
from bonsai.bim.module.drawing.data import DecoratorData 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.font_size = str(text_data["FontSize"])
props.newline_at = text_data["Newline_At"] props.newline_at = text_data["Newline_At"]