diff options
author | Campbell Barton <ideasman42@gmail.com> | 2017-07-24 18:42:10 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2017-07-24 18:43:02 +0300 |
commit | 49230a4c122013bf851ab37b029e7d4f624d68ad (patch) | |
tree | 5cbf15367b776c1086376071a16d66604177c998 /space_view3d_stored_views | |
parent | d1b57c76ad5cfd475ad270a93fed2c530fc3e7d0 (diff) |
Cleanup: use common prefix for addons
Diffstat (limited to 'space_view3d_stored_views')
-rw-r--r-- | space_view3d_stored_views/__init__.py | 137 | ||||
-rw-r--r-- | space_view3d_stored_views/core.py | 391 | ||||
-rw-r--r-- | space_view3d_stored_views/io.py | 327 | ||||
-rw-r--r-- | space_view3d_stored_views/operators.py | 170 | ||||
-rw-r--r-- | space_view3d_stored_views/properties.py | 126 | ||||
-rw-r--r-- | space_view3d_stored_views/ui.py | 263 |
6 files changed, 1414 insertions, 0 deletions
diff --git a/space_view3d_stored_views/__init__.py b/space_view3d_stored_views/__init__.py new file mode 100644 index 00000000..8deca8f0 --- /dev/null +++ b/space_view3d_stored_views/__init__.py @@ -0,0 +1,137 @@ +# ##### 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, 6), + "blender": (2, 7, 8), + "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: + from . import core + from . import ui + from . import properties + from . import operators + from . import io + +import bpy +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(): + bpy.utils.register_module(__name__) + + +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/space_view3d_stored_views/core.py b/space_view3d_stored_views/core.py new file mode 100644 index 00000000..b3e7c6f4 --- /dev/null +++ b/space_view3d_stored_views/core.py @@ -0,0 +1,391 @@ +# 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.user_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 + stored_view.camera_pointer = view3d.camera.as_pointer() + if view3d.lock_object is not 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 is 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 perspective + # and perspective_matrix are checked + POV.logger = logging.getLogger('%s.POV' % __name__) + view3d = context.space_data + region3d = view3d.region_3d + if region3d.view_perspective != stored_view.perspective: + POV.logger.debug('view_perspective') + return True + + md5 = POV._get_perspective_matrix_md5(region3d) + if (md5 != stored_view.perspective_matrix_md5 and + region3d.view_perspective != "CAMERA"): + POV.logger.debug('perspective_matrix') + return True + + return False + + @staticmethod + def _get_perspective_matrix_md5(region3d): + md5 = hashlib.md5(str(region3d.perspective_matrix).encode('utf-8')).hexdigest() + return md5 + + +class Layers(StoredView): + def __init__(self, index=None): + super().__init__(mode='LAYERS', index=index) + self.logger = logging.getLogger('%s.Layers' % __name__) + + def from_v3d(self, stored_view): + view3d = self.view3d + stored_view.view_layers = view3d.layers + stored_view.scene_layers = self.scene.layers + stored_view.lock_camera_and_layers = view3d.lock_camera_and_layers + + def update_v3d(self, stored_view): + view3d = self.view3d + view3d.lock_camera_and_layers = stored_view.lock_camera_and_layers + if stored_view.lock_camera_and_layers is True: + self.scene.layers = stored_view.scene_layers + else: + view3d.layers = stored_view.view_layers + + @staticmethod + def is_modified(context, stored_view): + Layers.logger = logging.getLogger('%s.Layers' % __name__) + if stored_view.lock_camera_and_layers != context.space_data.lock_camera_and_layers: + Layers.logger.debug('lock_camera_and_layers') + return True + if stored_view.lock_camera_and_layers is True: + for i in range(20): + if stored_view.scene_layers[i] != context.scene.layers[i]: + Layers.logger.debug('scene_layers[%s]' % (i, )) + return True + else: + for i in range(20): + if stored_view.view_layers[i] != context.space_data.view3d.layers[i]: + return True + return False + + +class Display(StoredView): + def __init__(self, index=None): + super().__init__(mode='DISPLAY', index=index) + self.logger = logging.getLogger('%s.Display' % __name__) + + def from_v3d(self, stored_view): + view3d = self.view3d + stored_view.viewport_shade = view3d.viewport_shade + stored_view.show_only_render = view3d.show_only_render + stored_view.show_outline_selected = view3d.show_outline_selected + stored_view.show_all_objects_origin = view3d.show_all_objects_origin + stored_view.show_relationship_lines = view3d.show_relationship_lines + stored_view.show_floor = view3d.show_floor + stored_view.show_axis_x = view3d.show_axis_x + stored_view.show_axis_y = view3d.show_axis_y + stored_view.show_axis_z = view3d.show_axis_z + stored_view.grid_lines = view3d.grid_lines + stored_view.grid_scale = view3d.grid_scale + stored_view.grid_subdivisions = view3d.grid_subdivisions + stored_view.material_mode = self.scene.game_settings.material_mode + stored_view.show_textured_solid = view3d.show_textured_solid + + def update_v3d(self, stored_view): + view3d = self.view3d + view3d.viewport_shade = stored_view.viewport_shade + view3d.show_only_render = stored_view.show_only_render + view3d.show_outline_selected = stored_view.show_outline_selected + view3d.show_all_objects_origin = stored_view.show_all_objects_origin + view3d.show_relationship_lines = stored_view.show_relationship_lines + view3d.show_floor = stored_view.show_floor + view3d.show_axis_x = stored_view.show_axis_x + view3d.show_axis_y = stored_view.show_axis_y + view3d.show_axis_z = stored_view.show_axis_z + view3d.grid_lines = stored_view.grid_lines + view3d.grid_scale = stored_view.grid_scale + view3d.grid_subdivisions = stored_view.grid_subdivisions + self.scene.game_settings.material_mode = stored_view.material_mode + view3d.show_textured_solid = stored_view.show_textured_solid + + @staticmethod + def is_modified(context, stored_view): + Display.logger = logging.getLogger('%s.Display' % __name__) + view3d = context.space_data + excludes = ["material_mode", "quad_view", "lock_rotation", "show_sync_view", "use_box_clip", "name"] + for k, v in stored_view.items(): + if k not in excludes: + if getattr(view3d, k) != getattr(stored_view, k): + return True + + if stored_view.material_mode != context.scene.game_settings.material_mode: + Display.logger.debug('material_mode') + return True + + +class View(StoredView): + def __init__(self, index=None): + super().__init__(mode='VIEW', index=index) + self.logger = logging.getLogger('%s.View' % __name__) + self.pov = POV() + self.layers = Layers() + self.display = Display() + + def from_v3d(self, stored_view): + self.pov.from_v3d(stored_view.pov) + self.layers.from_v3d(stored_view.layers) + self.display.from_v3d(stored_view.display) + + def update_v3d(self, stored_view): + self.pov.update_v3d(stored_view.pov) + self.layers.update_v3d(stored_view.layers) + self.display.update_v3d(stored_view.display) + + @staticmethod + def is_modified(context, stored_view): + if POV.is_modified(context, stored_view.pov) or \ + Layers.is_modified(context, stored_view.layers) or \ + Display.is_modified(context, stored_view.display): + return True + return False + + +class DataStore(): + def __init__(self, scene=None, mode=None): + if scene is None: + scene = bpy.context.scene + stored_views = scene.stored_views + self.mode = mode + + if mode is None: + self.mode = stored_views.mode + + if self.mode == 'VIEW': + self.list = stored_views.view_list + self.current_index = stored_views.current_indices[0] + elif self.mode == 'POV': + self.list = stored_views.pov_list + self.current_index = stored_views.current_indices[1] + elif self.mode == 'LAYERS': + self.list = stored_views.layers_list + self.current_index = stored_views.current_indices[2] + elif self.mode == 'DISPLAY': + self.list = stored_views.display_list + self.current_index = stored_views.current_indices[3] + + def create(self): + item = self.list.add() + item.name = self._generate_name() + index = len(self.list) - 1 + self._set_current_index(index) + return item, index + + def get(self, index): + self._set_current_index(index) + return self.list[index] + + def delete(self, index): + if self.current_index > index: + self._set_current_index(self.current_index - 1) + elif self.current_index == index: + self._set_current_index(-1) + + self.list.remove(index) + + def _set_current_index(self, index): + self.current_index = index + mode = self.mode + stored_views = bpy.context.scene.stored_views + if mode == 'VIEW': + stored_views.current_indices[0] = index + elif mode == 'POV': + stored_views.current_indices[1] = index + elif mode == 'LAYERS': + stored_views.current_indices[2] = index + elif mode == 'DISPLAY': + stored_views.current_indices[3] = index + + def _generate_name(self): + default_name = str(self.mode) + names = [] + for i in self.list: + i_name = i.name + if i_name.startswith(default_name): + names.append(i_name) + names.sort() + try: + l_name = names[-1] + post_fix = l_name.rpartition('.')[2] + if post_fix.isnumeric(): + post_fix = str(int(post_fix) + 1).zfill(3) + else: + if post_fix == default_name: + post_fix = "001" + return default_name + "." + post_fix + except: + return default_name + + @staticmethod + def sanitize_data(scene): + + def check_objects_references(mode, list): + 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/space_view3d_stored_views/io.py b/space_view3d_stored_views/io.py new file mode 100644 index 00000000..b6aec4dd --- /dev/null +++ b/space_view3d_stored_views/io.py @@ -0,0 +1,327 @@ +# 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] + # adress 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" + 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" + 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'} diff --git a/space_view3d_stored_views/operators.py b/space_view3d_stored_views/operators.py new file mode 100644 index 00000000..1ec3026b --- /dev/null +++ b/space_view3d_stored_views/operators.py @@ -0,0 +1,170 @@ +# gpl authors: nfloyd, Francesco Siddi + +import bpy +from bpy.props import ( + IntProperty, + StringProperty, + ) +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" + + def execute(self, context): + try: + # check for operator's poll (there is no active object, for instance) + if bpy.ops.object.mode_set.poll(): + 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'} + except: + self.report({'WARNING'}, + "Operation Cancelled. New Camera to View failed to add a new camera") + return {'CANCELLED'} + + +# 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" + + chosen_camera = 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(Operator): + bl_idname = "cameraselector.add_camera_marker" + bl_label = "Add Camera Marker" + bl_description = "Add a timeline marker bound to chosen camera" + + chosen_camera = StringProperty() + + def execute(self, context): + chosen_camera = bpy.data.objects.get(self.chosen_camera, None) + scene = context.scene + if not chosen_camera: + self.report({'WARNING'}, "Camera %s not found. Operation Cancelled") + 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/space_view3d_stored_views/properties.py b/space_view3d_stored_views/properties.py new file mode 100644 index 00000000..6dffc3d0 --- /dev/null +++ b/space_view3d_stored_views/properties.py @@ -0,0 +1,126 @@ +# gpl authors: nfloyd, Francesco Siddi + +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'), + ('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 = 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", "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 + ) diff --git a/space_view3d_stored_views/ui.py b/space_view3d_stored_views/ui.py new file mode 100644 index 00000000..c35acfff --- /dev/null +++ b/space_view3d_stored_views/ui.py @@ -0,0 +1,263 @@ +# 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.user_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.user_preferences.system.dpi) + text_size = blf.dimensions(0, self.view_name) + + # compute the text location + text_location = 0 + overlap = context.user_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(), 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" + + 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", 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 + + # 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", text="", icon="IMPORT") + row.operator("stored_views.export", 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("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") |