1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "Stored Views",
7 "description": "Save and restore User defined views, pov, layers and display configs",
8 "author": "nfloyd, Francesco Siddi",
10 "blender": (2, 80, 0),
11 "location": "View3D > Properties > Stored Views",
13 "doc_url": "https://wiki.blender.org/index.php/Extensions:2.5/"
14 "Py/Scripts/3D_interaction/stored_views",
21 import/export functionality is mostly based
22 on Bart Crouch's Theme Manager Addon
24 TODO: quadview complete support : investigate. Where's the data?
25 TODO: lock_camera_and_layers. investigate usage
28 NOTE: logging setup has to be provided by the user in a separate config file
29 as Blender will not try to configure logging by default in an add-on
30 The Config File should be in the Blender Config folder > /scripts/startup/config_logging.py
31 For setting up /location of the config folder see:
32 https://docs.blender.org/manual/en/latest/getting_started/
33 installing/configuration/directories.html
34 For configuring logging itself in the file, general Python documentation should work
35 As the logging calls are not configured, they can be kept in the other modules of this add-on
36 and will not have output until the logging configuration is set up
41 from bpy
.props
import (
46 from bpy
.types
import (
53 module_logger
= logging
.getLogger(__name__
)
60 from bpy_extras
.io_utils
import (
71 # Utility function get preferences setting for exporters
72 def get_preferences():
73 # replace the key if the add-on name changes
74 addon
= bpy
.context
.preferences
.addons
[__package__
]
75 show_warn
= (addon
.preferences
.show_exporters
if addon
else False)
81 def __init__(self
, mode
, index
=None):
82 self
.logger
= logging
.getLogger('%s.StoredView' % __name__
)
83 self
.scene
= bpy
.context
.scene
84 self
.view3d
= bpy
.context
.space_data
86 self
.data_store
= DataStore(mode
=mode
)
90 stored_view
, self
.index
= self
.data_store
.create()
92 stored_view
= self
.data_store
.get(self
.index
)
93 self
.from_v3d(stored_view
)
94 self
.logger
.debug('index: %s name: %s' % (self
.data_store
.current_index
, stored_view
.name
))
97 stored_view
= self
.data_store
.get(self
.index
)
98 self
.update_v3d(stored_view
)
99 self
.logger
.debug('index: %s name: %s' % (self
.data_store
.current_index
, stored_view
.name
))
101 def from_v3d(self
, stored_view
):
102 raise NotImplementedError("Subclass must implement abstract method")
104 def update_v3d(self
, stored_view
):
105 raise NotImplementedError("Subclass must implement abstract method")
108 def is_modified(context
, stored_view
):
109 raise NotImplementedError("Subclass must implement abstract method")
112 class POV(StoredView
):
113 def __init__(self
, index
=None):
114 super().__init
__(mode
='POV', index
=index
)
115 self
.logger
= logging
.getLogger('%s.POV' % __name__
)
117 def from_v3d(self
, stored_view
):
119 region3d
= view3d
.region_3d
121 stored_view
.distance
= region3d
.view_distance
122 stored_view
.location
= region3d
.view_location
123 stored_view
.rotation
= region3d
.view_rotation
124 stored_view
.perspective_matrix_md5
= POV
._get
_perspective
_matrix
_md
5(region3d
)
125 stored_view
.perspective
= region3d
.view_perspective
126 stored_view
.lens
= view3d
.lens
127 stored_view
.clip_start
= view3d
.clip_start
128 stored_view
.clip_end
= view3d
.clip_end
130 if region3d
.view_perspective
== 'CAMERA':
131 stored_view
.camera_type
= view3d
.camera
.type # type : 'CAMERA' or 'MESH'
132 stored_view
.camera_name
= view3d
.camera
.name
# store string instead of object
133 if view3d
.lock_object
is not None:
134 stored_view
.lock_object_name
= view3d
.lock_object
.name
# idem
136 stored_view
.lock_object_name
= ""
137 stored_view
.lock_cursor
= view3d
.lock_cursor
138 stored_view
.cursor_location
= view3d
.cursor_location
140 def update_v3d(self
, stored_view
):
142 region3d
= view3d
.region_3d
143 region3d
.view_distance
= stored_view
.distance
144 region3d
.view_location
= stored_view
.location
145 region3d
.view_rotation
= stored_view
.rotation
146 region3d
.view_perspective
= stored_view
.perspective
147 view3d
.lens
= stored_view
.lens
148 view3d
.clip_start
= stored_view
.clip_start
149 view3d
.clip_end
= stored_view
.clip_end
150 view3d
.lock_cursor
= stored_view
.lock_cursor
151 if stored_view
.lock_cursor
is True:
152 # update cursor only if view is locked to cursor
153 self
.scene
.cursor
.location
= stored_view
.cursor_location
155 if stored_view
.perspective
== "CAMERA":
157 lock_obj
= self
._get
_object
(stored_view
.lock_object_name
)
159 view3d
.lock_object
= lock_obj
161 cam
= self
._get
_object
(stored_view
.camera_name
)
166 def _get_object(name
, pointer
=None):
167 return bpy
.data
.objects
.get(name
)
170 def is_modified(context
, stored_view
):
171 # TODO: check for others param, currently only perspective
172 # and perspective_matrix are checked
173 POV
.logger
= logging
.getLogger('%s.POV' % __name__
)
174 view3d
= context
.space_data
175 region3d
= view3d
.region_3d
176 if region3d
.view_perspective
!= stored_view
.perspective
:
177 POV
.logger
.debug('view_perspective')
180 md5
= POV
._get
_perspective
_matrix
_md
5(region3d
)
181 if (md5
!= stored_view
.perspective_matrix_md5
and
182 region3d
.view_perspective
!= "CAMERA"):
183 POV
.logger
.debug('perspective_matrix')
189 def _get_perspective_matrix_md5(region3d
):
190 md5
= hashlib
.md5(str(region3d
.perspective_matrix
).encode('utf-8')).hexdigest()
194 class Layers(StoredView
):
195 def __init__(self
, index
=None):
196 super().__init
__(mode
='LAYERS', index
=index
)
197 self
.logger
= logging
.getLogger('%s.Layers' % __name__
)
199 def from_v3d(self
, stored_view
):
201 stored_view
.view_layers
= view3d
.layers
202 stored_view
.scene_layers
= self
.scene
.layers
203 stored_view
.lock_camera_and_layers
= view3d
.lock_camera_and_layers
205 def update_v3d(self
, stored_view
):
207 view3d
.lock_camera_and_layers
= stored_view
.lock_camera_and_layers
208 if stored_view
.lock_camera_and_layers
is True:
209 self
.scene
.layers
= stored_view
.scene_layers
211 view3d
.layers
= stored_view
.view_layers
214 def is_modified(context
, stored_view
):
215 Layers
.logger
= logging
.getLogger('%s.Layers' % __name__
)
216 if stored_view
.lock_camera_and_layers
!= context
.space_data
.lock_camera_and_layers
:
217 Layers
.logger
.debug('lock_camera_and_layers')
219 if stored_view
.lock_camera_and_layers
is True:
221 if stored_view
.scene_layers
[i
] != context
.scene
.layers
[i
]:
222 Layers
.logger
.debug('scene_layers[%s]' % (i
, ))
226 if stored_view
.view_layers
[i
] != context
.space_data
.view3d
.layers
[i
]:
231 class Display(StoredView
):
232 def __init__(self
, index
=None):
233 super().__init
__(mode
='DISPLAY', index
=index
)
234 self
.logger
= logging
.getLogger('%s.Display' % __name__
)
236 def from_v3d(self
, stored_view
):
238 stored_view
.viewport_shade
= view3d
.viewport_shade
239 stored_view
.show_only_render
= view3d
.show_only_render
240 stored_view
.show_outline_selected
= view3d
.show_outline_selected
241 stored_view
.show_all_objects_origin
= view3d
.show_all_objects_origin
242 stored_view
.show_relationship_lines
= view3d
.show_relationship_lines
243 stored_view
.show_floor
= view3d
.show_floor
244 stored_view
.show_axis_x
= view3d
.show_axis_x
245 stored_view
.show_axis_y
= view3d
.show_axis_y
246 stored_view
.show_axis_z
= view3d
.show_axis_z
247 stored_view
.grid_lines
= view3d
.grid_lines
248 stored_view
.grid_scale
= view3d
.grid_scale
249 stored_view
.grid_subdivisions
= view3d
.grid_subdivisions
250 stored_view
.material_mode
= self
.scene
.game_settings
.material_mode
251 stored_view
.show_textured_solid
= view3d
.show_textured_solid
253 def update_v3d(self
, stored_view
):
255 view3d
.viewport_shade
= stored_view
.viewport_shade
256 view3d
.show_only_render
= stored_view
.show_only_render
257 view3d
.show_outline_selected
= stored_view
.show_outline_selected
258 view3d
.show_all_objects_origin
= stored_view
.show_all_objects_origin
259 view3d
.show_relationship_lines
= stored_view
.show_relationship_lines
260 view3d
.show_floor
= stored_view
.show_floor
261 view3d
.show_axis_x
= stored_view
.show_axis_x
262 view3d
.show_axis_y
= stored_view
.show_axis_y
263 view3d
.show_axis_z
= stored_view
.show_axis_z
264 view3d
.grid_lines
= stored_view
.grid_lines
265 view3d
.grid_scale
= stored_view
.grid_scale
266 view3d
.grid_subdivisions
= stored_view
.grid_subdivisions
267 self
.scene
.game_settings
.material_mode
= stored_view
.material_mode
268 view3d
.show_textured_solid
= stored_view
.show_textured_solid
271 def is_modified(context
, stored_view
):
272 Display
.logger
= logging
.getLogger('%s.Display' % __name__
)
273 view3d
= context
.space_data
274 excludes
= ["material_mode", "quad_view", "lock_rotation", "show_sync_view", "use_box_clip", "name"]
275 for k
, v
in stored_view
.items():
276 if k
not in excludes
:
277 if getattr(view3d
, k
) != getattr(stored_view
, k
):
280 if stored_view
.material_mode
!= context
.scene
.game_settings
.material_mode
:
281 Display
.logger
.debug('material_mode')
285 class View(StoredView
):
286 def __init__(self
, index
=None):
287 super().__init
__(mode
='VIEW', index
=index
)
288 self
.logger
= logging
.getLogger('%s.View' % __name__
)
290 self
.layers
= Layers()
291 self
.display
= Display()
293 def from_v3d(self
, stored_view
):
294 self
.pov
.from_v3d(stored_view
.pov
)
295 self
.layers
.from_v3d(stored_view
.layers
)
296 self
.display
.from_v3d(stored_view
.display
)
298 def update_v3d(self
, stored_view
):
299 self
.pov
.update_v3d(stored_view
.pov
)
300 self
.layers
.update_v3d(stored_view
.layers
)
301 self
.display
.update_v3d(stored_view
.display
)
304 def is_modified(context
, stored_view
):
305 if POV
.is_modified(context
, stored_view
.pov
) or \
306 Layers
.is_modified(context
, stored_view
.layers
) or \
307 Display
.is_modified(context
, stored_view
.display
):
313 def __init__(self
, scene
=None, mode
=None):
315 scene
= bpy
.context
.scene
316 stored_views
= scene
.stored_views
320 self
.mode
= stored_views
.mode
322 if self
.mode
== 'VIEW':
323 self
.list = stored_views
.view_list
324 self
.current_index
= stored_views
.current_indices
[0]
325 elif self
.mode
== 'POV':
326 self
.list = stored_views
.pov_list
327 self
.current_index
= stored_views
.current_indices
[1]
328 elif self
.mode
== 'LAYERS':
329 self
.list = stored_views
.layers_list
330 self
.current_index
= stored_views
.current_indices
[2]
331 elif self
.mode
== 'DISPLAY':
332 self
.list = stored_views
.display_list
333 self
.current_index
= stored_views
.current_indices
[3]
336 item
= self
.list.add()
337 item
.name
= self
._generate
_name
()
338 index
= len(self
.list) - 1
339 self
._set
_current
_index
(index
)
342 def get(self
, index
):
343 self
._set
_current
_index
(index
)
344 return self
.list[index
]
346 def delete(self
, index
):
347 if self
.current_index
> index
:
348 self
._set
_current
_index
(self
.current_index
- 1)
349 elif self
.current_index
== index
:
350 self
._set
_current
_index
(-1)
352 self
.list.remove(index
)
354 def _set_current_index(self
, index
):
355 self
.current_index
= index
357 stored_views
= bpy
.context
.scene
.stored_views
359 stored_views
.current_indices
[0] = index
361 stored_views
.current_indices
[1] = index
362 elif mode
== 'LAYERS':
363 stored_views
.current_indices
[2] = index
364 elif mode
== 'DISPLAY':
365 stored_views
.current_indices
[3] = index
367 def _generate_name(self
):
368 default_name
= str(self
.mode
)
372 if i_name
.startswith(default_name
):
377 post_fix
= l_name
.rpartition('.')[2]
378 if post_fix
.isnumeric():
379 post_fix
= str(int(post_fix
) + 1).zfill(3)
381 if post_fix
== default_name
:
383 return default_name
+ "." + post_fix
388 def sanitize_data(scene
):
390 def check_objects_references(mode
, list):
392 for i
, list_item
in enumerate(list.items()):
393 key
, item
= list_item
394 if mode
== 'POV' or mode
== 'VIEWS':
398 if item
.perspective
== "CAMERA":
400 camera
= bpy
.data
.objects
.get(item
.camera_name
)
402 try: # pick a default camera TODO: ask to pick?
403 camera
= bpy
.data
.cameras
[0]
404 item
.camera_name
= camera
.name
405 except: # couldn't find a camera in the scene
408 obj
= bpy
.data
.objects
.get(item
.lock_object_name
)
409 if obj
is None and camera
is None:
412 for i
in reversed(to_remove
):
415 modes
= ['POV', 'VIEW', 'DISPLAY', 'LAYERS']
417 data
= DataStore(scene
=scene
, mode
=mode
)
418 check_objects_references(mode
, data
.list)
421 def stored_view_factory(mode
, *args
, **kwargs
):
423 return POV(*args
, **kwargs
)
424 elif mode
== 'LAYERS':
425 return Layers(*args
, **kwargs
)
426 elif mode
== 'DISPLAY':
427 return Display(*args
, **kwargs
)
429 return View(*args
, **kwargs
)
432 If view name display is enabled,
433 it will check periodically if the view has been modified
435 get_preferences_timer() is the time in seconds between these checks.
436 It can be increased, if the view become sluggish
437 It is set in the add-on preferences
441 # Utility function get_preferences_timer for update of 3d view draw
442 def get_preferences_timer():
443 # replace the key if the add-on name changes
444 # TODO: expose refresh rate to ui???
445 addon
= bpy
.context
.preferences
.addons
[__package__
]
446 timer_update
= (addon
.preferences
.view_3d_update_rate
if addon
else False)
451 def init_draw(context
=None):
453 context
= bpy
.context
455 if "stored_views_osd" not in context
.window_manager
:
456 context
.window_manager
["stored_views_osd"] = False
458 if not context
.window_manager
["stored_views_osd"]:
459 context
.window_manager
["stored_views_osd"] = True
460 bpy
.ops
.stored_views
.draw()
463 def _draw_callback_px(self
, context
):
465 if area
and area
.type == 'VIEW_3D':
466 ui_scale
= context
.preferences
.system
.ui_scale
467 r_width
= text_location
= context
.region
.width
468 r_height
= context
.region
.height
469 font_id
= 0 # TODO: need to find out how best to get font_id
470 blf
.size(font_id
, 11 * ui_scale
)
471 text_size
= blf
.dimensions(0, self
.view_name
)
473 # compute the text location
475 overlap
= context
.preferences
.system
.use_region_overlap
477 for region
in area
.regions
:
478 if region
.type == "UI":
479 text_location
= r_width
- region
.width
481 text_x
= text_location
- text_size
[0] - 10
482 text_y
= r_height
- text_size
[1] - 8
483 blf
.position(font_id
, text_x
, text_y
, 0)
484 blf
.draw(font_id
, self
.view_name
)
487 class VIEW3D_OT_stored_views_draw(Operator
):
488 bl_idname
= "stored_views.draw"
489 bl_label
= "Show current"
490 bl_description
= "Toggle the display current view name in the view 3D"
496 def handle_add(self
, context
):
497 VIEW3D_OT_stored_views_draw
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(
498 _draw_callback_px
, (self
, context
), 'WINDOW', 'POST_PIXEL')
499 VIEW3D_OT_stored_views_draw
._timer
= \
500 context
.window_manager
.event_timer_add(get_preferences_timer(), context
.window
)
503 def handle_remove(context
):
504 if VIEW3D_OT_stored_views_draw
._handle
is not None:
505 bpy
.types
.SpaceView3D
.draw_handler_remove(VIEW3D_OT_stored_views_draw
._handle
, 'WINDOW')
506 if VIEW3D_OT_stored_views_draw
._timer
is not None:
507 context
.window_manager
.event_timer_remove(VIEW3D_OT_stored_views_draw
._timer
)
508 VIEW3D_OT_stored_views_draw
._handle
= None
509 VIEW3D_OT_stored_views_draw
._timer
= None
512 def poll(cls
, context
):
513 # return context.mode == 'OBJECT'
516 def modal(self
, context
, event
):
518 context
.area
.tag_redraw()
520 if not context
.area
or context
.area
.type != "VIEW_3D":
521 return {"PASS_THROUGH"}
524 stored_views
= context
.scene
.stored_views
526 if len(data
.list) > 0 and \
527 data
.current_index
>= 0 and \
528 not stored_views
.view_modified
:
530 if not stored_views
.view_modified
:
531 sv
= data
.list[data
.current_index
]
532 self
.view_name
= sv
.name
533 if event
.type == 'TIMER':
535 if data
.mode
== 'VIEW':
536 is_modified
= View
.is_modified(context
, sv
)
537 elif data
.mode
== 'POV':
538 is_modified
= POV
.is_modified(context
, sv
)
539 elif data
.mode
== 'LAYERS':
540 is_modified
= Layers
.is_modified(context
, sv
)
541 elif data
.mode
== 'DISPLAY':
542 is_modified
= Display
.is_modified(context
, sv
)
545 'view modified - index: %s name: %s' % (data
.current_index
, sv
.name
)
548 stored_views
.view_modified
= is_modified
550 return {"PASS_THROUGH"}
552 module_logger
.debug('exit')
553 context
.window_manager
["stored_views_osd"] = False
554 VIEW3D_OT_stored_views_draw
.handle_remove(context
)
558 def execute(self
, context
):
559 if context
.area
.type == "VIEW_3D":
561 VIEW3D_OT_stored_views_draw
.handle_add(self
, context
)
562 context
.window_manager
.modal_handler_add(self
)
564 return {"RUNNING_MODAL"}
566 self
.report({"WARNING"}, "View3D not found. Operation Cancelled")
570 class VIEW3D_OT_stored_views_initialize(Operator
):
571 bl_idname
= "view3d.stored_views_initialize"
572 bl_label
= "Initialize"
575 def poll(cls
, context
):
576 return not hasattr(bpy
.types
.Scene
, 'stored_views')
578 def execute(self
, context
):
579 bpy
.types
.Scene
.stored_views
: PointerProperty(
580 type=properties
.StoredViewsData
582 scenes
= bpy
.data
.scenes
585 DataStore
.sanitize_data(scene
)
589 from bpy
.types
import PropertyGroup
590 from bpy
.props
import (
604 class POVData(PropertyGroup
):
605 distance
: FloatProperty()
606 location
: FloatVectorProperty(
607 subtype
='TRANSLATION'
609 rotation
: FloatVectorProperty(
610 subtype
='QUATERNION',
613 name
: StringProperty()
614 perspective
: EnumProperty(
615 items
=[('PERSP', '', ''),
619 lens
: FloatProperty()
620 clip_start
: FloatProperty()
621 clip_end
: FloatProperty()
622 lock_cursor
: BoolProperty()
623 cursor_location
: FloatVectorProperty()
624 perspective_matrix_md5
: StringProperty()
625 camera_name
: StringProperty()
626 camera_type
: StringProperty()
627 lock_object_name
: StringProperty()
630 class LayersData(PropertyGroup
):
631 view_layers
: BoolVectorProperty(size
=20)
632 scene_layers
: BoolVectorProperty(size
=20)
633 lock_camera_and_layers
: BoolProperty()
634 name
: StringProperty()
637 class DisplayData(PropertyGroup
):
638 name
: StringProperty()
639 viewport_shade
: EnumProperty(
640 items
=[('BOUNDBOX', 'BOUNDBOX', 'BOUNDBOX'),
641 ('WIREFRAME', 'WIREFRAME', 'WIREFRAME'),
642 ('SOLID', 'SOLID', 'SOLID'),
643 ('TEXTURED', 'TEXTURED', 'TEXTURED'),
644 ('MATERIAL', 'MATERIAL', 'MATERIAL'),
645 ('RENDERED', 'RENDERED', 'RENDERED')]
647 show_only_render
: BoolProperty()
648 show_outline_selected
: BoolProperty()
649 show_all_objects_origin
: BoolProperty()
650 show_relationship_lines
: BoolProperty()
651 show_floor
: BoolProperty()
652 show_axis_x
: BoolProperty()
653 show_axis_y
: BoolProperty()
654 show_axis_z
: BoolProperty()
655 grid_lines
: IntProperty()
656 grid_scale
: FloatProperty()
657 grid_subdivisions
: IntProperty()
658 material_mode
: StringProperty()
659 show_textured_solid
: BoolProperty()
660 quad_view
: BoolProperty()
661 lock_rotation
: BoolProperty()
662 show_sync_view
: BoolProperty()
663 use_box_clip
: BoolProperty()
666 class ViewData(PropertyGroup
):
667 pov
: PointerProperty(
670 layers
: PointerProperty(
673 display
: PointerProperty(
676 name
: StringProperty()
679 class StoredViewsData(PropertyGroup
):
680 pov_list
: CollectionProperty(
683 layers_list
: CollectionProperty(
686 display_list
: CollectionProperty(
689 view_list
: CollectionProperty(
694 items
=[('VIEW', "View", "3D View settings"),
695 ('POV', "POV", "POV settings"),
696 ('LAYERS', "Layers", "Layers settings"),
697 ('DISPLAY', "Display", "Display settings")],
700 current_indices
: IntVectorProperty(
702 default
=[-1, -1, -1, -1]
704 view_modified
: BoolProperty(
708 class VIEW3D_OT_stored_views_save(Operator
):
709 bl_idname
= "stored_views.save"
710 bl_label
= "Save Current"
711 bl_description
= "Save the view 3d current state"
715 def execute(self
, context
):
716 mode
= context
.scene
.stored_views
.mode
717 sv
= stored_view_factory(mode
, self
.index
)
719 context
.scene
.stored_views
.view_modified
= False
725 class VIEW3D_OT_stored_views_set(Operator
):
726 bl_idname
= "stored_views.set"
728 bl_description
= "Update the view 3D according to this view"
732 def execute(self
, context
):
733 mode
= context
.scene
.stored_views
.mode
734 sv
= stored_view_factory(mode
, self
.index
)
736 context
.scene
.stored_views
.view_modified
= False
742 class VIEW3D_OT_stored_views_delete(Operator
):
743 bl_idname
= "stored_views.delete"
745 bl_description
= "Delete this view"
749 def execute(self
, context
):
751 data
.delete(self
.index
)
756 class VIEW3D_OT_New_Camera_to_View(Operator
):
757 bl_idname
= "stored_views.newcamera"
758 bl_label
= "New Camera To View"
759 bl_description
= "Add a new Active Camera and align it to this view"
762 def poll(cls
, context
):
764 context
.space_data
is not None and
765 context
.space_data
.type == 'VIEW_3D' and
766 context
.space_data
.region_3d
.view_perspective
!= 'CAMERA'
769 def execute(self
, context
):
771 if bpy
.ops
.object.mode_set
.poll():
772 bpy
.ops
.object.mode_set(mode
='OBJECT')
774 bpy
.ops
.object.camera_add()
775 cam
= context
.active_object
776 cam
.name
= "View_Camera"
777 # make active camera by hand
778 context
.scene
.camera
= cam
780 bpy
.ops
.view3d
.camera_to_view()
784 # Camera marker & switcher by Fsiddi
785 class VIEW3D_OT_SetSceneCamera(Operator
):
786 bl_idname
= "cameraselector.set_scene_camera"
787 bl_label
= "Set Scene Camera"
788 bl_description
= "Set chosen camera as the scene's active camera"
792 def execute(self
, context
):
793 chosen_camera
= context
.active_object
794 scene
= context
.scene
797 for c
in [o
for o
in scene
.objects
if o
.type == 'CAMERA']:
798 c
.hide
= (c
!= chosen_camera
)
799 scene
.camera
= chosen_camera
800 bpy
.ops
.object.select_all(action
='DESELECT')
801 chosen_camera
.select_set(True)
804 def invoke(self
, context
, event
):
806 self
.hide_others
= True
808 return self
.execute(context
)
811 class VIEW3D_OT_PreviewSceneCamera(Operator
):
812 bl_idname
= "cameraselector.preview_scene_camera"
813 bl_label
= "Preview Camera"
814 bl_description
= "Preview chosen camera and make scene's active camera"
816 def execute(self
, context
):
817 chosen_camera
= context
.active_object
818 bpy
.ops
.view3d
.object_as_camera()
819 bpy
.ops
.object.select_all(action
="DESELECT")
820 chosen_camera
.select_set(True)
824 class VIEW3D_OT_AddCameraMarker(Operator
):
825 bl_idname
= "cameraselector.add_camera_marker"
826 bl_label
= "Add Camera Marker"
827 bl_description
= "Add a timeline marker bound to chosen camera"
829 def execute(self
, context
):
830 chosen_camera
= context
.active_object
831 scene
= context
.scene
833 current_frame
= scene
.frame_current
835 for m
in reversed(sorted(filter(lambda m
: m
.frame
<= current_frame
,
836 scene
.timeline_markers
),
837 key
=lambda m
: m
.frame
)):
840 if marker
and (marker
.camera
== chosen_camera
):
841 # Cancel if the last marker at or immediately before
842 # current frame is already bound to the camera.
845 marker_name
= "F_%02d_%s" % (current_frame
, chosen_camera
.name
)
846 if marker
and (marker
.frame
== current_frame
):
847 # Reuse existing marker at current frame to avoid
848 # overlapping bound markers.
849 marker
.name
= marker_name
851 marker
= scene
.timeline_markers
.new(marker_name
)
852 marker
.frame
= scene
.frame_current
853 marker
.camera
= chosen_camera
856 for other_marker
in [m
for m
in scene
.timeline_markers
if m
!= marker
]:
857 other_marker
.select
= False
861 # gpl authors: nfloyd, Francesco Siddi
867 # TODO: reinstate filters?
871 def get_preset_path():
872 # locate stored_views preset folder
873 paths
= bpy
.utils
.preset_paths("stored_views")
875 # stored_views preset folder doesn't exist, so create it
876 paths
= [os
.path
.join(bpy
.utils
.user_resource('SCRIPTS'), "presets",
878 if not os
.path
.exists(paths
[0]):
879 os
.makedirs(paths
[0])
884 def stored_views_apply_from_scene(scene_name
, replace
=True):
885 scene
= bpy
.context
.scene
886 scene_exists
= True if scene_name
in bpy
.data
.scenes
.keys() else False
889 sv
= bpy
.context
.scene
.stored_views
890 # io_filters = sv.settings.io_filters
892 structs
= [sv
.view_list
, sv
.pov_list
, sv
.layers_list
, sv
.display_list
]
894 for st
in structs
: # clear swap and list
898 f_sv
= bpy
.data
.scenes
[scene_name
].stored_views
899 # f_sv = bpy.data.scenes[scene_name].stored_views
900 f_structs
= [f_sv
.view_list
, f_sv
.pov_list
, f_sv
.layers_list
, f_sv
.display_list
]
902 is_filtered = [io_filters.views, io_filters.point_of_views,
903 io_filters.layers, io_filters.displays]
905 for i
in range(len(f_structs
)):
907 if is_filtered[i] is False:
910 for j
in f_structs
[i
]:
911 item
= structs
[i
].add()
912 # stored_views_copy_item(j, item)
913 for k
, v
in j
.items():
915 DataStore
.sanitize_data(scene
)
921 def stored_views_export_to_blsv(filepath
, name
='Custom Preset'):
922 # create dictionary with all information
923 dump
= {"info": {}, "data": {}}
924 dump
["info"]["script"] = bl_info
['name']
925 dump
["info"]["script_version"] = bl_info
['version']
926 dump
["info"]["version"] = bpy
.app
.version
927 dump
["info"]["preset_name"] = name
929 # get current stored views settings
930 scene
= bpy
.context
.scene
931 sv
= scene
.stored_views
933 def dump_view_list(dict, list):
934 if str(type(list)) == "<class 'bpy_prop_collection_idprop'>":
935 for i
, struct_dict
in enumerate(list):
936 dict[i
] = {"name": str,
940 dict[i
]["name"] = struct_dict
.name
941 dump_item(dict[i
]["pov"], struct_dict
.pov
)
942 dump_item(dict[i
]["layers"], struct_dict
.layers
)
943 dump_item(dict[i
]["display"], struct_dict
.display
)
945 def dump_list(dict, list):
946 if str(type(list)) == "<class 'bpy_prop_collection_idprop'>":
947 for i
, struct
in enumerate(list):
949 dump_item(dict[i
], struct
)
951 def dump_item(dict, struct
):
952 for prop
in struct
.bl_rna
.properties
:
953 if prop
.identifier
== "rna_type":
954 # not a setting, so skip
957 val
= getattr(struct
, prop
.identifier
)
958 if str(type(val
)) in ["<class 'bpy_prop_array'>"]:
960 dict[prop
.identifier
] = [v
for v
in val
]
961 # address the pickle limitations of dealing with the Vector class
962 elif str(type(val
)) in ["<class 'Vector'>",
963 "<class 'Quaternion'>"]:
964 dict[prop
.identifier
] = [v
for v
in val
]
967 dict[prop
.identifier
] = val
969 # io_filters = sv.settings.io_filters
970 dump
["data"] = {"point_of_views": {},
975 others_data
= [(dump
["data"]["point_of_views"], sv
.pov_list
), # , io_filters.point_of_views),
976 (dump
["data"]["layers"], sv
.layers_list
), # , io_filters.layers),
977 (dump
["data"]["displays"], sv
.display_list
)] # , io_filters.displays)]
978 for list_data
in others_data
:
979 # if list_data[2] is True:
980 dump_list(list_data
[0], list_data
[1])
982 views_data
= (dump
["data"]["views"], sv
.view_list
)
983 # if io_filters.views is True:
984 dump_view_list(views_data
[0], views_data
[1])
988 filepath
= bpy
.path
.ensure_ext(filepath
, '.blsv')
989 file = gzip
.open(filepath
, mode
='wb')
990 pickle
.dump(dump
, file, protocol
=pickle
.HIGHEST_PROTOCOL
)
994 def stored_views_apply_preset(filepath
, replace
=True):
998 file = gzip
.open(filepath
, mode
='rb')
999 dump
= pickle
.load(file)
1002 scene
= bpy
.context
.scene
1003 sv
= getattr(scene
, "stored_views", None)
1008 # io_filters = sv.settings.io_filters
1010 "point_of_views": sv
.pov_list
,
1011 "views": sv
.view_list
,
1012 "layers": sv
.layers_list
,
1013 "displays": sv
.display_list
1015 for sv_struct
, props
in dump
["data"].items():
1017 is_filtered = getattr(io_filters, sv_struct)
1018 if is_filtered is False:
1021 sv_list
= sv_data
[sv_struct
] # .list
1022 if replace
is True: # clear swap and list
1023 while len(sv_list
) > 0:
1025 for key
, prop_struct
in props
.items():
1026 sv_item
= sv_list
.add()
1028 for subprop
, subval
in prop_struct
.items():
1029 if isinstance(subval
, dict): # views : pov, layers, displays
1030 v_subprop
= getattr(sv_item
, subprop
)
1031 for v_subkey
, v_subval
in subval
.items():
1032 if isinstance(v_subval
, list): # array like of pov,...
1033 v_array_like
= getattr(v_subprop
, v_subkey
)
1034 for i
in range(len(v_array_like
)):
1035 v_array_like
[i
] = v_subval
[i
]
1037 setattr(v_subprop
, v_subkey
, v_subval
) # others
1038 elif isinstance(subval
, list):
1039 array_like
= getattr(sv_item
, subprop
)
1040 for i
in range(len(array_like
)):
1041 array_like
[i
] = subval
[i
]
1043 setattr(sv_item
, subprop
, subval
)
1045 DataStore
.sanitize_data(scene
)
1050 class VIEW3D_OT_stored_views_import(Operator
, ImportHelper
):
1051 bl_idname
= "stored_views.import_blsv"
1052 bl_label
= "Import Stored Views preset"
1053 bl_description
= "Import a .blsv preset file to the current Stored Views"
1055 filename_ext
= ".blsv"
1056 filter_glob
: StringProperty(
1060 replace
: BoolProperty(
1063 description
="Replace current stored views, otherwise append"
1067 def poll(cls
, context
):
1068 return get_preferences()
1070 def execute(self
, context
):
1071 # the usual way is to not select the file in the file browser
1072 exists
= os
.path
.isfile(self
.filepath
) if self
.filepath
else False
1074 self
.report({'WARNING'},
1075 "No filepath specified or file could not be found. Operation Cancelled")
1076 return {'CANCELLED'}
1078 # apply chosen preset
1079 apply_preset
= IO_Utils
.stored_views_apply_preset(
1080 filepath
=self
.filepath
, replace
=self
.replace
1082 if not apply_preset
:
1083 self
.report({'WARNING'},
1084 "Please Initialize Stored Views first (in the 3D View Properties Area)")
1085 return {'CANCELLED'}
1087 # copy preset to presets folder
1088 filename
= os
.path
.basename(self
.filepath
)
1090 shutil
.copyfile(self
.filepath
,
1091 os
.path
.join(IO_Utils
.get_preset_path()[0], filename
))
1093 self
.report({'WARNING'},
1094 "Stored Views: preset applied, but installing failed (preset already exists?)")
1100 class VIEW3D_OT_stored_views_import_from_scene(Operator
):
1101 bl_idname
= "stored_views.import_from_scene"
1102 bl_label
= "Import stored views from scene"
1103 bl_description
= "Import currently stored views from an another scene"
1105 scene_name
: StringProperty(
1107 description
="A current blend scene",
1110 replace
: BoolProperty(
1113 description
="Replace current stored views, otherwise append"
1117 def poll(cls
, context
):
1118 return get_preferences()
1120 def draw(self
, context
):
1121 layout
= self
.layout
1123 layout
.prop_search(self
, "scene_name", bpy
.data
, "scenes")
1124 layout
.prop(self
, "replace")
1126 def invoke(self
, context
, event
):
1127 return context
.window_manager
.invoke_props_dialog(self
)
1129 def execute(self
, context
):
1130 # filepath should always be given
1131 if not self
.scene_name
:
1132 self
.report({"WARNING"},
1133 "No scene name was given. Operation Cancelled")
1136 is_finished
= IO_Utils
.stored_views_apply_from_scene(
1137 self
.scene_name
, replace
=self
.replace
1140 self
.report({"WARNING"},
1141 "Could not find the specified scene. Operation Cancelled")
1142 return {"CANCELLED"}
1147 class VIEW3D_OT_stored_views_export(Operator
, ExportHelper
):
1148 bl_idname
= "stored_views.export_blsv"
1149 bl_label
= "Export Stored Views preset"
1150 bl_description
= "Export the current Stored Views to a .blsv preset file"
1152 filename_ext
= ".blsv"
1153 filepath
: StringProperty(
1154 default
=os
.path
.join(IO_Utils
.get_preset_path()[0], "untitled")
1156 filter_glob
: StringProperty(
1160 preset_name
: StringProperty(
1163 description
="Name of the stored views preset"
1167 def poll(cls
, context
):
1168 return get_preferences()
1170 def execute(self
, context
):
1171 IO_Utils
.stored_views_export_to_blsv(self
.filepath
, self
.preset_name
)
1176 class VIEW3D_PT_properties_stored_views(Panel
):
1177 bl_label
= "Stored Views"
1178 bl_space_type
= "VIEW_3D"
1179 bl_region_type
= "UI"
1180 bl_category
= "View"
1182 def draw(self
, context
):
1183 self
.logger
= logging
.getLogger('%s Properties panel' % __name__
)
1184 layout
= self
.layout
1186 if bpy
.ops
.view3d
.stored_views_initialize
.poll():
1187 layout
.operator("view3d.stored_views_initialize")
1190 stored_views
= context
.scene
.stored_views
1193 col
= layout
.column(align
=True)
1194 col
.prop_enum(stored_views
, "mode", 'VIEW')
1195 row
= layout
.row(align
=True)
1196 row
.operator("view3d.camera_to_view", text
="Camera To view")
1197 row
.operator("stored_views.newcamera")
1199 row
= col
.row(align
=True)
1200 row
.prop_enum(stored_views
, "mode", 'POV')
1201 row
.prop_enum(stored_views
, "mode", 'LAYERS')
1202 row
.prop_enum(stored_views
, "mode", 'DISPLAY')
1206 row
.operator("stored_views.save").index
= -1
1209 if core
.get_preferences():
1210 row
= layout
.row(align
=True)
1211 row
.operator("stored_views.import_from_scene", text
="Import from Scene")
1212 row
.operator("stored_views.import_blsv", text
="", icon
="IMPORT")
1213 row
.operator("stored_views.export_blsv", text
="", icon
="EXPORT")
1215 data_store
= DataStore()
1216 list = data_store
.list
1222 mode
= stored_views
.mode
1223 for i
in range(len(list)):
1225 icon_string
= "MESH_CUBE" # default icon
1226 # TODO: icons for view
1228 persp
= list[i
].perspective
1229 if persp
== 'PERSP':
1230 icon_string
= "MESH_CUBE"
1231 elif persp
== 'ORTHO':
1232 icon_string
= "MESH_PLANE"
1233 elif persp
== 'CAMERA':
1234 if list[i
].camera_type
!= 'CAMERA':
1235 icon_string
= 'OBJECT_DATAMODE'
1237 icon_string
= "OUTLINER_DATA_CAMERA"
1238 if mode
== 'LAYERS':
1239 if list[i
].lock_camera_and_layers
is True:
1240 icon_string
= 'SCENE_DATA'
1242 icon_string
= 'RENDERLAYERS'
1243 if mode
== 'DISPLAY':
1244 shade
= list[i
].viewport_shade
1245 if shade
== 'TEXTURED':
1246 icon_string
= 'TEXTURE_SHADED'
1247 if shade
== 'MATERIAL':
1248 icon_string
= 'MATERIAL_DATA'
1249 elif shade
== 'SOLID':
1250 icon_string
= 'SOLID'
1251 elif shade
== 'WIREFRAME':
1252 icon_string
= "WIRE"
1253 elif shade
== 'BOUNDBOX':
1254 icon_string
= 'BBOX'
1255 elif shade
== 'RENDERED':
1256 icon_string
= 'MATERIAL'
1258 subrow
= box
.row(align
=True)
1259 # current view indicator
1260 if data_store
.current_index
== i
and context
.scene
.stored_views
.view_modified
is False:
1261 subrow
.label(text
="", icon
='CHECKMARK')
1262 subrow
.operator("stored_views.set",
1263 text
="", icon
=icon_string
).index
= i
1264 subrow
.prop(list[i
], "name", text
="")
1265 subrow
.operator("stored_views.save",
1266 text
="", icon
="REC").index
= i
1267 subrow
.operator("stored_views.delete",
1268 text
="", icon
="PANEL_CLOSE").index
= i
1270 layout
= self
.layout
1271 scene
= context
.scene
1272 layout
.label(text
="Camera Selector")
1273 cameras
= sorted([o
for o
in scene
.objects
if o
.type == 'CAMERA'],
1274 key
=lambda o
: o
.name
)
1276 if len(cameras
) > 0:
1277 for camera
in cameras
:
1278 row
= layout
.row(align
=True)
1279 row
.context_pointer_set("active_object", camera
)
1280 row
.operator("cameraselector.set_scene_camera",
1281 text
=camera
.name
, icon
='OUTLINER_DATA_CAMERA')
1282 row
.operator("cameraselector.preview_scene_camera",
1283 text
='', icon
='RESTRICT_VIEW_OFF')
1284 row
.operator("cameraselector.add_camera_marker",
1285 text
='', icon
='MARKER')
1287 layout
.label(text
="No cameras in this scene")
1290 class VIEW3D_OT_stored_views_preferences(AddonPreferences
):
1291 bl_idname
= __name__
1293 show_exporters
: BoolProperty(
1294 name
="Enable I/O Operators",
1296 description
="Enable Import/Export Operations in the UI:\n"
1297 "Import Stored Views preset,\n"
1298 "Export Stored Views preset and \n"
1299 "Import stored views from scene",
1301 view_3d_update_rate
: IntProperty(
1302 name
="3D view update",
1303 description
="Update rate of the 3D view redraw\n"
1304 "Increase the value if the UI feels sluggish",
1309 def draw(self
, context
):
1310 layout
= self
.layout
1312 row
= layout
.row(align
=True)
1313 row
.prop(self
, "view_3d_update_rate", toggle
=True)
1314 row
.prop(self
, "show_exporters", toggle
=True)
1319 VIEW3D_OT_stored_views_initialize
,
1320 VIEW3D_OT_stored_views_preferences
,
1321 VIEW3D_PT_properties_stored_views
,
1327 VIEW3D_OT_stored_views_draw
,
1328 VIEW3D_OT_stored_views_save
,
1329 VIEW3D_OT_stored_views_set
,
1330 VIEW3D_OT_stored_views_delete
,
1331 VIEW3D_OT_New_Camera_to_View
,
1332 VIEW3D_OT_SetSceneCamera
,
1333 VIEW3D_OT_PreviewSceneCamera
,
1334 VIEW3D_OT_AddCameraMarker
,
1336 VIEW3D_OT_stored_views_import
,
1337 VIEW3D_OT_stored_views_import_from_scene
,
1338 VIEW3D_OT_stored_views_export
1343 from bpy
.utils
import register_class
1349 ui
.VIEW3D_OT_stored_views_draw
.handle_remove(bpy
.context
)
1350 from bpy
.utils
import unregister_class
1351 for cls
in reversed(classes
):
1352 unregister_class(cls
)
1353 if hasattr(bpy
.types
.Scene
, "stored_views"):
1354 del bpy
.types
.Scene
.stored_views
1357 if __name__
== "__main__":