1# gpl authors: nfloyd, Francesco Siddi 2 3import gzip 4import os 5import pickle 6import shutil 7 8import bpy 9from bpy.types import Operator 10from bpy.props import ( 11 BoolProperty, 12 StringProperty, 13) 14from bpy_extras.io_utils import ( 15 ExportHelper, 16 ImportHelper, 17) 18from .import bl_info 19from .core import get_preferences 20from .operators import DataStore 21 22 23# TODO: reinstate filters? 24class IO_Utils(): 25 26 @staticmethod 27 def get_preset_path(): 28 # locate stored_views preset folder 29 paths = bpy.utils.preset_paths("stored_views") 30 if not paths: 31 # stored_views preset folder doesn't exist, so create it 32 paths = [os.path.join(bpy.utils.user_resource('SCRIPTS'), "presets", 33 "stored_views")] 34 if not os.path.exists(paths[0]): 35 os.makedirs(paths[0]) 36 37 return(paths) 38 39 @staticmethod 40 def stored_views_apply_from_scene(scene_name, replace=True): 41 scene = bpy.context.scene 42 scene_exists = True if scene_name in bpy.data.scenes.keys() else False 43 44 if scene_exists: 45 sv = bpy.context.scene.stored_views 46 # io_filters = sv.settings.io_filters 47 48 structs = [sv.view_list, sv.pov_list, sv.layers_list, sv.display_list] 49 if replace is True: 50 for st in structs: # clear swap and list 51 while len(st) > 0: 52 st.remove(0) 53 54 f_sv = bpy.data.scenes[scene_name].stored_views 55 # f_sv = bpy.data.scenes[scene_name].stored_views 56 f_structs = [f_sv.view_list, f_sv.pov_list, f_sv.layers_list, f_sv.display_list] 57 """ 58 is_filtered = [io_filters.views, io_filters.point_of_views, 59 io_filters.layers, io_filters.displays] 60 """ 61 for i in range(len(f_structs)): 62 """ 63 if is_filtered[i] is False: 64 continue 65 """ 66 for j in f_structs[i]: 67 item = structs[i].add() 68 # stored_views_copy_item(j, item) 69 for k, v in j.items(): 70 item[k] = v 71 DataStore.sanitize_data(scene) 72 return True 73 else: 74 return False 75 76 @staticmethod 77 def stored_views_export_to_blsv(filepath, name='Custom Preset'): 78 # create dictionary with all information 79 dump = {"info": {}, "data": {}} 80 dump["info"]["script"] = bl_info['name'] 81 dump["info"]["script_version"] = bl_info['version'] 82 dump["info"]["version"] = bpy.app.version 83 dump["info"]["preset_name"] = name 84 85 # get current stored views settings 86 scene = bpy.context.scene 87 sv = scene.stored_views 88 89 def dump_view_list(dict, list): 90 if str(type(list)) == "<class 'bpy_prop_collection_idprop'>": 91 for i, struct_dict in enumerate(list): 92 dict[i] = {"name": str, 93 "pov": {}, 94 "layers": {}, 95 "display": {}} 96 dict[i]["name"] = struct_dict.name 97 dump_item(dict[i]["pov"], struct_dict.pov) 98 dump_item(dict[i]["layers"], struct_dict.layers) 99 dump_item(dict[i]["display"], struct_dict.display) 100 101 def dump_list(dict, list): 102 if str(type(list)) == "<class 'bpy_prop_collection_idprop'>": 103 for i, struct in enumerate(list): 104 dict[i] = {} 105 dump_item(dict[i], struct) 106 107 def dump_item(dict, struct): 108 for prop in struct.bl_rna.properties: 109 if prop.identifier == "rna_type": 110 # not a setting, so skip 111 continue 112 113 val = getattr(struct, prop.identifier) 114 if str(type(val)) in ["<class 'bpy_prop_array'>"]: 115 # array 116 dict[prop.identifier] = [v for v in val] 117 # address the pickle limitations of dealing with the Vector class 118 elif str(type(val)) in ["<class 'Vector'>", 119 "<class 'Quaternion'>"]: 120 dict[prop.identifier] = [v for v in val] 121 else: 122 # single value 123 dict[prop.identifier] = val 124 125 # io_filters = sv.settings.io_filters 126 dump["data"] = {"point_of_views": {}, 127 "layers": {}, 128 "displays": {}, 129 "views": {}} 130 131 others_data = [(dump["data"]["point_of_views"], sv.pov_list), # , io_filters.point_of_views), 132 (dump["data"]["layers"], sv.layers_list), # , io_filters.layers), 133 (dump["data"]["displays"], sv.display_list)] # , io_filters.displays)] 134 for list_data in others_data: 135 # if list_data[2] is True: 136 dump_list(list_data[0], list_data[1]) 137 138 views_data = (dump["data"]["views"], sv.view_list) 139 # if io_filters.views is True: 140 dump_view_list(views_data[0], views_data[1]) 141 142 # save to file 143 filepath = filepath 144 filepath = bpy.path.ensure_ext(filepath, '.blsv') 145 file = gzip.open(filepath, mode='wb') 146 pickle.dump(dump, file, protocol=pickle.HIGHEST_PROTOCOL) 147 file.close() 148 149 @staticmethod 150 def stored_views_apply_preset(filepath, replace=True): 151 if not filepath: 152 return False 153 154 file = gzip.open(filepath, mode='rb') 155 dump = pickle.load(file) 156 file.close() 157 # apply preset 158 scene = bpy.context.scene 159 sv = getattr(scene, "stored_views", None) 160 161 if not sv: 162 return False 163 164 # io_filters = sv.settings.io_filters 165 sv_data = { 166 "point_of_views": sv.pov_list, 167 "views": sv.view_list, 168 "layers": sv.layers_list, 169 "displays": sv.display_list 170 } 171 for sv_struct, props in dump["data"].items(): 172 """ 173 is_filtered = getattr(io_filters, sv_struct) 174 if is_filtered is False: 175 continue 176 """ 177 sv_list = sv_data[sv_struct] # .list 178 if replace is True: # clear swap and list 179 while len(sv_list) > 0: 180 sv_list.remove(0) 181 for key, prop_struct in props.items(): 182 sv_item = sv_list.add() 183 184 for subprop, subval in prop_struct.items(): 185 if isinstance(subval, dict): # views : pov, layers, displays 186 v_subprop = getattr(sv_item, subprop) 187 for v_subkey, v_subval in subval.items(): 188 if isinstance(v_subval, list): # array like of pov,... 189 v_array_like = getattr(v_subprop, v_subkey) 190 for i in range(len(v_array_like)): 191 v_array_like[i] = v_subval[i] 192 else: 193 setattr(v_subprop, v_subkey, v_subval) # others 194 elif isinstance(subval, list): 195 array_like = getattr(sv_item, subprop) 196 for i in range(len(array_like)): 197 array_like[i] = subval[i] 198 else: 199 setattr(sv_item, subprop, subval) 200 201 DataStore.sanitize_data(scene) 202 203 return True 204 205 206class VIEW3D_stored_views_import(Operator, ImportHelper): 207 bl_idname = "stored_views.import_blsv" 208 bl_label = "Import Stored Views preset" 209 bl_description = "Import a .blsv preset file to the current Stored Views" 210 211 filename_ext = ".blsv" 212 filter_glob: StringProperty( 213 default="*.blsv", 214 options={'HIDDEN'} 215 ) 216 replace: BoolProperty( 217 name="Replace", 218 default=True, 219 description="Replace current stored views, otherwise append" 220 ) 221 222 @classmethod 223 def poll(cls, context): 224 return get_preferences() 225 226 def execute(self, context): 227 # the usual way is to not select the file in the file browser 228 exists = os.path.isfile(self.filepath) if self.filepath else False 229 if not exists: 230 self.report({'WARNING'}, 231 "No filepath specified or file could not be found. Operation Cancelled") 232 return {'CANCELLED'} 233 234 # apply chosen preset 235 apply_preset = IO_Utils.stored_views_apply_preset( 236 filepath=self.filepath, replace=self.replace 237 ) 238 if not apply_preset: 239 self.report({'WARNING'}, 240 "Please Initialize Stored Views first (in the 3D View Properties Area)") 241 return {'CANCELLED'} 242 243 # copy preset to presets folder 244 filename = os.path.basename(self.filepath) 245 try: 246 shutil.copyfile(self.filepath, 247 os.path.join(IO_Utils.get_preset_path()[0], filename)) 248 except: 249 self.report({'WARNING'}, 250 "Stored Views: preset applied, but installing failed (preset already exists?)") 251 return{'CANCELLED'} 252 253 return{'FINISHED'} 254 255 256class VIEW3D_stored_views_import_from_scene(Operator): 257 bl_idname = "stored_views.import_from_scene" 258 bl_label = "Import stored views from scene" 259 bl_description = "Import currently stored views from an another scene" 260 261 scene_name: StringProperty( 262 name="Scene Name", 263 description="A current blend scene", 264 default="" 265 ) 266 replace: BoolProperty( 267 name="Replace", 268 default=True, 269 description="Replace current stored views, otherwise append" 270 ) 271 272 @classmethod 273 def poll(cls, context): 274 return get_preferences() 275 276 def draw(self, context): 277 layout = self.layout 278 279 layout.prop_search(self, "scene_name", bpy.data, "scenes") 280 layout.prop(self, "replace") 281 282 def invoke(self, context, event): 283 return context.window_manager.invoke_props_dialog(self) 284 285 def execute(self, context): 286 # filepath should always be given 287 if not self.scene_name: 288 self.report({"WARNING"}, 289 "No scene name was given. Operation Cancelled") 290 return{'CANCELLED'} 291 292 is_finished = IO_Utils.stored_views_apply_from_scene( 293 self.scene_name, replace=self.replace 294 ) 295 if not is_finished: 296 self.report({"WARNING"}, 297 "Could not find the specified scene. Operation Cancelled") 298 return {"CANCELLED"} 299 300 return{'FINISHED'} 301 302 303class VIEW3D_stored_views_export(Operator, ExportHelper): 304 bl_idname = "stored_views.export_blsv" 305 bl_label = "Export Stored Views preset" 306 bl_description = "Export the current Stored Views to a .blsv preset file" 307 308 filename_ext = ".blsv" 309 filepath: StringProperty( 310 default=os.path.join(IO_Utils.get_preset_path()[0], "untitled") 311 ) 312 filter_glob: StringProperty( 313 default="*.blsv", 314 options={'HIDDEN'} 315 ) 316 preset_name: StringProperty( 317 name="Preset name", 318 default="", 319 description="Name of the stored views preset" 320 ) 321 322 @classmethod 323 def poll(cls, context): 324 return get_preferences() 325 326 def execute(self, context): 327 IO_Utils.stored_views_export_to_blsv(self.filepath, self.preset_name) 328 329 return{'FINISHED'} 330 331classes = ( 332 VIEW3D_stored_views_import, 333 VIEW3D_stored_views_import_from_scene, 334 VIEW3D_stored_views_export 335) 336 337def register(): 338 for cls in classes: 339 bpy.utils.register_class(cls) 340 341def unregister(): 342 for cls in classes: 343 bpy.utils.unregister_class(cls) 344