Fix T83275: Crash with scene statics and empty scene
[blender.git] / release / scripts / startup / bl_ui / properties_data_camera.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20 import bpy
21 from bpy.types import Panel
22 from rna_prop_ui import PropertyPanel
23 from bl_ui.utils import PresetPanel
24
25
26 class CameraButtonsPanel:
27     bl_space_type = 'PROPERTIES'
28     bl_region_type = 'WINDOW'
29     bl_context = "data"
30
31     @classmethod
32     def poll(cls, context):
33         engine = context.engine
34         return context.camera and (engine in cls.COMPAT_ENGINES)
35
36
37 class CAMERA_PT_presets(PresetPanel, Panel):
38     bl_label = "Camera Presets"
39     preset_subdir = "camera"
40     preset_operator = "script.execute_preset"
41     preset_add_operator = "camera.preset_add"
42     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
43
44
45 class SAFE_AREAS_PT_presets(PresetPanel, Panel):
46     bl_label = "Camera Presets"
47     preset_subdir = "safe_areas"
48     preset_operator = "script.execute_preset"
49     preset_add_operator = "safe_areas.preset_add"
50     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
51
52
53 class DATA_PT_context_camera(CameraButtonsPanel, Panel):
54     bl_label = ""
55     bl_options = {'HIDE_HEADER'}
56     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
57
58     def draw(self, context):
59         layout = self.layout
60
61         ob = context.object
62         cam = context.camera
63         space = context.space_data
64
65         if ob:
66             layout.template_ID(ob, "data")
67         elif cam:
68             layout.template_ID(space, "pin_id")
69
70
71 class DATA_PT_lens(CameraButtonsPanel, Panel):
72     bl_label = "Lens"
73     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
74
75     def draw(self, context):
76         layout = self.layout
77         layout.use_property_split = True
78
79         cam = context.camera
80
81         layout.prop(cam, "type")
82
83         col = layout.column()
84         col.separator()
85
86         if cam.type == 'PERSP':
87             if cam.lens_unit == 'MILLIMETERS':
88                 col.prop(cam, "lens")
89             elif cam.lens_unit == 'FOV':
90                 col.prop(cam, "angle")
91             col.prop(cam, "lens_unit")
92
93         elif cam.type == 'ORTHO':
94             col.prop(cam, "ortho_scale")
95
96         elif cam.type == 'PANO':
97             engine = context.engine
98             if engine == 'CYCLES':
99                 ccam = cam.cycles
100                 col.prop(ccam, "panorama_type")
101                 if ccam.panorama_type == 'FISHEYE_EQUIDISTANT':
102                     col.prop(ccam, "fisheye_fov")
103                 elif ccam.panorama_type == 'FISHEYE_EQUISOLID':
104                     col.prop(ccam, "fisheye_lens", text="Lens")
105                     col.prop(ccam, "fisheye_fov")
106                 elif ccam.panorama_type == 'EQUIRECTANGULAR':
107                     sub = col.column(align=True)
108                     sub.prop(ccam, "latitude_min", text="Latitude Min")
109                     sub.prop(ccam, "latitude_max", text="Max")
110                     sub = col.column(align=True)
111                     sub.prop(ccam, "longitude_min", text="Longitude Min")
112                     sub.prop(ccam, "longitude_max", text="Max")
113             elif engine in {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}:
114                 if cam.lens_unit == 'MILLIMETERS':
115                     col.prop(cam, "lens")
116                 elif cam.lens_unit == 'FOV':
117                     col.prop(cam, "angle")
118                 col.prop(cam, "lens_unit")
119
120         col = layout.column()
121         col.separator()
122
123         sub = col.column(align=True)
124         sub.prop(cam, "shift_x", text="Shift X")
125         sub.prop(cam, "shift_y", text="Y")
126
127         col.separator()
128         sub = col.column(align=True)
129         sub.prop(cam, "clip_start", text="Clip Start")
130         sub.prop(cam, "clip_end", text="End")
131
132
133 class DATA_PT_camera_stereoscopy(CameraButtonsPanel, Panel):
134     bl_label = "Stereoscopy"
135     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
136
137     @classmethod
138     def poll(cls, context):
139         render = context.scene.render
140         return (super().poll(context) and render.use_multiview and
141                 render.views_format == 'STEREO_3D')
142
143     def draw(self, context):
144         layout = self.layout
145         layout.use_property_split = True
146
147         render = context.scene.render
148         st = context.camera.stereo
149         cam = context.camera
150
151         is_spherical_stereo = cam.type != 'ORTHO' and render.use_spherical_stereo
152         use_spherical_stereo = is_spherical_stereo and st.use_spherical_stereo
153
154         layout.prop(st, "convergence_mode")
155
156         col = layout.column()
157         sub = col.column()
158         sub.active = st.convergence_mode != 'PARALLEL'
159         sub.prop(st, "convergence_distance")
160
161         col.prop(st, "interocular_distance")
162
163         if is_spherical_stereo:
164             col.separator()
165             col.prop(st, "use_spherical_stereo")
166             sub = col.column()
167             sub.active = st.use_spherical_stereo
168             sub.prop(st, "use_pole_merge")
169
170             sub = col.column(align=True)
171             sub.active = st.use_pole_merge
172             sub.prop(st, "pole_merge_angle_from", text="Pole Merge Angle Start")
173             sub.prop(st, "pole_merge_angle_to", text="End")
174
175         col = layout.column()
176         col.active = not use_spherical_stereo
177         col.separator()
178         col.prop(st, "pivot")
179
180
181 class DATA_PT_camera(CameraButtonsPanel, Panel):
182     bl_label = "Camera"
183     bl_options = {'DEFAULT_CLOSED'}
184     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
185
186     def draw_header_preset(self, _context):
187         CAMERA_PT_presets.draw_panel_header(self.layout)
188
189     def draw(self, context):
190         layout = self.layout
191
192         cam = context.camera
193
194         layout.use_property_split = True
195
196         col = layout.column()
197         col.prop(cam, "sensor_fit")
198
199         if cam.sensor_fit == 'AUTO':
200             col.prop(cam, "sensor_width", text="Size")
201         else:
202             sub = col.column(align=True)
203             sub.active = cam.sensor_fit == 'HORIZONTAL'
204             sub.prop(cam, "sensor_width", text="Width")
205
206             sub = col.column(align=True)
207             sub.active = cam.sensor_fit == 'VERTICAL'
208             sub.prop(cam, "sensor_height", text="Height")
209
210
211 class DATA_PT_camera_dof(CameraButtonsPanel, Panel):
212     bl_label = "Depth of Field"
213     bl_options = {'DEFAULT_CLOSED'}
214     COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
215
216     def draw_header(self, context):
217         cam = context.camera
218         dof = cam.dof
219         self.layout.prop(dof, "use_dof", text="")
220
221     def draw(self, context):
222         layout = self.layout
223         layout.use_property_split = True
224
225         cam = context.camera
226         dof = cam.dof
227         layout.active = dof.use_dof
228
229         col = layout.column()
230         col.prop(dof, "focus_object", text="Focus on Object")
231         sub = col.column()
232         sub.active = (dof.focus_object is None)
233         sub.prop(dof, "focus_distance", text="Focus Distance")
234
235
236 class DATA_PT_camera_dof_aperture(CameraButtonsPanel, Panel):
237     bl_label = "Aperture"
238     bl_parent_id = "DATA_PT_camera_dof"
239     COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
240
241     def draw(self, context):
242         layout = self.layout
243         layout.use_property_split = True
244
245         cam = context.camera
246         dof = cam.dof
247         layout.active = dof.use_dof
248
249         flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
250
251         col = flow.column()
252         col.prop(dof, "aperture_fstop")
253
254         col = flow.column()
255         col.prop(dof, "aperture_blades")
256         col.prop(dof, "aperture_rotation")
257         col.prop(dof, "aperture_ratio")
258
259
260 class DATA_PT_camera_background_image(CameraButtonsPanel, Panel):
261     bl_label = "Background Images"
262     bl_options = {'DEFAULT_CLOSED'}
263     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
264
265     def draw_header(self, context):
266         cam = context.camera
267
268         self.layout.prop(cam, "show_background_images", text="")
269
270     def draw(self, context):
271         layout = self.layout
272         layout.use_property_split = True
273         layout.use_property_decorate = False
274
275         cam = context.camera
276         use_multiview = context.scene.render.use_multiview
277
278         col = layout.column()
279         col.operator("view3d.background_image_add", text="Add Image")
280
281         for i, bg in enumerate(cam.background_images):
282             layout.active = cam.show_background_images
283             box = layout.box()
284             row = box.row(align=True)
285             row.prop(bg, "show_expanded", text="", emboss=False)
286             if bg.source == 'IMAGE' and bg.image:
287                 row.prop(bg.image, "name", text="", emboss=False)
288             elif bg.source == 'MOVIE_CLIP' and bg.clip:
289                 row.prop(bg.clip, "name", text="", emboss=False)
290             elif bg.source and bg.use_camera_clip:
291                 row.label(text="Active Clip")
292             else:
293                 row.label(text="Not Set")
294
295             row.prop(
296                 bg,
297                 "show_background_image",
298                 text="",
299                 emboss=False,
300                 icon='RESTRICT_VIEW_OFF' if bg.show_background_image else 'RESTRICT_VIEW_ON',
301             )
302
303             row.operator("view3d.background_image_remove", text="", emboss=False, icon='X').index = i
304
305             if bg.show_expanded:
306                 row = box.row()
307                 row.prop(bg, "source", expand=True)
308
309                 has_bg = False
310                 if bg.source == 'IMAGE':
311                     row = box.row()
312                     row.template_ID(bg, "image", open="image.open")
313                     if bg.image is not None:
314                         box.template_image(bg, "image", bg.image_user, compact=True)
315                         has_bg = True
316
317                         if use_multiview:
318                             box.prop(bg.image, "use_multiview")
319
320                             column = box.column()
321                             column.active = bg.image.use_multiview
322
323                             column.label(text="Views Format:")
324                             column.row().prop(bg.image, "views_format", expand=True)
325
326                             sub = column.box()
327                             sub.active = bg.image.views_format == 'STEREO_3D'
328                             sub.template_image_stereo_3d(bg.image.stereo_3d_format)
329
330                 elif bg.source == 'MOVIE_CLIP':
331                     box.prop(bg, "use_camera_clip", text="Active Clip")
332
333                     column = box.column()
334                     column.active = not bg.use_camera_clip
335                     column.template_ID(bg, "clip", open="clip.open")
336
337                     if bg.clip:
338                         column.template_movieclip(bg, "clip", compact=True)
339
340                     if bg.use_camera_clip or bg.clip:
341                         has_bg = True
342
343                     column = box.column()
344                     column.active = has_bg
345                     column.prop(bg.clip_user, "use_render_undistorted")
346                     column.prop(bg.clip_user, "proxy_render_size")
347
348                 if has_bg:
349                     col = box.column()
350                     col.prop(bg, "alpha", slider=True)
351                     col.row().prop(bg, "display_depth", expand=True)
352
353                     col.row().prop(bg, "frame_method", expand=True)
354
355                     row = box.row()
356                     row.prop(bg, "offset")
357
358                     col = box.column()
359                     col.prop(bg, "rotation")
360                     col.prop(bg, "scale")
361
362                     col = box.column(heading="Flip")
363                     col.prop(bg, "use_flip_x", text="X")
364                     col.prop(bg, "use_flip_y", text="Y")
365
366
367 class DATA_PT_camera_display(CameraButtonsPanel, Panel):
368     bl_label = "Viewport Display"
369     bl_options = {'DEFAULT_CLOSED'}
370     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
371
372     def draw(self, context):
373         layout = self.layout
374         layout.use_property_split = True
375
376         cam = context.camera
377
378         col = layout.column(align=True)
379
380         col.prop(cam, "display_size", text="Size")
381
382         col = layout.column(heading="Show")
383         col.prop(cam, "show_limits", text="Limits")
384         col.prop(cam, "show_mist", text="Mist")
385         col.prop(cam, "show_sensor", text="Sensor")
386         col.prop(cam, "show_name", text="Name")
387
388         col = layout.column(align=False, heading="Passepartout")
389         col.use_property_decorate = False
390         row = col.row(align=True)
391         sub = row.row(align=True)
392         sub.prop(cam, "show_passepartout", text="")
393         sub = sub.row(align=True)
394         sub.active = cam.show_passepartout
395         sub.prop(cam, "passepartout_alpha", text="")
396         row.prop_decorator(cam, "passepartout_alpha")
397
398
399 class DATA_PT_camera_display_composition_guides(CameraButtonsPanel, Panel):
400     bl_label = "Composition Guides"
401     bl_parent_id = "DATA_PT_camera_display"
402     bl_options = {'DEFAULT_CLOSED'}
403     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
404
405     def draw(self, context):
406         layout = self.layout
407         layout.use_property_split = True
408
409         cam = context.camera
410
411         layout.prop(cam, "show_composition_thirds")
412
413         col = layout.column(heading="Center", align=True)
414         col.prop(cam, "show_composition_center")
415         col.prop(cam, "show_composition_center_diagonal", text="Diagonal")
416
417         col = layout.column(heading="Golden", align=True)
418         col.prop(cam, "show_composition_golden", text="Ratio")
419         col.prop(cam, "show_composition_golden_tria_a", text="Triangle A")
420         col.prop(cam, "show_composition_golden_tria_b", text="Triangle B")
421
422         col = layout.column(heading="Harmony", align=True)
423         col.prop(cam, "show_composition_harmony_tri_a", text="Triangle A")
424         col.prop(cam, "show_composition_harmony_tri_b", text="Triangle B")
425
426
427 class DATA_PT_camera_safe_areas(CameraButtonsPanel, Panel):
428     bl_label = "Safe Areas"
429     bl_options = {'DEFAULT_CLOSED'}
430     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
431
432     def draw_header(self, context):
433         cam = context.camera
434
435         self.layout.prop(cam, "show_safe_areas", text="")
436
437     def draw_header_preset(self, _context):
438         SAFE_AREAS_PT_presets.draw_panel_header(self.layout)
439
440     def draw(self, context):
441         layout = self.layout
442         safe_data = context.scene.safe_areas
443         camera = context.camera
444
445         layout.use_property_split = True
446
447         layout.active = camera.show_safe_areas
448
449         col = layout.column()
450
451         sub = col.column()
452         sub.prop(safe_data, "title", slider=True)
453         sub.prop(safe_data, "action", slider=True)
454
455
456 class DATA_PT_camera_safe_areas_center_cut(CameraButtonsPanel, Panel):
457     bl_label = "Center-Cut Safe Areas"
458     bl_parent_id = "DATA_PT_camera_safe_areas"
459     bl_options = {'DEFAULT_CLOSED'}
460     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
461
462     def draw_header(self, context):
463         cam = context.camera
464
465         layout = self.layout
466         layout.active = cam.show_safe_areas
467         layout.prop(cam, "show_safe_center", text="")
468
469     def draw(self, context):
470         layout = self.layout
471         safe_data = context.scene.safe_areas
472         camera = context.camera
473
474         layout.use_property_split = True
475
476         layout.active = camera.show_safe_areas and camera.show_safe_center
477
478         col = layout.column()
479         col.prop(safe_data, "title_center", slider=True)
480         col.prop(safe_data, "action_center", slider=True)
481
482
483 class DATA_PT_custom_props_camera(CameraButtonsPanel, PropertyPanel, Panel):
484     COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
485     _context_path = "object.data"
486     _property_type = bpy.types.Camera
487
488
489 def draw_display_safe_settings(layout, safe_data, settings):
490     show_safe_areas = settings.show_safe_areas
491     show_safe_center = settings.show_safe_center
492
493     layout.use_property_split = True
494
495     col = layout.column()
496     col.active = show_safe_areas
497
498     sub = col.column()
499     sub.prop(safe_data, "title", slider=True)
500     sub.prop(safe_data, "action", slider=True)
501
502     col.separator()
503
504     col.prop(settings, "show_safe_center", text="Center-Cut Safe Areas")
505
506     sub = col.column()
507     sub.active = show_safe_areas and show_safe_center
508     sub.prop(safe_data, "title_center", slider=True)
509     sub.prop(safe_data, "action_center", slider=True)
510
511
512 classes = (
513     CAMERA_PT_presets,
514     SAFE_AREAS_PT_presets,
515     DATA_PT_context_camera,
516     DATA_PT_lens,
517     DATA_PT_camera_dof,
518     DATA_PT_camera_dof_aperture,
519     DATA_PT_camera,
520     DATA_PT_camera_stereoscopy,
521     DATA_PT_camera_safe_areas,
522     DATA_PT_camera_safe_areas_center_cut,
523     DATA_PT_camera_background_image,
524     DATA_PT_camera_display,
525     DATA_PT_camera_display_composition_guides,
526     DATA_PT_custom_props_camera,
527 )
528
529 if __name__ == "__main__":  # only for live edit.
530     from bpy.utils import register_class
531     for cls in classes:
532         register_class(cls)