mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-16 21:42:19 +00:00
Experimental undo and redo Python prototype (#1539)
* Experimental undo and redo Python prototype * Simplify history, use transaction jargon, use walk for more robust serialisation * Black file, set history size, add file reference to entity_instance constructor * Ensure that files are always passed when entities are wrapped * Add support for undo/redo of all project module operations * Minor fix * Blender to IFC mappings are now managed by Blender RNA, so they don't break on undo operations. See #1475. * Implement undo for all attribute operations. See #1475. * Update documentation for installation of add-on * Revert Blender RNA approach for Blender-IFC mappings, because it didn't scale, but still fix the undo/redo object memory corruption with new "reload_linked_elements" method.
This commit is contained in:
@@ -110,6 +110,8 @@ if bpy is not None:
|
|||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||||
|
bpy.app.handlers.undo_post.append(handler.undo_post)
|
||||||
|
bpy.app.handlers.redo_post.append(handler.redo_post)
|
||||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||||
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def name_callback(obj, data):
|
|||||||
collection = obj.users_collection[0]
|
collection = obj.users_collection[0]
|
||||||
collection.name = obj.name
|
collection.name = obj.name
|
||||||
if element.is_a("IfcGrid"):
|
if element.is_a("IfcGrid"):
|
||||||
axis_obj = IfcStore.id_map[element.UAxes[0].id()]
|
axis_obj = IfcStore.get_element(element.UAxes[0].id())
|
||||||
axis_collection = axis_obj.users_collection[0]
|
axis_collection = axis_obj.users_collection[0]
|
||||||
grid_collection = None
|
grid_collection = None
|
||||||
for collection in bpy.data.collections:
|
for collection in bpy.data.collections:
|
||||||
@@ -103,14 +103,26 @@ def loadIfcStore(scene):
|
|||||||
if not ifc_file:
|
if not ifc_file:
|
||||||
return
|
return
|
||||||
IfcStore.get_schema()
|
IfcStore.get_schema()
|
||||||
[
|
IfcStore.reload_linked_elements()
|
||||||
IfcStore.link_element(ifc_file.by_id(o.BIMObjectProperties.ifc_definition_id), o)
|
|
||||||
for o in bpy.data.objects
|
|
||||||
if o.BIMObjectProperties.ifc_definition_id
|
|
||||||
]
|
|
||||||
purge_module_data()
|
purge_module_data()
|
||||||
|
|
||||||
|
|
||||||
|
@persistent
|
||||||
|
def undo_post(scene):
|
||||||
|
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||||
|
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||||
|
IfcStore.undo()
|
||||||
|
IfcStore.reload_linked_elements(should_reload_selected=True)
|
||||||
|
|
||||||
|
|
||||||
|
@persistent
|
||||||
|
def redo_post(scene):
|
||||||
|
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||||
|
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||||
|
IfcStore.redo()
|
||||||
|
IfcStore.reload_linked_elements(should_reload_selected=True)
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def ensureIfcExported(scene):
|
def ensureIfcExported(scene):
|
||||||
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
import uuid
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import blenderbim.bim.handler
|
import blenderbim.bim.handler
|
||||||
|
|
||||||
@@ -15,6 +16,9 @@ class IfcStore:
|
|||||||
library_path = ""
|
library_path = ""
|
||||||
library_file = None
|
library_file = None
|
||||||
element_listeners = set()
|
element_listeners = set()
|
||||||
|
last_transaction = ""
|
||||||
|
history = []
|
||||||
|
future = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def purge():
|
def purge():
|
||||||
@@ -65,6 +69,21 @@ class IfcStore:
|
|||||||
def add_element_listener(callback):
|
def add_element_listener(callback):
|
||||||
IfcStore.element_listeners.add(callback)
|
IfcStore.element_listeners.add(callback)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reload_linked_elements(should_reload_selected=False):
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
if not file:
|
||||||
|
return
|
||||||
|
if should_reload_selected:
|
||||||
|
objects = bpy.context.selected_objects + [bpy.context.active_object]
|
||||||
|
else:
|
||||||
|
objects = bpy.data.objects
|
||||||
|
[
|
||||||
|
IfcStore.link_element(file.by_id(obj.BIMObjectProperties.ifc_definition_id), obj)
|
||||||
|
for obj in objects
|
||||||
|
if obj.BIMObjectProperties.ifc_definition_id
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def link_element(element, obj):
|
def link_element(element, obj):
|
||||||
IfcStore.id_map[element.id()] = obj
|
IfcStore.id_map[element.id()] = obj
|
||||||
@@ -100,3 +119,43 @@ class IfcStore:
|
|||||||
|
|
||||||
if obj:
|
if obj:
|
||||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_transaction_key(operator):
|
||||||
|
if not getattr(operator, "transaction_key", None):
|
||||||
|
setattr(operator, "transaction_key", str(uuid.uuid4()))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_transaction(operator, rollback=None, commit=None):
|
||||||
|
IfcStore.generate_transaction_key(operator)
|
||||||
|
key = getattr(operator, "transaction_key", None)
|
||||||
|
data = getattr(operator, "transaction_data", None)
|
||||||
|
bpy.context.scene.BIMProperties.last_transaction = key
|
||||||
|
IfcStore.last_transaction = key
|
||||||
|
rollback = rollback or getattr(operator, "rollback", lambda data: True)
|
||||||
|
commit = commit or getattr(operator, "commit", lambda data: True)
|
||||||
|
if IfcStore.history and IfcStore.history[-1]["key"] == key:
|
||||||
|
IfcStore.history[-1]["transactions"].append({"rollback": rollback, "commit": commit, "data": data})
|
||||||
|
else:
|
||||||
|
IfcStore.history.append(
|
||||||
|
{"key": key, "transactions": [{"rollback": rollback, "commit": commit, "data": data}]}
|
||||||
|
)
|
||||||
|
IfcStore.future = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def undo():
|
||||||
|
if not IfcStore.history:
|
||||||
|
return
|
||||||
|
event = IfcStore.history.pop()
|
||||||
|
for transaction in event["transactions"][::-1]:
|
||||||
|
transaction["rollback"](transaction["data"])
|
||||||
|
IfcStore.future.append(event)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def redo():
|
||||||
|
if not IfcStore.future:
|
||||||
|
return
|
||||||
|
event = IfcStore.future.pop()
|
||||||
|
for transaction in event["transactions"]:
|
||||||
|
transaction["commit"](transaction["data"])
|
||||||
|
IfcStore.history.append(event)
|
||||||
|
|||||||
@@ -704,7 +704,7 @@ class IfcImporter:
|
|||||||
product = self.file.by_id(shape.guid)
|
product = self.file.by_id(shape.guid)
|
||||||
# Facetation is to accommodate broken Revit files
|
# Facetation is to accommodate broken Revit files
|
||||||
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
|
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
|
||||||
if shape.context not in ["Body", "Facetation"] and shape.guid in IfcStore.guid_map:
|
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
|
||||||
# We only load a single context, and we prioritise the Body context. See #1290.
|
# We only load a single context, and we prioritise the Body context. See #1290.
|
||||||
pass
|
pass
|
||||||
elif product.is_a("IfcAnnotation") and product.ObjectType == "DRAWING":
|
elif product.is_a("IfcAnnotation") and product.ObjectType == "DRAWING":
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from ifcopenshell.api.attribute.data import Data
|
|||||||
class EnableEditingAttributes(bpy.types.Operator):
|
class EnableEditingAttributes(bpy.types.Operator):
|
||||||
bl_idname = "bim.enable_editing_attributes"
|
bl_idname = "bim.enable_editing_attributes"
|
||||||
bl_label = "Enable Editing Attributes"
|
bl_label = "Enable Editing Attributes"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
obj: bpy.props.StringProperty()
|
obj: bpy.props.StringProperty()
|
||||||
obj_type: bpy.props.StringProperty()
|
obj_type: bpy.props.StringProperty()
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ class EnableEditingAttributes(bpy.types.Operator):
|
|||||||
class DisableEditingAttributes(bpy.types.Operator):
|
class DisableEditingAttributes(bpy.types.Operator):
|
||||||
bl_idname = "bim.disable_editing_attributes"
|
bl_idname = "bim.disable_editing_attributes"
|
||||||
bl_label = "Disable Editing Attributes"
|
bl_label = "Disable Editing Attributes"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
obj: bpy.props.StringProperty()
|
obj: bpy.props.StringProperty()
|
||||||
obj_type: bpy.props.StringProperty()
|
obj_type: bpy.props.StringProperty()
|
||||||
|
|
||||||
@@ -61,6 +63,8 @@ class DisableEditingAttributes(bpy.types.Operator):
|
|||||||
class EditAttributes(bpy.types.Operator):
|
class EditAttributes(bpy.types.Operator):
|
||||||
bl_idname = "bim.edit_attributes"
|
bl_idname = "bim.edit_attributes"
|
||||||
bl_label = "Edit Attributes"
|
bl_label = "Edit Attributes"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
transaction_key: bpy.props.StringProperty()
|
||||||
obj: bpy.props.StringProperty()
|
obj: bpy.props.StringProperty()
|
||||||
obj_type: bpy.props.StringProperty()
|
obj_type: bpy.props.StringProperty()
|
||||||
|
|
||||||
@@ -95,9 +99,11 @@ class EditAttributes(bpy.types.Operator):
|
|||||||
elif attribute["type"] == "enum":
|
elif attribute["type"] == "enum":
|
||||||
attributes[attribute["name"]] = blender_attribute.enum_value
|
attributes[attribute["name"]] = blender_attribute.enum_value
|
||||||
product = self.file.by_id(oprops.ifc_definition_id)
|
product = self.file.by_id(oprops.ifc_definition_id)
|
||||||
|
self.file.begin_transaction()
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes}
|
"attribute.edit_attributes", self.file, **{"product": product, "attributes": attributes}
|
||||||
)
|
)
|
||||||
|
self.file.end_transaction()
|
||||||
if "Name" in attributes:
|
if "Name" in attributes:
|
||||||
new_name = "{}/{}".format(product.is_a(), product.Name or "Unnamed")
|
new_name = "{}/{}".format(product.is_a(), product.Name or "Unnamed")
|
||||||
collection = bpy.data.collections.get(obj.name)
|
collection = bpy.data.collections.get(obj.name)
|
||||||
@@ -106,12 +112,23 @@ class EditAttributes(bpy.types.Operator):
|
|||||||
obj.name = new_name
|
obj.name = new_name
|
||||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
bpy.ops.bim.disable_editing_attributes(obj=obj.name, obj_type=self.obj_type)
|
||||||
|
self.transaction_data = {"ifc_definition_id": oprops.ifc_definition_id}
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
IfcStore.get_file().undo()
|
||||||
|
Data.load(IfcStore.get_file(), data["ifc_definition_id"])
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
IfcStore.get_file().redo()
|
||||||
|
Data.load(IfcStore.get_file(), data["ifc_definition_id"])
|
||||||
|
|
||||||
|
|
||||||
class GenerateGlobalId(bpy.types.Operator):
|
class GenerateGlobalId(bpy.types.Operator):
|
||||||
bl_idname = "bim.generate_global_id"
|
bl_idname = "bim.generate_global_id"
|
||||||
bl_label = "Regenerate GlobalId"
|
bl_label = "Regenerate GlobalId"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
|
index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import bpy
|
|||||||
from . import ui, prop, operator
|
from . import ui, prop, operator
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
operator.ValidateIfcFile,
|
||||||
operator.ProfileImportIFC,
|
operator.ProfileImportIFC,
|
||||||
operator.CreateAllShapes,
|
operator.CreateAllShapes,
|
||||||
operator.CreateShapeFromStepId,
|
operator.CreateShapeFromStepId,
|
||||||
|
|||||||
@@ -5,6 +5,19 @@ import blenderbim.bim.import_ifc as import_ifc
|
|||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateIfcFile(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.validate_ifc_file"
|
||||||
|
bl_label = "Validate IFC File"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
import ifcopenshell.validate
|
||||||
|
|
||||||
|
logger = logging.getLogger("validate")
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ProfileImportIFC(bpy.types.Operator):
|
class ProfileImportIFC(bpy.types.Operator):
|
||||||
bl_idname = "bim.profile_import_ifc"
|
bl_idname = "bim.profile_import_ifc"
|
||||||
bl_label = "Profile Import IFC"
|
bl_label = "Profile Import IFC"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from . import ui, prop, operator
|
|||||||
classes = (
|
classes = (
|
||||||
operator.CreateProject,
|
operator.CreateProject,
|
||||||
operator.CreateProjectLibrary,
|
operator.CreateProjectLibrary,
|
||||||
operator.ValidateIfcFile,
|
|
||||||
operator.SelectLibraryFile,
|
operator.SelectLibraryFile,
|
||||||
operator.ChangeLibraryElement,
|
operator.ChangeLibraryElement,
|
||||||
operator.RefreshLibrary,
|
operator.RefreshLibrary,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import bpy
|
|||||||
import logging
|
import logging
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
|
import ifcopenshell.util.representation
|
||||||
import bpy
|
import bpy
|
||||||
import blenderbim.bim.handler
|
import blenderbim.bim.handler
|
||||||
from blenderbim.bim.ifc import IfcStore
|
from blenderbim.bim.ifc import IfcStore
|
||||||
@@ -11,6 +12,8 @@ from blenderbim.bim import import_ifc
|
|||||||
class CreateProject(bpy.types.Operator):
|
class CreateProject(bpy.types.Operator):
|
||||||
bl_idname = "bim.create_project"
|
bl_idname = "bim.create_project"
|
||||||
bl_label = "Create Project"
|
bl_label = "Create Project"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
transaction_key: bpy.props.StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
@@ -22,6 +25,9 @@ class CreateProject(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
|
|
||||||
|
self.transaction_data = {"file": self.file}
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
|
|
||||||
bpy.ops.bim.add_person()
|
bpy.ops.bim.add_person()
|
||||||
bpy.ops.bim.add_organisation()
|
bpy.ops.bim.add_organisation()
|
||||||
|
|
||||||
@@ -30,7 +36,7 @@ class CreateProject(bpy.types.Operator):
|
|||||||
building = bpy.data.objects.new("My Building", None)
|
building = bpy.data.objects.new("My Building", None)
|
||||||
building_storey = bpy.data.objects.new("Ground Floor", None)
|
building_storey = bpy.data.objects.new("Ground Floor", None)
|
||||||
|
|
||||||
bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject")
|
bpy.ops.bim.assign_class(transaction_key=self.transaction_key, obj=project.name, ifc_class="IfcProject")
|
||||||
bpy.ops.bim.assign_unit()
|
bpy.ops.bim.assign_unit()
|
||||||
bpy.ops.bim.add_subcontext(context="Model")
|
bpy.ops.bim.add_subcontext(context="Model")
|
||||||
bpy.ops.bim.add_subcontext(context="Model", subcontext="Body", target_view="MODEL_VIEW")
|
bpy.ops.bim.add_subcontext(context="Model", subcontext="Body", target_view="MODEL_VIEW")
|
||||||
@@ -38,24 +44,35 @@ class CreateProject(bpy.types.Operator):
|
|||||||
bpy.ops.bim.add_subcontext(context="Plan")
|
bpy.ops.bim.add_subcontext(context="Plan")
|
||||||
bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW")
|
bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW")
|
||||||
|
|
||||||
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
|
bpy.context.scene.BIMProperties.contexts = str(
|
||||||
if subcontext.ContextIdentifier == "Body":
|
ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id()
|
||||||
bpy.context.scene.BIMProperties.contexts = str(subcontext.id())
|
)
|
||||||
break
|
|
||||||
|
|
||||||
bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite")
|
bpy.ops.bim.assign_class(transaction_key=self.transaction_key, obj=site.name, ifc_class="IfcSite")
|
||||||
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding")
|
bpy.ops.bim.assign_class(transaction_key=self.transaction_key, obj=building.name, ifc_class="IfcBuilding")
|
||||||
bpy.ops.bim.assign_class(obj=building_storey.name, ifc_class="IfcBuildingStorey")
|
bpy.ops.bim.assign_class(
|
||||||
|
transaction_key=self.transaction_key, obj=building_storey.name, ifc_class="IfcBuildingStorey"
|
||||||
|
)
|
||||||
bpy.ops.bim.assign_object(related_object=site.name, relating_object=project.name)
|
bpy.ops.bim.assign_object(related_object=site.name, relating_object=project.name)
|
||||||
bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name)
|
bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name)
|
||||||
bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name)
|
bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name)
|
||||||
# Data.load()
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
IfcStore.file = None
|
||||||
|
blenderbim.bim.handler.purge_module_data()
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
blenderbim.bim.handler.purge_module_data()
|
||||||
|
IfcStore.file = data["file"]
|
||||||
|
|
||||||
|
|
||||||
class CreateProjectLibrary(bpy.types.Operator):
|
class CreateProjectLibrary(bpy.types.Operator):
|
||||||
bl_idname = "bim.create_project_library"
|
bl_idname = "bim.create_project_library"
|
||||||
bl_label = "Create Project Library"
|
bl_label = "Create Project Library"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
transaction_key: bpy.props.StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
@@ -67,45 +84,62 @@ class CreateProjectLibrary(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
|
|
||||||
|
self.transaction_data = {"file": self.file}
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
|
|
||||||
if self.file.schema == "IFC2X3":
|
if self.file.schema == "IFC2X3":
|
||||||
bpy.ops.bim.add_person()
|
bpy.ops.bim.add_person()
|
||||||
bpy.ops.bim.add_organisation()
|
bpy.ops.bim.add_organisation()
|
||||||
|
|
||||||
project_library = bpy.data.objects.new("My Project Library", None)
|
project_library = bpy.data.objects.new("My Project Library", None)
|
||||||
bpy.ops.bim.assign_class(obj=project_library.name, ifc_class="IfcProjectLibrary")
|
bpy.ops.bim.assign_class(
|
||||||
|
transaction_key=self.transaction_key, obj=project_library.name, ifc_class="IfcProjectLibrary"
|
||||||
|
)
|
||||||
bpy.ops.bim.assign_unit()
|
bpy.ops.bim.assign_unit()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
IfcStore.file = None
|
||||||
|
blenderbim.bim.handler.purge_module_data()
|
||||||
|
|
||||||
class ValidateIfcFile(bpy.types.Operator):
|
def commit(self, data):
|
||||||
bl_idname = "bim.validate_ifc_file"
|
blenderbim.bim.handler.purge_module_data()
|
||||||
bl_label = "Validate IFC File"
|
IfcStore.file = data["file"]
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
import ifcopenshell.validate
|
|
||||||
|
|
||||||
logger = logging.getLogger("validate")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
ifcopenshell.validate.validate(IfcStore.get_file(), logger)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class SelectLibraryFile(bpy.types.Operator):
|
class SelectLibraryFile(bpy.types.Operator):
|
||||||
bl_idname = "bim.select_library_file"
|
bl_idname = "bim.select_library_file"
|
||||||
bl_label = "Select Library File"
|
bl_label = "Select Library File"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
old_filepath = IfcStore.library_path
|
||||||
IfcStore.library_path = self.filepath
|
IfcStore.library_path = self.filepath
|
||||||
IfcStore.library_file = ifcopenshell.open(self.filepath)
|
IfcStore.library_file = ifcopenshell.open(self.filepath)
|
||||||
bpy.ops.bim.refresh_library()
|
bpy.ops.bim.refresh_library()
|
||||||
|
|
||||||
|
self.transaction_data = {"old_filepath": old_filepath, "filepath": self.filepath}
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
context.window_manager.fileselect_add(self)
|
context.window_manager.fileselect_add(self)
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
if data["old_filepath"]:
|
||||||
|
IfcStore.library_path = data["old_filepath"]
|
||||||
|
IfcStore.library_file = ifcopenshell.open(data["old_filepath"])
|
||||||
|
else:
|
||||||
|
IfcStore.library_path = ""
|
||||||
|
IfcStore.library_file = None
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
IfcStore.library_path = data["filepath"]
|
||||||
|
IfcStore.library_file = ifcopenshell.open(data["filepath"])
|
||||||
|
|
||||||
|
|
||||||
class RefreshLibrary(bpy.types.Operator):
|
class RefreshLibrary(bpy.types.Operator):
|
||||||
bl_idname = "bim.refresh_library"
|
bl_idname = "bim.refresh_library"
|
||||||
@@ -132,6 +166,7 @@ class RefreshLibrary(bpy.types.Operator):
|
|||||||
class ChangeLibraryElement(bpy.types.Operator):
|
class ChangeLibraryElement(bpy.types.Operator):
|
||||||
bl_idname = "bim.change_library_element"
|
bl_idname = "bim.change_library_element"
|
||||||
bl_label = "Change Library Element"
|
bl_label = "Change Library Element"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
element_name: bpy.props.StringProperty()
|
element_name: bpy.props.StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -163,6 +198,7 @@ class ChangeLibraryElement(bpy.types.Operator):
|
|||||||
class RewindLibrary(bpy.types.Operator):
|
class RewindLibrary(bpy.types.Operator):
|
||||||
bl_idname = "bim.rewind_library"
|
bl_idname = "bim.rewind_library"
|
||||||
bl_label = "Rewind Library"
|
bl_label = "Rewind Library"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.props = context.scene.BIMProjectProperties
|
self.props = context.scene.BIMProjectProperties
|
||||||
@@ -180,40 +216,62 @@ class RewindLibrary(bpy.types.Operator):
|
|||||||
class AssignLibraryDeclaration(bpy.types.Operator):
|
class AssignLibraryDeclaration(bpy.types.Operator):
|
||||||
bl_idname = "bim.assign_library_declaration"
|
bl_idname = "bim.assign_library_declaration"
|
||||||
bl_label = "Assign Library Declaration"
|
bl_label = "Assign Library Declaration"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
definition: bpy.props.IntProperty()
|
definition: bpy.props.IntProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.props = context.scene.BIMProjectProperties
|
self.props = context.scene.BIMProjectProperties
|
||||||
|
self.file = IfcStore.library_file
|
||||||
|
self.file.begin_transaction()
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"project.assign_declaration",
|
"project.assign_declaration",
|
||||||
IfcStore.library_file,
|
self.file,
|
||||||
definition=IfcStore.library_file.by_id(self.definition),
|
definition=self.file.by_id(self.definition),
|
||||||
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
|
relating_context=self.file.by_type("IfcProjectLibrary")[0],
|
||||||
)
|
)
|
||||||
|
self.file.end_transaction()
|
||||||
element_name = self.props.active_library_element
|
element_name = self.props.active_library_element
|
||||||
bpy.ops.bim.rewind_library()
|
bpy.ops.bim.rewind_library()
|
||||||
bpy.ops.bim.change_library_element(element_name = element_name)
|
bpy.ops.bim.change_library_element(element_name=element_name)
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
IfcStore.library_file.undo()
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
IfcStore.library_file.redo()
|
||||||
|
|
||||||
|
|
||||||
class UnassignLibraryDeclaration(bpy.types.Operator):
|
class UnassignLibraryDeclaration(bpy.types.Operator):
|
||||||
bl_idname = "bim.unassign_library_declaration"
|
bl_idname = "bim.unassign_library_declaration"
|
||||||
bl_label = "Unassign Library Declaration"
|
bl_label = "Unassign Library Declaration"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
definition: bpy.props.IntProperty()
|
definition: bpy.props.IntProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.props = context.scene.BIMProjectProperties
|
self.props = context.scene.BIMProjectProperties
|
||||||
|
self.file = IfcStore.library_file
|
||||||
|
self.file.begin_transaction()
|
||||||
ifcopenshell.api.run(
|
ifcopenshell.api.run(
|
||||||
"project.unassign_declaration",
|
"project.unassign_declaration",
|
||||||
IfcStore.library_file,
|
self.file,
|
||||||
definition=IfcStore.library_file.by_id(self.definition),
|
definition=self.file.by_id(self.definition),
|
||||||
relating_context=IfcStore.library_file.by_type("IfcProjectLibrary")[0],
|
relating_context=self.file.by_type("IfcProjectLibrary")[0],
|
||||||
)
|
)
|
||||||
|
self.file.end_transaction()
|
||||||
element_name = self.props.active_library_element
|
element_name = self.props.active_library_element
|
||||||
bpy.ops.bim.rewind_library()
|
bpy.ops.bim.rewind_library()
|
||||||
bpy.ops.bim.change_library_element(element_name = element_name)
|
bpy.ops.bim.change_library_element(element_name=element_name)
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
IfcStore.library_file.undo()
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
IfcStore.library_file.redo()
|
||||||
|
|
||||||
|
|
||||||
class SaveLibraryFile(bpy.types.Operator):
|
class SaveLibraryFile(bpy.types.Operator):
|
||||||
bl_idname = "bim.save_library_file"
|
bl_idname = "bim.save_library_file"
|
||||||
@@ -227,17 +285,22 @@ class SaveLibraryFile(bpy.types.Operator):
|
|||||||
class AppendLibraryElement(bpy.types.Operator):
|
class AppendLibraryElement(bpy.types.Operator):
|
||||||
bl_idname = "bim.append_library_element"
|
bl_idname = "bim.append_library_element"
|
||||||
bl_label = "Append Library Element"
|
bl_label = "Append Library Element"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
definition: bpy.props.IntProperty()
|
definition: bpy.props.IntProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
self.file = IfcStore.get_file()
|
||||||
|
self.file.begin_transaction()
|
||||||
element = ifcopenshell.api.run(
|
element = ifcopenshell.api.run(
|
||||||
"project.append_asset",
|
"project.append_asset",
|
||||||
IfcStore.get_file(),
|
self.file,
|
||||||
library=IfcStore.library_file,
|
library=IfcStore.library_file,
|
||||||
element=IfcStore.library_file.by_id(self.definition),
|
element=IfcStore.library_file.by_id(self.definition),
|
||||||
)
|
)
|
||||||
|
self.file.end_transaction()
|
||||||
self.import_type_from_ifc(element)
|
self.import_type_from_ifc(element)
|
||||||
blenderbim.bim.handler.purge_module_data()
|
blenderbim.bim.handler.purge_module_data()
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def import_type_from_ifc(self, element):
|
def import_type_from_ifc(self, element):
|
||||||
@@ -259,10 +322,17 @@ class AppendLibraryElement(bpy.types.Operator):
|
|||||||
ifc_importer.create_type_product(element)
|
ifc_importer.create_type_product(element)
|
||||||
ifc_importer.place_objects_in_spatial_tree()
|
ifc_importer.place_objects_in_spatial_tree()
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
IfcStore.get_file().undo()
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
IfcStore.get_file().redo()
|
||||||
|
|
||||||
|
|
||||||
class EnableEditingHeader(bpy.types.Operator):
|
class EnableEditingHeader(bpy.types.Operator):
|
||||||
bl_idname = "bim.enable_editing_header"
|
bl_idname = "bim.enable_editing_header"
|
||||||
bl_label = "Enable Editing Header"
|
bl_label = "Enable Editing Header"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
@@ -294,23 +364,53 @@ class EnableEditingHeader(bpy.types.Operator):
|
|||||||
class EditHeader(bpy.types.Operator):
|
class EditHeader(bpy.types.Operator):
|
||||||
bl_idname = "bim.edit_header"
|
bl_idname = "bim.edit_header"
|
||||||
bl_label = "Edit Header"
|
bl_label = "Edit Header"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
props = context.scene.BIMProjectProperties
|
props = context.scene.BIMProjectProperties
|
||||||
props.is_editing = True
|
props.is_editing = True
|
||||||
|
|
||||||
self.file.wrapped_data.header.file_description.description = (f'ViewDefinition[{props.mvd}]',)
|
self.transaction_data = {}
|
||||||
|
self.transaction_data["old"] = self.record_state()
|
||||||
|
|
||||||
|
self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
|
||||||
self.file.wrapped_data.header.file_name.author = (props.author_name, props.author_email)
|
self.file.wrapped_data.header.file_name.author = (props.author_name, props.author_email)
|
||||||
self.file.wrapped_data.header.file_name.organization = (props.organisation_name, props.organisation_email)
|
self.file.wrapped_data.header.file_name.organization = (props.organisation_name, props.organisation_email)
|
||||||
self.file.wrapped_data.header.file_name.authorization = props.authorisation
|
self.file.wrapped_data.header.file_name.authorization = props.authorisation
|
||||||
bpy.ops.bim.disable_editing_header()
|
bpy.ops.bim.disable_editing_header()
|
||||||
|
|
||||||
|
self.transaction_data["new"] = self.record_state()
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def record_state(self):
|
||||||
|
return {
|
||||||
|
"description": self.file.wrapped_data.header.file_description.description,
|
||||||
|
"author": self.file.wrapped_data.header.file_name.author,
|
||||||
|
"organisation": self.file.wrapped_data.header.file_name.organization,
|
||||||
|
"authorisation": self.file.wrapped_data.header.file_name.authorization,
|
||||||
|
}
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
file.wrapped_data.header.file_description.description = data["old"]["description"]
|
||||||
|
file.wrapped_data.header.file_name.author = data["old"]["author"]
|
||||||
|
file.wrapped_data.header.file_name.organization = data["old"]["organisation"]
|
||||||
|
file.wrapped_data.header.file_name.authorization = data["old"]["authorisation"]
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
file = IfcStore.get_file()
|
||||||
|
file.wrapped_data.header.file_description.description = data["new"]["description"]
|
||||||
|
file.wrapped_data.header.file_name.author = data["new"]["author"]
|
||||||
|
file.wrapped_data.header.file_name.organization = data["new"]["organisation"]
|
||||||
|
file.wrapped_data.header.file_name.authorization = data["new"]["authorisation"]
|
||||||
|
|
||||||
|
|
||||||
class DisableEditingHeader(bpy.types.Operator):
|
class DisableEditingHeader(bpy.types.Operator):
|
||||||
bl_idname = "bim.disable_editing_header"
|
bl_idname = "bim.disable_editing_header"
|
||||||
bl_label = "Disable Editing Header"
|
bl_label = "Disable Editing Header"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
context.scene.BIMProjectProperties.is_editing = False
|
context.scene.BIMProjectProperties.is_editing = False
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ class AssignClass(bpy.types.Operator):
|
|||||||
bl_idname = "bim.assign_class"
|
bl_idname = "bim.assign_class"
|
||||||
bl_label = "Assign IFC Class"
|
bl_label = "Assign IFC Class"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
transaction_key: bpy.props.StringProperty()
|
||||||
obj: bpy.props.StringProperty()
|
obj: bpy.props.StringProperty()
|
||||||
ifc_class: bpy.props.StringProperty()
|
ifc_class: bpy.props.StringProperty()
|
||||||
predefined_type: bpy.props.StringProperty()
|
predefined_type: bpy.props.StringProperty()
|
||||||
@@ -87,6 +88,7 @@ class AssignClass(bpy.types.Operator):
|
|||||||
ifc_representation_class: bpy.props.StringProperty()
|
ifc_representation_class: bpy.props.StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
self.transaction_data = []
|
||||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
|
||||||
self.file = IfcStore.get_file()
|
self.file = IfcStore.get_file()
|
||||||
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
|
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
|
||||||
@@ -96,11 +98,13 @@ class AssignClass(bpy.types.Operator):
|
|||||||
predefined_type = None
|
predefined_type = None
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
self.assign_class(context, obj)
|
self.assign_class(context, obj)
|
||||||
|
IfcStore.add_transaction(self)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def assign_class(self, context, obj):
|
def assign_class(self, context, obj):
|
||||||
if obj.BIMObjectProperties.ifc_definition_id:
|
if obj.BIMObjectProperties.ifc_definition_id:
|
||||||
return
|
return
|
||||||
|
self.file.begin_transaction()
|
||||||
product = ifcopenshell.api.run(
|
product = ifcopenshell.api.run(
|
||||||
"root.create_entity",
|
"root.create_entity",
|
||||||
self.file,
|
self.file,
|
||||||
@@ -110,8 +114,10 @@ class AssignClass(bpy.types.Operator):
|
|||||||
"name": obj.name,
|
"name": obj.name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
self.file.end_transaction()
|
||||||
obj.name = "{}/{}".format(product.is_a(), obj.name)
|
obj.name = "{}/{}".format(product.is_a(), obj.name)
|
||||||
IfcStore.link_element(product, obj)
|
IfcStore.link_element(product, obj)
|
||||||
|
self.transaction_data.append({"element": product.id(), "obj": obj.name})
|
||||||
|
|
||||||
if self.should_add_representation:
|
if self.should_add_representation:
|
||||||
bpy.ops.bim.add_representation(
|
bpy.ops.bim.add_representation(
|
||||||
@@ -171,6 +177,20 @@ class AssignClass(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def rollback(self, data):
|
||||||
|
for linked_element in data:
|
||||||
|
IfcStore.unlink_element(
|
||||||
|
IfcStore.get_file().by_id(linked_element["element"]), bpy.data.objects.get(linked_element["obj"])
|
||||||
|
)
|
||||||
|
IfcStore.get_file().undo()
|
||||||
|
|
||||||
|
def commit(self, data):
|
||||||
|
IfcStore.get_file().redo()
|
||||||
|
for linked_element in data:
|
||||||
|
IfcStore.link_element(
|
||||||
|
IfcStore.get_file().by_id(linked_element["element"]), bpy.data.objects.get(linked_element["obj"])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UnassignClass(bpy.types.Operator):
|
class UnassignClass(bpy.types.Operator):
|
||||||
bl_idname = "bim.unassign_class"
|
bl_idname = "bim.unassign_class"
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
|||||||
|
|
||||||
class BIMTaskTreeProperties(PropertyGroup):
|
class BIMTaskTreeProperties(PropertyGroup):
|
||||||
# This belongs by itself for performance reasons. https://developer.blender.org/T87737
|
# This belongs by itself for performance reasons. https://developer.blender.org/T87737
|
||||||
# In Blender if you add thousands of tasks it makes other property access in the same group really slow.
|
# In Blender if you add many collection items it makes other property access in the same group really slow.
|
||||||
tasks: CollectionProperty(name="Tasks", type=Task)
|
tasks: CollectionProperty(name="Tasks", type=Task)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -174,9 +174,8 @@ class BIMProperties(PropertyGroup):
|
|||||||
default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory", update=updateDataDir
|
default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory", update=updateDataDir
|
||||||
)
|
)
|
||||||
ifc_file: StringProperty(name="IFC File", update=updateIfcFile)
|
ifc_file: StringProperty(name="IFC File", update=updateIfcFile)
|
||||||
id_map: StringProperty(name="ID Map")
|
|
||||||
guid_map: StringProperty(name="GUID Map")
|
|
||||||
export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema")
|
export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema")
|
||||||
|
last_transaction: StringProperty(name="Last Transaction")
|
||||||
contexts: EnumProperty(items=getContexts, name="Contexts")
|
contexts: EnumProperty(items=getContexts, name="Contexts")
|
||||||
available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts")
|
available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts")
|
||||||
available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts")
|
available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts")
|
||||||
|
|||||||
@@ -1,91 +1,219 @@
|
|||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
BlenderBIM is packaged as a Blender add-on, so installation is the same as any
|
There are different methods of installation, depending on your situation.
|
||||||
other Blender add-on. The full instructions for end-user installation is
|
|
||||||
available at the `Get BlenderBIM <https://blenderbim.org/download.html>`_
|
1. **Packaged installation** is recommended for regular users.
|
||||||
website.
|
2. **Daily build installation** is recommended for power users helping with testing.
|
||||||
|
3. **Unpackaged installation** is recommended for package managers.
|
||||||
|
4. **Source installation** is recommended for developers.
|
||||||
|
|
||||||
|
Packaged installation
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The BlenderBIM Add-on is packaged like a regular Blender add-on, so installation
|
||||||
|
is the same as any other Blender add-on. The full instructions for end-user
|
||||||
|
installation is available at the `Get BlenderBIM
|
||||||
|
<https://blenderbim.org/download.html>`__ website. The latest release is
|
||||||
|
typically updated every few weeks.
|
||||||
|
|
||||||
If you downloaded Blender as a ``.zip`` file without running an installer, you
|
If you downloaded Blender as a ``.zip`` file without running an installer, you
|
||||||
will find the BlenderBIM plug-in installed in:
|
will find the BlenderBIM plug-in installed in the following directory, where
|
||||||
|
``2.XX`` is the Blender version:
|
||||||
::
|
::
|
||||||
|
|
||||||
/path/to/blender/2.81/scripts/addons/
|
/path/to/blender/2.XX/scripts/addons/
|
||||||
|
|
||||||
Otherwise, if you installed Blender using an installation package, the add-ons
|
Otherwise, if you installed Blender using an installation package, the add-ons
|
||||||
folder depends on which operating system you use. On Linux:
|
folder depends on which operating system you use. On Linux:
|
||||||
::
|
::
|
||||||
|
|
||||||
~/.config/blender/2.81/scripts/addons/
|
~/.config/blender/2.XX/scripts/addons/
|
||||||
|
|
||||||
On Mac:
|
On Mac:
|
||||||
::
|
::
|
||||||
|
|
||||||
/Users/{YOUR_USER}/Library/Application Support/Blender/2.81/
|
/Users/{YOUR_USER}/Library/Application Support/Blender/2.XX/
|
||||||
|
|
||||||
On Windows:
|
On Windows:
|
||||||
::
|
::
|
||||||
|
|
||||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\2.81\scripts\addons
|
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\2.XX\scripts\addons
|
||||||
|
|
||||||
Upon installation, a series of files will be created. This is necessary as
|
Upon installation, the BlenderBIM Add-on is stored in the ``blenderbim/``
|
||||||
BlenderBIM has a variety of complex dependencies. A full list is below:
|
directory.
|
||||||
|
|
||||||
|
Daily build installation
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Daily builds are almost the same as **Packaged installation**, except that they
|
||||||
|
are typically updated every day. Simply download a daily build from the `Github
|
||||||
|
releases page <https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then
|
||||||
|
follow the same instructions as a packaged installation.
|
||||||
|
|
||||||
|
You will need to choose which daily build to download.
|
||||||
|
|
||||||
|
- If you are on Blender <2.93, choose py37
|
||||||
|
- If you are on Blender >=2.93, choose py39
|
||||||
|
- Choose linux, macos, or win depending on your operating system
|
||||||
|
|
||||||
|
Daily builds are not always stable. Sometimes, a build may be delayed, or
|
||||||
|
contain broken code. We try to avoid this, but it happens.
|
||||||
|
|
||||||
|
Unpackaged installation
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The BlenderBIM Add-on is fully contained in the ``blenderbim/`` subfolder of the
|
||||||
|
Blender add-ons directory. This is typically distributed as a zipfile as per
|
||||||
|
Blender add-on conventions. Within this folder, you'll find the following file
|
||||||
|
structure:
|
||||||
::
|
::
|
||||||
|
|
||||||
blenderbim/
|
bim/ (core code)
|
||||||
ifcopenshell/
|
libs/ (dependencies)
|
||||||
OCC/
|
__init__.py
|
||||||
pystache/
|
|
||||||
svgwrite/
|
|
||||||
deepdiff/
|
|
||||||
jsonpickle/
|
|
||||||
lib/ # Note: this only exists on MacOS and Linux
|
|
||||||
ordered_set.py
|
|
||||||
pyparsing.py
|
|
||||||
|
|
||||||
If you are not on Windows, when BlenderBIM first launches, it will create a
|
This corresponds to the structure found in the source code `here
|
||||||
bunch of library files in the ``2.81/`` folder too. This is a non-standard
|
<https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/blenderbim/blenderbim>`__.
|
||||||
location to place files, but is a hack to allow people to use precompiled builds
|
|
||||||
from Conda.
|
The BlenderBIM Add-on is complex, and requires many dependencies, including
|
||||||
|
Python modules, binaries, and static assets. These dependencies are bundled with
|
||||||
|
the add-on for convenience in the **Packaged installation** and **Daily build
|
||||||
|
installation** methods.
|
||||||
|
|
||||||
|
If you choose to install the BlenderBIM Add-on and use your own system
|
||||||
|
dependencies, the source of truth for how dependencies are bundled are found in
|
||||||
|
the `Makefile
|
||||||
|
<https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.6.0/src/blenderbim/Makefile>`__.
|
||||||
|
|
||||||
|
Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||||
|
::
|
||||||
|
|
||||||
|
ifcopenshell
|
||||||
|
bcf
|
||||||
|
ifcclash
|
||||||
|
bimtester
|
||||||
|
ifccobie
|
||||||
|
ifcdiff
|
||||||
|
ifccsv
|
||||||
|
ifcpatch
|
||||||
|
ifcp6
|
||||||
|
pystache
|
||||||
|
svgwrite
|
||||||
|
dateutil
|
||||||
|
isodate
|
||||||
|
networkx
|
||||||
|
deepdiff
|
||||||
|
jsonpickle
|
||||||
|
ordered_set
|
||||||
|
pyparsing
|
||||||
|
xmlschema
|
||||||
|
elementpath
|
||||||
|
six
|
||||||
|
lark-parser
|
||||||
|
fcl
|
||||||
|
behave
|
||||||
|
parse
|
||||||
|
parse_type
|
||||||
|
xlsxwriter
|
||||||
|
odfpy
|
||||||
|
defusedxml
|
||||||
|
boto3
|
||||||
|
botocore
|
||||||
|
jmespath
|
||||||
|
s3transfer
|
||||||
|
ifcjson
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1. ``ifcopenshell`` almost always requires the latest version due to the fast paced nature of the add-on development.
|
||||||
|
2. ``fcl`` is not bundled for MacOS, due to lack of maintained community build. This is required for clash detection.
|
||||||
|
3. ``behave`` requires `patches <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.6.0/src/ifcbimtester/patch>`__.
|
||||||
|
4. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
|
||||||
|
|
||||||
|
Required binaries are:
|
||||||
|
::
|
||||||
|
|
||||||
|
libs/IfcConvert
|
||||||
|
|
||||||
|
Required static assets are:
|
||||||
|
::
|
||||||
|
|
||||||
|
bim/data/gantt/jsgantt.js (from jsgantt-improved)
|
||||||
|
bim/data/gantt/jsgantt.css (from jsgantt-improved)
|
||||||
|
|
||||||
If you receive an error when enabling the add-on, you may have installed the
|
If you receive an error when enabling the add-on, you may have installed the
|
||||||
package for the wrong platform.
|
package for the wrong platform.
|
||||||
|
|
||||||
Updating
|
Source installation
|
||||||
--------
|
-------------------
|
||||||
|
|
||||||
It is recommended to uninstall the current BlenderBIM add-on before installing
|
|
||||||
the latest version to ensure the update goes well.
|
|
||||||
|
|
||||||
From Source
|
|
||||||
-----------
|
|
||||||
|
|
||||||
It is possible to run the latest bleeding edge version of BlenderBIM without
|
It is possible to run the latest bleeding edge version of BlenderBIM without
|
||||||
having to wait for an official release, since BlenderBIM is coded in Python and
|
having to wait for an official release, since BlenderBIM is coded in Python and
|
||||||
doesn't require any compilation. First, install the latest official release, and
|
doesn't require any compilation.
|
||||||
then `download the latest source code
|
|
||||||
<https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.6.0.zip>`_. If you know
|
You can create your own package by using the Makefile as shown below. You can
|
||||||
how to use Git, you can also stay up to date like so:
|
choose between a ``PLATFORM`` of ``linux``, ``macos``, and ``win``. You can
|
||||||
|
choose between a ``PYVERSION`` of ``py39`` and ``py37``.
|
||||||
|
::
|
||||||
|
|
||||||
|
$ cd src/blenderbim
|
||||||
|
$ make dist PLATFORM=linux PYVERSION=py39
|
||||||
|
$ ls dist/
|
||||||
|
|
||||||
|
However, creating a build, uninstalling the old add-on, and installing a new
|
||||||
|
build is a slow process. A more rapid approach is to follow the **Daily build
|
||||||
|
installation** method, as this provides all dependencies for you out of the box.
|
||||||
|
Then, we can replace certain Python files that tend to be updated frequently
|
||||||
|
with those from the Git repository. We're going to use symlinks (Windows user
|
||||||
|
can use ``mklink``), so we can code in our Git repository, and see the changes
|
||||||
|
in our Blender installation.
|
||||||
|
|
||||||
|
In addition, we're also going to replace the Python code of the IfcOpenShell
|
||||||
|
dependency with our Git repository, since most of the BlenderBIM Add-on
|
||||||
|
functionality is agnostic of Blender, and is actually part of IfcOpenShell.
|
||||||
|
Therefore, we need to keep this dependency highly updated as well.
|
||||||
|
|
||||||
|
The downside with this approach is that if a new dependency is added, or a
|
||||||
|
compiled dependency version requirement has changed, or the build system
|
||||||
|
changes, you'll need to fix your setup manually. But this is relatively rare.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
$ git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||||
$ cd IfcOpenShell
|
|
||||||
$ git checkout v0.6.0
|
|
||||||
|
|
||||||
Then, just copy the files from the source code's
|
# Remove the Blender add-on Python code
|
||||||
``src/blenderbim/blenderbim/`` folder and replace the files in your
|
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/bim/
|
||||||
Blender add-on's ``blenderbim/`` folder.
|
|
||||||
|
|
||||||
Restart Blender for the changes to take effect. In ``Edit > Preferences >
|
# Replace them with links to the Git repository
|
||||||
Add-ons`` you will see that the version number of BlenderBIM has changed to
|
$ ln -s src/blenderbim/blenderbim/bim /path/to/blender/2.XX/scripts/addons/blenderbim/bim
|
||||||
``0.0.999999``, which represents an un-versioned BlenderBIM.
|
|
||||||
|
# Remove the IfcOpenShell dependency Python code
|
||||||
|
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||||
|
$ rm -r /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||||
|
|
||||||
|
# Replace them with links to the Git repository
|
||||||
|
$ ln -s src/ifcopenshell-python/ifcopenshell/api /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/api
|
||||||
|
$ ln -s src/ifcopenshell-python/ifcopenshell/util /path/to/blender/2.XX/scripts/addons/blenderbim/libs/site/packages/ifcopenshell/util
|
||||||
|
|
||||||
|
After you modify your code in the Git repository, you will need to restart
|
||||||
|
Blender for the changes to take effect. In ``Edit > Preferences > Add-ons`` you
|
||||||
|
will see that the version number of BlenderBIM has changed to ``0.0.999999``,
|
||||||
|
which represents an un-versioned BlenderBIM.
|
||||||
|
|
||||||
|
Updating
|
||||||
|
--------
|
||||||
|
|
||||||
|
First uninstall the current BlenderBIM add-on, then install the latest version.
|
||||||
|
|
||||||
Uninstalling
|
Uninstalling
|
||||||
------------
|
------------
|
||||||
|
|
||||||
You can remove all of the files added by BlenderBIM in the Blender add-ons
|
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||||
folder and then remove the add-on using the Blender interface through ``Edit >
|
you have to first disable the BlenderBIM Add-on in your Blender preferences by
|
||||||
Preferences > Add-ons`` just like any other add-on.
|
pressing the checkbox next to the add-on, then restart Blender. After
|
||||||
|
restarting, you can uninstall the BlenderBIM Add-on by pressing the ``Remove``
|
||||||
|
button in the Blender preferences window.
|
||||||
|
|
||||||
If you are not on Windows, then ensure that all library files are deleted in the
|
Alternatively, you may uninstall manually by deleting the ``blenderbim/``
|
||||||
``2.81/`` directory. Do not delete any of Blender's own folders.
|
directory in your Blender add-ons directory.
|
||||||
|
|||||||
@@ -48,20 +48,21 @@ class entity_instance(object):
|
|||||||
>>> #423=IfcProductDefinitionShape($,$,(#409,#421))
|
>>> #423=IfcProductDefinitionShape($,$,(#409,#421))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, e):
|
def __init__(self, e, file):
|
||||||
if isinstance(e, tuple):
|
if isinstance(e, tuple):
|
||||||
e = ifcopenshell_wrapper.new_IfcBaseClass(*e)
|
e = ifcopenshell_wrapper.new_IfcBaseClass(*e)
|
||||||
super(entity_instance, self).__setattr__("wrapped_data", e)
|
super(entity_instance, self).__setattr__("wrapped_data", e)
|
||||||
|
self.wrapped_data.file = file
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
INVALID, FORWARD, INVERSE = range(3)
|
INVALID, FORWARD, INVERSE = range(3)
|
||||||
attr_cat = self.wrapped_data.get_attribute_category(name)
|
attr_cat = self.wrapped_data.get_attribute_category(name)
|
||||||
if attr_cat == FORWARD:
|
if attr_cat == FORWARD:
|
||||||
return entity_instance.wrap_value(
|
return entity_instance.wrap_value(
|
||||||
self.wrapped_data.get_argument(self.wrapped_data.get_argument_index(name))
|
self.wrapped_data.get_argument(self.wrapped_data.get_argument_index(name)), self.wrapped_data.file
|
||||||
)
|
)
|
||||||
elif attr_cat == INVERSE:
|
elif attr_cat == INVERSE:
|
||||||
return entity_instance.wrap_value(self.wrapped_data.get_inverse(name))
|
return entity_instance.wrap_value(self.wrapped_data.get_inverse(name), self.wrapped_data.file)
|
||||||
else:
|
else:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(), name)
|
"entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(), name)
|
||||||
@@ -77,9 +78,9 @@ class entity_instance(object):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def wrap_value(v):
|
def wrap_value(v, file):
|
||||||
def wrap(e):
|
def wrap(e):
|
||||||
return entity_instance(e)
|
return entity_instance(e, file)
|
||||||
|
|
||||||
def is_instance(e):
|
def is_instance(e):
|
||||||
return isinstance(e, ifcopenshell_wrapper.entity_instance)
|
return isinstance(e, ifcopenshell_wrapper.entity_instance)
|
||||||
@@ -116,12 +117,15 @@ class entity_instance(object):
|
|||||||
return self.wrapped_data.get_argument_name(attr_idx)
|
return self.wrapped_data.get_argument_name(attr_idx)
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
self[self.wrapped_data.get_argument_index(key)] = value
|
index = self.wrapped_data.get_argument_index(key)
|
||||||
|
if self.wrapped_data.file.transaction:
|
||||||
|
self.wrapped_data.file.transaction.store_edit(self, index, value)
|
||||||
|
self[index] = value
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if key < 0 or key >= len(self):
|
if key < 0 or key >= len(self):
|
||||||
raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a()))
|
raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a()))
|
||||||
return entity_instance.wrap_value(self.wrapped_data.get_argument(key))
|
return entity_instance.wrap_value(self.wrapped_data.get_argument(key), self.wrapped_data.file)
|
||||||
|
|
||||||
def __setitem__(self, idx, value):
|
def __setitem__(self, idx, value):
|
||||||
attr_type = real_attr_type = self.attribute_type(idx).title().replace(" ", "")
|
attr_type = real_attr_type = self.attribute_type(idx).title().replace(" ", "")
|
||||||
@@ -271,7 +275,7 @@ class entity_instance(object):
|
|||||||
return return_type(_())
|
return return_type(_())
|
||||||
|
|
||||||
__dict__ = property(get_info)
|
__dict__ = property(get_info)
|
||||||
|
|
||||||
def get_info_2(self, include_identifier=True, recursive=False, return_type=dict, ignore=()):
|
def get_info_2(self, include_identifier=True, recursive=False, return_type=dict, ignore=()):
|
||||||
assert include_identifier
|
assert include_identifier
|
||||||
assert recursive
|
assert recursive
|
||||||
|
|||||||
@@ -35,6 +35,105 @@ except NameError:
|
|||||||
basestring = (str, bytes)
|
basestring = (str, bytes)
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction:
|
||||||
|
def __init__(self, ifc_file):
|
||||||
|
self.file = ifc_file
|
||||||
|
self.operations = []
|
||||||
|
|
||||||
|
def serialise_entity_instance(self, element):
|
||||||
|
info = element.get_info()
|
||||||
|
for key, value in info.items():
|
||||||
|
info[key] = self.serialise_value(element, value)
|
||||||
|
return info
|
||||||
|
|
||||||
|
def serialise_value(self, element, value):
|
||||||
|
return element.walk(lambda v: isinstance(v, entity_instance), lambda v: {"id": v.id()}, value)
|
||||||
|
|
||||||
|
def unserialise_value(self, element, value):
|
||||||
|
return element.walk(lambda v: isinstance(v, dict), lambda v: self.file.by_id(v["id"]), value)
|
||||||
|
|
||||||
|
def store_create(self, element):
|
||||||
|
self.operations.append({"action": "create", "value": self.serialise_entity_instance(element)})
|
||||||
|
|
||||||
|
def store_edit(self, element, index, value):
|
||||||
|
self.operations.append(
|
||||||
|
{
|
||||||
|
"action": "edit",
|
||||||
|
"id": element.id(),
|
||||||
|
"index": index,
|
||||||
|
"old": self.serialise_value(element, element[index]),
|
||||||
|
"new": self.serialise_value(element, value),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def store_delete(self, element):
|
||||||
|
inverses = {}
|
||||||
|
for inverse in self.file.get_inverse(element):
|
||||||
|
inverse_references = []
|
||||||
|
for i, attribute in enumerate(inverse):
|
||||||
|
if attribute == element:
|
||||||
|
inverse_references.append((i, "single"))
|
||||||
|
elif isinstance(attribute, tuple) and element in attribute:
|
||||||
|
inverse_references.append((i, "multiple"))
|
||||||
|
inverses[inverse.id()] = inverse_references
|
||||||
|
self.operations.append(
|
||||||
|
{"action": "delete", "inverses": inverses, "value": self.serialise_entity_instance(element)}
|
||||||
|
)
|
||||||
|
|
||||||
|
def rollback(self):
|
||||||
|
for operation in self.operations[::-1]:
|
||||||
|
if operation["action"] == "create":
|
||||||
|
element = self.file.by_id(operation["value"]["id"])
|
||||||
|
if hasattr(element, "GlobalId"):
|
||||||
|
# hack, otherwise ifcopenshell gets upset
|
||||||
|
element.GlobalId = "x"
|
||||||
|
self.file.remove(element)
|
||||||
|
elif operation["action"] == "edit":
|
||||||
|
element = self.file.by_id(operation["id"])
|
||||||
|
try:
|
||||||
|
element[operation["index"]] = self.unserialise_value(element, operation["old"])
|
||||||
|
except:
|
||||||
|
# Catch discrepancy where IfcOpenShell creates but doesn't allow editing of invalid values
|
||||||
|
pass
|
||||||
|
elif operation["action"] == "delete":
|
||||||
|
e = self.file.create_entity(operation["value"]["type"], id=operation["value"]["id"])
|
||||||
|
for k, v in operation["value"].items():
|
||||||
|
try:
|
||||||
|
setattr(e, k, self.unserialise_value(e, v))
|
||||||
|
except:
|
||||||
|
# Catch discrepancy where IfcOpenShell creates but doesn't allow editing of invalid values
|
||||||
|
pass
|
||||||
|
for inverse_id, data in operation["inverses"].items():
|
||||||
|
inverse = self.file.by_id(inverse_id)
|
||||||
|
for index, data_type in data:
|
||||||
|
if data_type == "single":
|
||||||
|
inverse[index] = e
|
||||||
|
elif data_type == "multiple":
|
||||||
|
if inverse[index] is None:
|
||||||
|
inverse[index] = e
|
||||||
|
else:
|
||||||
|
new = list(inverse[index])
|
||||||
|
new.append(e)
|
||||||
|
inverse[index] = new
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
for operation in self.operations:
|
||||||
|
if operation["action"] == "create":
|
||||||
|
e = self.file.create_entity(operation["value"]["type"], id=operation["value"]["id"])
|
||||||
|
for k, v in operation["value"].items():
|
||||||
|
try:
|
||||||
|
setattr(e, k, self.unserialise_value(e, v))
|
||||||
|
except:
|
||||||
|
# Catch discrepancy where IfcOpenShell creates but doesn't allow editing of invalid values
|
||||||
|
pass
|
||||||
|
elif operation["action"] == "edit":
|
||||||
|
element = self.file.by_id(operation["id"])
|
||||||
|
element[operation["index"]] = self.unserialise_value(element, operation["new"])
|
||||||
|
elif operation["action"] == "delete":
|
||||||
|
element = self.file.by_id(operation["value"]["id"])
|
||||||
|
self.file.remove(element)
|
||||||
|
|
||||||
|
|
||||||
class file(object):
|
class file(object):
|
||||||
"""Base class for containing IFC files.
|
"""Base class for containing IFC files.
|
||||||
|
|
||||||
@@ -59,6 +158,45 @@ class file(object):
|
|||||||
args = filter(None, [schema])
|
args = filter(None, [schema])
|
||||||
args = map(ifcopenshell_wrapper.schema_by_name, args)
|
args = map(ifcopenshell_wrapper.schema_by_name, args)
|
||||||
self.wrapped_data = ifcopenshell_wrapper.file(*args)
|
self.wrapped_data = ifcopenshell_wrapper.file(*args)
|
||||||
|
self.history_size = 64
|
||||||
|
self.history = []
|
||||||
|
self.future = []
|
||||||
|
self.transaction = None
|
||||||
|
|
||||||
|
def set_history_size(self, size):
|
||||||
|
self.history_size = size
|
||||||
|
while len(self.history) > self.history_size:
|
||||||
|
self.history.pop(0)
|
||||||
|
|
||||||
|
def begin_transaction(self):
|
||||||
|
self.transaction = Transaction(self)
|
||||||
|
|
||||||
|
def end_transaction(self):
|
||||||
|
if self.transaction:
|
||||||
|
self.history.append(self.transaction)
|
||||||
|
if len(self.history) > self.history_size:
|
||||||
|
self.history.pop(0)
|
||||||
|
self.future = []
|
||||||
|
self.transaction = None
|
||||||
|
|
||||||
|
def discard_transaction(self):
|
||||||
|
if self.transaction:
|
||||||
|
self.transaction.rollback()
|
||||||
|
self.transaction = None
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if not self.history:
|
||||||
|
return
|
||||||
|
transaction = self.history.pop()
|
||||||
|
transaction.rollback()
|
||||||
|
self.future.append(transaction)
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
if not self.future:
|
||||||
|
return
|
||||||
|
transaction = self.future.pop()
|
||||||
|
transaction.commit()
|
||||||
|
self.history.append(transaction)
|
||||||
|
|
||||||
def create_entity(self, type, *args, **kwargs):
|
def create_entity(self, type, *args, **kwargs):
|
||||||
"""Create a new IFC entity in the file.
|
"""Create a new IFC entity in the file.
|
||||||
@@ -82,14 +220,17 @@ class file(object):
|
|||||||
"""
|
"""
|
||||||
eid = -1
|
eid = -1
|
||||||
try:
|
try:
|
||||||
eid = kwargs.pop("_id", -1)
|
eid = kwargs.pop("id", -1)
|
||||||
except: pass
|
except:
|
||||||
e = entity_instance((self.schema, type))
|
pass
|
||||||
|
e = entity_instance((self.schema, type), self)
|
||||||
self.wrapped_data.add(e.wrapped_data, eid)
|
self.wrapped_data.add(e.wrapped_data, eid)
|
||||||
e.wrapped_data.this.disown()
|
e.wrapped_data.this.disown()
|
||||||
attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
|
attrs = list(enumerate(args)) + [(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
|
||||||
for idx, arg in attrs:
|
for idx, arg in attrs:
|
||||||
e[idx] = arg
|
e[idx] = arg
|
||||||
|
if self.transaction:
|
||||||
|
self.transaction.store_create(e)
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
@@ -100,9 +241,9 @@ class file(object):
|
|||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, numbers.Integral):
|
if isinstance(key, numbers.Integral):
|
||||||
return entity_instance(self.wrapped_data.by_id(key))
|
return entity_instance(self.wrapped_data.by_id(key), self)
|
||||||
elif isinstance(key, basestring):
|
elif isinstance(key, basestring):
|
||||||
return entity_instance(self.wrapped_data.by_guid(str(key)))
|
return entity_instance(self.wrapped_data.by_guid(str(key)), self)
|
||||||
|
|
||||||
def by_id(self, id):
|
def by_id(self, id):
|
||||||
"""Return an IFC entity instance filtered by IFC ID.
|
"""Return an IFC entity instance filtered by IFC ID.
|
||||||
@@ -129,7 +270,7 @@ class file(object):
|
|||||||
|
|
||||||
If the entity already exists, it is not re-added."""
|
If the entity already exists, it is not re-added."""
|
||||||
inst.wrapped_data.this.disown()
|
inst.wrapped_data.this.disown()
|
||||||
return entity_instance(self.wrapped_data.add(inst.wrapped_data, -1 if _id is None else _id))
|
return entity_instance(self.wrapped_data.add(inst.wrapped_data, -1 if _id is None else _id), self)
|
||||||
|
|
||||||
def by_type(self, type, include_subtypes=True):
|
def by_type(self, type, include_subtypes=True):
|
||||||
"""Return IFC objects filtered by IFC Type and wrapped with the entity_instance class.
|
"""Return IFC objects filtered by IFC Type and wrapped with the entity_instance class.
|
||||||
@@ -144,8 +285,8 @@ class file(object):
|
|||||||
:rtype: list
|
:rtype: list
|
||||||
"""
|
"""
|
||||||
if include_subtypes:
|
if include_subtypes:
|
||||||
return [entity_instance(e) for e in self.wrapped_data.by_type(type)]
|
return [entity_instance(e, self) for e in self.wrapped_data.by_type(type)]
|
||||||
return [entity_instance(e) for e in self.wrapped_data.by_type_excl_subtypes(type)]
|
return [entity_instance(e, self) for e in self.wrapped_data.by_type_excl_subtypes(type)]
|
||||||
|
|
||||||
def traverse(self, inst, max_levels=None):
|
def traverse(self, inst, max_levels=None):
|
||||||
"""Get a list of all referenced instances for a particular instance including itself
|
"""Get a list of all referenced instances for a particular instance including itself
|
||||||
@@ -159,7 +300,7 @@ class file(object):
|
|||||||
"""
|
"""
|
||||||
if max_levels is None:
|
if max_levels is None:
|
||||||
max_levels = -1
|
max_levels = -1
|
||||||
return [entity_instance(e) for e in self.wrapped_data.traverse(inst.wrapped_data, max_levels)]
|
return [entity_instance(e, self) for e in self.wrapped_data.traverse(inst.wrapped_data, max_levels)]
|
||||||
|
|
||||||
def get_inverse(self, inst):
|
def get_inverse(self, inst):
|
||||||
"""Return a list of entities that reference this entity
|
"""Return a list of entities that reference this entity
|
||||||
@@ -169,7 +310,7 @@ class file(object):
|
|||||||
:returns: A list of ifcopenshell.entity_instance.entity_instance objects
|
:returns: A list of ifcopenshell.entity_instance.entity_instance objects
|
||||||
:rtype: list
|
:rtype: list
|
||||||
"""
|
"""
|
||||||
return [entity_instance(e) for e in self.wrapped_data.get_inverse(inst.wrapped_data)]
|
return [entity_instance(e, self) for e in self.wrapped_data.get_inverse(inst.wrapped_data)]
|
||||||
|
|
||||||
def remove(self, inst):
|
def remove(self, inst):
|
||||||
"""Deletes an IFC object in the file.
|
"""Deletes an IFC object in the file.
|
||||||
@@ -182,12 +323,14 @@ class file(object):
|
|||||||
:type inst: ifcopenshell.entity_instance.entity_instance
|
:type inst: ifcopenshell.entity_instance.entity_instance
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
if self.transaction:
|
||||||
|
self.transaction.store_delete(inst)
|
||||||
return self.wrapped_data.remove(inst.wrapped_data)
|
return self.wrapped_data.remove(inst.wrapped_data)
|
||||||
|
|
||||||
def batch(self):
|
def batch(self):
|
||||||
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
||||||
return self.wrapped_data.batch()
|
return self.wrapped_data.batch()
|
||||||
|
|
||||||
def unbatch(self):
|
def unbatch(self):
|
||||||
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
||||||
return self.wrapped_data.unbatch()
|
return self.wrapped_data.unbatch()
|
||||||
|
|||||||
Reference in New Issue
Block a user