Grappling with .egg Transform matrix

I am trying to get the Poser2egg.py exporter working fully, to finish the abandoned project at https://github.com/is-blackhole/poser2egg. The effort is going well, aside from trouble with the matrix handling for the joint transforms. The only information I have about the formatting of these matrices is from the eggSyntax documentation at http://panda3d.cvs.sourceforge.net/panda3d/panda/src/doc/eggSyntax.txt?view=markup and whatever I can extrapolate from the Blender .egg export scripts. So far these sources aren’t leading me in the right direction.

In Blender the exporter seems to use the usual Blender process of modifying the armature bone rest matrix for each joint by the inverse rest matrix of its parent. In Blender, this is a fairly simple and common process. In Poser, it is a bit more obscure. Poser offers no built-in equivalent to the armature rest matrix, so I need to try to construct it. This involves a bunch of assumptions which apparently aren’t correct. I assumed that I needed to include information about each joint’s origin and orientation in the matrix. It seems like this information needs to be included somewhere in the .egg file, and this seems to be where it should go. But… no dice. The results are bad with every variation on this idea that I attempt. I need to understand more about what Panda3D wants in the Transform matrix of the .egg file.

Does anyone know anything about this? I’ve been sort of groping along blindly, and at this point any hints or revelations would be helpful. What does Panda need, in a Transform matrix?

Also, hiya. :smiley: I am new to this community and about a week into trying to work with Panda 3D. I’ve worked with Poser, Blender, Game Maker, and Dark Basic Pro in the past, and I’m pleased to find a way to bring Python coding, 3D graphics, and game creation together in a single bundle. I’m excited about Panda, but current efforts have stalled while I try to find a path to covert parts of my projects from other software packages into Panda’s file formats.

Hi, welcome to the forums! :slight_smile: It’s great that you’re working on this.

I’m not really sure about the specifics, but reading about the struggles I had in the past (though never resolved) might help:

That is actually helpful. Thank you! It hadn’t occurred to me to reduce my test animation to nothing but the rest pose. Using that approach, I’ve come closer to success.

And yet… there are complications. :laughing: Always complications. Umm. Poser Python is, well, you might say it isn’t very well-maintained or well-documented. Heh. The scale matrices don’t give the right information, the translation portion of the local and world matrices is broken, and some of the results I’m seeing in tests suggest that those local and world matrices aren’t doing what one might expect them to, in the first place. That might actually be good news. It sort of looks like those matrices may contain the armature rest pose embedded in their data. Which would be useful for me in some respects, but tricky insofar as it becomes harder to strip scaling and rest pose data from the matrix so I can isolate the rotation portion.

So… here’s another question, if anyone can take it up. The .egg documentation tells me, regarding the listings:

Yet when I write out a test .egg containing rotation and translation listings and no matrix, Panda3D rejects the file. (The Loader error reporting could be a bit more informative, perhaps. :laughing: )

So… does anyone know how these options might be handled? Can I send rotation and translation instead of a matrix? For that matter, could I format the pose rotations as matrix or quaternion, rather than Euler angles? I don’t seem to have a wholly reliable matrix-to-Euler or quaternion-to-Euler conversion function available. Ah, Euler angles have a demon in them! :laughing:

Does anyone have any idea? :question:

It should be possible to use only translate, scale, rotation information. If you put together a small .egg file that fails to load, then I could perhaps look at why it is failing to load.

Are you using the EggData API or are you writing out the .egg syntax manually? It is always recommended to use the EggData API since it handles a lot of stuff for you (it would also allow you to use the Panda functions for matrix conversion), but you might not be able to use it if Poser uses a different Python version than your Panda SDK is built for.

Are you sure that the Poser matrices are incorrect and don’t simply use a different storage convention (column-major vs row-major)?

Poser matrices should be compatible with Panda matrices. Umm. Pretty sure. Poser uses:

def vector_by_matrix(p, m):
  return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0],
          p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1],
          p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2]]

… which fits in with what the Panda Matrix Representation docs at https://www.panda3d.org/manual/index.php/Matrix_Representation reveal.

