From 36a3ee3d1e7223af5db0a95759a6eeac44be956e Mon Sep 17 00:00:00 2001 From: KirilStrezikozin <kirilstrezikozin@gmail.com> Date: Thu, 6 Oct 2022 17:42:05 +0300 Subject: [PATCH] addfeature: requested Presets System (copy from dev) copy from dev repo. Waiting to be confirmed and closed --- README.md | 2 +- __init__.py | 23 +- presets.py | 935 ++++++++++++++++++++++++++++++++++++++++++++++- ui_panel_base.py | 43 ++- 4 files changed, 982 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ece3d19..1f78e34 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ BakeMaster is an add-on specified for baking various texture maps <br/>and creat > 🎯 - New map: ColorID - 🎯<br> > 🎯 - New map: Vector Displacement - 🎯<br> > -> Currently in progress: Presets - 1/8 tasks done%<br> +> Currently in progress: Presets - 99%<br> > Milestone: [Presets](https://github.com/KirilStrezikozin/BakeMaster-Blender-Addon/milestone/1) > > Stay tuned for Announcements ✅</span> diff --git a/__init__.py b/__init__.py index ee28eef..3e6be64 100644 --- a/__init__.py +++ b/__init__.py @@ -76,8 +76,21 @@ BM_PT_Main_Help, + BM_PT_ObjectConfigurator_Presets, + BM_MT_ObjectConfigurator_Presets, + BM_PT_ObjectSettings_Presets, + BM_MT_ObjectSettings_Presets, + BM_PT_STTSettings_Presets, + BM_MT_STTSettings_Presets, + BM_PT_UVSettings_Presets, + BM_MT_UVSettings_Presets, + BM_PT_OutputSettings_Presets, + BM_MT_OutputSettings_Presets, + BM_PT_MapsConfigurator_Presets, + BM_MT_MapsConfigurator_Presets, + BM_PT_MapSettings_Presets, + BM_MT_MapSettings_Presets, BM_PT_BakeSettings_Presets, - BM_MT_BakeSettings_Presets, BM_OT_AOL, @@ -89,7 +102,15 @@ BM_OT_ITEM_Bake, BM_OT_Help, + BM_OT_ObjectConfigutator_Preset_Add, + BM_OT_ObjectSettings_Preset_Add, + BM_OT_STTSettings_Preset_Add, + BM_OT_UVSettings_Preset_Add, + BM_OT_OutputSettings_Preset_Add, + BM_OT_MapsConfigutator_Preset_Add, + BM_OT_MapSettings_Preset_Add, BM_OT_BakeSettings_Preset_Add, + BM_OT_ExecutePreset, BM_Item_Map, BM_Item, diff --git a/presets.py b/presets.py index d06378b..290b703 100644 --- a/presets.py +++ b/presets.py @@ -38,10 +38,10 @@ def BM_Presets_FolderSetup(): ########################################################### # bakemaster full configurator presets - presets_makedir(bm_presets_dir_path, "full_configurator_presets") + presets_makedir(bm_presets_dir_path, "object_configurator_presets") ########################################################### # object settings presets and subdirs for stt, uv, output presets - presets_makedir(bm_presets_dir_path, "all_object_settings_presets") + presets_makedir(bm_presets_dir_path, "object_settings_presets") # stt settings presets subdir presets_makedir(bm_presets_dir_path, "stt_settings_presets") # uv settings presets subdir @@ -50,15 +50,590 @@ def BM_Presets_FolderSetup(): presets_makedir(bm_presets_dir_path, "output_settings_presets") ########################################################### # map configurator presets and subdir for map settings presets - presets_makedir(bm_presets_dir_path, "all_map_settings_presets") + presets_makedir(bm_presets_dir_path, "maps_configurator_presets") # map settings presets subdir presets_makedir(bm_presets_dir_path, "map_settings_presets") ########################################################### # bake settings presets subdir - presets_makedir(bm_presets_dir_path, "all_bake_settings_presets") + presets_makedir(bm_presets_dir_path, "bake_settings_presets") ### PRESET BASES ### +# AddPresetBase script +# original from https://developer.blender.org/diffusion/B/browse/master/release/scripts/startup/bl_operators/presets.py%2424 +# modified for bakemaster preset base +class BM_Configurator_AddPresetBase(): + """Configurator base preset class, + for subclassing""" + # class has to define: + # preset_menu + # preset_subdir + # preset_defines + # preset_values + + # only because invoke_props_popup requires. Also do not add to search menu. + bl_options = {'REGISTER', 'INTERNAL'} + + name: bpy.props.StringProperty( + name="Name", + description="Name of the preset, used to make the path name", + maxlen=64, + options={'SKIP_SAVE'}, + ) + remove_name: bpy.props.BoolProperty( + default=False, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + remove_active: bpy.props.BoolProperty( + default=False, + options={'HIDDEN', 'SKIP_SAVE'}, + ) + + @staticmethod + def as_filename(name): # could reuse for other presets + + # lazy init maketrans + def maketrans_init(): + cls = AddPresetBase + attr = "_as_filename_trans" + + trans = getattr(cls, attr, None) + if trans is None: + trans = str.maketrans({char: "_" for char in " !@#$%^&*(){}:\";'[]<>,.\\/?"}) + setattr(cls, attr, trans) + return trans + + name = name.strip() + name = bpy.path.display_name_to_filepath(name) + trans = maketrans_init() + # Strip surrounding "_" as they are displayed as spaces. + return name.translate(trans).strip("_") + + def execute(self, context): + import os + from bpy.utils import is_path_builtin + + if hasattr(self, "pre_cb"): + self.pre_cb(context) + + preset_menu_class = getattr(bpy.types, self.preset_menu) + + is_xml = getattr(preset_menu_class, "preset_type", None) == 'XML' + is_preset_add = not (self.remove_name or self.remove_active) + + if is_xml: + ext = ".xml" + else: + ext = ".py" + + name = self.name.strip() if is_preset_add else self.name + + if is_preset_add: + if not name: + return {'FINISHED'} + + # Reset preset name + wm = bpy.data.window_managers[0] + if name == wm.preset_name: + wm.preset_name = 'New Preset' + + filename = self.as_filename(name) + + target_path = os.path.join("presets", self.preset_subdir) + target_path = bpy.utils.user_resource('SCRIPTS', path=target_path, create=True) + + if not target_path: + self.report({'WARNING'}, "Failed to create presets path") + return {'CANCELLED'} + + filepath = os.path.join(target_path, filename) + ext + + if hasattr(self, "add"): + self.add(context, filepath) + else: + print("Writing Preset: %r" % filepath) + + if is_xml: + import rna_xml + rna_xml.xml_file_write(context, + filepath, + preset_menu_class.preset_xml_map) + else: + + def rna_recursive_attr_expand(value, rna_path_step, level): + if isinstance(value, bpy.types.PropertyGroup): + for sub_value_attr in value.bl_rna.properties.keys(): + if sub_value_attr == "rna_type": + continue + sub_value = getattr(value, sub_value_attr) + rna_recursive_attr_expand(sub_value, "%s.%s" % (rna_path_step, sub_value_attr), level) + elif type(value).__name__ == "bpy_prop_collection_idprop": # could use nicer method + file_preset.write("%s.clear()\n" % rna_path_step) + for sub_value in value: + file_preset.write("item_sub_%d = %s.add()\n" % (level, rna_path_step)) + rna_recursive_attr_expand(sub_value, "item_sub_%d" % level, level + 1) + else: + # convert thin wrapped sequences + # to simple lists to repr() + try: + value = value[:] + except: + pass + + file_preset.write("%s = %r\n" % (rna_path_step, value)) + + file_preset = open(filepath, 'w', encoding="utf-8") + file_preset.write("import bpy\n") + + if hasattr(self, "preset_defines"): + for rna_path in self.preset_defines: + exec(rna_path) + file_preset.write("%s\n" % rna_path) + file_preset.write("\n") + + ### *start of custom code block for bakemaster* ### + # writing additional properties to the file_preset + + # storing data for every map added + map_props_names = [ + "use_bake", + "map_type", + "bake_target", + "use_denoise", + "file_format", + "res_enum", + "res_height", + "res_width", + "margin", + "margin_type", + "use_32bit", + "use_alpha", + "use_source_target", + "udim_start_tile", + "udim_end_tile", + "cycles_use_pass_direct", + "cycles_use_pass_indirect", + "cycles_use_pass_color", + "cycles_use_pass_diffuse", + "cycles_use_pass_glossy", + "cycles_use_pass_transmission", + "cycles_use_pass_ambient_occlusion", + "cycles_use_pass_emit", + "normal_space", + "normal_r", + "normal_g", + "normal_b", + "use_smooth_normals", + "normal_cage", + "displacement_subdiv_levels", + "ao_use_preview", + "ao_use_default", + "ao_samples", + "ao_distance", + "ao_black_point", + "ao_white_point", + "ao_brightness", + "ao_contrast", + "ao_opacity", + "ao_use_local", + "ao_use_invert", + "cavity_use_preview", + "cavity_use_default", + "cavity_black_point", + "cavity_white_point", + "cavity_power", + "cavity_use_invert", + "curv_use_preview", + "curv_use_default", + "curv_samples", + "curv_radius", + "curv_edge_contrast", + "curv_body_contrast", + "curv_use_invert", + "thick_use_preview", + "thick_use_default", + "thick_samples", + "thick_distance", + "thick_black_point", + "thick_white_point", + "thick_brightness", + "thick_contrast", + "thick_use_invert", + "xyzmask_use_preview", + "xyzmask_use_default", + "xyzmask_use_x", + "xyzmask_use_y", + "xyzmask_use_z", + "xyzmask_coverage", + "xyzmask_saturation", + "xyzmask_opacity", + "xyzmask_use_invert", + "gmask_use_preview", + "gmask_use_default", + "gmask_type", + "gmask_location_x", + "gmask_location_y", + "gmask_location_z", + "gmask_rotation_x", + "gmask_rotation_y", + "gmask_rotation_z", + "gmask_scale_x", + "gmask_scale_y", + "gmask_scale_z", + "gmask_coverage", + "gmask_contrast", + "gmask_saturation", + "gmask_opacity", + "gmask_use_invert" + ] + + for index, map in enumerate(context.scene.bm_aol[context.scene.bm_props.active_index].maps): + for prop in map_props_names: + self.preset_values.append("bm_item.maps[%d].%s" % (index, prop)) + + ### *end of custom code block for bakemaster* ### + + for rna_path in self.preset_values: + value = eval(rna_path) + rna_recursive_attr_expand(value, rna_path, 1) + + file_preset.close() + + preset_menu_class.bl_label = bpy.path.display_name(filename) + + # removing preset + else: + if self.remove_active is True: + name = preset_menu_class.bl_label + + # fairly sloppy but convenient. + filepath = bpy.utils.preset_find(name, self.preset_subdir, ext=ext) + + if not filepath: + filepath = bpy.utils.preset_find(name, self.preset_subdir, display_name=True, ext=ext) + + if not filepath: + return {'CANCELLED'} + + # Do not remove bundled presets + if is_path_builtin(filepath): + self.report({'WARNING'}, "Unable to remove default presets") + return {'CANCELLED'} + + try: + if hasattr(self, "remove"): + self.remove(context, filepath) + else: + os.remove(filepath) + except Exception as e: + self.report({'ERROR'}, "Unable to remove preset: %r" % e) + import traceback + traceback.print_exc() + return {'CANCELLED'} + + # XXX, stupid! + preset_menu_class.bl_label = "Presets" + + return {'FINISHED'} + + def check(self, _context): + self.name = self.as_filename(self.name.strip()) + + def invoke(self, context, _event): + if not (self.remove_active or self.remove_name): + wm = context.window_manager + return wm.invoke_props_dialog(self) + else: + return self.execute(context) + +########################################################### +# object configurator preset base +class BM_OT_ObjectConfigutator_Preset_Add(BM_Configurator_AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.objectconfigurator_preset_add" + bl_label = "Add Object Configuration Preset" + bl_description = "Add or Remove Object Configuration Preset" + bl_opetions = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_ObjectConfigurator_Presets' + preset_subdir = 'bakemaster_presets\\object_configurator_presets\\' + + preset_defines = [ + "bm_item = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index]" + ] + + preset_values = [ + # all object settings props + "bm_item.use_bake", + "bm_item.use_target", + "bm_item.use_source", + "bm_item.source", + "bm_item.source_name", + "bm_item.use_cage", + "bm_item.cage_extrusion", + "bm_item.max_ray_distance", + "bm_item.cage_object", + "bm_item.active_uv", + "bm_item.uv_type", + "bm_item.use_islands_pack", + "bm_item.use_overwrite", + "bm_item.overwrite_bake_target", + "bm_item.overwrite_use_denoise", + "bm_item.overwrite_file_format", + "bm_item.overwrite_res_enum", + "bm_item.overwrite_res_height", + "bm_item.overwrite_res_width", + "bm_item.overwrite_margin", + "bm_item.overwrite_margin_type", + "bm_item.overwrite_use_32bit", + "bm_item.overwrite_use_alpha", + "bm_item.overwrite_udim_start_tile", + "bm_item.overwrite_udim_end_tile", + #all bake settings props + "bm_item.use_internal", + "bm_item.output_filepath", + "bm_item.use_subfolder", + "bm_item.subfolder_name", + "bm_item.batch_name", + "bm_item.use_material", + "bm_item.bake_samples", + "bm_item.bake_use_adaptive_sampling", + "bm_item.bake_adaptive_threshold", + "bm_item.bake_min_samples", + "bm_item.bake_device" + ] + +# object settings preset base +class BM_OT_ObjectSettings_Preset_Add(AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.objectsettings_preset_add" + bl_label = "Add Object Settings Preset" + bl_description = "Add or Remove Object Settings Preset" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_ObjectSettings_Presets' + preset_subdir = 'bakemaster_presets\\object_settings_presets\\' + + preset_defines = [ + "bm_item = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index]" + ] + + preset_values = [ + "bm_item.use_target", + "bm_item.use_source", + "bm_item.source", + "bm_item.source_name", + "bm_item.use_cage", + "bm_item.cage_extrusion", + "bm_item.max_ray_distance", + "bm_item.cage_object", + "bm_item.active_uv", + "bm_item.uv_type", + "bm_item.use_islands_pack", + "bm_item.use_overwrite", + "bm_item.overwrite_bake_target", + "bm_item.overwrite_use_denoise", + "bm_item.overwrite_file_format", + "bm_item.overwrite_res_enum", + "bm_item.overwrite_res_height", + "bm_item.overwrite_res_width", + "bm_item.overwrite_margin", + "bm_item.overwrite_margin_type", + "bm_item.overwrite_use_32bit", + "bm_item.overwrite_use_alpha", + "bm_item.overwrite_udim_start_tile", + "bm_item.overwrite_udim_end_tile" + ] + +# stt settings preset base +class BM_OT_STTSettings_Preset_Add(AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.sttsettings_preset_add" + bl_label = "Add Source to Target Settings Preset" + bl_description = "Add or Remove Source to Target Settings Preset" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_STTSettings_Presets' + preset_subdir = 'bakemaster_presets\\stt_settings_presets\\' + + preset_defines = [ + "bm_item = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index]" + ] + + preset_values = [ + "bm_item.use_target", + "bm_item.use_source", + "bm_item.source", + "bm_item.source_name", + "bm_item.use_cage", + "bm_item.cage_extrusion", + "bm_item.max_ray_distance", + "bm_item.cage_object" + ] + +# uv settings preset base +class BM_OT_UVSettings_Preset_Add(AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.uvsettings_preset_add" + bl_label = "Add UV Settings Preset" + bl_description = "Add or Remove UV Settings Preset" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_UVSettings_Presets' + preset_subdir = 'bakemaster_presets\\uv_settings_presets' + + preset_defines = [ + "bm_item = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index]" + ] + + preset_values = [ + "bm_item.active_uv", + "bm_item.uv_type", + "bm_item.use_islands_pack", + "bm_item.use_overwrite", + ] + +# output settings preset base +class BM_OT_OutputSettings_Preset_Add(AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.outputsettings_preset_add" + bl_label = "Add Output Settings Preset" + bl_description = "Add or Remove Output Settings Preset" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_OutputSettings_Presets' + preset_subdir = 'bakemaster_presets\\output_settings_presets\\' + + preset_defines = [ + "bm_item = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index]" + ] + + preset_values = [ + "bm_item.use_overwrite", + "bm_item.overwrite_bake_target", + "bm_item.overwrite_use_denoise", + "bm_item.overwrite_file_format", + "bm_item.overwrite_res_enum", + "bm_item.overwrite_res_height", + "bm_item.overwrite_res_width", + "bm_item.overwrite_margin", + "bm_item.overwrite_margin_type", + "bm_item.overwrite_use_32bit", + "bm_item.overwrite_use_alpha", + "bm_item.overwrite_udim_start_tile", + "bm_item.overwrite_udim_end_tile" + ] + +########################################################### +# map configurator preset base +class BM_OT_MapsConfigutator_Preset_Add(BM_Configurator_AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.mapsconfigurator_preset_add" + bl_label = "Add Maps Configuration Preset" + bl_description = "Add or Remove Maps Configuration Preset" + bl_opetions = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_MapsConfigurator_Presets' + preset_subdir = 'bakemaster_presets\\maps_configurator_presets\\' + + preset_defines = [ + "bm_item = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index]" + ] + + preset_values = [] + +# map settings preset base +class BM_OT_MapSettings_Preset_Add(AddPresetBase, bpy.types.Operator): + bl_idname = "bakemaster.mapsettings_preset_add" + bl_label = "Add Map Settings Preset" + bl_description = "Add or Remove Map Settings Preset" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + preset_menu = 'BM_MT_MapSettings_Presets' + preset_subdir = 'bakemaster_presets\\map_settings_presets\\' + + preset_defines = [ + "bm_map = bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index].maps[bpy.context.scene.bm_aol[bpy.context.scene.bm_props.active_index].maps_active_index]" + ] + + preset_values = [ + "bm_map.bake_target", + "bm_map.use_denoise", + "bm_map.file_format", + "bm_map.res_enum", + "bm_map.res_height", + "bm_map.res_width", + "bm_map.margin", + "bm_map.margin_type", + "bm_map.use_32bit", + "bm_map.use_alpha", + "bm_map.use_source_target", + "bm_map.udim_start_tile", + "bm_map.udim_end_tile", + "bm_map.cycles_use_pass_direct", + "bm_map.cycles_use_pass_indirect", + "bm_map.cycles_use_pass_color", + "bm_map.cycles_use_pass_diffuse", + "bm_map.cycles_use_pass_glossy", + "bm_map.cycles_use_pass_transmission", + "bm_map.cycles_use_pass_ambient_occlusion", + "bm_map.cycles_use_pass_emit", + "bm_map.normal_space", + "bm_map.normal_r", + "bm_map.normal_g", + "bm_map.normal_b", + "bm_map.use_smooth_normals", + "bm_map.normal_cage", + "bm_map.displacement_subdiv_levels", + "bm_map.ao_use_preview", + "bm_map.ao_use_default", + "bm_map.ao_sample", + "bm_map.ao_distance", + "bm_map.ao_black_point", + "bm_map.ao_white_point", + "bm_map.ao_brightness", + "bm_map.ao_contrast", + "bm_map.ao_opacity", + "bm_map.ao_use_local", + "bm_map.ao_use_invert", + "bm_map.cavity_use_preview", + "bm_map.cavity_use_default", + "bm_map.cavity_black_point", + "bm_map.cavity_white_point", + "bm_map.cavity_power", + "bm_map.cavity_use_invert", + "bm_map.curv_use_preview", + "bm_map.curv_use_default", + "bm_map.curv_sample", + "bm_map.curv_radius", + "bm_map.curv_edge_contrast", + "bm_map.curv_body_contrast", + "bm_map.curv_use_invert", + "bm_map.thick_use_preview", + "bm_map.thick_use_default", + "bm_map.thick_samples", + "bm_map.thick_distance", + "bm_map.thick_black_point", + "bm_map.thick_white_point", + "bm_map.thick_brightness", + "bm_map.thick_contrast", + "bm_map.thick_use_invert", + "bm_map.xyzmask_use_preview", + "bm_map.xyzmask_use_default", + "bm_map.xyzmask_use_x", + "bm_map.xyzmask_use_y", + "bm_map.xyzmask_use_z", + "bm_map.xyzmask_coverage", + "bm_map.xyzmask_saturation", + "bm_map.xyzmask_opacity", + "bm_map.xyzmask_use_invert", + "bm_map.gmask_use_preview", + "bm_map.gmask_use_default", + "bm_map.gmask_type", + "bm_map.gmask_location_x", + "bm_map.gmask_location_y", + "bm_map.gmask_location_z", + "bm_map.gmask_rotation_x", + "bm_map.gmask_rotation_y", + "bm_map.gmask_rotation_z", + "bm_map.gmask_scale_x", + "bm_map.gmask_scale_y", + "bm_map.gmask_scale_z", + "bm_map.gmask_coverage", + "bm_map.gmask_contrast", + "bm_map.gmask_saturation", + "bm_map.gmask_opacity", + "bm_map.gmask_use_invert" + ] + +########################################################### # bake settings preset base class BM_OT_BakeSettings_Preset_Add(AddPresetBase, bpy.types.Operator): bl_idname = "bakemaster.bakesettings_preset_add" @@ -88,17 +663,359 @@ class BM_OT_BakeSettings_Preset_Add(AddPresetBase, bpy.types.Operator): ### UI ### +########################################################### +# object configurator preset panel and menu +class BM_PT_ObjectConfigurator_Presets(PresetPanel, bpy.types.Panel): + bl_label = "Object Configurator Preset" + preset_subdir = 'bakemaster_presets\\object_configurator_presets\\' + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.objectconfigurator_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_ObjectConfigurator_Presets' + } +class BM_MT_ObjectConfigurator_Presets(bpy.types.Menu): + bl_label = "Object Configurator Preset" + preset_subdir = 'bakemaster_presets\\object_configurator_presets\\' + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +# object settings preset panel and menu +class BM_PT_ObjectSettings_Presets(PresetPanel, bpy.types.Panel): + bl_label = "Object Settings Preset" + preset_subdir = 'bakemaster_presets\\object_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.objectsettings_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_ObjectSettings_Presets' + } +class BM_MT_ObjectSettings_Presets(bpy.types.Menu): + bl_label = "Object Settings Preset" + preset_subdir = 'bakemaster_presets\\object_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +# stt settings preset panel and menu +class BM_PT_STTSettings_Presets(PresetPanel, bpy.types.Panel): + bl_label = "Source to Target Settings Preset" + preset_subdir = 'bakemaster_presets\\stt_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.sttsettings_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_STTSettings_Presets' + } +class BM_MT_STTSettings_Presets(bpy.types.Menu): + bl_label = "Source to Target Settings Preset" + preset_subdir = 'bakemaster_presets\\stt_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +# uv settings preset panel and menu +class BM_PT_UVSettings_Presets(PresetPanel, bpy.types.Panel): + bl_label = "UV Settings Preset" + preset_subdir = 'bakemaster_presets\\uv_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.uvsettings_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_UVSettings_Presets' + } +class BM_MT_UVSettings_Presets(bpy.types.Menu): + bl_label = "UV Settings Preset" + preset_subdir = 'bakemaster_presets\\uv_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +# output settings preset panel and menu +class BM_PT_OutputSettings_Presets(PresetPanel, bpy.types.Panel): + bl_label = "Output Settings Preset" + preset_subdir = 'bakemaster_presets\\output_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.outputsettings_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_OutputSettings_Presets' + } +class BM_MT_OutputSettings_Presets(bpy.types.Menu): + bl_label = "Output Settings Preset" + preset_subdir = 'bakemaster_presets\\output_settings_presets\\' + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +########################################################### +# map configurator preset panel and menu +class BM_PT_MapsConfigurator_Presets(PresetPanel, bpy.types.Panel): + bl_label = "Maps Configuration Presets" + preset_subdir = "bakemaster_presets\\maps_configurator_presets\\" + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.mapsconfigurator_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_MapsConfigurator_Presets' + } +class BM_MT_MapsConfigurator_Presets(bpy.types.Menu): + bl_label = "Maps Configuration Presets" + preset_subdir = "bakemaster_presets\\maps_configurator_presets\\" + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +# map settings preset panel and menu +class BM_PT_MapSettings_Presets(PresetPanel, bpy.types.Panel): + bl_label = "Map Settings Presets" + preset_subdir = "bakemaster_presets\\map_settings_presets\\" + preset_operator = "script.execute_preset_bakemaster" + preset_add_operator = "bakemaster.mapsettings_preset_add" + preset_operator_defaults = { + "menu_idname" : 'BM_MT_MapSettings_Presets' + } +class BM_MT_MapSettings_Presets(bpy.types.Menu): + bl_label = "Map Settings Presets" + preset_subdir = "bakemaster_presets\\map_settings_presets\\" + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +########################################################### # bake settings preset panel and menu class BM_PT_BakeSettings_Presets(PresetPanel, bpy.types.Panel): bl_label = "Bake Settings Presets" preset_subdir = "bakemaster_presets\\bake_settings_presets\\" - preset_operator = "script.execute_preset" + preset_operator = "script.execute_preset_bakemaster" preset_add_operator = "bakemaster.bakesettings_preset_add" - + preset_operator_defaults = { + "menu_idname" : 'BM_MT_BakeSettings_Presets' + } class BM_MT_BakeSettings_Presets(bpy.types.Menu): bl_label = "Bake Settings Presets" - bl_idname = "BM_MT_BakeSettings_Presets" preset_subdir = "bakemaster_presets\\bake_settings_presets\\" - preset_operator = "script.execute_preset" + preset_operator = "script.execute_preset_bakemaster" + + draw = bpy.types.Menu.draw_preset + +### EXECUTE PRESET ### + +def data_insert_smartprops_source_cage(data) -> list[str]: + # get the preset's source and cage_object value + source_line_index = 0 + source_line = "" + for index, line in enumerate(data): + if line.find("bm_item.source") != -1: + source_line_index = index + source_line = line.strip() + break + source_value = source_line[source_line.find("= ") + 2:] + + cage_line_index = 0 + cage_line = "" + for index, line in enumerate(data): + if line.find("bm_item.cage_object") != -1: + cage_line_index = index + cage_line = line.strip() + break + cage_value = cage_line[cage_line.find("= ") + 2:] + + # checking if use_source and use_cage is true to write data + write_source = False + write_cage = False + for index, line in enumerate(data): + if line.find("bm_item.use_target") != -1: + write_source = data[index].strip()[data[index].find("= ") + 2:] + break + for index, line in enumerate(data): + if line.find("bm_item.use_cage") != -1: + write_cage = data[index].strip()[data[index].find("= ") + 2:] + break + + # writing data + if data[source_line_index - 1].strip() != "try:" and write_source == 'True': + data[source_line_index] = "\nsource_assigned = False\ntry_sources = ['high', 'hpoly', 'high-poly', 'highpoly', 'source']\nfor index, item in enumerate(bpy.context.scene.bm_aol):\n\tif any(name.lower() in item.object_pointer.name.lower() for name in try_sources):\n\t\ttry:\n\t\t\tbm_item.source = str(index)\n\t\texcept (TypeError, KeyError):\n\t\t\tpass\n\t\telse:\n\t\t\tbm_item.source = str(index)\n\t\t\tsource_assigned = True\n\t\t\tbreak\n"#if source_assigned is False:\n\t\ttry:\n\t\t\tbm_item.source = %s\n\t\texcept (TypeError, KeyError):\n\t\t\tpass\n" % source_value + + if data[cage_line_index - 1].strip() != "try:" and write_cage == 'True': + data[cage_line_index] = "\ncage_assinged = False\ntry_cages = ['cage', 'cageobject']\nfor index, obj in enumerate(bpy.context.scene.objects):\n\tif any(name.lower() in obj.name.lower() for name in try_cages):\n\t\ttry:\n\t\t\tbm_item.cage_object = obj\n\t\texcept (TypeError, KeyError):\n\t\t\tpass\n\t\telse:\n\t\t\tbm_item.cage_object = obj\n\t\t\tcage_assinged = True\n\t\t\tbreak\n"#if cage_assinged is False:\n\ttry:\n\t\tbm_item.cage_object = %s\n\texcept (TypeError, KeyError):\n\t\tpass\n" % cage_value + + return data + +def data_insert_smartprops_active_uv(data) -> list[str]: + # get the preset's active_uv value + active_uv_line_index = 0 + active_uv_line = "" + for index, line in enumerate(data): + if line.find("bm_item.active_uv") != -1: + active_uv_line_index = index + active_uv_line = line.strip() + break + active_uv_value = active_uv_line[active_uv_line.find("= ") + 2:] + + # writing data + if data[active_uv_line_index - 1].strip() != "try:": + data[active_uv_line_index] = "\ntry:\n\t%s\nexcept TypeError:\n\tpass\n\n" % active_uv_line + + return data + +def data_insert_smartprops_mapsconfig(data) -> list[str]: + # get the max index of map = aka length of maps + map_index = -1 + for index, line in enumerate(reversed(data)): + if line.find("bm_item.maps[") != -1: + + map_index_temp = int(line.strip()[line.find("bm_item.maps[") + 13:line.find("].")]) + map_index = map_index_temp if map_index_temp > map_index else map_index + break + + # do not add maps if there are none saved to the preset + if map_index == -1: + return data + + insert_line_index = 0 + for index, line in enumerate(data): + if line.strip() == "": + insert_line_index = index + break + + # writing data + if data[insert_line_index - 1].find("bm_item.maps_active_index = len(bm_item.maps) - 1") == -1: + data[insert_line_index] = "to_remove = []\nfor index, map in enumerate(bm_item.maps):\n\tto_remove.append(index)\nfor index in to_remove[::-1]:\n\tbm_item.maps.remove(index)\nbm_item.maps_active_index = 0\nfor index in range(%d + 1):\n\tnew_pass = bm_item.maps.add()\n\tnew_pass.map_type = 'ALBEDO'\n\tbm_item.maps_active_index = len(bm_item.maps) - 1\n\n" % map_index + + return data + +# unused +def data_insert_configurator_affectall(data) -> list[str]: + # stupid, but only this line 1 is possible + data[1] = "for bm_item in bpy.context.scene.bm_aol:\n\tif bm_item.use_source is True:\n\t\tcontinue\n\ttry:\n\t\tbpy.context.scene.objects[bm_item.object_pointer.name]\n\texcept (KeyError, UnboundLocalError):\n\t\tcontinue\n\telse:\n" + + data_copy = data + + for index, line in enumerate(data): + if index >= 2: + data_copy[index] = "\t\t" + line + + return data_copy + +# original from https://developer.blender.org/diffusion/B/browse/master/release/scripts/startup/bl_operators/presets.py%24213 +# for bakemaster presets modified execution +class BM_OT_ExecutePreset(bpy.types.Operator): + """Execute a BakeMaster preset""" + bl_idname = "script.execute_preset_bakemaster" + bl_label = "Load BakeMaster Preset" + bl_options = {'REGISTER', 'UNDO'} + + filepath: bpy.props.StringProperty( + subtype='FILE_PATH', + options={'SKIP_SAVE'}, + ) + menu_idname: bpy.props.StringProperty( + name="Menu ID Name", + description="ID name of the menu this was called from", + options={'SKIP_SAVE'}, + ) + affect_all_objects: bpy.props.BoolProperty( + name="Affect all Objects", + description="Load Preset for every object in the List of Objects\n(every object that is not source already and exists in the scene)", + default=False, + options={'SKIP_SAVE'} + ) + + def execute(self, context): + from os.path import basename, splitext + filepath = self.filepath + + # change the menu title to the most recently chosen option + preset_class = getattr(bpy.types, self.menu_idname) + preset_class.bl_label = bpy.path.display_name(basename(filepath), title_case=False) + + ext = splitext(filepath)[1].lower() + + if ext not in {".py", ".xml"}: + self.report({'ERROR'}, "Unknown file type: %r" % ext) + return {'CANCELLED'} + + if hasattr(preset_class, "reset_cb"): + preset_class.reset_cb(context) + + if ext == ".py": + ### SMART PRESETS ### + # adding custom script to presest file + # for specific props proper execution + ### + menus_ids = ["BM_MT_ObjectConfigurator_Presets", + "BM_MT_ObjectSettings_Presets", + "BM_MT_STTSettings_Presets", + "BM_MT_UVSettings_Presets", + "BM_MT_MapsConfigurator_Presets"] + if self.menu_idname in menus_ids: + with open(filepath, 'r') as preset_file: + data = preset_file.readlines() + + # Object preset uv + stt props + if self.menu_idname in ["BM_MT_ObjectSettings_Presets", "BM_MT_ObjectConfigurator_Presets"]: + data = data_insert_smartprops_source_cage(data) + data = data_insert_smartprops_active_uv(data) + + # STT preset source and cage_object props + # try except for TypeError when assigning source + # smart source and cage_object assignment + if self.menu_idname == "BM_MT_STTSettings_Presets": + data = data_insert_smartprops_source_cage(data) + + # UV preset active_uv prop + # try except for avoiding TypeError when assigning active_uv + if self.menu_idname == "BM_MT_UVSettings_Presets": + data = data_insert_smartprops_active_uv(data) + + # Maps Configurator prest adding maps and props + if self.menu_idname in ["BM_MT_MapsConfigurator_Presets", "BM_MT_ObjectConfigurator_Presets"]: + data = data_insert_smartprops_mapsconfig(data) + + with open(filepath, 'w') as preset_file: + preset_file.writelines(data) + ### + + try: + # load preset for every object in the aol + # execute preset, change item index, execute again, ... + if self.menu_idname == "BM_MT_ObjectConfigurator_Presets" and self.affect_all_objects is True: + for index, bm_item in enumerate(context.scene.bm_aol): + if bm_item.use_source: + continue + try: + context.scene.objects[bm_item.object_pointer.name] + except (KeyError, UnboundLocalError): + continue + else: + context.scene.bm_props.active_index = index + bpy.utils.execfile(filepath) + + else: + bpy.utils.execfile(filepath) + + except Exception as ex: + self.report({'ERROR'}, "Failed to execute the preset: " + repr(ex)) + + elif ext == ".xml": + import rna_xml + rna_xml.xml_file_run(context, + filepath, + preset_class.preset_xml_map) + + if hasattr(preset_class, "post_cb"): + preset_class.post_cb(context) + + return {'FINISHED'} + + def draw(self, context): + from os.path import basename + + layout = self.layout + layout.label(text="Preset: %s" % basename(self.filepath)) + layout.separator(factor=0.35) + if self.menu_idname == "BM_MT_ObjectConfigurator_Presets": + layout.prop(self, "affect_all_objects") - draw = bpy.types.Menu.draw_preset \ No newline at end of file + def invoke(self, context, event): + wm = context.window_manager + # if self.menu_idname == "BM_MT_ObjectConfigurator_Presets": + return wm.invoke_props_dialog(self, width=400) + # else: + # return self.execute(context) diff --git a/ui_panel_base.py b/ui_panel_base.py index a289b4d..d8bd04d 100644 --- a/ui_panel_base.py +++ b/ui_panel_base.py @@ -79,13 +79,13 @@ def draw(self, context): row.scale_y = 1.15 refresh = False - min_rows = 3 + min_rows = 4 for index, item in enumerate(scene.bm_aol): try: scene.objects[item.object_pointer.name] except KeyError: refresh = True - min_rows = 4 + min_rows = 5 break if len(scene.bm_aol) > min_rows: @@ -103,6 +103,11 @@ def draw(self, context): if refresh: col.operator(BM_OT_AOL_Refresh.bl_idname, text="", icon='FILE_REFRESH') col.separator() + if len(scene.bm_aol): + item = BM_ITEM_Get(context) + if item[1] is True and item[0].use_source is False: + BM_PT_ObjectConfigurator_Presets.draw_panel_header(col) + col.separator() col.operator(BM_OT_AOL_Trash.bl_idname, text="", icon='TRASH') class BM_PT_ItemBase(bpy.types.Panel): @@ -122,6 +127,11 @@ def draw_header(self, context): else: self.layout.label(text="Item not found", icon='GHOST_DISABLED') + def draw_header_preset(self, context): + item = BM_ITEM_Get(context) + if item[1] is True and item[0].use_source is False: + BM_PT_ObjectSettings_Presets.draw_panel_header(self.layout) + def draw(self, context): pass @@ -137,6 +147,11 @@ def poll(cls, context): def draw_header(self, context): self.layout.label(text="Source to Target")#, icon='FILE_TICK') + def draw_header_preset(self, context): + item = BM_ITEM_Get(context) + if item[1] is True and item[0].use_source is False: + BM_PT_STTSettings_Presets.draw_panel_header(self.layout) + def draw(self, context): layout = self.layout layout.use_property_split = True @@ -178,6 +193,9 @@ def poll(cls, context): def draw_header(self, context): self.layout.label(text="UV Maps")#, icon='GROUP_UVS') + def draw_header_preset(self, context): + BM_PT_UVSettings_Presets.draw_panel_header(self.layout) + def draw(self, context): layout = self.layout layout.use_property_split = True @@ -218,6 +236,9 @@ def poll(cls, context): def draw_header(self, context): self.layout.label(text="Output")#, icon='OUTPUT') + def draw_header_preset(self, context): + BM_PT_OutputSettings_Presets.draw_panel_header(self.layout) + def draw(self, context): layout = self.layout layout.use_property_split = True @@ -285,16 +306,18 @@ def draw(self, context): box = layout.box() row = box.row() - if len(item.maps) > 3: + if len(item.maps) > 4: rows = len(item.maps) else: - rows = 3 + rows = 4 row.template_list('BM_UL_ITEM_Maps', "", item, 'maps', item, 'maps_active_index', rows = rows) col = row.column(align=True) col.operator(BM_OT_ITEM_Maps.bl_idname, text="", icon='ADD').control = 'ADD' col.operator(BM_OT_ITEM_Maps.bl_idname, text="", icon='REMOVE').control = 'REMOVE' col.separator(factor=1.0) + BM_PT_MapsConfigurator_Presets.draw_panel_header(col) + col.separator(factor=1.0) col.operator(BM_OT_ITEM_Maps.bl_idname, text="", icon='TRASH').control = 'TRASH' class BM_PT_Item_MapBase(bpy.types.Panel): @@ -316,6 +339,9 @@ def draw_header(self, context): if label.find('_c_') != -1: label = "Cycles " + label[3:] self.layout.label(text=f"{label} settings")#, icon='IMAGE_DATA') + + def draw_header_preset(self, context): + BM_PT_MapSettings_Presets.draw_panel_header(self.layout) def draw(self, context): layout = self.layout @@ -535,17 +561,14 @@ class BM_PT_Item_MainBakeBase(bpy.types.Panel): bl_idname = "BM_PT_Item_MainBakeBase" bl_options = {'DEFAULT_CLOSED'} - _preset_class = None - @classmethod def poll(cls, context): return len(context.scene.bm_aol) def draw_header_preset(self, context): - BM_PT_BakeSettings_Presets.draw_panel_header(self.layout) - - if BM_PT_Item_MainBakeBase._preset_class is None: - BM_PT_Item_MainBakeBase._preset_class = bpy.types.BM_MT_BakeSettings_Presets + item = BM_ITEM_Get(context) + if item[1] is True and item[0].use_source is False: + BM_PT_BakeSettings_Presets.draw_panel_header(self.layout) def draw_header(self, context): self.layout.label(text="Bake Settings", icon='RENDER_STILL')