mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 06:32:09 +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.bim.handler
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from blenderbim.tool.brick import BrickStore
|
||||||
|
|
||||||
|
|
||||||
class IfcStore:
|
class IfcStore:
|
||||||
@@ -405,7 +406,10 @@ class IfcStore:
|
|||||||
|
|
||||||
if is_top_level_operator:
|
if is_top_level_operator:
|
||||||
IfcStore.begin_transaction(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
|
# 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)
|
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
|
||||||
else:
|
else:
|
||||||
@@ -417,10 +421,13 @@ class IfcStore:
|
|||||||
result = getattr(operator, "_execute")(context)
|
result = getattr(operator, "_execute")(context)
|
||||||
|
|
||||||
if is_top_level_operator:
|
if is_top_level_operator:
|
||||||
IfcStore.get_file().end_transaction()
|
if tool.Ifc.get():
|
||||||
IfcStore.add_transaction_operation(
|
tool.Ifc.get().end_transaction()
|
||||||
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
|
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)
|
IfcStore.end_transaction(operator)
|
||||||
blenderbim.bim.handler.refresh_ui_data()
|
blenderbim.bim.handler.refresh_ui_data()
|
||||||
|
|
||||||
@@ -456,6 +463,7 @@ class IfcStore:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def undo():
|
def undo():
|
||||||
|
BrickStore.undo()
|
||||||
if not IfcStore.history:
|
if not IfcStore.history:
|
||||||
return
|
return
|
||||||
event = IfcStore.history.pop()
|
event = IfcStore.history.pop()
|
||||||
@@ -465,6 +473,7 @@ class IfcStore:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def redo():
|
def redo():
|
||||||
|
BrickStore.redo()
|
||||||
if not IfcStore.future:
|
if not IfcStore.future:
|
||||||
return
|
return
|
||||||
event = IfcStore.future.pop()
|
event = IfcStore.future.pop()
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ classes = (
|
|||||||
operator.RewindBrickClass,
|
operator.RewindBrickClass,
|
||||||
operator.ViewBrickClass,
|
operator.ViewBrickClass,
|
||||||
operator.ViewBrickItem,
|
operator.ViewBrickItem,
|
||||||
operator.UndoBrick,
|
|
||||||
operator.RedoBrick,
|
|
||||||
operator.SerializeBrick,
|
operator.SerializeBrick,
|
||||||
operator.AddBrickNamespace,
|
operator.AddBrickNamespace,
|
||||||
prop.Brick,
|
prop.Brick,
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ import blenderbim.bim.handler
|
|||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
from blenderbim.tool.brick import BrickStore
|
from blenderbim.tool.brick import BrickStore
|
||||||
|
|
||||||
|
|
||||||
class Operator:
|
class Operator:
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self._execute(context)
|
IfcStore.execute_ifc_operator(self, context)
|
||||||
blenderbim.bim.handler.refresh_ui_data()
|
blenderbim.bim.handler.refresh_ui_data()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ class AddBrick(bpy.types.Operator, Operator):
|
|||||||
namespace=props.namespace,
|
namespace=props.namespace,
|
||||||
brick_class=props.brick_equipment_class,
|
brick_class=props.brick_equipment_class,
|
||||||
library=library,
|
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)
|
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_idname = "bim.new_brick_file"
|
||||||
bl_label = "New Brick File"
|
bl_label = "New Brick File"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
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):
|
def _execute(self, context):
|
||||||
core.new_brick_file(tool.Brick)
|
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):
|
class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||||
bl_idname = "bim.refresh_brick_viewer"
|
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,
|
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):
|
class SerializeBrick(bpy.types.Operator):
|
||||||
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_idname = "bim.serialize_brick"
|
||||||
bl_label = "Serialize Brick"
|
bl_label = "Serialize Brick"
|
||||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||||
@@ -220,18 +230,19 @@ class SerializeBrick(bpy.types.Operator, Operator):
|
|||||||
else:
|
else:
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def _execute(self, context):
|
def execute(self, context):
|
||||||
if self.should_save_as or not BrickStore.path:
|
if self.should_save_as or not BrickStore.path:
|
||||||
BrickStore.path = self.filepath
|
BrickStore.path = self.filepath
|
||||||
core.serialize_brick(tool.Brick)
|
core.serialize_brick(tool.Brick)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, properties):
|
def description(cls, context, properties):
|
||||||
if properties.should_save_as:
|
if properties.should_save_as:
|
||||||
return "Save Brick project to a selected file"
|
return "Save Brick project to a selected file"
|
||||||
return "Save the Brick project"
|
return "Save the Brick project"
|
||||||
|
|
||||||
|
|
||||||
class AddBrickNamespace(bpy.types.Operator, Operator):
|
class AddBrickNamespace(bpy.types.Operator, Operator):
|
||||||
bl_idname = "bim.add_brick_namespace"
|
bl_idname = "bim.add_brick_namespace"
|
||||||
bl_label = "Add Brick Namespace"
|
bl_label = "Add Brick Namespace"
|
||||||
@@ -240,4 +251,4 @@ class AddBrickNamespace(bpy.types.Operator, Operator):
|
|||||||
props = context.scene.BIMBrickProperties
|
props = context.scene.BIMBrickProperties
|
||||||
alias = props.new_brick_namespace_alias
|
alias = props.new_brick_namespace_alias
|
||||||
uri = props.new_brick_namespace_uri
|
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.add_brick_feed", text="", icon="PLUGIN")
|
||||||
row.operator("bim.remove_brick", text="", icon="X")
|
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 = self.layout.row(align=True)
|
||||||
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
|
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
|
||||||
op.should_save_as = False
|
op.should_save_as = False
|
||||||
|
|||||||
@@ -112,18 +112,9 @@ def remove_brick(ifc, brick, library=None, brick_uri=None):
|
|||||||
brick.run_refresh_brick_viewer()
|
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):
|
def serialize_brick(brick):
|
||||||
brick.serialize_brick()
|
brick.serialize_brick()
|
||||||
|
|
||||||
|
|
||||||
def add_namespace(brick, alias=None, uri=None):
|
def add_namespace(brick, alias=None, uri=None):
|
||||||
brick.add_namespace(alias, uri)
|
brick.add_namespace(alias, uri)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import ifcopenshell
|
|||||||
import ifcopenshell.util.brick
|
import ifcopenshell.util.brick
|
||||||
import blenderbim.core.tool
|
import blenderbim.core.tool
|
||||||
import blenderbim.tool as tool
|
import blenderbim.tool as tool
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import brickschema
|
import brickschema
|
||||||
@@ -36,15 +37,17 @@ except:
|
|||||||
# silence known rdflib_sqlalchemy TypeError warning
|
# silence known rdflib_sqlalchemy TypeError warning
|
||||||
# see https://github.com/BrickSchema/Brick/issues/513#issuecomment-1558493675
|
# see https://github.com/BrickSchema/Brick/issues/513#issuecomment-1558493675
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger("rdflib")
|
logger = logging.getLogger("rdflib")
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
|
||||||
class Brick(blenderbim.core.tool.Brick):
|
class Brick(blenderbim.core.tool.Brick):
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_brick(cls, namespace, brick_class, label):
|
def add_brick(cls, namespace, brick_class, label):
|
||||||
ns = Namespace(namespace)
|
ns = Namespace(namespace)
|
||||||
brick = ns[ifcopenshell.guid.expand(ifcopenshell.guid.new())]
|
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, RDF.type, URIRef(brick_class)))
|
||||||
cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal(label)))
|
cs.add((brick, URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal(label)))
|
||||||
return str(brick)
|
return str(brick)
|
||||||
@@ -256,7 +259,7 @@ class Brick(blenderbim.core.tool.Brick):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_brick_file(cls, filepath):
|
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__))
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||||
BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
||||||
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
||||||
@@ -268,7 +271,7 @@ class Brick(blenderbim.core.tool.Brick):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_brick_file(cls):
|
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__))
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||||
BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
BrickStore.schema = os.path.join(cwd, "..", "bim", "schema", "Brick.ttl")
|
||||||
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
BrickStore.graph = brickschema.persistent.VersionedGraphCollection("sqlite://")
|
||||||
@@ -286,8 +289,8 @@ class Brick(blenderbim.core.tool.Brick):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_brick(cls, brick_uri):
|
def remove_brick(cls, brick_uri):
|
||||||
if(BrickStore.graph.triples((URIRef(brick_uri), None, None))):
|
if BrickStore.graph.triples((URIRef(brick_uri), None, None)):
|
||||||
with BrickStore.graph.new_changeset("PROJECT") as cs:
|
with BrickStore.new_changeset() as cs:
|
||||||
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
|
for triple in BrickStore.graph.triples((URIRef(brick_uri), None, None)):
|
||||||
cs.remove(triple)
|
cs.remove(triple)
|
||||||
|
|
||||||
@@ -315,41 +318,73 @@ class Brick(blenderbim.core.tool.Brick):
|
|||||||
def set_active_brick_class(cls, brick_class):
|
def set_active_brick_class(cls, brick_class):
|
||||||
bpy.context.scene.BIMBrickProperties.active_brick_class = 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
|
@classmethod
|
||||||
def serialize_brick(cls):
|
def serialize_brick(cls):
|
||||||
BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle")
|
BrickStore.get_project().serialize(destination=BrickStore.path, format="turtle")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_namespace(cls, alias, uri):
|
def add_namespace(cls, alias, uri):
|
||||||
BrickStore.graph.bind(alias, Namespace(uri))
|
BrickStore.graph.bind(alias, Namespace(uri))
|
||||||
# need some way to reload namespace enum view
|
# need some way to reload namespace enum view
|
||||||
|
|
||||||
|
|
||||||
class BrickStore:
|
class BrickStore:
|
||||||
schema = None # this is now a os path
|
schema = None # this is now a os path
|
||||||
path = None # file path if the project was loaded in
|
path = None # file path if the project was loaded in
|
||||||
graph = None # this is the VersionedGraphCollection with 2 arbitrarily named graphs: "schema" and "project"
|
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
|
@staticmethod
|
||||||
def purge():
|
def purge():
|
||||||
BrickStore.schema = None
|
BrickStore.schema = None
|
||||||
BrickStore.graph = None
|
BrickStore.graph = None
|
||||||
BrickStore.path = None
|
BrickStore.path = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_project(cls):
|
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