From 38b708ff1fdf79edfd688a38b9eff8579d001fb7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 15 Mar 2022 14:48:59 +1100 Subject: [PATCH] Redesigned document UI to handle hierarchical documents and make it easier to reference --- .../bim/module/document/__init__.py | 24 ++-- .../blenderbim/bim/module/document/data.py | 12 +- .../bim/module/document/operator.py | 64 +++------ .../blenderbim/bim/module/document/prop.py | 11 +- .../blenderbim/bim/module/document/ui.py | 123 +++++++++--------- src/blenderbim/blenderbim/core/document.py | 103 ++++++++------- src/blenderbim/blenderbim/core/tool.py | 15 ++- src/blenderbim/blenderbim/tool/document.py | 99 +++++++++----- src/blenderbim/pytest.ini | 1 + .../test/bim/feature/document.feature | 112 ++++++++++++++++ src/blenderbim/test/core/test_document.py | 105 ++++++++------- src/blenderbim/test/tool/test_document.py | 112 +++++++++++----- 12 files changed, 485 insertions(+), 296 deletions(-) create mode 100644 src/blenderbim/test/bim/feature/document.feature diff --git a/src/blenderbim/blenderbim/bim/module/document/__init__.py b/src/blenderbim/blenderbim/bim/module/document/__init__.py index edfe7a60ef..93dbf703b2 100644 --- a/src/blenderbim/blenderbim/bim/module/document/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/document/__init__.py @@ -20,35 +20,29 @@ import bpy from . import ui, prop, operator classes = ( - operator.LoadInformation, - operator.LoadDocumentReferences, - operator.DisableDocumentEditingUI, - operator.EnableEditingDocument, - operator.DisableEditingDocument, - operator.AddInformation, operator.AddDocumentReference, - operator.EditInformation, - operator.EditDocumentReference, - operator.RemoveDocument, - operator.EnableAssigningDocument, - operator.DisableAssigningDocument, + operator.AddInformation, operator.AssignDocument, + operator.DisableDocumentEditingUI, + operator.DisableEditingDocument, + operator.EditDocument, + operator.EnableEditingDocument, + operator.LoadDocument, + operator.LoadParentDocument, + operator.LoadProjectDocuments, + operator.RemoveDocument, operator.UnassignDocument, prop.Document, prop.BIMDocumentProperties, - prop.BIMObjectDocumentProperties, ui.BIM_PT_documents, ui.BIM_PT_object_documents, ui.BIM_UL_documents, - ui.BIM_UL_object_documents, ) def register(): bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties) - bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties) def unregister(): del bpy.types.Scene.BIMDocumentProperties - del bpy.types.Object.BIMObjectDocumentProperties diff --git a/src/blenderbim/blenderbim/bim/module/document/data.py b/src/blenderbim/blenderbim/bim/module/document/data.py index fa64c1ce05..bd15c610ac 100644 --- a/src/blenderbim/blenderbim/bim/module/document/data.py +++ b/src/blenderbim/blenderbim/bim/module/document/data.py @@ -35,7 +35,7 @@ class DocumentData: def load(cls): cls.data = { "total_information": cls.total_information(), - "total_references": cls.total_references(), + "parent_document": cls.parent_document(), } cls.is_loaded = True @@ -50,8 +50,14 @@ class DocumentData: ) @classmethod - def total_references(cls): - return len(tool.Ifc.get().by_type("IfcDocumentReference")) + def parent_document(cls): + props = bpy.context.scene.BIMDocumentProperties + 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 "" class ObjectDocumentData: diff --git a/src/blenderbim/blenderbim/bim/module/document/operator.py b/src/blenderbim/blenderbim/bim/module/document/operator.py index d97070a38d..32777cb314 100644 --- a/src/blenderbim/blenderbim/bim/module/document/operator.py +++ b/src/blenderbim/blenderbim/bim/module/document/operator.py @@ -26,22 +26,32 @@ from blenderbim.bim.ifc import IfcStore from ifcopenshell.api.document.data import Data -class LoadInformation(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.load_information" - bl_label = "Load Information" +class LoadProjectDocuments(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.load_project_documents" + bl_label = "Load Project Documents" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.load_information(tool.Document) + core.load_project_documents(tool.Document) -class LoadDocumentReferences(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.load_document_references" - bl_label = "Load Document References" +class LoadDocument(bpy.types.Operator, tool.Ifc.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)) + + +class LoadParentDocument(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.load_parent_document" + bl_label = "Load Parent Document" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.load_references(tool.Document) + core.load_parent_document(tool.Document) class DisableDocumentEditingUI(bpy.types.Operator, tool.Ifc.Operator): @@ -90,24 +100,14 @@ class AddDocumentReference(bpy.types.Operator, tool.Ifc.Operator): core.add_reference(tool.Ifc, tool.Document) -class EditInformation(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.edit_information" +class EditDocument(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.edit_document" bl_label = "Edit Information" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): props = context.scene.BIMDocumentProperties - core.edit_information(tool.Ifc, tool.Document, information=tool.Ifc.get().by_id(props.active_document_id)) - - -class EditDocumentReference(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.edit_document_reference" - bl_label = "Edit Document Reference" - bl_options = {"REGISTER", "UNDO"} - - def _execute(self, context): - props = context.scene.BIMDocumentProperties - core.edit_reference(tool.Ifc, tool.Document, reference=tool.Ifc.get().by_id(props.active_document_id)) + core.edit_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(props.active_document_id)) class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator): @@ -120,28 +120,6 @@ class RemoveDocument(bpy.types.Operator, tool.Ifc.Operator): core.remove_document(tool.Ifc, tool.Document, document=tool.Ifc.get().by_id(self.document)) -class EnableAssigningDocument(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.enable_assigning_document" - bl_label = "Enable Assigning Document" - bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() - - def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - core.enable_assigning_document(tool.Document, obj) - - -class DisableAssigningDocument(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.disable_assigning_document" - bl_label = "Disable Assigning Document" - bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() - - def _execute(self, context): - obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object - core.enable_assigning_document(tool.Document, obj) - - class AssignDocument(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_document" bl_label = "Assign Document" diff --git a/src/blenderbim/blenderbim/bim/module/document/prop.py b/src/blenderbim/blenderbim/bim/module/document/prop.py index a470e715ae..1a9c7412cf 100644 --- a/src/blenderbim/blenderbim/bim/module/document/prop.py +++ b/src/blenderbim/blenderbim/bim/module/document/prop.py @@ -34,6 +34,7 @@ from bpy.props import ( class Document(PropertyGroup): name: StringProperty(name="Name") identification: StringProperty(name="Identification") + is_information: BoolProperty(name="Is Information") ifc_definition_id: IntProperty(name="IFC Definition ID") @@ -41,12 +42,6 @@ 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") - is_editing: StringProperty(name="Is Editing") - - -class BIMObjectDocumentProperties(PropertyGroup): - is_adding: StringProperty(name="Is Adding") - available_document_types: EnumProperty( - items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types" - ) + is_editing: BoolProperty(name="Is Editing", default=False) diff --git a/src/blenderbim/blenderbim/bim/module/document/ui.py b/src/blenderbim/blenderbim/bim/module/document/ui.py index a13995f694..71a0ca04cd 100644 --- a/src/blenderbim/blenderbim/bim/module/document/ui.py +++ b/src/blenderbim/blenderbim/bim/module/document/ui.py @@ -41,34 +41,40 @@ class BIM_PT_documents(Panel): self.props = context.scene.BIMDocumentProperties - if not self.props.is_editing or self.props.is_editing == "information": - row = self.layout.row(align=True) - row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE") - if self.props.is_editing == "information": - row.operator("bim.add_information", text="", icon="ADD") - row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL") - else: - row.operator("bim.load_information", text="", icon="IMPORT") - - if not self.props.is_editing or self.props.is_editing == "reference": - row = self.layout.row(align=True) - row.label(text="{} References Found".format(DocumentData.data["total_references"]), icon="FILE_HIDDEN") - if self.props.is_editing == "reference": - row.operator("bim.add_document_reference", text="", icon="ADD") - row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL") - else: - row.operator("bim.load_document_references", text="", icon="IMPORT") - + row = self.layout.row(align=True) + row.label(text="{} Documents Found".format(DocumentData.data["total_information"]), icon="FILE") if self.props.is_editing: - self.layout.template_list( - "BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index" - ) + row.operator("bim.disable_document_editing_ui", text="", icon="CANCEL") + else: + row.operator("bim.load_project_documents", text="", icon="IMPORT") + + if not self.props.is_editing: + return + + row = self.layout.row(align=True) + if self.props.breadcrumbs: + row.operator("bim.load_parent_document", text="", icon="FRAME_PREV") + row.label(text=DocumentData.data["parent_document"]) + else: + row.alignment = "RIGHT" + row.operator("bim.add_information", text="", icon="ADD") + if self.props.breadcrumbs: + row.operator("bim.add_document_reference", text="", icon="FILE_HIDDEN") if self.props.active_document_id: - self.draw_editable_ui(context) + row.operator("bim.edit_document", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_document", text="", icon="CANCEL") + elif self.props.documents and self.props.active_document_index < len(self.props.documents): + ifc_definition_id = self.props.documents[self.props.active_document_index].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 - def draw_editable_ui(self, context): - draw_attributes(self.props.document_attributes, self.layout) + self.layout.template_list( + "BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index" + ) + + if self.props.active_document_id: + draw_attributes(self.props.document_attributes, self.layout) class BIM_PT_object_documents(Panel): @@ -94,8 +100,7 @@ class BIM_PT_object_documents(Panel): obj = context.active_object self.oprops = obj.BIMObjectProperties - self.sprops = context.scene.BIMDocumentProperties - self.props = obj.BIMObjectDocumentProperties + self.props = context.scene.BIMDocumentProperties self.file = IfcStore.get_file() self.draw_add_ui() @@ -111,49 +116,43 @@ class BIM_PT_object_documents(Panel): row.operator("bim.unassign_document", text="", icon="X").document = document["id"] def draw_add_ui(self): - if self.props.is_adding: + if not self.props.is_editing: row = self.layout.row(align=True) - icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN" - row.label(text="Adding {}".format(self.props.is_adding), icon=icon) - row.operator("bim.disable_assigning_document", text="", icon="CANCEL") - self.layout.template_list( - "BIM_UL_object_documents", - "", - self.sprops, - "documents", - self.sprops, - "active_document_index", - ) + row.operator("bim.load_project_documents", text="Assign Document References", icon="ADD") + 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 = self.layout.row(align=True) - row.prop(self.props, "available_document_types", text="") - row.operator("bim.enable_assigning_document", text="", icon="ADD") + 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") + + 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) - row.label(text=item.identification) - row.label(text=item.name) - if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id: - if context.scene.BIMDocumentProperties.is_editing == "information": - row.operator("bim.edit_information", text="", icon="CHECKMARK") - elif context.scene.BIMDocumentProperties.is_editing == "reference": - row.operator("bim.edit_document_reference", text="", icon="CHECKMARK") - row.operator("bim.disable_editing_document", text="", icon="CANCEL") - elif context.scene.BIMDocumentProperties.active_document_id: - row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id - else: - op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL") + + if item.is_information: + op = row.operator("bim.load_document", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT") op.document = item.ifc_definition_id - row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id + row.label(text="", icon="FILE") + else: + row.label(text="", icon="BLANK1") + row.label(text="", icon="FILE_HIDDEN") - -class BIM_UL_object_documents(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - if item: - row = layout.row(align=True) - row.label(text=item.identification) - row.label(text=item.name) - row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id + split1 = row.split(factor=0.1) + split1.label(text=item.identification) + split2 = split1.split(factor=0.9) + split2.label(text=item.name) diff --git a/src/blenderbim/blenderbim/core/document.py b/src/blenderbim/blenderbim/core/document.py index d9e2fa22b1..873af9e642 100644 --- a/src/blenderbim/blenderbim/core/document.py +++ b/src/blenderbim/blenderbim/core/document.py @@ -17,16 +17,31 @@ # along with BlenderBIM Add-on. If not, see . -def load_information(document): - document.import_information() - document.enable_information_editing_ui() - document.disable_editing_document() +def load_project_documents(document): + document.clear_document_tree() + document.import_project_documents() + document.clear_breadcrumbs() + document.enable_editing_ui() -def load_references(document): - document.import_references() - document.enable_reference_editing_ui() - document.disable_editing_document() +def load_document(document_tool, document=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): + 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): @@ -44,52 +59,52 @@ def disable_editing_document(document): def add_information(ifc, document): - result = ifc.run("document.add_information") - document.import_information() - document.import_document_attributes(result) - document.set_active_document(result) + document.clear_document_tree() + parent = document.get_active_breadcrumb() + ifc.run("document.add_information", parent=parent) + if parent: + document.import_subdocuments(parent) + document.import_references(parent) + else: + document.import_project_documents() def add_reference(ifc, document): - result = ifc.run("document.add_reference") - document.import_references() - document.import_document_attributes(result) - document.set_active_document(result) + parent = document.get_active_breadcrumb() + ifc.run("document.add_reference", information=parent) + document.clear_document_tree() + document.import_subdocuments(parent) + document.import_references(parent) -def edit_information(ifc, document, information=None): - attributes = document.export_document_attributes() - ifc.run("document.edit_information", information=information, attributes=attributes) - document.disable_editing_document() - document.import_information() - - -def edit_reference(ifc, document, reference=None): - attributes = document.export_document_attributes() - ifc.run("document.edit_reference", reference=reference, attributes=attributes) - document.disable_editing_document() - document.import_references() +def edit_document(ifc, document_tool, document=None): + attributes = document_tool.export_document_attributes() + if document_tool.is_document_information(document): + ifc.run("document.edit_information", information=document, attributes=attributes) + 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, document_tool, document=None): + document_tool.clear_document_tree() if document_tool.is_document_information(document): - ifc.run("document.remove_document", document=document) - document_tool.import_information() + ifc.run("document.remove_information", information=document) else: - ifc.run("document.remove_document", document=document) - document_tool.import_references() - document_tool.disable_editing_document() - - -def enable_assigning_document(document, obj=None): - document.import_references() - document.enable_reference_editing_ui() - document.disable_editing_document() - document.enable_document_assignment_ui(obj) - - -def disable_assigning_document(document, obj=None): - document.disable_document_assignment_ui(obj) + ifc.run("document.remove_reference", reference=document) + parent = document_tool.get_active_breadcrumb() + if parent: + document_tool.import_subdocuments(parent) + document_tool.import_references(parent) + else: + document_tool.import_project_documents() def assign_document(ifc, product=None, document=None): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 4a6ce898eb..e37a93a967 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -135,17 +135,20 @@ class Debug: @interface class Document: - def disable_document_assignment_ui(cls, obj): pass + def add_breadcrumb(cls, document): pass + def clear_breadcrumbs(cls): pass + def clear_document_tree(cls): pass def disable_editing_document(cls): pass def disable_editing_ui(cls): pass - def enable_document_assignment_ui(cls, obj): pass - def enable_information_editing_ui(cls): pass - def enable_reference_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_information(cls): pass - def import_references(cls): pass + def import_project_documents(cls): pass + def import_references(cls, document): pass + def import_subdocuments(cls, document): pass def is_document_information(cls, document): pass + def remove_latest_breadcrumb(cls): pass def set_active_document(cls, document): pass diff --git a/src/blenderbim/blenderbim/tool/document.py b/src/blenderbim/blenderbim/tool/document.py index a8b80b975d..ce1e68ce49 100644 --- a/src/blenderbim/blenderbim/tool/document.py +++ b/src/blenderbim/blenderbim/tool/document.py @@ -25,8 +25,20 @@ from blenderbim.bim import import_ifc class Document(blenderbim.core.tool.Document): @classmethod - def disable_document_assignment_ui(cls, obj): - obj.BIMObjectDocumentProperties.is_adding = "" + def add_breadcrumb(cls, document): + props = bpy.context.scene.BIMDocumentProperties + new = props.breadcrumbs.add() + new.name = str(document.id()) + + @classmethod + def clear_breadcrumbs(cls): + props = bpy.context.scene.BIMDocumentProperties + props.breadcrumbs.clear() + + @classmethod + def clear_document_tree(cls): + props = bpy.context.scene.BIMDocumentProperties + props.documents.clear() @classmethod def disable_editing_document(cls): @@ -34,24 +46,22 @@ class Document(blenderbim.core.tool.Document): @classmethod def disable_editing_ui(cls): - bpy.context.scene.BIMDocumentProperties.is_editing = "" + bpy.context.scene.BIMDocumentProperties.is_editing = False @classmethod - def enable_document_assignment_ui(cls, obj): - obj.BIMObjectDocumentProperties.is_adding = "IfcDocumentReference" - - @classmethod - def enable_information_editing_ui(cls): - bpy.context.scene.BIMDocumentProperties.is_editing = "information" - - @classmethod - def enable_reference_editing_ui(cls): - bpy.context.scene.BIMDocumentProperties.is_editing = "reference" + def enable_editing_ui(cls): + bpy.context.scene.BIMDocumentProperties.is_editing = True @classmethod def export_document_attributes(cls): return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMDocumentProperties.document_attributes) + @classmethod + def get_active_breadcrumb(cls): + props = bpy.context.scene.BIMDocumentProperties + if len(props.breadcrumbs): + return tool.Ifc.get().by_id(int(props.breadcrumbs[-1].name)) + @classmethod def import_document_attributes(cls, document): props = bpy.context.scene.BIMDocumentProperties @@ -59,35 +69,64 @@ class Document(blenderbim.core.tool.Document): blenderbim.bim.helper.import_attributes2(document, props.document_attributes) @classmethod - def import_information(cls): + def import_project_documents(cls): props = bpy.context.scene.BIMDocumentProperties props.documents.clear() - for element in tool.Ifc.get().by_type("IfcDocumentInformation"): - new = props.documents.add() - new.ifc_definition_id = element.id() - new.name = element.Name or "Unnamed" - if tool.Ifc.get_schema() == "IFC2X3": - new.identification = element.DocumentId or "*" - else: - new.identification = element.Identification or "*" + project = tool.Ifc.get().by_type("IfcProject")[0] + 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 + if tool.Ifc.get_schema() == "IFC2X3": + new.identification = element.DocumentId or "*" + else: + new.identification = element.Identification or "*" @classmethod - def import_references(cls): + def import_references(cls, document): props = bpy.context.scene.BIMDocumentProperties - props.documents.clear() - for element in tool.Ifc.get().by_type("IfcDocumentReference"): - new = props.documents.add() - new.ifc_definition_id = element.id() - new.name = element.Name or "Unnamed" - if tool.Ifc.get_schema() == "IFC2X3": + if tool.Ifc.get_schema() == "IFC2X3": + for element in document.DocumentReferences or []: + new = props.documents.add() + new.ifc_definition_id = element.id() + new.name = element.Name or "Unnamed" new.identification = element.ItemReference or "*" - else: + new.is_information = False + else: + for element in document.HasDocumentReferences: + new = props.documents.add() + new.ifc_definition_id = element.id() + new.name = element.Name or "Unnamed" new.identification = element.Identification or "*" + new.is_information = False + + @classmethod + def import_subdocuments(cls, document): + props = bpy.context.scene.BIMDocumentProperties + 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 + if tool.Ifc.get_schema() == "IFC2X3": + new.identification = element.DocumentId or "*" + else: + new.identification = element.Identification or "*" @classmethod def is_document_information(cls, document): return document.is_a("IfcDocumentInformation") + @classmethod + def remove_latest_breadcrumb(cls): + props = bpy.context.scene.BIMDocumentProperties + if len(props.breadcrumbs): + props.breadcrumbs.remove(len(props.breadcrumbs) - 1) + @classmethod def set_active_document(cls, document): bpy.context.scene.BIMDocumentProperties.active_document_id = document.id() diff --git a/src/blenderbim/pytest.ini b/src/blenderbim/pytest.ini index 2da67b7e82..5cd46aa294 100644 --- a/src/blenderbim/pytest.ini +++ b/src/blenderbim/pytest.ini @@ -7,6 +7,7 @@ markers = context debug demo + document drawing geometry library diff --git a/src/blenderbim/test/bim/feature/document.feature b/src/blenderbim/test/bim/feature/document.feature new file mode 100644 index 0000000000..db14740fbe --- /dev/null +++ b/src/blenderbim/test/bim/feature/document.feature @@ -0,0 +1,112 @@ +@document +Feature: Document + +Scenario: Load project documents + Given an empty IFC project + 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" + When I press "bim.disable_document_editing_ui" + Then nothing happens + +Scenario: Enable editing 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.enable_editing_document(document={information})" + Then nothing happens + +Scenario: Disable editing 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.enable_editing_document(document={information})" + When I press "bim.disable_editing_document" + Then nothing happens + +Scenario: Add information + Given an empty IFC project + And I press "bim.load_project_documents" + When I press "bim.add_information" + Then nothing happens + +Scenario: Add document reference + 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.add_document_reference" + Then nothing happens + +Scenario: Edit 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.enable_editing_document(document={information})" + When I press "bim.edit_document" + Then nothing happens + +Scenario: Remove 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.remove_document(document={information})" + Then nothing happens + +Scenario: Assign 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})" + 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 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={reference})" + Then nothing happens + +Scenario: Unassign 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})" + 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 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={reference})" + When I press "bim.unassign_document(document={reference})" + Then nothing happens diff --git a/src/blenderbim/test/core/test_document.py b/src/blenderbim/test/core/test_document.py index cbd9ed3e01..e2ddfe7f45 100644 --- a/src/blenderbim/test/core/test_document.py +++ b/src/blenderbim/test/core/test_document.py @@ -21,20 +21,23 @@ import blenderbim.core.document as subject from test.core.bootstrap import ifc, document -class TestLoadInformation: +class TestLoadProjectDocuments: def test_run(self, document): - document.import_information().should_be_called() - document.enable_information_editing_ui().should_be_called() - document.disable_editing_document().should_be_called() - subject.load_information(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 TestLoadReferences: +class TestLoadDocument: def test_run(self, document): - document.import_references().should_be_called() - document.enable_reference_editing_ui().should_be_called() + 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() - subject.load_references(document) + document.add_breadcrumb("document").should_be_called() + subject.load_document(document, document="document") class TestDisableDocumentEditingUi: @@ -58,72 +61,74 @@ class TestDisableEditingDocument: class TestAddInformation: - def test_run(self, ifc, document): - ifc.run("document.add_information").should_be_called().will_return("information") - document.import_information().should_be_called() - document.import_document_attributes("information").should_be_called() - document.set_active_document("information").should_be_called() + 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() + 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() + document.import_subdocuments("parent").should_be_called() + document.import_references("parent").should_be_called() subject.add_information(ifc, document) class TestAddReference: def test_run(self, ifc, document): - ifc.run("document.add_reference").should_be_called().will_return("reference") - document.import_references().should_be_called() - document.import_document_attributes("reference").should_be_called() - document.set_active_document("reference").should_be_called() + document.get_active_breadcrumb().should_be_called().will_return("parent") + ifc.run("document.add_reference", information="parent").should_be_called() + document.clear_document_tree().should_be_called() + document.import_subdocuments("parent").should_be_called() + document.import_references("parent").should_be_called() subject.add_reference(ifc, document) -class TestEditInformation: - def test_run(self, ifc, document): +class TestEditDocument: + def test_edit_information(self, ifc, document): document.export_document_attributes().should_be_called().will_return("attributes") - ifc.run("document.edit_information", information="information", attributes="attributes").should_be_called() + document.is_document_information("document").should_be_called().will_return(True) + ifc.run("document.edit_information", information="document", attributes="attributes").should_be_called() document.disable_editing_document().should_be_called() - document.import_information().should_be_called() - subject.edit_information(ifc, document, information="information") + 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") - -class TestEditInformation: - def test_run(self, ifc, document): + def test_edit_reference(self, ifc, document): document.export_document_attributes().should_be_called().will_return("attributes") - ifc.run("document.edit_reference", reference="reference", attributes="attributes").should_be_called() + document.is_document_information("document").should_be_called().will_return(False) + ifc.run("document.edit_reference", reference="document", attributes="attributes").should_be_called() document.disable_editing_document().should_be_called() - document.import_references().should_be_called() - subject.edit_reference(ifc, document, reference="reference") + 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") class TestRemoveDocument: def test_remove_information(self, ifc, document): + document.clear_document_tree().should_be_called() document.is_document_information("document").should_be_called().will_return(True) - ifc.run("document.remove_document", document="document").should_be_called() - document.import_information().should_be_called() - document.disable_editing_document().should_be_called() + ifc.run("document.remove_information", information="document").should_be_called() + document.get_active_breadcrumb().should_be_called().will_return(None) + document.import_project_documents().should_be_called() subject.remove_document(ifc, document, 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_document", document="document").should_be_called() - document.import_references().should_be_called() - document.disable_editing_document().should_be_called() + ifc.run("document.remove_reference", reference="document").should_be_called() + document.get_active_breadcrumb().should_be_called().will_return("parent") + document.import_subdocuments("parent").should_be_called() + document.import_references("parent").should_be_called() subject.remove_document(ifc, document, document="document") -class TestEnableAssigningDocument: - def test_run(self, document): - document.import_references().should_be_called() - document.enable_reference_editing_ui().should_be_called() - document.disable_editing_document().should_be_called() - document.enable_document_assignment_ui("obj").should_be_called() - subject.enable_assigning_document(document, obj="obj") - - -class TestDisableAssigningDocument: - def test_run(self, document): - document.disable_document_assignment_ui("obj").should_be_called() - subject.disable_assigning_document(document, obj="obj") - - class TestAssignDocument: def test_run(self, ifc): ifc.run("document.assign_document", product="product", document="document").should_be_called() diff --git a/src/blenderbim/test/tool/test_document.py b/src/blenderbim/test/tool/test_document.py index 8353e5c8f2..b458eedf1e 100644 --- a/src/blenderbim/test/tool/test_document.py +++ b/src/blenderbim/test/tool/test_document.py @@ -29,12 +29,30 @@ class TestImplementsTool(NewFile): assert isinstance(subject(), blenderbim.core.tool.Document) -class TestDisableDocumentAssignmentUI(NewFile): +class TestAddBreadcrumb(NewFile): def test_run(self): - obj = bpy.data.objects.new("Object", None) - obj.BIMObjectDocumentProperties.is_adding = "foo" - subject.disable_document_assignment_ui(obj) - assert obj.BIMObjectDocumentProperties.is_adding == "" + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + document = ifc.createIfcDocumentInformation() + subject.add_breadcrumb(document) + props = bpy.context.scene.BIMDocumentProperties + assert props.breadcrumbs[0].name == str(document.id()) + + +class TestClearBreadcrumbs(NewFile): + def test_run(self): + props = bpy.context.scene.BIMDocumentProperties + props.breadcrumbs.add() + subject.clear_breadcrumbs() + assert len(props.breadcrumbs) == 0 + + +class TestClearDocumentTree(NewFile): + def test_run(self): + props = bpy.context.scene.BIMDocumentProperties + new = props.documents.add() + subject.clear_document_tree() + assert len(props.documents) == 0 class TestDisableEditingDocument(NewFile): @@ -46,31 +64,16 @@ class TestDisableEditingDocument(NewFile): class TestDisableEditingUI(NewFile): def test_run(self): - bpy.context.scene.BIMDocumentProperties.is_editing = "is_editing" + bpy.context.scene.BIMDocumentProperties.is_editing = True subject.disable_editing_ui() - assert bpy.context.scene.BIMDocumentProperties.is_editing == "" + assert bpy.context.scene.BIMDocumentProperties.is_editing == False -class TestEnableDocumentAssignmentUI(NewFile): +class TestEnableEditingUI(NewFile): def test_run(self): - obj = bpy.data.objects.new("Object", None) - obj.BIMObjectDocumentProperties.is_adding = "" - subject.enable_document_assignment_ui(obj) - assert obj.BIMObjectDocumentProperties.is_adding == "IfcDocumentReference" - - -class TestEnableInformationEditingUI(NewFile): - def test_run(self): - bpy.context.scene.BIMDocumentProperties.is_editing = "" - subject.enable_information_editing_ui() - assert bpy.context.scene.BIMDocumentProperties.is_editing == "information" - - -class TestEnableReferenceEditingUI(NewFile): - def test_run(self): - bpy.context.scene.BIMDocumentProperties.is_editing = "" - subject.enable_reference_editing_ui() - assert bpy.context.scene.BIMDocumentProperties.is_editing == "reference" + bpy.context.scene.BIMDocumentProperties.is_editing = False + subject.enable_editing_ui() + assert bpy.context.scene.BIMDocumentProperties.is_editing == True class TestExportDocumentAttributes(NewFile): @@ -95,6 +98,15 @@ 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() @@ -149,30 +161,51 @@ class TestImportDocumentAttributes(NewFile): assert props.document_attributes.get("Description").string_value == "Description" -class TestImportInformation(NewFile): +class TestImportProjectDocuments(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) - document = ifc.createIfcDocumentInformation() - subject.import_information() + ifc.createIfcProject() + document = ifcopenshell.api.run("document.add_information", ifc) + subject.import_project_documents() props = bpy.context.scene.BIMDocumentProperties 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 == "*" + assert props.documents[0].identification == "X" + assert props.documents[0].is_information is True -class TestImportReference(NewFile): +class TestImportReferences(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc().set(ifc) - document = ifc.createIfcDocumentReference() - subject.import_references() + ifc.createIfcProject() + document = ifcopenshell.api.run("document.add_information", ifc) + reference = ifcopenshell.api.run("document.add_reference", ifc, information=document) + subject.import_references(document) props = bpy.context.scene.BIMDocumentProperties assert len(props.documents) == 1 - assert props.documents[0].ifc_definition_id == document.id() + assert props.documents[0].ifc_definition_id == reference.id() assert props.documents[0].name == "Unnamed" - assert props.documents[0].identification == "*" + assert props.documents[0].identification == "X" + assert props.documents[0].is_information is False + + +class TestImportSubdocuments(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + ifc.createIfcProject() + document = ifcopenshell.api.run("document.add_information", ifc) + subdocument = ifcopenshell.api.run("document.add_information", ifc, parent=document) + subject.import_subdocuments(document) + props = bpy.context.scene.BIMDocumentProperties + assert len(props.documents) == 1 + assert props.documents[0].ifc_definition_id == subdocument.id() + assert props.documents[0].name == "Unnamed" + assert props.documents[0].identification == "X" + assert props.documents[0].is_information is True class TestIsDocumentInformation(NewFile): @@ -184,6 +217,15 @@ class TestIsDocumentInformation(NewFile): assert subject.is_document_information(reference) is False +class TestRemoveLatestBreadcrumb(NewFile): + def test_run(self): + props = bpy.context.scene.BIMDocumentProperties + 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()