From a3572f3372c985403701bda82182983a6c7cabc5 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Mon, 19 Jun 2023 21:54:28 -0700 Subject: [PATCH 01/21] Implement VersionedGraphCollection to Brick module - Reworked `load_brick_file` and `new_brick_file` to fit under the VersionedGraphCollection implementation. (Other methods may have broken). - Added `undo_brick` and `redo_brick`. - Kept BrickStore.graph the same as to generally still work with the rest of the code. This was done by parsing the VersionedGraphCollection with the new `reload_brick_graph` method. --- src/blenderbim/blenderbim/tool/brick.py | 54 ++++++++++++++++--------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 61e498d861..4d4449865a 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -25,6 +25,7 @@ import blenderbim.tool as tool try: import brickschema + import brickschema.persistent import urllib.parse from rdflib import Literal, URIRef, Namespace from rdflib.namespace import RDF @@ -32,7 +33,6 @@ except: # See #1860 print("Warning: brickschema not available.") - class Brick(blenderbim.core.tool.Brick): @classmethod def add_brick(cls, namespace, brick_class): @@ -249,27 +249,29 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def load_brick_file(cls, filepath): - if not BrickStore.schema: - BrickStore.schema = brickschema.Graph() + if not BrickStore.schema: # important check for running under test cases cwd = os.path.dirname(os.path.realpath(__file__)) - schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") - BrickStore.schema.load_file(schema_path) - BrickStore.graph = brickschema.Graph().load_file(filepath) + BrickStore.schema + BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") + BrickStore.VersionedGraphCollection = brickschema.persistent.VersionedGraphCollection("sqlite://") + with BrickStore.VersionedGraphCollection.new_changeset("schema") as cs: + cs.load_file(BrickStore.schema) + with BrickStore.VersionedGraphCollection.new_changeset("project") as cs: + cs.load_file(filepath) + BrickStore.reload_brick_graph() BrickStore.path = filepath @classmethod def new_brick_file(cls): - if not BrickStore.schema: - BrickStore.schema = brickschema.Graph() - #BrickStore.schema = brickschema.persistent.VersionedGraphCollection("sqlite://") + if not BrickStore.schema: # important check for running under test cases cwd = os.path.dirname(os.path.realpath(__file__)) - schema_path = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") - BrickStore.schema.load_file(schema_path) - #BrickStore.schema.load_graph(schema_path) - BrickStore.graph = brickschema.Graph() + BrickStore.schema - BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#")) - BrickStore.graph.bind("brick", Namespace("https://brickschema.org/schema/Brick#")) - BrickStore.graph.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#")) + BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") + BrickStore.VersionedGraphCollection = brickschema.persistent.VersionedGraphCollection("sqlite://") + with BrickStore.VersionedGraphCollection.new_changeset("schema") as cs: + cs.load_file(BrickStore.schema) + BrickStore.VersionedGraphCollection.bind("digitaltwin", Namespace("https://example.org/digitaltwin#")) + BrickStore.VersionedGraphCollection.bind("brick", Namespace("https://brickschema.org/schema/Brick#")) + BrickStore.VersionedGraphCollection.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#")) + BrickStore.reload_brick_graph() @classmethod def pop_brick_breadcrumb(cls): @@ -308,10 +310,22 @@ class Brick(blenderbim.core.tool.Brick): def set_active_brick_class(cls, brick_class): bpy.context.scene.BIMBrickProperties.active_brick_class = brick_class + @classmethod + def undo_brick(cls): + BrickStore.VersionedGraphCollection.undo() + BrickStore.reload_brick_graph() + + @classmethod + def redo_brick(cls): + BrickStore.VersionedGraphCollection.redo() + BrickStore.reload_brick_graph() class BrickStore: - schema = None - graph = None + schema = None # this is now a path + # I've decided to arbitrarily split th VersionedGraphCollection into two graph names: "schema" and "project" + # "schema" holds the Brick.ttl metadata; "project" holds all the authored entities + VersionedGraphCollection = None + graph = None # this is the graph named "project" from the VersionedGraphCollection path = None @staticmethod @@ -319,3 +333,7 @@ class BrickStore: BrickStore.schema = None BrickStore.graph = None BrickStore.path = None + + @classmethod + def reload_brick_graph(cls): + BrickStore.graph = BrickStore.VersionedGraphCollection.graph_at("project") \ No newline at end of file From bcf5f07718ee418d989feb65cadd61c1289bc0ce Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Mon, 19 Jun 2023 21:59:17 -0700 Subject: [PATCH 02/21] Rework NewBrick and LoadBrick in test_brick.py to fit with new VGC format - VGC means VersionedGraphCollection. - BrickStore.schema is now a filepath, so make that the case in the testing also. --- src/blenderbim/test/tool/test_brick.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/test/tool/test_brick.py b/src/blenderbim/test/tool/test_brick.py index 1fc2a9b962..14687ce694 100644 --- a/src/blenderbim/test/tool/test_brick.py +++ b/src/blenderbim/test/tool/test_brick.py @@ -307,10 +307,8 @@ class TestImportBrickItems(NewFile): class TestLoadBrickFile(NewFile): def test_run(self): # We stub the schema to make tests run faster - BrickStore.schema = brickschema.Graph() cwd = os.path.dirname(os.path.realpath(__file__)) - schema_path = os.path.join(cwd, "..", "files", "BrickStub.ttl") - BrickStore.schema.load_file(schema_path) + BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl") # This is the actual test cwd = os.path.dirname(os.path.realpath(__file__)) @@ -322,10 +320,8 @@ class TestLoadBrickFile(NewFile): class TestNewBrickFile(NewFile): def test_run(self): # We stub the schema to make tests run faster - BrickStore.schema = brickschema.Graph() cwd = os.path.dirname(os.path.realpath(__file__)) - schema_path = os.path.join(cwd, "..", "files", "BrickStub.ttl") - BrickStore.schema.load_file(schema_path) + BrickStore.schema = os.path.join(cwd, "..", "files", "BrickStub.ttl") # This is the actual test subject.new_brick_file() From 046ce2b8e7cd4efd5d2b9f7f0bd996a23481ebfb Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Fri, 23 Jun 2023 16:55:34 -0700 Subject: [PATCH 03/21] Update BrickStore.purge() I wanted to just have a BrickStore.clear() which would so this: BrickStore.VersionedGraphCollection = None BrickStore.graph = None BrickStore.path = None (aka not also set BrickStore.schema = None, since it should theoretically just load in the same path anyway) but for some reason Blender crashes when clearing a project and loading one again this way. --- src/blenderbim/blenderbim/tool/brick.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 4d4449865a..fb7d645dd7 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -100,7 +100,7 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def clear_project(cls): - BrickStore.graph = None + BrickStore.purge() bpy.context.scene.BIMBrickProperties.active_brick_class == "" bpy.context.scene.BIMBrickProperties.brick_breadcrumbs.clear() @@ -331,8 +331,9 @@ class BrickStore: @staticmethod def purge(): BrickStore.schema = None + BrickStore.VersionedGraphCollection = None BrickStore.graph = None - BrickStore.path = None + BrickStore.path = None @classmethod def reload_brick_graph(cls): From e177f5f0511801f65822fd122e76af053c78f4ac Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Sun, 25 Jun 2023 23:20:56 -0700 Subject: [PATCH 04/21] Add Brick.ttl to .gitignore --- src/blenderbim/blenderbim/bim/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/.gitignore b/src/blenderbim/blenderbim/bim/.gitignore index 4b1304fd48..bc8a4d8df1 100644 --- a/src/blenderbim/blenderbim/bim/.gitignore +++ b/src/blenderbim/blenderbim/bim/.gitignore @@ -1,2 +1,3 @@ # addon writes tmp stuff directly to its dir -/data/ \ No newline at end of file +/data/ +/schema/Brick.ttl \ No newline at end of file From 580f8f6218ff45a78c497a8c3a67fadde02f809d Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Sun, 25 Jun 2023 23:21:27 -0700 Subject: [PATCH 05/21] Change graph naming to all caps --- src/blenderbim/blenderbim/tool/brick.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index fb7d645dd7..8df69726a4 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -253,9 +253,9 @@ class Brick(blenderbim.core.tool.Brick): cwd = os.path.dirname(os.path.realpath(__file__)) BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") BrickStore.VersionedGraphCollection = brickschema.persistent.VersionedGraphCollection("sqlite://") - with BrickStore.VersionedGraphCollection.new_changeset("schema") as cs: + with BrickStore.VersionedGraphCollection.new_changeset("SCHEMA") as cs: cs.load_file(BrickStore.schema) - with BrickStore.VersionedGraphCollection.new_changeset("project") as cs: + with BrickStore.VersionedGraphCollection.new_changeset("PROJECT") as cs: cs.load_file(filepath) BrickStore.reload_brick_graph() BrickStore.path = filepath @@ -266,7 +266,7 @@ class Brick(blenderbim.core.tool.Brick): cwd = os.path.dirname(os.path.realpath(__file__)) BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") BrickStore.VersionedGraphCollection = brickschema.persistent.VersionedGraphCollection("sqlite://") - with BrickStore.VersionedGraphCollection.new_changeset("schema") as cs: + with BrickStore.VersionedGraphCollection.new_changeset("SCHEMA") as cs: cs.load_file(BrickStore.schema) BrickStore.VersionedGraphCollection.bind("digitaltwin", Namespace("https://example.org/digitaltwin#")) BrickStore.VersionedGraphCollection.bind("brick", Namespace("https://brickschema.org/schema/Brick#")) @@ -320,6 +320,7 @@ class Brick(blenderbim.core.tool.Brick): BrickStore.VersionedGraphCollection.redo() BrickStore.reload_brick_graph() + class BrickStore: schema = None # this is now a path # I've decided to arbitrarily split th VersionedGraphCollection into two graph names: "schema" and "project" @@ -337,4 +338,4 @@ class BrickStore: @classmethod def reload_brick_graph(cls): - BrickStore.graph = BrickStore.VersionedGraphCollection.graph_at("project") \ No newline at end of file + BrickStore.graph = BrickStore.VersionedGraphCollection.graph_at("PROJECT") \ No newline at end of file From d9196882dcc9efafe5edefade5eeef6a39e930a2 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Sun, 25 Jun 2023 23:56:15 -0700 Subject: [PATCH 06/21] Implement early testing for serialize operator (not functional) All the right code seems to be in place, but it seems the package won't go through with the serialize function because of read/write permissions (ERRNO 13) --- src/blenderbim/blenderbim/bim/module/brick/__init__.py | 1 + src/blenderbim/blenderbim/bim/module/brick/operator.py | 7 +++++++ src/blenderbim/blenderbim/bim/module/brick/ui.py | 3 +++ src/blenderbim/blenderbim/core/brick.py | 3 +++ src/blenderbim/blenderbim/tool/brick.py | 6 ++++++ 5 files changed, 20 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/brick/__init__.py b/src/blenderbim/blenderbim/bim/module/brick/__init__.py index 5e0afa989a..0bff9e01e6 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/brick/__init__.py @@ -33,6 +33,7 @@ classes = ( operator.RewindBrickClass, operator.ViewBrickClass, operator.ViewBrickItem, + operator.SerializeBrick, prop.Brick, prop.BIMBrickProperties, ui.BIM_PT_brickschema, diff --git a/src/blenderbim/blenderbim/bim/module/brick/operator.py b/src/blenderbim/blenderbim/bim/module/brick/operator.py index 33d877ebd1..1aa40303a2 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/operator.py +++ b/src/blenderbim/blenderbim/bim/module/brick/operator.py @@ -189,3 +189,10 @@ class RemoveBrick(bpy.types.Operator, Operator): library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None, brick_uri=props.bricks[props.active_brick_index].uri, ) + +class SerializeBrick(bpy.types.Operator, Operator): + bl_idname = "bim.serialize_brick" + bl_label = "Serialize Brick" + + def _execute(self, context): + core.serialize_brick(tool.Brick) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index 568c5a4ab5..2d58de6758 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -58,6 +58,9 @@ class BIM_PT_brickschema(Panel): row.operator("bim.add_brick_feed", text="", icon="PLUGIN") row.operator("bim.remove_brick", text="", icon="X") + row = self.layout.row(align=True) + row.operator("bim.serialize_brick") + self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index") for attribute in BrickschemaData.data["attributes"]: diff --git a/src/blenderbim/blenderbim/core/brick.py b/src/blenderbim/blenderbim/core/brick.py index d4df98654e..93fe475bb2 100644 --- a/src/blenderbim/blenderbim/core/brick.py +++ b/src/blenderbim/blenderbim/core/brick.py @@ -109,3 +109,6 @@ def remove_brick(ifc, brick, library=None, brick_uri=None): ifc.run("library.remove_reference", reference=reference) brick.remove_brick(brick_uri) brick.run_refresh_brick_viewer() + +def serialize_brick(brick, file_name="BlenderBIMSerializeTest.ttl"): + brick.serialize_brick(file_name) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 8df69726a4..2ccc5433cc 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -320,6 +320,12 @@ class Brick(blenderbim.core.tool.Brick): BrickStore.VersionedGraphCollection.redo() BrickStore.reload_brick_graph() + @classmethod + def serialize_brick(cls, file_name): + BrickStore.reload_brick_graph() + print("Serializing: \"" + file_name + "\" ... ") + BrickStore.graph.serialize(file_name) + print("finished!") class BrickStore: schema = None # this is now a path From 81a4ff711cb9cfd4b3ab313ac87532391c56f560 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Mon, 26 Jun 2023 13:59:28 -0700 Subject: [PATCH 07/21] Implement fully serialize operator - Turns out, you need the keyword "graph=" in graph_at() to actually select a graph of that name from the collection, otherwise it just returns the entire collection, so I changed that, which correctly isolates the project from the collection for serialization now. - With this same change, I opted turn BrickStore.VersionedGraphCollection simply into BrickStore.graph and create a new BrickStore.get_project() to return the isolated graph. - This meant I should remove the reload_graph() function because I was actually just loading the entire collection into it still, and its functionality breaks when it isn't the entire collection --- src/blenderbim/blenderbim/tool/brick.py | 45 ++++++++++++------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 2ccc5433cc..d09d5fd84d 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -252,12 +252,11 @@ class Brick(blenderbim.core.tool.Brick): if not BrickStore.schema: # important check for running under test cases cwd = os.path.dirname(os.path.realpath(__file__)) BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") - BrickStore.VersionedGraphCollection = brickschema.persistent.VersionedGraphCollection("sqlite://") - with BrickStore.VersionedGraphCollection.new_changeset("SCHEMA") as cs: + BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") + with BrickStore.graph.new_changeset("SCHEMA") as cs: cs.load_file(BrickStore.schema) - with BrickStore.VersionedGraphCollection.new_changeset("PROJECT") as cs: + with BrickStore.graph.new_changeset("PROJECT") as cs: cs.load_file(filepath) - BrickStore.reload_brick_graph() BrickStore.path = filepath @classmethod @@ -265,13 +264,12 @@ class Brick(blenderbim.core.tool.Brick): if not BrickStore.schema: # important check for running under test cases cwd = os.path.dirname(os.path.realpath(__file__)) BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") - BrickStore.VersionedGraphCollection = brickschema.persistent.VersionedGraphCollection("sqlite://") - with BrickStore.VersionedGraphCollection.new_changeset("SCHEMA") as cs: + BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") + with BrickStore.graph.new_changeset("SCHEMA") as cs: cs.load_file(BrickStore.schema) - BrickStore.VersionedGraphCollection.bind("digitaltwin", Namespace("https://example.org/digitaltwin#")) - BrickStore.VersionedGraphCollection.bind("brick", Namespace("https://brickschema.org/schema/Brick#")) - BrickStore.VersionedGraphCollection.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#")) - BrickStore.reload_brick_graph() + BrickStore.graph.bind("digitaltwin", Namespace("https://example.org/digitaltwin#")) + BrickStore.graph.bind("brick", Namespace("https://brickschema.org/schema/Brick#")) + BrickStore.graph.bind("rdfs", Namespace("http://www.w3.org/2000/01/rdf-schema#")) @classmethod def pop_brick_breadcrumb(cls): @@ -312,36 +310,35 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def undo_brick(cls): - BrickStore.VersionedGraphCollection.undo() - BrickStore.reload_brick_graph() + BrickStore.graph.undo() @classmethod def redo_brick(cls): - BrickStore.VersionedGraphCollection.redo() - BrickStore.reload_brick_graph() + BrickStore.graph.redo() @classmethod def serialize_brick(cls, file_name): - BrickStore.reload_brick_graph() + #temporary file path, could either be user selected for "save as" or use the BrickStore.path for simply "save" print("Serializing: \"" + file_name + "\" ... ") - BrickStore.graph.serialize(file_name) + cwd = os.path.dirname(os.path.realpath(__file__)) + dest = os.path.join(cwd, "..", "bim", "schema", file_name) + BrickStore.get_project().serialize(destination=dest, format="turtle") print("finished!") class BrickStore: - schema = None # this is now a path - # I've decided to arbitrarily split th VersionedGraphCollection into two graph names: "schema" and "project" - # "schema" holds the Brick.ttl metadata; "project" holds all the authored entities - VersionedGraphCollection = None - graph = None # this is the graph named "project" from the VersionedGraphCollection + schema = None # this is now a os path + graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project" + # "schema" holds the Brick.ttl metadata; "project" holds all the authored entities + project = None # this is the graph named "project" from the VersionedGraphCollection path = None @staticmethod def purge(): BrickStore.schema = None - BrickStore.VersionedGraphCollection = None BrickStore.graph = None + BrickStore.project = None BrickStore.path = None @classmethod - def reload_brick_graph(cls): - BrickStore.graph = BrickStore.VersionedGraphCollection.graph_at("PROJECT") \ No newline at end of file + def get_project(cls): + return BrickStore.graph.graph_at(graph="PROJECT") \ No newline at end of file From 8341931a62156c85ff6719e1dfd25a7cf95b04d6 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Mon, 26 Jun 2023 16:01:26 -0700 Subject: [PATCH 08/21] Reformat commenting, remove prints, and remove BrickStore.project --- src/blenderbim/blenderbim/tool/brick.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index d09d5fd84d..348fb33467 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -319,24 +319,21 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def serialize_brick(cls, file_name): #temporary file path, could either be user selected for "save as" or use the BrickStore.path for simply "save" - print("Serializing: \"" + file_name + "\" ... ") cwd = os.path.dirname(os.path.realpath(__file__)) dest = os.path.join(cwd, "..", "bim", "schema", file_name) BrickStore.get_project().serialize(destination=dest, format="turtle") - print("finished!") class BrickStore: schema = None # this is now a os path - graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project" - # "schema" holds the Brick.ttl metadata; "project" holds all the authored entities - project = None # this is the graph named "project" from the VersionedGraphCollection - path = None + path = None # file path if the project was loaded in + graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project" + # "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities + @staticmethod def purge(): BrickStore.schema = None BrickStore.graph = None - BrickStore.project = None BrickStore.path = None @classmethod From 9d84172f15538cc16a8e6b82a691253c89769cc6 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Mon, 26 Jun 2023 23:16:52 -0700 Subject: [PATCH 09/21] Add changeset versioning to Brick add/remove --- src/blenderbim/blenderbim/tool/brick.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 348fb33467..ea95ab8eff 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -38,8 +38,9 @@ class Brick(blenderbim.core.tool.Brick): def add_brick(cls, namespace, brick_class): ns = Namespace(namespace) brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())] - BrickStore.graph.add((brick, RDF.type, URIRef(brick_class))) - BrickStore.graph.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed"))) + with BrickStore.graph.new_changeset("PROJECT") as cs: + cs.add((brick, RDF.type, URIRef(brick_class))) + cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("Unnamed"))) return str(brick) @classmethod @@ -281,8 +282,9 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def remove_brick(cls, brick_uri): - for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)): - BrickStore.graph.remove(triple) + with BrickStore.graph.new_changeset("PROJECT") as cs: + for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)): + cs.remove(triple) @classmethod def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None): @@ -329,7 +331,6 @@ class BrickStore: graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project" # "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities - @staticmethod def purge(): BrickStore.schema = None From 2ce32c6521c88ec60769a72a52e1fc73ec3f7d98 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Mon, 26 Jun 2023 23:17:11 -0700 Subject: [PATCH 10/21] Change namespace selector to only show alias --- src/blenderbim/blenderbim/bim/module/brick/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/brick/data.py b/src/blenderbim/blenderbim/bim/module/brick/data.py index 072f9b2f70..bb8ba06089 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/data.py +++ b/src/blenderbim/blenderbim/bim/module/brick/data.py @@ -110,7 +110,8 @@ class BrickschemaData: return [] results = [] for alias, uri in BrickStore.graph.namespaces(): - results.append((uri, f"{alias}: {uri}", "")) + # results.append((uri, f"{alias}: {uri}", "")) + results.append((uri, f"{alias}", "")) return results @classmethod From 37e4f988574fb90f0cce5448bef5504af0203864 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Tue, 27 Jun 2023 13:21:44 -0700 Subject: [PATCH 11/21] Implement fully undo/redo with checks While these actions are short in the backend--taking about 0.01 seconds to run--the Blender UI lags a lot leading to 2-3 second pauses because of the "refresh_brick_viewer" function being slow. This should be investigated. --- .../blenderbim/bim/module/brick/__init__.py | 2 ++ .../blenderbim/bim/module/brick/operator.py | 14 ++++++++++++++ .../blenderbim/bim/module/brick/ui.py | 4 ++++ src/blenderbim/blenderbim/core/brick.py | 8 ++++++++ src/blenderbim/blenderbim/tool/brick.py | 17 ++++++++++++----- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/brick/__init__.py b/src/blenderbim/blenderbim/bim/module/brick/__init__.py index 0bff9e01e6..744e679dab 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/brick/__init__.py @@ -33,6 +33,8 @@ classes = ( operator.RewindBrickClass, operator.ViewBrickClass, operator.ViewBrickItem, + operator.UndoBrick, + operator.RedoBrick, operator.SerializeBrick, prop.Brick, prop.BIMBrickProperties, diff --git a/src/blenderbim/blenderbim/bim/module/brick/operator.py b/src/blenderbim/blenderbim/bim/module/brick/operator.py index 1aa40303a2..72ef846c87 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/operator.py +++ b/src/blenderbim/blenderbim/bim/module/brick/operator.py @@ -190,6 +190,20 @@ class RemoveBrick(bpy.types.Operator, Operator): brick_uri=props.bricks[props.active_brick_index].uri, ) +class UndoBrick(bpy.types.Operator, Operator): + bl_idname = "bim.undo_brick" + bl_label = "Undo Brick" + + def _execute(self, context): + core.undo_brick(tool.Brick) + +class RedoBrick(bpy.types.Operator, Operator): + bl_idname = "bim.redo_brick" + bl_label = "Redo Brick" + + def _execute(self, context): + core.redo_brick(tool.Brick) + class SerializeBrick(bpy.types.Operator, Operator): bl_idname = "bim.serialize_brick" bl_label = "Serialize Brick" diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index 2d58de6758..717ee3eb12 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -58,6 +58,10 @@ class BIM_PT_brickschema(Panel): row.operator("bim.add_brick_feed", text="", icon="PLUGIN") row.operator("bim.remove_brick", text="", icon="X") + row = self.layout.row(align=True) + row.operator("bim.undo_brick", icon="LOOP_BACK") + row.operator("bim.redo_brick", icon="LOOP_FORWARDS") + row = self.layout.row(align=True) row.operator("bim.serialize_brick") diff --git a/src/blenderbim/blenderbim/core/brick.py b/src/blenderbim/blenderbim/core/brick.py index 93fe475bb2..cd7fafaed0 100644 --- a/src/blenderbim/blenderbim/core/brick.py +++ b/src/blenderbim/blenderbim/core/brick.py @@ -110,5 +110,13 @@ def remove_brick(ifc, brick, library=None, brick_uri=None): brick.remove_brick(brick_uri) brick.run_refresh_brick_viewer() +def undo_brick(brick): + brick.undo_brick() + brick.run_refresh_brick_viewer() + +def redo_brick(brick): + brick.redo_brick() + brick.run_refresh_brick_viewer() + def serialize_brick(brick, file_name="BlenderBIMSerializeTest.ttl"): brick.serialize_brick(file_name) \ No newline at end of file diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index ea95ab8eff..608a87ddb1 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -282,9 +282,10 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def remove_brick(cls, brick_uri): - with BrickStore.graph.new_changeset("PROJECT") as cs: - for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)): - cs.remove(triple) + if(BrickStore.graph.triples((URIRef(brick_uri), None, None))): + with BrickStore.graph.new_changeset("PROJECT") as cs: + for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)): + cs.remove(triple) @classmethod def run_assign_brick_reference(cls, element=None, library=None, brick_uri=None): @@ -312,11 +313,17 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def undo_brick(cls): - BrickStore.graph.undo() + if(len(BrickStore.graph.versions()) > 1): + BrickStore.graph.undo() @classmethod def redo_brick(cls): - BrickStore.graph.redo() + with BrickStore.graph.conn() as conn: + redo_record = conn.execute( + "SELECT * from redos " "ORDER BY timestamp ASC LIMIT 1" + ).fetchone() + if redo_record is not None: + BrickStore.graph.redo() @classmethod def serialize_brick(cls, file_name): From a34b8d06ec21a467585f10665a39c9274c152fca Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Thu, 29 Jun 2023 23:42:00 -0700 Subject: [PATCH 12/21] Silence known rdflib_sqlalchemy TypeError warning --- src/blenderbim/blenderbim/tool/brick.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 608a87ddb1..b4539c64aa 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -33,6 +33,12 @@ except: # See #1860 print("Warning: brickschema not available.") +# silence known rdflib_sqlalchemy TypeError warning +# see https://github.com/BrickSchema/Brick/issues/513#issuecomment-1558493675 +import logging +logger = logging.getLogger("rdflib") +logger.setLevel(logging.ERROR) + class Brick(blenderbim.core.tool.Brick): @classmethod def add_brick(cls, namespace, brick_class): From 01da8498f289c003f75c43d53fcfccfd483783c0 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Thu, 29 Jun 2023 23:56:20 -0700 Subject: [PATCH 13/21] Move Brick.ttl ignore to main git-ignore file --- .gitignore | 3 +++ src/blenderbim/blenderbim/bim/.gitignore | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 806fbe7bfd..2a4cd8f419 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,6 @@ src/blenderbim/layouts # ifcopenshell swig and compiled files src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py + +# Brickschema +src/blenderbim/blenderbim/bim/schema/Brick.ttl \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/.gitignore b/src/blenderbim/blenderbim/bim/.gitignore index bc8a4d8df1..4b1304fd48 100644 --- a/src/blenderbim/blenderbim/bim/.gitignore +++ b/src/blenderbim/blenderbim/bim/.gitignore @@ -1,3 +1,2 @@ # addon writes tmp stuff directly to its dir -/data/ -/schema/Brick.ttl \ No newline at end of file +/data/ \ No newline at end of file From bd8afabce3dc117bcc50af32b0952a4212d41942 Mon Sep 17 00:00:00 2001 From: Trashman247 Date: Fri, 30 Jun 2023 00:03:46 -0700 Subject: [PATCH 14/21] Resolve .git-ignore file conflict --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2a4cd8f419..247d590e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -93,5 +93,8 @@ src/blenderbim/layouts src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py +# apple +.DS_Store + # Brickschema src/blenderbim/blenderbim/bim/schema/Brick.ttl \ No newline at end of file From c094517a10b436b9344fd7831c7fce733ac45ffd Mon Sep 17 00:00:00 2001 From: Carlos Dias <57261862+c4rlosdias@users.noreply.github.com> Date: Sun, 2 Jul 2023 20:25:51 -0300 Subject: [PATCH 15/21] Update operator.py missing output file format in arguments --- src/blenderbim/blenderbim/bim/module/csv/operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blenderbim/blenderbim/bim/module/csv/operator.py b/src/blenderbim/blenderbim/bim/module/csv/operator.py index 02b7fabe41..5b74ec3ec9 100644 --- a/src/blenderbim/blenderbim/bim/module/csv/operator.py +++ b/src/blenderbim/blenderbim/bim/module/csv/operator.py @@ -152,7 +152,7 @@ class ExportIfcCsv(bpy.types.Operator): ifc_csv = ifccsv.IfcCsv() attributes = [a.name for a in props.csv_attributes] sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter - ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=args.format, delimiter=sep) + ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format="csv", delimiter=sep) return {"FINISHED"} From 51b7f24a0031f6000b581ea4235d0b701c1879da Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 3 Jul 2023 16:46:58 +1000 Subject: [PATCH 16/21] You can now unload BCF projects to load another one. --- src/blenderbim/blenderbim/bim/module/bcf/__init__.py | 1 + src/blenderbim/blenderbim/bim/module/bcf/operator.py | 12 +++++++++++- src/blenderbim/blenderbim/bim/module/bcf/ui.py | 10 ++++++---- src/blenderbim/blenderbim/bim/module/project/ui.py | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py index 37b5f4f271..5e6b55e072 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/__init__.py @@ -57,6 +57,7 @@ classes = ( operator.SelectBcfBimSnippetReference, operator.SelectBcfDocumentReference, operator.SelectBcfHeaderFile, + operator.UnloadBcfProject, operator.ViewBcfTopic, prop.BcfReferenceLink, prop.BcfLabel, diff --git a/src/blenderbim/blenderbim/bim/module/bcf/operator.py b/src/blenderbim/blenderbim/bim/module/bcf/operator.py index f41d70851b..6b44e3cecc 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/operator.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/operator.py @@ -58,7 +58,6 @@ class LoadBcfProject(bpy.types.Operator): filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"}) def execute(self, context): - context.scene.BCFProperties.is_loaded = False if self.filepath: bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath) bcfxml = bcfstore.BcfStore.get_bcfxml() @@ -72,6 +71,17 @@ class LoadBcfProject(bpy.types.Operator): return {"RUNNING_MODAL"} +class UnloadBcfProject(bpy.types.Operator): + bl_idname = "bim.unload_bcf_project" + bl_label = "Unload BCF Project" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + bcfstore.BcfStore.set(None) + context.scene.BCFProperties.is_loaded = False + return {"FINISHED"} + + class LoadBcfTopics(bpy.types.Operator): bl_idname = "bim.load_bcf_topics" bl_label = "Load BCF Topics" diff --git a/src/blenderbim/blenderbim/bim/module/bcf/ui.py b/src/blenderbim/blenderbim/bim/module/bcf/ui.py index 9c4c5f796b..821d028dbb 100644 --- a/src/blenderbim/blenderbim/bim/module/bcf/ui.py +++ b/src/blenderbim/blenderbim/bim/module/bcf/ui.py @@ -39,14 +39,16 @@ class BIM_PT_bcf(Panel): scene = context.scene props = scene.BCFProperties - row = layout.row(align=True) - row.operator("bim.new_bcf_project", text="New Project") - row.operator("bim.load_bcf_project", text="Load Project") if not props.is_loaded: + row = layout.row(align=True) + row.operator("bim.new_bcf_project", text="New Project") + row.operator("bim.load_bcf_project", text="Load Project") return - row.operator("bim.save_bcf_project", text="Save Project") + row = layout.row(align=True) + row.operator("bim.save_bcf_project", icon="EXPORT", text="Save Project") + row.operator("bim.unload_bcf_project", text="", icon="CANCEL") row = layout.row() row.prop(props, "name") diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 7c2730f957..a57c097e25 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -164,7 +164,7 @@ class BIM_PT_project(Panel): op.should_save_as = False op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As") op.should_save_as = True - row.operator("bim.unload_project", text="", icon="X") + row.operator("bim.unload_project", text="", icon="CANCEL") def draw_create_project_ui(self, context): props = context.scene.BIMProperties From a980ce1bfad49b00f6b5b9495ac7921acc25e8e7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 3 Jul 2023 16:49:04 +1000 Subject: [PATCH 17/21] Fix #3355. Reimplement graphical clash snapshots in BCF from IfcClash in the BlenderBIM Add-on. --- src/bcf/src/bcf/v2/topic.py | 2 ++ .../blenderbim/bim/module/clash/operator.py | 26 ++++++++++++++++--- src/ifcclash/ifcclash/ifcclash.py | 12 ++++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/bcf/src/bcf/v2/topic.py b/src/bcf/src/bcf/v2/topic.py index 87cea5744a..c1188c9a06 100644 --- a/src/bcf/src/bcf/v2/topic.py +++ b/src/bcf/src/bcf/v2/topic.py @@ -275,6 +275,7 @@ class TopicHandler: """ new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler) self.add_visinfo_handler(new_viewpoint) + return new_viewpoint def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None: """Add a viewpoint pointing at an XYZ point in space @@ -287,6 +288,7 @@ class TopicHandler: position, *guids, xml_handler=self._xml_handler ) self.add_visinfo_handler(vi_handler) + return vi_handler def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None: self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint diff --git a/src/blenderbim/blenderbim/bim/module/clash/operator.py b/src/blenderbim/blenderbim/bim/module/clash/operator.py index f224d40859..933cc06b48 100644 --- a/src/blenderbim/blenderbim/bim/module/clash/operator.py +++ b/src/blenderbim/blenderbim/bim/module/clash/operator.py @@ -23,7 +23,7 @@ import bmesh import logging import numpy as np import ifcopenshell -from mathutils import Matrix +from mathutils import Matrix, Vector from math import radians from blenderbim.bim.ifc import IfcStore @@ -222,6 +222,7 @@ class ExecuteIfcClash(bpy.types.Operator): _, extension = os.path.splitext(self.filepath) if extension != ".json": self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf") + settings = ifcclash.ClashSettings() settings.output = self.filepath settings.logger = logging.getLogger("Clash") @@ -230,12 +231,28 @@ class ExecuteIfcClash(bpy.types.Operator): if context.scene.BIMClashProperties.should_create_clash_snapshots: - def get_viewpoint_snapshot(viewpoint, mat): + def get_viewpoint_snapshot(viewpoint): camera = bpy.data.objects.get("IFC Clash Camera") if not camera: camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera")) context.scene.collection.objects.link(camera) - camera.matrix_world = Matrix(mat) + + bcf_camera = viewpoint.visualization_info.perspective_camera + p = bcf_camera.camera_view_point + z = bcf_camera.camera_direction + z = Vector([z.x, z.y, z.z]) * -1 + y = bcf_camera.camera_up_vector + y = Vector([y.x, y.y, y.z]) + x = y.cross(z) + + mat = Matrix([ + [x[0], y[0], z[0], p.x], + [x[1], y[1], z[1], p.y], + [x[2], y[2], z[2], p.z], + [0, 0, 0, 0], + ]) + + camera.matrix_world = mat context.scene.camera = camera camera.data.angle = radians(60) area = next(area for area in context.screen.areas if area.type == "VIEW_3D") @@ -246,7 +263,8 @@ class ExecuteIfcClash(bpy.types.Operator): context.scene.render.image_settings.file_format = "PNG" context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png") bpy.ops.render.opengl(write_still=True) - return context.scene.render.filepath + with open(context.scene.render.filepath, "rb") as f: + return ("snapshot.png", f.read()) clasher.get_viewpoint_snapshot = get_viewpoint_snapshot diff --git a/src/ifcclash/ifcclash/ifcclash.py b/src/ifcclash/ifcclash/ifcclash.py index 1513e89c36..1406f40aee 100644 --- a/src/ifcclash/ifcclash/ifcclash.py +++ b/src/ifcclash/ifcclash/ifcclash.py @@ -122,14 +122,20 @@ class Clasher: for clash in clash_set["clashes"].values(): title = f'{clash["a_ifc_class"]}/{clash["a_name"]} and {clash["b_ifc_class"]}/{clash["b_name"]}' topic = bcfxml.add_topic(title, title, "IfcClash") - topic.add_viewpoint_from_point_and_guids( + viewpoint = topic.add_viewpoint_from_point_and_guids( np.array(clash["position"]), clash["a_global_id"], clash["b_global_id"], ) + snapshot = self.get_viewpoint_snapshot(viewpoint) + if snapshot: + topic.markup.viewpoints[0].snapshot = snapshot[0] + viewpoint.snapshot = snapshot[1] suffix = f".{i}" if i else "" bcfxml.save_project(f"{self.settings.output}{suffix}") - def get_viewpoint_snapshot(self, viewpoint, mat): - return None # Possible to overload this function in a GUI application if used as a library + def get_viewpoint_snapshot(self, viewpoint): + # Possible to overload this function in a GUI application if used as a library. + # Should return a tuple of (filename, bytes). + return None def export_json(self): clash_sets = self.clash_sets.copy() From 7b9205722747565f0f042360290c164f3e9fe85a Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 3 Jul 2023 11:52:30 +0500 Subject: [PATCH 18/21] Fixed errors displaying IFC Connections for IfcRelConnectsElements IfcRelConnectsElements type of connection wasn't handled properly resulting in errors. Now you should be able to see connections for connected slabs, walls etc - https://i.imgur.com/LpFBKBC.png Traceback: Traceback (most recent call last): File "\blenderbim\bim\module\geometry\ui.py", line 102, in draw ConnectionsData.load() File "\blenderbim\bim\module\geometry\data.py", line 100, in load cls.data = {"connections": cls.connections()} File "\blenderbim\bim\module\geometry\data.py", line 134, in connections relating_element_connection_type = rel.RelatingConnectionType File "\blenderbim\libs\site\packages\ifcopenshell\entity_instance.py", line 171, in __getattr__ raise AttributeError( AttributeError: entity instance of type 'IFC4.IfcRelConnectsElements' has no attribute 'RelatingConnectionType' --- src/blenderbim/blenderbim/bim/module/geometry/data.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/data.py b/src/blenderbim/blenderbim/bim/module/geometry/data.py index 0c95c61d2f..8b1849b200 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/data.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/data.py @@ -111,10 +111,13 @@ class ConnectionsData: for rel in connected_to: if element.is_a("IfcDistributionPort"): related_element = rel.RelatedPort - related_element_connection_type = "" else: related_element = rel.RelatedElement + + if element.is_a("IfcRelConnectsPathElements"): related_element_connection_type = rel.RelatedConnectionType + else: + related_element_connection_type = "" results.append( { @@ -128,10 +131,13 @@ class ConnectionsData: for rel in connected_from: if element.is_a("IfcDistributionPort"): relating_element = rel.RelatingPort - relating_element_connection_type = "" else: relating_element = rel.RelatingElement + + if element.is_a("IfcRelConnectsPathElements"): relating_element_connection_type = rel.RelatingConnectionType + else: + relating_element_connection_type = "" results.append( { From b67c5a0828a7620347eeeb6ec2f11824eb1ad776 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 3 Jul 2023 17:28:20 +1000 Subject: [PATCH 19/21] Fix #3366. Fix crash in IfcDiff if you are comparing a model with no geometry. --- src/ifcdiff/ifcdiff.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ifcdiff/ifcdiff.py b/src/ifcdiff/ifcdiff.py index e372c7b18c..4c61782a0f 100755 --- a/src/ifcdiff/ifcdiff.py +++ b/src/ifcdiff/ifcdiff.py @@ -29,9 +29,10 @@ import multiprocessing import ifcopenshell import ifcopenshell.geom import ifcopenshell.util.element +import ifcopenshell.util.selector import ifcopenshell.util.placement import ifcopenshell.util.classification -import ifcopenshell.util.selector +import ifcopenshell.util.representation from deepdiff import DeepDiff @@ -141,8 +142,9 @@ class IfcDiff: continue if should_check_geometry: # Option 1: check everything heuristically using the iterator (seems faster) - potential_old_changes.append(old) - potential_new_changes.append(new) + if ifcopenshell.util.representation.get_representation(new, "Model", "Body", "MODEL_VIEW"): + potential_old_changes.append(old) + potential_new_changes.append(new) # Option 2: check first using Python, then fallback to iterator (twice as slow) # diff = self.diff_element_basic_geometry(old, new) # if diff: From 485d41ae09c914ca30a18890326ac654c6979406 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 3 Jul 2023 12:09:25 +0500 Subject: [PATCH 20/21] disable chaching sound running the bim tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit to save someone's ears (probably mine 😁) from running 10 chaching sounds per second --- src/blenderbim/test/bim/test_feature.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 816c762c28..8fcc687c2d 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -75,7 +75,8 @@ def an_empty_blender_session(): # default project settings bpy.context.scene.unit_settings.system = "METRIC" bpy.context.scene.unit_settings.length_unit = "MILLIMETERS" - bpy.context.scene.BIMProjectProperties.template_file = '0' + bpy.context.scene.BIMProjectProperties.template_file = "0" + bpy.context.preferences.addons["blenderbim"].preferences.should_play_chaching_sound = False @given("an empty IFC project") From ccf09b962a2789cc573b498bc3cd9c1368d208f4 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 3 Jul 2023 14:31:54 +0500 Subject: [PATCH 21/21] Update type manager thumbnails on editing profiles Example - https://imgur.com/a/IcV8IIR --- .../blenderbim/bim/module/model/product.py | 102 +----------------- .../blenderbim/bim/module/model/profile.py | 26 ++++- src/blenderbim/blenderbim/tool/loader.py | 1 - src/blenderbim/blenderbim/tool/model.py | 99 +++++++++++++++++ 4 files changed, 125 insertions(+), 103 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index f938ea1e49..f85037b07b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -292,8 +292,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): offset: bpy.props.IntProperty() def _execute(self, context): - from PIL import Image, ImageDraw - if bpy.app.background: return @@ -310,106 +308,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): offset = 0 queue = queue[offset : offset + 9] - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - while queue: # if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation element = queue.pop() - obj = tool.Ifc.get_object(element) - - if not obj: - continue # Nothing to process - elif AuthoringData.type_thumbnails.get(element.id(), None): - continue # Already processed - elif obj.preview and obj.preview.icon_id: - AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id - continue - - if obj.data: - obj.asset_generate_preview() - while not obj.preview: - pass - else: - size = 128 - img = Image.new("RGBA", (size, size)) - draw = ImageDraw.Draw(img) - - material = ifcopenshell.util.element.get_material(element) - if material and material.is_a("IfcMaterialProfileSet"): - profile = material.MaterialProfiles[0].Profile - tool.Profile.draw_image_for_ifc_profile(draw, profile, size) - - elif material and material.is_a("IfcMaterialLayerSet"): - thicknesses = [l.LayerThickness for l in material.MaterialLayers] - total_thickness = sum(thicknesses) - si_total_thickness = total_thickness * unit_scale - if si_total_thickness <= 0.051: - width = 10 - elif si_total_thickness <= 0.11: - width = 20 - elif si_total_thickness <= 0.21: - width = 30 - elif si_total_thickness <= 0.31: - width = 40 - else: - width = 50 - - height = 100 - - is_horizontal = False - if element.is_a("IfcSlabType"): - is_horizontal = True - - parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", None) - if layer_set_direction == "AXIS2": - is_horizontal = False - elif layer_set_direction == "AXIS3": - is_horizontal = True - - if is_horizontal: - width, height = height, width - - x_offset = (size / 2) - (width / 2) - y_offset = (size / 2) - (height / 2) - draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) - current_thickness = 0 - del thicknesses[-1] - for thickness in thicknesses: - current_thickness += thickness - if element.is_a("IfcSlabType"): - y = (current_thickness / total_thickness) * height - line = [x_offset, y_offset + y, x_offset + width, y_offset + y] - else: - x = (current_thickness / total_thickness) * width - line = [x_offset + x, y_offset, x_offset + x, y_offset + height] - draw.line(line, fill="white", width=2) - elif False: - # TODO: things like parametric duct segments - pass - elif not element.RepresentationMaps: - # Empties are represented by a generic thumbnail - width = height = 100 - x_offset = (size / 2) - (width / 2) - y_offset = (size / 2) - (height / 2) - draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2) - draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2) - draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) - else: - draw.line([0, 0, size, size], fill="red", width=2) - draw.line([0, size, size, 0], fill="red", width=2) - - pixels = [item for sublist in img.getdata() for item in sublist] - - obj.asset_generate_preview() - while not obj.preview: - pass - - obj.preview.image_size = size, size - obj.preview.image_pixels_float = pixels - - queue.append(element) + if tool.Model.update_thumbnail_for_element(element): + queue.append(element) return {"FINISHED"} @@ -472,7 +375,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator): obj.matrix_world = newmat - def generate_box(usecase_path, ifc_file, settings): box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW") if not box_context: diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 113b192da4..6e4b4ec16c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -145,12 +145,21 @@ class DumbProfileRegenerator: objs = [] if not profile: return + + element_types = set() for element in self.get_elements_using_profile(profile): obj = tool.Ifc.get_object(element) if obj: objs.append(obj) + if element.is_a("IfcElementType"): + element_types.add(element) + DumbProfileRecalculator().recalculate(objs) + # update related thumbnails + for element in self.get_element_types_using_profile(profile): + tool.Model.update_thumbnail_for_element(element, refresh=True) + def regenerate_from_profile(self, usecase_path, ifc_file, settings): self.file = ifc_file objs = [] @@ -165,9 +174,10 @@ class DumbProfileRegenerator: def get_elements_using_profile(self, profile): results = [] - for profile_set in [ + profile_sets = [ mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") - ]: + ] + for profile_set in profile_sets: for inverse in self.file.get_inverse(profile_set): if not inverse.is_a("IfcMaterialProfileSetUsage"): continue @@ -181,6 +191,18 @@ class DumbProfileRegenerator: results.extend(rel.RelatedObjects) return results + def get_element_types_using_profile(self, profile): + results = [] + profile_sets = [ + mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") + ] + for profile_set in profile_sets: + for inverse in self.file.get_inverse(profile_set): + if not inverse.is_a("IfcRelAssociatesMaterial"): + continue + results.extend(inverse.RelatedObjects) + return results + def regenerate_from_type(self, usecase_path, ifc_file, settings): obj = tool.Ifc.get_object(settings["related_object"]) if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index e695ebafbb..1bb487728d 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -202,7 +202,6 @@ class Loader(blenderbim.core.tool.Loader): ifc_path = Path(tool.Ifc.get_path()) image_url = ifc_path.parent / image_url - # import pdb; pdb.set_trace() if not image_url.exists(): print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.") continue diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index c9d8249a4d..c958e47fbe 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -29,6 +29,7 @@ from mathutils import Matrix, Vector from blenderbim.bim import import_ifc from blenderbim.bim.module.geometry.helper import Helper import collections +from blenderbim.bim.module.model.data import AuthoringData class Model(blenderbim.core.tool.Model): @@ -630,3 +631,101 @@ class Model(blenderbim.core.tool.Model): is_global=True, should_sync_changes_first=False, ) + + @classmethod + def update_thumbnail_for_element(cls, element, refresh=False): + if bpy.app.background: + return + + from PIL import Image, ImageDraw + + obj = tool.Ifc.get_object(element) + if not obj: + return # Nothing to process + + if not refresh and element.id() in AuthoringData.type_thumbnails: + return # Already processed + + obj.asset_generate_preview() + while not obj.preview: + pass + + # if object has .data we can use default blender .asset_generate_preview() + if not obj.data: + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + size = 128 + img = Image.new("RGBA", (size, size)) + draw = ImageDraw.Draw(img) + + material = ifcopenshell.util.element.get_material(element) + if material and material.is_a("IfcMaterialProfileSet"): + profile = material.MaterialProfiles[0].Profile + tool.Profile.draw_image_for_ifc_profile(draw, profile, size) + + elif material and material.is_a("IfcMaterialLayerSet"): + thicknesses = [l.LayerThickness for l in material.MaterialLayers] + total_thickness = sum(thicknesses) + si_total_thickness = total_thickness * unit_scale + if si_total_thickness <= 0.051: + width = 10 + elif si_total_thickness <= 0.11: + width = 20 + elif si_total_thickness <= 0.21: + width = 30 + elif si_total_thickness <= 0.31: + width = 40 + else: + width = 50 + + height = 100 + + is_horizontal = False + if element.is_a("IfcSlabType"): + is_horizontal = True + + parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") + if parametric: + layer_set_direction = parametric.get("LayerSetDirection", None) + if layer_set_direction == "AXIS2": + is_horizontal = False + elif layer_set_direction == "AXIS3": + is_horizontal = True + + if is_horizontal: + width, height = height, width + + x_offset = (size / 2) - (width / 2) + y_offset = (size / 2) - (height / 2) + draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) + current_thickness = 0 + del thicknesses[-1] + for thickness in thicknesses: + current_thickness += thickness + if element.is_a("IfcSlabType"): + y = (current_thickness / total_thickness) * height + line = [x_offset, y_offset + y, x_offset + width, y_offset + y] + else: + x = (current_thickness / total_thickness) * width + line = [x_offset + x, y_offset, x_offset + x, y_offset + height] + draw.line(line, fill="white", width=2) + elif False: + # TODO: things like parametric duct segments + pass + elif not element.RepresentationMaps: + # Empties are represented by a generic thumbnail + width = height = 100 + x_offset = (size / 2) - (width / 2) + y_offset = (size / 2) - (height / 2) + draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2) + draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2) + draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) + else: + draw.line([0, 0, size, size], fill="red", width=2) + draw.line([0, size, size, 0], fill="red", width=2) + + pixels = [item for sublist in img.getdata() for item in sublist] + + obj.preview.image_size = size, size + obj.preview.image_pixels_float = pixels + + AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id