def docyl(S, R, Pts): coords = [] p0 = Vector(Pts[0][0], Pts[0][1], Pts[0][2]) p1 = Vector(Pts[1][0], Pts[1][1], Pts[1][2]) t = Vector(Pts[1][0] - Pts[0][0], Pts[1][1] - Pts[0][1], Pts[1][2] - Pts[0][2]) t.normalize() if t * Vector(1, 0, 0) < 0.8: n = Vector(1, 0, 0) - (Vector(1, 0, 0) * t) * t else: n = Vector(0, 1, 0) - (Vector(0, 1, 0) * t) * t n.normalize() b = Vector(t[1] * n[2] - t[2] * n[1], t[2] * n[0] - t[0] * n[2], t[0] * n[1] - t[1] * n[0]) b.normalize() for i in xrange(S): angle = float(i) * 2. * pi / float(S) vec = cos(angle) * R * n + sin(angle) * R * b c0 = p0 + vec c1 = p1 + vec coords += [[c0[0], c0[1], c0[2]]] coords += [[c1[0], c1[1], c1[2]]] faces = [] for i in xrange(S): faces += [[ 2 * i, (2 * i + 2) % (2 * S), (2 * i + 3) % (2 * S), (2 * i + 1) % (2 * S) ]] # midpoints coords += [[p0[0], p0[1], p0[2]]] coords += [[p1[0], p1[1], p1[2]]] cid0 = len(coords) - 2 cid1 = len(coords) - 1 for i in xrange(S): idx = [] idx2 = [] idx.append(2 * i + 0) idx.append(cid0) idx.append(2 * ((i + 1) % S)) idx2.append(2 * i + 1) idx2.append(2 * ((i + 1) % S) + 1) idx2.append(cid1) faces += [idx] faces += [idx2] print faces me = Mesh.New('Strut') me.verts.extend(coords) me.faces.extend(faces) # Add new object to scene and return it return Scene.GetCurrent().objects.new(me)
def makeVNormal(me, vert, origEdgeLen): vNormal = Vector([0,0,0]) eIdx = 0 while eIdx < origEdgeLen: e = me.edges[eIdx] if e.v1 == vert or e.v2 == vert: pass else: eIdx+=1 continue v1 = -1 if vert == e.v1: v1 = False if vert == e.v2: v1 = 1 if v1 == -1: eIdx+=1 continue # one of the verts in the face is the same as the vert that we are finding teyh normal for. # Make a vector from the edge v1 and v2 are the indivies for this edge v2 = (not v1) ev = [e.v1, e.v2] newVec = Vector([\ ev[v1].co[0]-ev[v2].co[0],\ ev[v1].co[1]-ev[v2].co[1],\ ev[v1].co[2]-ev[v2].co[2]] ) newVec.normalize() vNormal = vNormal + newVec eIdx+=1 vNormal.normalize() return vNormal
def docyl(S,R,Pts): coords = [] N = len(Pts) for i in xrange(N): prev = Vector(Pts[(i-1)%N][0],Pts[(i-1)%N][1],Pts[(i-1)%N][2]) cur = Vector(Pts[i][0],Pts[i][1],Pts[i][2]) next = Vector(Pts[(i+1)%N][0],Pts[(i+1)%N][1],Pts[(i+1)%N][2]) t = next - prev t.normalize() if t*Vector(1,0,0) < 0.8: n = Vector(1,0,0)-(Vector(1,0,0)*t)*t else: n = Vector(0,1,0)-(Vector(0,1,0)*t)*t n.normalize() b = Vector(t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]) b.normalize() for i in xrange(S): angle = float(i)*2.*pi/float(S) vec = cos(angle)*R*n + sin(angle)*R*b c0 = cur + vec coords += [[ c0[0],c0[1],c0[2] ]] faces = [] for j in xrange(N): for i in xrange(S): faces += [[ S*j + i, S*j + (i + 1)%S, S*((j+1)%N) + (i+1)%S, S*((j+1)%N) + i ]] me = Mesh.New('Billiard') me.verts.extend(coords) me.faces.extend(faces) Scene.GetCurrent().objects.new(me)
def docyl(S, R, P0, P1): coords = [] p0 = Vector(P0[0], P0[1], P0[2]) p1 = Vector(P1[0], P1[1], P1[2]) t = Vector(P1[0] - P0[0], P1[1] - P0[1], P1[2] - P0[2]) t.normalize() if t * Vector(1, 0, 0) < 0.8: n = Vector(1, 0, 0) - (Vector(1, 0, 0) * t) * t else: n = Vector(0, 1, 0) - (Vector(0, 1, 0) * t) * t n.normalize() b = Vector(t[1] * n[2] - t[2] * n[1], t[2] * n[0] - t[0] * n[2], t[0] * n[1] - t[1] * n[0]) b.normalize() for i in xrange(S): angle = float(i) * 2. * pi / float(S) vec = cos(angle) * R * n + sin(angle) * R * b c0 = p0 + vec c1 = p1 + vec coords += [[c0[0], c0[1], c0[2]]] coords += [[c1[0], c1[1], c1[2]]] faces = [] for i in xrange(S): faces += [[ 2 * i, (2 * i + 1) % (2 * S), (2 * i + 3) % (2 * S), (2 * i + 2) % (2 * S) ]] me = Mesh.New('Strut') me.verts.extend(coords) me.faces.extend(faces) return Scene.GetCurrent().objects.new(me)
class edgeLoop(object): __slots__ = 'centre', 'edges', 'normal', 'closed', 'backup_edges' def __init__(self, loop, me, closed): # Vert loop # Use next and prev, nextDist, prevDist # Get Loops centre. fac = len(loop) verts = me.verts self.centre = reduce(lambda a, b: a + verts[b].co / fac, loop, Vector()) # Convert Vert loop to Edges. self.edges = [ edge(verts[loop[vIdx - 1]], verts[loop[vIdx]]) for vIdx in xrange(len(loop)) ] if not closed: self.edges[0].fake = True # fake edge option self.closed = closed # Assign linked list for eIdx in xrange(len(self.edges) - 1): self.edges[eIdx].next = self.edges[eIdx + 1] self.edges[eIdx].prev = self.edges[eIdx - 1] # Now last self.edges[-1].next = self.edges[0] self.edges[-1].prev = self.edges[-2] # GENERATE AN AVERAGE NORMAL FOR THE WHOLE LOOP. self.normal = Vector() for e in self.edges: n = (self.centre - e.co1).cross(self.centre - e.co2) # Do we realy need tot normalize? n.normalize() self.normal += n # Generate the angle va = e.cent - e.prev.cent vb = e.next.cent - e.cent e.angle = AngleBetweenVecs(va, vb) # Blur the angles #for e in self.edges: # e.angle= (e.angle+e.next.angle)/2 # Blur the angles #for e in self.edges: # e.angle= (e.angle+e.prev.angle)/2 self.normal.normalize() # Generate a normal for each edge. for e in self.edges: n1 = e.co1 n2 = e.co2 n3 = e.prev.co1 a = n1 - n2 b = n1 - n3 normal1 = a.cross(b) normal1.normalize() n1 = e.co2 n3 = e.next.co2 n2 = e.co1 a = n1 - n2 b = n1 - n3 normal2 = a.cross(b) normal2.normalize() # Reuse normal1 var normal1 += normal1 + normal2 normal1.normalize() e.normal = normal1 #print e.normal def backup(self): # Keep a backup of the edges self.backup_edges = self.edges[:] def restore(self): self.edges = self.backup_edges[:] for e in self.edges: e.removed = 0 def reverse(self): self.edges.reverse() self.normal.negate() for e in self.edges: e.normal.negate() e.v1, e.v2 = e.v2, e.v1 e.co1, e.co2 = e.co2, e.co1 e.next, e.prev = e.prev, e.next def removeSmallest(self, cullNum, otherLoopLen): ''' Removes N Smallest edges and backs up the loop, this is so we can loop between 2 loops as if they are the same length, backing up and restoring incase the loop needs to be skinned with another loop of a different length. ''' global CULL_METHOD if CULL_METHOD == 1: # Shortest edge eloopCopy = self.edges[:] # Length sort, smallest first try: eloopCopy.sort(key=lambda e1: e1.length) except: eloopCopy.sort(lambda e1, e2: cmp(e1.length, e2.length)) # Dont use atm #eloopCopy.sort(lambda e1, e2: cmp(e1.angle*e1.length, e2.angle*e2.length)) # Length sort, smallest first #eloopCopy.sort(lambda e1, e2: cmp(e1.angle, e2.angle)) # Length sort, smallest first remNum = 0 for i, e in enumerate(eloopCopy): if not e.fake: e.removed = 1 self.edges.remove( e) # Remove from own list, still in linked list. remNum += 1 if not remNum < cullNum: break else: # CULL METHOD is even culled = 0 step = int(otherLoopLen / float(cullNum)) * 2 currentEdge = self.edges[0] while culled < cullNum: # Get the shortest face in the next STEP step_count = 0 bestAng = 360.0 smallestEdge = None while step_count <= step or smallestEdge == None: step_count += 1 if not currentEdge.removed: # 0 or -1 will not be accepted if currentEdge.angle < bestAng and not currentEdge.fake: smallestEdge = currentEdge bestAng = currentEdge.angle currentEdge = currentEdge.next # In that stepping length we have the smallest edge.remove it smallestEdge.removed = 1 self.edges.remove(smallestEdge) # Start scanning from the edge we found? - result is over fanning- no good. #currentEdge= smallestEdge.next culled += 1
def wireMesh(me): globalSize = Draw.PupFloatInput('wire width', 0.1, -10.0, 10.0, 0.01, 4) if globalSize == None: return globalHalfsz =globalSize/2 ratio = Draw.PupMenu('Edge Width%t|Fixed Width|Scale with edge len') normalMethod = Draw.PupMenu('Use Normal%t|Vertex Normal|Edge Normal') if ratio == -1 or normalMethod == -1: return t = sys.time() origEdgeLen = len(me.edges) origFaceLen = len(me.faces) eIdx = 0 while eIdx < origEdgeLen: e = me.edges[eIdx] if not e.flag & NMesh.EdgeFlags['SELECT']: eIdx+=1 continue if ratio == 1: size = globalSize halfsz = globalHalfsz else: size = globalSize * measure(e.v1, e.v2) halfsz = size/2 if normalMethod == 1: # vertex normal v1nor = makeVNormal(me, e.v1, origEdgeLen) v2nor = makeVNormal(me, e.v2, origEdgeLen) newPt1 = Vector( e.v1.co[0] + (v1nor[0]*size), e.v1.co[1] + (v1nor[1]*size), e.v1.co[2] + (v1nor[2]*size) ) newPt2 = Vector( e.v2.co[0] + (v2nor[0]*size), e.v2.co[1] + (v2nor[1]*size), e.v2.co[2] + (v2nor[2]*size) ) if normalMethod == 2: # Face Normal edgeNormal = Vector([e.v1.no[0] + e.v2.no[0], e.v1.no[1]+e.v2.no[1], e.v1.no[2]+e.v2.no[2] ]) edgeNormal.normalize() newPt1 = Vector( e.v1.co[0] + (edgeNormal[0]*size), e.v1.co[1] + (edgeNormal[1]*size), e.v1.co[2] + (edgeNormal[2]*size) ) newPt2 = Vector( e.v2.co[0] + (edgeNormal[0]*size), e.v2.co[1] + (edgeNormal[1]*size), e.v2.co[2] + (edgeNormal[2]*size) ) # #~ newPt1************newPt2 # #~ * * # #~ * * # #~ * * # #~ ****vert1**************Vert2**** * * * * norA = TriangleNormal(e.v1.co, e.v2.co, newPt1) norB = TriangleNormal(e.v2.co, newPt2, newPt1) nor1 = norA + norB nor1.normalize() # make face A me.verts.append(NMesh.Vert(e.v1.co[0] + (nor1[0]*halfsz), e.v1.co[1] + (nor1[1]*halfsz), e.v1.co[2] + (nor1[2]*halfsz)) ) me.verts.append(NMesh.Vert(e.v2.co[0] + (nor1[0]*halfsz), e.v2.co[1] + (nor1[1]*halfsz), e.v2.co[2] + (nor1[2]*halfsz)) ) me.verts.append(NMesh.Vert(newPt2[0] + (nor1[0]*halfsz), newPt2[1] + (nor1[1]*halfsz), newPt2[2] + (nor1[2]*halfsz)) ) me.verts.append(NMesh.Vert(newPt1[0] + (nor1[0]*halfsz), newPt1[1] + (nor1[1]*halfsz), newPt1[2] + (nor1[2]*halfsz)) ) fA = NMesh.Face(me.verts[-4:]) # make face B me.verts.append(NMesh.Vert(e.v1.co[0] + (nor1[0]*-halfsz), e.v1.co[1] + (nor1[1]*-halfsz), e.v1.co[2] + (nor1[2]*-halfsz)) ) me.verts.append(NMesh.Vert(e.v2.co[0] + (nor1[0]*-halfsz), e.v2.co[1] + (nor1[1]*-halfsz), e.v2.co[2] + (nor1[2]*-halfsz)) ) me.verts.append(NMesh.Vert(newPt2[0] + (nor1[0]*-halfsz), newPt2[1] + (nor1[1]*-halfsz), newPt2[2] + (nor1[2]*-halfsz)) ) me.verts.append(NMesh.Vert(newPt1[0] + (nor1[0]*-halfsz), newPt1[1] + (nor1[1]*-halfsz), newPt1[2] + (nor1[2]*-halfsz)) ) fB = NMesh.Face([me.verts[-1], me.verts[-2], me.verts[-3], me.verts[-4]]) # make face C- top fC = NMesh.Face([fB.v[1], fB.v[0], fA.v[3], fA.v[2]]) # make face D- bottom fD = NMesh.Face([fA.v[1], fA.v[0], fB.v[3], fB.v[2]]) # a negative number is used for an inset wire- this flips the normals the wrong way- For easyness this is a simple way to fix the problem. if globalSize < 0: fA.v.reverse() fB.v.reverse() fC.v.reverse() fD.v.reverse() me.faces.extend([fA, fB, fC, fD]) eIdx+=1 print 'Wire Time: %.6f' % (sys.time()-t)
def main(): global USER_FILL_HOLES global USER_FILL_HOLES_QUALITY global USER_STRETCH_ASPECT global USER_ISLAND_MARGIN objects = bpy.data.scenes.active.objects # we can will tag them later. obList = [ob for ob in objects.context if ob.type == 'Mesh'] # Face select object may not be selected. ob = objects.active if ob and ob.sel == 0 and ob.type == 'Mesh': # Add to the list obList = [ob] del objects if not obList: Draw.PupMenu('error, no selected mesh objects') return # Create the variables. USER_PROJECTION_LIMIT = Draw.Create(66) USER_ONLY_SELECTED_FACES = Draw.Create(1) USER_SHARE_SPACE = Draw.Create(1) # Only for hole filling. USER_STRETCH_ASPECT = Draw.Create(1) # Only for hole filling. USER_ISLAND_MARGIN = Draw.Create(0.0) # Only for hole filling. USER_FILL_HOLES = Draw.Create(0) USER_FILL_HOLES_QUALITY = Draw.Create(50) # Only for hole filling. USER_VIEW_INIT = Draw.Create(0) # Only for hole filling. USER_AREA_WEIGHT = Draw.Create(1) # Only for hole filling. pup_block = [\ 'Projection',\ ('Angle Limit:', USER_PROJECTION_LIMIT, 1, 89, 'lower for more projection groups, higher for less distortion.'),\ ('Selected Faces Only', USER_ONLY_SELECTED_FACES, 'Use only selected faces from all selected meshes.'),\ ('Init from view', USER_VIEW_INIT, 'The first projection will be from the view vector.'),\ ('Area Weight', USER_AREA_WEIGHT, 'Weight projections vector by face area.'),\ '',\ '',\ '',\ 'UV Layout',\ ('Share Tex Space', USER_SHARE_SPACE, 'Objects Share texture space, map all objects into 1 uvmap.'),\ ('Stretch to bounds', USER_STRETCH_ASPECT, 'Stretch the final output to texture bounds.'),\ ('Island Margin:', USER_ISLAND_MARGIN, 0.0, 0.5, 'Margin to reduce bleed from adjacent islands.'),\ 'Fill in empty areas',\ ('Fill Holes', USER_FILL_HOLES, 'Fill in empty areas reduced texture waistage (slow).'),\ ('Fill Quality:', USER_FILL_HOLES_QUALITY, 1, 100, 'Depends on fill holes, how tightly to fill UV holes, (higher is slower)'),\ ] # Reuse variable if len(obList) == 1: ob = "Unwrap %i Selected Mesh" else: ob = "Unwrap %i Selected Meshes" # HACK, loop until mouse is lifted. ''' while Window.GetMouseButtons() != 0: sys.sleep(10) ''' if not Draw.PupBlock(ob % len(obList), pup_block): return del ob # Convert from being button types USER_PROJECTION_LIMIT = USER_PROJECTION_LIMIT.val USER_ONLY_SELECTED_FACES = USER_ONLY_SELECTED_FACES.val USER_SHARE_SPACE = USER_SHARE_SPACE.val USER_STRETCH_ASPECT = USER_STRETCH_ASPECT.val USER_ISLAND_MARGIN = USER_ISLAND_MARGIN.val USER_FILL_HOLES = USER_FILL_HOLES.val USER_FILL_HOLES_QUALITY = USER_FILL_HOLES_QUALITY.val USER_VIEW_INIT = USER_VIEW_INIT.val USER_AREA_WEIGHT = USER_AREA_WEIGHT.val USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD) USER_PROJECTION_LIMIT_HALF_CONVERTED = cos( (USER_PROJECTION_LIMIT / 2) * DEG_TO_RAD) # Toggle Edit mode is_editmode = Window.EditMode() if is_editmode: Window.EditMode(0) # Assume face select mode! an annoying hack to toggle face select mode because Mesh dosent like faceSelectMode. if USER_SHARE_SPACE: # Sort by data name so we get consistant results try: obList.sort(key=lambda ob: ob.getData(name_only=1)) except: obList.sort(lambda ob1, ob2: cmp(ob1.getData(name_only=1), ob2.getData(name_only=1))) collected_islandList = [] Window.WaitCursor(1) time1 = sys.time() # Tag as False se we dont operate on teh same mesh twice. bpy.data.meshes.tag = False for ob in obList: me = ob.getData(mesh=1) if me.tag or me.lib: continue # Tag as used me.tag = True if not me.faceUV: # Mesh has no UV Coords, dont bother. me.faceUV = True if USER_ONLY_SELECTED_FACES: meshFaces = [thickface(f) for f in me.faces if f.sel] else: meshFaces = map(thickface, me.faces) if not meshFaces: continue Window.DrawProgressBar( 0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces))) # ======= # Generate a projection list from face normals, this is ment to be smart :) # make a list of face props that are in sync with meshFaces # Make a Face List that is sorted by area. # meshFaces = [] # meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first. try: meshFaces.sort(key=lambda a: -a.area) except: meshFaces.sort(lambda a, b: cmp(b.area, a.area)) # remove all zero area faces while meshFaces and meshFaces[-1].area <= SMALL_NUM: # Set their UV's to 0,0 for uv in meshFaces[-1].uv: uv.zero() meshFaces.pop() # Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data. # Generate Projection Vecs # 0d is 1.0 # 180 IS -0.59846 # Initialize projectVecs if USER_VIEW_INIT: # Generate Projection projectVecs = [ Vector(Window.GetViewVector()) * ob.matrixWorld.copy().invert().rotationPart() ] # We add to this allong the way else: projectVecs = [] newProjectVec = meshFaces[0].no newProjectMeshFaces = [] # Popping stuffs it up. # Predent that the most unique angke is ages away to start the loop off mostUniqueAngle = -1.0 # This is popped tempMeshFaces = meshFaces[:] # This while only gathers projection vecs, faces are assigned later on. while 1: # If theres none there then start with the largest face # add all the faces that are close. for fIdx in xrange(len(tempMeshFaces) - 1, -1, -1): # Use half the angle limit so we dont overweight faces towards this # normal and hog all the faces. if newProjectVec.dot(tempMeshFaces[fIdx].no ) > USER_PROJECTION_LIMIT_HALF_CONVERTED: newProjectMeshFaces.append(tempMeshFaces.pop(fIdx)) # Add the average of all these faces normals as a projectionVec averageVec = Vector(0, 0, 0) if USER_AREA_WEIGHT: for fprop in newProjectMeshFaces: averageVec += (fprop.no * fprop.area) else: for fprop in newProjectMeshFaces: averageVec += fprop.no if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN projectVecs.append(averageVec.normalize()) # Get the next vec! # Pick the face thats most different to all existing angles :) mostUniqueAngle = 1.0 # 1.0 is 0d. no difference. mostUniqueIndex = 0 # dummy for fIdx in xrange(len(tempMeshFaces) - 1, -1, -1): angleDifference = -1.0 # 180d difference. # Get the closest vec angle we are to. for p in projectVecs: temp_angle_diff = p.dot(tempMeshFaces[fIdx].no) if angleDifference < temp_angle_diff: angleDifference = temp_angle_diff if angleDifference < mostUniqueAngle: # We have a new most different angle mostUniqueIndex = fIdx mostUniqueAngle = angleDifference if mostUniqueAngle < USER_PROJECTION_LIMIT_CONVERTED: #print 'adding', mostUniqueAngle, USER_PROJECTION_LIMIT, len(newProjectMeshFaces) # Now weight the vector to all its faces, will give a more direct projection # if the face its self was not representive of the normal from surrounding faces. newProjectVec = tempMeshFaces[mostUniqueIndex].no newProjectMeshFaces = [tempMeshFaces.pop(mostUniqueIndex)] else: if len(projectVecs) >= 1: # Must have at least 2 projections break # If there are only zero area faces then its possible # there are no projectionVecs if not len(projectVecs): Draw.PupMenu( 'error, no projection vecs where generated, 0 area faces can cause this.' ) return faceProjectionGroupList = [[] for i in xrange(len(projectVecs))] # MAP and Arrange # We know there are 3 or 4 faces here for fIdx in xrange(len(meshFaces) - 1, -1, -1): fvec = meshFaces[fIdx].no i = len(projectVecs) # Initialize first bestAng = fvec.dot(projectVecs[0]) bestAngIdx = 0 # Cycle through the remaining, first alredy done while i - 1: i -= 1 newAng = fvec.dot(projectVecs[i]) if newAng > bestAng: # Reverse logic for dotvecs bestAng = newAng bestAngIdx = i # Store the area for later use. faceProjectionGroupList[bestAngIdx].append(meshFaces[fIdx]) # Cull faceProjectionGroupList, # Now faceProjectionGroupList is full of faces that face match the project Vecs list for i in xrange(len(projectVecs)): # Account for projectVecs having no faces. if not faceProjectionGroupList[i]: continue # Make a projection matrix from a unit length vector. MatProj = VectoMat(projectVecs[i]) # Get the faces UV's from the projected vertex. for f in faceProjectionGroupList[i]: f_uv = f.uv for j, v in enumerate(f.v): f_uv[j][:] = (MatProj * v.co)[:2] if USER_SHARE_SPACE: # Should we collect and pack later? islandList = getUvIslands(faceProjectionGroupList, me) collected_islandList.extend(islandList) else: # Should we pack the islands for this 1 object? islandList = getUvIslands(faceProjectionGroupList, me) packIslands(islandList) # update the mesh here if we need to. # We want to pack all in 1 go, so pack now if USER_SHARE_SPACE: Window.DrawProgressBar(0.9, "Box Packing for all objects...") packIslands(collected_islandList) print "Smart Projection time: %.2f" % (sys.time() - time1) # Window.DrawProgressBar(0.9, "Smart Projections done, time: %.2f sec." % (sys.time() - time1)) if is_editmode: Window.EditMode(1) Window.DrawProgressBar(1.0, "") Window.WaitCursor(0) Window.RedrawAll()
def uvcalc_main(obList): global USER_FILL_HOLES global USER_FILL_HOLES_QUALITY global USER_STRETCH_ASPECT global USER_ISLAND_MARGIN # objects= bpy.data.scenes.active.objects # we can will tag them later. # obList = [ob for ob in objects.context if ob.type == 'Mesh'] # Face select object may not be selected. # ob = objects.active # if ob and ob.sel == 0 and ob.type == 'Mesh': # # Add to the list # obList =[ob] # del objects if not obList: Draw.PupMenu("error, no selected mesh objects") return # Create the variables. USER_PROJECTION_LIMIT = Draw.Create(66) USER_ONLY_SELECTED_FACES = Draw.Create(1) USER_SHARE_SPACE = Draw.Create(1) # Only for hole filling. USER_STRETCH_ASPECT = Draw.Create(1) # Only for hole filling. USER_ISLAND_MARGIN = Draw.Create(0.0) # Only for hole filling. USER_FILL_HOLES = Draw.Create(0) USER_FILL_HOLES_QUALITY = Draw.Create(50) # Only for hole filling. USER_VIEW_INIT = Draw.Create(0) # Only for hole filling. USER_AREA_WEIGHT = Draw.Create(1) # Only for hole filling. pup_block = [ "Projection", ("Angle Limit:", USER_PROJECTION_LIMIT, 1, 89, "lower for more projection groups, higher for less distortion."), ("Selected Faces Only", USER_ONLY_SELECTED_FACES, "Use only selected faces from all selected meshes."), ("Init from view", USER_VIEW_INIT, "The first projection will be from the view vector."), ("Area Weight", USER_AREA_WEIGHT, "Weight projections vector by face area."), "", "", "", "UV Layout", ("Share Tex Space", USER_SHARE_SPACE, "Objects Share texture space, map all objects into 1 uvmap."), ("Stretch to bounds", USER_STRETCH_ASPECT, "Stretch the final output to texture bounds."), ("Island Margin:", USER_ISLAND_MARGIN, 0.0, 0.5, "Margin to reduce bleed from adjacent islands."), "Fill in empty areas", ("Fill Holes", USER_FILL_HOLES, "Fill in empty areas reduced texture waistage (slow)."), ( "Fill Quality:", USER_FILL_HOLES_QUALITY, 1, 100, "Depends on fill holes, how tightly to fill UV holes, (higher is slower)", ), ] # Reuse variable if len(obList) == 1: ob = "Unwrap %i Selected Mesh" else: ob = "Unwrap %i Selected Meshes" # if not Draw.PupBlock(ob % len(obList), pup_block): # return # del ob # Convert from being button types USER_PROJECTION_LIMIT = USER_PROJECTION_LIMIT.val USER_ONLY_SELECTED_FACES = USER_ONLY_SELECTED_FACES.val USER_SHARE_SPACE = USER_SHARE_SPACE.val USER_STRETCH_ASPECT = USER_STRETCH_ASPECT.val USER_ISLAND_MARGIN = USER_ISLAND_MARGIN.val USER_FILL_HOLES = USER_FILL_HOLES.val USER_FILL_HOLES_QUALITY = USER_FILL_HOLES_QUALITY.val USER_VIEW_INIT = USER_VIEW_INIT.val USER_AREA_WEIGHT = USER_AREA_WEIGHT.val USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD) USER_PROJECTION_LIMIT_HALF_CONVERTED = cos((USER_PROJECTION_LIMIT / 2) * DEG_TO_RAD) # Toggle Edit mode # is_editmode = Window.EditMode() # if is_editmode: # Window.EditMode(0) # Assume face select mode! an annoying hack to toggle face select mode because Mesh dosent like faceSelectMode. if USER_SHARE_SPACE: # Sort by data name so we get consistant results try: obList.sort(key=lambda ob: ob.getData(name_only=1)) except: obList.sort(lambda ob1, ob2: cmp(ob1.getData(name_only=1), ob2.getData(name_only=1))) collected_islandList = [] # Window.WaitCursor(1) time1 = sys.time() # Tag as False se we dont operate on teh same mesh twice. bpy.data.meshes.tag = False for ob in obList: me = ob.getData(mesh=1) if me.tag or me.lib: continue # Tag as used me.tag = True if not me.faceUV: # Mesh has no UV Coords, dont bother. me.faceUV = True if USER_ONLY_SELECTED_FACES: meshFaces = [thickface(f) for f in me.faces if f.sel] else: meshFaces = map(thickface, me.faces) if not meshFaces: continue # Window.DrawProgressBar(0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces))) # ======= # Generate a projection list from face normals, this is ment to be smart :) # make a list of face props that are in sync with meshFaces # Make a Face List that is sorted by area. # meshFaces = [] # meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first. try: meshFaces.sort(key=lambda a: -a.area) except: meshFaces.sort(lambda a, b: cmp(b.area, a.area)) # remove all zero area faces while meshFaces and meshFaces[-1].area <= SMALL_NUM: # Set their UV's to 0,0 for uv in meshFaces[-1].uv: uv.zero() meshFaces.pop() # Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data. # Generate Projection Vecs # 0d is 1.0 # 180 IS -0.59846 # Initialize projectVecs if USER_VIEW_INIT: # Generate Projection projectVecs = [ Vector(Window.GetViewVector()) * ob.matrixWorld.copy().invert().rotationPart() ] # We add to this allong the way else: projectVecs = [] newProjectVec = meshFaces[0].no newProjectMeshFaces = [] # Popping stuffs it up. # Predent that the most unique angke is ages away to start the loop off mostUniqueAngle = -1.0 # This is popped tempMeshFaces = meshFaces[:] # This while only gathers projection vecs, faces are assigned later on. while 1: # If theres none there then start with the largest face # add all the faces that are close. for fIdx in xrange(len(tempMeshFaces) - 1, -1, -1): # Use half the angle limit so we dont overweight faces towards this # normal and hog all the faces. if newProjectVec.dot(tempMeshFaces[fIdx].no) > USER_PROJECTION_LIMIT_HALF_CONVERTED: newProjectMeshFaces.append(tempMeshFaces.pop(fIdx)) # Add the average of all these faces normals as a projectionVec averageVec = Vector(0, 0, 0) if USER_AREA_WEIGHT: for fprop in newProjectMeshFaces: averageVec += fprop.no * fprop.area else: for fprop in newProjectMeshFaces: averageVec += fprop.no if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN projectVecs.append(averageVec.normalize()) # Get the next vec! # Pick the face thats most different to all existing angles :) mostUniqueAngle = 1.0 # 1.0 is 0d. no difference. mostUniqueIndex = 0 # dummy for fIdx in xrange(len(tempMeshFaces) - 1, -1, -1): angleDifference = -1.0 # 180d difference. # Get the closest vec angle we are to. for p in projectVecs: temp_angle_diff = p.dot(tempMeshFaces[fIdx].no) if angleDifference < temp_angle_diff: angleDifference = temp_angle_diff if angleDifference < mostUniqueAngle: # We have a new most different angle mostUniqueIndex = fIdx mostUniqueAngle = angleDifference if mostUniqueAngle < USER_PROJECTION_LIMIT_CONVERTED: # print 'adding', mostUniqueAngle, USER_PROJECTION_LIMIT, len(newProjectMeshFaces) # Now weight the vector to all its faces, will give a more direct projection # if the face its self was not representive of the normal from surrounding faces. newProjectVec = tempMeshFaces[mostUniqueIndex].no newProjectMeshFaces = [tempMeshFaces.pop(mostUniqueIndex)] else: if len(projectVecs) >= 1: # Must have at least 2 projections break # If there are only zero area faces then its possible # there are no projectionVecs if not len(projectVecs): Draw.PupMenu("error, no projection vecs where generated, 0 area faces can cause this.") return faceProjectionGroupList = [[] for i in xrange(len(projectVecs))] # MAP and Arrange # We know there are 3 or 4 faces here for fIdx in xrange(len(meshFaces) - 1, -1, -1): fvec = meshFaces[fIdx].no i = len(projectVecs) # Initialize first bestAng = fvec.dot(projectVecs[0]) bestAngIdx = 0 # Cycle through the remaining, first alredy done while i - 1: i -= 1 newAng = fvec.dot(projectVecs[i]) if newAng > bestAng: # Reverse logic for dotvecs bestAng = newAng bestAngIdx = i # Store the area for later use. faceProjectionGroupList[bestAngIdx].append(meshFaces[fIdx]) # Cull faceProjectionGroupList, # Now faceProjectionGroupList is full of faces that face match the project Vecs list for i in xrange(len(projectVecs)): # Account for projectVecs having no faces. if not faceProjectionGroupList[i]: continue # Make a projection matrix from a unit length vector. MatProj = VectoMat(projectVecs[i]) # Get the faces UV's from the projected vertex. for f in faceProjectionGroupList[i]: f_uv = f.uv for j, v in enumerate(f.v): f_uv[j][:] = (MatProj * v.co)[:2] if USER_SHARE_SPACE: # Should we collect and pack later? islandList = getUvIslands(faceProjectionGroupList, me) collected_islandList.extend(islandList) else: # Should we pack the islands for this 1 object? islandList = getUvIslands(faceProjectionGroupList, me) packIslands(islandList) # update the mesh here if we need to. # We want to pack all in 1 go, so pack now if USER_SHARE_SPACE: # Window.DrawProgressBar(0.9, "Box Packing for all objects...") packIslands(collected_islandList) print "Smart Projection time: %.2f" % (sys.time() - time1)
class edgeLoop(object): __slots__ = 'centre', 'edges', 'normal', 'closed', 'backup_edges' def __init__(self, loop, me, closed): # Vert loop # Use next and prev, nextDist, prevDist # Get Loops centre. fac= len(loop) verts = me.verts self.centre= reduce(lambda a,b: a+verts[b].co/fac, loop, Vector()) # Convert Vert loop to Edges. self.edges = [edge(verts[loop[vIdx-1]], verts[loop[vIdx]]) for vIdx in xrange(len(loop))] if not closed: self.edges[0].fake = True # fake edge option self.closed = closed # Assign linked list for eIdx in xrange(len(self.edges)-1): self.edges[eIdx].next = self.edges[eIdx+1] self.edges[eIdx].prev = self.edges[eIdx-1] # Now last self.edges[-1].next = self.edges[0] self.edges[-1].prev = self.edges[-2] # GENERATE AN AVERAGE NORMAL FOR THE WHOLE LOOP. self.normal = Vector() for e in self.edges: n = (self.centre-e.co1).cross(self.centre-e.co2) # Do we realy need tot normalize? n.normalize() self.normal += n # Generate the angle va= e.cent - e.prev.cent vb= e.next.cent - e.cent e.angle= AngleBetweenVecs(va, vb) # Blur the angles #for e in self.edges: # e.angle= (e.angle+e.next.angle)/2 # Blur the angles #for e in self.edges: # e.angle= (e.angle+e.prev.angle)/2 self.normal.normalize() # Generate a normal for each edge. for e in self.edges: n1 = e.co1 n2 = e.co2 n3 = e.prev.co1 a = n1-n2 b = n1-n3 normal1 = a.cross(b) normal1.normalize() n1 = e.co2 n3 = e.next.co2 n2 = e.co1 a = n1-n2 b = n1-n3 normal2 = a.cross(b) normal2.normalize() # Reuse normal1 var normal1 += normal1 + normal2 normal1.normalize() e.normal = normal1 #print e.normal def backup(self): # Keep a backup of the edges self.backup_edges = self.edges[:] def restore(self): self.edges = self.backup_edges[:] for e in self.edges: e.removed = 0 def reverse(self): self.edges.reverse() self.normal.negate() for e in self.edges: e.normal.negate() e.v1, e.v2 = e.v2, e.v1 e.co1, e.co2 = e.co2, e.co1 e.next, e.prev = e.prev, e.next def removeSmallest(self, cullNum, otherLoopLen): ''' Removes N Smallest edges and backs up the loop, this is so we can loop between 2 loops as if they are the same length, backing up and restoring incase the loop needs to be skinned with another loop of a different length. ''' global CULL_METHOD if CULL_METHOD == 1: # Shortest edge eloopCopy = self.edges[:] # Length sort, smallest first try: eloopCopy.sort(key = lambda e1: e1.length) except: eloopCopy.sort(lambda e1, e2: cmp(e1.length, e2.length )) # Dont use atm #eloopCopy.sort(lambda e1, e2: cmp(e1.angle*e1.length, e2.angle*e2.length)) # Length sort, smallest first #eloopCopy.sort(lambda e1, e2: cmp(e1.angle, e2.angle)) # Length sort, smallest first remNum = 0 for i, e in enumerate(eloopCopy): if not e.fake: e.removed = 1 self.edges.remove( e ) # Remove from own list, still in linked list. remNum += 1 if not remNum < cullNum: break else: # CULL METHOD is even culled = 0 step = int(otherLoopLen / float(cullNum)) * 2 currentEdge = self.edges[0] while culled < cullNum: # Get the shortest face in the next STEP step_count= 0 bestAng= 360.0 smallestEdge= None while step_count<=step or smallestEdge==None: step_count+=1 if not currentEdge.removed: # 0 or -1 will not be accepted if currentEdge.angle<bestAng and not currentEdge.fake: smallestEdge= currentEdge bestAng= currentEdge.angle currentEdge = currentEdge.next # In that stepping length we have the smallest edge.remove it smallestEdge.removed = 1 self.edges.remove(smallestEdge) # Start scanning from the edge we found? - result is over fanning- no good. #currentEdge= smallestEdge.next culled+=1