Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorno-author <no-author>2006-01-27 01:47:31 +0300
committerno-author <no-author>2006-01-27 01:47:31 +0300
commite8afec9f9af12c9c622813c7f8ea3a32c9f0cd4a (patch)
treeeab1f87cc41bd002ef5a3be2307926ec0efc17ef /release
parent4a52c6ac6ffb203ae55ad603c1f924fffd77da6a (diff)
parent6dbe25dd9dfe3d5dd41dade0ec72a689441e9383 (diff)
This commit was manufactured by cvs2svn to create branch 'orange'.
Diffstat (limited to 'release')
-rw-r--r--release/scripts/archimap.py1138
-rw-r--r--release/scripts/bpydata/mesh_bbrush.py919
-rw-r--r--release/scripts/bpymodules/boxpack2d.py478
-rw-r--r--release/scripts/image_edit.py95
-rw-r--r--release/scripts/mesh_bbrush_menu.py44
-rw-r--r--release/scripts/mesh_cleanup.py184
-rw-r--r--release/scripts/mesh_tri2quad.py453
-rw-r--r--release/scripts/object_batch_name_edit.py253
-rw-r--r--release/scripts/ply_export.py113
-rw-r--r--release/scripts/ply_import.py292
-rw-r--r--release/scripts/xfig_export.py441
11 files changed, 4410 insertions, 0 deletions
diff --git a/release/scripts/archimap.py b/release/scripts/archimap.py
new file mode 100644
index 00000000000..b1881cf0938
--- /dev/null
+++ b/release/scripts/archimap.py
@@ -0,0 +1,1138 @@
+#!BPY
+
+""" Registration info for Blender menus: <- these words are ignored
+Name: 'ArchiMap UV Projection Unwrapper'
+Blender: 240
+Group: 'UV'
+Tooltip: 'ArchiMap UV Unwrap mesh faces for all select mesh objects'
+"""
+
+
+__author__ = "Campbell Barton"
+__url__ = ("blender", "elysiun")
+__version__ = "1.1 12/18/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.1 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 Object, Scene, Draw, Window, sys, Mesh
+from Blender.Mathutils import CrossVecs, Matrix, Vector, RotationMatrix, DotVecs, TriangleArea
+
+from math import cos
+
+DEG_TO_RAD = 0.017453292519943295 # pi/180.0
+SMALL_NUM = 0.000000001
+BIG_NUM = 1e15
+
+global USER_FILL_HOLES
+global USER_FILL_HOLES_QUALITY
+USER_FILL_HOLES = None
+USER_FILL_HOLES_QUALITY = None
+
+import boxpack2d
+# reload(boxpack2d) # for developing.
+
+
+# Do 2 lines intersect?
+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
+
+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 = 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 = 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
+
+ uvw = (v - v1) * mtx
+ return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
+
+
+def faceArea(f):
+ if len(f.v) == 3:
+ return TriangleArea(f.v[0].co, f.v[1].co, f.v[2].co)
+ elif len(f.v) == 4:
+ return\
+ TriangleArea(f.v[0].co, f.v[1].co, f.v[2].co) +\
+ TriangleArea(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.v)):
+ 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.v) == 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]:
+ p = Vector(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]:
+ p = Vector(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:
+ v = vecs[i] = 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) 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.v)+i] ]
+ i += len(f.v)
+
+
+# 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 = [Vector(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
+ elif Intersect == 0: # No intersection?? Place it.
+ # Progress
+ removedCount +=1
+ Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount)
+
+ # Move faces into new island and offset
+ targetIsland[0].extend(sourceIsland[0])
+ while sourceIsland[0]:
+ f = sourceIsland[0].pop()
+ f.uv = [Vector(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
+ i = len(islandList)
+ while i:
+ i-=1
+ if not islandList[i]:
+ islandList.pop(i) # Can increment islands removed here.
+
+
+# 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] is 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] is 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, 'Optimizing Rotation for %i UV Islands' % len(islandList))
+
+ for island in islandList:
+ optiRotateUvIsland(island)
+
+
+
+ if USER_FILL_HOLES:
+ Window.DrawProgressBar(0.1, 'Merging Islands (Ctrl: skip merge)...')
+ 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 = boxpack2d.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:
+
+ if USER_STRETCH_ASPECT:
+ # Maximize to uv area?? Will write a normalize function.
+ xfactor = 1.0 / packWidth
+ yfactor = 1.0 / packHeight
+ else:
+ # Keep proportions.
+ xfactor = yfactor = 1.0 / max(packWidth, 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]
+
+ if USER_MARGIN:
+ USER_MARGIN_SCALE = 1-(USER_MARGIN*2)
+ for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box, margin
+ f.uv = [Vector((((uv[0]+xoffset)*xfactor)*USER_MARGIN_SCALE)+USER_MARGIN, (((uv[1]+yoffset)*yfactor)*USER_MARGIN_SCALE)+USER_MARGIN) for uv in f.uv]
+ else:
+ for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box
+ f.uv = [Vector(((uv[0]+xoffset)*xfactor), ((uv[1]+yoffset)*yfactor)) for uv in f.uv]
+
+
+
+def VectoMat(vec):
+
+ a3 = Vector(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
+ global USER_STRETCH_ASPECT
+ global USER_MARGIN
+
+ # Use datanames as kesy so as not to unwrap a mesh more then once.
+ obList = dict([(ob.getData(name_only=1), ob) for ob in Object.GetSelected() if ob.getType() == 'Mesh'])
+
+
+ # Face select object may not be selected.
+ scn = Scene.GetCurrent()
+ ob = scn.getActiveObject()
+ if ob and ob.sel == 0 and ob.getType() == 'Mesh':
+ # Add to the list
+ obList[ob.getData(name_only=1)] = ob
+ del scn # Sone use the scene again.
+
+ obList = obList.values() # turn from a dict to a list.
+
+ 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_STRETCH_ASPECT = Draw.Create(1) # Only for hole filling.
+ USER_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.
+
+
+ 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.'),\
+ 'UV Layout',\
+ ('Stretch to bounds', USER_STRETCH_ASPECT, 'Stretch the final output to texture bounds.'),\
+ ('Bleed Margin:', USER_MARGIN, 0.0, 0.25, 'Margin to reduce bleed from texture tiling.'),\
+ '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_STRETCH_ASPECT = USER_STRETCH_ASPECT.val
+ USER_MARGIN = USER_MARGIN.val
+ USER_FILL_HOLES = USER_FILL_HOLES.val
+ USER_FILL_HOLES_QUALITY = USER_FILL_HOLES_QUALITY.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.
+
+
+ Window.WaitCursor(1)
+ SELECT_FLAG = Mesh.FaceFlags['SELECT']
+ time1 = sys.time()
+ for ob in obList:
+
+ # Only meshes
+ if ob.getType() != 'Mesh':
+ continue
+
+ me = ob.getData(mesh=1)
+
+ if not me.faceUV: # Mesh has no UV Coords, dont bother.
+ continue
+
+ if USER_ONLY_SELECTED_FACES:
+ meshFaces = [f for f in me.faces if f.flag & SELECT_FLAG]
+ else:
+ meshFaces = [f for f in me.faces]
+
+ 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 = [Vector(0.0, 0.0)] * len(f.v) # Assign dummy UV
+ 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
+
+
+ # 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])
+ 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]:
+ f.uv = [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.
+ # OLD NMESH # me.update(0, (me.edges != []), 0)
+
+ Window.DrawProgressBar(1.0, "")
+ Window.WaitCursor(0)
+ Window.RedrawAll()
+
+if __name__ == '__main__':
+ try:
+ main()
+
+ except KeyboardInterrupt:
+ print '\nUser Canceled.'
+ Draw.PupMenu('user canceled execution, unwrap aborted.')
+ Window.DrawProgressBar(1.0, "")
+ Window.WaitCursor(0) \ No newline at end of file
diff --git a/release/scripts/bpydata/mesh_bbrush.py b/release/scripts/bpydata/mesh_bbrush.py
new file mode 100644
index 00000000000..eac576b2e28
--- /dev/null
+++ b/release/scripts/bpydata/mesh_bbrush.py
@@ -0,0 +1,919 @@
+# SPACEHANDLER.VIEW3D.EVENT
+# Dont run, event handelers are accessed in the from the 3d View menu.
+
+import Blender
+from Blender import Mathutils, Window, Scene, Draw, Mesh, NMesh
+from Blender.Mathutils import CrossVecs, Matrix, Vector, Intersect, LineIntersect
+
+
+# DESCRIPTION:
+# screen_x, screen_y the origin point of the pick ray
+# it is either the mouse location
+# localMatrix is used if you want to have the returned values in an objects localspace.
+# this is usefull when dealing with an objects data such as verts.
+# or if useMid is true, the midpoint of the current 3dview
+# returns
+# Origin - the origin point of the pick ray
+# Direction - the direction vector of the pick ray
+# in global coordinates
+epsilon = 1e-3 # just a small value to account for floating point errors
+
+def getPickRay(screen_x, screen_y, localMatrix=None, useMid = False):
+
+ # Constant function variables
+ p = getPickRay.p
+ d = getPickRay.d
+
+ for win3d in Window.GetScreenInfo(Window.Types.VIEW3D): # we search all 3dwins for the one containing the point (screen_x, screen_y) (could be the mousecoords for example)
+ win_min_x, win_min_y, win_max_x, win_max_y = win3d['vertices']
+ # calculate a few geometric extents for this window
+
+ win_mid_x = (win_max_x + win_min_x + 1.0) * 0.5
+ win_mid_y = (win_max_y + win_min_y + 1.0) * 0.5
+ win_size_x = (win_max_x - win_min_x + 1.0) * 0.5
+ win_size_y = (win_max_y - win_min_y + 1.0) * 0.5
+
+ #useMid is for projecting the coordinates when we subdivide the screen into bins
+ if useMid: # == True
+ screen_x = win_mid_x
+ screen_y = win_mid_y
+
+ # if the given screencoords (screen_x, screen_y) are within the 3dwin we fount the right one...
+ if (win_max_x > screen_x > win_min_x) and ( win_max_y > screen_y > win_min_y):
+ # first we handle all pending events for this window (otherwise the matrices might come out wrong)
+ Window.QHandle(win3d['id'])
+
+ # now we get a few matrices for our window...
+ # sorry - i cannot explain here what they all do
+ # - if you're not familiar with all those matrices take a look at an introduction to OpenGL...
+ pm = Window.GetPerspMatrix() # the prespective matrix
+ pmi = Matrix(pm); pmi.invert() # the inverted perspective matrix
+
+ if (1.0 - epsilon < pmi[3][3] < 1.0 + epsilon):
+ # pmi[3][3] is 1.0 if the 3dwin is in ortho-projection mode (toggled with numpad 5)
+ hms = getPickRay.hms
+ ortho_d = getPickRay.ortho_d
+
+ # ortho mode: is a bit strange - actually there's no definite location of the camera ...
+ # but the camera could be displaced anywhere along the viewing direction.
+
+ ortho_d.x, ortho_d.y, ortho_d.z = Window.GetViewVector()
+ ortho_d.w = 0
+
+ # all rays are parallel in ortho mode - so the direction vector is simply the viewing direction
+ hms.x, hms.y, hms.z, hms.w = (screen_x-win_mid_x) /win_size_x, (screen_y-win_mid_y) / win_size_y, 0.0, 1.0
+
+ # these are the homogenious screencoords of the point (screen_x, screen_y) ranging from -1 to +1
+ p=(hms*pmi) + (1000*ortho_d)
+ p.resize3D()
+ d.x, d.y, d.z = ortho_d.x, ortho_d.y, ortho_d.z
+
+
+ # Finally we shift the position infinitely far away in
+ # the viewing direction to make sure the camera if outside the scene
+ # (this is actually a hack because this function
+ # is used in sculpt_mesh to initialize backface culling...)
+ else:
+ # PERSPECTIVE MODE: here everything is well defined - all rays converge at the camera's location
+ vmi = Matrix(Window.GetViewMatrix()); vmi.invert() # the inverse viewing matrix
+ fp = getPickRay.fp
+
+ dx = pm[3][3] * (((screen_x-win_min_x)/win_size_x)-1.0) - pm[3][0]
+ dy = pm[3][3] * (((screen_y-win_min_y)/win_size_y)-1.0) - pm[3][1]
+
+ fp.x, fp.y, fp.z = \
+ pmi[0][0]*dx+pmi[1][0]*dy,\
+ pmi[0][1]*dx+pmi[1][1]*dy,\
+ pmi[0][2]*dx+pmi[1][2]*dy
+
+ # fp is a global 3dpoint obtained from "unprojecting" the screenspace-point (screen_x, screen_y)
+ #- figuring out how to calculate this took me quite some time.
+ # The calculation of dxy and fp are simplified versions of my original code
+ #- so it's almost impossible to explain what's going on geometrically... sorry
+
+ p.x, p.y, p.z = vmi[3][:3]
+
+ # the camera's location in global 3dcoords can be read directly from the inverted viewmatrix
+ #d.x, d.y, d.z =normalize_v3(sub_v3v3(p, fp))
+ d.x, d.y, d.z = p.x-fp.x, p.y-fp.y, p.z-fp.z
+
+ #print 'd', d, 'p', p, 'fp', fp
+
+
+ # the direction vector is simply the difference vector from the virtual camera's position
+ #to the unprojected (screenspace) point fp
+
+ # Do we want to return a direction in object's localspace?
+
+ if localMatrix:
+ localInvMatrix = Matrix(localMatrix)
+ localInvMatrix.invert()
+ p = p*localInvMatrix
+ d = d*localInvMatrix # normalize_v3
+ p.x += localInvMatrix[3][0]
+ p.y += localInvMatrix[3][1]
+ p.z += localInvMatrix[3][2]
+
+ #else: # Worldspace, do nothing
+
+ d.normalize()
+ return True, p, d # Origin, Direction
+
+ # Mouse is not in any view, return None.
+ return False, None, None
+
+# Constant function variables
+getPickRay.d = Vector(0,0,0) # Perspective, 3d
+getPickRay.p = Vector(0,0,0)
+getPickRay.fp = Vector(0,0,0)
+
+getPickRay.hms = Vector(0,0,0,0) # ortho only 4d
+getPickRay.ortho_d = Vector(0,0,0,0) # ortho only 4d
+
+
+
+def ui_set_preferences(user_interface=1):
+ # Create data and set defaults.
+ ADAPTIVE_GEOMETRY_but = Draw.Create(0)
+ BRUSH_MODE_but = Draw.Create(1)
+ BRUSH_PRESSURE_but = Draw.Create(0.05)
+ BRUSH_RADIUS_but = Draw.Create(0.25)
+ RESOLUTION_MIN_but = Draw.Create(0.1)
+ DISPLACE_NORMAL_MODE_but = Draw.Create(2)
+ STATIC_NORMAL_but = Draw.Create(1)
+ XPLANE_CLIP_but = Draw.Create(0)
+ STATIC_MESH_but = Draw.Create(1)
+ FIX_TOPOLOGY_but = Draw.Create(0)
+
+ # Remember old variables if alredy set.
+ try:
+ ADAPTIVE_GEOMETRY_but.val = Blender.bbrush['ADAPTIVE_GEOMETRY']
+ BRUSH_MODE_but.val = Blender.bbrush['BRUSH_MODE']
+ BRUSH_PRESSURE_but.val = Blender.bbrush['BRUSH_PRESSURE']
+ BRUSH_RADIUS_but.val = Blender.bbrush['BRUSH_RADIUS']
+ RESOLUTION_MIN_but.val = Blender.bbrush['RESOLUTION_MIN']
+ DISPLACE_NORMAL_MODE_but.val = Blender.bbrush['DISPLACE_NORMAL_MODE']
+ STATIC_NORMAL_but.val = Blender.bbrush['STATIC_NORMAL']
+ XPLANE_CLIP_but.val = Blender.bbrush['XPLANE_CLIP']
+ STATIC_MESH_but.val = Blender.bbrush['STATIC_MESH']
+ FIX_TOPOLOGY_but.val = Blender.bbrush['FIX_TOPOLOGY']
+ except:
+ Blender.bbrush = {}
+
+ if user_interface:
+ pup_block = [\
+ 'Brush Options',\
+ ('Adaptive Geometry', ADAPTIVE_GEOMETRY_but, 'Add and remove detail as needed. Uses min/max resolution.'),\
+ ('Brush Type: ', BRUSH_MODE_but, 1, 5, 'Push/Pull:1, Grow/Shrink:2, Spin:3, Relax:4, Goo:5'),\
+ ('Pressure: ', BRUSH_PRESSURE_but, 0.01, 1.0, 'Pressure of the brush.'),\
+ ('Size: ', BRUSH_RADIUS_but, 0.02, 2.0, 'Size of the brush.'),\
+ ('Geometry Res: ', RESOLUTION_MIN_but, 0.01, 0.5, 'Size of the brush & Adaptive Subdivision.'),\
+ ('Displace Vector: ', DISPLACE_NORMAL_MODE_but, 1, 4, 'Vertex Normal:1, Median Normal:2, Face Normal:3, View Normal:4'),\
+ ('Static Normal', STATIC_NORMAL_but, 'Use the initial normal only.'),\
+ ('No X Crossing', XPLANE_CLIP_but, 'Dont allow verts to have a negative X axis (use for x-mirror).'),\
+ ('Static Mesh', STATIC_MESH_but, 'During mouse interaction, dont update the mesh.'),\
+ #('Fix Topology', FIX_TOPOLOGY_but, 'Fix the mesh structure by rotating edges '),\
+ ]
+
+ Draw.PupBlock('BlenBrush Prefs (RMB)', pup_block)
+
+ Blender.bbrush['ADAPTIVE_GEOMETRY'] = ADAPTIVE_GEOMETRY_but.val
+ Blender.bbrush['BRUSH_MODE'] = BRUSH_MODE_but.val
+ Blender.bbrush['BRUSH_PRESSURE'] = BRUSH_PRESSURE_but.val
+ Blender.bbrush['BRUSH_RADIUS'] = BRUSH_RADIUS_but.val
+ Blender.bbrush['RESOLUTION_MIN'] = RESOLUTION_MIN_but.val
+ Blender.bbrush['DISPLACE_NORMAL_MODE'] = DISPLACE_NORMAL_MODE_but.val
+ Blender.bbrush['STATIC_NORMAL'] = STATIC_NORMAL_but.val
+ Blender.bbrush['XPLANE_CLIP'] = XPLANE_CLIP_but.val
+ Blender.bbrush['STATIC_MESH'] = STATIC_MESH_but.val
+ Blender.bbrush['FIX_TOPOLOGY'] = FIX_TOPOLOGY_but.val
+
+
+def triangulateNMesh(nm):
+ '''
+ Converts the meshes faces to tris, modifies the mesh in place.
+ '''
+
+ #============================================================================#
+ # Returns a new face that has the same properties as the origional face #
+ # but with no verts #
+ #============================================================================#
+ def copyFace(face):
+ newFace = NMesh.Face()
+ # Copy some generic properties
+ newFace.mode = face.mode
+ if face.image != None:
+ newFace.image = face.image
+ newFace.flag = face.flag
+ newFace.mat = face.mat
+ newFace.smooth = face.smooth
+ return newFace
+
+ # 2 List comprehensions are a lot faster then 1 for loop.
+ tris = [f for f in nm.faces if len(f) == 3]
+ quads = [f for f in nm.faces if len(f) == 4]
+
+
+ if quads: # Mesh may have no quads.
+ has_uv = quads[0].uv
+ has_vcol = quads[0].col
+ for quadFace in quads:
+ # Triangulate along the shortest edge
+ #if (quadFace.v[0].co - quadFace.v[2].co).length < (quadFace.v[1].co - quadFace.v[3].co).length:
+ a1 = Mathutils.TriangleArea(quadFace.v[0].co, quadFace.v[1].co, quadFace.v[2].co)
+ a2 = Mathutils.TriangleArea(quadFace.v[0].co, quadFace.v[2].co, quadFace.v[3].co)
+ b1 = Mathutils.TriangleArea(quadFace.v[1].co, quadFace.v[2].co, quadFace.v[3].co)
+ b2 = Mathutils.TriangleArea(quadFace.v[1].co, quadFace.v[3].co, quadFace.v[0].co)
+ a1,a2 = min(a1, a2), max(a1, a2)
+ b1,b2 = min(b1, b2), max(b1, b2)
+ if a1/a2 < b1/b2:
+
+ # Method 1
+ triA = 0,1,2
+ triB = 0,2,3
+ else:
+ # Method 2
+ triA = 0,1,3
+ triB = 1,2,3
+
+ for tri1, tri2, tri3 in (triA, triB):
+ newFace = copyFace(quadFace)
+ newFace.v = [quadFace.v[tri1], quadFace.v[tri2], quadFace.v[tri3]]
+ if has_uv: newFace.uv = [quadFace.uv[tri1], quadFace.uv[tri2], quadFace.uv[tri3]]
+ if has_vcol: newFace.col = [quadFace.col[tri1], quadFace.col[tri2], quadFace.col[tri3]]
+
+ nm.addEdge(quadFace.v[tri1], quadFace.v[tri3]) # Add an edge where the 2 tris are devided.
+ tris.append(newFace)
+
+ nm.faces = tris
+
+import mesh_tri2quad
+def fix_topolagy(mesh):
+ ob = Scene.GetCurrent().getActiveObject()
+
+ for f in mesh.faces:
+ f.sel = 1
+ mesh.quadToTriangle(0)
+ nmesh = ob.getData()
+
+ mesh_tri2quad.tri2quad(nmesh, 100, 0)
+ triangulateNMesh(nmesh)
+ nmesh.update()
+
+ mesh = Mesh.Get(mesh.name)
+ for f in mesh.faces:
+ f.sel=1
+ mesh.quadToTriangle()
+ Mesh.Mode(Mesh.SelectModes['EDGE'])
+
+
+
+
+
+
+def event_main():
+ #print Blender.event
+ #mod =[Window.Qual.CTRL, Window.Qual.ALT, Window.Qual.SHIFT]
+ mod =[Window.Qual.CTRL, Window.Qual.ALT]
+
+ qual = Window.GetKeyQualifiers()
+ SHIFT_FLAG = Window.Qual.SHIFT
+ CTRL_FLAG = Window.Qual.CTRL
+
+
+ # UNDO
+ """
+ is_editmode = Window.EditMode() # Exit Editmode.
+ if is_editmode: Window.EditMode(0)
+ if Blender.event == Draw.UKEY:
+ if is_editmode:
+ Blender.event = Draw.UKEY
+ return
+ else:
+ winId = [win3d for win3d in Window.GetScreenInfo(Window.Types.VIEW3D)][0]
+ Blender.event = None
+ Window.QHandle(winId['id'])
+ Window.EditMode(1)
+ Window.QHandle(winId['id'])
+ Window.QAdd(winId['id'],Draw.UKEY,1) # Change KeyPress Here for EditMode
+ Window.QAdd(winId['id'],Draw.UKEY,0)
+ Window.QHandle(winId['id'])
+ Window.EditMode(0)
+ Blender.event = None
+ return
+ """
+
+ ob = Scene.GetCurrent().getActiveObject()
+ if not ob or ob.getType() != 'Mesh':
+ return
+
+ # Mouse button down with no modifiers.
+ if Blender.event == Draw.LEFTMOUSE and not [True for m in mod if m & qual]:
+ # Do not exit (draw)
+ pass
+ elif Blender.event == Draw.RIGHTMOUSE and not [True for m in mod if m & qual]:
+ ui_set_preferences()
+ return
+ else:
+ return
+
+ del qual
+
+
+ try:
+ Blender.bbrush
+ except:
+ # First time run
+ ui_set_preferences() # No ui
+ return
+
+ ADAPTIVE_GEOMETRY = Blender.bbrush['ADAPTIVE_GEOMETRY'] # 1
+ BRUSH_MODE = Blender.bbrush['BRUSH_MODE'] # 1
+ BRUSH_PRESSURE_ORIG = Blender.bbrush['BRUSH_PRESSURE'] # 0.1
+ BRUSH_RADIUS = Blender.bbrush['BRUSH_RADIUS'] # 0.5
+ RESOLUTION_MIN = Blender.bbrush['RESOLUTION_MIN'] # 0.08
+ STATIC_NORMAL = Blender.bbrush['STATIC_NORMAL'] # 0
+ XPLANE_CLIP = Blender.bbrush['XPLANE_CLIP'] # 0
+ DISPLACE_NORMAL_MODE = Blender.bbrush['DISPLACE_NORMAL_MODE'] # 'Vertex Normal%x1|Median Normal%x2|Face Normal%x3|View Normal%x4'
+ STATIC_MESH = Blender.bbrush['STATIC_MESH']
+ FIX_TOPOLOGY = Blender.bbrush['FIX_TOPOLOGY']
+
+
+ # Angle between Vecs wrapper.
+ AngleBetweenVecs = Mathutils.AngleBetweenVecs
+ def ang(v1,v2):
+ try:
+ return AngleBetweenVecs(v1,v2)
+ except:
+ return 180
+ """
+ def Angle2D(x1, y1, x2, y2):
+ import math
+ RAD2DEG = 57.295779513082323
+ '''
+ Return the angle between two vectors on a plane
+ The angle is from vector 1 to vector 2, positive anticlockwise
+ The result is between -pi -> pi
+ '''
+ dtheta = math.atan2(y2,x2) - math.atan2(y1,x1) # theta1 - theta2
+ while dtheta > math.pi:
+ dtheta -= (math.pi*2)
+ while dtheta < -math.pi:
+ dtheta += (math.pi*2)
+ return dtheta * RAD2DEG #(180.0 / math.pi)
+ """
+
+ def faceIntersect(f):
+ isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, Direction, Origin, 1) # Clipped.
+ if isect:
+ return isect
+ elif len(f.v) == 4:
+ isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, Direction, Origin, 1) # Clipped.
+ return isect
+ """
+ # Unused so farm, too slow.
+ def removeDouble(v1,v2, me):
+ v1List = [f for f in me.faces if v1 in f.v]
+ v2List = [f for f in me.faces if v2 in f.v]
+ #print v1List
+ #print v2List
+ remFaces = []
+ newFaces = []
+ for f2 in v2List:
+ f2ls = list(f2.v)
+ i = f2ls.index(v2)
+ f2ls[i] = v1
+ #remFaces.append(f2)
+ if f2ls.count(v1) == 1:
+ newFaces.append(tuple(f2ls))
+ if remFaces:
+ me.faces.delete(1, remFaces)
+ #me.verts.delete(v2)
+ if newFaces:
+ me.faces.extend(newFaces)
+ """
+
+
+ me = ob.getData(mesh=1)
+
+ is_editmode = Window.EditMode() # Exit Editmode.
+ if is_editmode: Window.EditMode(0)
+
+ Mesh.Mode(Mesh.SelectModes['EDGE'])
+
+ # At the moment ADAPTIVE_GEOMETRY is the only thing that uses selection.
+ if ADAPTIVE_GEOMETRY:
+ # Deslect all
+ SEL_FLAG = Mesh.EdgeFlags['SELECT']
+ '''
+ for ed in me.edges:
+ #ed.flag &= ~SEL_FLAG # deselect. 34
+ ed.flag = 32
+ '''
+ #filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
+
+ '''for v in me.verts:
+ v.sel = 0'''
+ #filter(lambda v: setattr(v, 'sel', 0), me.verts)
+ # DESELECT ABSOLUTLY ALL
+ Mesh.Mode(Mesh.SelectModes['FACE'])
+ filter(lambda f: setattr(f, 'sel', 0), me.faces)
+
+ Mesh.Mode(Mesh.SelectModes['EDGE'])
+ filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
+
+ Mesh.Mode(Mesh.SelectModes['VERTEX'])
+ filter(lambda v: setattr(v, 'sel', 0), me.verts)
+
+ Mesh.Mode(Mesh.SelectModes['EDGE'])
+
+ i = 0
+ time = Blender.sys.time()
+ last_best_isect = None # used for goo only
+ old_screen_x, old_screen_y = 1<<30, 1<<30
+ goo_dir_vec = last_goo_dir_vec = gooRotMatrix = None # goo mode only.
+
+ # Normal stuff
+ iFaceNormal = medainNormal = None
+
+ # Store all vert normals for now.
+ if BRUSH_MODE == 1 and STATIC_NORMAL: # Push pull
+ vert_orig_normals = dict([(v, v.no) for v in me.verts])
+
+ elif BRUSH_MODE == 4: # RELAX, BUILD EDGE CONNECTIVITE DATA.
+ # we need edge connectivity
+ #vertEdgeUsers = [list() for i in xrange(len(me.verts))]
+ verts_connected_by_edge = [list() for i in xrange(len(me.verts))]
+
+ for ed in me.edges:
+ i1, i2 = ed.v1.index, ed.v2.index
+ #vertEdgeUsers[i1].append(ed)
+ #vertEdgeUsers[i2].append(ed)
+
+ verts_connected_by_edge[i1].append(ed.v2)
+ verts_connected_by_edge[i2].append(ed.v1)
+
+ if STATIC_MESH:
+
+ # Try and find a static mesh to reuse.
+ # this is because we dont want to make a new mesh for each stroke.
+ mesh_static = None
+ for _me_name_ in Blender.NMesh.GetNames():
+ _me_ = Mesh.Get(_me_name_)
+ #print _me_.users , len(me.verts)
+ if _me_.users == 0 and len(_me_.verts) == 0:
+ mesh_static = _me_
+ #print 'using', _me_.name
+ break
+ del _me_name_
+ del _me_
+
+ if not mesh_static:
+ mesh_static = Mesh.New()
+ print 'Making new mesh', mesh_static.name
+
+ mesh_static.verts.extend([v.co for v in me.verts])
+ mesh_static.faces.extend([tuple([mesh_static.verts[v.index] for v in f.v]) for f in me.faces])
+
+
+ best_isect = gooPlane = None
+
+ while Window.GetMouseButtons() == 1:
+ i+=1
+ screen_x, screen_y = Window.GetMouseCoords()
+
+ # Skip when no mouse movement, Only for Goo!
+ if screen_x == old_screen_x and screen_y == old_screen_y:
+ if BRUSH_MODE == 5: # Dont modify while mouse is not moved for goo.
+ continue
+ else: # mouse has moved get the new mouse ray.
+ old_screen_x, old_screen_y = screen_x, screen_y
+ mouseInView, Origin, Direction = getPickRay(screen_x, screen_y, ob.matrixWorld)
+ if not mouseInView or not Origin:
+ return
+ Origin_SCALE = Origin * 100
+
+ # Find an intersecting face!
+ bestLen = 1<<30 # start with an assumed realy bad match.
+ best_isect = None # last intersect is used for goo.
+ best_face = None
+
+ if not last_best_isect:
+ last_best_isect = best_isect
+
+ if not mouseInView:
+ last_best_isect = None
+
+ else:
+ # Find Face intersection closest to the view.
+ #for f in [f for f in me.faces if ang(f.no, Direction) < 90]:
+
+ # Goo brush only intersects faces once, after that the brush follows teh view plain.
+ if BRUSH_MODE == 5 and gooPlane != None and gooPlane:
+ best_isect = Intersect( gooPlane[0], gooPlane[1], gooPlane[2], Direction, Origin, 0) # Non clipped
+ else:
+ if STATIC_MESH:
+ intersectingFaces = [(f, ix) for f in mesh_static.faces for ix in (faceIntersect(f),) if ix]
+ else:
+ intersectingFaces = [(f, ix) for f in me.faces for ix in (faceIntersect(f),) if ix]
+
+ for f, isect in intersectingFaces:
+ l = (Origin_SCALE-isect).length
+ if l < bestLen:
+ best_face = f
+ best_isect = isect
+ bestLen = l
+
+ if not best_isect:
+ # Dont interpolate once the mouse moves off the mesh.
+ lastGooVec = last_best_isect = None
+
+ else: # mouseInView must be true also
+
+ # Use the shift key to modify the pressure.
+ if SHIFT_FLAG & Window.GetKeyQualifiers():
+ BRUSH_PRESSURE = -BRUSH_PRESSURE_ORIG
+ else:
+ BRUSH_PRESSURE = BRUSH_PRESSURE_ORIG
+
+ brush_verts = [(v,le) for v in me.verts for le in ((v.co-best_isect).length,) if le < BRUSH_RADIUS]
+
+ # SETUP ONCE ONLY VARIABLES
+ if STATIC_NORMAL: # Only set the normal once.
+ if not iFaceNormal:
+ iFaceNormal = best_face.no
+ else:
+ if best_face:
+ iFaceNormal = best_face.no
+
+
+ if DISPLACE_NORMAL_MODE == 2: # MEDIAN NORMAL
+ if (STATIC_NORMAL and medainNormal == None) or not STATIC_NORMAL:
+ medainNormal = Vector(0,0,0)
+ for v, l in brush_verts:
+ medainNormal += v.no*(BRUSH_RADIUS-l)
+ medainNormal.normalize()
+
+
+ # ================================================================#
+ # == Tool code, loop on the verts and operate on them ============#
+ # ================================================================#
+ if BRUSH_MODE == 1: # NORMAL PAINT
+ for v,l in brush_verts:
+ if XPLANE_CLIP:
+ origx = False
+ if abs(v.co.x) < 0.001: origx = True
+
+
+ v.sel = 1 # MARK THE VERT AS DIRTY.
+ falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
+
+ if DISPLACE_NORMAL_MODE == 1: # VERTEX NORMAL
+ if STATIC_NORMAL:
+ try:
+ no = vert_orig_normals[v]
+ except:
+ no = vert_orig_normals[v] = v.no
+ v.co += (no * BRUSH_PRESSURE) * falloff
+ else:
+ v.co += (v.no * BRUSH_PRESSURE) * falloff
+ elif DISPLACE_NORMAL_MODE == 2: # MEDIAN NORMAL # FIXME
+ v.co += (medainNormal * BRUSH_PRESSURE) * falloff
+ elif DISPLACE_NORMAL_MODE == 3: # FACE NORMAL
+ v.co += (iFaceNormal * BRUSH_PRESSURE) * falloff
+ elif DISPLACE_NORMAL_MODE == 4: # VIEW NORMAL
+ v.co += (Direction * BRUSH_PRESSURE) * falloff
+
+ # Clamp back to original x if needs be.
+ if XPLANE_CLIP and origx:
+ v.co.x = 0
+
+ elif BRUSH_MODE == 2: # SCALE
+ for v,l in brush_verts:
+
+ if XPLANE_CLIP:
+ origx = False
+ if abs(v.co.x) < 0.001: origx = True
+
+ v.sel = 1 # MARK THE VERT AS DIRTY.
+ falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
+
+ vert_scale_vec = v.co - best_isect
+ vert_scale_vec.normalize()
+ # falloff needs to be scaled for this tool
+ falloff = falloff / 10
+ v.co += (vert_scale_vec * BRUSH_PRESSURE) * falloff # FLAT BRUSH
+
+ # Clamp back to original x if needs be.
+ if XPLANE_CLIP and origx:
+ v.co.x = 0
+
+ if BRUSH_MODE == 3: # ROTATE.
+
+ if DISPLACE_NORMAL_MODE == 1: # VERTEX NORMAL
+ ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', iFaceNormal) # Cant use vertex normal, use face normal
+ elif DISPLACE_NORMAL_MODE == 2: # MEDIAN NORMAL
+ ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', medainNormal) # Cant use vertex normal, use face normal
+ elif DISPLACE_NORMAL_MODE == 3: # FACE NORMAL
+ ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', iFaceNormal) # Cant use vertex normal, use face normal
+ elif DISPLACE_NORMAL_MODE == 4: # VIEW NORMAL
+ ROTATE_MATRIX = Mathutils.RotationMatrix(BRUSH_PRESSURE*10, 4, 'r', Direction) # Cant use vertex normal, use face normal
+ # Brush code
+
+ for v,l in brush_verts:
+
+ if XPLANE_CLIP:
+ origx = False
+ if abs(v.co.x) < 0.001: origx = True
+
+ # MARK THE VERT AS DIRTY.
+ v.sel = 1
+ falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
+
+ # Vectors handeled with rotation matrix creation.
+ rot_vert_loc = (ROTATE_MATRIX * (v.co-best_isect)) + best_isect
+ v.co = (v.co*(1-falloff)) + (rot_vert_loc*(falloff))
+
+ # Clamp back to original x if needs be.
+ if XPLANE_CLIP and origx:
+ v.co.x = 0
+
+ elif BRUSH_MODE == 4: # RELAX
+ vert_orig_loc = [Vector(v.co) for v in me.verts ] # save orig vert location.
+ #vertOrigNor = [Vector(v.no) for v in me.verts ] # save orig vert location.
+
+ # Brush code
+ for v,l in brush_verts:
+
+ if XPLANE_CLIP:
+ origx = False
+ if abs(v.co.x) < 0.001: origx = True
+
+ v.sel = 1 # Mark the vert as dirty.
+ falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
+ connected_verts = verts_connected_by_edge[v.index]
+ relax_point = reduce(lambda a,b: a + vert_orig_loc[b.index], connected_verts, Mathutils.Vector(0,0,0)) * (1.0/len(connected_verts))
+ falloff = falloff * BRUSH_PRESSURE
+ # Old relax.
+ #v.co = (v.co*(1-falloff)) + (relax_point*(falloff))
+
+ ll = (v.co-relax_point).length
+ newpoint = (v.co*(1-falloff)) + (relax_point*(falloff)) - v.co
+ newpoint = newpoint * (1/(1+ll))
+ v.co = v.co + newpoint
+
+ '''
+ # New relax
+ relax_normal = vertOrigNor[v.index]
+ v1,v2,v3,v4 = v.co, v.co+relax_normal, relax_point-(relax_normal*10), relax_point+(relax_normal*10)
+ print v1,v2,v3,v4
+ try:
+ a,b = LineIntersect(v1,v2,v3,v4) # Scale the normal to make a line. we know we will intersect with.
+ v.co = (v.co*(1-falloff)) + (a*(falloff))
+ except:
+ pass
+ '''
+
+ # Clamp back to original x if needs be.
+ if XPLANE_CLIP and origx:
+ v.co.x = 0
+
+ elif BRUSH_MODE == 5: # GOO
+ #print last_best_isect, best_isect, 'AA'
+ if not last_best_isect:
+ last_best_isect = best_isect
+
+ # Set up a triangle orthographic to the view plane
+ gooPlane = [best_isect, CrossVecs(best_isect, Direction), None]
+
+
+ if DISPLACE_NORMAL_MODE == 4: # View Normal
+ tempRotMatrix = Mathutils.RotationMatrix(90, 3, 'r', Direction)
+ else:
+ tempRotMatrix = Mathutils.RotationMatrix(90, 3, 'r', CrossVecs(best_face.no, Direction))
+
+ gooPlane[2] = best_isect + (tempRotMatrix * gooPlane[1])
+ gooPlane[1] = gooPlane[1] + best_isect
+
+ continue # we need another point of reference.
+
+ elif last_best_isect == best_isect:
+ # Mouse has not moved, no point in trying to goo.
+ continue
+ else:
+ if goo_dir_vec:
+ last_goo_dir_vec = goo_dir_vec
+ # The direction the mouse moved in 3d space. use for gooing
+
+ # Modify best_isect so its not moving allong the view z axis.
+ # Assume Origin hasnt changed since the view wont change while the mouse is drawing. ATM.
+ best_isect = Intersect( gooPlane[0], gooPlane[1], gooPlane[2], Direction, Origin, 0) # Non clipped
+ goo_dir_vec = (best_isect - last_best_isect) * 2
+
+
+ # make a goo rotation matrix so the head of the goo rotates with the mouse.
+ """
+ if last_goo_dir_vec and goo_dir_vec != last_goo_dir_vec:
+ '''
+ vmi = Matrix(Window.GetViewMatrix()); vmi.invert() # the inverse viewing matrix
+ a = last_goo_dir_vec * vmi
+ b = goo_dir_vec * vmi
+ c = Angle2D(a.x, a.y, b.x, b.y)
+ gooRotMatrix = Mathutils.RotationMatrix((c * goo_dir_vec.length)*-20, 3, 'r', Direction)
+ '''
+ pass
+ else:
+ gooRotMatrix = None
+ """
+
+ if goo_dir_vec.x == 0 and goo_dir_vec.y == 0 and goo_dir_vec.z == 0:
+ continue
+
+ # Brush code
+ for v,l in brush_verts:
+
+ if XPLANE_CLIP:
+ origx = False
+ if abs(v.co.x) < 0.001: origx = True
+
+ # MARK THE VERT AS DIRTY.
+ v.sel = 1
+
+ ''' # ICICLES!!!
+ a = AngleBetweenVecs(goo_dir_vec, v.no)
+ if a > 66:
+ continue
+
+ l = l * ((1+a)/67.0)
+ l = max(0.00000001, l)
+ '''
+
+ falloff = (BRUSH_RADIUS-l) / BRUSH_RADIUS # falloff between 0 and 1
+ goo_loc = (v.co*(1-falloff)) + ((v.co+goo_dir_vec) *falloff)
+
+ v.co = (goo_loc*BRUSH_PRESSURE) + (v.co*(1-BRUSH_PRESSURE))
+
+ '''
+ if gooRotMatrix:
+ rotatedVertLocation = (gooRotMatrix * (v.co-best_isect)) + best_isect
+ v.co = (v.co*(1-falloff)) + (rotatedVertLocation*(falloff))
+ # USe for goo only.
+ '''
+
+ # Clamp back to original x if needs be.
+ if XPLANE_CLIP and origx:
+ v.co.x = 0
+
+
+ # Remember for the next sample
+ last_best_isect = best_isect
+ last_goo_dir_vec = goo_dir_vec
+
+ # Post processing after the verts have moved
+ # Subdivide any large edges, all but relax.
+
+ MAX_SUBDIV = 10 # Maximum number of subdivisions per redraw. makes things useable.
+ SUBDIV_COUNT = 0
+ # Cant use adaptive geometry for relax because it keeps connectivity data.
+ if ADAPTIVE_GEOMETRY and (BRUSH_MODE == 1 or BRUSH_MODE == 2 or BRUSH_MODE == 3 or BRUSH_MODE == 5):
+ Mesh.Mode(Mesh.SelectModes['EDGE'])
+ orig_len_edges = 0
+ #print 'ADAPTIVE_GEOMETRY'
+ while len(me.edges) != orig_len_edges and SUBDIV_COUNT < MAX_SUBDIV:
+ #print 'orig_len_edges', len(me.edges)
+ #me = ob.getData(mesh=1)
+ orig_len_edges = len(me.edges)
+ EDGE_COUNT = 0
+ for ed in me.edges:
+ if ed.v1.sel or ed.v2.sel:
+ l = (ed.v1.co - ed.v2.co).length
+ if l > max(RESOLUTION_MIN*1.5, BRUSH_RADIUS):
+ #if l > BRUSH_RADIUS:
+ #print 'adding edge'
+ ed.flag |= SEL_FLAG
+ #ed.flag = 35
+ SUBDIV_COUNT += 1
+ EDGE_COUNT +=1
+ """
+ elif l < RESOLUTION_MIN:
+ '''
+ print 'removing edge'
+ v1 =e.v1
+ v2 =e.v2
+ v1.co = v2.co = (v1.co + v2.co) * 0.5
+ v1.sel = v2.sel = 1
+ me.remDoubles(0.001)
+ me = ob.getData(mesh=1)
+ break
+ '''
+ # Remove edge in python
+ print 'removing edge'
+ v1 =ed.v1
+ v2 =ed.v2
+ v1.co = v2.co = (v1.co + v2.co) * 0.5
+
+ removeDouble(v1, v2, me)
+ me = ob.getData(mesh=1)
+ break
+ """
+
+ if EDGE_COUNT:
+ me.subdivide(1)
+ me = ob.getData(mesh=1)
+ filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
+
+ # Deselect all, we know theres only 2 selected
+ '''
+ for ee in me.edges:
+ if ee.flag & SEL_FLAG:
+ #ee.flag &= ~SEL_FLAG
+ ee.flag = 32
+ elif l < RESOLUTION_MIN:
+ print 'removing edge'
+ e.v1.co = e.v2.co = (e.v1.co + e.v2.co) * 0.5
+ me.remDoubles(0.001)
+ break
+ '''
+ # Done subdividing
+ # Now remove doubles
+ #print Mesh.SelectModes['VERT']
+ #Mesh.Mode(Mesh.SelectModes['VERTEX'])
+
+ filter(lambda v: setattr(v, 'sel', 1), me.verts)
+ filter(lambda v: setattr(v[0], 'sel', 0), brush_verts)
+
+ # Cycling editmode is too slow.
+
+ remdoubles = False
+ for ed in me.edges:
+
+ if (not ed.v1.sel) and (not ed.v1.sel):
+ if XPLANE_CLIP:
+ # If 1 vert is on the edge and abother is off dont collapse edge.
+ if (abs(ed.v1.co.x) < 0.001) !=\
+ (abs(ed.v2.co.x) < 0.001):
+ continue
+ l = (ed.v1.co - ed.v2.co).length
+ if l < RESOLUTION_MIN:
+ ed.v1.sel = ed.v2.sel = 1
+ newco = (ed.v1.co + ed.v2.co)*0.5
+ ed.v1.co.x = ed.v2.co.x = newco.x
+ ed.v1.co.y = ed.v2.co.y = newco.y
+ ed.v1.co.z = ed.v2.co.z = newco.z
+ remdoubles = True
+
+ #if remdoubles:
+
+
+ filter(lambda v: setattr(v, 'sel', 0), me.verts)
+ #Mesh.Mode(Mesh.SelectModes['EDGE'])
+ # WHILE OVER
+ # Clean up selection.
+ #for v in me.verts:
+ # v.sel = 0
+ '''
+ for ee in me.edges:
+ if ee.flag & SEL_FLAG:
+ ee.flag &= ~SEL_FLAG
+ #ee.flag = 32
+ '''
+ filter(lambda ed: setattr(ed, 'flag', 32), me.edges)
+
+
+ if XPLANE_CLIP:
+ filter(lambda v: setattr(v.co, 'x', max(0, v.co.x)), me.verts)
+
+
+ me.update()
+ #Window.SetCursorPos(best_isect.x, best_isect.y, best_isect.z)
+ Window.Redraw(Window.Types.VIEW3D)
+ #Window.DrawProgressBar(1.0, '')
+ if STATIC_MESH:
+ #try:
+ mesh_static.verts = None
+ print len(mesh_static.verts)
+ mesh_static.update()
+ #except:
+ # pass
+ if FIX_TOPOLOGY:
+ fix_topolagy(me)
+
+ # Remove short edges of we have edaptive geometry enabled.
+ if ADAPTIVE_GEOMETRY:
+ Mesh.Mode(Mesh.SelectModes['VERTEX'])
+ filter(lambda v: setattr(v, 'sel', 1), me.verts)
+ me.remDoubles(0.001)
+ print 'removing doubles'
+ me = ob.getData(mesh=1) # Get new vert data
+ Blender.event = Draw.LEFTMOUSE
+
+ Mesh.Mode(Mesh.SelectModes['EDGE'])
+
+ if i:
+ Window.EditMode(1)
+ if not is_editmode: # User was in edit mode, so stay there.
+ Window.EditMode(0)
+ print '100 draws in %.6f' % (((Blender.sys.time()-time) / float(i))*100)
+
+if __name__ == '__main__':
+ event_main() \ No newline at end of file
diff --git a/release/scripts/bpymodules/boxpack2d.py b/release/scripts/bpymodules/boxpack2d.py
new file mode 100644
index 00000000000..5acfe06e563
--- /dev/null
+++ b/release/scripts/bpymodules/boxpack2d.py
@@ -0,0 +1,478 @@
+'''
+# 2D Box packing function used by archimap
+# packs any list of 2d boxes into a square and returns a list of packed boxes.
+# Example of usage.
+import boxpack2d
+
+# Build boxe list.
+# the unique ID is not used.
+# just the width and height.
+boxes2Pack = []
+anyUniqueID = 0; w = 2.2; h = 3.8
+boxes2Pack.append([anyUniqueID, w,h])
+anyUniqueID = 1; w = 4.1; h = 1.2
+boxes2Pack.append([anyUniqueID, w,h])
+anyUniqueID = 2; w = 5.2; h = 9.2
+boxes2Pack.append([anyUniqueID, w,h])
+anyUniqueID = 3; w = 8.3; h = 7.3
+boxes2Pack.append([anyUniqueID, w,h])
+anyUniqueID = 4; w = 1.1; h = 5.1
+boxes2Pack.append([anyUniqueID, w,h])
+anyUniqueID = 5; w = 2.9; h = 8.1
+boxes2Pack.append([anyUniqueID, w,h])
+anyUniqueID = 6; w = 4.2; h = 6.2
+boxes2Pack.append([anyUniqueID, w,h])
+# packedLs is a list of [(anyUniqueID, left, bottom, width, height)...]
+packWidth, packHeight, packedLs = boxpack2d.boxPackIter(boxes2Pack)
+'''
+
+from Blender import NMesh, Window
+
+# 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:
+ def __init__(self, width, height, id=None):
+
+ 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):
+ boxList.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 vert pool, stores used lists
+ packedVerts = vertList() # will be vertList()
+
+ 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
+ # buggy but dont know why???, dont use.
+ '''
+ 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):
+ 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
+ boxList.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.
+ boxList.packedVerts.sortCorner(unpackedboxes.boxes[freeBoxIdx].width, unpackedboxes.boxes[freeBoxIdx].height)
+
+ vertIdx = 0
+
+ while vertIdx < len(boxList.packedVerts.verts):
+ baseVert = boxList.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
+
+ boxList.packedVerts.verts = [] # Free the list, so it dosent use ram between runs.
+
+ self.width = packedboxes.width
+ self.height = packedboxes.height
+ # All boxes as a list - X/Y/WIDTH/HEIGHT
+ def list(self):
+ return [(b.id, b.getLeft(), b.getBottom(), b.width, b.height ) for b in self.boxes]
+
+
+''' Define all globals here '''
+# vert IDX's, make references easier to understand.
+BL = 0; TR = 1; TL = 2; BR = 3
+
+# iterate through these
+# Flags for vert idx used quads
+BLF = 1; TRF = 2; TLF = 4; BRF = 8
+quadFlagLs = (BLF,BRF,TLF,TRF)
+
+
+# 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() \ No newline at end of file
diff --git a/release/scripts/image_edit.py b/release/scripts/image_edit.py
new file mode 100644
index 00000000000..17dc182d235
--- /dev/null
+++ b/release/scripts/image_edit.py
@@ -0,0 +1,95 @@
+#!BPY
+"""
+Name: 'Image Edit (External App)'
+Blender: 241
+Group: 'UV'
+Tooltip: 'Edits the image in another application. The Gimp for eg.'
+"""
+
+__author__ = "Campbell Barton"
+__url__ = ["blender", "elysiun"]
+__version__ = "1.0"
+
+__bpydoc__ = """\
+This script opens the current image in an external application for editing.
+
+Useage:
+Choose an image for editing in the UV/Image view.
+Select UVs, Image Edit (External App)
+For first time users try running the default application *
+If the application does not open you can type in the full path.
+The last entered application will be saved as a default.
+
+* Note, Start for win32 and open for macos will use system default assosiated application.
+"""
+
+# ***** 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 *****
+# --------------------------------------------------------------------------
+
+
+try:
+ import os
+ import sys as py_sys
+ platform = py_sys.platform
+except:
+ Draw.PupMenu('Error, python not installed')
+ os=None
+
+import Blender
+from Blender import Image, sys, Draw, Registry
+
+def main():
+ image = Image.GetCurrent()
+ if not image: # Image is None
+ print 'ERROR: You must select an active Image.'
+ return
+ imageFileName = sys.expandpath( image.filename )
+
+ pupblock = [imageFileName.split('/')[-1].split('\\')[-1]]
+
+ try:
+ appstring = Registry.GetKey('ExternalImageEditor', True)
+ appstring = appstring['path']
+ except:
+ pupblock.append('first time, set path.')
+ if platform == 'win32':
+ appstring = 'start "%f"'
+ elif platform == 'darwin':
+ appstring = 'open "%f"'
+ else:
+ appstring = 'gimp-remote "%f"'
+
+ appstring_but = Draw.Create(appstring)
+
+ pupblock.append(('editor: ', appstring_but, 0, 48, 'Path to application, %f will be replaced with the image path.'))
+
+ if not Draw.PupBlock('External Image Editor...', pupblock):
+ return
+
+ appstring = appstring_but.val
+
+ Registry.SetKey('ExternalImageEditor', {'path':appstring}, True)
+
+ # -------------------------------
+
+ appstring = appstring.replace('%f', imageFileName)
+ os.system(appstring)
+
+if __name__ == '__main__' and os != None:
+ main() \ No newline at end of file
diff --git a/release/scripts/mesh_bbrush_menu.py b/release/scripts/mesh_bbrush_menu.py
new file mode 100644
index 00000000000..e79e2095608
--- /dev/null
+++ b/release/scripts/mesh_bbrush_menu.py
@@ -0,0 +1,44 @@
+#!BPY
+"""
+Name: 'B-Brush Sculpter'
+Blender: 240
+Group: 'Mesh'
+Tooltip: 'Sculpt the active mesh (adds a scriptlink)'
+"""
+
+import Blender
+def main():
+ name = 'mesh_bbrush.py'
+ for t in Blender.Text.Get():
+ if t.name.startswith(name):
+ Blender.Draw.PupMenu('ERROR: Script "%s" alredy imported, aborting load.' % name)
+ return
+
+ # Load the text
+ datadir = Blender.Get('datadir')
+ if not datadir.endswith(Blender.sys.sep):
+ datadir += Blender.sys.sep
+ path= datadir + name
+ try:
+ t = Blender.Text.Load(path)
+ except:
+ Blender.Draw.PupMenu('ERROR: "%s" not found.' % path)
+
+ pup_input = [\
+ 'B-Brush Usage (message only)%t',
+ 'Enable B-Brush by',
+ 'selecting the "View" menu, then',
+ '"Space Handeler Scripts",',
+ '"%s"' % name,
+ 'LMB to sculpt, RMB for prefs, Shift reverses pressure',
+ ]
+
+ #Blender.Draw.PupBlock('%s loaded' % name ,pup_input)
+ Blender.Draw.PupMenu('|'.join(pup_input))
+
+if __name__ == '__main__':
+
+
+
+ main()
+ \ No newline at end of file
diff --git a/release/scripts/mesh_cleanup.py b/release/scripts/mesh_cleanup.py
new file mode 100644
index 00000000000..09b1ef7376c
--- /dev/null
+++ b/release/scripts/mesh_cleanup.py
@@ -0,0 +1,184 @@
+#!BPY
+"""
+Name: 'Clean meshes'
+Blender: 228
+Group: 'Mesh'
+Tooltip: 'Clean unused data from all selected mesh objects.'
+"""
+from Blender import *
+from Blender.Mathutils import TriangleArea
+
+def rem_free_verts(me):
+ vert_users = [0] * len(me.verts)
+ for f in me.faces:
+ for v in f.v:
+ vert_users[v.index]+=1
+
+ for e in me.edges:
+ for v in e: # loop on edge verts
+ vert_users[v.index]+=1
+
+ verts_free = []
+ for i, users in enumerate(vert_users):
+ if not users:
+ verts_free.append(i)
+
+ if verts_free:
+ pass
+ me.verts.delete(verts_free)
+ return len(verts_free)
+
+def rem_free_edges(me, limit=None):
+ ''' Only remove based on limit if a limit is set, else remove all '''
+ def sortPair(a,b):
+ return min(a,b), max(a,b)
+
+ edgeDict = {} # will use a set when python 2.4 is standard.
+
+ for f in me.faces:
+ for i in xrange(len(f.v)):
+ edgeDict[sortPair(f.v[i].index, f.v[i-1].index)] = None
+
+ edges_free = []
+ for e in me.edges:
+ if not edgeDict.has_key(sortPair(e.v1.index, e.v2.index)):
+ edges_free.append(e)
+
+ if limit != None:
+ edges_free = [e for e in edges_free if (e.v1.co-e.v2.co).length <= limit]
+
+ me.edges.delete(edges_free)
+ return len(edges_free)
+
+def rem_area_faces(me, limit=0.001):
+ ''' Faces that have an area below the limit '''
+ def faceArea(f):
+ if len(f.v) == 3:
+ return TriangleArea(f.v[0].co, f.v[1].co, f.v[2].co)
+ elif len(f.v) == 4:
+ return\
+ TriangleArea(f.v[0].co, f.v[1].co, f.v[2].co) +\
+ TriangleArea(f.v[0].co, f.v[2].co, f.v[3].co)
+ rem_faces = [f for f in me.faces if faceArea(f) <= limit]
+ if rem_faces:
+ me.faces.delete( 0, rem_faces )
+ return len(rem_faces)
+
+def rem_perimeter_faces(me, limit=0.001):
+ ''' Faces whos combine edge length is below the limit '''
+ def faceEdLen(f):
+ if len(f.v) == 3:
+ return\
+ (f.v[0].co-f.v[1].co).length +\
+ (f.v[1].co-f.v[2].co).length +\
+ (f.v[2].co-f.v[0].co).length
+ elif len(f.v) == 4:
+ return\
+ (f.v[0].co-f.v[1].co).length +\
+ (f.v[1].co-f.v[2].co).length +\
+ (f.v[2].co-f.v[3].co).length +\
+ (f.v[3].co-f.v[0].co).length
+ rem_faces = [f for f in me.faces if faceEdLen(f) <= limit]
+ if rem_faces:
+ me.faces.delete( 0, rem_faces )
+ return len(rem_faces)
+
+def main():
+
+ def getLimit(text):
+ return Draw.PupFloatInput(text, 0.001, 0.0, 1.0, 0.1, 4)
+
+
+ scn = Scene.GetCurrent()
+ obsel = Object.GetSelected()
+ actob = scn.getActiveObject()
+
+ is_editmode = Window.EditMode()
+
+ # Edit mode object is not active, add it to the list.
+ if is_editmode and (not actob.sel):
+ obsel.append(actob)
+
+ meshes = [ob.getData(mesh=1) for ob in obsel if ob.getType() == 'Mesh']
+
+
+ #====================================#
+ # Popup menu to select the functions #
+ #====================================#
+ '''
+ if not meshes:
+ Draw.PupMenu('ERROR%t|no meshes in selection')
+ return
+ method = Draw.PupMenu("""
+Clean Mesh, Remove...%t|
+Verts: free standing|
+Edges: not in a face|
+Edges: below a length|
+Faces: below an area|%l|
+All of the above|""")
+ if method == -1:
+ return
+
+ if method >= 3:
+ limit = getLimit('threshold: ')
+
+ print 'method', method
+ '''
+
+
+ CLEAN_VERTS_FREE = Draw.Create(1)
+ CLEAN_EDGE_NOFACE = Draw.Create(0)
+ CLEAN_EDGE_SMALL = Draw.Create(0)
+ CLEAN_FACE_PERIMETER = Draw.Create(0)
+ CLEAN_FACE_SMALL = Draw.Create(0)
+ limit = Draw.Create(0.01)
+
+ # Get USER Options
+
+ pup_block = [\
+ ('Verts: free', CLEAN_VERTS_FREE, 'Remove verts that are not used by an edge or a face.'),\
+ ('Edges: free', CLEAN_EDGE_NOFACE, 'Remove edges that are not in a face.'),\
+ ('Edges: short', CLEAN_EDGE_SMALL, 'Remove edges that are below the length limit.'),\
+ ('Faces: small perimeter', CLEAN_FACE_PERIMETER, 'Remove faces below the perimeter limit.'),\
+ ('Faces: small area', CLEAN_FACE_SMALL, 'Remove faces below the area limit (may remove faces stopping T-face artifacts).'),\
+ ('limit: ', limit, 0.001, 1.0, 'Limit used for the area and length tests above (a higher limit will remove more data).'),\
+ ]
+
+
+ if not Draw.PupBlock('Clean Selected Meshes...', pup_block):
+ return
+
+
+ CLEAN_VERTS_FREE = CLEAN_VERTS_FREE.val
+ CLEAN_EDGE_NOFACE = CLEAN_EDGE_NOFACE.val
+ CLEAN_EDGE_SMALL = CLEAN_EDGE_SMALL.val
+ CLEAN_FACE_PERIMETER = CLEAN_FACE_PERIMETER.val
+ CLEAN_FACE_SMALL = CLEAN_FACE_SMALL.val
+ limit = limit.val
+
+ if is_editmode: Window.EditMode(0)
+
+ rem_face_count = rem_edge_count = rem_vert_count = 0
+
+ for me in meshes:
+ if CLEAN_FACE_SMALL:
+ rem_face_count += rem_area_faces(me, limit)
+
+ if CLEAN_FACE_PERIMETER:
+ rem_face_count += rem_perimeter_faces(me, limit)
+
+ if CLEAN_EDGE_SMALL: # for all use 2- remove all edges.
+ rem_edge_count += rem_free_edges(me, limit)
+
+ if CLEAN_EDGE_NOFACE:
+ rem_edge_count += rem_free_edges(me)
+
+ if CLEAN_VERTS_FREE:
+ rem_vert_count += rem_free_verts(me)
+
+ if is_editmode: Window.EditMode(0)
+ Draw.PupMenu('Removed from ' + str(len(meshes)) +' Mesh(es)%t|' + 'Verts:' + str(rem_vert_count) + ' Edges:' + str(rem_edge_count) + ' Faces:' + str(rem_face_count))
+
+if __name__ == '__main__':
+ main()
+
diff --git a/release/scripts/mesh_tri2quad.py b/release/scripts/mesh_tri2quad.py
new file mode 100644
index 00000000000..4a73418aee4
--- /dev/null
+++ b/release/scripts/mesh_tri2quad.py
@@ -0,0 +1,453 @@
+#!BPY
+
+"""
+Name: 'Triangles to Quads'
+Blender: 240
+Group: 'Mesh'
+Tooltip: 'Triangles to Quads for all selected mesh objects.'
+"""
+
+__author__ = "Campbell Barton AKA Ideasman"
+__url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun"]
+
+__bpydoc__ = """\
+This script joins any triangles into quads for all selected mesh objects.
+
+Usage:
+
+Select the mesh(es) and run this script. Mesh data will be edited in place.
+so make a backup copy first if your not sure of the results
+
+The limit value allows you to choose how pedantic the algorithum is when detecting errors between 2 triangles.
+Over 50 could result in quads that are not correct.
+
+The joining of quads takes into account UV mapping, UV Images and Vertex colours
+and will not join faces that have mis-matching data.
+"""
+
+
+from Blender import Object, Mathutils, Draw, Window, sys
+import math
+
+TRI_LIST = (0,1,2)
+
+vecAngle = Mathutils.AngleBetweenVecs
+TriangleNormal = Mathutils.TriangleNormal
+
+#=============================================================================#
+# All measurement algorithums for face compatibility when joining into quads #
+# every function returns a value between 0.0 and 1.0 #
+#=============================================================================#
+# total diff is 1.0, no diff is 0.0
+# measure accross 2 possible triangles in the imagined quad.
+def isfaceNoDiff(imagQuag):
+ # Divide the quad one way and measure normals
+ noA1 = TriangleNormal(imagQuag[0].co, imagQuag[1].co, imagQuag[2].co)
+ noA2 = TriangleNormal(imagQuag[0].co, imagQuag[2].co, imagQuag[3].co)
+
+ if noA1 == noA2:
+ normalADiff = 0.0
+ else:
+ try:
+ normalADiff = vecAngle(noA1, noA2)
+ except:
+ #print noA1, noA2
+ normalADiff = 179
+
+ #print normalADiff, noA1, noA2
+
+ # Alternade division of the quad
+ noB1 = TriangleNormal(imagQuag[1].co, imagQuag[2].co, imagQuag[3].co)
+ noB2 = TriangleNormal(imagQuag[3].co, imagQuag[0].co, imagQuag[1].co)
+ if noB1 == noB2:
+ normalBDiff = 0.0
+ else:
+ try:
+ normalBDiff = vecAngle(noB1, noB2)
+ except:
+ # print noB1, noB2
+ normalBDiff = 179
+
+ # Should never be NAN anymore.
+ '''
+ if normalBDiff != normalBDiff or normalADiff != normalADiff:
+ raise "NAN"
+ '''
+ # The greatest possible difference is 180 for each
+ return (normalADiff/360) + (normalBDiff/360)
+
+
+# 4 90d angles == 0, each corner diff from 90 is added together.
+# 360 is total possible difference,
+def isfaceCoLin(imagQuag):
+
+ edgeVec1 = imagQuag[0].co - imagQuag[1].co
+ edgeVec2 = imagQuag[1].co - imagQuag[2].co
+ edgeVec3 = imagQuag[2].co - imagQuag[3].co
+ edgeVec4 = imagQuag[3].co - imagQuag[0].co
+
+ # Work out how different from 90 each edge is.
+ diff = 0
+ try:
+ diff += abs(vecAngle(edgeVec1, edgeVec2) - 90)
+ diff += abs(vecAngle(edgeVec2, edgeVec3) - 90)
+ diff += abs(vecAngle(edgeVec3, edgeVec4) - 90)
+ diff += abs(vecAngle(edgeVec4, edgeVec1) - 90)
+
+ except:
+ return 1.0
+
+ # Avoid devide by 0
+ if not diff:
+ return 0.0
+
+ return diff/360
+
+
+# Meause the areas of the 2 possible ways of subdividing the imagined quad.
+# if 1 is very different then the quad is concave.
+# We should probably throw out any pairs that are at all concave,
+# since even a slightly concacve paor will definetly be co linear.
+# though even virging on this can be a recipe for a bad join.
+def isfaceConcave(imagQuag):
+ # Add the 2 areas the deviding one way
+ areaA =\
+ Mathutils.TriangleArea(imagQuag[0].co, imagQuag[1].co, imagQuag[2].co) +\
+ Mathutils.TriangleArea(imagQuag[0].co, imagQuag[2].co, imagQuag[3].co)
+
+ # Add the tri's triangulated the alternate way.
+ areaB =\
+ Mathutils.TriangleArea(imagQuag[1].co, imagQuag[2].co, imagQuag[3].co) +\
+ Mathutils.TriangleArea(imagQuag[3].co, imagQuag[0].co, imagQuag[1].co)
+
+ # Make a ratio of difference so they are between 1 and 0
+ # Need to invert the value so 1.0 is 0
+ minarea = min(areaA, areaB)
+ maxarea = max(areaA, areaB)
+
+ # Aviod devide by 0
+ if maxarea == 0.0:
+ return 1
+ else:
+ return 1 - (minarea / maxarea)
+
+
+
+# This returns a list of verts, to test
+# dosent modify the actual faces.
+def meshJoinFacesTest(f1, f2, V1FREE, V2FREE):
+ # pretend face
+ dummyFace = f1.v[:]
+
+ # We know the 2 free verts. insert the f2 free vert in
+ if V1FREE is 0:
+ dummyFace.insert(2, f2.v[V2FREE])
+ elif V1FREE is 1:
+ dummyFace.append(f2.v[V2FREE])
+ elif V1FREE is 2:
+ dummyFace.insert(1, f2.v[V2FREE])
+
+ return dummyFace
+
+
+# Measure how good a quad the 2 tris will make,
+def measureFacePair(f1, f2, f1free, f2free):
+ # Make a imaginary quad. from 2 tris into 4 verts
+ imagFace = meshJoinFacesTest(f1, f2, f1free, f2free)
+ if imagFace is False:
+ return False
+
+ measure = 0 # start at 0, a lower value is better.
+
+ # Do a series of tests,
+ # each value will add to the measure value
+ # and be between 0 and 1.0
+
+ measure+= isfaceNoDiff(imagFace)
+ measure+= isfaceCoLin(imagFace)
+ measure+= isfaceConcave(imagFace)
+
+ # For debugging.
+ '''
+ a1= isfaceNoDiff(imagFace)
+ a2= isfaceCoLin(imagFace)
+ a3= isfaceConcave(imagFace)
+
+ print 'a1 %f' % a1
+ print 'a2 %f' % a2
+ print 'a3 %f' % a3
+
+ measure = a1+a2+a3
+ '''
+
+ return [f1,f2, measure/3, f1free, f2free]
+
+
+# We know the faces are good to join, simply execute the join
+# by making f1 into a quad and f2 inde an edge (2 vert face.)
+INSERT_LOOKUP = (2,3,1)
+OTHER_LOOKUP = ((1,2),(0,2),(0,1))
+def meshJoinFaces(f1, f2, V1FREE, V2FREE, mesh):
+
+ # Only used if we have edges.
+ # DEBUG
+ edgeVert1, edgeVert2 = OTHER_LOOKUP[V1FREE]
+ edgeVert1, edgeVert2 = f1[edgeVert1], f1[edgeVert2]
+
+
+ fverts = f1.v[:]
+ if mesh.hasFaceUV():
+ fuvs = f1.uv[:]
+ if f1.col:
+ fcols = f1.col[:]
+
+
+ # We know the 2 free verts. insert the f2 free vert in
+ # Work out which vert to insert
+ i = INSERT_LOOKUP[V1FREE]
+
+ # Insert the vert in the desired location.
+ fverts.insert(i, f2.v[V2FREE])
+ if mesh.hasFaceUV():
+ fuvs.insert(i, f2.uv[V2FREE])
+ if f1.col:
+ fcols.insert(i, f2.col[V2FREE])
+
+ # Assign the data to the faces.
+ f1.v = fverts
+ if mesh.hasFaceUV():
+ f1.uv = fuvs
+ if f1.col:
+ f1.col = fcols
+
+ # Make an edge from the 2nd vert.
+ # removing anything other then the free vert will
+ # remove the edge from accress the new quad
+ f2.v.pop(not V2FREE)
+
+
+ # DEBUG
+ if mesh.edges:
+ mesh.removeEdge(edgeVert1, edgeVert2)
+
+ #return f2
+
+
+
+def compare2(v1, v2, limit):
+ if v1[0] + limit > v2[0] and v1[0] - limit < v2[0] and\
+ v1[1] + limit > v2[1] and v1[1] - limit < v2[1]:
+ return True
+ return False
+
+
+def compare3(v1, v2, limit):
+ if v1[0] + limit > v2[0] and v1[0] - limit < v2[0] and\
+ v1[1] + limit > v2[1] and v1[1] - limit < v2[1] and\
+ v1[2] + limit > v2[2] and v1[2] - limit < v2[2]:
+ return True
+ return False
+
+
+UV_LIMIT = 0.005 # 0.0 to 1.0, can be greater then these bounds tho
+def compareFaceUV(f1, f2):
+ if f1.image == None and f1.image == None:
+ # No Image, ignore uv's
+ return True
+ elif f1.image != f2.image:
+ # Images differ, dont join faces.
+ return False
+
+ # We know 2 of these will match.
+ for v1i in TRI_LIST:
+ for v2i in TRI_LIST:
+ if f1[v1i] is f2[v2i]:
+ # We have a vertex index match.
+ # now match the UV's
+ if not compare2(f1.uv[v1i], f2.uv[v2i], UV_LIMIT):
+ # UV's are different
+ return False
+
+ return True
+
+
+COL_LIMIT = 3 # 0 to 255
+def compareFaceCol(f1, f2):
+ # We know 2 of these will match.
+ for v1i in TRI_LIST:
+ for v2i in TRI_LIST:
+ if f1[v1i] is f2[v2i]:
+ # We have a vertex index match.
+ # now match the UV's
+ if not compare3(f1.col[v1i], f2.col[v2i], COL_LIMIT):
+ # UV's are different
+ return False
+
+ return True
+
+def sortPair(a,b):
+ return min(a,b), max(a,b)
+
+def tri2quad(mesh, limit, selectedFacesOnly):
+ print '\nStarting tri2quad for mesh: %s' % mesh.name
+ print '\t...finding pairs'
+ time1 = sys.time() # Time the function
+ pairCount = 0
+
+ # each item in this list will be a list
+ # [face1, face2, measureFacePairValue]
+ facePairLs = []
+
+ if selectedFacesOnly:
+ faceList = [f for f in mesh.faces if f.sel if len(f) is 3 if not f.hide]
+ else:
+ faceList = [f for f in mesh.faces if len(f) == 3]
+
+ has_face_uv = mesh.hasFaceUV()
+ has_vert_col = mesh.hasVertexColours()
+
+
+ # Build a list of edges and tris that use those edges.
+ edgeFaceUsers = {}
+ for f in faceList:
+ i1,i2,i3 = f.v[0].index, f.v[1].index, f.v[2].index
+ ed1, ed2, ed3 = sortPair(i1, i2), sortPair(i2, i3), sortPair(i3, i1)
+
+ # The second int in the tuple is the free vert, its easier to store then to work it out again.
+ try: edgeFaceUsers[ed1].append((f, 2))
+ except: edgeFaceUsers[ed1] = [(f, 2)]
+
+ try: edgeFaceUsers[ed2].append((f, 0))
+ except: edgeFaceUsers[ed2] = [(f, 0)]
+
+ try: edgeFaceUsers[ed3].append((f, 1))
+ except: edgeFaceUsers[ed3] = [(f, 1)]
+
+
+ edgeDoneCount = 0
+ for faceListEdgeShared in edgeFaceUsers.itervalues():
+ if len(faceListEdgeShared) == 2:
+ f1, f1free = faceListEdgeShared[0]
+ f2, f2free = faceListEdgeShared[1]
+
+ if f1.mat != f2.mat:
+ pass # faces have different materials.
+ elif has_face_uv and (not compareFaceUV(f1, f2)):
+ pass # UV's are there but dont match.
+ elif has_vert_col and not compareFaceCol(f1, f2):
+ pass # Colours are there but dont match.
+ else:
+ # We can now store the qpair and measure
+ # there eligability for becoming 1 quad.
+ pair = measureFacePair(f1, f2, f1free, f2free)
+ if pair is not False and pair[2] < limit: # Some terraible error
+ facePairLs.append(pair)
+ pairCount += 1
+
+ edgeDoneCount += 1
+ if not edgeDoneCount % 20:
+ p = float(edgeDoneCount) / len(edgeFaceUsers)
+ Window.DrawProgressBar(p*0.5, 'Found pairs: %i' % pairCount)
+
+
+ # Sort, best options first :)
+ facePairLs.sort(lambda a,b: cmp(a[2], b[2]))
+ draws = 0
+ print '\t...joining pairs'
+ joinCount = 0
+ len_facePairLs = len(facePairLs)
+
+ #faces_to_remove = []
+
+ for pIdx, pair in enumerate(facePairLs):
+ # We know the last item is the best option, and no other face pairs will get in the way.
+ # now join the faces.
+
+ # If any of the faces have alredy been used then they will not have a lengh of 3 verts
+ if len(pair[0]) is 3 and len(pair[1]) is 3:
+ joinCount +=1
+ # print 'joining faces', joinCount, 'Limit:', facePairLs[-1][2]
+ #faces_to_remove.append( meshJoinFaces(pair[0], pair[1], mesh) )
+ meshJoinFaces(pair[0], pair[1], pair[3], pair[4], mesh)
+
+ if not pIdx % 20:
+ p = (0.5 + ((float((len_facePairLs - (len_facePairLs - pIdx))))/len_facePairLs*0.5)) * 0.99
+ Window.DrawProgressBar(p, 'Joining Face count: %i' % joinCount)
+ draws +=1
+
+ # print 'Draws', draws
+
+ # Remove faces, due to a bug in ZR's new BF-Blender CVS
+
+ fIdx = len(mesh.faces)
+ while fIdx:
+ fIdx -=1
+ if len(mesh.faces[fIdx]) < 3:
+ mesh.faces.pop(fIdx)
+
+ # Was buggy in 2.4.alpha fixed now I think
+ #mesh.faces[0].sel = 0
+
+
+ if joinCount:
+ print "tri2quad time for %s: %s joined %s tri's into quads" % (mesh.name, sys.time()-time1, joinCount)
+
+ #mesh.update(0, (mesh.edges != []), 0)
+ mesh.update(0, 0, 0)
+
+ else:
+ print "tri2quad nothing done %s: %s joined none" % (mesh.name, sys.time()-time1)
+
+
+
+#====================================#
+# Sanity checks #
+#====================================#
+def error(str):
+ Draw.PupMenu('ERROR%t|'+str)
+
+def main():
+ #selection = Object.Get()
+ selection = Object.GetSelected()
+ if len(selection) is 0:
+ error('No object selected')
+ return
+
+ # GET UNIQUE MESHES.
+ meshDict = {}
+ # Mesh names
+ for ob in selection:
+ if ob.getType() == 'Mesh':
+ meshDict[ob.getData(1)] = ob # dont do doubles.
+
+ # Create the variables.
+ selectedFacesOnly = Draw.Create(1)
+ limit = Draw.Create(25)
+
+
+ pup_block = [\
+ ('Selected Faces Only', selectedFacesOnly, 'Use only selected faces from all selected meshes.'),\
+ ('Limit: ', limit, 1, 100, 'A higher value will join more tris to quads, even if the quads are not perfect.'),\
+ ]
+ selectedFacesOnly = selectedFacesOnly.val
+ limit = limit.val
+
+ if not Draw.PupBlock('Tri2Quad for %i mesh object(s)' % len(meshDict), pup_block):
+ return
+
+ # We now know we can execute
+ is_editmode = Window.EditMode()
+ if is_editmode: Window.EditMode(0)
+
+ limit = limit/100.0 # Make between 1 and 0
+
+ for ob in meshDict.itervalues():
+ mesh = ob.getData()
+ tri2quad(mesh, limit, selectedFacesOnly)
+ if is_editmode: Window.EditMode(1)
+
+# Dont run when importing
+if __name__ == '__main__':
+ Window.DrawProgressBar(0.0, 'Triangles to Quads 1.1 ')
+ main()
+ Window.DrawProgressBar(1.0, '') \ No newline at end of file
diff --git a/release/scripts/object_batch_name_edit.py b/release/scripts/object_batch_name_edit.py
new file mode 100644
index 00000000000..f5cec15ecec
--- /dev/null
+++ b/release/scripts/object_batch_name_edit.py
@@ -0,0 +1,253 @@
+#!BPY
+
+"""
+Name: 'Batch Object Name Edit'
+Blender: 240
+Group: 'Object'
+Tooltip: 'Apply the chosen rule to rename all selected objects at once.'
+"""
+
+__author__ = "Campbell Barton"
+__url__ = ("blender", "elysiun")
+__version__ = "1.0"
+
+__bpydoc__ = """\
+"Batch Object Name Edit" allows you to change multiple names of Blender
+objects at once. It provides options to define if you want to: replace text
+in the current names, truncate their beginnings or endings or prepend / append
+strings to them.
+
+Usage:
+
+Select the objects to be renamed and run this script from the Object->Scripts
+menu of the 3d View.
+"""
+
+
+# $Id$
+#
+# --------------------------------------------------------------------------
+# Batch Name Edit 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 *
+
+global renameCount
+renameCount = 0
+
+def main():
+ global renameCount
+ # Rename the datablocks that are used by the object.
+ def renameLinkedDataFromObject():
+
+ # Result 1, we want to rename data
+ for ob in Object.GetSelected():
+ if ob.name == ob.getData(1):
+ return # Alredy the same name, dont bother.
+
+ try:
+ if ob.getType() == 'Mesh':
+ data = ob.getData(mesh=1) # use mesh so we dont have to update the nmesh.
+ else:
+ data = ob.getData()
+ data.name = ob.name
+ except:
+ # Maybe trying to renasme an empty, dont worry about this.
+ pass
+
+
+
+ def new():
+ global renameCount
+ NEW_NAME_STRING = Draw.Create('')
+ RENAME_LINKED = Draw.Create(0)
+ pup_block = [\
+ ('New Name: ', NEW_NAME_STRING, 19, 19, 'New Name'),\
+ ('Rename ObData', RENAME_LINKED, 'Renames objects data to match the obname'),\
+ ]
+
+ if not Draw.PupBlock('Replace in name...', pup_block):
+ return 0
+
+ NEW_NAME_STRING= NEW_NAME_STRING.val
+
+ Window.WaitCursor(1)
+ for ob in Object.GetSelected():
+ if ob.name != NEW_NAME_STRING:
+ ob.name = NEW_NAME_STRING
+ renameCount+=1
+
+ return RENAME_LINKED.val
+
+ def replace():
+ global renameCount
+ REPLACE_STRING = Draw.Create('')
+ WITH_STRING = Draw.Create('')
+ RENAME_LINKED = Draw.Create(0)
+
+ pup_block = [\
+ ('Replace: ', REPLACE_STRING, 19, 19, 'Text to find'),\
+ ('With:', WITH_STRING, 19, 19, 'Text to replace with'),\
+ ('Rename ObData', RENAME_LINKED, 'Renames objects data to match the obname'),\
+ ]
+
+ if not Draw.PupBlock('Replace in name...', pup_block) or\
+ ((not REPLACE_STRING.val) and (not WITH_STRING)):
+ return 0
+
+ REPLACE_STRING = REPLACE_STRING.val
+ WITH_STRING = WITH_STRING.val
+
+ Window.WaitCursor(1)
+ for ob in Object.GetSelected():
+ newname = ob.name.replace(REPLACE_STRING, WITH_STRING)
+ if ob.name != newname:
+ ob.name = newname
+ renameCount+=1
+ return RENAME_LINKED.val
+
+
+ def prefix():
+ global renameCount
+ PREFIX_STRING = Draw.Create('')
+ RENAME_LINKED = Draw.Create(0)
+
+ pup_block = [\
+ ('Prefix: ', PREFIX_STRING, 19, 19, 'Name prefix'),\
+ ('Rename ObData', RENAME_LINKED, 'Renames objects data to match the obname'),\
+ ]
+
+ if not Draw.PupBlock('Prefix...', pup_block) or\
+ not PREFIX_STRING.val:
+ return 0
+
+ PREFIX_STRING = PREFIX_STRING.val
+
+ Window.WaitCursor(1)
+ for ob in Object.GetSelected():
+ ob.name = PREFIX_STRING + ob.name
+ renameCount+=1 # we knows these are different.
+ return RENAME_LINKED.val
+
+ def suffix():
+ global renameCount
+ SUFFIX_STRING = Draw.Create('')
+ RENAME_LINKED = Draw.Create(0)
+
+ pup_block = [\
+ ('Suffix: ', SUFFIX_STRING, 19, 19, 'Name suffix'),\
+ ('Rename ObData', RENAME_LINKED, 'Renames objects data to match the obname'),\
+ ]
+
+ if not Draw.PupBlock('Suffix...', pup_block) or\
+ not SUFFIX_STRING.val:
+ return 0
+
+ SUFFIX_STRING = SUFFIX_STRING.val
+
+ Window.WaitCursor(1)
+ for ob in Object.GetSelected():
+ ob.name = ob.name + SUFFIX_STRING
+ renameCount+=1 # we knows these are different.
+ return RENAME_LINKED.val
+
+ def truncate_start():
+ global renameCount
+ TRUNCATE_START = Draw.Create(0)
+ RENAME_LINKED = Draw.Create(0)
+
+ pup_block = [\
+ ('Truncate Start: ', TRUNCATE_START, 0, 19, 'Truncate chars from the start of the name'),\
+ ('Rename ObData', RENAME_LINKED, 'Renames objects data to match the obname'),\
+ ]
+
+ if not Draw.PupBlock('Truncate Start...', pup_block) or\
+ not TRUNCATE_START.val:
+ return 0
+
+ Window.WaitCursor(1)
+ TRUNCATE_START = TRUNCATE_START.val
+ for ob in Object.GetSelected():
+ newname = ob.name[TRUNCATE_START: ]
+ ob.name = newname
+ renameCount+=1
+
+ return RENAME_LINKED.val
+
+
+ def truncate_end():
+ global renameCount
+ TRUNCATE_END = Draw.Create(0)
+ RENAME_LINKED = Draw.Create(0)
+
+ pup_block = [\
+ ('Truncate End: ', TRUNCATE_END, 0, 19, 'Truncate chars from the end of the name'),\
+ ('Rename ObData', RENAME_LINKED, 'Renames objects data to match the obname'),\
+ ]
+
+ if not Draw.PupBlock('Truncate End...', pup_block) or\
+ not TRUNCATE_END.val:
+ return 0
+
+ Window.WaitCursor(1)
+ TRUNCATE_END = TRUNCATE_END.val
+ for ob in Object.GetSelected():
+ newname = ob.name[: -TRUNCATE_END]
+ ob.name = newname
+ renameCount+=1
+
+ return RENAME_LINKED.val
+
+ def renameObjectFromLinkedData():
+ global renameCount
+ Window.WaitCursor(1)
+
+ for ob in Object.GetSelected():
+ newname = ob.getData(1)
+ if newname != None and ob.name != newname:
+ ob.name = newname
+ renameCount+=1
+ return 0
+
+
+ name = "Selected Object Names%t|New Name|Replace Text|Add Prefix|Add Suffix|Truncate Start|Truncate End|Rename Objects to Data Names"
+ result = Draw.PupMenu(name)
+
+ renLinked = 0 # Rename linked data to the object name?
+
+ if result == -1:
+ return
+ elif result == 1: renLinked= new()
+ elif result == 2: renLinked= replace()
+ elif result == 3: renLinked= prefix()
+ elif result == 4: renLinked= suffix()
+ elif result == 5: renLinked= truncate_start()
+ elif result == 6: renLinked= truncate_end()
+ elif result == 7: renameObjectFromLinkedData()
+
+ if renLinked:
+ renameLinkedDataFromObject()
+
+ Window.WaitCursor(0)
+
+ Draw.PupMenu('renamed: %d objects.' % renameCount)
+
+main()
diff --git a/release/scripts/ply_export.py b/release/scripts/ply_export.py
new file mode 100644
index 00000000000..86197692082
--- /dev/null
+++ b/release/scripts/ply_export.py
@@ -0,0 +1,113 @@
+#!BPY
+
+"""
+Name: 'PLY...'
+Blender: 237
+Group: 'Export'
+Tooltip: 'Export to Stanford PLY format'
+"""
+
+import Blender
+import meshtools
+import math
+
+__author__ = "Bruce Merry"
+__version__ = "0.9"
+__bpydoc__ = """\
+This script exports Stanford PLY files from Blender. It supports per-vertex
+normals and per-face colours and texture coordinates.
+"""
+
+# Copyright (C) 2004, 2005: Bruce Merry, bmerry@cs.uct.ac.za
+#
+# 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.
+
+def file_callback(filename):
+ if filename.find('.ply', -4) < 0: filename += '.ply'
+ file = open(filename, "wb")
+ objects = Blender.Object.GetSelected()
+ obj = objects[0]
+ mesh = objects[0].data
+
+ have_uv = mesh.hasFaceUV()
+ have_col = meshtools.has_vertex_colors(mesh)
+ verts = [] # list of dictionaries
+ vdict = {} # (index, normal, uv) -> new index
+ for (i, f) in enumerate(mesh.faces):
+ for (j, v) in enumerate(f.v):
+ index = v.index
+ key = index, tuple(v.no)
+ vdata = {'position': v.co, 'normal': v.no}
+ if have_uv:
+ vdata['uv'] = f.uv[j]
+ key = key + (tuple(f.uv[j]), )
+ if have_col:
+ vdata['col'] = f.col[j]
+ key = key + ((f.col[j].r, f.col[j].g, f.col[j].b, f.col[j].a), )
+ if not vdict.has_key(key):
+ vdict[key] = len(verts);
+ verts.append(vdata)
+ if not i % 100 and meshtools.show_progress:
+ Blender.Window.DrawProgressBar(float(i) / len(mesh.faces), "Organising vertices")
+
+ print >> file, "ply"
+ print >> file, "format ascii 1.0"
+ print >> file, "comment created by ply_export.py from Blender"
+ print >> file, "element vertex %d" % len(verts)
+ print >> file, "property float32 x"
+ print >> file, "property float32 y"
+ print >> file, "property float32 z"
+ print >> file, "property float32 nx"
+ print >> file, "property float32 ny"
+ print >> file, "property float32 nz"
+ if have_uv:
+ print >> file, "property float32 s"
+ print >> file, "property float32 t"
+ if have_col:
+ print >> file, "property uint8 red"
+ print >> file, "property uint8 green"
+ print >> file, "property uint8 blue"
+ print >> file, "element face %d" % len(mesh.faces)
+ print >> file, "property list uint8 int32 vertex_indices"
+ print >> file, "end_header"
+
+ for (i, v) in enumerate(verts):
+ print >> file, "%f %f %f %f %f %f" % (tuple(v['position']) + tuple(v['normal'])),
+ if have_uv: print >> file, "%f %f" % tuple(v['uv']),
+ if have_col: print >> file, "%u %u %u" % (v['col'].r, v['col'].g, v['col'].b),
+ print >> file
+ if not i % 100 and meshtools.show_progress:
+ Blender.Window.DrawProgressBar(float(i) / len(verts), "Writing vertices")
+ for (i, f) in enumerate(mesh.faces):
+ print >> file, "%d" % len(f.v),
+ for j in range(len(f.v)):
+ v = f.v[j]
+ index = v.index
+ key = index, tuple(v.no)
+ if have_uv:
+ key = key + (tuple(f.uv[j]), )
+ if have_col:
+ key = key + ((f.col[j].r, f.col[j].g, f.col[j].b, f.col[j].a), )
+ print >> file, "%d" % vdict[key],
+ print >> file
+ if not i % 100 and meshtools.show_progress:
+ Blender.Window.DrawProgressBar(float(i) / len(mesh.faces), "Writing faces")
+
+ Blender.Window.DrawProgressBar(1.0, '') # clear progressbar
+ file.close()
+ message = "Successfully exported " + Blender.sys.basename(filename)
+ meshtools.print_boxed(message)
+
+Blender.Window.FileSelector(file_callback, "PLY Export")
diff --git a/release/scripts/ply_import.py b/release/scripts/ply_import.py
new file mode 100644
index 00000000000..9df72ca8484
--- /dev/null
+++ b/release/scripts/ply_import.py
@@ -0,0 +1,292 @@
+#!BPY
+
+"""
+Name: 'PLY...'
+Blender: 237
+Group: 'Import'
+Tip: 'Import a Stanford PLY file'
+"""
+
+__author__ = 'Bruce Merry'
+__version__ = '0.92'
+__bpydoc__ = """\
+This script imports Stanford PLY files into Blender. It supports per-vertex
+normals, and per-face colours and texture coordinates.
+
+Usage:
+
+Run this script from "File->Import" and select the desired PLY file.
+"""
+
+# Copyright (C) 2004, 2005: Bruce Merry, bmerry@cs.uct.ac.za
+#
+# 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.
+
+
+# Updated by Campbell Barton AKA Ideasman, 10% faster code.
+
+# Portions of this code are taken from mod_meshtools.py in Blender
+# 2.32.
+
+import Blender, meshtools
+import re, struct, StringIO
+
+class element_spec:
+ name = ''
+ count = 0
+ def __init__(self, name, count):
+ self.name = name
+ self.count = count
+ self.properties = []
+
+ def load(self, format, stream):
+ if format == 'ascii':
+ stream = re.split('\s+', stream.readline())
+ return map(lambda x: x.load(format, stream), self.properties)
+
+ def index(self, name):
+ for i, p in enumerate(self.properties):
+ if p.name == name: return i
+ return -1
+
+class property_spec:
+ name = ''
+ list_type = ''
+ numeric_type = ''
+ def __init__(self, name, list_type, numeric_type):
+ self.name = name
+ self.list_type = list_type
+ self.numeric_type = numeric_type
+
+ def read_format(self, format, count, num_type, stream):
+ if format == 'ascii':
+ if (num_type == 's'):
+ ans = []
+ for i in xrange(count):
+ s = stream[i]
+ if len(s) < 2 or s[0] != '"' or s[-1] != '"':
+ print 'Invalid string', s
+ print 'Note: ply_import.py does not handle whitespace in strings'
+ return None
+ ans.append(s[1:-1])
+ stream[:count] = []
+ return ans
+ if (num_type == 'f' or num_type == 'd'):
+ mapper = float
+ else:
+ mapper = int
+ ans = map(lambda x: mapper(x), stream[:count])
+ stream[:count] = []
+ return ans
+ else:
+ if (num_type == 's'):
+ ans = []
+ for i in xrange(count):
+ fmt = format + 'i'
+ data = stream.read(struct.calcsize(fmt))
+ length = struct.unpack(fmt, data)[0]
+ fmt = '%s%is' % (format, length)
+ data = stream.read(struct.calcsize(fmt))
+ s = struct.unpack(fmt, data)[0]
+ ans.append(s[:-1]) # strip the NULL
+ return ans
+ else:
+ fmt = '%s%i%s' % (format, count, num_type)
+ data = stream.read(struct.calcsize(fmt));
+ return struct.unpack(fmt, data)
+
+ def load(self, format, stream):
+ if (self.list_type != None):
+ count = int(self.read_format(format, 1, self.list_type, stream)[0])
+ return self.read_format(format, count, self.numeric_type, stream)
+ else:
+ return self.read_format(format, 1, self.numeric_type, stream)[0]
+
+class object_spec:
+ 'A list of element_specs'
+ specs = []
+
+ def load(self, format, stream):
+ return dict([(i.name,[i.load(format, stream) for j in xrange(i.count) ]) for i in self.specs])
+
+ '''
+ answer = {}
+ for i in self.specs:
+ answer[i.name] = []
+ for j in xrange(i.count):
+ if not j % 100 and meshtools.show_progress:
+ Blender.Window.DrawProgressBar(float(j) / i.count, 'Loading ' + i.name)
+ answer[i.name].append(i.load(format, stream))
+ return answer
+ '''
+
+
+def read(filename):
+ format = ''
+ version = '1.0'
+ format_specs = {'binary_little_endian': '<',
+ 'binary_big_endian': '>',
+ 'ascii': 'ascii'}
+ type_specs = {'char': 'b',
+ 'uchar': 'B',
+ 'int8': 'b',
+ 'uint8': 'B',
+ 'int16': 'h',
+ 'uint16': 'H',
+ 'int': 'i',
+ 'int32': 'i',
+ 'uint': 'I',
+ 'uint32': 'I',
+ 'float': 'f',
+ 'float32': 'f',
+ 'float64': 'd',
+ 'string': 's'}
+ obj_spec = object_spec()
+
+ try:
+ file = open(filename, 'rb')
+ signature = file.readline()
+ if (signature != 'ply\n'):
+ print 'Signature line was invalid'
+ return None
+ while 1:
+ tokens = re.split(r'[ \n]+', file.readline())
+ if (len(tokens) == 0):
+ continue
+ if (tokens[0] == 'end_header'):
+ break
+ elif (tokens[0] == 'comment' or tokens[0] == 'obj_info'):
+ continue
+ elif (tokens[0] == 'format'):
+ if (len(tokens) < 3):
+ print 'Invalid format line'
+ return None
+ if (tokens[1] not in format_specs.keys()):
+ print 'Unknown format', tokens[1]
+ return None
+ if (tokens[2] != version):
+ print 'Unknown version', tokens[2]
+ return None
+ format = tokens[1]
+ elif (tokens[0] == 'element'):
+ if (len(tokens) < 3):
+ print 'Invalid element line'
+ return None
+ obj_spec.specs.append(element_spec(tokens[1], int(tokens[2])))
+ elif (tokens[0] == 'property'):
+ if (not len(obj_spec.specs)):
+ print 'Property without element'
+ return None
+ if (tokens[1] == 'list'):
+ obj_spec.specs[-1].properties.append(property_spec(tokens[4], type_specs[tokens[2]], type_specs[tokens[3]]))
+ else:
+ obj_spec.specs[-1].properties.append(property_spec(tokens[2], None, type_specs[tokens[1]]))
+ obj = obj_spec.load(format_specs[format], file)
+
+ except IOError, (errno, strerror):
+ file.close()
+ return None
+
+ file.close()
+ return (obj_spec, obj);
+
+def add_face(vertices, varr, indices, uvindices, colindices):
+ face = Blender.NMesh.Face([varr[i] for i in indices])
+ for index in indices:
+ vertex = vertices[index];
+
+ if uvindices:
+ face.uv.append((vertex[uvindices[0]], 1.0 - vertex[uvindices[1]]))
+ if colindices:
+ if not uvindices: face.uv.append((0, 0)) # Force faceUV
+ face.col.append(Blender.NMesh.Col(vertex[colindices[0]], vertex[colindices[1]], vertex[colindices[2]], 255))
+ return face
+
+def filesel_callback(filename):
+ t = Blender.sys.time()
+ (obj_spec, obj) = read(filename)
+ if obj == None:
+ print 'Invalid file'
+ return
+ vmap = {}
+ varr = []
+ uvindices = None
+ noindices = None
+ colindices = None
+ for el in obj_spec.specs:
+ if el.name == 'vertex':
+ vindices = vindices_x, vindices_y, vindices_z = (el.index('x'), el.index('y'), el.index('z'))
+ if el.index('nx') >= 0 and el.index('ny') >= 0 and el.index('nz') >= 0:
+ noindices = (el.index('nx'), el.index('ny'), el.index('nz'))
+ if el.index('s') >= 0 and el.index('t') >= 0:
+ uvindices = (el.index('s'), el.index('t'))
+ if el.index('red') >= 0 and el.index('green') and el.index('blue') >= 0:
+ colindices = (el.index('red'), el.index('green'), el.index('blue'))
+ elif el.name == 'face':
+ findex = el.index('vertex_indices')
+
+
+ mesh = Blender.NMesh.GetRaw()
+ NMVert = Blender.NMesh.Vert
+ for v in obj['vertex']:
+
+ if noindices > 0:
+ x,y,z,nx,ny,nz = vkey =\
+ (v[vindices_x], v[vindices_y], v[vindices_z],\
+ v[noindices[0]], v[noindices[1]], v[noindices[2]])
+ else:
+ x,y,z = vkey = (v[vindices_x], v[vindices_y], v[vindices_z])
+ #if not vmap.has_key(vkey):
+ try: # try uses 1 less dict lookup
+ varr.append(vmap[vkey])
+ except:
+ nmv = NMVert(vkey[0], vkey[1], vkey[2])
+ mesh.verts.append(nmv)
+ if noindices > 0:
+ nmv.no[0] = vkey[3]
+ nmv.no[1] = vkey[4]
+ nmv.no[2] = vkey[5]
+ vmap[vkey] = nmv
+ varr.append(vmap[vkey])
+
+ verts = obj['vertex']
+ for f in obj['face']:
+ ind = f[findex]
+ nind = len(ind)
+ if nind <= 4:
+ mesh.faces.append(add_face(verts, varr, ind, uvindices, colindices))
+ else:
+ for j in xrange(nind - 2):
+ mesh.faces.append(add_face(verts, varr, (ind[0], ind[j + 1], ind[j + 2]), uvindices, colindices))
+
+
+ del obj # Reclaim memory
+
+ if noindices:
+ normals = 1
+ else:
+ normals = 0
+ objname = Blender.sys.splitext(Blender.sys.basename(filename))[0]
+ if not meshtools.overwrite_mesh_name:
+ objname = meshtools.versioned_name(objname)
+ Blender.NMesh.PutRaw(mesh, objname, not normals)
+ Blender.Object.GetSelected()[0].name = objname
+ Blender.Redraw()
+ Blender.Window.DrawProgressBar(1.0, '')
+ message = 'Successfully imported ' + Blender.sys.basename(filename) + ' ' + str(Blender.sys.time()-t)
+ meshtools.print_boxed(message)
+
+Blender.Window.FileSelector(filesel_callback, 'Import PLY')
+
diff --git a/release/scripts/xfig_export.py b/release/scripts/xfig_export.py
new file mode 100644
index 00000000000..94284120f34
--- /dev/null
+++ b/release/scripts/xfig_export.py
@@ -0,0 +1,441 @@
+#!BPY
+
+"""
+Name: 'xfig export (.fig)'
+Blender: 237
+Group: 'Export'
+Tooltip: 'Export selected mesh to xfig Format (.fig)'
+"""
+
+__author__ = "Dino Ghilardi "
+__url__ = ("blender", "elysiun")
+__version__ = "1.1"
+
+__bpydoc__ = """\
+ This script exports the selected mesh to xfig (www.xfig.org) file format (i.e.: .fig)
+
+ The starting point of this script was Anthony D'Agostino's raw triangle format export.
+ (some code is still here and there, cut'n pasted from his script)
+
+ Usage:<br>
+ Select the mesh to be exported and run this script from "File->Export" menu.
+ The toggle button 'export 3 files' enables the generation of 4 files: one global
+ and three with the three different views of the object.
+ This script is licensed under the GPL license. (c) Dino Ghilardi, 2005
+
+"""
+
+# .fig export, mostly brutally cut-n pasted from the
+# 'Raw triangle export' (Anthony D'Agostino, http://www.redrival.com/scorpius)|
+
+import Blender
+from Blender import NMesh, Draw, BGL
+from Blender.Window import DrawProgressBar
+#, meshtools
+import sys
+#import time
+
+# =================================
+# === Write xfig Format.===
+# =================================
+
+#globals definition and init
+mystring = ""
+mymsg = ""
+toggle=0
+sel3files=0
+maxX=-1000000000
+maxY=-1000000000
+maxZ=-1000000000
+minX=minY=minZ=10000000000
+boolmode=0 #0= export in inches, 1= export in cm
+dimscale=float(1200) #scale due to the cm/inches select. default: inches
+hidden_flag=0
+
+space = float(2) #space between figures, in blender units.
+scale= float(1200) #conversion scale to xfig units.
+guiscale=float(1) #scale shown on the ruler in the GUI
+#return values from gui items, just to deallocate them on exit.
+guiret1=guiret2=guiret3=guiret4=guiret5=guiret6=guiret7=0
+
+ScalePopup=0
+DistancePopup=0
+SpacePopup=0
+#end of globals definition
+
+
+def getmaxmin():
+ """Gets the max-min coordinates of the mesh"""
+
+ global maxX,maxY,maxZ,minX,minY,minZ
+ """Getting the extremes of the mesh to be exported"""
+ objects = Blender.Object.GetSelected()
+ objname = objects[0].name
+ meshname = objects[0].data.name
+ mesh = Blender.NMesh.GetRaw(meshname)
+ obj = Blender.Object.Get(objname)
+ #initializing max-min find.
+ # ...is there a standard python function to find those values?
+ face =mesh.faces[1]
+ if len(face.v)==3:
+ v1,v2,v3=face.v
+ if len(face.v)==4:
+ v1,v2,v3,v4=face.v
+ if len(face.v)==2:
+ v1,v2=face.v
+ #is the next condition a nonsense for a face? ...Anyway, to be sure....
+ if len(face.v)==1:
+ v1=face.v
+
+ maxX,maxY,maxZ = v1
+ minX, minY, minZ = v1
+
+ for face in mesh.faces:
+ #if (face.flag & Blender.NMesh.FaceFlags['ACTIVE']):
+ if len(face.v) == 3: # triangle
+ v1, v2, v3 = face.v
+ x1,y1,z1 = v1.co
+ x2,y2,z2 = v2.co
+ x3,y3,z3 = v3.co
+ maxX = max (maxX, x1, x2, x3)
+ maxY = max (maxY, y1, y2, y3)
+ maxZ = max (maxZ, z1, z2, z3)
+ minX = min (minX, x1, x2, x3)
+ minY = min (minY, y1, y2, y3)
+ minZ = min (minZ, z1, z2, z3)
+ elif len(face.v)==2:
+ v1,v2=face.v
+ x1,y1,z1 = v1.co
+ x2,y2,z2 = v2.co
+ maxX = max (maxX, x1, x2)
+ maxY = max (maxY, y1, y2)
+ maxZ = max (maxZ, z1, z2)
+ minX = min (minX, x1, x2)
+ minY = min (minY, y1, y2)
+ minZ = min (minZ, z1, z2)
+ elif len(face.v)==1:
+ v1=face.v
+ x1,y1,z1 = v1.co
+ maxX = max (maxX, x1)
+ maxY = max (maxY, y1)
+ maxZ = max (maxZ, z1)
+ minX = min (minX, x1)
+ minY = min (minY, y1)
+ minZ = min (minZ, z1)
+ elif len(face.v)==4:
+ v1,v2,v3,v4=face.v
+ x1,y1,z1 = v1.co
+ x2,y2,z2 = v2.co
+ x3,y3,z3 = v3.co
+ x4,y4,z4 = v4.co
+ maxX = max (maxX, x1, x2, x3, x4)
+ maxY = max (maxY, y1, y2, y3, y4)
+ maxZ = max (maxZ, z1, z2, z3, z4)
+ minX = min (minX, x1, x2, x3, x4)
+ minY = min (minY, y1, y2, y3, y4)
+ minZ = min (minZ, z1, z2, z3, z4)
+def xfigheader():
+ global export_type
+ print "#FIG 3.2 Produced by xfig version 3.2.5-alpha5"
+ print "Landscape"
+ print"Center"
+ if boolmode==0:
+ print"Inches"
+ else:
+ print"Metric"
+ #print export_type
+ print"Letter"
+ print"100.00"
+ print"Single"
+ print "-2"
+ print "1200 2"
+
+def xytransform (face):
+ """gives the face vertexes coordinates in the xfig format/translation (view xy)"""
+ v4=None
+ x4=y4=z4=None
+ if len(face.v)==3:
+ v1,v2,v3=face.v
+ else:
+ v1,v2,v3,v4=face.v
+ x1,y1,z1 = v1.co
+ x2,y2,z2 = v2.co
+ x3,y3,z3 = v3.co
+ y1=-y1
+ y2=-y2
+ y3=-y3
+ if v4 !=None:
+ x4,y4,z4 = v4.co
+ y4=-y4
+ return x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4
+
+def xztransform(face):
+ """gives the face vertexes coordinates in the xfig format/translation (view xz)"""
+ v4=None
+ x4=y4=z4=None
+ if len(face.v)==3:
+ v1,v2,v3=face.v
+ else:
+ v1,v2,v3,v4=face.v
+
+ #Order vertexes
+ x1,y1,z1 = v1.co
+ x2,y2,z2 = v2.co
+ x3,y3,z3 = v3.co
+ y1=-y1
+ y2=-y2
+ y3=-y3
+
+ z1=-z1+maxZ-minY +space
+ z2=-z2+maxZ-minY +space
+ z3=-z3+maxZ-minY +space
+
+ if v4 !=None:
+ x4,y4,z4 = v4.co
+ y4=-y4
+ z4=-z4+maxZ-minY +space
+ return x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4
+
+def yztransform(face):
+ """gives the face vertexes coordinates in the xfig format/translation (view xz)"""
+ v4=None
+ x4=y4=z4=None
+ if len(face.v)==3:
+ v1,v2,v3=face.v
+ else:
+ v1,v2,v3,v4=face.v
+
+ #Order vertexes
+ x1,y1,z1 = v1.co
+ x2,y2,z2 = v2.co
+ x3,y3,z3 = v3.co
+ y1=-y1
+ y2=-y2
+ y3=-y3
+ z1=-(z1-maxZ-maxX-space)
+ z2=-(z2-maxZ-maxX-space)
+ z3=-(z3-maxZ-maxX-space)
+
+ if v4 !=None:
+ x4,y4,z4 = v4.co
+ y4=-y4
+ z4=-(z4-maxZ-maxX-space)
+ return x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4
+
+def figdata(expview):
+ """Prints all the xfig data (no header)"""
+ objects = Blender.Object.GetSelected()
+ objname = objects[0].name
+ meshname = objects[0].data.name
+ mesh = Blender.NMesh.GetRaw(meshname)
+ obj = Blender.Object.Get(objname)
+ facenumber = len(mesh.faces)
+ for face in mesh.faces:
+ if len(face.v) == 3: # triangle
+ print "2 3 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4"
+ if expview=="xy":
+ x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=xytransform(face)
+ faceverts = int(x1*scale),int(y1*scale),int(x2*scale),int(y2*scale),int(x3*scale),int(y3*scale), int(x1*scale),int(y1*scale)
+ elif expview=="xz":
+ x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=xztransform(face)
+ faceverts = int(x1*scale),int(z1*scale),int(x2*scale),int(z2*scale),int(x3*scale),int(z3*scale), int(x1*scale),int(z1*scale)
+ elif expview=="yz":
+ x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=yztransform(face)
+ faceverts = int(z1*scale),int(y1*scale),int(z2*scale),int(y2*scale),int(z3*scale),int(y3*scale),int(z1*scale),int(y1*scale)
+ print "\t% i % i % i % i % i % i % i % i" % faceverts
+ else:
+ if len(face.v) == 4: #quadrilateral
+ print "2 3 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5"
+ if expview=="xy":
+ x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=xytransform(face)
+ faceverts = int(x1*scale),int(y1*scale),int(x2*scale),int(y2*scale),int(x3*scale),int(y3*scale),int(x4*scale),int(y4*scale), int(x1*scale),int(y1*scale)
+
+ elif expview=="xz":
+ x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=xztransform(face)
+ faceverts = int(x1*scale),int(z1*scale),int(x2*scale),int(z2*scale),int(x3*scale),int(z3*scale),int(x4*scale),int(z4*scale), int(x1*scale),int(z1*scale)
+
+ elif expview=="yz":
+ x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4=yztransform(face)
+ faceverts = int(z1*scale),int(y1*scale),int(z2*scale),int(y2*scale),int(z3*scale),int(y3*scale),int(z4*scale),int(y4*scale), int(z1*scale),int(y1*scale)
+ print "\t% i % i % i % i % i % i % i % i % i % i" % faceverts
+ else:
+ pass #it should not occour, but....
+
+
+def writexy(filename):
+ """writes the x-y view file exported"""
+ global maxX, maxY, maxZ
+ global minX, minY, minZ
+ global space
+ global scale
+ #start = time.clock()
+ file = open(filename, "wb")
+ std=sys.stdout
+ sys.stdout=file
+ xfigheader()
+ figdata("xy")# xydata()
+ sys.stdout=std
+ Blender.Window.DrawProgressBar(1.0, '') # clear progressbar
+ file.close()
+ #end = time.clock()
+ #seconds = " in %.2f %s" % (end-start, "seconds")
+ message = "Successfully exported " + Blender.sys.basename(filename)# + seconds
+ print message
+
+def writexz(filename):
+ """writes the x-z view file exported"""
+ global space,maxX,maxY,maxZ, scale
+ #start = time.clock()
+ file = open(filename, "wb")
+ std=sys.stdout
+ sys.stdout=file
+ xfigheader()
+ figdata("xz")#xzdata()
+ sys.stdout=std
+ Blender.Window.DrawProgressBar(1.0, '') # clear progressbar
+ file.close()
+ #end = time.clock()
+ #seconds = " in %.2f %s" % (end-start, "seconds")
+ message = "Successfully exported " + Blender.sys.basename(filename)# + seconds
+ print message
+
+def writeyz(filename):
+ """writes the y-z view file exported"""
+ global maxX, maxY, maxZ, minX, minY, minZ,scale
+ #start = time.clock()
+ file = open(filename, "wb")
+
+ std=sys.stdout
+ sys.stdout=file
+
+ xfigheader()
+ figdata("yz")#yzdata()
+ sys.stdout=std
+ Blender.Window.DrawProgressBar(1.0, '') # clear progressbar
+ file.close()
+ #end = time.clock()
+ #seconds = " in %.2f %s" % (end-start, "seconds")
+ message = "Successfully exported " + Blender.sys.basename(filename)# + seconds
+ print message
+
+def writeall(filename):
+ """writes all 3 views
+
+ Every view is a combined object in the resulting xfig. file."""
+ global maxX, maxY, maxZ, minX, minY, minZ,scale
+ #start = time.clock()
+ file = open(filename, "wb")
+
+ std=sys.stdout
+ sys.stdout=file
+
+ xfigheader()
+ print "#upper view (7)"
+ print "6 % i % i % i % i ", minX, minY, maxX, maxY
+ figdata("xy") #xydata()
+ print "-6"
+ print "#bottom view (1)"
+ print "6 %i %i %i %i", minX, -minZ+maxZ-minY +space, maxX,-maxZ+maxZ-minY +space
+ figdata ("xz") #xzdata()
+ print "-6"
+
+ print "#right view (3)"
+ print "6 %i %i %i %i", minX, minZ-maxZ-maxX-space, maxX,maxZ-maxZ-maxX-space
+ figdata ("yz") #yzdata()
+ print "-6"
+
+ sys.stdout=std
+ Blender.Window.DrawProgressBar(1.0, '') # clear progressbar
+ file.close()
+ #end = time.clock()
+ #seconds = " in %.2f %s" % (end-start, "seconds")
+ message = "Successfully exported " + Blender.sys.basename(filename)# + seconds
+
+
+#********************************************************USER INTERFACE*****************************************************
+#********************************************************USER INTERFACE*****************************************************
+#********************************************************USER INTERFACE*****************************************************
+def gui(): # the function to draw the screen
+ global mystring, mymsg, toggle, sel3files, scale
+ global guiret1, guiret2, guiret3, guiret4, guiret5, guiret6, guiret7
+ global ScalePopup, SpacePopup, boolmode, guiscale,hidden_flag
+ if len(mystring) > 90: mystring = ""
+ BGL.glClearColor(0,0,1,1)
+ BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
+ BGL.glColor3f(1,1,1)
+ guiret2=Draw.PushButton("Cancel", 2, 10, 10, 55, 20,"Cancel")
+ guiret3=Draw.Toggle("1 file per view", 3, 10, 40, 110,20, sel3files, "3 files")
+ guiret4=Draw.PushButton("Export", 4, 70, 10, 70, 20, "Select filename and export")
+
+ ScalePopup=Draw.Number("Scale", 5, 10,70, 110,20, guiscale, 0.0001, 1000.1, "Scaling factor")
+ SpacePopup=Draw.Number("Space", 6, 10,90, 110,20, space, 0, 10000, "Space between projections")
+
+ guiret5=Draw.Toggle("cm", 7, 120,70, 40,20, boolmode, "set scale to 1 blender unit = 1 cm in xfig")
+ guiret6=Draw.Toggle("in", 8, 162,70, 40,20, not boolmode, "set scale to 1 blender unit = 1 in in xfig")
+# guiret7 = guiret6=Draw.Toggle("only visible", 9, 120,90, 82,20, hidden_flag, "hidden faces")
+
+
+ BGL.glRasterPos2i(72, 16)
+ if toggle: toggle_state = "down"
+ else: toggle_state = "up"
+ #Draw.Text("The toggle button is %s." % toggle_state, "small")
+ BGL.glRasterPos2i(10, 230)
+ #Draw.Text("Type letters from a to z, ESC to leave.")
+ BGL.glRasterPos2i(20, 200)
+ Draw.Text(mystring)
+ BGL.glColor3f(1,0.4,0.3)
+ BGL.glRasterPos2i(340, 70)
+ Draw.Text(mymsg, "tiny")
+
+
+def event(evt, val): # the function to handle input events
+ Draw.Redraw(1)
+
+def button_event(evt): # the function to handle Draw Button events
+ global toggle, guiret5,scale, space, SpacePopup, boolmode, dimscale, guiscale
+ global hidden_flag, sel3files
+ if evt==1:
+ toggle = 1 - toggle
+ Draw.Redraw(1)
+ if evt==2:
+ Draw.Exit()
+ return
+ if evt==3:
+ sel3files = 1-sel3files
+ Draw.Redraw(1)
+ if evt==4:
+ Blender.Window.FileSelector(fs_callback, "Export fig")
+ Draw.Exit()
+ return
+ if evt==5:
+ guiscale = ScalePopup.val
+ scale=dimscale*guiscale
+ Draw.Redraw(1)
+ if evt==6:
+ space =SpacePopup.val
+ if evt==7:
+ boolmode=1
+ dimscale=450 #converting to cm
+ scale = dimscale*guiscale
+ Draw.Redraw(1)
+ if evt==8:
+ boolmode=0
+ dimscale = 1200
+ scale = dimscale*guiscale
+ Draw.Redraw(1)
+ if evt==9:
+ hidden_flag=1-hidden_flag
+ Draw.Redraw(1)
+
+Draw.Register(gui, event, button_event) # registering the 3 callbacks
+
+def fs_callback(filename):
+ if filename.find('.fig', -4) > 0: filename = filename[:-4]
+ getmaxmin()
+ if sel3files:
+ writexy(filename+"_XY.fig")
+ writexz(filename+"_XZ.fig")
+ writeyz(filename+"_YZ.fig")
+ writeall(filename+"_ALL.fig")
+ print scale
+ Draw.Exit()