Fix T52833: OBJ triangulate doesn't match viewport
[blender-addons.git] / object_cloud_gen.py
blob008763fc5a46435ce7fb10be46f7e42c58148831
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(scene, 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 = True
196 scene.objects.active = object
198 # Go into Edit Mode
199 bpy.ops.object.mode_set(mode='EDIT')
201 return object.data
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
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(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)
239 # get objects mesh
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
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(scene, obj):
296 # Deselect All
297 bpy.ops.object.select_all(action='DESELECT')
299 # Select the object
300 obj.select = True
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)
327 ob_new.select = 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(scene, obj):
347 # Deselect All
348 bpy.ops.object.select_all(action='DESELECT')
350 # Select the object
351 obj.select = True
352 scene.objects.active = obj
354 bpy.ops.object.particle_system_remove()
356 # Deselect All
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
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(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
397 # Deselect All
398 bpy.ops.object.select_all(action='DESELECT')
400 # Select the new object.
401 combined.select = True
402 scene.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 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':
518 numOfPoints = 1.0
519 maxNumOfPoints = 100000
520 maxPointDensityRadius = 1.5
521 scattering = 2.5
522 pointDensityRadiusFactor = 1.0
523 densityScale = 1.5
524 elif bpy.context.scene.render.engine == 'CYCLES':
525 numOfPoints = .80
526 maxNumOfPoints = 100000
527 maxPointDensityRadius = 1.0
528 scattering = 2.5
529 pointDensityRadiusFactor = .37
530 densityScale = 1.5
532 # What should we do?
533 WhatToDo = getActionToDo(active_object)
535 if WhatToDo == 'DEGENERATE':
536 # Degenerate Cloud
537 mainObj = active_object
539 bpy.ops.object.hide_view_clear()
541 cloudMembers = active_object.children
542 createdObjects = []
543 definitionObjects = []
545 for member in cloudMembers:
546 applyScaleRotLoc(scene, member)
547 if member["CloudMember"] == "CreatedObj":
548 createdObjects.append(member)
549 else:
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)
594 else:
595 # Generate Cloud
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",
606 selectedObjects[0]
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
635 selObj.hide = 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 #
667 # Set time to 0
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
688 eWeights.gravity = 0
690 # Create Volume Material #
691 # Deselect All
692 bpy.ops.object.select_all(action='DESELECT')
694 # Select the object.
695 bounds.select = True
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()
706 # Add a new material
707 cloudMaterial = bpy.data.materials.new("CloudMaterial")
708 bpy.ops.object.material_slot_add()
709 bounds.material_slots[0].material = cloudMaterial
711 # Set time
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
721 mVolume.density = 0
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
728 # Add a texture
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
738 # Set time
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"
770 # Add a texture
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
831 # Set time to 1.
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)
845 # Add a modifier.
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
855 # Apply modifier
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)
868 else:
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
922 cloud.hide = True
924 # Select the object.
925 bounds.select = 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)
934 else:
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))
944 return {'FINISHED'}
947 def register():
948 bpy.utils.register_module(__name__)
950 bpy.types.Scene.cloudparticles = BoolProperty(
951 name="Particles",
952 description="Generate Cloud as Particle System",
953 default=False
955 bpy.types.Scene.cloudsmoothing = BoolProperty(
956 name="Smoothing",
957 description="Smooth Resultant Geometry From Gen Cloud Operation",
958 default=True
960 bpy.types.Scene.cloud_type = EnumProperty(
961 name="Type",
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"),
968 default='0'
972 def unregister():
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__":
981 register()