Implemented tree like structure for documents

This commit is contained in:
falken10
2025-06-20 10:14:02 +02:00
committed by falken10vdl
parent 0c3153ab7f
commit 5ea66730d4
11 changed files with 873 additions and 301 deletions
@@ -18,32 +18,47 @@
import bpy
from . import ui, prop, operator
from bpy.types import VIEW3D_MT_object_context_menu
classes = (
operator.AddDocumentReference,
operator.AddInformation,
operator.AssignDocument,
operator.DisableDocumentEditingUI,
operator.DisableObjectDocumentEditingUI,
operator.DisableEditingDocument,
operator.EditDocument,
operator.EnableEditingDocument,
operator.LoadDocument,
operator.LoadParentDocument,
operator.LoadObjectDocuments,
operator.LoadProjectDocuments,
operator.RemoveDocument,
operator.SelectDocumentObjects,
operator.ToggleDocument,
operator.UnassignDocument,
operator.UpdateAssignedDocuments,
operator.OpenIFCDocument,
prop.Document,
prop.DocumentObject,
prop.AssignedDocument,
prop.ExpandedDocuments,
prop.BIMDocumentProperties,
ui.BIM_PT_documents,
ui.BIM_PT_object_documents,
ui.BIM_UL_documents,
ui.BIM_UL_document_objects,
ui.BIM_UL_assigned_documents,
ui.BIM_MT_object_documents_context_menu,
)
def register():
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
bpy.types.Scene.ExpandedDocuments = bpy.props.PointerProperty(type=prop.ExpandedDocuments)
VIEW3D_MT_object_context_menu.append(ui.add_object_documents_context_menu)
def unregister():
del bpy.types.Scene.BIMDocumentProperties
del bpy.types.Scene.ExpandedDocuments
VIEW3D_MT_object_context_menu.remove(ui.add_object_documents_context_menu)
+90 -33
View File
@@ -35,30 +35,69 @@ class DocumentData:
@classmethod
def load(cls):
cls.data = {
"total_information": cls.total_information(),
"parent_document": cls.parent_document(),
"total_document_informations": cls.total_document_informations(),
"total_document_references": cls.total_document_references(),
"total_referenced_objects": cls.total_referenced_objects(),
"document_objects": cls.document_objects(),
}
cls.is_loaded = True
@classmethod
def total_information(cls):
return len(
[
rel
for rel in tool.Ifc.get().by_type("IfcProject")[0].HasAssociations or []
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation")
]
)
def total_document_informations(cls):
file = tool.Ifc.get()
info_count = len(file.by_type("IfcDocumentInformation"))
return info_count
@classmethod
def parent_document(cls):
def total_document_references(cls):
file = tool.Ifc.get()
ref_count = len(file.by_type("IfcDocumentReference"))
return ref_count
@classmethod
def total_referenced_objects(cls):
file = tool.Ifc.get()
document_rels = file.by_type("IfcRelAssociatesDocument")
documented_objects = set()
for rel in document_rels:
for related_object in rel.RelatedObjects:
obj = tool.Ifc.get_object(related_object)
if obj:
documented_objects.add(related_object.id())
return len(documented_objects)
@classmethod
def document_objects(cls):
document_objects = {}
file = tool.Ifc.get()
for rel in file.by_type("IfcRelAssociatesDocument"):
document_id = rel.RelatingDocument.id()
if document_id not in document_objects:
document_objects[document_id] = []
for related_object in rel.RelatedObjects:
element = related_object
obj = tool.Ifc.get_object(element)
if obj:
document_objects[document_id].append({"id": element.id(), "name": obj.name, "obj": obj})
return document_objects
@classmethod
def load_document_objects_into_props(cls, document_id):
props = tool.Document.get_document_props()
if len(props.breadcrumbs):
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
if tool.Ifc.get_schema() == "IFC2X3":
return str(parent.DocumentId)
return str(parent.Identification)
return ""
props.document_objects.clear()
if "document_objects" not in cls.data or document_id not in cls.data["document_objects"]:
return
sorted_objects = sorted(cls.data["document_objects"][document_id], key=lambda x: x["name"].lower())
for obj_data in sorted_objects:
item = props.document_objects.add()
item.name = obj_data["name"]
class ObjectDocumentData:
@@ -80,31 +119,47 @@ class ObjectDocumentData:
return results
for rel in getattr(element, "HasAssociations", []):
if rel.is_a("IfcRelAssociatesDocument"):
if not rel.RelatingDocument.is_a("IfcDocumentReference"):
is_information = rel.RelatingDocument.is_a("IfcDocumentInformation")
is_reference = rel.RelatingDocument.is_a("IfcDocumentReference")
if not (is_information or is_reference):
continue
name = rel.RelatingDocument.Name
if tool.Ifc.get_schema() == "IFC2X3":
if not name and rel.RelatingDocument.ReferenceToDocument:
name = rel.RelatingDocument.ReferenceToDocument[0].Name
location = None
identification = None
description = None
identification = rel.RelatingDocument.ItemReference
if not identification and rel.RelatingDocument.ReferenceToDocument:
identification = rel.RelatingDocument.ReferenceToDocument[0].DocumentId
if is_information:
if tool.Ifc.get_schema() == "IFC2X3":
identification = rel.RelatingDocument.DocumentId
else:
identification = rel.RelatingDocument.Identification
location = getattr(rel.RelatingDocument, "Location", None)
location = rel.RelatingDocument.Location
else:
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
description = rel.RelatingDocument.Description
if tool.Ifc.get_schema() == "IFC2X3":
if not name and rel.RelatingDocument.ReferenceToDocument:
name = rel.RelatingDocument.ReferenceToDocument[0].Name
identification = rel.RelatingDocument.Identification
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.Identification
identification = rel.RelatingDocument.ItemReference
if not identification and rel.RelatingDocument.ReferenceToDocument:
identification = rel.RelatingDocument.ReferenceToDocument[0].DocumentId
location = rel.RelatingDocument.Location
else:
if not name and rel.RelatingDocument.ReferencedDocument:
name = rel.RelatingDocument.ReferencedDocument.Name
location = rel.RelatingDocument.Location
if location is None and rel.RelatingDocument.ReferencedDocument:
location = rel.RelatingDocument.ReferencedDocument.Location
identification = rel.RelatingDocument.Identification
if not identification and rel.RelatingDocument.ReferencedDocument:
identification = rel.RelatingDocument.ReferencedDocument.Identification
location = rel.RelatingDocument.Location
if location is None and rel.RelatingDocument.ReferencedDocument:
location = rel.RelatingDocument.ReferencedDocument.Location
if location:
if not "://" in location:
@@ -118,6 +173,8 @@ class ObjectDocumentData:
"identification": identification,
"name": name,
"location": location,
"is_information": is_information,
"description": description,
}
)
return results
+277 -15
View File
@@ -24,6 +24,24 @@ import ifcopenshell.util.element
import bonsai.bim.handler
import bonsai.tool as tool
import bonsai.core.document as core
import subprocess
import os
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
def update_document_objects(document_id=None):
DocumentData.is_loaded = False
DocumentData.load()
if document_id is None:
props = tool.Document.get_document_props()
if props.documents and props.active_document_index < len(props.documents):
document = props.documents[props.active_document_index]
if document.ifc_definition_id:
document_id = document.ifc_definition_id
if document_id:
DocumentData.load_document_objects_into_props(document_id)
class LoadProjectDocuments(bpy.types.Operator):
@@ -33,7 +51,7 @@ class LoadProjectDocuments(bpy.types.Operator):
def execute(self, context):
core.load_project_documents(tool.Document)
bonsai.bim.handler.refresh_ui_data() # Update breadcrumbs data.
update_document_objects()
return {"FINISHED"}
@@ -45,18 +63,8 @@ class LoadDocument(bpy.types.Operator):
def execute(self, context):
core.load_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
bonsai.bim.handler.refresh_ui_data() # Update breadcrumbs data.
return {"FINISHED"}
class LoadParentDocument(bpy.types.Operator):
bl_idname = "bim.load_parent_document"
bl_label = "Load Parent Document"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
core.load_parent_document(tool.Document)
bonsai.bim.handler.refresh_ui_data() # Update breadcrumbs data.
bonsai.bim.handler.refresh_ui_data() # Is this needed?
update_document_objects()
return {"FINISHED"}
@@ -70,6 +78,17 @@ class DisableDocumentEditingUI(bpy.types.Operator):
return {"FINISHED"}
class DisableObjectDocumentEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_object_document_editing_ui"
bl_label = "Disable Object Document Editing UI"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = tool.Document.get_document_props()
props.is_object_editing = False
return {"FINISHED"}
class EnableEditingDocument(bpy.types.Operator):
bl_idname = "bim.enable_editing_document"
bl_label = "Enable Editing Document"
@@ -77,6 +96,8 @@ class EnableEditingDocument(bpy.types.Operator):
document: bpy.props.IntProperty()
def execute(self, context):
props = tool.Document.get_document_props()
props.is_document_editing = True
core.enable_editing_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
return {"FINISHED"}
@@ -87,6 +108,8 @@ class DisableEditingDocument(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = tool.Document.get_document_props()
props.is_document_editing = False
core.disable_editing_document(tool.Document)
return {"FINISHED"}
@@ -97,7 +120,41 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.add_information(tool.Ifc, tool.Document)
props = tool.Document.get_document_props()
parent = None
if props.documents and props.active_document_index < len(props.documents):
selected_document = props.documents[props.active_document_index]
if selected_document.ifc_definition_id == -1:
parent = tool.Ifc.get().by_type("IfcProject")[0] if tool.Ifc.get().by_type("IfcProject") else None
elif selected_document.is_information:
parent = tool.Ifc.get().by_id(selected_document.ifc_definition_id)
else:
self.report({"ERROR"}, "Cannot add an information element as a child of a reference element")
return {"CANCELLED"}
else:
parent = tool.Ifc.get().by_type("IfcProject")[0] if tool.Ifc.get().by_type("IfcProject") else None
core.add_information(tool.Ifc, tool.Document, parent)
expanded_docs = []
try:
expanded_docs = json.loads(context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
pass
project = tool.Ifc.get().by_type("IfcProject")[0]
virtual_root_id = -project.id()
if virtual_root_id in expanded_docs:
expanded_docs.remove(virtual_root_id)
if parent and parent.is_a("IfcDocumentInformation"):
if parent.id() not in expanded_docs:
expanded_docs.append(parent.id())
context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents()
class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
@@ -106,7 +163,33 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = tool.Document.get_document_props()
if not props.documents or props.active_document_index >= len(props.documents):
self.report({"ERROR"}, "No document selected")
return {"CANCELLED"}
selected_document = props.documents[props.active_document_index]
if not selected_document.is_information:
self.report({"ERROR"}, "Cannot add a reference to a reference element")
return {"CANCELLED"}
parent = tool.Ifc.get().by_id(selected_document.ifc_definition_id)
props.document_attributes.clear()
core.add_reference(tool.Ifc, tool.Document)
expanded_docs = []
try:
expanded_docs = json.loads(context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
pass
if parent.id() not in expanded_docs:
expanded_docs.append(parent.id())
context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents()
class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -116,7 +199,16 @@ class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = tool.Document.get_document_props()
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
if props.active_document_id:
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
props.active_document_id = 0
props.is_document_editing = False
DocumentData.is_loaded = False
DocumentData.load()
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
bpy.ops.bim.update_assigned_documents()
bonsai.bim.handler.refresh_ui_data()
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -129,6 +221,39 @@ class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document))
class UpdateAssignedDocuments(bpy.types.Operator):
bl_idname = "bim.update_assigned_documents"
bl_label = "Update Assigned Documents"
bl_description = "Update the list of documents assigned to the active object"
bl_options = {"REGISTER"}
def execute(self, context):
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
props = tool.Document.get_document_props()
props.assigned_documents.clear()
if not ObjectDocumentData.data.get("documents"):
return {"FINISHED"}
sorted_docs = sorted(
ObjectDocumentData.data["documents"],
key=lambda doc: ((doc.get("identification") or "").lower(), (doc.get("name") or "").lower()),
)
for document in sorted_docs:
new = props.assigned_documents.add()
new.name = document["name"] or "Unnamed"
new.identification = document["identification"] or "*"
new.is_information = document.get("is_information", False)
new.ifc_definition_id = document["id"]
new.location = document.get("location") or ""
new.description = document.get("description") or ""
return {"FINISHED"}
class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_document"
bl_label = "Assign Document"
@@ -145,6 +270,11 @@ class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
if element:
core.assign_document(tool.Ifc, product=element, document=document)
update_document_objects(self.document)
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
bpy.ops.bim.update_assigned_documents()
class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_document"
@@ -160,6 +290,19 @@ class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(obj)
if element:
core.unassign_document(tool.Ifc, product=element, document=document)
props = tool.Document.get_document_props()
active_document_id = None
if props.documents and props.active_document_index < len(props.documents):
active_document = props.documents[props.active_document_index]
active_document_id = active_document.ifc_definition_id
if active_document_id and active_document_id != self.document:
update_document_objects(active_document_id)
else:
update_document_objects(self.document)
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
bpy.ops.bim.update_assigned_documents()
class SelectDocumentObjects(bpy.types.Operator):
@@ -182,3 +325,122 @@ class SelectDocumentObjects(bpy.types.Operator):
i += 1
self.report({"INFO"}, f"{i} objects selected.")
return {"FINISHED"}
class LoadObjectDocuments(bpy.types.Operator):
bl_idname = "bim.load_object_documents"
bl_label = "Load Object Documents"
bl_description = "Load documents to assign to the selected object"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
if not ObjectDocumentData.is_loaded:
ObjectDocumentData.load()
core.load_project_documents(tool.Document)
props = tool.Document.get_document_props()
props.is_object_editing = True
bonsai.bim.handler.refresh_ui_data()
self.update_assigned_documents(props)
return {"FINISHED"}
def update_assigned_documents(self, props):
props.assigned_documents.clear()
if not ObjectDocumentData.data.get("documents"):
return
sorted_docs = sorted(
ObjectDocumentData.data["documents"],
key=lambda doc: ((doc.get("identification") or "").lower(), (doc.get("name") or "").lower()),
)
for document in sorted_docs:
new = props.assigned_documents.add()
new.name = document["name"] or "Unnamed"
new.identification = document["identification"] or "*"
new.is_information = document.get("is_information", False)
new.ifc_definition_id = document["id"]
new.location = document["location"] or ""
new.description = document["description"] or ""
class OpenIFCDocument(bpy.types.Operator):
bl_idname = "bim.open_ifc_document"
bl_label = "Open IFC Document"
bl_description = "Open the IFC document in a new Blender instance and load the project"
bl_options = {"REGISTER", "UNDO"}
uri: bpy.props.StringProperty(name="URI")
def execute(self, context):
if not self.uri:
self.report({"ERROR"}, "No URI provided")
return {"CANCELLED"}
file_path = self.uri
if file_path.startswith("file://"):
file_path = file_path[7:]
elif file_path.startswith("file:"):
file_path = file_path[5:]
if not os.path.isabs(file_path):
file_path = os.path.abspath(file_path)
if not os.path.exists(file_path):
self.report({"ERROR"}, f"IFC file not found: {file_path}")
return {"CANCELLED"}
try:
subprocess.Popen(
[
"blender",
"--python-expr",
f"import bpy; bpy.ops.bim.load_project(filepath='{file_path}', should_start_fresh_session=True)",
]
)
self.report({"INFO"}, f"Opening IFC file: {file_path} in a new Blender instance.")
except Exception as e:
self.report({"ERROR"}, f"Failed to open IFC file: {str(e)}")
return {"FINISHED"}
class ToggleDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.toggle_document"
bl_label = "Toggle Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
option: bpy.props.StringProperty()
def _execute(self, context):
expanded_documents = []
try:
expanded_documents = json.loads(context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_documents = []
document_id = self.document
if self.option == "Expand" and document_id not in expanded_documents:
expanded_documents.append(document_id)
elif self.option == "Collapse" and document_id in expanded_documents:
expanded_documents.remove(document_id)
elif document_id == -1:
project = tool.Ifc.get().by_type("IfcProject")[0]
virtual_root_id = -project.id()
if self.option == "Expand" and virtual_root_id not in expanded_documents:
expanded_documents.append(virtual_root_id)
elif self.option == "Collapse" and virtual_root_id in expanded_documents:
expanded_documents.remove(virtual_root_id)
context.scene.ExpandedDocuments.json_string = json.dumps(expanded_documents)
bpy.ops.bim.load_project_documents()
return {"FINISHED"}
+67 -9
View File
@@ -30,6 +30,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from bonsai.bim.module.document.data import DocumentData
from typing import TYPE_CHECKING, Union
@@ -49,36 +50,93 @@ def update_document_identification(self: "Document", context: bpy.types.Context)
tool.Document.set_external_reference_id(document, self.identification)
def update_active_document(self, context):
if self.documents and self.active_document_index < len(self.documents):
document = self.documents[self.active_document_index]
if document.ifc_definition_id:
DocumentData.load_document_objects_into_props(document.ifc_definition_id)
class Document(PropertyGroup):
name: StringProperty(name="Name", update=update_document_name)
identification: StringProperty(name="Identification", update=update_document_identification)
is_information: BoolProperty(
name="Is Information",
description="Whether element is IfcDocumentInformation, otherwise it's IfcDocumentReference.",
)
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
description: StringProperty(name="Description")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
location: StringProperty(name="Location", default="")
tree_depth: IntProperty(name="Tree Depth", default=0)
has_children: BoolProperty(name="Has Children", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=False)
if TYPE_CHECKING:
name: str
identification: str
description: str
is_information: bool
ifc_definition_id: int
location: str
tree_depth: int
has_children: bool
is_expanded: bool
class ExpandedDocuments(PropertyGroup):
json_string: StringProperty(name="JSON String", default="[]")
if TYPE_CHECKING:
json_string: str
class DocumentObject(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
name: str
ifc_definition_id: int
class AssignedDocument(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
description: StringProperty(name="Description", default="")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
location: StringProperty(name="Location", default="")
if TYPE_CHECKING:
name: str
identification: str
is_information: bool
ifc_definition_id: int
location: str
class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
active_document_id: IntProperty(name="Active Document Id")
documents: CollectionProperty(name="Documents", type=Document)
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty)
active_document_index: IntProperty(name="Active Document Index")
active_document_index: IntProperty(name="Active Document Index", update=update_active_document)
is_editing: BoolProperty(name="Is Editing", default=False)
is_document_editing: BoolProperty(name="Is Document Editing", default=False)
is_object_editing: BoolProperty(name="Is Object Editing", default=False)
document_objects: CollectionProperty(name="Document Objects", type=DocumentObject)
active_document_object_index: IntProperty(name="Active Document Object Index")
assigned_documents: CollectionProperty(name="Assigned Documents", type=AssignedDocument)
active_assigned_document_index: IntProperty(name="Active Assigned Document Index")
if TYPE_CHECKING:
document_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
active_document_id: int
documents: bpy.types.bpy_prop_collection_idprop[Document]
breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty]
active_document_index: int
is_editing: bool
is_document_editing: bool
is_object_editing: bool
document_objects: bpy.types.bpy_prop_collection_idprop[DocumentObject]
active_document_object_index: int
assigned_documents: bpy.types.bpy_prop_collection_idprop[AssignedDocument]
active_assigned_document_index: int
@property
def active_document(self) -> Union[Document, None]:
+238 -60
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.helper import draw_attributes
@@ -42,43 +43,74 @@ class BIM_PT_documents(Panel):
self.props = tool.Document.get_document_props()
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE")
split = row.split(factor=0.55)
left_row = split.row(align=True)
left_row.label(text="{} Informations".format(DocumentData.data["total_document_informations"]), icon="FILE")
left_row.label(text="{} References".format(DocumentData.data["total_document_references"]), icon="FILE_HIDDEN")
right_row = split.row(align=True)
right_row.label(
text="{} Objects Referenced".format(DocumentData.data["total_referenced_objects"]), icon="OBJECT_DATA"
)
if self.props.is_editing:
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
right_row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_project_documents", text="", icon="IMPORT")
right_row.operator("bim.load_project_documents", text="", icon="IMPORT")
if not self.props.is_editing:
return
row = self.layout.row(align=True)
if self.props.breadcrumbs:
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
row.label(text=DocumentData.data["parent_document"])
else:
row.alignment = "RIGHT"
row.operator("bim.add_information", text="", icon="ADD")
if self.props.breadcrumbs:
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
row.alignment = "RIGHT"
active_document = self.props.active_document
if self.props.active_document_id:
if self.props.is_document_editing:
row.operator("bim.edit_document", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
elif active_document:
ifc_definition_id = active_document.ifc_definition_id
row.operator("bim.select_document_objects", text="", icon="RESTRICT_SELECT_OFF").document = (
ifc_definition_id
)
row.operator("bim.assign_document", text="", icon="BRUSH_DATA").document = ifc_definition_id
row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL").document = ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = ifc_definition_id
else:
row.operator("bim.add_information", text="", icon="ADD")
if self.props.documents and self.props.active_document_index < len(self.props.documents):
active_doc = self.props.documents[self.props.active_document_index]
if active_doc.is_information and active_doc.ifc_definition_id != -1:
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
active_document = self.props.active_document
if active_document:
ifc_definition_id = active_document.ifc_definition_id
row.operator("bim.select_document_objects", text="", icon="RESTRICT_SELECT_OFF").document = (
ifc_definition_id
)
row.operator("bim.assign_document", text="", icon="BRUSH_DATA").document = ifc_definition_id
row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL").document = ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = ifc_definition_id
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
if self.props.active_document_id:
draw_attributes(self.props.document_attributes, self.layout)
if self.props.is_document_editing:
active_document = self.props.active_document
if active_document.is_information:
draw_attributes(self.props.document_attributes, self.layout)
else:
draw_attributes(self.props.document_attributes, self.layout, filter_attributes=["Name"])
if (
self.props.is_editing
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()
row = box.row(align=True)
row.label(text="Assigned Objects", icon="OUTLINER_OB_EMPTY")
box.template_list(
"BIM_UL_document_objects",
"",
self.props,
"document_objects",
self.props,
"active_document_object_index",
)
class BIM_PT_object_documents(Panel):
@@ -110,57 +142,203 @@ class BIM_PT_object_documents(Panel):
self.props = tool.Document.get_document_props()
self.file = tool.Ifc.get()
self.draw_add_ui()
if not ObjectDocumentData.data["documents"]:
row = self.layout.row(align=True)
row.label(text="No Documents", icon="FILE")
for document in ObjectDocumentData.data["documents"]:
row = self.layout.row(align=True)
row.label(text=document["identification"] or "*", icon="FILE")
row.label(text=document["name"] or "Unnamed")
if document["location"]:
row.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
row.operator("bim.unassign_document", text="", icon="X").document = document["id"]
def draw_add_ui(self):
if not self.props.is_editing:
row = self.layout.row(align=True)
row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD")
return
doc_count = len(ObjectDocumentData.data["documents"])
row = self.layout.row(align=True)
if self.props.breadcrumbs:
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
row.label(text=DocumentData.data["parent_document"])
row.label(text="{} Documents Assigned".format(doc_count), icon="FILE")
if self.props.is_object_editing:
row.operator("bim.disable_object_document_editing_ui", text="", icon="CANCEL")
else:
row.operator("bim.load_object_documents", text="", icon="IMPORT")
if not self.props.is_object_editing and doc_count == 0:
row = self.layout.row()
row.label(text="No documents assigned", icon="INFO")
return
if self.props.is_object_editing:
self.draw_add_ui()
if doc_count > 0:
box = self.layout.box()
row = box.row(align=True)
row.label(text="Assigned Documents", icon="OUTLINER_OB_EMPTY")
box.template_list(
"BIM_UL_assigned_documents",
"",
self.props,
"assigned_documents",
self.props,
"active_assigned_document_index",
)
def draw_add_ui(self):
if self.props.is_object_editing:
row = self.layout.row(align=True)
row.alignment = "RIGHT"
if self.props.documents and self.props.active_document_index < len(self.props.documents):
document = self.props.documents[self.props.active_document_index]
if not document.is_information:
row.operator("bim.assign_document", text="", icon="ADD").document = document.ifc_definition_id
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
if self.props.documents and self.props.active_document_index < len(self.props.documents):
document = self.props.documents[self.props.active_document_index]
self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
assigned_doc_ids = []
for doc in ObjectDocumentData.data["documents"]:
assigned_doc_ids.append(doc["id"])
if document.ifc_definition_id not in assigned_doc_ids:
doc_op = row.operator("bim.assign_document", text="", icon="BRUSH_DATA")
doc_op.document = document.ifc_definition_id # Pass the current document's ID
else:
row.label(text="", icon="CHECKMARK")
self.layout.template_list(
"BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index"
)
class BIM_UL_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
indent_depth = 0
if item.ifc_definition_id != -1:
if item.tree_depth > 1:
indent_depth = item.tree_depth - 1
for i in range(indent_depth):
row.label(text="", icon="BLANK1")
if item.ifc_definition_id == -1:
row.label(text="", icon="OUTLINER_COLLECTION")
row.label(text=item.name)
return
if item.is_information and item.has_children:
op = row.operator(
"bim.toggle_document", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
)
op.document = item.ifc_definition_id
op.option = "Collapse" if item.is_expanded else "Expand"
elif item.is_information:
row.label(text="", icon="BLANK1")
if item.is_information:
row.label(text="", icon="FILE")
text = " - ".join([x for x in [item.name, item.location] if x])
else:
row.label(text="", icon="FILE_HIDDEN")
text = " - ".join([x for x in [item.description, item.location] if x])
split1 = row.split(factor=0.1)
split1.prop(item, "identification", text="", emboss=False)
split2 = split1.split(factor=0.8)
split2.label(text=text)
if item.location:
if item.location.lower().endswith(".ifc"):
row.operator("bim.open_ifc_document", icon="HIDE_OFF", text="").uri = item.location
row.operator("bim.open_uri", icon="URL", text="").uri = item.location
class BIM_UL_document_objects(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.prop(item, "name", text="", emboss=False, icon="OBJECT_DATA")
row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name
props = tool.Document.get_document_props()
if props.documents and props.active_document_index < len(props.documents):
document = props.documents[props.active_document_index]
op = row.operator("bim.unassign_document", text="", icon="X")
op.document = document.ifc_definition_id
op.obj = item.name
class BIM_UL_assigned_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
if item.is_information:
op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
op.document = item.ifc_definition_id
row.label(text="", icon="FILE")
else:
row.label(text="", icon="BLANK1")
row.label(text="", icon="FILE_HIDDEN")
split1 = row.split(factor=0.1)
# split1.label(text=item.identification)
split1.prop(item, "identification", text="", emboss=False)
split2 = split1.split(factor=0.9)
split2.prop(item, "name", text="", emboss=False)
split1 = row.split(factor=0.2)
split1.label(text=item.identification or "")
split2 = split1.split(factor=1.0)
if item.is_information:
split2.label(text=item.name or "Unnamed")
else:
split2.label(text=item.description or "No Description")
if item.location:
if item.location.lower().endswith(".ifc"):
row.operator("bim.open_ifc_document", icon="HIDE_OFF", text="").uri = item.location
row.operator("bim.open_uri", icon="URL", text="").uri = item.location
op = row.operator("bim.unassign_document", text="", icon="X")
op.document = item.ifc_definition_id
def add_object_documents_context_menu(self, context):
if not context.active_object:
return
if not tool.Blender.get_ifc_definition_id(context.active_object):
return
self.layout.separator()
self.layout.menu("BIM_MT_object_documents_context_menu", icon="FILE")
class BIM_MT_object_documents_context_menu(bpy.types.Menu):
bl_idname = "BIM_MT_object_documents_context_menu"
bl_label = "Documents"
def draw(self, context):
layout = self.layout
if not context.selected_objects:
layout.label(text="No documents", icon="INFO")
return
if len(context.selected_objects) > 1:
layout.label(text="Select a single object to see its referenced documents", icon="INFO")
return
obj = context.active_object
if not obj or not tool.Blender.get_ifc_definition_id(obj):
layout.label(text="No documents", icon="INFO")
return
if not ObjectDocumentData.is_loaded:
ObjectDocumentData.load()
if not ObjectDocumentData.data["documents"]:
layout.label(text="No Documents", icon="FILE")
else:
for document in ObjectDocumentData.data["documents"]:
row = layout.row(align=True)
with_ifc_icon = document["location"] and document["location"].lower().endswith(".ifc")
with_url_icon = bool(document["location"])
if with_ifc_icon:
row.operator("bim.open_ifc_document", icon="HIDE_OFF", text="").uri = document["location"]
else:
row.label(text="", icon="BLANK1")
if with_url_icon:
row.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
else:
row.label(text="", icon="BLANK1")
doc_entity = None
if "id" in document:
doc_entity = tool.Ifc.get().by_id(document["id"])
if doc_entity and doc_entity.is_a("IfcDocumentReference"):
display_text = document.get("description") or ""
else:
display_text = document.get("name") or ""
row.label(text=f"{document['identification'] or ''}: {display_text}")
+58 -44
View File
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
import bpy
import json
import ifcopenshell
import bonsai.tool as tool
@@ -28,28 +29,22 @@ if TYPE_CHECKING:
def load_project_documents(document: tool.Document) -> None:
document.clear_document_tree()
document.import_project_documents()
document.clear_breadcrumbs()
document.enable_editing_ui()
def load_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
document_tool.clear_document_tree()
document_tool.import_subdocuments(document)
document_tool.import_references(document)
try:
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.disable_editing_document()
document_tool.add_breadcrumb(document)
def load_parent_document(document: tool.Document) -> None:
document.clear_document_tree()
document.remove_latest_breadcrumb()
parent = document.get_active_breadcrumb()
if parent:
document.import_subdocuments(parent)
document.import_references(parent)
document.disable_editing_document()
else:
document.import_project_documents()
def disable_document_editing_ui(document: tool.Document) -> None:
@@ -58,33 +53,62 @@ def disable_document_editing_ui(document: tool.Document) -> None:
def enable_editing_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
props = document_tool.get_document_props()
props.active_document_id = document.id()
props.is_document_editing = True
document_tool.import_document_attributes(document)
document_tool.set_active_document(document)
def disable_editing_document(document: tool.Document) -> None:
document.disable_editing_document()
props = document.get_document_props()
props.active_document_id = 0
props.is_document_editing = False
props.document_attributes.clear()
def add_information(ifc: tool.Ifc, document: tool.Document) -> None:
document.clear_document_tree()
parent = document.get_active_breadcrumb()
def add_information(ifc: tool.Ifc, document_tool: tool.Document, parent=None) -> ifcopenshell.entity_instance:
document_tool.clear_document_tree()
if parent is None and ifc.get().by_type("IfcProject"):
parent = ifc.get().by_type("IfcProject")[0]
information = ifc.run("document.add_information", parent=parent)
ifc.run("document.add_reference", information=information)
if parent:
document.import_subdocuments(parent)
document.import_references(parent)
else:
document.import_project_documents()
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:
expanded_docs.append(parent.id())
bpy.context.scene.ExpandedDocuments.json_string = json.dumps(expanded_docs)
document_tool.import_project_documents()
def add_reference(ifc: tool.Ifc, document: tool.Document) -> None:
parent = document.get_active_breadcrumb()
assert parent
ifc.run("document.add_reference", information=parent)
document.clear_document_tree()
document.import_subdocuments(parent)
document.import_references(parent)
props = document.get_document_props()
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:
reference = ifc.run("document.add_reference", information=parent)
reference.Location = ""
try:
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()
def edit_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
@@ -95,12 +119,7 @@ def edit_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcopen
ifc.run("document.edit_reference", reference=document, attributes=attributes)
document_tool.disable_editing_document()
document_tool.clear_document_tree()
parent = document_tool.get_active_breadcrumb()
if parent:
document_tool.import_subdocuments(parent)
document_tool.import_references(parent)
else:
document_tool.import_project_documents()
document_tool.import_project_documents()
def remove_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
@@ -109,12 +128,7 @@ def remove_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcop
ifc.run("document.remove_information", information=document)
else:
ifc.run("document.remove_reference", reference=document)
parent = document_tool.get_active_breadcrumb()
if parent:
document_tool.import_subdocuments(parent)
document_tool.import_references(parent)
else:
document_tool.import_project_documents()
document_tool.import_project_documents()
def assign_document(
-6
View File
@@ -287,20 +287,14 @@ class Debug:
@interface
class Document:
def add_breadcrumb(cls, document): pass
def clear_breadcrumbs(cls): pass
def clear_document_tree(cls): pass
def disable_editing_document(cls): pass
def disable_editing_ui(cls): pass
def enable_editing_ui(cls): pass
def export_document_attributes(cls): pass
def get_active_breadcrumb(cls): pass
def import_document_attributes(cls, document): pass
def import_project_documents(cls): pass
def import_references(cls, document): pass
def import_subdocuments(cls, document): pass
def is_document_information(cls, document): pass
def remove_latest_breadcrumb(cls): pass
def set_active_document(cls, document): pass
+127 -54
View File
@@ -18,6 +18,7 @@
from __future__ import annotations
import bpy
import json
import ifcopenshell.util.system
import bonsai.bim.helper
import bonsai.core.tool
@@ -33,17 +34,6 @@ class Document(bonsai.core.tool.Document):
def get_document_props(cls) -> BIMDocumentProperties:
return bpy.context.scene.BIMDocumentProperties
@classmethod
def add_breadcrumb(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props()
new = props.breadcrumbs.add()
new.name = str(document.id())
@classmethod
def clear_breadcrumbs(cls) -> None:
props = cls.get_document_props()
props.breadcrumbs.clear()
@classmethod
def clear_document_tree(cls) -> None:
props = cls.get_document_props()
@@ -69,18 +59,15 @@ class Document(bonsai.core.tool.Document):
props = cls.get_document_props()
return bonsai.bim.helper.export_attributes(props.document_attributes)
@classmethod
def get_active_breadcrumb(cls) -> Union[ifcopenshell.entity_instance, None]:
props = cls.get_document_props()
if len(props.breadcrumbs):
return tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
@classmethod
def import_document_attributes(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props()
props.document_attributes.clear()
def callback(attr_name: str, _, data: dict[str, Any]) -> Union[bool, None]:
def callback(attr_name: str, attr_value: Any, data: dict[str, Any]) -> Union[bool, None]:
if attr_name == "Location" and attr_value is None:
data[attr_name] = ""
return True
if attr_name != "Name":
return None # Proceed normally
@@ -100,52 +87,138 @@ class Document(bonsai.core.tool.Document):
def import_project_documents(cls) -> None:
props = cls.get_document_props()
props.documents.clear()
project = tool.Ifc.get().by_type("IfcProject")[0]
file = tool.Ifc.get()
try:
expanded_documents = json.loads(bpy.context.scene.ExpandedDocuments.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_documents = []
project = file.by_type("IfcProject")[0] if file.by_type("IfcProject") else None
if not project:
return
document_children = {}
for rel in file.by_type("IfcDocumentInformationRelationship"):
parent_id = rel.RelatingDocument.id()
if parent_id not in document_children:
document_children[parent_id] = []
for child in rel.RelatedDocuments:
document_children[parent_id].append(child)
is_ifc2x3 = file.schema == "IFC2X3"
if is_ifc2x3:
for ref in file.by_type("IfcDocumentReference"):
if ref.ReferenceToDocument:
parent = ref.ReferenceToDocument[0]
parent_id = parent.id()
if parent_id not in document_children:
document_children[parent_id] = []
document_children[parent_id].append(ref)
else:
for ref in file.by_type("IfcDocumentReference"):
if hasattr(ref, "ReferencedDocument") and ref.ReferencedDocument:
parent = ref.ReferencedDocument
parent_id = parent.id()
if parent_id not in document_children:
document_children[parent_id] = []
document_children[parent_id].append(ref)
root_documents = []
for rel in project.HasAssociations or []:
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation"):
element = rel.RelatingDocument
new = props.documents.add()
new.ifc_definition_id = element.id()
new["name"] = element.Name or "Unnamed"
new.is_information = True
new["identification"] = cls.get_document_information_id(element)
is_child = False
for children in document_children.values():
if rel.RelatingDocument in children:
is_child = True
break
if not is_child:
root_documents.append(rel.RelatingDocument)
root = props.documents.add()
root.ifc_definition_id = -1
root.is_information = True
root.name = f"Project Documents ({project.Name or 'Unnamed Project'})"
root.identification = ""
root.location = ""
root.tree_depth = 0
root.has_children = bool(root_documents)
root_id = -project.id()
root.is_expanded = root_id not in expanded_documents
if root.is_expanded:
root_documents.sort(
key=lambda doc: ((cls.get_document_information_id(doc) or "").lower(), (doc.Name or "").lower())
)
for doc in root_documents:
cls._process_document(doc, props, document_children, expanded_documents, 1)
@classmethod
def import_references(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props()
is_ifc2x3 = tool.Ifc.get_schema() == "IFC2X3"
references = cls.get_document_references(document)
for element in references:
new = props.documents.add()
new.ifc_definition_id = element.id()
# Use Description + Location instead of Name as IFC has a restriction
# for IfcDocumentReference to have Name only if it has no ReferencedDocument.
name = " - ".join([x for x in [element.Description, element.Location] if x])
new["name"] = name or "Unnamed"
new["identification"] = cls.get_external_reference_id(element)
new.is_information = False
def _process_document(cls, document, props, document_children, expanded_documents, depth):
new = props.documents.add()
new.ifc_definition_id = document.id()
new.is_information = document.is_a("IfcDocumentInformation")
new.tree_depth = depth
@classmethod
def import_subdocuments(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props()
if document.IsPointer:
for element in document.IsPointer[0].RelatedDocuments or []:
new = props.documents.add()
new.ifc_definition_id = element.id()
new["name"] = element.Name or "Unnamed"
new.is_information = True
new["identification"] = cls.get_document_information_id(element) or "*"
file = document.file
if new.is_information:
new.name = document.Name or "Unnamed"
new.identification = cls.get_document_information_id(document) or ""
new.location = document.Location or ""
else:
new.name = document.Name or ""
new.identification = cls.get_external_reference_id(document) or ""
new.description = document.Description or ""
new.location = document.Location or ""
if not new.is_information:
if file.schema == "IFC2X3":
if document.ReferenceToDocument:
doc_info = document.ReferenceToDocument[0]
if not new.name:
new.name = doc_info.Name or ""
new.location = new.location or ""
else:
if hasattr(document, "ReferencedDocument") and document.ReferencedDocument:
doc_info = document.ReferencedDocument
if not new.name:
new.name = doc_info.Name or ""
new.location = new.location or ""
doc_id = document.id()
has_children = doc_id in document_children and bool(document_children[doc_id])
new.has_children = has_children
new.is_expanded = doc_id in expanded_documents
if has_children and new.is_expanded:
children = document_children[doc_id]
children.sort(
key=lambda doc: (
doc.is_a("IfcDocumentInformation"),
(
cls.get_document_information_id(doc)
if doc.is_a("IfcDocumentInformation")
else cls.get_external_reference_id(doc) or ""
).lower(),
(doc.Name or "").lower(),
),
reverse=True,
)
for child in children:
cls._process_document(child, props, document_children, expanded_documents, depth + 1)
@classmethod
def is_document_information(cls, document: ifcopenshell.entity_instance) -> bool:
return document.is_a("IfcDocumentInformation")
@classmethod
def remove_latest_breadcrumb(cls) -> None:
props = cls.get_document_props()
if len(props.breadcrumbs):
props.breadcrumbs.remove(len(props.breadcrumbs) - 1)
@classmethod
def set_active_document(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props()
@@ -14,15 +14,6 @@ Scenario: Load document
When I press "bim.load_document(document={information})"
Then nothing happens
Scenario: Load parent document
Given an empty IFC project
And I press "bim.load_project_documents"
And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
When I press "bim.load_parent_document"
Then nothing happens
Scenario: Disable document editing UI
Given an empty IFC project
And I press "bim.load_project_documents"
-18
View File
@@ -25,7 +25,6 @@ class TestLoadProjectDocuments:
def test_run(self, document):
document.clear_document_tree().should_be_called()
document.import_project_documents().should_be_called()
document.clear_breadcrumbs().should_be_called()
document.enable_editing_ui().should_be_called()
subject.load_project_documents(document)
@@ -33,8 +32,6 @@ class TestLoadProjectDocuments:
class TestLoadDocument:
def test_run(self, document):
document.clear_document_tree().should_be_called()
document.import_subdocuments("document").should_be_called()
document.import_references("document").should_be_called()
document.disable_editing_document().should_be_called()
document.add_breadcrumb("document").should_be_called()
subject.load_document(document, document="document")
@@ -63,7 +60,6 @@ class TestDisableEditingDocument:
class TestAddInformation:
def test_add_and_reload_tree_at_project_root(self, ifc, document):
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None)
ifc.run("document.add_information", parent=None).should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called()
document.import_project_documents().should_be_called()
@@ -71,21 +67,15 @@ class TestAddInformation:
def test_add_and_reload_tree_at_current_parent(self, ifc, document):
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent")
ifc.run("document.add_information", parent="parent").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called()
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.add_information(ifc, document)
class TestAddReference:
def test_run(self, ifc, document):
document.get_active_breadcrumb().should_be_called().will_return("parent")
ifc.run("document.add_reference", information="parent").should_be_called()
document.clear_document_tree().should_be_called()
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.add_reference(ifc, document)
@@ -96,7 +86,6 @@ class TestEditDocument:
ifc.run("document.edit_information", information="document", attributes="attributes").should_be_called()
document.disable_editing_document().should_be_called()
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None)
document.import_project_documents().should_be_called()
subject.edit_document(ifc, document, document="document")
@@ -106,9 +95,6 @@ class TestEditDocument:
ifc.run("document.edit_reference", reference="document", attributes="attributes").should_be_called()
document.disable_editing_document().should_be_called()
document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent")
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.edit_document(ifc, document, document="document")
@@ -117,7 +103,6 @@ class TestRemoveDocument:
document.clear_document_tree().should_be_called()
document.is_document_information("document").should_be_called().will_return(True)
ifc.run("document.remove_information", information="document").should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None)
document.import_project_documents().should_be_called()
subject.remove_document(ifc, document, document="document")
@@ -125,9 +110,6 @@ class TestRemoveDocument:
document.clear_document_tree().should_be_called()
document.is_document_information("document").should_be_called().will_return(False)
ifc.run("document.remove_reference", reference="document").should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent")
document.import_subdocuments("parent").should_be_called()
document.import_references("parent").should_be_called()
subject.remove_document(ifc, document, document="document")
-52
View File
@@ -31,24 +31,6 @@ class TestImplementsTool(NewFile):
assert isinstance(subject(), bonsai.core.tool.Document)
class TestAddBreadcrumb(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation()
subject.add_breadcrumb(document)
props = tool.Document.get_document_props()
assert props.breadcrumbs[0].name == str(document.id())
class TestClearBreadcrumbs(NewFile):
def test_run(self):
props = tool.Document.get_document_props()
props.breadcrumbs.add()
subject.clear_breadcrumbs()
assert len(props.breadcrumbs) == 0
class TestClearDocumentTree(NewFile):
def test_run(self):
props = tool.Document.get_document_props()
@@ -103,15 +85,6 @@ class TestExportDocumentAttributes(NewFile):
}
class TestGetActiveBreadcrumb(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
document = ifc.createIfcDocumentInformation()
subject.add_breadcrumb(document)
assert subject.get_active_breadcrumb() == document
class TestImportDocumentAttributes(NewFile):
def test_importing_information(self):
ifc = ifcopenshell.file()
@@ -197,22 +170,6 @@ class TestImportReferences(NewFile):
assert props.documents[0].is_information is False
class TestImportSubdocuments(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
ifc.createIfcProject()
document = ifcopenshell.api.document.add_information(ifc)
subdocument = ifcopenshell.api.document.add_information(ifc, parent=document)
subject.import_subdocuments(document)
props = tool.Document.get_document_props()
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == subdocument.id()
assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "X"
assert props.documents[0].is_information is True
class TestIsDocumentInformation(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
@@ -222,15 +179,6 @@ class TestIsDocumentInformation(NewFile):
assert subject.is_document_information(reference) is False
class TestRemoveLatestBreadcrumb(NewFile):
def test_run(self):
props = tool.Document.get_document_props()
props.breadcrumbs.add()
props.breadcrumbs.add()
subject.remove_latest_breadcrumb()
assert len(props.breadcrumbs) == 1
class TestSetActiveDocument(NewFile):
def test_run(self):
ifc = ifcopenshell.file()