mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Merge pull request #6825 from falken10vdl/UI_documents_tree
UI documents tree
This commit is contained in:
@@ -24,26 +24,33 @@ classes = (
|
||||
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.OpenIFCDocument,
|
||||
prop.Document,
|
||||
prop.DocumentObject,
|
||||
prop.BIMDocumentProperties,
|
||||
ui.BIM_PT_documents,
|
||||
ui.BIM_PT_object_documents,
|
||||
ui.BIM_UL_documents,
|
||||
ui.BIM_UL_document_objects,
|
||||
ui.BIM_MT_object_documents_context_menu,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
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():
|
||||
del bpy.types.Scene.BIMDocumentProperties
|
||||
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.add_object_documents_context_menu)
|
||||
|
||||
@@ -21,6 +21,7 @@ import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import bonsai.tool as tool
|
||||
from natsort import natsorted
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -35,30 +36,50 @@ class DocumentData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_information": cls.total_information(),
|
||||
"parent_document": cls.parent_document(),
|
||||
"total_documents": cls.total_documents(),
|
||||
"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_documents(cls):
|
||||
file = tool.Ifc.get()
|
||||
return len(file.by_type("IfcDocumentInformation")) + len(file.by_type("IfcDocumentReference"))
|
||||
|
||||
@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()
|
||||
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_id not in cls.data["document_objects"]:
|
||||
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:
|
||||
@@ -72,6 +93,18 @@ class ObjectDocumentData:
|
||||
}
|
||||
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
|
||||
def documents(cls):
|
||||
results = []
|
||||
@@ -80,44 +113,61 @@ class ObjectDocumentData:
|
||||
return results
|
||||
for rel in getattr(element, "HasAssociations", []):
|
||||
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
|
||||
|
||||
name = rel.RelatingDocument.Name
|
||||
name = relating_document.Name
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
if not name and rel.RelatingDocument.ReferenceToDocument:
|
||||
name = rel.RelatingDocument.ReferenceToDocument[0].Name
|
||||
location = None
|
||||
identification = 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 = 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:
|
||||
if not name and rel.RelatingDocument.ReferencedDocument:
|
||||
name = rel.RelatingDocument.ReferencedDocument.Name
|
||||
description = relating_document.Description
|
||||
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
|
||||
if not identification and rel.RelatingDocument.ReferencedDocument:
|
||||
identification = rel.RelatingDocument.ReferencedDocument.Identification
|
||||
identification = relating_document.ItemReference
|
||||
if not identification and reference_to_document:
|
||||
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
|
||||
if location is None and rel.RelatingDocument.ReferencedDocument:
|
||||
location = rel.RelatingDocument.ReferencedDocument.Location
|
||||
identification = relating_document.Identification
|
||||
if not identification and referenced_document:
|
||||
identification = referenced_document.Identification
|
||||
|
||||
if location:
|
||||
if not "://" in location:
|
||||
if not os.path.isabs(location):
|
||||
location = os.path.abspath(os.path.join(os.path.dirname(tool.Ifc.get_path()), location))
|
||||
location = "file://" + location
|
||||
location = relating_document.Location
|
||||
if location is None and referenced_document:
|
||||
location = referenced_document.Location
|
||||
|
||||
location = cls.convert_to_file_uri(location) if location else None
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.RelatingDocument.id(),
|
||||
"id": relating_document.id(),
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"location": location,
|
||||
"is_information": is_information,
|
||||
"description": description,
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.bim.handler
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.document as core
|
||||
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
|
||||
|
||||
|
||||
class LoadProjectDocuments(bpy.types.Operator):
|
||||
@@ -33,30 +31,6 @@ class LoadProjectDocuments(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
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"}
|
||||
|
||||
|
||||
@@ -70,6 +44,16 @@ 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):
|
||||
core.disable_object_document_editing_ui(tool.Document)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingDocument(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_document"
|
||||
bl_label = "Enable Editing Document"
|
||||
@@ -77,7 +61,7 @@ class EnableEditingDocument(bpy.types.Operator):
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
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"}
|
||||
|
||||
|
||||
@@ -97,7 +81,35 @@ 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.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):
|
||||
@@ -106,7 +118,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.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)
|
||||
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):
|
||||
@@ -116,7 +154,9 @@ 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, ifc_document=tool.Ifc.get().by_id(props.active_document_id))
|
||||
props.active_document_id = 0
|
||||
|
||||
|
||||
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -126,7 +166,7 @@ class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
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):
|
||||
@@ -138,12 +178,15 @@ class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
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()
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
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):
|
||||
@@ -154,12 +197,25 @@ class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
|
||||
document: bpy.props.IntProperty()
|
||||
|
||||
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()
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
core.unassign_document(tool.Ifc, product=element, document=document)
|
||||
if obj:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
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):
|
||||
@@ -182,3 +238,81 @@ 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):
|
||||
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"}
|
||||
|
||||
@@ -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 bonsai.tool as tool
|
||||
from bonsai.bim.prop import StrProperty, Attribute
|
||||
from bonsai.bim.module.document.data import refresh
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -30,6 +13,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from bonsai.bim.module.document.data import DocumentData
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
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")
|
||||
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)
|
||||
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")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
identification: str
|
||||
is_information: bool
|
||||
name: str
|
||||
ifc_definition_id: int
|
||||
|
||||
|
||||
@@ -68,17 +84,23 @@ 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_index)
|
||||
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:
|
||||
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_object_editing: bool
|
||||
document_objects: bpy.types.bpy_prop_collection_idprop[DocumentObject]
|
||||
active_document_object_index: int
|
||||
json_string: str
|
||||
|
||||
@property
|
||||
def active_document(self) -> Union[Document, None]:
|
||||
|
||||
@@ -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,7 +43,8 @@ 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")
|
||||
row.label(text="{} Documents found".format(DocumentData.data["total_documents"]), icon="FILE")
|
||||
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL")
|
||||
else:
|
||||
@@ -52,34 +54,54 @@ class BIM_PT_documents(Panel):
|
||||
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.active_document_id > 0:
|
||||
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:
|
||||
if not self.props.active_document or self.props.active_document.document_type in ["INFORMATION", "PROJECT"]:
|
||||
row.operator("bim.add_information", text="", icon="ADD")
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
bl_label = "Documents"
|
||||
@@ -102,65 +124,209 @@ class BIM_PT_object_documents(Panel):
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
if not ObjectDocumentData.is_loaded:
|
||||
ObjectDocumentData.load()
|
||||
|
||||
obj = context.active_object
|
||||
self.oprops = tool.Blender.get_object_bim_props(obj)
|
||||
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()
|
||||
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"
|
||||
|
||||
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.active_document:
|
||||
document = self.props.active_document
|
||||
|
||||
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):
|
||||
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.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:
|
||||
if item.document_type != "PROJECT":
|
||||
if item.tree_depth > 1:
|
||||
indent_depth = item.tree_depth - 1
|
||||
|
||||
for i in range(indent_depth):
|
||||
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.label(text=item.identification)
|
||||
split1.prop(item, "identification", text="", emboss=False)
|
||||
split2 = split1.split(factor=0.9)
|
||||
split2.prop(item, "name", text="", emboss=False)
|
||||
split2 = split1.split(factor=0.8)
|
||||
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}")
|
||||
|
||||
@@ -20,7 +20,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import bonsai.tool as tool
|
||||
|
||||
@@ -28,102 +27,82 @@ 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)
|
||||
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:
|
||||
document.disable_editing_ui()
|
||||
document.disable_editing_document()
|
||||
|
||||
|
||||
def enable_editing_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
|
||||
document_tool.import_document_attributes(document)
|
||||
document_tool.set_active_document(document)
|
||||
def disable_object_document_editing_ui(document: tool.Document) -> None:
|
||||
document.disable_object_editing_ui()
|
||||
|
||||
|
||||
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:
|
||||
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()
|
||||
parent = document.get_active_breadcrumb()
|
||||
|
||||
if parent is None:
|
||||
parent = document.get_default_parent_for_information()
|
||||
|
||||
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 document.is_document_information(parent):
|
||||
document.expand_document(parent)
|
||||
|
||||
document.import_project_documents()
|
||||
return information
|
||||
|
||||
|
||||
def add_reference(ifc: tool.Ifc, document: tool.Document) -> None:
|
||||
parent = document.get_active_breadcrumb()
|
||||
assert parent
|
||||
ifc.run("document.add_reference", information=parent)
|
||||
parent = document.get_selected_document_information()
|
||||
|
||||
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.import_subdocuments(parent)
|
||||
document.import_references(parent)
|
||||
document.import_project_documents()
|
||||
|
||||
|
||||
def edit_document(ifc: tool.Ifc, document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
|
||||
attributes = document_tool.export_document_attributes()
|
||||
if document_tool.is_document_information(document):
|
||||
ifc.run("document.edit_information", information=document, attributes=attributes)
|
||||
def remove_document(ifc: tool.Ifc, document: tool.Document, ifc_document: ifcopenshell.entity_instance) -> None:
|
||||
document.clear_document_tree()
|
||||
if document.is_document_information(ifc_document):
|
||||
ifc.run("document.remove_information", information=ifc_document)
|
||||
else:
|
||||
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()
|
||||
|
||||
|
||||
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()
|
||||
ifc.run("document.remove_reference", reference=ifc_document)
|
||||
document.import_project_documents()
|
||||
|
||||
|
||||
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:
|
||||
ifc.run("document.assign_document", products=[product], document=document)
|
||||
ifc.run("document.assign_document", products=[product], document=ifc_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:
|
||||
ifc.run("document.unassign_document", products=[product], document=document)
|
||||
ifc.run("document.unassign_document", products=[product], document=ifc_document)
|
||||
|
||||
@@ -287,21 +287,29 @@ 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_object_editing_ui(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
|
||||
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
|
||||
|
||||
@@ -22,6 +22,8 @@ import ifcopenshell.util.system
|
||||
import bonsai.bim.helper
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
import json
|
||||
from natsort import natsorted
|
||||
from typing import Any, Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -33,17 +35,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()
|
||||
@@ -54,6 +45,11 @@ class Document(bonsai.core.tool.Document):
|
||||
props = cls.get_document_props()
|
||||
props.active_document_id = 0
|
||||
|
||||
@classmethod
|
||||
def disable_object_editing_ui(cls) -> None:
|
||||
props = cls.get_document_props()
|
||||
props.is_object_editing = False
|
||||
|
||||
@classmethod
|
||||
def disable_editing_ui(cls) -> None:
|
||||
props = cls.get_document_props()
|
||||
@@ -69,18 +65,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 +93,132 @@ 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(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 []:
|
||||
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 = -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
|
||||
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.document_type = "INFORMATION" if document.is_a("IfcDocumentInformation") else "REFERENCE"
|
||||
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 "*"
|
||||
new.name = document.Name or ""
|
||||
new.identification = (
|
||||
cls.get_document_information_id(document)
|
||||
if new.document_type == "INFORMATION"
|
||||
else cls.get_external_reference_id(document)
|
||||
)
|
||||
new.identification = new.identification or ""
|
||||
new.description = document.Description or ""
|
||||
new.location = document.Location 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
|
||||
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()
|
||||
@@ -179,3 +252,65 @@ class Document(bonsai.core.tool.Document):
|
||||
if document.file.schema == "IFC2X3":
|
||||
return document.DocumentReferences or ()
|
||||
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)
|
||||
|
||||
@@ -6,23 +6,6 @@ Scenario: Load project documents
|
||||
When I press "bim.load_project_documents"
|
||||
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
|
||||
Given an empty IFC project
|
||||
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.add_information"
|
||||
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"
|
||||
Then nothing happens
|
||||
|
||||
@@ -83,16 +67,12 @@ Scenario: Assign document
|
||||
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})"
|
||||
And I press "bim.add_document_reference"
|
||||
And the variable "reference" is "{ifc}.by_type('IfcDocumentReference')[-1].id()"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I look at the "Class" panel
|
||||
And I set the "Products" property to "IfcElement"
|
||||
And I set the "Class" property to "IfcWall"
|
||||
And I click "Assign IFC Class"
|
||||
When I press "bim.assign_document(document={reference})"
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
When I press "bim.assign_document(document={information})"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Unassign document
|
||||
@@ -100,15 +80,11 @@ Scenario: Unassign document
|
||||
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})"
|
||||
And I press "bim.add_document_reference"
|
||||
And the variable "reference" is "{ifc}.by_type('IfcDocumentReference')[-1].id()"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I look at the "Class" panel
|
||||
And I set the "Products" property to "IfcElement"
|
||||
And I set the "Class" property to "IfcWall"
|
||||
And I click "Assign IFC Class"
|
||||
And I press "bim.assign_document(document={reference})"
|
||||
When I press "bim.unassign_document(document={reference})"
|
||||
Then nothing happens
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And I press "bim.assign_document(document={information})"
|
||||
When I press "bim.unassign_document(document={information})"
|
||||
Then nothing happens
|
||||
@@ -16,7 +16,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bonsai.core.document as subject
|
||||
from test.core.bootstrap import ifc, document
|
||||
|
||||
@@ -25,21 +24,10 @@ 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)
|
||||
|
||||
|
||||
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:
|
||||
def test_run(self, document):
|
||||
document.disable_editing_ui().should_be_called()
|
||||
@@ -47,45 +35,71 @@ class TestDisableDocumentEditingUi:
|
||||
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:
|
||||
def test_run(self, document):
|
||||
document.import_document_attributes("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:
|
||||
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)
|
||||
|
||||
|
||||
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")
|
||||
document.get_default_parent_for_information().should_be_called().will_return("default_parent")
|
||||
ifc.run("document.add_information", parent="default_parent").should_be_called().will_return("information")
|
||||
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()
|
||||
|
||||
subject.add_information(ifc, document)
|
||||
|
||||
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)
|
||||
document.is_document_information("parent").should_be_called().will_return(True)
|
||||
document.expand_document("parent").should_be_called()
|
||||
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:
|
||||
def test_run(self, ifc, document):
|
||||
document.get_active_breadcrumb().should_be_called().will_return("parent")
|
||||
def test_run_with_selected_parent(self, ifc, document):
|
||||
document.get_selected_document_information().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()
|
||||
document.expand_document("parent").should_be_called()
|
||||
document.import_project_documents().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)
|
||||
|
||||
|
||||
@@ -96,9 +110,8 @@ 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")
|
||||
subject.edit_document(ifc, document, ifc_document="document")
|
||||
|
||||
def test_edit_reference(self, ifc, document):
|
||||
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()
|
||||
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")
|
||||
document.import_project_documents().should_be_called()
|
||||
subject.edit_document(ifc, document, ifc_document="document")
|
||||
|
||||
|
||||
class TestRemoveDocument:
|
||||
@@ -117,27 +128,24 @@ 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")
|
||||
subject.remove_document(ifc, document, ifc_document="document")
|
||||
|
||||
def test_remove_reference(self, ifc, document):
|
||||
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")
|
||||
document.import_project_documents().should_be_called()
|
||||
subject.remove_document(ifc, document, ifc_document="document")
|
||||
|
||||
|
||||
class TestAssignDocument:
|
||||
def test_run(self, ifc):
|
||||
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:
|
||||
def test_run(self, ifc):
|
||||
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")
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell.api
|
||||
import ifcopenshell.api.document
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
import json
|
||||
from test.bim.bootstrap import NewFile
|
||||
from bonsai.tool.document import Document as subject
|
||||
|
||||
@@ -31,24 +32,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 +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):
|
||||
def test_importing_information(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -166,51 +140,64 @@ class TestImportDocumentAttributes(NewFile):
|
||||
assert props.document_attributes["Description"].string_value == "Description"
|
||||
|
||||
|
||||
class TestImportProjectDocuments(NewFile):
|
||||
class TestImportProjectDocumentsExpanded(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc().set(ifc)
|
||||
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()
|
||||
project = ifc.createIfcProject()
|
||||
document = ifcopenshell.api.document.add_information(ifc)
|
||||
reference = ifcopenshell.api.document.add_reference(ifc, information=document)
|
||||
subject.import_references(document)
|
||||
|
||||
props = tool.Document.get_document_props()
|
||||
assert len(props.documents) == 1
|
||||
assert props.documents[0].ifc_definition_id == reference.id()
|
||||
assert props.documents[0].name == "Unnamed"
|
||||
assert props.documents[0].identification == "X"
|
||||
assert props.documents[0].is_information is False
|
||||
expanded_docs = [document.id()] # Mark document as expanded
|
||||
props.json_string = json.dumps(expanded_docs)
|
||||
|
||||
subject.import_project_documents()
|
||||
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):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc().set(ifc)
|
||||
ifc.createIfcProject()
|
||||
project = ifc.createIfcProject()
|
||||
document = ifcopenshell.api.document.add_information(ifc)
|
||||
subdocument = ifcopenshell.api.document.add_information(ifc, parent=document)
|
||||
subject.import_subdocuments(document)
|
||||
reference = ifcopenshell.api.document.add_reference(ifc, information=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
|
||||
props.json_string = json.dumps([]) # Empty expanded list
|
||||
|
||||
subject.import_project_documents()
|
||||
props = tool.Document.get_document_props()
|
||||
|
||||
# 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):
|
||||
@@ -222,15 +209,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()
|
||||
|
||||
Reference in New Issue
Block a user