diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2017-04-17 05:11:25 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2017-04-17 05:11:25 +0300 |
commit | 6e967f1ac2432a69dd4808018165689850adb511 (patch) | |
tree | 3cce26d694cde71bacc4caefe66619543a4ebf5f /stored_views | |
parent | 844db32ac4e719a5ec84b028a12746fe062f3e74 (diff) |
Initial Commit Stored Views: T51228 T50357
Diffstat (limited to 'stored_views')
-rw-r--r-- | stored_views/__init__.py | 96 | ||||
-rw-r--r-- | stored_views/core.py | 382 | ||||
-rw-r--r-- | stored_views/io.py | 238 | ||||
-rw-r--r-- | stored_views/operators.py | 156 | ||||
-rw-r--r-- | stored_views/properties.py | 90 | ||||
-rw-r--r-- | stored_views/ui.py | 227 |
6 files changed, 1189 insertions, 0 deletions
diff --git a/stored_views/__init__.py b/stored_views/__init__.py new file mode 100644 index 00000000..cfc0beb0 --- /dev/null +++ b/stored_views/__init__.py @@ -0,0 +1,96 @@ +# ##### 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, 4,), + "blender": (2, 7, 8), + "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/maniphest/task/edit/form/2/", + "category": "3D View"} + +# ACKNOWLEDGMENT +# ============== +# import/export functionality is mostly based +# on Bart Crouch's Theme Manager Addon + +# 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() diff --git a/stored_views/core.py b/stored_views/core.py new file mode 100644 index 00000000..21e49b31 --- /dev/null +++ b/stored_views/core.py @@ -0,0 +1,382 @@ +''' +import logging +module_logger = logging.getLogger(__name__) +''' +import hashlib + +import bpy + + +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 + stored_view.camera_pointer = view3d.camera.as_pointer() + if view3d.lock_object != None: + stored_view.lock_object_name = view3d.lock_object.name # idem + stored_view.lock_object_pointer = view3d.lock_object.as_pointer() # idem + + 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 == True: + # update cursor only if view is locked to cursor + view3d.cursor_location = stored_view.cursor_location + + if stored_view.perspective == "CAMERA": + cam = self._get_object(stored_view.camera_name, stored_view.camera_pointer) + if cam: + # in case the camera is found by pointer, update name + stored_view.camera_name = cam.name + view3d.camera = cam + else: + # TODO: camera object not found + pass + + if stored_view.lock_object_name != "": + lock_obj = self._get_object(stored_view.lock_object_name, stored_view.lock_object_pointer) + if lock_obj: + # in case the lock object is found by pointer, update name + stored_view.lock_object_name = lock_obj.name + view3d.lock_object = lock_obj + else: + # TODO: handle lock object not found + pass + + @staticmethod + def _get_object(name, pointer=None): + obj = None + try: + obj = bpy.data.objects[name] + except: + if pointer: + scene_objects = bpy.data.objects + for o in scene_objects: + p = o.as_pointer() + if p == pointer: + obj = o + break + return obj + + @staticmethod + def is_modified(context, stored_view): + # TODO: check for others param, currently only perspectiveand perspective_matrix are checked +# logger = logging.getLogger('%s.POV' % __name__) + view3d = context.space_data + region3d = view3d.region_3d + if region3d.view_perspective != stored_view.perspective: +# 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"): +# 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 == True: + self.scene.layers = stored_view.scene_layers + else: + view3d.layers = stored_view.view_layers + + @staticmethod + def is_modified(context, stored_view): +# logger = logging.getLogger('%s.Layers' % __name__) + if stored_view.lock_camera_and_layers != context.space_data.lock_camera_and_layers: +# logger.debug('lock_camera_and_layers') + return True + if stored_view.lock_camera_and_layers == True: + for i in range(20): + if stored_view.scene_layers[i] != context.scene.layers[i]: +# 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]: +# logger.debug('view_layers[%s]' % (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): + # 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): +# logger.debug('%s' % (k ,)) + return True + + if stored_view.material_mode != context.scene.game_settings.material_mode: +# 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 == None: + scene = bpy.context.scene + stored_views = scene.stored_views + self.mode = mode + + if mode == 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): + for key, item in list.items(): + if mode == 'POV' or mode == 'VIEWS': + if mode == 'VIEWS': + item = item.pov + + if item.perspective == "CAMERA": + try: + camera = bpy.data.objects[item.camera_name] + item.camera_pointer = camera.as_pointer() + except: + try: # pick a default camera TODO: ask to pick? + camera = bpy.data.cameras[0] + item.camera_name = camera.name + item.camera_pointer = camera.as_pointer() + except: # couldn't find a camera in the scene + list.remove(key) # TODO: create one instead? + + if item.lock_object_name != "": + try: # get object from name string + object = bpy.data.objects[item.lock_object_name] + item.lock_object_pointer = object.as_pointer() + except: + item.lock_object_name = "" + + 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/stored_views/io.py b/stored_views/io.py new file mode 100644 index 00000000..862b2f92 --- /dev/null +++ b/stored_views/io.py @@ -0,0 +1,238 @@ +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='wb') + pickle.dump(dump, file, protocol=pickle.HIGHEST_PROTOCOL) + file.close() + + @staticmethod + def stored_views_apply_preset(filepath, replace=True): + file = gzip.open(filepath, mode='rb') + 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'} diff --git a/stored_views/operators.py b/stored_views/operators.py new file mode 100644 index 00000000..d4500502 --- /dev/null +++ b/stored_views/operators.py @@ -0,0 +1,156 @@ +import bpy + +from bpy.props import (FloatProperty, BoolProperty, IntProperty, + FloatVectorProperty, StringProperty, EnumProperty) +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'} + +class VIEW3D_New_Camera_to_View(bpy.types.Operator): + bl_idname = "stored_views.newcamera" + bl_label = "New Camera To View" + bl_description = "Add a new Casmera Active & Aligned to this view" + + def execute(self, context): + bpy.ops.object.mode_set(mode='OBJECT') + + bpy.ops.object.camera_add( + view_align=True) + cam = bpy.context.active_object + bpy.ops.view3d.object_as_camera() + bpy.ops.view3d.camera_to_view() + + + # this will name the Camera Object + if 'View_Camera' not in context.scene.objects: + cam.name = "View_Camera" + else: + cam.name = "View_Camera.000" + + return {'FINISHED'} + +### Camera marker & switcher by Fsiddi +class SetSceneCamera(bpy.types.Operator): + bl_idname = "cameraselector.set_scene_camera" + bl_label = "Set Scene Camera" + bl_description = "Set chosen camera as the scene's active camera." + + chosen_camera = bpy.props.StringProperty() + select_chosen = False + + def execute(self, context): + chosen_camera = bpy.data.objects.get(self.chosen_camera, None) + scene = context.scene + if not chosen_camera: + self.report({'ERROR'}, "Camera %s not found.") + return {'CANCELLED'} + + if self.select_chosen: + if context.mode == 'OBJECT': + for o in context.selected_objects: + o.select = False + chosen_camera.select = True + scene.objects.active = chosen_camera + for c in [o for o in scene.objects if o.type == 'CAMERA']: + c.hide = (c != chosen_camera) + scene.camera = chosen_camera + bpy.context.scene.objects.active = chosen_camera + bpy.ops.object.select_all(action='TOGGLE') + chosen_camera.select = True + return {'FINISHED'} + + def invoke(self, context, event): + if event.ctrl: + self.select_chosen = True + + return self.execute(context) + + +class AddCameraMarker(bpy.types.Operator): + bl_idname = "cameraselector.add_camera_marker" + bl_label = "Add Camera Marker" + bl_description = "Add a timeline marker bound to chosen camera." + + chosen_camera = bpy.props.StringProperty() + + def execute(self, context): + chosen_camera = bpy.data.objects.get(self.chosen_camera, None) + scene = context.scene + if not chosen_camera: + self.report({'ERROR'}, "Camera %s not found.") + return {'CANCELLED'} + + 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, self.chosen_camera) + 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'} diff --git a/stored_views/properties.py b/stored_views/properties.py new file mode 100644 index 00000000..f011ce68 --- /dev/null +++ b/stored_views/properties.py @@ -0,0 +1,90 @@ +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'), + ('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 = 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) diff --git a/stored_views/ui.py b/stored_views/ui.py new file mode 100644 index 00000000..f1d7df12 --- /dev/null +++ b/stored_views/ui.py @@ -0,0 +1,227 @@ +''' +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): + + 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.operator("stored_views.newcamera", text="New 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' + 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 == 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("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) + btn = row.operator("cameraselector.set_scene_camera", + text=camera.name, icon='OUTLINER_DATA_CAMERA') + btn.chosen_camera = camera.name + + btn = row.operator("cameraselector.add_camera_marker", + text='', icon='MARKER') + btn.chosen_camera = camera.name + else: + layout.label("No cameras in this scene") |