import poser import Numeric, time from Tkinter import * from _tdmt import * scene = poser.Scene() root = Tk() do_print = 0 def run(actS,all_acts,wbox,ntype,avg,offset,addDeltas,distEpsilon,useraxis,direx,invert): #invert = 0 #Collide with front or back side of polys; 0=front, normal offset; 1=back, inverted normal offset; 2=back, normal offset bake = 0 #Create morph = 0; Bake shape into geometry = 1 x_symm = 0 disp = 1 actname,figname = actS.split(" <") figname = figname.replace(">","") if figname == "None": shrinkObj = scene.Actor(actname) else: shrinkObj = scene.Figure(figname).Actor(actname) #Get the geometry data, using Spanki's Vector .pyd where possible shrinkMesh = myMesh(shrinkObj.Geometry()) shrinkMesh.verts = shrinkMesh.mesh.GetWorldVertices() shrinkMesh.vpolys = shrinkMesh.mesh.GetVertNgons() shrinkMesh.get_myMesh_vnorms() shrinkMesh.baseverts = shrinkMesh.mesh.GetVertices() if offset: ngons = shrinkMesh.mesh.GetNgons() shrinkMesh.ngons = [ngon.vertIndices for ngon in ngons] shrinkMesh.mesh.StructureChanged() # To clear .pyd internal copies of the above lists boundbox = Bbox(shrinkMesh.verts,0.005) boundbox.box2 = wbox boundbox.center2 = box_center(wbox) boundbox.mixcenters(boundbox.center1,boundbox.center2) if ntype != "normal": define_normals(shrinkMesh,boundbox,ntype,shrinkMesh.verts,useraxis=useraxis,direx=direx) if avg: # Optionally average out the shrinkMesh normals before the run - this can actually help a lot average_normals(shrinkMesh,avg) if invert: # work from inside-out instead of outside-in. Reverses the direction of valid collision faces, to collide with backsides. for vni in range(shrinkMesh.mesh.NumVertices()): vn = shrinkMesh.vnorms[vni] vn = -vn shrinkMesh.vnorms[vni] = vn.Clone() shrinkMesh.points = shrinkMesh.mesh.GetWorldVertices() no_poke(shrinkMesh,all_acts,distEpsilon,disp=disp) if offset: makeOffset(shrinkMesh,offset) setShape(shrinkMesh,shrinkObj,addDeltas=addDeltas) del shrinkMesh del boundbox #=============================================================================================== # class myMesh() #=============================================================================================== class myMesh(object): """Initialize the mesh data""" def __init__(self,geom): self.mesh = Mesh(geom) # pyd Mesh instance embedded self.safe_vpolys = [] self.geom = geom def get_myMesh_vnorms(self): """ Zero-length vpoly entries indicate stray vertices (without a polygon association); these are created by PoserPython's geometry.Weld() function and can crash the _tdmt.pyd vertex normal functions, crashing P7. This function protects against that case. """ if self.vpolys.count([]): self.zero_len_vpolys() self.vertnorms() else: self.vnorms = self.mesh.GetWorldNormals() # This seems to be what can crash the pyd if zero-length polys are present: float division for the average. def vertnorms(self,testing=0): """ [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 zero_len_vpolys(self): """ Create fake entries in the myMesh vpolys list, to avoid divide by zero errors later in the script (as with makeOffset and refresh=2). """ self.safe_vpolys = self.mesh.GetVertNgons() for vi in range(self.mesh.NumVertices()): if not self.safe_vpolys[vi]: self.safe_vpolys[vi].append(0) # Add fake listing for poly zero. Any mesh will have at least one polygon. def no_poke(shrinkMesh,all_acts,distEpsilon,disp=0): """A simple no-poke function - GROUPS""" if disp: for norm in shrinkMesh.vnorms: norm = -norm for item in all_acts: actname,figname = item.split(" <") # P7 seems to have trouble with ActorByInternalName() for body parts figname = figname.replace(">","") if figname == "None": act = scene.Actor(actname) else: act = scene.Figure(figname).Actor(actname) wMesh = Mesh(act.Geometry()) hitlist = shrinkMesh.mesh.CorrelateTo(wMesh,shrinkMesh.vnorms) for hit in range(len(hitlist)): hp = hitlist[hit] if hp: if abs(hp.HitDist) < distEpsilon: if hp.HitDist > 0.0: shrinkMesh.points[hit] = hp.HitPoint.Clone() def in_box(bbox,verts): """Simple bounding box check""" for vi in range(len(verts)): vert = verts[vi] if vert.x > bbox[0] and vert.x < bbox[1]: if vert.y > bbox[2] and vert.y < bbox[3]: if vert.z > bbox[4] and vert.z < bbox[5]: return 1 return 0 #================ Extra pre-processing (optional) ================================ def average_normals(mesh,avg): if do_print: print "Averaging normals data..." mesh.vneighbors = mesh.mesh.GetVertNeighborsByNgons() vtn = mesh.vneighbors for rep in range(avg): #Will repeat number of times of avg value if do_print: print "Averaging pass %s" %(rep) norms = mesh.vnorms for vi in range(len(mesh.verts)): l = len(vtn[vi]) nn = [i for i in norms[vi]] for ni in vtn[vi]: #average normals nn[0] += norms[ni][0] nn[1] += norms[ni][1] nn[2] += norms[ni][2] mesh.vnorms[vi] = Vector(nn[0]/l,nn[1]/l,nn[2]/l) ##=============Shaping functions================================================ def setShape(mesh,shrinkObj,addDeltas=0,bake=0): # Set the morphs or bake the shape """Bake the mesh or create the morphs""" if not bake: if do_print: print "Creating the new morph..." morphs = {} if addDeltas: deltas = [Vector() for i in mesh.verts] for parm in shrinkObj.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 = Vector() + parm.MorphTargetDelta(vi) # coerce tuple into Vector md = md.Scale(pv) # scale by parameter setting deltas[vi] += md.Clone() # merge it into the deltas list parm.SetValue(0.0) mtname = unique_name("nopoke",shrinkObj,"") shrinkObj.SpawnTarget(mtname) newMT = shrinkObj.Parameter(mtname) newMT.SetValue(1.0) for parmname in morphs.keys(): parm = shrinkObj.Parameter(parmname) parm.SetValue(morphs[parmname]) else: if do_print: print "Baking the geometry..." for vi in range(len(mesh.points)): if bake: vert = shrinkObj.Geometry().Vertex(vi) vert.SetX(mesh.points[vi][0]) vert.SetY(mesh.points[vi][1]) vert.SetZ(mesh.points[vi][2]) else: vec = ((mesh.points[vi] - mesh.baseverts[vi]) - (mesh.verts[vi] - mesh.baseverts[vi])) # Calculate delta, remove world transforms and deformations if addDeltas: # Optionally add morph settings back in vec += deltas[vi] if vec: newMT.SetMorphTargetDelta(vi,vec.x,vec.y,vec.z) if bake: shrinkObj.MarkGeomChanged() scene.ProcessSomeEvents() scene.DrawAll() def makeOffset(mesh,offset,invert=0,refresh=1): """Create an inflate offset from the match points""" if invert == 1: # 0 for no invert, 2 for invert poly side without inverting offset offset = -offset if do_print: print "Applying surface offset of %s" %(offset) if refresh == 0: norms = mesh.vnorms # Use the altered vnorms used for the wrap if refresh == 1: # New vertex normals from unaltered base mesh positions mesh.mesh.VertPositionsChanged() mesh.get_myMesh_vnorms() # Call vnorms separately because of pyd problem with stray verts (See notes in disused safe_pyd_mesh()). norms = mesh.vnorms if refresh == 2: # New vertex normals from points list positions pgons = mesh.pgons # This won't change and is initialized in run() pnorms = PolyFaceNormals(pgons,mesh.points) if mesh.safe_vpolys: vpolys = mesh.safe_vpolys else: vpolys = mesh.vpolys norms = PolyVertexNormals(pnorms,vpolys) points = mesh.points for vi in range(len(points)): if points[vi] - mesh.verts[vi]: v = points[vi] no = norms[vi] mesh.points[vi] = v.Project(no,offset) #=============================================================================================== # unique_name() #=============================================================================================== 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 #=================== vertex normals handling============================================================ def define_normals(shrinkMesh,boundbox,ntype,vtgs,useraxis=-1,direx="-z",div=3,mul=2): # These just fill the shrinkMesh.vnorms vertex normals list with different values if ntype == "sphere": # Move toward common center (like sphere-mapping) center = boundbox.mcenter for vi in range(len(vtgs)): shrinkMesh.vnorms[vi] = shrinkMesh.verts[vi]-center if ntype == "cylinder": # Move toward common center except along dominant axis center = boundbox.mcenter nx,mx,ny,my,nz,mz = boundbox.box2 if (useraxis == -1) or (useraxis > 2): axis = [abs(mx-nx),abs(my-ny),abs(mz-nz)] max_axis = axis.index(max(axis)) else: max_axis = useraxis for vi in range(len(vtgs)): center[max_axis] = vtgs[vi][max_axis] shrinkMesh.vnorms[vi] = vtgs[vi]-center if ntype == "capsule": # Move toward common center except along dominant axis, move y value toward center as the cap is approached nlist = [[] for i in vtgs] c = boundbox.center2 nx,mx,ny1,my1,nz,mz = boundbox.box2 if (useraxis == -1) or (useraxis > 2): axis = [abs(mx-nx),abs(my1-ny1),abs(mz-nz)] m_a = axis.index(max(axis)) else: m_a = useraxis my = [mx,my1,mz][m_a] ny = [nx,ny1,nz][m_a] nx,mx,ny2,my2,nz,mz = boundbox.box1 my2 = [mx,my2,mz][m_a] ny2 = [nx,ny2,nz][m_a] offval = 0.94 for vi in range(len(vtgs)): v = vtgs[vi] #Trying to set a threshold beyond which the max_axis coord will begin to descend gradually #toward the center. if abs(v[m_a]) > (c[m_a] + ((my*offval) - c[m_a])): # max and min end caps off = my - c[m_a] if my2-((c[m_a]+off)*offval) > 0.0: # The math here works as intended, but the two mapping types can't be mixed in a direct, linear manner. #div = 3 # These need to vary to adjust the way the cap is "mixed" in with the middle #mul = 2 # Not sure how to define them properly algorithmically.... c2 = c[m_a] + (((((c[m_a]+off)*offval) - c[m_a])/div)*mul) trange = my2-((c[m_a]+off)*offval) val = my2-abs(v[m_a]) nval = (trange-val)/trange c[m_a] = ((1-val)*c2) + (val*v[m_a]) if v[m_a] != abs(v[m_a]): c[m_a] = -c[m_a] else: if my2-((c[m_a]+off)*offval) == 0.0: if do_print: print vi, "float div endcap", my2, my*offval, v[m_a] == abs(v[m_a]) c[m_a] = v[m_a] else: # middle c[m_a] = v[m_a] shrinkMesh.vnorms[vi] = v - c if ntype == "box": # Send along dominant axis in vert normal. Like box-mapping. #Is ignoring verts with no dominant axis? for vi in range(len(vtgs)): no1 = list(shrinkMesh.vnorms[vi]) no2 = [abs(no1[0]),abs(no1[1]),abs(no1[2])] max_norm = no2.index(max(no2)) norm = 1.0 if no1[max_norm] != no2[max_norm]: norm = -1.0 no = Vector(0.0,0.0,0.0) no[max_norm] = norm shrinkMesh.vnorms[vi] = Vector(no[0],no[1],no[2]) if ntype == "planar": # Send everything along a set axis. # Negative values don't determine direction, but front/back for poly collisions. # Sign indicates direction of motion for shrink # e.g. "-z" moves colliding verts backwards on z. if direx == "+x": n = [1.0,0.0,0.0] if direx == "-x": n = [-1.0,0.0,0.0] if direx == "+y": n = [0.0,1.0,0.0] if direx == "-y": n = [0.0,-1.0,0.0] if direx == "+z": n = [0.0,0.0,1.0] if direx == "-z": n = [0.0,0.0,-1.0] if "x" in direx: max_norm = 0 if "y" in direx: max_notm = 1 if "z" in direx: max_norm = 2 for vi in range(len(vtgs)): norm = 1.0 no = Vector(0.0,0.0,0.0) if "+" in direx: no[max_norm] = -norm else: no[max_norm] = norm shrinkMesh.vnorms[vi] = no if ntype == "toward": # Move along normal defined by direction between centers of the two objects. n = boundbox.center2 - boundbox.center1 n = abs(n) # To point it outwards. #max_norm = max([abs(n[0]),abs(n[1]),abs(n[2])]) for vi in range(len(shrinkMesh.vnorms)): no = shrinkMesh.vnorms[vi] ndot = n^no if ndot < 0.0: #shrinkMesh.vnorms[vi] = -n shrinkMesh.vnorms[vi] = Vector() else: shrinkMesh.vnorms[vi] = n if ntype == "user": # Same as toward, but with an arbitrary user-submitted normal vector. pass #===================bounding box stuff============================= class Bbox: """ class to store bounding box data """ def __init__(self,vertpos1,increase): self.box1 = b_box_ock(vertpos1) self.box1 = increase_box(self.box1,increase) self.center1 = box_center(self.box1) def mixcenters(self,c1,c2): self.mcenter = c1 + c2 for i in range(3): self.mcenter[i] /= 2 def b_box_ock(vertpos): """ create bounding boxes """ minX = 1000000.0; maxX = -1000000.0 minY = 1000000.0; maxY = -1000000.0 minZ = 1000000.0; maxZ = -1000000.0 for i in range(len(vertpos)): tx = vertpos[i][0] ty = vertpos[i][1] tz = vertpos[i][2] if tx < minX: minX = tx if tx > maxX: maxX = tx if ty < minY: minY = ty if ty > maxY: maxY = ty if tz < minZ: minZ = tz if tz > maxZ: maxZ = tz return [minX,maxX,minY,maxY,minZ,maxZ] def mix_boxes(b1,b2,box,increase): """ merge two bounding boxes """ g = 1000000.0 box = [g,-g,g,-g,g,-g] for i in range(6): if (i+1)%2 != 0: # Odd is min if b1[i] < box[i]: box[i] = b1[i] elif (i+1)%2 == 0: if b1[i] > box[i]: box[i] = b1[i] for i in range(6): if (i+1)%2 != 0: # Odd is min if b2[i] < box[i]: box[i] = b2[i] elif (i+1)%2 == 0: if b2[i] > box[i]: box[i] = b2[i] box = increase_box(box,increase) return box def increase_box(box,increase): """ increase the bounding box size """ for i in range(6): if i%1 == 0: increase = -increase box[i] = box[i] + increase return box def box_center(box): """ find the center of the box To use results as a cylinder: use box_center x,z with vertex y Game gems: can also do (min + max)/2 for each axis. """ minX,maxX,minY,maxY,minZ,maxZ = box cx = (abs(minX-maxX)/2)+minX cy = (abs(minY-maxY)/2)+minY cz = (abs(minZ-maxZ)/2)+minZ return Vector(cx,cy,cz) class App: def __init__(self, master): #useraxis = -1 #direx = "-z" self.master = master master.title("No Poke pyd") self.deltVar = IntVar() self.deltVar.set(0) self.invVar = IntVar() self.invVar.set(0) self.figS = None self.actS = None self.masterFrame = Frame(self.master,borderwidth=2,relief=RIDGE) self.masterFrame.grid(row=1,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.mixFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.mixFrame.grid(row=1,column=2) self.normFrame = Frame(self.mixFrame,borderwidth=2) self.normFrame.grid(row=0,column=0) self.checkFrame = Frame(self.mixFrame,borderwidth=2) self.checkFrame.grid(row=1,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=3,column=0) # --- Listboxes --- self.listLabel = Label(self.ListFrame, text="Actor to Unpoke" , anchor=N, justify=LEFT) self.listLabel.grid(row=1, column=1) self.ListScroll = Scrollbar(self.ListFrame, orient=VERTICAL) # Source actors self.ListScroll.grid( row=2, column=0,sticky=N+S+E) self.List = Listbox(self.ListFrame, height=21, 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="Actor(s) from which to Unpoke" , 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=21, width=25, selectmode=MULTIPLE,exportselection=0, yscrollcommand=self.ListScroll2.set) self.List2.grid( row=2, column=1) self.ListScroll2["command"] = self.List2.yview self.listLabel3 = Label(self.normFrame, text="Normals Type" , anchor=N, justify=LEFT) self.listLabel3.grid(row=1, column=1) self.List3 = Listbox(self.normFrame, height=7, width=8, selectmode=SINGLE,exportselection=0) self.List3.grid( row=2, column=1) # --- Checkbuttons --- self.deltCheck = Checkbutton(self.checkFrame,text="Add Deltas", variable=self.deltVar) self.deltCheck.grid(row = 1, column = 0) self.invCheck = Checkbutton(self.checkFrame,text="Invert", variable=self.invVar) self.invCheck.grid(row = 2, column = 0) # --- Entries --- self.avgLabel = Label(self.buttonFrame, text="Average Normals" , anchor=N, justify=LEFT) self.avgLabel.grid(row=0, column=0) self.avgEntry = Entry(self.buttonFrame, width=3) self.avgEntry.insert(0,"0") self.avgEntry.grid(row=1,column=0) self.offLabel = Label(self.buttonFrame, text="Amount of offset" , anchor=N, justify=LEFT) self.offLabel.grid(row=2, column=0) self.offEntry = Entry(self.buttonFrame, width=8) self.offEntry.insert(0,"0.0") self.offEntry.grid(row=3,column=0) 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.8") self.distepEntry.grid(row=5,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.handleSource(e.y)) self.List2.bind('',lambda e, s=self: s.handleMorphs(e.y)) """ self.List.bind('',lambda e, s=self: s.handleShrink(e.y)) #Bind mouse to ntypes listbox. use grid and grid_forget to hide-show useraxis and direx options self.fill_boxes(self.List) self.fill_ntypes() def grid_mats(self): """Show the materials listbox and buttons for hide-show""" self.listframe.grid(row=0,column=2) self.ListScroll.grid(row=0,column=0,sticky=N+S+E) self.List.grid(row=0,column=1) self.buttonSMats.grid(row=0,column=0) self.buttonHMats.grid(row=0,column=1) self.buttonMatsCancel.grid(row=0,column=2) def ungrid_mats(self): """Hide the materials listbox and buttons for hide-show""" self.ListScroll.grid_forget() self.List.grid_forget() self.buttonSMats.grid_forget() self.buttonHMats.grid_forget() self.buttonMatsCancel.grid_forget() self.listframe.grid_forget() def handleShrink(self,event): self.figS = None self.List2.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()) 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 fill_ntypes(self): ntypes = ["normal","cylinder","sphere","capsule","box"]#,"planar","toward"] for n in ntypes: self.List3.insert(END,n) self.List3.selection_set(0) def die(self): """End the script""" root.destroy() root.quit() def handleRun(self): if not self.actS: # No shrink actor selected return s = self.List2.curselection() if s != (): all_acts = [self.List2.get(int(c)) for c in s] wboxes = [] for t in all_acts: name,fig = t.split(" <") fig = fig.replace(">","") if fig == "None": mesh = myMesh(scene.Actor(name).Geometry()) else: figS = scene.Figure(fig) actS = figS.Actor(name) mesh = myMesh(actS.Geometry()) verts = mesh.mesh.GetWorldVertices() box = b_box_ock(verts) wboxes.append(box) del mesh del verts wbox = wboxes[0] if len(wboxes) > 1: for i in range(1,len(wboxes)): box = wboxes[i] mix_boxes(wbox,box,wbox,0.0) else: # No wrap actor(s) selected return if self.figS: fig = self.figS.Name() else: fig = "None" actS = "%s <%s>" %(self.actS.Name(),fig) ntype = self.List3.get(int(self.List3.curselection()[0])) try: avg = int(self.avgEntry.get()) except ValueError: avg = 0 try: offset = float(self.offEntry.get()) except ValueError: offset = 0.0 try: distEpsilon = float(self.distepEntry.get()) except ValueError: distEpsilon = 0.8 addDeltas = self.deltVar.get() invert = self.invVar.get() useraxis = -1 # Change direx = "-z" # Change run(actS,all_acts,wbox,ntype,avg,offset,addDeltas,distEpsilon,useraxis,direx,invert) app = App(root) root.mainloop()