import poser, math, Numeric from Tkinter import * """ This script applies a smoothing algorithm which drags morphed vertices back toward their distance relationships in the unmorphed mesh. Script developed by Cage as part of the TDMT project. Some functions borrowed from TDMT code were written or modified by Spanki. Originally developed for use with Spanki's tdmt.pyd extension for PoserPython. Free for any use. Problems, questions, or comments can be directed to Cage23drei@yahoo.com or member Cage at www.renderosity.com. """ scene = poser.Scene() root = Tk() do_print = 0 FLT_EPSILON = 1.19209290e-07 def run(screen,reps,threshold,morphs,highlow,screenlist): theAct = scene.CurrentActor() theMesh = myMesh(theAct.Geometry()) # Instantiate the mesh geom = theAct.Geometry() theMesh.baseverts = vertexPos(geom,worldspace=0) # Used for worldspace removal in setShape theMesh.detailverts = vertexPos(geom,worldspace=0) # Used by find_detail; can have morph deltas added in. theMesh.verts = vertexPos(geom,worldspace=1) # Used for worldspace removal in setShape theMesh.points = vertexPos(geom,worldspace=1) # Store hit locations to pass to post-processing functions theMesh.matchlist = [-1 for i in theMesh.verts] # Screening for restore_detail if morphs: # Mix in deltas for selected morphs mix_deltas(theAct,theMesh,morphs) find_detail(theMesh) restore_detail(theMesh,reps=reps,screen=screen,threshold=threshold,highlow=highlow,screenlist=screenlist) setShape(theMesh,theAct) 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 """ 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 class myMesh(object): """Initialize the mesh data - just a container class for pyd Mesh instance""" def __init__(self,geom): self.geom = geom self.polyverts(geom,geom.Polygons()) pgons = self.pverts self.vertpolys(geom,pgons) self.vertneighbors(pgons) def polyverts(self,geom,polys): """ vertices by polygon - this is the Sets() list broken down by polygon index. Returns a list of Numeric arrays """ 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 vertneighbors(self,pgons): """ neighbor verts of verts pgons is self.pverts or self.tverts """ self.vneighbors = [[] for i in range(len(self.vpolys))] for vi in range(len(self.vpolys)): buflist = [vi] # Need to exclude the vert itself! for ps in self.vpolys[vi]: for v in pgons[ps]: if not v in buflist: self.vneighbors[vi].append(v) buflist.append(v) self.vneighbors = [Numeric.array(j,Numeric.Int) for j in self.vneighbors] def vertpolys(self,geom,pgons): """ polygons by vertex - each vert will have multiple listings returns a list of Numeric arrays """ 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] #------blender2cal3d.py------ (with some substitution of Numeric) def point_distance(p1, p2): return Numeric.sqrt((p2[0] - p1[0]) ** 2 + \ (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2) def mix_deltas(act,mesh,morphs): for name in morphs: mt = act.Parameter(name) for vi in range(mesh.geom.NumVertices()): d = mt.MorphTargetDelta(vi) if (abs(d[0]) > FLT_EPSILON) or (abs(d[1]) > FLT_EPSILON) or (abs(d[2]) > FLT_EPSILON): for i in range(3): mesh.detailverts[vi][i] += d[i] def find_detail(mesh): """ Finds center (averaged position) of all neighbor vertices for each vertex. These are then used by restore_detail to try to reconstruct the basic vertex relationships after the shrink-wrapping. Use of centers integrates a smoothing effect into the process, which both shrinks the mesh and removes detail. """ mesh.detail = [[0.0,0.0,0.0] for i in mesh.detailverts] mesh.dists = [0.0 for i in mesh.detailverts] for vi in range(len(mesh.detailverts)): nbs = mesh.vneighbors[vi] vert = mesh.detailverts[vi] avg = [0.0,0.0,0.0] for i in range(len(nbs)): nvi = nbs[i] nv = mesh.detailverts[nvi] for i in range(3): avg[i] += nv[i] for i in range(3): avg[i] /= len(nbs) for i in range(3): mesh.detail[vi][i] = vert[i] - avg[i] mesh.dists[vi] = point_distance(vert,avg) def restore_detail(mesh,reps=1000,screen=1,threshold=0.0015,highlow=0,screenlist=[1,1,1]): #reps = Maximum number of looped repititions for function #screen = favor verts without matches 0=process all, 1=process only unmatched verts #threshold = difference in distance between base geom and adjusted shape at which script will stop affecting a vert #highlow = screen by whether distance is higher or lower at outset. 0 = low; 1 = high #screenlist = screen x,y, or z component of restoration by sending 0 instead of one for the proper indexed position in the list detail = mesh.detail dists = mesh.dists oldcount = mesh.matchlist.count(1) for rep in range(reps): points = mesh.points for vi in range(len(mesh.detailverts)): if ((screen == 1) and (mesh.matchlist[vi] == -1)) or screen == 0: vert = mesh.detailverts[vi] nbs = mesh.vneighbors[vi] avg = [0.0,0.0,0.0] for ni in range(len(nbs)): nvi = nbs[ni] for i in range(3): avg[i] += points[nvi][i] for i in range(3): avg[i] /= len(nbs) dist = point_distance(vert, avg) distdiff = dist - dists[vi] if ((not highlow) and distdiff <= threshold) or (highlow and distdiff >= threshold): mesh.matchlist[vi] = 1 newpoint = [i for i in points[vi]] for i in range(3): if screenlist[i]: newpoint[i] = avg[i] + detail[vi][i] mesh.points[vi] = [i for i in newpoint] if do_print: print rep,mesh.matchlist.count(1),oldcount if mesh.matchlist.count(1) == oldcount: # Quit once we reach the point where passes don't change the count - point of diminishing returns break oldcount = mesh.matchlist.count(1) def setShape(mesh,theAct,addDeltas=0): # Set the morphs or bake the shape """create the morphs""" morphs = {} if addDeltas: deltas = [[0.0,0.0,0.0] for i in mesh.verts] for parm in theAct.Parameters(): if parm.IsMorphTarget(): morphs[parm.Name()] = parm.Value() if addDeltas: if parm.Value() <> 0.0: pv = parm.Value() for vi in range(mesh.mesh.NumVertices()): md = [i for i in parm.MorphTargetDeltas(vi)] for i in range(3): deltas[vi][i] + (md[i] * pv) parm.SetValue(0.0) mtname = unique_name("restore_detail",theAct,"") theAct.SpawnTarget(mtname) newMT = theAct.Parameter(mtname) newMT.SetValue(1.0) for parmname in morphs.keys(): parm = theAct.Parameter(parmname) parm.SetValue(morphs[parmname]) for vi in range(len(mesh.points)): vec = [0.0,0.0,0.0] for i in range(3): vec[i] = ((mesh.points[vi][i] - mesh.baseverts[vi][i]) - (mesh.verts[vi][i] - mesh.baseverts[vi][i])) if addDeltas: # Optionally add morph settings back in for i in range(3): vec[i] += deltas[vi][i] if vec: newMT.SetMorphTargetDelta(vi,vec[0],vec[1],vec[2]) scene.ProcessSomeEvents() scene.DrawAll() #=============================================================================================== # unique_name() #=============================================================================================== def unique_name(morphName,act,suffix,internal=0): """ Keep dial naming for the new morph from being automatically changed by Poser """ #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 class App: def __init__(self, master): self.master = master master.title("Restore Detail") self.scrVar = IntVar() self.scrVar.set(1) self.xVar = IntVar() self.xVar.set(1) self.yVar = IntVar() self.yVar.set(1) self.zVar = IntVar() self.zVar.set(1) self.hiloVar = IntVar() self.hiloVar.set(1) self.masterFrame = Frame(self.master,borderwidth=2,relief=RIDGE) self.masterFrame.grid(row=1,column=0) self.mixFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.mixFrame.grid(row=0,column=0) self.checkFrame = Frame(self.mixFrame) self.checkFrame.grid(row=0,column=0) self.buttonFrame = Frame(self.mixFrame) self.buttonFrame.grid(row=1,column=0) self.ListFrame2 = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.ListFrame2.grid(row=0,column=1) # --- Radio buttons --- self.radioHi = Radiobutton(self.checkFrame,text="Screen Low",variable=self.hiloVar,value=0) self.radioHi.grid(row=0,column=0) self.radioLo = Radiobutton(self.checkFrame,text="Screen High",variable=self.hiloVar,value=1) self.radioLo.grid(row=1,column=0) # --- Checkbuttons --- self.scrCheck = Checkbutton(self.checkFrame,text="Screen matched\nverts", variable=self.scrVar) self.scrCheck.grid(row = 2, column = 0) self.scr2Check = Checkbutton(self.checkFrame,text="Use X", variable=self.xVar) self.scr2Check.grid(row = 3, column = 0) self.scr2Check = Checkbutton(self.checkFrame,text="Use Y", variable=self.yVar) self.scr2Check.grid(row = 4, column = 0) self.scr2Check = Checkbutton(self.checkFrame,text="Use Z", variable=self.zVar) self.scr2Check.grid(row = 5, column = 0) # --- Listboxes --- self.listLabel2 = Label(self.ListFrame2, text="Select morph(s)" , anchor=N, justify=LEFT) self.listLabel2.grid(row=1, column=1) self.ListScroll2 = Scrollbar(self.ListFrame2, orient=VERTICAL) # Morphs self.ListScroll2.grid( row=2, column=0,sticky=N+S+E) self.List2 = Listbox(self.ListFrame2, height=20, width=20, selectmode=MULTIPLE,exportselection=0, yscrollcommand=self.ListScroll2.set) self.List2.grid( row=2, column=1) self.ListScroll2["command"] = self.List2.yview # --- Entries --- self.cycsLabel = Label(self.buttonFrame, text="Max Repititions:" , anchor=N, justify=LEFT) self.cycsLabel.grid(row=4, column=0) self.entryCycs = Entry(self.buttonFrame, width=10) self.entryCycs.insert(0,"1000") self.entryCycs.grid(row=5,column=0) self.angLabel = Label(self.buttonFrame, text="Threshold:" , anchor=N, justify=LEFT) self.angLabel.grid(row=6, column=0) self.entryAng = Entry(self.buttonFrame, width=10) self.entryAng.insert(0,"0.0015") self.entryAng.grid(row=7,column=0) # --- Buttons --- self.buttonRun = Button(self.buttonFrame, text="Run", command=self.handleRun) self.buttonRun.grid(row=8, column=0) self.buttonQuit = Button(self.buttonFrame, text="Quit", command=self.die) self.buttonQuit.grid(row=9, column=0) self.fill_boxes() def die(self): """End the script""" root.destroy() root.quit() def fill_boxes(self): act = scene.CurrentActor() for p in act.Parameters(): if p.IsMorphTarget(): self.List2.insert(END,p.Name()) def handleRun(self): s = self.List2.curselection() if s != (): morphs = [self.List2.get(int(c)) for c in s] else: morphs = [] screen = self.scrVar.get() reps = self.entryCycs.get() highlow = self.hiloVar.get() x = self.xVar.get() y = self.yVar.get() z = self.zVar.get() screenlist = [x,y,z] try: reps = int(reps) except ValueError: reps = 1000 threshold = self.entryAng.get() try: threshold = float(threshold) except ValueError: threshold = 0.0015 run(screen,reps,threshold,morphs,highlow,screenlist) root.destroy() root.quit() app = App(root) root.mainloop()