Help with bone position/rotation export script

Moho allows users to write new tools and plugins. Discuss scripting ideas and problems here.

Moderators: Víctor Paredes, Belgarath, slowtiger

User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Help with bone position/rotation export script

Post by torncanvas »

We need to create a script to export bone positions and rotations per frame. I'm new to scripting with ASPro, so I'm wondering if I could get some suggestions for how to go about doing this.

Here's a little background for how things are set up: All the bones are in one bone layer, and then there are several vector layers with shapes region bound and some points rigid bound to the bones. The bones are offset for binding the legs.

Based on what I've learned so far, it seems like the easiest way would be to make this an embedded script, since it will automatically run on every frame. Right?

So then the next step is to go through each bone (by name hopefully) and spit out the position and rotation. Is there a specific function I need to call to get the true position and rotation because of the bone offset?

EDIT: One other thing - is there a way to save to a text file? I can't seem to find one.

Any other advice? Thanks in advance!
Last edited by torncanvas on Mon Jan 07, 2008 9:32 pm, edited 1 time in total.
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

I am working on a script to do just this very thing. It isn't near finished yet so I don't know what your deadline is.

However there is a script already that does this. It is a Macton script I believe. If you look in the script forum for Macton scripts it is a script for saving out layer animation or something like that. The side effect is that it saves out bone animation that can be loaded in to another layer.

The reason I am working on my own version is that the other one has more than it needs and I want to add my own features.

The last I heard the Macton scripts might be unavailable. If they are I can try to track down the one I have and put it on my web site for download.

-vern
User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Post by torncanvas »

That would be awesome, but I'm trying to get this done in the next day or two and I think I've almost got it. Here's what I have so far, but the position is not showing up:

Code: Select all

--[[
	Embedded script for exporting bone positions and rotations.
]]

function LayerScript(moho)
	if (moho.frame == 0) then
		return
	end
	
	local skel = moho:Skeleton()
	if (skel == nil) then
		print("No skeleton found in layer:", moho.layer:Name())
		return
	end

	for i = 0, skel:CountBones() - 1 do
		local bone = skel:Bone(i)
		local boneName = bone:Name()
		local bonePos = bone.fAnimPos
		print(i .. "(" .. boneName .. "): ", bonePos)
	end
end
EDIT: Oh one more thing. This is really weird, but for some reason my output starts at bone 58 (of 61), then goes through the whole set twice. wtf??
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

First off you had an error in the print function:

Code: Select all

print(i .. "(" .. boneName .. "): ", bonePos)
That comma doesn't belong. That doesn't really matter since this won't work anyway... but you would have at least seen the proper lua error. ;)

fAnimPos (AnimVec2) and fPos (Vector2) are actually a set of 2 numbers for the position, x and y. You can't just print "fAnimPos" or "fPos". You need to get the x or y value and print that. It's easy with fPos:

Code: Select all

bone.fPos.x
fAnimPos is trickier.

fAnimPos is the animated value/channel and needs to be accessed differently. You can "get" this value and assign it to another Vector2 variable but you must specify a frame number.

Best bet is to create a new temporary variable to store it in and then print that value.

Here is the new code:

Code: Select all

   for i = 0, skel:CountBones() - 1 do
      local boneVec = LM.Vector2:new_local() -- creating an empty Vector2 to store the fAnimPos value

      local bone = skel:Bone(i)
      local boneName = bone:Name()
      	  
      boneVec:Set(bone.fAnimPos:GetValue(moho.frame)) -- set boneVec to fAnimPos of the bone
      
      -- assign x and y of boneVec to new variables for printing
      local bonePosX = boneVec.x 
      local bonePosY = boneVec.y

      print(i .. "(" .. boneName .. "): " .. "(" .. bonePosX .. ", " .. bonePosY .. ")")
   end
moho.frame is the current frame. This will print the value of that bone on each frame.

Keep in mind the translation values are CRAZY... 16 decimal places by default. You can do some lua.math magic on it to round it down. I will let you figure that out if you need to.

--------

The "starting with bone 58" thing...
Not sure about why that is happening. Are you sure the names match the order? I just tested it with a skeleton rig I have with 40 bones and it worked. It started with 0(1) and went to 39(40).

If you add and change bone names after they are numbered you will sometimes end up with autonamed bones that don't match the ID number. Bone ID is based on the order they were created... period. Every bone you create is based on that. You can't change it.

Code: Select all

