Merge pull request #6825 from falken10vdl/UI_documents_tree

UI documents tree
This commit is contained in:
falken10vdl
2026-01-10 11:43:42 +01:00
committed by GitHub
11 changed files with 902 additions and 439 deletions
@@ -24,26 +24,33 @@ classes = (
operator.AddInformation, operator.AddInformation,
operator.AssignDocument, operator.AssignDocument,
operator.DisableDocumentEditingUI, operator.DisableDocumentEditingUI,
operator.DisableObjectDocumentEditingUI,
operator.DisableEditingDocument, operator.DisableEditingDocument,
operator.EditDocument, operator.EditDocument,
operator.EnableEditingDocument, operator.EnableEditingDocument,
operator.LoadDocument, operator.LoadObjectDocuments,
operator.LoadParentDocument,
operator.LoadProjectDocuments, operator.LoadProjectDocuments,
operator.RemoveDocument, operator.RemoveDocument,
operator.SelectDocumentObjects, operator.SelectDocumentObjects,
operator.ToggleDocument,
operator.UnassignDocument, operator.UnassignDocument,
operator.OpenIFCDocument,
prop.Document, prop.Document,
prop.DocumentObject,
prop.BIMDocumentProperties, prop.BIMDocumentProperties,
ui.BIM_PT_documents, ui.BIM_PT_documents,
ui.BIM_PT_object_documents, ui.BIM_PT_object_documents,
ui.BIM_UL_documents, ui.BIM_UL_documents,
ui.BIM_UL_document_objects,
ui.BIM_MT_object_documents_context_menu,
) )
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.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
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.add_object_documents_context_menu)
+90 -40
View File
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.util.schema import ifcopenshell.util.schema
import bonsai.tool as tool import bonsai.tool as tool
from natsort import natsorted
def refresh(): def refresh():
@@ -35,30 +36,50 @@ class DocumentData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = { cls.data = {
"total_information": cls.total_information(), "total_documents": cls.total_documents(),
"parent_document": cls.parent_document(), "document_objects": cls.document_objects(),
} }
cls.is_loaded = True cls.is_loaded = True
@classmethod @classmethod
def total_information(cls): def total_documents(cls):
return len( file = tool.Ifc.get()
[ return len(file.by_type("IfcDocumentInformation")) + len(file.by_type("IfcDocumentReference"))
rel
for rel in tool.Ifc.get().by_type("IfcProject")[0].HasAssociations or []
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation")
]
)
@classmethod @classmethod
def parent_document(cls): 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):
if not cls.is_loaded:
cls.load()
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
if len(props.breadcrumbs): props.document_objects.clear()
parent = tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name))
if tool.Ifc.get_schema() == "IFC2X3": if document_id not in cls.data["document_objects"]:
return str(parent.DocumentId) return
return str(parent.Identification)
return "" sorted_objects = natsorted(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: class ObjectDocumentData:
@@ -72,6 +93,18 @@ class ObjectDocumentData:
} }
cls.is_loaded = True cls.is_loaded = True
@staticmethod
def convert_to_file_uri(location: str) -> str:
if not location:
return ""
uri = location
if not uri.startswith("file://"):
if not os.path.isabs(uri):
uri = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), uri))
uri = "file://" + uri
return uri
@classmethod @classmethod
def documents(cls): def documents(cls):
results = [] results = []
@@ -80,44 +113,61 @@ 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"):
if not rel.RelatingDocument.is_a("IfcDocumentReference"): relating_document = rel.RelatingDocument
is_information = relating_document.is_a("IfcDocumentInformation")
is_reference = relating_document.is_a("IfcDocumentReference")
if not (is_information or is_reference):
continue continue
name = rel.RelatingDocument.Name name = relating_document.Name
if tool.Ifc.get_schema() == "IFC2X3": location = None
if not name and rel.RelatingDocument.ReferenceToDocument: identification = None
name = rel.RelatingDocument.ReferenceToDocument[0].Name
identification = rel.RelatingDocument.ItemReference if is_information:
if not identification and rel.RelatingDocument.ReferenceToDocument: if tool.Ifc.get_schema() == "IFC2X3":
identification = rel.RelatingDocument.ReferenceToDocument[0].DocumentId identification = relating_document.DocumentId
else:
identification = relating_document.Identification
location = rel.RelatingDocument.Location location = getattr(relating_document, "Location", None)
description = getattr(relating_document, "Description", "No description")
else: else:
if not name and rel.RelatingDocument.ReferencedDocument: description = relating_document.Description
name = rel.RelatingDocument.ReferencedDocument.Name if tool.Ifc.get_schema() == "IFC2X3":
reference_to_document = relating_document.ReferenceToDocument
if not name and reference_to_document:
name = reference_to_document[0].Name
identification = rel.RelatingDocument.Identification identification = relating_document.ItemReference
if not identification and rel.RelatingDocument.ReferencedDocument: if not identification and reference_to_document:
identification = rel.RelatingDocument.ReferencedDocument.Identification identification = reference_to_document[0].DocumentId
location = relating_document.Location
else:
referenced_document = relating_document.ReferencedDocument
if not name and referenced_document:
name = referenced_document.Name
location = rel.RelatingDocument.Location identification = relating_document.Identification
if location is None and rel.RelatingDocument.ReferencedDocument: if not identification and referenced_document:
location = rel.RelatingDocument.ReferencedDocument.Location identification = referenced_document.Identification
if location: location = relating_document.Location
if not "://" in location: if location is None and referenced_document:
if not os.path.isabs(location): location = referenced_document.Location
location = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), location))
location = "file://" + location location = cls.convert_to_file_uri(location) if location else None
results.append( results.append(
{ {
"id": rel.RelatingDocument.id(), "id": relating_document.id(),
"identification": identification, "identification": identification,
"name": name, "name": name,
"location": location, "location": location,
"is_information": is_information,
"description": description,
} }
) )
return results return results
+171 -37
View File
@@ -18,12 +18,10 @@
import bpy import bpy
import json import json
import ifcopenshell.api
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import bonsai.bim.handler import bonsai.bim.handler
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.document as core import bonsai.core.document as core
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
class LoadProjectDocuments(bpy.types.Operator): class LoadProjectDocuments(bpy.types.Operator):
@@ -33,30 +31,6 @@ class LoadProjectDocuments(bpy.types.Operator):
def execute(self, context): def execute(self, context):
core.load_project_documents(tool.Document) core.load_project_documents(tool.Document)
bonsai.bim.handler.refresh_ui_data() # Update breadcrumbs data.
return {"FINISHED"}
class LoadDocument(bpy.types.Operator):
bl_idname = "bim.load_document"
bl_label = "Load Document"
bl_options = {"REGISTER", "UNDO"}
document: bpy.props.IntProperty()
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.
return {"FINISHED"} return {"FINISHED"}
@@ -70,6 +44,16 @@ class DisableDocumentEditingUI(bpy.types.Operator):
return {"FINISHED"} 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):
core.disable_object_document_editing_ui(tool.Document)
return {"FINISHED"}
class EnableEditingDocument(bpy.types.Operator): class EnableEditingDocument(bpy.types.Operator):
bl_idname = "bim.enable_editing_document" bl_idname = "bim.enable_editing_document"
bl_label = "Enable Editing Document" bl_label = "Enable Editing Document"
@@ -77,7 +61,7 @@ class EnableEditingDocument(bpy.types.Operator):
document: bpy.props.IntProperty() document: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
core.enable_editing_document(tool.Document, document=tool.Ifc.get().by_id(self.document)) core.enable_editing_document(tool.Document, ifc_document=tool.Ifc.get().by_id(self.document))
return {"FINISHED"} return {"FINISHED"}
@@ -97,7 +81,35 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
core.add_information(tool.Ifc, tool.Document) props = tool.Document.get_document_props()
parent = None
if props.active_document:
selected_document = props.active_document
if selected_document.document_type == "PROJECT":
parent = tool.Ifc.get().by_type("IfcProject")[0]
elif selected_document.document_type == "INFORMATION":
parent = tool.Ifc.get().by_id(selected_document.ifc_definition_id)
elif selected_document.document_type == "REFERENCE":
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]
core.add_information(tool.Ifc, tool.Document, parent)
expanded_docs = []
try:
expanded_docs = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError):
pass
if parent and parent.is_a("IfcDocumentInformation"):
if parent.id() not in expanded_docs:
expanded_docs.append(parent.id())
props.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents()
class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator): class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
@@ -106,7 +118,33 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
props = tool.Document.get_document_props()
if not props.active_document:
self.report({"ERROR"}, "No document selected")
return {"CANCELLED"}
selected_document = props.active_document
if selected_document.document_type != "INFORMATION":
self.report({"ERROR"}, "Cannot add a reference to a document that is not an information 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) core.add_reference(tool.Ifc, tool.Document)
expanded_docs = []
try:
expanded_docs = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError):
pass
if parent.id() not in expanded_docs:
expanded_docs.append(parent.id())
props.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents()
class EditDocument(bpy.types.Operator, tool.Ifc.Operator): class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -116,7 +154,9 @@ class EditDocument(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()
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, ifc_document=tool.Ifc.get().by_id(props.active_document_id))
props.active_document_id = 0
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator): class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -126,7 +166,7 @@ class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
document: bpy.props.IntProperty() document: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document)) core.remove_document(tool.Ifc, tool.Document, ifc_document=tool.Ifc.get().by_id(self.document))
class AssignDocument(bpy.types.Operator, tool.Ifc.Operator): class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -138,12 +178,15 @@ class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
document: bpy.props.IntProperty() document: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
document = tool.Ifc.get().by_id(self.document)
objs = [bpy.data.objects[self.obj]] if self.obj else tool.Blender.get_selected_objects() objs = [bpy.data.objects[self.obj]] if self.obj else tool.Blender.get_selected_objects()
for obj in objs: for obj in objs:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if element: if element:
core.assign_document(tool.Ifc, product=element, document=document) core.assign_document(tool.Ifc, product=element, ifc_document=tool.Ifc.get().by_id(self.document))
tool.Document.update_document_objects(self.document)
ObjectDocumentData.load()
return {"FINISHED"}
class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator): class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -154,12 +197,25 @@ class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
document: bpy.props.IntProperty() document: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
document = tool.Ifc.get().by_id(self.document)
objs = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects() objs = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
for obj in objs: for obj in objs:
element = tool.Ifc.get_entity(obj) if obj:
if element: element = tool.Ifc.get_entity(obj)
core.unassign_document(tool.Ifc, product=element, document=document) if element:
core.unassign_document(tool.Ifc, product=element, ifc_document=tool.Ifc.get().by_id(self.document))
props = tool.Document.get_document_props()
active_document_id = None
if props.active_document:
active_document_id = props.active_document.ifc_definition_id
if active_document_id and active_document_id != self.document:
tool.Document.update_document_objects(active_document_id)
else:
tool.Document.update_document_objects()
ObjectDocumentData.load()
return {"FINISHED"}
class SelectDocumentObjects(bpy.types.Operator): class SelectDocumentObjects(bpy.types.Operator):
@@ -182,3 +238,81 @@ class SelectDocumentObjects(bpy.types.Operator):
i += 1 i += 1
self.report({"INFO"}, f"{i} objects selected.") self.report({"INFO"}, f"{i} objects selected.")
return {"FINISHED"} 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):
core.load_project_documents(tool.Document)
props = tool.Document.get_document_props()
props.is_object_editing = True
ObjectDocumentData.load()
return {"FINISHED"}
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):
import subprocess
import os
if not self.uri or not self.uri.lower().startswith("file://"):
self.report({"ERROR"}, "Only local file:// URIs are supported")
return {"CANCELLED"}
filepath = self.uri[7:]
if not os.path.exists(filepath):
self.report({"ERROR"}, f"File not found: {filepath}")
return {"CANCELLED"}
blender_path = bpy.app.binary_path
args = [
blender_path,
"--python-expr",
"import bpy; bpy.ops.bim.load_project(filepath='{}')".format(filepath),
]
subprocess.Popen(args)
self.report({"INFO"}, f"Opening {filepath} in a new Blender instance")
return {"FINISHED"}
class ToggleDocument(bpy.types.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 = []
props = tool.Document.get_document_props()
try:
expanded_documents = json.loads(props.json_string)
except (AttributeError, json.JSONDecodeError):
expanded_documents = []
document_id = self.document
document = tool.Ifc.get().by_id(document_id)
if 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)
props.json_string = json.dumps(expanded_documents)
bpy.ops.bim.load_project_documents()
return {"FINISHED"}
+50 -28
View File
@@ -1,24 +1,7 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 bpy
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.module.document.data import refresh
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -30,6 +13,7 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
from bonsai.bim.module.document.data import DocumentData
from typing import TYPE_CHECKING, Union from typing import TYPE_CHECKING, Union
@@ -49,18 +33,50 @@ def update_document_identification(self: "Document", context: bpy.types.Context)
tool.Document.set_external_reference_id(document, self.identification) tool.Document.set_external_reference_id(document, self.identification)
def update_active_document_index(self, context):
refresh()
if document := self.active_document:
if document.ifc_definition_id:
DocumentData.load_document_objects_into_props(document.ifc_definition_id)
class Document(PropertyGroup): class Document(PropertyGroup):
name: StringProperty(name="Name", update=update_document_name) name: StringProperty(name="Name")
identification: StringProperty(name="Identification", update=update_document_identification) identification: StringProperty(name="Identification")
is_information: BoolProperty( description: StringProperty(name="Description")
name="Is Information", ifc_definition_id: IntProperty(name="IFC Definition ID")
description="Whether element is IfcDocumentInformation, otherwise it's IfcDocumentReference.", 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)
document_type: EnumProperty(
name="Document Type",
items=[
("PROJECT", "Project", "Virtual project root node"),
("INFORMATION", "Information", "IfcDocumentInformation"),
("REFERENCE", "Reference", "IfcDocumentReference"),
],
default="INFORMATION",
) )
if TYPE_CHECKING:
name: str
identification: str
description: str
ifc_definition_id: int
location: str
tree_depth: int
has_children: bool
is_expanded: bool
document_type: str
class DocumentObject(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING: if TYPE_CHECKING:
identification: str name: str
is_information: bool
ifc_definition_id: int ifc_definition_id: int
@@ -68,17 +84,23 @@ class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute) document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
active_document_id: IntProperty(name="Active Document Id") active_document_id: IntProperty(name="Active Document Id")
documents: CollectionProperty(name="Documents", type=Document) documents: CollectionProperty(name="Documents", type=Document)
breadcrumbs: CollectionProperty(name="Breadcrumbs", type=StrProperty) active_document_index: IntProperty(name="Active Document Index", update=update_active_document_index)
active_document_index: IntProperty(name="Active Document Index")
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is 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")
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]
active_document_id: int active_document_id: int
documents: bpy.types.bpy_prop_collection_idprop[Document] documents: bpy.types.bpy_prop_collection_idprop[Document]
breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty]
active_document_index: int active_document_index: int
is_editing: bool is_editing: bool
is_object_editing: bool
document_objects: bpy.types.bpy_prop_collection_idprop[DocumentObject]
active_document_object_index: int
json_string: str
@property @property
def active_document(self) -> Union[Document, None]: def active_document(self) -> Union[Document, None]:
+225 -59
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool import bonsai.tool as tool
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from bonsai.bim.helper import draw_attributes from bonsai.bim.helper import draw_attributes
@@ -42,7 +43,8 @@ class BIM_PT_documents(Panel):
self.props = tool.Document.get_document_props() self.props = tool.Document.get_document_props()
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE") row.label(text="{} Documents found".format(DocumentData.data["total_documents"]), icon="FILE")
if self.props.is_editing: if self.props.is_editing:
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL") row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
else: else:
@@ -52,34 +54,54 @@ class BIM_PT_documents(Panel):
return return
row = self.layout.row(align=True) row = self.layout.row(align=True)
if self.props.breadcrumbs: row.alignment = "RIGHT"
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")
active_document = self.props.active_document if self.props.active_document_id > 0:
if self.props.active_document_id:
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")
elif active_document: else:
ifc_definition_id = active_document.ifc_definition_id if not self.props.active_document or self.props.active_document.document_type in ["INFORMATION", "PROJECT"]:
row.operator("bim.select_document_objects", text="", icon="RESTRICT_SELECT_OFF").document = ( row.operator("bim.add_information", text="", icon="ADD")
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
if self.props.active_document and (
self.props.active_document.document_type == "INFORMATION"
and self.props.active_document.document_type != "PROJECT"
):
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
if active_document.document_type != "PROJECT":
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") self.layout.template_list("BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index")
if self.props.active_document_id: if self.props.active_document_id > 0:
active_document = self.props.active_document
draw_attributes(self.props.document_attributes, self.layout) draw_attributes(self.props.document_attributes, self.layout)
if self.props.is_editing and self.props.active_document:
document = self.props.active_document
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): class BIM_PT_object_documents(Panel):
bl_label = "Documents" bl_label = "Documents"
@@ -102,65 +124,209 @@ class BIM_PT_object_documents(Panel):
return True return True
def draw(self, context): def draw(self, context):
obj = context.active_object
if not ObjectDocumentData.is_loaded: if not ObjectDocumentData.is_loaded:
ObjectDocumentData.load() ObjectDocumentData.load()
obj = context.active_object
self.oprops = tool.Blender.get_object_bim_props(obj) self.oprops = tool.Blender.get_object_bim_props(obj)
self.props = tool.Document.get_document_props() self.props = tool.Document.get_document_props()
self.file = tool.Ifc.get() self.file = tool.Ifc.get()
self.draw_add_ui() doc_count = len(ObjectDocumentData.data["documents"])
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
row = self.layout.row(align=True) row = self.layout.row(align=True)
if self.props.breadcrumbs: row.label(text="{} Documents Assigned".format(doc_count), icon="FILE")
row.operator("bim.load_parent_document", text="", icon="FRAME_PREV")
row.label(text=DocumentData.data["parent_document"]) if self.props.is_object_editing:
row.operator("bim.disable_object_document_editing_ui", text="", icon="CANCEL")
else: 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()
box = self.layout.box()
row = box.row(align=True)
row.label(text="Assigned Documents", icon="OUTLINER_OB_EMPTY")
if doc_count > 0:
col = box.column(align=True)
for document in ObjectDocumentData.data["documents"]:
row = col.row(align=True)
# Create a split layout to separate left and right sides
split = row.split(factor=0.7) # Adjust factor as needed (0.7 = 70% left, 30% right)
# Left side - Document identification and name
left_side = split.row(align=True)
left_side.alignment = "LEFT"
left_side.label(text=document["identification"] or "*", icon="FILE")
left_side.label(text=document["name"] or "Unnamed")
# Right side - Action buttons
right_side = split.row(align=True)
right_side.alignment = "RIGHT" # Align buttons to the right
if document["location"]:
if document["location"].lower().endswith(".ifc"):
right_side.operator("bim.open_ifc_document", icon="HIDE_OFF", text="").uri = document[
"location"
]
right_side.operator("bim.open_uri", icon="URL", text="").uri = document["location"]
right_side.operator("bim.unassign_document", text="", icon="X").document = document["id"]
def draw_add_ui(self):
if self.props.is_object_editing:
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
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")
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.document_type == "INFORMATION"
and document.document_type != "PROJECT"
and 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
elif document.ifc_definition_id in assigned_doc_ids:
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): class BIM_UL_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
indent_depth = 0
if item.is_information: if item.document_type != "PROJECT":
op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT") if item.tree_depth > 1:
op.document = item.ifc_definition_id indent_depth = item.tree_depth - 1
row.label(text="", icon="FILE")
else: for i in range(indent_depth):
row.label(text="", icon="BLANK1") row.label(text="", icon="BLANK1")
row.label(text="", icon="FILE_HIDDEN")
if item.document_type == "PROJECT":
row.label(text="", icon="OUTLINER_COLLECTION")
row.label(text=item.name)
return
if item.document_type == "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.document_type == "INFORMATION":
row.label(text="", icon="BLANK1")
if item.document_type == "INFORMATION":
row.label(text="", icon="FILE")
text = " - ".join([x for x in [item.location, item.description, item.name] if x])
else:
row.label(text="", icon="FILE_HIDDEN")
text = " - ".join([x for x in [item.location, item.description] if x])
split1 = row.split(factor=0.1) split1 = row.split(factor=0.1)
# split1.label(text=item.identification)
split1.prop(item, "identification", text="", emboss=False) split1.prop(item, "identification", text="", emboss=False)
split2 = split1.split(factor=0.9) split2 = split1.split(factor=0.8)
split2.prop(item, "name", text="", emboss=False) split2.label(text=text)
if item.location:
uri = ObjectDocumentData.convert_to_file_uri(item.location)
if item.location.lower().endswith(".ifc"):
row.operator("bim.open_ifc_document", icon="HIDE_OFF", text="").uri = uri
row.operator("bim.open_uri", icon="URL", text="").uri = uri
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.active_document:
document = props.active_document
op = row.operator("bim.unassign_document", text="", icon="X")
op.document = document.ifc_definition_id
op.obj = item.name
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}")
+48 -69
View File
@@ -20,7 +20,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
import bpy
import ifcopenshell import ifcopenshell
import bonsai.tool as tool import bonsai.tool as tool
@@ -28,102 +27,82 @@ if TYPE_CHECKING:
def load_project_documents(document: tool.Document) -> None: def load_project_documents(document: tool.Document) -> None:
document.clear_document_tree() document.clear_document_tree()
document.import_project_documents() document.import_project_documents()
document.clear_breadcrumbs()
document.enable_editing_ui() 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)
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: def disable_document_editing_ui(document: tool.Document) -> None:
document.disable_editing_ui() document.disable_editing_ui()
document.disable_editing_document() document.disable_editing_document()
def enable_editing_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None: def disable_object_document_editing_ui(document: tool.Document) -> None:
document_tool.import_document_attributes(document) document.disable_object_editing_ui()
document_tool.set_active_document(document)
def enable_editing_document(document: tool.Document, ifc_document: ifcopenshell.entity_instance) -> None:
document.set_active_document(ifc_document)
document.import_document_attributes(ifc_document)
def disable_editing_document(document: tool.Document) -> None: def disable_editing_document(document: tool.Document) -> None:
document.disable_editing_document() document.clear_active_document()
document.clear_document_attributes()
def add_information(ifc: tool.Ifc, document: tool.Document) -> None: def add_information(ifc: tool.Ifc, document: tool.Document, parent=None) -> ifcopenshell.entity_instance:
document.clear_document_tree() document.clear_document_tree()
parent = document.get_active_breadcrumb()
if parent is None:
parent = document.get_default_parent_for_information()
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:
document.import_subdocuments(parent) if document.is_document_information(parent):
document.import_references(parent) document.expand_document(parent)
else:
document.import_project_documents() document.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:
parent = document.get_active_breadcrumb() parent = document.get_selected_document_information()
assert parent
ifc.run("document.add_reference", information=parent) if parent:
reference = ifc.run("document.add_reference", information=parent)
reference.Location = ""
document.expand_document(parent)
document.import_project_documents()
def edit_document(ifc: tool.Ifc, document: tool.Document, ifc_document: ifcopenshell.entity_instance) -> None:
attributes = document.export_document_attributes()
if document.is_document_information(ifc_document):
ifc.run("document.edit_information", information=ifc_document, attributes=attributes)
else:
ifc.run("document.edit_reference", reference=ifc_document, attributes=attributes)
document.disable_editing_document()
document.clear_document_tree() document.clear_document_tree()
document.import_subdocuments(parent) document.import_project_documents()
document.import_references(parent)
def edit_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None: def remove_document(ifc: tool.Ifc, document: tool.Document, ifc_document: ifcopenshell.entity_instance) -> None:
attributes = document_tool.export_document_attributes() document.clear_document_tree()
if document_tool.is_document_information(document): if document.is_document_information(ifc_document):
ifc.run("document.edit_information", information=document, attributes=attributes) ifc.run("document.remove_information", information=ifc_document)
else: else:
ifc.run("document.edit_reference", reference=document, attributes=attributes) ifc.run("document.remove_reference", reference=ifc_document)
document_tool.disable_editing_document() document.import_project_documents()
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()
def remove_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
document_tool.clear_document_tree()
if document_tool.is_document_information(document):
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()
def assign_document( def assign_document(
ifc: tool.Ifc, product: ifcopenshell.entity_instance, document: ifcopenshell.entity_instance ifc: tool.Ifc, product: ifcopenshell.entity_instance, ifc_document: ifcopenshell.entity_instance
) -> None: ) -> None:
ifc.run("document.assign_document", products=[product], document=document) ifc.run("document.assign_document", products=[product], document=ifc_document)
def unassign_document( def unassign_document(
ifc: tool.Ifc, product: ifcopenshell.entity_instance, document: ifcopenshell.entity_instance ifc: tool.Ifc, product: ifcopenshell.entity_instance, ifc_document: ifcopenshell.entity_instance
) -> None: ) -> None:
ifc.run("document.unassign_document", products=[product], document=document) ifc.run("document.unassign_document", products=[product], document=ifc_document)
+14 -6
View File
@@ -287,21 +287,29 @@ class Debug:
@interface @interface
class Document: class Document:
def add_breadcrumb(cls, document): pass
def clear_breadcrumbs(cls): pass
def clear_document_tree(cls): pass def clear_document_tree(cls): pass
def disable_editing_document(cls): pass def disable_editing_document(cls): pass
def disable_object_editing_ui(cls): pass
def disable_editing_ui(cls): pass def disable_editing_ui(cls): pass
def enable_editing_ui(cls): pass def enable_editing_ui(cls): pass
def export_document_attributes(cls): pass def export_document_attributes(cls): pass
def get_active_breadcrumb(cls): pass
def import_document_attributes(cls, document): pass def import_document_attributes(cls, document): pass
def import_project_documents(cls): 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 is_document_information(cls, document): pass
def remove_latest_breadcrumb(cls): pass
def set_active_document(cls, document): pass def set_active_document(cls, document): pass
def clear_active_document(cls): pass
def clear_document_attributes(cls): pass
def expand_document(cls, document): pass
def get_default_parent_for_information(cls): pass
def get_selected_document_information(cls): pass
def get_document_information_id(cls, document): pass
def set_document_information_id(cls, document, value): pass
def get_external_reference_id(cls, reference): pass
def set_external_reference_id(cls, reference, value): pass
def get_document_references(cls, document): pass
def refresh_document_data(cls): pass
def load_document_objects_into_props(cls, document_id): pass
def update_document_objects(cls, document_id): pass
@interface @interface
+189 -54
View File
@@ -22,6 +22,8 @@ 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 natsort import natsorted
from typing import Any, Union, TYPE_CHECKING from typing import Any, Union, TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -33,17 +35,6 @@ class Document(bonsai.core.tool.Document):
def get_document_props(cls) -> BIMDocumentProperties: def get_document_props(cls) -> BIMDocumentProperties:
return bpy.context.scene.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 @classmethod
def clear_document_tree(cls) -> None: def clear_document_tree(cls) -> None:
props = cls.get_document_props() props = cls.get_document_props()
@@ -54,6 +45,11 @@ class Document(bonsai.core.tool.Document):
props = cls.get_document_props() props = cls.get_document_props()
props.active_document_id = 0 props.active_document_id = 0
@classmethod
def disable_object_editing_ui(cls) -> None:
props = cls.get_document_props()
props.is_object_editing = False
@classmethod @classmethod
def disable_editing_ui(cls) -> None: def disable_editing_ui(cls) -> None:
props = cls.get_document_props() props = cls.get_document_props()
@@ -69,18 +65,15 @@ class Document(bonsai.core.tool.Document):
props = cls.get_document_props() props = cls.get_document_props()
return bonsai.bim.helper.export_attributes(props.document_attributes) 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 @classmethod
def import_document_attributes(cls, document: ifcopenshell.entity_instance) -> None: def import_document_attributes(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props() props = cls.get_document_props()
props.document_attributes.clear() 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": if attr_name != "Name":
return None # Proceed normally return None # Proceed normally
@@ -100,52 +93,132 @@ class Document(bonsai.core.tool.Document):
def import_project_documents(cls) -> None: def import_project_documents(cls) -> None:
props = cls.get_document_props() props = cls.get_document_props()
props.documents.clear() props.documents.clear()
project = tool.Ifc.get().by_type("IfcProject")[0] file = tool.Ifc.get()
try:
expanded_documents = json.loads(props.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 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 []: for rel in project.HasAssociations or []:
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation"): if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument.is_a("IfcDocumentInformation"):
element = rel.RelatingDocument is_child = False
new = props.documents.add() for children in document_children.values():
new.ifc_definition_id = element.id() if rel.RelatingDocument in children:
new["name"] = element.Name or "Unnamed" is_child = True
new.is_information = True break
new["identification"] = cls.get_document_information_id(element)
if not is_child:
root_documents.append(rel.RelatingDocument)
root = props.documents.add()
root.ifc_definition_id = -project.id()
root.document_type = "PROJECT"
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 = natsorted(
root_documents, key=lambda doc: (cls.get_document_information_id(doc) or "", doc.Name or "")
)
for doc in root_documents:
cls._process_document(doc, props, document_children, expanded_documents, 1)
@classmethod @classmethod
def import_references(cls, document: ifcopenshell.entity_instance) -> None: def _process_document(cls, document, props, document_children, expanded_documents, depth):
props = cls.get_document_props() new = props.documents.add()
is_ifc2x3 = tool.Ifc.get_schema() == "IFC2X3" new.ifc_definition_id = document.id()
references = cls.get_document_references(document) new.document_type = "INFORMATION" if document.is_a("IfcDocumentInformation") else "REFERENCE"
for element in references: new.tree_depth = depth
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
@classmethod new.name = document.Name or ""
def import_subdocuments(cls, document: ifcopenshell.entity_instance) -> None: new.identification = (
props = cls.get_document_props() cls.get_document_information_id(document)
if document.IsPointer: if new.document_type == "INFORMATION"
for element in document.IsPointer[0].RelatedDocuments or []: else cls.get_external_reference_id(document)
new = props.documents.add() )
new.ifc_definition_id = element.id() new.identification = new.identification or ""
new["name"] = element.Name or "Unnamed" new.description = document.Description or ""
new.is_information = True new.location = document.Location or ""
new["identification"] = cls.get_document_information_id(element) or "*"
if new.document_type == "INFORMATION":
new.name = document.Name or "Unnamed"
elif new.document_type == "REFERENCE":
file = document.file
if file.schema == "IFC2X3":
if document.ReferenceToDocument and not new.name:
new.name = document.ReferenceToDocument[0].Name or ""
else:
if document.ReferencedDocument and not new.name:
new.name = document.ReferencedDocument.Name 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]
info_children = natsorted(
[d for d in children if d.is_a("IfcDocumentInformation")],
key=lambda doc: (cls.get_document_information_id(doc) or "", doc.Name or ""),
)
ref_children = natsorted(
[d for d in children if not d.is_a("IfcDocumentInformation")],
key=lambda doc: (cls.get_external_reference_id(doc) or "", doc.Description or doc.Name or ""),
)
for child in info_children + ref_children:
cls._process_document(child, props, document_children, expanded_documents, depth + 1)
@classmethod @classmethod
def is_document_information(cls, document: ifcopenshell.entity_instance) -> bool: def is_document_information(cls, document: ifcopenshell.entity_instance) -> bool:
return document.is_a("IfcDocumentInformation") 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 @classmethod
def set_active_document(cls, document: ifcopenshell.entity_instance) -> None: def set_active_document(cls, document: ifcopenshell.entity_instance) -> None:
props = cls.get_document_props() props = cls.get_document_props()
@@ -179,3 +252,65 @@ 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 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) -> Union[ifcopenshell.entity_instance, None]:
file = tool.Ifc.get()
projects = file.by_type("IfcProject")
return projects[0] if projects else None
@classmethod
def get_selected_document_information(cls) -> Union[ifcopenshell.entity_instance, None]:
props = cls.get_document_props()
if props.active_document and props.active_document.document_type == "INFORMATION":
file = tool.Ifc.get()
return file.by_id(props.active_document.ifc_definition_id)
return None
@classmethod
def refresh_document_data(cls) -> None:
import bonsai.bim.module.document.data as document_data
document_data.DocumentData.is_loaded = False
document_data.DocumentData.load()
@classmethod
def load_document_objects_into_props(cls, document_id: int) -> None:
import bonsai.bim.module.document.data as document_data
document_data.DocumentData.load_document_objects_into_props(document_id)
@classmethod
def update_document_objects(cls, document_id: Union[int, None] = None) -> None:
cls.refresh_document_data()
if document_id is None:
props = cls.get_document_props()
if props.active_document and props.active_document.ifc_definition_id > 0:
document_id = props.active_document.ifc_definition_id
if document_id:
cls.load_document_objects_into_props(document_id)
+12 -36
View File
@@ -6,23 +6,6 @@ Scenario: Load project documents
When I press "bim.load_project_documents" When I press "bim.load_project_documents"
Then nothing happens Then nothing happens
Scenario: Load 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()"
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 Scenario: Disable document editing UI
Given an empty IFC project Given an empty IFC project
And I press "bim.load_project_documents" And I press "bim.load_project_documents"
@@ -57,7 +40,8 @@ Scenario: Add document reference
And I press "bim.load_project_documents" And I press "bim.load_project_documents"
And I press "bim.add_information" And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()" And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})" And I press "bim.load_project_documents"
And I set "scene.BIMDocumentProperties.active_document_index" to "1"
When I press "bim.add_document_reference" When I press "bim.add_document_reference"
Then nothing happens Then nothing happens
@@ -83,16 +67,12 @@ Scenario: Assign document
And I press "bim.load_project_documents" And I press "bim.load_project_documents"
And I press "bim.add_information" And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()" And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
And I press "bim.add_document_reference"
And the variable "reference" is "{ifc}.by_type('IfcDocumentReference')[-1].id()"
And I add a cube And I add a cube
And the object "Cube" is selected And the object "Cube" is selected
And I look at the "Class" panel And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set the "Products" property to "IfcElement" And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set the "Class" property to "IfcWall" And I press "bim.assign_class"
And I click "Assign IFC Class" When I press "bim.assign_document(document={information})"
When I press "bim.assign_document(document={reference})"
Then nothing happens Then nothing happens
Scenario: Unassign document Scenario: Unassign document
@@ -100,15 +80,11 @@ Scenario: Unassign document
And I press "bim.load_project_documents" And I press "bim.load_project_documents"
And I press "bim.add_information" And I press "bim.add_information"
And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()" And the variable "information" is "{ifc}.by_type('IfcDocumentInformation')[-1].id()"
And I press "bim.load_document(document={information})"
And I press "bim.add_document_reference"
And the variable "reference" is "{ifc}.by_type('IfcDocumentReference')[-1].id()"
And I add a cube And I add a cube
And the object "Cube" is selected And the object "Cube" is selected
And I look at the "Class" panel And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
And I set the "Products" property to "IfcElement" And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I set the "Class" property to "IfcWall" And I press "bim.assign_class"
And I click "Assign IFC Class" And I press "bim.assign_document(document={information})"
And I press "bim.assign_document(document={reference})" When I press "bim.unassign_document(document={information})"
When I press "bim.unassign_document(document={reference})" Then nothing happens
Then nothing happens
+48 -40
View File
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bonsai.core.document as subject import bonsai.core.document as subject
from test.core.bootstrap import ifc, document from test.core.bootstrap import ifc, document
@@ -25,21 +24,10 @@ class TestLoadProjectDocuments:
def test_run(self, document): def test_run(self, document):
document.clear_document_tree().should_be_called() document.clear_document_tree().should_be_called()
document.import_project_documents().should_be_called() document.import_project_documents().should_be_called()
document.clear_breadcrumbs().should_be_called()
document.enable_editing_ui().should_be_called() document.enable_editing_ui().should_be_called()
subject.load_project_documents(document) subject.load_project_documents(document)
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")
class TestDisableDocumentEditingUi: class TestDisableDocumentEditingUi:
def test_run(self, document): def test_run(self, document):
document.disable_editing_ui().should_be_called() document.disable_editing_ui().should_be_called()
@@ -47,45 +35,71 @@ class TestDisableDocumentEditingUi:
subject.disable_document_editing_ui(document) subject.disable_document_editing_ui(document)
class TestDisableObjectDocumentEditingUi:
def test_run(self, document):
document.disable_object_editing_ui().should_be_called()
subject.disable_object_document_editing_ui(document)
class TestEnableEditingDocument: class TestEnableEditingDocument:
def test_run(self, document): def test_run(self, document):
document.import_document_attributes("document").should_be_called()
document.set_active_document("document").should_be_called() document.set_active_document("document").should_be_called()
subject.enable_editing_document(document, document="document") document.import_document_attributes("document").should_be_called()
subject.enable_editing_document(document, ifc_document="document")
class TestDisableEditingDocument: class TestDisableEditingDocument:
def test_run(self, document): def test_run(self, document):
document.disable_editing_document().should_be_called() document.clear_active_document().should_be_called()
document.clear_document_attributes().should_be_called()
subject.disable_editing_document(document) subject.disable_editing_document(document)
class TestAddInformation: class TestAddInformation:
def test_add_and_reload_tree_at_project_root(self, ifc, document): def test_add_and_reload_tree_at_project_root(self, ifc, document):
document.clear_document_tree().should_be_called() document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return(None) document.get_default_parent_for_information().should_be_called().will_return("default_parent")
ifc.run("document.add_information", parent=None).should_be_called().will_return("information") ifc.run("document.add_information", parent="default_parent").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called() ifc.run("document.add_reference", information="information").should_be_called()
document.is_document_information("default_parent").should_be_called().will_return(True)
document.expand_document("default_parent").should_be_called()
document.import_project_documents().should_be_called() document.import_project_documents().should_be_called()
subject.add_information(ifc, document) subject.add_information(ifc, document)
def test_add_and_reload_tree_at_current_parent(self, ifc, document): def test_add_and_reload_tree_at_current_parent(self, ifc, document):
document.clear_document_tree().should_be_called() 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_information", parent="parent").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called() ifc.run("document.add_reference", information="information").should_be_called()
document.import_subdocuments("parent").should_be_called() document.is_document_information("parent").should_be_called().will_return(True)
document.import_references("parent").should_be_called() document.expand_document("parent").should_be_called()
subject.add_information(ifc, document) document.import_project_documents().should_be_called()
subject.add_information(ifc, document, parent="parent")
def test_add_without_expanding_if_parent_is_not_information(self, ifc, document):
document.clear_document_tree().should_be_called()
ifc.run("document.add_information", parent="parent").should_be_called().will_return("information")
ifc.run("document.add_reference", information="information").should_be_called()
document.is_document_information("parent").should_be_called().will_return(False)
document.import_project_documents().should_be_called()
subject.add_information(ifc, document, parent="parent")
class TestAddReference: class TestAddReference:
def test_run(self, ifc, document): def test_run_with_selected_parent(self, ifc, document):
document.get_active_breadcrumb().should_be_called().will_return("parent") document.get_selected_document_information().should_be_called().will_return("parent")
ifc.run("document.add_reference", information="parent").should_be_called() ifc.run("document.add_reference", information="parent").should_be_called()
document.clear_document_tree().should_be_called() document.expand_document("parent").should_be_called()
document.import_subdocuments("parent").should_be_called() document.import_project_documents().should_be_called()
document.import_references("parent").should_be_called()
subject.add_reference(ifc, document)
def test_run_without_selected_parent(self, ifc, document):
document.get_selected_document_information().should_be_called().will_return(None)
document.import_project_documents().should_be_called()
subject.add_reference(ifc, document) subject.add_reference(ifc, document)
@@ -96,9 +110,8 @@ class TestEditDocument:
ifc.run("document.edit_information", information="document", attributes="attributes").should_be_called() ifc.run("document.edit_information", information="document", attributes="attributes").should_be_called()
document.disable_editing_document().should_be_called() document.disable_editing_document().should_be_called()
document.clear_document_tree().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() document.import_project_documents().should_be_called()
subject.edit_document(ifc, document, document="document") subject.edit_document(ifc, document, ifc_document="document")
def test_edit_reference(self, ifc, document): def test_edit_reference(self, ifc, document):
document.export_document_attributes().should_be_called().will_return("attributes") document.export_document_attributes().should_be_called().will_return("attributes")
@@ -106,10 +119,8 @@ class TestEditDocument:
ifc.run("document.edit_reference", reference="document", attributes="attributes").should_be_called() ifc.run("document.edit_reference", reference="document", attributes="attributes").should_be_called()
document.disable_editing_document().should_be_called() document.disable_editing_document().should_be_called()
document.clear_document_tree().should_be_called() document.clear_document_tree().should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent") document.import_project_documents().should_be_called()
document.import_subdocuments("parent").should_be_called() subject.edit_document(ifc, document, ifc_document="document")
document.import_references("parent").should_be_called()
subject.edit_document(ifc, document, document="document")
class TestRemoveDocument: class TestRemoveDocument:
@@ -117,27 +128,24 @@ class TestRemoveDocument:
document.clear_document_tree().should_be_called() document.clear_document_tree().should_be_called()
document.is_document_information("document").should_be_called().will_return(True) document.is_document_information("document").should_be_called().will_return(True)
ifc.run("document.remove_information", information="document").should_be_called() 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() document.import_project_documents().should_be_called()
subject.remove_document(ifc, document, document="document") subject.remove_document(ifc, document, ifc_document="document")
def test_remove_reference(self, ifc, document): def test_remove_reference(self, ifc, document):
document.clear_document_tree().should_be_called() document.clear_document_tree().should_be_called()
document.is_document_information("document").should_be_called().will_return(False) document.is_document_information("document").should_be_called().will_return(False)
ifc.run("document.remove_reference", reference="document").should_be_called() ifc.run("document.remove_reference", reference="document").should_be_called()
document.get_active_breadcrumb().should_be_called().will_return("parent") document.import_project_documents().should_be_called()
document.import_subdocuments("parent").should_be_called() subject.remove_document(ifc, document, ifc_document="document")
document.import_references("parent").should_be_called()
subject.remove_document(ifc, document, document="document")
class TestAssignDocument: class TestAssignDocument:
def test_run(self, ifc): def test_run(self, ifc):
ifc.run("document.assign_document", products=["product"], document="document").should_be_called() ifc.run("document.assign_document", products=["product"], document="document").should_be_called()
subject.assign_document(ifc, product="product", document="document") subject.assign_document(ifc, product="product", ifc_document="document")
class TestUnassignDocument: class TestUnassignDocument:
def test_run(self, ifc): def test_run(self, ifc):
ifc.run("document.unassign_document", products=["product"], document="document").should_be_called() ifc.run("document.unassign_document", products=["product"], document="document").should_be_called()
subject.unassign_document(ifc, product="product", document="document") subject.unassign_document(ifc, product="product", ifc_document="document")
+46 -68
View File
@@ -22,6 +22,7 @@ import ifcopenshell.api
import ifcopenshell.api.document import ifcopenshell.api.document
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
import json
from test.bim.bootstrap import NewFile from test.bim.bootstrap import NewFile
from bonsai.tool.document import Document as subject from bonsai.tool.document import Document as subject
@@ -31,24 +32,6 @@ class TestImplementsTool(NewFile):
assert isinstance(subject(), bonsai.core.tool.Document) 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): class TestClearDocumentTree(NewFile):
def test_run(self): def test_run(self):
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
@@ -103,15 +86,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): class TestImportDocumentAttributes(NewFile):
def test_importing_information(self): def test_importing_information(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
@@ -166,51 +140,64 @@ class TestImportDocumentAttributes(NewFile):
assert props.document_attributes["Description"].string_value == "Description" assert props.document_attributes["Description"].string_value == "Description"
class TestImportProjectDocuments(NewFile): class TestImportProjectDocumentsExpanded(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
ifc.createIfcProject() project = ifc.createIfcProject()
document = ifcopenshell.api.document.add_information(ifc)
subject.import_project_documents()
props = tool.Document.get_document_props()
assert len(props.documents) == 1
assert props.documents[0].ifc_definition_id == document.id()
assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "X"
assert props.documents[0].is_information is True
class TestImportReferences(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc().set(ifc)
ifc.createIfcProject()
document = ifcopenshell.api.document.add_information(ifc) document = ifcopenshell.api.document.add_information(ifc)
reference = ifcopenshell.api.document.add_reference(ifc, information=document) reference = ifcopenshell.api.document.add_reference(ifc, information=document)
subject.import_references(document)
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
assert len(props.documents) == 1 expanded_docs = [document.id()] # Mark document as expanded
assert props.documents[0].ifc_definition_id == reference.id() props.json_string = json.dumps(expanded_docs)
assert props.documents[0].name == "Unnamed"
assert props.documents[0].identification == "X" subject.import_project_documents()
assert props.documents[0].is_information is False props = tool.Document.get_document_props()
# Should have project root + document + reference = 3 total
assert len(props.documents) == 3
assert props.documents[0].ifc_definition_id == -project.id()
assert props.documents[0].document_type == "PROJECT"
doc_info = next((d for d in props.documents if d.ifc_definition_id == document.id()), None)
assert doc_info is not None
assert doc_info.document_type == "INFORMATION"
doc_ref = next((d for d in props.documents if d.ifc_definition_id == reference.id()), None)
assert doc_ref is not None
assert doc_ref.location == ""
assert doc_ref.identification == "X"
assert doc_ref.document_type == "REFERENCE"
class TestImportSubdocuments(NewFile): class TestImportProjectDocumentsCollapsed(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
tool.Ifc().set(ifc) tool.Ifc().set(ifc)
ifc.createIfcProject() project = ifc.createIfcProject()
document = ifcopenshell.api.document.add_information(ifc) document = ifcopenshell.api.document.add_information(ifc)
subdocument = ifcopenshell.api.document.add_information(ifc, parent=document) reference = ifcopenshell.api.document.add_reference(ifc, information=document)
subject.import_subdocuments(document)
props = tool.Document.get_document_props() props = tool.Document.get_document_props()
assert len(props.documents) == 1 props.json_string = json.dumps([]) # Empty expanded list
assert props.documents[0].ifc_definition_id == subdocument.id()
assert props.documents[0].name == "Unnamed" subject.import_project_documents()
assert props.documents[0].identification == "X" props = tool.Document.get_document_props()
assert props.documents[0].is_information is True
# Should have project root + document = 2 total (reference not imported because parent is collapsed)
assert len(props.documents) == 2
assert props.documents[0].ifc_definition_id == -project.id()
assert props.documents[0].document_type == "PROJECT"
doc_info = next((d for d in props.documents if d.ifc_definition_id == document.id()), None)
assert doc_info is not None
assert doc_info.document_type == "INFORMATION"
doc_ref = next((d for d in props.documents if d.ifc_definition_id == reference.id()), None)
assert doc_ref is None
class TestIsDocumentInformation(NewFile): class TestIsDocumentInformation(NewFile):
@@ -222,15 +209,6 @@ class TestIsDocumentInformation(NewFile):
assert subject.is_document_information(reference) is False 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): class TestSetActiveDocument(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()