define VECTOR4_MATRIX4_PRODUCT(output, input, M)  \
output._0 = input._0*M._00 + input._1*M._10 + input._2*M._20 + input._3*M._30;  \
output._1 = input._0*M._01 + input._1*M._11 + input._2*M._21 + input._3*M._31;  \
output._2 = input._0*M._02 + input._1*M._12 + input._2*M._22 + input._3*M._32;  \
output._3 = input._0*M._03 + input._1*M._13 + input._2*M._23 + input._3*M._33;

I guess that makes them both row major, but I must confess to not really thinking in those terms. I studied the books on 3D math, those that weren’t beyond my level of comprehension, but several months away from programming seems to allow all such knowledge to slip right out of my head. As such, I know how to do a thing or two, but I’m not always able to throw around the correct terminology. :blush:

I’m not actually sure what you’re asking me, about the EggData API. Can you explain?

When I tried to set the Transform data as rotation, I may have misunderstood the form. I used { degrees x y z }, but I think I assumed that it should be stated as e.g. {15.00000, 76.34576, 3.141596}. That doesn’t present a separate “degrees” argument, which omission would have crashed the Loader, I assume. (The Loader really likes to crash on me. :laughing: ) However, if I did make that mistake, I’m really not sure how should be stated in a file.

It really is beginning to look like Poser does actually give me information about the rest pose matrix, but that isn’t available separately, as in Blender. Poser gloms everything together in the local and world matrices, and they seem to reveal the rest pose when an actor is zeroed. This means the rest pose is embedded in any rotation information returned by the matrix, which may or may not be a problem. I haven’t decided yet. :laughing: I’ve been running various tests in Poser, trying to make sense of the matter. No one seems to know about such matters, in Poserdom, or, if they do know, they aren’t talking.

Okay, wow. That was no fun. :laughing:

I seem to have worked out the handling for the Transform matrix, though, and the animation. My test figure is animating properly, and my problem now becomes gathering the vertex weights more effectively. Poser splits its figures into grouped geometry portions, and the only tool Poser Python offers to gather vertex weights returns weights only for an actor itself, not for any adjacent actors which are also affected by a joint. Solving this matter might be tricky. I think it will be easy, compared to puzzling out the Transforms, though.

I ended up having to build my own rest pose matrix for each actor. Given knowledge of the origin, endpoint, orientation, and rotation order of a joint, I was able to generate an orthogonal matrix aligned with the twist axis of the joint. That matrix could then be rotated by the joint orientation to produce the correct rotational portion of the Transform matrix. For the offsets, the root actor uses its true worldspace origin, while each subsequent actor has a delta vector recording the offset from its parent, as parentOffset = childOrigin - parentOrigin. This matrix form was derived by studying sample egg files, and (unlike Blender’s chicken exporter) I did not need to transform an actor’s matrix by its parent’s inverse matrix.

For the animation portion, the main struggle was finding a good conversion from matrix format back to Euler angles. I had to fall back on some code which chooses among six cases, for the six possible rotation orders of a Poser actor, to provide decent Euler conversion. Using that, there’s a high level of accuracy going from Euler to matrix and back to Euler.

Also complicating the animation portion was the trouble with Poser’s built-in matrix reporting. Testing revealed a high error level in Poser’s matrix data, at least for body part actors on a figure. For some reason being a body part makes a difference. :open_mouth: I had to construct my own rotation matrices, again using the known joint order of each actor. This was derived from the input dial settings in Poser, and the resulting matrix was multiplied by the joint matrix for each actor. The final step, after conversion back to Euler, was re-ordering the rotations. Poser’s XYZ had to become YZX for Panda, with the Z sign flipped in cases where Y had been the twist axis in Poser. And all of that seems to do it.

Now… if I ever start talking about doing this sort of thing again, somebody needs to have me committed. :laughing: Almost nothing I needed to know, going into this, was documented anywhere. The best examples I had proved to be completely useless for my situation. The solution finally came through hours or trial and error.

I may or may not have learned some things, though. If anyone has questions, I’ll try to answer them. Anyone who figures out any of this stuff needs to start talking, to get the information out there and make it easier for others in the future. :smiley:

Well… it wasn’t quite where I thought it was. :laughing: And it still isn’t quite there. Mostly. Further testing revealed some anomalies, and after a few days of testing I ended up back where Sartori started, with the Matrix handling. Sending an identity matrix with the proper joint centers gives the best results so far, but that leaves out any additional joint orientation when a joint is not aligned with an axis. As a result, most rotations on my test figure are correct, but the forearms, hands and fingers bend incorrectly.

