Forum Moderators: Staff
Poser Python Scripting F.A.Q (Last Updated: 2024 Dec 02 3:16 pm)
Nuts, my method only works if the morph doesn't creep onto the other side. Mirror doesn't do exactly what I need it to do. It doesn't reflect. Mirror, to me, means reverse along an axis. Or maybe it's because I edited the morph inconsistently. A master-synced morph confuses things. Did I edit thigh, hip, waist? I think it matters.
Uhg, I wish there was a knowledge base of Poser.
I made a lib to deal with morphs. Last entry on my webpage (see footer).
When mirroring a morph you have to distinguish if you want to mirror per actor or per figure.
Mirroring per figure requires that you know the opposite actor. If this is the case you can simply copy all vertices with inverted values for the axis to be mirrored from the original.
adp001 posted at 4:45PM Fri, 06 March 2020 - #4382593
I made a lib to deal with morphs. Last entry on my webpage (see footer).
When mirroring a morph you have to distinguish if you want to mirror per actor or per figure.
Mirroring per figure requires that you know the opposite actor. If this is the case you can simply copy all vertices with inverted values for the axis to be mirrored from the original.
Sweet, thank you! Figured I'm not unique in what I want :) . I'll check it out.
I wrote a script to create a mirror of a morph. It checks for symmetry, matches each vertex to its mirror, then just goes through the list of mirrored vertices and grabs the morph delta for the un-mirrored vertex. I was to the point of creating the new morph and applying the delats but I couldn't find a "create morph parameter" type function. It seems like every time I try to automate something that's not quite right or clunky in the GUI, I come up against API shortfall. Seemed like the "loadmorph" with a template, changing the name as needed was my only option.
Mirror isn't quite what I want, really. It's "reverse" or "flip".
adp001 posted at 12:45PM Sat, 07 March 2020 - #4382717
Have a look in my lib for morphs. Mathematical functions (like reverse the morph_values of an actor) are one-liners (numpy functionality). A "CreateMorph" function is also included ;)
The create function is almost exactly what I came up with. Except I cleared it as a matter of course. Your function is better (being flexible). :)
I'm not seeing the logic for symmetric reverse. I see the vertices being read into the array. And I know it's easy to reverse an array. I think this is what needs to happen. geometry delta 1: 1,1,1 .1,0,0 2: 2,1,1 .2,0,0 3: 4,7,8 .15,0,0 4: 1,4,5 .17,0,0 5: -1,1,1 0,0,0 6: -2,1,1 0,0,0 7: -4,7,8 0,0,0 8: -1,4,5 0,0,0
flipped (x) morph 1: 1,1,1 0,0,0 2: 2,1,1 0,0,0 3: 4,7,8 0,0,0 4: 1,4,5 0,0,0 5: -1,1,1 .1,0,0 6: -2,1,1 .2,0,0 7: -4,7,8 .15,0,0 8: -1,4,5 .17,0,0
Assumptions: geometry vertices are not in any particular order. Also, the order of left and right don't necessarily correspond. In this example, 1 is not necessarily the mirror of 5. I don't know how Poser stores vertices; if there's any consistency to it's ordering.
Anyway, For this "morph flip) to happen, I have to know 1) is_symmetric is true and 2) The vertex index to mirror vertex index.
To do that I make a triple key dict: dict[x][y][z] = i, where i is the geometry vertex index.
For x,y,z I use: int(1e7*x) to make the indexes INTs and allow for some degree of inaccuracy in the mesh. At this point, if the mesh is symmetric, for each x,y,z=i1 I should have a corresponding -x,y,z=i2
NOTE: x=0 is axial and not relevant to test symmetry.
Going through this dict, if there is any failure to find an entry for -x,y,z, given x,y,z then it's not symmetric (by my standards).
And given any point's x,y,z, I have its index, and can find each corresponding mirrored point index by just negating x (looking up using -x,y,z)
I can spawn a morph target, go through one sides vertices (the indexes where x>0 or x<0) pull the morphdelta for each and set the mirrored vertex index morphdelta to that.
Now I have a new morph that is the reverse (flipped x) of the original morph.
This is the only way I can figure to do it, making no assumptions about the ordering of vertices, and allowing for some "play in the values".
Is this a reasonable approach? It works. But it does take 2 passes through the vertices just to build the index then be able to prove symmetry. It happens very quickly so it's not about speed. But I'm wondering is there even faster.
Cheers :)
Sure that 0 is allways the center?
Try this following with a few actors. Select the actor to inspect and run the script:
from collections import namedtuple
sc = poser.Scene()
ac = sc.CurrentActor()
geom = ac.Geometry()
VERT_REC = namedtuple("VERT", "X Y Z idx")
verts = [VERT_REC(v.X(), v.Y(), v.Z(), i) for i, v in enumerate(geom.Vertices())]
_min, _max = min(verts), max(verts)
print "Min vert value in", ac.Name(), "=", _min
print "Max vert value in", ac.Name(), "=", _max
And a bit extended:
from collections import namedtuple
sc = poser.Scene()
ac = sc.CurrentActor()
geom = ac.Geometry()
VERT_REC = namedtuple("VERT", "X Y Z idx")
verts = [VERT_REC(v.X(), v.Y(), v.Z(), i) for i, v in enumerate(geom.Vertices())]
_min, _max = min(verts), max(verts)
print "Min vert value in", ac.Name(), "=", _min
print "Max vert value in", ac.Name(), "=", _max
left_verts = [v for v in verts if v.X < 0]
right_verts = [v for v in verts if v.X > 0]
zeros = len(verts) - len(left_verts) - len(right_verts)
if zeros == 0:
print "There is no zeropoint in X."
else:
print zeros, "Zero points"
# now with tolerance
tolerance = 0.001
left_verts = [v for v in verts if v.X <= -tolerance]
right_verts = [v for v in verts if v.X >= tolerance]
zeros = len(verts) - len(left_verts) - len(right_verts)
if zeros == 0:
print "There is no point between", -tolerance, "and", tolerance
else:
print zeros, "Zero points with tolerance +/-{}".format(tolerance)
adp001 posted at 8:01AM Mon, 09 March 2020 - #4382800
Sure that 0 is always the center?
Output from second script:
Min vert value in Hip = VERT(X=-0.06517799943685532, Y=0.4192480146884918, Z=-0.0016769999638199806, idx=314)
Max vert value in Hip = VERT(X=0.06517799943685532, Y=0.4192480146884918, Z=-0.0016769999638199806, idx=787)
36 Zero points
36 Zero points with tolerance +/-0.001
I'm pretty sure it is, since this is clothing I made for a figure (La Femme). The mesh was made in Blender, left vertices deleted, centerline vertices forced to 0 (just in case anything misaligned them), then a mirror mod applied to the remaining mesh and the mesh cleaned to merge effectively duplicate points and strays.
But my logic only works for something symmetric, centered globally... Hmm....
Here are the vert indexes and their x_mirror (for the hip actor). They're actually ordered nicely probably because of that mirror mod
474 1
475 2
476 3
477 4
478 5
479 6
480 7
481 8
482 9
483 10
...
940 467
941 468
942 469
943 470
944 471
945 472
946 473
Hmmmm... I see a problem even in a perfectly symmetric mesh. When Poser auto groups in rigging, I have no guarantee that it will portion out the mesh symmetrically. Probably, I need to go more along the lines of combining the actor geoms into a single mesh, keeping a map of actor.geom_verts to unified_mesh.verts, do what I need to do with morphs in the unified mesh, then write those deltas back to the actor geoms using that map.
I'm clearly taking a naive approach to this. I seem to recall a lot more complexity in the thread about obj based bridge. Your bridge, no?
Don't test the middle actors. Try the arms, legs, fingers, feets.
One has to find the true center inside a bounding-box of an actor. Then part right and left along this center. Sorting this parts, and left[0] should theoretically match right[0]. If the vertices are used like I did above, each namedtuple vertex.idx points to the original position in the array.
If the mesh is not 100% symetrical, one can use bisectLeft() from lib bisect to find matches fast.
(yes, I scripted the bridge)
Thank you for the code and tests!
Figure.UnimeshInfo()
In your MorphTools, it looks like I could extend it with the few things I need.
Using the unimesh geom, I can check for symmetry & create the mirror map, get the deltas for a specific source morph, create the new morph in the appropriate actor(s), write the deltas using the mirror:unimesh map and the actor.geom map(s), then create the master in the body.
critically asserting that unimesh is symmetric about x=0.
Feel like I'm missing something. time to test and see what explodes :D
adp001 posted at 12:54PM Mon, 09 March 2020 - #4382938
Don't test the middle actors. Try the arms, legs, fingers, feets.
Right, soon as you mentioned it I got that problem. Didn't even think about it as the part I made was all torso :D
Can't I test it using the GEOM from figure.UnimeshInfo()? Like how you're doing the bridge import/export?
From that, I use a nested dict (ie: dict[x][y][z] = unimesh.geom_index) so I can run through it and look for dict[-x][y][z]; any failure there is a failure in symmetry.
I just have a lookup of geom.vert_index to geom.mirror_vert_index. where x=0 => vertex_index = mirror_vertex_index
for each vert, I can grab the delta. I write that into the correct actor.geom.vert_index using my map to get mirror_vert_index, then mapping that vertex back to the appropriate actor.geom.vert_index using the UnimeshInfo.
Right? o.o
I need coffee
from collections import defaultdict
PRECISION = 3
def nested_dict(n, type):
if n == 1:
return defaultdict(type)
else:
return defaultdict(lambda: nested_dict(n-1, type))
def get_mesh (geometry):
mesh = nested_dict(3, int)
i = 1
for v in geometry.Vertices():
(x, y, z) = int(v.X()*10**PRECISION), int(v.Y()*10**PRECISION), int(v.Z()*10**PRECISION)
mesh[x][y][z] = i
i += 1
return mesh
def check_symmetry (mesh):
sym_map = dict()
try:
for (x, a) in mesh.items():
for (y, b) in a.items():
for (z, i1) in b.items():
if x == 0:
i2 = i1
else:
i2 = mesh[x*-1][y][z] # index of X mirrored vertex
if i2 == 0:
raise KeyError()
sym_map[i2-1] = i1-1
return sym_map
except KeyError:
print "Non-symmetric vertex:", i1, i2
return None
SCENE = poser.Scene()
FIGURE = SCENE.Figure('LaFemme')
ACTOR = FIGURE.Actor('Right Thigh')
#GEOM = ACTOR.Geometry()
(GEOM, actor_list, per_actor_vertices) = FIGURE.UnimeshInfo()
MESH = get_mesh(GEOM)
SYM_MAP = check_symmetry(MESH)
print SYM_MAP
LaFemme, it seems is only symmetric to this precision. Using this on a non-torso part barfs on the first vertex test :)
I can use the resulting SYM_MAP with the mappings supplied by unimesh() and reverse (mirror, flip) any FMB, I think.
Here is a very simple approach:
Given a class that can hold vertex coordinates and an index. Like this (simplyfied):
class IndexedVertex(object):
__slots__ = "ar", "idx"
def __init__(self, *args, **kwargs):
self.ar = array("f", *args[:3])
self.idx = args[4] if len(args) >= 3
else kwargs.get("idx", None)
def set_index(self, value=None):
if value:
self.idx = value
return self
Now you create an array with all the points of a geometry packed in the vertex class (simplified):
ar = [IndexedVertex(v.X(), v.Y(), v.Z(), i) for i, v in enumerate(geom.Vertices())
Then you sort this list (if necessary with focus on the mirror axis; this can be done in the vertex class).
In an ideal situation the sorted array should contain mirrored points at (vertex[0], vertex[-1]), (vertex[1], vertex[-2]), and so on. Because each vertex carries his original index, anything else is simple.
I wrote a whole class now. Looks long and complicated, resulting in low speed. But it isn't. It's very fast. My desktop machine needs 0.08 seconds to build 100.000 entries in a standard list with random coordinates.
(No special characters in there, so i can post it here):
from array import array
X, Y, Z = 0, 1, 2
class IndexedVertex(object):
__slots__ = "ar", "idx"
def __init__(self, *args, **kwargs):
self.ar = array("f", args[:3]) if args
else array("f", (0,0,0))
if len(args) >= 4:
self.idx = args[3]
else:
kwargs.get("idx", None)
def __getitem__(self, item):
return self.ar.__getitem__(item)
def __eq__(self, other):
return self.ar == other.ar
def __gt__(self, other):
return self.ar > other.ar
def __lt__(self, other):
return self.ar < other.ar
def __ge__(self, other):
return self.ar >= other.ar
def __le__(self, other):
return self.ar >= other.ar
def __add__(self, other):
return self.__class__((a + b for a, b in zip(self.ar, other.ar)), idx=self.idx)
def __radd__(self, other):
return self.__class__((a + other for a in self.ar), idx=self.idx)
def __sub__(self, other):
return self.__class__((a - b for a, b in zip(self.ar, other.ar)), idx=self.idx)
def __rsub__(self, other):
return self.__class__((a - other for a in self.ar), idx=self.idx)
def __mul__(self, other):
return self.__class__((a * b for a, b in zip(self.ar, other.ar)), idx=self.idx)
def __rmul__(self, other):
return self.__class__((a * other for a in self.ar), idx=self.idx)
def __div__(self, other):
return self.__class__((a / b for a, b in zip(self.ar, other.ar)), idx=self.idx)
def __rdiv__(self, other):
return self.__class__((a / other for a in self.ar), idx=self.idx)
def __pow__(self, other):
return self.__class__((a ** b for a, b in zip(self.ar, other.ar)), idx=self.idx)
def __rpow__(self, other):
return self.__class__((a ** other for a in self.ar), idx=self.idx)
def __abs__(self):
return self.__class__((abs(a) for a in self.ar), idx=self.idx)
def __repr__(self):
ar = ", ".join(map(str, self.ar))
return "{}([{}], idx={})".format(self.__class__.__name__, ar, self.idx)
def __str__(self):
return "(X={}, Y={}, Z={}, idx={})".format(round(self.X, 5),
round(self.Y, 5),
round(self.Z, 5),
self.idx
)
def __getstate__(self):
return [self.X, self.Y, self.Z, self.idx]
def __setstate__(self, state):
self.ar = array("f", state[:3])
self.idx = state[3] if len(state)>3 else None
@property
def X(self): return self.ar[X]
@X.setter
def X(self, v):
self.ar[X] = v
@property
def Y(self): return self.ar[Y]
@Y.setter
def Y(self, v):
self.ar[Y] = v
@property
def Z(self): return self.ar[Z]
@Z.setter
def Z(self, v):
self.ar[Z] = v
def set_index(self, value=None):
if value is not None:
self.idx = value
return self
def rounded(self, precision):
return self.__class__(round(self.X, precision),
round(self.Y, precision),
round(self.Z, precision),
self.idx
)
def round(self, precision):
for i in range(len(self.ar)):
self.ar[i] = round(self.ar[i], precision)
return self
Here is how to build sorted indices (time to build index from 100.000 vertices: 0.06 seconds):
def sorted_index(vertices, axis=0):
return [vert.idx for vert in sorted(vertices, key=lambda v: v[axis])]
Building an array with vertices from a Poser geometry:
geom = poser.Scene().CurrentActor().Geometry()
vertices = [IndexedVertex(v.X(), v.Y(), v.Z(), idx) for idx, v in enumerate(geom.Vertices())]
Then you can something like this:
sorted_X = sorted_index(vertices, X)
sorted_Y = sorted_index(vertices, Y)
sorted_Z = sorted_index(vertices, Z)
vertices[sorted_X[0]] has the vertex with the lowest X, vertices[sorted_X[-1]] has the highest. And the same for the other ones, Y and Z.
And the centerpoint in each axis should be vertices[sorted_n[len(sorted_n)/2]]
Now my brain really hurts.
I'll have to go through that after some coffee.
I went back to basics and thought about what I was doing. And came away NOT understanding unimeshinfo. I thought I did. I thought you got 3 parts that made sense. A kind of convenience geometry that was little more than the contact of each actor.geometry.vertices. And, indeed
len(unimesh.geometry.vertices) = sum(len(actor_vertex_map))
which would make sense if each vertex in actor.geometry results in a vertex in unimesh geometry.
But if that held, then [unimesh.geometry.vertex_indices].sort() should = sum([actor_vertex_map]).sort()
and it doesn't. Some of the unimesh vertex indices are missing from the mappings, and some are doubled.
How can a unimesh vertex NOT have come from an actor vertex???? If they all come from actor vertices, where did the mapping go????
Hmm... I suppose I can just sum the actor meshes myself. I think that would work for my purposes. Fundamentally, for each actor vertex, I need to know the mirror vertex (regardless to the actor(s) it's in). If I have a map of vertex to mirror_vertex, then I'm just reading parm.morphdelta from vertex and writing new_parm.morphdelta to mirror_vertex.
I get (a bit) what you're doing. But I'm missing the "prove the mesh is symmetric (to precision) logic" and the resulting vertex to mirror_vertex mapping. Which is the essential goal of this.
OK. This seems to work:
Mirror a morph (create a "flip" or "reverse") morph on a symmetric mesh actor or set of actors that, combined, yields a symmetric mesh.
IE: a symmetric Hip mesh or a Hip+rThigh+lThigh mesh. This is symmetric about X=0 (the presumed centerline of a body)
Reason: to only have to create JCMs on one side of a symmetric clothing mesh, then having those automatically made into the other side.
Mirror caveats: symmetric about x, with centerline of x=0, to a specific precision. For my mesh, this is out to over 6 digits. For LaFemme mesh, it's only 3.
The script exits (with message) without doing anything if the mesh is not symmetric.
The approach: unimeshinfo seems to produce a mesh that's incorrect. That is, not every vertex_index in the unimesh GEOM is mapped by the sum(actor_vertices). I don't know why. Spent days on that. Since I'm dealing with morphs which will come down to actor parms and geometries anyway, I just create a "unimesh" by combining the verts of all actors involved.
Then I make a 3 key recursive dict on vert[x][y][z] = list_of_actor.verts_with_that_coordinate (with XYZ being ints to specific precision). Once I have that dict, I can check each point for either x=0 (centerline) or [-x][y][z]. Any keyerror means asymmetric. While I'm doing that, I also make a lookup each point to it's mirror point (that is, the mirror (actor_idx, vert_idx). With that mirror lookup, I can go through a morphtargetdelta (by actor,vert) for a source morph and write a new morph to the mirror (actor,vert). And combine them with a master control at the body (if I want)
I only have to do the indexing once (and it's pretty quick), then I can read and write the all the morphs. In essence, for a symmetric figure, I can do one half the JCMs (and any other reversible FBM) then produce all the opposite side morphs in seconds.
Now for testing...
thoennes posted at 4:28AM Sat, 14 March 2020 - #4383501
OK. This seems to work:
Mirror a morph (create a "flip" or "reverse") morph on a symmetric mesh actor or set of actors that, combined, yields a symmetric mesh.
IE: a symmetric Hip mesh or a Hip+rThigh+lThigh mesh. This is symmetric about X=0 (the presumed centerline of a body)
Just for the records:
Lets take the hands as an example- Left and right, both don't start at X-coordinate 0. But - if you add the lowerst X-coordinate from both geometries, you get zero. Or, with other words: Assuming 2 symetric points from left and right hands geometry, the distance to 0 is equal for both of them. The bodys centerpoint can be ignored :)
The approach: unimeshinfo seems to produce a mesh that's incorrect. That is, not every vertex_index in the unimesh GEOM is mapped by the sum(actor_vertices).
If you simply add up all actor vertices. you add a bunch of duplicates. Each point on the seam of a bodypart is counted twice: In the current actor and in the actors parent.
That is why unimesh exist: Unimesh geometry is not splitted in parts. To preserve vertex count, the Poser-programmers used to set the duplicate coordinates to 0, 0, 0. This works (each vertex stays in his original position), but leads to another problem: No polygon refers to this entries. So external modeling apps want to remove this bad vertices. Some while loading this object, others don't want so save unused points. But that's another story. Fact is: Adding up the vertices and comparing with unimesh can't work.
Then I make a 3 key recursive dict on vert[x][y][z] = list_of_actor.verts_with_that_coordinate (with XYZ being ints to specific precision).
Maybe it's not a good idea to use numerics as keys for a dictionary. They are not made for this (look into the Python docs for deeper info how dictionary keys are handled internally). Beside of that, a dictionary needs a lot of memory. And you are using three of them per vertex...
Gotcha!
The hands don't start at x=0 but are symmetric about x=0. Combining the verts for each mesh, for each vert v@xyz would have a corresponding v@-x,y,z. If this is true for all non x=0 verts, then the combined mesh is symmetric. For multiple verts with the same x,y,z, there must be the same quantity of verts with -x,y,z
my awk days tend to push me towards dicts for stuff :D
I knew about the welded seams but I didn't know about 0,0,0 of the dupes. That explains it. Actually, I did find this out in another thread, regarding your blender bridge, and I looked through that code.
I actually check the verts that are colocated. They should all have the same delta. If they don't, the points would separate as the delta is dialed. I assume Poser somehow tags shared (welded?) verts or edges? I think I get this. let's say there are two actors, with a quad each, and one edge shared between them. In poser, per actor, there are 4 verts in each actor. Each with an x,y,z and dx,dy,dz. A unimesh would have 6 verts, with the 2 dupes dropped. But in actor sum, there are 8 verts, with 2 of them having the same x,y,z and dz,dy,dz. For me, that's ok. Each x,y,z is actually a list of verts. Most have only one. Some have more. They may be a mesh that for whatever reason has colocated points. Of they may be a vert shared by actors. Still should be the same delta for each colocated vert, right?
I know it's not a good idea to use dicts. My programming instinct says "aw, hell no". But python doesn't have sparse arrays (right?). numpy does? I do convert the xyz to ints, rather than reals. Mostly because I originally wanted to do it as sparse arrays. Any time I can use ints, I prefer it. Past financial systems development where money error are very not good :D
I wish Poser used ints and a units rather than reals.The rounding is ugly in the dials.
I went with your class based structure although I probably went overboard "classing it up" (too much JS programming). I think I'm catching all the errors that would leave a messy state. I just tested this on a mesh (pants, consisting of a hip and waist) and it mirrored all the JCMs in about a second. Most of that time printing the info messages. Even with the classes and dicts. Granted, it's only a suspiciously even (but real) 5000 verts :D
My first run through I totally forgot that the mirrored delta_x's had to be negated. Duh!
In any case, it works! I can now just do the JCMs on one side of a symmetric figure and mirror them all to the opposite side.
I'm going to look at numpy arrays. Also, the classes are carrying info they don't really need to. quarantine (social distancing, lockdown, etc.) gives a lot of time to code.
Thank you your help. To do the morphmaking and masterparameter linking, I used a bit of your code. I tried to make my variables the same so the code just drops in. And it does. Although I tend to use try/except/raise
Hope you're safe!
Cheers
If using integers where such a good idea, most programmers would use integers. But they don't. For a reason. Using integers instead of float did make sense in the old times we had seperate floatingpoint processors. But not in all machines or just slow ones.
Python works with 64-bit integers by default. If you use floats (internally Python uses C-doubles), your maths are more then precise enough. Use decimals if you want to avoid rounding errors, especially for financial maths. Or use (lib) array as I did above. array.array("d",....) gives you 64-bit double-precision floats. But be ashured, this is not required while working with morphs in Poser. There is a problem with code written in C using single-precision floats in some 3D-modules. Or numpy forced to single precision in some Python code.
Regarding to numpy: array.array() needs less memory. That's while I use it often ;) (but numpy is more common)
thoennes posted at 5:56PM Mon, 23 March 2020 - #4384260
Gotcha!
The hands don't start at x=0 but are symmetric about x=0.
Thats true for hands in Poser. Normally. But generalized centerpoint is min(bodypartLeft.XYZ) + max(bodypartRight.XYZ) for bodyparts in a symetric figure.
Working with distances instead of or in addition to absolute coordinates is another aproach to find symetric points that do not fit each other perfectly.
Annnnd, I just hit that problem O.O
No way to covert the XYZ to a world coordinate?
In financial apps, we used bigdecimal (or int). The problem with reals or floats comes then a heap of opperations are performed. Is there a reason Poser is so inaccurate in it's GUI, then? Often, I put a number in. Say "-0.4". When I get that number back I get something like 0.3999987 (example, not real). Or when I read the number out in Python, it's not the exact number I put in. That seems to have something to do with the number I put in being converted. Also, the GUI rounds the number for presentation, but that's not the real number internally. What gives?
I tend to round values in python when dealing with morphs as it seems useless to carry information that's not visible. I have a script that cleans up keyframes (removes keyframes where there is no consequential change from previous to next). I don't like when Poser drops a keyframe for every single parameter, even though I have't changed the value. When I go back later to animate that parameter at some other frame, I have to go back an clean up the key that was dropped into place. instead of moving from frame 0, value 0 to frame end value end, it has a key holding o at some in between frame. plus, I don't like keys that don't need to be there. Why have keys on a parameter that never changes? When I clean those up, sometimes there is a tiny little value (usually a mistake as on Mac the mouse doesn't always "let go" after button up). So there's a 0.00000123 value on some parameter, in the middle of the animation, bookended by zeros. My script drops that keyframe. The movement is essentially zero.
Now I need to convert to abs coords. Nuts. The script is so nice. the JCMs in LaFemme are extensive.
Hmmmm.
Can you think of a situation where a shared edge between two body parts (Chest and Right Collar), where a shared vert has exactly the same XYZ but different deltas for the same morph?
Chest -0.0453279986978 0.620618999004 -0.0246409997344 Right Collar -0.0453279986978 0.620618999004 -0.0246409997344 (0.0021, -0.00229, 0.00091) (0.00215, -0.00238, 0.00093)
They're close. But not exact. I can always force them to be the same. But I want to understand why they're not. Shouldn't that play hell when the morph in activated???? O.o
The two (right and left) collars:
0: (-0.0506439991295, 0.594950973988, 0.00305499997921)
1: (-0.0431739985943, 0.610477983952, -0.046967998147)
2: (-0.0440779998899, 0.613897979259, -0.0442500002682)
3: (-0.0449649989605, 0.616851985455, -0.0409809984267)
4: (-0.0455849990249, 0.61890900135, -0.0375140011311)
5: (-0.0460539981723, 0.620163977146, -0.0336049981415)
6: (-0.0462599992752, 0.620572984219, -0.0291729997844)
7: (-0.0463039986789, 0.620172023773, -0.024606000632)
8: (-0.0462549999356, 0.619189023972, -0.0201709996909)
9: (-0.0461920015514, 0.61769002676, -0.0157299991697)
10: (-0.0459779985249, 0.615665018559, -0.0118960002437)
11: (-0.0456600002944, 0.613122999668, -0.00828699953854)
12: (-0.046849001199, 0.598013997078, 0.00330400001258)
13: (-0.0473570004106, 0.60199201107, 0.000852999975905)
14: (-0.0508389994502, 0.60016900301, 0.000780000002123)
15: (-0.0476580001414, 0.605727016926, -0.00185500003863)
...
0: (0.0506439991295, 0.594950973988, 0.00305499997921)
1: (0.0431739985943, 0.610477983952, -0.046967998147)
2: (0.0440779998899, 0.613897979259, -0.0442500002682)
3: (0.0449649989605, 0.616851985455, -0.0409809984267)
4: (0.0455849990249, 0.61890900135, -0.0375140011311)
5: (0.0460539981723, 0.620163977146, -0.0336049981415)
6: (0.0462599992752, 0.620572984219, -0.0291729997844)
7: (0.0463039986789, 0.620172023773, -0.024606000632)
8: (0.0462549999356, 0.619189023972, -0.0201709996909)
9: (0.0461920015514, 0.61769002676, -0.0157299991697)
10: (0.0459779985249, 0.615665018559, -0.0118960002437)
11: (0.0456600002944, 0.613122999668, -0.00828699953854)
12: (0.046849001199, 0.598013997078, 0.00330400001258)
13: (0.0473570004106, 0.60199201107, 0.000852999975905)
14: (0.0508389994502, 0.60016900301, 0.000780000002123)
15: (0.0476580001414, 0.605727016926, -0.00185500003863)
They are mirrors of each other. The order is nicely convenient. Probably something blender does and poser just reads them in order from the OBJ file?
If Poser always uses the data from the same body part (and ignores the others), there will be no visible error. So the question is: How does Poser work internally?
Posers morphsystem works perfectly well. So I assume Poser uses always data from the same bodypart.
What you can do to find out more: From an unmorphed figure export one bodypart. Morph one edge-vertex with an external modeller. Import as morph. Do the same for the other bodypart and compare the result.
My assumption is that Poser will set both affected body parts when importing. Who or what will cause a difference in the values remains IMHO unclear, because it can happen in many places. The only important thing is that Poser stays consistent and uses the same entry over and over again.
But since the morph system works well, I think it's more of an academic question.
Victory!
I did the vertex-by-vertex thing. And discovered two vertices invisibly close together. In the same location, when rounded off (as I do). But not in the exact same location, to the precision with which OBJ was exported (and to which Poser keeps). So I treated them as the same point, but poser didn't. Thus, the vertex & mirror was wrong and the delta was wrong. Because I was treating two separate points as the same. Derp!
Two lessons here. Be very careful modeling and (in blender) make sure to clean up the mesh before exporting. I had done an offset that was very tiny, during my modeling process and did not get rid of it after I used it. And unless zoomed in crazy close, It was effectively invisible.
I also found a couple things about my code. One, I was making a mistake in creating the mirror morphs. I was making the morphs, then adding deltas. But, I only needed to make a parm for the actor if at least ONE vertex had a non (0,0,0). Fixed that. It was creating parameters that weren't needed. Two, the code and it's checking for symmetry and delta variation.... If the mesh is supposed to be symmetric and it's not, then I have an error I'm not seeing in the mesh. The code is solid on this check. Another check: delta variance for colocated vertices. All the vertices at the same x,y,z must have the same deltas. As you pointed out: Poser works. So it must be my error. And, in this case, it was. My code was rounding non-colocated vertices to be the same. And they weren't.
The final saving grace: if there's anything that doesn't make sense, the code exits before doing anything. I built the new morph in a data structure. When everything checks out, then I write the struc to an actual morph. This saved a lot of hair pulling. I just ran this on a top, with left and right collars, abdomen, and chest (with geometries) and waist, shoulders, and neck (for influence). I started with the JCMr(arms) from the LaFemme HR dev rig. I tuned the joints themselves, then very little JCM is needed. I added the right side (both top and bottom). Then ran my script and it exactly created the mirrored JCMs for the left side. Probably saved a day of morphing for each clothing item. Plus, accurate. I can concentrate on the one side morphs and really nail them with precision. Not having to do the other side is a HUGE relief :D
Quick GPU render. No morph tweaks in the clothing. It's subd=1. I figured out a really nice way to do edges with very few quads (uses smoothing and subd to round), and there are mats at the edges that make it very quick to morph the clothing quickly and with a high level of control (in fact, much using fit and smooth brushes). That is a stock La Femme, nothing but her joints and JCMs. Same for the clothing. The place the fit brush fails is, of course, when actors overlap or are too close. But then the mats give precise control over moving and smoothing parts of the edge.
Incidentally, I've noticed that messed up OBJs may be imported by Poser and look fine. But sometime later, when I do something like copy morphs or joints from some other figure, Poser may crash. I wonder if this is triggering similar things as I found while working on the code. For instance, I was making an assumption about the number of mirror verts for a vert (and there should always be at least one, in a symmetric mesh), but I went for the first mirror vert (index 0) just to see what it was, and got an index error on one vert. (There was no mirror because I had an error in the code O.O ). Obviously, Python snags the index error but Poser might be more brittle in it's trapping. Because I did get a crash on the mesh that failed the delta-variance check. When I would copy morphs from a previous version.
The code works but wow, I need to document it before I forget how it works and why I did some things the way I did :D
This site uses cookies to deliver the best experience. Our own cookies make user accounts and other features possible. Third-party cookies are used to display relevant ads and to analyze how Renderosity is used. By using our site, you acknowledge that you have read and understood our Terms of Service, including our Cookie Policy and our Privacy Policy.
The API doesn't seem to expose everything that can be done using the GUI. In this case, there's no obvious slam dunk "copy" method for a morph, nor do I see a "mirror" option. Really, all I want is a "create reverse morph"
I use both functions to quickly create reversed JCMs on symmetric clothing. Obviously, a huge time savings :)
Here's the manual process I use. Prep:
For each JCMr in the body actor: (in this case, I made all the right JCMs)
Now there's a correctly named reverse morph wired from body through the actors. When conformed, it will work magically like the original.
What stops easy scripting is that there is no copy or mirror for morphs in the Python API. I suppose one could go through the geometry, vertex by vertex and create a manual mirrored copy. Surely someone has done this? I hate to recreate obvious code. I'm "engineer" lazy :D