Maxscript, trigger another action if the script is called again?

I have a script that cycles the move gizmo through the three options I most commonly use which I use because its faster than using the drop down menu. I actually have similar scripts for move scale and rotate but they are nearly identical.


(
	max move
	if getRefCoordSys() == #screen then (
		toolmode.coordsys #view
	)
	else (
		if getRefCoordSys() == #hybrid then (
			toolmode.coordsys #local
		) 
		else (
			toolmode.coordsys #screen 
		)
	)
)

The thing is, I want the fist time I trigger it to leave the reference coordinate system as it is and only cycle to the next mode if I press it again within a few hundred milliseconds, but I can’t figure out how to do some kind of event that expires?

It seems like someone would have done something like this already but I can’t find anything like it. But then maybe I’m searching for the wrong terms, or maybe you have to go outside of maxscript? Which I’m not familiar with but willing to research and experiment, always good to grow and learn new things.

So does anyone have any ideas where I can look for info on a timer, count down or expiring event?

I think you would need to probably reference system time in some way. According to maxscript help, these would be functions for doing that:

getLocalTime()
getUniversalTime()

Basically, get the current time upon clicking and assign it to a variable. Then, when you click again, get the time at the latest click and compare it to the original click, and if the difference between the two is less than say, 1 second (or whatever time range you want), then the action will be taken, otherwise it won’t.

Ahhh thanks for your help! That makes sense in theory, but I’m not sure how I can keep the first variable from being redefined when the script is called again.


	s = getlocaltime()

	fn getcurtime = (
		t = getlocaltime()
		return t[7]
	)

	if s[7] - 2 < getcurtime() then (
		print "No Change"
		)
		else (
		print "Change"
	)

Each time I call the script “s” gets updated with the new local time. It’s the end of the day and it seems like I’m missing something very simple. I’ll come back at this in the morning with fresh eyes… hopefully it will be staring me in the face then ha. Maybe I need to write the local time to an external file and only update it if its greater than 2 sec… meh brain is fried going home =P

Thanks for the help that was a great nudge.

Lol, this is what happens when you try to use stateful globals!

The answer is to move things into a class/struct, and work from an instance of that. So in pseudocode:


struct SpaceSwitcher:
  interval = 500
  def CycleMove():
    if getlocaltime() < self.lastTime + interval:
      return
    <move/space switch code>
    self.lastTime = getlocaltime()

macroscript CycleMove:
  global g_cycler
  if g_cycler == undefined:
    g_cycler = SpaceSwitcher()
  g_cycler.CycleMove()

Notice you now have a nice struct which you can break up and store your code on, rather than copy and pasting it around.

Thanks Rob, but I’m not sure I understand what you’re doing. I think I’m getting hung up on some of the syntax in your pseudocode…

This is what I managed to work out


function f_localtime = (--Return the seconds of the current time
	t = getlocaltime()
	return t[7]
)

struct CoordSwitcher (interval = 500)

function CycleMove = (
	if f_localtime() < (self.lastTime + interval) return
    --RefCoordSys Toggle Code goes here
    self.lastTime = getlocaltime()
)

macroscript CycleMove (
	global g_cycler
	if g_cycler == undefined then (
		g_cycler = CoordSwitcher()
	)
	else (
		g_cycler.CycleMove()
	)
)
  1. I’m not sure what “self.lastTime” is defining?
  2. I turned “def CycleMove():” into a function, but I’m not sure if it belongs in the struct or if the only thing in the struct should be the “interval = 500”?
  1. self.lastTime is holding the timestamp of the last time you executed your script. It’s what is compared against to determine if your script should just display the gizmo, or if it should actually change it.

  2. You want your function in the struct.

Basically, rather than storing your state data (variables, time, switches, etc) globally, you want to store them inside an object/struct. Then you create an instance of that object (global g_cycler) which will hold all of that data for you. So you want all of your “should I display the gizmo or switch it” logic inside the struct.

  • Phil

Oh ok, so the struct is capable of capturing the time and when I make a copy of the struct/data, that is the snap shot that I can compare to later?

You create a single, global instance of your struct (so, treat it as a singleton), and then invoke methods on that same instance. Each time the method runs, it captures the time, for comparison against the next run. Now that you’re provided the MXS I think I can brush up and provide something more working:


_now = (dotNetClass "System.DateTime").UtcNow

struct CoordSwitcher (
    interval = 500,
    lastTime = 0,

    function Cycle Move = (
        if _now() > self.lastTime + interval then
            --Toggle stuff
            self.lastTime = _now()
    )
)

global g_coordSwitcher = CoordSwitcher()

macroscript CycleMove (
    g_cycler.CycleMove()
)

Thanks for the help! That cleared up a few questions I had.

