space_view3d_stored_views: Restore release status: T63750 d9ffde707f

This commit is contained in:
Brendon Murphy 2019-06-15 10:59:45 +10:00
parent 15b00e9396
commit 20a8e9d3f5
7 changed files with 2828 additions and 0 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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)