diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index fa20886959..875614e1bd 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -26,6 +26,7 @@ import ifcopenshell import blenderbim.bim.handler import blenderbim.tool as tool from pathlib import Path +from blenderbim.tool.brick import BrickStore class IfcStore: @@ -405,7 +406,10 @@ class IfcStore: if is_top_level_operator: IfcStore.begin_transaction(operator) - IfcStore.get_file().begin_transaction() + if tool.Ifc.get(): + tool.Ifc.get().begin_transaction() + if BrickStore.graph: + BrickStore.begin_transaction() # This empty transaction ensures that each operator has at least one transaction IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True) else: @@ -417,10 +421,13 @@ class IfcStore: result = getattr(operator, "_execute")(context) if is_top_level_operator: - IfcStore.get_file().end_transaction() - IfcStore.add_transaction_operation( - operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo() - ) + if tool.Ifc.get(): + tool.Ifc.get().end_transaction() + IfcStore.add_transaction_operation( + operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo() + ) + if BrickStore.graph: + BrickStore.end_transaction() IfcStore.end_transaction(operator) blenderbim.bim.handler.refresh_ui_data() @@ -456,6 +463,7 @@ class IfcStore: @staticmethod def undo(): + BrickStore.undo() if not IfcStore.history: return event = IfcStore.history.pop() @@ -465,6 +473,7 @@ class IfcStore: @staticmethod def redo(): + BrickStore.redo() if not IfcStore.future: return event = IfcStore.future.pop() diff --git a/src/blenderbim/blenderbim/bim/module/brick/__init__.py b/src/blenderbim/blenderbim/bim/module/brick/__init__.py index 6c8499cb46..c3bb7f5c12 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/brick/__init__.py @@ -33,8 +33,6 @@ classes = ( operator.RewindBrickClass, operator.ViewBrickClass, operator.ViewBrickItem, - operator.UndoBrick, - operator.RedoBrick, operator.SerializeBrick, operator.AddBrickNamespace, prop.Brick, diff --git a/src/blenderbim/blenderbim/bim/module/brick/operator.py b/src/blenderbim/blenderbim/bim/module/brick/operator.py index 896904d3fa..a9950bdc24 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/operator.py +++ b/src/blenderbim/blenderbim/bim/module/brick/operator.py @@ -24,9 +24,10 @@ import blenderbim.bim.handler from blenderbim.bim.ifc import IfcStore from blenderbim.tool.brick import BrickStore + class Operator: def execute(self, context): - self._execute(context) + IfcStore.execute_ifc_operator(self, context) blenderbim.bim.handler.refresh_ui_data() return {"FINISHED"} @@ -126,7 +127,7 @@ class AddBrick(bpy.types.Operator, Operator): namespace=props.namespace, brick_class=props.brick_equipment_class, library=library, - label=props.new_brick_label + label=props.new_brick_label, ) @@ -159,14 +160,36 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator): core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library) -class NewBrickFile(bpy.types.Operator, Operator): +class NewBrickFile(bpy.types.Operator): bl_idname = "bim.new_brick_file" bl_label = "New Brick File" bl_options = {"REGISTER", "UNDO"} + def execute(self, context): + IfcStore.begin_transaction(self) + IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True) + self._execute(context) + self.transaction_data = { + "schema": BrickStore.schema, + "path": BrickStore.path, + "graph": BrickStore.graph, + } + IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit) + IfcStore.end_transaction(self) + blenderbim.bim.handler.refresh_ui_data() + return {"FINISHED"} + def _execute(self, context): core.new_brick_file(tool.Brick) + def rollback(self, data): + BrickStore.purge() + + def commit(self, data): + BrickStore.schema = data["schema"] + BrickStore.path = data["path"] + BrickStore.graph = data["graph"] + class RefreshBrickViewer(bpy.types.Operator, Operator): bl_idname = "bim.refresh_brick_viewer" @@ -191,21 +214,8 @@ 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): +class SerializeBrick(bpy.types.Operator): bl_idname = "bim.serialize_brick" bl_label = "Serialize Brick" filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"}) @@ -220,18 +230,19 @@ class SerializeBrick(bpy.types.Operator, Operator): else: return self.execute(context) - def _execute(self, context): + def execute(self, context): if self.should_save_as or not BrickStore.path: BrickStore.path = self.filepath core.serialize_brick(tool.Brick) return {"FINISHED"} - + @classmethod def description(cls, context, properties): if properties.should_save_as: return "Save Brick project to a selected file" return "Save the Brick project" - + + class AddBrickNamespace(bpy.types.Operator, Operator): bl_idname = "bim.add_brick_namespace" bl_label = "Add Brick Namespace" @@ -240,4 +251,4 @@ class AddBrickNamespace(bpy.types.Operator, Operator): props = context.scene.BIMBrickProperties alias = props.new_brick_namespace_alias uri = props.new_brick_namespace_uri - core.add_namespace(tool.Brick, alias=alias, uri=uri) \ No newline at end of file + core.add_namespace(tool.Brick, alias=alias, uri=uri) diff --git a/src/blenderbim/blenderbim/bim/module/brick/ui.py b/src/blenderbim/blenderbim/bim/module/brick/ui.py index 008f53b4cd..594a7bef1c 100644 --- a/src/blenderbim/blenderbim/bim/module/brick/ui.py +++ b/src/blenderbim/blenderbim/bim/module/brick/ui.py @@ -87,10 +87,6 @@ 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) op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save") op.should_save_as = False diff --git a/src/blenderbim/blenderbim/core/brick.py b/src/blenderbim/blenderbim/core/brick.py index a675b15500..ab9e8afe53 100644 --- a/src/blenderbim/blenderbim/core/brick.py +++ b/src/blenderbim/blenderbim/core/brick.py @@ -112,18 +112,9 @@ def remove_brick(ifc, brick, library=None, brick_uri=None): 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): brick.serialize_brick() + def add_namespace(brick, alias=None, uri=None): brick.add_namespace(alias, uri) diff --git a/src/blenderbim/blenderbim/tool/brick.py b/src/blenderbim/blenderbim/tool/brick.py index 01596bb8e7..eeaa4b430b 100644 --- a/src/blenderbim/blenderbim/tool/brick.py +++ b/src/blenderbim/blenderbim/tool/brick.py @@ -22,6 +22,7 @@ import ifcopenshell import ifcopenshell.util.brick import blenderbim.core.tool import blenderbim.tool as tool +from contextlib import contextmanager try: import brickschema @@ -36,15 +37,17 @@ except: # 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, label): ns = Namespace(namespace) brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())] - with BrickStore.graph.new_changeset("PROJECT") as cs: + with BrickStore.new_changeset() as cs: cs.add((brick, RDF.type, URIRef(brick_class))) cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal(label))) return str(brick) @@ -256,7 +259,7 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def load_brick_file(cls, filepath): - if not BrickStore.schema: # important check for running under test cases + 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.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") @@ -268,7 +271,7 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def new_brick_file(cls): - if not BrickStore.schema: # important check for running under test cases + 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.graph = brickschema.persistent.VersionedGraphCollection("sqlite://") @@ -286,8 +289,8 @@ class Brick(blenderbim.core.tool.Brick): @classmethod def remove_brick(cls, brick_uri): - if(BrickStore.graph.triples((URIRef(brick_uri), None, None))): - with BrickStore.graph.new_changeset("PROJECT") as cs: + if BrickStore.graph.triples((URIRef(brick_uri), None, None)): + with BrickStore.new_changeset() as cs: for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)): cs.remove(triple) @@ -315,41 +318,73 @@ 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): BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle") - + @classmethod def add_namespace(cls, alias, uri): BrickStore.graph.bind(alias, Namespace(uri)) # need some way to reload namespace enum view - + + class BrickStore: - schema = None # this is now a os path - path = None # file path if the project was loaded in + 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 - + # "SCHEMA" holds the Brick.ttl metadata; "PROJECT" holds all the authored entities + history = [] + future = [] + current_changesets = 0 + history_size = 64 + @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 + return BrickStore.graph.graph_at(graph="PROJECT") + + @classmethod + def set_history_size(cls, size): + cls.history_size = size + while len(cls.history) > cls.history_size: + cls.history.pop(0) + + @classmethod + def begin_transaction(cls): + cls.current_changesets = 0 + + @classmethod + def end_transaction(cls): + cls.history.append(cls.current_changesets) + if len(cls.history) > cls.history_size: + cls.history.pop(0) + + @classmethod + @contextmanager + def new_changeset(cls): + cls.current_changesets += 1 + with BrickStore.graph.new_changeset("PROJECT") as cs: + yield cs + + @classmethod + def undo(cls): + if not BrickStore.graph or not BrickStore.history: + return + total_changesets = BrickStore.history.pop() + for i in range(0, total_changesets): + BrickStore.graph.undo() + BrickStore.future.append(total_changesets) + + @classmethod + def redo(cls): + if not BrickStore.graph or not BrickStore.future: + return + total_changesets = BrickStore.future.pop() + for i in range(0, total_changesets): + BrickStore.graph.redo() + BrickStore.history.append(total_changesets)