_now = (dotNetClass “System.DateTime”).UtcNow
_now() will return: dotNetObject:System.DateTime instead of a date/time
I.Now.ToString()[/I] will return:“3/19/2012 1:46:30 PM”
which is what I think you want? But I can’t add integers to a string, so I substituted it with a function that returns the seconds

function f_now = (--Return the seconds of the current time
	t = getlocaltime() 
	return t[7]--seconds
)

Here is the code I have so far,

_now = (dotNetClass "System.DateTime").UtcNow

function f_now = (--Return the seconds of the current time
	t = getlocaltime() 
	return t[7]--seconds
)

struct CoordSwitcher (
    intval = 10,
	lasttime = 0,
	
    function CycleMove = (
        if f_now() > (lastTime + intval) then (
            --Toggle stuff
			print "Change!"
			print lasttime
            lastTime = f_now()
		)
		else (
			print "No change..."
		)
    )
)

global g_coordSwitcher = CoordSwitcher()

function Cycle_Move = (
    g_CoordSwitcher.CycleMove()
)
cycle_Move()

It is sort of working with some changes.
I had to rename “function Cycle Move” to “function CycleMove”
The macroscript part at the end caused the script to constantly define a new macroscript that contained only “g_cycler.CycleMove()” and nothing would be run.
I’m not sure I understand what you where doing so I reworked as best I could making it work like above.

When all is said and done it more or less works but it always re-defines lastTime as 0. So it compares the current seconds to 0+10 which doesn’t work and is probably related to my butchered understanding of what you are doing at the end of the script.

This line:


global g_coordSwitcher = CoordSwitcher()

Is creating an new instance of your CoordSwitcher every time and assigning that to your global variable g_coordSwitcher - that is why your lastTime is being set to 0 (it’s always being set to a new object with default values).

You probably want to remove that line, and make your Cycle_Move function at the bottom look more like this, so that it is only instantiated once (that is to say, the line “g_CoordSwitcher = CoordSwitcher()” is executed only once):


 
--Take this line out:
-- global g_coordSwitcher = CoordSwitcher()
 
function Cycle_Move = (
    global g_coordSwitcher
 
    if g_coordSwitcher == undefined then (
        g_coordSwitcher = CoordSwitcher()
    )
 
    g_CoordSwitcher.CycleMove()
)
 
cycle_Move()

Phil

Sorry to go against the grain in this thread, but we could probably just keep this script simple and use timestamp().


macroscript whenClicked
(
	-- define lastClicked, but we don't need to assign it to anything. 
	global lastClicked
	
	fn checkLastClicked = 
	(
		local thisClicked = timeStamp()
		if lastClicked != undefined then
		(
			if (thisClicked - lastClicked) < 200 do
			(
				messageBox "CLICKIN FAST YO!"
				/* put your switching code here */
			)

			print "lastClicked"
			print (thisClicked - lastClicked)

			lastClicked = thisClicked
		)
		else
		(
			lastClicked = timeStamp()
		)
		OK		
	)

	on execute do
	(
		checkLastClicked()
	)
)

There’s nothing that fills me with as much doubt about my own ability as replying to a technical question on a forum. lol

f_now: The problem is, I think, localTime[7] will be the seconds in the minute- so if, say, you do ‘59’ and then one second later it is ‘0’, which won’t compare properly. You want to use UTC time, which will be always-incrementing seconds. I did screw some things up with it though (I thought UtcNow was a method):

fn f_now = (
     (dotNetClass "System.DateTime").UtcNow.Seconds --Something like this
)

EDIT: timestamp would work, use that instead.

Regarding the macro, it depends how you have your macros defined. If you read everything in on Max startup, what I wrote would be fine. Basically you want to evaluate the code once (the struct and the global declaration), and refer to that inside of your macro. I’d have to understand how you have things organized to solve the issue (or you need a good understanding of MXS loading to solve it yourself, which is preferable).

I missed that this thread had slipped to page two, so I apologize for the late reply.

Roxol, wow I don’t know where you dug up “timeStamp()” because I can’t find any reference to it in the maxscript help files but that solves a lot of the issues I was having with defining the time, like Rob pointed out.

Rob, when the script is run once it is added to the “usermacros” folder which runs when max starts. Also thank you for introducing me to structs that is going to be very useful going forward!

What I ended up trying to do on my own was define the min and sec, then convert that over to seconds and compare that to the last time. But timestamp() simplifies that process a lot and makes it a lot less buggy because it suffered from the same problem but only when a min went by. I was going to try and expand it to include hours also but tapping the key again was easier than digging back into it, so I hadn’t done that yet heh…

So yea here is the final script I’m using:


