updates based on developers feedback

This commit is contained in:
falken10
2025-07-07 23:58:02 +02:00
committed by falken10vdl
parent aa8c146f9d
commit b6cfd165c2
7 changed files with 218 additions and 255 deletions
@@ -28,14 +28,12 @@ classes = (
operator.DisableEditingDocument,
operator.EditDocument,
operator.EnableEditingDocument,
operator.LoadDocument,
operator.LoadObjectDocuments,
operator.LoadProjectDocuments,
operator.RemoveDocument,
operator.SelectDocumentObjects,
operator.ToggleDocument,
operator.UnassignDocument,
operator.UpdateAssignedDocuments,
operator.OpenIFCDocument,
prop.Document,
prop.DocumentObject,
+20 -9
View File
@@ -21,7 +21,7 @@ import bpy
import ifcopenshell
import ifcopenshell.util.schema
import bonsai.tool as tool
from natsort import natsorted
def refresh():
DocumentData.is_loaded = False
@@ -87,19 +87,21 @@ class DocumentData:
@classmethod
def load_document_objects_into_props(cls, document_id):
if not cls.is_loaded:
cls.load()
props = tool.Document.get_document_props()
props.document_objects.clear()
if "document_objects" not in cls.data or document_id not in cls.data["document_objects"]:
if document_id not in cls.data["document_objects"]:
return
sorted_objects = sorted(cls.data["document_objects"][document_id], key=lambda x: x["name"].lower())
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:
data = {}
is_loaded = False
@@ -111,6 +113,19 @@ 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 = []
@@ -165,11 +180,7 @@ class ObjectDocumentData:
if location is None and referenced_document:
location = referenced_document.Location
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 = cls.convert_to_file_uri(location) if location else None
results.append(
{
+47 -162
View File
@@ -18,29 +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
import subprocess
import os
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
def update_document_objects(document_id=None):
DocumentData.is_loaded = False
DocumentData.load()
if document_id is None:
props = tool.Document.get_document_props()
if props.active_document and props.active_document.ifc_definition_id:
document_id = props.active_document.ifc_definition_id
if document_id:
DocumentData.load_document_objects_into_props(document_id)
from .data import DocumentData, ObjectDocumentData
class LoadProjectDocuments(bpy.types.Operator):
bl_idname = "bim.load_project_documents"
@@ -49,23 +30,8 @@ class LoadProjectDocuments(bpy.types.Operator):
def execute(self, context):
core.load_project_documents(tool.Document)
update_document_objects()
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() # Is this needed?
update_document_objects()
return {"FINISHED"}
class DisableDocumentEditingUI(bpy.types.Operator):
bl_idname = "bim.disable_document_editing_ui"
bl_label = "Disable Document Editing UI"
@@ -82,8 +48,7 @@ class DisableObjectDocumentEditingUI(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = tool.Document.get_document_props()
props.is_object_editing = False
core.disable_object_document_editing_ui(tool.Document)
return {"FINISHED"}
@@ -94,8 +59,6 @@ class EnableEditingDocument(bpy.types.Operator):
document: bpy.props.IntProperty()
def execute(self, context):
props = tool.Document.get_document_props()
props.is_document_editing = True
core.enable_editing_document(tool.Document, document=tool.Ifc.get().by_id(self.document))
return {"FINISHED"}
@@ -106,8 +69,6 @@ class DisableEditingDocument(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = tool.Document.get_document_props()
props.is_document_editing = False
core.disable_editing_document(tool.Document)
return {"FINISHED"}
@@ -123,15 +84,15 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
if props.active_document:
selected_document = props.active_document
if selected_document.ifc_definition_id == -1:
parent = tool.Ifc.get().by_type("IfcProject")[0] if tool.Ifc.get().by_type("IfcProject") else None
elif selected_document.is_information:
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)
else:
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] if tool.Ifc.get().by_type("IfcProject") else None
parent = tool.Ifc.get().by_type("IfcProject")[0]
core.add_information(tool.Ifc, tool.Document, parent)
@@ -141,20 +102,13 @@ class AddInformation(bpy.types.Operator, tool.Ifc.Operator):
except (AttributeError, json.JSONDecodeError):
pass
project = tool.Ifc.get().by_type("IfcProject")[0]
virtual_root_id = -project.id()
if virtual_root_id in expanded_docs:
expanded_docs.remove(virtual_root_id)
if parent and parent.is_a("IfcDocumentInformation"):
if parent.id() not in expanded_docs:
expanded_docs.append(parent.id())
props.json_string = json.dumps(expanded_docs)
bpy.ops.bim.load_project_documents()
class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_document_reference"
bl_label = "Add Document Reference"
@@ -169,8 +123,8 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator):
selected_document = props.active_document
if not selected_document.is_information:
self.report({"ERROR"}, "Cannot add a reference to a reference element")
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)
@@ -201,12 +155,7 @@ class EditDocument(bpy.types.Operator, tool.Ifc.Operator):
core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id))
props.active_document_id = 0
props.is_document_editing = False
DocumentData.is_loaded = False
DocumentData.load()
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
bpy.ops.bim.update_assigned_documents()
bonsai.bim.handler.refresh_ui_data()
tool.Document.update_assigned_documents()
class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -218,40 +167,6 @@ class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document))
class UpdateAssignedDocuments(bpy.types.Operator):
bl_idname = "bim.update_assigned_documents"
bl_label = "Update Assigned Documents"
bl_description = "Update the list of documents assigned to the active object"
bl_options = {"REGISTER"}
def execute(self, context):
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
props = tool.Document.get_document_props()
props.assigned_documents.clear()
if not ObjectDocumentData.data.get("documents"):
return {"FINISHED"}
sorted_docs = sorted(
ObjectDocumentData.data["documents"],
key=lambda doc: ((doc.get("identification") or "").lower(), (doc.get("name") or "").lower()),
)
for document in sorted_docs:
new = props.assigned_documents.add()
new.name = document["name"] or "Unnamed"
new.identification = document["identification"] or "*"
new.is_information = document.get("is_information", False)
new.ifc_definition_id = document["id"]
new.location = document.get("location") or ""
new.description = document.get("description") or ""
return {"FINISHED"}
class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_document"
bl_label = "Assign Document"
@@ -268,10 +183,12 @@ class AssignDocument(bpy.types.Operator, tool.Ifc.Operator):
if element:
core.assign_document(tool.Ifc, product=element, document=document)
update_document_objects(self.document)
tool.Document.update_document_objects(self.document)
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
bpy.ops.bim.update_assigned_documents()
tool.Document.update_assigned_documents()
return {"FINISHED"}
class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
@@ -285,21 +202,26 @@ class UnassignDocument(bpy.types.Operator, tool.Ifc.Operator):
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, document=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:
update_document_objects(active_document_id)
tool.Document.update_document_objects(active_document_id)
else:
update_document_objects(self.document)
tool.Document.update_document_objects()
ObjectDocumentData.is_loaded = False
ObjectDocumentData.load()
bpy.ops.bim.update_assigned_documents()
tool.Document.update_assigned_documents()
return {"FINISHED"}
class SelectDocumentObjects(bpy.types.Operator):
@@ -339,32 +261,10 @@ class LoadObjectDocuments(bpy.types.Operator):
props = tool.Document.get_document_props()
props.is_object_editing = True
bonsai.bim.handler.refresh_ui_data()
self.update_assigned_documents(props)
tool.Document.update_assigned_documents()
return {"FINISHED"}
def update_assigned_documents(self, props):
props.assigned_documents.clear()
if not ObjectDocumentData.data.get("documents"):
return
sorted_docs = sorted(
ObjectDocumentData.data["documents"],
key=lambda doc: ((doc.get("identification") or "").lower(), (doc.get("name") or "").lower()),
)
for document in sorted_docs:
new = props.assigned_documents.add()
new.name = document["name"] or "Unnamed"
new.identification = document["identification"] or "*"
new.is_information = document.get("is_information", False)
new.ifc_definition_id = document["id"]
new.location = document["location"] or ""
new.description = document["description"] or ""
class OpenIFCDocument(bpy.types.Operator):
bl_idname = "bim.open_ifc_document"
@@ -375,47 +275,37 @@ class OpenIFCDocument(bpy.types.Operator):
uri: bpy.props.StringProperty(name="URI")
def execute(self, context):
if not self.uri:
self.report({"ERROR"}, "No URI provided")
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"}
file_path = self.uri
if file_path.startswith("file://"):
file_path = file_path[7:]
elif file_path.startswith("file:"):
file_path = file_path[5:]
if not os.path.isabs(file_path):
file_path = os.path.abspath(file_path)
if not os.path.exists(file_path):
self.report({"ERROR"}, f"IFC file not found: {file_path}")
filepath = self.uri[7:] # Remove file:// prefix
if not os.path.exists(filepath):
self.report({"ERROR"}, f"File not found: {filepath}")
return {"CANCELLED"}
try:
subprocess.Popen(
[
"blender",
"--python-expr",
f"import bpy; bpy.ops.bim.load_project(filepath='{file_path}', should_start_fresh_session=True)",
]
)
self.report({"INFO"}, f"Opening IFC file: {file_path} in a new Blender instance.")
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")
except Exception as e:
self.report({"ERROR"}, f"Failed to open IFC file: {str(e)}")
return {"FINISHED"}
class ToggleDocument(bpy.types.Operator, tool.Ifc.Operator):
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):
def execute(self, context):
expanded_documents = []
props = tool.Document.get_document_props()
try:
@@ -425,19 +315,14 @@ class ToggleDocument(bpy.types.Operator, tool.Ifc.Operator):
document_id = self.document
if self.option == "Expand" and document_id not in expanded_documents:
expanded_documents.append(document_id)
elif self.option == "Collapse" and document_id in expanded_documents:
expanded_documents.remove(document_id)
elif document_id == -1:
project = tool.Ifc.get().by_type("IfcProject")[0]
virtual_root_id = -project.id()
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)
if self.option == "Expand" and virtual_root_id not in expanded_documents:
expanded_documents.append(virtual_root_id)
elif self.option == "Collapse" and virtual_root_id in expanded_documents:
expanded_documents.remove(virtual_root_id)
props.json_string = json.dumps(expanded_documents)
bpy.ops.bim.load_project_documents()
return {"FINISHED"}
+21 -7
View File
@@ -51,8 +51,7 @@ def update_document_identification(self: "Document", context: bpy.types.Context)
def update_active_document(self, context):
if self.documents and self.active_document_index < len(self.documents):
document = self.documents[self.active_document_index]
if (document := self.active_document):
if document.ifc_definition_id:
DocumentData.load_document_objects_into_props(document.ifc_definition_id)
@@ -61,23 +60,31 @@ class Document(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
description: StringProperty(name="Description")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
location: StringProperty(name="Location", default="")
tree_depth: IntProperty(name="Tree Depth", default=0)
has_children: BoolProperty(name="Has Children", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=False)
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
is_information: bool
ifc_definition_id: int
location: str
tree_depth: int
has_children: bool
is_expanded: bool
document_type: str
class DocumentObject(PropertyGroup):
@@ -93,17 +100,24 @@ class AssignedDocument(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
description: StringProperty(name="Description", default="")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
location: StringProperty(name="Location", default="")
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
is_information: bool
ifc_definition_id: int
location: str
document_type: str
class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
+48 -36
View File
@@ -20,8 +20,7 @@ import bpy
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.helper import draw_attributes
from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData
from .data import DocumentData, ObjectDocumentData
class BIM_PT_documents(Panel):
bl_label = "Documents"
@@ -46,8 +45,8 @@ class BIM_PT_documents(Panel):
split = row.split(factor=0.55)
left_row = split.row(align=True)
left_row.label(text="{} Informations".format(DocumentData.data["total_document_informations"]), icon="FILE")
left_row.label(text="{} References".format(DocumentData.data["total_document_references"]), icon="FILE_HIDDEN")
total_documents = DocumentData.data["total_document_informations"] + DocumentData.data["total_document_references"]
left_row.label(text="{} Documents".format(total_documents), icon="FILE")
right_row = split.row(align=True)
right_row.label(
@@ -68,31 +67,31 @@ class BIM_PT_documents(Panel):
row.operator("bim.edit_document", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="CANCEL")
else:
if not self.props.active_document or self.props.active_document.is_information:
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:
if self.props.active_document.is_information and self.props.active_document.ifc_definition_id != -1:
row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN")
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
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
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.is_document_editing:
active_document = self.props.active_document
if active_document.is_information:
draw_attributes(self.props.document_attributes, self.layout)
else:
draw_attributes(self.props.document_attributes, self.layout, filter_attributes=["Name"])
draw_attributes(self.props.document_attributes, self.layout)
if self.props.is_editing and self.props.active_document:
document = self.props.active_document
@@ -108,7 +107,6 @@ class BIM_PT_documents(Panel):
"active_document_object_index",
)
class BIM_PT_object_documents(Panel):
bl_label = "Documents"
bl_idname = "BIM_PT_object_documents"
@@ -119,6 +117,9 @@ class BIM_PT_object_documents(Panel):
bl_order = 1
bl_parent_id = "BIM_PT_tab_misc"
# Class variable to track the last selected object
_last_object_id = None
@classmethod
def poll(cls, context):
if not (obj := context.active_object):
@@ -130,10 +131,14 @@ class BIM_PT_object_documents(Panel):
return True
def draw(self, context):
if not ObjectDocumentData.is_loaded:
obj = context.active_object
current_ifc_id = tool.Blender.get_ifc_definition_id(obj)
if BIM_PT_object_documents._last_object_id != current_ifc_id:
BIM_PT_object_documents._last_object_id = current_ifc_id
ObjectDocumentData.is_loaded = False
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()
@@ -181,10 +186,11 @@ class BIM_PT_object_documents(Panel):
for doc in ObjectDocumentData.data["documents"]:
assigned_doc_ids.append(doc["id"])
# Only show assign button if the document is information (not reference) and not already assigned
if document.is_information and document.ifc_definition_id not in assigned_doc_ids:
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 # Pass the current document's ID
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(
@@ -198,24 +204,28 @@ class BIM_UL_documents(UIList):
row = layout.row(align=True)
indent_depth = 0
if item.ifc_definition_id != -1:
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")
if item.ifc_definition_id == -1:
if item.document_type == "PROJECT":
row.label(text="", icon="OUTLINER_COLLECTION")
row.label(text=item.name)
return
if item.is_information and item.has_children:
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.is_information:
elif item.document_type == "INFORMATION":
row.label(text="", icon="BLANK1")
if item.is_information:
if item.document_type == "INFORMATION":
row.label(text="", icon="FILE")
text = " - ".join([x for x in [item.name, item.location] if x])
else:
@@ -227,9 +237,10 @@ class BIM_UL_documents(UIList):
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 = item.location
row.operator("bim.open_uri", icon="URL", text="").uri = item.location
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):
@@ -253,7 +264,7 @@ class BIM_UL_assigned_documents(UIList):
if item:
row = layout.row(align=True)
if item.is_information:
if item.document_type == "INFORMATION":
row.label(text="", icon="FILE")
else:
row.label(text="", icon="FILE_HIDDEN")
@@ -262,15 +273,16 @@ class BIM_UL_assigned_documents(UIList):
split1.label(text=item.identification or "")
split2 = split1.split(factor=1.0)
if item.is_information:
if item.document_type == "INFORMATION":
split2.label(text=item.name or "Unnamed")
else:
split2.label(text=item.description or "No Description")
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 = item.location
row.operator("bim.open_uri", icon="URL", text="").uri = item.location
row.operator("bim.open_ifc_document", icon="HIDE_OFF", text="").uri = uri
row.operator("bim.open_uri", icon="URL", text="").uri = uri
op = row.operator("bim.unassign_document", text="", icon="X")
op.document = item.ifc_definition_id
+7 -11
View File
@@ -29,31 +29,27 @@ def load_project_documents(document: tool.Document) -> None:
document.import_project_documents()
document.enable_editing_ui()
def load_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
document_tool.clear_document_tree()
document_tool.expand_document(document)
document_tool.import_project_documents()
document_tool.disable_editing_document()
def disable_document_editing_ui(document: tool.Document) -> None:
document.disable_editing_ui()
document.disable_editing_document()
def disable_object_document_editing_ui(document: tool.Document) -> None:
props = document.get_document_props()
props.is_object_editing = False
def enable_editing_document(document_tool: tool.Document, document: ifcopenshell.entity_instance) -> None:
props = document_tool.get_document_props()
props.is_document_editing = True
document_tool.set_active_document(document)
document_tool.enable_document_editing()
document_tool.import_document_attributes(document)
def disable_editing_document(document: tool.Document) -> None:
props = document.get_document_props()
props.is_document_editing = False
document.clear_active_document()
document.disable_document_editing()
document.clear_document_attributes()
def add_information(ifc: tool.Ifc, document_tool: tool.Document, parent=None) -> ifcopenshell.entity_instance:
document_tool.clear_document_tree()
+75 -28
View File
@@ -18,12 +18,11 @@
from __future__ import annotations
import bpy
import json
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:
@@ -70,12 +69,15 @@ class Document(bonsai.core.tool.Document):
data[attr_name] = ""
return True
if attr_name != "Name":
return None
return None # Proceed normally
current_value = data[attr_name]
# If Name is already filled, display it so user would be able to correct invalid IFC.
if current_value is not None:
return None
# Skip import since IFC restricts Name to be filled
# for IfcDocumentReference with ReferencedDocument.
return False
import_callback = callback if document.is_a("IfcDocumentReference") else None
@@ -137,8 +139,8 @@ class Document(bonsai.core.tool.Document):
root_documents.append(rel.RelatingDocument)
root = props.documents.add()
root.ifc_definition_id = -1
root.is_information = True
root.ifc_definition_id = -project.id()
root.document_type = "PROJECT"
root.name = f"Project Documents ({project.Name or 'Unnamed Project'})"
root.identification = ""
root.location = ""
@@ -150,8 +152,9 @@ class Document(bonsai.core.tool.Document):
root.is_expanded = root_id not in expanded_documents
if root.is_expanded:
root_documents.sort(
key=lambda doc: ((cls.get_document_information_id(doc) or "").lower(), (doc.Name or "").lower())
root_documents = natsorted(
root_documents,
key=lambda doc: (cls.get_document_information_id(doc) or "", doc.Name or "")
)
for doc in root_documents:
@@ -161,11 +164,11 @@ class Document(bonsai.core.tool.Document):
def _process_document(cls, document, props, document_children, expanded_documents, depth):
new = props.documents.add()
new.ifc_definition_id = document.id()
new.is_information = document.is_a("IfcDocumentInformation")
new.document_type = "INFORMATION" if document.is_a("IfcDocumentInformation") else "REFERENCE"
new.tree_depth = depth
file = document.file
if new.is_information:
if new.document_type == "INFORMATION":
new.name = document.Name or "Unnamed"
new.identification = cls.get_document_information_id(document) or ""
new.location = document.Location or ""
@@ -175,7 +178,7 @@ class Document(bonsai.core.tool.Document):
new.description = document.Description or ""
new.location = document.Location or ""
if not new.is_information:
if new.document_type == "REFERENCE":
if file.schema == "IFC2X3":
if document.ReferenceToDocument:
doc_info = document.ReferenceToDocument[0]
@@ -183,7 +186,7 @@ class Document(bonsai.core.tool.Document):
new.name = doc_info.Name or ""
new.location = new.location or ""
else:
if hasattr(document, "ReferencedDocument") and document.ReferencedDocument:
if document.ReferencedDocument:
doc_info = document.ReferencedDocument
if not new.name:
new.name = doc_info.Name or ""
@@ -200,20 +203,22 @@ class Document(bonsai.core.tool.Document):
info_children = [d for d in children if d.is_a("IfcDocumentInformation")]
ref_children = [d for d in children if not d.is_a("IfcDocumentInformation")]
info_children.sort(
key=lambda doc: ((cls.get_document_information_id(doc) or "").lower(), (doc.Name or "").lower())
info_children = natsorted(
info_children,
key=lambda doc: (cls.get_document_information_id(doc) or "", doc.Name or "")
)
ref_children.sort(
ref_children = natsorted(
ref_children,
key=lambda doc: (
(cls.get_external_reference_id(doc) or "").lower(),
(doc.Description or doc.Name or "").lower(),
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")
@@ -252,16 +257,6 @@ class Document(bonsai.core.tool.Document):
return document.DocumentReferences or ()
return document.HasDocumentReferences
@classmethod
def enable_document_editing(cls) -> None:
props = cls.get_document_props()
props.is_editing = True
@classmethod
def disable_document_editing(cls) -> None:
props = cls.get_document_props()
props.is_editing = False
@classmethod
def clear_active_document(cls) -> None:
props = cls.get_document_props()
@@ -293,6 +288,58 @@ class Document(bonsai.core.tool.Document):
def get_selected_document_information(cls, ifc) -> Union[ifcopenshell.entity_instance, None]:
props = cls.get_document_props()
if props.active_document and props.active_document.is_information:
if props.active_document and props.active_document.document_type == "INFORMATION":
return ifc.get().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)
@classmethod
def update_assigned_documents(cls) -> None:
from bonsai.bim.module.document.data import ObjectDocumentData
ObjectDocumentData.is_loaded = False
props = cls.get_document_props()
props.assigned_documents.clear()
if not ObjectDocumentData.is_loaded:
ObjectDocumentData.load()
if not ObjectDocumentData.data.get("documents"):
return
sorted_docs = sorted(
ObjectDocumentData.data["documents"],
key=lambda doc: ((doc.get("identification") or "").lower(), (doc.get("name") or "").lower()),
)
for document in sorted_docs:
new = props.assigned_documents.add()
new.name = document["name"] or "Unnamed"
new.identification = document["identification"] or "*"
new.document_type = "INFORMATION" if document.get("is_information", False) else "REFERENCE"
new.ifc_definition_id = document["id"]
new.location = document.get("location") or ""
new.description = document.get("description") or ""