Any effort at all to set orientation on a joint which is not axially-aligned (which is where it really matters) leads to a misplaced joint which jumps when it rotates. So obviously my matrix handling is off, but I don’t understand matrices well enough to know how or why. :frowning: Every resource I can find which relates to this issue says the same thing: modify a joint’s rest position matrix by the inverse of its parent’s rest position matrix. Unfortunately every resource starts with those rest position matrices being already available. In Poser, I need to calculate them, which is our problem.

Example code exists for calculating the rest position matrix, in the Blender source code and the MakeHuman source code. These both do the same thing, deriving the twist axis from the origin and endpoint of a bone/actor/joint, then using a known roll angle to create a matrix as an axis-angle calculation. I have the axis, I have an angle that should be roll (Poser’s orientation around that twist axis)… but my application of this approach is wrong in every variation I’ve attempted.

Clearly this project needs some expert input, but no one is talking. :laughing: I find a lot of people asking about how to do this, in my research, and no one answering them. Anyone who knows how to do this sort of thing seems to obey some code of silence. If anyone around here has any ideas, or a clear knowledge of matrix handling, please speak up.

The attached image shows my test figure in Panda. I hope a naked low-res Antonia with bad joints is not offensive or NSFW or something. Poser doesn’t offer vertex weight information in the form I need, so I still need to do some work before the weights will be correct, as can be seen. Really, Poser doesn’t offer most of the information I need in the form I need. :laughing:

I’m kind of wondering if this should have been posted in Pipeline. Can a thread be moved? :question:


Rather than merely ask questions, I’m going to post here the information I have gathered. Even if no one is able to help me sort out the problem now, putting this someplace where others can find it may help other projects in the future.

All of the information I am posting here is from open source code or the Panda documentation.

First, here are the blocks from the Panda3D Egg Syntax docs which seem to relate to the problem:

Regarding the statements:

Regarding the animation handling:

Next, relevant excerpts from the Blender ChickenR91 .egg export script, with my annotations:

"""
        Animation handling from chicken_exportR91.py for Blender egg export.
        Transforms are being extracted from the actor's local pose matrix (act.LocalMatrix() in Poser).
        The modification by the armature matrix ('mat', below) is Blender-specific and can be ignored.
        The actor pose matrix is modified by the inverse of the parent pose matrix, to strip out
        parent transforms.

        The 'SyntheticRootBone' (srb) stuff in chicken_export is Blender-specific, and
        has been removed.

        if not animOnly:
          if not bone.parent:
            self.transform = bone.matrix['ARMATURESPACE'] * mat
          else:
            imat = bone.parent.matrix['ARMATURESPACE'] * mat
            imat.invert()
            self.transform = bone.matrix['ARMATURESPACE'] * mat * imat
          self.weights = {} #dictionary of weights [(weight, poolname):[indices]]

        ...
        
        poses = dict([(name, poses[i]) for i, name in enumerate(boneNames)]) # convert poses to a dictionary like this (bonename:[posematrices])

        ...

        self.armature = armature
        arm_mat = armature.matrix
        self.mat = arm_mat
        if not animOnly:
          self.children = [Group(mesh=m, anim = True,addTangents = addTangents) for m in meshes]
        arm_data = armature.getData()
        bones = arm_data.bones.values()
        self.bones = arm_data.bones
        self.boneNames = arm_data.bones.keys()        

        ...

        # collect local posematrices for every frame and bone in that range
        bones = self.bones
        boneNames = self.boneNames
        armature = self.armature
        mat = self.mat
        poses = [[]for name in boneNames] # lists of local posematrices for every bone        
        ...
        
        for f in xrange(minframe,maxframe+1):
          context.currentFrame(f)
          scn.update(1)
          pbones = armature.getPose().bones          
          for i, name in enumerate(boneNames):
            pbone = pbones[name]
            parent = bones[name].parent
            if parent:
              imat = (pbones[parent.name].poseMatrix*mat).invert()
              poses[i].append(pbone.poseMatrix*mat * imat)
            else:
              poses[i].append(pbone.poseMatrix*mat)

        ...
        
        mats = poses[self.origName]
        for f in aRange:
          mat = mats[f]
          vals = list(mat.scalePart())
          vals.extend(list(mat.toEuler()))
          vals.extend(list(mat.translationPart()))
          vals = map(str, vals) #convert all to strings
        """

