mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Bonsai: position drawings on sheets without leaving Blender
Sheet items could only be placed by the automatic row-packing layout in SheetBuilder.add_drawing/add_document. Any correction meant opening the layout SVG in Inkscape via bim.open_layout, which is the round trip that D&D Phase 1 Item 07 asks to remove. Adds bim.edit_sheet_item_position, which reads the current position of the selected drawing, schedule or reference from the sheet layout SVG, offers it as X/Y in millimetres, and writes the whole group back so the view title keeps its offset from the content. Scale is deliberately not exposed. build_drawings/build_documents inline the referenced SVG under a plain translate(), so the image width/height only drives the clip rect and cannot scale the view. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -63,6 +63,7 @@ classes = (
|
||||
operator.EditAssignedProduct,
|
||||
operator.EditElementFilter,
|
||||
operator.EditSheet,
|
||||
operator.EditSheetItemPosition,
|
||||
operator.EditText,
|
||||
operator.EditTextPopup,
|
||||
operator.EnableAddAnnotationType,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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="")
|
||||
|
||||
Reference in New Issue
Block a user