diff options
author | Willian Padovani Germano <wpgermano@gmail.com> | 2005-12-18 04:29:15 +0300 |
---|---|---|
committer | Willian Padovani Germano <wpgermano@gmail.com> | 2005-12-18 04:29:15 +0300 |
commit | 9d55687d305925987fe2cb1133a6fcbd89ee765b (patch) | |
tree | e3bd183e20e5494ac18aea21f3ca9ff7325fc4d3 /release | |
parent | 66d0d7e2dbd327988b7418f5ce1bd1cd4beb5927 (diff) |
Scripts:
- A Vanpoucke (xand) updated Axis Orientation Copy.
- Additions:
- Campbell Barton contributed the Archimap UV Unwrapper.
- Mikael Lagre contributed Collada importer and exporter (ongoing
work, no support for animation yet).
Thanks to them!
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/Axiscopy.py | 18 | ||||
-rw-r--r-- | release/scripts/archimap.py | 1634 |
2 files changed, 1643 insertions, 9 deletions
diff --git a/release/scripts/Axiscopy.py b/release/scripts/Axiscopy.py index 0a9a411ef08..a6fa2e8b627 100644 --- a/release/scripts/Axiscopy.py +++ b/release/scripts/Axiscopy.py @@ -2,7 +2,7 @@ """ Registration info for Blender menus: <- these words are ignored Name: 'Axis Orientation Copy' -Blender: 233 +Blender: 239 Group: 'Object' Tip: 'Copy the axis orientation of the active object to all selected mesh objects' """ @@ -10,7 +10,7 @@ Tip: 'Copy the axis orientation of the active object to all selected mesh object __author__ = "A Vanpoucke (xand)" __url__ = ("blender", "elysiun", "French Blender support forum, http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender") -__version__ = "1.1 11/05/04" +__version__ = "2 17/12/05" __bpydoc__ = """\ This script copies the axis orientation -- X, Y and Z rotations -- of the @@ -75,7 +75,7 @@ from Blender.Mathutils import * def applyTransform(mesh,mat): for v in mesh.verts: - vec = VecMultMat(v.co,mat) + vec = v.co*mat v.co[0], v.co[1], v.co[2] = vec[0], vec[1], vec[2] @@ -87,13 +87,13 @@ lenob=len(oblist) error = 0 for o in oblist[1:]: if o.getType() != "Mesh": - Draw.PupMenu("ERROR%t|Selected objects must be meshes") + Draw.PupMenu("Error: selected objects must be meshes") error = 1 if not error: if lenob<2: - Draw.PupMenu("ERROR%t|You must select at least 2 objects") - else : + Draw.PupMenu("Error: you must select at least 2 objects") + else : source=oblist[0] nsource=source.name texte="Copy axis orientation from: " + nsource + " ?%t|OK" @@ -102,9 +102,9 @@ if not error: for cible in oblist[1:]: if source.rot!=cible.rot: - rotcible=cible.mat.toEuler().toMatrix() - rotsource=source.mat.toEuler().toMatrix() - rotsourcet = CopyMat(rotsource) + rotcible=cible.mat.rotationPart().toEuler().toMatrix() + rotsource=source.mat.rotationPart().toEuler().toMatrix() + rotsourcet = Matrix(rotsource) rotsourcet.invert() mat=rotcible*rotsourcet ncible=cible.name diff --git a/release/scripts/archimap.py b/release/scripts/archimap.py new file mode 100644 index 00000000000..02444816634 --- /dev/null +++ b/release/scripts/archimap.py @@ -0,0 +1,1634 @@ +#!BPY + +""" Registration info for Blender menus: <- these words are ignored +Name: 'ArchiMap UV Unwrapper' +Blender: 237 +Group: 'UV' +Tooltip: 'ArchiMap UV Unwrap Mesh faces.' +""" + + +__author__ = "Campbell Barton" +__url__ = ("blender", "elysiun") +__version__ = "1.0 6/13/05" + +__bpydoc__ = """\ +This script projection unwraps the selected faces of a mesh. + +It operates on all selected mesh objects, and can be set to unwrap +selected faces, or all faces. + +""" + +# -------------------------------------------------------------------------- +# Archimap UV Projection Unwrapper v1.0 by Campbell Barton (AKA Ideasman) +# -------------------------------------------------------------------------- +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# 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 2 +# 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, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** +# -------------------------------------------------------------------------- + + + +from Blender import * +from Blender import Mathutils +from Blender.Mathutils import * + +from math import cos, acos,pi,sqrt + + +try: + import sys as py_sys +except: + py_sys = None + + +DEG_TO_RAD = pi/180.0 +SMALL_NUM = 0.000000001 +BIG_NUM = 1e15 + + + +try: + dummy = Mathutils.Matrix( Mathutils.Matrix() ) + del dummy + NEW_2_38_MATHUTILS = True +except TypeError: + NEW_2_38_MATHUTILS = False + + +global USER_FILL_HOLES +global USER_FILL_HOLES_QUALITY +USER_FILL_HOLES = None +USER_FILL_HOLES_QUALITY = None + +# ================================================== USER_BOX_PACK_MOD.py +from Blender import NMesh, Window, sys + +# a box packing vert +class vt: + def __init__(self, x,y): + self.x, self.y = x, y + + self.free = 15 + + # Set flags so cant test bottom left of 0/0 + #~ BLF = 1; TRF = 2; TLF = 4; BRF = 8 + + #self.users = [] # A list of boxes. + # Rather then users, store Quadrents + self.blb = self.tlb = self.brb = self.trb = None + + + # A hack to remember the box() that last intersectec this vert + self.intersectCache = ([], [], [], []) + +class vertList: + def __init__(self, verts=[]): + self.verts = verts + + # Sorts closest first. - uses the box w/h as a bias, + # this makes it so its less likely to have lots of poking out bits + # that use too much + # Lambada based sort + def sortCorner(self,w,h): + self.verts.sort(lambda A, B: cmp(max(A.x+w, A.y+h) , max(B.x+w, B.y+h))) # Reverse area sort + + +class box: + global packedVerts + def __init__(self, width, height, id=None): + global packedVerts + + self.id= id + + self.area = width * height # real area + self.farea = width + height # fake area + #self.farea = float(min(width, height)) / float(max(width, height)) # fake area + + self.width = width + self.height = height + + # Append 4 new verts + # (BL,TR,TL,BR) / 0,1,2,3 + self.v = [vt(0,0), vt(width,height), vt(0,height), vt(width,0)] + + # Set the interior quadrents as used. + self.v[0].free &= ~TRF + self.v[1].free &= ~BLF + self.v[2].free &= ~BRF + self.v[3].free &= ~TLF + + #for v in self.v: + # v.users.append(self) + self.v[0].trb = self + self.v[1].blb = self + self.v[2].brb = self + self.v[3].tlb = self + + + + # Updates verts 3 & 4 from 1 and 2 + # since 3 and 4 are only there foill need is resizing/ rotating of patterns on the fly while I painr new box placement + # but may be merged later with other verts + def updateV34(self): + + self.v[TL].x = self.v[BL].x + self.v[TL].y = self.v[TR].y + + self.v[BR].x = self.v[TR].x + self.v[BR].y = self.v[BL].y + + + def setLeft(self, lft=None): + self.v[TR].x = lft + self.v[TR].x - self.v[BL].x + self.v[BL].x = lft + # update othere verts + self.updateV34() + + def setRight(self, rgt=None): + self.v[BL].x = rgt - (self.v[TR].x - self.v[BL].x) + self.v[TR].x = rgt + self.updateV34() + + def setBottom(self, btm=None): + self.v[TR].y = btm + self.v[TR].y - self.v[BL].y + self.v[BL].y = btm + self.updateV34() + + def setTop(self, tp=None): + self.v[BL].y = tp - (self.v[TR].y - self.v[BL].y) + self.v[TR].y = tp + self.updateV34() + + def getLeft(self): + return self.v[BL].x + + def getRight(self): + return self.v[TR].x + + def getBottom(self): + return self.v[BL].y + + def getTop(self): + return self.v[TR].y + + # Returns none, meaning it didnt overlap any new boxes + def overlapAll(self, boxLs, intersectCache): # Flag index lets us know which quadere + if self.v[BL].x < 0: + return None + elif self.v[BL].y < 0: + return None + else: + bIdx = len(intersectCache) + while bIdx: + bIdx-=1 + b = intersectCache[bIdx] + if not (self.v[TR].y <= b.v[BL].y or\ + self.v[BL].y >= b.v[TR].y or\ + self.v[BL].x >= b.v[TR].x or\ + self.v[TR].x <= b.v[BL].x ): + + return None # Intersection with existing box + #return 0 # Must keep looking + + + for b in boxLs.boxes: + if not (self.v[TR].y <= b.v[BL].y or\ + self.v[BL].y >= b.v[TR].y or\ + self.v[BL].x >= b.v[TR].x or\ + self.v[TR].x <= b.v[BL].x ): + + return b # Intersection with new box. + return 0 + + + + def place(self, vert, quad): + if quad == BLF: + self.setLeft(vert.x) + self.setBottom(vert.y) + + elif quad == TRF: + self.setRight(vert.x) + self.setBottom(vert.y) + + elif quad == TLF: + self.setLeft(vert.x) + self.setTop(vert.y) + + elif quad == BRF: + self.setRight(vert.x) + self.setTop(vert.y) + + # Trys to lock a box onto another box's verts + # cleans up double verts after + def tryVert(self, boxes, baseVert): + flagIndex = -1 + for freeQuad in quadFlagLs: + flagIndex +=1 + #print 'Testing ', self.width + if not baseVert.free & freeQuad: + continue + + self.place(baseVert, freeQuad) + overlapBox = self.overlapAll(boxes, baseVert.intersectCache[flagIndex]) + if overlapBox == 0: # There is no overlap + baseVert.free &= ~freeQuad # Removes quad + # Appends all verts but the one that matches. this removes the need for remove doubles + for vIdx in range(4): # (BL,TR,TL,BR): # (BL,TR,TL,BR) / 0,1,2,3 + self_v = self.v[vIdx] # shortcut + if not (self_v.x == baseVert.x and self_v.y == baseVert.y): + packedVerts.verts.append(self_v) + else: + baseVert.free &= self.v[vIdx].free # make sure the + + # Inherit used boxes from old verts + if self_v.blb: baseVert.blb = self_v.blb + if self_v.brb: baseVert.brb = self_v.brb #print 'inherit2' + if self_v.tlb: baseVert.tlb = self_v.tlb #print 'inherit3' + if self_v.trb: baseVert.trb = self_v.trb #print 'inherit4' + self.v[vIdx] = baseVert + + + # ========================== WHY DOSENT THIS WORK??? + #~ if baseVert.tlb and baseVert.trb: + #~ if self == baseVert.tlb or self == baseVert.trb: + + #~ if baseVert.tlb.height > baseVert.trb.height: + #~ #baseVert.trb.v[TL].free &= ~TLF & ~BLF + #~ baseVert.trb.v[TL].free &= ~TLF + #~ baseVert.trb.v[TL].free &= ~BLF + + #~ elif baseVert.tlb.height < baseVert.trb.height: + #~ #baseVert.trb.v[TL].free &= ~TLF & ~BLF + #~ baseVert.tlb.v[TR].free &= ~TRF + #~ baseVert.tlb.v[TR].free &= ~BRF + #~ else: # same + #~ baseVert.tlb.v[TR].free &= ~BLF + #~ baseVert.trb.v[TL].free &= ~BRF + + + #~ if baseVert.blb and baseVert.brb: + #~ if self == baseVert.blb or self == baseVert.brb: + + #~ if baseVert.blb.height > baseVert.brb.height: + #~ #baseVert.trb.v[TL].free &= ~TLF & ~BLF + #~ baseVert.brb.v[BL].free &= ~TLF + #~ baseVert.brb.v[BL].free &= ~BLF + + #~ elif baseVert.blb.height < baseVert.brb.height: + #~ #baseVert.trb.v[TL].free &= ~TLF & ~BLF + #~ baseVert.blb.v[BR].free &= ~TRF + #~ baseVert.blb.v[BR].free &= ~BRF + #~ else: # same + #~ baseVert.blb.v[BR].free &= ~TLF + #~ baseVert.brb.v[BL].free &= ~TRF + + #~ # print 'Hay', baseVert.tlb.height, baseVert.trb.height + + + + return 1 # Working + + # We have a box that intersects that quadrent. + elif overlapBox != None: # None is used for a box thats alredt in the freq list. + + # There was an overlap, add this box to the verts list + #quadFlagLs = (BLF,BRF,TLF,TRF) + baseVert.intersectCache[flagIndex].append(overlapBox) + + + + return 0 + + +class boxList: + global packedVerts + def __init__(self, boxes): + self.boxes = boxes + + # keep a running update of the width and height so we know the area + # initialize with first box, fixes but where we whwere only packing 1 box + self.width = 0 + self.height = 0 + if len(boxes) > 0: + for b in boxes: + self.width = max(self.width, b.width) + self.height = max(self.height, b.height) + + + + + # boxArea is the total area of all boxes in the list, + # can be used with packArea() to determine waistage. + self.boxArea = 0 # incremented with addBox() + + + # Just like MyBoxLs.boxes.append(), but sets bounds + def addBoxPack(self, box): + + + # Resize this boxlist bounds for the current box. + self.width = max(self.width, box.getRight()) + self.height = max(self.height, box.getTop()) + + self.boxArea += box.area + + + + # iterate through these + #~ quadFlagLs = (1,8,4,2) + #~ # Flags for vert idx used quads + #~ BLF = 1; TRF = 2; TLF = 4; BRF = 8 + #~ quadFlagLs = (BLF,BRF,TLF,TRF) + + # Look through all the free vert quads and see if there are some we can remove + ''' + for v in box.v: + + # Is my bottom being used. + + if v.free & BLF and v.free & BRF: # BLF and BRF + for b in self.boxes: + if b.v[TR].y == v.y: + if b.v[TR].x > v.x: + if b.v[BL].x < v.x: + v.free &= ~BLF # Removes quad + v.free &= ~BRF # Removes quad + + # Is my left being used. + if v.free & BLF and v.free & TLF: + for b in self.boxes: + if b.v[TR].x == v.x: + if b.v[TR].y > v.y: + if b.v[BL].y < v.y: + v.free &= ~BLF # Removes quad + v.free &= ~TLF # Removes quad + + if v.free & TRF and v.free & TLF: + # Is my top being used. + for b in self.boxes: + if b.v[BL].y == v.y: + if b.v[TR].x > v.x: + if b.v[BL].x < v.x: + v.free &= ~TLF # Removes quad + v.free &= ~TRF # Removes quad + + + # Is my right being used. + if v.free & TRF and v.free & BRF: + for b in self.boxes: + if b.v[BL].x == v.x: + if b.v[TR].y > v.y: + if b.v[BL].y < v.y: + v.free &= ~BRF # Removes quad + v.free &= ~TRF # Removes quad + + ''' + self.boxes.append(box) + + + + # Just like MyBoxLs.boxes.append(), but sets bounds + def addBox(self, box): + self.boxes.append(box) + self.boxArea += box.area + + # The area of the backing bounds. + def packedArea(self): + return self.width * self.height + + # Sort boxes by area + # TODO REPLACE WITH SORT(LAMBDA(CMP...)) + def sortArea(self): + self.boxes.sort(lambda A, B: cmp(B.area, A.area) ) # Reverse area sort + + # BLENDER only + def draw(self): + m = NMesh.GetRaw() + + + for b in self.boxes: + z = min(b.width, b.height ) / max(b.width, b.height ) + #z = b.farea + #z=0 + f = NMesh.Face() + m.verts.append(NMesh.Vert(b.getLeft(), b.getBottom(), z)) + f.v.append(m.verts[-1]) + m.verts.append(NMesh.Vert(b.getRight(), b.getBottom(), z)) + f.v.append(m.verts[-1]) + m.verts.append(NMesh.Vert(b.getRight(), b.getTop(), z)) + f.v.append(m.verts[-1]) + m.verts.append(NMesh.Vert(b.getLeft(), b.getTop(), z)) + f.v.append(m.verts[-1]) + m.faces.append(f) + NMesh.PutRaw(m, 's') + Window.Redraw(1) + + def pack(self): + global packedVerts + packedVerts = vertList() + + self.sortArea() + + if len(self.boxes) == 0: + return + + packedboxes = boxList([self.boxes[0]]) + + # Remove verts we KNOW cant be added to + + unpackedboxes = boxList(self.boxes[1:]) + + # STart with this box + packedVerts.verts.extend(packedboxes.boxes[0].v) + + while unpackedboxes.boxes != []: + + freeBoxIdx = 0 + while freeBoxIdx < len(unpackedboxes.boxes): + + # Sort the verts with this boxes dimensions as a bias, so less poky out bits are made. + packedVerts.sortCorner(unpackedboxes.boxes[freeBoxIdx].width, unpackedboxes.boxes[freeBoxIdx].height) + + vertIdx = 0 + + while vertIdx < len(packedVerts.verts): + baseVert = packedVerts.verts[vertIdx] + + if baseVert.free != 0: + # This will lock the box if its possibel + if unpackedboxes.boxes[freeBoxIdx].tryVert(packedboxes, baseVert): + packedboxes.addBoxPack(unpackedboxes.boxes[freeBoxIdx]) + unpackedboxes.boxes.pop(freeBoxIdx) + freeBoxIdx = -1 + break + + vertIdx +=1 + freeBoxIdx +=1 + self.width = packedboxes.width + self.height = packedboxes.height + # All boxes as a list - X/Y/WIDTH/HEIGHT + def list(self): + ls = [] + for b in self.boxes: + ls.append( (b.id, b.getLeft(), b.getBottom(), b.width, b.height ) ) + return ls + + +''' Define all globals here ''' +# vert IDX's, make references easier to understand. +BL = 0; TR = 1; TL = 2; BR = 3 + +# iterate through these +quadFlagLs = (1,8,4,2) +# Flags for vert idx used quads +BLF = 1; TRF = 2; TLF = 4; BRF = 8 +quadFlagLs = (BLF,BRF,TLF,TRF) + + +# Global vert pool, stores used lists +packedVerts = vertList() + + +# Packs a list w/h's into box types and places then #Iter times +def boxPackIter(boxLs, iter=1, draw=0): + iterIdx = 0 + bestArea = None + # Iterate over packing the boxes to get the best FIT! + while iterIdx < iter: + myBoxLs = boxList([]) + for b in boxLs: + myBoxLs.addBox( box(b[1], b[2], b[0]) ) # w/h/id + + myBoxLs.pack() + # myBoxLs.draw() # Draw as we go? + + newArea = myBoxLs.packedArea() + + #print 'pack test %s of %s, area:%.2f' % (iterIdx, iter, newArea) + + # First time? + if bestArea == None: + bestArea = newArea + bestBoxLs = myBoxLs + elif newArea < bestArea: + bestArea = newArea + bestBoxLs = myBoxLs + iterIdx+=1 + + + if draw: + bestBoxLs.draw() + + #print 'best area: %.4f, %.2f%% efficient' % (bestArea, (float(bestBoxLs.boxArea) / (bestArea+0.000001))*100) + return bestBoxLs.width, bestBoxLs.height, bestBoxLs.list() +# END USER_BOX_PACK_MOD.py + +# ============================================================== + + +# Box Packer is included for distrobution. +#import box_pack_mod +#reload(box_pack_mod) + + + + +# Do 2 lines intersect, if so where, DOSENT HANDLE HOZ/VERT LINES!!! + +def lineIntersection2D(x1,y1, x2,y2, _x1,_y1, _x2,_y2): + + # Bounding box intersection first. + if min(x1, x2) > max(_x1, _x2) or \ + max(x1, x2) < min(_x1, _x2) or \ + min(y1, y2) > max(_y1, _y2) or \ + max(y1, y2) < min(_y1, _y2): + return None, None # BAsic Bounds intersection TEST returns false. + + # are either of the segments points? Check Seg1 + if abs(x1 - x2) + abs(y1 - y2) <= SMALL_NUM: + return None, None + + # are either of the segments points? Check Seg2 + if abs(_x1 - _x2) + abs(_y1 - _y2) <= SMALL_NUM: + return None, None + + + # Make sure the HOZ/Vert Line Comes first. + if abs(_x1 - _x2) < SMALL_NUM or abs(_y1 - _y2) < SMALL_NUM: + x1, x2, y1, y2, _x1, _x2, _y1, _y2 = _x1, _x2, _y1, _y2, x1, x2, y1, y2 + + + if abs(x2-x1) < SMALL_NUM: # VERTICLE LINE + if abs(_x2-_x1) < SMALL_NUM: # VERTICLE LINE SEG2 + return None, None # 2 verticle lines dont intersect. + + elif abs(_y2-_y1) < SMALL_NUM: + return x1, _y1 # X of vert, Y of hoz. no calculation. + + yi = ((_y1 / abs(_x1 - _x2)) * abs(_x2 - x1)) + ((_y2 / abs(_x1 - _x2)) * abs(_x1 - x1)) + + if yi > max(y1, y2): # New point above seg1's vert line + return None, None + elif yi < min(y1, y2): # New point below seg1's vert line + return None, None + + return x1, yi # Intersecting. + + if abs(y2-y1) < SMALL_NUM: # HOZ LINE + if abs(_y2-_y1) < SMALL_NUM: # HOZ LINE SEG2 + return None, None # 2 hoz lines dont intersect. + + # Can skip vert line check for seg 2 since its covered above. + xi = ((_x1 / abs(_y1 - _y2)) * abs(_y2 - y1)) + ((_x2 / abs(_y1 - _y2)) * abs(_y1 - y1)) + if xi > max(x1, x2): # New point right of seg1's hoz line + return None, None + elif xi < min(x1, x2): # New point left of seg1's hoz line + return None, None + + return xi, y1 # Intersecting. + + + # ACCOUNTED FOR HOZ/VERT LINES. GO ON WITH BOTH ANGLULAR. + + b1 = (y2-y1)/(x2-x1) + b2 = (_y2-_y1)/(_x2-_x1) + a1 = y1-b1*x1 + a2 = _y1-b2*_x1 + + if b1 - b2 == 0.0: + return None, None + + xi = - (a1-a2)/(b1-b2) + yi = a1+b1*xi + if (x1-xi)*(xi-x2) >= 0 and (_x1-xi)*(xi-_x2) >= 0 and (y1-yi)*(yi-y2) >= 0 and (_y1-yi)*(yi-_y2)>=0: + return xi, yi + else: + return None, None + +def triArea(p1, p2, p3): + return CrossVecs(p1-p2, p3-p2).length/2 + +# IS a point inside our triangle? +''' +def pointInTri2D(PT, triP1, triP2, triP3): + def triArea(p1, p2, p3): + return CrossVecs(p1-p2, p3-p2).length /2 + area1 = triArea(PT, triP2, triP3) + area2 = triArea(PT, triP1, triP3) + area3 = triArea(PT, triP1, triP2) + triArea = triArea(triP1, triP2, triP3) + if area1 + area2 + area3 > triArea+0.01: + return False + else: + return True +''' + +dict_matrix = {} + +def pointInTri2D(v, v1, v2, v3): + global dict_matrix + + key = (v1.x, v1.y, v2.x, v2.y, v3.x, v3.y) + + try: + mtx = dict_matrix[key] + if not mtx: + return False + except: + side1 = v2 - v1 + side2 = v3 - v1 + + nor = Mathutils.CrossVecs(side1, side2) + + l1 = [side1[0], side1[1], side1[2]] + l2 = [side2[0], side2[1], side2[2]] + l3 = [nor[0], nor[1], nor[2]] + + mtx = Mathutils.Matrix(l1, l2, l3) + + # Zero area 2d tri, even tho we throw away zerop area faces + # the projection UV can result in a zero area UV. + if not mtx.determinant(): + dict_matrix[key] = None + return False + + mtx.invert() + + dict_matrix[key] = mtx + + if NEW_2_38_MATHUTILS: + uvw = (v - v1) * mtx + else: + uvw = Mathutils.VecMultMat(v - v1, mtx) + + return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1 + + + +def faceArea(f): + if len(f) == 3: + return triArea(f.v[0].co, f.v[1].co, f.v[2].co) + elif len(f) == 4: + return\ + triArea(f.v[0].co, f.v[1].co, f.v[2].co) +\ + triArea(f.v[0].co, f.v[2].co, f.v[3].co) + + + + +def boundsIsland(faces): + minx = maxx = faces[0].uv[0][0] # Set initial bounds. + miny = maxy = faces[0].uv[0][1] + # print len(faces), minx, maxx, miny , maxy + for f in faces: + for uv in f.uv: + minx = min(minx, uv[0]) + maxx = max(maxx, uv[0]) + + miny = min(miny, uv[1]) + maxy = max(maxy, uv[1]) + + return minx, miny, maxx, maxy + +def boundsEdgeLoop(edges): + minx = maxx = edges[0][0] # Set initial bounds. + miny = maxy = edges[0][1] + # print len(faces), minx, maxx, miny , maxy + for ed in edges: + for pt in ed: + minx = min(minx, pt[0]) + maxx = max(maxx, pt[0]) + + miny = min(miny, pt[1]) + maxy = max(maxy, pt[1]) + + return minx, miny, maxx, maxy + + +# Turns the islands into a list of unpordered edges (Non internal) +# Onlt for UV's + +def island2Edge(island): + # Vert index edges + edges = {} + + for f in island: + for vIdx in range(len(f)): + if f.v[vIdx].index > f.v[vIdx-1].index: + edges[((f.uv[vIdx-1][0], f.uv[vIdx-1][1]), (f.uv[vIdx][0], f.uv[vIdx][1]))] =\ + (Vector([f.uv[vIdx-1][0], f.uv[vIdx-1][1]]) - Vector([f.uv[vIdx][0], f.uv[vIdx][1]])).length + else: + edges[((f.uv[vIdx][0], f.uv[vIdx][1]), (f.uv[vIdx-1][0], f.uv[vIdx-1][1]) )] =\ + (Vector([f.uv[vIdx-1][0], f.uv[vIdx-1][1]]) - Vector([f.uv[vIdx][0], f.uv[vIdx][1]])).length + + # If 2 are the same then they will be together, but full [a,b] order is not correct. + + # Sort by length + length_sorted_edges = [] + for key in edges.keys(): + length_sorted_edges.append([key[0], key[1], edges[key]]) + + length_sorted_edges.sort(lambda A, B: cmp(B[2], A[2])) + #for e in length_sorted_edges: + # e.pop(2) + + return length_sorted_edges + +# ========================= NOT WORKING???? +# Find if a points inside an edge loop, un-orderd. +# pt is and x/y +# edges are a non ordered loop of edges. +# #offsets are the edge x and y offset. +def pointInEdges(pt, edges): + # + x1 = pt[0] + y1 = pt[1] + + # Point to the left of this line. + x2 = -100000 + y2 = -10000 + intersectCount = 0 + for ed in edges: + xi, yi = lineIntersection2D(x1,y1, x2,y2, ed[0][0], ed[0][1], ed[1][0], ed[1][1]) + if xi != None: # Is there an intersection. + intersectCount+=1 + + return intersectCount % 2 + + +def uniqueEdgePairPoints(edges): + points = {} + pointsVec = [] + for e in edges: + points[e[0]] = points[e[1]] = None + + for p in points.keys(): + pointsVec.append( Vector([p[0], p[1], 0]) ) + return pointsVec + + +def pointInIsland(pt, island): + vec1 = Vector(); vec2 = Vector(); vec3 = Vector() + for f in island: + vec1.x, vec1.y = f.uv[0] + vec2.x, vec2.y = f.uv[1] + vec3.x, vec3.y = f.uv[2] + + if pointInTri2D(pt, vec1, vec2, vec3): + return True + + if len(f) == 4: + vec1.x, vec1.y = f.uv[0] + vec2.x, vec2.y = f.uv[2] + vec3.x, vec3.y = f.uv[3] + if pointInTri2D(pt, vec1, vec2, vec3): + return True + return False + + +# box is (left,bottom, right, top) +def islandIntersectUvIsland(source, target, xSourceOffset, ySourceOffset): + # Is 1 point in the box, inside the vertLoops + edgeLoopsSource = source[6] # Pretend this is offset + edgeLoopsTarget = target[6] + + + + # Edge intersect test + for ed in edgeLoopsSource: + for seg in edgeLoopsTarget: + xi, yi = lineIntersection2D(\ + seg[0][0], seg[0][1], seg[1][0], seg[1][1],\ + xSourceOffset+ed[0][0], ySourceOffset+ed[0][1], xSourceOffset+ed[1][0], ySourceOffset+ed[1][1]) + if xi != None: + return 1 # LINE INTERSECTION + + # 1 test for source being totally inside target + for pv in source[7]: + if NEW_2_38_MATHUTILS: + p = Vector(pv) + else: + p = CopyVec(pv) + + p.x += xSourceOffset + p.y += ySourceOffset + if pointInIsland(p, target[0]): + return 2 # SOURCE INSIDE TARGET + + # 2 test for a part of the target being totaly inside the source. + for pv in target[7]: + + if NEW_2_38_MATHUTILS: + p = Vector(pv) + else: + p = CopyVec(pv) + + p.x -= xSourceOffset + p.y -= ySourceOffset + if pointInIsland(p, source[0]): + return 3 # PART OF TARGET INSIDE SOURCE. + + return 0 # NO INTERSECTION + + + + +# Returns the X/y Bounds of a list of vectors. +def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1): + + # UV's will never extend this far. + minx = miny = BIG_NUM + maxx = maxy = -BIG_NUM + + for i, v in enumerate(vecs): + + # Do this allong the way + if mat != -1: + # 2.37 depricated + if NEW_2_38_MATHUTILS: + v = vecs[i] = v*mat + else: + v = vecs[i] = VecMultMat(v, mat) + + minx = min(minx, v.x) + maxx = max(maxx, v.x) + + miny = min(miny, v.y) + maxy = max(maxy, v.y) + + # Spesific to this algo, bail out if we get bigger then the current area + if bestAreaSoFar != -1 and (maxx-minx) * (maxy-miny) > bestAreaSoFar: + return (BIG_NUM, None), None + w = maxx-minx + h = maxy-miny + return (w*h, w,h), vecs # Area, vecs + +# Takes a list of faces that make up a UV island and rotate +# until they optimally fit inside a square. +ROTMAT_2D_POS_90D = RotationMatrix( 90, 2) +ROTMAT_2D_POS_45D = RotationMatrix( 45, 2) + +RotMatStepRotation = [] +rot_angle = 22.5 #45.0/2 +while rot_angle > 0.1: + RotMatStepRotation.append([\ + RotationMatrix( rot_angle, 2),\ + RotationMatrix( -rot_angle, 2)]) + + rot_angle = rot_angle/2.0 + + +def optiRotateUvIsland(faces): + global currentArea + + # Bestfit Rotation + def best2dRotation(uvVecs, MAT1, MAT2): + global currentArea + + newAreaPos, newfaceProjectionGroupListPos =\ + testNewVecLs2DRotIsBetter(uvVecs[:], MAT1, currentArea[0]) + + + # Why do I use newpos here? May as well give the best area to date for an early bailout + # some slight speed increase in this. + # If the new rotation is smaller then the existing, we can + # avoid copying a list and overwrite the old, crappy one. + + if newAreaPos[0] < currentArea[0]: + newAreaNeg, newfaceProjectionGroupListNeg =\ + testNewVecLs2DRotIsBetter(uvVecs, MAT2, newAreaPos[0]) # Reuse the old bigger list. + else: + newAreaNeg, newfaceProjectionGroupListNeg =\ + testNewVecLs2DRotIsBetter(uvVecs[:], MAT2, currentArea[0]) # Cant reuse, make a copy. + + + # Now from the 3 options we need to discover which to use + # we have cerrentArea/newAreaPos/newAreaNeg + bestArea = min(currentArea[0], newAreaPos[0], newAreaNeg[0]) + + if currentArea[0] == bestArea: + return uvVecs + elif newAreaPos[0] == bestArea: + uvVecs = newfaceProjectionGroupListPos + currentArea = newAreaPos + elif newAreaNeg[0] == bestArea: + uvVecs = newfaceProjectionGroupListNeg + currentArea = newAreaNeg + + return uvVecs + + + # Serialized UV coords to Vectors + uvVecs = [Vector(uv[:2]) for f in faces for uv in f.uv] + + # Theres a small enough number of these to hard code it + # rather then a loop. + + # Will not modify anything + currentArea, dummy =\ + testNewVecLs2DRotIsBetter(uvVecs) + + + # Try a 45d rotation + newAreaPos, newfaceProjectionGroupListPos = testNewVecLs2DRotIsBetter(uvVecs[:], ROTMAT_2D_POS_45D, currentArea[0]) + + if newAreaPos[0] < currentArea[0]: + uvVecs = newfaceProjectionGroupListPos + currentArea = newAreaPos + # 45d done + + # Testcase different rotations and find the onfe that best fits in a square + for ROTMAT in RotMatStepRotation: + uvVecs = best2dRotation(uvVecs, ROTMAT[0], ROTMAT[1]) + + + # Only if you want it, make faces verticle! + if currentArea[1] > currentArea[2]: + # Rotate 90d + # Work directly on the list, no need to return a value. + testNewVecLs2DRotIsBetter(uvVecs, ROTMAT_2D_POS_90D) + + + + + # Now write the vectors back to the face UV's + i = 0 # count the serialized uv/vectors + for f in faces: + f.uv = [uv for uv in uvVecs[i:len(f)+i] ] + i += len(f) + + +# Takes an island list and tries to find concave, hollow areas to pack smaller islands into. +def mergeUvIslands(islandList, islandListArea): + global USER_FILL_HOLES + global USER_FILL_HOLES_QUALITY + + # Pack islands to bottom LHS + # Sync with island + + #islandTotFaceArea = [] # A list of floats, each island area + #islandArea = [] # a list of tuples ( area, w,h) + + + decoratedIslandList = [] + + islandIdx = len(islandList) + while islandIdx: + islandIdx-=1 + minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx]) + w, h = maxx-minx, maxy-miny + + totFaceArea = 0 + fIdx = len(islandList[islandIdx]) + while fIdx: + fIdx-=1 + f = islandList[islandIdx][fIdx] + f.uv = [(uv[0]-minx, uv[1]-miny) for uv in f.uv] + totFaceArea += islandListArea[islandIdx][fIdx] # Use Cached area. dont recalculate. + islandBoundsArea = w*h + efficiency = abs(islandBoundsArea - totFaceArea) + + # UV Edge list used for intersections + edges = island2Edge(islandList[islandIdx]) + + + uniqueEdgePoints = uniqueEdgePairPoints(edges) + + decoratedIslandList.append([islandList[islandIdx], totFaceArea, efficiency, islandBoundsArea, w,h, edges, uniqueEdgePoints]) + + + # Sort by island bounding box area, smallest face area first. + # no.. chance that to most simple edge loop first. + decoratedIslandListAreaSort =decoratedIslandList[:] + decoratedIslandListAreaSort.sort(lambda A, B: cmp(A[1], B[1])) + + # sort by efficiency, Most Efficient first. + decoratedIslandListEfficSort = decoratedIslandList[:] + decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2])) + + # ================================================== THESE CAN BE TWEAKED. + # This is a quality value for the number of tests. + # from 1 to 4, generic quality value is from 1 to 100 + USER_STEP_QUALITY = ((USER_FILL_HOLES_QUALITY - 1) / 25.0) + 1 + + # If 100 will test as long as there is enough free space. + # this is rarely enough, and testing takes a while, so lower quality speeds this up. + + # 1 means they have the same quaklity + USER_FREE_SPACE_TO_TEST_QUALITY = 1 + (((100 - USER_FILL_HOLES_QUALITY)/100.0) *5) + + #print 'USER_STEP_QUALITY', USER_STEP_QUALITY + #print 'USER_FREE_SPACE_TO_TEST_QUALITY', USER_FREE_SPACE_TO_TEST_QUALITY + + removedCount = 0 + + areaIslandIdx = 0 + ctrl = Window.Qual.CTRL + while areaIslandIdx < len(decoratedIslandListAreaSort) and (not Window.GetKeyQualifiers() & ctrl): + sourceIsland = decoratedIslandListAreaSort[areaIslandIdx] + + # Alredy packed? + if not sourceIsland[0]: + areaIslandIdx+=1 + else: + efficIslandIdx = 0 + while efficIslandIdx < len(decoratedIslandListEfficSort): + + # Now we have 2 islands, is the efficience of the islands lowers theres an + # increasing likely hood that we can fit merge into the bigger UV island. + # this ensures a tight fit. + + # Just use figures we have about user/unused area to see if they might fit. + + targetIsland = decoratedIslandListEfficSort[efficIslandIdx] + + + if sourceIsland[0] == targetIsland[0] or\ + targetIsland[0] == [] or\ + sourceIsland[0] == []: + efficIslandIdx+=1 + continue + + + # ([island, totFaceArea, efficiency, islandArea, w,h]) + # Waisted space on target is greater then UV bounding island area. + + + # if targetIsland[3] > (sourceIsland[2]) and\ # + + if targetIsland[3] > (sourceIsland[1] * USER_FREE_SPACE_TO_TEST_QUALITY) and\ + targetIsland[4] > sourceIsland[4] and\ + targetIsland[5] > sourceIsland[5]: + + + # DEBUG # print '%.10f %.10f' % (targetIsland[3], sourceIsland[1]) + + # These enough spare space lets move the box until it fits + + # How many times does the source fit into the target x/y + blockTestXUnit = targetIsland[4]/sourceIsland[4] + blockTestYUnit = targetIsland[5]/sourceIsland[5] + + boxLeft = 0 + + # Distance we can move between whilst staying inside the targets bounds. + testWidth = targetIsland[4] - sourceIsland[4] + testHeight = targetIsland[5] - sourceIsland[5] + + # Increment we move each test. x/y + xIncrement = (testWidth / (blockTestXUnit * USER_STEP_QUALITY)) + yIncrement = (testHeight / (blockTestYUnit * USER_STEP_QUALITY)) + + boxLeft = 0 # Start 1 back so we can jump into the loop. + boxBottom= 0 #-yIncrement + + while boxLeft <= testWidth or boxBottom <= testHeight: + + + Intersect = islandIntersectUvIsland(sourceIsland, targetIsland, boxLeft, boxBottom) + + if Intersect == 1: # Line intersect, dont bother with this any more + pass + + if Intersect == 2: # Source inside target + ''' + We have an intersection, if we are inside the target + then move us 1 whole width accross, + Its possible this is a bad idea since 2 skinny Angular faces + could join without 1 whole move, but its a lot more optimal to speed this up + since we have alredy tested for it. + + It gives about 10% speedup with minimal errors. + ''' + # Move the test allong its width + SMALL_NUM + boxLeft += sourceIsland[4] + SMALL_NUM + #py_sys.stdout.write('>') + #pass + elif Intersect == 0: # No intersection?? Place it. + # Progress + removedCount +=1 + Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount) + + ''' + if py_sys: + py_sys.stdout.write('#') + py_sys.stdout.flush() + else: + print '#' + ''' + + # Move faces into new island and offset + targetIsland[0].extend(sourceIsland[0]) + while sourceIsland[0]: + f = sourceIsland[0].pop() + f.uv = [(uv[0]+boxLeft, uv[1]+boxBottom) for uv in f.uv] + + # Move edge loop into new and offset. + # targetIsland[6].extend(sourceIsland[6]) + while sourceIsland[6]: + e = sourceIsland[6].pop() + targetIsland[6].append(\ + ((e[0][0]+boxLeft, e[0][1]+boxBottom),\ + (e[1][0]+boxLeft, e[1][1]+boxBottom), e[2])\ + ) + + # Sort by edge length, reverse so biggest are first. + targetIsland[6].sort(lambda B,A: cmp(A[2], B[2] )) + + targetIsland[7].extend(sourceIsland[7]) + while sourceIsland[7]: + p = sourceIsland[7].pop() + p.x += boxLeft; p.y += boxBottom + + + # Decrement the efficiency + targetIsland[1]+=sourceIsland[1] # Increment totFaceArea + targetIsland[2]-=sourceIsland[1] # Decrement efficiency + # IF we ever used these again, should set to 0, eg + sourceIsland[2] = 0 # No area is anyone wants to know + + break + + + # INCREMENR NEXT LOCATION + if boxLeft > testWidth: + boxBottom += yIncrement + boxLeft = 0.0 + else: + boxLeft += xIncrement + + + efficIslandIdx+=1 + areaIslandIdx+=1 + + # Remove empty islands + # removedCount = 0 + i = len(islandList) + while i: + i-=1 + if not islandList[i]: + islandList.pop(i) + # removedCount+=1 + + # Dont need to return anything + # if py_sys: py_sys.stdout.flush() + + # print '' + # print removedCount, 'merged' + +# Takes groups of faces. assumes face groups are UV groups. +def packLinkedUvs(faceGroups, faceGroupsArea, me): + islandList = [] + islandListArea = [] + + Window.DrawProgressBar(0.0, 'Splitting %d projection groups into UV islands:' % len(faceGroups)) + #print '\tSplitting %d projection groups into UV islands:' % len(faceGroups), + # Find grouped faces + + faceGroupIdx = len(faceGroups) + + while faceGroupIdx: + faceGroupIdx-=1 + faces = faceGroups[faceGroupIdx] + facesArea = faceGroupsArea[faceGroupIdx] + # print '.', + + faceUsers = [[] for i in xrange(len(me.verts)) ] + faceUsersArea = [[] for i in xrange(len(me.verts)) ] + # Do the first face + fIdx = len(faces) + while fIdx: + fIdx-=1 + for v in faces[fIdx].v: + faceUsers[v.index].append(faces[fIdx]) + faceUsersArea[v.index].append(facesArea[fIdx]) + + + while 1: + + # This is an index that is used to remember + # what was the last face that was removed, so we know which faces are new and need to have + # faces next to them added into the list + searchFaceIndex = 0 + + # Find a face that hasnt been used alredy to start the search with + newIsland = [] + newIslandArea = [] + while not newIsland: + hasBeenUsed = 1 # Assume its been used. + if searchFaceIndex >= len(faces): + break + for v in faces[searchFaceIndex].v: + if faces[searchFaceIndex] in faceUsers[v.index]: + # This has not yet been used, it still being used by a vert + hasBeenUsed = 0 + break + if hasBeenUsed == 0: + newIsland.append(faces.pop(searchFaceIndex)) + newIslandArea.append(facesArea.pop(searchFaceIndex)) + + searchFaceIndex+=1 + + if newIsland == []: + break + + + # Before we start remove the first, search face from being used. + for v in newIsland[0].v: + popoffset = 0 + for fIdx in xrange(len(faceUsers[v.index])): + if faceUsers[v.index][fIdx - popoffset] == newIsland[0]: + faceUsers[v.index].pop(fIdx - popoffset) + faceUsersArea[v.index].pop(fIdx - popoffset) + + popoffset += 1 + + searchFaceIndex = 0 + while searchFaceIndex != len(newIsland): + for v in newIsland[searchFaceIndex].v: + + # Loop through all faces that use this vert + while faceUsers[v.index]: + sharedFace = faceUsers[v.index][-1] + sharedFaceArea = faceUsersArea[v.index][-1] + + newIsland.append(sharedFace) + newIslandArea.append(sharedFaceArea) + # Before we start remove the first, search face from being used. + for vv in sharedFace.v: + #faceUsers = [f for f in faceUsers[vv.index] if f != sharedFace] + fIdx = 0 + for fIdx in xrange(len(faceUsers[vv.index])): + if faceUsers[vv.index][fIdx] == sharedFace: + faceUsers[vv.index].pop(fIdx) + faceUsersArea[vv.index].pop(fIdx) + break # Can only be used once. + + searchFaceIndex += 1 + + # If all the faces are done and no face has been added then we can quit + if newIsland: + islandList.append(newIsland) + + islandListArea.append(newIslandArea) + + else: + print '\t(empty island found, ignoring)' + + + + + + #Window.DrawProgressBar(0.1, 'Found %i UV Islands' % len(islandList)) + #print '\n\tFound %i UV Islands' % len(islandList) + + #print '\tOptimizing Island Rotation...' + + Window.DrawProgressBar(0.1, 'Optimizing Rotation for %i UV Islands' % len(islandList)) + + for island in islandList: + optiRotateUvIsland(island) + + + + if USER_FILL_HOLES: + Window.DrawProgressBar(0.1, 'Merging Islands...') + #print '\tMerging islands to save space ("#" == one merge):\n', + if py_sys: py_sys.stdout.flush() + mergeUvIslands(islandList, islandListArea) # Modify in place + + + # Now we have UV islands, we need to pack them. + + # Make a synchronised list with the islands + # so we can box pak the islands. + boxes2Pack = [] + + # Keep a list of X/Y offset so we can save time by writing the + # uv's and packed data in one pass. + islandOffsetList = [] + + islandIdx = 0 + + while islandIdx < len(islandList): + minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx]) + w, h = maxx-minx, maxy-miny + + if w < 0.00001 or h < 0.00001: + del islandList[islandIdx] + islandIdx -=1 + continue + + '''Save the offset to be applied later, + we could apply to the UVs now and allign them to the bottom left hand area + of the UV coords like the box packer imagines they are + but, its quicker just to remember their offset and + apply the packing and offset in 1 pass ''' + islandOffsetList.append((minx, miny)) + + # Add to boxList. use the island idx for the BOX id. + boxes2Pack.append([islandIdx, w,h]) + islandIdx+=1 + + # Now we have a list of boxes to pack that syncs + # with the islands. + + #print '\tPacking UV Islands...' + Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(boxes2Pack) ) + + time1 = sys.time() + packWidth, packHeight, packedLs = boxPackIter(boxes2Pack) + # print 'Box Packing Time:', sys.time() - time1 + + #if len(pa ckedLs) != len(islandList): + # raise "Error packed boxes differes from original length" + + #print '\tWriting Packed Data to faces' + Window.DrawProgressBar(0.8, 'Writing Packed Data to faces') + packedLs.sort(lambda A, B: cmp(A[0] , B[0])) # Sort by ID, so there in sync again + + islandIdx = len(islandList) + # Having these here avoids devide by 0 + if islandIdx: + xfactor = 1.0 / packWidth + yfactor = 1.0 / packHeight + + while islandIdx: + islandIdx -=1 + # Write the packed values to the UV's + + + xoffset = packedLs[islandIdx][1] - islandOffsetList[islandIdx][0] + yoffset = packedLs[islandIdx][2] - islandOffsetList[islandIdx][1] + for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box + f.uv = [(((uv[0]+xoffset)*xfactor), ((uv[1]+yoffset)*yfactor)) for uv in f.uv] + +def VectoMat(vec): + + if NEW_2_38_MATHUTILS: + a3 = Vector(vec) + else: + a3 = CopyVec(vec) + + a3.normalize() + + up = Vector([0,0,1]) + if abs(DotVecs(a3, up)) == 1.0: + up = Vector([0,1,0]) + + a1 = CrossVecs(a3, up) + a1.normalize() + a2 = CrossVecs(a3, a1) + return Matrix([a1[0], a1[1], a1[2]], [a2[0], a2[1], a2[2]], [a3[0], a3[1], a3[2]]) + + +global ob +ob = None +def main(): + global USER_FILL_HOLES + global USER_FILL_HOLES_QUALITY + try: + obList = Object.GetSelected() + + except: + Draw.PupMenu('error, no selected objects or mesh') + return + + USER_FILL_HOLES = Draw.PupMenu('ArchiMap UV Unwrapper%t|Fill in holes (space efficient, slow)%x1|No Filling (waste space, fast)%x0') + if USER_FILL_HOLES == -1: + return + + USER_ONLY_SELECTED_FACES = Draw.PupMenu('Faces to Unwrap%t|Only Selected%x1|Unwrap All%x0') + if USER_ONLY_SELECTED_FACES == -1: + return + + if USER_FILL_HOLES: + USER_FILL_HOLES_QUALITY = Draw.PupIntInput('compression: ', 50, 1, 100) + if USER_FILL_HOLES_QUALITY == None: + return + + USER_PROJECTION_LIMIT = Draw.PupIntInput('angle limit:', 66, 1, 89) + if USER_PROJECTION_LIMIT == None: + return + + + # Toggle Edit mode + if Window.EditMode(): + Window.EditMode(0) + + Window.WaitCursor(1) + + time1 = sys.time() + for ob in obList: + + # Only meshes + if ob.getType() != 'Mesh': + continue + + me = ob.getData() + if USER_ONLY_SELECTED_FACES: + meshFaces = [f for f in me.getSelectedFaces() if len(f) > 2] + else: + meshFaces = [f for f in me.faces if len(f) > 2] + + if not meshFaces: + continue + + #print '\n\n\nArchimap UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces)) + Window.DrawProgressBar(0.1, 'Archimap UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces))) + + # Generate Projection + projectVecs = [] # We add to this allong the way + + # ======= + # 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. + faceListProps = [] + + for f in meshFaces: + area = faceArea(f) + if area <= SMALL_NUM: + f.uv = [(0.0, 0.0)] * len(f) + print 'found zero area face, removing.' + + else: + # Store all here + n = f.no + faceListProps.append( [f, area, Vector(n)] ) + + del meshFaces + + faceListProps.sort( lambda A, B: cmp(B[1] , A[1]) ) # Biggest first. + # 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 + + USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD) + #print USER_PROJECTION_LIMIT_CONVERTED + USER_PROJECTION_LIMIT_HALF_CONVERTED = cos((USER_PROJECTION_LIMIT/2) * DEG_TO_RAD) + + # Initialize projectVecs + newProjectVec = faceListProps[0][2] + newProjectFacePropList = [faceListProps[0]] # Popping stuffs it up. + + # Predent that the most unique angke is ages away to start the loop off + mostUniqueAngle = -1.0 + + # This is popped + tempFaceListProps = faceListProps[:] + + while 1: + # If theres none there then start with the largest face + + # Pick the face thats most different to all existing angles :) + mostUniqueAngle = 1.0 # 1.0 is 0d. no difference. + mostUniqueIndex = 0 # fake + + fIdx = len(tempFaceListProps) + while fIdx: + fIdx-=1 + angleDifference = -1.0 # 180d difference. + + # Get the closest vec angle we are to. + for p in projectVecs: + angleDifference = max(angleDifference, DotVecs(p, tempFaceListProps[fIdx][2])) + + 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(newProjectFacePropList) + newProjectVec = tempFaceListProps[mostUniqueIndex][2] + newProjectFacePropList = [tempFaceListProps.pop(mostUniqueIndex)] + else: + if len(projectVecs) >= 1: # Must have at least 2 projections + break + + + # Now we have found the most different vector, add all the faces that are close. + fIdx = len(tempFaceListProps) + while fIdx: + fIdx -= 1 + + # Use half the angle limit so we dont overweight faces towards this + # normal and hog all the faces. + if DotVecs(newProjectVec, tempFaceListProps[fIdx][2]) > USER_PROJECTION_LIMIT_HALF_CONVERTED: + newProjectFacePropList.append(tempFaceListProps.pop(fIdx)) + + + # 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. + averageVec = Vector([0,0,0]) + for fprop in newProjectFacePropList: + averageVec += (fprop[2] * fprop[1]) # / len(newProjectFacePropList) + + if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN + averageVec.normalize() + projectVecs.append(averageVec) + + # Now we have used it, ignore it. + newProjectFacePropList = [] + + # 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)) ] + faceProjectionGroupListArea =[[] for i in xrange(len(projectVecs)) ] + + # We need the area later, and we alredy have calculated it. so store it here. + #faceProjectionGroupListArea =[[] for i in xrange(len(projectVecs)) ] + + # MAP and Arrange # We know there are 3 or 4 faces here + fIdx = len(faceListProps) + while fIdx: + fIdx-=1 + fvec = Vector(faceListProps[fIdx][2]) + i = len(projectVecs) + + # Initialize first + + bestAng = DotVecs(fvec, projectVecs[0]) + # print bestAng + bestAngIdx = 0 + + # Cycle through the remaining, first alredy done + while i-1: + i-=1 + + newAng = DotVecs(fvec, projectVecs[i]) + if newAng > bestAng: # Reverse logic for dotvecs + bestAng = newAng + bestAngIdx = i + + # Store the area for later use. + faceProjectionGroupList[bestAngIdx].append(faceListProps[fIdx][0]) + faceProjectionGroupListArea[bestAngIdx].append(faceListProps[fIdx][1]) + + + # Cull faceProjectionGroupList, + + + # Now faceProjectionGroupList is full of faces that face match the project Vecs list + i= len(projectVecs) + while i: + i-=1 + + # 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]: + + if NEW_2_38_MATHUTILS: + f.uv = [MatProj * v.co for v in f.v] + else: + f.uv = [MatMultVec(MatProj, v.co) for v in f.v] + + packLinkedUvs(faceProjectionGroupList, faceProjectionGroupListArea, me) + + #print "ArchiMap time: %.2f" % (sys.time() - time1) + Window.DrawProgressBar(0.9, "ArchiMap Done, time: %.2f sec." % (sys.time() - time1)) + + # Update and dont mess with edge data. + me.update(0, (me.edges != []), 0) + Window.RedrawAll() + + +try: + main() +except KeyboardInterrupt: + print '\nUser Canceled.' + Draw.PupMenu('user canceled execution, unwrap aborted.') + +Window.WaitCursor(0) |