Combined Use of Blender with OpenFOAM G. Douglas Baldwin
Abstract Blender from blender.org contains a powerful, widely accepted, open source, surface mesh generation tool that was developed developed for the digital arts community. Furthermore, Further more, Blender includes a robust and well documented Python application programming interface (API) which facilitates the development of user tools for mesh manipulation, layered manifolds, and import/export. The author developed a Python script and technique for manipulating and exporting Blender objects into CalculiX for hexagonal hexagonal grid generation, which are then exported into OpenFOAM OpenFOAM format. The benefit of this approach is that manifolds allow absolute control of maximum cell growth rates from the boundary layer out to the edge of the computational domain, while also directing the path of stacked hexagonal cells to avoid avoid skew. skew. This technique was used to streamline an aircraft fuselage using a pure hexagonal grid with OpenFOAM, and then import the grid into Scientific Simulations NSU3D for authoritative drag analysis. The technique is ripe with possibilities for developing meshes for many classes of simulations. Highlights of the hexahedral grids used in the aircraft streamlining effort will be shown and discussed below, followed by an illustrative grid that combines hexahedral cells at the boundary layer with tetrahedral cells in the bulk flow. This paper will conclude with a copy of the Python script and a discussion of how it may may be further developed. The author has no intention at this time of leading further development of this script due to other demands on his time. It is hoped that others too will find value in this approach and perhaps carry it forward.
Fuselage Streamlining In general, Blender meshes are comprised of both triangles and quadrilaterals. quadrilaterals . For the fuselage streamlining project, it was decided to use exclusively exclusively quadrilaterals for the surface. Illustrations of the final surface are provided below. below. Notice the pink vertices. The only verticies defined by the user are those shown in pink. All of the other verticies are automatically generated generated by Blender when the subsurf modifier is applied to the object. The strength of the lines connecting any two two pink verticies is controlled by the crease feature. feature. It is not the purpose of this paper to provide provide a tutorial on Blender, subsurf, and crease. Many excellent excellent Blender tutorials are readily available on the internet. Notice however the technique of having evenly space pink verticies to maintain a relatively consistent grid size. The close-up illustration below reveals better the individual quadrilateral elements automatically generated by the Blender subsurf modifier. Also note the two two different colored Blender materials used on the object. These Blender materials eventually eventually translate to OpenFOAM patches. The decision to have two patches for this fuselage model is unrelated to the present topic and will not be discussed further to avoid a confusing digression.
Presented at the 3 rd OpenFOAM Workshop, Milan, Italy, 10-11 July 2008.
Manifolds surrounding this fuselage object were created by using the Python script that will be provided towards the end of this paper. The procedure for creating manifolds is that a copy of the base object is created by the user, and then an automatic algorithm moves each pink vertex along its normal by a user specified amount, making automatic adjustments until all computer generated verticies have also moved approximately along their normals by approximately the same distance. As a byproduct of this algorithm, color coded representations of cell height, aspect ratio, and skew are generated as a visual aid for manual tweaking of the manifold verticies. The first manifold surrounds the fuselage/base object as shown in the illustration below.
The following illustrations show the color-coded representations of cell height, aspect ratio, and skew, in that order. The color codes for these illustrations have the following meanings. For the height and aspect ratio illustrations, blue represents the minimum within the range of values and red represents the maximum within the range of values. For skew, blue through red represent the range of zero to ninety degrees in 12.5 degree increments.
The design goal for this fuselage grid was to have a cell height at the fuselage with y+ in the range of 1 to 10, and a cell growth factor of 1.15 extending to the edge of the computational domain, which was approximately 10 times the reference length away from the fuselage in all directions. A series of four manifolds were needed to avoid high skew angles. As can be seen in the following two illustrations, the manifolds further away from the fuselage become become more sphere-like. The first three manifolds are shown in the cutaway illustration below, followed by an illustration of the fourth manifold enclosing the third manifold. Notice that the fourth and largest manifold has two materials assigned, shown as blue and red, which represent the inlet patch and the outlet patch.
The user interface for the Python script is shown below. So far, we have only discussed the manifold generation capabilities of this script which are controlled by the Displacement Slider and the Apply button. The units of the Displacement Slider are equivalent to the average edge length of the manifold quads. The procedure for creating a manifold is as follows: 1) duplicate the the Blender Object; 2) while in Edit Mode select the vertices that will be displaced; and 3) click on the Apply button. Only the selected vertices will be displaced. The standard Blender Undo command can be used to move the vertices back to their original position if needed.
The column of buttons and sliders on the left side of the user interface are used to specify the grid characteristics, and then the Export button is pressed to export the selected manifolds as a solid geometry in CalculiX format. When the button labeled “Mesh the boundary layer” has been depressed, a boundary layer manifold will be automatically generated for the original/base object, with a displacement distance as specified by the Displacement slider. This button is useful for relatively simple geometries when performing multiple iterations of mesh generation and OpenFOAM analysis. The vertices of the base object can be tweaked, and then a new boundary layer manifold is automatically produced each time the Export button is clicked. For more complex geometries such as the fuselage shown above, the boundary layer manifold can be manually generated and then manually tweaked each time the fuselage vertices are tweaked. The next two sliders specify the cell height. The slider named “Target max aspect ratio” is used to specify the desired aspect ratio of the first cell. OpenFOAM rejects meshes having any cell aspect ratio that is greater than 1000. This slider is useful for achieving a small y+ while staying within an aspect ratio of 1000. Due to the nature of the meshing algorithms, the actual aspect ratio of the first cell may be slightly larger or smaller than the specified target. The slider “Cell Growth Factor” is used to specify the maximum cell height growth between any two stacked cells, and applies to the entire computational domain from the first cell at the base object out to the last manifold. The next three buttons are used to specify planes of symmetry normal to the X, Y, and Z axes, respectively. Under most circumstances these planes of symmetry will need to pass through the origin, but if necessary they can be offset by using the accompanying sliders. The units for these sliders are Blender units, which for the purposes of OpenFOAM will usually be meters. As will be seen in subsequent illustrations, a plane of symmetry normal to the Y-axis was applied when exporting the fuselage grid.
When the Export button is clicked, the user is asked to specify the folder and file name for the CalculiX .fbd file. File generation usually takes only a few seconds for large models, and certainly less than a minute for the very largest of models. If Blender is initialized through a command line console interface, then some useful statistics related to the resulting mesh are printed in this console. Also, the height, aspect ratio, and skew illustrations as shown above are automatically generated and placed in Blender’s layers 11, 12, and 13 respectively. The exported file can now be loaded into Calculix by using the command: “cgx -b filename.fbd”. For larger grids this load process can take a very long time because CalculiX automatically generates a geometric body for each pair of quads at each pair of manifolds. After generating the geometry, CalculiX automatically generates the mesh which also takes some time. Immediately after the file completes loading into CalculiX, you have a mesh ready to export in OpenFOAM format. First, type in the “save” command; this will save you the trouble of having CalculiX regenerate the geometry if you ever need to load this file again. A simple command for exporting to OpenFOAM a test mesh is: “send all foam patch all”. When you are satisfied with checkMesh, you will then want to specify the individual patches in your send command. These patches were defined earlier by using Blender materials. An example send command with patches is “send all foam patch inlet patch outlet wall yourObject”. Also, if plane(s) of symmetry were used in Blender, then one extra step is needed in CalculiX before sending the mesh with individual patches to OpenFOAM. Use the command “setr symmetry se notSymmetry” to remove the non-symmetry faces from the symmetry set, and then include the phrase “symmetryPlane symmetry” in your send command, e.g. “send all foam patch inlet patch outlet wall yourObject symmetryPlane symmetry”. Very little proficiency with CalculiX is necessary, though it would not hurt to learn some of the other features of this tool. Sample paraFoam plots of the final fuselage grid are shown below.
The above four plots show: 1) the surface quads of the fuselage, 2) the symmetry plane close-in to the fuselage, revealing the fine structure at the boundary layer, 3) the symmetry plane showing the 1 st, 2nd, and 3rd manifolds, and 4) the symmetry plane of the full computational domain, out to the 4 th manifold. After several iterations between Blender and OpenFOAM, a fuselage geometry having no flow separation was achieved. The OpenFOAM Lift/Drag tool was used to estimate the drag coefficients. The grid was then delivered to Scientific Simulations LLC, where an import tool was developed for converting the OpenFOAM pure hex grid into a NSU3D grid for further CFD analysis. Using the same operating conditions and same Spalart Allmaras wall model, the difference in predicted drag was significant. NSU3D, which has been publicly validated for these kinds of streamlined fuselages at the AIAA CFD Drag Workshop, predicted 30% less drag than OpenFOAM. Furthermore, NSU3D accepts cell aspect ratios of at least 10,000, which allowed for a finer resolution of the grid at the wall without the need to further refine the fuselage surface mesh. Together, Blender and OpenFOAM were a powerful combination of free tools for developing this streamlined, low drag fuselage.
Combining with tetrahedrals CalculiX can export to OpenFOAM only pure hexahedral grids, and as such is limited. Some cases cannot be modeled with pure hex, stacked grids. For instance, an effort was made to use the above techniques to develop a sample coarse grid of the Ahmed wind tunnel experiment. Unfortunately, the space between the bottom of the automobile and the test section floor became crowed with many high
aspect ratio cells. A better approach is to have quadrahedrals at the boundary layer combined with tetrahedrals filling the remainder of the computational domain. An attempt at creating such a sample grid was undertaken, using Blender with CalculiX for the boundary layer and Netgen for the bulk flow field. Blender images from this grid generation experiment are shown below. The first image shows the Ahmed body and the first manifold. These two objects were exported to Calculix using the techniques described above. The second image shows the tunnel, which was created by extruding a copy of the first manifold and adjusting its verticies.
The tunnel object was exported from Blender in stereo-lithography (STL) format, and then read into Netgen for tetrahedral mesh generation. Since the cavity of the tunnel object was originally a copy of the first manifold, this provides a perfect interface for potentially using the stitchMesh tool in OpenFOAM. Both the CalculiX generated boundary layer mesh and the Netgen bulk flow mesh were combined in OpenFOAM using mergeMeshes. An image of the combined meshes is shown below, which includes the tunnel inlet on the left, the symmetry plane, the surface of the Ahmed body, and the tunnel outlet on the right. The next image shows that the Ahmed body has a quad surface with hexahedral cells. Following this is a close-up image of the interface between the hexahedral cells and the tetrahedral cells. Unfortunately, stitchMesh was unable to remove the interface patches. It would appear that stitchMesh is under active development and should eventually be capable of removing this interface.
Further uses and further developments Blender has the ability to import STL objects, and the Python script described in this paper can be used to create a boundary layer mesh on these STL objects. One approach is to let CalculiX convert each trilateral into three quadrilaterals, and then use the techniques described above for creating a hexahedral mesh. Another approach would be to modify the Python script to have it output the hexahedral grid directly to OpenFOAM format, which would dramatically speed up the mesh generation process. Direct export from Blender would allow generation of prism cells, which could be stacked directly onto the STL trilaterals. A key architectural feature of the Blender data model provides the foundation for manifold development. Each mesh object includes a linked list of vertices. When a mesh object is copied, the new mesh object contains the same verticies in the same linked list order. The coordinates of these verticies may be change without changing their order within the linked list. By strategic placement of the verticies of the new mesh relative to the original mesh, a collection of uniform, low skew hexahedrals and/or prisms can be generated. This architectural feature was exploited to generate the CalculiX bodies from the Blender manifolds. This same architectural feature could be exploited to directly generate an OpenFOAM mesh comprised of hexahedrals and/or prisms with a uniform growth factor extending from the body wall up to the first manifold, or further to a second manifold, etc. The outer two manifolds can then be exported as an STL mesh for tetrahedral mesh generation in Netgen. Blender has a very efficient mesh manipulation kernel, and when combined with Python scripts can rapidly export large objects. The script provided below is both useful as is, and potentially even more valuable with a direct export to OpenFOAM capability. Also worth noting, modifications to CalculiX were developed to reduce memory allocation requirements and increase precision. A modification was needed when generating the fuselage grid in order to avoid malloc errors on a 32-bit machine. The modified source file named foamFaces.c was posted to the CalculiX support group site on Yahoo. Also, CalculiX outputs OpenFOAM grid coordinates in float format, and the format statement in the file write2foam.c can easily be changed to exponential format for improved precision.
Conclusions A combination of Blender and Calculix was used to generate pure hexahedral grids for the purpose of streamlining an aircraft fuselage. This technique can potentially be extended to mixed hexahedral/tetrahedral grids by employing Netgen. Furthermore, the Python script for exporting Blender geometry in CalculiX format could through modification directly export to OpenFOAM format, which would allow generation of a stacked prism boundary layer grid on top of an STL object. This Python script is very useful as-is, and its value to the OpenFOAM community could be significantly enhanced by adding these relatively minor modifications.
Python script #!BPY ###################################################################### # Export_fbd v0.2 for Blender # This script lets you export manifolds to Calculix format (.fbd) # (c) 2007 G. Douglas Baldwin (doug AT baldwintechnology DOT com) # released under Blender Artistic License ######################################################################
""" Name: 'Calculix Export (.fbd)...' Blender: Group: 'Export' Tooltip: 'Export selected objects to Calculix Format (.fbd)' """ __author__ = "Doug Baldwin" __version__ = "0.2" __bpydoc__ = """\ This script exports manifolds to Calculix Format (.fbd). Usage: Create a base mesh object, then make a first copy of this object. After a copy is made vertices of any object may be repositioned, but vertices, lines, and faces may neither be added nor deleted. Successive copies should be stretched into ever larger manifolds. This script will create a 3D Calculix mesh successively from the base mesh to the first copy, then from the first copy to the second copy, etc. The Displacement button is useful for stretching the manifolds. This function displaces the selected vertices of the selected object in the direction normal to the local surface. The goal is to displace the vertices of each manifold generally normal to its preceding mesh, while also reshaping as needed. Graphic plots of first cell height, aspect ratio, and skew are output to layers 11, 12, and 13.
Blender Material names of the first and last object will be exported as Calculix sets for generating patches. The GUI parameters Target Aspect Ratio and Growth Factor are used for calculating the hex cell height at the base mesh object, and the ratio of successive cell heights extending to the outermost manifold. The values Target Aspect Ratio and Growth Factor are the maximum values for the entire hex mesh, regardless of any undulations in the manifolds. Aspect Ratio is calculated to be the square root of the quad surface divided by the first cell height normal to the quad surface. Symmetry planes may be toggled on/off and positioned along the X, Y, and Z axes. If one or more symmetry planes are toggled on, the exported file will include a set of external surfaces called "symmetry". Symmetry planes are generally useful only when vertices of the base object and manifolds lie on the symmetry plane. Select all objects to be exported, with the outermost object active. Press the Export button to export, then select the file location and filename. Press the Exit button to exit the script. """ import Blender from Blender import Scene, Window, Material from math import ceil, log, sqrt, floor, acos, pi # ================================= # === Write FBD Calculix Format === # ================================= from Blender.BGL import * from Blender.Draw import * import BPyMessages import bpy # Parameters X_Plane = Create(0) Y_Plane = Create(1) Z_Plane = Create(0) X_Location = Create(0.0) Y_Location = Create(0.0) Z_Location = Create(0.0) growth = Create(1.15) aspect = Create(1000) mesh_BL = Create(0)
factor = Create(1.0) # Events EVENT_NOEVENT = 1 EVENT_EXPORT = 2 EVENT_EXIT = 3 EVENT_APPLY = 4 ###################################################### # GUI drawing ###################################################### def draw(): global X_Plane,Y_Plane,Z_Plane,X_Location,Y_Location,Z_Location global growth, aspect, mesh_BL, factor global EVENT_NOEVENT,EVENT_EXPORT,EVENT_EXIT ########## Titles glClear(GL_COLOR_BUFFER_BIT) glRasterPos2d(8, 183) Text("Export with optional symmetry plane(s)") glRasterPos2d(250, 85) Text("Displace selected vertices normal to the subsurf") ######### Parameters GUI Buttons X_Plane = Toggle("X: ", EVENT_NOEVENT, 10, 55, 18, 18, X_Plane.val, "Toggle X-symmetry plane"); X_Location = Slider("Location: ", EVENT_NOEVENT, 30, 55, 200, 18, X_Location.val, -20.000, 20.0000, 1, "X-plane location"); Y_Plane = Toggle("Y: ", EVENT_NOEVENT, 10, 75, 18, 18, Y_Plane.val, "Toggle Y-symmetry plane"); Y_Location = Slider("Location: ", EVENT_NOEVENT, 30, 75, 200, 18, Y_Location.val, -20.000, 20.0000, 1, "Y-plane location"); Z_Plane = Toggle("Z: ", EVENT_NOEVENT, 10, 95, 18, 18, Z_Plane.val, "Toggle Z-symmetry plane"); Z_Location = Slider("Location: ", EVENT_NOEVENT, 30, 95, 200, 18, Z_Location.val, -20.000, 20.0000, 1, "Z-plane location"); growth = Number("Cell Growth Factor:", EVENT_NOEVENT, 10, 115, 220, 18, growth.val, 1.00, 2.00, "Ratio of cell heights in direction normal to the base object") aspect = Number("Target max aspect ratio:", EVENT_NOEVENT, 10, 135, 220, 18, aspect.val, 100.0, 2000.0, "Target aspect ratio of first cell layer") mesh_BL = Toggle("Mesh the boundary layer", EVENT_NOEVENT, 10, 155, 220, 18, mesh_BL.val, "Generate a mesh extruded normal to the base object"); factor = Slider("Displacement: ", EVENT_NOEVENT, 250, 55, 260, 18, factor.val, -1.000, 10.000, 1,
"Magnitude relative to average subsurf edge length"); ######### Export and Exit Buttons glRasterPos2d(8, 36) Text("Note: Select all manifolds") Button("Export",EVENT_EXPORT , 10, 10, 80, 18, "Export to Calculix file") Button("Apply",EVENT_APPLY , 250, 10, 80, 18, "Apply displacement to the active object") Button("Exit",EVENT_EXIT , 430, 10, 80, 18, "Exit script") def event(evt, val): if (evt == QKEY and not val): Exit() def bevent(evt): # global EVENT_NOEVENT,EVENT_EXPORT,EVENT_EXIT ######### Manages GUI events if (evt == EVENT_EXIT): Exit() elif (evt== EVENT_EXPORT): if not Blender.Object.GetSelected(): BPyMessages.Error_NoActive() return Blender.Window.FileSelector(write_ui, 'FBD Export', Blender.sys.makename(ext='.fbd')) Blender.Redraw() elif (evt == EVENT_APPLY): manDisplace(factor.val) Register(draw, event, bevent) def manDisplace(factor): sce = bpy.data.scenes.active ob_act = sce.objects.active if not ob_act or ob_act.type != 'Mesh': BPyMessages.Error_NoMeshActive() return is_editmode = Window.EditMode() Window.EditMode(0) Window.WaitCursor(1) displace(ob_act, factor)
# Restore editmode if it was enabled if is_editmode: Window.EditMode(1) Window.WaitCursor(0) def write_ui(filename): global growth, aspect, mesh_BL, factor # open file if not filename.lower().endswith('.fbd'): filename += '.fbd' file = open(filename, 'wb') file.write('asgn s C \n') # Calculix auto-generated surfaces to begin with "C" #initialize values t = Blender.sys.time() me = [] ratio = [] bl = None scn = Scene.GetCurrent() obs = [ob for ob in scn.objects.context] obList = obs if mesh_BL.val == 1: # create boundary layer object scn.objects.selected = [obs[len(obs)-1]] Blender.Object.Duplicate(mesh=1) bl = scn.objects.selected[0] # Select vertices in base mesh mesh = bl.getData(mesh=1) for v in mesh.verts: v.sel = 1 displace(bl, factor.val) obList.insert(len(obList)-1, bl) n = len(obList) for i in range(n): me.append(Blender.Mesh.New()) # create a new mesh me[i].getFromObject(obList[i], 0, 0) # get subsurf mesh me[i].transform(obList[i].matrix) # scale and rotate as needed writePoints(file,me[i],i) # write mesh points writeLines(file,me[i],i) # write mesh lines writeSurfaces(file,me[i],i) # write mesh surfaces and patch names if i != 0: writeThickness(file,me[i],me[i-1],i) writeBodies(file,me[i],me[i-1],i)
# calculate and generate div and bia minH, maxH, ratio = heightAnalysis(me) dH = 1.0/aspect.val for i in reversed(range(0,n-1)): dH = dH/ratio[i] height = 0.0 div = 0 while height < 1.0: height += dH dH = dH*growth.val div += 1 if i == n-2: div -= 1 firstDiv = div bia = pow(growth.val,div-1) file.write('div %s%s %d\n' % ("thickness", i, div)) file.write('bia %s%s %f\n' % ("thickness", i, bia)) print "i, div, bia", i, div, bia if len(obList) >= 2: surfacePlots(obList, firstDiv, growth.val) # generate symmetry set while also generating 3-D mesh mats = Blender.Material.get() for mat in mats: file.write('comp %s d\n' % mat.name) file.write('SETA notSymmetry se %s\n' % mat.name) file.write('comp notSymmetry d\n') file.write('elty all he8\n') file.write('mesh all\n') file.write('seta symmetry se all\n') file.write('setr symmetry se notSymmetry\n') file.write('plot me all\n') file.write('plus s all\n') file.close() if mesh_BL == 1 and n == 2: PupMenu('Returning boundary layer object with base object') else: if bl != None: scn.objects.unlink(bl) scn.objects.selected = obs # Timing the script is a good way to be aware on any speed hits when scripting print 'Script finished in %.2f seconds' % (Blender.sys.time()-t) def writePoints(file,mi,i): # write out all points in this mesh
for v in mi.verts: file.write('PNT P%x%x ' % (i, v.index) ) file.write('%.6f %.6f %.6f\n' % tuple(v.co)) def writeLines(file,mi,i): # write out all lines in this mesh for e in mi.edges: file.write('LINE L%x%x P%x%x P%x%x 1\n' % \ (i, e.index, i, e.v1.index, i, e.v2.index)) def writeThickness(file,m1,m0,i): for j in range(len(m1.verts)): m1vji = m1.verts[j].index file.write('LINE M%x%x P%x%x P%x%x 2\n' % \ (i, m1vji, i, m1vji, i-1, m0.verts[j].index)) file.write('SETA %s%s l M%x%x\n' % \ ("thickness", i-1, i, m1vji)) def heightAnalysis(me): ratio = [] for i in range(len(me)-2): ratio.append(0.0) for f0,f1,f2 in zip(me[i].faces,me[i+1].faces,me[i+2].faces): upperH = abs((f0.cent - f1.cent)*(f0.no + f1.no)/2) lowerH = abs((f1.cent - f2.cent)*(f1.no + f2.no)/2) ratio[i] = max(ratio[i], upperH/lowerH) n = len(me)-2 f0 = me[n].faces[0] f1 = me[n+1].faces[0] h = abs((f0.cent - f1.cent)*(f0.no + f1.no)/2) minH = h maxH = h ratio.append(h/sqrt(f1.area)) for f0, f1 in zip(me[n].faces, me[n+1].faces): h = abs((f0.cent - f1.cent)*(f0.no + f1.no)/2) minH = min(minH, h) maxH = max(maxH, h) ratio[n] = min(ratio[n],h/sqrt(f1.area)) return minH, maxH, ratio def surfacePlots(obs, firstDiv, growth): heights = [] aspectRatios = [] skews = [] mat = []
factor = 0 for i in range(firstDiv): factor = factor + pow(growth,i) factor = 1/factor scn = Scene.GetCurrent() meH = Blender.Mesh.New('height') # create a new mesh meH.getFromObject(obs[len(obs)-1], 0, 0) # get subsurf mesh meH.transform(obs[len(obs)-1].matrix) # scale and rotate as needed obH = scn.objects.new(meH) obH.layers = [11] meAR = Blender.Mesh.New('aspectRatio') # create a new mesh meAR.getFromObject(obH, 1, 0) # copy mesh obAR = scn.objects.new(meAR) obAR.layers = [12] meSkew = Blender.Mesh.New('skew') # create a new mesh meSkew.getFromObject(obH, 1, 0) # copy mesh obSkew = scn.objects.new(meSkew) obSkew.layers = [13] me1 = Blender.Mesh.New() # create a new mesh me1.getFromObject(obs[len(obs)-2], 0, 0) # get subsurf mesh me1.transform(obs[len(obs)-2].matrix) # scale and rotate as needed for f0, f1 in zip(meH.faces, me1.faces): vC = f1.cent - f0.cent heights.append(abs(vC*(f0.no + f1.no)/2)*factor) aspectRatios.append(sqrt(f0.area)/heights[len(heights)-1]) cosine = min(1, vC*f0.no/sqrt(vC*vC)) skews.append(acos(cosine)) minH = min(heights) maxH = max(heights) minAR = min(aspectRatios) maxAR = max(aspectRatios) print "min/max first cell height", minH, maxH print "min/max first cell AR", minAR, maxAR mat.append(Material.New()) mat[0].rgbCol = [0.,0.,1.] mat.append(Material.New()) mat[1].rgbCol = [0.,0.5,1.] mat.append(Material.New()) mat[2].rgbCol = [0.,1.,1.]
mat.append(Material.New()) mat[3].rgbCol = [0.,1.,5.] mat.append(Material.New()) mat[4].rgbCol = [0.,1.,0.] mat.append(Material.New()) mat[5].rgbCol = [1.,1.,0.] mat.append(Material.New()) mat[6].rgbCol = [1.,0.5,0.] mat.append(Material.New()) mat[7].rgbCol = [1.,0.,0.] meH.materials = mat meAR.materials = mat meSkew.materials = mat obH.setMaterials(mat) obAR.setMaterials(mat) obSkew.setMaterials(mat) hRange = maxH - minH for f0, h in zip(meH.faces, heights): f0.mat = int(floor(7.999*(h-minH)/hRange)) ARRange = maxAR - minAR for f0, AR in zip(meAR.faces, aspectRatios): f0.mat = int(floor(7.999*(AR-minAR)/ARRange)) for f0, s in zip(meSkew.faces, skews): if s > pi/2.0: f0.mat = 7 else: f0.mat = int(floor(7.999*(2.0*s/pi))) Blender.Redraw()
def writeSurfaces(file,m,i): key_index = {} for e in m.edges: key_index[e.key] = e.index for j in range(len(m.faces)): mfi = m.faces[j].index file.write('GSUR A%x%x + BLEND ' % (i, mfi)) for k in m.faces[j].edge_keys: file.write(' + L%x%x' % (i, key_index[k])) file.write('\n') if m.materials != []: mat = m.materials[m.faces[j].mat] if mat != None: file.write('SETA %s s A%x%x\n' % (mat.name, i, mfi))
def writeBodies(file,m1,m0,i): global X_Plane,Y_Plane,Z_Plane,X_Location,Y_Location,Z_Location for j in range(len(m1.faces)): m1fi = m1.faces[j].index # cut at symmetry planes as specified by GUI if ( (X_Plane.val == 0 or m1.faces[j].cent[0] > X_Location.val) and \ (Y_Plane.val == 0 or m1.faces[j].cent[1] > Y_Location.val) and \ (Z_Plane.val == 0 or m1.faces[j].cent[2] > Z_Location.val) ): file.write('body B%x%x A%x%x A%x%x\n' % \ (i, m1fi, i, m1fi, \ i-1, m0.faces[j].index)) file.write('SETA bods b B%x%x\n' % \ (i, m1fi)) def displace(ob, factor): #
Get subsurf mesh and calculate its normals me0 = Blender.Mesh.New() me0.getFromObject(ob, 0, 0) me0.calcNormals()
#
Create dictionary of subsurf vertices to faces vert_faces = dict([(v.index, []) for v in me0.verts]) for f in me0.faces: for v in f.verts: vert_faces[v.index].append(f.index)
#
Calculate target heights of selected subsurf vertex normals heights = [] displacements = [] totalH = 0 nFaces = 0 for vi in me0.verts.selected(): for fi in vert_faces[vi]: totalH += sqrt(me0.faces[fi].area) nFaces += 1 hFace = totalH/nFaces for vi in me0.verts.selected(): hTotal = 0 nFaces = 0 for fi in vert_faces[vi]: hTotal += hFace/abs(me0.verts[vi].no*me0.faces[fi].no) nFaces += 1 hNormal = hTotal/nFaces heights.append(abs(hNormal*factor))
displacements.append(hNormal*factor) loop = 1 count = 0 #
Iterate base mesh displacements needed to achieve target heights while loop == 1 and count <= 100:
#
Copy base mesh and displace scn = Scene.GetCurrent() scn.objects.selected = [ob] Blender.Object.Duplicate(mesh=1) obCopy = scn.objects.selected[0] meCopy = obCopy.getData(mesh=1) for vi, d in zip(meCopy.verts.selected(), displacements): meCopy.verts[vi].co += meCopy.verts[vi].no*d
#
Subsurf displaced mesh meCopy0 = Blender.Mesh.New() meCopy0.getFromObject(obCopy, 0, 0) loop = 0 sumErrorSquare = 0
#
Measure differences, correct displacements, and sum errors for vi, i in zip(meCopy0.verts.selected(), range(len(heights))): dv = meCopy0.verts[vi].co - me0.verts[vi].co correction = heights[i]/sqrt(dv*dv) displacements[i] = displacements[i]*correction sumErrorSquare += (1-correction)*(1-correction) if sumErrorSquare/len(heights) > 1e-8: loop=1 count += 1 scn.objects.unlink(obCopy) print "Average Error Square=", sumErrorSquare/len(heights) print "Loop count=", count
#
Apply displacements to base mesh me = ob.getData(mesh=1) for vi, d in zip(me.verts.selected(), displacements): me.verts[vi].co += me.verts[vi].no*d scn.objects.active = ob