Refactoring based on developer's feedback

This commit is contained in:
falken10
2025-07-01 17:34:45 +02:00
committed by falken10vdl
parent d2adfc8c5d
commit aa8c146f9d
7 changed files with 122 additions and 126 deletions
@@ -18,7 +18,6 @@
import bpy import bpy
from . import ui, prop, operator from . import ui, prop, operator
from bpy.types import VIEW3D_MT_object_context_menu
classes = ( classes = (
operator.AddDocumentReference, operator.AddDocumentReference,
@@ -41,7 +40,6 @@ classes = (
prop.Document, prop.Document,
prop.DocumentObject, prop.DocumentObject,
prop.AssignedDocument, prop.AssignedDocument,
prop.ExpandedDocuments,
prop.BIMDocumentProperties, prop.BIMDocumentProperties,
ui.BIM_PT_documents, ui.BIM_PT_documents,
ui.BIM_PT_object_documents, ui.BIM_PT_object_documents,
@@ -54,11 +52,9 @@ classes = (
def register(): def register():
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties) bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
bpy.types.Scene.ExpandedDocuments = bpy.props.PointerProperty(type=prop.ExpandedDocuments) bpy.types.VIEW3D_MT_object_context_menu.append(ui.add_object_documents_context_menu)
VIEW3D_MT_object_context_menu.append(ui.add_object_documents_context_menu)
def unregister(): def unregister():
del bpy.types.Scene.BIMDocumentProperties del bpy.types.Scene.BIMDocumentProperties
del bpy.types.Scene.ExpandedDocuments bpy.types.VIEW3D_MT_object_context_menu.remove(ui.add_object_documents_context_menu)
VIEW3D_MT_object_context_menu.remove(ui.add_object_documents_context_menu)
+26 -22
View File
@@ -119,13 +119,15 @@ class ObjectDocumentData:
return results return results
for rel in getattr(element, "HasAssociations", []): for rel in getattr(element, "HasAssociations", []):
if rel.is_a("IfcRelAssociatesDocument"): if rel.is_a("IfcRelAssociatesDocument"):
is_information = rel.RelatingDocument.is_a("IfcDocumentInformation") relating_document = rel.RelatingDocument
is_reference = rel.RelatingDocument.is_a("IfcDocumentReference")
is_information = relating_document.is_a("IfcDocumentInformation")
is_reference = relating_document.is_a("IfcDocumentReference")
if not (is_information or is_reference): if not (is_information or is_reference):
continue continue
name = rel.RelatingDocument.Name name = relating_document.Name
location = None location = None
identification = None identification = None
@@ -133,33 +135,35 @@ class ObjectDocumentData:
if is_information: if is_information:
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
identification = rel.RelatingDocument.DocumentId identification = relating_document.DocumentId
else: else:
identification = rel.RelatingDocument.Identification identification = relating_document.Identification
location = getattr(rel.RelatingDocument, "Location", None) location = getattr(relating_document, "Location", None)
else: else:
description = rel.RelatingDocument.Description description = relating_document.Description
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
if not name and rel.RelatingDocument.ReferenceToDocument: reference_to_document = relating_document.ReferenceToDocument
name = rel.RelatingDocument.ReferenceToDocument[0].Name if not name and reference_to_document:
name = reference_to_document[0].Name
identification = rel.RelatingDocument.ItemReference identification = relating_document.ItemReference
if not identification and rel.RelatingDocument.ReferenceToDocument: if not identification and reference_to_document:
identification = rel.RelatingDocument.ReferenceToDocument[0].DocumentId identification = reference_to_document[0].DocumentId
location = rel.RelatingDocument.Location location = relating_document.Location
else: else:
if not name and rel.RelatingDocument.ReferencedDocument: referenced_document = relating_document.ReferencedDocument
name = rel.RelatingDocument.ReferencedDocument.Name if not name and referenced_document:
name = referenced_document.Name
identification = rel.RelatingDocument.Identification identification = relating_document.Identification
if not identification and rel.RelatingDocument.ReferencedDocument: if not identification and referenced_document:
identification = rel.RelatingDocument.ReferencedDocument.Identification identification = referenced_document.Identification
location = rel.RelatingDocument.Location location = relating_document.Location
if location is None and rel.RelatingDocument.ReferencedDocument: if location is None and referenced_document:
location = rel.RelatingDocument.ReferencedDocument.Location location = referenced_document.Location
if location: if location:
if not "://" in location: if not "://" in location:
@@ -169,7 +173,7 @@ class ObjectDocumentData:
results.append( results.append(
{ {
"id": rel.RelatingDocument.id(), "id": relating_document.id(),
"identification": identification, "identification": identification,
"name": name, "name": name,
"location": location, "location": location,
@@ -35,10 +35,8 @@ def update_document_objects(document_id=None):
if document_id is None: if document_id is None:
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
if props.documents and props.active_document_index < len(props.documents): if props.active_document and props.active_document.ifc_definition_id:
document = props.documents[props.active_document_index] document_id = props.active_document.ifc_definition_id
if document.ifc_definition_id:
document_id = document.ifc_definition_id
if document_id: if document_id:
DocumentData.load_document_objects_into_props(document_id) DocumentData.load_document_objects_into_props(document_id)
@@ -122,8 +120,8 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
parent = None parent = None
if props.documents and props.active_document_index < len(props.documents): if props.active_document:
selected_document = props.documents[props.active_document_index] selected_document = props.active_document
if selected_document.ifc_definition_id == -1: if selected_document.ifc_definition_id == -1:
parent = tool.Ifc.get().by_type("IfcProject")[0] if tool.Ifc.get().by_type("IfcProject") else None parent = tool.Ifc.get().by_type("IfcProject")[0] if tool.Ifc.get().by_type("IfcProject") else None
@@ -139,7 +137,7 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
expanded_docs = [] expanded_docs = []
try: try:
expanded_docs = json.loads(context.scene.ExpandedDocuments.json_string) expanded_docs = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError): except (AttributeError, json.JSONDecodeError):
pass pass
@@ -152,7 +150,7 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
if parent.id() not in expanded_docs: if parent.id() not in expanded_docs:
expanded_docs.append(parent.id()) expanded_docs.append(parent.id())
context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs) props.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents() bpy.ops.bim.load_project_documents()
@@ -165,11 +163,11 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
if not props.documents or props.active_document_index >= len(props.documents): if not props.active_document:
self.report({"ERROR"}, "No document selected") self.report({"ERROR"}, "No document selected")
return {"CANCELLED"} return {"CANCELLED"}
selected_document = props.documents[props.active_document_index] selected_document = props.active_document
if not selected_document.is_information: if not selected_document.is_information:
self.report({"ERROR"}, "Cannot add a reference to a reference element") self.report({"ERROR"}, "Cannot add a reference to a reference element")
@@ -181,13 +179,13 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
core.add_reference(tool.Ifc, tool.Document) core.add_reference(tool.Ifc, tool.Document)
expanded_docs = [] expanded_docs = []
try: try:
expanded_docs = json.loads(context.scene.ExpandedDocuments.json_string) expanded_docs = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError): except (AttributeError, json.JSONDecodeError):
pass pass
if parent.id() not in expanded_docs: if parent.id() not in expanded_docs:
expanded_docs.append(parent.id()) expanded_docs.append(parent.id())
context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs) props.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents() bpy.ops.bim.load_project_documents()
@@ -292,9 +290,8 @@ class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
core.unassign_document(tool.Ifc, product=element, document=document) core.unassign_document(tool.Ifc, product=element, document=document)
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
active_document_id = None active_document_id = None
if props.documents and props.active_document_index < len(props.documents): if props.active_document:
active_document = props.documents[props.active_document_index] active_document_id = props.active_document.ifc_definition_id
active_document_id = active_document.ifc_definition_id
if active_document_id and active_document_id != self.document: if active_document_id and active_document_id != self.document:
update_document_objects(active_document_id) update_document_objects(active_document_id)
@@ -420,8 +417,9 @@ class ToggleDocument(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
expanded_documents = [] expanded_documents = []
props = tool.Document.get_document_props()
try: try:
expanded_documents = json.loads(context.scene.ExpandedDocuments.json_string) expanded_documents = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError): except (AttributeError, json.JSONDecodeError):
expanded_documents = [] expanded_documents = []
@@ -439,8 +437,7 @@ class ToggleDocument(bpy.types.Operator, tool.Ifc.Operator):
expanded_documents.append(virtual_root_id) expanded_documents.append(virtual_root_id)
elif self.option == "Collapse" and virtual_root_id in expanded_documents: elif self.option == "Collapse" and virtual_root_id in expanded_documents:
expanded_documents.remove(virtual_root_id) expanded_documents.remove(virtual_root_id)
props.json_string = json.dumps(expanded_documents)
context.scene.ExpandedDocuments.json_string = json.dumps(expanded_documents)
bpy.ops.bim.load_project_documents() bpy.ops.bim.load_project_documents()
return {"FINISHED"} return {"FINISHED"}
@@ -80,13 +80,6 @@ class Document(PropertyGroup):
is_expanded: bool is_expanded: bool
class ExpandedDocuments(PropertyGroup):
json_string: StringProperty(name="JSON String", default="[]")
if TYPE_CHECKING:
json_string: str
class DocumentObject(PropertyGroup): class DocumentObject(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
@@ -124,6 +117,7 @@ class BIMDocumentProperties(PropertyGroup):
active_document_object_index: IntProperty(name="Active Document Object Index") active_document_object_index: IntProperty(name="Active Document Object Index")
assigned_documents: CollectionProperty(name="Assigned Documents", type=AssignedDocument) assigned_documents: CollectionProperty(name="Assigned Documents", type=AssignedDocument)
active_assigned_document_index: IntProperty(name="Active Assigned Document Index") active_assigned_document_index: IntProperty(name="Active Assigned Document Index")
json_string: StringProperty(name="JSON String", default="[]")
if TYPE_CHECKING: if TYPE_CHECKING:
document_attributes: bpy.types.bpy_prop_collection_idprop[Attribute] document_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
@@ -137,6 +131,7 @@ class BIMDocumentProperties(PropertyGroup):
active_document_object_index: int active_document_object_index: int
assigned_documents: bpy.types.bpy_prop_collection_idprop[AssignedDocument] assigned_documents: bpy.types.bpy_prop_collection_idprop[AssignedDocument]
active_assigned_document_index: int active_assigned_document_index: int
json_string: str
@property @property
def active_document(self) -> Union[Document, None]: def active_document(self) -> Union[Document, None]:
+10 -19
View File
@@ -68,14 +68,11 @@ class BIM_PT_documents(Panel):
row.operator("bim.edit_document", text="", icon="CHECKMARK") row.operator("bim.edit_document", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL") row.operator("bim.disable_editing_document", text="", icon="CANCEL")
else: else:
if not self.props.documents or not self.props.active_document_index < len(self.props.documents) or \ if not self.props.active_document or self.props.active_document.is_information:
(self.props.active_document_index < len(self.props.documents) and
self.props.documents[self.props.active_document_index].is_information):
row.operator("bim.add_information", text="", icon="ADD") row.operator("bim.add_information", text="", icon="ADD")
if self.props.documents and self.props.active_document_index < len(self.props.documents): if self.props.active_document:
active_doc = self.props.documents[self.props.active_document_index] if self.props.active_document.is_information and self.props.active_document.ifc_definition_id != -1:
if active_doc.is_information and active_doc.ifc_definition_id != -1:
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN") row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
active_document = self.props.active_document active_document = self.props.active_document
@@ -97,12 +94,8 @@ class BIM_PT_documents(Panel):
else: else:
draw_attributes(self.props.document_attributes, self.layout, filter_attributes=["Name"]) draw_attributes(self.props.document_attributes, self.layout, filter_attributes=["Name"])
if ( if self.props.is_editing and self.props.active_document:
self.props.is_editing document = self.props.active_document
and self.props.documents
and self.props.active_document_index < len(self.props.documents)
):
document = self.props.documents[self.props.active_document_index]
box = self.layout.box() box = self.layout.box()
row = box.row(align=True) row = box.row(align=True)
row.label(text="Assigned Objects", icon="OUTLINER_OB_EMPTY") row.label(text="Assigned Objects", icon="OUTLINER_OB_EMPTY")
@@ -181,21 +174,19 @@ class BIM_PT_object_documents(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.alignment = "RIGHT" row.alignment = "RIGHT"
if self.props.documents and self.props.active_document_index < len(self.props.documents): if self.props.active_document:
document = self.props.documents[self.props.active_document_index] document = self.props.active_document
assigned_doc_ids = [] assigned_doc_ids = []
for doc in ObjectDocumentData.data["documents"]: for doc in ObjectDocumentData.data["documents"]:
assigned_doc_ids.append(doc["id"]) assigned_doc_ids.append(doc["id"])
# Only show assign button if the document is information (not reference) and not already assigned # Only show assign button if the document is information (not reference) and not already assigned
if (document.is_information and if document.is_information and document.ifc_definition_id not in assigned_doc_ids:
document.ifc_definition_id not in assigned_doc_ids):
doc_op = row.operator("bim.assign_document", text="", icon="BRUSH_DATA") doc_op = row.operator("bim.assign_document", text="", icon="BRUSH_DATA")
doc_op.document = document.ifc_definition_id # Pass the current document's ID doc_op.document = document.ifc_definition_id # Pass the current document's ID
elif document.ifc_definition_id in assigned_doc_ids: elif document.ifc_definition_id in assigned_doc_ids:
row.label(text="", icon="CHECKMARK") row.label(text="", icon="CHECKMARK")
self.layout.template_list( self.layout.template_list(
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index" "BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
) )
@@ -249,8 +240,8 @@ class BIM_UL_document_objects(UIList):
row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
if props.documents and props.active_document_index < len(props.documents): if props.active_document:
document = props.documents[props.active_document_index] document = props.active_document
op = row.operator("bim.unassign_document", text="", icon="X") op = row.operator("bim.unassign_document", text="", icon="X")
op.document = document.ifc_definition_id op.document = document.ifc_definition_id
+13 -43
View File
@@ -18,8 +18,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
import bpy
import json
if TYPE_CHECKING: if TYPE_CHECKING:
import ifcopenshell import ifcopenshell
@@ -34,15 +32,7 @@ def load_project_documents(document: tool.Document) -> None:
def load_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None: def load_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
document_tool.clear_document_tree() document_tool.clear_document_tree()
try: document_tool.expand_document(document)
expanded_docs = json.loads(bpy.context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_docs = []
if document.id() not in expanded_docs:
expanded_docs.append(document.id())
bpy.context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs)
document_tool.import_project_documents() document_tool.import_project_documents()
document_tool.disable_editing_document() document_tool.disable_editing_document()
@@ -53,60 +43,40 @@ def disable_document_editing_ui(document: tool.Document) -> None:
def enable_editing_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None: def enable_editing_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
props = document_tool.get_document_props() document_tool.set_active_document(document)
props.active_document_id = document.id() document_tool.enable_document_editing()
props.is_document_editing = True
document_tool.import_document_attributes(document) document_tool.import_document_attributes(document)
def disable_editing_document(document: tool.Document) -> None: def disable_editing_document(document: tool.Document) -> None:
props = document.get_document_props() document.clear_active_document()
props.active_document_id = 0 document.disable_document_editing()
props.is_document_editing = False document.clear_document_attributes()
props.document_attributes.clear()
def add_information(ifc: tool.Ifc, document_tool: tool.Document, parent=None) -> ifcopenshell.entity_instance: def add_information(ifc: tool.Ifc, document_tool: tool.Document, parent=None) -> ifcopenshell.entity_instance:
document_tool.clear_document_tree() document_tool.clear_document_tree()
if parent is None and ifc.get().by_type("IfcProject"): if parent is None:
parent = ifc.get().by_type("IfcProject")[0] parent = document_tool.get_default_parent_for_information(ifc)
information = ifc.run("document.add_information", parent=parent) information = ifc.run("document.add_information", parent=parent)
ifc.run("document.add_reference", information=information) ifc.run("document.add_reference", information=information)
if parent and parent.is_a("IfcDocumentInformation"):
try:
expanded_docs = json.loads(bpy.context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_docs = []
if parent.id() not in expanded_docs: if document_tool.is_document_information(parent):
expanded_docs.append(parent.id()) document_tool.expand_document(parent)
bpy.context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs)
document_tool.import_project_documents() document_tool.import_project_documents()
return information
def add_reference(ifc: tool.Ifc, document: tool.Document) -> None: def add_reference(ifc: tool.Ifc, document: tool.Document) -> None:
props = document.get_document_props() parent = document.get_selected_document_information(ifc)
parent = None
if props.documents and props.active_document_index < len(props.documents):
selected_document = props.documents[props.active_document_index]
if selected_document.is_information:
parent = ifc.get().by_id(selected_document.ifc_definition_id)
if parent: if parent:
reference = ifc.run("document.add_reference", information=parent) reference = ifc.run("document.add_reference", information=parent)
reference.Location = "" reference.Location = ""
try: document.expand_document(parent)
expanded_docs = json.loads(bpy.context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_docs = []
if parent.id() not in expanded_docs:
expanded_docs.append(parent.id())
bpy.context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs)
document.import_project_documents() document.import_project_documents()
+50 -7
View File
@@ -23,6 +23,7 @@ import ifcopenshell.util.system
import bonsai.bim.helper import bonsai.bim.helper
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
import json
from typing import Any, Union, TYPE_CHECKING from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -86,7 +87,7 @@ class Document(bonsai.core.tool.Document):
props.documents.clear() props.documents.clear()
file = tool.Ifc.get() file = tool.Ifc.get()
try: try:
expanded_documents = json.loads(bpy.context.scene.ExpandedDocuments.json_string) expanded_documents = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError): except (AttributeError, json.JSONDecodeError):
expanded_documents = [] expanded_documents = []
@@ -116,7 +117,7 @@ class Document(bonsai.core.tool.Document):
document_children[parent_id].append(ref) document_children[parent_id].append(ref)
else: else:
for ref in file.by_type("IfcDocumentReference"): for ref in file.by_type("IfcDocumentReference"):
if hasattr(ref, "ReferencedDocument") and ref.ReferencedDocument: if ref.ReferencedDocument:
parent = ref.ReferencedDocument parent = ref.ReferencedDocument
parent_id = parent.id() parent_id = parent.id()
if parent_id not in document_children: if parent_id not in document_children:
@@ -200,16 +201,13 @@ class Document(bonsai.core.tool.Document):
ref_children = [d for d in children if not d.is_a("IfcDocumentInformation")] ref_children = [d for d in children if not d.is_a("IfcDocumentInformation")]
info_children.sort( info_children.sort(
key=lambda doc: ( key=lambda doc: ((cls.get_document_information_id(doc) or "").lower(), (doc.Name or "").lower())
(cls.get_document_information_id(doc) or "").lower(),
(doc.Name or "").lower()
)
) )
ref_children.sort( ref_children.sort(
key=lambda doc: ( key=lambda doc: (
(cls.get_external_reference_id(doc) or "").lower(), (cls.get_external_reference_id(doc) or "").lower(),
(doc.Description or doc.Name or "").lower() (doc.Description or doc.Name or "").lower(),
) )
) )
@@ -253,3 +251,48 @@ class Document(bonsai.core.tool.Document):
if document.file.schema == "IFC2X3": if document.file.schema == "IFC2X3":
return document.DocumentReferences or () return document.DocumentReferences or ()
return document.HasDocumentReferences return document.HasDocumentReferences
@classmethod
def enable_document_editing(cls) -> None:
props = cls.get_document_props()
props.is_editing = True
@classmethod
def disable_document_editing(cls) -> None:
props = cls.get_document_props()
props.is_editing = False
@classmethod
def clear_active_document(cls) -> None:
props = cls.get_document_props()
props.active_document_id = 0
@classmethod
def clear_document_attributes(cls) -> None:
props = cls.get_document_props()
props.document_attributes.clear()
@classmethod
def expand_document(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props()
try:
expanded_docs = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_docs = []
if document.id() not in expanded_docs:
expanded_docs.append(document.id())
props.json_string = json.dumps(expanded_docs)
@classmethod
def get_default_parent_for_information(cls, ifc) -> Union[ifcopenshell.entity_instance, None]:
projects = ifc.get().by_type("IfcProject")
return projects[0] if projects else None
@classmethod
def get_selected_document_information(cls, ifc) -> Union[ifcopenshell.entity_instance, None]:
props = cls.get_document_props()
if props.active_document and props.active_document.is_information:
return ifc.get().by_id(props.active_document.ifc_definition_id)
return None