diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index a55b97fc67..42c9d32775 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -101,6 +101,7 @@ classes = [ operator.BIM_OT_remove_section_plane, operator.BIM_OT_select_object, operator.BIM_OT_show_description, + operator.BIM_OT_multiple_file_selector, operator.ClippingPlaneCutWithCappings, operator.CloseError, operator.EditBlenderCollection, @@ -132,6 +133,7 @@ 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/module/fm/__init__.py b/src/blenderbim/blenderbim/bim/module/fm/__init__.py index bae513a98f..0d8081b2a9 100644 --- a/src/blenderbim/blenderbim/bim/module/fm/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/fm/__init__.py @@ -22,7 +22,6 @@ from . import ui, prop, operator classes = ( operator.ExecuteIfcFM, operator.ExecuteIfcFMFederate, - operator.SelectFMIfcFile, operator.SelectFMSpreadsheetFiles, prop.BIMFMProperties, ui.BIM_PT_fm, diff --git a/src/blenderbim/blenderbim/bim/module/fm/operator.py b/src/blenderbim/blenderbim/bim/module/fm/operator.py index da5e4304da..0c224b08e0 100644 --- a/src/blenderbim/blenderbim/bim/module/fm/operator.py +++ b/src/blenderbim/blenderbim/bim/module/fm/operator.py @@ -24,20 +24,6 @@ import logging import tempfile import ifcopenshell import blenderbim.tool as tool -from blenderbim.bim.operator import MultipleFileSelector - - -class SelectFMIfcFile(MultipleFileSelector): - bl_idname = "bim.select_fm_ifc_file" - bl_label = "Select FM IFC File" - filename_ext = ".ifc" - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) - - def execute(self, context): - props = context.scene.BIMFMProperties - - self.update_props(props, "ifc_file", props.ifc_files) - return {"FINISHED"} class ExecuteIfcFM(bpy.types.Operator): @@ -50,7 +36,7 @@ class ExecuteIfcFM(bpy.types.Operator): @classmethod def poll(cls, context): props = context.scene.BIMFMProperties - return props.should_load_from_memory or props.ifc_file + return props.should_load_from_memory or props.ifc_files.single_file def invoke(self, context, event): props = context.scene.BIMFMProperties @@ -63,14 +49,13 @@ class ExecuteIfcFM(bpy.types.Operator): props = context.scene.BIMFMProperties ifc_file = tool.Ifc.get() filepaths = [] - if not (ifc_file and props.should_load_from_memory): - if len(props.ifc_files): - ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files] - filepaths = [f.name for f in props.ifc_files] - else: - ifc_files = [ifcopenshell.open(props.ifc_file)] - else: + if ifc_file and props.should_load_from_memory: ifc_files = [ifc_file] + else: + ifc_files = [ifcopenshell.open(f.name) for f in props.ifc_files.file_list] + + if len(ifc_files) > 1: + filepaths = [f.name for f in props.ifc_files.file_list] for i, ifc_file in enumerate(ifc_files): if filepaths: diff --git a/src/blenderbim/blenderbim/bim/module/fm/prop.py b/src/blenderbim/blenderbim/bim/module/fm/prop.py index f4a1868645..0dd2c6c927 100644 --- a/src/blenderbim/blenderbim/bim/module/fm/prop.py +++ b/src/blenderbim/blenderbim/bim/module/fm/prop.py @@ -18,7 +18,7 @@ import bpy from blenderbim.bim.module.fm.data import FMData -from blenderbim.bim.prop import StrProperty +from blenderbim.bim.prop import MultipleFileSelect, StrProperty from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -39,8 +39,7 @@ def get_engine(self, context): class BIMFMProperties(PropertyGroup): - ifc_file: StringProperty(default="", name="IFC File") - ifc_files: CollectionProperty(name="IFC Files", type=StrProperty) + ifc_files: PointerProperty(type=MultipleFileSelect) spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty) should_load_from_memory: BoolProperty(default=False, name="Load from Memory") engine: EnumProperty(items=get_engine, name="Engine") diff --git a/src/blenderbim/blenderbim/bim/module/fm/ui.py b/src/blenderbim/blenderbim/bim/module/fm/ui.py index ca5efe36f1..1456c89cb9 100644 --- a/src/blenderbim/blenderbim/bim/module/fm/ui.py +++ b/src/blenderbim/blenderbim/bim/module/fm/ui.py @@ -45,12 +45,8 @@ class BIM_PT_fm(Panel): row.prop(props, "should_load_from_memory") if not IfcStore.get_file() or not props.should_load_from_memory: - row = layout.row(align=True) - if len(props.ifc_files) > 1: - row.label(text=f"{len(props.ifc_files)} Files Selected") - else: - row.prop(props, "ifc_file") - row.operator("bim.select_fm_ifc_file", icon="FILE_FOLDER", text="") + row = layout.row() + props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)") row = layout.row() row.prop(props, "engine") diff --git a/src/blenderbim/blenderbim/bim/module/light/ui.py b/src/blenderbim/blenderbim/bim/module/light/ui.py index 83bf547b6f..c9b2c045f5 100644 --- a/src/blenderbim/blenderbim/bim/module/light/ui.py +++ b/src/blenderbim/blenderbim/bim/module/light/ui.py @@ -48,7 +48,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel): if not tool.Ifc.get() or not props.should_load_from_memory: row = self.layout.row(align=True) row.prop(props, "ifc_file") - # row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="") row = layout.row() row.prop(props, "output_dir") diff --git a/src/blenderbim/blenderbim/bim/module/tester/__init__.py b/src/blenderbim/blenderbim/bim/module/tester/__init__.py index f635b993b1..fa3ab04089 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/tester/__init__.py @@ -21,8 +21,6 @@ from . import ui, prop, operator classes = ( operator.ExecuteIfcTester, - operator.SelectSpecs, - operator.SelectIfcTesterIfcFile, operator.SelectRequirement, operator.SelectFailedEntities, operator.SelectEntity, diff --git a/src/blenderbim/blenderbim/bim/module/tester/operator.py b/src/blenderbim/blenderbim/bim/module/tester/operator.py index 2885617055..27cc2f4523 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/operator.py +++ b/src/blenderbim/blenderbim/bim/module/tester/operator.py @@ -27,7 +27,6 @@ import ifctester.reporter import ifcopenshell import blenderbim.tool as tool import blenderbim.bim.handler -from blenderbim.bim.operator import MultipleFileSelector from pathlib import Path @@ -38,7 +37,7 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator): @classmethod def poll(cls, context): props = context.scene.IfcTesterProperties - return (props.ifc_file or props.should_load_from_memory) and props.specs + return (props.ifc_files.single_file or props.should_load_from_memory) and props.specs def execute(self, context): props = context.scene.IfcTesterProperties @@ -49,13 +48,13 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator): ifc_data = tool.Ifc.get() ifc_path = tool.Ifc.get_path() - for f in props.specs_files: + for f in props.specs.file_list: self.execute_tester(ifc_data, ifc_path, f.name) else: - for ifc_file in props.ifc_files: + for ifc_file in props.ifc_files.file_list: ifc_data = ifcopenshell.open(ifc_file.name) - for f in props.specs_files: + for f in props.specs.file_list: self.execute_tester(ifc_data, ifc_file.name, f.name) blenderbim.bim.handler.refresh_ui_data() @@ -103,33 +102,6 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator): new_spec.status = spec["status"] -class SelectSpecs(MultipleFileSelector): - bl_idname = "bim.select_specs" - bl_label = "Select IDS" - filename_ext = ".ids" - filter_glob: bpy.props.StringProperty(default="*.ids;*.xml", options={"HIDDEN"}) - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - props = context.scene.IfcTesterProperties - - self.update_props(props, "specs", props.specs_files) - return {"FINISHED"} - - -class SelectIfcTesterIfcFile(MultipleFileSelector): - bl_idname = "bim.select_ifctester_ifc_file" - bl_label = "Select IfcTester IFC File" - filename_ext = ".ifc" - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) - - def execute(self, context): - props = context.scene.IfcTesterProperties - - self.update_props(props, "ifc_file", props.ifc_files) - return {"FINISHED"} - - class SelectRequirement(bpy.types.Operator): bl_idname = "bim.select_requirement" bl_label = "Select Specification" diff --git a/src/blenderbim/blenderbim/bim/module/tester/prop.py b/src/blenderbim/blenderbim/bim/module/tester/prop.py index bc2f65faa1..7c496c7d84 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/prop.py +++ b/src/blenderbim/blenderbim/bim/module/tester/prop.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . from blenderbim.bim.module.tester.data import TesterData -from blenderbim.bim.prop import StrProperty +from blenderbim.bim.prop import StrProperty, MultipleFileSelect from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -48,10 +48,8 @@ class FailedEntities(PropertyGroup): class IfcTesterProperties(PropertyGroup): - specs: StringProperty(default="", name="IDS File(s)") - specs_files: CollectionProperty(name="IDS Files", type=StrProperty) - ifc_file: StringProperty(default="", name="IFC File(s)") - ifc_files: CollectionProperty(name="IFC Files", type=StrProperty) + specs: PointerProperty(type=MultipleFileSelect) + ifc_files: PointerProperty(type=MultipleFileSelect) should_load_from_memory: BoolProperty(default=False, name="Load from Memory", options=set()) generate_html_report: BoolProperty(default=False, name="Generate HTML report", options=set()) generate_ods_report: BoolProperty(default=False, name="Generate ODS report", options=set()) diff --git a/src/blenderbim/blenderbim/bim/module/tester/ui.py b/src/blenderbim/blenderbim/bim/module/tester/ui.py index b87a68bdad..29b2f35eb9 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/tester/ui.py @@ -50,19 +50,10 @@ class BIM_PT_tester(Panel): if not tool.Ifc.get() or not props.should_load_from_memory: row = self.layout.row(align=True) - if len(props.ifc_files) > 1: - row.label(text=f"{len(props.ifc_files)} Files Selected") - else: - row.prop(props, "ifc_file") - - row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="") + props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)") row = self.layout.row(align=True) - if len(props.specs_files) > 1: - row.label(text=f"{len(props.specs_files)} Files Selected") - else: - row.prop(props, "specs") - row.operator("bim.select_specs", icon="FILE_FOLDER", text="") + props.specs.layout_file_select(row, "*.ids;*.xml", "IDS File(s)") row = self.layout.row() row.operator("bim.execute_ifc_tester") diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 3b1f4b7210..e085a2cb8f 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -143,22 +143,30 @@ class SelectURIAttribute(bpy.types.Operator): return {"RUNNING_MODAL"} -class MultipleFileSelector(bpy.types.Operator): +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_options = {"REGISTER", "UNDO"} filepath: bpy.props.StringProperty(subtype="FILE_PATH") files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement) + filter_glob: bpy.props.StringProperty(default="*", options={"HIDDEN"}) + filepath: bpy.props.StringProperty(subtype="FILE_PATH") - def update_props(self, props, single_file: str, file_list: bpy.types.CollectionProperty): + @classmethod + def poll(self, context): + return getattr(context, "file_props", None) is not None + + def execute(self, context): dirname = os.path.dirname(self.filepath) - file_list.clear() + self.file_props.single_file = self.filepath + self.file_props.set_file_list(dirname, [f.name for f in self.files]) - for f in self.files: - new = file_list.add() - new.name = os.path.join(dirname, f.name) - setattr(props, single_file, self.filepath) + return {"FINISHED"} def invoke(self, context, event): + self.file_props = context.file_props context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} @@ -458,7 +466,8 @@ class BIM_OT_add_section_plane(bpy.types.Operator): group.interface.new_socket(name="Line Decorator", in_out="OUTPUT", socket_type="NodeSocketFloat") else: group.inputs.new("NodeSocketFloat", "Value") - group.inputs["Value"].default_value = 1.0 # Mandatory multiplier for the last node group + # Mandatory multiplier for the last node group + group.inputs["Value"].default_value = 1.0 group.inputs.new("NodeSocketVector", "Vector") group.inputs.new("NodeSocketFloat", "Line Decorator") group.outputs.new("NodeSocketFloat", "Value") @@ -526,12 +535,14 @@ class BIM_OT_add_section_plane(bpy.types.Operator): mix_decorator = group.nodes.new(type="ShaderNodeMixShader") mix_decorator.name = "Line Decorator Mix" - mix_decorator.inputs[0].default_value = 0 # Directly pass input shader when there is no cutaway + # Directly pass input shader when there is no cutaway + mix_decorator.inputs[0].default_value = 0 mix_decorator.location = group_output.location - Vector((200, 0)) mix_section = group.nodes.new(type="ShaderNodeMixShader") mix_section.name = "Section Mix" - mix_section.inputs[0].default_value = 1 # Directly pass input shader when there is no cutaway + # Directly pass input shader when there is no cutaway + mix_section.inputs[0].default_value = 1 mix_section.location = mix_decorator.location - Vector((200, 200)) transparent = nodes.new(type="ShaderNodeBsdfTransparent") diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index b94dc4ef68..37f08f4e79 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -478,3 +478,31 @@ 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