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