refactor: Create property group MultipleFileSelect

To easier create and display properties that allow one or more files as input, a `MultipleFileSelect` property group has been extracted, together with an operator for selecting the files and the code for displaying the property.
This commit is contained in:
Blender Defender
2024-07-29 12:42:31 +02:00
committed by Dion Moult
parent 98edb5a34c
commit 95a7ea8dce
12 changed files with 71 additions and 93 deletions
@@ -101,6 +101,7 @@ classes = [
operator.BIM_OT_remove_section_plane, operator.BIM_OT_remove_section_plane,
operator.BIM_OT_select_object, operator.BIM_OT_select_object,
operator.BIM_OT_show_description, operator.BIM_OT_show_description,
operator.BIM_OT_multiple_file_selector,
operator.ClippingPlaneCutWithCappings, operator.ClippingPlaneCutWithCappings,
operator.CloseError, operator.CloseError,
operator.EditBlenderCollection, operator.EditBlenderCollection,
@@ -132,6 +133,7 @@ classes = [
prop.BIMMeshProperties, prop.BIMMeshProperties,
prop.BIMFacet, prop.BIMFacet,
prop.BIMFilterGroup, prop.BIMFilterGroup,
prop.MultipleFileSelect,
ui.BIM_UL_clipping_plane, ui.BIM_UL_clipping_plane,
ui.BIM_UL_generic, ui.BIM_UL_generic,
ui.BIM_UL_topics, ui.BIM_UL_topics,
@@ -22,7 +22,6 @@ from . import ui, prop, operator
classes = ( classes = (
operator.ExecuteIfcFM, operator.ExecuteIfcFM,
operator.ExecuteIfcFMFederate, operator.ExecuteIfcFMFederate,
operator.SelectFMIfcFile,
operator.SelectFMSpreadsheetFiles, operator.SelectFMSpreadsheetFiles,
prop.BIMFMProperties, prop.BIMFMProperties,
ui.BIM_PT_fm, ui.BIM_PT_fm,
@@ -24,20 +24,6 @@ import logging
import tempfile import tempfile
import ifcopenshell import ifcopenshell
import blenderbim.tool as tool 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): class ExecuteIfcFM(bpy.types.Operator):
@@ -50,7 +36,7 @@ class ExecuteIfcFM(bpy.types.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.BIMFMProperties 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): def invoke(self, context, event):
props = context.scene.BIMFMProperties props = context.scene.BIMFMProperties
@@ -63,14 +49,13 @@ class ExecuteIfcFM(bpy.types.Operator):
props = context.scene.BIMFMProperties props = context.scene.BIMFMProperties
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
filepaths = [] filepaths = []
if not (ifc_file and props.should_load_from_memory): if 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:
ifc_files = [ifc_file] 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): for i, ifc_file in enumerate(ifc_files):
if filepaths: if filepaths:
@@ -18,7 +18,7 @@
import bpy import bpy
from blenderbim.bim.module.fm.data import FMData 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.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -39,8 +39,7 @@ def get_engine(self, context):
class BIMFMProperties(PropertyGroup): class BIMFMProperties(PropertyGroup):
ifc_file: StringProperty(default="", name="IFC File") ifc_files: PointerProperty(type=MultipleFileSelect)
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty) spreadsheet_files: CollectionProperty(name="Spreadsheets", type=StrProperty)
should_load_from_memory: BoolProperty(default=False, name="Load from Memory") should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
engine: EnumProperty(items=get_engine, name="Engine") engine: EnumProperty(items=get_engine, name="Engine")
@@ -45,12 +45,8 @@ class BIM_PT_fm(Panel):
row.prop(props, "should_load_from_memory") row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory: if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True) row = layout.row()
if len(props.ifc_files) > 1: props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
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() row = layout.row()
row.prop(props, "engine") row.prop(props, "engine")
@@ -48,7 +48,6 @@ class BIM_PT_radiance_exporter(bpy.types.Panel):
if not tool.Ifc.get() or not props.should_load_from_memory: if not tool.Ifc.get() or not props.should_load_from_memory:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "ifc_file") row.prop(props, "ifc_file")
# row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row() row = layout.row()
row.prop(props, "output_dir") row.prop(props, "output_dir")
@@ -21,8 +21,6 @@ from . import ui, prop, operator
classes = ( classes = (
operator.ExecuteIfcTester, operator.ExecuteIfcTester,
operator.SelectSpecs,
operator.SelectIfcTesterIfcFile,
operator.SelectRequirement, operator.SelectRequirement,
operator.SelectFailedEntities, operator.SelectFailedEntities,
operator.SelectEntity, operator.SelectEntity,
@@ -27,7 +27,6 @@ import ifctester.reporter
import ifcopenshell import ifcopenshell
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.handler import blenderbim.bim.handler
from blenderbim.bim.operator import MultipleFileSelector
from pathlib import Path from pathlib import Path
@@ -38,7 +37,7 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
props = context.scene.IfcTesterProperties 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): def execute(self, context):
props = context.scene.IfcTesterProperties props = context.scene.IfcTesterProperties
@@ -49,13 +48,13 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
ifc_data = tool.Ifc.get() ifc_data = tool.Ifc.get()
ifc_path = tool.Ifc.get_path() 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) self.execute_tester(ifc_data, ifc_path, f.name)
else: else:
for ifc_file in props.ifc_files: for ifc_file in props.ifc_files.file_list:
ifc_data = ifcopenshell.open(ifc_file.name) 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) self.execute_tester(ifc_data, ifc_file.name, f.name)
blenderbim.bim.handler.refresh_ui_data() blenderbim.bim.handler.refresh_ui_data()
@@ -103,33 +102,6 @@ class ExecuteIfcTester(bpy.types.Operator, tool.Ifc.Operator):
new_spec.status = spec["status"] 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): class SelectRequirement(bpy.types.Operator):
bl_idname = "bim.select_requirement" bl_idname = "bim.select_requirement"
bl_label = "Select Specification" bl_label = "Select Specification"
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.bim.module.tester.data import TesterData 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.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -48,10 +48,8 @@ class FailedEntities(PropertyGroup):
class IfcTesterProperties(PropertyGroup): class IfcTesterProperties(PropertyGroup):
specs: StringProperty(default="", name="IDS File(s)") specs: PointerProperty(type=MultipleFileSelect)
specs_files: CollectionProperty(name="IDS Files", type=StrProperty) ifc_files: PointerProperty(type=MultipleFileSelect)
ifc_file: StringProperty(default="", name="IFC File(s)")
ifc_files: CollectionProperty(name="IFC Files", type=StrProperty)
should_load_from_memory: BoolProperty(default=False, name="Load from Memory", options=set()) 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_html_report: BoolProperty(default=False, name="Generate HTML report", options=set())
generate_ods_report: BoolProperty(default=False, name="Generate ODS report", options=set()) generate_ods_report: BoolProperty(default=False, name="Generate ODS report", options=set())
@@ -50,19 +50,10 @@ class BIM_PT_tester(Panel):
if not tool.Ifc.get() or not props.should_load_from_memory: if not tool.Ifc.get() or not props.should_load_from_memory:
row = self.layout.row(align=True) row = self.layout.row(align=True)
if len(props.ifc_files) > 1: props.ifc_files.layout_file_select(row, "*.ifc;*.ifczip;*.ifcxml", "IFC File(s)")
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="")
row = self.layout.row(align=True) row = self.layout.row(align=True)
if len(props.specs_files) > 1: props.specs.layout_file_select(row, "*.ids;*.xml", "IDS File(s)")
row.label(text=f"{len(props.specs_files)} Files Selected")
else:
row.prop(props, "specs")
row.operator("bim.select_specs", icon="FILE_FOLDER", text="")
row = self.layout.row() row = self.layout.row()
row.operator("bim.execute_ifc_tester") row.operator("bim.execute_ifc_tester")
+21 -10
View File
@@ -143,22 +143,30 @@ class SelectURIAttribute(bpy.types.Operator):
return {"RUNNING_MODAL"} 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_label = "Select Multiple Files"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
filepath: bpy.props.StringProperty(subtype="FILE_PATH") filepath: bpy.props.StringProperty(subtype="FILE_PATH")
files: bpy.props.CollectionProperty(name="File Path", type=bpy.types.OperatorFileListElement) 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) 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: return {"FINISHED"}
new = file_list.add()
new.name = os.path.join(dirname, f.name)
setattr(props, single_file, self.filepath)
def invoke(self, context, event): def invoke(self, context, event):
self.file_props = context.file_props
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"} 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") group.interface.new_socket(name="Line Decorator", in_out="OUTPUT", socket_type="NodeSocketFloat")
else: else:
group.inputs.new("NodeSocketFloat", "Value") 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("NodeSocketVector", "Vector")
group.inputs.new("NodeSocketFloat", "Line Decorator") group.inputs.new("NodeSocketFloat", "Line Decorator")
group.outputs.new("NodeSocketFloat", "Value") 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 = group.nodes.new(type="ShaderNodeMixShader")
mix_decorator.name = "Line Decorator Mix" 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_decorator.location = group_output.location - Vector((200, 0))
mix_section = group.nodes.new(type="ShaderNodeMixShader") mix_section = group.nodes.new(type="ShaderNodeMixShader")
mix_section.name = "Section Mix" 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)) mix_section.location = mix_decorator.location - Vector((200, 200))
transparent = nodes.new(type="ShaderNodeBsdfTransparent") transparent = nodes.new(type="ShaderNodeBsdfTransparent")
+28
View File
@@ -478,3 +478,31 @@ class BIMFacet(PropertyGroup):
class BIMFilterGroup(PropertyGroup): class BIMFilterGroup(PropertyGroup):
filters: CollectionProperty(type=BIMFacet, name="filters") 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