import poser from Tkinter import * import Numeric, math, time from _tdmt import * scene = poser.Scene() root = Tk() # Not sure whether longdeltas is screening backwards. May need a third loop w/ a screen list? # Do weld neighbors also need to average weld goal normals? (Presumably not) # Can add pseudo-weld handling to TDMT, shrinkWrap, nopoke. # Other pyd scripts need the vnorms crash error protections applied here. # Other pyd scripts need baked-in rotation protection, using points_no_transforms() # Omit split edge with inflate is bugged? def run(actS,runtype,smSel,smWhat,mats,groups,safeEdge,morph,coords,\ reps,omit,angle,lessgreater,threshold,invedge,volume,pseudoWeld,extEdge,weight,aweld): types = [["All","Longdeltas","Angle"], ["Morph","Base","Current"], ["Smooth","Trim","Inflate","Subtract"]] name,fig = actS.split(" <") fig = fig.replace(">","") if fig == "None": theAct = scene.Actor(name) else: theAct = scene.Figure(fig).Actor(name) theMesh = myMesh(theAct.Geometry(),"vert",theAct) theMesh.get_myMesh_lists() #theMesh.get_myMesh_vnorms() # Call vnorms separately because of pyd problem with stray verts (See notes in disused safe_pyd_mesh()). # --- Screening types --- theMesh.screen = [1 for i in range(theMesh.mesh.NumVertices())] if mats: # Screen mats screen_vgroups_vmats(theAct,theMesh,invedge,1,mats) if groups: # Screen groups screen_vgroups_vmats(theAct,theMesh,invedge,0,groups) if safeEdge and not pseudoWeld: # Protect edges - overruled by pseudo-welding screen_vedges(theAct,theMesh) if omit and morph: # Omit Zero deltas screen_zerodeltas(theAct,theMesh,morph[0]) if omit and (not morph): screen_zerodeltas_current(theMesh) # --- Pseudo-weld handling for split edges --- theMesh.psweld = [] # Need a blank for condition checks in tighten_smooth() if pseudoWeld: # Pseudo-weld split edges matched_verts(theMesh) # --- Split edge fake "extension" handling to preserve edges --- theMesh.extend = 0 # Need a blank for condition checks in tighten_smooth() if extEdge and (not pseudoWeld): theMesh.extend = 1 if not runtype: # Smooth if smWhat == 0: #Morph points_no_transforms(theMesh,theAct,setverts=1,setnorms=1,nomorphs=1,nomags=1,setmorph=morph[0]) if smWhat == 1: # Base Geometry points_no_transforms(theMesh,theAct,setverts=1,setnorms=1,nomorphs=1,nomags=1) if smWhat == 2: #Current Shape points_no_transforms(theMesh,theAct,setverts=1,setnorms=1) if volume: # Set up Cheapie Volume Preservation. # Part two is run in tighten_smooth(); it needs to be run for each smoothing iteration. # Running once at end of script causes inflation if smooth is run with reps > 1. bbox = b_box_ock(theMesh.verts) theMesh.center = box_center(bbox) # Works by comparing before and after distances from center of unsmoothed mesh shape. theMesh.startDists = [v.PointDistance(theMesh.center) for v in theMesh.verts] #theMesh.pgons = psrPolygonList(theAct.Geometry()) # To keep from calling this repeatedly with makeOffset() and refresh=2. theMesh.pgons = [ngon.vertIndices for ngon in theMesh.ngons] # --- Weld neighbor handling --- theMesh.awelds = [] # Need a blank for condition checks in tighten_smooth() if fig != "None" and aweld: # Create theMesh.awelds to assist with proper smoothing along weld joins. setmorph = "" nomorphs = 1 if smWhat == 0: setmorph = morph[0] if smWhat == 2: nomorphs = 0 screen_welds(theMesh,theAct,nomorphs=nomorphs,setmorph=setmorph) if smSel == 1 and morph: # Long Deltas screen_longdeltas(theAct,theMesh,morph[0],threshold) if smSel == 2: # Angle theMesh.hardedges(angle,lessgreater) tighten_smooth(theMesh,reps,coords,volume=volume,weight=weight) if theMesh.psweld: correct_weld_deviation(theMesh,testing=0) if morph: types[1][0] = morph[0] name = "%s_%s" %(types[1][smWhat],types[2][runtype]) setShape(theMesh,theAct,name,remove=1) if runtype: points_no_transforms(theMesh,theAct,setverts=1,setnorms=1,refresh=1) if runtype == 1: # Trim trim_morph(theMesh,theAct,coords,morph,0) name_add = morph[0] rem = 1 if runtype == 2: # Inflate makeOffset(theMesh,0.005*reps,coords,refresh=0,useall=1) # Use reps setting to raise the offset level name_add = "Normals" rem = 1 if theMesh.psweld: correct_weld_deviation(theMesh,testing=0) if runtype == 3: # Subtract trim_morph(theMesh,theAct,coords,morph,1) name_add = morph[0] rem = 1 name = "%s_%s" %(name_add,types[2][runtype]) setShape(theMesh,theAct,name,setit=0,remove=rem) del theMesh return def points_no_transforms(mesh,act,setpoints=1,setverts=0,setnorms=0,nomorphs=0,nomags=0,setlist=None,setmorph="",refresh=0): """ Even with Spanki's worldspace transforms removal code, some functions still seem to bake rotations into the final morph. This code sets up points (and optionally verts and vnorms) without any rotations. Note: It seems that the rotation-baking derives from the fact that we can only get worldspace normals with the pyd. The worldspace removal can strip out the scaling and translation, but any use of vertex normals will bake the rotations back into the result. - Scaling and translation are removed by worldspace removal in setShape. - Zero rotations in all cases. - Zero tapers for base shape or single morph. - Use of DrawAll is necessary for inflate. For smooth, it can cause problems in cases where e.g. a conformed actor is rotated by its conformee actor. """ parmsD = {} fig = None """ if act.IsBodyPart(): fig = act.ItsFigure() if fig.NumIkChains(): iklist = [] for i in range(fig.NumIkChains()): # Turn off IK and store the settings iklist.append(fig.IkStatus(i)) fig.SetIkStatus(i,0) """ for parm in act.Parameters(): if nomorphs: if parm.TypeCode() == poser.kParmCodeTARGET: # Optionally zero morphs parmsD[parm.Name()] = parm.Value() parm.SetValue(0.0) if nomags: if parm.TypeCode() == poser.kParmCodeDEFORMERPROP: # Optionally zero deformers parmsD[parm.Name()] = parm.Value() parm.SetValue(0.0) if nomorphs and nomags: # We don't want tapers if we're getting base shape or a single morph. if parm.TypeCode() == poser.kParmCodeTAPERX or parm.TypeCode() == poser.kParmCodeTAPERY or\ parm.TypeCode() == poser.kParmCodeTAPERZ: # Optionally zero tapers. parmsD[parm.Name()] = parm.Value() parm.SetValue(0.0) if parm.TypeCode() == poser.kParmCodeXROT or parm.TypeCode() == poser.kParmCodeYROT or\ parm.TypeCode() == poser.kParmCodeZROT: # Always zero rotations. parmsD[parm.Name()] = parm.Value() parm.SetValue(0.0) if setmorph: act.Parameter(setmorph).SetValue(1.0) # Optionally set a specified morph mesh.mesh.VertPositionsChanged() # Tell the pyd that we've altered the mesh shape. if refresh: scene.DrawAll() # This is essential for inflate! In some unusual cases, Poser will return very odd results if we don't update the scene here. if setpoints: mesh.points = mesh.mesh.GetWorldVertices() if setverts: mesh.verts = mesh.mesh.GetWorldVertices() if setnorms: mesh.get_myMesh_vnorms() # Call vnorms separately because of pyd problem with stray verts (See notes in disused safe_pyd_mesh()). if setlist != None: # Set submitted list in place (submit as e.g. mesh.mylisttoset - can be blank list [].) setlist = mesh.mesh.GetWorldVertices() for i in parmsD.items(): act.Parameter(i[0]).SetValue(i[1]) mesh.mesh.VertPositionsChanged() """ if fig: if fig.NumIkChains(): # Restore IK for i in range(len(iklist)): fig.SetIkStatus(i,iklist[i]) """ if refresh: scene.DrawAll() # Just to be safe, we'll update the scene again. def distdiffs(mesh): """ The Cheapie (technically-incorrect) Volume Preservation process bakes the unsmoothed shape back into the smoothed results. To compensate, average the distance deltas used to create the offset, using vertex neighbors. Secondary smoothing. The result sort of smooths and sort of preserves volume, but isn't as effective as a more technically-correct approach might be, although results can be decent with repeated applications. """ startDists = mesh.startDists center = mesh.center vnbs = mesh.neighbors dists = [mesh.points[vi].PointDistance(center) for vi in range(mesh.mesh.NumVertices())] tempDiffs = [abs(startDists[vi] - dists[vi]) for vi in range(mesh.mesh.NumVertices())] distDiffs = [0.0 for vi in range(mesh.mesh.NumVertices())] for vi in range(mesh.mesh.NumVertices()): nbs = vnbs[vi] nl = len(nbs) + 1 dist = tempDiffs[vi] for vni in nbs: dist += tempDiffs[vni] dist /= nl if mesh.psweld: # Pseudo-weld handling for matched-location verts on split edges if mesh.psweld[vi]: m = mesh.psweld[vi] ml = len(m) + 1 # Number of match verts plus current vert mvdist = 0.0 for mvi in m: nbs = vnbs[mvi] nl = len(nbs) if nl: for vni in nbs: mvdist += tempDiffs[mvi] mvdist /= nl dist += mvdist dist /= ml distDiffs[vi] = dist mesh.startDists = dists # Reset the startDists for progressive looping with tighten_smooth() return distDiffs def correct_weld_deviation(mesh,testing=0): """ With all the averaging that's taking place, along with the value decimal-clamping used to locate matched verts, we can eventually pick up adequate positional deviation in matched verts to break the whole process, after repeated runs. Hence, this bit of a kludge. At the end, we go through and just make sure the matched vert locations are all the same, within our clamped value tolerance level. The deviation seems to occur when enough was present in the unsmoothed mesh to have failed the match test initially if value clamping was not in use. An example of such a case is the Poser box prop. Some of the corners are not perfectly matched and will fail a check for shared vert locations if the X,Y,Z values submitted by Poser and passed on by the pyd are compared. """ if mesh.psweld: for vi in range(mesh.mesh.NumVertices()): if mesh.psweld[vi]: v = mesh.points[vi] vs = "%.5f,%.5f,%.5f" %(v[0],v[1],v[2]) if testing: print "Welds for %s:" %(vi) print "%s: (%s)" %(vi,vs) for wvi in mesh.psweld[vi]: wv = mesh.points[wvi] wvs = "%.5f,%.5f,%.5f" %(wv[0],wv[1],wv[2]) if testing: print "%s: (%s)" %(wvi,wvs) if wvs != vs: mesh.points[wvi] = mesh.points[vi].Clone() def screen_vgroups_vmats(act,mesh,inv,g_or_m,thescreen): """ Screen by submitted group or material names thescreen is just the group or mat names selected in the GUI """ #print thescreen gmscreen = [0 for i in range(mesh.mesh.NumVertices())] for pi in range(mesh.mesh.NumNgons()): nvs = mesh.ngons[pi].vertIndices p = act.Geometry().Polygon(pi) for gmname in thescreen: if ((not g_or_m) and p.InGroup(gmname)) or (g_or_m and (gmname == p.MaterialName())): for vi in nvs: gmscreen[vi] = 1 for vi in range(mesh.mesh.NumVertices()): if ((not inv) and (not gmscreen[vi])) or (inv and gmscreen[vi]): mesh.screen[vi] = 0 def screen_vedges(act,mesh,use=1,testing=0): """ Screen edges with no polygon neighbors. This will locate split edges, including weld edges on body part actors. """ mesh.no_neighbors() for vi in range(mesh.mesh.NumVertices()): if mesh.noNeighborsV[vi]: # 1 is split; 0 is not. mesh.screen[vi] = 0 if testing: testing_boxes(mesh.verts[vi],0.0005,"_EDGE_",0,1) mesh.noNeighbors = [] if use: mesh.noNeighborsV = [] def screen_zerodeltas(act,mesh,morph): """ Screens out zero deltas in submitted morph """ parm = act.Parameter(morph) for vi in range(mesh.mesh.NumVertices()): d = parm.MorphTargetDelta(vi) d = Vector() + d if not d: mesh.screen[vi] = 0 def screen_zerodeltas_current(mesh): """ Screens out verts in current shape if their positions match those of the base geometry. """ for vi in range(mesh.mesh.NumVertices()): d = mesh.verts[vi] - mesh.baseverts[vi] if not d: mesh.screen[vi] = 0 def screen_longdeltas(act,mesh,morph,threshold,testing=0): """ Locates deltas which "move faster" than those of neighbor verts, by a submitted threshold. Requires mesh.neighbors """ dists = [0.0 for i in range(mesh.mesh.NumVertices())] parm = act.Parameter(morph) for vi in range(mesh.mesh.NumVertices()): d = parm.MorphTargetDelta(vi) d = Vector() + d dists[vi] = d.Length() for vi in range(mesh.mesh.NumVertices()): for vn in mesh.neighbors[vi]: vdist = abs(dists[vn]-dists[vi]) if vdist < threshold: mesh.screen[vi] = 0 if testing: testing_boxes(mesh.verts[vi],0.0005,"_LONGDELTA_",0,1) def polycenters(mesh,verts,use=0,testing=0): """ Calculate centers of ngons. use == 1: Finds the actual ngon centers use == 0: Pre-compiles averages of ngon centers for each vert, to send to tighten_smooth() """ pcents = [] pgons = [ngon.vertIndices for ngon in mesh.ngons] # TDMT Classic Geom.pverts array for pvi in range(len(pgons)): p = Vector() pv = pgons[pvi] ln = len(pgons[pvi]) for vert in pv: v = verts[vert] p += v v_avg = p.Scale(1.0/ln) pcents.append(v_avg.Clone()) if use: # return the actual list of ngon centers mesh.pcenters = pcents return mesh.pcenters = [Vector() for i in range(mesh.mesh.NumVertices())] for vi in range(mesh.mesh.NumVertices()): d = Vector() # d will be average of poly centers for all polys of vert for poly in mesh.vpolys[vi]: d += pcents[poly] ln = max(1,len(mesh.vpolys[vi])) #if not ln: # A stray vert will mean a zero length vpoly listing and float division error. #continue d = d.Scale(1.0/ln) if testing: testing_boxes(d,0.0005,"_PCENTERS_",0,1) mesh.pcenters[vi] = d.Clone() def matched_verts(mesh): """ Find verts with identical locations (for pseudo-weld handling of split edges). Values here are clamped to 5 decimal places. At full Poser precision, some split edge vertices (such as those on some corners of the Poser box prop) will not actually be found to match. So, for the purpose of this function, if two verts match to five decimal places, they are in identical locations. This should be run before normals are acquired for the myMesh. Otherwise, cases where stray verts have match locations will be caught, because normals protection creates fake vpolys listings for stray verts. """ mesh.psweld = [[] for i in range(mesh.mesh.NumVertices())] matchdict = {} has_appended = 0 # Whether or not we found matches; can also be queried for match count. for vi in range(mesh.mesh.NumVertices()): v = mesh.verts[vi] coords = "%.5f,%.5f,%.5f" %(v[0],v[1],v[2]) # Clamp the values to 5 decimal places to facilitate matches. if matchdict.has_key(coords): matchdict[coords].append(vi) else: matchdict[coords] = [vi] for vi in range(mesh.mesh.NumVertices()): if len(mesh.vpolys[vi]): # Skip stray verts v = mesh.verts[vi] coords = "%.5f,%.5f,%.5f" %(v[0],v[1],v[2]) # Clamp the values to 5 decimal places to facilitate matches. for wvi in matchdict[coords]: if wvi != vi: if len(mesh.vpolys[wvi]): # Skip stray verts mesh.psweld[vi].append(wvi) has_appended += 1 app.status_update("Pseudo-weld found %s verts with matched locations." %(has_appended)) if not has_appended: # If no matches were found, keep the list blank, so we won't try to iterate over an empty VBA mesh.psweld = [] def tighten_smooth(mesh,reps,coords,volume=0,weight=1.0,screen=[]): """ Vertex smoothing using averages of all poly centers of the vertex. Adapted from the Wings3D Tighten function source code. Smooths by polygon centers. """ if mesh.extend: # Need mesh.noNeighborsV for edge screening mesh.no_neighbors() if not screen: # Can submit a custom list screen = mesh.screen for rep in range(reps): app.status_update("Smoothing %s of %s repititions..." %(rep+1,reps)) polycenters(mesh,mesh.points) # Refresh the poly centers information with each cycle pass. pcenters = mesh.pcenters for vi in range(len(mesh.points)): if screen[vi]: # Skip verts with screened materials, groups, or edges p = mesh.points[vi] d = pcenters[vi] not_welded = 1 if mesh.psweld: # Pseudo-weld handling for matched-location verts on split edges if mesh.psweld[vi]: m = mesh.psweld[vi] ml = len(m) + 1 # Number of match verts plus current vert for mvi in m: d += pcenters[mvi] d = d.Scale(1.0/ml) if mesh.awelds: # Weld-neighbor handling for body parts if mesh.awelds[vi]: aw = mesh.awelds[vi][0] # These are pre-compiled polycenter averages, so we divide only by 2. d += aw d = d.Scale(1.0/2) not_welded = 0 if mesh.extend: # This is mutually-exclusive with pseudo-weld if mesh.noNeighborsV[vi] and not_welded: # If we're on a split edge which isn't an actor weld d2 = ((p - d) * weight) + p # Create pcenters copy for vert by mirroring across the vert. d += d2 d = d.Scale(1.0/2) for i in range(3): if coords[i]: # Screen coords p[i] = d[i] # p is screened point for coords mesh.points[vi] = p.Clone() # We loop progressively over the changed points positions if volume: # Average dists by vneighbor for volume preservation distDiffs = distdiffs(mesh) makeOffset(mesh,0.0,coords,refresh=2,offlist=distDiffs) # refresh=2 calculates new normals from points def trim_morph(mesh,act,coords,morphs,bymorph): """ Trim a submitted morph, using the screening functions of the script. If bymorph is set and a second morph is submitted, sets to zero all the deltas in morph 1 which are non-zero in morph 2. Useful for developing custom screening sets by magnet-morphing the desired area of an actor. """ screen = mesh.screen points = mesh.points mname1 = morphs[0] m1 = act.Parameter(mname1) if bymorph: mname2 = morphs[1] m2 = act.Parameter(mname2) for vi in range(mesh.mesh.NumVertices()): if ((not bymorph) and screen[vi]) or (bymorph): vert = points[vi] d1 = m1.MorphTargetDelta(vi) d1 = Vector() + d1 dv = vert + d1 proceed = 1 if bymorph: d2 = m2.MorphTargetDelta(vi) d2 = Vector() + d2 if d2: # Trim by morph if subtraction morph delta is not zero proceed = 0 if proceed: for i in range(3): if coords[i]: mesh.points[vi][i] = dv[i] def makeOffset(mesh,offset,coords,invert=0,refresh=1,useall=0,offlist=[],testing=0): """ Create a normals inflate offset from the points """ if invert == 1: # Invert normal direction offset = -offset if refresh == 0: # Use normals initialized at start norms = mesh.vnorms 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 ((not useall) and (points[vi] - mesh.verts[vi])) or (useall): # Restrict offset to only those verts which have been moved if mesh.screen[vi]: if offlist: offset = offlist[vi] if invert: offset = -offset v = points[vi] no = norms[vi] if mesh.psweld: # Pseudo-weld handling for matched-location verts on split edges if mesh.psweld[vi]: m = mesh.psweld[vi] ml = len(m) + 1 # Number of match verts plus current vert for mvi in m: no += norms[mvi] no = no.Scale(1.0/ml) p = v.Project(no,offset) if testing: testing_boxes(v,0.0005,"_NORM_%s" %(vi),0,1) testing_boxes(p,0.0005,"_NORM_%s" %(vi),0,1) for i in range(3): if coords[i]: mesh.points[vi][i] = p[i] def screen_welds(mesh,theact,testing=0,nomorphs=0,setmorph=""): """ Here, we loop through children of the selected body part actor and pull up a Mesh instance for each one which has geometry, and weldgoals with the selected actor. We find the polycenter averages for the weldgoal vertices and put them into the mesh.awelds list, to be used by tighten_smooth() for weld-neighbor edge preservation. After the children, we handle the selected actor's parent. This will try to get worldspace vert positions for weld neighbors. Generally, this function should give good results when smoothing by Base Geometry. It should work well with Current Shape if the welded actors are shaped compatibly at script runtime. Smoothing a morph, however, will only return decent results if A) welded actors also have a compatible morph of the same name which can be set to get good edges; B) the current shape of the welded actor is compatible with the shape of the smoothing actor when only the smoothing morph is set. Script will try to set morph of the same name in the welded actor, but will use the current shape if no morph is found. """ if (not theact.IsBodyPart()) or (not theact.Geometry()): return app.status_update("Getting actor weld data....") mesh.awelds = [[] for i in range(mesh.mesh.NumVertices())] actname = theact.Name() parent = theact.Parent() acts = theact.Children() for act in acts: if act.Geometry(): wgs = [a.Name() for a in act.WeldGoalActors()] for wgi in range(len(wgs)): if wgs[wgi] == actname: nomorphs_a = nomorphs setmorph_a = act_has_morph(act,setmorph) # Check whether weld actor has matching morph if setmorph_a != setmorph: # If no morph was found, don't zero out morphs (use current shape) nomorphs_a = 0 wmesh = myMesh(act.Geometry(),"poly",act) points_no_transforms(wmesh,act,setpoints=0,setverts=1,nomorphs=nomorphs_a,nomags=nomorphs_a,setmorph=setmorph_a) wmesh.ngons = wmesh.mesh.GetNgons() wmesh.vpolys = wmesh.mesh.GetVertNgons() #if wmesh.vpolys.count([]): # Stray vert crash protection #wmesh.zero_len_vpolys() polycenters(wmesh,wmesh.verts) for wvi in range(len(act.WeldGoals()[wgi])): if act.WeldGoals()[wgi][wvi] != -1: mesh.awelds[act.WeldGoals()[wgi][wvi]] = [wmesh.pcenters[wvi].Clone()] if testing: testing_boxes(wmesh.verts[wvi],0.0005,"_ACT_CHILD_",0,1) testing_boxes(mesh.verts[act.WeldGoals()[wgi][wvi]],0.0005,"_CHILD_",0,1) testing_boxes(wmesh.pcenters[wvi],0.0005,"_CHILD_CENTERS_",0,1) del wmesh if parent.Geometry(): wgs = [a.Name() for a in theact.WeldGoalActors()] for wgi in range(len(wgs)): if wgs[wgi] == parent.Name(): nomorphs_a = nomorphs setmorph_a = act_has_morph(parent,setmorph) if setmorph_a != setmorph: nomorphs_a = 0 wmesh = myMesh(parent.Geometry(),"poly",parent) points_no_transforms(wmesh,parent,setpoints=0,setverts=1,nomorphs=nomorphs_a,nomags=nomorphs_a,setmorph=setmorph_a) wmesh.ngons = wmesh.mesh.GetNgons() wmesh.vpolys = wmesh.mesh.GetVertNgons() #if wmesh.vpolys.count([]): # Stray vert crash protection #wmesh.zero_len_vpolys() polycenters(wmesh,wmesh.verts) for wvi in range(len(theact.WeldGoals()[wgi])): if theact.WeldGoals()[wgi][wvi] != -1: mesh.awelds[wvi] = [wmesh.pcenters[theact.WeldGoals()[wgi][wvi]].Clone()] if testing: testing_boxes(mesh.verts[wvi],0.0005,"_ACT_PARENT_",0,1) testing_boxes(wmesh.verts[theact.WeldGoals()[wgi][wvi]],0.0005,"_PARENT_",0,1) testing_boxes(wmesh.pcenters[theact.WeldGoals()[wgi][wvi]],0.0005,"_PARENT_CENTERS_",0,1) del wmesh if mesh.awelds.count([]) == mesh.mesh.NumVertices(): # Clear awelds if we're blank mesh.awelds = [] def act_has_morph(act,morphname): """ Check to see if an actor has a morph of a given name. Used by screen_welds(). """ if not morphname: return morphname for parm in act.Parameters(): if parm.Name() == morphname: return morphname return "" #=============================================================================================== # class myMesh() #=============================================================================================== """ Guide to the Very Big Arrays carried around by a myMesh instance: These are always present in a script run: theMesh.points: List of vertex positions which will be altered by all functions to generate final morph theMesh.screen: Screening list to exempt some points vertices from changes Created for any runtype == 0 (Smooth) run: theMesh.pcenters: Pre-compiled averages of ngon centers, for use by tighten_smooth() The pcenters list can also contain just the actual centers, rather than the averages, but this is not currently in use. Automatically created for body part actors, otherwise always present as a blank: theMesh.awelds: Lists averaged ngon centers for verts which have actor weld goals, used by tighten_smooth() Created when pseudoWeld is set, otherwise present as a blank: theMesh.psweld: Lists pseudo-neighbors of verts for pseudo-weld handling in smooth, inflate, and volume preservation Note that extend and psweld represent mutually-exclusive cases. Use of psweld will be given precedence. Created only when volume variable is set: theMesh.startDists: Initial distances of points from theMesh.center theMesh.pgons: Old TDMT Classic pverts-style pgon list for type 2 refresh in makeOffset Other VBAs are instantiated in myMesh as common use pyd list types (see the myMesh class, below), or are restricted to local use by functions. A myMesh may have some other variables associated with it: Created only when volume variable is set: theMesh.center: Center point for the actor A myMesh also always stores a reference to its actor and actor geometry. """ class myMesh(object): """Initialize the mesh data""" def __init__(self,geom,meshtype,act): if meshtype == "vert": self.mesh = Mesh(geom) # pyd Mesh instance embedded self.act = act self.geom = geom self.safe_mesh = 1 # Set to zero if mesh has stray vertices if meshtype == "poly": self.mesh = Mesh(geom) def get_myMesh_lists(self,baseverts=1,edges=1,neighbors=1,ngons=1,verts=1,vpolys=1): """ A bit of control for instantiation of common use myMesh lists """ if baseverts: self.baseverts = self.mesh.GetVertices() if edges: self.edges = self.mesh.GetNgonEdgeList() if neighbors: self.neighbors = self.mesh.GetVertNeighborsByNgons() if ngons: self.ngons = self.mesh.GetNgons() if verts: self.verts = self.mesh.GetWorldVertices() if vpolys: self.vpolys = self.mesh.GetVertNgons() self.safe_vpolys = [] def get_myMesh_vnorms(self): """ If a mesh contains vertices without any polygon, the _tdmt.pyd can crash when vertex normals are calculated using mesh.GetWorldNormals(). Vertex normals are derived by averaging the normals of all the polys of the vert, so a vert with zero polys can cause a float division error (unreported by Python - P7 just crashes) when normals are calculated. This checks for that case and uses an alternate approach, if necessary. It also creates fake entries in the myMesh vpolys list, to avoid divide by zero errors later in the script. The pyd will use its internal version of vpolys with mesh.GetWorldNormals(), so we need to be sure to avoid calling it in a case where stray verts have been found. A mesh with stray verts can be easily created by using Geometry().Weld() on a geometry which has split (weld-able) edges. Stray verts can be automatically removed by importing as .obj into Poser, with weld identical verts selected, or by importing and exporting using Wings3D. The following is the old pyd approach for gathering vertex normals. It looks like it may suffer from the same issues, but in this case we can control the submitted data in vpolys and fill in fake entries to avoid divide-by-zero. (See zero_len_vpolys(), below.) pgons = psrPolygonList(theAct.Geometry()) pnorms = PolyFaceNormals(pgons,mesh.points) norms = PolyVertexNormals(pnorms,mesh.vpolys) **NOTE TO SPANKI: mesh.GetWorldNormals() needs divide-by-zero crash protections! HAALLLP!** """ if self.vpolys.count([]): self.safe_mesh = 0 pname = "None" if self.act.ItsFigure(): pname = self.act.ItsFigure().Name() app.status_update("Getting alternate normals - geometry for < %s, %s > has %s stray verts." %(self.act.Name(),pname,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 polyneighbors(self): """ neighbor polys of polys Adapted from TDMT Classic. Used by hardedges() """ pgons = self.mesh.NumNgons() self.pneighbors = [[] for i in range(pgons)] for pi in range(pgons): for e1,e2 in self.edges[pi]: for p in self.vpolys[e1]: if p != pi: for edge in range(len(self.edges[p])): e3,e4 = self.edges[p][edge] if (e1,e2) == (e4,e3): self.pneighbors[pi].append(p) def hardedges(self,angle,lessgreater,testing=0): """ Find neighbor polys Find edges which fit submitted parameters for "hardness" Requires pneighbors, pnorms, edges, (verts for testing). """ RAD_TO_DEG = 180.0/3.14159265359 if angle > 360: angle = angle % 360 screenvs = [len(i) for i in self.vpolys] # Decrement the count to identify verts of hard edges self.polyneighbors() # Get the poly neighbors for pi in range(len(self.pneighbors)): for ni in range(len(self.pneighbors[pi])): nb = self.pneighbors[pi][ni] vn1 = self.ngons[pi].normal # Angle between vectors vn2 = self.ngons[nb].normal s = vn1.Length() * vn2.Length() f = vn1.Dot(vn2) / s if f >= 1.0: na = 0.0 elif f <= -1.0: na = math.pi / 2.0 else: na = Numeric.arctan(-f / Numeric.sqrt(1.0 - f * f)) + math.pi / 2.0 na *= RAD_TO_DEG # Convert from radians to degrees if na < 90.0 or na > 90.0: na = 180-na # flip non-perpendicular angles edge = self.edges[pi][ni] if (lessgreater and (na < angle)) or ((not lessgreater) and (na > angle)): # Not hard for ei in range(2): screenvs[edge[ei]] -= 1 if (lessgreater and (na >= angle)) or ((not lessgreater) and (na <= angle)): # Hard if testing: v1 = self.verts[self.edges[pi][ni][0]] v2 = self.verts[self.edges[pi][ni][1]] c = v1 + v2 c = c.Scale(1.0/2) testing_boxes(c,0.0005,"_HARD_",0,1) for poly in self.edges: for edge in poly: if screenvs[edge[0]] and screenvs[edge[1]]: # If both verts of an edge are still 1 after being decremented for vi in edge: self.screen[vi] = 0 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.mesh.NumNgons() verts = self.mesh.NumVertices() 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]]) #================================================================================================= # --- Vector math not included in _tdmt.pyd (Not used) --- #================================================================================================= def vector_angle(v1, v2, r2d=0): """ Originally derived from blender2cal3d.py. Altered for use of Numeric and _tdmt.pyd. Returns radians. if r2d is sent as 1, will return degrees. """ RAD_TO_DEG = 180.0/3.14159265359 s = v1.Length() * v2.Length() f = v1.Dot(v2) / s if f >= 1.0: return 0.0 if f <= -1.0: return math.pi / 2.0 if r2d: return (Numeric.arctan(-f / Numeric.sqrt(1.0 - f * f)) + math.pi / 2.0) * RAD_TO_DEG return Numeric.arctan(-f / Numeric.sqrt(1.0 - f * f)) + math.pi / 2.0 def vector_intersect(p1,d1,p2,d2,tolerance=0.0001,mirror=0): """ Intersection of two lines Adapted from 3D Math Primer for Graphics and Game Development (book). www.gamemath.com p1,p2 = origins of two vectors to test d1,d2 = direction vector (normal, delta) or lines t1,t2 will be the points on the lines to test for intersection. If mirror is set, the returned point will be flipped over the center point of the line between the two submitted points. tolerance=1.19209290e-07 """ dcross = d1 % d2 psub = p2 - p1 if not dcross: # Lines are parallel (or coincident) return Vector() t1 = ((psub % d2) ^ dcross) / (dcross.Length())**2 t2 = ((psub % d1) ^ dcross) / (dcross.Length())**2 v1 = p1 + (t1 * d1) # points found by the above v2 = p2 + (t2 * d2) if mirror: # Flip the intersection point across the line between submitted points v3 = (p1 + d1) + (p2 + d2) # Center of line between to submitted points v3 = v3.Scale(1.0/2) v4 = (v3 - v1) + v3 if not (v1 - v2): # Exact match - return now to skip point distance calc if mirror: return v4 # Return the flipped version return v1 # Either one will do vd = v1.PointDistance(v2) if vd < tolerance: # If the two points are closer than the submitted tolerance if mirror: return v4 return v1 return Vector() # Else, return null vector #================================================================================================= # --- Morph-handling --- #================================================================================================= 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 def setShape(mesh,act,name,addDeltas=0,bake=0,setit=1,remove=1,pointslist=[]): # Set the morphs or bake the shape """Bake the mesh or create the morphs""" if pointslist: points = pointslist else: points = mesh.points if not bake: morphs = {} if addDeltas: deltas = [Vector() for i in mesh.verts] for parm in act.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(name,act,"") act.SpawnTarget(mtname) newMT = act.Parameter(mtname) if setit: newMT.SetValue(1.0) for parmname in morphs.keys(): parm = act.Parameter(parmname) parm.SetValue(morphs[parmname]) for vi in range(len(points)): if bake: vert = act.Geometry().Vertex(vi) vert.SetX(points[vi][0]) vert.SetY(points[vi][1]) vert.SetZ(points[vi][2]) else: if remove: vec = ((points[vi] - mesh.baseverts[vi]) - (mesh.verts[vi] - mesh.baseverts[vi])) # Calculate delta, remove world transforms and deformations else: vec = (points[vi] - mesh.baseverts[vi]) if addDeltas: # Optionally add morph settings back in vec += deltas[vi] if vec: newMT.SetMorphTargetDelta(vi,vec.x,vec.y,vec.z) if bake: act.MarkGeomChanged() scene.ProcessSomeEvents() scene.DrawAll() #================================================================================================= # --- Bounding boxes, testing boxes --- #================================================================================================= def in_box(bbox,verts): """Simple bounding box check""" inbox = [0 for i in range(len(verts))] 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]: inbox[vi] = 1 return inbox 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 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) def testing_boxes(point,sz,name,disp,merge): """ build the testing boxes """ b = [point[0]-sz, point[0]+sz, point[1]-sz, point[1]+sz, point[2]-sz, point[2]+sz] build_box(b,name,disp,merge) def build_box(b1,name,display,merge,vertarray=None): """ Create a box using the Poser geometry API b1 format is [minX,maxX,minY,maxY,minZ,maxZ] if merge == 1, prop will be merged with any pre-existing prop of the same name. """ addmesh = 0 if merge: props = [a for a in scene.Actors() if a.Name() == name and a.IsProp()] if len(props) >= 1: addmesh = 1 if addmesh: newbox = props[0] bbox = newbox.Geometry() else: bbox = poser.NewGeometry() if vertarray == None: Verts = Numeric.array([ [b1[0],b1[2],b1[4]], [b1[0],b1[3],b1[4]], [b1[0],b1[3],b1[5]], [b1[0],b1[2],b1[5]],[b1[1],b1[2],b1[4]],[b1[1],b1[3],b1[4]], [b1[1],b1[3],b1[5]],[b1[1],b1[2],b1[5]] ], Numeric.Float) else: # Use vertarray Verts = vertarray Polys = Numeric.array([[0,4],[4,4],[8,4],[12,4],[16,4],[20,4]],Numeric.Int) Sets = Numeric.array([3,2,1,0,4,5,6,7,5,4,0,1,6,5,1,2,7,6,2,3,4,7,3,0],Numeric.Int) bbox.AddGeneralMesh(Polys,Sets,Verts) if addmesh: newbox.SetGeometry(bbox) newbox.MarkGeomChanged() else: newbox = scene.CreatePropFromGeom(bbox,name) if display == 1: newbox.SetDisplayStyle(poser.kDisplayCodeEDGESONLY) if display == 2: newbox.SetDisplayStyle(poser.kDisplayCodeWIREFRAME) scene.DrawAll() #================================================================================================= # --- GUI --- #================================================================================================= class App: def __init__(self, master): self.master = master master.title("Smoother_pyd 4a") self.xVar = IntVar() self.xVar.set(1) self.yVar = IntVar() self.yVar.set(1) self.zVar = IntVar() self.zVar.set(1) self.lessgreaterVar = IntVar() self.lessgreaterVar.set(0) self.safeVar = IntVar() self.safeVar.set(0) self.omitVar = IntVar() self.omitVar.set(0) self.invertVar = IntVar() self.invertVar.set(0) self.invedgeVar = IntVar() self.invedgeVar.set(0) self.useVolumeVar = IntVar() self.useVolumeVar.set(1) self.pseudoVar = IntVar() self.pseudoVar.set(0) self.extVar = IntVar() self.extVar.set(0) self.aweldVar = IntVar() self.aweldVar.set(1) self.figS = None # Selected figure (or None) self.actS = None # Selected actor self.morphs = [] # Track order in which morphs are selected; for subtract 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.morFrame = Frame(self.masterFrame,borderwidth=2,relief=RIDGE) self.morFrame.grid(row=1,column=1) self.morbuttonFrame = Frame(self.morFrame,borderwidth=2) self.morbuttonFrame.grid(row=3,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.buttonFrame = Frame(self.mixFrame,borderwidth=2) self.buttonFrame.grid(row=2,column=0) self.checkFrame = Frame(self.mixFrame,borderwidth=2) self.checkFrame.grid(row=1,column=0) self.extrasFrame = Frame(self.mixFrame,borderwidth=2,relief=RIDGE) self.extrasFrame.grid(row=3,column=0) self.thrFrame = Frame(self.extrasFrame,borderwidth=2) self.thrFrame.grid(row=3,column=0) self.angleFrame = Frame(self.extrasFrame,borderwidth=2) self.angleFrame.grid(row=4,column=0) self.quitFrame = Frame(self.mixFrame,borderwidth=3) self.quitFrame.grid(row=5,column=0) # --- Listboxes --- self.listLabel = Label(self.ListFrame, text="Select Actor" , anchor=N, justify=LEFT) self.listLabel.grid(row=1, column=1) self.ListScroll = Scrollbar(self.ListFrame, orient=VERTICAL) # Actor self.ListScroll.grid( row=2, column=0,sticky=N+S+E) self.List = Listbox(self.ListFrame, height=24, width=25, selectmode=SINGLE,exportselection=0, yscrollcommand=self.ListScroll.set) self.List.grid( row=2, column=1) self.ListScroll["command"] = self.List.yview self.listLabel3 = Label(self.morFrame, text="Morph(s)" , anchor=N, justify=LEFT) self.listLabel3.grid(row=1, column=1) self.ListScroll3 = Scrollbar(self.morFrame, orient=VERTICAL) # Morphs self.ListScroll3.grid( row=2, column=0,sticky=N+S+E) self.List3 = Listbox(self.morFrame, height=22, width=25, selectmode=MULTIPLE,exportselection=0, yscrollcommand=self.ListScroll3.set) self.List3.grid( row=2, column=1) self.ListScroll3["command"] = self.List3.yview self.List4 = Listbox(self.displayFrame, height=5, width=106, selectmode=SINGLE,exportselection=0) # Status bar self.List4.grid( row=3, column=0) 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=11, 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="Screen Groups" , 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=11, width=25, selectmode=MULTIPLE,exportselection=0, yscrollcommand=self.matScroll2.set) self.matL2.grid( row=4, column=1) self.matScroll2["command"] = self.matL2.yview self.listLabel5 = Label(self.normFrame, text="Selection type" , anchor=N, justify=LEFT) self.listLabel5.grid(row=4, column=0) self.List5 = Listbox(self.normFrame, height=3, width=14, selectmode=SINGLE,exportselection=0) self.List5.grid( row=5, column=0) self.listLabel7 = Label(self.normFrame, text="What to Smooth" , anchor=N, justify=LEFT) self.listLabel7.grid(row=2, column=0) self.List7 = Listbox(self.normFrame, height=3, width=14, selectmode=SINGLE,exportselection=0) self.List7.grid( row=3, column=0) self.listLabel8 = Label(self.normFrame, text="Run Type" , anchor=N, justify=LEFT) self.listLabel8.grid(row=0, column=0) self.List8 = Listbox(self.normFrame, height=4, width=14, selectmode=SINGLE,exportselection=0) self.List8.grid( row=1, column=0) # --- Menu bar --- self.bar = Menu(self.master) self.optm = Menu(self.bar) self.optm.add_checkbutton(label="Cheapie Volume Preservation",variable=self.useVolumeVar) self.optm.add_checkbutton(label="Omit Zero Deltas",variable=self.omitVar) self.optm.add_checkbutton(label="Use X",variable=self.xVar) self.optm.add_checkbutton(label="Use Y",variable=self.yVar) self.optm.add_checkbutton(label="Use Z",variable=self.zVar) self.optm.add_checkbutton(label="Invert Mat/Group Screening",variable=self.invertVar) self.optm.add_checkbutton(label="Invert Screened Edges",variable=self.invedgeVar) self.edgm = Menu(self.bar) self.edgm.add_checkbutton(label="Omit Split Edges",variable=self.safeVar) self.edgm.add_checkbutton(label="Pseudo-Weld Split Edges",variable=self.pseudoVar) self.edgm.add_checkbutton(label="Extend Split Edges",variable=self.extVar) self.edgm.add_checkbutton(label="Handle Actor Welds",variable=self.aweldVar) self.bar.add_cascade(label="Options",menu=self.optm) self.bar.add_cascade(label="Edges",menu=self.edgm) self.master.config(menu=self.bar) # --- Radio Buttons --- self.radioInj = Radiobutton(self.angleFrame, text="<", variable=self.lessgreaterVar, value=0) self.radioInj.grid(row=1, column=0) self.radioRem = Radiobutton(self.angleFrame, text=">", variable=self.lessgreaterVar, value=1) self.radioRem.grid(row=1, column=1) # --- Checkbuttons --- #self.scr2Check = Checkbutton(self.checkFrame,text="X", variable=self.xVar) #self.scr2Check.grid(row = 3, column = 0) # --- Entries --- self.smcLabel = Label(self.buttonFrame, text="Smooth Cycles:" , anchor=N, justify=LEFT) self.smcLabel.grid(row=0, column=0) self.smcEntry = Entry(self.buttonFrame, width=3) self.smcEntry.insert(0,"1") self.smcEntry.grid(row=0,column=1) self.sewLabel = Label(self.buttonFrame, text="Split Edge Weight:" , anchor=N, justify=LEFT) self.sewLabel.grid(row=1, column=0) self.sewEntry = Entry(self.buttonFrame, width=3) self.sewEntry.insert(0,"1.0") self.sewEntry.grid(row=1,column=1) self.thrLabel = Label(self.thrFrame, text="Long Delta:" , anchor=N, justify=LEFT) self.thrLabel.grid(row=1, column=0) self.thrEntry = Entry(self.thrFrame, width=5) self.thrEntry.insert(0,"0.001") self.thrEntry.grid(row=1,column=1) self.angLabel = Label(self.angleFrame, text="Angle:" , anchor=N, justify=LEFT) self.angLabel.grid(row=0, column=0) self.angEntry = Entry(self.angleFrame, width=5) self.angEntry.insert(0,"90.0") self.angEntry.grid(row=0,column=1) # --- 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) self.buttonAll = Button(self.morbuttonFrame, text="All", command=self.allmorphs) self.buttonAll.grid(row=0, column=0) self.buttonNone = Button(self.morbuttonFrame, text="None", command=self.nomorphs) self.buttonNone.grid(row=0, column=1) # ---- Mouse-button binding ---- self.List.bind('',lambda e, s=self: s.handleTarget(e.y)) self.List3.bind('',lambda e, s=self: s.handleMorphs(e.y)) self.fill_boxes(self.List) self.fill_typeboxes() self.status_update("Ready") def status_update(self,line): self.List4.insert(0,line) self.List4.update() def handleTarget(self,event): self.figS = None self.matL.delete(0,END) self.matL2.delete(0,END) self.List3.delete(0,END) self.morphs = [] 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) mats = self.actS.Geometry().Materials() for mat in mats: self.matL.insert(END,mat.Name()) self.get_groups(self.actS) self.fill_morphbox(self.actS,name,fig) def handleMorphs(self,event): """ We need to gather two morphs in the order selected, for the subtract fxn. List.curselection() returns items in list order. To get the selection order, we have to maintain a list. To prevent selection looping or other errors with the list, we need to send to this fxn with the ButtonRelease event, not the Button event. """ clicked = self.List3.nearest(event) name = self.List3.get(clicked) if name in self.morphs: self.morphs.remove(name) elif (not name in self.morphs): self.morphs.append(name) #self.status_update("%s" %(self.morphs)) def fill_morphbox(self,act,name,fig): for parm in act.Parameters(): if parm.IsMorphTarget(): self.List3.insert(END,parm.Name()) def allmorphs(self): self.List3.selection_set(0,END) def nomorphs(self): self.List3.selection_clear(0,END) def get_groups(self,act): for group in act.Geometry().Groups(): self.matL2.insert(END,group) def fill_typeboxes(self): """Fills the various listboxes at script startup.""" types = ["All","Long Deltas","Angle", "Use Morph","Base Geometry","Current Shape", "Smooth","Trim Morph","Inflate","Subtract Morphs"] for n,t in enumerate(types): if n<3: self.List5.insert(END,t) if n>=3 and n<6: self.List7.insert(END,t) if n>=6: self.List8.insert(END,t) self.List5.selection_set(0,0) self.List7.selection_set(0,0) self.List8.selection_set(0,0) 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): """ types = ["All","Longdeltas","Angle", "Use Morph","Base Geometry","Current Shape", "Smooth","Trim Morph","Inflate","Subtract Morphs"] """ if not self.actS: # No target actor selected self.status_update("No Actor selected.") return runtype = int(self.List8.curselection()[0]) smSel = int(self.List5.curselection()[0]) smWhat = int(self.List7.curselection()[0]) if runtype != 2: L = len(self.List3.curselection()) if runtype == 0 and smWhat == 0: if not L: self.status_update("No morph selected.") return if runtype > 0: if not L: self.status_update("No morph selected.") return if runtype == 3 and L < 2: self.status_update("Subtract function requires selection of two morphs.") return StartTime = time.time() if self.figS: fig = self.figS.Name() else: fig = "None" actS = "%s <%s>" %(self.actS.Name(),fig) invert = self.invertVar.get() # Zero excludes screen selections, one includes mats = [] if self.matL.curselection(): mats = [self.matL.get(int(i)) for i in self.matL.curselection()] if not invert: mats = [i for i in self.matL.get(0,END) if i not in mats] groups = [] if self.matL2.curselection(): groups = [self.matL2.get(int(i)) for i in self.matL2.curselection()] if not invert: groups = [i for i in self.matL2.get(0,END) if i not in groups] morph = [] if runtype != 2 and (not (runtype == 0 and smWhat > 0)): morph = [self.morphs[0]] if runtype == 3: morph.append(self.morphs[1]) coords = [self.xVar.get(),self.yVar.get(),self.zVar.get()] safeEdge = self.safeVar.get() omit = self.omitVar.get() lessgreater = self.lessgreaterVar.get() useVolume = self.useVolumeVar.get() pseudoWeld = self.pseudoVar.get() extEdge = self.extVar.get() aweld = self.aweldVar.get() invedge = self.invedgeVar.get() # Mat/group edge invert. #Invert inverts selection list entries. Invedge changes selection conditions in screen functions. #invert=0,invedge=0: selection excluded, edges inclusive - default (Lists unselected and screens list inclusively) #invert=0,invedge=1: selection excluded, edges exclusive (Lists unselected and screens list exclusively) #invert=1,invedge=0: selection included, edges exclusive (Lists selected and screens list inclusively) #invert=1,invedge=1: selection included, edges inclusive (Lists selected and screens list exclusively) try: reps = int(self.smcEntry.get()) except ValueError: reps = 1 try: angle = float(self.angEntry.get()) except ValueError: angle = 90.0 try: threshold = float(self.thrEntry.get()) except ValueError: threshold = 0.001 try: weight = float(self.sewEntry.get()) except ValueError: weight = 1.0 run(actS,runtype,smSel,smWhat,mats,groups,safeEdge,morph,coords,reps,\ omit,angle,lessgreater,threshold,invedge,useVolume,pseudoWeld,extEdge,weight,aweld) #EndTime = (time.time() - StartTime)/60 EndTime = ((((time.time() - StartTime)/60)/10)*6)*60 #self.status_update("Done in %s minutes." %(EndTime)) self.status_update("Done in %s seconds." %(EndTime)) scene.ProcessSomeEvents() self.master.lift() app = App(root) root.mainloop()