macroscript ToggleMove
Category:" VigTools"
toolTip:"ToggleMove (Q)*"
(--MOVE
	max move--switch to move mode first
	global lastClickedMove-- define lastClickedMove, but we don't need to assign it to anything. 

	fn RefCoordCycle_LV = (--Cycles the RefCoordsys between Local & View
	if getRefCoordSys() == #local then (
		toolmode.coordsys #view
	)
	else (
			toolmode.coordsys #local
		) 
	)
	
	fn RefCoordCycle_LVS = (--Cycles the RefCoordsys between Local, View &Screen
		if getRefCoordSys() == #screen then (
			toolmode.coordsys #view
		)
		else (
			if getRefCoordSys() == #hybrid then (
				toolmode.coordsys #local
			) 
			else (
				toolmode.coordsys #screen 
			)
		)
	)
	
	fn checklastClickedMove = (
		local thisClickedMove = timeStamp()
		if lastClickedMove != undefined then (
			if (thisClickedMove - lastClickedMove) < 500 do (--adjust this value to shorten or lengthen wait time
				max move
				RefCoordCycle_LV()--Switch this to _LVS to cycle between Local, View & Screen
			)
			print (getRefCoordSys() as string)
			lastClickedMove = thisClickedMove
		)
		else (--switch gizmo to move, ignore pivot setting, update "lastClickedMove"
			max move 
			print (getRefCoordSys() as string)
			lastClickedMove = timeStamp()
		)
		OK		
	)

	checklastClickedMove()
)

macroscript ToggleRot
Category:" VigTools"
toolTip:"ToggleRot (W)*"
(--ROTATION 
	max rotate --switch to rotate mode first
	global lastClickedRot-- define lastClickedRot, but we don't need to assign it to anything. 
	
	fn RefCoordCycle_LV = (--Cycles the RefCoordsys between Local & View
	if getRefCoordSys() == #local then (
		toolmode.coordsys #view
	)
	else (
			toolmode.coordsys #local
		) 
	)
	
	fn RefCoordCycle_LVS = (--Cycles the RefCoordsys between Local, View &Screen
		if getRefCoordSys() == #screen then (
			toolmode.coordsys #view
		)
		else (
			if getRefCoordSys() == #hybrid then (
				toolmode.coordsys #local
			) 
			else (
				toolmode.coordsys #screen 
			)
		)
	)
	
	fn checklastClickedRot = 	(
		local thisClickedRot = timeStamp()
		if lastClickedRot != undefined then (
			if (thisClickedRot - lastClickedRot) < 500 do (--adjust this value to shorten or lengthen wait time
				max rotate
				RefCoordCycle_LV()--Switch this to _LVS to cycle between Local, View & Screen
			)
			print (getRefCoordSys() as string)
			lastClickedRot = thisClickedRot
		)
		else (--switch gizmo to Rot, ignore pivot setting, update "lastClickedRot"
			max rotate
			print (getRefCoordSys() as string)
			lastClickedRot = timeStamp()
		)
		OK		
	)

	checklastClickedRot()
)

macroscript ToggleScale
Category:" VigTools"
toolTip:"ToggleScale (E)*"
(--SCALE
	max scale--switch to scale mode first
	-- define lastClickedScale, but we don't need to assign it to anything. 
	global lastClickedScale
	
	fn RefCoordCycle_LV = (--Cycles the RefCoordsys between Local & View
	if getRefCoordSys() == #local then (
		toolmode.coordsys #view
	)
	else (
			toolmode.coordsys #local
		) 
	)
	
	fn RefCoordCycle_LVS = (--Cycles the RefCoordsys between Local, View &Screen
		if getRefCoordSys() == #screen then (
			toolmode.coordsys #view
		)
		else (
			if getRefCoordSys() == #hybrid then (
				toolmode.coordsys #local
			) 
			else (
				toolmode.coordsys #screen 
			)
		)
	)
	
	fn checklastClickedScale = (
		local thisClickedScale = timeStamp()
		if lastClickedScale != undefined then (
			if (thisClickedScale - lastClickedScale) < 500 do (--adjust this value to shorten or lengthen wait time
				max scale
				RefCoordCycle_LV()--Switch this to _LVS to cycle between Local, View & Screen
				
			)
			print (getRefCoordSys() as string)
			lastClickedScale = thisClickedScale
		)
		else (--switch gizmo to Scale, ignore pivot setting, update "lastClickedScale"
			max Scale 
			print (getRefCoordSys() as string)
			lastClickedScale = timeStamp()
		)
		OK		
	)

	checklastClickedScale()
)

It’s one .ms script file with 3 macroscripts included, Move Rotation and Scale. There is a lot similar code but had to be dupliated for each macro. I think I could simplify if I started housing bits of code in external files and called those external functions from within the macros but that’s a challenge for another day and makes it kind of hard to trouble shoot because when an error pops up it just points to another file and doesn’t tell me what line in that file…

At this point its working great, so I’m going to leave well enough alone! Thanks guys! You taught me a lot of great stuff!

Yeah you need a proper library system set up to reuse code in these shitty, archaic systems like maxscript.