mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +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.EditAssignedProduct,
|
||||||
operator.EditElementFilter,
|
operator.EditElementFilter,
|
||||||
operator.EditSheet,
|
operator.EditSheet,
|
||||||
|
operator.EditSheetItemPosition,
|
||||||
operator.EditText,
|
operator.EditText,
|
||||||
operator.EditTextPopup,
|
operator.EditTextPopup,
|
||||||
operator.EnableAddAnnotationType,
|
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))
|
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):
|
class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.create_sheets"
|
bl_idname = "bim.create_sheets"
|
||||||
bl_label = "Create Sheets"
|
bl_label = "Create Sheets"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import urllib.parse
|
|||||||
import uuid
|
import uuid
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Union
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
|
|
||||||
import ifcopenshell.util.geolocation
|
import ifcopenshell.util.geolocation
|
||||||
@@ -211,6 +212,71 @@ class SheetBuilder:
|
|||||||
|
|
||||||
layout_tree.write(layout_path)
|
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:
|
def remove_drawing(self, reference: ifcopenshell.entity_instance, sheet: ifcopenshell.entity_instance) -> None:
|
||||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
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.separator(factor=0.5, type="SPACE")
|
||||||
|
|
||||||
row3.operator("bim.edit_sheet", icon="GREASEPENCIL", text="")
|
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_drawing_to_sheet", icon="IMAGE_PLANE", text="")
|
||||||
row3.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
|
row3.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
|
||||||
row3.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
|
row3.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
|
||||||
|
|||||||
Reference in New Issue
Block a user