for i = 0, skel:CountBones() - 1 do 
What this is saying is start counting bones based on the total number of bones (CountBones). The i in your code represents the ID number. It should print the numbers properly but not necessarily the BONE NAMES if they don't match the ID number.

EDIT:
If you delete bones that are autonamed, the autonaming is based on the next ID number. If you delete a bone that is autonamed "12" and there are 50 bones, the next autonamed bone will be "50"... you will end up with 2 bones that have the same name since the FIRST autonamed "50" bone is still there. You deleted bone "12" and if you create a new bone it is called "50" not 12. Bone 13 became bone 12 when you deleted bone 12. Isn't this just so clear and simple? ;)

-------

Now the "repeating" bit...

yup, those darn layer scripts repeat 3 times for each frame. I created a function that forces a layer script to only run ONCE per frame. There is some extra stuff you have to add in if you want to use it. If you are writing to an external text file it probably isn't necessary. If you write to an external file 3 times the file only has it one time. It isn't going to print to the text file 3 times.

I wrote this fast so I may have made a mistake in my explanations. I know this stuff... but I don't "think about it" when I do it. It is weird to write it down.

-vern
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

Writing to a file is tricky but once you get it working it is wonderfully easy and fast.

I'm working on a simple sample you can use to write the bone information to a text file to the same folder of your Anime Studio file. It is the code I use for my Bone Groups script. Basically most of it is stripping out the path name and adding a new name for the file you write to based on the Anime Studio file. That way you won't overwrite the file created with another AS file.

I will post it either later tonight or tomorrow (it is 7 pm EST right now for me) I have to visit my ailing, elderly Mennonite parents who couldn't LIVE without a visit from THEIR FAVORITE CHILD!


... okay fine. I'm going for the free dinner. But I still think I'm the favorite. ;)

-vern
User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Post by torncanvas »

Haha, well I hope you had a great dinner. Thanks for the help. The comma actually isn't a problem, it's just a way to separate things. The next thing is concatenated with a tab so it's similar to the ".." in that sense.

I guess I thought GetValue would run when I did my assigning, but I guess not. Thanks for that example.

The starting with bone 58 thing ended up being (what I assume is) a 128 line limit for the console. I have 62 bones, so it shows the last 128 lines, which ends up being 2 copies plus 4.

Also, thanks for letting me know about the Save Layer Animation script. I was able to find it by searching here and I hacked it up to export what I needed. Now I just need to export it in XML for Flash. If you do finish up your script, I'd be interested to see it anyway.

Any tips on escaping characters for creating an XML file with Lua?
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

Woohoo! I'm off the hook!

I never did finish that code snippet for the file export for you. If you have it working I won't bother. If you have trouble with it I can look at it and maybe make changes.

Escaping characters is pretty straight forward. You should have no trouble exporting to XML. Do a search on google for "lua strings" or "lua escaping" or whatever. I always forget how to escape characters and have to look it up. :oops: I am pretty sure it is the "\" that escapes ANY character using lua string patterns. But that is usually when doing string searches in lua. If you are just using strings to write stuff you don't need to escape.

I think strings inside quotes are usually treated pretty much like strings with out the need to escape but I'm not 100% sure about that.

I'm looking at the code I found on the web that I use for writing a lua table to a text file. The output text format is similar to XML and NONE of the characters are escaped (except returns, /n or /r), just in quotes. You will have to escape quotes or use double quotes if the quote symbol is part of the string being written.

I have two functions that write out and read in any lua table structure. It is wonderful.

I am really really curious about how you are using this. You are converting bone position/rotation to use in Flash??? Sounds intriguing!

I have the "reverse" problem. I am creating a "physics" utility script of functions for Anime Studio based on Flash Action Script. I have to reverse engineer action script to work in lua using AS bones. Flashes work space coordinates are "upside down" so all the positions have to be reversed... or I could just stand on my head but I get headaches when I do that. ;)

-vern
User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Post by torncanvas »

Once I get done with my thingy, I'll make sure to post it here. :)

Yeah escaping is straightforward, I'm using "\n" for newlines and "\t" for tabs to make the XML all pretty and indented.

I've also discovered here that you can use double brackets to assign a big block a la:

Code: Select all

skelXml = [[
<skeleton>
    <bone>
        <boneNumber>0</boneNumber>
   </bone>
</skeleton>]]
Anyway, I'm nearly done, but I need to reorganize the Save Animation Layer so that the position and angle values are both embedded per frame, which is different than the standard setup for that script. I guess I need to do my own version of accounting for units and stuff,too.

