diff options
author | no-author <no-author> | 2006-01-27 01:47:31 +0300 |
---|---|---|
committer | no-author <no-author> | 2006-01-27 01:47:31 +0300 |
commit | e8afec9f9af12c9c622813c7f8ea3a32c9f0cd4a (patch) | |
tree | eab1f87cc41bd002ef5a3be2307926ec0efc17ef | |
parent | 4a52c6ac6ffb203ae55ad603c1f924fffd77da6a (diff) | |
parent | 6dbe25dd9dfe3d5dd41dade0ec72a689441e9383 (diff) |
This commit was manufactured by cvs2svn to create branch 'orange'.
-rw-r--r-- | release/scripts/archimap.py | 1138 | ||||
-rw-r--r-- | release/scripts/bpydata/mesh_bbrush.py | 919 | ||||
-rw-r--r-- | release/scripts/bpymodules/boxpack2d.py | 478 | ||||
-rw-r--r-- | release/scripts/image_edit.py | 95 | ||||
-rw-r--r-- | release/scripts/mesh_bbrush_menu.py | 44 | ||||
-rw-r--r-- | release/scripts/mesh_cleanup.py | 184 | ||||
-rw-r--r-- | release/scripts/mesh_tri2quad.py | 453 | ||||
-rw-r--r-- | release/scripts/object_batch_name_edit.py | 253 | ||||
-rw-r--r-- | release/scripts/ply_export.py | 113 | ||||
-rw-r--r-- | release/scripts/ply_import.py | 292 | ||||
-rw-r--r-- | release/scripts/xfig_export.py | 441 | ||||
-rw-r--r-- | source/blender/python/api2_2x/doc/Pose.py | 104 |
12 files changed, 4514 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() diff --git a/source/blender/python/api2_2x/doc/Pose.py b/source/blender/python/api2_2x/doc/Pose.py new file mode 100644 index 00000000000..c35807e8187 --- /dev/null +++ b/source/blender/python/api2_2x/doc/Pose.py @@ -0,0 +1,104 @@ +# Blender.Object.Pose module + +""" +The Blender.Object.Pose submodule. + +Pose +==== + +This module provides access to B{Pose} objects in Blender. These Pose is the +current object-level (as opposed to armature-data level) transformation. + +Example:: + + +@var ROT: +@type ROT: Constant +@var LOC: +@type LOC: Constant +@var SIZE: +@type SIZE: Constant +""" + +class Pose: + """ + The Pose object + =============== + This object gives access to Pose-specific data in Blender. + @ivar bones: A Dictionary of PosePoseBones (PoseDict) that make up this Pose. + @type bones: PoseDict Object + """ + + def update(): + """ + Save all changes and update the Pose. + @rtype: None + """ + +class PoseBonesDict: + """ + The PoseBonesDict object + ======================== + This object gives gives dictionary like access to the PoseBones in a Pose. + It is internal to blender but is called as 'Pose.bones' + """ + + def items(): + """ + Retun the key, value pairs in this dictionary + @rtype: string, PosePoseBone + @return: All strings, and PosePoseBones in the Pose (in that order) + """ + + def keys(): + """ + Retun the keys in this dictionary + @rtype: string + @return: All strings representing the PosePoseBone names + """ + + def values(): + """ + Retun the values in this dictionary + @rtype: BPy_PoseBone + @return: All PosePoseBones in this dictionary + """ + +class PoseBone: + """ + The PoseBone object + =================== + This object gives access to PoseBone-specific data in Blender. + @ivar name: The name of this PoseBone. + @type name: String + @ivar loc: The change in location for this PoseBone. + @type loc: Vector object + @ivar size: The change in size for this PoseBone (no chane is 1,1,1) + @type size: Vector object + @ivar quat: The change in rotation for this PoseBone. + @type quat: Quaternion object + @ivar head: The final head location for this PoseBone. (not settable) + @type head: Vector object + @ivar tail: The final tail location for this PoseBone. (not settable) + @type tail: Vector object + @ivar localMatrix: The matrix combination of rot/quat/loc. + @type localMatrix: Matrix object + @ivar poseMatrix: The total transformation of this PoseBone including constraints. (not settable) + @type poseMatrix: Matrix object + """ + + def insertKey(parentObject, frameNumber, type): + """ + Insert a pose key for this PoseBone at a frame. + @type parentObject: Object object + @param parentObject: The object the pose came from. + @type frameNumber: integer + @param frameNumber: The frame number to insert the pose key on. + @type type: Constant object + @param type: Can be any combination of 3 Module constants: + - Pose.LOC + - Pose.ROT + - Pose.QUAT + @rtype: None + """ + |