Page 1 of 1

MaxScipt to Python Script (Spline IK)?

Posted: Mon Mar 17, 2008 10:07 pm
by JiriH
Hello,

as an introduction I have to say that I am not a programmer. So I really do not know whether this is possible at all or how difficult for programmers this may be.

You may know that there are some discussions how useful spline IK constraint may be. See for example this BlenderNation article http://www.blendernation.com/2006/09...es-and-snakes/
Nice explanation here http://www.studiopc.com/support/3dsmax5/Spline_IK.pdf

Recently I have discovered that there is a special script to solve spline IK. This script was for 3ds Max 4 (Spline IK was implemented in 3dsmax 5 in 2002) http://www.comet-cartoons.com/maxscript.php

So I would like to ask for help some coder if he would be so kind and skilled to be able to "translate" this Maxscript to Python. Probably as a special PyConstraint which are now possible in Blender 2.46).

I hope that using of this Max script could save a lot of time and help as well. Even though I am not a programmer I have tried to do my best to make coding of this feature as easy as possible. This would be really great as spline IK is very useful and also a standard of all other 3d applications.

I would like to thank very much to coder who will pick up this useful code and translate it into python. I have already contacted Michael B. Comet who is the author of the original script regarding his approval to use his code for Blender implementation. He has confirmed: "Sure that is fine, enjoy!"


Here is the Spline IK MaxScript in matter:

Code: Select all

/*
 * Global Variables
 */
global node thePath=undefined;
global node swivelObj=undefined;
global int numBones=15;
global boolean allowSep=true;

/*
 * Functions
 */


-- is_path() - Determines if the given object is a valid path or not
fn is_path obj =
(
    local isALine, isASpline;
    isALine = (classOf obj == line);
    isASpline = (classOf obj == SplineShape);
    return (isALine or isASpline);    
) 


-- Adds the roll and twist float attributes to the given object
fn add_rolltwist_attr obj =
(
    if (obj == undefined) then (
        format ("splineIK INTERNAL ERROR: add_rolltwist_attr(), obj is undefined!\n");
        return();
        )

    local str = ( "rolltwistAttr = attributes Custom_Attributes "+
        "( "+
        "parameters main rollout: rolltwistRoll "+
        "    ( "+
        "    roll type:#float default:0.0 ui:rollSpin; "+
        "    twist type:#float default:0.0 ui:twistSpin; "+
        "    offset type:#float default:0.0 ui:offsetSpin; "+
        "    )"+
        "   "+
        "rollout rolltwistRoll \"splineIK Parameters\" "+ 
        "    ( "+
        "   spinner rollSpin \"roll\" type:#float range:[-9999,9999,0]"+
        "   spinner twistSpin \"twist\" type:#float range:[-9999,9999,0]"+
        "   spinner offsetSpin \"offset\" type:#float range:[-100,100,0]"+
        "    )"+
        "); "+
        "  "+
        "custAttributes.add $"+(obj.name)+" rolltwistAttr;" );

    print str;
    execute str;
)


