# SPDX-License-Identifier: GPL-2.0-or-later # Authors: nfloyd, Francesco Siddi import gzip import os import pickle import shutil import bpy from bpy.types import Operator from bpy.props import ( BoolProperty, StringProperty, ) from bpy_extras.io_utils import ( ExportHelper, ImportHelper, ) from .import bl_info from .core import get_preferences from .operators import DataStore # TODO: reinstate filters? class IO_Utils(): @staticmethod def get_preset_path(): # locate stored_views preset folder paths = bpy.utils.preset_paths("stored_views") if not paths: # stored_views preset folder doesn't exist, so create it paths = [os.path.join(bpy.utils.user_resource('SCRIPTS'), "presets", "stored_views")] if not os.path.exists(paths[0]): os.makedirs(paths[0]) return(paths) @staticmethod def stored_views_apply_from_scene(scene_name, replace=True): scene = bpy.context.scene scene_exists = True if scene_name in bpy.data.scenes.keys() else False if scene_exists: sv = bpy.context.scene.stored_views # io_filters = sv.settings.io_filters structs = [sv.view_list, sv.pov_list, sv.layers_list, sv.display_list] if replace is True: for st in structs: # clear swap and list while len(st) > 0: st.remove(0) f_sv = bpy.data.scenes[scene_name].stored_views # f_sv = bpy.data.scenes[scene_name].stored_views f_structs = [f_sv.view_list, f_sv.pov_list, f_sv.layers_list, f_sv.display_list] """ is_filtered = [io_filters.views, io_filters.point_of_views, io_filters.layers, io_filters.displays] """ for i in range(len(f_structs)): """ if is_filtered[i] is False: continue """ for j in f_structs[i]: item = structs[i].add() # stored_views_copy_item(j, item) for k, v in j.items(): item[k] = v DataStore.sanitize_data(scene) return True else: return False @staticmethod def stored_views_export_to_blsv(filepath, name='Custom Preset'): # create dictionary with all information dump = {"info": {}, "data": {}} dump["info"]["script"] = bl_info['name'] dump["info"]["script_version"] = bl_info['version'] dump["info"]["version"] = bpy.app.version dump["info"]["preset_name"] = name # get current stored views settings scene = bpy.context.scene sv = scene.stored_views def dump_view_list(dict, list): if str(type(list)) == "": for i, struct_dict in enumerate(list): dict[i] = {"name": str, "pov": {}, "layers": {}, "display": {}} dict[i]["name"] = struct_dict.name dump_item(dict[i]["pov"], struct_dict.pov) dump_item(dict[i]["layers"], struct_dict.layers) dump_item(dict[i]["display"], struct_dict.display) def dump_list(dict, list): if str(type(list)) == "": for i, struct in enumerate(list): dict[i] = {} dump_item(dict[i], struct) def dump_item(dict, struct): for prop in struct.bl_rna.properties: if prop.identifier == "rna_type": # not a setting, so skip continue val = getattr(struct, prop.identifier) if str(type(val)) in [""]: # array dict[prop.identifier] = [v for v in val] # address the pickle limitations of dealing with the Vector class elif str(type(val)) in ["", ""]: dict[prop.identifier] = [v for v in val] else: # single value dict[prop.identifier] = val # io_filters = sv.settings.io_filters dump["data"] = {"point_of_views": {}, "layers": {}, "displays": {}, "views": {}} others_data = [(dump["data"]["point_of_views"], sv.pov_list), # , io_filters.point_of_views), (dump["data"]["layers"], sv.layers_list), # , io_filters.layers), (dump["data"]["displays"], sv.display_list)] # , io_filters.displays)] for list_data in others_data: # if list_data[2] is True: dump_list(list_data[0], list_data[1]) views_data = (dump["data"]["views"], sv.view_list) # if io_filters.views is True: dump_view_list(views_data[0], views_data[1]) # save to file filepath = filepath filepath = bpy.path.ensure_ext(filepath, '.blsv') file = gzip.open(filepath, mode='wb') pickle.dump(dump, file, protocol=pickle.HIGHEST_PROTOCOL) file.close() @staticmethod def stored_views_apply_preset(filepath, replace=True): if not filepath: return False file = gzip.open(filepath, mode='rb') dump = pickle.load(file) file.close() # apply preset scene = bpy.context.scene sv = getattr(scene, "stored_views", None) if not sv: return False # io_filters = sv.settings.io_filters sv_data = { "point_of_views": sv.pov_list, "views": sv.view_list, "layers": sv.layers_list, "displays": sv.display_list } for sv_struct, props in dump["data"].items(): """ is_filtered = getattr(io_filters, sv_struct) if is_filtered is False: continue """ sv_list = sv_data[sv_struct] # .list if replace is True: # clear swap and list while len(sv_list) > 0: sv_list.remove(0) for key, prop_struct in props.items(): sv_item = sv_list.add() for subprop, subval in prop_struct.items(): if isinstance(subval, dict): # views : pov, layers, displays v_subprop = getattr(sv_item, subprop) for v_subkey, v_subval in subval.items(): if isinstance(v_subval, list): # array like of pov,... v_array_like = getattr(v_subprop, v_subkey) for i in range(len(v_array_like)): v_array_like[i] = v_subval[i] else: setattr(v_subprop, v_subkey, v_subval) # others elif isinstance(subval, list): array_like = getattr(sv_item, subprop) for i in range(len(array_like)): array_like[i] = subval[i] else: setattr(sv_item, subprop, subval) DataStore.sanitize_data(scene) return True class VIEW3D_stored_views_import(Operator, ImportHelper): bl_idname = "stored_views.import_blsv" bl_label = "Import Stored Views preset" bl_description = "Import a .blsv preset file to the current Stored Views" filename_ext = ".blsv" filter_glob: StringProperty( default="*.blsv", options={'HIDDEN'} ) replace: BoolProperty( name="Replace", default=True, description="Replace current stored views, otherwise append" ) @classmethod def poll(cls, context): return get_preferences() def execute(self, context): # the usual way is to not select the file in the file browser exists = os.path.isfile(self.filepath) if self.filepath else False if not exists: self.report({'WARNING'}, "No filepath specified or file could not be found. Operation Cancelled") return {'CANCELLED'} # apply chosen preset apply_preset = IO_Utils.stored_views_apply_preset( filepath=self.filepath, replace=self.replace ) if not apply_preset: self.report({'WARNING'}, "Please Initialize Stored Views first (in the 3D View Properties Area)") return {'CANCELLED'} # copy preset to presets folder filename = os.path.basename(self.filepath) try: shutil.copyfile(self.filepath, os.path.join(IO_Utils.get_preset_path()[0], filename)) except: self.report({'WARNING'}, "Stored Views: preset applied, but installing failed (preset already exists?)") return{'CANCELLED'} return{'FINISHED'} class VIEW3D_stored_views_import_from_scene(Operator): bl_idname = "stored_views.import_from_scene" bl_label = "Import stored views from scene" bl_description = "Import currently stored views from an another scene" scene_name: StringProperty( name="Scene Name", description="A current blend scene", default="" ) replace: BoolProperty( name="Replace", default=True, description="Replace current stored views, otherwise append" ) @classmethod def poll(cls, context): return get_preferences() def draw(self, context): layout = self.layout layout.prop_search(self, "scene_name", bpy.data, "scenes") layout.prop(self, "replace") def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def execute(self, context): # filepath should always be given if not self.scene_name: self.report({"WARNING"}, "No scene name was given. Operation Cancelled") return{'CANCELLED'} is_finished = IO_Utils.stored_views_apply_from_scene( self.scene_name, replace=self.replace ) if not is_finished: self.report({"WARNING"}, "Could not find the specified scene. Operation Cancelled") return {"CANCELLED"} return{'FINISHED'} class VIEW3D_stored_views_export(Operator, ExportHelper): bl_idname = "stored_views.export_blsv" bl_label = "Export Stored Views preset" bl_description = "Export the current Stored Views to a .blsv preset file" filename_ext = ".blsv" filepath: StringProperty( default=os.path.join(IO_Utils.get_preset_path()[0], "untitled") ) filter_glob: StringProperty( default="*.blsv", options={'HIDDEN'} ) preset_name: StringProperty( name="Preset name", default="", description="Name of the stored views preset" ) @classmethod def poll(cls, context): return get_preferences() def execute(self, context): IO_Utils.stored_views_export_to_blsv(self.filepath, self.preset_name) return{'FINISHED'} classes = ( VIEW3D_stored_views_import, VIEW3D_stored_views_import_from_scene, VIEW3D_stored_views_export ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in classes: bpy.utils.unregister_class(cls)