mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 10:57:49 +00:00
Simple support for API listeners for parametric IFC authoring - editing any parameter can now trigger events from a "parametric engine" which synchronises other related geometry and attributes in the IFC dataset. Implement auto box generation as a first demo.
This commit is contained in:
@@ -10,6 +10,7 @@ if bpy is not None:
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
"parametric": None,
|
||||
"search": None,
|
||||
"bcf": None,
|
||||
"root": None,
|
||||
|
||||
@@ -14,14 +14,6 @@ from ifcopenshell.api.void.data import Data as VoidData
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def get_context_id(context_type, context_identifier, target_view):
|
||||
for context in ContextData.contexts.values():
|
||||
if context["ContextType"] == context_type:
|
||||
for i, subcontext in context["HasSubContexts"].items():
|
||||
if subcontext["ContextIdentifier"] == context_identifier and subcontext["TargetView"] == target_view:
|
||||
return i
|
||||
|
||||
|
||||
class EditObjectPlacement(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_object_placement"
|
||||
bl_label = "Edit Object Placement"
|
||||
@@ -107,23 +99,6 @@ class AddRepresentation(bpy.types.Operator):
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
|
||||
# TODO: Migrate to parametric engine system
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
if old_box:
|
||||
bpy.ops.bim.remove_representation(representation_id=old_box.id(), obj=obj.name)
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, **{"product": product, "representation": new_box}
|
||||
)
|
||||
|
||||
[
|
||||
bpy.ops.bim.add_style(material=s.material.name)
|
||||
for s in obj.material_slots
|
||||
@@ -326,20 +301,6 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
|
||||
box_context_id = get_context_id("Model", "Box", "MODEL_VIEW")
|
||||
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if (
|
||||
box_context_id
|
||||
and old_box
|
||||
and context_of_items.ContextType == "Model"
|
||||
and context_of_items.ContextIdentifier
|
||||
and context_of_items.ContextIdentifier == "Body"
|
||||
):
|
||||
representation_data["context"] = self.file.by_id(box_context_id)
|
||||
new_box = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
for inverse in self.file.get_inverse(old_box):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_box, new_box)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_styles",
|
||||
self.file,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import bpy
|
||||
from . import ui, operator
|
||||
|
||||
classes = (
|
||||
operator.ActivateParametricEngine,
|
||||
ui.BIM_PT_parametric,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
@@ -0,0 +1,38 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.representation
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def generate_box(usecase_path, ifc_file, **settings):
|
||||
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
|
||||
if not box_context:
|
||||
return
|
||||
obj = settings["blender_object"]
|
||||
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
|
||||
if old_box:
|
||||
bpy.ops.bim.remove_representation(representation_id=old_box.id(), obj=obj.name)
|
||||
|
||||
new_settings = settings.copy()
|
||||
new_settings["context"] = box_context
|
||||
new_box = ifcopenshell.api.run(
|
||||
"geometry.add_representation", ifc_file, should_run_listeners=False, **new_settings
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation",
|
||||
ifc_file,
|
||||
should_run_listeners=False,
|
||||
**{"product": product, "representation": new_box}
|
||||
)
|
||||
|
||||
|
||||
class ActivateParametricEngine(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_parametric_engine"
|
||||
bl_label = "Activate Parametric Engine"
|
||||
|
||||
def execute(self, context):
|
||||
ifcopenshell.api.add_post_listener("geometry.add_representation", IfcStore.get_file(), generate_box)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,22 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.type.data import Data
|
||||
|
||||
|
||||
class BIM_PT_parametric(Panel):
|
||||
bl_label = "IFC Parametric Engines"
|
||||
bl_idname = "BIM_PT_parametric"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
#props = context.active_object.BIMTypeProperties
|
||||
row = self.layout.row(align=True)
|
||||
#row.prop(props, "relating_type_class", text="")
|
||||
row.operator("bim.activate_parametric_engine", icon="PLUGIN")
|
||||
@@ -1,11 +1,38 @@
|
||||
import importlib
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
def run(usecase_path, ifc_file=None, **settings):
|
||||
registered_ifcs = {}
|
||||
pre_listeners = {}
|
||||
post_listeners = {}
|
||||
|
||||
|
||||
def run(usecase_path, ifc_file=None, should_run_listeners=True, **settings):
|
||||
ifc_key = registered_ifcs.setdefault(ifc_file, ifcopenshell.guid.new())
|
||||
|
||||
if should_run_listeners:
|
||||
for listener in pre_listeners.get(".".join([ifc_key, usecase_path]), []):
|
||||
listener(usecase_path, ifc_file, **settings)
|
||||
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||
module, usecase = usecase_path.split(".")
|
||||
usecase_class = getattr(getattr(getattr(ifcopenshell.api, module), usecase), "Usecase")
|
||||
|
||||
if ifc_file:
|
||||
return usecase_class(ifc_file, **settings).execute()
|
||||
return usecase_class(**settings).execute()
|
||||
result = usecase_class(ifc_file, **settings).execute()
|
||||
else:
|
||||
result = usecase_class(**settings).execute()
|
||||
|
||||
if should_run_listeners:
|
||||
for listener in post_listeners.get(".".join([ifc_key, usecase_path]), []):
|
||||
listener(usecase_path, ifc_file, **settings)
|
||||
return result
|
||||
|
||||
|
||||
def add_pre_listener(usecase_path, ifc_file, callback):
|
||||
ifc_key = registered_ifcs.setdefault(ifc_file, ifcopenshell.guid.new())
|
||||
pre_listeners.setdefault(".".join([ifc_key, usecase_path]), set()).add(callback)
|
||||
|
||||
def add_post_listener(usecase_path, ifc_file, callback):
|
||||
ifc_key = registered_ifcs.setdefault(ifc_file, ifcopenshell.guid.new())
|
||||
post_listeners.setdefault(".".join([ifc_key, usecase_path]), set()).add(callback)
|
||||
|
||||
Reference in New Issue
Block a user