import poser import Numeric from _tdmt import * scene = poser.Scene() box_info = [] #Will contain [box name, act name, act.ItsFigure().Name() (or "None")] """ Make_Sphere.py This is a novelty script which implements Spanki's _tdmt.pyd (Poser 7 (+?), Windows only) to create a morph on select actor(s) which makes a sphere. The script will create a bounding box which should be positioned to contain the actor(s) which you want to shape. It will work only on the current actor if that actor is a prop. If the current actor is a body part, the script will work on all visible actors of the current figure which have vertices within the bounding box. Script by Cage (Cagedrei@aol.com) Use as desired. No restrictions. The whole GNU Public License thing applies. Spanki's website, where the _tdmt.pyd can be found: http://skinprops.com Use: - Run the script. - The bounding box is selected when created. Scale and position it as desired. - The bounding box contains a much smaller box which indicates the position of the box center. - Set the RADIUS dial to the desired radius (in Poser Native Units) for the resulting sphere shape. - By default, the center of the sphere will be the center of the bounding box. To use the center of all of the vertices that fall within the bounding box as the sphere center, set "Use Box Center" to 0.0. (NOTE: When using the box center, if the box center falls outside of the surface of the selected mesh area, the resulting morph will be distorted, with those vertices which were on the wrong side of the box center bulging inward, not outward. I think the solution to this must involve a dot product, but I haven't worked it out yet.) - Set "Use Auto-Radius" to >= 1.0 to have the script automatically use as the radius value the longest distance from the chosen center to the center of one face of the bounding box. Set this to < 1.0 to use the radius value entered using the RADIUS dial. - Set "Prevent Poke-In" to >= 1.0 to prevent the script from morphing vertices which would poke back into the actor surface when morphed. Set this to < 1.0 to enable poke-in. - Set "Smooth Morph Edges" to >= 1.0 to have the script smooth the sphere morph along edges where neighboring vertices were not included in the bounding box. Set this to < 1.0 to disable such smoothing. - If running on a body part, hide any other actors on the figure which fall within the bounding box, if you don't want to affect them. - If running on a body part, best results will be obtained if the figure has IK deactivated and has been zeroed. The script should compensate for any scaling or translation, but rotations may distort the results. This is true for props as well as body parts. Any rotation settings will be "baked" into the morphed vert locations (which can make for interesting results, if that's what you want). - Move the RUN SCRIPT dial to run. Any non-zero dial value will run the script. - Move the END SCRIPT dial to end the script. Any non-zero dial value will end the script. - The script bounding box will be deleted from the scene when RUN or END is activated. Copy the box before running the script, to be able to recreate settings in a subsequent run. The box can be mirrored across the X axis between body parts using the same methods applied for mirroring magnets. - The new sphere morphs will be set to 1.0 when created. - The script uses a scene callback. If the box is deleted or renamed, it will try to compensate, but it's probably best not to temp fate, as Cage is fairly new to callback use. Avoid deleting the script bounding box. Use the END dial if you want to quit the script, to be absolutely certain of cancelling the callback. Given that a scene callback is used, be aware that this script will override any pre-existing scene callbacks in use. """ def run(): global box_info act = scene.CurrentActor() boxname = unique_actname("**") box, boxlist, center = sphere_box(boxname,act) scene.SelectActor(box) fig = "None" if act.IsBodyPart(): fig = act.ItsFigure().Name() box_info = [boxname,act.Name(),fig] set_up_callback(box,act,center) def get_radius_full(box,center): """Get the default radius setting (distance between box center and center of one side).""" bverts = psrWorldVertexList(box.Geometry()) pverts = psrNgonList(box.Geometry()) radii = [] for pi in range(6): # Only check the first six ngons added to the cube (omits center display box). pvs = pverts[pi].vertIndices centerp = Vector() for vi in pvs: centerp += bverts[vi] centerp = centerp.Scale(1.0/len(pvs)) radii.append(center.PointDistance(centerp)) return max(radii) def sphere_box(boxname,act): """Make the bounding box""" names = [p.Name() for p in scene.Actors() if p.Name() == boxname] if names: return scene.Actor(boxname) averts = psrWorldVertexList(act.Geometry()) boxlist = b_box_ock(averts) boxlist = increase_box(boxlist,0.005) center = box_center(boxlist) box = build_box(boxlist,boxname,2,0) # Use has_center=0 for a cleaner box with no visible center testing_boxes(center,0.001,boxname,2,1) # Add a center to the box for display. return box, boxlist, center def set_up_callback(box,act,center): """ Set up the scene callback for the box. P7 PPy's parameter.ApplyLimits() doesn't seem to work. Otherwise, Min/Max would be set for the parameters which use integer values. Lacking this, we treat any value which is >= 1.0 as 1 and any lesser value as 0 for such dials. """ hide_rotparms(box) endst = "END SCRIPT" runst = "RUN SCRIPT" radst = "RADIUS:" ubc = "Use Box Center" uar = "Use Auto-Radius" npk = "Prevent Poke-In" usm = "Smooth Morph Edges" box.CreateValueParameter(endst) box.CreateValueParameter(runst) box.CreateValueParameter(radst) box.CreateValueParameter(ubc) box.CreateValueParameter(uar) box.CreateValueParameter(npk) box.CreateValueParameter(usm) runparm = box.Parameter(runst) endparm = box.Parameter(endst) radparm = box.Parameter(radst) ubcparm = box.Parameter(ubc) uarparm = box.Parameter(uar) npkparm = box.Parameter(npk) usmparm = box.Parameter(usm) runparm.SetValue(0.0) endparm.SetValue(0.0) ubcparm.SetValue(1.0) npkparm.SetValue(1.0) radparm.SetSensitivity(0.001) runparm.SetSensitivity(1.0) endparm.SetSensitivity(1.0) ubcparm.SetSensitivity(1.0) uarparm.SetSensitivity(1.0) npkparm.SetSensitivity(1.0) usmparm.SetSensitivity(1.0) radius = get_radius_full(box,center) radparm.SetValue(radius) scene.SetEventCallback(eventCB) def hide_rotparms(box): """Bounding box should be axially-aligned; hide the rotation dials.""" for parm in box.Parameters(): if parm.TypeCode() == poser.kParmCodeXROT or parm.TypeCode() == poser.kParmCodeYROT or\ parm.TypeCode() == poser.kParmCodeZROT: parm.SetHidden(1) def eventCB(scn,etype): """Scene event callback function.""" global box_info if( (etype & poser.kEventCodePARMCHANGED) != 0): box = scene.CurrentActor() name,act,fig = box_info if box.Name() == name: runparm = box.Parameter("RUN SCRIPT") if runparm.Value(): scene.ClearEventCallback() usebox = box.Parameter("Use Box Center").Value() if usebox >= 1.0: usebox = 1 else: usebox = 0 usealt = box.Parameter("Use Auto-Radius").Value() if usealt >= 1.0: usealt = 1 else: usealt = 0 smooth = box.Parameter("Smooth Morph Edges").Value() if smooth >= 1.0: smooth = 1 else: smooth = 0 nopoke = box.Parameter("Prevent Poke-In").Value() if nopoke >= 1.0: nopoke = 1 else: nopoke = 0 radius = abs(box.Parameter("RADIUS:").Value()) if radius == 0.0: radius = 0.000001 sphere_morph(box,act,fig,radius,usebox,usealt,nopoke,smooth) # Run it. scene.DeleteCurrentProp() if fig == "None": scene.SelectActor(scene.Actor(act)) else: scene.SelectActor(scene.Figure(fig).Actor(act)) return endparm = box.Parameter("END SCRIPT") if endparm.Value(): scene.ClearEventCallback() scene.DeleteCurrentProp() if fig == "None": scene.SelectActor(scene.Actor(act)) else: scene.SelectActor(scene.Figure(fig).Actor(act)) return if ( (etype & poser.kEventCodeACTORDELETED) != 0): name,act,fig = box_info names = [p.Name() for p in scene.Actors() if (p.Name() == name or p.Name() == act)] if (not name in names) or (not act in names): # The box (or actor) was deleted scene.ClearEventCallback() if name in names: scene.SelectActor(scene.Actor(name)) scene.DeleteCurrentProp() if fig == "None": scene.SelectActor(scene.Actor(act)) else: scene.SelectActor(scene.Figure(fig).Actor(act)) return if( (etype & poser.kEventCodeITEMRENAMED) != 0): name,act,fig = box_info names = [p.Name() for p in scene.Actors() if p.Name() == name] if not names: # The box was renamed box_info[0] = scene.CurrentActor().Name() # Assume the callback is prompt and no new actor could be selected before we get here. return def unique_actname(name): """Get a unique actor name before creating the prop""" names = [a.Name() for a in scene.Actors()] if name in names: temp = 1 while "%s_%i" %(name,temp) in names: temp += 1 name = "%s_%i" %(name,temp) return name def sphere_morph(box,act,fig,radius,usebox,usealt,nopoke,smooth,testing=0): """Create the sphere morph on desired actor(s)""" acts = [scene.Actor(act)] if fig != "None": acts = [a for a in scene.Figure(fig).Actors() if a.OnOff() and a.Geometry()] verts = psrWorldVertexList(box.Geometry()) region = b_box_ock(verts) boxcenter = box_center(region) inside = [1 for i in acts] center = Vector() screens = [] for ai in range(len(acts)): # Do actor and vertex screening for the bounds, compile vert center verts = psrWorldVertexList(acts[ai].Geometry()) inbox = in_box(region,verts) if (not inbox.count(1)): screens.append([]) inside[ai] = 0 else: screens.append([i for i in inbox]) acenter = Vector() for v in verts: acenter += v acenter = acenter.Scale(1.0/max(1.0,len(verts))) center += acenter acts = [acts[i] for i in range(len(acts)) if inside[i]] screens = [screens[i] for i in range(len(screens)) if inside[i]] if not acts: print "%s is not positioned to contain any verts of the selected actor(s)." %(box.Name()) print "Ending script." return center = center.Scale(1.0/len(acts)) # Average out the center vector which was constructed above if usebox: center = boxcenter # Use the box center if usealt: radius = get_radius_full(box,center) # Get the radius from the box morphs2set = [] for ai in range(len(acts)): act = acts[ai] mesh = myMesh(act.Geometry()) verts = mesh.mesh.GetWorldVertices() screen = screens[ai] if smooth: deltas = [Vector() for i in range(mesh.mesh.NumVertices())] parmsD = {} for parm in act.Parameters(): # Zero all morphs and deformers if parm.IsMorphTarget(): if parm.TypeCode() == poser.kParmCodeTARGET or parm.TypeCode() == poser.kParmCodeDEFORMERPROP: parmsD[parm.Name()] = parm.Value() parm.SetValue(0.0) MtName = unique_name(act.Name(),act,"_sphere") act.SpawnTarget(MtName) NewMTParm = act.Parameter(MtName) morphs2set.append(NewMTParm) for item in parmsD.items(): # Reset parameters act.Parameter(item[0]).SetValue(item[1]) #weight = 1.0 - (ISectDist divided by EdgeDist) # Spanki's formula for vi in range(mesh.mesh.NumVertices()): if screen[vi]: vert = verts[vi] norm = vert - center if (not nopoke) or (nopoke and (radius >= norm.Length())): # Poke-in prevention norm = ~norm norm = norm.Scale(radius) delta = vert + norm delta -= vert weight = 1.0 - (vert.PointDistance(center)/radius) # If box center is outside the mesh, this will behave oddly delta = delta.Scale(weight) if smooth: deltas[vi] = delta.Clone() else: if delta: NewMTParm.SetMorphTargetDelta(vi,delta[0],delta[1],delta[2]) if smooth: vnbs = mesh.mesh.GetVertNeighborsByNgons() matched_verts(mesh) good = [0 for i in range(mesh.mesh.NumVertices())] for vi in range(mesh.mesh.NumVertices()): # Isolate morph edges for smoothing if screen[vi]: delta = deltas[vi].Clone() nbs = vnbs[vi] nbs = [i for i in nbs if (not screen[i])] if nbs: nbs = vnbs[vi] if nbs: #testing_boxes(verts[vi],0.001,"_EDGE_",0,1) good[vi] = 1 for nbi in nbs: good[nbi] = 1 #if screen[nbi]: #testing_boxes(verts[nbi],0.001,"_EDGE_",0,1) #good[nbi] = 1 else: if delta: NewMTParm.SetMorphTargetDelta(vi,delta[0],delta[1],delta[2]) for vi in range(mesh.mesh.NumVertices()): # Smooth edges of morph and neighbors if good[vi]: delta = Vector() nbs = vnbs[vi] for nbi in nbs: delta += (verts[nbi] + deltas[nbi]) delta = delta.Scale(1.0/len(nbs)) delta -= verts[vi] if mesh.psweld: if mesh.psweld[vi]: deltas[vi] = delta.Clone() else: if delta: NewMTParm.SetMorphTargetDelta(vi,delta[0],delta[1],delta[2]) else: if delta: NewMTParm.SetMorphTargetDelta(vi,delta[0],delta[1],delta[2]) if mesh.psweld: # Split edge protection for vi in range(mesh.mesh.NumVertices()): if mesh.psweld[vi]: delta = deltas[vi] pswvs = mesh.psweld[vi] for pswv in pswvs: delta += deltas[pswv] delta = delta.Scale(1.0/(len(pswvs)+1)) pswvs.append(vi) for wvi in pswvs: mesh.psweld[wvi] = [] if delta: NewMTParm.SetMorphTargetDelta(wvi,delta[0],delta[1],delta[2]) del mesh for morph in morphs2set: morph.SetValue(1.0) #=============================================================================================== # class myMesh() #=============================================================================================== class myMesh(object): """Initialize the mesh data""" def __init__(self,geom): self.mesh = Mesh(geom) # pyd Mesh instance embedded 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. """ verts = mesh.mesh.GetVertices() # Use the baseverts vpolys = mesh.mesh.GetVertNgons() 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 = 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(vpolys[vi]): # Skip stray verts v = 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(vpolys[wvi]): # Skip stray verts mesh.psweld[vi].append(wvi) has_appended += 1 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 = [] #================================================================================================= # --- 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 #================================================================================================= # --- 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 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 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() return newbox run()