diff --git a/.gitignore b/.gitignore index ee3454b468..247d590e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -94,4 +94,7 @@ src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py # apple -.DS_Store \ No newline at end of file +.DS_Store + +# Brickschema +src/blenderbim/blenderbim/bim/schema/Brick.ttl \ No newline at end of file diff --git a/src/blenderbim/blenderbim/bim/module/brick/__init__.py b/src/blenderbim/blenderbim/bim/module/brick/__init__.py index 5e0afa989a..744e679dab 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/brick/__init__.py @@ -33,6 +33,9 @@ classes = ( operator.RewindBrickClass, operator.ViewBrickClass, operator.ViewBrickItem, + operator.UndoBrick, + operator.RedoBrick, + operator.SerializeBrick, prop.Brick, prop.BIMBrickProperties, ui.BIM_PT_brickschema, 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 diff --git a/src/blenderbim/blenderbim/bim/module/brick/operator.py b/src/blenderbim/blenderbim/bim/module/brick/operator.py index 33d877ebd1..72ef846c87 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/operator.py +++ b/src/blenderbim/blenderbim/bim/module/brick/operator.py @@ -189,3 +189,24 @@ 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 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" + + 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..717ee3eb12 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -58,6 +58,13 @@ 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") + 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..cd7fafaed0 100644 --- a/src/blenderbim/blenderbim/core/brick.py +++ b/src/blenderbim/blenderbim/core/brick.py @@ -109,3 +109,14 @@ 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 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 61e498d861..b4539c64aa 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,14 +33,20 @@ 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): 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 @@ -100,7 +107,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() @@ -249,24 +256,24 @@ 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.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") + with BrickStore.graph.new_changeset("SCHEMA") as cs: + cs.load_file(BrickStore.schema) + with BrickStore.graph.new_changeset("PROJECT") as cs: + cs.load_file(filepath) 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.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl") + BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") + with BrickStore.graph.new_changeset("SCHEMA") as cs: + cs.load_file(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#")) @@ -281,8 +288,10 @@ 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) + 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): @@ -308,14 +317,39 @@ 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): + if(len(BrickStore.graph.versions()) > 1): + BrickStore.graph.undo() + + @classmethod + def redo_brick(cls): + 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): + #temporary file path, could either be user selected for "save as" or use the BrickStore.path for simply "save" + 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") class BrickStore: - schema = None - graph = None - path = None - + schema = None # this is now a os path + 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.path = None + BrickStore.path = None + + @classmethod + def get_project(cls): + return BrickStore.graph.graph_at(graph="PROJECT") \ No newline at end of file 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()