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>
20import bpy
21from bpy.types import Panel
22from rna_prop_ui import PropertyPanel
23from bl_ui.utils import PresetPanel
24
25
26class 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
37class 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
45class 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
53class 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
71class 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
133class 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
181class 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
211class 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
236class 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
260class 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
367class 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
399class 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
427class 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
456class 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
483class 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
489def 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
512classes = (
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
529if __name__ == "__main__":  # only for live edit.
530    from bpy.utils import register_class
531    for cls in classes:
532        register_class(cls)
533