# ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_addon_info = { 'name': 'Cloud generator', 'author': 'Nick Keeline(nrk)', 'version': '0.1', 'blender': (2, 5, 3), 'location': 'Tool Shelf ', 'description': 'Creates Volumetric Clouds', 'url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \ 'Scripts/Object/Cloud_Gen', 'category': 'Object'} """ Place this file in the .blender/scripts/addons dir You have to activated the script in the "Add-Ons" tab (user preferences). The functionality can then be accessed via the Tool shelf when objects are selected Rev 0 initial release Rev 0.1 added scene to create_mesh per python api change. """ import bpy import mathutils from math import * from bpy.props import * # Deselect All bpy.ops.object.select_all(action='DESELECT') # This routine takes an object and deletes all of the geometry in it # and adds a bounding box to it. # It will add or subtract the bound box size by the variable sizeDifference. def makeObjectIntoBoundBox(object, sizeDifference): # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object object.selected = True # Go into Edit Mode bpy.ops.object.mode_set(mode='EDIT') mesh = object.data verts = mesh.verts #Set the max and min verts to the first vertex on the list maxVert = [verts[0].co[0], verts[0].co[1], verts[0].co[2]] minVert = [verts[0].co[0], verts[0].co[1], verts[0].co[2]] #Create Max and Min Vertex array for the outer corners of the box for vert in verts: #Max vertex if vert.co[0] > maxVert[0]: maxVert[0] = vert.co[0] if vert.co[1] > maxVert[1]: maxVert[1] = vert.co[1] if vert.co[2] > maxVert[2]: maxVert[2] = vert.co[2] #Min Vertex if vert.co[0] < minVert[0]: minVert[0] = vert.co[0] if vert.co[1] < minVert[1]: minVert[1] = vert.co[1] if vert.co[2] < minVert[2]: minVert[2] = vert.co[2] #Add the size difference to the max size of the box maxVert[0] = maxVert[0] + sizeDifference maxVert[1] = maxVert[1] + sizeDifference maxVert[2] = maxVert[2] + sizeDifference #subtract the size difference to the min size of the box minVert[0] = minVert[0] - sizeDifference minVert[1] = minVert[1] - sizeDifference minVert[2] = minVert[2] - sizeDifference #Create arrays of verts and faces to be added to the mesh addVerts = [] #X high loop addVerts.append([maxVert[0], maxVert[1], maxVert[2]]) addVerts.append([maxVert[0], maxVert[1], minVert[2]]) addVerts.append([maxVert[0], minVert[1], minVert[2]]) addVerts.append([maxVert[0], minVert[1], maxVert[2]]) #x low loop addVerts.append([minVert[0], maxVert[1], maxVert[2]]) addVerts.append([minVert[0], maxVert[1], minVert[2]]) addVerts.append([minVert[0], minVert[1], minVert[2]]) addVerts.append([minVert[0], minVert[1], maxVert[2]]) # Make the faces of the bounding box. addFaces = [] # Draw a box on paper and number the vertices. # Use right hand rule to come up with number orders for faces on # the box (with normals pointing out). addFaces.append([0, 3, 2, 1]) addFaces.append([4, 5, 6, 7]) addFaces.append([0, 1, 5, 4]) addFaces.append([1, 2, 6, 5]) addFaces.append([2, 3, 7, 6]) addFaces.append([0, 4, 7, 3]) # Delete all geometry from the object. bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.delete(type='ALL') # Must be in object mode for from_pydata to work bpy.ops.object.mode_set(mode='OBJECT') # Add the mesh data. mesh.from_pydata(addVerts, [], addFaces) # Update the mesh mesh.update() def applyScaleRotLoc(scene, obj): # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object obj.selected = True scene.objects.active = obj #bpy.ops.object.rotation_apply() bpy.ops.object.location_apply() bpy.ops.object.scale_apply() def makeParent(parentobj, childobj, scene): applyScaleRotLoc(scene, parentobj) applyScaleRotLoc(scene, childobj) childobj.parent = parentobj #childobj.location = childobj.location - parentobj.location def addNewObject(scene, name, copyobj): ''' Add an object and do other silly stuff. ''' # Create new mesh mesh = bpy.data.meshes.new(name) # Create a new object. ob_new = bpy.data.objects.new(name, mesh) tempme = copyobj.data ob_new.data = tempme.copy() ob_new.scale = copyobj.scale ob_new.location = copyobj.location # Link new object to the given scene and select it. scene.objects.link(ob_new) ob_new.selected = True return ob_new def combineObjects(scene, combined, listobjs): # scene is the current scene # combined is the object we want to combine everything into # listobjs is the list of objects to stick into combined # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the new object. combined.selected = True scene.objects.active = combined # Add data if (len(listobjs) > 0): for i in listobjs: # Add a modifier bpy.ops.object.modifier_add(type='BOOLEAN') union = combined.modifiers union[0].name = "AddEmUp" union[0].object = i union[0].operation = 'UNION' # Apply modifier # Can't use bpy.ops.object.modifier_apply because poll fails. combined.data = combined.create_mesh(scene, apply_modifiers=True, settings='PREVIEW') combined.modifiers.remove(union[0]) # Returns True if we want to degenerate # and False if we want to generate a new cloud. def degenerateCloud(obj): if not obj: return False if "CloudMember" in obj: if obj["CloudMember"] != None: if obj.parent: return True else: del(obj["CloudMember"]) return False class View3DPanel(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' class VIEW3D_PT_tools_cloud(View3DPanel): bl_label = "Cloud Generator" bl_context = "objectmode" def draw(self, context): active_obj = context.active_object degenerate = degenerateCloud(active_obj) if active_obj and degenerate: layout = self.layout col = layout.column(align=True) col.operator("cloud.generate_cloud", text="DeGenerate") elif active_obj and active_obj.type == 'MESH': layout = self.layout col = layout.column(align=True) col.operator("cloud.generate_cloud", text="Generate Cloud") else: layout = self.layout col = layout.column(align=True) col.label(text="Select one or more") col.label(text="objects to generate") col.label(text="a cloud.") classes = [VIEW3D_PT_tools_cloud] def register(): register = bpy.types.register for cls in classes: register(cls) def unregister(): unregister = bpy.types.unregister for cls in classes: unregister(cls) if __name__ == "__main__": register() class GenerateCloud(bpy.types.Operator): bl_idname = "cloud.generate_cloud" bl_label = "Generate Cloud" bl_description = "Create a Cloud." bl_register = True bl_undo = True def execute(self, context): # Make variable that is the current .blend file main data blocks main = context.main # Make variable that is the active object selected by user active_object = context.active_object # Make variable scene that is current scene scene = context.scene if active_object and active_object.type == 'MESH': # Parameters the user may want to change: # Number of points this number is multiplied by the volume to get # the number of points the scripts will put in the volume. numOfPoints = 35 maxNumOfPoints = 100000 scattering = 3 pointDensityRadius = 0.4 # Should we degnerate? degenerate = degenerateCloud(active_object) if degenerate: # Degenerate Cloud if active_object["CloudMember"] == "MainObj": mainObj = active_object else: mainObj = active_object.parent cloudMembers = active_object.children # Find the created objects children of main and delete. createdObjFound = False createdObjects = [] i = 0 for member in cloudMembers: applyScaleRotLoc(scene, member) if (member["CloudMember"] == "CreatedObj"): createdObjects.append(member) del cloudMembers[i] # @todo check if it wouldn't be better to remove this # in the first place (see del() in degenerateCloud) member["CloudMember"] = None i += 1 for createdObj in createdObjects: # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object and delete it. createdObj.selected = True scene.objects.active = createdObj bpy.ops.object.delete() # Delete the main object # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object and delete it. mainObj.selected = True scene.objects.active = mainObj # Delete all material slots in mainObj object for i in range(len(mainObj.material_slots)): mainObj.active_material_index = i - 1 bpy.ops.object.material_slot_remove() # Delete the Main Object bpy.ops.object.delete() # Select all of the left over boxes so people can immediately # press generate again if they want. for eachMember in cloudMembers: eachMember.max_draw_type = 'SOLID' eachMember.selected = True scene.objects.active = eachMember else: # Generate Cloud ###############Create Combined Object bounds################## # Make a list of all Selected objects. selectedObjects = bpy.context.selected_objects # Create a new object bounds if len(selectedObjects) == 0: bounds = addNewObject(scene, "CloudBounds", []) else: bounds = addNewObject(scene, "CloudBounds", selectedObjects[0]) bounds.max_draw_type = 'BOUNDS' bounds.restrict_render = False # Just add a Definition Property designating this # as the main object. bounds["CloudMember"] = "MainObj" # Since we used iteration 0 to copy with object we # delete it off the list. firstObject = selectedObjects[0] del selectedObjects[0] # Apply location Rotation and Scale to all objects involved. applyScaleRotLoc(scene, bounds) for each in selectedObjects: applyScaleRotLoc(scene, each) # Let's combine all of them together. combineObjects(scene, bounds, selectedObjects) # Let's add some property info to the objects. for selObj in selectedObjects: selObj["CloudMember"] = "DefinitioinObj" selObj.name = "DefinitioinObj" selObj.max_draw_type = 'WIRE' selObj.restrict_render = True makeParent(bounds, selObj, scene) # Do the same to the 1. object since it is no longer in list. firstObject["CloudMember"] = "DefinitioinObj" firstObject.name = "DefinitioinObj" firstObject.max_draw_type = 'WIRE' firstObject.restrict_render = True makeParent(bounds, firstObject, scene) ###############Create Cloud for putting Cloud Mesh############ # Create a new object cloud. cloud = addNewObject(scene, "CloudMesh", bounds) cloud["CloudMember"] = "CreatedObj" cloud.max_draw_type = 'WIRE' cloud.restrict_render = True makeParent(bounds, cloud, scene) bpy.ops.object.editmode_toggle() bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.subdivide(number_cuts=2, fractal=0, smoothness=1) bpy.ops.object.location_apply() bpy.ops.mesh.vertices_smooth(repeat=20) bpy.ops.mesh.tris_convert_to_quads() bpy.ops.mesh.faces_shade_smooth() bpy.ops.object.editmode_toggle() ###############Create Particles in cloud obj################## # Turn off gravity. scene.use_gravity = False # Set time to 0. scene.frame_current = 0 # Add a new particle system. bpy.ops.object.particle_system_add() #Particle settings setting it up! cloudParticles = cloud.active_particle_system cloudParticles.name = "CloudParticles" cloudParticles.settings.start = 0 cloudParticles.settings.end = 0 cloudParticles.settings.emit_from = 'VOLUME' cloudParticles.settings.draw_as = 'DOT' cloudParticles.settings.ren_as = 'NONE' cloudParticles.settings.normal_factor = 0 cloudParticles.settings.distribution = 'RAND' ####################Create Volume Material#################### # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object. bounds.selected = True scene.objects.active = bounds # Turn bounds object into a box. makeObjectIntoBoundBox(bounds, .2) # Delete all material slots in bounds object. for i in range(len(bounds.material_slots)): bounds.active_material_index = i - 1 bpy.ops.object.material_slot_remove() # Add a new material. cloudMaterial = main.materials.new("CloudMaterial") bpy.ops.object.material_slot_add() bounds.material_slots[0].material = cloudMaterial # Set Up the Cloud Material cloudMaterial.name = "CloudMaterial" cloudMaterial.type = 'VOLUME' mVolume = cloudMaterial.volume mVolume.scattering = scattering mVolume.density = 0 mVolume.density_scale = 1 mVolume.transmission_color = [3, 3, 3] mVolume.step_size = 0.1 mVolume.light_cache = True mVolume.cache_resolution = 75 # Add a texture vMaterialTextureSlots = cloudMaterial.texture_slots cloudtex = main.textures.new("CloudTex") cloudMaterial.add_texture(cloudtex, 'ORCO') cloudtex.type = 'CLOUDS' cloudtex.noise_type = 'HARD_NOISE' cloudtex.noise_size = 2 # Add a texture cloudPointDensity = main.textures.new("CloudPointDensity") cloudPointDensity.type = 'POINT_DENSITY' cloudMaterial.add_texture(cloudPointDensity, 'ORCO') pDensity = vMaterialTextureSlots[1].texture vMaterialTextureSlots[1].map_density = True vMaterialTextureSlots[1].rgb_to_intensity = True vMaterialTextureSlots[1].texture_coordinates = 'GLOBAL' pDensity.pointdensity.radius = pointDensityRadius pDensity.pointdensity.vertices_cache = 'WORLD_SPACE' pDensity.use_color_ramp = True pRamp = pDensity.color_ramp pRamp.interpolation = 'LINEAR' pRampElements = pRamp.elements #pRampElements[1].position = .9 #pRampElements[1].color = [.18,.18,.18,.8] # Estimate the number of particles for the size of bounds. numParticles = int(bounds.dimensions[0] * bounds.dimensions[1] * bounds.dimensions[2]) * numOfPoints if numParticles > maxNumOfPoints: numParticles = maxNumOfPoints print(numParticles) # Set the number of particles according to the volume # of bounds. cloudParticles.settings.amount = numParticles # Set time to 1. scene.frame_current = 1 ###############Create CloudPnts for putting points in######### # Create a new object cloudPnts cloudPnts = addNewObject(scene, "CloudPoints", bounds) cloudPnts["CloudMember"] = "CreatedObj" cloudPnts.max_draw_type = 'WIRE' cloudPnts.restrict_render = True makeParent(bounds, cloudPnts, scene) bpy.ops.object.editmode_toggle() bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.delete(type='ALL') meshPnts = cloudPnts.data listCloudParticles = cloudParticles.particles listMeshPnts = [] for pTicle in listCloudParticles: listMeshPnts.append(pTicle.location) # Must be in object mode fro from_pydata to work. bpy.ops.object.mode_set(mode='OBJECT') # Add in the mesh data. meshPnts.from_pydata(listMeshPnts, [], []) # Update the mesh. meshPnts.update() # Add a modifier. bpy.ops.object.modifier_add(type='DISPLACE') cldPntsModifiers = cloudPnts.modifiers cldPntsModifiers[0].name = "CloudPnts" cldPntsModifiers[0].texture = cloudtex cldPntsModifiers[0].texture_coordinates = 'OBJECT' cldPntsModifiers[0].texture_coordinate_object = cloud cldPntsModifiers[0].strength = -1.4 # Apply modifier # Can't use bpy.ops.object.modifier_apply because poll fails. cloudPnts.data = cloudPnts.create_mesh(scene, apply_modifiers=True, settings='PREVIEW') cloudPnts.modifiers.remove(cldPntsModifiers[0]) pDensity.pointdensity.point_source = 'OBJECT' pDensity.pointdensity.object = cloudPnts # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object. cloud.selected = True scene.objects.active = cloud bpy.ops.object.particle_system_remove() # Deselect All bpy.ops.object.select_all(action='DESELECT') # Select the object. bounds.selected = True scene.objects.active = bounds # Add a force field to the points. #cloudField = bounds.field #cloudField.type = 'TEXTURE' #cloudField.strength = 2 #cloudField.texture = cloudtex # Set time #for i in range(12): # scene.current_frame = i # scene.update() #bpy.ops.ptcache.bake_all(bake=False) #self.report({'WARNING'}, "Generating Cloud") return {'FINISHED'} bpy.types.register(GenerateCloud) if __name__ == "__main__": bpy.ops.cloud.generate_cloud()