diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index 4d34f271ad..1d57877a16 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -52,6 +52,7 @@ def draw_attributes( layout: bpy.types.UILayout, copy_operator: Optional[str] = None, popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None, + callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None, ) -> None: """you can set attribute active in popup with `active_attribute` meaning you will be able to type into attribute's field without having to click @@ -62,6 +63,8 @@ def draw_attributes( if attribute == popup_active_attribute: row.activate_init = True draw_attribute(attribute, row, copy_operator) + if callback: + callback(attribute, row) def draw_attribute( diff --git a/src/bonsai/bonsai/bim/module/patch/__init__.py b/src/bonsai/bonsai/bim/module/patch/__init__.py index e7029e8781..0a35e4b0d8 100644 --- a/src/bonsai/bonsai/bim/module/patch/__init__.py +++ b/src/bonsai/bonsai/bim/module/patch/__init__.py @@ -21,6 +21,7 @@ from . import ui, prop, operator classes = ( operator.ExecuteIfcPatch, + operator.ExtractSelectedElements, operator.RunMigratePatch, operator.SelectIfcPatchInput, operator.SelectIfcPatchOutput, diff --git a/src/bonsai/bonsai/bim/module/patch/operator.py b/src/bonsai/bonsai/bim/module/patch/operator.py index f960d1875d..fe2b021c1d 100644 --- a/src/bonsai/bonsai/bim/module/patch/operator.py +++ b/src/bonsai/bonsai/bim/module/patch/operator.py @@ -89,6 +89,7 @@ class ExecuteIfcPatch(bpy.types.Operator): value = value[0] arguments.append(value) + arguments = tool.Patch.post_process_patch_arguments(recipe_name, arguments) args = ifcpatch.ArgumentsDict( recipe=props.ifc_patch_recipes, arguments=arguments, @@ -208,3 +209,21 @@ class RunMigratePatch(bpy.types.Operator): pass # Probably running in headless mode bonsai.bim.handler.refresh_ui_data() return {"FINISHED"} + + +class ExtractSelectedElements(bpy.types.Operator): + bl_idname = "bim.patch_query_from_selected" + bl_label = "Fill patch query based on the selected elements" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + props = context.scene.BIMPatchProperties + recipe_name = props.ifc_patch_recipes + + if recipe_name != "ExtractElements": + self.report({"ERROR"}, "Only supported for the 'ExtractElements' recipe.") + return {"CANCELLED"} + + query = tool.Search.get_query_for_selected_elements() + props.ifc_patch_args_attr[0].string_value = query + return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/patch/ui.py b/src/bonsai/bonsai/bim/module/patch/ui.py index 7f0435a17b..539544055d 100644 --- a/src/bonsai/bonsai/bim/module/patch/ui.py +++ b/src/bonsai/bonsai/bim/module/patch/ui.py @@ -55,6 +55,12 @@ class BIM_PT_patch(bpy.types.Panel): row.prop(props, "ifc_patch_output") row.operator("bim.select_ifc_patch_output", icon="FILE_FOLDER", text="") + def draw_callback_(_, row: bpy.types.UILayout) -> None: + if props.ifc_patch_recipes == "ExtractElements": + row.operator("bim.patch_query_from_selected", text="", icon="EYEDROPPER") + if props.ifc_patch_args_attr: - draw_attributes(props.ifc_patch_args_attr, layout) + draw_callback = draw_callback_ if props.ifc_patch_recipes == "ExtractElements" else None + draw_attributes(props.ifc_patch_args_attr, layout, callback=draw_callback) + op = layout.operator("bim.execute_ifc_patch") diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py index 992f1cf7aa..f2223e7ad9 100644 --- a/src/bonsai/bonsai/bim/module/search/operator.py +++ b/src/bonsai/bonsai/bim/module/search/operator.py @@ -101,20 +101,10 @@ class SelectFilterElements(bpy.types.Operator): def execute(self, context): filter_groups = tool.Search.get_filter_groups(self.module) - global_ids = [] - for obj in context.selected_objects: - if element := tool.Ifc.get_entity(obj): - if global_id := getattr(element, "GlobalId", None): - global_ids.append(global_id) - if len(global_ids) > 50: - # Too much to store in a string property - name = f"globalid-filter-{ifcopenshell.guid.new()}" - text_data = bpy.data.texts.new(name) - text_data.from_string(",".join(global_ids)) - filter_groups[self.group_index].filters[self.index].value = f"bpy.data.texts['{name}']" + query = tool.Search.get_query_for_selected_elements() + filter_groups[self.group_index].filters[self.index].value = query + if query.startswith("bpy.data.texts['"): self.report({"INFO"}, f'List of Global Ids was saved to the text file "{name}" in the current .blend file') - else: - filter_groups[self.group_index].filters[self.index].value = ",".join(global_ids) return {"FINISHED"} diff --git a/src/bonsai/bonsai/tool/patch.py b/src/bonsai/bonsai/tool/patch.py index 33c8142210..a178f99e2b 100644 --- a/src/bonsai/bonsai/tool/patch.py +++ b/src/bonsai/bonsai/tool/patch.py @@ -20,6 +20,7 @@ import bpy import ifcopenshell import ifcpatch import bonsai.core.tool +from typing import Any class Patch(bonsai.core.tool.Patch): @@ -40,3 +41,13 @@ class Patch(bonsai.core.tool.Patch): @classmethod def does_patch_has_output(cls, recipe: str) -> bool: return recipe != "SplitByBuildingStorey" + + @classmethod + def post_process_patch_arguments(cls, recipe: str, args: list[Any]) -> list[Any]: + if recipe == "ExtractElements": + query = args[0] + assert isinstance(query, str) + if "bpy.data.texts" in query: + text_name = query.split("bpy.data.texts")[1][2:-2] + args[0] = bpy.data.texts[text_name].as_string() + return args diff --git a/src/bonsai/bonsai/tool/search.py b/src/bonsai/bonsai/tool/search.py index 179ad210dd..f0cebb708b 100644 --- a/src/bonsai/bonsai/tool/search.py +++ b/src/bonsai/bonsai/tool/search.py @@ -1,6 +1,26 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2022 Dion Moult +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . + + import bpy import json import lark +import bonsai.tool as tool import bonsai.core.tool import ifcopenshell.guid import ifcopenshell.util.selector @@ -267,6 +287,23 @@ class Search(bonsai.core.tool.Search): return palette[-1] return cls.interpolate_color(palette[index], palette[index + 1], fraction) + @classmethod + def get_query_for_selected_elements(cls) -> str: + global_ids = [] + for obj in tool.Blender.get_selected_objects(): + if element := tool.Ifc.get_entity(obj): + if global_id := getattr(element, "GlobalId", None): + global_ids.append(global_id) + + query = ",".join(global_ids) + if len(global_ids) > 50: + # Too much to store in a string property. + name = f"globalid-filter-{ifcopenshell.guid.new()}" + text_data = bpy.data.texts.new(name) + text_data.from_string(query) + query = f"bpy.data.texts['{name}']" + return query + class ImportFilterQueryTransformer(lark.Transformer): def __init__(self, filter_groups):