1 # Dynamic Sky.py (c) 2015 Pratik Solanki (Draguu)
3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
23 "name": "Dynamic Sky",
24 "author": "Pratik Solanki",
26 "blender": (2, 80, 0),
27 "location": "View3D > Sidebar > Create Tab",
28 "description": "Creates Dynamic Sky for Cycles",
30 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
31 "lighting/dynamic_sky.html",
32 "category": "Lighting",
36 from bpy
.props
import StringProperty
37 from bpy
.types
import (
43 # Handle error notifications
44 def error_handlers(self
, error
, reports
="ERROR"):
46 self
.report({'WARNING'}, reports
+ " (See Console for more info)")
48 print("\n[Dynamic Sky]\nError: {}\n".format(error
))
51 def check_world_name(name_id
="Dynamic"):
52 # check if the new name pattern is in world data
56 name_list
= [world
.name
for world
in bpy
.data
.worlds
if name_id
in world
.name
]
57 new_name
= "{}_{}".format(name_id
, len(name_list
) + suffix
)
58 if new_name
in name_list
:
59 # KISS failed - numbering is not sequential
60 # try harvesting numbers in world names, find the rightmost ones
62 from re
import findall
63 for words
in name_list
:
64 test_num
.append(findall("\d+", words
))
66 suffix
+= max([int(l
[-1]) for l
in test_num
])
67 new_name
= "{}_{}".format(name_id
, suffix
)
69 except Exception as e
:
70 error_handlers(False, e
)
76 return ('cycles' in bpy
.context
.preferences
.addons
.keys())
81 bl_label
= "Make a Procedural sky"
82 bl_description
= ("Make a Procedural Sky with parameters in the 3D View\n"
83 "Note: Available just for Cycles renderer\n"
84 "Only the last created Dynamic World can be accessed from this panel")
87 def poll(cls
, context
):
90 def get_node_types(self
, node_tree
, node_type
):
91 for node
in node_tree
.nodes
:
92 if node
.type == node_type
:
96 def execute(self
, context
):
98 get_name
= check_world_name()
99 context
.scene
.dynamic_sky_name
= get_name
100 bpy
.context
.scene
.render
.engine
= 'CYCLES'
102 world
= bpy
.data
.worlds
.new(get_name
)
103 world
.cycles
.sample_as_light
= True
104 world
.cycles
.sample_map_resolution
= 2048
105 world
.use_nodes
= True
108 # Note: (see T52714) to avoid string localization problems, assign the name for
109 # nodes that will be exposed in the 3D view (pattern UI name with underscore)
110 bg
= self
.get_node_types(nt
, "BACKGROUND")
111 bg
.name
= "Scene_Brightness"
112 bg
.inputs
[0].default_value
[:3] = (0.5, .1, 0.6)
113 bg
.inputs
[1].default_value
= 1
114 bg
.location
= (6708.3, 360)
117 tcor
= nt
.nodes
.new(type="ShaderNodeTexCoord")
118 tcor
.location
= (243.729, 1005)
120 map1
= nt
.nodes
.new(type="ShaderNodeMapping")
121 map1
.vector_type
= 'NORMAL'
122 map1
.location
= (786.54, 730)
124 nor
= nt
.nodes
.new(type="ShaderNodeNormal")
125 nor
.name
= "Sky_normal"
126 nor
.location
= (1220.16, 685)
128 cr1
= nt
.nodes
.new(type="ShaderNodeValToRGB")
129 cr1
.color_ramp
.elements
[0].position
= 0.969
130 cr1
.color_ramp
.interpolation
= 'EASE'
131 cr1
.location
= (1671.33, 415)
132 cr2
= nt
.nodes
.new(type="ShaderNodeValToRGB")
133 cr2
.color_ramp
.elements
[0].position
= 0.991
134 cr2
.color_ramp
.elements
[1].position
= 1
135 cr2
.color_ramp
.interpolation
= 'EASE'
136 cr2
.location
= (2196.6, 415)
137 cr3
= nt
.nodes
.new(type="ShaderNodeValToRGB")
138 cr3
.color_ramp
.elements
[0].position
= 0.779
139 cr3
.color_ramp
.elements
[1].position
= 1
140 cr3
.color_ramp
.interpolation
= 'EASE'
141 cr3
.location
= (2196.6, 415)
143 mat1
= nt
.nodes
.new(type="ShaderNodeMath")
144 mat1
.operation
= 'MULTIPLY'
145 mat1
.inputs
[1].default_value
= 0.2
146 mat1
.location
= (2196.6, 685)
147 mat2
= nt
.nodes
.new(type="ShaderNodeMath")
148 mat2
.operation
= 'MULTIPLY'
149 mat2
.inputs
[1].default_value
= 2
150 mat2
.location
= (3294, 685)
151 mat3
= nt
.nodes
.new(type="ShaderNodeMath")
152 mat3
.operation
= 'MULTIPLY'
153 mat3
.inputs
[1].default_value
= 40.9
154 mat3
.location
= (2745.24, 415)
155 mat4
= nt
.nodes
.new(type="ShaderNodeMath")
156 mat4
.operation
= 'SUBTRACT'
157 mat4
.inputs
[1].default_value
= 1
158 mat4
.location
= (3294, 415)
159 ntl(mat2
.inputs
[0], mat1
.outputs
[0])
160 ntl(mat4
.inputs
[0], mat3
.outputs
[0])
161 ntl(mat1
.inputs
[0], cr3
.outputs
[0])
162 ntl(mat3
.inputs
[0], cr2
.outputs
[0])
164 soft
= nt
.nodes
.new(type="ShaderNodeMixRGB")
165 soft
.name
= "Soft_hard"
166 soft
.location
= (3819.3, 550)
167 soft_1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
168 soft_1
.location
= (3819.3, 185)
169 soft
.inputs
[0].default_value
= 1
170 soft_1
.inputs
[0].default_value
= 0.466
171 ntl(soft
.inputs
[1], mat2
.outputs
[0])
172 ntl(soft
.inputs
[2], mat4
.outputs
[0])
173 ntl(soft_1
.inputs
[1], mat2
.outputs
[0])
174 ntl(soft_1
.inputs
[2], cr2
.outputs
[0])
176 mix1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
177 mix1
.blend_type
= 'MULTIPLY'
178 mix1
.inputs
[0].default_value
= 1
179 mix1
.location
= (4344.3, 630)
180 mix1_1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
181 mix1_1
.blend_type
= 'MULTIPLY'
182 mix1_1
.inputs
[0].default_value
= 1
183 mix1_1
.location
= (4344.3, 90)
185 mix2
= nt
.nodes
.new(type="ShaderNodeMixRGB")
186 mix2
.location
= (4782, 610)
187 mix2_1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
188 mix2_1
.location
= (5131.8, 270)
189 mix2
.inputs
[1].default_value
= (0, 0, 0, 1)
190 mix2
.inputs
[2].default_value
= (32, 22, 14, 200)
191 mix2_1
.inputs
[1].default_value
= (0, 0, 0, 1)
192 mix2_1
.inputs
[2].default_value
= (1, 0.820, 0.650, 1)
194 ntl(mix1
.inputs
[1], soft
.outputs
[0])
195 ntl(mix1_1
.inputs
[1], soft_1
.outputs
[0])
196 ntl(mix2
.inputs
[0], mix1
.outputs
[0])
197 ntl(mix2_1
.inputs
[0], mix1_1
.outputs
[0])
199 gam
= nt
.nodes
.new(type="ShaderNodeGamma")
200 gam
.inputs
[1].default_value
= 2.3
201 gam
.location
= (5131.8, 610)
203 gam2
= nt
.nodes
.new(type="ShaderNodeGamma")
204 gam2
.name
= "Sun_value"
205 gam2
.inputs
[1].default_value
= 1
206 gam2
.location
= (5524.5, 610)
208 gam3
= nt
.nodes
.new(type="ShaderNodeGamma")
209 gam3
.name
= "Shadow_color_saturation"
210 gam3
.inputs
[1].default_value
= 1
211 gam3
.location
= (5524.5, 880)
213 sunopa
= nt
.nodes
.new(type="ShaderNodeMixRGB")
214 sunopa
.blend_type
= 'ADD'
215 sunopa
.inputs
[0].default_value
= 1
216 sunopa
.location
= (5940.6, 610)
217 sunopa_1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
218 sunopa_1
.blend_type
= 'ADD'
219 sunopa_1
.inputs
[0].default_value
= 1
220 sunopa_1
.location
= (5524.5, 340)
222 combine
= nt
.nodes
.new(type="ShaderNodeMixRGB")
223 combine
.location
= (6313.8, 360)
224 ntl(combine
.inputs
[1], sunopa
.outputs
[0])
225 ntl(combine
.inputs
[2], sunopa_1
.outputs
[0])
226 lp
= nt
.nodes
.new(type="ShaderNodeLightPath")
227 lp
.location
= (5940.6, 130)
228 ntl(combine
.inputs
[0], lp
.outputs
[0])
230 ntl(gam2
.inputs
[0], gam
.outputs
[0])
231 ntl(gam
.inputs
[0], mix2
.outputs
[0])
232 ntl(bg
.inputs
[0], combine
.outputs
[0])
234 map2
= nt
.nodes
.new(type="ShaderNodeMapping")
235 map2
.inputs
['Scale'].default_value
[2] = 6.00
236 map2
.inputs
['Scale'].default_value
[0] = 1.5
237 map2
.inputs
['Scale'].default_value
[1] = 1.5
238 map2
.location
= (2196.6, 1510)
240 n1
= nt
.nodes
.new(type="ShaderNodeTexNoise")
241 n1
.inputs
['Scale'].default_value
= 3.8
242 n1
.inputs
['Detail'].default_value
= 2.4
243 n1
.inputs
['Distortion'].default_value
= 0.5
244 n1
.location
= (2745.24, 1780)
246 n2
= nt
.nodes
.new(type="ShaderNodeTexNoise")
247 n2
.inputs
['Scale'].default_value
= 2.0
248 n2
.inputs
['Detail'].default_value
= 10
249 n2
.inputs
['Distortion'].default_value
= 0.2
250 n2
.location
= (2745.24, 1510)
252 ntl(n2
.inputs
[0], map2
.outputs
[0])
253 ntl(n1
.inputs
[0], map2
.outputs
[0])
255 sc1
= nt
.nodes
.new(type="ShaderNodeValToRGB")
256 sc1
.location
= (3294, 1780)
257 sc2
= nt
.nodes
.new(type="ShaderNodeValToRGB")
258 sc2
.location
= (3294, 1510)
259 sc3
= nt
.nodes
.new(type="ShaderNodeValToRGB")
260 sc3
.location
= (3819.3, 820)
261 sc3_1
= nt
.nodes
.new(type="ShaderNodeValToRGB")
262 sc3_1
.location
= (4344.3, 1360)
263 sc4
= nt
.nodes
.new(type="ShaderNodeValToRGB")
264 sc4
.location
= (3819.3, 1090)
266 sc1
.color_ramp
.elements
[1].position
= 0.649
267 sc1
.color_ramp
.elements
[0].position
= 0.408
269 sc2
.color_ramp
.elements
[1].position
= 0.576
270 sc2
.color_ramp
.elements
[0].position
= 0.408
272 sc3
.color_ramp
.elements
.new(0.5)
273 sc3
.color_ramp
.elements
[2].position
= 0.435
275 sc3
.color_ramp
.elements
[1].position
= 0.160
276 sc3
.color_ramp
.elements
[0].position
= 0.027
278 sc3
.color_ramp
.elements
[1].color
= (1, 1, 1, 1)
279 sc3
.color_ramp
.elements
[0].color
= (0.419, 0.419, 0.419, 0.419)
281 sc3
.color_ramp
.elements
[0].position
= 0.0
282 sc4
.color_ramp
.elements
[0].position
= 0.0
283 sc4
.color_ramp
.elements
[1].position
= 0.469
284 sc4
.color_ramp
.elements
[1].color
= (0, 0, 0, 1)
285 sc4
.color_ramp
.elements
[0].color
= (1, 1, 0.917412, 1)
287 sc3_1
.color_ramp
.elements
.new(0.5)
288 sc3_1
.color_ramp
.elements
[2].position
= 0.435
290 sc3_1
.color_ramp
.elements
[1].position
= 0.187
291 sc3_1
.color_ramp
.elements
[1].color
= (1, 1, 1, 1)
292 sc3_1
.color_ramp
.elements
[0].color
= (0, 0, 0, 0)
293 sc3_1
.color_ramp
.elements
[0].position
= 0.0
295 smix1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
296 smix1
.location
= (3819.3, 1550)
297 smix1
.name
= "Cloud_color"
298 smix2
= nt
.nodes
.new(type="ShaderNodeMixRGB")
299 smix2
.location
= (4344.3, 1630)
300 smix2
.name
= "Cloud_density"
301 smix2_1
= nt
.nodes
.new(type="ShaderNodeMixRGB")
302 smix2_1
.location
= (4782, 1360)
304 smix3
= nt
.nodes
.new(type="ShaderNodeMixRGB")
305 smix3
.location
= (4344.3, 1090)
306 smix3
.name
= "Sky_and_Horizon_colors"
308 smix4
= nt
.nodes
.new(type="ShaderNodeMixRGB")
309 smix4
.location
= (4782, 880)
311 smix5
= nt
.nodes
.new(type="ShaderNodeMixRGB")
312 smix5
.name
= "Cloud_opacity"
313 smix5
.location
= (5131.8, 880)
315 smix1
.inputs
[1].default_value
= (1, 1, 1, 1)
316 smix1
.inputs
[2].default_value
= (0, 0, 0, 1)
317 smix2
.inputs
[0].default_value
= 0.267
318 smix2
.blend_type
= 'MULTIPLY'
319 smix2_1
.inputs
[0].default_value
= 1
320 smix2_1
.blend_type
= 'MULTIPLY'
322 smix3
.inputs
[1].default_value
= (0.434, 0.838, 1, 1)
323 smix3
.inputs
[2].default_value
= (0.962, 0.822, 0.822, 1)
324 smix4
.blend_type
= 'MULTIPLY'
325 smix4
.inputs
[0].default_value
= 1
326 smix5
.blend_type
= 'SCREEN'
327 smix5
.inputs
[0].default_value
= 1
329 srgb
= nt
.nodes
.new(type="ShaderNodeSeparateRGB")
330 srgb
.location
= (786.54, 1370)
331 aniadd
= nt
.nodes
.new(type="ShaderNodeMath")
332 aniadd
.location
= (1220.16, 1235)
333 crgb
= nt
.nodes
.new(type="ShaderNodeCombineRGB")
334 crgb
.location
= (1671.33, 1510)
335 sunrgb
= nt
.nodes
.new(type="ShaderNodeMixRGB")
336 sunrgb
.name
= "Sun_color"
338 sunrgb
.blend_type
= 'MULTIPLY'
339 sunrgb
.inputs
[2].default_value
= (32, 30, 30, 200)
340 sunrgb
.inputs
[0].default_value
= 1
341 sunrgb
.location
= (4344.3, 360)
343 ntl(mix2
.inputs
[2], sunrgb
.outputs
[0])
345 ntl(smix1
.inputs
[0], sc2
.outputs
[0])
346 ntl(smix2
.inputs
[1], smix1
.outputs
[0])
347 ntl(smix2
.inputs
[2], sc1
.outputs
[0])
348 ntl(smix2_1
.inputs
[2], sc3_1
.outputs
[0])
349 ntl(smix3
.inputs
[0], sc4
.outputs
[0])
350 ntl(smix4
.inputs
[2], smix3
.outputs
[0])
351 ntl(smix4
.inputs
[1], sc3
.outputs
[0])
352 ntl(smix5
.inputs
[1], smix4
.outputs
[0])
353 ntl(smix2_1
.inputs
[1], smix2
.outputs
[0])
354 ntl(smix5
.inputs
[2], smix2_1
.outputs
[0])
355 ntl(sunopa
.inputs
[1], gam3
.outputs
[0])
356 ntl(gam3
.inputs
[0], smix5
.outputs
[0])
357 ntl(mix1
.inputs
[2], sc3
.outputs
[0])
358 ntl(sunopa
.inputs
[2], gam2
.outputs
[0])
360 ntl(sc1
.inputs
[0], n1
.outputs
['Fac'])
361 ntl(sc2
.inputs
[0], n2
.outputs
['Fac'])
363 skynor
= nt
.nodes
.new(type="ShaderNodeNormal")
364 skynor
.location
= (3294, 1070)
366 ntl(sc3
.inputs
[0], skynor
.outputs
[1])
367 ntl(sc4
.inputs
[0], skynor
.outputs
[1])
368 ntl(sc3_1
.inputs
[0], skynor
.outputs
[1])
369 ntl(map2
.inputs
[0], crgb
.outputs
[0])
370 ntl(skynor
.inputs
[0], tcor
.outputs
[0])
371 ntl(mix1_1
.inputs
[2], sc3
.outputs
[0])
372 ntl(srgb
.inputs
[0], tcor
.outputs
[0])
373 ntl(crgb
.inputs
[1], srgb
.outputs
[1])
374 ntl(crgb
.inputs
[2], srgb
.outputs
[2])
375 ntl(aniadd
.inputs
[1], srgb
.outputs
[0])
376 ntl(crgb
.inputs
[0], aniadd
.outputs
[0])
378 ntl(cr1
.inputs
[0], nor
.outputs
[1])
379 ntl(cr2
.inputs
[0], cr1
.outputs
[0])
380 ntl(cr3
.inputs
[0], nor
.outputs
[1])
381 ntl(nor
.inputs
[0], map1
.outputs
[0])
382 ntl(map1
.inputs
[0], tcor
.outputs
[0])
383 ntl(sunopa_1
.inputs
[1], smix5
.outputs
[0])
384 ntl(sunopa_1
.inputs
[2], mix2_1
.outputs
[0])
386 world_out
= self
.get_node_types(nt
, "OUTPUT_WORLD")
387 world_out
.location
= (7167.3, 360)
389 except Exception as e
:
390 error_handlers(self
, e
, "Make a Procedural sky has failed")
397 def draw_world_settings(col
, context
):
398 get_world
= context
.scene
.world
399 stored_name
= context
.scene
.dynamic_sky_name
400 get_world_keys
= bpy
.data
.worlds
.keys()
402 if stored_name
not in get_world_keys
or len(get_world_keys
) < 1:
403 col
.label(text
="The {} World could not".format(stored_name
),
405 col
.label(text
="be found in the Worlds' Data", icon
="BLANK1")
408 elif not (get_world
and get_world
.name
== stored_name
):
409 col
.label(text
="Please select the World", icon
="INFO")
410 col
.label(text
="named {}".format(stored_name
), icon
="BLANK1")
411 col
.label(text
="from the Properties > World", icon
="BLANK1")
414 pick_world
= bpy
.data
.worlds
[stored_name
]
416 m
= pick_world
.node_tree
.nodes
[28]
418 m
= pick_world
.node_tree
.nodes
['Sky_and_Horizon_colors'].inputs
[1]
419 n
= pick_world
.node_tree
.nodes
['Sky_and_Horizon_colors'].inputs
[2]
420 c
= pick_world
.node_tree
.nodes
['Cloud_color'].inputs
[1]
421 o
= pick_world
.node_tree
.nodes
['Cloud_opacity'].inputs
[0]
422 d
= pick_world
.node_tree
.nodes
['Cloud_density'].inputs
[0]
423 so
= pick_world
.node_tree
.nodes
['Sun_value'].inputs
[1]
424 so2
= pick_world
.node_tree
.nodes
['Shadow_color_saturation'].inputs
[1]
425 no
= pick_world
.node_tree
.nodes
['Sky_normal'].outputs
[0]
426 sof
= pick_world
.node_tree
.nodes
['Soft_hard'].inputs
[0]
427 bgp
= pick_world
.node_tree
.nodes
['Scene_Brightness'].inputs
[1]
429 suc
= pick_world
.node_tree
.nodes
['Sun_color'].inputs
[1]
431 col
.label(text
="Please Create a new World", icon
="INFO")
432 col
.label(text
="seems that there was already", icon
="BLANK1")
433 col
.label(text
="one called {}".format(stored_name
), icon
="BLANK1")
436 col
.label(text
="World: %s" % stored_name
)
439 col
.label(text
="Scene Control")
440 col
.prop(bgp
, "default_value", text
="Brightness")
441 col
.prop(so2
, "default_value", text
="Shadow color saturation")
443 col
.label(text
="Sky Control")
444 col
.prop(m
, "default_value", text
="Sky color")
445 col
.prop(n
, "default_value", text
="Horizon Color")
446 col
.prop(c
, "default_value", text
="Cloud color")
447 col
.prop(o
, "default_value", text
="Cloud opacity")
448 col
.prop(d
, "default_value", text
="Cloud density")
450 col
.label(text
="Sun Control")
451 col
.prop(suc
, "default_value", text
="")
452 col
.prop(so
, "default_value", text
="Sun value")
453 col
.prop(sof
, "default_value", text
="Soft hard")
455 col
.prop(no
, "default_value", text
="")
458 class Dynapanel(Panel
):
459 bl_label
= "Dynamic sky"
460 bl_idname
= "DYNSKY_PT_tools"
461 bl_space_type
= 'VIEW_3D'
462 bl_region_type
= 'UI'
463 bl_context
= "objectmode"
464 bl_category
= "Create"
465 bl_options
= {'DEFAULT_CLOSED'}
467 def draw(self
, context
):
469 layout
.operator("sky.dyn", text
="Create", icon
='MAT_SPHERE_SKY')
471 col
= layout
.column()
472 draw_world_settings(col
, context
)
476 bpy
.utils
.register_class(Dynapanel
)
477 bpy
.utils
.register_class(dsky
)
478 bpy
.types
.Scene
.dynamic_sky_name
= StringProperty(
485 bpy
.utils
.unregister_class(Dynapanel
)
486 bpy
.utils
.unregister_class(dsky
)
487 del bpy
.types
.Scene
.dynamic_sky_name
490 if __name__
== "__main__":