# 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. # *** Completed 5/17/2011 *** # - Generate three displacement modifiers. # - The first only uses a Musgrave for initial intentations. # *** Now generating four displacement modifiers *** # *** Completed on 5/17/2011 *** # - Set a randomness for the type and values of the displacement texture. # *** Completed 5/9/2011 *** # - 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 # *** Completed 6/9/2011 *** # - Mossiness of the rocks. # *** Completed 6/9/2011 *** # - Color of the rocks. # *** Completed 5/16/2011 *** # - Wetness/shinyness of the rock. # *** Completed 5/6/2011 *** # - For all the user provides a mean value for a skewed distribution. # *** Removed to lessen usage complexity *** # Add some presets (mesh) to make it easier to use # - Examples: river rock, asteroid, quaried rock, etc # *** Completed 7/12/2011 *** # # Code Optimization: # Remove all "bpy.ops" operations with "bpy.data" base operations. # Remove material/texture cataloging with building a list of # returned values from bpy.data.*.new() operations. # *** Completed on 9/6/2011 *** # Search for places where list comprehensions can be used. # Look for alternate methods # - Possible alternate and more efficient data structures # - Possible alternate algorithms may realize greater performance # - Look again at multi-processing. Without bpy.ops is might # be viable. # # Future tasks: # Multi-thread the script # *** Will not be implemented. Multi-processing is adding to much # overhead to realize a performance increase *** # - Learn basic multi-threading in Python (multiprocessing) # - Break material generation into separate threads (processes) # - Break mesh generation into separate threads (processes) # - Move name generation, texture ID generation, etc to process first # - Roll version to 2.0 on completion # # Paul "BrikBot" Marshall # Created: April 17, 2011 # Last Modified: November 17, 2011 # Homepage (blog): http://post.darkarsenic.com/ # //blog.darkarsenic.com/ # Thanks to Meta-Androco, RickyBlender, Ace Dragon, and PKHG for ideas # and testing. # # Coded in IDLE, tested in Blender 2.59. NumPy Recommended. # 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. # 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 ##### # import bpy import time from . import ( settings, utils ) from .utils import skewedGauss from .randomize_texture import randomizeTexture from bpy_extras import object_utils from mathutils import ( Vector ) from bpy.props import ( BoolProperty, IntProperty, FloatProperty, FloatVectorProperty, EnumProperty ) # This try block allows for the script to psudo-intelligently select the # appropriate random to use. If Numpy's random is present it will use that. # If Numpy's random is not present, it will through a "module not found" # exception and instead use the slower built-in random that Python has. try: from numpy.random import random_integers as randint from numpy.random import normal as gauss from numpy.random import ( seed, weibull) # print("Rock Generator: Numpy found.") numpy = True except: from random import ( randint, gauss, seed) from random import weibullvariate as weibull print("Rock Generator: Numpy not found. Using Python's random.") numpy = False # Global variables: LASTROCK = 0 # 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 createMeshObject(context, verts, edges, faces, name): # 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) # Set mesh to use auto smoothing: mesh.use_auto_smooth = True # Update mesh geometry after adding stuff. mesh.update() return object_utils.object_data_add(context, mesh, operator=None) # 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? # base - 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, scale_fac): x = [] y = [] z = [] shape = randint(0, 11) # 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) elif shape == 4: for j in range(10): if j in [0, 9]: if sigmaX == 0: x.append(0) else: x.append(skewedGauss(0, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j in [1, 2, 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 [5, 7]: if sigmaX == 0: x.append(0) else: x.append(skewedGauss(0, sigmaX, scaleX, upperSkewX) / 3) if sigmaY == 0: y.append(scaleY[0] / 3) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) / 3) if sigmaZ == 0: z.append(0) else: z.append(skewedGauss(0, sigmaZ, scaleZ, upperSkewZ) / 6) elif j in [6, 8]: if sigmaX == 0: x.append(scaleX[0] / 3) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 3) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 3) if sigmaZ == 0: z.append(0) else: z.append(skewedGauss(0, sigmaZ, scaleZ, upperSkewZ) / 6) elif shape == 5: for j in range(10): if j == 0: 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(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j in [1, 2]: if sigmaX == 0: x.append(scaleZ[0] * .125) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) * 0.125) if sigmaY == 0: y.append(scaleZ[0] * 0.2165) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) * 0.2165) if sigmaZ == 0: z.append(0) else: z.append(skewedGauss(0, sigmaZ, scaleZ, upperSkewZ) / 4) elif j == 3: 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(0) else: z.append(skewedGauss(0, sigmaZ, scaleZ, upperSkewZ) / 4) elif j in [4, 6]: if sigmaX == 0: x.append(scaleX[0] * 0.25) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) * 0.25) if sigmaY == 0: y.append(scaleY[0] * 0.433) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) * 0.433) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j == 5: 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) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j in [7, 9]: if sigmaX == 0: x.append(scaleX[0] * 0.10825) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) * 0.10825) if sigmaY == 0: y.append(scaleY[0] * 0.2165) else: y.append(skewedGauss(muY, sigmaY, scaleY, upperSkewY) * 0.2165) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif j == 8: if sigmaX == 0: x.append(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) 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 == 6: for j in range(7): 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(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif shape == 7: for j in range(10): if j in [1, 3, 4, 5, 8, 9]: 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(scaleX[0] / 2) else: x.append(skewedGauss(muX, sigmaX, scaleX, upperSkewX) / 2) if sigmaY == 0: y.append(0) else: y.append(skewedGauss(0, sigmaY, scaleY, upperSkewY) / 2) if sigmaZ == 0: z.append(scaleZ[0] / 2) else: z.append(skewedGauss(muZ, sigmaZ, scaleZ, upperSkewZ) / 2) elif shape == 8: for j in range(7): 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 == 9: 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 == 10: for j in range(7): 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 == 11: for j in range(7): 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) # This is for scaling the displacement textures. # Scale the vertices so that their average is equal to 1 * scale factor. if scaleDisplace: averageX = (sum(x) / len(x)) * scale_fac[0] for i in range(len(x)): x[i] /= averageX averageY = (sum(y) / len(y)) * scale_fac[1] for i in range(len(y)): y[i] /= averageY averageZ = (sum(z) / len(z)) * scale_fac[2] for i in range(len(z)): z[i] /= averageZ # Build vertex and face arrays: if shape == 1: verts = [(-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])] 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: verts = [(-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])] 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: verts = [(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])] 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]] elif shape == 4: verts = [(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]), (-x[8], y[8], -z[8]), (x[9], y[9], -z[9])] faces = [[0, 1, 6], [0, 6, 2], [0, 2, 7], [0, 7, 3], [0, 3, 8], [0, 8, 4], [0, 4, 5], [0, 5, 1], [1, 9, 2], [2, 9, 3], [3, 9, 4], [4, 9, 1], [1, 6, 2], [2, 7, 3], [3, 8, 4], [4, 5, 1]] elif shape == 5: verts = [(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]), (-x[8], y[8], -z[8]), (-x[9], -y[9], -z[9])] faces = [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 4, 5], [1, 5, 2], [2, 5, 6], [2, 6, 7], [2, 7, 3], [3, 7, 8], [3, 8, 9], [3, 9, 1], [1, 9, 4], [4, 5, 9], [5, 6, 7], [7, 8, 9], [9, 5, 7]] elif shape == 6: verts = [(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])] faces = [[0, 1, 2], [0, 2, 3, 4], [0, 1, 6, 5], [0, 4, 5], [1, 2, 3, 6], [3, 4, 5, 6]] elif shape == 7: verts = [(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]), (-x[8], -y[8], -z[8]), (-x[9], -y[9], z[9])] faces = [[0, 1, 2], [0, 2, 3], [0, 5, 6], [0, 6, 9], [0, 1, 8, 9], [0, 3, 4, 5], [1, 2, 7, 8], [2, 3, 4, 7], [4, 5, 6, 7], [6, 7, 8, 9]] elif shape == 8: verts = [(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])] faces = [[0, 2, 1], [0, 1, 4], [0, 4, 5], [0, 5, 6], [0, 6, 3, 2], [2, 1, 4, 3], [3, 6, 5, 4]] elif shape == 9: verts = [(-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])] faces = [[0, 1, 6, 2], [1, 5, 7, 6], [5, 4, 3, 7], [4, 0, 2, 3], [0, 1, 5, 4], [3, 2, 6, 7]] elif shape == 10: verts = [(-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])] faces = [[0, 2, 3], [0, 3, 6], [0, 1, 5, 6], [2, 3, 4], [0, 1, 2], [1, 2, 4, 5], [3, 4, 5, 6]] elif shape == 11: verts = [(-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])] faces = [[0, 2, 3], [0, 3, 6], [0, 1, 5, 6], [2, 3, 4], [5, 6, 3], [1, 5, 3, 4], [0, 1, 4, 2]] else: verts = [(-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])] 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) name = "rock" # Make object: obj = createMeshObject(context, verts, [], faces, name) if scaleDisplace: # bpy.data.objects[name].scale = Vector((averageX, averageY, averageZ)) obj.scale = Vector((averageX, averageY, averageZ)) # For a slight speed bump / Readability: # mesh = bpy.data.meshes[name] mesh = obj.data # Apply creasing: if shape == 0: for i in range(12): # todo: "0.375 / 3"? WTF? That = 0.125. . . . # *** Completed 7/15/2011: Changed second one *** mesh.edges[i].crease = gauss(0.125, 0.125) elif shape == 1: for i in [0, 2]: mesh.edges[i].crease = gauss(0.5, 0.125) for i in [6, 9, 11, 12]: mesh.edges[i].crease = gauss(0.25, 0.05) for i in [5, 7, 15, 16]: mesh.edges[i].crease = gauss(0.125, 0.025) elif shape == 2: for i in range(18): mesh.edges[i].crease = gauss(0.125, 0.025) elif shape == 3: for i in [0, 1, 6, 10, 13]: mesh.edges[i].crease = gauss(0.25, 0.05) mesh.edges[8].crease = gauss(0.5, 0.125) elif shape == 4: for i in [5, 6, 7, 10, 14, 16, 19, 21]: mesh.edges[i].crease = gauss(0.5, 0.125) elif shape == 7: for i in range(18): if i in [0, 1, 2, 3, 6, 7, 8, 9, 13, 16]: mesh.edges[i].crease = gauss(0.5, 0.125) elif i in [11, 17]: mesh.edges[i].crease = gauss(0.25, 0.05) else: mesh.edges[i].crease = gauss(0.125, 0.025) elif shape == 8: for i in range(12): if i in [0, 3, 8, 9, 10]: mesh.edges[i].crease = gauss(0.5, 0.125) elif i == 11: mesh.edges[i].crease = gauss(0.25, 0.05) else: mesh.edges[i].crease = gauss(0.125, 0.025) elif shape == 9: for i in range(12): if i in [0, 3, 4, 11]: mesh.edges[i].crease = gauss(0.5, 0.125) else: mesh.edges[i].crease = gauss(0.25, 0.05) elif shape == 10: for i in range(12): if i in [0, 2, 3, 4, 8, 11]: mesh.edges[i].crease = gauss(0.5, 0.125) elif i in [1, 5, 7]: mesh.edges[i].crease = gauss(0.25, 0.05) else: mesh.edges[i].crease = gauss(0.125, 0.025) elif shape == 11: for i in range(11): if i in [1, 2, 3, 4, 8, 11]: mesh.edges[i].crease = gauss(0.25, 0.05) else: mesh.edges[i].crease = gauss(0.125, 0.025) return obj # Creates rock objects: def generateRocks(context, scaleX, skewX, scaleY, skewY, scaleZ, skewZ, scale_fac, detail, display_detail, deform, rough, smooth_fac, smooth_it, numOfRocks=1, userSeed=1.0, scaleDisplace=False, randomSeed=True): global LASTROCK sigmaX = 0 sigmaY = 0 sigmaZ = 0 upperSkewX = False upperSkewY = False upperSkewZ = False shift = 0 # vertexScaling = [] # Seed the random Gaussian value generator: if randomSeed: seed(int(time.time())) else: seed(userSeed) # 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 # Verify that the min really is the min: if scaleX[1] < scaleX[0]: scaleX[0], scaleX[1] = scaleX[1], scaleX[0] if scaleY[1] < scaleY[0]: scaleY[0], scaleY[1] = scaleY[1], scaleY[0] if scaleZ[1] < scaleZ[0]: scaleZ[0], scaleZ[1] = scaleZ[1], scaleZ[0] # 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, rock = generateObject( context, muX, sigmaX, scaleX, upperSkewX, muY, sigmaY, scaleY, upperSkewY, muZ, sigmaZ, scaleZ, upperSkewZ, i, LASTROCK, scaleDisplace, scale_fac) # rock = bpy.data.objects[name] # 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 *** # *** Script no longer needs to map new texture names 9/6/2011 *** # Create the four new textures: # todo Set displacement texture parameters: # *** todo completed on 5/31/2011 *** # Voronoi has been removed from being an option for the fine detail # texture. texTypes = ['CLOUDS', 'MUSGRAVE', 'DISTORTED_NOISE', 'STUCCI', 'VORONOI'] newTex = [] # The first texture is to give a more ranodm base shape appearance: newTex.append(bpy.data.textures.new( name='rock_displacement', type=texTypes[1])) randomizeTexture(newTex[0], 0) newTex.append(bpy.data.textures.new( name='rock_displacement', type=texTypes[4])) randomizeTexture(newTex[1], 0) if numpy: newTex.append(bpy.data.textures.new( name='rock_displacement', type=texTypes[int(round(weibull(1, 1)[0] / 2.125))])) randomizeTexture(newTex[2], 1) newTex.append(bpy.data.textures.new( name='rock_displacement', type=texTypes[int(round(weibull(1, 1)[0] / 2.125))])) randomizeTexture(newTex[3], 2) else: newTex.append(bpy.data.textures.new( name='rock_displacement', type=texTypes[int(round(weibull(1, 1) / 2.125))])) randomizeTexture(newTex[2], 1) newTex.append(bpy.data.textures.new( name='rock_displacement', type=texTypes[int(round(weibull(1, 1) / 2.125))])) randomizeTexture(newTex[3], 2) # Add modifiers: rock.modifiers.new(name="Subsurf", type='SUBSURF') rock.modifiers.new(name="Subsurf", type='SUBSURF') rock.modifiers.new(name="Displace", type='DISPLACE') rock.modifiers.new(name="Displace", type='DISPLACE') rock.modifiers.new(name="Displace", type='DISPLACE') rock.modifiers.new(name="Displace", 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: rock.modifiers.new(name="Smooth", type='SMOOTH') rock.modifiers[6].factor = gauss(smooth_fac, (smooth_fac ** 0.5) / 12) rock.modifiers[6].iterations = smooth_it # Make a call to random to keep things consistant: else: gauss(0, 1) # Set subsurf modifier parameters: rock.modifiers[0].levels = display_detail rock.modifiers[0].render_levels = detail rock.modifiers[1].levels = display_detail rock.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 *** # *** texture access changed on 9/6/2011 *** rock.modifiers[2].texture = newTex[0] rock.modifiers[2].strength = gauss(deform / 100, (1 / 300) * deform) rock.modifiers[2].mid_level = 0 rock.modifiers[3].texture = newTex[1] rock.modifiers[3].strength = gauss(deform, (1 / 3) * deform) rock.modifiers[3].mid_level = 0 rock.modifiers[4].texture = newTex[2] rock.modifiers[4].strength = gauss(rough * 2, (1 / 3) * rough) rock.modifiers[5].texture = newTex[3] rock.modifiers[5].strength = gauss(rough, (1 / 3) * rough) # Set mesh to be smooth and fix the normals: utils.smooth(rock.data) # utils.smooth(bpy.data.meshes[name]) bpy.ops.object.editmode_toggle() bpy.ops.mesh.normals_make_consistent() bpy.ops.object.editmode_toggle() # Store the last value of i: shift = i # Add the shift to LASTROCK: LASTROCK += shift + 1 return # Much of the code below is more-or-less imitation of other addons and as such # I have left it undocumented. class OBJECT_OT_add_mesh_rock(bpy.types.Operator): """Add rock objects""" bl_idname = "mesh.add_mesh_rock" bl_label = "Add Rocks" bl_options = {'REGISTER', 'UNDO'} bl_description = "Add rocks" # Get the preset values from the XML file. # -> The script was morphed into a Python module # to support this. # Tell settings.py to parse the XML file with the settings. # Then get the default values resulting from the parsing. # Make a list containing the default values and append to that # the presets specified in the same XML file. This list will # be used to load preset values. settings.parse() defaults = settings.getDefault() presetsList = [defaults] presetsList += settings.getPresetLists() presets = [] lastPreset = 0 # Build the presets list for the enum property. # This needs to be a for loop as the user might add presets to # the XML file and those should show here: for i, preset in enumerate(presetsList): presets.append((str(i), preset[0], preset[0] + " preset values")) preset_values: EnumProperty( name="Presets", items=presets, description="Preset values for some rock types") num_of_rocks: IntProperty( name="Number of rocks", description="Number of rocks to generate. WARNING: Slow at high values!", min=1, max=1048576, soft_max=20, default=1) scale_X: FloatVectorProperty( name="X scale", description="X axis scaling range", min=0.0, max=256.0, step=1, default=defaults[1], size=2) skew_X: FloatProperty( name="X skew", description="X Skew ratio. 0.5 is no skew", min=-1.0, max=1.0, default=defaults[4]) scale_Y: FloatVectorProperty( name="Y scale", description="Y axis scaling range", min=.0, max=256.0, step=1, default=defaults[2], size=2) skew_Y: FloatProperty( name="Y skew", description="Y Skew ratio. 0.5 is no skew", min=-1.0, max=1.0, default=defaults[5]) scale_Z: FloatVectorProperty( name="Z scale", description="Z axis scaling range", min=0.0, max=256.0, step=1, default=defaults[3], size=2) skew_Z: FloatProperty( name="Z skew", description="Z Skew ratio. 0.5 is no skew", min=-1.0, max=1.0, default=defaults[6]) use_scale_dis: BoolProperty( name="Scale displace textures", description="Scale displacement textures with dimensions. May cause streched textures", default=defaults[7]) scale_fac: FloatVectorProperty( name="Scaling Factor", description="XYZ scaling factor. 1: no scaling", min=0.0001, max=256.0, step=0.1, default=defaults[8], size=3) # @todo Possible to title this section "Physical Properties:"? deform: FloatProperty( name="Deformation", description="Rock deformation", min=0.0, soft_max=50, max=1024.0, default=defaults[9]) rough: FloatProperty( name="Roughness", description="Rock roughness", min=0.0, soft_max=50, max=1024.0, default=defaults[10]) detail: IntProperty( name="Detail level", description="Detail level. WARNING: Slow at high values!", min=1, soft_max=4, max=10, default=defaults[11]) display_detail: IntProperty( name="Display Detail", description="Display detail. Use a lower value for high numbers of rocks", min=1, soft_max=4, max=10, default=defaults[12]) smooth_fac: FloatProperty( name="Smooth Factor", description="Smoothing factor. A value of 0 disables", min=0.0, max=128.0, default=defaults[13]) smooth_it: IntProperty( name="Smooth Iterations", description="Smoothing iterations. A value of 0 disables", min=0, max=50, default=defaults[14]) use_generate: BoolProperty( name="Generate Rocks", description="Enable actual generation", default=defaults[15]) use_random_seed: BoolProperty( name="Use a random seed", description="Create a seed based on time. Causes user seed to be ignored", default=defaults[16]) user_seed: IntProperty( name="User seed", description="Use a specific seed for the generator", min=0, max=1048576, default=defaults[17]) 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') if self.use_scale_dis: box.prop(self, 'scale_fac') else: self.scale_fac = utils.toFloats(self.defaults[8]) 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, 'use_generate') box.prop(self, 'use_random_seed') if not self.use_random_seed: box.prop(self, 'user_seed') box.prop(self, 'preset_values') @classmethod def poll(cls, context): return context.mode == 'OBJECT' # return (context.object is not None and context.object.mode == 'OBJECT') def execute(self, context): # The following "if" block loads preset values: if self.lastPreset != int(self.preset_values): self.scale_X = utils.toFloats(self.presetsList[int(self.preset_values)][1]) self.scale_Y = utils.toFloats(self.presetsList[int(self.preset_values)][2]) self.scale_Z = utils.toFloats(self.presetsList[int(self.preset_values)][3]) self.skew_X = float(self.presetsList[int(self.preset_values)][4]) self.skew_Y = float(self.presetsList[int(self.preset_values)][5]) self.skew_Z = float(self.presetsList[int(self.preset_values)][6]) self.use_scale_dis = bool(self.presetsList[int(self.preset_values)][7]) self.scale_fac = utils.toFloats(self.presetsList[int(self.preset_values)][8]) self.deform = float(self.presetsList[int(self.preset_values)][9]) self.rough = float(self.presetsList[int(self.preset_values)][10]) self.detail = int(self.presetsList[int(self.preset_values)][11]) self.display_detail = int(self.presetsList[int(self.preset_values)][12]) self.smooth_fac = float(self.presetsList[int(self.preset_values)][13]) self.smooth_it = int(self.presetsList[int(self.preset_values)][14]) self.use_generate = bool(self.presetsList[int(self.preset_values)][15]) self.use_random_seed = bool(self.presetsList[int(self.preset_values)][16]) self.user_seed = int(self.presetsList[int(self.preset_values)][17]) self.lastPreset = int(self.preset_values) # 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. *** if self.use_generate: generateRocks(context, self.scale_X, self.skew_X, self.scale_Y, self.skew_Y, self.scale_Z, self.skew_Z, self.scale_fac, self.detail, self.display_detail, self.deform, self.rough, self.smooth_fac, self.smooth_it, self.num_of_rocks, self.user_seed, self.use_scale_dis, self.use_random_seed) return {'FINISHED'} # Register: def menu_func_rocks(self, context): layout = self.layout layout.separator() layout.operator( OBJECT_OT_add_mesh_rock.bl_idname, text="Rock Generator", icon="MESH_ICOSPHERE") classes = ( OBJECT_OT_add_mesh_rock, ) def register(): from bpy.utils import register_class for cls in classes: register_class(cls) bpy.types.VIEW3D_MT_mesh_add.append(menu_func_rocks) def unregister(): from bpy.utils import unregister_class for cls in reversed(classes): unregister_class(cls) bpy.types.VIEW3D_MT_mesh_add.remove(menu_func_rocks) if __name__ == "__main__": register()