mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-29 19:51:00 +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 = {
|
modules = {
|
||||||
"project": None,
|
"project": None,
|
||||||
|
"parametric": None,
|
||||||
"search": None,
|
"search": None,
|
||||||
"bcf": None,
|
"bcf": None,
|
||||||
"root": None,
|
"root": None,
|
||||||
|
|||||||
@@ -14,14 +14,6 @@ from ifcopenshell.api.void.data import Data as VoidData
|
|||||||
from mathutils import Vector
|
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):
|
class EditObjectPlacement(bpy.types.Operator):
|
||||||
bl_idname = "bim.edit_object_placement"
|
bl_idname = "bim.edit_object_placement"
|
||||||
bl_label = "Edit Object Placement"
|
bl_label = "Edit Object Placement"
|
||||||
@@ -107,23 +99,6 @@ class AddRepresentation(bpy.types.Operator):
|
|||||||
print("Failed to write shape representation")
|
print("Failed to write shape representation")
|
||||||
return {"FINISHED"}
|
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)
|
bpy.ops.bim.add_style(material=s.material.name)
|
||||||
for s in obj.material_slots
|
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)
|
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(
|
ifcopenshell.api.run(
|
||||||
"geometry.assign_styles",
|
"geometry.assign_styles",
|
||||||
self.file,
|
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 importlib
|
||||||
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
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}")
|
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||||
module, usecase = usecase_path.split(".")
|
module, usecase = usecase_path.split(".")
|
||||||
usecase_class = getattr(getattr(getattr(ifcopenshell.api, module), usecase), "Usecase")
|
usecase_class = getattr(getattr(getattr(ifcopenshell.api, module), usecase), "Usecase")
|
||||||
|
|
||||||
if ifc_file:
|
if ifc_file:
|
||||||
return usecase_class(ifc_file, **settings).execute()
|
result = usecase_class(ifc_file, **settings).execute()
|
||||||
return usecase_class(**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