Commit f0d811cb authored by Brendon Murphy's avatar Brendon Murphy

adding stored views to addons contrib for development.

contributors, nfloyd, CoDEmanX, meta-androcto
tracker: https://developer.blender.org/T27476Signed-off-by: default avatarBrendon Murphy <meta.androcto1@gmail.com>
parent 50b966d3
# ##### 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",
"version": (0, 3, 3, 'beta'),
"blender": (2, 71, 0),
"location": "View3D > Properties > Stored Views",
"warning": 'beta release, single view only',
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/3D_interaction/stored_views",
"tracker_url": "https://developer.blender.org/T27476",
"category": "3D View"}
# ACKNOWLEDGMENT
# ==============
# import/export functionality is mostly based
# on Bart Crouch's Theme Manager Addon
# CHANGELOG
# =========
# 0.1.0 : _ initial release
# 0.2.0 : _ quadview support
# _ import/export functionality from/to preset files
# inspired - that is an euphemism - from Bart Crouch Theme Manager Addon
# _ import data from an another scene
# 0.2.1 : _ improved previous / toggle logic
# _ fix : object reference works if name has changed
# _ fix for python api change 36710
# _ checks on data import (scene or preset file)
# 0.2.2 : _ fix : previous / toggle
# _ io filtering
# _ stored views name display in 3d view (experimental)
# _ UI tweaks
# _ generate unique view name
# _ added wiki and tracker url
# 0.3.0 _ refactor
# _ removed previous, io ui
# _ fix: POV.is_modified - use perspective_matrix insted of view_matrix
# - presets are no longer backward compatible
# TODO: check against 2.63
# TODO: quadview complete support : investigate. Where's the data?
# TODO: lock_camera_and_layers. investigate usage
# TODO: list reordering
# logging setup
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
hdlr = logging.StreamHandler()
fmtr = logging.Formatter('%(asctime)s %(levelname)s %(name)s : %(funcName)s - %(message)s')
hdlr.setFormatter(fmtr)
logger.addHandler(hdlr)
if "bpy" in locals():
import imp
imp.reload(ui)
imp.reload(properties)
imp.reload(core)
imp.reload(operators)
imp.reload(io)
else:
#from . import properties, core
from . import ui, properties, core, operators, io
import bpy
from bpy.props import PointerProperty
class VIEW3D_stored_views_initialize(bpy.types.Operator):
bl_idname = "view3d.stored_views_initialize"
bl_label = "Initilize"
@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'}
def register():
bpy.utils.register_module(__name__)
# Context restricted, need to initialize different (button to be clicked by user)
#initialize()
def unregister():
ui.VIEW3D_stored_views_draw.handle_remove(bpy.context)
bpy.utils.unregister_module(__name__)
if hasattr(bpy.types.Scene, "stored_views"):
del bpy.types.Scene.stored_views
if __name__ == "__main__":
register()
This diff is collapsed.
import gzip
import os
import pickle
import shutil
import bpy
from bpy.props import (BoolProperty,
StringProperty)
from bpy_extras.io_utils import ExportHelper, ImportHelper
from . import bl_info
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
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 == 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_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] == 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)
@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"]["build_revision"] = bpy.app.build_revision
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'>",
"<class 'mathutils.Quaternion'>",
"<class 'mathutils.Vector'>"]:
# array
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] == True:
dump_list(list_data[0], list_data[1])
views_data = (dump["data"]["views"], sv.view_list)
# if io_filters.views == 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='w')
pickle.dump(dump, file)
file.close()
@staticmethod
def stored_views_apply_preset(filepath, replace=True):
file = gzip.open(filepath, mode='r')
dump = pickle.load(file)
file.close()
# apply preset
scene = bpy.context.scene
sv = scene.stored_views
# 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 == False:
# continue
sv_list = sv_data[sv_struct]#.list
if replace == 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 type(subval) == type({}): # views : pov, layers, displays
v_subprop = getattr(sv_item, subprop)
for v_subkey, v_subval in subval.items():
if type(v_subval) == type([]): # 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 type(subval) == type([]):
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)
class VIEW3D_stored_views_import(bpy.types.Operator, ImportHelper):
bl_idname = "stored_views.import"
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")
def execute(self, context):
# apply chosen preset
IO_Utils.stored_views_apply_preset(filepath=self.filepath, replace=self.replace)
# 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(bpy.types.Operator):
bl_idname = "stored_views.import_from_scene"
bl_label = "Import stored views from scene"
bl_description = "Import current stored views by those from another scene"
scene_name = bpy.props.StringProperty(name="Scene Name",
description="A current blend scene",
default="")
replace = BoolProperty(name="Replace",
default=True,
description="Replace current stored views, otherwise append")
def execute(self, context):
# filepath should always be given
if not self.scene_name:
self.report("ERROR", "Could not find scene")
return{'CANCELLED'}
IO_Utils.stored_views_apply_from_scene(self.scene_name, replace=self.replace)
return{'FINISHED'}
class VIEW3D_stored_views_export(bpy.types.Operator, ExportHelper):
bl_idname = "stored_views.export"
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")
def execute(self, context):
IO_Utils.stored_views_export_to_blsv(self.filepath, self.preset_name)
return{'FINISHED'}
import bpy
from bpy.props import IntProperty
from . core import stored_view_factory, DataStore
from . ui import init_draw
class VIEW3D_stored_views_save(bpy.types.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(bpy.types.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(bpy.types.Operator):
bl_idname = "stored_views.delete"
bl_label = "Delete"
bl_description = "Delete this view"
index = bpy.props.IntProperty()
def execute(self, context):
data = DataStore()
data.delete(self.index)
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()
camera_pointer = IntProperty()
lock_object_name = StringProperty()
lock_object_pointer = IntProperty()
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')])
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 = EnumProperty(items=[('TEXTURE_FACE', '', ''),
('MULTITEXTURE', '', ''),
('GLSL', '', '')])
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', ''),
('POV', 'POV', ''),
('LAYERS', 'Layers', ''),
('DISPLAY', 'Display', '')],
default='VIEW')
current_indices = IntVectorProperty(size=4, default=[-1, -1, -1, -1])
view_modified = BoolProperty(default=False)
import logging
module_logger = logging.getLogger(__name__)
import bpy
import blf
from . import core
# If view name display is enabled,
# it will check periodically if the view has been modified
# since last set.
# VIEW_MODIFIED_TIMER is the time in seconds between these checks.
# It can be increased, if the view become sluggish
VIEW_MODIFIED_TIMER = 1
# TODO: expose refresh rate to ui???
# TODO: ui for import/export
def init_draw(context=None):
if context == 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):
try:
print("SELF", self)
except:
print("red err?")
r_width = 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, 72)
text_size = blf.dimensions(0, self.view_name)
text_x = r_width - 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(bpy.types.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(VIEW_MODIFIED_TIMER, 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.type 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, can't run operator")
return {"CANCELLED"}
class VIEW3D_PT_properties_stored_views(bpy.types.Panel):
bl_label = "Stored Views"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self, context):
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()
row.operator("view3d.camera_to_view", text="Camera To view")
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
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 == True:
icon_string = 'SCENE_DATA'
else:
icon_string = 'RENDERLAYERS'
if mode == 'DISPLAY':
shade = list[i].viewport_shade
if shade == 'TEXTURED':
icon_string = 'TEXTURE_SHADED'
elif shade == 'SOLID':
icon_string = 'SOLID'
elif shade == 'WIREFRAME':
icon_string = "WIRE"
elif shade == 'BOUNDBOX':
icon_string = 'BBOX'
# stored view row
subrow = box.row(align=True)
# current view indicator
if data_store.current_index == i and context.scene.stored_views.view_modified == 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment