From 939cc63cae6e299824f86c60ec8fdee9ed632740 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 5 Dec 2024 17:16:21 +0500 Subject: [PATCH] SplitByBuildingStorey - fix errors, fix Bonsai UI #4721 - The patch was failed when executed from Bonsai since self.src wasn't provided, fixed now. - Added a temporary hack to diplsay file selector for this patch. - small refactor. --- .../bonsai/bim/module/patch/operator.py | 14 +++++- src/bonsai/bonsai/tool/patch.py | 7 +++ .../ifcpatch/recipes/SplitByBuildingStorey.py | 48 +++++++++++++------ 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/patch/operator.py b/src/bonsai/bonsai/bim/module/patch/operator.py index 02c1e9ec90..22db743e9e 100644 --- a/src/bonsai/bonsai/bim/module/patch/operator.py +++ b/src/bonsai/bonsai/bim/module/patch/operator.py @@ -79,7 +79,12 @@ class ExecuteIfcPatch(bpy.types.Operator): props = context.scene.BIMPatchProperties arguments = [] if props.ifc_patch_args_attr: - arguments = [arg.get_value() for arg in props.ifc_patch_args_attr] + arguments = [] + for arg in props.ifc_patch_args_attr: + value = arg.get_value() + if arg.data_type == "file" and arg.metadata == "single_file": + value = value[0] + arguments.append(value) if props.should_load_from_memory and tool.Ifc.get(): input_file = props.ifc_patch_input @@ -123,8 +128,13 @@ class UpdateIfcPatchArguments(bpy.types.Operator): arg_info = inputs[arg_name] new_attr = patch_args.add() data_type = arg_info.get("type", "str") + + if tool.Patch.is_filepath_argument(self.recipe, arg_name): + data_type = "file" + new_attr.metadata = "single_file" + if isinstance(data_type, list): - if "file" in data_type: + if "file" in data_type or tool.Patch.is_filepath_argument(self.recipe, arg_name): data_type = ["file"] data_type = [dt for dt in data_type if dt != "NoneType"][0] diff --git a/src/bonsai/bonsai/tool/patch.py b/src/bonsai/bonsai/tool/patch.py index f1de7d892a..3c6502d08f 100644 --- a/src/bonsai/bonsai/tool/patch.py +++ b/src/bonsai/bonsai/tool/patch.py @@ -29,3 +29,10 @@ class Patch(bonsai.core.tool.Patch): {"input": infile, "file": ifcopenshell.open(infile), "recipe": "Migrate", "arguments": [schema]} ) ifcpatch.write(output, outfile) + + @classmethod + def is_filepath_argument(cls, recipe: str, arg_name: str) -> bool: + # TODO: Temporary hack to identify filepath arguments. + # Should mark them as such in the patches documentation + # and process it later. + return recipe == "SplitByBuildingStorey" and arg_name == "output_dir" diff --git a/src/ifcpatch/ifcpatch/recipes/SplitByBuildingStorey.py b/src/ifcpatch/ifcpatch/recipes/SplitByBuildingStorey.py index ac0db99d9e..fee1421314 100644 --- a/src/ifcpatch/ifcpatch/recipes/SplitByBuildingStorey.py +++ b/src/ifcpatch/ifcpatch/recipes/SplitByBuildingStorey.py @@ -16,9 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcPatch. If not, see . +import os +import logging +import ifcopenshell +from pathlib import Path +from typing import Union + class Patcher: - def __init__(self, src, file, logger, output_dir=None): + def __init__(self, src: str, file: ifcopenshell.file, logger: logging.Logger, output_dir: Union[str, None] = None): """Split an IFC model into multiple models based on building storey The new IFC model names will be named after the storey name in the @@ -26,7 +32,6 @@ class Patcher: 0 and {name} is the name of the storey. :param output_dir: Specifies an output directory where the new IFC models will be saved. - :type output_dir: str Example: @@ -39,25 +44,37 @@ class Patcher: self.logger = logger self.output_dir = output_dir - def patch(self): + def patch(self) -> None: import ifcopenshell + import tempfile from shutil import copyfile + if self.output_dir is None: + output_dir = None + else: + output_dir = Path(self.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + temp_file = None + if not self.src: + temp_file = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) + self.src = temp_file.name + self.file.write(self.src) + storeys = self.file.by_type("IfcBuildingStorey") for i, storey in enumerate(storeys): - dest = ( - "{}-{}.ifc".format(i, storey.Name) - if self.output_dir == None - else "{}/{}-{}.ifc".format(self.output_dir, i, storey.Name) - ) + filename = f"{i}-{storey.Name}.ifc" + dest = filename if output_dir == None else output_dir / filename copyfile(self.src, dest) - old_ifc = ifcopenshell.open(dest) + old_ifc: ifcopenshell.file = ifcopenshell.open(dest) new_ifc = ifcopenshell.file(schema=self.file.schema) + if self.file.schema == "IFC2X3": elements = old_ifc.by_type("IfcProject") + old_ifc.by_type("IfcProduct") else: elements = old_ifc.by_type("IfcContext") + old_ifc.by_type("IfcProduct") - inverse_elements = [] + + inverse_elements: list[ifcopenshell.entity_instance] = [] for element in elements: if element.is_a("IfcElement") and not self.is_in_storey(element, storey): element.Representation = None @@ -76,9 +93,12 @@ class Patcher: new_ifc.remove(element) new_ifc.write(dest) - def is_in_storey(self, element, storey): + if temp_file is not None: + os.unlink(temp_file.name) + + def is_in_storey(self, element: ifcopenshell.entity_instance, storey: ifcopenshell.entity_instance) -> bool: return ( - element.ContainedInStructure - and element.ContainedInStructure[0].RelatingStructure.is_a("IfcBuildingStorey") - and element.ContainedInStructure[0].RelatingStructure.GlobalId == storey.GlobalId + (contained_in_structure := element.ContainedInStructure) + and (relating_structure := contained_in_structure[0].RelatingStructure).is_a("IfcBuildingStorey") + and relating_structure.GlobalId == storey.GlobalId )