diff --git a/src/bonsai/bonsai/bim/module/drawing/__init__.py b/src/bonsai/bonsai/bim/module/drawing/__init__.py index 12b63d9372..fa5f44d31c 100644 --- a/src/bonsai/bonsai/bim/module/drawing/__init__.py +++ b/src/bonsai/bonsai/bim/module/drawing/__init__.py @@ -63,6 +63,7 @@ classes = ( operator.EditAssignedProduct, operator.EditElementFilter, operator.EditSheet, + operator.EditSheetItemPosition, operator.EditText, operator.EditTextPopup, operator.EnableAddAnnotationType, diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 35d93db7d2..e0bd05d536 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -2165,6 +2165,54 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator): tool.Drawing.remove_drawing_from_sheet(ifc_file.by_id(self.reference)) +class EditSheetItemPosition(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.edit_sheet_item_position" + bl_label = "Edit Position On Sheet" + bl_description = "Set the position of the selected drawing, schedule or reference on its sheet layout" + bl_options = {"REGISTER", "UNDO"} + + x: bpy.props.FloatProperty(name="X", description="Distance in mm from the left edge of the sheet", unit="NONE") + y: bpy.props.FloatProperty(name="Y", description="Distance in mm from the top edge of the sheet", unit="NONE") + + @classmethod + def poll(cls, context): + active_item = tool.Drawing.get_active_sheet_item() + if active_item is None or active_item.is_sheet: + cls.poll_message_set("No sheet item selected.") + return False + if active_item.reference_type not in ("DRAWING", "SCHEDULE", "REFERENCE"): + cls.poll_message_set("Only drawings, schedules and references can be positioned.") + return False + return True + + def invoke(self, context, event): + assert context.window_manager + position = self.get_position() + if position is None: + self.report({"ERROR"}, "Item was not found in the sheet layout.") + return {"CANCELLED"} + self.x, self.y = position + return context.window_manager.invoke_props_dialog(self) + + def get_position(self): + sheet_item = tool.Drawing.get_active_sheet_item() + assert sheet_item + reference = tool.Ifc.get().by_id(sheet_item.ifc_definition_id) + sheet = tool.Drawing.get_reference_document(reference) + if sheet is None: + return None + return sheeter.SheetBuilder().get_sheet_item_position(reference, sheet) + + def _execute(self, context): + sheet_item = tool.Drawing.get_active_sheet_item() + assert sheet_item + reference = tool.Ifc.get().by_id(sheet_item.ifc_definition_id) + sheet = tool.Drawing.get_reference_document(reference) + if sheet is None or not sheeter.SheetBuilder().set_sheet_item_position(reference, sheet, self.x, self.y): + self.report({"ERROR"}, "Item was not found in the sheet layout.") + return {"CANCELLED"} + + class CreateSheets(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.create_sheets" bl_label = "Create Sheets" diff --git a/src/bonsai/bonsai/bim/module/drawing/sheeter.py b/src/bonsai/bonsai/bim/module/drawing/sheeter.py index df57b6efb5..14a638c866 100644 --- a/src/bonsai/bonsai/bim/module/drawing/sheeter.py +++ b/src/bonsai/bonsai/bim/module/drawing/sheeter.py @@ -24,6 +24,7 @@ import urllib.parse import uuid import xml.etree.ElementTree as ET from pathlib import Path +from typing import Union from xml.dom import minidom import ifcopenshell.util.geolocation @@ -211,6 +212,71 @@ class SheetBuilder: layout_tree.write(layout_path) + def find_sheet_item( + self, layout_root: ET.Element, reference: ifcopenshell.entity_instance + ) -> Union[ET.Element, None]: + for g in layout_root.findall(f"{SVG}g"): + if g.attrib.get("data-id") == str(reference.id()): + return g + return None + + def get_sheet_item_position( + self, reference: ifcopenshell.entity_instance, sheet: ifcopenshell.entity_instance + ) -> Union[Vector, None]: + """Position in mm of a drawing/schedule/reference placed on a sheet layout.""" + layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT") + assert layout_path + if not os.path.exists(layout_path): + return None + + layout_root = ET.parse(layout_path).getroot() + view = self.find_sheet_item(layout_root, reference) + if view is None: + return None + + content = self.get_sheet_item_content(view) + if content is None: + return None + return Vector((self.convert_to_mm(content.attrib["x"]), self.convert_to_mm(content.attrib["y"]))) + + def set_sheet_item_position( + self, reference: ifcopenshell.entity_instance, sheet: ifcopenshell.entity_instance, x: float, y: float + ) -> bool: + """Move a sheet item and its view title to an absolute position in mm.""" + ET.register_namespace("", "http://www.w3.org/2000/svg") + ET.register_namespace("xlink", "http://www.w3.org/1999/xlink") + + layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT") + assert layout_path + if not os.path.exists(layout_path): + return False + + layout_tree = ET.parse(layout_path) + view = self.find_sheet_item(layout_tree.getroot(), reference) + if view is None: + return False + + content = self.get_sheet_item_content(view) + if content is None: + return False + + offset = Vector((x, y)) - Vector( + (self.convert_to_mm(content.attrib["x"]), self.convert_to_mm(content.attrib["y"])) + ) + + for image in view.findall(f"{SVG}image"): + image.attrib["x"] = str(round(self.convert_to_mm(image.attrib["x"]) + offset.x, 4)) + image.attrib["y"] = str(round(self.convert_to_mm(image.attrib["y"]) + offset.y, 4)) + + layout_tree.write(layout_path) + return True + + def get_sheet_item_content(self, view: ET.Element) -> Union[ET.Element, None]: + for image in view.findall(f"{SVG}image"): + if image.attrib.get("data-type") in ("foreground", "content"): + return image + return None + def remove_drawing(self, reference: ifcopenshell.entity_instance, sheet: ifcopenshell.entity_instance) -> None: ET.register_namespace("", "http://www.w3.org/2000/svg") diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 5c690e7282..e1bbfaa856 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -516,6 +516,7 @@ class BIM_PT_sheets(Panel): row3.separator(factor=0.5, type="SPACE") row3.operator("bim.edit_sheet", icon="GREASEPENCIL", text="") + row3.operator("bim.edit_sheet_item_position", icon="ORIENTATION_VIEW", text="") row3.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="") row3.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="") row3.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")