diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index d19273e9f4..b2363a58ff 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -10,6 +10,7 @@ if bpy is not None: modules = { "project": None, + "parametric": None, "search": None, "bcf": None, "root": None, diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 3cd832d83e..cc3ba7058e 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -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, diff --git a/src/blenderbim/blenderbim/bim/module/parametric/__init__.py b/src/blenderbim/blenderbim/bim/module/parametric/__init__.py new file mode 100644 index 0000000000..a5f9cac3fd --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/parametric/__init__.py @@ -0,0 +1,15 @@ +import bpy +from . import ui, operator + +classes = ( + operator.ActivateParametricEngine, + ui.BIM_PT_parametric, +) + + +def register(): + pass + + +def unregister(): + pass diff --git a/src/blenderbim/blenderbim/bim/module/parametric/operator.py b/src/blenderbim/blenderbim/bim/module/parametric/operator.py new file mode 100644 index 0000000000..78edab65a2 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/parametric/operator.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/parametric/ui.py b/src/blenderbim/blenderbim/bim/module/parametric/ui.py new file mode 100644 index 0000000000..4b2470d10b --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/parametric/ui.py @@ -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") diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index 0468132d86..8566330952 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -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)