mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
Integrate brickschema undo system into BlenderBIM Add-on undo system wrapper
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -33,8 +33,6 @@ classes = (
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
operator.UndoBrick,
|
||||
operator.RedoBrick,
|
||||
operator.SerializeBrick,
|
||||
operator.AddBrickNamespace,
|
||||
prop.Brick,
|
||||
|
||||
@@ -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)
|
||||
core.add_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user