1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
22 "name": "Cloud Generator",
23 "author": "Nick Keeline(nrk)",
25 "blender": (2, 78, 5),
26 "location": "Tool Shelf > Create Tab",
27 "description": "Creates Volumetric Clouds",
28 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
29 "Scripts/Object/Cloud_Gen",
34 from bpy
.props
import (
38 from bpy
.types
import (
44 # For Cycles Render we create node groups or if it already exists we return it.
45 def CreateNodeGroup(Type
):
47 # Look for NodeTree if it already exists return it
49 for Group
in bpy
.data
.node_groups
:
50 if Group
.name
== Type
:
54 if CreateGroup
is True:
55 NodeGroup
= bpy
.data
.node_groups
.new(name
=Type
, type="ShaderNodeTree")
57 NodeGroup
.bl_label
= Type
58 NodeGroup
.nodes
.clear()
60 # Create a bunch of nodes and group them based on input to the def
62 if Type
== 'CloudGen_VolumeProperties':
63 AddAddAndEmission
= NodeGroup
.nodes
.new('ShaderNodeAddShader')
64 AddAddAndEmission
.location
= [300, 395]
65 AddAbsorptionAndScatter
= NodeGroup
.nodes
.new('ShaderNodeAddShader')
66 AddAbsorptionAndScatter
.location
= [0, 395]
67 VolumeAbsorption
= NodeGroup
.nodes
.new('ShaderNodeVolumeAbsorption')
68 VolumeAbsorption
.location
= [-300, 395]
69 VolumeScatter
= NodeGroup
.nodes
.new('ShaderNodeVolumeScatter')
70 VolumeScatter
.location
= [-300, 0]
71 VolumeEmission
= NodeGroup
.nodes
.new('ShaderNodeEmission')
72 VolumeEmission
.location
= [-300, -300]
73 MathAbsorptionMultiply
= NodeGroup
.nodes
.new('ShaderNodeMath')
74 MathAbsorptionMultiply
.location
= [-750, 395]
75 MathAbsorptionMultiply
.operation
= 'MULTIPLY'
76 MathScatterMultiply
= NodeGroup
.nodes
.new('ShaderNodeMath')
77 MathScatterMultiply
.location
= [-750, 0]
78 MathScatterMultiply
.operation
= 'MULTIPLY'
79 MathEmissionMultiply
= NodeGroup
.nodes
.new('ShaderNodeMath')
80 MathEmissionMultiply
.location
= [-750, -300]
81 MathEmissionMultiply
.operation
= 'MULTIPLY'
82 MathBrightnessMultiply
= NodeGroup
.nodes
.new('ShaderNodeMath')
83 MathBrightnessMultiply
.location
= [-1200, 0]
84 MathBrightnessMultiply
.operation
= 'MULTIPLY'
85 MathGreaterThan
= NodeGroup
.nodes
.new('ShaderNodeMath')
86 MathGreaterThan
.location
= [-1200, 600]
87 MathGreaterThan
.operation
= 'GREATER_THAN'
88 MathGreaterThan
.inputs
[1].default_value
= 0
90 NodeGroup
.links
.new(AddAddAndEmission
.inputs
[0], AddAbsorptionAndScatter
.outputs
[0])
91 NodeGroup
.links
.new(AddAddAndEmission
.inputs
[1], VolumeEmission
.outputs
[0])
92 NodeGroup
.links
.new(AddAbsorptionAndScatter
.inputs
[0], VolumeAbsorption
.outputs
[0])
93 NodeGroup
.links
.new(AddAbsorptionAndScatter
.inputs
[1], VolumeScatter
.outputs
[0])
94 NodeGroup
.links
.new(VolumeAbsorption
.inputs
[1], MathAbsorptionMultiply
.outputs
[0])
95 NodeGroup
.links
.new(VolumeScatter
.inputs
[1], MathScatterMultiply
.outputs
[0])
96 NodeGroup
.links
.new(VolumeEmission
.inputs
[1], MathEmissionMultiply
.outputs
[0])
97 NodeGroup
.links
.new(MathAbsorptionMultiply
.inputs
[0], MathGreaterThan
.outputs
[0])
98 NodeGroup
.links
.new(MathScatterMultiply
.inputs
[0], MathGreaterThan
.outputs
[0])
99 NodeGroup
.links
.new(MathEmissionMultiply
.inputs
[0], MathGreaterThan
.outputs
[0])
100 NodeGroup
.links
.new(VolumeAbsorption
.inputs
[0], MathBrightnessMultiply
.outputs
[0])
102 # Create and Link In/Out to Group Node
104 group_outputs
= NodeGroup
.nodes
.new('NodeGroupOutput')
105 group_outputs
.location
= (600, 395)
106 NodeGroup
.outputs
.new('NodeSocketShader', 'shader_out')
107 NodeGroup
.links
.new(AddAddAndEmission
.outputs
[0], group_outputs
.inputs
['shader_out'])
110 group_inputs
= NodeGroup
.nodes
.new('NodeGroupInput')
111 group_inputs
.location
= (-1500, -300)
112 NodeGroup
.inputs
.new('NodeSocketFloat', 'Density')
113 NodeGroup
.inputs
.new('NodeSocketFloat', 'Absorption Multiply')
114 NodeGroup
.inputs
.new('NodeSocketColor', 'Absorption Color')
115 NodeGroup
.inputs
.new('NodeSocketFloat', 'Scatter Multiply')
116 NodeGroup
.inputs
.new('NodeSocketColor', 'Scatter Color')
117 NodeGroup
.inputs
.new('NodeSocketFloat', 'Emission Amount')
118 NodeGroup
.inputs
.new('NodeSocketFloat', 'Cloud Brightness')
120 NodeGroup
.links
.new(group_inputs
.outputs
['Density'], MathGreaterThan
.inputs
[0])
121 NodeGroup
.links
.new(group_inputs
.outputs
['Absorption Multiply'], MathAbsorptionMultiply
.inputs
[1])
122 NodeGroup
.links
.new(group_inputs
.outputs
['Absorption Color'], MathBrightnessMultiply
.inputs
[0])
123 NodeGroup
.links
.new(group_inputs
.outputs
['Scatter Multiply'], MathScatterMultiply
.inputs
[1])
124 NodeGroup
.links
.new(group_inputs
.outputs
['Scatter Color'], VolumeScatter
.inputs
[0])
125 NodeGroup
.links
.new(group_inputs
.outputs
['Emission Amount'], MathEmissionMultiply
.inputs
[1])
126 NodeGroup
.links
.new(group_inputs
.outputs
['Cloud Brightness'], MathBrightnessMultiply
.inputs
[1])
128 if Type
== 'CloudGen_TextureProperties':
129 MathAdd
= NodeGroup
.nodes
.new('ShaderNodeMath')
130 MathAdd
.location
= [-200, 0]
131 MathAdd
.operation
= 'ADD'
132 MathDensityMultiply
= NodeGroup
.nodes
.new('ShaderNodeMath')
133 MathDensityMultiply
.location
= [-390, 0]
134 MathDensityMultiply
.operation
= 'MULTIPLY'
135 PointDensityRamp
= NodeGroup
.nodes
.new('ShaderNodeValToRGB')
136 PointDensityRamp
.location
= [-675, -250]
137 PointRamp
= PointDensityRamp
.color_ramp
138 PElements
= PointRamp
.elements
139 PElements
[0].position
= 0.418
140 PElements
[0].color
= 0, 0, 0, 1
141 PElements
[1].position
= 0.773
142 PElements
[1].color
= 1, 1, 1, 1
143 CloudRamp
= NodeGroup
.nodes
.new('ShaderNodeValToRGB')
144 CloudRamp
.location
= [-675, 0]
145 CRamp
= CloudRamp
.color_ramp
146 CElements
= CRamp
.elements
147 CElements
[0].position
= 0.527
148 CElements
[0].color
= 0, 0, 0, 1
149 CElements
[1].position
= 0.759
150 CElements
[1].color
= 1, 1, 1, 1
151 NoiseTex
= NodeGroup
.nodes
.new('ShaderNodeTexNoise')
152 NoiseTex
.location
= [-940, 0]
153 NoiseTex
.inputs
['Detail'].default_value
= 4
154 TexCoord
= NodeGroup
.nodes
.new('ShaderNodeTexCoord')
155 TexCoord
.location
= [-1250, 0]
157 NodeGroup
.links
.new(MathAdd
.inputs
[0], MathDensityMultiply
.outputs
[0])
158 NodeGroup
.links
.new(MathAdd
.inputs
[1], PointDensityRamp
.outputs
[0])
159 NodeGroup
.links
.new(MathDensityMultiply
.inputs
[0], CloudRamp
.outputs
[0])
160 NodeGroup
.links
.new(CloudRamp
.inputs
[0], NoiseTex
.outputs
[0])
161 NodeGroup
.links
.new(NoiseTex
.inputs
[0], TexCoord
.outputs
[3])
163 # Create and Link In/Out to Group Nodes
165 group_outputs
= NodeGroup
.nodes
.new('NodeGroupOutput')
166 group_outputs
.location
= (0, 0)
167 NodeGroup
.outputs
.new('NodeSocketFloat', 'Density W_CloudTex')
168 NodeGroup
.links
.new(MathAdd
.outputs
[0], group_outputs
.inputs
['Density W_CloudTex'])
171 group_inputs
= NodeGroup
.nodes
.new('NodeGroupInput')
172 group_inputs
.location
= (-1250, -300)
173 NodeGroup
.inputs
.new('NodeSocketFloat', 'Scale')
174 NodeGroup
.inputs
.new('NodeSocketFloat', 'Point Density In')
175 NodeGroup
.links
.new(group_inputs
.outputs
['Scale'], NoiseTex
.inputs
['Scale'])
176 NodeGroup
.links
.new(group_inputs
.outputs
['Point Density In'], MathDensityMultiply
.inputs
[1])
177 NodeGroup
.links
.new(group_inputs
.outputs
['Point Density In'], PointDensityRamp
.inputs
[0])
182 # This routine takes an object and deletes all of the geometry in it
183 # and adds a bounding box to it.
184 # It will add or subtract the bound box size by the variable sizeDifference.
186 def getMeshandPutinEditMode(view_layer
, object):
188 # Go into Object Mode
189 bpy
.ops
.object.mode_set(mode
='OBJECT')
192 bpy
.ops
.object.select_all(action
='DESELECT')
195 object.select_set(True)
196 view_layer
.objects
.active
= object
199 bpy
.ops
.object.mode_set(mode
='EDIT')
204 def maxAndMinVerts(view_layer
, object):
206 mesh
= getMeshandPutinEditMode(view_layer
, object)
207 verts
= mesh
.vertices
209 # Set the max and min verts to the first vertex on the list
210 maxVert
= [verts
[0].co
[0], verts
[0].co
[1], verts
[0].co
[2]]
211 minVert
= [verts
[0].co
[0], verts
[0].co
[1], verts
[0].co
[2]]
213 # Create Max and Min Vertex array for the outer corners of the box
216 if vert
.co
[0] > maxVert
[0]:
217 maxVert
[0] = vert
.co
[0]
218 if vert
.co
[1] > maxVert
[1]:
219 maxVert
[1] = vert
.co
[1]
220 if vert
.co
[2] > maxVert
[2]:
221 maxVert
[2] = vert
.co
[2]
224 if vert
.co
[0] < minVert
[0]:
225 minVert
[0] = vert
.co
[0]
226 if vert
.co
[1] < minVert
[1]:
227 minVert
[1] = vert
.co
[1]
228 if vert
.co
[2] < minVert
[2]:
229 minVert
[2] = vert
.co
[2]
231 return [maxVert
, minVert
]
234 def makeObjectIntoBoundBox(view_layer
, objects
, sizeDifference
, takeFromObject
):
235 # Let's find the max and min of the reference object,
236 # it can be the same as the destination object
237 [maxVert
, minVert
] = maxAndMinVerts(view_layer
, takeFromObject
)
240 mesh
= getMeshandPutinEditMode(view_layer
, objects
)
242 # Add the size difference to the max size of the box
243 maxVert
[0] = maxVert
[0] + sizeDifference
244 maxVert
[1] = maxVert
[1] + sizeDifference
245 maxVert
[2] = maxVert
[2] + sizeDifference
247 # subtract the size difference to the min size of the box
248 minVert
[0] = minVert
[0] - sizeDifference
249 minVert
[1] = minVert
[1] - sizeDifference
250 minVert
[2] = minVert
[2] - sizeDifference
252 # Create arrays of verts and faces to be added to the mesh
256 addVerts
.append([maxVert
[0], maxVert
[1], maxVert
[2]])
257 addVerts
.append([maxVert
[0], maxVert
[1], minVert
[2]])
258 addVerts
.append([maxVert
[0], minVert
[1], minVert
[2]])
259 addVerts
.append([maxVert
[0], minVert
[1], maxVert
[2]])
262 addVerts
.append([minVert
[0], maxVert
[1], maxVert
[2]])
263 addVerts
.append([minVert
[0], maxVert
[1], minVert
[2]])
264 addVerts
.append([minVert
[0], minVert
[1], minVert
[2]])
265 addVerts
.append([minVert
[0], minVert
[1], maxVert
[2]])
267 # Make the faces of the bounding box.
270 # Draw a box on paper and number the vertices.
271 # Use right hand rule to come up with number orders for faces on
272 # the box (with normals pointing out).
273 addFaces
.append([0, 3, 2, 1])
274 addFaces
.append([4, 5, 6, 7])
275 addFaces
.append([0, 1, 5, 4])
276 addFaces
.append([1, 2, 6, 5])
277 addFaces
.append([2, 3, 7, 6])
278 addFaces
.append([0, 4, 7, 3])
280 # Delete all geometry from the object.
281 bpy
.ops
.mesh
.select_all(action
='SELECT')
282 bpy
.ops
.mesh
.delete(type='VERT')
284 # Must be in object mode for from_pydata to work
285 bpy
.ops
.object.mode_set(mode
='OBJECT')
288 mesh
.from_pydata(addVerts
, [], addFaces
)
295 def applyScaleRotLoc(view_layer
, obj
):
297 bpy
.ops
.object.select_all(action
='DESELECT')
301 view_layer
.objects
.active
= obj
303 bpy
.ops
.object.transform_apply(location
=True, rotation
=True, scale
=True)
306 def totallyDeleteObject(obj
):
307 bpy
.data
.objects
.remove(obj
, do_unlink
=True)
310 def makeParent(parentobj
, childobj
, view_layer
):
311 applyScaleRotLoc(view_layer
, parentobj
)
312 applyScaleRotLoc(view_layer
, childobj
)
313 childobj
.parent
= parentobj
316 def addNewObject(collection
, name
, copyobj
):
317 # avoid creating not needed meshes pro forme
318 # Create a new object
319 tempme
= copyobj
.data
320 ob_new_data
= tempme
.copy()
321 ob_new
= bpy
.data
.objects
.new(name
, ob_new_data
)
322 ob_new
.scale
= copyobj
.scale
323 ob_new
.location
= copyobj
.location
325 # Link new object to the given scene and select it
326 collection
.objects
.link(ob_new
)
327 ob_new
.select_set(True)
332 def getpdensitytexture(object):
334 for mslot
in object.material_slots
:
335 # Material slot can be empty
336 mat
= getattr(mslot
, "material", None)
338 for tslot
in mat
.texture_slots
:
339 if tslot
!= 'NoneType':
341 if tex
.type == 'POINT_DENSITY':
342 if tex
.point_density
.point_source
== 'PARTICLE_SYSTEM':
346 def removeParticleSystemFromObj(view_layer
, obj
):
348 bpy
.ops
.object.select_all(action
='DESELECT')
352 view_layer
.objects
.active
= obj
354 bpy
.ops
.object.particle_system_remove()
357 bpy
.ops
.object.select_all(action
='DESELECT')
360 def convertParticlesToMesh(view_layer
, particlesobj
, destobj
, replacemesh
):
361 # Select the Destination object
362 destobj
.select_set(True)
363 view_layer
.objects
.active
= destobj
366 bpy
.ops
.object.mode_set(mode
='EDIT', toggle
=False)
368 # Delete everything in mesh if replace is true
370 bpy
.ops
.mesh
.select_all(action
='SELECT')
371 bpy
.ops
.mesh
.delete(type='VERT')
373 meshPnts
= destobj
.data
375 listCloudParticles
= particlesobj
.particles
378 for pTicle
in listCloudParticles
:
379 listMeshPnts
.append(pTicle
.location
)
381 # Must be in object mode for from_pydata to work
382 bpy
.ops
.object.mode_set(mode
='OBJECT')
384 # Add in the mesh data
385 meshPnts
.from_pydata(listMeshPnts
, [], [])
387 # Update and Validate the mesh
392 def combineObjects(view_layer
, combined
, listobjs
):
393 # scene is the current scene
394 # combined is the object we want to combine everything into
395 # listobjs is the list of objects to stick into combined
398 bpy
.ops
.object.select_all(action
='DESELECT')
400 # Select the new object.
401 combined
.select_set(True)
402 view_layer
.objects
.active
= combined
405 if len(listobjs
) > 0:
408 bpy
.ops
.object.modifier_add(type='BOOLEAN')
410 union
= combined
.modifiers
411 union
[0].name
= "AddEmUp"
413 union
[0].operation
= 'UNION'
416 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
=union
[0].name
)
419 # Returns the action we want to take
420 def getActionToDo(obj
):
422 if not obj
or obj
.type != 'MESH':
423 return 'NOT_OBJ_DO_NOTHING'
426 return 'NO_SELECTION_DO_NOTHING'
428 elif "CloudMember" in obj
:
429 if obj
["CloudMember"] is not None:
430 if obj
["CloudMember"] == "MainObj":
432 elif obj
["CloudMember"] == "CreatedObj" and len(obj
.particle_systems
) > 0:
433 return 'CLOUD_CONVERT_TO_MESH'
435 return 'CLOUD_DO_NOTHING'
437 elif obj
.type == 'MESH':
444 class VIEW3D_PT_tools_cloud(Panel
):
445 bl_space_type
= 'VIEW_3D'
446 bl_region_type
= 'TOOLS'
447 bl_category
= 'Create'
448 bl_label
= "Cloud Generator"
449 bl_context
= "objectmode"
450 bl_options
= {'DEFAULT_CLOSED'}
452 def draw(self
, context
):
453 active_obj
= context
.active_object
455 col
= layout
.column(align
=True)
457 WhatToDo
= getActionToDo(active_obj
)
459 if WhatToDo
== 'DEGENERATE':
460 col
.operator("cloud.generate_cloud", text
="DeGenerate")
462 elif WhatToDo
== 'CLOUD_CONVERT_TO_MESH':
463 col
.operator("cloud.generate_cloud", text
="Convert to Mesh")
465 elif WhatToDo
== 'NO_SELECTION_DO_NOTHING':
466 col
.label(text
="Select one or more")
467 col
.label(text
="objects to generate")
468 col
.label(text
="a cloud")
470 elif WhatToDo
== 'CLOUD_DO_NOTHING':
471 col
.label(text
="Must select")
472 col
.label(text
="bound box")
474 elif WhatToDo
== 'GENERATE':
475 col
.operator("cloud.generate_cloud", text
="Generate Cloud")
477 col
.prop(context
.scene
, "cloud_type")
478 col
.prop(context
.scene
, "cloudsmoothing")
480 col
.label(text
="Select one or more", icon
="INFO")
481 col
.label(text
="objects to generate", icon
="BLANK1")
482 col
.label(text
="a cloud", icon
="BLANK1")
485 class GenerateCloud(Operator
):
486 bl_idname
= "cloud.generate_cloud"
487 bl_label
= "Generate Cloud"
488 bl_description
= ("Create a Cloud, Undo a Cloud, or convert to "
489 "Mesh Cloud depending on selection\n"
490 "Needs an Active Mesh Object")
491 bl_options
= {"REGISTER", "UNDO"}
494 def poll(cls
, context
):
495 obj
= context
.active_object
496 return (obj
and obj
.type == 'MESH')
498 def execute(self
, context
):
499 # Prevent unsupported Execution in Local View modes
500 space_data
= bpy
.context
.space_data
502 if True in space_data
.layers_local_view
:
503 self
.report({'INFO'},
504 "Works with Global Perspective modes only. Operation Cancelled")
507 # Make variable that is the active object selected by user
508 active_object
= context
.active_object
510 # Make variable scene that is current scene
511 collection
= context
.collection
512 scene
= context
.scene
513 view_layer
= context
.view_layer
515 # Parameters the user may want to change:
516 # Number of points this number is multiplied by the volume to get
517 # the number of points the scripts will put in the volume.
519 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
521 maxNumOfPoints
= 100000
522 maxPointDensityRadius
= 1.5
524 pointDensityRadiusFactor
= 1.0
526 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
528 maxNumOfPoints
= 100000
529 maxPointDensityRadius
= 1.0
531 pointDensityRadiusFactor
= .37
535 WhatToDo
= getActionToDo(active_object
)
537 if WhatToDo
== 'DEGENERATE':
539 mainObj
= active_object
541 bpy
.ops
.object.hide_view_clear()
543 cloudMembers
= active_object
.children
545 definitionObjects
= []
547 for member
in cloudMembers
:
548 applyScaleRotLoc(view_layer
, member
)
549 if member
["CloudMember"] == "CreatedObj":
550 createdObjects
.append(member
)
552 definitionObjects
.append(member
)
554 for defObj
in definitionObjects
:
555 # Delete cloudmember data from objects
556 if "CloudMember" in defObj
:
557 del(defObj
["CloudMember"])
559 for createdObj
in createdObjects
:
560 totallyDeleteObject(createdObj
)
562 # Delete the blend_data object
563 totallyDeleteObject(mainObj
)
565 # Select all of the left over boxes so people can immediately
566 # press generate again if they want
567 for eachMember
in definitionObjects
:
568 eachMember
.display_type
= 'SOLID'
569 eachMember
.select_set(True)
570 eachMember
.hide_render
= False
572 elif WhatToDo
== 'CLOUD_CONVERT_TO_MESH':
573 cloudParticles
= active_object
.particle_systems
.active
575 bounds
= active_object
.parent
577 # Create CloudPnts for putting points in #
578 # Create a new object cloudPnts
579 cloudPnts
= addNewObject(collection
, "CloudPoints", bounds
)
580 cloudPnts
["CloudMember"] = "CreatedObj"
581 cloudPnts
.display_type
= 'WIRE'
582 cloudPnts
.hide_render
= True
584 makeParent(bounds
, cloudPnts
, view_layer
)
585 convertParticlesToMesh(view_layer
, cloudParticles
, cloudPnts
, True)
586 removeParticleSystemFromObj(view_layer
, active_object
)
588 pDensity
= getpdensitytexture(bounds
)
589 pDensity
.point_density
.point_source
= 'OBJECT'
590 pDensity
.point_density
.object = cloudPnts
592 # Let's resize the bound box to be more accurate
593 how_much_bigger
= pDensity
.point_density
.radius
594 makeObjectIntoBoundBox(view_layer
, bounds
, how_much_bigger
, cloudPnts
)
599 # Create Combined Object bounds #
600 # Make a list of all Selected objects
601 selectedObjects
= bpy
.context
.selected_objects
602 if not selectedObjects
:
603 selectedObjects
= [bpy
.context
.active_object
]
605 # Create a new object bounds
606 bounds
= addNewObject(
607 collection
, "CloudBounds",
611 bounds
.display_type
= 'BOUNDS'
612 bounds
.hide_render
= False
614 # Just add a Definition Property designating this
615 # as the blend_data object
616 bounds
["CloudMember"] = "MainObj"
618 # Since we used iteration 0 to copy with object we
619 # delete it off the list.
620 firstObject
= selectedObjects
[0]
621 del selectedObjects
[0]
623 # Apply location Rotation and Scale to all objects involved
624 applyScaleRotLoc(view_layer
, bounds
)
625 for each
in selectedObjects
:
626 applyScaleRotLoc(view_layer
, each
)
628 # Let's combine all of them together.
629 combineObjects(view_layer
, bounds
, selectedObjects
)
631 # Let's add some property info to the objects
632 for selObj
in selectedObjects
:
633 selObj
["CloudMember"] = "DefinitionObj"
634 selObj
.name
= "DefinitionObj"
635 selObj
.display_type
= 'WIRE'
636 selObj
.hide_render
= True
638 makeParent(bounds
, selObj
, view_layer
)
640 # Do the same to the 1. object since it is no longer in list.
641 firstObject
["CloudMember"] = "DefinitionObj"
642 firstObject
.name
= "DefinitionObj"
643 firstObject
.display_type
= 'WIRE'
644 firstObject
.hide_render
= True
645 makeParent(bounds
, firstObject
, view_layer
)
647 # Create Cloud for putting Cloud Mesh #
648 # Create a new object cloud.
649 cloud
= addNewObject(collection
, "CloudMesh", bounds
)
650 cloud
["CloudMember"] = "CreatedObj"
651 cloud
.display_type
= 'WIRE'
652 cloud
.hide_render
= True
654 makeParent(bounds
, cloud
, view_layer
)
656 bpy
.ops
.object.editmode_toggle()
657 bpy
.ops
.mesh
.select_all(action
='SELECT')
659 # Don't subdivide object or smooth if smoothing box not checked.
660 if scene
.cloudsmoothing
:
661 bpy
.ops
.mesh
.subdivide(number_cuts
=2, fractal
=0, smoothness
=1)
662 bpy
.ops
.mesh
.vertices_smooth(repeat
=20)
663 bpy
.ops
.mesh
.tris_convert_to_quads()
664 bpy
.ops
.mesh
.faces_shade_smooth()
665 bpy
.ops
.object.editmode_toggle()
667 # Create Particles in cloud obj #
670 scene
.frame_current
= 0
672 # Add a new particle system
673 bpy
.ops
.object.particle_system_add()
675 # Particle settings setting it up!
676 cloudParticles
= cloud
.particle_systems
.active
677 cloudParticles
.name
= "CloudParticles"
678 cloudParticles
.settings
.frame_start
= 0
679 cloudParticles
.settings
.frame_end
= 0
680 cloudParticles
.settings
.emit_from
= 'VOLUME'
681 cloudParticles
.settings
.lifetime
= scene
.frame_end
682 cloudParticles
.settings
.display_method
= 'DOT'
683 cloudParticles
.settings
.render_type
= 'NONE'
684 cloudParticles
.settings
.distribution
= 'RAND'
685 cloudParticles
.settings
.physics_type
= 'NEWTON'
686 cloudParticles
.settings
.normal_factor
= 0
688 # Gravity does not affect the particle system
689 eWeights
= cloudParticles
.settings
.effector_weights
692 # Create Volume Material #
694 bpy
.ops
.object.select_all(action
='DESELECT')
697 bounds
.select_set(True)
698 view_layer
.objects
.active
= bounds
700 # Turn bounds object into a box. Use itself as a reference
701 makeObjectIntoBoundBox(view_layer
, bounds
, 1.0, bounds
)
703 # Delete all material slots in bounds object
704 for i
in range(len(bounds
.material_slots
)):
705 bounds
.active_material_index
= i
- 1
706 bpy
.ops
.object.material_slot_remove()
709 cloudMaterial
= bpy
.data
.materials
.new("CloudMaterial")
710 bpy
.ops
.object.material_slot_add()
711 bounds
.material_slots
[0].material
= cloudMaterial
714 scene
.frame_current
= 1
716 # Set Up Material for Blender Internal
717 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
718 # Set Up the Cloud Material
719 cloudMaterial
.name
= "CloudMaterial"
720 cloudMaterial
.type = 'VOLUME'
721 mVolume
= cloudMaterial
.volume
722 mVolume
.scattering
= scattering
724 mVolume
.density_scale
= densityScale
725 mVolume
.transmission_color
= 3.0, 3.0, 3.0
726 mVolume
.step_size
= 0.1
727 mVolume
.use_light_cache
= True
728 mVolume
.cache_resolution
= 45
731 # vMaterialTextureSlots = cloudMaterial.texture_slots # UNUSED
732 cloudtex
= bpy
.data
.textures
.new("CloudTex", type='CLOUDS')
733 cloudtex
.noise_type
= 'HARD_NOISE'
734 cloudtex
.noise_scale
= 2
735 mtex
= cloudMaterial
.texture_slots
.add()
736 mtex
.texture
= cloudtex
737 mtex
.texture_coords
= 'ORCO'
738 mtex
.use_map_color_diffuse
= True
741 scene
.frame_current
= 1
743 # Add a Point Density texture
744 pDensity
= bpy
.data
.textures
.new("CloudPointDensity", 'POINT_DENSITY')
746 mtex
= cloudMaterial
.texture_slots
.add()
747 mtex
.texture
= pDensity
748 mtex
.texture_coords
= 'GLOBAL'
749 mtex
.use_map_density
= True
750 mtex
.use_rgb_to_intensity
= True
751 mtex
.texture_coords
= 'GLOBAL'
753 pDensity
.point_density
.vertex_cache_space
= 'WORLD_SPACE'
754 pDensity
.point_density
.use_turbulence
= True
755 pDensity
.point_density
.noise_basis
= 'VORONOI_F2'
756 pDensity
.point_density
.turbulence_depth
= 3
758 pDensity
.use_color_ramp
= True
759 pRamp
= pDensity
.color_ramp
760 # pRamp.use_interpolation = 'LINEAR'
761 pRampElements
= pRamp
.elements
762 # pRampElements[1].position = .9
763 # pRampElements[1].color = 0.18, 0.18, 0.18, 0.8
764 bpy
.ops
.texture
.slot_move(type='UP')
766 # Set Up Material for Cycles Engine
767 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
768 VolumePropertiesGroup
= CreateNodeGroup('CloudGen_VolumeProperties')
769 CloudTexPropertiesGroup
= CreateNodeGroup('CloudGen_TextureProperties')
771 cloudMaterial
.name
= "CloudMaterial"
773 cloudtex
= bpy
.data
.textures
.new("CloudTex", type='CLOUDS')
774 cloudtex
.noise_type
= 'HARD_NOISE'
775 cloudtex
.noise_scale
= 2
777 cloudMaterial
.use_nodes
= True
778 cloudTree
= cloudMaterial
.node_tree
779 cloudMatNodes
= cloudTree
.nodes
780 cloudMatNodes
.clear()
782 outputNode
= cloudMatNodes
.new('ShaderNodeOutputMaterial')
783 outputNode
.location
= (200, 300)
785 tranparentNode
= cloudMatNodes
.new('ShaderNodeBsdfTransparent')
786 tranparentNode
.location
= (0, 300)
788 volumeGroup
= cloudMatNodes
.new("ShaderNodeGroup")
789 volumeGroup
.node_tree
= VolumePropertiesGroup
790 volumeGroup
.location
= (0, 150)
792 cloudTexGroup
= cloudMatNodes
.new("ShaderNodeGroup")
793 cloudTexGroup
.node_tree
= CloudTexPropertiesGroup
794 cloudTexGroup
.location
= (-200, 150)
796 PointDensityNode
= cloudMatNodes
.new("ShaderNodeTexPointDensity")
797 PointDensityNode
.location
= (-400, 150)
798 PointDensityNode
.resolution
= 100
799 PointDensityNode
.space
= 'OBJECT'
800 PointDensityNode
.interpolation
= 'Linear'
801 # PointDensityNode.color_source = 'CONSTANT'
803 cloudTree
.links
.new(outputNode
.inputs
[0], tranparentNode
.outputs
[0])
804 cloudTree
.links
.new(outputNode
.inputs
[1], volumeGroup
.outputs
[0])
805 cloudTree
.links
.new(volumeGroup
.inputs
[0], cloudTexGroup
.outputs
[0])
806 cloudTree
.links
.new(cloudTexGroup
.inputs
[1], PointDensityNode
.outputs
[1])
808 # Estimate the number of particles for the size of bounds.
809 volumeBoundBox
= (bounds
.dimensions
[0] * bounds
.dimensions
[1] * bounds
.dimensions
[2])
810 numParticles
= int((2.4462 * volumeBoundBox
+ 430.4) * numOfPoints
)
811 if numParticles
> maxNumOfPoints
:
812 numParticles
= maxNumOfPoints
813 if numParticles
< 10000:
814 numParticles
= int(numParticles
+ 15 * volumeBoundBox
)
816 # Set the number of particles according to the volume of bounds
817 cloudParticles
.settings
.count
= numParticles
819 PDensityRadius
= (.00013764 * volumeBoundBox
+ .3989) * pointDensityRadiusFactor
821 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
822 pDensity
.point_density
.radius
= PDensityRadius
824 if pDensity
.point_density
.radius
> maxPointDensityRadius
:
825 pDensity
.point_density
.radius
= maxPointDensityRadius
827 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
828 PointDensityNode
.radius
= PDensityRadius
830 if PDensityRadius
> maxPointDensityRadius
:
831 PointDensityNode
.radius
= maxPointDensityRadius
834 scene
.frame_current
= 1
836 if not scene
.cloudparticles
:
837 # Create CloudPnts for putting points in #
838 # Create a new object cloudPnts
839 cloudPnts
= addNewObject(collection
, "CloudPoints", bounds
)
840 cloudPnts
["CloudMember"] = "CreatedObj"
841 cloudPnts
.display_type
= 'WIRE'
842 cloudPnts
.hide_render
= True
844 makeParent(bounds
, cloudPnts
, view_layer
)
845 convertParticlesToMesh(view_layer
, cloudParticles
, cloudPnts
, True)
848 bpy
.ops
.object.modifier_add(type='DISPLACE')
850 cldPntsModifiers
= cloudPnts
.modifiers
851 cldPntsModifiers
[0].name
= "CloudPnts"
852 cldPntsModifiers
[0].texture
= cloudtex
853 cldPntsModifiers
[0].texture_coords
= 'OBJECT'
854 cldPntsModifiers
[0].texture_coords_object
= cloud
855 cldPntsModifiers
[0].strength
= -1.4
858 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
=cldPntsModifiers
[0].name
)
860 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
861 pDensity
.point_density
.point_source
= 'OBJECT'
862 pDensity
.point_density
.object = cloudPnts
864 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
865 PointDensityNode
.point_source
= 'OBJECT'
866 PointDensityNode
.object = cloudPnts
868 removeParticleSystemFromObj(view_layer
, cloud
)
871 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
872 pDensity
.point_density
.point_source
= 'PARTICLE_SYSTEM'
873 pDensity
.point_density
.object = cloud
874 pDensity
.point_density
.particle_system
= cloudParticles
876 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
877 PointDensityNode
.point_source
= 'PARTICLE_SYSTEM'
878 PointDensityNode
.particle_system
= cloudPnts
880 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
881 if scene
.cloud_type
== '1': # Cumulous
882 mVolume
.density_scale
= 2.22
883 pDensity
.point_density
.turbulence_depth
= 10
884 pDensity
.point_density
.turbulence_strength
= 6.3
885 pDensity
.point_density
.turbulence_scale
= 2.9
886 pRampElements
[1].position
= .606
887 pDensity
.point_density
.radius
= pDensity
.point_density
.radius
+ 0.1
889 elif scene
.cloud_type
== '2': # Cirrus
890 pDensity
.point_density
.turbulence_strength
= 22
891 mVolume
.transmission_color
= 3.5, 3.5, 3.5
892 mVolume
.scattering
= 0.13
894 elif scene
.cloud_type
== '3': # Explosion
895 mVolume
.emission
= 1.42
896 mtex
.use_rgb_to_intensity
= False
897 pRampElements
[0].position
= 0.825
898 pRampElements
[0].color
= 0.119, 0.119, 0.119, 1
899 pRampElements
[1].position
= .049
900 pRampElements
[1].color
= 1.0, 1.0, 1.0, 0
901 pDensity
.point_density
.turbulence_strength
= 1.5
902 pRampElement1
= pRampElements
.new(.452)
903 pRampElement1
.color
= 0.814, 0.112, 0, 1
904 pRampElement2
= pRampElements
.new(.234)
905 pRampElement2
.color
= 0.814, 0.310, 0.002, 1
906 pRampElement3
= pRampElements
.new(0.669)
907 pRampElement3
.color
= 0.0, 0.0, 0.040, 1
909 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
911 volumeGroup
.inputs
['Absorption Multiply'].default_value
= 50
912 volumeGroup
.inputs
['Absorption Color'].default_value
= (1.0, 1.0, 1.0, 1.0)
913 volumeGroup
.inputs
['Scatter Multiply'].default_value
= 30
914 volumeGroup
.inputs
['Scatter Color'].default_value
= (.58, .58, .58, 1.0)
915 volumeGroup
.inputs
['Emission Amount'].default_value
= .1
916 volumeGroup
.inputs
['Cloud Brightness'].default_value
= 1.3
917 noiseCloudScale
= volumeBoundBox
* (-.001973) + 5.1216
918 if noiseCloudScale
< .05:
919 noiseCloudScale
= .05
920 cloudTexGroup
.inputs
['Scale'].default_value
= noiseCloudScale
922 # to cloud to view in cycles in render mode we need to hide geometry meshes...
923 firstObject
.hide
= True
927 bounds
.select_set(True)
928 view_layer
.objects
.active
= bounds
930 # Let's resize the bound box to be more accurate.
931 how_much_bigger
= PDensityRadius
+ 0.1
933 # If it's a particle cloud use cloud mesh if otherwise use point mesh
934 if not scene
.cloudparticles
:
935 makeObjectIntoBoundBox(view_layer
, bounds
, how_much_bigger
, cloudPnts
)
937 makeObjectIntoBoundBox(view_layer
, bounds
, how_much_bigger
, cloud
)
939 cloud_string
= "Cumulous" if scene
.cloud_type
== '1' else "Cirrus" if \
940 scene
.cloud_type
== '2' else "Stratus" if \
941 scene
.cloud_type
== '0' else "Explosion"
943 self
.report({'INFO'},
944 "Created the cloud of type {}".format(cloud_string
))
950 bpy
.utils
.register_module(__name__
)
952 bpy
.types
.Scene
.cloudparticles
= BoolProperty(
954 description
="Generate Cloud as Particle System",
957 bpy
.types
.Scene
.cloudsmoothing
= BoolProperty(
959 description
="Smooth Resultant Geometry From Gen Cloud Operation",
962 bpy
.types
.Scene
.cloud_type
= EnumProperty(
964 description
="Select the type of cloud to create with material settings",
965 items
=[("0", "Stratus", "Generate Stratus (foggy) Cloud"),
966 ("1", "Cumulous", "Generate Cumulous (puffy) Cloud"),
967 ("2", "Cirrus", "Generate Cirrus (wispy) Cloud"),
968 ("3", "Explosion", "Generate Explosion"),
975 bpy
.utils
.unregister_module(__name__
)
977 del bpy
.types
.Scene
.cloudparticles
978 del bpy
.types
.Scene
.cloudsmoothing
979 del bpy
.types
.Scene
.cloud_type
982 if __name__
== "__main__":