From 7dd40149aa0388cbc340d45dfdc6d3b271ac81b2 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 18 Dec 2020 16:28:17 +1100 Subject: [PATCH] Migrate from bcfplugin to new bcf library in the BlenderBIM Add-on --- src/bcf/bcf/bcfxml.py | 27 +- src/ifcblenderexport/Makefile | 55 +--- .../blenderbim/bim/__init__.py | 13 +- src/ifcblenderexport/blenderbim/bim/bcf.py | 4 - .../blenderbim/bim/bcfstore.py | 11 + .../blenderbim/bim/operator.py | 246 ++++++++-------- src/ifcblenderexport/blenderbim/bim/prop.py | 276 +++--------------- src/ifcblenderexport/blenderbim/bim/ui.py | 166 +++++------ 8 files changed, 292 insertions(+), 506 deletions(-) delete mode 100644 src/ifcblenderexport/blenderbim/bim/bcf.py create mode 100644 src/ifcblenderexport/blenderbim/bim/bcfstore.py diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index a1d0f5b674..40069ceb15 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -30,8 +30,19 @@ class BcfXml: self.logger = logging.getLogger("bcfxml") self.author = "john@doe.com" self.project = bcf.data.Project() + self.version = "2.1" self.topics = {} + def new_project(self): + self.project.project_id = str(uuid.uuid4()) + self.project.name = "New Project" + self.topics = {} + if self.filepath: + self.close_project() + self.filepath = tempfile.mkdtemp() + self.edit_project() + self.edit_version() + def get_project(self, filepath=None): if not filepath: return self.project @@ -62,7 +73,15 @@ class BcfXml: def get_version(self): data = self._read_xml("bcf.version", "version.xsd") - return data["@VersionId"] + self.version = data["@VersionId"] + return self.version + + def edit_version(self): + self.document = minidom.Document() + root = self._create_element(self.document, "Version", {"VersionId": self.version}) + version = self._create_element(root, "DetailedVersion", text=self.version) + with open(os.path.join(self.filepath, "bcf.version"), "wb") as f: + f.write(self.document.toprettyxml(encoding="utf-8")) def get_topics(self): self.topics = {} @@ -117,14 +136,15 @@ class BcfXml: "priority": "Priority", "index": "Index", "labels": "Labels", + "reference_links": "ReferenceLink", "modified_date": "ModifiedDate", "modified_author": "ModifiedAuthor", "due_date": "DueDate", "assigned_to": "AssignedTo", "stage": "Stage", "description": "Description", - "topic_status": "TopicStatus", - "topic_type": "TopicType", + "topic_status": "@TopicStatus", + "topic_type": "@TopicType", } for key, value in optional_keys.items(): if value in data["Topic"]: @@ -173,6 +193,7 @@ class BcfXml: topic.title = "New Topic" os.mkdir(os.path.join(self.filepath, topic.guid)) self.edit_topic(topic) + return topic def edit_topic(self, topic): if not topic.creation_date: diff --git a/src/ifcblenderexport/Makefile b/src/ifcblenderexport/Makefile index b56b956ed8..842a8c08eb 100644 --- a/src/ifcblenderexport/Makefile +++ b/src/ifcblenderexport/Makefile @@ -138,57 +138,22 @@ endif cp -r dist/working/pyparsing-2.4.5/pyparsing.py dist/blenderbim/libs/site/packages/ rm -rf dist/working - # Required by bcfplugin - mkdir dist/working - cd dist/working && wget https://files.pythonhosted.org/packages/82/c3/534ddba230bd4fbbd3b7a3d35f3341d014cca213f369a9940925e7e5f691/pytz-2019.3.tar.gz - cd dist/working && tar -xzvf pytz* - cp -r dist/working/pytz-2019.3/pytz dist/blenderbim/libs/site/packages/ - rm -rf dist/working - - # Required by bcfplugin - mkdir dist/working - cd dist/working && wget https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz - cd dist/working && tar -xzvf python-dateutil* - cp -r dist/working/python-dateutil-2.8.1/dateutil dist/blenderbim/libs/site/packages/ - rm -rf dist/working - - # Required by bcfplugin - mkdir dist/working - cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz - cd dist/working && tar -xzvf six* - cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/ - rm -rf dist/working - - # Required by bcfplugin + # Required by bcf mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/bb/41/ad9ce53bb978b68af8ae415293cafc89b165b8ad55a593725299dca76729/xmlschema-1.1.1.tar.gz cd dist/working && tar -xzvf xmlschema* cp -r dist/working/xmlschema-1.1.1/xmlschema dist/blenderbim/libs/site/packages/ rm -rf dist/working - # Required by bcfplugin - mkdir dist/working - cd dist/working && wget https://files.pythonhosted.org/packages/12/f9/f9960222d5274944b01391749e55e4dcdf28d8f0c108b64ac931ceff6fdb/elementpath-1.4.3.tar.gz - cd dist/working && tar -xzvf elementpath* - cp -r dist/working/elementpath-1.4.3/elementpath dist/blenderbim/libs/site/packages/ - rm -rf dist/working - - # Provides bcfplugin functionality - mkdir dist/working - cd dist/working && wget https://github.com/podestplatz/bcf/archive/master.zip - cd dist/working && unzip master* - cp -r dist/working/bcf-master/bcfplugin dist/blenderbim/libs/site/packages/ - rm -rf dist/working - - # Required by bcfplugin - mkdir dist/working - cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/project.xsd - cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Extension%20Schemas/extensions.xsd - cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/markup.xsd - cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/version.xsd - cd dist/working && wget https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_2_1/Schemas/visinfo.xsd - mkdir dist/blenderbim/libs/site/packages/bcfplugin/schemas/ - cp -r dist/working/*.xsd dist/blenderbim/libs/site/packages/bcfplugin/schemas/ + # Provides bcf functionality + mkdir dist/blenderbim/libs/site/packages/bcf + mkdir dist/blenderbim/libs/site/packages/bcf/xsd + cd dist/blenderbim/libs/site/packages/bcf && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/bcfxml.py + cd dist/blenderbim/libs/site/packages/bcf && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/data.py + cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/markup.xsd + cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/project.xsd + cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/version.xsd + cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/visinfo.xsd rm -rf dist/working # Required by IFCCSV and ifcopenshell.util.selector diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 10327fbd87..0a0c216529 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -27,15 +27,13 @@ if bpy is not None: operator.SelectType, operator.NewBcfProject, operator.LoadBcfProject, + operator.LoadBcfTopics, operator.SaveBcfProject, operator.AddBcfTopic, operator.ViewBcfTopic, operator.ActivateBcfViewpoint, - operator.OpenBcfFileReference, + operator.OpenUri, operator.OpenBcfReferenceLink, - operator.OpenBcfBimSnippetSchema, - operator.OpenBcfBimSnippetReference, - operator.OpenBcfDocumentReference, operator.SelectFeaturesDir, operator.SelectDiffJsonFile, operator.SelectDiffNewFile, @@ -260,12 +258,9 @@ if bpy is not None: prop.Schedule, prop.DrawingStyle, prop.Sheet, + prop.BcfBimSnippet, + prop.BcfDocumentReference, prop.BcfTopic, - prop.BcfTopicLabel, - prop.BcfTopicFile, - prop.BcfTopicLink, - prop.BcfTopicDocumentReference, - prop.BcfTopicRelatedTopic, prop.Subcontext, prop.PresentationLayer, prop.BIMProperties, diff --git a/src/ifcblenderexport/blenderbim/bim/bcf.py b/src/ifcblenderexport/blenderbim/bim/bcf.py deleted file mode 100644 index 7fad365aac..0000000000 --- a/src/ifcblenderexport/blenderbim/bim/bcf.py +++ /dev/null @@ -1,4 +0,0 @@ -class BcfStore: - topics = [] - viewpoints = [] - comments = [] diff --git a/src/ifcblenderexport/blenderbim/bim/bcfstore.py b/src/ifcblenderexport/blenderbim/bim/bcfstore.py new file mode 100644 index 0000000000..36960fc0a5 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/bcfstore.py @@ -0,0 +1,11 @@ +import bcf +import bcf.bcfxml + +class BcfStore: + bcfxml = None + + @staticmethod + def get_bcfxml(): + if not BcfStore.bcfxml: + BcfStore.bcfxml = bcf.bcfxml.BcfXml() + return BcfStore.bcfxml diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 0d67291e18..5c2b750aa1 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -20,7 +20,7 @@ from . import svgwriter from . import sheeter from . import scheduler from . import schema -from . import bcf +from . import bcfstore from . import ifc from . import annotation from . import helper @@ -512,16 +512,9 @@ class NewBcfProject(bpy.types.Operator): bl_label = "New BCF Project" def execute(self, context): - import bcfplugin - bcfplugin.newProject("New BCF Project") - bcf.BcfStore.topics = bcfplugin.getTopics() - bpy.context.scene.BCFProperties.is_editable = False - bpy.context.scene.BCFProperties.name = bcfplugin.getProjectName() - while len(bpy.context.scene.BCFProperties.topics) > 0: - bpy.context.scene.BCFProperties.topics.remove(0) - bpy.context.scene.BCFProperties.is_editable = True - bpy.context.scene.BCFProperties.directory = bcfplugin.util.getBcfDir() - bpy.context.scene.BCFProperties.is_loaded = True + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.new_project() + bpy.ops.bim.load_bcf_project() return {"FINISHED"} @@ -531,18 +524,12 @@ class LoadBcfProject(bpy.types.Operator): filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): - import bcfplugin - - bcfplugin.openProject(self.filepath) - bcf.BcfStore.topics = bcfplugin.getTopics() + bcfxml = bcfstore.BcfStore.get_bcfxml() + if self.filepath: + bcfxml.get_project(self.filepath) bpy.context.scene.BCFProperties.is_editable = False - bpy.context.scene.BCFProperties.name = bcfplugin.getProjectName() - while len(bpy.context.scene.BCFProperties.topics) > 0: - bpy.context.scene.BCFProperties.topics.remove(0) - for topic in bcf.BcfStore.topics: - new = bpy.context.scene.BCFProperties.topics.add() - new.name = topic[0] - bpy.context.scene.BCFProperties.is_editable = True + bpy.context.scene.BCFProperties.name = bcfxml.project.name + bpy.ops.bim.load_bcf_topics() bpy.context.scene.BCFProperties.is_loaded = True return {"FINISHED"} @@ -551,14 +538,76 @@ class LoadBcfProject(bpy.types.Operator): return {"RUNNING_MODAL"} +class LoadBcfTopics(bpy.types.Operator): + bl_idname = "bim.load_bcf_topics" + bl_label = "Load BCF Topics" + + def execute(self, context): + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.get_topics() + while len(bpy.context.scene.BCFProperties.topics) > 0: + bpy.context.scene.BCFProperties.topics.remove(0) + for topic in bcfxml.topics.values(): + new = bpy.context.scene.BCFProperties.topics.add() + data_map = { + "name": topic.title, + "guid": topic.guid, + "type": topic.topic_type, + "status": topic.topic_status, + "priority": topic.priority, + "stage": topic.stage, + "creation_date": topic.creation_date, + "creation_author": topic.creation_author, + "modified_date": topic.modified_date, + "modified_author": topic.modified_author, + "assigned_to": topic.assigned_to, + "due_date": topic.due_date, + "description": topic.description + } + for key, value in data_map.items(): + if value is not None: + setattr(new, key, str(value)) + for reference_link in topic.reference_links: + new2 = new.reference_links.add() + new2.name = reference_link + for label in topic.labels: + new2 = new.labels.add() + new2.name = label + if topic.bim_snippet: + data_map = { + "type": topic.bim_snippet.snippet_type, + "is_external": topic.bim_snippet.is_external, + "reference": topic.bim_snippet.reference, + "schema": topic.bim_snippet.reference_schema + } + for key, value in data_map.items(): + if value is not None: + setattr(new.bim_snippet, key, value) + for doc in topic.document_references: + new2 = new.document_references.add() + data_map = { + "reference": doc.referenced_document, + "description": doc.description, + "guid": doc.guid, + "is_external": doc.is_external + } + for key, value in data_map.items(): + if value is not None: + setattr(new2, key, value) + for related_topic in topic.related_topics: + new2 = new.related_topics.add() + new2.name = related_topic.guid + return {"FINISHED"} + + class SaveBcfProject(bpy.types.Operator): bl_idname = "bim.save_bcf_project" bl_label = "Save BCF Project" filepath: bpy.props.StringProperty(subtype="FILE_PATH") def execute(self, context): - import bcfplugin - bcfplugin.saveProject(self.filepath) + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.save_project(self.filepath) return {"FINISHED"} def invoke(self, context, event): @@ -571,13 +620,11 @@ class AddBcfTopic(bpy.types.Operator): bl_label = "Add BCF Topic" def execute(self, context): - import bcfplugin - bcfplugin.addTopic("New Topic", bpy.context.scene.BCFProperties.author) - bpy.context.scene.BCFProperties.is_editable = False + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcfxml.add_topic() new = bpy.context.scene.BCFProperties.topics.add() new.name = "New Topic" - bcf.BcfStore.topics = bcfplugin.getTopics() - bpy.context.scene.BCFProperties.is_editable = True + bpy.ops.bim.load_bcf_topics() return {"FINISHED"} @@ -587,8 +634,8 @@ class ViewBcfTopic(bpy.types.Operator): topic_guid: bpy.props.StringProperty() def execute(self, context): - for index, topic in enumerate(bcf.BcfStore.topics): - if str(topic[1].xmlId) == self.topic_guid: + for index, topic in enumerate(bpy.context.scene.BCFProperties.topics): + if topic.guid.lower() == self.topic_guid.lower(): bpy.context.scene.BCFProperties.active_topic_index = index return {"FINISHED"} @@ -598,18 +645,15 @@ class ActivateBcfViewpoint(bpy.types.Operator): bl_label = "Activate BCF Viewpoint" def execute(self, context): - import bcfplugin - - topics = bcf.BcfStore.topics - if not topics: + bcfxml = bcfstore.BcfStore.get_bcfxml() + props = bpy.context.scene.BCFProperties + blender_topic = props.topics[props.active_topic_index] + topic = bcfxml.topics[blender_topic.guid] + if not topic.viewpoints: return {"FINISHED"} - topic = topics[bpy.context.scene.BCFProperties.active_topic_index][1] - viewpoints = bcf.BcfStore.viewpoints - if not viewpoints: - return {"FINISHED"} - viewpoint_reference = viewpoints[int(bpy.context.scene.BCFProperties.viewpoints)][1] - viewpoint = viewpoint_reference.viewpoint + viewpoint_guid = blender_topic.viewpoints + viewpoint = topic.viewpoints[viewpoint_guid] obj = bpy.data.objects.get("Viewpoint") if not obj: obj = bpy.data.objects.new("Viewpoint", bpy.data.cameras.new("Viewpoint")) @@ -620,13 +664,13 @@ class ActivateBcfViewpoint(bpy.types.Operator): cam_height = bpy.context.scene.render.resolution_y cam_aspect = cam_width / cam_height - if viewpoint_reference.snapshot: + if viewpoint.snapshot: obj.data.show_background_images = True while len(obj.data.background_images) > 0: obj.data.background_images.remove(obj.data.background_images[0]) background = obj.data.background_images.new() background.image = bpy.data.images.load( - os.path.join(bcfplugin.util.getBcfDir(), str(topic.xmlId), viewpoint_reference.snapshot.uri) + os.path.join(bcfxml.filepath, topic.guid, viewpoint.snapshot) ) src_width = background.image.size[0] src_height = background.image.size[1] @@ -640,18 +684,18 @@ class ActivateBcfViewpoint(bpy.types.Operator): area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") area.spaces[0].region_3d.view_perspective = "CAMERA" - if viewpoint.oCamera: - camera = viewpoint.oCamera + if viewpoint.orthogonal_camera: + camera = viewpoint.orthogonal_camera obj.data.type = "ORTHO" - obj.data.ortho_scale = viewpoint.oCamera.viewWorldScale - elif viewpoint.pCamera: - camera = viewpoint.pCamera + obj.data.ortho_scale = viewpoint.orthogonal_camera.view_to_world_scale + elif viewpoint.perspective_camera: + camera = viewpoint.perspective_camera obj.data.type = "PERSP" if cam_aspect >= 1: - obj.data.angle = radians(camera.fieldOfView) + obj.data.angle = radians(camera.field_of_view) else: # https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov - obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.fieldOfView) / 2))) + obj.data.angle = 2 * atan((0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))) self.set_viewpoint_components(viewpoint) @@ -662,37 +706,37 @@ class ActivateBcfViewpoint(bpy.types.Operator): self.draw_lines(viewpoint) self.delete_clipping_planes() - if viewpoint.clippingPlanes: + if viewpoint.clipping_planes: self.create_clipping_planes(viewpoint) self.delete_bitmaps() if viewpoint.bitmaps: - self.create_bitmaps(viewpoint) + self.create_bitmaps(bcfxml, viewpoint, topic) - z_axis = Vector((-camera.direction.x, -camera.direction.y, -camera.direction.z)).normalized() - y_axis = Vector((camera.upVector.x, camera.upVector.y, camera.upVector.z)).normalized() + z_axis = Vector((-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)).normalized() + y_axis = Vector((camera.camera_up_vector.x, camera.camera_up_vector.y, camera.camera_up_vector.z)).normalized() x_axis = y_axis.cross(z_axis).normalized() rotation = Matrix((x_axis, y_axis, z_axis)) rotation.invert() - location = Vector((camera.viewPoint.x, camera.viewPoint.y, camera.viewPoint.z)) + location = Vector((camera.camera_view_point.x, camera.camera_view_point.y, camera.camera_view_point.z)) obj.matrix_world = rotation.to_4x4() obj.location = location return {"FINISHED"} def set_viewpoint_components(self, viewpoint): - selected_global_ids = [s.ifcId for s in viewpoint.components.selection] - exception_global_ids = [v.ifcId for v in viewpoint.components.visibilityExceptions] + selected_global_ids = [s.ifc_guid for s in viewpoint.components.selection] + exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions] global_id_colours = {} - for colouring in viewpoint.components.colouring: - for component in colouring.components: - global_id_colours.setdefault(component.ifcId, colouring.colour) + for coloring in viewpoint.components.coloring: + for component in coloring.components: + global_id_colours.setdefault(component.ifc_guid, coloring.color) for obj in bpy.data.objects: global_id = obj.BIMObjectProperties.attributes.get("GlobalId") if not global_id: continue global_id = global_id.string_value - is_visible = viewpoint.components.visibilityDefault + is_visible = viewpoint.components.visibility.default_visibility if global_id in exception_global_ids: is_visible = not is_visible if not is_visible: @@ -723,12 +767,12 @@ class ActivateBcfViewpoint(bpy.types.Operator): stroke.points.add(len(viewpoint.lines) * 2) coords = [] for l in viewpoint.lines: - coords.extend([l.start.x, l.start.y, l.start.z, l.end.x, l.end.y, l.end.z]) + coords.extend([l.start_point.x, l.start_point.y, l.start_point.z, l.end_point.x, l.end_point.y, l.end_point.z]) stroke.points.foreach_set("co", coords) def create_clipping_planes(self, viewpoint): n = 0 - for plane in viewpoint.clippingPlanes: + for plane in viewpoint.clipping_planes: bpy.ops.bim.add_section_plane() if n == 0: obj = bpy.data.objects["Section"] @@ -757,18 +801,14 @@ class ActivateBcfViewpoint(bpy.types.Operator): for bitmap in collection.objects: bpy.data.objects.remove(bitmap) - def create_bitmaps(self, viewpoint): - import bcfplugin - - topics = bcf.BcfStore.topics - topic = topics[bpy.context.scene.BCFProperties.active_topic_index][1] + def create_bitmaps(self, bcfxml, viewpoint, topic): collection = bpy.data.collections.get("Bitmaps") if not collection: collection = bpy.data.collections.new("Bitmaps") for bitmap in viewpoint.bitmaps: obj = bpy.data.objects.new("Bitmap", None) obj.empty_display_type = "IMAGE" - image = bpy.data.images.load(os.path.join(bcfplugin.util.getBcfDir(), str(topic.xmlId), bitmap.reference)) + image = bpy.data.images.load(os.path.join(bcfxml.filepath, topic.guid, bitmap.reference)) src_width = image.size[0] src_height = image.size[1] if src_height > src_width: @@ -776,7 +816,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): else: obj.empty_display_size = bitmap.height * (src_width / src_height) obj.data = image - y = Vector((bitmap.upVector.x, bitmap.upVector.y, bitmap.upVector.z)) + y = Vector((bitmap.up.x, bitmap.up.y, bitmap.up.z)) z = Vector((bitmap.normal.x, bitmap.normal.y, bitmap.normal.z)) x = y.cross(z) obj.matrix_world = Matrix( @@ -792,22 +832,13 @@ class ActivateBcfViewpoint(bpy.types.Operator): return [t[0] / 255.0, t[1] / 255.0, t[2] / 255.0, 1] -class OpenBcfFileReference(bpy.types.Operator): - bl_idname = "bim.open_bcf_file_reference" - bl_label = "Open BCF File Reference" - data: bpy.props.StringProperty() +class OpenUri(bpy.types.Operator): + bl_idname = "bim.open_uri" + bl_label = "Open URI" + uri: bpy.props.StringProperty() def execute(self, context): - if "/" not in self.data: - webbrowser.open(bpy.context.scene.BCFProperties.topic_files[int(self.data)].reference) - return {"FINISHED"} - import bcfplugin - - topic_guid, index = self.data.split("/") - path = os.path.join(bcfplugin.util.getBcfDir(), topic_guid) - # bpy.context.scene.BCFProperties.topic_files[int(index)].reference) - # TODO - maybe allow immediate importing? - webbrowser.open(path) + webbrowser.open(self.uri) return {"FINISHED"} @@ -821,53 +852,6 @@ class OpenBcfReferenceLink(bpy.types.Operator): return {"FINISHED"} -class OpenBcfBimSnippetSchema(bpy.types.Operator): - bl_idname = "bim.open_bcf_bim_snippet_schema" - bl_label = "Open BCF BIM Snippet Schema" - - def execute(self, context): - webbrowser.open(bpy.context.scene.BCFProperties.topic_snippet_schema) - return {"FINISHED"} - - -class OpenBcfBimSnippetReference(bpy.types.Operator): - bl_idname = "bim.open_bcf_bim_snippet_reference" - bl_label = "Open BCF BIM Snippet Reference" - topic_guid: bpy.props.StringProperty() - - def execute(self, context): - import bcfplugin - - if bpy.context.scene.BCFProperties.topic_snippet_is_external: - webbrowser.open(bpy.context.scene.BCFProperties.topic_snippet_reference) - return {"FINISHED"} - webbrowser.open( - "file://" - + os.path.join( - bcfplugin.util.getBcfDir(), self.topic_guid, bpy.context.scene.BCFProperties.topic_snippet_reference - ) - ) - return {"FINISHED"} - - -class OpenBcfDocumentReference(bpy.types.Operator): - bl_idname = "bim.open_bcf_document_reference" - bl_label = "Open BCF Document Reference" - data: bpy.props.StringProperty() - - def execute(self, context): - import bcfplugin - - topic_guid, index = self.data.split("/") - doc = bpy.context.scene.BCFProperties.topic_document_references[int(index)] - uri = doc.name - if doc.is_external: - webbrowser.open(uri) - return {"FINISHED"} - webbrowser.open("file://" + os.path.join(bcfplugin.util.getBcfDir(), topic_guid, uri)) - return {"FINISHED"} - - class SelectAudited(bpy.types.Operator): bl_idname = "bim.select_audited" bl_label = "Select Audited" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 5b032daec9..b7d167f6aa 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -5,7 +5,7 @@ import ifcopenshell.util.pset from pathlib import Path from . import export_ifc from . import schema -from . import bcf +from . import bcfstore from . import ifc from . import annotation from . import decoration @@ -52,7 +52,7 @@ persons_enum = [] organisations_enum = [] sheets_enum = [] vector_styles_enum = [] -bcfviewpoints_enum = [] +bcfviewpoints_enum = None @persistent @@ -910,227 +910,64 @@ class Constraint(PropertyGroup): user_defined_qualifier: StringProperty(name="Custom Qualifier") -def updateBcfProjectName(self, context): - import bcfplugin - bcfplugin.setProjectName(self.name) +def getBcfViewpoints(self, context): + global bcfviewpoints_enum + if bcfviewpoints_enum is None: + bcfviewpoints_enum = [] + props = bpy.context.scene.BCFProperties + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = props.topics[props.active_topic_index] + viewpoints = bcfxml.get_viewpoints(topic.guid) + bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())]) + return bcfviewpoints_enum -def updateBcfTopicAttribute(context, name, value): - if not bpy.context.scene.BCFProperties.is_editable: - return - import bcfplugin - props = bpy.context.scene.BCFProperties - topic = bcf.BcfStore.topics[props.active_topic_index][1] - if getattr(topic, name) != value: - setattr(topic, name, value) - bcfplugin.modifyElement(topic, props.author) - - -def updateBcfTopicName(self, context): - updateBcfTopicAttribute(context, "title", self.name) - - -def updateBcfTopicType(self, context): - updateBcfTopicAttribute(context, "type", self.topic_type) - - -def updateBcfTopicStatus(self, context): - updateBcfTopicAttribute(context, "status", self.topic_status) - - -def updateBcfTopicPriority(self, context): - updateBcfTopicAttribute(context, "priority", self.topic_priority) - - -def updateBcfTopicStage(self, context): - updateBcfTopicAttribute(context, "stage", self.topic_stage) - - -def updateBcfTopicDescription(self, context): - updateBcfTopicAttribute(context, "description", self.topic_description) - - -class BcfTopic(PropertyGroup): - name: StringProperty(name="Name", update=updateBcfTopicName) - - -class BcfTopicLabel(PropertyGroup): - name: StringProperty(name="Name") - - -class BcfTopicLink(PropertyGroup): - name: StringProperty(name="Name") - - -class BcfTopicFile(PropertyGroup): - name: StringProperty(name="Name") +class BcfBimSnippet(PropertyGroup): + schema: StringProperty(name="Schema") reference: StringProperty(name="Reference") - date: StringProperty(name="Date") + type: StringProperty(name="Type") is_external: BoolProperty(name="Is External") - ifc_project: StringProperty(name="IFC Project") - ifc_spatial: StringProperty(name="IFC Spatial") -class BcfTopicDocumentReference(PropertyGroup): - name: StringProperty(name="Reference") +class BcfDocumentReference(PropertyGroup): + reference: StringProperty(name="Reference") description: StringProperty(name="Description") guid: StringProperty(name="GUID") is_external: BoolProperty(name="Is External") -class BcfTopicRelatedTopic(PropertyGroup): +class BcfTopic(PropertyGroup): name: StringProperty(name="Name") - guid: StringProperty(name="GUID") + guid: StringProperty(default="", name="GUID") + type: StringProperty(default="", name="Type") + status: StringProperty(default="", name="Status") + priority: StringProperty(default="", name="Priority") + stage: StringProperty(default="", name="Stage") + creation_date: StringProperty(default="", name="Date") + creation_author: StringProperty(default="", name="Author") + modified_date: StringProperty(default="", name="Modified Date") + modified_author: StringProperty(default="", name="Modified By") + 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") + files: CollectionProperty(name="Files", type=StrProperty) + reference_links: CollectionProperty(name="Reference Links", type=StrProperty) + labels: CollectionProperty(name="Labels", type=StrProperty) + bim_snippet: PointerProperty(type=BcfBimSnippet) + document_references: CollectionProperty(name="Document References", type=BcfDocumentReference) + related_topics: CollectionProperty(name="Related Topics", type=StrProperty) def refreshBcfTopic(self, context): - RefreshBcfTopic.refresh(context) - - -class RefreshBcfTopic: - props: None - topic: None - - @classmethod - def refresh(cls, context): - - global bcfviewpoints_enum - - cls.props = bpy.context.scene.BCFProperties - cls.topic = bcf.BcfStore.topics[cls.props.active_topic_index][1] - - cls.load_topic_metadata() - cls.load_topic_labels() - cls.load_topic_files() - cls.load_topic_links() - cls.load_snippet() - cls.load_document_references() - cls.load_related_topics() - cls.load_viewpoints() - cls.load_comments() - - @classmethod - def load_topic_metadata(cls): - cls.props.topic_guid = str(cls.topic.xmlId) - cls.props.topic_type = cls.topic.type - cls.props.topic_status = cls.topic.status - cls.props.topic_priority = cls.topic.priority - cls.props.topic_stage = cls.topic.stage - if cls.topic.date: - cls.props.topic_creation_date = cls.topic.date.strftime("%a %Y-%m-%d %H:%S") - else: - cls.props.topic_creation_date = "" - cls.props.topic_creation_author = cls.topic.author - if cls.topic.modDate: - cls.props.topic_modified_date = cls.topic.modDate.strftime("%a %Y-%m-%d %H:%S") - else: - cls.props.topic_modified_date = "" - cls.props.topic_modified_author = cls.topic.modAuthor - cls.props.topic_assigned_to = cls.topic.assignee - if cls.topic.dueDate: - cls.props.topic_due_date = cls.topic.dueDate.strftime("%a %Y-%m-%d %H:%S") - else: - cls.props.topic_due_date = "" - cls.props.topic_description = cls.topic.description - - @classmethod - def load_topic_labels(cls): - while len(cls.props.topic_labels) > 0: - cls.props.topic_labels.remove(0) - for label in cls.topic.labels: - new = cls.props.topic_labels.add() - new.name = label.value - - @classmethod - def load_topic_files(cls): - import bcfplugin - - while len(cls.props.topic_files) > 0: - cls.props.topic_files.remove(0) - files = bcfplugin.getRelevantIfcFiles(cls.topic) - for f in files: - new = cls.props.topic_files.add() - new.name = f.filename - new.date = f.time.strftime("%a %Y-%m-%d %H:%S") - new.reference = f.reference.uri - new.ifc_project = f.ifcProjectId - new.ifc_spatial = f.ifcSpatialStructureElement - new.is_external = f.external - - @classmethod - def load_topic_links(cls): - while len(cls.props.topic_links) > 0: - cls.props.topic_links.remove(0) - for link in cls.topic.referenceLinks: - new = cls.props.topic_links.add() - new.name = link.value - - @classmethod - def load_snippet(cls): - cls.props.topic_has_snippet = bool(cls.topic.bimSnippet) - if cls.topic.bimSnippet: - cls.props.topic_snippet_reference = cls.topic.bimSnippet.reference.uri - if cls.topic.bimSnippet.schema.uri: - cls.props.topic_snippet_schema = cls.topic.bimSnippet.schema.uri - cls.props.topic_snippet_type = cls.topic.bimSnippet.type - if cls.topic.bimSnippet.external: - cls.props.topic_snippet_is_external = cls.topic.bimSnippet.external - else: - cls.props.topic_snippet_is_external = False - - @classmethod - def load_document_references(cls): - while len(cls.props.topic_document_references) > 0: - cls.props.topic_document_references.remove(0) - for doc in cls.topic.docRefs: - new = cls.props.topic_document_references.add() - new.name = doc.reference.uri - new.description = doc.description - new.guid = str(doc.guid) - new.is_external = doc.external - - @classmethod - def load_related_topics(cls): - import bcfplugin - - while len(cls.props.topic_related_topics) > 0: - cls.props.topic_related_topics.remove(0) - for t in cls.topic.relatedTopics: - new = cls.props.topic_related_topics.add() - new.name = bcfplugin.getTopicFromUUID(t.guid).title - new.guid = str(t.guid) - - @classmethod - def load_viewpoints(cls): - import bcfplugin - - bcfviewpoints_enum.clear() - bcf.BcfStore.viewpoints = bcfplugin.getViewpoints(cls.topic, realViewpoint=False) - for i, viewpoint in enumerate(bcf.BcfStore.viewpoints): - bcfviewpoints_enum.append((str(i), "View {}".format(i + 1), "")) - - @classmethod - def load_comments(cls): - import bcfplugin - - bcf.BcfStore.comments = bcfplugin.getComments(cls.topic) - comments = bpy.data.texts.get("BCF Comments") - if comments: - comments.clear() - else: - comments = bpy.data.texts.new("BCF Comments") - for i, comment in enumerate(bcf.BcfStore.comments): - comments.write("# Comment {} - {}\n".format(i + 1, comment[1].xmlId)) - comments.write("# From: {} on {}\n".format(comment[1].author, comment[1].date)) - if comment[1].modDate: - comments.write("# Modified by {} on {}\n".format(comment[1].modAuthor, comment[1].modDate)) - comments.write(comment[1].comment) - comments.write("\n\n-----\n\n") - - -def getBcfViewpoints(self, context): global bcfviewpoints_enum - return bcfviewpoints_enum + bcfviewpoints_enum = None + + props = bpy.context.scene.BCFProperties + bcfxml = bcfstore.BcfStore.get_bcfxml() + topic = props.topics[props.active_topic_index] + header = bcfxml.get_header(topic.guid) + getBcfViewpoints(self, context) class PropertySetTemplate(PropertyGroup): @@ -1641,33 +1478,10 @@ class BIMProperties(PropertyGroup): class BCFProperties(PropertyGroup): is_editable: BoolProperty(name="Is Editable", default=False) is_loaded: BoolProperty(name="Is Loaded", default=False) - name: StringProperty(default="", name="Project Name", update=updateBcfProjectName) + name: StringProperty(default="", name="Project Name") author: StringProperty(default="john@doe.com", name="Author Email") topics: CollectionProperty(name="BCF Topics", type=BcfTopic) active_topic_index: IntProperty(name="Active BCF Topic Index", update=refreshBcfTopic) - viewpoints: EnumProperty(items=getBcfViewpoints, name="BCF Viewpoints") - topic_guid: StringProperty(default="", name="Topic GUID") - topic_type: StringProperty(default="", name="Topic Type", update=updateBcfTopicType) - topic_status: StringProperty(default="", name="Topic Status", update=updateBcfTopicStatus) - topic_priority: StringProperty(default="", name="Topic Priority", update=updateBcfTopicPriority) - topic_stage: StringProperty(default="", name="Topic Stage", update=updateBcfTopicStage) - topic_creation_date: StringProperty(default="", name="Topic Date") - topic_creation_author: StringProperty(default="", name="Topic Author") - topic_modified_date: StringProperty(default="", name="Topic Modified Date") - topic_modified_author: StringProperty(default="", name="Topic Modified By") - topic_assigned_to: StringProperty(default="", name="Topic Assigned To") - topic_due_date: StringProperty(default="", name="Topic Due Date") - topic_description: StringProperty(default="", name="Topic Description", update=updateBcfTopicDescription) - topic_labels: CollectionProperty(name="BCF Topic Labels", type=BcfTopicLabel) - topic_files: CollectionProperty(name="BCF Topic Files", type=BcfTopicFile) - topic_links: CollectionProperty(name="BCF Topic Links", type=BcfTopicLink) - topic_has_snippet: BoolProperty(name="BCF Topic Has Snippet", default=False) - topic_snippet_reference: StringProperty(name="BIM Snippet Reference") - topic_snippet_schema: StringProperty(name="BIM Snippet Schema") - topic_snippet_type: StringProperty(name="BIM Snippet Type") - topic_snippet_is_external: BoolProperty(name="Is BIM Snippet External") - topic_document_references: CollectionProperty(name="BCF Topic Document References", type=BcfTopicDocumentReference) - topic_related_topics: CollectionProperty(name="BCF Topic Related Topics", type=BcfTopicRelatedTopic) class MapConversion(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index c8fe83e02c..af4ba16f44 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1,4 +1,6 @@ +import os import bpy +from . import bcfstore from bpy.types import Panel from bpy.props import StringProperty @@ -1681,97 +1683,95 @@ class BIM_PT_bcf(Panel): col = row.column(align=True) col.operator("bim.add_bcf_topic", icon="ADD", text="") - if not props.topics: - return - - row = layout.row() - row.prop(props, "topic_description", text="") - - row = layout.row() - row.prop(props, "viewpoints") - row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") - - col = layout.column(align=True) - col.prop(props, "topic_type", text="Type") - col.prop(props, "topic_status", text="Status") - col.prop(props, "topic_priority", text="Priority") - col.prop(props, "topic_stage", text="Stage") - col.prop(props, "topic_assigned_to", text="Assigned To") - col.prop(props, "topic_due_date", text="Due Date") - - col = layout.column(align=True) - col.enabled = False - col.prop(props, "topic_creation_date", text="Date") - col.prop(props, "topic_creation_author", text="Author") - col.prop(props, "topic_modified_date", text="Modified On") - col.prop(props, "topic_modified_author", text="Modified By") - - - layout.label(text="Header Files:") - for index, f in enumerate(props.topic_files): + if props.active_topic_index < len(props.topics): + topic = props.topics[props.active_topic_index] row = layout.row() - row.prop(f, "name", text="File {} Name".format(index + 1)) + row.prop(topic, "description", text="") + row = layout.row() - row.prop(f, "reference", text="File {} URI".format(index + 1)) - if f.is_external: - row.operator("bim.open_bcf_file_reference", icon="URL", text="").data = index - else: - row.operator("bim.open_bcf_file_reference", icon="FILE_FOLDER", text="").data = "{}/{}".format( - props.topic_guid, index - ) - row = layout.row() - row.prop(f, "date", text="File {} Date".format(index + 1)) - row = layout.row() - row.prop(f, "ifc_project", text="File {} Project".format(index + 1)) - row = layout.row() - row.prop(f, "ifc_spatial", text="File {} Spatial".format(index + 1)) + row.prop(topic, "viewpoints") + row.operator("bim.activate_bcf_viewpoint", icon="SCENE", text="") - layout.label(text="Reference Links:") - for index, label in enumerate(props.topic_links): - row = layout.row() - row.prop(label, "name", text="Link {}".format(index + 1)) - row.operator("bim.open_bcf_reference_link", icon="URL", text="").index = index + col = layout.column(align=True) + col.prop(topic, "type") + col.prop(topic, "status") + col.prop(topic, "priority") + col.prop(topic, "stage") + col.prop(topic, "assigned_to") + col.prop(topic, "due_date") - layout.label(text="Labels:") - for index, label in enumerate(props.topic_labels): - row = layout.row(align=True) - row.prop(label, "name", text="") + col = layout.column(align=True) + col.enabled = False + col.prop(topic, "creation_date") + col.prop(topic, "creation_author") + col.prop(topic, "modified_date") + col.prop(topic, "modified_author") - layout.label(text="BIM Snippet:") - if props.topic_has_snippet: - row = layout.row(align=True) - row.prop(props, "topic_snippet_type") - if props.topic_snippet_schema: - row.operator("bim.open_bcf_bim_snippet_schema", icon="URL", text="") + bcfxml = bcfstore.BcfStore.get_bcfxml() + bcf_topic = bcfxml.topics[topic.guid] - row = layout.row(align=True) - row.prop(props, "topic_snippet_reference") - if props.topic_snippet_is_external: - row.operator("bim.open_bcf_bim_snippet_reference", icon="URL", text="") - else: - row.operator( - "bim.open_bcf_bim_snippet_reference", icon="FILE_FOLDER", text="" - ).topic_guid = props.topic_guid + if bcf_topic.header: + layout.label(text="Header Files:") + for index, f in enumerate(bcf_topic.header.files): + box = self.layout.box() + row = box.row(align=True) + row.label(text=f.filename, icon="FILE_BLANK") + if f.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = f.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.guid, f.reference) + box.label(text=f.date) + #box.label(text=f.ifc_project) + #box.label(text=f.ifc_spatial_structure_element) - layout.label(text="Document References:") - for index, doc in enumerate(props.topic_document_references): - row = layout.row(align=True) - row.prop(doc, "name", text=f"File {index+1} URI") - if doc.is_external: - row.operator("bim.open_bcf_document_reference", icon="URL", text="").data = "{}/{}".format( - props.topic_guid, index - ) - else: - row.operator("bim.open_bcf_document_reference", icon="FILE_FOLDER", text="").data = "{}/{}".format( - props.topic_guid, index - ) - row = layout.row(align=True) - row.prop(doc, "description", text=f"File {index+1} Description:") + if topic.reference_links: + layout.label(text="Reference Links:") + for index, link in enumerate(topic.reference_links): + row = layout.row(align=True) + row.prop(link, "name") + row.operator("bim.open_uri", icon="URL", text="").uri = link.name - layout.label(text="Related Topics:") - for topic in props.topic_related_topics: - row = layout.row(align=True) - row.operator("bim.view_bcf_topic", text=topic.name).topic_guid = topic.guid + if topic.labels: + layout.label(text="Labels:") + for index, label in enumerate(topic.labels): + row = layout.row(align=True) + row.prop(label, "name", text="") + + if topic.bim_snippet.schema: + layout.label(text="BIM Snippet:") + row = layout.row(align=True) + row.prop(topic.bim_snippet, "type") + if topic.bim_snippet.schema: + row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.schema + + row = layout.row(align=True) + row.prop(topic.bim_snippet, "reference") + if topic.bim_snippet.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = topic.bim_snippet.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.guid, topic.bim_snippet.reference) + + if topic.document_references: + layout.label(text="Document References:") + for index, doc in enumerate(topic.document_references): + box = self.layout.box() + row = box.row(align=True) + row.prop(doc, "reference") + if doc.is_external: + row.operator("bim.open_uri", icon="URL", text="").uri = doc.reference + else: + op = row.operator("bim.open_uri", icon="FILE_FOLDER", text="") + op.uri = os.path.join(bcfxml.filepath, topic.guid, doc.reference) + row = box.row(align=True) + row.prop(doc, "description") + + if topic.related_topics: + layout.label(text="Related Topics:") + for related_topic in topic.related_topics: + row = layout.row(align=True) + row.operator("bim.view_bcf_topic", text=related_topic.name).topic_guid = related_topic.name class BIM_PT_qa(Panel):