import poser import Numeric, time, os, gzip, math from Tkinter import * no_pyd = 0 # With this script, we only use the .pyd minimally; there is an option to run without it. try: from _tdmt import * except: no_pyd = 1 if (poser.Version() < 7.0) and (not poser.IsPro()): no_pyd = 1 if os.name == 'mac': no_pyd = 1 scene = poser.Scene() root = Tk() compressFiles = 0 FILE_VERSION = "1.0" FLT_EPSILON = 1.19209290e-07 SAVE_SETTINGS = 0 # Set to 1 to save script options settings to "comment" lines of .vmf, for display by Transfer script. def run(act1,act2,path,threshold,matsT,matsS,dotnorms,basenorms,num_influences,digits=5): """ num_influences is maximum number of target vertices which should be correlated to any source vertex. digits is number of decimal digits at which .vmf weighting data will be clamped **These are not yet implemented. """ """ From MorphFromObj6.py - how to allow user to set delta trim precision. deltaLine=r'd %d '+ ('%%.%df %%.%df %%.%df\n' % (Num,Num,Num)) deltaValues+='\t\t\t\t'+deltaLine % (i,dx,dy,dz) """ app.status_update("Starting comparison. Please wait....") mesh1 = myMesh(act1.Geometry(),act1) # act1 is Source mesh1.get_myMesh_lists(dotnorms,basenorms) mesh2 = myMesh(act2.Geometry(),act2) # act2 is Target mesh2.get_myMesh_lists(dotnorms,basenorms) #mesh1.clampline = r'%%.%df,%%.%df,%%.%df' % (digits,digits,digits) # Disused; retained here as record of process to use.... mesh2.lines = ["" for i in range(mesh2.geom.NumVertices())] close_verts_weights(mesh1,mesh2,threshold,matsT,matsS,dotnorms,num_influences=num_influences) missed = mesh2.lines.count("") if missed == mesh2.geom.NumVertices(): return 0 app.numhits = mesh2.geom.NumVertices() - missed comment = "# Saved with TDMT_Match\n" comment += "# Matched: %s\tMissed: %s\n" %(app.numhits,missed) if matsT.count(1) or matsS.count(1): comment += "# Target materials:\n" # For user reference only comment += "#\t%s\n" %(matsT) m = act2.Geometry().Materials() matnames = [m[i].Name() for i in range(len(m)) if matsT[i] == 1] comment += "#\t%s\n" %(matnames) comment += "# Source materials:\n" comment += "#\t%s\n" %(matsS) m = act1.Geometry().Materials() matnames = [m[i].Name() for i in range(len(m)) if matsS[i] == 1] comment += "#\t%s\n" %(matnames) settings = [dotnorms,basenorms,threshold,num_influences,no_pyd] write_matched(act1,act2,mesh2,path,myComment=comment,settings=settings) del mesh1 del mesh2 return 1 def screen_vmats(mesh,matscreen): """ Materials by vertex - verts can belong to as many materials as polys. Need to count them up and decement them to return a good matscreen list for verts. """ act = mesh.act exc = app.incVar.get() mscreen = [-1 for i in range(mesh.geom.NumVertices())] for pi in range(mesh.geom.NumPolygons()): nvs = mesh.pverts[pi] pmi = act.Geometry().Polygon(pi).MaterialIndex() for v in nvs: if matscreen[pmi] == 1: mscreen[v] = 1 if exc: for pi in range(mesh.geom.NumPolygons()): nvs = mesh.pverts[pi] pmi = act.Geometry().Polygon(pi).MaterialIndex() for v in nvs: if matscreen[pmi] == -1: mscreen[v] = -1 screen = [0 for i in range(mesh.geom.NumVertices())] for pi in range(mesh.geom.NumPolygons()): ngv = mesh.pverts[pi] for vi in ngv: if mscreen[vi] == 1: screen[vi] = 1 mesh.screenT = [i for i in screen] def vertexPos(geom,worldspace=1,multiple=1.0): """ Pre-fetch vertex coordinates for faster access. Returns a Numeric array of vert coords by vert indices Send worldspace=0 keyword when calling function, to use localspace From TDMT Classic source code. """ numVerts = geom.NumVertices() verts = [[0.0,0.0,0.0] for i in range(numVerts)] verts = Numeric.array(verts,Numeric.Float) for i in range(numVerts): if worldspace: v = geom.WorldVertex(i) else: v = geom.Vertex(i) verts[i] = [v.X(),v.Y(),v.Z()] return verts def vector_normalize(v): l = Numeric.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) if l == 0.0: return [v[0], v[1], v[2]] # Spanki - avoid divide-by-zero (test added) return [v[0] / l, v[1] / l, v[2] / l] def vector_dotproduct(v1, v2): return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] def vector_crossproduct(v1, v2): return [ v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0], ] #=============================================================================================== # class myMesh() #=============================================================================================== class myMesh(object): """Initialize the mesh data""" def __init__(self,geom,act): if not no_pyd: self.mesh = Mesh(geom) # pyd Mesh instance embedded self.act = act # If we need the actor Name() later.... self.geom = geom def get_myMesh_lists(self,norms,basenorms,ws=1): """ A bit of control for instantiation of common use myMesh lists """ if not no_pyd: self.verts = self.mesh.GetWorldVertices() else: self.verts = vertexPos(self.geom,worldspace=ws) self.polyverts(self.geom,self.geom.Polygons()) if norms: if no_pyd: if basenorms: self.polynorms(self.pverts,vertexPos(self.geom,worldspace=0)) else: self.polynorms(self.pverts) self.vertpolys(self.geom,self.pverts) self.vertnorms_Geom() else: if basenorms: verts = psrVertexList(self.geom) else: verts = psrWorldVertexList(self.geom) pgons = psrPolygonList(self.geom) pnorms = PolyFaceNormals(pgons,verts) vpolys = VertexPolys(pgons,self.mesh.NumVertices()) if vpolys.count([]): app.status_update("Stray Vertex Found.") for vi in range(self.mesh.NumVertices()): if not vpolys[vi]: vpolys[vi].append(0) self.vnorms = PolyVertexNormals(pnorms,vpolys) def vertnorms(self,testing=0): """ **PYD FUNCTION** [Spanki] normals of vertices, computed as the average of the face normals of the polygons which use each vertex """ pgons = psrPolygonList(self.geom) pnorms = PolyFaceNormals(pgons,self.verts) self.vnorms = [Vector() for i in range(len(self.vpolys))] for vi in range(len(self.vpolys)): polys = self.vpolys[vi] numNorms = max(1,len(polys)) #if numNorms == 0: # make sure we have some normals, so we... #if testing: #print vi, numNorms #continue # ...don't divide by zero crash due to a bad mesh. numNorms = 1.0 / numNorms # convert numNorms to inv_numNorms, so we can multiply instead of divide for p in polys: self.vnorms[vi] += pnorms[p] self.vnorms[vi] = ~(self.vnorms[vi].Scale(numNorms)) # get average (multiply by inv_numNorms) and make into a unit vector def polyverts(self,geom,polys): """ vertices by polygon - this is the Sets() list broken down by polygon index. Returns a list of Numeric arrays From TDMT Classic source code. """ self.pverts = [] gset = geom.Sets() for p in polys: l = p.NumVertices() s = p.Start() e = s + l vl = Numeric.array([v for v in gset[s:e]],Numeric.Int) self.pverts.append(vl) def vertpolys(self,geom,pgons,tex=0): """ polygons by vertex - each vert will have multiple listings returns a list of Numeric arrays """ if tex: self.vpolys = [[] for i in range(geom.NumTexVertices())] else: self.vpolys = [[] for i in range(geom.NumVertices())] for pvi in range(len(pgons)): for v in pgons[pvi]: if not pvi in self.vpolys[v]: self.vpolys[v].append(pvi) self.vpolys = [Numeric.array(i,Numeric.Int) for i in self.vpolys] def polynorms(self,pgons,verts=[]): """ [Spanki] compute normal for each polygon (don't rely on Poser supplied Normals - they're broken) returns a Numeric array """ self.pnorms = [[0.0,0.0,0.0] for i in range(len(pgons))] self.pnorms = Numeric.array(self.pnorms,Numeric.Float) if verts == []: verts = self.verts for p_i in range(len(pgons)): p = pgons[p_i] v0 = verts[p[0]] v1 = verts[p[1]] v2 = verts[p[2]] # Create 2 edge vectors to define a plane #e1 = vecsub(v1,v0) #e2 = vecsub(v2,v0) e1 = v1 - v0 e2 = v2 - v0 # Cross-product is normal to that plane vN = vector_normalize(vector_crossproduct(e1, e2)) self.pnorms[p_i] = vN def vertnorms_Geom(self): """ [Spanki] normals of vertices, computed as the average of the face normals of the polygons which use each vertex returns a Numeric array """ self.vnorms = [[0.0,0.0,0.0] for i in range(len(self.vpolys))] self.vnorms = Numeric.array(self.vnorms,Numeric.Float) for vi in range(len(self.vpolys)): polys = self.vpolys[vi] nx = 0; ny = 0; nz = 0 l = len(polys) if l == 0: # In some unusual geometries, there may be vertices with no polygon associations - we try to skip these. 1/26/08 app.status_update("Stray vertex: %s, %s" %(vi,l)) continue # If we don't skip these, we get a divide by zero crash error due to a bad mesh. for p in polys: nx += self.pnorms[p][0] ny += self.pnorms[p][1] nz += self.pnorms[p][2] n = [nx/l,ny/l,nz/l] n = vector_normalize(n) # Shouldn't need to do this, but it can't hurt self.vnorms[vi] = n #=============================================================================================== # Correlation methods #=============================================================================================== #no_pyd = 1 # Toggle this value to switch use of pyd on or off. 1 = off, 0 = on. def close_verts_weights(mesh1,mesh2,threshold,matsT,matsS,dotnorms,num_influences=5,digits=5): """ Match verts by submitted area with weighted influence by distance. This is the main function of the script. Screening code by odf! """ mesh1.screenT = [1 for i in range(mesh1.geom.NumVertices())] if matsS.count(1): screen_vmats(mesh1,matsS) mesh2.screenT = [1 for i in range(mesh2.geom.NumVertices())] if matsT.count(1): screen_vmats(mesh2,matsT) #----odf------------ grid_cells = {} for nvi in range(mesh1.geom.NumVertices()): p = mesh1.verts[nvi] key = tuple([int(math.floor(x / threshold)) for x in p]) grid_cells.setdefault(key, []).append(nvi) #------------------- srcnumv = mesh1.geom.NumVertices() numv = mesh2.geom.NumVertices() old_amount = 0 # For % progress update dists = {} # For the sort for vi in range(numv): # Print percentage of progress for comparison amount = int((float(vi+1)/float(numv))*100) if vi and (amount%5 == 0) and (amount != old_amount): progress = "Vertex %s - Processed %s%s. Please wait...." %(vi,amount,"%") app.status_update(progress) old_amount = amount if mesh2.screenT[vi]: vert = mesh2.verts[vi] go = 0 #----odf------------ xmin, xmax = vert[0] - threshold, vert[0] + threshold ymin, ymax = vert[1] - threshold, vert[1] + threshold zmin, zmax = vert[2] - threshold, vert[2] + threshold #------------------- for nvi in range(srcnumv): if mesh1.screenT[nvi]: #--------odf------------------ p2 = mesh1.verts[nvi] if (p2[1] < ymin or p2[1] > ymax or p2[0] < xmin or p2[0] > xmax or p2[2] < zmin or p2[2] > zmax): continue #----------------------------- if no_pyd: p3 = p2 - vert # This is much faster than the old code. dist = Numeric.sqrt( pow(p3[0],2) + pow(p3[1],2) + pow(p3[2],2) ) else: dist = vert.PointDistance(p2) if dist <= threshold: if dotnorms: # Dot Product to test normals - suggested by Spanki. norm2 = mesh2.vnorms[vi] norm1 = mesh1.vnorms[nvi] if no_pyd: dot = norm1[0] * norm2[0] + norm1[1] * norm2[1] + norm1[2] * norm2[2] else: dot = norm1 ^ norm2 if dot < 0.0: continue if not go: go = 1 if dists.has_key(dist): dists[dist].append(nvi) else: dists[dist] = [nvi] else: continue if go: sortdists = dists.keys() sortdists.sort() num_influences2 = min(num_influences+1,len(sortdists)) # Slight bug: always returns one zero weight, so we add an extra, then remove it. Need to fix. close_dist = [] close_index = [] total = 0 for i in range(num_influences2): if total < num_influences2: d = dists[sortdists[i]] for j in range(len(d)): close_dist.append(sortdists[i]) close_index.append(d[j]) total += 1 if total >= num_influences2: break sortdists = [] dists = {} indices = "" sweights = "" #--------- spanki --------------- rayhit = 0 if not no_pyd: #----------------------------------------------------------------------------------------- # New routine below just does a ray-cast on the (neighboring) tripolys associated with the # predetermined (single/very) closest vertex. It returns a single HitPoint type, with the # hit info (tripoly hit, weights, distance, deltas, etc). #----------------------------------------------------------------------------------------- hitpoint = mesh2.mesh.CorrelateVertToNearSurface( vi, mesh1.mesh, close_index[0] ) if hitpoint: rayhit = 1 num_influences2 = 3 # only 3 weights (2 of which could possibly be 0.0, so that's checked below) weights = hitpoint.Weights tp = hitpoint.TriPolyHit tpi = [tp.v0, tp.v1, tp.v2] # vertex indices of the hit tripoly for ci in range(3): if weights[ci] > FLT_EPSILON: indices += " %s" %(tpi[ci]) sweights += " %.5f" %(weights[ci]) else: num_influences2 -= 1 # Removing any zero weights. #-------------------------------- if not rayhit: dmax = max(close_dist) dmin = min(close_dist) if dmin == dmax: dmin = 0.0 # Float division error protection. weights = get_weights(close_dist,dmin=dmin,dmax=dmax) for ci in range(len(close_dist)): if weights[ci] > FLT_EPSILON: indices += " %s" %(close_index[ci]) sweights += " %.5f" %(weights[ci]) else: num_influences2 -= 1 # Removing the extra zero weight. Why does it happen? Weighting code is wrong.... line = "w %s %s" %(vi,num_influences2) line += indices line += sweights line += "\n" mesh2.lines[vi] = line def get_weights(dists,dmin=0.0,dmax=100.0): """Calculate weights for correlated vertices, by distance.""" if dmax <= FLT_EPSILON: # Float division error protection. return [1.0/len(dists) for i in dists] total = 0.0 result = [0.0 for i in dists] for di in range(len(dists)): dist = dists[di] val = (dmax - dist) / (dmax - dmin) total += val result[di] = val if total <= FLT_EPSILON: # Float division error protection. return [1.0/len(dists) for i in dists] for i in range(len(result)): result[i] /= total return result #=============================================================================================== # File-writing (adapted from TDMT Classic with minimal necessary changes) - Original function by Spanki. #=============================================================================================== def write_matched(act1,act2,mesh2,path,myComment="#\n",settings=[]): """ Write the correlation file. A vertex match file (.wmf or .vmz) is based on the TDMT .vwt file, with changes largely designed to allow an arbitrary number (from 1 to n) of weighted correlations. """ if len(settings) < 5: settings = [] version = 1.0 if compressFiles: fOut = gzip.GzipFile(path,'wb') else: fOut=open(path,'wt') text = "#-------------------------------------------------------------------------------------\n" text += "# Sample comment - Insert some witty text here.\n" text += "#\n" text += "# (this could list copyrights, etc.)\n" text += myComment text += "#-------------------------------------------------------------------------------------\n" fOut.write(text) text = "TDMT_Matched %s %i %i\n\n" %(version, act1.Geometry().NumVertices(), act2.Geometry().NumVertices()) fOut.write(text) if SAVE_SETTINGS and settings: text = "comment: norms %s %s\n" %(settings[0],settings[1]) text += "comment: threshold %s\n" %(settings[2]) text += "comment: influences %s\n" %(settings[3]) text += "comment: no_pyd %s\n\n" %(settings[4]) else: user_comment = "sample comment - just ignore" text = "comment: %s\n\n" %(user_comment) fOut.write(text) a1 = act1.InternalName().split(":")[0].replace(" ","_") a2 = act2.InternalName().split(":")[0].replace(" ","_") text = "actors: %s-%s\n\n" %(a1, a2) fOut.write(text) text = "#-------------------------------------------------------------------------------------\n" text += "# vertex match data...\n" text += "# w (vertex index) (number of matches(N)) (matched vertex indices x N) (weights x N)\n" text += "#-------------------------------------------------------------------------------------\n" fOut.write(text) for line in mesh2.lines: fOut.write(line) fOut.close() #=============================================================================================== # Path-handling functions #=============================================================================================== def geomPath(a1,a2,dataName,overwrite=0): """ Get the necessary actor and geometry file information, to construct the dataPath. """ gf1, name1 = get_geomfiles(a1) gf2, name2 = get_geomfiles(a2) dataPath = get_pathname(gf1,gf2,name1,name2,create=1,dataName=dataName) if not dataPath: return "" if not overwrite: dataPath = protect_naming(dataPath) return os.path.normpath(dataPath) def get_geomfiles(act): """ act.GeomFileName() will equal figure.GeomFileName() unless a figure actor uses GeomCustom (GeomFileName() == None) or has an external geometry listing in the.cr2. """ agf = act.GeomFileName() if agf: fig = act.ItsFigure() if fig: fgf = fig.GeomFileName() if fgf: if fgf == agf: return os.path.splitext(os.path.basename(fgf))[0],\ act.InternalName().split(":")[0].replace(" ","_") # Normal figure actor else: return os.path.splitext(os.path.basename(fgf))[0],\ os.path.splitext(os.path.basename(agf))[0] # Figure actor with external geom listing return os.path.splitext(os.path.basename(agf))[0],\ act.InternalName().split(":")[0].replace(" ","_") # Standard prop with external geom listing if act.IsBodyPart(): fig = act.ItsFigure() fgf = fig.GeomFileName() # If an actor has been processed using Grouping tool, will have GeomCustom. if fgf: return os.path.splitext(os.path.basename(fgf))[0],\ act.InternalName().split(":")[0].replace(" ","_") # Normal figure actor return act.Name(),\ act.InternalName().split(":")[0].replace(" ","_") # Any other actor with GeomCustom use def get_pathname(g1,g2,a1,a2,create=1,dataName="full"): """ Get the path and file name for datafile, creating folders if needed. Some systems may require administrator access to create the directories. g1,g2 are GeomFileNames; a1,a2 are actor InternalNames. """ corr_folder = "matched" ext = ".vmf" if compressFiles: ext = ".vmz" nofolder = os.path.join(os.path.dirname(poser.AppLocation()),"%s%s" %(dataName,ext)) pyfolder = os.path.join("Runtime","Python") pyfolder2 = os.path.join(pyfolder,"poserScripts") base = os.path.join(os.path.dirname(poser.AppLocation()),pyfolder2) if not os.path.exists(base): return "" scriptfolder = os.path.join(base,"TDMTfiles") if not os.path.exists(scriptfolder): try: os.mkdir(os.path.abspath(scriptfolder)) except: return nofolder nofolder = scriptfolder scriptfolder2 = os.path.join(scriptfolder,corr_folder) # Changed to reflect new version number if not os.path.exists(scriptfolder2): try: os.mkdir(os.path.abspath(scriptfolder2)) except: return nofolder geomfolder = os.path.join(scriptfolder2, "%s-%s" %(g1.replace(" ","_"),g2.replace(" ","_"))) if create: if not os.path.exists(geomfolder): try: os.mkdir(os.path.abspath(geomfolder)) except: return nofolder actfolder = os.path.join(geomfolder, "%s-%s" %(a1.replace(" ","_"),a2.replace(" ","_"))) if create: if not os.path.exists(actfolder): try: os.mkdir(os.path.abspath(actfolder)) except: return nofolder dataPath = os.path.abspath(os.path.join(actfolder,"%s%s" %(dataName,ext))) if not create: return dataPath # ? if os.path.exists(actfolder): return dataPath else: return nofolder def protect_naming(path): folder = os.path.dirname(path) filename = os.path.basename(path) name,ext = filename.split(".") if os.path.exists(path): num = 0 newname = "%s_%s.%s" %(name,num,ext) while os.path.exists(os.path.join(folder,newname)): num += 1 newname = "%s_%s.%s" %(name,num,ext) return os.path.join(folder,newname) else: return path #=============================================================================================== # Morph-related functions #=============================================================================================== def unique_name(morphName,act,suffix,internal=0): """ Keep dial naming for the new morph from being automatically changed by Poser Used by transfer_morph, transfer_shape, inflate_normals, and the smoothing functions. """ #Morph dial names max out at 30 characters, after which Poser #won't recognize the dial as a morph (?!?) if len(morphName) >= 20 and suffix != "": morphName = morphName[0:12] if suffix != "" and morphName.find(suffix) == -1: morphName = "%s%s" %(morphName,suffix) # Check the dial name to avoid duplication if internal: parms = [p.InternalName() for p in act.Parameters()] else: parms = [p.Name() for p in act.Parameters()] if morphName in parms: temp = 1 while "%s_%i" %(morphName,temp) in parms: temp += 1 # Name the target morph dial morphName = "%s_%i" %(morphName,temp) return morphName #=============================================================================================== # Compression-handling #=============================================================================================== def gzip_data(dataPath,replace=0,swap_ext=1): """ Compress or decompress file, with option to replace. """ try: compressed = check_gzip(dataPath) if swap_ext: path,ext = dataPath.split(".") if ext == "vmf": dataPath = "%s.vmz" %(path) if ext == "vmz": dataPath = "%s.vmf" %(path) if compressed: f = gzip.GzipFile(dataPath) else: f = open(dataPath,"r") g = f.read() f.close() if not replace: dataPath = protect_naming(dataPath) if compressed: f = open(dataPath,"w") else: f = gzip.GzipFile(dataPath,"wb") f.write(g) f.close() except: print "gzip_data() error: could not process selected file." return def check_gzip(dataPath): f = open(dataPath,"rb") magic = f.read(2) f.close() if magic == '\037\213': return 1 else: return 0 """ Note: GUI retains multiple Source actor selection setup from .pyd version of TDMT. Multiple actors currently disabled, but format retained as possible foundation for batch handling of actors if that feature is added at some point. """ class App: def __init__(self, master): self.master = master master.title("TDMT Closest Vertices Comparison 1") self.gzVar = IntVar() self.gzVar.set(0) self.incVar = IntVar() self.incVar.set(0) self.pydVar = IntVar() self.pydVar.set(1) self.dotVar = IntVar() self.dotVar.set(0) self.dot2Var = IntVar() self.dot2Var.set(0) self.figS = None self.actS = None self.basepath = "Runtime:Python:PoserScripts:TDMTfiles:" self.corrfolder = "matched:" self.numhits = 0 self.masterFrame = Frame(self.master) self.masterFrame.grid(row=1,column=0) self.displayFrame = Frame(self.master) self.displayFrame.grid(row=2,column=0) self.ListFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.ListFrame.grid(row=1,column=0) self.ListFrame2 = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.ListFrame2.grid(row=1,column=1) self.matFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.matFrame.grid(row=1,column=2) self.mixFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.mixFrame.grid(row=1,column=3) self.normFrame = Frame(self.mixFrame,borderwidth=2,relief=RIDGE) self.normFrame.grid(row=0,column=0) self.checkFrame = Frame(self.mixFrame,borderwidth=2) self.checkFrame.grid(row=1,column=0,sticky=W) self.radioFrame = Frame(self.checkFrame,borderwidth=2,relief=RIDGE) self.radioFrame.grid(row=4,column=0) self.buttonFrame = Frame(self.mixFrame,borderwidth=2) self.buttonFrame.grid(row=2,column=0) self.quitFrame = Frame(self.mixFrame,borderwidth=3) self.quitFrame.grid(row=4,column=0) # --- Listboxes --- self.listLabel = Label(self.ListFrame, text="Target Actor" , anchor=N, justify=LEFT) self.listLabel.grid(row=1, column=1) self.ListScroll = Scrollbar(self.ListFrame, orient=VERTICAL) # Target actor self.ListScroll.grid( row=2, column=0,sticky=N+S+E) self.List = Listbox(self.ListFrame, height=20, width=25, selectmode=SINGLE,exportselection=0, yscrollcommand=self.ListScroll.set) self.List.grid( row=2, column=1) self.ListScroll["command"] = self.List.yview self.listLabel2 = Label(self.ListFrame2, text="Source Actor" , anchor=N, justify=LEFT) self.listLabel2.grid(row=1, column=1) self.ListScroll2 = Scrollbar(self.ListFrame2, orient=VERTICAL) # Source actors self.ListScroll2.grid( row=2, column=0,sticky=N+S+E) self.List2 = Listbox(self.ListFrame2, height=20, width=25, selectmode=SINGLE,exportselection=0, yscrollcommand=self.ListScroll2.set) self.List2.grid( row=2, column=1) self.ListScroll2["command"] = self.List2.yview self.List4 = Listbox(self.displayFrame, height=10, width=106, selectmode=SINGLE,exportselection=1) # Status bar self.List4.grid( row=3, column=0) self.matLabel = Label(self.matFrame, text="Target Materials" , anchor=N, justify=LEFT) self.matLabel.grid(row=1, column=1) self.matScroll = Scrollbar(self.matFrame, orient=VERTICAL) # Target materials self.matScroll.grid( row=2, column=0,sticky=N+S+E) self.matL = Listbox(self.matFrame, height=9, width=25, selectmode=MULTIPLE,exportselection=0, yscrollcommand=self.matScroll.set) self.matL.grid( row=2, column=1) self.matScroll["command"] = self.matL.yview self.matLabel2 = Label(self.matFrame, text="Source Materials" , anchor=N, justify=LEFT) self.matLabel2.grid(row=3, column=1) self.matScroll2 = Scrollbar(self.matFrame, orient=VERTICAL) # Source materials self.matScroll2.grid( row=4, column=0,sticky=N+S+E) self.matL2 = Listbox(self.matFrame, height=9, width=25, selectmode=MULTIPLE,exportselection=0, yscrollcommand=self.matScroll2.set) self.matL2.grid( row=4, column=1) self.matScroll2["command"] = self.matL2.yview # --- Radio Buttons --- self.radioLabel = Label(self.radioFrame, text="Vertex Material Edge\nScreening:",anchor=N,justify=CENTER) self.radioLabel.grid(row=1,column=0) self.radioInj = Radiobutton(self.radioFrame, text="Inclusive", variable=self.incVar, value=0) self.radioInj.grid(row=2, column=0) self.radioRem = Radiobutton(self.radioFrame, text="Exclusive", variable=self.incVar, value=1) self.radioRem.grid(row=3, column=0) # --- Checkbuttons --- self.gzCheck = Checkbutton(self.checkFrame,text="Compress Files", variable=self.gzVar) self.gzCheck.grid(row = 1, column = 0,sticky=W) if not no_pyd: self.pydCheck = Checkbutton(self.checkFrame,text="Use _tdmt.pyd", variable=self.pydVar, command=self.toggle_pyd) self.pydCheck.grid(row = 0, column = 0,sticky=W) self.dotCheck = Checkbutton(self.checkFrame,text="Test Normals", variable=self.dotVar) self.dotCheck.grid(row = 2, column = 0,sticky=W) self.dot2Check = Checkbutton(self.checkFrame,text="Test Using\nBase Geom", variable=self.dot2Var) self.dot2Check.grid(row = 3, column = 0) # --- Entries --- self.distepLabel = Label(self.buttonFrame, text="Distance Cutoff" , anchor=N, justify=LEFT) self.distepLabel.grid(row=4, column=0) self.distepEntry = Entry(self.buttonFrame, width=8) self.distepEntry.insert(0,"0.006") self.distepEntry.grid(row=5,column=0) self.infLabel = Label(self.buttonFrame, text="Number of Influences" , anchor=N, justify=LEFT) self.infLabel.grid(row=6, column=0) self.infEntry = Entry(self.buttonFrame, width=8) self.infEntry.insert(0,"5") self.infEntry.grid(row=7,column=0) self.pathEntry = Entry(self.displayFrame, width=106) self.pathEntry.insert(0,"%s%s" %(self.basepath,self.corrfolder)) self.pathEntry.grid(row=1,column=0) # --- Buttons --- self.buttonRun = Button(self.quitFrame, text="Run", command=self.handleRun) self.buttonRun.grid(row=0, column=0) self.buttonQuit = Button(self.quitFrame, text="Quit", command=self.die) self.buttonQuit.grid(row=0, column=1) # ---- Mouse-button binding ---- self.List.bind('',lambda e, s=self: s.handleTarget(e.y)) self.List2.bind('',lambda e, s=self: s.handleSources(e.y)) self.fill_boxes(self.List) self.pyd_update() self.status_update("Ready") def status_update(self,line): self.List4.insert(0,line) self.List4.update() def path_update(self,path): path = path.replace(os.sep,":") path = "Runtime:%s" %(path.split("Runtime:")[1]) self.pathEntry.delete(0,END) self.pathEntry.insert(0,path) self.pathEntry.index(len(self.basepath)-1) # self.pathEntry.update() def pyd_update(self): if not no_pyd: self.status_update("Running using Spanki's tdmt.pyd for greater speed.") else: self.status_update("Not running using Spanki's tdmt.pyd") if poser.Version() < 7.0: self.status_update("Reason: Poser version is less than 7.0.") if os.name == "mac": self.status_update("Reason: OS is Mac.") if poser.Version() >= 7.0 and os.name != "mac": self.status_update("Reason: tdmt.pyd not found.") def toggle_pyd(self): global no_pyd if not no_pyd: no_pyd = 1 self.status_update("Not running using Spanki's tdmt.pyd") self.status_update("Reason: tdmt.pyd disabled by user.") else: no_pyd = 0 self.status_update("Running using Spanki's tdmt.pyd for greater speed.") def handleTarget(self,event): self.figS = None self.List2.delete(0,END) self.matL.delete(0,END) self.matL2.delete(0,END) clicked = self.List.nearest(event) s1 = self.List.get(clicked) name,fig = s1.split(" <") fig = fig.replace(">","") if fig == "None": self.actS = scene.Actor(name) else: self.figS = scene.Figure(fig) self.actS = self.figS.Actor(name) self.fill_boxes(self.List2,self.actS.InternalName()) mats = self.get_mats(self.actS) for mat in mats: self.matL.insert(END,mat) def handleSources(self,event): clicked = self.List2.nearest(event) s1 = self.List2.get(clicked) name,fig = s1.split(" <") fig = fig.replace(">","") if fig == "None": actS = scene.Actor(name) figS = None else: figS = scene.Figure(fig) actS = figS.Actor(name) mats = self.get_mats(actS) #matlist = self.matL2.get(0,END) self.matL2.delete(0,END) for mat in mats: #if mat not in matlist: self.matL2.insert(END,mat) path = geomPath(self.actS,actS,"full",overwrite=0) if path: self.path_update(path) def get_mats(self,act): """ It should be noted that act.Materials() != act.Geometry().Materials(). The Geometry materials are inherited from the .obj file. The Actor materials are inherited from the .cr2, and may contain unused listings, as well as the ubiquitous and often extraneous Preview material. Because of this mismatch, the two Materials() lists are not compatible and cannot be cross-indexed! This is one of the (many) things which is not clarified in the PPy docs, although it does make sense. Actor.Geometry().Materials() should generally be the same as UnimeshInfo[0].Materials(), although it seems the latter may sometimes contain mysterious duplicate listings. At any rate, the act.Geometry().Materials() will return all materials used in the unimesh for the figure. If an actor has GeomCustom use, however, the act.Geometry().Materials() will reflect only those for that actor and will not necessarily list all (or any) mats found in UnimeshInfo[0].Materials(). This applies to external geometry listings as custom geometry in the .cr2, as well. Cage learned all of this the hard way. :-P """ m = act.Geometry().Materials() matnames = [] for p in act.Geometry().Polygons(): #Display only materials present in the selected actor. mn = p.MaterialName() if not mn in matnames: matnames.append(mn) return ["%s <%s>" %(m[i].Name(),i) for i in range(len(m)) if m[i].Name() in matnames] def fill_boxes(self,theList,notme=""): """ Adds the figures and props to the first set of listboxes """ for act in scene.Actors(): if act.IsLight() or act.IsCamera() or act.IsDeformer() or act.IsBase() or\ act.IsZone() or (not act.Geometry()) or act.Name() == 'GROUND' or\ act.Name() == 'FocusDistanceControl' or 'CenterOfMass' in act.Name(): continue if not act.OnOff(): continue if act.InternalName() == notme: continue line = [act.Name()] if act.ItsFigure(): line.append(act.ItsFigure().Name()) else: line.append(act.ItsFigure()) theList.insert(END,"%s <%s>" %(line[0],line[1])) def die(self): """End the script""" root.destroy() root.quit() def handleRun(self): global compressFiles if not self.actS: # No target actor selected self.status_update("No Target actor selected.") return StartTime = time.time() self.numhits = 0 s = self.List2.curselection() if s != (): t = self.List2.get(s) name,fig = t.split(" <") fig = fig.replace(">","") if fig == "None": actS = scene.Actor(name) else: actS = scene.Figure(fig).Actor(name) else: # No source actor selected self.status_update("No Source actor selected.") return if actS.ItsFigure(): figSn = actS.ItsFigure().Name() else: figSn = "" actT = self.actS if actT.ItsFigure(): figTn = actT.ItsFigure().Name() else: figTn = "" compressFiles = self.gzVar.get() dot = self.dotVar.get() if dot: dot2 = self.dot2Var.get() else: dot2 = 0 try: distEpsilon = float(self.distepEntry.get()) distEpsilon = max([distEpsilon,0.0000001]) except ValueError: distEpsilon = 0.006 try: influences = int(self.infEntry.get()) influences = max([influences,1]) except ValueError: influences = 5 mT = [self.matL.get(int(i)).split(" <") for i in self.matL.curselection()] matsT = [-1 for i in range(len(actT.Materials()))] mS = [self.matL2.get(int(i)).split(" <") for i in self.matL2.curselection()] matsS = [-1 for i in range(len(actS.Materials()))] for m in mT: matname,index = m index = index.replace(">","") matsT[int(index)] = 1 for m in mS: matname,index = m index = index.replace(">","") matsS[int(index)] = 1 dataName = "full" if matsS.count(1) or matsT.count(1): dataName = "part" path = geomPath(actS,actT,dataName,overwrite=0) if path: self.path_update(path) else: self.status_update("Error getting filepath for .vmf files.") return if not os.path.exists(path): self.status_update("Comparing %s %s to %s %s...." %(figSn,actS.Name(),figTn,actT.Name())) goodrun = run(actS,actT,path,distEpsilon,matsT,matsS,dot,dot2,influences,digits=5) if goodrun: EndTime = (time.time() - StartTime)/60 minutes = int(EndTime) seconds = int((EndTime - float(minutes))*60) self.status_update("Done in %s minutes, %s seconds. Matched %s of %s" %(minutes,seconds,self.numhits,actT.Geometry().NumVertices())) else: self.status_update("No matches found for %s %s to %s %s." %(figSn,actS.Name(),figTn,actT.Name())) app = App(root) root.mainloop()