Why are we doing this? We need to attach stuff to the dinos dynamically. As you know from our other discussions, all the dino animation is done in ASPro, and we need to dynamically attach accessories, weapons, and even the dino head in Flash. Since there's no way to know where exactly to attach stuff, we need to find out, so that means exporting bone positions and rotations. Then for whatever is attached, that will just have some offset and will look up the position and rotation of the bone, offset itself, and therefore be in the right spot every frame.

I hope your physics stuffs goes well. :) We'd have a good use for that.
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

I hope your physics stuffs goes well. :) We'd have a good use for that.
It works... for me anyway because I know what it does.. I need to "simplify" it so it could be more useful for everyone. Add a pop up interface for applying all the physics properties (springs, rigid bodies, etc) I've come up with to different bones. Since I know how to write an external file now I can put the physics stuff in there as well.

I just finished my bone groups script and it works great... finally. I am using it now in production to make sure no odd bugs pop up before I post it again (like I did the last time :oops:). I've been using it now for about a week and so far no trouble. That means I can go back to the physics script again.

That thing is going to be cool as heck. I can't wait! ;)

-vern
User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Post by torncanvas »

Man, I'm so close. I just need to get a global rotation value in degrees. First of all, fPos and fAngle seem to just give me the bind pose values. So I'm using fAnimAngle and fAnimPos to get the actual values per frame. However, fAnimPos gives me the rotation in radians in relationship to its parent. Surely there's a global rotation function somewhere?!
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

Use this to convert to degress:

Code: Select all

math.deg(value in radians)
And to convert to radians (which is what AS uses internally)

Code: Select all

math.rad(value in degree)
The value in parentheses can be a variable that represents a number.

don't know if you have this link saved but this is my "go to choice" for math stuff in lua. It has everything you need to do any math stuff:

http://lua-users.org/wiki/MathLibraryTutorial

----

The other thing about getting the "global" rotation or position. I completely forgot about that part. I will hunt through my code so you can get that value. It is a little trickier because you have to check through the WHOLE chain and get ALL of the rotations.

You have to check if each bone has a parent bone, then check that bone's parent... etc.. all the way up to the last bone. Then add all of those together to get the entire value.

Just to be clear, you want the ENTIRE transformation (rotation, translation) of the bone including the parent bones rotation correct?

I have this in a function I used for my "copy/flip bones" script. I just have see how hard it will be to pull it out for easy use.

-vern
User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Post by torncanvas »

That would be awesome. I have that sort of functionality going right now, but for some reason, it's only running on the first frame...?
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

There is one problem you will run into trying to apply these values to Flash.

This is the same problem I have with my physics script. Flash is FREAKING BACKWARDS!!!! This is a Flash problem. Whoever "invented" or designed the grid system for Flash went with an "artistic" or "visual" concept instead of using the logical "math" system of coordinates.

Every program that uses an X Y grid system, like 3D software has 0 in the FREAKING CENTER with negative values down (Y) to the left (X) from the center 0 point and positive numbers up and to the right of 0.

This is PROPER MATH. This is how math guys do it. But NOOOOOO, the idiots at Macromedia (and now Adobe but they are stuck with it) had to go and do it completely differently. Flash has 0 at the top left. Down to the right is a positive number.

AND, rotations are BACKWARDS in Flash! Angles SHOULD get smaller in the clockwise direction and larger in the counter clockwise direction. But once again in Flash this is BACKWARDS. That is what is annoying in Flash. For instance a bone in Anime Studio with 0 rotation will "point" to the right. A "36 degree" rotation in AS goes "up" or counter clockwise. In Flash it goes down or clockwise.

Since Action Script and lua are so similar, and the scripting in Flash and AS is so similar using freely available scripts from Flash phyiscs stuff and converting it to lua makes sense for me... since I am not very good with physics math at all. This stupid backwards thing is a real pain though.

I have yet to figure out a simple way to rotate these coordinates when converting from action script to lua in AS with my physics script. Still working on it.

So position values from AS will be meaningless in Flash coordinates unless you convert the spaces somehow. It probably shouldn't be too hard though since you just want the straight values. I need to perform complex math on them which makes it really hard.

