Home Technical Talk

3DS Max Transform Gizmo question...

MoP
polycounter lvl 18
Offline / Send Message
MoP polycounter lvl 18
Since I've been using Lightwave a little at work, I've grown to like the way it handles moving objects... no visible "gizmo", just click and drag to move around, and if you hold down CTRL, it constrains movement to the nearest axis (so if you were moving the mouse across the perspective plane from left to right, it'd constrain to that axis alone).

I've found that's more convenient than clicking on the little gizmo arrows to lock movement to an axis, and more efficient than having 3 different shortcut keys for X, Y and Z constraints as standard Max uses when you hide the gizmo.

I was wondering if there was a plugin or script which could use a similar behaviour in max? I've scoured the help to no avail, so I don't think it's a standard setting, and I doubt it'd be scriptable behaviour since it relies on knowing which way the mouse is moving relative to the screen.

For all I know it might not even be possible with a plugin, again probably due to the way Max has been coded, it was never intended to be used that way.

Just wondering if anyone knows for definite if it's possible or not. I've searched scriptspot and google without much luck. At the moment I'm leaning towards thinking it's physically impossible due to the way Max is coded, but I'd love to be proved wrong smile.gif

Any ideas would be awesome.

Replies

  • Sage
    Offline / Send Message
    Sage polycounter lvl 19
    Mop what you are looking to make is a toggle behavior. I noticed in the past you had messed around with maxscript. The only idea I can think of is see if you can for example

    set the default axis to x
    if crtl is pressed then the axis is y
    if alt is pressed then the axis is z
    else
    set default axis to x

    I don't know how to write that in maxscript but then I like the gizmo. You can also remap the constrait short cuts to something closer to your fingers

    example
    c = x
    v = y
    b = z

    Alex
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Good suggestions Alex, I might give that a try and see how it feels.
    Using separate shortcuts for separate axes just seems a tad cumbersome to me, it'd be a more elegant solution to be able to do it like lightwave, unfortunately it probably isn't possible frown.gif
  • Sage
    Offline / Send Message
    Sage polycounter lvl 19
    hey mop I did some more digging but it would really be nice is the listener and macro recorder just freaking called everything would make this easy...

    checks mouse buttons:

    shiftKey Boolean

    true if SHIFT key was down.

    ctrlKey Boolean

    true if CTRL key was down.

    altKey Boolean

    true if ALT key was down.

    lButton Boolean

    true if left mouse button was down.

    mButton Boolean

    true if middle mouse button was down.

    rButton Boolean

    true if right mouse button was down.

    I kind of have fallen in love with XSI's supra functions. It's kind of like the shift drag thing in max. In XSI you can hold down a button like say move = v and tool turns on as long as the button is pressed. At any rate it this kind of behavior would be nice to use with some tools like inset

    I know what you mean Mop about the 3 button thing, it's clunky so I just like the gizmo for that. In Lightwave as well as other programs that don't allow select and move,rotate, scale in the same tool feels annoying as hell. I didn't play enough with the latest version of lightwave but I think selections are locked, maybe there is a faster way to do it. At any rate I do prefer how photoshop contrains things and wish 3ds apps adopted it. I hope you find something closer to what you want.

    Alex
  • Eric Chadwick
    Have you looked into Strokes? When you make a certain shape with the mouse (can use either Left or Middle button), Max will change to whatever mode you set up for that stroke shape. Might do what you want.
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Cool stuff Eric, I didn't know about that at all!
    I wonder if the strokes method is exposed to maxscript enough (at all?) to be able to use it to detect mouse angle based on the rotation of a perspective viewport... that sounds immensely complex though :P

    Edit: Also, having given it a quick try, it seems rather cumbersome (i love using the middle mouse for viewport pan/rotate/zoom, so I don't want to assign it solely to strokes in the preferences setting), and the Utilities method is not ideal, since you have to toggle it on and off or else you lose left-click functionality while it's enabled.
    Still, sounds like a good starting point, now that I know max can record that sort of movement.
  • Eric Chadwick
    Somebody made a strokes maxscript long ago, maybe Max 3 or 4, but I think it did basically the same thing.

    Try contacting a hardcore maxscript guy, like the maker of Orionflame, or the maker of Kaldera. These guys are always on the lookout for a tool that people might need (and might be willing to pay for).
  • CrazyButcher
    Offline / Send Message
    CrazyButcher polycounter lvl 20
    regular maxscript should allow this, as well

    there is a "mousetool" or "mousetrack" class, which allow you to get feedback on the mouse movement. They can also find out if you drag the mouse and what special keys you hold down (alt, shift, ctrl)

    say you use mousetrack, you would constantly let it run in the background, then you have some macroscropt that toggles a global variable, like "global mopsultimatemovemode"
    whenever that variable is true the mousetrack callback function would do the following:

    # with getViewTM() you find out the orientation of the current viewport

    # find out the orientation vectors that are biggest (get Length of each axis vector), to get the main axis orientations ( 2 vectors which now span the 2d plane on which you can move the object)

    # you now either constrain max's movetool's axis to the plane you computed before, or you do the moving yourself

    --

    this means that based on the current viewport orientation the axis constraints are automatically set

    not sure if that is exactly what you wanted, also maybe the moustrack is not the proper place to register the callback, max allows various callbacks where you could register that function
    I picked the moustrack as I wanted to do the moving in it as well, but I guess it's easier to use the regula max move tool

    edit:
    actually there is the possibility to register a callback whenever the viewport changes smile.gif
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Thanks a lot, CrazyButcher, that's good info smile.gif

    Just reading through the MaxScript reference on Mousetrack, it seems that the mousetracker uses the currently active grid to track on, does this mean that it'd only be possible to find the mouse movement along two axes, rather than all three?
    Hence using this setup, would it even be able to move the selected objects along X, Y or Z?
  • CrazyButcher
    Offline / Send Message
    CrazyButcher polycounter lvl 20
    if you only want to change the axis constraints, you dont need to use the mousetrack at all, but only register a callback that is run when viewport is changed

    because when viewport isnt changed (rotated..) the major axis wont change either, hence oyu can use the regular move tool

    if you want to do the moving yourself it gets a bit more complex. while mousetrack gives you a ray intersection with the object or grid, you can always calculate more 3d stuff yourself, see the sample code given at viewport.gettm()

    basically you would need to shoot a ray on the plane on which you want to move and calculate difference vectors to previous mouseframe and then add that to the object position.
    hence I think the first thing is sufficient, or I didnt understand your main motivation.

    edit:
    DAMN... stupid people, I just checked about how to restrict max tools to axis and you can do x,y,z,xy BUT NOT THE OTHER PLANES !! how stupid is that, the functionality exists in the SDK so, hence I can code a plugin that supports access to all axis constraints for maxscript as well, but come on who the hell was responsible for that.
  • Sage
    Offline / Send Message
    Sage polycounter lvl 19
    Mop I was wondering if the rotate and scale tool behaved the same way as the move tool? I would think it would but then again I'm not a programmer. wink.gif
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Hmm, I think I could probably be clearer on how this works, for those of you who haven't used lightwave.
    I did a quick image with the coloured lines indicating the mouse movements and which axes they control, and some points to how the tool would work.

    move-idea.jpg
  • CrazyButcher
    Offline / Send Message
    CrazyButcher polycounter lvl 20
    I finished a prototype to extract the major axis (run the script and then drag the mouse, at end of drag the proper axis constraint should be set)
    sadly maxscript has no global mousecallback, which means mousetrack blocks all other mouse operations. so one would need to do the move in maxscript, which would be ugly so, as it would not be as nice as regular max transform modes...

    anyway here is the script with some comments
    <font class="small">Code:</font><hr /><pre>
    -- some global values we need
    -- first an array which stores last frames' mouse positions
    global CBmouseposbuffer = #()
    global CBmousebuffersize = 8
    -- the current index in the buffer
    global CBmousebuffercur = 0
    -- what kind of mousemovement we have (dragged, clicked...)
    global CBmouselastmode = #freeMove

    function mopmovetrack msg ir obj facenum shift ctrl alt = (

    -- when we end a drag
    -- we calculate which axis to lock
    if (msg != CBmouselastmode and CBmouselastmode == #mouseMove) then (
    -- look back a couple frames and take that as direction
    -- direction is currentpos - startpos
    local mdir = mouse.screenpos - CBmouseposbuffer[CBmousebuffercur+1]
    -- normalize it
    -- invert Y because mousepos has different axis than
    -- projected screencoords
    local dist = 1/sqrt(mdir.x*mdir.x + mdir.y*mdir.y)
    mdir = [mdir.x*dist, -mdir.y*dist]


    -- dir is now the 2d vector of the mouse movement
    -- we now need to find the closest major axis

    -- to do this we project the major axis into 2d as well
    -- the view matrix gives us all the major axis after
    -- projection
    local mat = getViewTM()
    local dirx = mat.row1
    local diry = mat.row2
    local dirz = mat.row3
    -- we alre only interested in 2d screen vectors so
    -- convert to 2d vectors and normalize them
    dist = 1/sqrt(dirx.x*dirx.x + dirx.y*dirx.y)
    dirx = [dirx.x*dist, dirx.y*dist]

    dist = 1/sqrt(diry.x*diry.x + diry.y*diry.y)
    diry = [diry.x*dist, diry.y*dist]

    dist = 1/sqrt(dirz.x*dirz.x + dirz.y*dirz.y)
    dirz = [dirz.x*dist, dirz.y*dist]

    -- make dotproducts (projects length of vectors)
    -- also use only absolute values because direction
    -- (from a to b, vs b to a) should play no
    -- role, only "best" fit
    -- the dot product of normalized vectors gives us
    -- the cosine angle between the vectors
    -- which means 1 means they have a angle of 0
    -- and 0 means an angle of 90°

    dirx = abs ( dirx.x*mdir.x + dirx.y*mdir.y )
    diry = abs ( diry.x*mdir.x + diry.y*mdir.y )
    dirz = abs ( dirz.x*mdir.x + dirz.y*mdir.y )

    if (dirz > diry and dirz > dirx) then (
    return 1
    )
    else if (diry > dirz and diry > dirx) then (
    return 0
    )
    else (
    return -1
    )
    -- just for debug
    -- print mdir
    )

    -- dump current message, so that upper part is only called
    -- when there is a change
    CBmouselastmode = msg



    -- we store the current mouse positions in a array
    -- the array is dealt with like a ring buffer
    -- we do this to get a more averaged direction vector
    -- cause within a single mouseframe
    -- the direction travelled might not be precise enough

    CBmouseposbuffer[CBmousebuffercur+1] = mouse.screenpos
    CBmousebuffercur += 1
    CBmousebuffercur = mod CBmousebuffercur CBmousebuffersize

    return #continue

    )


    test = mouseTrack trackcallback:mopmovetrack
    if (test > 0) then max tool z
    else if (test < 0) then max tool x
    else max tool y

    </pre><hr />


    so as quickie workaround you could bind this script to a key run it to set the axis you want, and then do the move

    workflow could be
    press key (runs script), drag for axis (finishes the script), do the transform
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Ah, thanks for doing this, CrazyButcher, it's a lot of help (and very useful for my maxscript learning too - I've got a bunch of video and text tutorials, but commented code is always good too).

    Unfortunately, like you say, it can't do the moving when the axis has been found, so at the moment it'd probably be just as fast from a workflow point of view to have different keys bound to X Y and Z axis constraints frown.gif

    Thanks though, I'll have a play around with it and see how it feels! smile.gif

    Edit: Huh, for some reason it will never select the X axis ... it appears to work fine for Y and Z, but if i use "run script", it says I don't have enough maxscript memory when I move the mouse along the X axis ... and if I try to use Evaluate All, it just doesn't select the X axis at all, even though I can switch between Y and Z if I hit Evaluate All again.
    I can't figure out why that might be since it's too in-depth for me to understand yet smile.gif
  • Bryan Cavett
    Offline / Send Message
    Bryan Cavett polycounter lvl 19
    I played with this tonight and I've come up with something. Its not what Mop wanted but its a little closer and its not as complicated as mouse inputs and all that stuff. Basically its a script that changes your constraint depending on where your looking. It checks to see what axis plane your looking at every time the viewport is updated. I'm not sure how to get it to constrain to one axis but at least you don't have to grab the gizmo every time. Just rotate view and lock selection and move. It works as a toggle. I've only tested it in max 9 but should work in earlier versions. I'll try to get it working with ctrl to constrain to only one axis.

    also it only works in perspective and user views with the move or non-uniform scale tools. Doesnt work well with rotate. But it doesnt whin about it smile.gif

    edit: I just re read butchers earlier post and he bascially talks about what i did here lol... Thats what I get for skimming through the technical post after first seeing mouse track in it.

    http://www.bryancavett.com/mxs/autoConstraint.mcr

    autoconstraint.jpg
  • Bryan Cavett
    Offline / Send Message
    Bryan Cavett polycounter lvl 19
    Ok ive got single axis constraints kinda working but I need some help from people who actually know what the hell they are doing smile.gif The constraints seem to work if your selection is close to the world origin. When the selection is further away it gets hard to determine what axis the mouse is closest to (its the way im figuring out which axis you want selected) Im getting the mouse position in screen space and then getting its position on the construction plane that is defined by my script above. I need some way of offsetting the position of the mouse's point on the construction plane to the selection's point on the construction plane. Any ideas? here is the script

    <font class="small">Code:</font><hr /><pre>macroscript AutoConstraint2 category:"BCTools"
    (
    local autoConstraint_Enabled
    fn autoConstraint =
    (
    local coordSysTM = Inverse(getViewTM())
    local viewDir = -coordSysTM.row3
    local mousePos = gw.getPointOnCP mouse.pos
    if viewDir[1] < 0 then viewDir[1] = viewDir[1] * -1
    if viewDir[2] < 0 then viewDir[2] = viewDir[2] * -1
    if viewDir[3] < 0 then viewDir[3] = viewDir[3] * -1
    if viewDir[1] > viewDir[2] and viewDir[1] > viewDir[3]
    then
    (
    toolmode.axisConstraints = #YZ
    if mousePos[1] < 0 then mousePos[1] = mousePos[1] * -1
    if mousePos[2] < 0 then mousePos[2] = mousePos[2] * -1
    if mousePos[1] > mousePos[2] then toolmode.axisConstraints = #Z else toolmode.axisConstraints = #Y
    )
    else
    if viewDir[2] > viewDir[1] and viewDir[2] > viewDir[3]
    then
    (
    toolmode.axisConstraints = #ZX
    if mousePos[1] < 0 then mousePos[1] = mousePos[1] * -1
    if mousePos[2] < 0 then mousePos[2] = mousePos[2] * -1
    if mousePos[1] > mousePos[2] then toolmode.axisConstraints = #X else toolmode.axisConstraints = #Z
    )
    else
    if viewDir[3] > viewDir[2] and viewDir[3] > viewDir[1]
    then
    (
    toolmode.axisConstraints = #XY
    if mousePos[1] < 0 then mousePos[1] = mousePos[1] * -1
    if mousePos[2] < 0 then mousePos[2] = mousePos[2] * -1
    if mousePos[1] > mousePos[2] then toolmode.axisConstraints = #X else toolmode.axisConstraints = #Y
    )
    --print mousePos
    )
    on isChecked return autoConstraint_Enabled
    on Execute do
    (
    if autoConstraint_Enabled == undefined then
    autoConstraint_Enabled = true
    else
    autoConstraint_Enabled = not autoConstraint_Enabled
    if autoConstraint_Enabled then
    (
    registerRedrawViewsCallback autoConstraint
    )
    else
    (
    unRegisterRedrawViewsCallback autoConstraint
    )
    updateToolbarButtons()
    )--end execute
    )--end macroScript </pre><hr />
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Whoa, Bryan, that's awesome!
    Works almost exactly like I was thinking, shame it only seems to be able to choose from 2 axes, but given that to change the axes all you need to do is rotate the viewport a bit, it works pretty well!

    Great stuff guys, I wish I could help more but since I only just started learning Maxscript, this is all way over my head at the moment frown.gif
  • CrazyButcher
    Offline / Send Message
    CrazyButcher polycounter lvl 20
    hi bryan, nice work, about your "plane" macroscript, I'd think its better to use the "callbacks.addscript #viewportchange" instead of the redraw, because if you just move an object you get a redraw, but no actual viewmatrix change, also changing viewports might not cause a redraw event ?

    about the second script,
    "local mousePos = gw.getPointOnCP mouse.pos" should be called after you set the new toolmode.axisconstraints (atm it probably plays no role as a previous redraw already set the proper axis).
    about the mousepos, max gw.get... returns axis on the active grid object, not the local axis of an object.
    to do so you would need to gw.get... the coordinates of the current transform center, and compute relative position of mouse to it.
    however then the user would always need to keep his mouse along one of the axis, ie not the direction of his drag would influence, but the position to the transform center. still worth a try to get the feeling.

    by the way "abs" makes values absolute no need for the "if < 0 then * -1"

    edit:
    I will try to change my mousetrack based thing into a viewportredrwa callback (as that would get notified when user starts to move stuff)

    edit2:
    I got it to work as Mop wanted it =)
    uploading it soon to my website
  • CrazyButcher
    Offline / Send Message
    CrazyButcher polycounter lvl 20
    okay this should do it, the script now operates as suggest (if activated), can be bound to key whatever, it's a macroscript.

    when you start a drag it will take a few frames (you could edit those in the scripts) until the axis constraint is set and locked.

    http://www.luxinia.de/index.php/ArtTools/3dsmax#autoaxis
  • Bryan Cavett
    Offline / Send Message
    Bryan Cavett polycounter lvl 19
    Thanks for the help with the script CrazyButcher. Alot of what I do is trial and error as I still dont have a good grasp of scripting stuff other than really simple automated things.

    I was going to use a callback like that but I didnt see anything related to viewport change in the docs other than what I used. Good to know about abs aswell. I want to keep playing with mine just for practice but Im glad you got it to work the way mop wanted. Ill give a go when I make it into work.
  • MoP
    Offline / Send Message
    MoP polycounter lvl 18
    Awesome stuff, Crazybutcher, that works great... just as I imagined! smile.gif

    I highly recommend everyone try this, I'm sure I'll be using it every day now. Good work from Bryan implimenting this too, his was 99% of the way there... now this has all screwed up my learning maxscript cos instead of figuring it out myself, other people have done it for me! D'oh! smile.gif

    I'll donate some money to your Paypal tonight, CrazyButcher, thanks again! smile.gif
  • Bryan Cavett
    Offline / Send Message
    Bryan Cavett polycounter lvl 19
    Ive updated my auto 2d axis constraint macro... If anyone was interested in it. Made the corrections crazyButcher mentioned and tried to clean up the script. Still functions the same though. Not going to carry on with the single axis constraint since crazybutcher has already made it perfect.

    http://www.bryancavett.com/mxs/auto2dConstraint.mcr
Sign In or Register to comment.