#!BPY """ Name: 'Solidify Mesh' Blender: 240 Group: 'Mesh' Tooltip: 'Makes the mesh solid by creating a second skin of a set width.' """ __author__ = "Campbell Barton" __bpydoc__ = """\ This script makes a skin from the selected faces. Optionaly you can skin between the original and new faces to make a watertight solid object """ from Blender import * from Blender.Mathutils import Vector, Intersect, LineIntersect def reversed(L): return [L[l] for l in range(len(L)-1,-1,-1)] class solidMesh: class solidEdge: def __init__(self): self.faceUsers = [] class solidVert: ''' Used for generating and cutting the inset surface ''' def __init__(self, vec, solidVertGroup): self.co = vec self.index = -1 solidVertGroup.append(self) self.solidVertGroup = solidVertGroup # a list of other verts that came from the same point. def incIndex(self, inc): self.index = inc[0] inc[0] +=1 # increment in place return self class solidFace: def __init__(self, bFace, insetDist, solidVertGroupList): self.bFace = bFace self.no = Vector(bFace.no) self.cent = reduce(lambda x,y: x + y.co, bFace.v, Mathutils.Vector(0,0,0)) * (1.0/len(bFace.v)) self.insetDir = self.no * insetDist # Get the vert pairs and sort them so we can use them as a dictionary key. self.edges = [ (min(edgePair),max(edgePair)) for edgePair in [(bFace.v[i-1].index, bFace.v[i].index) for i in xrange( len(bFace.v) )]] # INSET DATA # Make the inset face self.insetCent = self.cent + self.insetDir self.insetVerts = [solidMesh.solidVert(v.co + self.insetDir, solidVertGroupList[v.index]) for v in bFace.v] # Clipping lines, used to clip the inset face, these points may be more then 4, so will need to scanfill. # for each edge we store a list of clipping lines. # For each face that shares teh same edge, a clipping line will be generated. # so an edge that has 4 face usprettyNormalsers with have 3 clipping lines stored by each of the face users # since the face cant be used to clip its self. # In most cases there will only be 1 clipping line. since normaly edges only have 2 face users. # ... These will be filled in the mesh building loop, self.clippingLines = [[] for i in xrange(len(bFace.v))] self.clipVerts = [None] * len(bFace.v) # Store location of the clipped, actual vert locations used for the inset face. def clipFace(self, prettyNormals): ''' Once the face has clipping lines we can clip the face's ''' # Get the raytraced intersection. for i in xrange(len(self.clipVerts)): targetVert = self.bFace.v[i] self.clipVerts[i] = Intersect(self.insetVerts[0].co, self.insetVerts[1].co, self.insetVerts[2].co, prettyNormals[targetVert.index], targetVert.co, 0) # no clipping. # Assign new location to the clipverts location for i in xrange(len(self.clipVerts)): self.insetVerts[i].co = self.clipVerts[i] def __init__(self, bMesh, insetDist, prettyNormals): self.prettyNormals = prettyNormals self.insetDist = insetDist self.bMesh = bMesh self.solidVertGroupList = [list() for v in bMesh.verts] # keep in sync with verts. stores a list of seperated s for each vert. self.solidFaces = [self.solidFace(f, insetDist, self.solidVertGroupList) for f in bMesh.faces if f.sel] # Get all the edges from faces and make a dict where each edge key has a solidEdge as its value. uniqueEdges = [edgeKey for solidFace in self.solidFaces for edgeKey in solidFace.edges] self.edges = dict([(edgePair, self.solidEdge()) for edgePair in uniqueEdges]) # Add faces to the edges for solidFace in self.solidFaces: for edgePair in solidFace.edges: self.edges[edgePair].faceUsers.append(solidFace) for sf in self.solidFaces: sf.clipFace(prettyNormals) def buildInset(self): # Deselect all for v in self.bMesh.verts: v.sel = 0 for f in self.bMesh.faces: f.sel = 0 # Move all solidverts to the same place, for i in xrange(len(self.bMesh.verts)): solidVerts = self.solidVertGroupList[i] co = Vector(0,0,0) noneCount = 0 for sv in solidVerts: if sv.co: co += sv.co else: noneCount +=1 _div_ = len(solidVerts)-noneCount if _div_: #not a zero user vert? co *= (1.0/_div_) for sv in solidVerts: sv.co = co unusedVec = Vector(0,0,0) # SetIndex returns self, i and ii are used to set up the index. inc = [len(self.bMesh.verts)] # so we can modify in place for each new vertex #newverts = [v.incIndex(inc).co for solidFace in self.solidFaces for v in solidFace.insetVerts] newverts = [] for sv in self.solidVertGroupList: if sv: newverts.append(sv[0].co) else: newverts.append(unusedVec) orig_vertlen = len(self.bMesh.verts) orig_facelen = len(self.bMesh.faces) self.bMesh.verts.extend(newverts) newfaces = [[ self.bMesh.verts[orig_vertlen+v.index] for v in reversed(solidFace.bFace.v)] for solidFace in self.solidFaces ] self.bMesh.faces.extend( [tuple(f) for f in newfaces] ) """ # REALY BUGGY BLENDER API # Copy face properties. for i in range(orig_facelen): origFace = self.bMesh.faces[i] copyFace = self.bMesh.faces[i+orig_facelen] copyFace.smooth = origFace.smooth copyFace.mat = origFace.mat # SHOULD BE INCLUDED, Mesh is BUGGY ''' if self.bMesh.faceUV: copyFace.mode = origFace.mode # copyFace.flag = origFace.flag # gives error? uv = list(origFace.uv) uv.reverse() copyFace.uv = tuple(uv) if self.bMesh.vertexColors: col = list(origFace.col) col.reverse() copyFace.col = tuple(col) ''' ''' if self.bMesh.faceUV: col = list(origFace.col) col.reverse() print 'AAANNN' for i, c in enumerate(origFace.col): copyFace.col[i].r = c.r copyFace.col[i].g = c.g copyFace.col[i].b = c.b ''' """ # Now add faces for 0 user edges edgeFaces = [] edgebFaces = [] for key, edge in self.edges.iteritems(): if len(edge.faceUsers) == 1: faceVerts = [\ self.bMesh.verts[key[0]],\ self.bMesh.verts[key[1]],\ self.bMesh.verts[key[1]+orig_vertlen],\ self.bMesh.verts[key[0]+orig_vertlen]] # See wich way to flip the face # we have sorted the edge's vertex order for dictionary hashing # so we need to look at the face t osee wich way to flip. bFace = edge.faceUsers[0].bFace for i in xrange(len(bFace.v)): index1 = bFace.v[i-1].index index2 = bFace.v[i].index if key[0] == index1 and key[1] == index2: if self.insetDist < 0: faceVerts.reverse() break elif key[1] == index1 and key[0] == index2: if self.insetDist > 0: faceVerts.reverse() break edgeFaces.append( tuple(faceVerts) ) edgebFaces.append(bFace) if edgeFaces: orig_facelen = len(self.bMesh.faces) self.bMesh.faces.extend( edgeFaces ) # SHOULD BE INCLUDED, Mesh is BUGGY ''' # Copy properties to the edge faces. for i in xrange(len(edgeFaces)): copyFace = self.bMesh.faces[i+orig_facelen] origFace = edgebFaces[i] copyFace.smooth = origFace.smooth copyFace.mat = origFace.mat print copyFace.mat, origFace.mat ''' def getAng3pt3d(a,b,c): ''' Get the angle between line AB and BC, think of 'b' as the elbow Warning, gets shortest angle ''' try: ang = Mathutils.AngleBetweenVecs(a-b, c-b) except: # Zero length vector, can have an angle return 0 if ang != ang: return 0 return ang def meshPrettyNormals(me): vertAngles = [list() for i in xrange(len(me.verts))] #faceAngles = [ [None] * len(f.v) for f in me.faces ] for f in me.faces: normal = f.no for i in xrange(len(f.v)): v1, v2, v3 = f.v[i-2].co, f.v[i-1].co, f.v[i].co vertAngles[f.v[i-1].index].append( (getAng3pt3d(v1, v2, v3), normal) ) vertNormals = [None] * len(me.verts) fake_normal = Vector(0,0,1) # Used when for v in me.verts: normal = Vector(0,0,0) vertAngle = vertAngles[v.index] if vertAngle: for ang, faceno in vertAngle: normal += (faceno * ang) else: normal = fake_normal normal.normalize() vertNormals[v.index] = normal return vertNormals def main(): scn = Scene.GetCurrent() ob = scn.getActiveObject() if ob.getType() != 'Mesh': Draw.PupMenu('ERROR: Active object is not a mesh, aborting.') return is_editmode = Window.EditMode() if is_editmode: Window.EditMode(0) inset = Draw.PupFloatInput('thick: ', 0.1, -10.0, 10.0, 0.1, 4) Window.WaitCursor(1) # Main code function me = ob.getData(mesh=1) prettyNormals = meshPrettyNormals(me) my_solidMesh = solidMesh(me, inset, prettyNormals) my_solidMesh.buildInset() Window.WaitCursor(0) if is_editmode: Window.EditMode(1) Window.RedrawAll() if __name__ == '__main__': main()