-- doSplineIK - Does the actual work of creating a splineIK chain
fn doSplineIK =
(
    local float len =(curveLength thePath);
    local float delta = (len / numBones);
    

    local node lastBone = undefined;
    local node cObj = undefined;
    local int b=0;
    local str="";
    local float p;

        -- Figure which obj gets the CA's.
    if (swivelObj != undefined) then
        cObj = swivelObj;
    else
        cObj = thePath;
    add_rolltwist_attr( cObj );        -- add cust attrs to the Control Object

    lastpCI = undefined;    
    
    for b in 0 to (numBones-1) do (
        
            -- First create a single bone chain.
        b0 = BoneSys.createBone [0,0,0] [0, delta ,0] [0,0,1];
        b1 = BoneSys.createBone [0,delta ,0] [0, delta+10,0] [0,0,1];
        b1.parent = b0;

        b0.name = ("splineIKbone"+(b as string));
        b1.name = ("splineIKboneEND"+(b as string));


            -- Create the IK chain solver/effector.
        endEf = IKSys.ikChain b0 b1 "IKHiSolver";

        endEf.name = ("splineIKEndEffector"+(b as string));

        if (lastBone == undefined or allowSep == true) then (
                -- Create a path constraint
            pCntl = b0.pos.controller = path();
            pCntl.path = thePath;

/*
                -- Apparently this auto creates keys for percentage, so blow that away.
            pc = pCntl.percent.controller;
            ka = pc.keys;
            deleteKeys ka #allKeys;
                -- Then setup where it is supposed to be at.

                -- figure real percent based along path length, since vtx spacing may be uneven.
            p = lengthToPathParam $Line01 (1.0/numBones *b);
            pCntl.percent = p*100.0;
            pCntl.follow = false;
*/
            pCntl.percent.controller = float_script();
            str = ("dependsOn $"+thePath.name+" $"+cObj.name+";\n");
            str += ("wantp = ("+((1.0/numBones *(b)) as string)+" + ($"+cObj.name+".offset / 100.0));\n");
            str += ("if ( wantp > 1.0 ) then ( wantp -= 1.0; )\n");
            str += ("else if ( wantp < 0.0 ) then ( wantp += 1.0; )\n");

            str += ("finalPercent = lengthToPathParam $"+thePath.name+" wantp;\n");
            str += ("if (finalPercent >= 1.0) then ( finalPercent = 1.0; )\n");
            str += ("finalPercent;");
            pCntl.percent.controller.script = str;

        ) else (
            -- else if it isn't on the path do a pos constraint to the last end bone.
            
            pCntl = b0.pos.controller = position_constraint();
            pCI = pCntl.constraints;
            pCI.appendTarget lastBone 50;
            )


            -- Now setup path constraint on end effector
    
            -- Hookup pos const for last chain
        if (lastpCI != undefined) then
            lastpCI.appendTarget b0 50;
            
            -- and now take care of this one
        if (allowSep == false or b >= (numBones-1) ) then (    -- last bone so set constraint on that too
            pCntl2 = endEf.pos.controller = path();
            pCntl2.path = thePath;

/*
                -- blow away stupid keys
            pc = pCntl2.percent.controller;
            ka = pc.keys;
            deleteKeys ka #allKeys;
                -- setup where it is at.
    
                    -- figure real percent based along path length, since vtx spacing may be uneven.
            p = lengthToPathParam $Line01 (1.0/numBones *(b+1));
            if (p >= 1.0) then
                p = 1.0;
            pCntl2.percent = p*100.0;
*/
            pCntl2.percent.controller = float_script();
            str = ("dependsOn $"+thePath.name+" $"+cObj.name+";\n");
            str += ("wantp = ("+((1.0/numBones *(b+1)) as string)+" + ($"+cObj.name+".offset / 100.0));\n");
            str += ("if ( wantp > 1.0 ) then ( wantp -= 1.0; )\n");
            str += ("else if ( wantp < 0.0 ) then ( wantp += 1.0; )\n");

            str += ("finalPercent = lengthToPathParam $"+thePath.name+" wantp;\n");
            str += ("if (finalPercent >= 1.0) then ( finalPercent = 1.0; )\n");
            str += ("finalPercent;");
            pCntl2.percent.controller.script = str;
    
            pCntl2.follow = false;
            
            pCI = undefined;
        ) else (
            pCntl2 = endEf.pos.controller = position_constraint();
            pCI = pCntl2.constraints;
            )
            
            -- now convert the swivel angle controller for twist to a script.
        endEf.transform.controller.swivelangle.controller = float_script();
        str = ("dependsOn $"+cObj.name+";\n");
        str += ("finalRoll = degToRad($"+cObj.name+".roll + ("+((1.0/numBones * b) as string)+" * $"+cObj.name+".twist));\n" );
            -- and use the new script.
        endEf.transform.controller.swivelangle.controller.script = str; 

        lastBone = b1;        -- store last tip bone.
        lastpCI = pCI;

        )  -- end of bone loop

)
 
 
/*
 * Rollouts
 */
rollout ro_splineMain "Parameters"
(
    group "Setup:"
    (
        pickbutton pb_curve "Pick Path" width:150 filter:is_path tooltip:"Choose a spline line or path";
        spinner spn_boneCount "Num. Bones:" range:[1,5000,15] type:#integer fieldwidth:48 align:#left tooltip:"Number of bones created for the chain";
        checkbox cb_allowSeparate "Allow Bone Separation" checked:true align:#left tooltip:"Can bones separate, or do they stay the same length?";
        pickbutton pb_swivelObj "Pick Control Object" width:150 tooltip:"Optional object for roll and twist Custom Attributes.";
    )

    label lbl_length "Path Length:" align:#left;
    button b_doSpline "Create Spline IK" width:150 enabled:false;


    -- the command stuff
    
    on pb_curve picked obj do
    ( 
        thePath = obj;
        pb_curve.text = obj.name;
        b_doSpline.enabled = true;
        lbl_length.text = "Path Length: "+((curveLength thePath) as string);
    )

    on spn_boneCount changed val do
    (
        numBones = val;
    )

    on cb_allowSeparate changed state do
    (
        allowSep = state;
    )

    on pb_swivelObj picked obj do
    ( 
        swivelObj = obj;
        pb_swivelObj.text = obj.name;
    )

    on b_doSpline pressed do
    (
        undo on (
            doSplineIK();        -- lets call the routine to do the work!
        )
    )

)

