import poser, Numeric, math FLT_EPSILON = 1.19209290e-07 scene = poser.Scene() """ Mister Looper: a loop-making script - Script only operates on visible actors. To prevent actors from contributing verts to the loop path, hide them before running. - Position the "MR.LOOPER PLANE" prop to define the plane in which your loop will be created. - Reduce scale of plane as much as possible, for best results. - If no vertices are found, try moving the plane closer to some vertices on your actors. If only edges are intersected, the script won't run. - Move the "RUN SCRIPT" dial to run. Typing the value into the entry field won't activate the script. - Move the "END SCRIPT" dial to end. Typing the value into the entry field won't activate the script. - Any non-zero dial value will activate either RUN SCRIPT or END SCRIPT. The dial can be turned to positive or negative. - Change "Sections" setting to alter number of radial divisions of main loop. - Change "Slices" setting to alter number of divisions in each cross-section ring of loop. - Change "Thickness" setting to vary radius of cross-section rings. - Set "Apply smoothing" to > 0.0 to smooth the resulting loop, <= 0.0 to disable smoothing. Smoothing may shrink the loop slightly. - Set "Add 'Fatness' morph" to > 0.0 to create a loop with a morph which will inflate the loop along its normals. Any value <= 0.0 will disable this option. - Set "Mirror loop" to > 0.0 to create a second loop, mirrored across X, in addition to the first loop. Will not run if plane has no X translation or rotation on Y or Z axes. - Set "Stack loops" to create multiple loops in one script run. A positive value will create a stack along the plane's local Y axis (against the surface). A negative value will stack the loops radially, within the local X-Z plane for the prop (along the surface). A value of zero will deactivate stacking. - Set "Stack offset", if using a positive value for "Stack loops", to define the spacing between loops and the direction in which to stack the loops. A positive value will stack loops upward, a negative value downward. This value will be multiplied by the Thickness to determine the offset distance. The Thickness is the radius of a cross-sectional ring of the loop. The default value of 2.0 will place loops directly on top of or beneath one another without overlap. - This script uses a poser scene callback. If run together with another script which uses a scene callback, they will interfere, with one cancelling out the other. - The script callback can be ended at any time by deleting the plane prop, or by adjusting the "END SCRIPT" dial. Possible bugs/limitations: - Convex hull code may fail with some complex vertex selections, creating a tangle. Try re-running with the plane scale reduced, to avoid this. - Not a bug, but the UV's are currently not being welded. To do: - Weld UV's upon creation. - Add automatic positioning and scaling of plane at creation, based on currently selected actor? - Adaptive subdivision of loop divisions near corners? Needs to accommodate existing UV setup, likely to require smoothing. - Hull code may still be failing in some cases. - Add a 'taper' morph, to taper the upper half of loop along Y axis. Possibly useful for belts, if resulting loop is scaled on Y. - Add chain-building feature Updates: - Convex hull code allegedly fails because of multiple verts at same location. Added screening to avoid this. - Fixed UV mapping tiled by toroid ring. Seems correct now, but texverts are not being welded. - Fixed P5/6 bug. Earlier PPy versions require special handling when adding a new material to a freshly-created geometry. See function notes, below, for details. - Altered RUN SCRIPT and END SCRIPT parameter labels to advise user to use dials for input. - Set dial sensitivy of "Thickness" to 0.001, rather than 0.01 default. - Moved UV seam to innermost ring of loop, rather than top, where it ends up by default. - Fixed loop shape distortion when X, Y, or Z scaling is used for plane. (Only the convex hull is now processed in 2D, at world origin.) - Now setting loop origin, endpoint, and orientation to match those of plane. This enables proper scaling of the resulting loop. - Modified hit distance check when determining valid intersection points (now uses min of edge length, extend size for plane AABB). - Added option for "Fatness" morph, to inflate the loop. - Changed default setting for smoothing parameter dial to zero. - Rearranged parameter dials, placing smoothing and fatness options beneath slices/sections/thickness. - Added increased range checks if no actor vertices are found within plane AABB. (Process still needs to be refined; fails in some cases.) - Fixed indexing bug which could create snarled geometry when 360 cannot be divided evenly by ring variable, in circle() code. - Added "Mirror loop" option, to run a second pass with plane flipped across X if appropriate. Second pass will exclude new loop created by first pass. - Fixed ring angles, which were incorrect. Correct handling uses previous edge when calculating 2D normal, not next edge. - Changed actor selection and intersection validation processes. New process should accommodate long edge intersections and reduce possibility of inappropriate hits. - Modified Mirror feature to allow inclusion of first loop created in run, if dial is set to 2.0 or greater. - Added loop-stacking feature, to allow creation of a series of loops, stacked along plane's Y axis, in one script run. - Modified stacking to build stack within plane if a negative value is used for "Stack loops" - Old convex hull code was still returning tangled paths, so script now uses two different hull functions, one which returns the proper hull set, unsorted, and another which creates the final, sorted hull using the unsorted hull data. Script by Cagedrei. Free for any use. Alter or re-distribute as desired. Cage23drei@yahoo.com First release: 4e, 4/3/2010 Update: 5a, 4/4/2010 Update: 5b, 4/5/2010 Update: 5f, 4/6/2010 Update: 5g, 4/7/2010 Update: 5h, 4/8/2010 Update: 6e, 4/8/2010 Bug fix: 6f, 4/8/2010 - Fixed error with new stacking feature Update: 6g, 4/9/2010 Some code used here is taken from functions developed for TDMT, and may have been written or modified by Spanki. Credit is given, below, where this is so. There are several functions below which use borrowed code, found online. See function notes for credits and URL's, where available. """ def run(): global plane_name, plane name = "MR. LOOPER PLANE" plane = build_plane(name,display=2) plane_name = plane.InternalName() scene.SelectActor(plane) set_up_callback(plane) def set_up_callback(plane): """Set up the plane prop dials, for use with the callback function.""" endst = "END SCRIPT - Use dial!" runst = "RUN SCRIPT - Use dial!" thk = "Thickness" rng = "Slices" div = "Sections" mir = "Mirror loop" smo = "Apply smoothing" inf = "Add 'Fatness' morph" stl = "Stack loops" sto = "Stacking offset" plane.CreateValueParameter(endst) plane.CreateValueParameter(runst) plane.CreateValueParameter(smo) plane.CreateValueParameter(inf) plane.CreateValueParameter(sto) plane.CreateValueParameter(stl) plane.CreateValueParameter(mir) plane.CreateValueParameter(thk) plane.CreateValueParameter(rng) plane.CreateValueParameter(div) runparm = plane.Parameter(runst) endparm = plane.Parameter(endst) thkparm = plane.Parameter(thk) stlparm = plane.Parameter(stl) stoparm = plane.Parameter(sto) mirparm = plane.Parameter(mir) rngparm = plane.Parameter(rng) divparm = plane.Parameter(div) smoparm = plane.Parameter(smo) infparm = plane.Parameter(inf) runparm.SetValue(0.0) endparm.SetValue(0.0) thkparm.SetValue(0.004) mirparm.SetValue(0.0) rngparm.SetValue(10) divparm.SetValue(30) smoparm.SetValue(0.0) infparm.SetValue(0.0) stlparm.SetValue(0.0) stoparm.SetValue(2.0) runparm.SetSensitivity(1.0) endparm.SetSensitivity(1.0) rngparm.SetSensitivity(1.0) divparm.SetSensitivity(1.0) mirparm.SetSensitivity(1.0) smoparm.SetSensitivity(1.0) infparm.SetSensitivity(1.0) stlparm.SetSensitivity(1.0) stoparm.SetSensitivity(0.01) thkparm.SetSensitivity(0.001) scene.SetEventCallback(eventCB) def eventCB(scn,event): """Scene event callback function.""" global plane_name, plane if( (event & poser.kEventCodePARMCHANGED) != 0): runparm = plane.Parameter("RUN SCRIPT - Use dial!") if runparm.Value() != 0.0: scene.ClearEventCallback() stack = int(plane.Parameter("Stack loops").Value()) offset = plane.Parameter("Stacking offset").Value() ring = abs(int(plane.Parameter("Slices").Value())) if ring <= 0: ring = 10 divs = abs(int(plane.Parameter("Sections").Value())) if divs <= 3: divs = 30 thickness = plane.Parameter("Thickness").Value() if thickness < 0.0000001: thickness = 0.004 smooth = plane.Parameter("Apply smoothing").Value() if smooth > 0.0: smooth = 1 else: smooth = 0 inflate = plane.Parameter("Add 'Fatness' morph").Value() if inflate > 0.0: inflate = 1 else: inflate = 0 xtran = plane.ParameterByCode(poser.kParmCodeXTRAN) ytran = plane.ParameterByCode(poser.kParmCodeYTRAN) ztran = plane.ParameterByCode(poser.kParmCodeZTRAN) dist = offset * thickness for loopnum in range(max(1,abs(stack))): if stack > 0: if loopnum > 0: newpos = Numeric.array([xtran.Value(),ytran.Value(),ztran.Value()],Numeric.Float) newpos += (pnorm*dist) xtran.SetValue(newpos[0]) ytran.SetValue(newpos[1]) ztran.SetValue(newpos[2]) newloop,pnorm = make_loop(plane,thickness=thickness,ring=ring,divs=divs,smooth=smooth,inflate=inflate) if newloop != "": mir = plane.Parameter("Mirror loop") if mir.Value() > 0.0: # Run a second pass using same plane, mirrored across X xparm = plane.ParameterByCode(poser.kParmCodeXTRAN) yparm = plane.ParameterByCode(poser.kParmCodeYROT) zparm = plane.ParameterByCode(poser.kParmCodeZROT) if xparm != 0.0 or yparm != 0.0 or zparm != 0.0: xparm.SetValue(-xparm.Value()) yparm.SetValue(-yparm.Value()) zparm.SetValue(-zparm.Value()) if mir.Value() < 2.0: exclude = newloop else: exclude = "" newloop2,pnorm = make_loop(plane,thickness=thickness,ring=ring,divs=divs,smooth=smooth,inflate=inflate,exclude=exclude) if scene.CurrentActor().InternalName() != plane_name: loop_act = scene.CurrentActor() scene.SelectActor(plane) scene.DeleteCurrentProp() scene.SelectActor(loop_act) else: scene.DeleteCurrentProp() return endparm = plane.Parameter("END SCRIPT - Use dial!") if endparm.Value() != 0.0: scene.ClearEventCallback() scene.DeleteCurrentProp() return if ( (event & poser.kEventCodeACTORDELETED) != 0): acts = [a.InternalName() for a in scene.Actors()] if not plane_name in acts: scene.ClearEventCallback() def get_actverts(): """Screen actor selections to be tested against plane, log vertexPos array for each selected actor""" acts_dict = {} for a in scene.Actors(): if a.IsBodyPart() or a.IsProp(): if a.OnOff(): g = a.Geometry() if g: if a.InternalName() != plane_name and a.InternalName() != "GROUND": acts_dict[a.InternalName()] = [vertexPos(g),a] return acts_dict def make_loop(plane,thickness=0.004,ring=10,divs=30,smooth=0,inflate=0,exclude=""): """ bounding box format is [minX,maxX,minY,maxY,minZ,maxZ] This function finds vertices or edges which intersect the plane, then creates a 2D convex hull from these points. A deformed toroid shape is extruded around this ringed path. """ actors = get_actverts() planemesh = myMesh(plane,vertexPos(plane.Geometry())) planemesh.get_norms() boxplane = b_box_ock(planemesh.verts) plane_center = box_center(boxplane) # Get the center of the plane, for use in identifying center loop ring, below. pnorm = planemesh.pnorms[0] pplane = planemesh.pplanes[0] """ Check plane for intersections with visible scene actors. 1) Screen actors by checking actor bounding boxes for intersection with the plane. 2) Get the plane's inverse worldmatrix. 3) Apply inverse matrix to actor bounding boxes, to allow quick in-bounds tests to screen actors further. 4) Apply inverse matrix to vertices of selected actors, to test intersections at world origin. 5) Now checking against a horizontal plane, so edges can be tested for plane intersection using only their Y coordinates. Plane's Y coord is 0.0. If both edge vertices are > or < 0.0, the edge does not intersect, and can be skipped. 6) If the edge intersects the plane, cast a ray along the edge to find the intersection point with plane. 7) Log intersection points for further use and delete lists of actors and actor mesh data. 8) Intersection points are already transformed by the inverse matrix, because of its use above. """ basemesh = myMesh(plane,vertexPos(plane.Geometry(),worldspace=0)) # Get the mesh data for the un-transformed plane basemesh.get_norms() basenorm = basemesh.pnorms[0] baseplane = basemesh.pplanes[0] base_bbox = b_box_ock(basemesh.verts) bp_nx, bp_mx, bp_ny, bp_my, bp_nz, bp_mz = base_bbox planemat = plane.WorldMatrix() # Get the world matrix and its inverse for the plane invplanemat = matrix_invert(planemat) meshes = [] acts = actors.keys() for a in acts: if a == exclude: # Skip the excluded actor name continue abox = b_box_ock(actors[a][0]) intersect = AABB_plane_intersect(abox,pnorm,pplane) # Test actor AABB for intersection with plane if intersect == 0: # The actor's bounding box intersects the plane averts = box_verts(abox) mat_averts = [] # Get bounding box for actor, move to world origin for i in range(8): mat_avert = point_by_matrix(averts[i],invplanemat) #testing_boxes(mat_avert,0.002,"Transformed",0,1) mat_averts.append([j for j in mat_avert]) mat_abox = b_box_ock(mat_averts) if mat_abox[0] > bp_mx or mat_abox[1] < bp_nx: # Check transformed actor AABB against plane X,Z limits continue if mat_abox[2] > bp_my or mat_abox[3] < bp_ny: continue if mat_abox[4] > bp_mz or mat_abox[5] < bp_nz: continue meshes.append(myMesh(actors[a][1],actors[a][0])) mesh = meshes[len(meshes)-1] mesh.matverts = [] for vi in range(len(mesh.verts)): point = point_by_matrix(mesh.verts[vi],invplanemat) # Transform actor vertices to world origin using inverse matrix of plane mesh.matverts.append([i for i in point]) #testing_boxes(point,0.002,"Transformed",0,1) mesh.matverts = Numeric.array(mesh.matverts,Numeric.Float) mesh.edgescreen = [[1 for j in i] for i in mesh.edges] for pi in range(mesh.geom.NumPolygons()): # Precompile a list of valid edges, to allow quick full-polygon skipping, below edges = mesh.edges[pi] for ei in range(len(edges)): edge = edges[ei] e0 = mesh.matverts[edge[0]] e1 = mesh.matverts[edge[1]] if (e0[1] > 0.0 and e1[1] > 0.0) or (e0[1] < 0.0 and e1[1] < 0.0): # Y coord for plane is now 0.0 mesh.edgescreen[pi][ei] = 0 # If both edge vertices are either above or below the plane, there's no intersection and we can skip this edge continue if (e0[0] < bp_nx and e1[0] < bp_nx) or (e0[0] > bp_mx and e1[0] > bp_mx): mesh.edgescreen[pi][ei] = 0 # Skip the edge if both points are out of range on X or Z continue if (e0[2] < bp_nz and e1[2] < bp_nz) or (e0[2] > bp_mz and e1[2] > bp_mz): mesh.edgescreen[pi][ei] = 0 continue del actors if meshes == []: # No actors were found print "No actors were found which intersect the plane. Try re-positioning the plane." return hitpoints = [] for mesh in meshes: # Find vertices with edges which intersect the plane, for each actor for pi in range(mesh.geom.NumPolygons()): if not mesh.edgescreen[pi].count(1): # If none of the edges for the poly will intersect the plane properly, skip the entire polygon. continue edges = mesh.edges[pi] for ei in range(len(edges)): if not mesh.edgescreen[pi][ei]: # If the edge will not intersect the plane, skip it. continue edge = edges[ei] e0 = mesh.matverts[edge[0]] e1 = mesh.matverts[edge[1]] # Much of this next bit was copied directly from Spanki's ray-casting code for TDMT. Good stuff. d = e1 - e0 ndota = d[0] * basenorm[0] + d[1] * basenorm[1] + d[2] * basenorm[2] if ndota <= 0.0: # Just check the edges on one side of the plane continue ndotv = basenorm[0] * e0[0] + basenorm[1] * e0[1] + basenorm[2] * e0[2] isect = (baseplane - ndotv) / ndota testPoint = [e0[0] + (d[0] * isect), e0[1] + (d[1] * isect), e0[2] + (d[2] * isect)] if testPoint[0] < bp_nx or testPoint[0] > bp_mx or testPoint[2] < bp_nz or testPoint[2] > bp_mz: continue # If one vert of edge was in bounds, we'll have a hit, but the intersection may not be in bounds. hitpoints.append([i for i in testPoint]) #testing_boxes(testPoint,0.002,"Intersect",0,1) for mesh in meshes: mesh = None del meshes if hitpoints == []: # Selection may fail if no vertices for intersecting edges were within bounding box. Should refine process. print "No vertices found. Try re-positioning the plane." return """ If we have intersection points, process these to find vertex positions for the final loop 1) Get the 2D convex hull of the points 2) Apply world matrix to restore convex hull points to position and orientation of plane. 3) Walk perimeter of hull to place evenly-spaced markers for final ring positions 4) Get the proper angle for each ring and generate the locations of final vertices, using the spaced markers 5) Build the final geometry """ hitpoints = [(i[0],i[2]) for i in hitpoints] """ screen = {} # Allegedly the convex hull code can fail when encountering verts with identical locations. for i in hitpoints: # Screen out duplicates, in hopes of avoiding the periodic "tangled path" error which seems to j = tuple(i) if screen.has_key(j): # come from the hull code. Also creating a secondary hull, below, to combat the same issue. print "Duplicate point screened! %s" %(j) screen[j] += 1 else: screen[j] = 1 hitpoints = [i for i in screen.keys()] screen = {} """ #hullpoints = convexHull(hitpoints) # Get the 2D convex hull for our edge points hullpoints = hulls(hitpoints) #for i in hullpoints: #testing_boxes2d(i,0.002,"Hull",0,1) hullpoints = convexHull(hullpoints) # Sometimes the hull function fails and the path includes inner verts. Try to correct with a secondary hull. hull2 = [[] for i in hullpoints] for i in range(len(hullpoints)): # Switch back to 3D use here, to avoid ring-squashing effect in final geometry, due to scaling of original plane point = [hullpoints[i][0],0.0,hullpoints[i][1]] point = point_by_matrix(point,planemat) hull2[i] = [point[0],point[1],point[2]] #testing_boxes(hull2[i],0.002,"Hull",0,1) hullpoints = Numeric.array(hull2,Numeric.Float) edges = [[] for i in hullpoints] edgelens = [0.0 for i in hullpoints] for i in range(len(hullpoints)): # Gather hull edge data up front to simplify subsequent looping if i == len(hullpoints)-1: j = 0 else: j = i + 1 edges[i] = [i,j] dist = hullpoints[j] - hullpoints[i] dist = Numeric.sqrt( pow(dist[0],2) + pow(dist[1],2) + pow(dist[2],2) ) edgelens[i] = dist pathlen = path_length(hullpoints) # Distance around hull perimeter seglen = pathlen/divs # Length of a final ring segment hitpoints = [[0.0,0.0,0.0] for i in range(divs)] hitpoints = Numeric.array(hitpoints,Numeric.Float) for i in range(divs): # Get the proper locations for each evenly-spaced ring of the final loop by walking the perimeter of the hull total = 0.0 segdist = seglen * i for j in range(len(edges)): # Walk the perimeter, placing ring-markers as appropriate total += edgelens[j] if total >= segdist: hitdist = total-segdist dirx = hullpoints[edges[j][0]] - hullpoints[edges[j][1]] dirx = vector_normalize(dirx) point = hullpoints[edges[j][1]] + (dirx * hitdist) #testing_boxes(point,0.002,"Spaced",0,1) for j in range(3): hitpoints[i][j] = point[j] break edgelens = [] hullpoints = [] edges = [[] for i in hitpoints] # Pre-compile the hitpoints edges, to simplify the following for i in range(len(hitpoints)): if i == len(hitpoints)-1: j = 0 else: j = i + 1 edges[i] = [i,j] verts = [] # Final vertex positions inner = -1 # Identify the inner ring for UV split vertical = pnorm # Having switched back to 3D use above, the relative "vertical" axis for use below is now the original normal of the plane. for i in range(len(hitpoints)): # Get the point locations for the ringed cross-sections point = hitpoints[i] e1 = edges[i] if i == 0: # We loop clockwise, so we need to use the neighboring edge in the counter-clockwise direction, to get the correct angles. e2 = edges[len(hitpoints)-1] else: e2 = edges[i-1] edge1 = hitpoints[e1[1]] - hitpoints[e1[0]] # Basically building a 2D vertex normal, here.... edge2 = hitpoints[e2[1]] - hitpoints[e2[0]] edge1 = vector_normalize(edge1) edge2 = vector_normalize(edge2) cross1 = vector_crossproduct(edge1,vertical) # Make two vertical planes cross2 = vector_crossproduct(edge2,vertical) cross1 = vector_normalize(cross1) # Plane normals are the crossproducts cross2 = vector_normalize(cross2) norm = (cross1 + cross2)/2 # Average and normalize norm = vector_normalize(norm) point = point + (norm * (thickness)) # Now that we have the 2D normal, inflate the loop by the thickness, to keep it from embedding seg = circle(point, thickness, precision=ring,ray1=vertical,ray2=norm) # Create the ring, aligned with vertical axis and our normal if i == 0: if len(seg) != ring: # The circle() function will change the number of verts per ring segment if 360 is not divisible by ring ring = len(seg) # Correct the ring variable here, to prevent indexing errors when creating polygons, below maxdist = 100000.0 for vi in range(len(seg)): v = seg[vi] if i == 0: # Identify the inner ring for UV split dist = distance3d(v,plane_center) if dist < maxdist: maxdist = dist inner = vi #testing_boxes(v,0.0005,"Rings",0,1) verts.append([j for j in v]) edges = [] hitpoints = [] """ Now go on to build the new mesh.... """ verts = Numeric.array(verts,Numeric.Float) polys = Numeric.array([[i,4] for i in range(0,divs*ring*4,4)],Numeric.Int) sets = [] for i in range(divs): thisring = i*ring if i == divs-1: nextring = 0 else: nextring = (i + 1)*ring for j in range(ring): if j == ring-1: k = 0 else: k = j + 1 sets.append(thisring+j) sets.append(thisring+k) sets.append(nextring+k) sets.append(nextring+j) sets = Numeric.array(sets,Numeric.Int) # Get the UV data # --- UV's tiled by toroid ring --- tverts,tsets = ring_map(divs,ring,inner) tpolys = [[j for j in i] for i in polys] tverts = Numeric.array(tverts,Numeric.Float) tsets = Numeric.array(tsets,Numeric.Int) tpolys = Numeric.array(tpolys,Numeric.Int) # Create the new geometry newgeom = poser.NewGeometry() newgeom.AddGeneralMesh(polys,sets,verts,tpolys,tsets,tverts) # Make a prop from the geometry newprop = scene.CreatePropFromGeom(newgeom,"newloop") if not newprop: return "" scene.SelectActor(newprop) # Refresh the geometry by pulling a copy of it from the created prop newgeom = newprop.Geometry() # This is necessary to enable Poser 5 and 6 to add new materials to the geom. # Add a new material and set diffuse to white. newgeom.AddMaterialName("loop") for pi in range(len(polys)): p = newgeom.Polygon(pi) p.SetMaterialIndex(1) # First entry is always 'Preview'; second is the new material # Set the new geometry with added material for the prop newprop.SetGeometry(newgeom) # Set the prop's diffuse color for the new material newprop.Material("loop").SetDiffuseColor(1.0,1.0,1.0) # Set the material color # Set the new loop's origin, endpoint, and orientation to match those of the plane. (Requested by lesbentley.) origin = plane.Origin() endpoint = plane.EndPoint() origin = point_by_matrix(origin,planemat) endpoint = point_by_matrix(endpoint,planemat) orientation = [0.0,0.0,0.0] # New loop's orientation will be X,Y,Z rotations of plane. orientation[0] = plane.ParameterByCode(poser.kParmCodeXROT).Value() orientation[1] = plane.ParameterByCode(poser.kParmCodeYROT).Value() orientation[2] = plane.ParameterByCode(poser.kParmCodeZROT).Value() newprop.SetOrigin(origin[0],origin[1],origin[2]) newprop.SetEndPoint(endpoint[0],endpoint[1],endpoint[2]) newprop.SetOrientation(orientation[0],orientation[1],orientation[2]) # Optionally apply some smoothing to the mesh # Optionally add a "Fatness" morph to the loop if smooth or inflate: loopmesh = myMesh(newprop,vertexPos(newprop.Geometry())) # Gathering geometry data for the actor. See the myMesh class, below, and further use if smooth: # of the class, at the top of this function. loopmesh.vertneighbors(loopmesh.pverts) find_detail(loopmesh) loopmesh.polynorms(loopmesh.pverts) loopmesh.vertnorms_Geom() if smooth: smooth_mesh(loopmesh) if inflate: inflate_morph(loopmesh) del loopmesh return newprop.InternalName(), pnorm def smooth_mesh(mesh): """Smooth the resulting loop""" for vi in range(mesh.geom.NumVertices()): vert = mesh.verts[vi] avg = Numeric.array([0.0,0.0,0.0],Numeric.Float) nbs = mesh.vneighbors[vi] for nbi in nbs: avg += mesh.verts[nbi] avg /= len(nbs) sub = mesh.verts[vi] - avg # 'Cheapie' volume preservation dist = Numeric.sqrt( pow(sub[0],2) + pow(sub[1],2) + pow(sub[2],2) ) distdiff = dist - mesh.dists[vi] avg += (mesh.vnorms[vi]*distdiff) avg = (vert + avg)/2 vert = mesh.geom.Vertex(vi) vert.SetX(avg[0]) vert.SetY(avg[1]) vert.SetZ(avg[2]) mesh.act.MarkGeomChanged() def inflate_morph(mesh): """ Add an optional "Fatness" morph to the loop. """ amt = 0.01 act = mesh.act act.SpawnTarget("Fatness") morph = act.Parameter("Fatness") for vi in range(mesh.geom.NumVertices()): vert = mesh.verts[vi] norm = mesh.vnorms[vi] delta = (vert + (norm * amt)) - vert morph.SetMorphTargetDelta(vi,delta[0],delta[1],delta[2]) def find_detail(mesh): """ Finds center (averaged position) of all neighbor vertices for each vertex. These are used for the 'cheapie' volume preservation applied by the smoothing """ mesh.detailverts = vertexPos(mesh.geom,worldspace=0) mesh.detail = [[0.0,0.0,0.0] for i in mesh.detailverts] mesh.dists = [0.0 for i in mesh.detailverts] for vi in range(len(mesh.detailverts)): nbs = mesh.vneighbors[vi] vert = mesh.detailverts[vi] avg = Numeric.array([0.0,0.0,0.0],Numeric.Float) for i in range(len(nbs)): nvi = nbs[i] nv = mesh.detailverts[nvi] for i in range(3): avg[i] += nv[i] for i in range(3): avg[i] /= len(nbs) mesh.detail[vi] = vert - avg sub = vert - avg mesh.dists[vi] = Numeric.sqrt( pow(sub[0],2) + pow(sub[1],2) + pow(sub[2],2) ) def ring_map(divs,ring,inner): """Tiled UV mapping by loop ring section - texverts are not welded""" tverts = [] tsets = [] count = 0 row = 1.0/ring umax = 1.0 umin = 0.0 splitring = [i+1 for i in range(inner,ring)] # Moving UV seam to inner ring of loop, where it will be less visible for i in range(inner): # The splitring list is ring, reordered splitring.append(i+1) splitring.reverse() for div in range(divs): for r in range(ring): top = splitring[r] bottom = splitring[r]-1 if bottom == ring: bottom = 0 for i in range(4): tsets.append(count) count += 1 a = [umin,row*top] b = [umax,row*top] c = [umax,row*bottom] d = [umin,row*bottom] tverts.append(a) tverts.append(d) tverts.append(c) tverts.append(b) return tverts,tsets def path_length(path): """Get the total length of the hull path""" length = 0.0 for i in range(len(path)): start = path[i] if i < len(path)-1: end = path[i+1] else: end = path[0] dist = end - start dist = Numeric.sqrt( pow(dist[0],2) + pow(dist[1],2) + pow(dist[2],2) ) length += dist return length def AABB_plane_intersect(box,plane_norm,plane_D): """ Perform static AABB-plane intersection test. Returns: <0 Box is completely on the BACK side of the plane >0 Box is completely on the FRONT side of the plane 0 Box intersects the plane Adapted from '3D Math Primer for Graphics and Game Development', by Fletcher Dunn and Ian Parberry www.gamemath.com original code is avaialble in downloads section of website. bounding box format is [minX,maxX,minY,maxY,minZ,maxZ] """ # Inspect the normal and compute the minimum and maximum D values. if plane_norm[0] > 0.0: minD = plane_norm[0] * box[0] maxD = plane_norm[0] * box[1] else: minD = plane_norm[0] * box[1] maxD = plane_norm[0] * box[0] if plane_norm[1] > 0.0: minD += plane_norm[1] * box[2] maxD += plane_norm[1] * box[3] else: minD += plane_norm[1] * box[3] maxD += plane_norm[1] * box[2] if plane_norm[2] > 0.0: minD += plane_norm[2] * box[4] maxD += plane_norm[2] * box[5] else: minD += plane_norm[2] * box[5] maxD += plane_norm[2] * box[4] # Check if completely on the front side of the plane if (minD >= plane_D): return 1 # Check if completely on the back side of the plane if (maxD <= plane_D): return -1 # We straddle the plane return 0 def lineline(A,B,C,D): """ Line-line intersection algorithm, returns point of intersection or None http://refactormycode.com/codes/1114-line-line-intersection-test Adapted from PyGame code posted by Mizipzor """ # ccw from http://www.bryceboe.com/2006/10/23/line-segment-intersection-algorithm/ def ccw(A,B,C): return (C[1]-A[1])*(B[0]-A[0]) > (B[1]-A[1])*(C[0]-A[0]) if ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D): # formula from http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ ua = float(((D[0]-C[0])*(A[1]-C[1]))-((D[1]-C[1])*(A[0]-C[0])))/float(((D[1]-C[1])*(B[0]-A[0]))-((D[0]-C[0])*(B[1]-A[1]))) ub = float(((B[0]-A[0])*(A[1]-C[1]))-((B[1]-A[1])*(A[0]-C[1])))/float(((D[1]-C[1])*(B[0]-A[0]))-((D[0]-C[0])*(B[1]-A[1]))) return (A[0]+(ua*(B[0]-A[0])), A[1]+(ua*(B[1]-A[1]))) return None def circle(pos, radius, precision=10, center=0, rot=0, ray1=None, ray2=None): """ http://ubuntuforums.org/showthread.php?t=946618 Adapted from openGL function posted by user crazyfuturamanoob. Arbitrary plane handling based on a post at MATLAB Central: http://www.mathworks.com/matlabcentral/newsreader/view_thread/164885 """ plane = 0 if (ray1 != None) and (ray2 != None): plane = 1 if type(pos) != "array": pos = Numeric.array(pos,Numeric.Float) pi = math.pi cverts = [] step = int(360/precision) for angle in range(0, 360, step): theta = pi*angle/180.0 cos = math.cos(theta) sin = math.sin(theta) if plane: # Apply rotation along an arbitrary axis cverts.append(pos + ((ray1*cos + ray2*sin) * radius)) else: if rot: cverts.append([pos[0], pos[1] + radius*cos, pos[2] + radius*sin]) else: cverts.append([pos[0] + radius*cos, pos[1], pos[2] + radius*sin]) if center: cverts.append([i for i in pos]) return cverts #================================================================================================= # Vector and matrix functions from Blender2Cal3D.py for Blender. # http://www-users.cs.umn.edu/~mein/blender/plugins/python/import_export/blend2cal3d/blender2cal3d.py # (Other versions of this script can be found online, with Google) # Poser matrices are compatible with Cal3D matrices, but not with Blender matrices. # Some functions have been altered from the original. # # (Xscale, 0.0, 0.0, 0.0), # (0.0, yScale, 0.0, 0.0), # (0.0, 0.0, zScale, 0.0), # (xTran, yTran, zTran, ) #================================================================================================= def matrix_invert(m): det = (m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2]) + m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2])) if det == 0.0: return None det = 1.0 / det r = [ [ det * (m[1][1] * m[2][2] - m[2][1] * m[1][2]), - det * (m[0][1] * m[2][2] - m[2][1] * m[0][2]), det * (m[0][1] * m[1][2] - m[1][1] * m[0][2]), 0.0, ], [ - det * (m[1][0] * m[2][2] - m[2][0] * m[1][2]), det * (m[0][0] * m[2][2] - m[2][0] * m[0][2]), - det * (m[0][0] * m[1][2] - m[1][0] * m[0][2]), 0.0 ], [ det * (m[1][0] * m[2][1] - m[2][0] * m[1][1]), - det * (m[0][0] * m[2][1] - m[2][0] * m[0][1]), det * (m[0][0] * m[1][1] - m[1][0] * m[0][1]), 0.0, ] ] r.append([ -(m[3][0] * r[0][0] + m[3][1] * r[1][0] + m[3][2] * r[2][0]), -(m[3][0] * r[0][1] + m[3][1] * r[1][1] + m[3][2] * r[2][1]), -(m[3][0] * r[0][2] + m[3][1] * r[1][2] + m[3][2] * r[2][2]), 1.0, ]) return r def matrix_rotate(axis, angle): vx = axis[0] vy = axis[1] vz = axis[2] vx2 = vx * vx vy2 = vy * vy vz2 = vz * vz cos = math.cos(angle) sin = math.sin(angle) co1 = 1.0 - cos return [ [vx2 * co1 + cos, vx * vy * co1 + vz * sin, vz * vx * co1 - vy * sin, 0.0], [vx * vy * co1 - vz * sin, vy2 * co1 + cos, vy * vz * co1 + vx * sin, 0.0], [vz * vx * co1 + vy * sin, vy * vz * co1 - vx * sin, vz2 * co1 + cos, 0.0], [0.0, 0.0, 0.0, 1.0], ] def point_by_matrix(p, m): return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0], p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1], p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2]] def matrix_translate(m, v): m[3][0] += v[0] m[3][1] += v[1] m[3][2] += v[2] return m def matrix_scale(fx, fy, fz): return [ [ fx, 0.0, 0.0, 0.0], [0.0, fy, 0.0, 0.0], [0.0, 0.0, fz, 0.0], [0.0, 0.0, 0.0, 1.0], ] def r2d(r): """Could also use math.degrees(r)""" #return round(r*180.0/math.pi,4) d = round(r*180.0/math.pi,4) if d > 360.0: d = d % 360 return d def d2r(d): """Could also use math.radians(d)""" return (d*math.pi)/180.0 def vector_normalize(v): l = Numeric.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) if l == 0.0: return Numeric.array([v[0], v[1], v[2]],Numeric.Float) # Spanki - avoid divide-by-zero (test added) return Numeric.array([v[0] / l, v[1] / l, v[2] / l],Numeric.Float) def vector_normalize2d(v): l = Numeric.sqrt(v[0] * v[0] + v[1] * v[1]) if l == 0.0: return Numeric.array([v[0], v[1]],Numeric.Float) # Spanki - avoid divide-by-zero (test added) return Numeric.array([v[0] / l, v[1] / l],Numeric.Float) def vector_crossproduct(v1, v2): return [ v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0], ] def distance2d(p1,p2): p1 = Numeric.array(p1,Numeric.Float) p2 = Numeric.array(p2,Numeric.Float) dist = p2 - p1 dist = Numeric.sqrt( pow(dist[0],2) + pow(dist[1],2) ) return dist def distance3d(p1,p2): p1 = Numeric.array(p1,Numeric.Float) p2 = Numeric.array(p2,Numeric.Float) dist = p2 - p1 dist = Numeric.sqrt( pow(dist[0],2) + pow(dist[1],2) + pow(dist[2],2) ) return dist def vector_angle(v1, v2, r2d=0): """ Originally derived from blender2cal3d.py. Returns radians. if r2d is sent as 1, will return degrees. """ s = vector_length(v1) * vector_length(v2) if s == 0: return 0.0 f = vector_dotproduct(v1,v2) / s if f > 1.0: return 0.0 if f < -1.0: return math.pi / 2.0 if r2d: return round(math.degrees(math.acos(f))) return math.acos(f) def vector_length(v): if len(v) == 2: return Numeric.sqrt( pow(v[0],2) + pow(v[1],2) ) elif len(v) == 3: return Numeric.sqrt( pow(v[0],2) + pow(v[1],2) + pow(v[2],2) ) def vector_dotproduct(v1,v2): if len(v1) == 2: v1 = [v1[0],v1[1],0.0] if len(v2) == 2: v2 = [v2[0],v2[1],0.0] return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] #================================================================================================= # convex hull (Graham scan by x-coordinate) and diameter of a set of points # David Eppstein, UC Irvine, 7 Mar 2002 # http://code.activestate.com/recipes/117225-convex-hull-and-diameter-of-2d-point-sets/ # This code seems to return the correct set of points, but not in any useful order. #================================================================================================= def orientation2(p,q,r): '''Return positive if p-q-r are clockwise, neg if ccw, zero if colinear.''' return (q[1]-p[1])*(r[0]-p[0]) - (q[0]-p[0])*(r[1]-p[1]) def orientation3(p,q,r): return ((q - p) * (r - p).conjugate()).imag def hulls(Points): '''Graham scan to find upper and lower convex hulls of a set of 2d points.''' U = [] L = [] Points.sort() for p in Points: while len(U) > 1 and orientation2(U[-2],U[-1],p) <= 0: U.pop() while len(L) > 1 and orientation2(L[-2],L[-1],p) >= 0: L.pop() U.append(p) L.append(p) #return U,L return U[:-1] + L #================================================================================================= # Convex hull code # http://code.activestate.com/recipes/66527-finding-the-convex-hull-of-a-set-of-2d-points/ # This code can return a set which includes one or more inner points, creating a snarled path #================================================================================================= """ convexhull.py Calculate the convex hull of a set of n 2D-points in O(n log n) time. Taken from Berg et al., Computational Geometry, Springer-Verlag, 1997. Prints output as EPS file. When run from the command line it generates a random set of points inside a square of given length and finds the convex hull for those, printing the result as an EPS file. Usage: convexhull.py Dinu C. Gherman """ ###################################################################### # Helpers ###################################################################### def _myDet(p, q, r): """Calc. determinant of a special matrix with three 2D points. The sign, "-" or "+", determines the side, right or left, respectivly, on which the point r lies, when measured against a directed vector from p to q. """ # We use Sarrus' Rule to calculate the determinant. # (could also use the Numeric package...) sum1 = q[0]*r[1] + p[0]*q[1] + r[0]*p[1] sum2 = q[0]*p[1] + r[0]*q[1] + p[0]*r[1] return sum1 - sum2 def _isRightTurn((p, q, r)): "Do the vectors pq:qr form a right turn, or not?" #assert p != q and q != r and p != r if (p == q) or (q == r) or (p == r): return 0 if _myDet(p, q, r) < 0: return 1 else: return 0 def _isPointInPolygon(r, P): "Is point r inside a given polygon P?" # We assume the polygon is a list of points, listed clockwise! for i in xrange(len(P[:-1])): p, q = P[i], P[i+1] if not _isRightTurn((p, q, r)): return 0 # Out! return 1 # It's within! ###################################################################### # Public interface ###################################################################### def convexHull(P): "Calculate the convex hull of a set of points." # Get a local list copy of the points and sort them lexically. points = map(None, P) points.sort() # Build upper half of the hull. upper = [points[0], points[1]] for p in points[2:]: upper.append(p) while len(upper) > 2 and not _isRightTurn(upper[-3:]): del upper[-2] # Build lower half of the hull. points.reverse() lower = [points[0], points[1]] for p in points[2:]: lower.append(p) while len(lower) > 2 and not _isRightTurn(lower[-3:]): del lower[-2] # Remove duplicates. del lower[0] del lower[-1] # Concatenate both halfs and return. return tuple(upper + lower) #================================================================================================= # End convex hull #================================================================================================= #================================================================================================= # class myMesh # A container class for geometry data # Functions developed for TDMT project, 2007 # Some functions written or modified by Spanki #================================================================================================= class myMesh(object): def __init__(self,act,verts): self.act = act self.geom = act.Geometry() self.verts = verts self.polyverts(self.geom,self.geom.Polygons()) self.polyedges(self.pverts) self.vertpolys(self.geom,self.pverts) def get_norms(self): self.polynorms(self.pverts) #self.vertnorms_Geom() self.polyplanes(self.pverts) def polyverts(self,geom,polys): """ vertices by polygon - this is the Sets() list broken down by polygon index. Returns a list of Numeric arrays From TDMT Classic source code. """ self.pverts = [] gset = geom.Sets() for p in polys: l = p.NumVertices() s = p.Start() e = s + l vl = Numeric.array([v for v in gset[s:e]],Numeric.Int) self.pverts.append(vl) def vertpolys(self,geom,pgons,tex=0): """ polygons by vertex - each vert will have multiple listings returns a list of Numeric arrays """ if tex: self.vpolys = [[] for i in range(geom.NumTexVertices())] else: self.vpolys = [[] for i in range(geom.NumVertices())] for pvi in range(len(pgons)): for v in pgons[pvi]: if not pvi in self.vpolys[v]: self.vpolys[v].append(pvi) self.vpolys = [Numeric.array(i,Numeric.Int) for i in self.vpolys] def polynorms(self,pgons,verts=[]): """ [Spanki] compute normal for each polygon (don't rely on Poser supplied Normals - they're broken) returns a Numeric array """ self.pnorms = [[0.0,0.0,0.0] for i in range(len(pgons))] self.pnorms = Numeric.array(self.pnorms,Numeric.Float) if verts == []: verts = self.verts for p_i in range(len(pgons)): p = pgons[p_i] v0 = verts[p[0]] v1 = verts[p[1]] v2 = verts[p[2]] # Create 2 edge vectors to define a plane e1 = v1 - v0 e2 = v2 - v0 # Cross-product is normal to that plane vN = vector_normalize(vector_crossproduct(e1, e2)) self.pnorms[p_i] = vN def vertnorms_Geom(self,norms2=0): """ [Spanki] normals of vertices, computed as the average of the face normals of the polygons which use each vertex returns a Numeric array """ if norms2: self.raynorms = [[0.0,0.0,0.0] for i in range(len(self.vpolys))] self.raynorms = Numeric.array(self.vnorms,Numeric.Float) else: self.vnorms = [[0.0,0.0,0.0] for i in range(len(self.vpolys))] self.vnorms = Numeric.array(self.vnorms,Numeric.Float) for vi in range(len(self.vpolys)): polys = self.vpolys[vi] nx = 0; ny = 0; nz = 0 l = len(polys) if l == 0: # In some unusual geometries, there may be vertices with no polygon associations - we try to skip these. 1/26/08 #print "Stray vertex: %s, %s" %(vi,l) continue # If we don't skip these, we get a divide by zero crash error due to a bad mesh. for p in polys: nx += self.pnorms[p][0] ny += self.pnorms[p][1] nz += self.pnorms[p][2] n = [nx/l,ny/l,nz/l] n = vector_normalize(n) # Shouldn't need to do this, but it can't hurt if norms2: self.raynorms[vi] = n else: self.vnorms[vi] = n def polyedges(self,pgons,tris=0): """ edges of polygons as [start vert,end vert] """ self.edges = [[[0,0] for j in range(len(i))] for i in pgons] for p in range(len(pgons)): for v in range(len(pgons[p])): if v != len(pgons[p])-1: self.edges[p][v] = [pgons[p][v],pgons[p][v+1]] else: self.edges[p][v] = [pgons[p][v],pgons[p][0]] def polyplanes(self,pgons): """ planes of polygons tries to use a single vertex to calculate the plane, unless two axes of the first vertex are at 0.0, in which case it will use the average of all three vertices to calculate the plane. returns a Numeric array [Spanki] - I flipped the order of these, so that the average is used as preference (it's less likely to have any 0.0 coordinate) """ self.pplanes = [] for pvi in range(len(pgons)): px = 0; py = 0; pz = 0 n = self.pnorms[pvi] pv = pgons[pvi] for vert in pv: vx = self.verts[vert] px += vx[0] py += vx[1] pz += vx[2] if (abs(px) < FLT_EPSILON and abs(py) < FLT_EPSILON) or\ (abs(px) < FLT_EPSILON and abs(pz) < FLT_EPSILON) or\ (abs(py) < FLT_EPSILON and abs(pz) < FLT_EPSILON): vx = self.verts[self.tverts[pvi][0]] D = n[0] * vx[0] + n[1] * vx[1] + n[2] * vx[2] else: v_avg = [px/3,py/3,pz/3] D = 0 for i in range(3): D += n[i]*v_avg[i] self.pplanes.append(D) self.pplanes = Numeric.array(self.pplanes,Numeric.Float) def vertneighbors(self,pgons): """ neighbor verts of verts pgons is self.pverts or self.tverts """ self.vneighbors = [[] for i in range(len(self.vpolys))] for vi in range(len(self.vpolys)): buflist = [vi] # Need to exclude the vert itself! for ps in self.vpolys[vi]: for v in pgons[ps]: if not v in buflist: self.vneighbors[vi].append(v) buflist.append(v) def vertexPos(geom,worldspace=1,multiple=1.0): """ Pre-fetch vertex coordinates for faster access. Returns a Numeric array of vert coords by vert indices Send worldspace=0 keyword when calling function, to use localspace From TDMT Classic source code. """ numVerts = geom.NumVertices() verts = [[0.0,0.0,0.0] for i in range(numVerts)] verts = Numeric.array(verts,Numeric.Float) for i in range(numVerts): if worldspace: v = geom.WorldVertex(i) else: v = geom.Vertex(i) verts[i] = [v.X(),v.Y(),v.Z()] return verts #================================================================================================= # --- 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[0] >= bbox[0] and vert[0] <= bbox[1]: if vert[1] >= bbox[2] and vert[1] <= bbox[3]: if vert[2] >= bbox[4] and vert[2] <= 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 (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 box_verts(b1): """ Derive vertices from a bounding box """ return [ [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]] ] 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 testing_boxes2d(point,sz,name,disp,merge): """ build the testing boxes """ b = [point[0]-sz, point[0]+sz, 0.0-sz, 0.0+sz, point[1]-sz, point[1]+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) return newbox def build_plane(name,display=0): newplane = poser.NewGeometry() vertices = [[-0.25, 0.0, 0.25], [0.25, 0.0, 0.25], [0.25, 0.0, -0.25], [-0.25, 0.0, -0.25]] vertices = Numeric.array(vertices,Numeric.Float) polys = Numeric.array( [[0,3],[3,3]] ,Numeric.Int) sets = Numeric.array( [0,1,3,1,2,3] ,Numeric.Int) newplane.AddGeneralMesh(polys,sets,vertices) plane = scene.CreatePropFromGeom(newplane,name) #if display == 0: plane.SetDisplayStyle(poser.kDisplayCodeSHADEDOUTLINED) if display == 1: plane.SetDisplayStyle(poser.kDisplayCodeEDGESONLY) elif display == 2: plane.SetDisplayStyle(poser.kDisplayCodeWIREFRAME) scene.ProcessSomeEvents(1) scene.DrawAll() return plane run()