From 653b9288c0ba1d63185321144e3d7f425b9be343 Mon Sep 17 00:00:00 2001 From: Blender Defender Date: Tue, 30 Jul 2024 12:41:05 +0200 Subject: [PATCH] feat: Allow merging multiple ifc projects at once with the `MergeProject` recipe --- src/blenderbim/blenderbim/bim/__init__.py | 2 +- src/blenderbim/blenderbim/bim/helper.py | 2 + .../blenderbim/bim/module/patch/operator.py | 10 +++ src/blenderbim/blenderbim/bim/operator.py | 2 +- src/blenderbim/blenderbim/bim/prop.py | 64 +++++++++++-------- src/ifcpatch/ifcpatch/__init__.py | 4 ++ src/ifcpatch/ifcpatch/recipes/MergeProject.py | 14 ++-- 7 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index 42c9d32775..15e7c8c47e 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -121,6 +121,7 @@ classes = [ prop.StrProperty, operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty prop.ObjProperty, + prop.MultipleFileSelect, prop.Attribute, prop.BIMAreaProperties, prop.BIMTabProperties, @@ -133,7 +134,6 @@ classes = [ prop.BIMMeshProperties, prop.BIMFacet, prop.BIMFilterGroup, - prop.MultipleFileSelect, ui.BIM_UL_clipping_plane, ui.BIM_UL_generic, ui.BIM_UL_topics, diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index 867e51b222..c2b250f6dd 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -50,6 +50,8 @@ def draw_attribute(attribute, layout, copy_operator=None): return if value_name == "enum_value": prop_with_search(layout, attribute, "enum_value", text=attribute.name) + elif value_name == "filepath_value": + attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name) elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]: propis = bpy.context.scene.BIMWorkScheduleProperties for item in propis.durations_attributes: diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index 2cf94800cf..75414d0da7 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -122,9 +122,14 @@ class UpdateIfcPatchArguments(bpy.types.Operator): new_attr = patch_args.add() data_type = arg_info.get("type", "str") if isinstance(data_type, list): + if "file" in data_type: + data_type = ["file"] + data_type = [dt for dt in data_type if dt != "NoneType"][0] + new_attr.data_type = { "Literal": "enum", + "file": "file", "str": "string", "float": "float", "int": "integer", @@ -136,6 +141,11 @@ class UpdateIfcPatchArguments(bpy.types.Operator): new_attr.enum_value = arg_info.get("default", new_attr.get_value_default()) continue + if new_attr.data_type == "file": + new_attr.filepath_value.single_file = arg_info.get("default", new_attr.get_value_default()) + new_attr.filter_glob = arg_info.get("filter_glob", "*.ifc;*.ifczip;*.ifcxml") + continue + new_attr.set_value(arg_info.get("default", new_attr.get_value_default())) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index e085a2cb8f..164038b1df 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -147,7 +147,7 @@ class BIM_OT_multiple_file_selector(bpy.types.Operator): """Open Blender's file explorer to select one or multiple files.""" bl_idname = "bim.multiple_file_selector" - bl_label = "Select Multiple Files" + bl_label = "Select File(s)" bl_options = {"REGISTER", "UNDO"} filepath: bpy.props.StringProperty(subtype="FILE_PATH") files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement) diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 37f08f4e79..9204446711 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -156,6 +156,34 @@ class ObjProperty(PropertyGroup): obj: bpy.props.PointerProperty(type=bpy.types.Object) +def update_single_file(self, context): + self.file_list.clear() + new = self.file_list.add() + new.name = self.single_file + + +class MultipleFileSelect(PropertyGroup): + single_file: bpy.props.StringProperty(name="Single File Path", description="", update=update_single_file) + file_list: bpy.props.CollectionProperty(type=StrProperty) + + def set_file_list(self, dirname: str, files: list[str]): + self.file_list.clear() + + for f in files: + new = self.file_list.add() + new.name = os.path.join(dirname, f) + + def layout_file_select(self, layout, filter_glob="", text=""): + if len(self.file_list) > 1: + layout.label(text=f"{len(self.file_list)} Files Selected") + else: + layout.prop(self, "single_file", text=text) + + layout.context_pointer_set("file_props", self) + op = layout.operator("bim.multiple_file_selector", icon="FILE_FOLDER", text="") + op.filter_glob = filter_glob + + def update_attribute_value(self, context): value_name = self.get_value_name() if value_name: @@ -248,6 +276,8 @@ class Attribute(PropertyGroup): enum_items: StringProperty(name="Value") enum_descriptions: CollectionProperty(type=StrProperty) enum_value: EnumProperty(items=get_attribute_enum_values, name="Value", update=update_attribute_value) + filepath_value: PointerProperty(type=MultipleFileSelect) + filter_glob: StringProperty() is_null: BoolProperty(name="Is Null", update=update_is_null) is_optional: BoolProperty(name="Is Optional") is_uri: BoolProperty(name="Is Uri", default=False) @@ -263,6 +293,8 @@ class Attribute(PropertyGroup): return None if self.data_type == "string": return self.string_value.replace("\\n", "\n") + if self.data_type == "file": + return [f.name for f in self.filepath_value.file_list] return getattr(self, str(self.get_value_name()), None) def get_value_default(self): @@ -276,6 +308,8 @@ class Attribute(PropertyGroup): return False elif self.data_type == "enum": return "0" + elif self.data_type == "file": + return "" def get_value_name(self, display_only=False): if self.data_type == "string": @@ -290,6 +324,8 @@ class Attribute(PropertyGroup): return "float_value" elif self.data_type == "enum": return "enum_value" + elif self.data_type == "file": + return "filepath_value" def set_value(self, value): if isinstance(value, str): @@ -478,31 +514,3 @@ class BIMFacet(PropertyGroup): class BIMFilterGroup(PropertyGroup): filters: CollectionProperty(type=BIMFacet, name="filters") - - -def update_single_file(self, context): - self.file_list.clear() - new = self.file_list.add() - new.name = self.single_file - - -class MultipleFileSelect(bpy.types.PropertyGroup): - single_file: bpy.props.StringProperty(name="Single File Path", description="", update=update_single_file) - file_list: bpy.props.CollectionProperty(type=StrProperty) - - def set_file_list(self, dirname: str, files: list[str]): - self.file_list.clear() - - for f in files: - new = self.file_list.add() - new.name = os.path.join(dirname, f) - - def layout_file_select(self, layout, filter_glob="", text=""): - if len(self.file_list) > 1: - layout.label(text=f"{len(self.file_list)} Files Selected") - else: - layout.prop(self, "single_file", text=text) - - layout.context_pointer_set("file_props", self) - op = layout.operator("bim.multiple_file_selector", icon="FILE_FOLDER", text="") - op.filter_glob = filter_glob diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index 09db1958e3..19eae9790e 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -177,6 +177,10 @@ def _extract_docs(cls, method_name, boilerplate_args): param_name = line.split(":")[1].strip().replace("param ", "") if param_name in inputs: inputs[param_name]["description"] = line.split(":")[2].strip() + elif line.startswith(":filter_glob"): + param_name = line.split(":")[1].strip().replace("filter_glob ", "") + if param_name in inputs: + inputs[param_name]["filter_glob"] = line.split(":")[2].strip() elif i == 2: description += line elif i > 2: diff --git a/src/ifcpatch/ifcpatch/recipes/MergeProject.py b/src/ifcpatch/ifcpatch/recipes/MergeProject.py index 68f315945b..6e37bc104e 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeProject.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeProject.py @@ -40,6 +40,8 @@ class Patcher: :param filepath: The filepath of the second IFC model to merge into the first. The first model is already specified as the input to IfcPatch. + :type filepath: Union[str, ifcopenshell.file] + :filter_glob filepath: *.ifc;*.ifczip;*.ifcxml Example: @@ -53,11 +55,15 @@ class Patcher: self.filepath = filepath def patch(self): - if isinstance(self.filepath, ifcopenshell.file): - other = self.filepath - else: - other = ifcopenshell.open(self.filepath) + for filepath in self.filepath: + if isinstance(filepath, ifcopenshell.file): + other = filepath + else: + other = ifcopenshell.open(filepath) + self.merge(other) + + def merge(self, other): if (main_unit := self.get_unit_name(self.file)) != self.get_unit_name(other): other = ifcopenshell.util.unit.convert_file_length_units(other, main_unit)