Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-06-15 03:59:45 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-06-15 03:59:45 +0300
commit20a8e9d3f58540cc85da396ca98ee0831903dd23 (patch)
treeef44bd591e72cba01cce178bedab31e09b4e3345 /space_view3d_stored_views
parent15b00e9396dfdf955fa1122d2464d5e96f2810b6 (diff)
space_view3d_stored_views: Restore release status: T63750 d9ffde707fe5
Diffstat (limited to 'space_view3d_stored_views')
-rw-r--r--space_view3d_stored_views/__init__.py149
-rw-r--r--space_view3d_stored_views/core.py369
-rw-r--r--space_view3d_stored_views/io.py343
-rw-r--r--space_view3d_stored_views/operators.py182
-rw-r--r--space_view3d_stored_views/properties.py137
-rw-r--r--space_view3d_stored_views/stored_views_test.py1371
-rw-r--r--space_view3d_stored_views/ui.py277
7 files changed, 2828 insertions, 0 deletions
diff --git a/space_view3d_stored_views/__init__.py b/space_view3d_stored_views/__init__.py
new file mode 100644
index 00000000..b438cdbf
--- /dev/null
+++ b/space_view3d_stored_views/__init__.py
@@ -0,0 +1,149 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Stored Views",
+ "description": "Save and restore User defined views, pov, layers and display configs",
+ "author": "nfloyd, Francesco Siddi",
+ "version": (0, 3, 7),
+ "blender": (2, 80, 0),
+ "location": "View3D > Properties > Stored Views",
+ "warning": "",
+ "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.5/"
+ "Py/Scripts/3D_interaction/stored_views",
+ "category": "3D View"
+}
+
+"""
+ACKNOWLEDGMENT
+==============
+import/export functionality is mostly based
+on Bart Crouch's Theme Manager Addon
+
+TODO: quadview complete support : investigate. Where's the data?
+TODO: lock_camera_and_layers. investigate usage
+TODO: list reordering
+
+NOTE: logging setup has to be provided by the user in a separate config file
+ as Blender will not try to configure logging by default in an add-on
+ The Config File should be in the Blender Config folder > /scripts/startup/config_logging.py
+ For setting up /location of the config folder see:
+ https://docs.blender.org/manual/en/dev/getting_started/
+ installing/configuration/directories.html
+ For configuring logging itself in the file, general Python documentation should work
+ As the logging calls are not configured, they can be kept in the other modules of this add-on
+ and will not have output until the logging configuration is set up
+"""
+
+# if "bpy" in locals():
+# import importlib
+# importlib.reload(core)
+# importlib.reload(ui)
+# importlib.reload(properties)
+# importlib.reload(operators)
+# importlib.reload(io)
+# else:
+import bpy
+from . import core
+from . import ui
+from . import properties
+from . import operators
+from . import io
+
+
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ PointerProperty,
+)
+from bpy.types import (
+ AddonPreferences,
+ Operator,
+)
+
+
+class VIEW3D_stored_views_initialize(Operator):
+ bl_idname = "view3d.stored_views_initialize"
+ bl_label = "Initialize"
+
+ @classmethod
+ def poll(cls, context):
+ return not hasattr(bpy.types.Scene, 'stored_views')
+
+ def execute(self, context):
+ bpy.types.Scene.stored_views = PointerProperty(
+ type=properties.StoredViewsData
+ )
+ scenes = bpy.data.scenes
+ for scene in scenes:
+ core.DataStore.sanitize_data(scene)
+ return {'FINISHED'}
+
+
+# Addon Preferences
+
+class VIEW3D_stored_views_preferences(AddonPreferences):
+ bl_idname = __name__
+
+ show_exporters : BoolProperty(
+ name="Enable I/O Operators",
+ default=False,
+ description="Enable Import/Export Operations in the UI:\n"
+ "Import Stored Views preset,\n"
+ "Export Stored Views preset and \n"
+ "Import stored views from scene",
+ )
+ view_3d_update_rate : IntProperty(
+ name="3D view update",
+ description="Update rate of the 3D view redraw\n"
+ "Increse the value if the UI feels sluggish",
+ min=1, max=10,
+ default=1
+ )
+
+ def draw(self, context):
+ layout = self.layout
+
+ row = layout.row(align=True)
+ row.prop(self, "view_3d_update_rate", toggle=True)
+ row.prop(self, "show_exporters", toggle=True)
+
+
+def register():
+ ui.register()
+ properties.register()
+ operators.register()
+ io.register()
+ bpy.utils.register_class(VIEW3D_stored_views_initialize)
+ bpy.utils.register_class(VIEW3D_stored_views_preferences)
+
+
+def unregister():
+ ui.unregister()
+ properties.unregister()
+ operators.unregister()
+ io.unregister()
+ bpy.utils.unregister_class(VIEW3D_stored_views_initialize)
+ bpy.utils.unregister_class(VIEW3D_stored_views_preferences)
+ ui.VIEW3D_stored_views_draw.handle_remove(bpy.context)
+ if hasattr(bpy.types.Scene, "stored_views"):
+ del bpy.types.Scene.stored_views
+
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_stored_views/core.py b/space_view3d_stored_views/core.py
new file mode 100644
index 00000000..5360b4bb
--- /dev/null
+++ b/space_view3d_stored_views/core.py
@@ -0,0 +1,369 @@
+# gpl authors: nfloyd, Francesco Siddi
+
+
+import logging
+module_logger = logging.getLogger(__name__)
+
+import hashlib
+import bpy
+
+
+#Utility function get preferences setting for exporters
+def get_preferences():
+ # replace the key if the add-on name changes
+ addon = bpy.context.preferences.addons[__package__]
+ show_warn = (addon.preferences.show_exporters if addon else False)
+
+ return show_warn
+
+
+class StoredView():
+ def __init__(self, mode, index=None):
+ self.logger = logging.getLogger('%s.StoredView' % __name__)
+ self.scene = bpy.context.scene
+ self.view3d = bpy.context.space_data
+ self.index = index
+ self.data_store = DataStore(mode=mode)
+
+ def save(self):
+ if self.index == -1:
+ stored_view, self.index = self.data_store.create()
+ else:
+ stored_view = self.data_store.get(self.index)
+ self.from_v3d(stored_view)
+ self.logger.debug('index: %s name: %s' % (self.data_store.current_index, stored_view.name))
+
+ def set(self):
+ stored_view = self.data_store.get(self.index)
+ self.update_v3d(stored_view)
+ self.logger.debug('index: %s name: %s' % (self.data_store.current_index, stored_view.name))
+
+ def from_v3d(self, stored_view):
+ raise NotImplementedError("Subclass must implement abstract method")
+
+ def update_v3d(self, stored_view):
+ raise NotImplementedError("Subclass must implement abstract method")
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ raise NotImplementedError("Subclass must implement abstract method")
+
+
+class POV(StoredView):
+ def __init__(self, index=None):
+ super().__init__(mode='POV', index=index)
+ self.logger = logging.getLogger('%s.POV' % __name__)
+
+ def from_v3d(self, stored_view):
+ view3d = self.view3d
+ region3d = view3d.region_3d
+
+ stored_view.distance = region3d.view_distance
+ stored_view.location = region3d.view_location
+ stored_view.rotation = region3d.view_rotation
+ stored_view.perspective_matrix_md5 = POV._get_perspective_matrix_md5(region3d)
+ stored_view.perspective = region3d.view_perspective
+ stored_view.lens = view3d.lens
+ stored_view.clip_start = view3d.clip_start
+ stored_view.clip_end = view3d.clip_end
+
+ if region3d.view_perspective == 'CAMERA':
+ stored_view.camera_type = view3d.camera.type # type : 'CAMERA' or 'MESH'
+ stored_view.camera_name = view3d.camera.name # store string instead of object
+ if view3d.lock_object is not None:
+ stored_view.lock_object_name = view3d.lock_object.name # idem
+ else:
+ stored_view.lock_object_name = ""
+ stored_view.lock_cursor = view3d.lock_cursor
+ stored_view.cursor_location = bpy.context.scene.cursor.location
+
+ def update_v3d(self, stored_view):
+ view3d = self.view3d
+ region3d = view3d.region_3d
+ region3d.view_distance = stored_view.distance
+ region3d.view_location = stored_view.location
+ region3d.view_rotation = stored_view.rotation
+ region3d.view_perspective = stored_view.perspective
+ view3d.lens = stored_view.lens
+ view3d.clip_start = stored_view.clip_start
+ view3d.clip_end = stored_view.clip_end
+ view3d.lock_cursor = stored_view.lock_cursor
+ if stored_view.lock_cursor is True:
+ # update cursor only if view is locked to cursor
+ view3d.cursor_location = stored_view.cursor_location
+
+ if stored_view.perspective == "CAMERA":
+
+ lock_obj = self._get_object(stored_view.lock_object_name)
+ if lock_obj:
+ view3d.lock_object = lock_obj
+ else:
+ cam = self._get_object(stored_view.camera_name)
+ if cam:
+ view3d.camera = cam
+
+ @staticmethod
+ def _get_object(name, pointer=None):
+ return bpy.data.objects.get(name)
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ # TODO: check for others param, currently only perspective
+ # and perspective_matrix are checked
+ POV.logger = logging.getLogger('%s.POV' % __name__)
+ view3d = context.space_data
+ region3d = view3d.region_3d
+ if region3d.view_perspective != stored_view.perspective:
+ POV.logger.debug('view_perspective')
+ return True
+
+ md5 = POV._get_perspective_matrix_md5(region3d)
+ if (md5 != stored_view.perspective_matrix_md5 and
+ region3d.view_perspective != "CAMERA"):
+ POV.logger.debug('perspective_matrix')
+ return True
+
+ return False
+
+ @staticmethod
+ def _get_perspective_matrix_md5(region3d):
+ md5 = hashlib.md5(str(region3d.perspective_matrix).encode('utf-8')).hexdigest()
+ return md5
+
+
+# class Layers(StoredView):
+# def __init__(self, index=None):
+# super().__init__(mode='COLLECTIONS', index=index)
+# self.logger = logging.getLogger('%s.Layers' % __name__)
+
+# def from_v3d(self, stored_view):
+# view3d = self.view3d
+# stored_view.view_layers = bpy.types.layer.collection
+# stored_view.scene_layers = self.scene.layers
+# stored_view.lock_camera = view3d.lock_camera
+
+# def update_v3d(self, stored_view):
+# view3d = self.view3d
+# view3d.camera = stored_view.camera
+# if stored_view.camera is True:
+# self.scene.layers = stored_view.scene_layers
+# else:
+# view3d.layers = stored_view.view_layers
+
+ # @staticmethod
+ # def is_modified(context, stored_view):
+ # Layers.logger = logging.getLogger('%s.Layers' % __name__)
+ # if stored_view.camera != context.space_data.camera:
+ # Layers.logger.debug('lock_camera')
+ # return True
+ # if stored_view.camera is True:
+ # for i in range(20):
+ # if stored_view.scene_layers[i] != context.scene.layers[i]:
+ # Layers.logger.debug('scene_layers[%s]' % (i, ))
+ # return True
+ # else:
+ # for i in range(20):
+ # if stored_view.view_layers[i] != context.space_data.view3d.layers[i]:
+ # return True
+ # return False
+
+
+# class Display(StoredView):
+# def __init__(self, index=None):
+# super().__init__(mode='DISPLAY', index=index)
+# self.logger = logging.getLogger('%s.Display' % __name__)
+
+# def from_v3d(self, stored_view):
+# view3d = self.view3d
+# stored_view.viewport_shade = view3d.viewport_shade
+# stored_view.show_only_render = view3d.show_only_render
+# stored_view.show_outline_selected = view3d.show_outline_selected
+# stored_view.show_all_objects_origin = view3d.show_all_objects_origin
+# stored_view.show_relationship_lines = view3d.show_relationship_lines
+# stored_view.show_floor = view3d.show_floor
+# stored_view.show_axis_x = view3d.show_axis_x
+# stored_view.show_axis_y = view3d.show_axis_y
+# stored_view.show_axis_z = view3d.show_axis_z
+# stored_view.grid_lines = view3d.grid_lines
+# stored_view.grid_scale = view3d.grid_scale
+# stored_view.grid_subdivisions = view3d.grid_subdivisions
+# stored_view.material_mode = self.scene.game_settings.material_mode
+# stored_view.show_textured_solid = view3d.show_textured_solid
+
+ # def update_v3d(self, stored_view):
+ # view3d = self.view3d
+ # view3d.viewport_shade = stored_view.viewport_shade
+ # view3d.show_only_render = stored_view.show_only_render
+ # view3d.show_outline_selected = stored_view.show_outline_selected
+ # view3d.show_all_objects_origin = stored_view.show_all_objects_origin
+ # view3d.show_relationship_lines = stored_view.show_relationship_lines
+ # view3d.show_floor = stored_view.show_floor
+ # view3d.show_axis_x = stored_view.show_axis_x
+ # view3d.show_axis_y = stored_view.show_axis_y
+ # view3d.show_axis_z = stored_view.show_axis_z
+ # view3d.grid_lines = stored_view.grid_lines
+ # view3d.grid_scale = stored_view.grid_scale
+ # view3d.grid_subdivisions = stored_view.grid_subdivisions
+ # self.scene.game_settings.material_mode = stored_view.material_mode
+ # view3d.show_textured_solid = stored_view.show_textured_solid
+
+ # @staticmethod
+ # def is_modified(context, stored_view):
+ # Display.logger = logging.getLogger('%s.Display' % __name__)
+ # view3d = context.space_data
+ # excludes = ["material_mode", "quad_view", "lock_rotation", "show_sync_view", "use_box_clip", "name"]
+ # for k, v in stored_view.items():
+ # if k not in excludes:
+ # if getattr(view3d, k) != getattr(stored_view, k):
+ # return True
+
+ # if stored_view.material_mode != context.scene.game_settings.material_mode:
+ # Display.logger.debug('material_mode')
+ # return True
+
+
+class View(StoredView):
+ def __init__(self, index=None):
+ super().__init__(mode='VIEW', index=index)
+ self.logger = logging.getLogger('%s.View' % __name__)
+ self.pov = POV()
+ # self.layers = Layers()
+ # self.display = Display()
+
+ def from_v3d(self, stored_view):
+ self.pov.from_v3d(stored_view.pov)
+ # self.layers.from_v3d(stored_view.layers)
+ # self.display.from_v3d(stored_view.display)
+
+ def update_v3d(self, stored_view):
+ self.pov.update_v3d(stored_view.pov)
+ # self.layers.update_v3d(stored_view.layers)
+ # self.display.update_v3d(stored_view.display)
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ POV.is_modified(context, stored_view.pov) #or \
+ # Layers.is_modified(context, stored_view.layers) or \
+ # Display.is_modified(context, stored_view.display):
+ return True
+ # return False
+
+
+class DataStore():
+ def __init__(self, scene=None, mode=None):
+ if scene is None:
+ scene = bpy.context.scene
+ stored_views = scene.stored_views
+ self.mode = mode
+
+ if mode is None:
+ self.mode = stored_views.mode
+
+ if self.mode == 'VIEW':
+ self.list = stored_views.view_list
+ self.current_index = stored_views.current_indices[0]
+ elif self.mode == 'POV':
+ self.list = stored_views.pov_list
+ self.current_index = stored_views.current_indices[1]
+ elif self.mode == 'LAYERS':
+ self.list = stored_views.layers_list
+ self.current_index = stored_views.current_indices[2]
+ elif self.mode == 'DISPLAY':
+ self.list = stored_views.display_list
+ self.current_index = stored_views.current_indices[3]
+
+ def create(self):
+ item = self.list.add()
+ item.name = self._generate_name()
+ index = len(self.list) - 1
+ self._set_current_index(index)
+ return item, index
+
+ def get(self, index):
+ self._set_current_index(index)
+ return self.list[index]
+
+ def delete(self, index):
+ if self.current_index > index:
+ self._set_current_index(self.current_index - 1)
+ elif self.current_index == index:
+ self._set_current_index(-1)
+
+ self.list.remove(index)
+
+ def _set_current_index(self, index):
+ self.current_index = index
+ mode = self.mode
+ stored_views = bpy.context.scene.stored_views
+ if mode == 'VIEW':
+ stored_views.current_indices[0] = index
+ elif mode == 'POV':
+ stored_views.current_indices[1] = index
+ elif mode == 'LAYERS':
+ stored_views.current_indices[2] = index
+ elif mode == 'DISPLAY':
+ stored_views.current_indices[3] = index
+
+ def _generate_name(self):
+ default_name = str(self.mode)
+ names = []
+ for i in self.list:
+ i_name = i.name
+ if i_name.startswith(default_name):
+ names.append(i_name)
+ names.sort()
+ try:
+ l_name = names[-1]
+ post_fix = l_name.rpartition('.')[2]
+ if post_fix.isnumeric():
+ post_fix = str(int(post_fix) + 1).zfill(3)
+ else:
+ if post_fix == default_name:
+ post_fix = "001"
+ return default_name + "." + post_fix
+ except:
+ return default_name
+
+ @staticmethod
+ def sanitize_data(scene):
+
+ def check_objects_references(mode, list):
+ to_remove = []
+ for i, list_item in enumerate(list.items()):
+ key, item = list_item
+ if mode == 'POV' or mode == 'VIEWS':
+ if mode == 'VIEWS':
+ item = item.pov
+
+ if item.perspective == "CAMERA":
+
+ camera = bpy.data.objects.get(item.camera_name)
+ if camera is None:
+ try: # pick a default camera TODO: ask to pick?
+ camera = bpy.data.cameras[0]
+ item.camera_name = camera.name
+ except: # couldn't find a camera in the scene
+ pass
+
+ obj = bpy.data.objects.get(item.lock_object_name)
+ if obj is None and camera is None:
+ to_remove.append(i)
+
+ for i in reversed(to_remove):
+ list.remove(i)
+
+ modes = ['POV', 'VIEW', 'DISPLAY', 'LAYERS']
+ for mode in modes:
+ data = DataStore(scene=scene, mode=mode)
+ check_objects_references(mode, data.list)
+
+
+def stored_view_factory(mode, *args, **kwargs):
+ if mode == 'POV':
+ return POV(*args, **kwargs)
+ elif mode == 'LAYERS':
+ return Layers(*args, **kwargs)
+ elif mode == 'DISPLAY':
+ return Display(*args, **kwargs)
+ elif mode == 'VIEW':
+ return View(*args, **kwargs)
diff --git a/space_view3d_stored_views/io.py b/space_view3d_stored_views/io.py
new file mode 100644
index 00000000..72237ed3
--- /dev/null
+++ b/space_view3d_stored_views/io.py
@@ -0,0 +1,343 @@
+# gpl 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)) == "<class 'bpy_prop_collection_idprop'>":
+ 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)) == "<class 'bpy_prop_collection_idprop'>":
+ 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 ["<class 'bpy_prop_array'>"]:
+ # array
+ dict[prop.identifier] = [v for v in val]
+ # address the pickle limitations of dealing with the Vector class
+ elif str(type(val)) in ["<class 'Vector'>",
+ "<class 'Quaternion'>"]:
+ 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) \ No newline at end of file
diff --git a/space_view3d_stored_views/operators.py b/space_view3d_stored_views/operators.py
new file mode 100644
index 00000000..4cf8a2cf
--- /dev/null
+++ b/space_view3d_stored_views/operators.py
@@ -0,0 +1,182 @@
+# gpl authors: nfloyd, Francesco Siddi
+
+import bpy
+from bpy.props import IntProperty
+from bpy.types import Operator
+from .core import (
+ stored_view_factory,
+ DataStore,
+ )
+from .ui import init_draw
+
+
+class VIEW3D_stored_views_save(Operator):
+ bl_idname = "stored_views.save"
+ bl_label = "Save Current"
+ bl_description = "Save the view 3d current state"
+
+ index : IntProperty()
+
+ def execute(self, context):
+ mode = context.scene.stored_views.mode
+ sv = stored_view_factory(mode, self.index)
+ sv.save()
+ context.scene.stored_views.view_modified = False
+ init_draw(context)
+
+ return {'FINISHED'}
+
+
+class VIEW3D_stored_views_set(Operator):
+ bl_idname = "stored_views.set"
+ bl_label = "Set"
+ bl_description = "Update the view 3D according to this view"
+
+ index : IntProperty()
+
+ def execute(self, context):
+ mode = context.scene.stored_views.mode
+ sv = stored_view_factory(mode, self.index)
+ sv.set()
+ context.scene.stored_views.view_modified = False
+ init_draw(context)
+
+ return {'FINISHED'}
+
+
+class VIEW3D_stored_views_delete(Operator):
+ bl_idname = "stored_views.delete"
+ bl_label = "Delete"
+ bl_description = "Delete this view"
+
+ index : IntProperty()
+
+ def execute(self, context):
+ data = DataStore()
+ data.delete(self.index)
+
+ return {'FINISHED'}
+
+
+class VIEW3D_New_Camera_to_View(Operator):
+ bl_idname = "stored_views.newcamera"
+ bl_label = "New Camera To View"
+ bl_description = "Add a new Active Camera and align it to this view"
+
+ @classmethod
+ def poll(cls, context):
+ return (
+ context.space_data is not None and
+ context.space_data.type == 'VIEW_3D' and
+ context.space_data.region_3d.view_perspective != 'CAMERA'
+ )
+
+ def execute(self, context):
+
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ bpy.ops.object.camera_add()
+ cam = context.active_object
+ cam.name = "View_Camera"
+ # make active camera by hand
+ context.scene.camera = cam
+
+ bpy.ops.view3d.camera_to_view()
+ return {'FINISHED'}
+
+
+# Camera marker & switcher by Fsiddi
+class SetSceneCamera(Operator):
+ bl_idname = "cameraselector.set_scene_camera"
+ bl_label = "Set Scene Camera"
+ bl_description = "Set chosen camera as the scene's active camera"
+
+ hide_others = False
+
+ def execute(self, context):
+ chosen_camera = context.active_object
+ scene = context.scene
+
+ if self.hide_others:
+ for c in [o for o in scene.objects if o.type == 'CAMERA']:
+ c.hide = (c != chosen_camera)
+ scene.camera = chosen_camera
+ bpy.ops.object.select_all(action ='DESELECT')
+ chosen_camera.select_set(True)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ if event.ctrl:
+ self.hide_others = True
+
+ return self.execute(context)
+
+
+class PreviewSceneCamera(Operator):
+ bl_idname = "cameraselector.preview_scene_camera"
+ bl_label = "Preview Camera"
+ bl_description = "Preview chosen camera and make scene's active camera"
+
+ def execute(self, context):
+ chosen_camera = context.active_object
+ bpy.ops.view3d.object_as_camera()
+ bpy.ops.object.select_all(action="DESELECT")
+ chosen_camera.select_set(True)
+ return {'FINISHED'}
+
+
+class AddCameraMarker(Operator):
+ bl_idname = "cameraselector.add_camera_marker"
+ bl_label = "Add Camera Marker"
+ bl_description = "Add a timeline marker bound to chosen camera"
+
+ def execute(self, context):
+ chosen_camera = context.active_object
+ scene = context.scene
+
+ current_frame = scene.frame_current
+ marker = None
+ for m in reversed(sorted(filter(lambda m: m.frame <= current_frame,
+ scene.timeline_markers),
+ key=lambda m: m.frame)):
+ marker = m
+ break
+ if marker and (marker.camera == chosen_camera):
+ # Cancel if the last marker at or immediately before
+ # current frame is already bound to the camera.
+ return {'CANCELLED'}
+
+ marker_name = "F_%02d_%s" % (current_frame, chosen_camera.name)
+ if marker and (marker.frame == current_frame):
+ # Reuse existing marker at current frame to avoid
+ # overlapping bound markers.
+ marker.name = marker_name
+ else:
+ marker = scene.timeline_markers.new(marker_name)
+ marker.frame = scene.frame_current
+ marker.camera = chosen_camera
+ marker.select = True
+
+ for other_marker in [m for m in scene.timeline_markers if m != marker]:
+ other_marker.select = True
+
+ return {'FINISHED'}
+
+classes = (
+ VIEW3D_stored_views_save,
+ VIEW3D_stored_views_set,
+ VIEW3D_stored_views_delete,
+ VIEW3D_New_Camera_to_View,
+ SetSceneCamera,
+ PreviewSceneCamera,
+ AddCameraMarker
+)
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls) \ No newline at end of file
diff --git a/space_view3d_stored_views/properties.py b/space_view3d_stored_views/properties.py
new file mode 100644
index 00000000..0bf95097
--- /dev/null
+++ b/space_view3d_stored_views/properties.py
@@ -0,0 +1,137 @@
+# gpl authors: nfloyd, Francesco Siddi
+import bpy
+from bpy.types import PropertyGroup
+from bpy.props import (
+ BoolProperty,
+ BoolVectorProperty,
+ CollectionProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ EnumProperty,
+ IntProperty,
+ IntVectorProperty,
+ PointerProperty,
+ StringProperty,
+ )
+
+
+class POVData(PropertyGroup):
+ distance : FloatProperty()
+ location : FloatVectorProperty(
+ subtype='TRANSLATION'
+ )
+ rotation : FloatVectorProperty(
+ subtype='QUATERNION',
+ size=4
+ )
+ name : StringProperty()
+ perspective : EnumProperty(
+ items=[('PERSP', '', ''),
+ ('ORTHO', '', ''),
+ ('CAMERA', '', '')]
+ )
+ lens : FloatProperty()
+ clip_start : FloatProperty()
+ clip_end : FloatProperty()
+ lock_cursor : BoolProperty()
+ cursor_location : FloatVectorProperty()
+ perspective_matrix_md5 = StringProperty()
+ camera_name : StringProperty()
+ camera_type : StringProperty()
+ lock_object_name : StringProperty()
+
+
+class LayersData(PropertyGroup):
+ view_layers : BoolVectorProperty(size=20)
+ scene_layers : BoolVectorProperty(size=20)
+ lock_camera_and_layers : BoolProperty()
+ name : StringProperty()
+
+
+class DisplayData(PropertyGroup):
+ name = StringProperty()
+ viewport_shade = EnumProperty(
+ items=[('BOUNDBOX', 'BOUNDBOX', 'BOUNDBOX'),
+ ('WIREFRAME', 'WIREFRAME', 'WIREFRAME'),
+ ('SOLID', 'SOLID', 'SOLID'),
+ ('TEXTURED', 'TEXTURED', 'TEXTURED'),
+ ('MATERIAL', 'MATERIAL', 'MATERIAL'),
+ ('RENDERED', 'RENDERED', 'RENDERED')]
+ )
+ show_only_render : BoolProperty()
+ show_outline_selected : BoolProperty()
+ show_all_objects_origin : BoolProperty()
+ show_relationship_lines : BoolProperty()
+ show_floor : BoolProperty()
+ show_axis_x : BoolProperty()
+ show_axis_y : BoolProperty()
+ show_axis_z : BoolProperty()
+ grid_lines : IntProperty()
+ grid_scale : FloatProperty()
+ grid_subdivisions : IntProperty()
+ material_mode : StringProperty()
+ show_textured_solid : BoolProperty()
+ quad_view : BoolProperty()
+ lock_rotation : BoolProperty()
+ show_sync_view : BoolProperty()
+ use_box_clip : BoolProperty()
+
+
+class ViewData(PropertyGroup):
+ pov : PointerProperty(
+ type=POVData
+ )
+ layers : PointerProperty(
+ type=LayersData
+ )
+ display : PointerProperty(
+ type=DisplayData
+ )
+ name : StringProperty()
+
+
+class StoredViewsData(PropertyGroup):
+ pov_list : CollectionProperty(
+ type=POVData
+ )
+ layers_list : CollectionProperty(
+ type=LayersData
+ )
+ display_list : CollectionProperty(
+ type=DisplayData
+ )
+ view_list : CollectionProperty(
+ type=ViewData
+ )
+ mode : EnumProperty(
+ name="Mode",
+ items=[('VIEW', "View", "3D View settings"),
+ ('POV', "POV", "POV settings"),
+ ('LAYERS', "Layers", "Layers settings"),
+ ('DISPLAY', "Display", "Display settings")],
+ default='VIEW'
+ )
+ current_indices : IntVectorProperty(
+ size=4,
+ default=[-1, -1, -1, -1]
+ )
+ view_modified : BoolProperty(
+ default=False
+ )
+
+
+classes = (
+ POVData,
+ LayersData,
+ DisplayData,
+ ViewData,
+ StoredViewsData,
+)
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls) \ No newline at end of file
diff --git a/space_view3d_stored_views/stored_views_test.py b/space_view3d_stored_views/stored_views_test.py
new file mode 100644
index 00000000..fa4866f0
--- /dev/null
+++ b/space_view3d_stored_views/stored_views_test.py
@@ -0,0 +1,1371 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Stored Views",
+ "description": "Save and restore User defined views, pov, layers and display configs",
+ "author": "nfloyd, Francesco Siddi",
+ "version": (0, 3, 7),
+ "blender": (2, 80, 0),
+ "location": "View3D > Properties > Stored Views",
+ "warning": "",
+ "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.5/"
+ "Py/Scripts/3D_interaction/stored_views",
+ "category": "3D View"
+}
+
+"""
+ACKNOWLEDGMENT
+==============
+import/export functionality is mostly based
+on Bart Crouch's Theme Manager Addon
+
+TODO: quadview complete support : investigate. Where's the data?
+TODO: lock_camera_and_layers. investigate usage
+TODO: list reordering
+
+NOTE: logging setup has to be provided by the user in a separate config file
+ as Blender will not try to configure logging by default in an add-on
+ The Config File should be in the Blender Config folder > /scripts/startup/config_logging.py
+ For setting up /location of the config folder see:
+ https://docs.blender.org/manual/en/dev/getting_started/
+ installing/configuration/directories.html
+ For configuring logging itself in the file, general Python documentation should work
+ As the logging calls are not configured, they can be kept in the other modules of this add-on
+ and will not have output until the logging configuration is set up
+"""
+
+
+import bpy
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ PointerProperty,
+)
+from bpy.types import (
+ AddonPreferences,
+ Operator,
+ Panel
+)
+
+import logging
+module_logger = logging.getLogger(__name__)
+
+import gzip
+import os
+import pickle
+import shutil
+
+from bpy_extras.io_utils import (
+ ExportHelper,
+ ImportHelper,
+)
+
+import blf
+
+import hashlib
+import bpy
+
+
+# Utility function get preferences setting for exporters
+def get_preferences():
+ # replace the key if the add-on name changes
+ addon = bpy.context.preferences.addons[__package__]
+ show_warn = (addon.preferences.show_exporters if addon else False)
+
+ return show_warn
+
+
+class StoredView():
+ def __init__(self, mode, index=None):
+ self.logger = logging.getLogger('%s.StoredView' % __name__)
+ self.scene = bpy.context.scene
+ self.view3d = bpy.context.space_data
+ self.index = index
+ self.data_store = DataStore(mode=mode)
+
+ def save(self):
+ if self.index == -1:
+ stored_view, self.index = self.data_store.create()
+ else:
+ stored_view = self.data_store.get(self.index)
+ self.from_v3d(stored_view)
+ self.logger.debug('index: %s name: %s' % (self.data_store.current_index, stored_view.name))
+
+ def set(self):
+ stored_view = self.data_store.get(self.index)
+ self.update_v3d(stored_view)
+ self.logger.debug('index: %s name: %s' % (self.data_store.current_index, stored_view.name))
+
+ def from_v3d(self, stored_view):
+ raise NotImplementedError("Subclass must implement abstract method")
+
+ def update_v3d(self, stored_view):
+ raise NotImplementedError("Subclass must implement abstract method")
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ raise NotImplementedError("Subclass must implement abstract method")
+
+
+class POV(StoredView):
+ def __init__(self, index=None):
+ super().__init__(mode='POV', index=index)
+ self.logger = logging.getLogger('%s.POV' % __name__)
+
+ def from_v3d(self, stored_view):
+ view3d = self.view3d
+ region3d = view3d.region_3d
+
+ stored_view.distance = region3d.view_distance
+ stored_view.location = region3d.view_location
+ stored_view.rotation = region3d.view_rotation
+ stored_view.perspective_matrix_md5 = POV._get_perspective_matrix_md5(region3d)
+ stored_view.perspective = region3d.view_perspective
+ stored_view.lens = view3d.lens
+ stored_view.clip_start = view3d.clip_start
+ stored_view.clip_end = view3d.clip_end
+
+ if region3d.view_perspective == 'CAMERA':
+ stored_view.camera_type = view3d.camera.type # type : 'CAMERA' or 'MESH'
+ stored_view.camera_name = view3d.camera.name # store string instead of object
+ if view3d.lock_object is not None:
+ stored_view.lock_object_name = view3d.lock_object.name # idem
+ else:
+ stored_view.lock_object_name = ""
+ stored_view.lock_cursor = view3d.lock_cursor
+ stored_view.cursor_location = view3d.cursor_location
+
+ def update_v3d(self, stored_view):
+ view3d = self.view3d
+ region3d = view3d.region_3d
+ region3d.view_distance = stored_view.distance
+ region3d.view_location = stored_view.location
+ region3d.view_rotation = stored_view.rotation
+ region3d.view_perspective = stored_view.perspective
+ view3d.lens = stored_view.lens
+ view3d.clip_start = stored_view.clip_start
+ view3d.clip_end = stored_view.clip_end
+ view3d.lock_cursor = stored_view.lock_cursor
+ if stored_view.lock_cursor is True:
+ # update cursor only if view is locked to cursor
+ view3d.cursor_location = stored_view.cursor_location
+
+ if stored_view.perspective == "CAMERA":
+
+ lock_obj = self._get_object(stored_view.lock_object_name)
+ if lock_obj:
+ view3d.lock_object = lock_obj
+ else:
+ cam = self._get_object(stored_view.camera_name)
+ if cam:
+ view3d.camera = cam
+
+ @staticmethod
+ def _get_object(name, pointer=None):
+ return bpy.data.objects.get(name)
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ # TODO: check for others param, currently only perspective
+ # and perspective_matrix are checked
+ POV.logger = logging.getLogger('%s.POV' % __name__)
+ view3d = context.space_data
+ region3d = view3d.region_3d
+ if region3d.view_perspective != stored_view.perspective:
+ POV.logger.debug('view_perspective')
+ return True
+
+ md5 = POV._get_perspective_matrix_md5(region3d)
+ if (md5 != stored_view.perspective_matrix_md5 and
+ region3d.view_perspective != "CAMERA"):
+ POV.logger.debug('perspective_matrix')
+ return True
+
+ return False
+
+ @staticmethod
+ def _get_perspective_matrix_md5(region3d):
+ md5 = hashlib.md5(str(region3d.perspective_matrix).encode('utf-8')).hexdigest()
+ return md5
+
+
+class Layers(StoredView):
+ def __init__(self, index=None):
+ super().__init__(mode='LAYERS', index=index)
+ self.logger = logging.getLogger('%s.Layers' % __name__)
+
+ def from_v3d(self, stored_view):
+ view3d = self.view3d
+ stored_view.view_layers = view3d.layers
+ stored_view.scene_layers = self.scene.layers
+ stored_view.lock_camera_and_layers = view3d.lock_camera_and_layers
+
+ def update_v3d(self, stored_view):
+ view3d = self.view3d
+ view3d.lock_camera_and_layers = stored_view.lock_camera_and_layers
+ if stored_view.lock_camera_and_layers is True:
+ self.scene.layers = stored_view.scene_layers
+ else:
+ view3d.layers = stored_view.view_layers
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ Layers.logger = logging.getLogger('%s.Layers' % __name__)
+ if stored_view.lock_camera_and_layers != context.space_data.lock_camera_and_layers:
+ Layers.logger.debug('lock_camera_and_layers')
+ return True
+ if stored_view.lock_camera_and_layers is True:
+ for i in range(20):
+ if stored_view.scene_layers[i] != context.scene.layers[i]:
+ Layers.logger.debug('scene_layers[%s]' % (i, ))
+ return True
+ else:
+ for i in range(20):
+ if stored_view.view_layers[i] != context.space_data.view3d.layers[i]:
+ return True
+ return False
+
+
+class Display(StoredView):
+ def __init__(self, index=None):
+ super().__init__(mode='DISPLAY', index=index)
+ self.logger = logging.getLogger('%s.Display' % __name__)
+
+ def from_v3d(self, stored_view):
+ view3d = self.view3d
+ stored_view.viewport_shade = view3d.viewport_shade
+ stored_view.show_only_render = view3d.show_only_render
+ stored_view.show_outline_selected = view3d.show_outline_selected
+ stored_view.show_all_objects_origin = view3d.show_all_objects_origin
+ stored_view.show_relationship_lines = view3d.show_relationship_lines
+ stored_view.show_floor = view3d.show_floor
+ stored_view.show_axis_x = view3d.show_axis_x
+ stored_view.show_axis_y = view3d.show_axis_y
+ stored_view.show_axis_z = view3d.show_axis_z
+ stored_view.grid_lines = view3d.grid_lines
+ stored_view.grid_scale = view3d.grid_scale
+ stored_view.grid_subdivisions = view3d.grid_subdivisions
+ stored_view.material_mode = self.scene.game_settings.material_mode
+ stored_view.show_textured_solid = view3d.show_textured_solid
+
+ def update_v3d(self, stored_view):
+ view3d = self.view3d
+ view3d.viewport_shade = stored_view.viewport_shade
+ view3d.show_only_render = stored_view.show_only_render
+ view3d.show_outline_selected = stored_view.show_outline_selected
+ view3d.show_all_objects_origin = stored_view.show_all_objects_origin
+ view3d.show_relationship_lines = stored_view.show_relationship_lines
+ view3d.show_floor = stored_view.show_floor
+ view3d.show_axis_x = stored_view.show_axis_x
+ view3d.show_axis_y = stored_view.show_axis_y
+ view3d.show_axis_z = stored_view.show_axis_z
+ view3d.grid_lines = stored_view.grid_lines
+ view3d.grid_scale = stored_view.grid_scale
+ view3d.grid_subdivisions = stored_view.grid_subdivisions
+ self.scene.game_settings.material_mode = stored_view.material_mode
+ view3d.show_textured_solid = stored_view.show_textured_solid
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ Display.logger = logging.getLogger('%s.Display' % __name__)
+ view3d = context.space_data
+ excludes = ["material_mode", "quad_view", "lock_rotation", "show_sync_view", "use_box_clip", "name"]
+ for k, v in stored_view.items():
+ if k not in excludes:
+ if getattr(view3d, k) != getattr(stored_view, k):
+ return True
+
+ if stored_view.material_mode != context.scene.game_settings.material_mode:
+ Display.logger.debug('material_mode')
+ return True
+
+
+class View(StoredView):
+ def __init__(self, index=None):
+ super().__init__(mode='VIEW', index=index)
+ self.logger = logging.getLogger('%s.View' % __name__)
+ self.pov = POV()
+ self.layers = Layers()
+ self.display = Display()
+
+ def from_v3d(self, stored_view):
+ self.pov.from_v3d(stored_view.pov)
+ self.layers.from_v3d(stored_view.layers)
+ self.display.from_v3d(stored_view.display)
+
+ def update_v3d(self, stored_view):
+ self.pov.update_v3d(stored_view.pov)
+ self.layers.update_v3d(stored_view.layers)
+ self.display.update_v3d(stored_view.display)
+
+ @staticmethod
+ def is_modified(context, stored_view):
+ if POV.is_modified(context, stored_view.pov) or \
+ Layers.is_modified(context, stored_view.layers) or \
+ Display.is_modified(context, stored_view.display):
+ return True
+ return False
+
+
+class DataStore():
+ def __init__(self, scene=None, mode=None):
+ if scene is None:
+ scene = bpy.context.scene
+ stored_views = scene.stored_views
+ self.mode = mode
+
+ if mode is None:
+ self.mode = stored_views.mode
+
+ if self.mode == 'VIEW':
+ self.list = stored_views.view_list
+ self.current_index = stored_views.current_indices[0]
+ elif self.mode == 'POV':
+ self.list = stored_views.pov_list
+ self.current_index = stored_views.current_indices[1]
+ elif self.mode == 'LAYERS':
+ self.list = stored_views.layers_list
+ self.current_index = stored_views.current_indices[2]
+ elif self.mode == 'DISPLAY':
+ self.list = stored_views.display_list
+ self.current_index = stored_views.current_indices[3]
+
+ def create(self):
+ item = self.list.add()
+ item.name = self._generate_name()
+ index = len(self.list) - 1
+ self._set_current_index(index)
+ return item, index
+
+ def get(self, index):
+ self._set_current_index(index)
+ return self.list[index]
+
+ def delete(self, index):
+ if self.current_index > index:
+ self._set_current_index(self.current_index - 1)
+ elif self.current_index == index:
+ self._set_current_index(-1)
+
+ self.list.remove(index)
+
+ def _set_current_index(self, index):
+ self.current_index = index
+ mode = self.mode
+ stored_views = bpy.context.scene.stored_views
+ if mode == 'VIEW':
+ stored_views.current_indices[0] = index
+ elif mode == 'POV':
+ stored_views.current_indices[1] = index
+ elif mode == 'LAYERS':
+ stored_views.current_indices[2] = index
+ elif mode == 'DISPLAY':
+ stored_views.current_indices[3] = index
+
+ def _generate_name(self):
+ default_name = str(self.mode)
+ names = []
+ for i in self.list:
+ i_name = i.name
+ if i_name.startswith(default_name):
+ names.append(i_name)
+ names.sort()
+ try:
+ l_name = names[-1]
+ post_fix = l_name.rpartition('.')[2]
+ if post_fix.isnumeric():
+ post_fix = str(int(post_fix) + 1).zfill(3)
+ else:
+ if post_fix == default_name:
+ post_fix = "001"
+ return default_name + "." + post_fix
+ except:
+ return default_name
+
+ @staticmethod
+ def sanitize_data(scene):
+
+ def check_objects_references(mode, list):
+ to_remove = []
+ for i, list_item in enumerate(list.items()):
+ key, item = list_item
+ if mode == 'POV' or mode == 'VIEWS':
+ if mode == 'VIEWS':
+ item = item.pov
+
+ if item.perspective == "CAMERA":
+
+ camera = bpy.data.objects.get(item.camera_name)
+ if camera is None:
+ try: # pick a default camera TODO: ask to pick?
+ camera = bpy.data.cameras[0]
+ item.camera_name = camera.name
+ except: # couldn't find a camera in the scene
+ pass
+
+ obj = bpy.data.objects.get(item.lock_object_name)
+ if obj is None and camera is None:
+ to_remove.append(i)
+
+ for i in reversed(to_remove):
+ list.remove(i)
+
+ modes = ['POV', 'VIEW', 'DISPLAY', 'LAYERS']
+ for mode in modes:
+ data = DataStore(scene=scene, mode=mode)
+ check_objects_references(mode, data.list)
+
+
+def stored_view_factory(mode, *args, **kwargs):
+ if mode == 'POV':
+ return POV(*args, **kwargs)
+ elif mode == 'LAYERS':
+ return Layers(*args, **kwargs)
+ elif mode == 'DISPLAY':
+ return Display(*args, **kwargs)
+ elif mode == 'VIEW':
+ return View(*args, **kwargs)
+
+"""
+ If view name display is enabled,
+ it will check periodically if the view has been modified
+ since last set.
+ get_preferences_timer() is the time in seconds between these checks.
+ It can be increased, if the view become sluggish
+ It is set in the add-on preferences
+"""
+
+
+# Utility function get_preferences_timer for update of 3d view draw
+def get_preferences_timer():
+ # replace the key if the add-on name changes
+ # TODO: expose refresh rate to ui???
+ addon = bpy.context.preferences.addons[__package__]
+ timer_update = (addon.preferences.view_3d_update_rate if addon else False)
+
+ return timer_update
+
+
+def init_draw(context=None):
+ if context is None:
+ context = bpy.context
+
+ if "stored_views_osd" not in context.window_manager:
+ context.window_manager["stored_views_osd"] = False
+
+ if not context.window_manager["stored_views_osd"]:
+ context.window_manager["stored_views_osd"] = True
+ bpy.ops.stored_views.draw()
+
+
+def _draw_callback_px(self, context):
+ if context.area and context.area.type == 'VIEW_3D':
+ r_width = text_location = context.region.width
+ r_height = context.region.height
+ font_id = 0 # TODO: need to find out how best to get font_id
+
+ blf.size(font_id, 11, context.preferences.system.dpi)
+ text_size = blf.dimensions(0, self.view_name)
+
+ # compute the text location
+ text_location = 0
+ overlap = context.preferences.system.use_region_overlap
+ if overlap:
+ for region in context.area.regions:
+ if region.type == "UI":
+ text_location = r_width - region.width
+
+ text_x = text_location - text_size[0] - 10
+ text_y = r_height - text_size[1] - 8
+ blf.position(font_id, text_x, text_y, 0)
+ blf.draw(font_id, self.view_name)
+
+
+class VIEW3D_OT_stored_views_draw(Operator):
+ bl_idname = "stored_views.draw"
+ bl_label = "Show current"
+ bl_description = "Toggle the display current view name in the view 3D"
+
+ _handle = None
+ _timer = None
+
+ @staticmethod
+ def handle_add(self, context):
+ VIEW3D_OT_stored_views_draw._handle = bpy.types.SpaceView3D.draw_handler_add(
+ _draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
+ VIEW3D_OT_stored_views_draw._timer = \
+ context.window_manager.event_timer_add(get_preferences_timer(), context.window)
+
+ @staticmethod
+ def handle_remove(context):
+ if VIEW3D_OT_stored_views_draw._handle is not None:
+ bpy.types.SpaceView3D.draw_handler_remove(VIEW3D_OT_stored_views_draw._handle, 'WINDOW')
+ if VIEW3D_OT_stored_views_draw._timer is not None:
+ context.window_manager.event_timer_remove(VIEW3D_OT_stored_views_draw._timer)
+ VIEW3D_OT_stored_views_draw._handle = None
+ VIEW3D_OT_stored_views_draw._timer = None
+
+ @classmethod
+ def poll(cls, context):
+ # return context.mode == 'OBJECT'
+ return True
+
+ def modal(self, context, event):
+ if context.area:
+ context.area.tag_redraw()
+
+ if not context.area or context.area.type != "VIEW_3D":
+ return {"PASS_THROUGH"}
+
+ data = DataStore()
+ stored_views = context.scene.stored_views
+
+ if len(data.list) > 0 and \
+ data.current_index >= 0 and \
+ not stored_views.view_modified:
+
+ if not stored_views.view_modified:
+ sv = data.list[data.current_index]
+ self.view_name = sv.name
+ if event.type == 'TIMER':
+ is_modified = False
+ if data.mode == 'VIEW':
+ is_modified = View.is_modified(context, sv)
+ elif data.mode == 'POV':
+ is_modified = POV.is_modified(context, sv)
+ elif data.mode == 'LAYERS':
+ is_modified = Layers.is_modified(context, sv)
+ elif data.mode == 'DISPLAY':
+ is_modified = Display.is_modified(context, sv)
+ if is_modified:
+ module_logger.debug(
+ 'view modified - index: %s name: %s' % (data.current_index, sv.name)
+ )
+ self.view_name = ""
+ stored_views.view_modified = is_modified
+
+ return {"PASS_THROUGH"}
+ else:
+ module_logger.debug('exit')
+ context.window_manager["stored_views_osd"] = False
+ VIEW3D_OT_stored_views_draw.handle_remove(context)
+
+ return {'FINISHED'}
+
+ def execute(self, context):
+ if context.area.type == "VIEW_3D":
+ self.view_name = ""
+ VIEW3D_OT_stored_views_draw.handle_add(self, context)
+ context.window_manager.modal_handler_add(self)
+
+ return {"RUNNING_MODAL"}
+ else:
+ self.report({"WARNING"}, "View3D not found. Operation Cancelled")
+
+ return {"CANCELLED"}
+
+class VIEW3D_OT_stored_views_initialize(Operator):
+ bl_idname = "view3d.stored_views_initialize"
+ bl_label = "Initialize"
+
+ @classmethod
+ def poll(cls, context):
+ return not hasattr(bpy.types.Scene, 'stored_views')
+
+ def execute(self, context):
+ bpy.types.Scene.stored_views: PointerProperty(
+ type=properties.StoredViewsData
+ )
+ scenes = bpy.data.scenes
+ data = DataStore()
+ for scene in scenes:
+ DataStore.sanitize_data(scene)
+ return {'FINISHED'}
+
+
+from bpy.types import PropertyGroup
+from bpy.props import (
+ BoolProperty,
+ BoolVectorProperty,
+ CollectionProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ EnumProperty,
+ IntProperty,
+ IntVectorProperty,
+ PointerProperty,
+ StringProperty,
+ )
+
+
+class POVData(PropertyGroup):
+ distance: FloatProperty()
+ location: FloatVectorProperty(
+ subtype='TRANSLATION'
+ )
+ rotation: FloatVectorProperty(
+ subtype='QUATERNION',
+ size=4
+ )
+ name: StringProperty()
+ perspective: EnumProperty(
+ items=[('PERSP', '', ''),
+ ('ORTHO', '', ''),
+ ('CAMERA', '', '')]
+ )
+ lens: FloatProperty()
+ clip_start: FloatProperty()
+ clip_end: FloatProperty()
+ lock_cursor: BoolProperty()
+ cursor_location: FloatVectorProperty()
+ perspective_matrix_md5: StringProperty()
+ camera_name: StringProperty()
+ camera_type: StringProperty()
+ lock_object_name: StringProperty()
+
+
+class LayersData(PropertyGroup):
+ view_layers: BoolVectorProperty(size=20)
+ scene_layers: BoolVectorProperty(size=20)
+ lock_camera_and_layers: BoolProperty()
+ name: StringProperty()
+
+
+class DisplayData(PropertyGroup):
+ name: StringProperty()
+ viewport_shade: EnumProperty(
+ items=[('BOUNDBOX', 'BOUNDBOX', 'BOUNDBOX'),
+ ('WIREFRAME', 'WIREFRAME', 'WIREFRAME'),
+ ('SOLID', 'SOLID', 'SOLID'),
+ ('TEXTURED', 'TEXTURED', 'TEXTURED'),
+ ('MATERIAL', 'MATERIAL', 'MATERIAL'),
+ ('RENDERED', 'RENDERED', 'RENDERED')]
+ )
+ show_only_render: BoolProperty()
+ show_outline_selected: BoolProperty()
+ show_all_objects_origin: BoolProperty()
+ show_relationship_lines: BoolProperty()
+ show_floor: BoolProperty()
+ show_axis_x: BoolProperty()
+ show_axis_y: BoolProperty()
+ show_axis_z: BoolProperty()
+ grid_lines: IntProperty()
+ grid_scale: FloatProperty()
+ grid_subdivisions: IntProperty()
+ material_mode: StringProperty()
+ show_textured_solid: BoolProperty()
+ quad_view: BoolProperty()
+ lock_rotation: BoolProperty()
+ show_sync_view: BoolProperty()
+ use_box_clip: BoolProperty()
+
+
+class ViewData(PropertyGroup):
+ pov: PointerProperty(
+ type=POVData
+ )
+ layers: PointerProperty(
+ type=LayersData
+ )
+ display: PointerProperty(
+ type=DisplayData
+ )
+ name: StringProperty()
+
+
+class StoredViewsData(PropertyGroup):
+ pov_list: CollectionProperty(
+ type=POVData
+ )
+ layers_list: CollectionProperty(
+ type=LayersData
+ )
+ display_list: CollectionProperty(
+ type=DisplayData
+ )
+ view_list: CollectionProperty(
+ type=ViewData
+ )
+ mode: EnumProperty(
+ name="Mode",
+ items=[('VIEW', "View", "3D View settings"),
+ ('POV', "POV", "POV settings"),
+ ('LAYERS', "Layers", "Layers settings"),
+ ('DISPLAY', "Display", "Display settings")],
+ default='VIEW'
+ )
+ current_indices: IntVectorProperty(
+ size=4,
+ default=[-1, -1, -1, -1]
+ )
+ view_modified: BoolProperty(
+ default=False
+ )
+
+class VIEW3D_OT_stored_views_save(Operator):
+ bl_idname = "stored_views.save"
+ bl_label = "Save Current"
+ bl_description = "Save the view 3d current state"
+
+ index: IntProperty()
+
+ def execute(self, context):
+ mode = context.scene.stored_views.mode
+ sv = stored_view_factory(mode, self.index)
+ sv.save()
+ context.scene.stored_views.view_modified = False
+ init_draw(context)
+
+ return {'FINISHED'}
+
+
+class VIEW3D_OT_stored_views_set(Operator):
+ bl_idname = "stored_views.set"
+ bl_label = "Set"
+ bl_description = "Update the view 3D according to this view"
+
+ index: IntProperty()
+
+ def execute(self, context):
+ mode = context.scene.stored_views.mode
+ sv = stored_view_factory(mode, self.index)
+ sv.set()
+ context.scene.stored_views.view_modified = False
+ init_draw(context)
+
+ return {'FINISHED'}
+
+
+class VIEW3D_OT_stored_views_delete(Operator):
+ bl_idname = "stored_views.delete"
+ bl_label = "Delete"
+ bl_description = "Delete this view"
+
+ index: IntProperty()
+
+ def execute(self, context):
+ data = DataStore()
+ data.delete(self.index)
+
+ return {'FINISHED'}
+
+
+class VIEW3D_OT_New_Camera_to_View(Operator):
+ bl_idname = "stored_views.newcamera"
+ bl_label = "New Camera To View"
+ bl_description = "Add a new Active Camera and align it to this view"
+
+ @classmethod
+ def poll(cls, context):
+ return (
+ context.space_data is not None and
+ context.space_data.type == 'VIEW_3D' and
+ context.space_data.region_3d.view_perspective != 'CAMERA'
+ )
+
+ def execute(self, context):
+
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ bpy.ops.object.camera_add()
+ cam = context.active_object
+ cam.name = "View_Camera"
+ # make active camera by hand
+ context.scene.camera = cam
+
+ bpy.ops.view3d.camera_to_view()
+ return {'FINISHED'}
+
+
+# Camera marker & switcher by Fsiddi
+class VIEW3D_OT_SetSceneCamera(Operator):
+ bl_idname = "cameraselector.set_scene_camera"
+ bl_label = "Set Scene Camera"
+ bl_description = "Set chosen camera as the scene's active camera"
+
+ hide_others = False
+
+ def execute(self, context):
+ chosen_camera = context.active_object
+ scene = context.scene
+
+ if self.hide_others:
+ for c in [o for o in scene.objects if o.type == 'CAMERA']:
+ c.hide = (c != chosen_camera)
+ scene.camera = chosen_camera
+ bpy.ops.object.select_all(action='DESELECT')
+ chosen_camera.select_set(True)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ if event.ctrl:
+ self.hide_others = True
+
+ return self.execute(context)
+
+
+class VIEW3D_OT_PreviewSceneCamera(Operator):
+ bl_idname = "cameraselector.preview_scene_camera"
+ bl_label = "Preview Camera"
+ bl_description = "Preview chosen camera and make scene's active camera"
+
+ def execute(self, context):
+ chosen_camera = context.active_object
+ bpy.ops.view3d.object_as_camera()
+ bpy.ops.object.select_all(action="DESELECT")
+ chosen_camera.select_set(True)
+ return {'FINISHED'}
+
+
+class VIEW3D_OT_AddCameraMarker(Operator):
+ bl_idname = "cameraselector.add_camera_marker"
+ bl_label = "Add Camera Marker"
+ bl_description = "Add a timeline marker bound to chosen camera"
+
+ def execute(self, context):
+ chosen_camera = context.active_object
+ scene = context.scene
+
+ current_frame = scene.frame_current
+ marker = None
+ for m in reversed(sorted(filter(lambda m: m.frame <= current_frame,
+ scene.timeline_markers),
+ key=lambda m: m.frame)):
+ marker = m
+ break
+ if marker and (marker.camera == chosen_camera):
+ # Cancel if the last marker at or immediately before
+ # current frame is already bound to the camera.
+ return {'CANCELLED'}
+
+ marker_name = "F_%02d_%s" % (current_frame, chosen_camera.name)
+ if marker and (marker.frame == current_frame):
+ # Reuse existing marker at current frame to avoid
+ # overlapping bound markers.
+ marker.name = marker_name
+ else:
+ marker = scene.timeline_markers.new(marker_name)
+ marker.frame = scene.frame_current
+ marker.camera = chosen_camera
+ marker.select = True
+
+ for other_marker in [m for m in scene.timeline_markers if m != marker]:
+ other_marker.select = False
+
+ return {'FINISHED'}
+
+# gpl authors: nfloyd, Francesco Siddi
+
+
+
+
+
+# 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)) == "<class 'bpy_prop_collection_idprop'>":
+ 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)) == "<class 'bpy_prop_collection_idprop'>":
+ 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 ["<class 'bpy_prop_array'>"]:
+ # array
+ dict[prop.identifier] = [v for v in val]
+ # address the pickle limitations of dealing with the Vector class
+ elif str(type(val)) in ["<class 'Vector'>",
+ "<class 'Quaternion'>"]:
+ 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_OT_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_OT_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_OT_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'}
+
+
+class VIEW3D_PT_properties_stored_views(Panel):
+ bl_label = "Stored Views"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "View"
+
+ def draw(self, context):
+ self.logger = logging.getLogger('%s Properties panel' % __name__)
+ layout = self.layout
+
+ if bpy.ops.view3d.stored_views_initialize.poll():
+ layout.operator("view3d.stored_views_initialize")
+ return
+
+ stored_views = context.scene.stored_views
+
+ # UI : mode
+ col = layout.column(align=True)
+ col.prop_enum(stored_views, "mode", 'VIEW')
+ row = layout.row(align=True)
+ row.operator("view3d.camera_to_view", text="Camera To view")
+ row.operator("stored_views.newcamera")
+
+ row = col.row(align=True)
+ row.prop_enum(stored_views, "mode", 'POV')
+ row.prop_enum(stored_views, "mode", 'LAYERS')
+ row.prop_enum(stored_views, "mode", 'DISPLAY')
+
+ # UI : operators
+ row = layout.row()
+ row.operator("stored_views.save").index = -1
+
+ # IO Operators
+ if core.get_preferences():
+ row = layout.row(align=True)
+ row.operator("stored_views.import_from_scene", text="Import from Scene")
+ row.operator("stored_views.import_blsv", text="", icon="IMPORT")
+ row.operator("stored_views.export_blsv", text="", icon="EXPORT")
+
+ data_store = DataStore()
+ list = data_store.list
+ # UI : items list
+ if len(list) > 0:
+ row = layout.row()
+ box = row.box()
+ # items list
+ mode = stored_views.mode
+ for i in range(len(list)):
+ # associated icon
+ icon_string = "MESH_CUBE" # default icon
+ # TODO: icons for view
+ if mode == 'POV':
+ persp = list[i].perspective
+ if persp == 'PERSP':
+ icon_string = "MESH_CUBE"
+ elif persp == 'ORTHO':
+ icon_string = "MESH_PLANE"
+ elif persp == 'CAMERA':
+ if list[i].camera_type != 'CAMERA':
+ icon_string = 'OBJECT_DATAMODE'
+ else:
+ icon_string = "OUTLINER_DATA_CAMERA"
+ if mode == 'LAYERS':
+ if list[i].lock_camera_and_layers is True:
+ icon_string = 'SCENE_DATA'
+ else:
+ icon_string = 'RENDERLAYERS'
+ if mode == 'DISPLAY':
+ shade = list[i].viewport_shade
+ if shade == 'TEXTURED':
+ icon_string = 'TEXTURE_SHADED'
+ if shade == 'MATERIAL':
+ icon_string = 'MATERIAL_DATA'
+ elif shade == 'SOLID':
+ icon_string = 'SOLID'
+ elif shade == 'WIREFRAME':
+ icon_string = "WIRE"
+ elif shade == 'BOUNDBOX':
+ icon_string = 'BBOX'
+ elif shade == 'RENDERED':
+ icon_string = 'MATERIAL'
+ # stored view row
+ subrow = box.row(align=True)
+ # current view indicator
+ if data_store.current_index == i and context.scene.stored_views.view_modified is False:
+ subrow.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ subrow.operator("stored_views.set",
+ text="", icon=icon_string).index = i
+ subrow.prop(list[i], "name", text="")
+ subrow.operator("stored_views.save",
+ text="", icon="REC").index = i
+ subrow.operator("stored_views.delete",
+ text="", icon="PANEL_CLOSE").index = i
+
+ layout = self.layout
+ scene = context.scene
+ layout.label(text="Camera Selector")
+ cameras = sorted([o for o in scene.objects if o.type == 'CAMERA'],
+ key=lambda o: o.name)
+
+ if len(cameras) > 0:
+ for camera in cameras:
+ row = layout.row(align=True)
+ row.context_pointer_set("active_object", camera)
+ row.operator("cameraselector.set_scene_camera",
+ text=camera.name, icon='OUTLINER_DATA_CAMERA')
+ row.operator("cameraselector.preview_scene_camera",
+ text='', icon='RESTRICT_VIEW_OFF')
+ row.operator("cameraselector.add_camera_marker",
+ text='', icon='MARKER')
+ else:
+ layout.label(text="No cameras in this scene")
+# Addon Preferences
+
+class VIEW3D_OT_stored_views_preferences(AddonPreferences):
+ bl_idname = __name__
+
+ show_exporters: BoolProperty(
+ name="Enable I/O Operators",
+ default=False,
+ description="Enable Import/Export Operations in the UI:\n"
+ "Import Stored Views preset,\n"
+ "Export Stored Views preset and \n"
+ "Import stored views from scene",
+ )
+ view_3d_update_rate: IntProperty(
+ name="3D view update",
+ description="Update rate of the 3D view redraw\n"
+ "Increse the value if the UI feels sluggish",
+ min=1, max=10,
+ default=1
+ )
+
+ def draw(self, context):
+ layout = self.layout
+
+ row = layout.row(align=True)
+ row.prop(self, "view_3d_update_rate", toggle=True)
+ row.prop(self, "show_exporters", toggle=True)
+
+
+# Register
+classes = [
+ VIEW3D_OT_stored_views_initialize,
+ VIEW3D_OT_stored_views_preferences,
+ VIEW3D_PT_properties_stored_views,
+ POVData,
+ LayersData,
+ DisplayData,
+ ViewData,
+ StoredViewsData,
+ VIEW3D_OT_stored_views_draw,
+ VIEW3D_OT_stored_views_save,
+ VIEW3D_OT_stored_views_set,
+ VIEW3D_OT_stored_views_delete,
+ VIEW3D_OT_New_Camera_to_View,
+ VIEW3D_OT_SetSceneCamera,
+ VIEW3D_OT_PreviewSceneCamera,
+ VIEW3D_OT_AddCameraMarker,
+# IO_Utils,
+ VIEW3D_OT_stored_views_import,
+ VIEW3D_OT_stored_views_import_from_scene,
+ VIEW3D_OT_stored_views_export
+ ]
+
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+
+def unregister():
+ ui.VIEW3D_OT_stored_views_draw.handle_remove(bpy.context)
+ from bpy.utils import unregister_class
+ for cls in reversed(classes):
+ unregister_class(cls)
+ if hasattr(bpy.types.Scene, "stored_views"):
+ del bpy.types.Scene.stored_views
+
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_stored_views/ui.py b/space_view3d_stored_views/ui.py
new file mode 100644
index 00000000..43f3d470
--- /dev/null
+++ b/space_view3d_stored_views/ui.py
@@ -0,0 +1,277 @@
+# gpl authors: nfloyd, Francesco Siddi
+
+import logging
+module_logger = logging.getLogger(__name__)
+
+import bpy
+import blf
+from . import core
+from bpy.types import (
+ Operator,
+ Panel,
+)
+
+"""
+ If view name display is enabled,
+ it will check periodically if the view has been modified
+ since last set.
+ get_preferences_timer() is the time in seconds between these checks.
+ It can be increased, if the view become sluggish
+ It is set in the add-on preferences
+"""
+
+
+# Utility function get_preferences_timer for update of 3d view draw
+def get_preferences_timer():
+ # replace the key if the add-on name changes
+ # TODO: expose refresh rate to ui???
+ addon = bpy.context.preferences.addons[__package__]
+ timer_update = (addon.preferences.view_3d_update_rate if addon else False)
+
+ return timer_update
+
+
+def init_draw(context=None):
+ if context is None:
+ context = bpy.context
+
+ if "stored_views_osd" not in context.window_manager:
+ context.window_manager["stored_views_osd"] = False
+
+ if not context.window_manager["stored_views_osd"]:
+ context.window_manager["stored_views_osd"] = True
+ bpy.ops.stored_views.draw()
+
+
+def _draw_callback_px(self, context):
+ if context.area and context.area.type == 'VIEW_3D':
+ r_width = text_location = context.region.width
+ r_height = context.region.height
+ font_id = 0 # TODO: need to find out how best to get font_id
+
+ blf.size(font_id, 11, context.preferences.system.dpi)
+ text_size = blf.dimensions(0, self.view_name)
+
+ # compute the text location
+ text_location = 0
+ overlap = context.preferences.system.use_region_overlap
+ if overlap:
+ for region in context.area.regions:
+ if region.type == "UI":
+ text_location = r_width - region.width
+
+ text_x = text_location - text_size[0] - 10
+ text_y = r_height - text_size[1] - 8
+ blf.position(font_id, text_x, text_y, 0)
+ blf.draw(font_id, self.view_name)
+
+
+class VIEW3D_stored_views_draw(Operator):
+ bl_idname = "stored_views.draw"
+ bl_label = "Show current"
+ bl_description = "Toggle the display current view name in the view 3D"
+
+ _handle = None
+ _timer = None
+
+ @staticmethod
+ def handle_add(self, context):
+ VIEW3D_stored_views_draw._handle = bpy.types.SpaceView3D.draw_handler_add(
+ _draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
+ VIEW3D_stored_views_draw._timer = \
+ context.window_manager.event_timer_add(get_preferences_timer(), window=context.window)
+
+ @staticmethod
+ def handle_remove(context):
+ if VIEW3D_stored_views_draw._handle is not None:
+ bpy.types.SpaceView3D.draw_handler_remove(VIEW3D_stored_views_draw._handle, 'WINDOW')
+ if VIEW3D_stored_views_draw._timer is not None:
+ context.window_manager.event_timer_remove(VIEW3D_stored_views_draw._timer)
+ VIEW3D_stored_views_draw._handle = None
+ VIEW3D_stored_views_draw._timer = None
+
+ @classmethod
+ def poll(cls, context):
+ # return context.mode == 'OBJECT'
+ return True
+
+ def modal(self, context, event):
+ if context.area:
+ context.area.tag_redraw()
+
+ if not context.area or context.area.type != "VIEW_3D":
+ return {"PASS_THROUGH"}
+
+ data = core.DataStore()
+ stored_views = context.scene.stored_views
+
+ if len(data.list) > 0 and \
+ data.current_index >= 0 and \
+ not stored_views.view_modified:
+
+ if not stored_views.view_modified:
+ sv = data.list[data.current_index]
+ self.view_name = sv.name
+ if event.type == 'TIMER':
+ is_modified = False
+ if data.mode == 'VIEW':
+ is_modified = core.View.is_modified(context, sv)
+ elif data.mode == 'POV':
+ is_modified = core.POV.is_modified(context, sv)
+ elif data.mode == 'LAYERS':
+ is_modified = core.Layers.is_modified(context, sv)
+ elif data.mode == 'DISPLAY':
+ is_modified = core.Display.is_modified(context, sv)
+ if is_modified:
+ module_logger.debug(
+ 'view modified - index: %s name: %s' % (data.current_index, sv.name)
+ )
+ self.view_name = ""
+ stored_views.view_modified = is_modified
+
+ return {"PASS_THROUGH"}
+ else:
+ module_logger.debug('exit')
+ context.window_manager["stored_views_osd"] = False
+ VIEW3D_stored_views_draw.handle_remove(context)
+
+ return {'FINISHED'}
+
+ def execute(self, context):
+ if context.area.type == "VIEW_3D":
+ self.view_name = ""
+ VIEW3D_stored_views_draw.handle_add(self, context)
+ context.window_manager.modal_handler_add(self)
+
+ return {"RUNNING_MODAL"}
+ else:
+ self.report({"WARNING"}, "View3D not found. Operation Cancelled")
+
+ return {"CANCELLED"}
+
+
+class VIEW3D_PT_properties_stored_views(Panel):
+ bl_label = "Stored Views"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "StoredViews"
+
+ def draw(self, context):
+ self.logger = logging.getLogger('%s Properties panel' % __name__)
+ layout = self.layout
+
+ if bpy.ops.view3d.stored_views_initialize.poll():
+ layout.operator("view3d.stored_views_initialize")
+ return
+
+ stored_views = context.scene.stored_views
+
+ # UI : mode
+ col = layout.column(align=True)
+ col.prop_enum(stored_views, "mode", 'VIEW')
+ row = layout.row(align=True)
+ row.operator("view3d.camera_to_view", text="Camera To view")
+ row.operator("stored_views.newcamera")
+
+ row = col.row(align=True)
+ row.prop_enum(stored_views, "mode", 'POV')
+ # row.prop_enum(stored_views, "mode", 'LAYERS')
+ # row.prop_enum(stored_views, "mode", 'DISPLAY')
+
+ # UI : operators
+ row = layout.row()
+ row.operator("stored_views.save").index = -1
+
+ # IO Operators
+ if core.get_preferences():
+ row = layout.row(align=True)
+ row.operator("stored_views.import_from_scene", text="Import from Scene")
+ row.operator("stored_views.import_blsv", text="", icon="IMPORT")
+ row.operator("stored_views.export_blsv", text="", icon="EXPORT")
+
+ data_store = core.DataStore()
+ list = data_store.list
+ # UI : items list
+ if len(list) > 0:
+ row = layout.row()
+ box = row.box()
+ # items list
+ mode = stored_views.mode
+ for i in range(len(list)):
+ # associated icon
+ icon_string = "MESH_CUBE" # default icon
+ # TODO: icons for view
+ if mode == 'POV':
+ persp = list[i].perspective
+ if persp == 'PERSP':
+ icon_string = "MESH_CUBE"
+ elif persp == 'ORTHO':
+ icon_string = "MESH_PLANE"
+ elif persp == 'CAMERA':
+ if list[i].camera_type != 'CAMERA':
+ icon_string = 'OBJECT_DATAMODE'
+ else:
+ icon_string = "OUTLINER_DATA_CAMERA"
+ if mode == 'LAYERS':
+ if list[i].lock_camera_and_layers is True:
+ icon_string = 'SCENE_DATA'
+ else:
+ icon_string = 'RENDERLAYERS'
+ if mode == 'DISPLAY':
+ shade = list[i].viewport_shade
+ if shade == 'TEXTURED':
+ icon_string = 'TEXTURE_SHADED'
+ if shade == 'MATERIAL':
+ icon_string = 'MATERIAL_DATA'
+ elif shade == 'SOLID':
+ icon_string = 'SOLID'
+ elif shade == 'WIREFRAME':
+ icon_string = "WIRE"
+ elif shade == 'BOUNDBOX':
+ icon_string = 'BBOX'
+ elif shade == 'RENDERED':
+ icon_string = 'MATERIAL'
+ # stored view row
+ subrow = box.row(align=True)
+ # current view indicator
+ if data_store.current_index == i and context.scene.stored_views.view_modified is False:
+ subrow.label(text="", icon='SMALL_TRI_RIGHT_VEC')
+ subrow.operator("stored_views.set",
+ text="", icon=icon_string).index = i
+ subrow.prop(list[i], "name", text="")
+ subrow.operator("stored_views.save",
+ text="", icon="REC").index = i
+ subrow.operator("stored_views.delete",
+ text="", icon="PANEL_CLOSE").index = i
+
+ layout = self.layout
+ scene = context.scene
+ layout.label(text="Camera Selector")
+ cameras = sorted([o for o in scene.objects if o.type == 'CAMERA'],
+ key=lambda o: o.name)
+
+ if len(cameras) > 0:
+ for camera in cameras:
+ row = layout.row(align=True)
+ row.context_pointer_set("active_object", camera)
+ row.operator("cameraselector.set_scene_camera",
+ text=camera.name, icon='OUTLINER_DATA_CAMERA')
+ row.operator("cameraselector.preview_scene_camera",
+ text='', icon='RESTRICT_VIEW_OFF')
+ row.operator("cameraselector.add_camera_marker",
+ text='', icon='MARKER')
+ else:
+ layout.label(text="No cameras in this scene")
+
+classes = (
+ VIEW3D_stored_views_draw,
+ VIEW3D_PT_properties_stored_views
+)
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)