Update for rename: constraint_orientation -> orient_type
[blender-addons.git] / object_cloud_gen.py
blobb06a1236c1198bad3c9ab0f216892c6aa552b850
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 #####
19 # <pep8 compliant>
21 bl_info = {
22 "name": "Cloud Generator",
23 "author": "Nick Keeline(nrk)",
24 "version": (1, 0, 2),
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",
30 "category": "Object",
33 import bpy
34 from bpy.props import (
35 BoolProperty,
36 EnumProperty,
38 from bpy.types import (
39 Operator,
40 Panel,
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
48 CreateGroup = True
49 for Group in bpy.data.node_groups:
50 if Group.name == Type:
51 CreateGroup = False
52 NodeGroup = Group
54 if CreateGroup is True:
55 NodeGroup = bpy.data.node_groups.new(name=Type, type="ShaderNodeTree")
56 NodeGroup.name = Type
57 NodeGroup.bl_label = Type
58 NodeGroup.nodes.clear()
60 # Create a bunch of nodes and group them based on input to the def
61 # Function type
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
103 # Outputs
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'])
109 # Inputs
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
164 # Outputs
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'])
170 # Inputs
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])
179 return NodeGroup
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')
191 # Deselect All
192 bpy.ops.object.select_all(action='DESELECT')
194 # Select the object
195 object.select_set(True)
196 view_layer.objects.active = object
198 # Go into Edit Mode
199 bpy.ops.object.mode_set(mode='EDIT')
201 return object.data
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
214 for vert in verts:
215 # Max vertex
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]
223 # Min Vertex
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)
239 # get objects mesh
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
253 addVerts = []
255 # X high loop
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]])
261 # X low loop
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.
268 addFaces = []
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')
287 # Add the mesh data.
288 mesh.from_pydata(addVerts, [], addFaces)
289 mesh.validate()
291 # Update the mesh
292 mesh.update()
295 def applyScaleRotLoc(view_layer, obj):
296 # Deselect All
297 bpy.ops.object.select_all(action='DESELECT')
299 # Select the object
300 obj.select_set(True)
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)
329 return 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)
337 if mat:
338 for tslot in mat.texture_slots:
339 if tslot != 'NoneType':
340 tex = tslot.texture
341 if tex.type == 'POINT_DENSITY':
342 if tex.point_density.point_source == 'PARTICLE_SYSTEM':
343 return tex
346 def removeParticleSystemFromObj(view_layer, obj):
347 # Deselect All
348 bpy.ops.object.select_all(action='DESELECT')
350 # Select the object
351 obj.select_set(True)
352 view_layer.objects.active = obj
354 bpy.ops.object.particle_system_remove()
356 # Deselect All
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
365 # Go to Edit Mode
366 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
368 # Delete everything in mesh if replace is true
369 if replacemesh:
370 bpy.ops.mesh.select_all(action='SELECT')
371 bpy.ops.mesh.delete(type='VERT')
373 meshPnts = destobj.data
375 listCloudParticles = particlesobj.particles
377 listMeshPnts = []
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
388 meshPnts.validate()
389 meshPnts.update()
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
397 # Deselect All
398 bpy.ops.object.select_all(action='DESELECT')
400 # Select the new object.
401 combined.select_set(True)
402 view_layer.objects.active = combined
404 # Add data
405 if len(listobjs) > 0:
406 for i in listobjs:
407 # Add a modifier
408 bpy.ops.object.modifier_add(type='BOOLEAN')
410 union = combined.modifiers
411 union[0].name = "AddEmUp"
412 union[0].object = i
413 union[0].operation = 'UNION'
415 # Apply modifier
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'
425 elif obj is None:
426 return 'NO_SELECTION_DO_NOTHING'
428 elif "CloudMember" in obj:
429 if obj["CloudMember"] is not None:
430 if obj["CloudMember"] == "MainObj":
431 return 'DEGENERATE'
432 elif obj["CloudMember"] == "CreatedObj" and len(obj.particle_systems) > 0:
433 return 'CLOUD_CONVERT_TO_MESH'
434 else:
435 return 'CLOUD_DO_NOTHING'
437 elif obj.type == 'MESH':
438 return 'GENERATE'
440 else:
441 return 'DO_NOTHING'
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
454 layout = self.layout
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")
479 else:
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"}
493 @classmethod
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")
505 return {'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':
520 numOfPoints = 1.0
521 maxNumOfPoints = 100000
522 maxPointDensityRadius = 1.5
523 scattering = 2.5
524 pointDensityRadiusFactor = 1.0
525 densityScale = 1.5
526 elif bpy.context.scene.render.engine == 'CYCLES':
527 numOfPoints = .80
528 maxNumOfPoints = 100000
529 maxPointDensityRadius = 1.0
530 scattering = 2.5
531 pointDensityRadiusFactor = .37
532 densityScale = 1.5
534 # What should we do?
535 WhatToDo = getActionToDo(active_object)
537 if WhatToDo == 'DEGENERATE':
538 # Degenerate Cloud
539 mainObj = active_object
541 bpy.ops.object.hide_view_clear()
543 cloudMembers = active_object.children
544 createdObjects = []
545 definitionObjects = []
547 for member in cloudMembers:
548 applyScaleRotLoc(view_layer, member)
549 if member["CloudMember"] == "CreatedObj":
550 createdObjects.append(member)
551 else:
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)
596 else:
597 # Generate Cloud
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",
608 selectedObjects[0]
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
637 selObj.hide = 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 #
669 # Set time to 0
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
690 eWeights.gravity = 0
692 # Create Volume Material #
693 # Deselect All
694 bpy.ops.object.select_all(action='DESELECT')
696 # Select the object.
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()
708 # Add a new material
709 cloudMaterial = bpy.data.materials.new("CloudMaterial")
710 bpy.ops.object.material_slot_add()
711 bounds.material_slots[0].material = cloudMaterial
713 # Set time
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
723 mVolume.density = 0
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
730 # Add a texture
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
740 # Set time
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"
772 # Add a texture
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
833 # Set time to 1.
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)
847 # Add a modifier.
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
857 # Apply modifier
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)
870 else:
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
924 cloud.hide = True
926 # Select the object.
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)
936 else:
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))
946 return {'FINISHED'}
949 def register():
950 bpy.utils.register_module(__name__)
952 bpy.types.Scene.cloudparticles = BoolProperty(
953 name="Particles",
954 description="Generate Cloud as Particle System",
955 default=False
957 bpy.types.Scene.cloudsmoothing = BoolProperty(
958 name="Smoothing",
959 description="Smooth Resultant Geometry From Gen Cloud Operation",
960 default=True
962 bpy.types.Scene.cloud_type = EnumProperty(
963 name="Type",
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"),
970 default='0'
974 def unregister():
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__":
983 register()