Another problem is that the grid coordinates in Anime Studio are "static" (I don't know the proper math term for this).

In Flash the distance from 0 (top left) is based on PIXELS. In AS it is based on the ratio of width/height.

For instance:

320 x 480 project in AS is 4/3 (h/w) = 1.33333. The y translation (vertical) values will always be 1 at the top, 0 in the center and -1 at the bottom. Always, for any size project.

The x (horizontal) values will be -1.333333 on the left edge, 0 in the center and 1.33333333 at the right edge. h/w.

If you have a different ratio like 2 x 4 or 1 x 3 only the x or horizontal translation values will change. the y (vertical) values will stay the same, 1 to 0 to -1.

In Flash the grid is based on the pixel dimension. An object placed at x480 y320 on a 320 x 480 document will be at the bottom right corner. Flash has the ability to change position with values "smaller" than a pixel because the document size is "relative" to how it is displayed on the screen. You can use Action Script to move objects in increments that are "technically" smaller than a pixel. They have a name for it... like "pion" or something... maybe I'm not remembering it properly on purpose. ;)

Some fiddling with the numbers from AS will need to be applied to ALL the values for use in Flash to account for this.

If my explanation is stinky... or even WRONG which is very possible, check out this link:

http://www.lukamaras.com/tutorials/acti ... ained.html

-vern
User avatar
torncanvas
Posts: 34
Joined: Wed Oct 31, 2007 12:22 am
Location: Des Moines, IA
Contact:

Post by torncanvas »

Yeah, I got the crazy position maths figured out no problem:

Code: Select all

local docRatio = docWidth/docHeight
...

Code: Select all

v1.x = ((v1.x+docRatio)/(docRatio*2)) * docWidth
v1.y = docHeight - (((v1.y+1)/2) * docHeight)
Yeah, I forgot Flash rotation is backwards. Good point there. Easy fix though, just do this:

Code: Select all

v2 = 360 - math.deg( v2 )
Is it true that fAnimPos is relative to the parent like fAnimAngle?! Main thats a pain... So I have to do the bone parent traversing stuff for that, too? *sigh...I guess it wasn't that much of a pain...
////////////////////
http://www.intuitiongames.com
http://blog.intuitiongames.com
////////////////////
User avatar
heyvern
Posts: 7042
Joined: Fri Sep 02, 2005 4:49 am

Post by heyvern »

Here's the code for parent iteration. It prints out some "extra" stuff so you will have to fit it into your code. It prints the angle and position in 2 lines.

The main new bit is the "while" loop and some extra variables.

Note too that I put the two variables for the "fAnimPos" values UNDER the while loop. The Vector2 variable gets changed by the while loop so those variables should be after. You don't really need to use those variables you could just access the values directly... but I didn't want to confuse anything you already did from before.

Code: Select all

function LayerScript(moho)
   if (moho.frame == 0) then
      return
   end
   
   local skel = moho:Skeleton()
   if (skel == nil) then
      print("No skeleton found in layer:", moho.layer:Name())
      return
   end

   for i = 0, skel:CountBones() - 1 do
      local bone = skel:Bone(i)
      local boneName = bone:Name()

      -- Bone angle and translation variables
      local boneVec = LM.Vector2:new_local()
      boneVec:Set(bone.fAnimPos:GetValue(moho.frame))
      local boneAng = bone.fAnimAngle:GetValue(moho.frame)

      -- Parent bone. If no parent bone then parent == -1
      local parent = bone.fParent

      -- this loop iterates through each bone's parent chain
      -- if a bone has no parent it returns -1 and stops the loop
      while parent>= 0 do
		  -- bone.fParent is just a number. This variable is for the bone "object".
          local pBone = skel:Bone(parent)

          -- add the angle and translation of the parent to the variables for the bone's angle and translation
          local pVec = LM.Vector2:new_local() -- new Vector2 for the parent bone's fAnimPos
          pVec:Set(pBone.fAnimPos:GetValue(moho.frame)) -- set pVec to the parent bones fAnimPos value
          boneVec = boneVec + pVec -- add the current bone's translation to the parent bone's translation
          boneAng = boneAng + skel:Bone(parent).fAnimAngle:GetValue(moho.frame) -- add the current bone's rotation to the parent bone's rotation

          -- Now to continue the "while" statement set the var parent to the parent of the parent... uh... who's on first?
          -- When there are no more parent bones to check parent == -1 which breaks the loop.
		  parent = skel:Bone(parent).fParent
	  end

      local bonePosX = boneVec.x
      local bonePosY = boneVec.y
      print("Bone Translation:")
      print(i .. "(" .. boneName .. "): " .. "(" .. bonePosX .. ", " .. bonePosY .. ")")
      print("                              ")
      print("Bone Angle:")
      print(i .. "(" .. boneName .. "): " .. "(" .. math.deg(boneAng) .. ")")
      print("------------------------------")
   end
end
Post Reply