To derive the matrix, here, the code pulls up those mysterious rest pose bind matrices, then multiplies the actor matrix by the inverse of the parent matrix. Pretty standard stuff.

The same basic process is then applied to derive the animations, this time using the localspace pose matrices for the bones.

The bind pose/ rest pose matrix contains information about the origin and orientation of the bone, as shown in the code samples below. The pose matrix has this information embedded in its rotations. As noted at rdb’s link (above), the two data sets are being stated in the same form, both times from localspace with parent rotations stripped out.

Excerpted from the Blender 2.37 source code, blenkernel/intern/armature.c:

void make_boneMatrixvr (float outmatrix[][4],float delta[3], float roll)
/*	Calculates the rest matrix of a bone based
	On its vector and a roll around that vector */
{
	float	nor[3],axis[3],target[3]={0,1,0};
	float	theta;
	float	rMatrix[3][3], bMatrix[3][3], fMatrix[3][3];

	VECCOPY (nor,delta);
	Normalise (nor);

	/*	Find Axis & Amount for bone matrix*/
	Crossf (axis,target,nor);

	if (Inpf(axis,axis) > 0.0000000000001) { # This is a test to determine whether nor was parallel to target
		/* if nor is *not* a multiple of target ... */
		Normalise (axis);
		theta=(float) acos (Inpf (target,nor));

		/*	Make Bone matrix*/
		VecRotToMat3(axis, theta, bMatrix);
	}
	else {
		/* if nor is a multiple of target ... */
		float updown;

		/* point same direction, or opposite? */
		updown = ( Inpf (target,nor) > 0 ) ? 1.0 : -1.0;

		/* I think this should work ... */
		bMatrix[0][0]=updown; bMatrix[0][1]=0.0;    bMatrix[0][2]=0.0;
		bMatrix[1][0]=0.0;    bMatrix[1][1]=updown; bMatrix[1][2]=0.0;
		bMatrix[2][0]=0.0;    bMatrix[2][1]=0.0;    bMatrix[2][2]=1.0;
	}

	/*	Make Roll matrix*/
	VecRotToMat3(nor, roll, rMatrix);

	/*	Combine and output result*/
	Mat3MulMat3 (fMatrix,rMatrix,bMatrix);
	Mat4CpyMat3 (outmatrix,fMatrix);
}

#Inpf in Blender code (maybe this is a C++ thing?) is dot product on floats
float Inpf( float *v1, float *v2)
{
	return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
}

This shows how Blender derives, or used to derive, its armaturespace rest pose-bind pose matrices.

Excerpted from the blender2cal3d.py exporter, for Blender 2.37:

def blender_bone2matrix(head, tail, roll):
  # Convert bone rest state (defined by bone.head, bone.tail and bone.roll)
  # to a matrix (the more standard notation).
  # Taken from blenkernel/intern/armature.c in Blender source.
  # See also DNA_armature_types.h:47.

  target = [0.0, 1.0, 0.0]
  delta  = [tail[0] - head[0], tail[1] - head[1], tail[2] - head[2]]
  nor    = vector_normalize(delta)
  axis   = vector_crossproduct(target, nor)

  if vector_dotproduct(axis, axis) > 0.0000000000001:
    axis    = vector_normalize(axis)
    theta   = math.acos(vector_dotproduct(target, nor))
    bMatrix = matrix_rotate(axis, theta)

  else:
    if vector_crossproduct(target, nor) > 0.0: updown =  1.0
    else:                                      updown = -1.0

    # Quoted from Blender source : "I think this should work ..."
    bMatrix = [
      [updown, 0.0, 0.0, 0.0],
      [0.0, updown, 0.0, 0.0],
      [0.0, 0.0, 1.0, 0.0],
      [0.0, 0.0, 0.0, 1.0],
      ]

  rMatrix = matrix_rotate(nor, roll)
  return matrix_multiply(rMatrix, bMatrix)

Note that here the “vector_crossproduct > 0.0” comparison seems (to me, at least) nonsensical. It seems likely that a dotproduct was intended for that test. :question:

This is essentially the Blender process from the above post, restated in Python, possibly with some error in translation where that cross product test is concerned.

