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
Any ideas would be awesome.
Replies
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
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
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
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.
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).
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
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?
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.
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.
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
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
Thanks though, I'll have a play around with it and see how it feels!
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
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
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
<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 />
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
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
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
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.
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!
I'll donate some money to your Paypal tonight, CrazyButcher, thanks again!
http://www.bryancavett.com/mxs/auto2dConstraint.mcr