From 6eaa13b5f1e1cc723181391782da9394ff42931a Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Thu, 19 Aug 2021 10:50:27 +0200 Subject: [PATCH] BlenderBim : Improve BCF project UX (#1669) * Improve related topic selection UX * Foolproof active topic access with property * Use self when available * Add utility method to explicitely refresh topic * Simplify Refresh topic code * BCF Topics can now be deleted using Blender UI * Revert dynamic enum for related topics * Minor fix and cleanup * Fix bug when trying to display deleted related topic --- .../blenderbim/bim/module/bcf/__init__.py | 1 + .../blenderbim/bim/module/bcf/operator.py | 104 ++++++++++++------ .../blenderbim/bim/module/bcf/prop.py | 56 ++++------ .../blenderbim/bim/module/bcf/ui.py | 23 ++-- 4 files changed, 109 insertions(+), 75 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py index 985caae11f..c64a01ba66 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py @@ -39,6 +39,7 @@ classes = ( operator.AddBcfLabel, operator.AddBcfRelatedTopic, operator.ViewBcfTopic, + operator.RemoveBcfTopic, operator.RemoveBcfComment, operator.RemoveBcfBimSnippet, operator.RemoveBcfReferenceLink, diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index 12ed1685b7..08276a0a33 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -214,7 +214,7 @@ class EditBcfTopicName(bpy.types.Operator): def execute(self, context): props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic bcfxml = bcfstore.BcfStore.get_bcfxml() topic = bcfxml.topics[blender_topic.name] topic.title = blender_topic.title @@ -229,7 +229,7 @@ class EditBcfTopic(bpy.types.Operator): def execute(self, context): props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic bcfxml = bcfstore.BcfStore.get_bcfxml() topic = bcfxml.topics[blender_topic.name] @@ -243,7 +243,7 @@ class EditBcfTopic(bpy.types.Operator): topic.topic_type = blender_topic.type or None bcfxml.edit_topic(topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + props.refresh_topic(context) return {"FINISHED"} @@ -296,7 +296,7 @@ class AddBcfBimSnippet(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bim_snippet = bcf.v2.data.BimSnippet() bim_snippet.reference = props.bim_snippet_reference @@ -316,19 +316,39 @@ class AddBcfRelatedTopic(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} @classmethod - def poll(cls, context): - return bcf_prop.get_related_topics(None, context) + def poll(cls, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = context.scene.BCFProperties + blender_topic = props.active_topic + if not props.related_topic: + return False + if props.related_topic == blender_topic.title: + # Prevent adding self as related topic + return False + related_topic = None + for topic in bcfxml.topics.values(): + if topic.title == props.related_topic: + related_topic = bcf.v2.data.RelatedTopic() + related_topic.guid = topic.guid + break + if not related_topic: + return False + if str(related_topic.guid) in [t.name for t in blender_topic.related_topics]: + # Prevent adding the same related topic more than once + return False + return True def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic related_topic = bcf.v2.data.RelatedTopic() - related_topic.guid = props.related_topic + related_topic.guid = next((t for t in bcfxml.topics.values() if t.title == props.related_topic)).guid topic = bcfxml.topics[blender_topic.name] topic.related_topics.append(related_topic) bcfxml.edit_topic(topic) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) + props.related_topic = "" return {"FINISHED"} @@ -344,7 +364,7 @@ class AddBcfHeaderFile(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] header_file = bcf.v2.data.HeaderFile() header_file.reference = props.file_reference @@ -355,10 +375,7 @@ class AddBcfHeaderFile(bpy.types.Operator): if len(props.file_ifc_spatial_structure_element) == 22: header_file.ifc_spatial_structure_element = props.file_ifc_spatial_structure_element bcfxml.add_file(topic, header_file) - props.active_topic_index = props.active_topic_index # refreshes the BCF Topic - props.file_reference = "" - props.file_ifc_project = "" - props.file_ifc_spatial_structure_element = "" + props.refresh_topic(context) return {"FINISHED"} @@ -389,7 +406,7 @@ class AddBcfViewpoint(bpy.types.Operator): bcfxml = bcfstore.BcfStore.get_bcfxml() blender_camera = context.scene.camera props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] viewpoint = bcf.v2.data.Viewpoint() @@ -424,7 +441,7 @@ class AddBcfViewpoint(bpy.types.Operator): bcfxml.add_viewpoint(topic, viewpoint) blender_render.filepath = old_filepath blender_render.image_settings.file_format = old_file_format - props.active_topic_index = props.active_topic_index # refreshes the BCF Topic + props.refresh_topic(context) return {"FINISHED"} @@ -440,11 +457,11 @@ class RemoveBcfViewpoint(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic viewpoint_guid = blender_topic.viewpoints topic = bcfxml.topics[blender_topic.name] bcfxml.delete_viewpoint(viewpoint_guid, topic) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + props.refresh_topic(context) return {"FINISHED"} @@ -457,10 +474,29 @@ class RemoveBcfFile(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_file(topic, self.index) - props.active_topic_index = props.active_topic_index # Refreshes the BCF Topic + props.refresh_topic(context) + return {"FINISHED"} + + +class RemoveBcfTopic(bpy.types.Operator): + bl_idname = "bim.remove_bcf_topic" + bl_label = "Remove BCF Topic" + bl_options = {"REGISTER", "UNDO"} + guid: bpy.props.StringProperty() + + @classmethod + def poll(cls, context): + return context.scene.BCFProperties.topics + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = context.scene.BCFProperties + topic_to_delete = props.active_topic + bcfxml.delete_topic(topic_to_delete.name) + bpy.ops.bim.load_bcf_topics() return {"FINISHED"} @@ -476,7 +512,7 @@ class AddBcfReferenceLink(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] topic.reference_links.append(props.reference_link) bcfxml.edit_topic(topic) @@ -497,7 +533,7 @@ class AddBcfDocumentReference(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] document_reference = bcf.v2.data.DocumentReference() document_reference.referenced_document = props.document_reference @@ -521,7 +557,7 @@ class AddBcfLabel(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] new = blender_topic.labels.add() new.name = props.label @@ -539,7 +575,7 @@ class EditBcfReferenceLinks(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] for index, reference_link in enumerate(topic.reference_links): if reference_link == blender_topic.reference_links[index].name: @@ -557,7 +593,7 @@ class EditBcfLabels(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] for index, label in enumerate(blender_topic.labels): if index >= len(topic.labels): @@ -578,7 +614,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] del topic.reference_links[self.index] bcfxml.edit_topic(topic) @@ -595,7 +631,7 @@ class RemoveBcfLabel(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] del topic.labels[self.index] bcfxml.edit_topic(topic) @@ -611,7 +647,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_bim_snippet(topic) blender_topic.bim_snippet.schema = "" @@ -629,7 +665,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_document_reference(topic, self.index) bpy.ops.bim.load_bcf_topic(topic_guid = topic.guid, topic_index = props.active_topic_index) @@ -645,7 +681,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] del topic.related_topics[self.index] bcfxml.edit_topic(topic) @@ -662,7 +698,7 @@ class RemoveBcfComment(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] bcfxml.delete_comment(self.comment_guid, topic) bpy.ops.bim.load_bcf_comments(topic_guid = topic.guid) @@ -678,7 +714,7 @@ class EditBcfComment(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic blender_comment = blender_topic.comments.get(self.comment_guid) topic = bcfxml.topics[blender_topic.name] comment = topic.comments[self.comment_guid] @@ -706,7 +742,7 @@ class AddBcfComment(bpy.types.Operator): def execute(self, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] comment = bcf.v2.data.Comment() comment.comment = props.comment @@ -729,7 +765,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): def poll(cls, context): bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] return topic.viewpoints @@ -737,7 +773,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): self.file = IfcStore.get_file() bcfxml = bcfstore.BcfStore.get_bcfxml() props = context.scene.BCFProperties - blender_topic = props.topics[props.active_topic_index] + blender_topic = props.active_topic topic = bcfxml.topics[blender_topic.name] viewpoint_guid = blender_topic.viewpoints diff --git a/src/blenderbim/blenderbim/bim/module/bcf/prop.py b/src/blenderbim/blenderbim/bim/module/bcf/prop.py index d21f315234..8f20938e64 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/prop.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/prop.py @@ -34,14 +34,11 @@ from bpy.props import ( bcfviewpoints_enum = None -related_topic_enum = [] def purge(): global bcfviewpoints_enum - global related_topic_enum bcfviewpoints_enum = None - related_topic_enum.clear() def updateBcfReferenceLink(self, context): @@ -55,12 +52,12 @@ def updateBcfLabel(self, context): def updateBcfProjectName(self, context): - if context.scene.BCFProperties.is_loaded: + if self.is_loaded: bpy.ops.bim.edit_bcf_project_name() def updateBcfAuthor(self, context): - if context.scene.BCFProperties.is_loaded: + if self.is_loaded: bpy.ops.bim.edit_bcf_author() @@ -79,30 +76,9 @@ def updateBcfCommentIsEditable(self, context): bpy.ops.bim.edit_bcf_comment(comment_guid = self.name) -def get_related_topics(self, context): - global related_topic - related_topic_enum.clear() - props = context.scene.BCFProperties - current_topic = props.topics[props.active_topic_index] - for topic in props.topics: - if topic == current_topic: - continue - if topic.name in current_topic.related_topics: - continue - related_topic_enum.append((topic.name, topic.title, topic.description)) - return related_topic_enum - - def refreshBcfTopic(self, context): - global bcfviewpoints_enum - bcfviewpoints_enum = None - - props = context.scene.BCFProperties - bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = props.topics[props.active_topic_index] - header = bcfxml.get_header(topic.name) - props.clear_input_fields() - getBcfViewpoints(self, context) + self.clear_input_fields() + getBcfViewpoints(None, context, force_update=True) class BcfReferenceLink(PropertyGroup): @@ -113,13 +89,13 @@ class BcfLabel(PropertyGroup): name: StringProperty(name="Name", update=updateBcfLabel) -def getBcfViewpoints(self, context): +def getBcfViewpoints(self, context, force_update=False): global bcfviewpoints_enum - if bcfviewpoints_enum is None: + if bcfviewpoints_enum is None or force_update: # Retrieving Viewpoints is slow. Make sure we only do when needed bcfviewpoints_enum = [] props = context.scene.BCFProperties bcfxml = bcfstore.BcfStore.get_bcfxml() - topic = props.topics[props.active_topic_index] + topic = props.active_topic viewpoints = bcfxml.get_viewpoints(topic.name) bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())]) return bcfviewpoints_enum @@ -164,7 +140,7 @@ class BcfTopic(PropertyGroup): assigned_to: StringProperty(default="", name="Assigned To") due_date: StringProperty(default="", name="Due Date") description: StringProperty(default="", name="Description") - viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") + viewpoints: EnumProperty(items=lambda _, context: getBcfViewpoints(_, context), name="BCF Viewpoints") files: CollectionProperty(name="Files", type=StrProperty) reference_links: CollectionProperty(name="Reference Links", type=BcfReferenceLink) labels: CollectionProperty(name="Labels", type=BcfLabel) @@ -192,7 +168,7 @@ class BCFProperties(PropertyGroup): bim_snippet_schema: StringProperty(default="", name="Schema") document_reference: StringProperty(default="", name="Referenced Document") document_reference_description: StringProperty(default="", name="Description") - related_topic: EnumProperty(name="Related Topic", items=get_related_topics) + related_topic: StringProperty(name="Related Topic") comment: StringProperty(default="", name="Comment") has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False) @@ -207,5 +183,19 @@ class BCFProperties(PropertyGroup): self.bim_snippet_schema = "" self.document_reference = "" self.document_reference_description = "" + self.related_topic = "" self.comment = "" self.has_related_viewpoint = False + + @property + def active_topic(self): + if len(self.topics) == 0: + return None + if self.active_topic_index < 0: + self.active_topic_index = 0 + if self.active_topic_index >= len(self.topics): + self.active_topic_index = len(self.topics) - 1 + return self.topics[self.active_topic_index] + + def refresh_topic(self, context): + refreshBcfTopic(self, context) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index 4aeb717a7c..dfa8de1aa6 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -58,12 +58,13 @@ class BIM_PT_bcf(Panel): row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index") col = row.column(align=True) col.operator("bim.add_bcf_topic", icon="ADD", text="") + col.operator("bim.remove_bcf_topic", icon="REMOVE", text="") if props.active_topic_index < len(props.topics): - topic = props.topics[props.active_topic_index] + topic = props.active_topic col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True) if props.active_topic_index < len(props.topics): - topic = props.topics[props.active_topic_index] + topic = props.active_topic row = layout.row() row.enabled = topic.is_editable row.prop(topic, "description", text="") @@ -118,7 +119,7 @@ class BIM_PT_bcf_metadata(Panel): layout.label(text="No BCF project is loaded") return - topic = props.topics[props.active_topic_index] + topic = props.active_topic bcfxml = bcfstore.BcfStore.get_bcfxml() bcf_topic = bcfxml.topics[topic.name] @@ -230,10 +231,16 @@ class BIM_PT_bcf_metadata(Panel): row.operator("bim.add_bcf_document_reference") layout.label(text="Related Topics:") - for index, related_topic in enumerate(topic.related_topics): - row = layout.row(align=True) - row.operator("bim.view_bcf_topic", text=bcfxml.topics[related_topic.name.lower()].title).topic_guid = related_topic.name - row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index + for index, related_topic in enumerate(topic.related_topics): + try: + row = layout.row(align=True) + op = row.operator( + "bim.view_bcf_topic", + text=f"Select {bcfxml.topics[related_topic.name.lower()].title}") + op.topic_guid = related_topic.name + row.operator("bim.remove_bcf_related_topic", icon="X", text="").index = index + except KeyError: + pass row = layout.row() row.prop(props, "related_topic") row = layout.row() @@ -264,7 +271,7 @@ class BIM_PT_bcf_comments(Panel): row = layout.row() row.prop(props, "comment_text_width") - topic = props.topics[props.active_topic_index] + topic = props.active_topic for comment in topic.comments: box = self.layout.box()