From the MakeHuman source code, MakeHuman-makehuman-4bb15a855373.zip, current as of this posting:

#From skeleton.py (also utils.py) in MakeHuman Python source code.
def toZisUp3(vec):
    """
    Convert vector from MH coordinate system (y is up) to Blender coordinate
    system (z is up).
    """
    return np.dot(ZYRotation[:3,:3], vec)

def fromZisUp4(mat):
    """
    Convert matrix from Blender coordinate system (z is up) to MH coordinate
    system (y is up).
    """
    return np.dot(YZRotation, mat)

np = numpy
import transformations as tm

YZRotation = np.array(((1,0,0,0),(0,0,1,0),(0,-1,0,0),(0,0,0,1)))
ZYRotation = np.array(((1,0,0,0),(0,0,-1,0),(0,1,0,0),(0,0,0,1)))

YUnit = np.array((0,1,0))

## TODO do y-z conversion inside this method or require caller to do it?
def getMatrix(head, tail, roll):
    """
    Calculate an orientation (rest) matrix for a bone between specified head
    and tail positions with given bone roll angle.
    Returns length of the bone and rest orientation matrix in global coordinates.
    """
    vector = toZisUp3(tail - head)
    length = math.sqrt(np.dot(vector, vector))
    if length == 0:
        vector = [0,0,1]
    else:
        vector = vector/length
    yproj = np.dot(vector, YUnit)

    if yproj > 1-1e-6:
        axis = YUnit
        angle = 0
    elif yproj < -1+1e-6:
        axis = YUnit
        angle = pi
    else:
        axis = np.cross(YUnit, vector)
        axis = axis / math.sqrt(np.dot(axis,axis))
        angle = math.acos(yproj)
    mat = tm.rotation_matrix(angle, axis)
    if roll:
        mat = np.dot(mat, tm.rotation_matrix(roll, YUnit))
    mat = fromZisUp4(mat)
    mat[:3,3] = head
    return length, mat

This seems to be doing pretty much the same thing as that Blender 2.37 code, with some slight variations in the process. The puzzling switch from MakeHuman Y-up to Blender Z-up before the process is run may indicate that the MakeHuman process is derived from the Blender process. :question:

I’m sorry that I’m not able to help you further, but I can at least reply to your questions earlier:

We have an API to generate .egg files, which is easier to use since it guarantees valid and consistent .egg files and provides useful functions:
panda3d.org/reference/1.8.1 … ggData.php

It is specified as a rotation around an axis. A heading in Panda is a rotation around the Z axis (assuming z-up coordinates), so a heading/yaw of 45 degrees would be specified as:

<Rotate> { 45.0   0 0 1 }

Note that it makes a difference in which order the transformations are applied!

Thanks, rdb. I eventually figured out that the rotation was axis-angle, and I have since been able to implement it in an exported egg file. Sadly, it does not help. :laughing:

Frankly, I would switch to using Yabee with Blender 2.66 and Poser Collada exports, at this point, but Yabee will not export a working file for me, while Chicken does. But to import .dae into Blender I need a later version which doesn’t support Chicken, and saving as .blend from later Blender to port files to earlier versions does not seem to work consistently without errors. So I have no import-export pipeline for conversions, because something is broken everywhere I turn. Which has been typical of my Blender experiences, frankly. And, of course, Collada and X conversion don’t actually work properly in Panda itself. It sort of seems like the only way to use Panda without it being a PITA is to own Maya. :open_mouth:

So I continue to struggle with the Poser exporter, yet Poser is not really giving me access to the information I need in order to make this work. I begin to despair of ever being able to actually use Panda as I had hoped. Alas. :cry:

Edit: Looking at the egg API, I note that the options seem to duplicate those available in egg-optchar, and perhaps one or more of the other binary utilities. There seems to be a lot of overlap of functionality between those utilities. The poser2egg script is set up to process an export with egg-optchar as an option, but so far I have not found any benefit from doing so. Any of the normals modifications available from the Panda utilities only ruin the exported file, and the tangents and binormals don’t seem to handle any differently than those I’m generating with Python. I’m not sure the API really offers any help for me, with this, but thank you for mentioning it. :slight_smile:

An alternative would be to try and get dae2egg to work, but the reason I stopped trying to get animations to work there is because I got stuck on the same sort of trouble that you’re facing with the Poser import. :frowning:

