mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-20 12:12:15 +00:00
Add external SVG reference support to manual drawing reference tags
Extends manual drawing reference annotations (elevation and section) to also support external SVG references imported via bim.add_reference. - Add "Is a Reference" checkbox to the annotation tool sidebar, shown when Elevation or Section is the active type; checking it and pressing Add opens a dialog to optionally link the tag to a Bonsai drawing or an external SVG reference - The MANUAL_DRAWING_REFERENCE dropdown type is retained for backwards compatibility; selecting it shows a style picker (Elevation/Section) and the same linking dialog - External-reference annotations are flagged with IsDocumentReference in EPset_Annotation and linked to their IfcDocumentInformation via IfcRelAssociatesDocument; drawing-reference annotations continue to use IfcRelAssignsToProduct as before - SVG export resolves the correct reference/sheet IDs for both link types via get_reference_and_sheet_id_from_annotation - Add IsDocumentReference to the EPset_Annotation pset template
This commit is contained in:
@@ -5,7 +5,7 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Anno
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2,#34));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2,#34,#35));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -39,5 +39,6 @@ DATA;
|
||||
#32=IFCSIMPLEPROPERTYTEMPLATE('0gjJzDYBX8P85qn1xcAOOo',$,'Reverse_List','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#33=IFCSIMPLEPROPERTYTEMPLATE('22TrcxF8jFNB4buSmzjGEF',$,'List_Separator','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#34=IFCSIMPLEPROPERTYTEMPLATE('0FauxIsAnnotFaux0001aB',$,'IsManualDrawingReference','Marks this annotation as a manually placed drawing reference, exempt from automatic drawing regeneration.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#35=IFCSIMPLEPROPERTYTEMPLATE('0FauxIsDocRefFaux001aB',$,'IsDocumentReference','Marks this annotation as pointing to an external document reference (not a Bonsai drawing camera).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -55,6 +55,14 @@ class ProductAssignmentsData:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation"):
|
||||
return
|
||||
# Document-reference annotations link to an IfcDocumentInformation, not a product.
|
||||
if tool.Drawing.is_document_reference(element):
|
||||
for rel in element.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
doc = rel.RelatingDocument
|
||||
if doc.is_a("IfcDocumentInformation"):
|
||||
return doc.Name or "Unnamed"
|
||||
return None
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
name = rel.RelatingProduct.Name or "Unnamed"
|
||||
|
||||
@@ -1737,20 +1737,32 @@ def _get_drawing_enum_items(self, context):
|
||||
return items
|
||||
|
||||
|
||||
def _get_reference_doc_enum_items(self, context):
|
||||
items = [("0", "None", "Clear the reference assignment")]
|
||||
if ifc := tool.Ifc.get():
|
||||
for d in ifc.by_type("IfcDocumentInformation"):
|
||||
if d.Scope == "REFERENCE":
|
||||
items.append((str(d.id()), d.Name or f"Reference {d.id()}", ""))
|
||||
return items
|
||||
|
||||
|
||||
class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_annotation"
|
||||
bl_label = "Add Annotation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
description: bpy.props.StringProperty()
|
||||
annotation_subtype: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
drawing_id: bpy.props.EnumProperty(name="Drawing", items=_get_drawing_enum_items)
|
||||
reference_doc_id: bpy.props.EnumProperty(name="Reference", items=_get_reference_doc_enum_items)
|
||||
is_document_reference: bpy.props.BoolProperty(name="External Reference", default=False)
|
||||
# Used only when placing via the legacy MANUAL_DRAWING_REFERENCE dropdown type.
|
||||
manual_style: bpy.props.EnumProperty(
|
||||
name="Style",
|
||||
items=[
|
||||
("ELEVATION", "Elevation", "Place a manual elevation tag"),
|
||||
("SECTION", "Section", "Place a manual section tag"),
|
||||
("ELEVATION", "Elevation", "Use the elevation tag visual (circle with arrow)"),
|
||||
("SECTION", "Section", "Use the section tag visual (line with end circles)"),
|
||||
],
|
||||
default="ELEVATION",
|
||||
)
|
||||
drawing_id: bpy.props.EnumProperty(name="Drawing", items=_get_drawing_enum_items)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -1761,14 +1773,27 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return operator.description or ""
|
||||
|
||||
def invoke(self, context, event):
|
||||
if tool.Drawing.get_annotation_props().object_type == "MANUAL_DRAWING_REFERENCE":
|
||||
props = tool.Drawing.get_annotation_props()
|
||||
needs_dialog = (
|
||||
(props.is_manual_reference and props.object_type in ("ELEVATION", "SECTION"))
|
||||
or props.object_type == "MANUAL_DRAWING_REFERENCE"
|
||||
)
|
||||
if needs_dialog:
|
||||
self.drawing_id = "0"
|
||||
self.reference_doc_id = "0"
|
||||
self.is_document_reference = False
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.prop(self, "annotation_subtype", expand=True)
|
||||
prop_with_search(self.layout, self, "drawing_id", should_click_ok=True, search_threshold=0, original_operator_path=f"{__name__}.AddAnnotation")
|
||||
props = tool.Drawing.get_annotation_props()
|
||||
if props.object_type == "MANUAL_DRAWING_REFERENCE":
|
||||
self.layout.prop(self, "manual_style", expand=True)
|
||||
self.layout.prop(self, "is_document_reference")
|
||||
if self.is_document_reference:
|
||||
prop_with_search(self.layout, self, "reference_doc_id", should_click_ok=True, search_threshold=0, original_operator_path=f"{__name__}.AddAnnotation")
|
||||
else:
|
||||
prop_with_search(self.layout, self, "drawing_id", should_click_ok=True, search_threshold=0, original_operator_path=f"{__name__}.AddAnnotation")
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Drawing.get_annotation_props()
|
||||
@@ -1777,7 +1802,15 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"WARNING"}, "No active drawing.")
|
||||
return
|
||||
|
||||
object_type = self.annotation_subtype if props.object_type == "MANUAL_DRAWING_REFERENCE" else props.object_type
|
||||
# MANUAL_DRAWING_REFERENCE is a legacy/convenience dropdown entry; resolve
|
||||
# it to the chosen style so create_annotation_object knows what to build.
|
||||
if props.object_type == "MANUAL_DRAWING_REFERENCE":
|
||||
object_type = self.manual_style
|
||||
is_manual = True
|
||||
else:
|
||||
object_type = props.object_type
|
||||
is_manual = props.is_manual_reference and object_type in ("ELEVATION", "SECTION")
|
||||
|
||||
obj = core.add_annotation(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
@@ -1790,12 +1823,18 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# item edit mode for these types.
|
||||
enable_editing=object_type not in ("ELEVATION", "SECTION"),
|
||||
)
|
||||
if props.object_type == "IMAGE":
|
||||
if object_type == "IMAGE":
|
||||
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", existing_object_by_name=obj.name)
|
||||
if object_type in ("ELEVATION", "SECTION"):
|
||||
if is_manual:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
tool.Drawing.set_manual_drawing_reference(element)
|
||||
if self.drawing_id != "0":
|
||||
if self.is_document_reference:
|
||||
tool.Drawing.set_document_reference_flag(element)
|
||||
if self.reference_doc_id != "0":
|
||||
core.assign_manual_reference_document(
|
||||
tool.Drawing, element=element, document=tool.Ifc.get().by_id(int(self.reference_doc_id))
|
||||
)
|
||||
elif self.drawing_id != "0":
|
||||
core.assign_manual_drawing_reference(
|
||||
tool.Ifc, tool.Drawing, element=element, drawing=tool.Ifc.get().by_id(int(self.drawing_id))
|
||||
)
|
||||
@@ -1804,9 +1843,10 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AssignManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_manual_drawing_reference"
|
||||
bl_label = "Assign Drawing"
|
||||
bl_description = "Assign a target drawing to this manual drawing reference tag"
|
||||
bl_description = "Assign a target drawing or reference to this manual drawing reference tag"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
drawing_id: bpy.props.EnumProperty(name="Drawing", items=_get_drawing_enum_items)
|
||||
reference_doc_id: bpy.props.EnumProperty(name="Reference", items=_get_reference_doc_enum_items)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -1825,17 +1865,29 @@ class AssignManualDrawingReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
current = tool.Drawing.get_annotation_element(element)
|
||||
self.drawing_id = str(current.id()) if current else "0"
|
||||
if tool.Drawing.is_document_reference(element):
|
||||
doc = tool.Drawing.get_annotation_reference_doc(element)
|
||||
self.reference_doc_id = str(doc.id()) if doc else "0"
|
||||
else:
|
||||
current = tool.Drawing.get_annotation_element(element)
|
||||
self.drawing_id = str(current.id()) if current else "0"
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
prop_with_search(self.layout, self, "drawing_id", search_threshold=0, original_operator_path=f"{__name__}.AssignManualDrawingReference")
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element and tool.Drawing.is_document_reference(element):
|
||||
prop_with_search(self.layout, self, "reference_doc_id", search_threshold=0, original_operator_path=f"{__name__}.AssignManualDrawingReference")
|
||||
else:
|
||||
prop_with_search(self.layout, self, "drawing_id", search_threshold=0, original_operator_path=f"{__name__}.AssignManualDrawingReference")
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
drawing = tool.Ifc.get().by_id(int(self.drawing_id)) if self.drawing_id != "0" else None
|
||||
core.assign_manual_drawing_reference(tool.Ifc, tool.Drawing, element=element, drawing=drawing)
|
||||
if tool.Drawing.is_document_reference(element):
|
||||
document = tool.Ifc.get().by_id(int(self.reference_doc_id)) if self.reference_doc_id != "0" else None
|
||||
core.assign_manual_reference_document(tool.Drawing, element=element, document=document)
|
||||
else:
|
||||
drawing = tool.Ifc.get().by_id(int(self.drawing_id)) if self.drawing_id != "0" else None
|
||||
core.assign_manual_drawing_reference(tool.Ifc, tool.Drawing, element=element, drawing=drawing)
|
||||
for area in context.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
area.tag_redraw()
|
||||
|
||||
@@ -1000,6 +1000,12 @@ class BIMAnnotationProperties(PropertyGroup):
|
||||
)
|
||||
is_adding_type: bpy.props.BoolProperty(default=False)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
is_manual_reference: bpy.props.BoolProperty(
|
||||
name="Is a Reference",
|
||||
default=False,
|
||||
description="Place as a manual reference tag (IsManualDrawingReference). "
|
||||
"Exempt from automatic drawing regeneration. Optionally link to a drawing or external reference.",
|
||||
)
|
||||
tag_rotation_mode: bpy.props.EnumProperty(
|
||||
name="Tag Rotation Mode",
|
||||
description="How to orient the tag relative to the tagged object",
|
||||
@@ -1020,3 +1026,4 @@ class BIMAnnotationProperties(PropertyGroup):
|
||||
create_representation_for_type: bool
|
||||
is_adding_type: bool
|
||||
type_name: str
|
||||
is_manual_reference: bool
|
||||
|
||||
@@ -896,8 +896,32 @@ class SvgWriter:
|
||||
)
|
||||
|
||||
def get_reference_and_sheet_id_from_annotation(self, element: ifcopenshell.entity_instance) -> tuple[str, str]:
|
||||
reference_id = "-"
|
||||
sheet_id = "-"
|
||||
is_ifc2x3 = tool.Ifc.get_schema() == "IFC2X3"
|
||||
|
||||
# Document-reference annotations link to an IfcDocumentInformation via
|
||||
# IfcRelAssociatesDocument rather than to a drawing product.
|
||||
if tool.Drawing.is_document_reference(element):
|
||||
doc_info = tool.Drawing.get_annotation_reference_doc(element)
|
||||
if not doc_info:
|
||||
return ("-", "-")
|
||||
ext_location = tool.Drawing.get_path_with_ext(
|
||||
(doc_info.DocumentReferences[0].Location if is_ifc2x3 else doc_info.HasDocumentReferences[0].Location),
|
||||
"svg",
|
||||
) if (doc_info.DocumentReferences if is_ifc2x3 else doc_info.HasDocumentReferences) else None
|
||||
if not ext_location:
|
||||
return ("-", "-")
|
||||
for sheet_reference in tool.Ifc.get().by_type("IfcDocumentReference"):
|
||||
if tool.Drawing.get_reference_description(sheet_reference) != "REFERENCE":
|
||||
continue
|
||||
if sheet_reference.Location != ext_location:
|
||||
continue
|
||||
sheet = tool.Drawing.get_reference_document(sheet_reference)
|
||||
if sheet:
|
||||
if is_ifc2x3:
|
||||
return (sheet_reference.ItemReference or "-", sheet.DocumentId or "-")
|
||||
return (sheet_reference.Identification or "-", sheet.Identification or "-")
|
||||
return ("-", "-")
|
||||
|
||||
drawing = tool.Drawing.get_annotation_element(element)
|
||||
if not drawing:
|
||||
return ("-", "-")
|
||||
@@ -909,13 +933,9 @@ class SvgWriter:
|
||||
continue
|
||||
sheet = tool.Drawing.get_reference_document(sheet_reference)
|
||||
if sheet:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_id = sheet_reference.ItemReference or "-"
|
||||
sheet_id = sheet.DocumentId or "-"
|
||||
else:
|
||||
reference_id = sheet_reference.Identification or "-"
|
||||
sheet_id = sheet.Identification or "-"
|
||||
return (reference_id, sheet_id)
|
||||
if is_ifc2x3:
|
||||
return (sheet_reference.ItemReference or "-", sheet.DocumentId or "-")
|
||||
return (sheet_reference.Identification or "-", sheet.Identification or "-")
|
||||
break
|
||||
return ("-", "-")
|
||||
|
||||
|
||||
@@ -539,8 +539,9 @@ class BIM_PT_product_assignments(Panel):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and tool.Drawing.is_manual_drawing_reference(element):
|
||||
row = self.layout.row(align=True)
|
||||
fallback = "No Reference Assigned" if element.ObjectType == "REFERENCE" else "No Drawing Assigned"
|
||||
row.label(
|
||||
text=ProductAssignmentsData.data["relating_product"] or "No Drawing Assigned", icon="IMAGE_DATA"
|
||||
text=ProductAssignmentsData.data["relating_product"] or fallback, icon="IMAGE_DATA"
|
||||
)
|
||||
row.operator("bim.assign_manual_drawing_reference", icon="GREASEPENCIL", text="")
|
||||
return
|
||||
|
||||
@@ -249,6 +249,10 @@ class AnnotationToolUI:
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
|
||||
|
||||
if object_type in ("ELEVATION", "SECTION"):
|
||||
row = cls.layout.row(align=True)
|
||||
row.prop(cls.props, "is_manual_reference")
|
||||
|
||||
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
|
||||
@@ -518,6 +518,14 @@ def assign_manual_drawing_reference(
|
||||
ifc.run("drawing.assign_product", relating_product=drawing, related_object=element)
|
||||
|
||||
|
||||
def assign_manual_reference_document(
|
||||
drawing_tool: type[tool.Drawing],
|
||||
element: ifcopenshell.entity_instance,
|
||||
document: Optional[ifcopenshell.entity_instance],
|
||||
) -> None:
|
||||
drawing_tool.set_annotation_reference_doc(element, document)
|
||||
|
||||
|
||||
def build_schedule(drawing: type[tool.Drawing], schedule: ifcopenshell.entity_instance) -> None:
|
||||
drawing.create_svg_schedule(schedule)
|
||||
drawing.open_svg(drawing.get_path_with_ext(drawing.get_document_uri(schedule), "svg"))
|
||||
|
||||
@@ -1889,6 +1889,49 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
|
||||
ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={"IsManualDrawingReference": True})
|
||||
|
||||
@classmethod
|
||||
def is_document_reference(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""Return True if this annotation links to an external document (not a Bonsai drawing camera)."""
|
||||
return bool(ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "IsDocumentReference"))
|
||||
|
||||
@classmethod
|
||||
def set_document_reference_flag(cls, element: ifcopenshell.entity_instance) -> None:
|
||||
"""Mark this annotation as pointing to an external document reference."""
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = tool.Pset.get_element_pset(element, "EPset_Annotation")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="EPset_Annotation")
|
||||
ifcopenshell.api.pset.edit_pset(ifc_file, pset=pset, properties={"IsDocumentReference": True})
|
||||
|
||||
@classmethod
|
||||
def get_annotation_reference_doc(
|
||||
cls, element: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Return the IfcDocumentInformation linked to a document-reference annotation."""
|
||||
for rel in element.HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
doc = rel.RelatingDocument
|
||||
if doc.is_a("IfcDocumentInformation"):
|
||||
return doc
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def set_annotation_reference_doc(
|
||||
cls,
|
||||
element: ifcopenshell.entity_instance,
|
||||
document: Union[ifcopenshell.entity_instance, None],
|
||||
) -> None:
|
||||
"""Associate (or clear) an IfcDocumentInformation on a document-reference annotation."""
|
||||
ifc_file = tool.Ifc.get()
|
||||
# Remove existing document associations on this annotation.
|
||||
for rel in list(element.HasAssociations):
|
||||
if rel.is_a("IfcRelAssociatesDocument"):
|
||||
ifcopenshell.api.document.unassign_document(
|
||||
ifc_file, products=[element], document=rel.RelatingDocument
|
||||
)
|
||||
if document:
|
||||
ifcopenshell.api.document.assign_document(ifc_file, products=[element], document=document)
|
||||
|
||||
@classmethod
|
||||
def regenerate_elevation_reference_annotation(
|
||||
cls,
|
||||
|
||||
Reference in New Issue
Block a user