1 # SPDX-FileCopyrightText: 2021-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """User interface to camera frame, optics distortions, and environment
7 with world, sky, atmospheric effects such as rainbows or smoke """
10 from bpy
.utils
import register_class
, unregister_class
11 from bpy
.types
import Operator
, Menu
, Panel
12 from bl_operators
.presets
import AddPresetBase
14 from bl_ui
import properties_data_camera
16 for member
in dir(properties_data_camera
):
17 subclass
= getattr(properties_data_camera
, member
)
18 if hasattr(subclass
, "COMPAT_ENGINES"):
19 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
20 del properties_data_camera
22 # -------- Use only a subset of the world panels
23 # from bl_ui import properties_world
25 # # TORECREATE##DEPRECATED#properties_world.WORLD_PT_preview.COMPAT_ENGINES.add('POVRAY_RENDER')
26 # properties_world.WORLD_PT_context_world.COMPAT_ENGINES.add('POVRAY_RENDER')
27 # # TORECREATE##DEPRECATED#properties_world.WORLD_PT_world.COMPAT_ENGINES.add('POVRAY_RENDER')
28 # del properties_world
31 # Physics Main wrapping every class 'as is'
32 from bl_ui
import properties_physics_common
34 for member
in dir(properties_physics_common
):
35 subclass
= getattr(properties_physics_common
, member
)
36 if hasattr(subclass
, "COMPAT_ENGINES"):
37 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
38 del properties_physics_common
40 # Physics Rigid Bodies wrapping every class 'as is'
41 from bl_ui
import properties_physics_rigidbody
43 for member
in dir(properties_physics_rigidbody
):
44 subclass
= getattr(properties_physics_rigidbody
, member
)
45 if hasattr(subclass
, "COMPAT_ENGINES"):
46 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
47 del properties_physics_rigidbody
49 # Physics Rigid Body Constraint wrapping every class 'as is'
50 from bl_ui
import properties_physics_rigidbody_constraint
52 for member
in dir(properties_physics_rigidbody_constraint
):
53 subclass
= getattr(properties_physics_rigidbody_constraint
, member
)
54 if hasattr(subclass
, "COMPAT_ENGINES"):
55 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
56 del properties_physics_rigidbody_constraint
58 # Physics Smoke and fluids wrapping every class 'as is'
59 from bl_ui
import properties_physics_fluid
61 for member
in dir(properties_physics_fluid
):
62 subclass
= getattr(properties_physics_fluid
, member
)
63 if hasattr(subclass
, "COMPAT_ENGINES"):
64 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
65 del properties_physics_fluid
67 # Physics softbody wrapping every class 'as is'
68 from bl_ui
import properties_physics_softbody
70 for member
in dir(properties_physics_softbody
):
71 subclass
= getattr(properties_physics_softbody
, member
)
72 if hasattr(subclass
, "COMPAT_ENGINES"):
73 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
74 del properties_physics_softbody
76 # Physics Field wrapping every class 'as is'
77 from bl_ui
import properties_physics_field
79 for member
in dir(properties_physics_field
):
80 subclass
= getattr(properties_physics_field
, member
)
81 if hasattr(subclass
, "COMPAT_ENGINES"):
82 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
83 del properties_physics_field
85 # Physics Cloth wrapping every class 'as is'
86 from bl_ui
import properties_physics_cloth
88 for member
in dir(properties_physics_cloth
):
89 subclass
= getattr(properties_physics_cloth
, member
)
90 if hasattr(subclass
, "COMPAT_ENGINES"):
91 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
92 del properties_physics_cloth
94 # Physics Dynamic Paint wrapping every class 'as is'
95 from bl_ui
import properties_physics_dynamicpaint
97 for member
in dir(properties_physics_dynamicpaint
):
98 subclass
= getattr(properties_physics_dynamicpaint
, member
)
99 if hasattr(subclass
, "COMPAT_ENGINES"):
100 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
101 del properties_physics_dynamicpaint
103 from bl_ui
import properties_particle
105 for member
in dir(properties_particle
): # add all "particle" panels from blender
106 subclass
= getattr(properties_particle
, member
)
107 if hasattr(subclass
, "COMPAT_ENGINES"):
108 subclass
.COMPAT_ENGINES
.add("POVRAY_RENDER")
109 del properties_particle
112 class CameraDataButtonsPanel
:
113 """Use this class to define buttons from the camera data tab of
114 properties window."""
116 bl_space_type
= "PROPERTIES"
117 bl_region_type
= "WINDOW"
119 COMPAT_ENGINES
= {"POVRAY_RENDER"}
122 def poll(cls
, context
):
124 rd
= context
.scene
.render
125 return cam
and (rd
.engine
in cls
.COMPAT_ENGINES
)
128 class WorldButtonsPanel
:
129 """Use this class to define buttons from the world tab of
130 properties window."""
132 bl_space_type
= "PROPERTIES"
133 bl_region_type
= "WINDOW"
135 COMPAT_ENGINES
= {"POVRAY_RENDER"}
138 def poll(cls
, context
):
140 rd
= context
.scene
.render
141 return wld
and (rd
.engine
in cls
.COMPAT_ENGINES
)
144 # ---------------------------------------------------------------- #
146 # ---------------------------------------------------------------- #
147 class CAMERA_PT_POV_cam_dof(CameraDataButtonsPanel
, Panel
):
148 """Use this class for camera depth of field focal blur buttons."""
150 bl_label
= "POV Aperture"
151 COMPAT_ENGINES
= {"POVRAY_RENDER"}
152 bl_parent_id
= "DATA_PT_camera_dof_aperture"
153 bl_options
= {"HIDE_HEADER"}
154 # def draw_header(self, context):
155 # cam = context.camera
157 # self.layout.prop(cam.pov, "dof_enable", text="")
159 def draw(self
, context
):
164 layout
.active
= cam
.dof
.use_dof
165 layout
.use_property_split
= True # Active single-column layout
167 flow
= layout
.grid_flow(
168 row_major
=True, columns
=0, even_columns
=True, even_rows
=False, align
=False
172 col
.label(text
="F-Stop value will export as")
173 col
.label(text
="POV aperture : " + "%.3f" % (1 / cam
.dof
.aperture_fstop
* 1000))
176 col
.prop(cam
.pov
, "dof_samples_min")
177 col
.prop(cam
.pov
, "dof_samples_max")
178 col
.prop(cam
.pov
, "dof_variance")
179 col
.prop(cam
.pov
, "dof_confidence")
182 class CAMERA_PT_POV_cam_nor(CameraDataButtonsPanel
, Panel
):
183 """Use this class for camera normal perturbation buttons."""
185 bl_label
= "POV Perturbation"
186 COMPAT_ENGINES
= {"POVRAY_RENDER"}
188 def draw_header(self
, context
):
191 self
.layout
.prop(cam
.pov
, "normal_enable", text
="")
193 def draw(self
, context
):
198 layout
.active
= cam
.pov
.normal_enable
200 layout
.prop(cam
.pov
, "normal_patterns")
201 layout
.prop(cam
.pov
, "cam_normal")
202 layout
.prop(cam
.pov
, "turbulence")
203 layout
.prop(cam
.pov
, "scale")
206 class CAMERA_PT_POV_replacement_text(CameraDataButtonsPanel
, Panel
):
207 """Use this class for camera text replacement field."""
209 bl_label
= "Custom POV Code"
210 COMPAT_ENGINES
= {"POVRAY_RENDER"}
212 def draw(self
, context
):
217 col
= layout
.column()
218 col
.label(text
="Replace properties with:")
219 col
.prop(cam
.pov
, "replacement_text", text
="")
222 # ---------------------------------------------------------------- #
223 # World background and sky sphere Settings
224 # ---------------------------------------------------------------- #
227 class WORLD_PT_POV_world(WorldButtonsPanel
, Panel
):
228 """Use this class to define pov world buttons"""
232 COMPAT_ENGINES
= {"POVRAY_RENDER"}
234 def draw(self
, context
):
237 world
= context
.world
.pov
239 row
= layout
.row(align
=True)
240 row
.menu(WORLD_MT_POV_presets
.__name
__, text
=WORLD_MT_POV_presets
.bl_label
)
241 row
.operator(WORLD_OT_POV_add_preset
.bl_idname
, text
="", icon
="ADD")
242 row
.operator(WORLD_OT_POV_add_preset
.bl_idname
, text
="", icon
="REMOVE").remove_active
= True
245 row
.prop(world
, "use_sky_paper")
246 row
.prop(world
, "use_sky_blend")
247 row
.prop(world
, "use_sky_real")
250 row
.column().prop(world
, "horizon_color")
252 col
.prop(world
, "zenith_color")
253 col
.active
= world
.use_sky_blend
254 row
.column().prop(world
, "ambient_color")
257 # row.prop(world, "exposure") #Re-implement later as a light multiplier
258 # row.prop(world, "color_range")
261 class WORLD_PT_POV_mist(WorldButtonsPanel
, Panel
):
262 """Use this class to define pov mist buttons."""
265 bl_options
= {"DEFAULT_CLOSED"}
266 COMPAT_ENGINES
= {"POVRAY_RENDER"}
268 def draw_header(self
, context
):
269 world
= context
.world
271 self
.layout
.prop(world
.mist_settings
, "use_mist", text
="")
273 def draw(self
, context
):
276 world
= context
.world
278 layout
.active
= world
.mist_settings
.use_mist
280 split
= layout
.split()
283 col
.prop(world
.mist_settings
, "intensity")
284 col
.prop(world
.mist_settings
, "start")
287 col
.prop(world
.mist_settings
, "depth")
288 col
.prop(world
.mist_settings
, "height")
290 layout
.prop(world
.mist_settings
, "falloff")
293 class WORLD_MT_POV_presets(Menu
):
294 """Apply world preset to all concerned properties"""
296 bl_label
= "World Presets"
297 preset_subdir
= "pov/world"
298 preset_operator
= "script.execute_preset"
299 draw
= bpy
.types
.Menu
.draw_preset
302 class WORLD_OT_POV_add_preset(AddPresetBase
, Operator
):
303 """Add a World Preset recording current values"""
305 bl_idname
= "object.world_preset_add"
306 bl_label
= "Add World Preset"
307 preset_menu
= "WORLD_MT_POV_presets"
309 # variable used for all preset values
310 preset_defines
= ["scene = bpy.context.scene"]
312 # properties to store in the preset
314 "scene.world.use_sky_blend",
315 "scene.world.horizon_color",
316 "scene.world.zenith_color",
317 "scene.world.ambient_color",
318 "scene.world.mist_settings.use_mist",
319 "scene.world.mist_settings.intensity",
320 "scene.world.mist_settings.depth",
321 "scene.world.mist_settings.start",
322 "scene.pov.media_enable",
323 "scene.pov.media_scattering_type",
324 "scene.pov.media_samples",
325 "scene.pov.media_diffusion_scale",
326 "scene.pov.media_diffusion_color",
327 "scene.pov.media_absorption_scale",
328 "scene.pov.media_absorption_color",
329 "scene.pov.media_eccentricity",
332 # where to store the preset
333 preset_subdir
= "pov/world"
336 class RENDER_PT_POV_media(WorldButtonsPanel
, Panel
):
337 """Use this class to define a pov global atmospheric media buttons."""
339 bl_label
= "Atmosphere Media"
340 COMPAT_ENGINES
= {"POVRAY_RENDER"}
342 def draw_header(self
, context
):
343 scene
= context
.scene
345 self
.layout
.prop(scene
.pov
, "media_enable", text
="")
347 def draw(self
, context
):
350 scene
= context
.scene
352 layout
.active
= scene
.pov
.media_enable
354 col
= layout
.column()
355 col
.prop(scene
.pov
, "media_scattering_type", text
="")
356 col
= layout
.column()
357 col
.prop(scene
.pov
, "media_samples", text
="Samples")
358 split
= layout
.split()
359 col
= split
.column(align
=True)
360 col
.label(text
="Scattering:")
361 col
.prop(scene
.pov
, "media_diffusion_scale")
362 col
.prop(scene
.pov
, "media_diffusion_color", text
="")
363 col
= split
.column(align
=True)
364 col
.label(text
="Absorption:")
365 col
.prop(scene
.pov
, "media_absorption_scale")
366 col
.prop(scene
.pov
, "media_absorption_color", text
="")
367 if scene
.pov
.media_scattering_type
== "5":
368 col
= layout
.column()
369 col
.prop(scene
.pov
, "media_eccentricity", text
="Eccentricity")
372 # ---------------------------------------------------------------- #
374 # ---------------------------------------------------------------- #
376 # ----------------------------------------------------------------
377 # from bl_ui import properties_data_light
378 # for member in dir(properties_data_light):
379 # subclass = getattr(properties_data_light, member)
381 # subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
382 # except BaseException as e:
384 # print('An exception occurred: {}'.format(e))
386 # del properties_data_light
387 # -------- LIGHTS -------- #
389 from bl_ui
import properties_data_light
391 # -------- These panels are kept
392 # properties_data_light.DATA_PT_custom_props_light.COMPAT_ENGINES.add('POVRAY_RENDER')
393 # properties_data_light.DATA_PT_context_light.COMPAT_ENGINES.add('POVRAY_RENDER')
395 # make some native panels contextual to some object variable
396 # by recreating custom panels inheriting their properties
399 class PovLightButtonsPanel(properties_data_light
.DataButtonsPanel
):
400 """Use this class to define buttons from the light data tab of
401 properties window."""
403 COMPAT_ENGINES
= {"POVRAY_RENDER"}
404 POV_OBJECT_TYPES
= {"RAINBOW"}
407 def poll(cls
, context
):
409 # We use our parent class poll func too, avoids to re-define too much things...
411 super(PovLightButtonsPanel
, cls
).poll(context
)
413 and obj
.pov
.object_as
not in cls
.POV_OBJECT_TYPES
417 # We cannot inherit from RNA classes (like e.g. properties_data_mesh.DATA_PT_vertex_groups).
418 # Complex py/bpy/rna interactions (with metaclass and all) simply do not allow it to work.
419 # So we simply have to explicitly copy here the interesting bits. ;)
420 from bl_ui
import properties_data_light
422 # for member in dir(properties_data_light):
423 # subclass = getattr(properties_data_light, member)
425 # subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
426 # except BaseException as e:
428 # print('An exception occurred: {}'.format(e))
431 # Now only These panels are kept
432 properties_data_light
.DATA_PT_custom_props_light
.COMPAT_ENGINES
.add("POVRAY_RENDER")
433 properties_data_light
.DATA_PT_context_light
.COMPAT_ENGINES
.add("POVRAY_RENDER")
436 class LIGHT_PT_POV_preview(PovLightButtonsPanel
, Panel
):
437 # XXX Needs update and docstring
438 bl_label
= properties_data_light
.DATA_PT_preview
.bl_label
440 draw
= properties_data_light
.DATA_PT_preview
.draw
443 class LIGHT_PT_POV_light(PovLightButtonsPanel
, Panel
):
444 """UI panel to main pov light parameters"""
446 # bl_label = properties_data_light.DATA_PT_light.bl_label
448 # draw = properties_data_light.DATA_PT_light.draw
449 # class DATA_PT_POV_light(DataButtonsPanel, Panel):
451 # COMPAT_ENGINES = {'POVRAY_RENDER'}
453 def draw(self
, context
):
456 light
= context
.light
458 layout
.row().prop(light
, "type", expand
=True)
460 split
= layout
.split()
464 sub
.prop(light
, "color", text
="")
465 sub
.prop(light
, "energy")
467 if light
.type in {"POINT", "SPOT"}:
468 sub
.prop(light
, "shadow_soft_size", text
="Radius")
470 if light
.type == "AREA":
471 col
.prop(light
, "shape")
473 sub
= col
.column(align
=True)
475 if light
.shape
in {'SQUARE', 'DISK'}:
476 sub
.prop(light
, "size")
477 elif light
.shape
in {'RECTANGLE', 'ELLIPSE'}:
478 sub
.prop(light
, "size", text
="Size X")
479 sub
.prop(light
, "size_y", text
="Y")
481 # restore later as interface to POV light groups ?
482 # col = split.column()
483 # col.prop(light, "use_own_layer", text="This Layer Only")
486 class LIGHT_MT_POV_presets(Menu
):
487 """Use this class to define preset menu for pov lights."""
489 bl_label
= "Lamp Presets"
490 preset_subdir
= "pov/light"
491 preset_operator
= "script.execute_preset"
492 draw
= bpy
.types
.Menu
.draw_preset
495 class LIGHT_OT_POV_add_preset(AddPresetBase
, Operator
):
496 """Operator to add a Light Preset"""
498 bl_idname
= "object.light_preset_add"
499 bl_label
= "Add Light Preset"
500 preset_menu
= "LIGHT_MT_POV_presets"
502 # variable used for all preset values
503 preset_defines
= ["lightdata = bpy.context.object.data"]
505 # properties to store in the preset
506 preset_values
= ["lightdata.type", "lightdata.color"]
508 # where to store the preset
509 preset_subdir
= "pov/light"
512 # Draw into the existing light panel
513 def light_panel_func(self
, context
):
514 """Menu to browse and add light preset"""
517 row
= layout
.row(align
=True)
518 row
.menu(LIGHT_MT_POV_presets
.__name
__, text
=LIGHT_MT_POV_presets
.bl_label
)
519 row
.operator(LIGHT_OT_POV_add_preset
.bl_idname
, text
="", icon
="ADD")
520 row
.operator(LIGHT_OT_POV_add_preset
.bl_idname
, text
="", icon
="REMOVE").remove_active
= True
523 """#TORECREATE##DEPRECATED#
524 class LIGHT_PT_POV_sunsky(PovLightButtonsPanel, Panel):
525 bl_label = properties_data_light.DATA_PT_sunsky.bl_label
528 def poll(cls, context):
530 engine = context.scene.render.engine
531 return (lamp and lamp.type == 'SUN') and (engine in cls.COMPAT_ENGINES)
533 draw = properties_data_light.DATA_PT_sunsky.draw
538 class LIGHT_PT_POV_shadow(PovLightButtonsPanel
, Panel
):
539 # Todo : update and docstring
543 def poll(cls
, context
):
544 light
= context
.light
545 engine
= context
.scene
.render
.engine
546 return light
and (engine
in cls
.COMPAT_ENGINES
)
548 def draw(self
, context
):
551 light
= context
.light
553 layout
.row().prop(light
.pov
, "shadow_method", expand
=True)
555 split
= layout
.split()
558 col
.prop(light
.pov
, "use_halo")
559 sub
= col
.column(align
=True)
560 sub
.active
= light
.pov
.use_halo
561 sub
.prop(light
.pov
, "halo_intensity", text
="Intensity")
563 if light
.pov
.shadow_method
== "NOSHADOW" and light
.type == "AREA":
564 split
= layout
.split()
567 col
.label(text
="Form factor sampling:")
569 sub
= col
.row(align
=True)
571 if light
.shape
== "SQUARE":
572 sub
.prop(light
, "shadow_ray_samples_x", text
="Samples")
573 elif light
.shape
== "RECTANGLE":
574 sub
.prop(light
.pov
, "shadow_ray_samples_x", text
="Samples X")
575 sub
.prop(light
.pov
, "shadow_ray_samples_y", text
="Samples Y")
577 if light
.pov
.shadow_method
!= "NOSHADOW":
578 split
= layout
.split()
581 col
.prop(light
, "shadow_color", text
="")
583 # col = split.column()
584 # col.prop(light.pov, "use_shadow_layer", text="This Layer Only")
585 # col.prop(light.pov, "use_only_shadow")
587 if light
.pov
.shadow_method
== "RAY_SHADOW":
588 split
= layout
.split()
591 col
.label(text
="Sampling:")
593 if light
.type in {"POINT", "SUN", "SPOT"}:
596 sub
.prop(light
.pov
, "shadow_ray_samples_x", text
="Samples")
597 # any equivalent in pov?
598 # sub.prop(light, "shadow_soft_size", text="Soft Size")
600 elif light
.type == "AREA":
601 sub
= col
.row(align
=True)
603 if light
.shape
== "SQUARE":
604 sub
.prop(light
.pov
, "shadow_ray_samples_x", text
="Samples")
605 elif light
.shape
== "RECTANGLE":
606 sub
.prop(light
.pov
, "shadow_ray_samples_x", text
="Samples X")
607 sub
.prop(light
.pov
, "shadow_ray_samples_y", text
="Samples Y")
610 class LIGHT_PT_POV_spot(PovLightButtonsPanel
, Panel
):
611 bl_label
= properties_data_light
.DATA_PT_spot
.bl_label
612 bl_parent_id
= "LIGHT_PT_POV_light"
616 def poll(cls
, context
):
618 engine
= context
.scene
.render
.engine
619 return (lamp
and lamp
.type == "SPOT") and (engine
in cls
.COMPAT_ENGINES
)
621 draw
= properties_data_light
.DATA_PT_spot
.draw
624 class OBJECT_PT_POV_rainbow(PovLightButtonsPanel
, Panel
):
625 """Use this class to define buttons from the rainbow panel of
626 properties window. inheriting lamp buttons panel class"""
628 bl_label
= "POV-Ray Rainbow"
629 COMPAT_ENGINES
= {"POVRAY_RENDER"}
630 # bl_options = {'HIDE_HEADER'}
633 def poll(cls
, context
):
634 engine
= context
.scene
.render
.engine
636 return obj
and obj
.pov
.object_as
== "RAINBOW" and (engine
in cls
.COMPAT_ENGINES
)
638 def draw(self
, context
):
643 col
= layout
.column()
645 if obj
.pov
.object_as
== "RAINBOW":
646 if not obj
.pov
.unlock_parameters
:
648 obj
.pov
, "unlock_parameters", text
="Exported parameters below", icon
="LOCKED"
650 col
.label(text
="Rainbow projection angle: " + str(obj
.data
.spot_size
))
651 col
.label(text
="Rainbow width: " + str(obj
.data
.spot_blend
))
652 col
.label(text
="Rainbow distance: " + str(obj
.data
.shadow_buffer_clip_start
))
653 col
.label(text
="Rainbow arc angle: " + str(obj
.pov
.arc_angle
))
654 col
.label(text
="Rainbow falloff angle: " + str(obj
.pov
.falloff_angle
))
658 obj
.pov
, "unlock_parameters", text
="Edit exported parameters", icon
="UNLOCKED"
660 col
.label(text
="3D view proxy may get out of synch")
661 col
.active
= obj
.pov
.unlock_parameters
663 layout
.operator("pov.cone_update", text
="Update", icon
="MESH_CONE")
665 # col.label(text="Parameters:")
666 col
.prop(obj
.data
, "spot_size", text
="Rainbow Projection Angle")
667 col
.prop(obj
.data
, "spot_blend", text
="Rainbow width")
668 col
.prop(obj
.data
, "shadow_buffer_clip_start", text
="Visibility distance")
669 col
.prop(obj
.pov
, "arc_angle")
670 col
.prop(obj
.pov
, "falloff_angle")
673 del properties_data_light
678 WORLD_MT_POV_presets
,
679 WORLD_OT_POV_add_preset
,
682 LIGHT_PT_POV_preview
,
686 LIGHT_MT_POV_presets
,
687 LIGHT_OT_POV_add_preset
,
688 OBJECT_PT_POV_rainbow
,
689 CAMERA_PT_POV_cam_dof
,
690 CAMERA_PT_POV_cam_nor
,
691 CAMERA_PT_POV_replacement_text
,
699 LIGHT_PT_POV_light
.prepend(light_panel_func
)
703 LIGHT_PT_POV_light
.remove(light_panel_func
)
704 for cls
in reversed(classes
):
705 unregister_class(cls
)