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