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(scene
, object):
188 # Go into Object Mode
189 bpy
.ops
.object.mode_set(mode
='OBJECT')
192 bpy
.ops
.object.select_all(action
='DESELECT')
196 scene
.objects
.active
= object
199 bpy
.ops
.object.mode_set(mode
='EDIT')
204 def maxAndMinVerts(scene
, object):
206 mesh
= getMeshandPutinEditMode(scene
, 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(scene
, 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(scene
, takeFromObject
)
240 mesh
= getMeshandPutinEditMode(scene
, 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(scene
, obj
):
297 bpy
.ops
.object.select_all(action
='DESELECT')
301 scene
.objects
.active
= obj
303 bpy
.ops
.object.transform_apply(location
=True, rotation
=True, scale
=True)
306 def totallyDeleteObject(scene
, obj
):
307 bpy
.data
.objects
.remove(obj
, do_unlink
=True)
310 def makeParent(parentobj
, childobj
, scene
):
311 applyScaleRotLoc(scene
, parentobj
)
312 applyScaleRotLoc(scene
, childobj
)
313 childobj
.parent
= parentobj
316 def addNewObject(scene
, 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 scene
.objects
.link(ob_new
)
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(scene
, obj
):
348 bpy
.ops
.object.select_all(action
='DESELECT')
352 scene
.objects
.active
= obj
354 bpy
.ops
.object.particle_system_remove()
357 bpy
.ops
.object.select_all(action
='DESELECT')
360 def convertParticlesToMesh(scene
, particlesobj
, destobj
, replacemesh
):
361 # Select the Destination object
362 destobj
.select
= True
363 scene
.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(scene
, 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
= True
402 scene
.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 scene
= context
.scene
513 # Parameters the user may want to change:
514 # Number of points this number is multiplied by the volume to get
515 # the number of points the scripts will put in the volume.
517 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
519 maxNumOfPoints
= 100000
520 maxPointDensityRadius
= 1.5
522 pointDensityRadiusFactor
= 1.0
524 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
526 maxNumOfPoints
= 100000
527 maxPointDensityRadius
= 1.0
529 pointDensityRadiusFactor
= .37
533 WhatToDo
= getActionToDo(active_object
)
535 if WhatToDo
== 'DEGENERATE':
537 mainObj
= active_object
539 bpy
.ops
.object.hide_view_clear()
541 cloudMembers
= active_object
.children
543 definitionObjects
= []
545 for member
in cloudMembers
:
546 applyScaleRotLoc(scene
, member
)
547 if member
["CloudMember"] == "CreatedObj":
548 createdObjects
.append(member
)
550 definitionObjects
.append(member
)
552 for defObj
in definitionObjects
:
553 # Delete cloudmember data from objects
554 if "CloudMember" in defObj
:
555 del(defObj
["CloudMember"])
557 for createdObj
in createdObjects
:
558 totallyDeleteObject(scene
, createdObj
)
560 # Delete the blend_data object
561 totallyDeleteObject(scene
, mainObj
)
563 # Select all of the left over boxes so people can immediately
564 # press generate again if they want
565 for eachMember
in definitionObjects
:
566 eachMember
.draw_type
= 'SOLID'
567 eachMember
.select
= True
568 eachMember
.hide_render
= False
570 elif WhatToDo
== 'CLOUD_CONVERT_TO_MESH':
571 cloudParticles
= active_object
.particle_systems
.active
573 bounds
= active_object
.parent
575 # Create CloudPnts for putting points in #
576 # Create a new object cloudPnts
577 cloudPnts
= addNewObject(scene
, "CloudPoints", bounds
)
578 cloudPnts
["CloudMember"] = "CreatedObj"
579 cloudPnts
.draw_type
= 'WIRE'
580 cloudPnts
.hide_render
= True
582 makeParent(bounds
, cloudPnts
, scene
)
583 convertParticlesToMesh(scene
, cloudParticles
, cloudPnts
, True)
584 removeParticleSystemFromObj(scene
, active_object
)
586 pDensity
= getpdensitytexture(bounds
)
587 pDensity
.point_density
.point_source
= 'OBJECT'
588 pDensity
.point_density
.object = cloudPnts
590 # Let's resize the bound box to be more accurate
591 how_much_bigger
= pDensity
.point_density
.radius
592 makeObjectIntoBoundBox(scene
, bounds
, how_much_bigger
, cloudPnts
)
597 # Create Combined Object bounds #
598 # Make a list of all Selected objects
599 selectedObjects
= bpy
.context
.selected_objects
600 if not selectedObjects
:
601 selectedObjects
= [bpy
.context
.active_object
]
603 # Create a new object bounds
604 bounds
= addNewObject(
605 scene
, "CloudBounds",
609 bounds
.draw_type
= 'BOUNDS'
610 bounds
.hide_render
= False
612 # Just add a Definition Property designating this
613 # as the blend_data object
614 bounds
["CloudMember"] = "MainObj"
616 # Since we used iteration 0 to copy with object we
617 # delete it off the list.
618 firstObject
= selectedObjects
[0]
619 del selectedObjects
[0]
621 # Apply location Rotation and Scale to all objects involved
622 applyScaleRotLoc(scene
, bounds
)
623 for each
in selectedObjects
:
624 applyScaleRotLoc(scene
, each
)
626 # Let's combine all of them together.
627 combineObjects(scene
, bounds
, selectedObjects
)
629 # Let's add some property info to the objects
630 for selObj
in selectedObjects
:
631 selObj
["CloudMember"] = "DefinitionObj"
632 selObj
.name
= "DefinitionObj"
633 selObj
.draw_type
= 'WIRE'
634 selObj
.hide_render
= True
636 makeParent(bounds
, selObj
, scene
)
638 # Do the same to the 1. object since it is no longer in list.
639 firstObject
["CloudMember"] = "DefinitionObj"
640 firstObject
.name
= "DefinitionObj"
641 firstObject
.draw_type
= 'WIRE'
642 firstObject
.hide_render
= True
643 makeParent(bounds
, firstObject
, scene
)
645 # Create Cloud for putting Cloud Mesh #
646 # Create a new object cloud.
647 cloud
= addNewObject(scene
, "CloudMesh", bounds
)
648 cloud
["CloudMember"] = "CreatedObj"
649 cloud
.draw_type
= 'WIRE'
650 cloud
.hide_render
= True
652 makeParent(bounds
, cloud
, scene
)
654 bpy
.ops
.object.editmode_toggle()
655 bpy
.ops
.mesh
.select_all(action
='SELECT')
657 # Don't subdivide object or smooth if smoothing box not checked.
658 if scene
.cloudsmoothing
:
659 bpy
.ops
.mesh
.subdivide(number_cuts
=2, fractal
=0, smoothness
=1)
660 bpy
.ops
.mesh
.vertices_smooth(repeat
=20)
661 bpy
.ops
.mesh
.tris_convert_to_quads()
662 bpy
.ops
.mesh
.faces_shade_smooth()
663 bpy
.ops
.object.editmode_toggle()
665 # Create Particles in cloud obj #
668 scene
.frame_current
= 0
670 # Add a new particle system
671 bpy
.ops
.object.particle_system_add()
673 # Particle settings setting it up!
674 cloudParticles
= cloud
.particle_systems
.active
675 cloudParticles
.name
= "CloudParticles"
676 cloudParticles
.settings
.frame_start
= 0
677 cloudParticles
.settings
.frame_end
= 0
678 cloudParticles
.settings
.emit_from
= 'VOLUME'
679 cloudParticles
.settings
.lifetime
= scene
.frame_end
680 cloudParticles
.settings
.draw_method
= 'DOT'
681 cloudParticles
.settings
.render_type
= 'NONE'
682 cloudParticles
.settings
.distribution
= 'RAND'
683 cloudParticles
.settings
.physics_type
= 'NEWTON'
684 cloudParticles
.settings
.normal_factor
= 0
686 # Gravity does not affect the particle system
687 eWeights
= cloudParticles
.settings
.effector_weights
690 # Create Volume Material #
692 bpy
.ops
.object.select_all(action
='DESELECT')
696 scene
.objects
.active
= bounds
698 # Turn bounds object into a box. Use itself as a reference
699 makeObjectIntoBoundBox(scene
, bounds
, 1.0, bounds
)
701 # Delete all material slots in bounds object
702 for i
in range(len(bounds
.material_slots
)):
703 bounds
.active_material_index
= i
- 1
704 bpy
.ops
.object.material_slot_remove()
707 cloudMaterial
= bpy
.data
.materials
.new("CloudMaterial")
708 bpy
.ops
.object.material_slot_add()
709 bounds
.material_slots
[0].material
= cloudMaterial
712 scene
.frame_current
= 1
714 # Set Up Material for Blender Internal
715 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
716 # Set Up the Cloud Material
717 cloudMaterial
.name
= "CloudMaterial"
718 cloudMaterial
.type = 'VOLUME'
719 mVolume
= cloudMaterial
.volume
720 mVolume
.scattering
= scattering
722 mVolume
.density_scale
= densityScale
723 mVolume
.transmission_color
= 3.0, 3.0, 3.0
724 mVolume
.step_size
= 0.1
725 mVolume
.use_light_cache
= True
726 mVolume
.cache_resolution
= 45
729 # vMaterialTextureSlots = cloudMaterial.texture_slots # UNUSED
730 cloudtex
= bpy
.data
.textures
.new("CloudTex", type='CLOUDS')
731 cloudtex
.noise_type
= 'HARD_NOISE'
732 cloudtex
.noise_scale
= 2
733 mtex
= cloudMaterial
.texture_slots
.add()
734 mtex
.texture
= cloudtex
735 mtex
.texture_coords
= 'ORCO'
736 mtex
.use_map_color_diffuse
= True
739 scene
.frame_current
= 1
741 # Add a Point Density texture
742 pDensity
= bpy
.data
.textures
.new("CloudPointDensity", 'POINT_DENSITY')
744 mtex
= cloudMaterial
.texture_slots
.add()
745 mtex
.texture
= pDensity
746 mtex
.texture_coords
= 'GLOBAL'
747 mtex
.use_map_density
= True
748 mtex
.use_rgb_to_intensity
= True
749 mtex
.texture_coords
= 'GLOBAL'
751 pDensity
.point_density
.vertex_cache_space
= 'WORLD_SPACE'
752 pDensity
.point_density
.use_turbulence
= True
753 pDensity
.point_density
.noise_basis
= 'VORONOI_F2'
754 pDensity
.point_density
.turbulence_depth
= 3
756 pDensity
.use_color_ramp
= True
757 pRamp
= pDensity
.color_ramp
758 # pRamp.use_interpolation = 'LINEAR'
759 pRampElements
= pRamp
.elements
760 # pRampElements[1].position = .9
761 # pRampElements[1].color = 0.18, 0.18, 0.18, 0.8
762 bpy
.ops
.texture
.slot_move(type='UP')
764 # Set Up Material for Cycles Engine
765 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
766 VolumePropertiesGroup
= CreateNodeGroup('CloudGen_VolumeProperties')
767 CloudTexPropertiesGroup
= CreateNodeGroup('CloudGen_TextureProperties')
769 cloudMaterial
.name
= "CloudMaterial"
771 cloudtex
= bpy
.data
.textures
.new("CloudTex", type='CLOUDS')
772 cloudtex
.noise_type
= 'HARD_NOISE'
773 cloudtex
.noise_scale
= 2
775 cloudMaterial
.use_nodes
= True
776 cloudTree
= cloudMaterial
.node_tree
777 cloudMatNodes
= cloudTree
.nodes
778 cloudMatNodes
.clear()
780 outputNode
= cloudMatNodes
.new('ShaderNodeOutputMaterial')
781 outputNode
.location
= (200, 300)
783 tranparentNode
= cloudMatNodes
.new('ShaderNodeBsdfTransparent')
784 tranparentNode
.location
= (0, 300)
786 volumeGroup
= cloudMatNodes
.new("ShaderNodeGroup")
787 volumeGroup
.node_tree
= VolumePropertiesGroup
788 volumeGroup
.location
= (0, 150)
790 cloudTexGroup
= cloudMatNodes
.new("ShaderNodeGroup")
791 cloudTexGroup
.node_tree
= CloudTexPropertiesGroup
792 cloudTexGroup
.location
= (-200, 150)
794 PointDensityNode
= cloudMatNodes
.new("ShaderNodeTexPointDensity")
795 PointDensityNode
.location
= (-400, 150)
796 PointDensityNode
.resolution
= 100
797 PointDensityNode
.space
= 'OBJECT'
798 PointDensityNode
.interpolation
= 'Linear'
799 # PointDensityNode.color_source = 'CONSTANT'
801 cloudTree
.links
.new(outputNode
.inputs
[0], tranparentNode
.outputs
[0])
802 cloudTree
.links
.new(outputNode
.inputs
[1], volumeGroup
.outputs
[0])
803 cloudTree
.links
.new(volumeGroup
.inputs
[0], cloudTexGroup
.outputs
[0])
804 cloudTree
.links
.new(cloudTexGroup
.inputs
[1], PointDensityNode
.outputs
[1])
806 # Estimate the number of particles for the size of bounds.
807 volumeBoundBox
= (bounds
.dimensions
[0] * bounds
.dimensions
[1] * bounds
.dimensions
[2])
808 numParticles
= int((2.4462 * volumeBoundBox
+ 430.4) * numOfPoints
)
809 if numParticles
> maxNumOfPoints
:
810 numParticles
= maxNumOfPoints
811 if numParticles
< 10000:
812 numParticles
= int(numParticles
+ 15 * volumeBoundBox
)
814 # Set the number of particles according to the volume of bounds
815 cloudParticles
.settings
.count
= numParticles
817 PDensityRadius
= (.00013764 * volumeBoundBox
+ .3989) * pointDensityRadiusFactor
819 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
820 pDensity
.point_density
.radius
= PDensityRadius
822 if pDensity
.point_density
.radius
> maxPointDensityRadius
:
823 pDensity
.point_density
.radius
= maxPointDensityRadius
825 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
826 PointDensityNode
.radius
= PDensityRadius
828 if PDensityRadius
> maxPointDensityRadius
:
829 PointDensityNode
.radius
= maxPointDensityRadius
832 scene
.frame_current
= 1
834 if not scene
.cloudparticles
:
835 # Create CloudPnts for putting points in #
836 # Create a new object cloudPnts
837 cloudPnts
= addNewObject(scene
, "CloudPoints", bounds
)
838 cloudPnts
["CloudMember"] = "CreatedObj"
839 cloudPnts
.draw_type
= 'WIRE'
840 cloudPnts
.hide_render
= True
842 makeParent(bounds
, cloudPnts
, scene
)
843 convertParticlesToMesh(scene
, cloudParticles
, cloudPnts
, True)
846 bpy
.ops
.object.modifier_add(type='DISPLACE')
848 cldPntsModifiers
= cloudPnts
.modifiers
849 cldPntsModifiers
[0].name
= "CloudPnts"
850 cldPntsModifiers
[0].texture
= cloudtex
851 cldPntsModifiers
[0].texture_coords
= 'OBJECT'
852 cldPntsModifiers
[0].texture_coords_object
= cloud
853 cldPntsModifiers
[0].strength
= -1.4
856 bpy
.ops
.object.modifier_apply(apply_as
='DATA', modifier
=cldPntsModifiers
[0].name
)
858 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
859 pDensity
.point_density
.point_source
= 'OBJECT'
860 pDensity
.point_density
.object = cloudPnts
862 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
863 PointDensityNode
.point_source
= 'OBJECT'
864 PointDensityNode
.object = cloudPnts
866 removeParticleSystemFromObj(scene
, cloud
)
869 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
870 pDensity
.point_density
.point_source
= 'PARTICLE_SYSTEM'
871 pDensity
.point_density
.object = cloud
872 pDensity
.point_density
.particle_system
= cloudParticles
874 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
875 PointDensityNode
.point_source
= 'PARTICLE_SYSTEM'
876 PointDensityNode
.particle_system
= cloudPnts
878 if bpy
.context
.scene
.render
.engine
== 'BLENDER_RENDER':
879 if scene
.cloud_type
== '1': # Cumulous
880 mVolume
.density_scale
= 2.22
881 pDensity
.point_density
.turbulence_depth
= 10
882 pDensity
.point_density
.turbulence_strength
= 6.3
883 pDensity
.point_density
.turbulence_scale
= 2.9
884 pRampElements
[1].position
= .606
885 pDensity
.point_density
.radius
= pDensity
.point_density
.radius
+ 0.1
887 elif scene
.cloud_type
== '2': # Cirrus
888 pDensity
.point_density
.turbulence_strength
= 22
889 mVolume
.transmission_color
= 3.5, 3.5, 3.5
890 mVolume
.scattering
= 0.13
892 elif scene
.cloud_type
== '3': # Explosion
893 mVolume
.emission
= 1.42
894 mtex
.use_rgb_to_intensity
= False
895 pRampElements
[0].position
= 0.825
896 pRampElements
[0].color
= 0.119, 0.119, 0.119, 1
897 pRampElements
[1].position
= .049
898 pRampElements
[1].color
= 1.0, 1.0, 1.0, 0
899 pDensity
.point_density
.turbulence_strength
= 1.5
900 pRampElement1
= pRampElements
.new(.452)
901 pRampElement1
.color
= 0.814, 0.112, 0, 1
902 pRampElement2
= pRampElements
.new(.234)
903 pRampElement2
.color
= 0.814, 0.310, 0.002, 1
904 pRampElement3
= pRampElements
.new(0.669)
905 pRampElement3
.color
= 0.0, 0.0, 0.040, 1
907 elif bpy
.context
.scene
.render
.engine
== 'CYCLES':
909 volumeGroup
.inputs
['Absorption Multiply'].default_value
= 50
910 volumeGroup
.inputs
['Absorption Color'].default_value
= (1.0, 1.0, 1.0, 1.0)
911 volumeGroup
.inputs
['Scatter Multiply'].default_value
= 30
912 volumeGroup
.inputs
['Scatter Color'].default_value
= (.58, .58, .58, 1.0)
913 volumeGroup
.inputs
['Emission Amount'].default_value
= .1
914 volumeGroup
.inputs
['Cloud Brightness'].default_value
= 1.3
915 noiseCloudScale
= volumeBoundBox
* (-.001973) + 5.1216
916 if noiseCloudScale
< .05:
917 noiseCloudScale
= .05
918 cloudTexGroup
.inputs
['Scale'].default_value
= noiseCloudScale
920 # to cloud to view in cycles in render mode we need to hide geometry meshes...
921 firstObject
.hide
= True
926 scene
.objects
.active
= bounds
928 # Let's resize the bound box to be more accurate.
929 how_much_bigger
= PDensityRadius
+ 0.1
931 # If it's a particle cloud use cloud mesh if otherwise use point mesh
932 if not scene
.cloudparticles
:
933 makeObjectIntoBoundBox(scene
, bounds
, how_much_bigger
, cloudPnts
)
935 makeObjectIntoBoundBox(scene
, bounds
, how_much_bigger
, cloud
)
937 cloud_string
= "Cumulous" if scene
.cloud_type
== '1' else "Cirrus" if \
938 scene
.cloud_type
== '2' else "Stratus" if \
939 scene
.cloud_type
== '0' else "Explosion"
941 self
.report({'INFO'},
942 "Created the cloud of type {}".format(cloud_string
))
948 bpy
.utils
.register_module(__name__
)
950 bpy
.types
.Scene
.cloudparticles
= BoolProperty(
952 description
="Generate Cloud as Particle System",
955 bpy
.types
.Scene
.cloudsmoothing
= BoolProperty(
957 description
="Smooth Resultant Geometry From Gen Cloud Operation",
960 bpy
.types
.Scene
.cloud_type
= EnumProperty(
962 description
="Select the type of cloud to create with material settings",
963 items
=[("0", "Stratus", "Generate Stratus (foggy) Cloud"),
964 ("1", "Cumulous", "Generate Cumulous (puffy) Cloud"),
965 ("2", "Cirrus", "Generate Cirrus (wispy) Cloud"),
966 ("3", "Explosion", "Generate Explosion"),
973 bpy
.utils
.unregister_module(__name__
)
975 del bpy
.types
.Scene
.cloudparticles
976 del bpy
.types
.Scene
.cloudsmoothing
977 del bpy
.types
.Scene
.cloud_type
980 if __name__
== "__main__":