In between tearing our hair out over animation problems with offset horse
meshes, Bwian and I have been working on an importer/exporter of
Milkshape .ms3d files into 3ds Max and Gmax. All the OBVIOUS bugs seem
to be gone now so consider this an alpha release. You can find the script
and some other files here:

http://www.twcenter.net/forums/downl...o=file&id=1718

The zip file contains the MaxScript ms3dimportexport_ver1_0.ms,
a readme.txt file, and two files needed by Gmax users but not by
3ds Max users. The script can be placed in your scripts subdirectory
or any place convenient to your workflow. Click the Max Script menu item,
then Run Script, then mouse to the directory containing
ms3dimportexport.ms and double click to run it. It will display a simple
rollout with import and export buttons.

Importing will bring in all the mesh data, bones, bone assignments and
vertex weighting, along with the bounding sphere and group names,
types, and flags needed to reconstruct a mesh file. Details are covered
in the Technical bits below.

Animations can be done with the Python animation utility animmerge and
then imported the same way as a regular model. After modification,
the animated model can be exported and the new animations extracted
to a cas file with the animextract utility.

Regular units, mounts, and siege engines are supported as are their
animated versions except for siege engines (the animation utilities
don't support animating siege engines).

Please report any bugs you find here or send a PM. I don't have a copy
of 3ds Max so if problems come up in the part of the code that writes
the binary .ms3d file, we'll have to work together on that.
(Gmax can't write files.)


Acknowledgements:

Bwian for getting me started with Gmax and sending a link to Vercingetorix's script.

Vercingetorix for writing the wonderful script in the first place.

Aymar de Bois Mauri for explaining that "groups" in 3ds Max can only be made by making multiple meshes,
a conceptual roadblock for me coming from Milkshape.

GrumpyOldMan for sending along the listener data grabbing utility: GmaxSLGRAB.exe (discussed below).


Technical Bits for Gmax Users:
------------------------------

Spoiler Alert, click show to read: 
(1) The export procedure for Gmax:

Among the many runtime features turned off in Gmax, the ability to write
ASCII or binary files is one of the most frustrating things to lose. The
procedure I've seen other authors do for Gmax is export to the listener
window and then cut and paste into a text file. Fortunately,
GrumpyOldMan heard my pain and found a utility that does this for you.
This has been included in the zip file and is called GMaxSLGRAB.exe. Once
you have exported to the listener double click this file. It displays a small
Window with one big button with "grab" as the label. Click the
"grab" button and it copies the data from the listener and prompts
you for a filename to save it to. Note that it supplies an .xml filter but
my recommendation is to explicitely type a .txt extension for your filename
to override this. It will warn you if you are going to overwrite an existing
file and it displays "done" after writing the file.

Now your data is out but in a useless made up format of mine. Now you
run the supplied Python utility ascii2ms3dconverter.py by either double
clicking it or right clicking and selecting open. It pops up a filechooser
and you select the text file you just made and it reads the data and
converts it to binary .ms3d format. Only slightly more complicated
than the 3ds Max way.

(2) Exporting big models clobbers the listener window, here's a workaround:

Cut and paste the following into a file and name it clearlistener.mcr.

macroScript clearListener
category:"Helpers and Gizmos"
internalcategory:"Helpers and Gizmos"
tooltip:"Clear Listener"
ButtonText:"Clear Listener"
Icon:#("Helpers",1)
(
on execute do clearlistener()
)


Place it in your ui\macroscripts directory. In Gmax click the customize menu
item and then select customize user interface. In the dialog that pops up
click the toolbars tab and scroll down until you see clearlistener, click and
drag it to your main toolbar. Then click the save button to update your
configuration. Now when the listener gets filled up by an export and won't
allow you to type in it, you can hit this button to clear it.



Technical Bits for All Users:
-----------------------------

Spoiler Alert, click show to read: 
(1) Where's the data stored?

In Milkshape the group name, type, and flag are stored in the group
comments and the bounding sphere data is stored in the model comment.
In 3ds Max and Gmax the name of a given mesh is the group name and
the group type and flag are stored in user defined properties. To see them
select the mesh with the Select Objects dialog, then under the edit menu
item select Object Properties, then select the User Defined tab to see
the group name and flag. The bounding sphere is stored in the root bone:
bone_pelvis for humanoids, bone_H_Saddle for horses, bone_camel_root
for camels, bone_E_platform for elephants, and USUALLY bone_body for
siege engines. The bounding sphere is stored as ASCII floats joined by
% signs. (Oddity of Gmax, maybe of 3ds Max, you can store strings with
spaces in User Properties and see them there, but only the first whole
word comes back out, hence the need to make strings with no spaces.)

When dealing with animated files, the .cas header, hierarchy, and footer
strings are stored in the first three joint comments in Milkshape.
In 3ds Max and Gmax they are stored in the root bone as well and you
can see them in User Properties. They also are ASCII format numbers
joined by percent signs. Underscores would have looked better but
texture paths for siege engines have underscores so I couldn't use
them because I tokenize the string based on the chosen delimeter.
Naturally for siege engines, the three texture paths are stored here,
it's just that their keywords are: animheader, animhierarchy, and
animfooter. This may change if I get animation utilities to work with
siege engines.


(2) Known Deficiencies:

The script works on unanimated regular units converted by GrumpyOldMan's
converter and unanimated mounts and siege engines converted by my
Python converter. It also works for animated regular units and mounts.
It WON'T work for animated siege engines because I store the three
texture paths for siege engines in the joints comments but I also
store the .cas file animation headers, hierarchy, and footers in the joints comments. Haven't worked through this conflict yet.

Not a deficiency of this script but if people are going to start using the
Python animation utilities (version 1_1 uploaded June 18, 2007) there are
three known bugs.
1 - on line 1092 in the animextract function there's this extraneous entry
float_vec3.fromfile( fidcas, 7 )
I can't fathom how it got there, it wasn't there when I tested everything.
Just comment it out with a pound sign like
# float_vec3.fromfile( fidcas, 7 )

2 - Converttxttocas doesn't convert the degrees back to radians. Oops,
it's fixed just have to post an updated copy.

3 - I didn't check for uppercase extensions so some cas files that look like
name.CAS
don't get exported to. Bwian found this one in the exportskeleton function. Also fixed just not released.


(3) Modifier Stack:

The export only works if the skin is at the top of the modifier stack for
each mesh in the model. You can do this manually by clicking and holding
the modifier and moving it. A "feature" of Gmax at least is that if you do
this to the skin modifier to move it to the top of the stack it loses all
its information about bone assignments and weighting. On the other hand,
if you manually move all other modifiers below the skin modifier, the
information is retained and the export works. I've duplicated this procedure
in the script so that if the skin modifier is NOT at the top of the stack,
I make full copies of all the modifiers above the skin modifier, delete
the originals so that the skin modifier is at the top, and then recreate
the other modifiers, in order, below the skin modifier. The script will
stop and ask if you want it to do this procedure or to terminate.


(4) Trying to reconstruct the basepose on exporting an animated model:

Having used Milkshape first I expected animated files to look the same way
in Gmax, that is, be in the basepose and only change to animations when
you hit the animate button. Bwian explained this isn't the way it works,
animations are just done differently between the two. If I simply exported
the same way I did a regular model I get the vertex positions of whatever
frame happens to be displayed at the time. If you opened this is Milkshape
you'd see the bones in the basepose and a distorted mesh. The animations
are perfectly good and you can run animextract to make a perfectly good
.cas file but this is still unsightly.

So to get an approximation to the basepose mesh I followed this procedure.
For each vertex get its primary bone assignment. In that bone's coordinate
system get the vertex position, i.e., relative to the pivot point. Now get
the relative bone positions all the way up the hierarchy to the root bone
and add them up. This SHOULD be the vertex position in world or object
or whatever the outside coordinate system is. (bone_pelvis is hard coded
to be at (0, 0, 0) since its position got overridden by the animation data.)
For armored_sergeants this was good to about a percent, too large to be
round-off error but too small to detect in Milkshape. So the warning is:
don't reimport a previously exported animated model, it's akin to making
a xerox of a xerox. Again, this is just for aesthetics, you only want the
animation data from an exported animated model, you never backconvert
these to meshes.


(5) Bone Assignments:

Mesh files always have two bone assignments, a primary and a secondary,
even if the vertex weighting on the secondary is zero. Gmax ignores an
assignment with a zero weight so to preserve these two bones I weight
the secondary one with 0.00001 and the primary with 0.99999. This
satisfies Gmax to keep two assignments but on conversion back to
Milkshape format it rounds back to 100 percent on the primary and
0 percent on the secondary and everybody's happy. Custom made models
in Milkshape frequently have only a single bone assignment so the
secondary Id is -1. To be safe and ensure that there are always two
bone assignments I arbitrarily made the secondary assignment to the
root bone with a weight of 0.00001.