The API is used to create .egg files itself in an easier way, not just to post-process .egg files, though I suppose there’s a benefit to writing out the .egg syntax directly if you want to avoid depending on the Panda3D libraries.

Well, I’m sort of learning this as I go. The poser2egg project has already had two coders working on it, and they started writing things by hand, as it were, rather than using the Panda utilities. This export script runs directly within Poser, by the way, using Poser Python. Poser uses its own specific Python installation, like Panda does, and trying to set up Panda as a site-package for Poser Python to be able to use its functionality would be an overwhelming problem in itself.

I noticed that you encountered the same basic problems I’m having, with your dae converter, which is one of the reasons I posted all of the information I’ve gathered. Obviously this is not an easy problem, and no one on the internet seems to have bothered to compile useful information for reference. Putting whatever we can learn about the matter in some accessible location seems like a good idea. Then maybe someone will come along, some day, and be able to make sense of things.

I am curious about your point regarding the Panda coordinate system orientation, however. In my tests, I always seem to be rotating around the wrong axis when I generate the Transform matrix. I am using the Poser axis, however, and not the Panda axis. So… let’s see. What’s the conversion matrix to put Poser Y-up into Panda Z-up coordinate space? Maybe I can just flip the matrix rows around, to generate a Panda-friendly matrix? Any thoughts?

Use a tag in the beginning of the .egg file to indicate that all of the transformations are in y-up-right instead of z-up-right, and Panda will automatically convert to z-up (or whatever you configured Panda’s coordinate system to be) when loading.

Okay, yes. The coordinate system is being specified for both the figure and the animation, so apparently the Panda-relative orientation of the axis I’m trying to use is not the problem. Drat.

I really can’t make sense of why the Yabee export isn’t working. Upon examination, the only obvious difference between the Yabee and Chicken exports is shown in the attached screengrab.

Chicken has:

<Group> Armature.001 {
  <DART> { 1 }
  <Group> Figure_1_node {
    <VertexPool> Figure_1_node {

Whereas Yabee has:

<Group> Figure_1_node {
    <Dart> { 1 }
    <VertexPool> Figure_1_node {

Someone in the Yabee thread seemed to note the problem of lacking that outermost group, but nothing seems to have come of it, in that thread. Trying to hand-edit the Yabee egg file to add the Armature.001 group doesn’t fix anything. The file won’t open in Pview, and Panda throws one of its ambiguous Loader errors when I try to use it. Loader really doesn’t like to tell us why it’s complaining, I notice. :laughing: When I’ve seen this kind of error with the poser2egg exporter, it’s usually been due to bad bracket handling. Lacking an egg editor with a collapsible treeview, however, it’s hard to scan a file to be sure where the problem may lie.

We really don’t seem to have the tools, in this Panda-verse, sadly. :cry:

Thanks for your help. :slight_smile:


What’s important is that the hierarchy of the joints relative to the root of the animated model (the group with the tag) matches up with the hierarchy of the animation table. If you change the hierarchy or the name of the character in either place, you have to also update it in the other place to make it work. You can use egg-optchar to verify both. If they match, pviewing both files simultaneously should automatically start the animation playing.

That does make sense, but my trouble with Yabee (which is admittedly just a bit off topic) isn’t with the animation. The model itself will not open in pview or Panda. Yabee could not open it in pview when the model was exported. The only indication I have of how the Yabee model might be broken comes from the Panda Loader module, with its standard loading error:

This is an error message which is… less than informative. :laughing: It tells me the Panda Loader has had a problem, and I have looked up the Loader code. It tells me nothing specific about the model or why Loader refused to process it, so I am left guessing about what the problem might be. When I have caused this error with my Poser exports it has usually been because I created an egg file with bad bracket handling, such as accidentally closing a block before adding the Rotate section. I have checked the Yabee export and the opening and closing bracket counts at least match, so that isn’t the problem. It’s a puzzle. :question:

With respect to your Collada importer, I could provide you with the same file in Poser cr2, Poser Collada export, Blender .blend import, Chicken .egg export, and converted .fbx and .x formats. Having one consistent file in multiple forms, perhaps you could reverse engineer the transform matrix handling. That’s what I’ve been trying to do, but I don’t have the programming chops that you seem to have. :laughing: