import poser, math, Numeric, time from Tkinter import * """ This script offers a variety of morph-trimming methods. Free for any use. Problems, questions, or comments can be directed to Cage23drei@yahoo.com or member Cage at www.renderosity.com. """ """ screening by: -xx- weld edges -xx- split edges -xx- zero deltas -xx- materials -xx- groups -xx- morph subtraction -xx- inverse morph subtraction -xx- x,y,z -xx- deltas by threshold """ scene = poser.Scene() root = Tk() FLT_EPSILON = 1.19209290e-07 def run(act,morph,submorph,threshold,dtrim,sub,inv,screenlist,weld,edge,zero,mats,group): update("Starting...") mesh = myMesh(act) mesh.init_lists() mesh.screen = [0 for i in range(mesh.numverts)] if mats.count(1): update("Screening materials...") screen_vmats(mesh,mats) if group: update("Screening group...") screen_group(mesh,group) if sub: update("Subtracting morph...") subtract_morph(mesh,submorph,inv) if weld: update("Screening weld edges...") mesh.screen_weld() if zero: update("Trimming zero deltas...") screen_zerodeltas(mesh,morph) if edge: update("Screening split edges...") mesh.init_screen() mesh.screen_edge() if dtrim: update("Screening deltas by threshold...") threshold_trim(mesh,morph,threshold) morphsD = {} # Zero all morphs and deformers for parm in act.Parameters(): if parm.IsMorphTarget() or parm.TypeCode() == 49: #poser.kParmCodeDEFORMERPROP: morphsD[parm.Name()] = parm.Value() parm.SetValue(0.0) update("Creating morph...") newname = unique_name(morph.Name(),act,"_TRIM") act.SpawnTarget(newname) newmorph = act.Parameter(newname) for vi in range(mesh.numverts): if not mesh.screen[vi]: delta = morph.MorphTargetDelta(vi) d = [0.0,0.0,0.0] for i in range(3): if screenlist[i]: d[i] = delta[i] newmorph.SetMorphTargetDelta(vi,d[0],d[1],d[2]) for parmname in morphsD.keys(): # Restore the morph and deformer settings parm = act.Parameter(parmname) parm.SetValue(morphsD[parmname]) morphsD = {} def update(msg): app.status_update(msg) 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 mscreen = [-1 for i in range(mesh.numverts)] 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 for pi in range(mesh.geom.NumPolygons()): ngv = mesh.pverts[pi] for vi in ngv: if mscreen[vi] == 1: mesh.screen[vi] = 1 def screen_zerodeltas(mesh,morph): """ Screens out zero deltas in submitted morph """ for vi in range(mesh.numverts): d = morph.MorphTargetDelta(vi) if abs(d[0]) < FLT_EPSILON and abs(d[1]) < FLT_EPSILON and abs(d[2]) < FLT_EPSILON: mesh.screen[vi] = 1 def screen_group(mesh,group): for pi in range(mesh.geom.NumPolygons()): p = mesh.geom.Polygon(pi) if p.InGroup(group): for vi in mesh.pverts[pi]: mesh.screen[vi] = 1 def subtract_morph(mesh,submorph,inv): for vi in range(mesh.numverts): ds = submorph.MorphTargetDelta(vi) if inv: if abs(ds[0]) > FLT_EPSILON or abs(ds[1]) > FLT_EPSILON or abs(ds[2]) > FLT_EPSILON: continue else: mesh.screen[vi] = 1 else: if abs(ds[0]) > FLT_EPSILON or abs(ds[1]) > FLT_EPSILON or abs(ds[2]) > FLT_EPSILON: mesh.screen[vi] = 1 def threshold_trim(mesh,morph,threshold): for vi in range(mesh.numverts): d = morph.MorphTargetDelta(vi) if abs(d[0]) >= threshold or abs(d[1]) >= threshold or abs(d[2]) >= threshold: continue else: mesh.screen[vi] = 1 class myMesh(object): """Initialize the mesh data - just a container class for pyd Mesh instance""" def __init__(self,act): self.act = act self.geom = act.Geometry() self.numverts = self.geom.NumVertices() def init_lists(self): self.polyverts(self.geom,self.geom.Polygons()) self.vertpolys(self.geom,self.pverts) self.vertneighbors(self.pverts) def init_screen(self): self.polyedges(self.pverts) self.no_neighbors() 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] def polyedges(self,pgons): """ edges of polygons as [start vert,end vert] returns a Numeric array """ self.edges = [[[0,0] for j in range(len(i))] for i in pgons] for p in range(len(pgons)): for v in range(len(pgons[p])): if v != len(pgons[p])-1: self.edges[p][v] = [pgons[p][v],pgons[p][v+1]] else: self.edges[p][v] = [pgons[p][v],pgons[p][0]] self.edges = [Numeric.array(j,Numeric.Int) for j in self.edges] def no_neighbors(self,group=None,reverse=0,useV=1,useE=0): """ Locate edges of polys which have no polygon neighbors noNeighbors lists edges by ngon, in the format of the edges list noNeighborsV converts the same data to vertices. noNeighborsE lists the clockwise (reverse is counter-clockwise) edge partner for each of these verts Used by screen_vedges() If submitted, group will restrict returns to polys within that group """ pgons = self.geom.NumPolygons() verts = self.numverts self.noNeighbors = [[-1 for j in i] for i in self.edges] # Result will be -1 for split edges, neighbor poly index for not. for pi in range(pgons): for e1,e2 in self.edges[pi]: for p in self.vpolys[e1]: if p != pi: if group == None or self.geom.Polygon(p).InGroup(group): for edge in range(len(self.edges[p])): e3,e4 = self.edges[p][edge] if (e1,e2) == (e4,e3): self.noNeighbors[p][edge] = pi if group != None: if not self.geom.Polygon(p).InGroup(group): for edge in range(len(self.edges[p])): self.noNeighbors[p][edge] = pi if useE or useV: if useV: self.noNeighborsV = [0 for i in range(verts)] # Will contain 1 for split edges, 0 for not. if useE: self.noNeighborsE = [-1 for i in range(verts)] if reverse == 2: self.noNeighborsE = [[] for i in range(verts)] for edge in range(len(self.edges)): for verts2 in range(len(self.edges[edge])): if self.noNeighbors[edge][verts2] == -1: if useV: for vi in range(2): self.noNeighborsV[self.edges[edge][verts2][vi]] = 1 if useE: if reverse == 1: self.noNeighborsE[self.edges[edge][verts2][1]] = self.edges[edge][verts2][0] if reverse == 0: self.noNeighborsE[self.edges[edge][verts2][0]] = self.edges[edge][verts2][1] if reverse == 2: self.noNeighborsE[self.edges[edge][verts2][0]].append([self.edges[edge][verts2][0],self.edges[edge][verts2][1]]) self.noNeighborsE[self.edges[edge][verts2][1]].append([self.edges[edge][verts2][0],self.edges[edge][verts2][1]]) def screen_edge(self): """ Screen edges with no polygon neighbors. This will locate split edges, including weld edges on body part actors. """ self.no_neighbors() for vi in range(self.numverts): if self.noNeighborsV[vi]: # 1 is split; 0 is not. self.screen[vi] = 1 self.noNeighbors = [] self.noNeighborsV = [] def screen_weld(self): if not self.act.IsBodyPart(): return acts = self.act.Children() acts.append(self.act) for act in acts: if act.Name() == self.act.Name(): welds = [[j for j in range(len(i)) if i[j] != -1] for i in act.WeldGoals()] else: welds = [[j for j in i if j != -1] for i in act.WeldGoals()] for j in welds: for i in j: self.screen[i] = 1 #=============================================================================================== # 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("Morph Trimmer 1") self.xVar = IntVar() self.xVar.set(1) self.yVar = IntVar() self.yVar.set(1) self.zVar = IntVar() self.zVar.set(1) self.subVar = IntVar() self.subVar.set(0) self.invVar = IntVar() self.invVar.set(0) self.weldVar = IntVar() self.weldVar.set(0) self.edgeVar = IntVar() self.edgeVar.set(0) self.zeroVar = IntVar() self.zeroVar.set(1) self.trimVar = IntVar() self.trimVar.set(0) 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.coordFrame = Frame(self.mixFrame) self.coordFrame.grid(row=0,column=0, sticky=E+W) self.subFrame = Frame(self.mixFrame,borderwidth=2,relief=RIDGE) self.subFrame.grid(row=1,column=0) self.subLabel = Label(self.subFrame, text="Morph Subtraction" , anchor=N, justify=LEFT) self.subLabel.grid(row=0, column=0, padx=35, pady=2) self.sub2Frame = Frame(self.subFrame) self.sub2Frame.grid(row=1,column=0, sticky=W) self.subLabel2 = Label(self.subFrame, text=" " , anchor=N, justify=LEFT) self.subLabel2.grid(row=3, column=0) self.dtrimFrame = Frame(self.mixFrame,borderwidth=2,relief=RIDGE) self.dtrimFrame.grid(row=2,column=0) self.dtrimLabel = Label(self.dtrimFrame, text="Delta Trimming" , anchor=N, justify=LEFT) self.dtrimLabel.grid(row=0, column=0, padx=44, pady=2) self.dtrim2Frame = Frame(self.dtrimFrame) self.dtrim2Frame.grid(row=1,column=0, sticky=W) self.entryFrame = Frame(self.dtrim2Frame) self.entryFrame.grid(row=2,column=0, sticky=W) self.dtrimLabel2 = Label(self.dtrimFrame, text=" " , anchor=N, justify=LEFT) self.dtrimLabel2.grid(row=4, column=0) self.edgeFrame = Frame(self.mixFrame,borderwidth=2,relief=RIDGE) self.edgeFrame.grid(row=3,column=0) self.edgeLabel = Label(self.edgeFrame, text="Edge Screening" , anchor=N, justify=LEFT) self.edgeLabel.grid(row=0, column=0, padx=42, pady=2) self.edge2Frame = Frame(self.edgeFrame) self.edge2Frame.grid(row=1,column=0, sticky=W) self.edgeLabel2 = Label(self.edgeFrame, text=" " , anchor=N, justify=LEFT) self.edgeLabel2.grid(row=4, column=0) self.quitFrame = Frame(self.mixFrame) self.quitFrame.grid(row=4,column=0) self.ListFrame2 = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.ListFrame2.grid(row=0,column=1) self.ListFrame3 = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.ListFrame3.grid(row=0,column=2) self.matFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.matFrame.grid(row=0,column=3) self.displayFrame = Frame(self.master) self.displayFrame.grid(row=2,column=0) # --- Checkbuttons --- self.weldCheck = Checkbutton(self.edge2Frame,text="Screen Weld Edges", variable=self.weldVar) self.weldCheck.grid(row = 0, column = 0, sticky=W) self.edgeCheck = Checkbutton(self.edge2Frame,text="Screen Split Edges", variable=self.edgeVar) self.edgeCheck.grid(row = 1, column = 0, sticky=W) self.zeroCheck = Checkbutton(self.dtrim2Frame,text="Remove Zero Deltas", variable=self.zeroVar) self.zeroCheck.grid(row = 0, column = 0, sticky=W) self.trimCheck = Checkbutton(self.dtrim2Frame,text="Trim by Threshold", variable=self.trimVar) self.trimCheck.grid(row = 1, column = 0, sticky=W) self.subCheck = Checkbutton(self.sub2Frame,text="Use Morph Subtraction", variable=self.subVar) self.subCheck.grid(row = 0, column = 0, sticky=W) self.invCheck = Checkbutton(self.sub2Frame,text="Invert (Subtract Zero Deltas)", variable=self.invVar) self.invCheck.grid(row = 1, column = 0, sticky=W) self.scrxCheck = Checkbutton(self.coordFrame,text="Use X", variable=self.xVar) self.scrxCheck.grid(row = 0, column = 0) self.scryCheck = Checkbutton(self.coordFrame,text="Use Y", variable=self.yVar) self.scryCheck.grid(row = 0, column = 1) self.scrzCheck = Checkbutton(self.coordFrame,text="Use Z", variable=self.zVar) self.scrzCheck.grid(row = 0, column = 2) # --- Listboxes --- self.listLabel2 = Label(self.ListFrame2, text="Morph to Trim" , 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=22, width=30, selectmode=SINGLE,exportselection=0, yscrollcommand=self.ListScroll2.set) self.List2.grid( row=2, column=1) self.ListScroll2["command"] = self.List2.yview self.listLabel3 = Label(self.ListFrame3, text="Subtraction Morph" , anchor=N, justify=LEFT) self.listLabel3.grid(row=1, column=1) self.ListScroll3 = Scrollbar(self.ListFrame3, orient=VERTICAL) # Subtraction Morphs self.ListScroll3.grid( row=2, column=0,sticky=N+S+E) self.List3 = Listbox(self.ListFrame3, height=22, width=30, selectmode=SINGLE,exportselection=0, yscrollcommand=self.ListScroll3.set) self.List3.grid( row=2, column=1) self.ListScroll3["command"] = self.List3.yview self.matLabel = Label(self.matFrame, text="Screen Materials" , anchor=N, justify=LEFT) self.matLabel.grid(row=1, column=1) self.matScroll = Scrollbar(self.matFrame, orient=VERTICAL) # Materials self.matScroll.grid( row=2, column=0,sticky=N+S+E) self.matL = Listbox(self.matFrame, height=10, width=20, 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="Screen Group" , anchor=N, justify=LEFT) self.matLabel2.grid(row=3, column=1) self.matScroll2 = Scrollbar(self.matFrame, orient=VERTICAL) # Groups self.matScroll2.grid( row=4, column=0,sticky=N+S+E) self.matL2 = Listbox(self.matFrame, height=10, width=20, selectmode=SINGLE,exportselection=0, yscrollcommand=self.matScroll2.set) self.matL2.grid( row=4, column=1) self.matScroll2["command"] = self.matL2.yview self.List5 = Listbox(self.displayFrame, height=5, width=122, selectmode=SINGLE,exportselection=0) # Status bar self.List5.grid( row=3, column=0) # --- Entries --- self.angLabel = Label(self.entryFrame, text="Threshold:" , anchor=N, justify=LEFT) self.angLabel.grid(row=0, column=0, sticky=W) self.entryAng = Entry(self.entryFrame, width=10) self.entryAng.insert(0,"0.000156") self.entryAng.grid(row=0,column=1,padx=8) # --- Buttons --- self.buttonRun = Button(self.quitFrame, text="Run", command=self.handleRun, padx=25) self.buttonRun.grid(row=0, column=0, padx=5, pady = 2) self.buttonQuit = Button(self.quitFrame, text="Quit", command=self.die, padx=25) self.buttonQuit.grid(row=0, column=3, padx=5, pady = 2) self.fill_boxes() self.status_update("Ready") def status_update(self,line): self.List5.insert(0,line) self.List5.update() 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()) self.List3.insert(END,p.Name()) groups = act.Geometry().Groups() for gi in range(len(groups)): group = groups[gi] self.matL2.insert(END,group) mats = self.get_mats(act) for i in mats: self.matL.insert(END,i) 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 handleRun(self): StartTime = time.time() act = scene.CurrentActor() if not self.List2.curselection(): self.status_update("No morph selected. Select a morph to trim.") return else: mname = self.List2.get(self.List2.curselection()) morph = act.Parameter(mname) sub = self.subVar.get() inv = self.invVar.get() if sub: if not self.List3.curselection(): self.status_update("No subtraction morph selected.") return else: mname = self.List3.get(self.List3.curselection()) submorph = act.Parameter(mname) else: submorph = None if self.matL2.curselection(): group = self.matL2.get(self.matL2.curselection()) else: group = None mT = [self.matL.get(int(i)).split(" <") for i in self.matL.curselection()] mats = [-1 for i in range(len(act.Materials()))] for m in mT: matname,index = m index = index.replace(">","") mats[int(index)] = 1 dtrim = self.trimVar.get() weld = self.weldVar.get() edge = self.edgeVar.get() zero = self.zeroVar.get() if edge: weld = 0 # Edge screening includes weld screening; no need to run both. x = self.xVar.get() y = self.yVar.get() z = self.zVar.get() screenlist = [x,y,z] threshold = self.entryAng.get() try: threshold = float(threshold) except ValueError: threshold = 0.000156 run(act,morph,submorph,threshold,dtrim,sub,inv,screenlist,weld,edge,zero,mats,group) EndTime = (time.time() - StartTime)/60 minutes = int(EndTime) seconds = int((EndTime - float(minutes))*60) self.status_update("Done in %s minutes, %s seconds." %(minutes,seconds)) app = App(root) root.mainloop()