rollout ro_about "About"
(
    label abt_lbl1 "splineIK Creator"
    label abt_lbl2 "Version 1.01"
    label abt_lbl3 "July 18, 2001"
    label abt_lbl4 "" 
    label abt_lbl5 "by Michael B. Comet"
    label abt_lbl6 "comet@comet-cartoons.com"
    label abt_lbl7 "www.comet-cartoons.com"
    label abt_lbl8 ""
    label abt_lbl9  "Copyright ©2001 Michael B. Comet";
    label abt_lbl10 "All Rights Reserved";

)

ro_floater = (newRolloutFloater "splineIK Creator" 200 255)
addRollout ro_splineMain ro_floater
addRollout ro_about ro_floater rolledUp:true

        Posted: Mon Mar 17, 2008 10:08 pm
        by JiriH
        Here are comments form Michael B. Comet:

        Code: Select all

        /*
         ******************************************************************************
         * splineIK.ms - Creates a splineIK chain similar to Mayas using a spline Line 
         *        path, and multiple new IK bone chains path constrained to the path at 
         *        increasing percentages.
         *
         ******************************************************************************
         * Copyright ©2001 Michael B. Comet  All Rights Reserved.
         ******************************************************************************
         *
         * Version 1.00 - July 7, 2001 - Michael B. Comet - www.comet-cartoons.com
         * Version 1.01 - July 18, 2001 - Michael B. Comet - www.comet-cartoons.com
         *                - Got a release version ready, with offset functionality.
         *
         ******************************************************************************
         * 
         * Notes:
         *   Pick Path:  Choose a spline LINE...must be a line and not a circle etc...
         *                Editable Spline is OK, so you can take an NGon or Circle and 
         *                convert it.
         *
         *   Num Bones:    This is the number of bones chains that will be created along 
         *            the path.  Typically you want at least 3 times as many bones as 
         *            there are control point curves on your path/spline.
         *
         *   Allow Bone Separation: 
         *            ON: Each bone chain keeps is general position along    the path, even 
         *                when the path shortens or lengthens.  This method is much much 
         *                more *stable* when the joint chain is animated, even if it 
         *                becomes a little shorter than its original length.  This is the 
         *                Preferred default.
         *             OFF: Each bone chain is point constrained to the previous bone 
         *                before it.  This means your chain will never accidentally 
         *                stretch or lengthen, but odd  things can happen such as 
         *                "jitter", or it may appear offset from path, if the    path length 
         *                does not match the original size and bone chain length.
         *
         *   Pick Control Object:  This object if chosen will receive the custom 
         *            "roll",    "twist"    and "offset" attributes on it's base level.  If
         *            not chosen, then the line curve itself receives these attributes 
         *            (which could be    wired to a different control later on).
         *
         *     Path Length: This is just a little display to show how long the chain 
         *            will be.
         *
         *   Create Spline IK: Once a path has been chosen, this button actually does 
         *            the work of creating a splineIK bone system, that can be used for 
         *            skinning.
         *
         ******************************************************************************
         *
         *   AFTER SplineIK Creation:
         *
         *        As mentioned above, after creating the bone chain, three attributes 
         *        are added to the specified "Control Object", or to the "Spline Path" 
         *        itself if no control object    was specified.
         *
         *        These attributes are called and function as follows:
         *
         *        roll    =    This attribute control the overall spin of each bone in the
         *                    chain.  If the bone system is used say as a tail for a 
         *                    character, you will need to animate this if the character
         *                    rolls or pitches to keep things correct.
         *
         *        twist     =    This also adjusts the spin of each bone, but as an 
         *                    increasing twist along the length of the chain, with very
         *                    little spin towards the base, and more and more towards 
         *                    the end.  Useful say, if used for a neck type control, and
         *                    you want the end with the head to spin, but not the base
         *                    where the chain would attach to the characters body.
         *
         *        offset    =    Mostly applicable only when the path is a closed loop like
         *                    a circle.  This allows you to effectively "slide" the 
         *                    entire chain, along the length 100% forward or back.  For
         *                    example on a circle type path, this value could be 
         *                    animated to get the chain to move along the path like a 
         *                    tire or tank tread.
         *
         ******************************************************************************
         *
         *   Advice: You will probably want to use the "Linked XForm" modifier to
         *            get easy control/animation grabbable objects to be used to 
         *            actually deform the spline path, as shown in the example file.
         *
         ******************************************************************************
         */