# Blender rock creation tool # # Based on BlenderGuru's asteroid tutorial and personal experimentation. # Tutorial: http://www.blenderguru.com/how-to-make-a-realistic-asteroid/ # Update with another tutorial shared by "rusted" of BlenderArtists: # Tutorial: http://saschahenrichs.blogspot.com/2010/03/3dsmax-environment-modeling-1.html # # Uses the NumPy Gaussian random number generator to generate a # a rock within a given range and give some randomness to the displacement # texture values. NumPy's gaussian generator was chosen as, based on # profiling I performed, it runs in about half the time as the built in # Python gaussian equivalent. I would like to shift the script to use the # NumPy beta distribution as it ran in about half the time as the NumPy # gaussian once the skew calculations are added. # # Set lower and upper bounds to the same for no randomness. # # Tasks: # Generate meshes with random scaling between given values. # - Allow for a skewed distribution # *** Completed on 4/17/2011 *** # - Create a set of meshes that can be used # Give the user the ability to set the subsurf level (detail level) # *** Completed on 4/29/2011 *** # - Set subsurf modifiers to default at view:3, render:3. # *** Completed on 4/17/2011 *** # - Set crease values to allow for hard edges on first subsurf. # *** COmpleted on 4/29/2011 *** # Be able to generate and add a texture to the displacement modifiers. # - Generate three displacement modifiers. # - The first only uses a Musgrave for initial intentations. # - Set a randomness for the type and values of the displacement texture. # - Allow the user to set a value for the range of displacement. # -> Modification: have user set "roughness" and "roughness range". # *** Compleded on 4/23/2011 *** # Set material settings and assign material textures # - Mossiness of the rocks. # - Color of the rocks. # - Wetness/shinyness of the rock. # - For all the user provides a mean value for a skewed distribution. # Add some presets (mesh) to make it easier to use # - Examples: river rock, asteroid, quaried rock, etc # # Paul "BrikBot" Marshall # Created: April 17, 2011 # Last Modified: May 1, 2011 # Homepage (blog): http://post.darkarsenic.com/ # //blog.darkarsenic.com/ # # Coded in IDLE, tested in Blender 2.57. Requires NumPy. # Search for "@todo" to quickly find sections that need work. # # Remeber - # Functional code comes before fast code. Once it works, then worry about # making it faster/more efficient. # # ##### BEGIN GPL LICENSE BLOCK ##### # # The Blender Rock Creation tool is for rapid generation of mesh rocks in Blender. # Copyright (C) 2011 Paul Marshall # # 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 3 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, see . # # ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Rock Generator", "author": "Paul Marshall (brikbot)", "version": (0,3), "blender": (2, 5, 7), "api": 36344, "location": "View3D > Add > Rocks", "description": "Adds a mesh rock to the Add Mesh menu", "warning": "Under development", "wiki_url": "", "tracker_url": "", "category": "Add Mesh"} import bpy, math, time, random from mathutils import Vector from bpy.props import BoolProperty, IntProperty from bpy.props import FloatProperty, FloatVectorProperty # Creates a new mesh: # # param: verts - Vector of vertices for the mesh. # edges - Edges for the mesh. Can be "[]". # faces - Face tuples corresponding to vertices. # name - Name of the mesh. def create_mesh_object(context, verts, edges, faces, name): scene = context.scene obj_act = scene.objects.active # Create new mesh mesh = bpy.data.meshes.new(name) # Make a mesh from a list of verts/edges/faces. mesh.from_pydata(verts, edges, faces) # Update mesh geometry after adding stuff. mesh.update() import add_object_utils return add_object_utils.object_data_add(context, mesh, operator=None) # Build a list of used "Texture.XXX" for tracking which textures will be added # for the displacement textures. # # bpy.ops.texture.new() starts with "Texture", "Texture.001", "Texture.002" # and so forth unless the Texture.XXX values is already used. Then it finds # the next availible Texture.XXX value. # # @todo # I would like to find a way to not have to do this each time the parameters are # changed as it might take a long time if there are already a lot of textures. # As it is I am learning Python and the Blender API on the fly here, so I am not # familar enough with either to know if it is possible. def usedTextures(): usedTexList = [] for i in bpy.data.textures: if i.name.startswith("Texture"): usedTexList.append(i.name) elif i.name.startswith("S") or i.name.startswith("s"): # Textures are stored in a lexigraphically ordered list. # If the textures starts with the letter "s", then there # are no more textures that need to be logged. break return usedTexList # Set the values for a texture from parameters. # # param: texture - bpy.data.texture to modify. def randomizeTexture(texture, level): noises = ['BLENDER_ORIGINAL', 'ORIGINAL_PERLIN', 'IMPROVED_PERLIN', 'VORONOI_F1', 'VORONOI_F2', 'VORONOI_F3', 'VORONOI_F4'] if texture.type == 'CLOUDS': if random.randint(0, 1) == 0: texture.noise_type = 'SOFT_NOISE' else: texture.noise_type = 'HARD_NOISE' tempInt = random.randint(0, 6) texture.noise_basis = noises[tempInt] texture.noise_depth = 6 elif texture.type == 'MUSGRAVE': musgraveType = ['MULTIFRACTAL', 'RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL', 'FBM', 'HETERO_TERRAIN'] tempInt = random.randint(0, 4) texture.musgrave_type = musgraveType[tempInt] elif texture.type == 'DISTORTED_NOISE': tempInt = random.randint(0, 6) texture.noise_distortion = noises[tempInt] tempInt = random.randint(0, 6) texture.noise_basis = noises[tempInt] elif texture.type == 'STUCCI': stucciTypes = ['PLASTIC', 'WALL_IN', 'WALL_OUT'] if random.randint(0, 1) == 0: texture.noise_type = 'SOFT_NOISE' else: texture.noise_type = 'HARD_NOISE' tempInt = random.randint(0, 2) texture.stucci_type = stucciTypes[tempInt] tempInt = random.randint(0, 6) texture.noise_basis = noises[tempInt] elif texture.type == 'VORONOI': metrics = ['DISTANCE', 'DISTANCE_SQUARED', 'MANHATTAN', 'CHEBYCHEV', 'MINKOVSKY_HALF', 'MINKOVSKY_FOUR', 'MINKOVSKY'] # Settings for first dispalcement level: if level == 0: tempInt = random.randint(0, 1) texture.distance_metric = metrics[tempInt] texture.contrast = 0.5 texture.intensity = 0.7 else: tempInt = random.randint(0, 6) texture.distance_metric = metrics[tempInt] # Texture scaling base on its level in the displacmement modifier stack: if level == 0: texture.noise_scale = random.gauss(0.625, 1/24) elif level == 2: texture.noise_scale = 0.15 return # Generates an object based on one of several different mesh types. # All meshes have exactly eight vertices, and may be built from either # tri's or quads. # # param: muX - mean X offset value # sigmaX - X offset standard deviation # scaleX - X upper and lower bounds # upperSkewX - Is the distribution upperskewed? # muY - mean Y offset value # sigmaY - Y offset standard deviation # scaleY - Y upper and lower bounds # upperSkewY - Is the distribution upperskewed? # muZ - mean Z offset value # sigmaZ - Z offset standard deviation # scaleZ - Z upper and lower bounds # upperSkewY - Is the distribution upperskewed? # i - base number on the end of the object name # shift - Addition to the base number for multiple runs. # scaleDisplace - Scale the displacement maps # # return: name - the built name of the object def generateObject(context, muX, sigmaX, scaleX, upperSkewX, muY, sigmaY, scaleY, upperSkewY, muZ, sigmaZ, scaleZ, upperSkewZ, base, shift, scaleDisplace): x = [] y = [] z = [] shape = random.randint(0,3) # Cube # Use parameters to re-scale cube: # Reversed if/for nesting. Should be a little faster. if shape == 0: for j in range(8): if sigmaX == 0: x.append(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(scaleY[0] / 2) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif shape == 1: for j in range(8): if j in (0, 1, 3, 4): if sigmaX == 0: x.append(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(scaleY[0] / 2) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j in (2, 5): if sigmaX == 0: x.append(0) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 4) if sigmaY == 0: y.append(scaleY[0] / 2) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j in (6, 7): if sigmaX == 0: x.append(0) else: x.append(skewedGauss(0, sigmaX, scaleX, upperSkewX) / 4) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 4) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif shape == 2: for j in range(8): if j in (0, 2, 5, 7): if sigmaX == 0: x.append(scaleX[0] / 4) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 4) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 4) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 4) elif j in (1, 3, 4, 6): if sigmaX == 0: x.append(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(scaleY[0] / 2) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif shape == 3: for j in range(8): if j > 0: if sigmaX == 0: x.append(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(scaleY[0] / 2) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) else: if sigmaX == 0: x.append(0) else: x.append(skewedGauss(0, sigmaX, scaleX, upperSkewX) / 8) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 8) if sigmaZ == 0: z.append(0) else: z.append(skewedGauss(0, sigmaZ, scaleZ, upperSkewZ) / 8) # This is for scaling the displacement textures. # Scale the vertices so that their average is equal to 1 if scaleDisplace: averageX = sum(x) / len(x) x /= averageX averageY = sum(y) / len(y) y /= averageY averageZ = sum(z) / len(z) z /= averageZ # Build vertex and face arrays: if shape == 1: v = [(-x[0],-y[0],-z[0]),(x[1],-y[1],-z[1]),(x[2],-y[2],z[2]), (-x[3],y[3],-z[3]),(x[4],y[4],-z[4]),(x[5],y[5],z[5]), (x[6],y[6],z[6]),(x[7],y[7],-z[7])] verts = [Vector(i) for i in v] faces = [[0,1,2],[0,1,7],[3,0,7],[3,4,7],[1,4,7],[3,4,5],[1,2,6], [1,4,6],[4,5,6],[0,2,6],[0,3,6],[3,5,6]] elif shape == 2: v = [(-x[0],y[0],-z[0]),(x[1],-y[1],-z[1]),(x[2],y[2],-z[2]), (-x[3],y[3],-z[3]),(-x[4],-y[4],z[4]),(x[5],y[5],z[5]), (x[6],y[6],z[6]),(-x[7],y[7],z[7])] verts = [Vector(i) for i in v] faces = [[0,1,2],[0,2,3],[0,3,7],[0,7,4],[1,4,5],[0,1,4],[5,1,2], [5,2,6],[3,2,6],[3,6,7],[5,4,7],[5,6,7]] elif shape == 3: v = [(x[0],y[0],z[0]),(x[1],-y[1],-z[1]),(x[2],y[2],-z[2]), (-x[3],y[3],-z[3]),(x[4],-y[4],z[4]),(x[5],y[5],z[5]), (-x[6],y[6],z[6]),(-x[7],-y[7],z[7])] verts = [Vector(i) for i in v] faces = [[0,1,2],[0,2,3],[0,3,6],[0,6,7],[0,7,4],[0,4,1],[5,4,1,2], [5,6,3,2],[5,4,7,6]] else: v = [(-x[0],-y[0],-z[0]),(-x[1],y[1],-z[1]),(-x[2],-y[2],z[2]), (-x[3],y[3],z[3]),(x[4],-y[4],-z[4]),(x[5],y[5],-z[5]), (x[6],-y[6],z[6]),(x[7],y[7],z[7])] verts = [Vector(i) for i in v] faces = [[0,1,3,2],[0,1,5,4],[0,4,6,2],[7,5,4,6],[7,3,2,6],[7,5,1,3]] name = "Rock." + str(base + shift).zfill(3) # Make object: obj = create_mesh_object(context, verts, [], faces, name) if scaleDisplace: bpy.data.objects[name].scale = Vector((averageX, averageY, averageZ)) # Apply creasing: if shape == 0: for i in range(12): bpy.data.meshes[name].edges[i].crease = random.gauss(0.125, 0.375 / 3) elif shape == 1: for i in [0,2]: bpy.data.meshes[name].edges[i].crease = 0.5 - random.betavariate(1, 17) for i in [6,9,11,12]: bpy.data.meshes[name].edges[i].crease = 0.25 - random.betavariate(1, 50) for i in [5,7,15,16]: bpy.data.meshes[name].edges[i].crease = 0.125 - random.betavariate(1, 75) elif shape == 2: for i in range(18): bpy.data.meshes[name].edges[i].crease = 0.125 - random.betavariate(1, 75) elif shape == 3: for i in [0,1,6,10,13]: bpy.data.meshes[name].edges[i].crease = 0.25 - random.betavariate(1, 50) bpy.data.meshes[name].edges[8].crease = 0.5 - random.betavariate(1, 17) return name # Gerenates a list of the names of the new materials that are going to # be added. # # param newRocksIndex - Number of new materials def generateMaterialsList(numOfMats): usedMats = [] newMats = [] lastUsedMat = 1 # Cataloge the used materials: for i in range(len(bpy.data.materials)): if bpy.data.materials[i].name.rsplit('.')[0] == 'Material': usedMats.append(bpy.data.materials[i].name) # Create the new materials: for j in range(numOfMats): if len(usedMats) > 0: if len(newMats) == 0: if 'Material' < usedMats[0]: newMats.append('Material') continue elif 'Material' == usedMats[0]: del(usedMats[0]) if len(usedMats) > 0: while 'Material.' + str(lastUsedMat).zfill(3) <= usedMats[0]: lastUsedMat = int(usedMats[0].rsplit('.', 1)[1]) + 1 del(usedMats[0]) if len(usedMats) == 0: # There are no more materials, so add whatever is next. break newMats.append('Material.' + str(lastUsedMat).zfill(3)) lastUsedMat += 1 else: if len(newMats) == 0: newMats.append('Material') else: newMats.append('Material.' + str(lastUsedMat).zfill(3)) lastUsedMat += 1 return newMats # Randomizes the given material given base values. # # param: Material to randomize def randomizeMaterial(material, dif_int, rough, spec_int, spec_hard, spec_IOR): skew = False stddev = 0.0 # Diffuse settings: material.diffuse_shader = 'OREN_NAYAR' if 0.5 > dif_int: stddev = dif_int / 3 skew = False else: stddev = (1 - dif_int) / 3 skew = True material.diffuse_intensity = skewedGauss(dif_int, stddev, (0.0, 1.0), skew) if 1.57 > rough: stddev = rough / 3 skew = False else: stddev = (3.14 - rough) / 3 skew = True material.roughness = skewedGauss(rough, stddev, (0.0, 3.14), skew) # Specular settings: material.specular_shader = 'BLINN' if 0.5 > spec_int: variance = spec_int / 3 skew = False else: variance = (1 - spec_int) / 3 skew = True material.specular_intensity = skewedGauss(spec_int, stddev, (0.0, 1.0), skew) if 256 > spec_hard: variance = (spec_hard - 1) / 3 skew = False else: variance = (511 - spec_hard) / 3 skew = True material.specular_hardness = int(round(skewedGauss(spec_hard, stddev, (1.0, 511.0), skew))) if 5.0 > spec_IOR: variance = spec_IOR / 3 skew = False else: variance = (10.0 - spec_IOR) / 3 skew = True material.specular_ior = skewedGauss(spec_IOR, stddev, (0.0, 10.0), skew) return # Artifically skews a normal (gaussian) distribution. This will not create a continuous # distribution curve but instead acts as a piecewise finction. # This linearly scales the output on one side to fit the bounds. # # Example output historgrams: # # Upper skewed: Lower skewed: # | ▄ | _ # | █ | █ # | █_ | █ # | ██ | _█ # | _██ | ██ # | _▄███_ | ██ _ # | ▄██████ | ▄██▄█▄_ # | _█▄███████ | ███████ # | _██████████_ | ████████▄▄█_ _ # | _▄▄████████████ | ████████████▄█_ # | _▄_ ▄███████████████▄_ | _▄███████████████▄▄_ # ------------------------- ----------------------- # |mu |mu # Historgrams were generated in R (http://www.r-project.org/) based on the # calculations below and manually duplicated here. # # param: mu - mu is the mean of the distribution. # sigma - sigma is the standard deviation of the distribution. # bounds - bounds[0] is the lower bound and bounds[1] is the upper bound. # upperSkewed - if the distribution is upper skewed. # return: out - Rondomly generated value from the skewed distribution. # # @todo: Because NumPy's random value generators are faster when called a bunch of times # at once, maybe allow this to generate and return multiple values at once? def skewedGauss(mu, sigma, bounds, upperSkewed=True): raw = random.gauss(mu, sigma) # Quicker to check an extra condition than do unnecessary math. . . . if raw < mu and not upperSkewed: out = ((mu - bounds[0]) / (3 * sigma)) * raw + ((mu * (bounds[0] - (mu - 3 * sigma))) / (3 * sigma)) elif raw > mu and upperSkewed: out = ((mu - bounds[1]) / (3 * -sigma)) * raw + ((mu * (bounds[1] - (mu + 3 * sigma))) / (3 * -sigma)) else: out = raw return out # @todo create a def for generating an alpha and beta for a beta distribution given # a mu, sigma, and an upper and lower bound. This proved faster in profiling in # addition to providing a much better distribution curve provided multiple # iterations happen within this function; otherwise it was slower. # This might be a scratch because of the bounds placed on mu and sigma: # # For alpha > 1 and beta > 1: # mu^2 - mu^3 mu^3 - mu^2 + mu # ----------- < sigma < ---------------- # 1 + mu 2 - mu # ##def generateBeta(mu, sigma, scale, repitions=1): ## results = [] ## ## return results # Creates rock objects: def generateRocks(context, scaleX, skewX, scaleY, skewY, scaleZ, skewZ, detail, display_detail, deform, rough, smooth_fac, smooth_it, mat_bright, mat_rough, mat_spec, mat_hard, numOfRocks=1, userSeed=1.0, scaleDisplace=False, randomSeed=True): newTex = [] newMat = [] sigmaX = 0 sigmaY = 0 sigmaZ = 0 upperSkewX = False upperSkewY = False upperSkewZ = False shift = 0 lastUsedTex = 1 vertexScaling = [] # Seed the random Gaussian value generator: if randomSeed: random.seed(int(time.time())) else: random.seed(userSeed) # Check for existing rocks: # @todo This is breaking after the first run. Possibly move it to a def and call # from the look if "i == 0" to make sure it is correct? for i in bpy.data.objects: if i.name.startswith('Rock.'): shift = int(i.name.rsplit('.', 1)[1]) # Calculate the number of materials to use. # If less than 10 rocks are being generated, generate one material per rock. # If more than 10 rocks are being generated, generate ceil[(1/9)n + (80/9)] materials. # -> 100 rocks will result in 20 materials # -> 1000 rocks will result in 120 materials. if numOfRocks < 10: numOfMats = numOfRocks else: numOfMats = math.ceil((1/9) * numOfRocks + (80/9)) newMat = generateMaterialsList(numOfMats) # @todo Set general material settings: # Material roughness actual max = 3.14. Needs scaling. mat_rough *= 0.628 mat_IOR = 1.875 * math.pow(mat_spec, 2) + 7.125 * mat_spec + 1 for i in newMat: bpy.ops.material.new() randomizeMaterial(bpy.data.materials[i], mat_bright, mat_rough, mat_spec, mat_hard, mat_IOR) # These values need to be really small to look good. # So the user does not have to use such ridiculously small values: deform /= 10 rough /= 100 # todo: edit below to allow for skewing the distribution # *** todo completed 4/22/2011 *** # *** Code now generating "int not scriptable error" in Blender *** # # Calculate mu and sigma for a Gaussian distributed random number generation: # If the lower and upper bounds are the same, skip the math. # # sigma is the standard deviation of the values. The 95% interval is three # standard deviations, which is what we want most generated values to fall # in. Since it might be skewed we are going to use half the difference # betwee the mean and the furthest bound and scale the other side down # post-number generation. if scaleX[0] != scaleX[1]: skewX = (skewX + 1) / 2 muX = scaleX[0] + ((scaleX[1] - scaleX[0]) * skewX) if skewX < 0.5: sigmaX = (scaleX[1] - muX) / 3 else: sigmaX = (muX - scaleX[0]) / 3 upperSkewX = True else: muX = scaleX[0] if scaleY[0] != scaleY[1]: skewY = (skewY + 1) / 2 muY = scaleY[0] + ((scaleY[1] - scaleY[0]) * skewY) if skewY < 0.5: sigmaY = (scaleY[1] - muY) / 3 else: sigmaY = (muY - scaleY[0]) / 3 upperSkewY = True else: muY = scaleY[0] if scaleZ[0] != scaleZ[1]: skewZ = (skewZ + 1) / 2 muZ = scaleZ[0] + ((scaleZ[1] - scaleZ[0]) * skewZ) if skewZ < 0.5: sigmaZ = (scaleZ[1] - muZ) / 3 else: sigmaZ = (muZ - scaleZ[0]) / 3 upperSkewZ = True else: muZ = scaleZ for i in range(numOfRocks): # todo: enable different random values for each (x,y,z) corrdinate for # each vertex. This will add additional randomness to the shape of the # generated rocks. # *** todo completed 4/19/2011 *** # *** Code is notably slower at high rock counts *** name = generateObject(context, muX, sigmaX, scaleX, upperSkewX, muY, sigmaY, scaleY, upperSkewY, muZ, sigmaZ, scaleZ, upperSkewZ, i, shift, scaleDisplace) # todo Map what the two new textures will be: # This is not working. It works on paper so . . . ??? # *** todo completed on 4/23/2011 *** # *** todo re-added as the first rock is getting 'Texture.001' twice. *** # *** todo completed on 4/25/2011 *** # The script will generate textures for the rocks but it needs to know what # textures to modify. Build a list of the textures to ignore: usedTex = usedTextures() # There are three displacement modifiers, so there will be three textures: for j in range(3): if len(usedTex) > 0: if len(newTex) == 0: if 'Texture' < usedTex[0]: newTex.append('Texture') continue elif 'Texture' == usedTex[0]: del(usedTex[0]) if len(usedTex) > 0: while 'Texture.' + str(lastUsedTex).zfill(3) <= usedTex[0]: lastUsedTex = int(usedTex[0].rsplit('.', 1)[1]) + 1 del(usedTex[0]) if len(usedTex) == 0: # There are no more used textures, so add whatever is next. break newTex.append('Texture.' + str(lastUsedTex).zfill(3)) lastUsedTex += 1 else: if len(newTex) == 0: newTex.append('Texture') newTex.append('Texture.001') newTex.append('Texture.002') # Three textures have been mapped. Don't repeat the loop. break else: newTex.append('Texture.' + str(lastUsedTex).zfill(3)) lastUsedTex += 1 # Create the three new textures: bpy.ops.texture.new() bpy.ops.texture.new() bpy.ops.texture.new() # Add modifiers: bpy.ops.object.modifier_add(type='SUBSURF') bpy.ops.object.modifier_add(type='SUBSURF') bpy.ops.object.modifier_add(type='DISPLACE') bpy.ops.object.modifier_add(type='DISPLACE') bpy.ops.object.modifier_add(type='DISPLACE') # If smoothing is enabled, allow a little randomness into the smoothing factor. # Then add the smoothing modifier. if smooth_fac > 0.0 and smooth_it > 0: bpy.ops.object.modifier_add(type='SMOOTH') bpy.data.objects[name].modifiers[5].factor = random.gauss(smooth_fac, math.pow(smooth_fac, 0.5) / 12) bpy.data.objects[name].modifiers[5].iterations = smooth_it # Set subsurf modifier parameters: bpy.data.objects[name].modifiers[0].levels = display_detail bpy.data.objects[name].modifiers[0].render_levels = detail bpy.data.objects[name].modifiers[1].levels = display_detail bpy.data.objects[name].modifiers[1].render_levels = detail # todo Set displacement modifier parameters: # *** todo completed on 4/23/2011 *** # *** toned down the variance on 4/26/2011 *** # *** added third modifier on 4/28/2011 *** bpy.data.objects[name].modifiers[2].texture = bpy.data.textures[newTex[len(newTex) - 3]] bpy.data.objects[name].modifiers[2].strength = random.gauss(deform, (1/3) * deform) bpy.data.objects[name].modifiers[2].mid_level = 0 bpy.data.objects[name].modifiers[3].texture = bpy.data.textures[newTex[len(newTex) - 2]] bpy.data.objects[name].modifiers[3].strength = random.gauss(rough * 2, (1/3) * rough) bpy.data.objects[name].modifiers[4].texture = bpy.data.textures[newTex[len(newTex) - 1]] bpy.data.objects[name].modifiers[4].strength = random.gauss(rough, (1/3) * rough) # @todo Set displacement texture parameters: # Voronoi has been removed from being an option for the fine detail texture. textureTypes = ['CLOUDS', 'STUCCI', 'DISTORTED_NOISE', 'MUSGRAVE', 'VORONOI'] # The first texture is to give a more ranodm base shape appearance: bpy.data.textures[newTex[len(newTex) - 3]].type = textureTypes[int(round(random.betavariate(10, 2) * 2 + 2))] randomizeTexture(bpy.data.textures[newTex[len(newTex) - 3]], 0) bpy.data.textures[newTex[len(newTex) - 2]].type = textureTypes[int(round(random.weibullvariate(1, 1)[0] / 2.125))] randomizeTexture(bpy.data.textures[newTex[len(newTex) - 2]], 1) bpy.data.textures[newTex[len(newTex) - 1]].type = textureTypes[int(round(random.weibullvariate(1, 1)[0] / 2.125))] randomizeTexture(bpy.data.textures[newTex[len(newTex) - 1]], 2) # Set mesh to be smooth and fix the normals: bpy.ops.object.shade_smooth() bpy.ops.object.editmode_toggle() bpy.ops.mesh.normals_make_consistent() bpy.ops.object.editmode_toggle() bpy.ops.object.material_slot_add() bpy.data.objects[name].material_slots[0].material = bpy.data.materials[newMat[random.randint(0, numOfMats - 1)]] return # Much of the code below is more-or-less imitation of other addons and as such # I have left it undocumented. class rocks(bpy.types.Operator): '''Add rock meshes''' bl_idname = "mesh.rocks" bl_label = "Add Rock" bl_options = {'REGISTER', 'UNDO'} bl_description = "Add rocks" num_of_rocks = IntProperty(name = "Number of rocks", description = "Number of rocks to generate. WARNING: Slow at high values!", min = 1, max = 1048576, default = 1) scale_X = FloatVectorProperty(name = "X scale", description = "X axis scaling range.", min = 0.0, max = 256.0, step = 1, default = [1.0, 1.0], size = 2) skew_X = FloatProperty(name = "X skew", description = "X Skew ratio. 0.5 is no skew.", min = -1.0, max = 1.0, default = 0.0) scale_Y = FloatVectorProperty(name = "Y scale", description = "Y axis scaling range.", min = 0.0, max = 256.0, step = 1, default = [1.0, 1.0], size = 2) skew_Y = FloatProperty(name = "Y skew", description = "Y Skew ratio. 0.5 is no skew.", min = -1.0, max = 1.0, default = 0.0) scale_Z = FloatVectorProperty(name = "Z scale", description = "Z axis scaling range.", min = 0.0, max = 256.0, step = 1, default = [1.0, 1.0], size = 2) skew_Z = FloatProperty(name = "Z skew", description = "Z Skew ratio. 0.5 is no skew.", min = -1.0, max = 1.0, default = 0.0) use_scale_dis = BoolProperty(name = "Scale displace textures", description = "Scale displacement textures with dimensions. May cause streched textures.", default = False) # @todo Possible to title this section "Physical Properties:"? deform = FloatProperty(name = "Deformation", description = "Rock deformation", min = 0.0, max = 256.0, default = 5.0) rough = FloatProperty(name = "Roughness", description = "Rock roughness", min = 0.0, max = 1024.0, default = 2.5) detail = IntProperty(name = "Detail level", description = "Detail level. WARNING: Slow at high values!", min = 1, max = 1024, default = 3) display_detail = IntProperty(name = "Display Detail", description = "Display detail. Use a lower value for high numbers of rocks.", min = 1, max = 128, default = 3) smooth_fac = FloatProperty(name = "Smooth Factor", description = "Smoothing factor. A value of 0 disables.", min = 0.0, max = 128.0, default = 0.0) smooth_it = IntProperty(name = "Smooth Iterations", description = "Smoothing iterations. A value of 0 disables.", min = 0, max = 128, default = 0) # @todo Add material properties mat_bright = FloatProperty(name = "Brightness", description = "Material brightness", min = 0.0, max = 1.0, default = 0.85) mat_rough = FloatProperty(name = "Roughness", description = "Material roughness", min = 0.0, max = 5.0, default = 1.0) mat_spec = FloatProperty(name = "Shine", description = "Material specularity strength", min = 0.0, max = 1.0, default = 0.2) mat_hard = IntProperty(name = "Hardness", description = "Material hardness", min = 0, max = 511, default = 50) ## mat_var = FloatProperty(name = "Varience", ## description = "Varience of generated materials", ## min = 0.0, max = 100.0, default = 5) use_random_seed = BoolProperty(name = "Use a random seed", description = "Create a seed based on time. Causes user seed to be ignored.", default = True) user_seed = IntProperty(name = "User seed", description = "Use a specific seed for the generator.", min = 0, max = 1048576, default = 1) def draw(self, context): layout = self.layout box = layout.box() box.prop(self, 'num_of_rocks') box = layout.box() box.prop(self, 'scale_X') box.prop(self, 'skew_X') box.prop(self, 'scale_Y') box.prop(self, 'skew_Y') box.prop(self, 'scale_Z') box.prop(self, 'skew_Z') box.prop(self, 'use_scale_dis') box = layout.box() box.prop(self, 'deform') box.prop(self, 'rough') box.prop(self, 'detail') box.prop(self, 'display_detail') box.prop(self, 'smooth_fac') box.prop(self, 'smooth_it') box = layout.box() box.prop(self, 'mat_bright') box.prop(self, 'mat_rough') box.prop(self, 'mat_spec') box.prop(self, 'mat_hard') ## box.prop(self, 'mat_var') box = layout.box() box.prop(self, 'user_seed') box.prop(self, 'use_random_seed') def execute(self, context): # todo Add deform, deform_Var, rough, and rough_Var: # *** todo completed 4/23/2011 *** # *** Eliminated "deform_Var" and "rough_Var" so the script is not # as complex to use. May add in again as advanced features. *** generateRocks(context, self.scale_X, self.skew_X, self.scale_Y, self.skew_Y, self.scale_Z, self.skew_Z, self.detail, self.display_detail, self.deform, self.rough, self.smooth_fac, self.smooth_it, self.mat_bright, self.mat_rough, self.mat_spec, self.mat_hard, ## self.mat_var, self.num_of_rocks, self.user_seed, self.use_scale_dis, self.use_random_seed) return {'FINISHED'} def menu_func_rocks(self, context): self.layout.operator(rocks.bl_idname, text="Rocks", icon = "PLUGIN") def register(): bpy.utils.register_module(__name__) bpy.types.INFO_MT_mesh_add.append(menu_func_rocks) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_mesh_add.remove(menu_func_rocks) if __name__ == "__main__": register()