Notifications
Clear all

[Closed] Call a function when an object is moved?

I’ve written a script that requires hitting an “update” button every time the object it affects is moved; this calls a function that reinitializes the relevant variables for the object’s new position.

I would like for that function to be called automatically every time that object is moved, but I’m not sure the right way to do that. My initial thought was to use a change handler, but I get an error when I put one in.

I found this in the documentation:

The do <expr> change handler code is executed in a special context and not in the context of the code that created it. This means that the <expr> code cannot contain references to local variables in outer code nestings surrounding the when, since those variables are on an execution stack that does not exist at the time the when handler is called. The compiler detects any illegal references to outer locals and generates a compiler message. An important exception to this is utility and rollout panel locals, such as local functions, rollout variables and nested rollouts. You can refer to them in change handler code inside rollout code as they are associated directly with their rollout or utility object.

Now, that should not be a problem, as all the variables in the script are defined in a rollout. But, when I try to put a change handler into the rollout code, I get
[size=1]
Syntax error: at when, expected <rollout clause>

[/size]So, what do I need to do in order to make this work?

11 Replies

you have to set up the change handler in the event of either the rollout opening, or a button you’d press, or… basically anything that -should- be setting up that handler

Next, because the change handler’s context will not be that of your rollout, access the variables within your rollout by using <rolloutname>.variableName ; or store the variables elsewhere.

The change handler (when transform … changes do etc.) is certainly the appropriate road to take.

Okay, following that advice, I moved the handlers outside of the rollout, and ended up with:

when transform selection changes id:#targetTransform handleAt:#redrawViews do cSecSlider.createCSec()

(cSecSlider is the name of the rollout, and createCSec is the function)

And I can see that it is starting to work, but then MAX immediately crashes. Am I doing something wrong?

rut roh… well that’s not good.

what does the function do? does the function crash things if you invoke it manually?

do you use handleAt:#redrawViews to make sure it only gets called when appropriate?

Here’s a very, very basic little rollout + change handler example…


rollout test_roll "test" (
	spinner spn_x "X: " range:[-1e6,1e6,0]
	spinner spn_y "Y: " range:[-1e6,1e6,0]
	spinner spn_z "Z: " range:[-1e6,1e6,0]

	button btn_create "create"

	fn updateSpinners = (
		spn_x.value = myObject.pos.x
		spn_y.value = myObject.pos.y
		spn_z.value = myObject.pos.z
	)
	
	on btn_create pressed do (
		myObject = sphere()
		when transform myObject changes handleAt:#redrawViews do ( test_roll.updateSpinners() )
	)
)
createDialog test_roll
 

To see the latest working version of my script, go here:

http://forums.cgsociety.org/showthread.php?p=5359452#post5359452

The script creates a cross section of a picked object (the “target”). The function is the one called createCSec(). It updates a bunch of values, and then creates objects based on those values.

I realized that I didn’t need an update button, as making any changes within the rollout will do the same. The script, as posted, works fine.

I’ve actually managed to get the script to start working from both inside and outside the rollout, but either way, it crashes the program. (And actually, after checking more carefully, it doesn’t look like it actually is working after all. It crashes before it has a chance to do anything.)

The most likely way seems to be to place this line:

when transform target changes handleAt:#redrawViews do createCSec()

right after everything else in the event handler for pck_Select.

(By the way, I got your sample code working, and it works the same with or without the rollout prefix in the name).

ahhhh, gotcha…

Well… there’s a heck of a lot going on in that function that may or may not trigger a crash. If I set up the change handler without handleAt:#redrawViews, sure enough, I get a crash. I I do specify the handleAt, I don’t get a crash, but the behavior is very iffy.

For example… one of the things the function does is hide the original object. So if I unhide the original object and move/rotate it, then that function will -while I’m manipulating that object – hide it again. I can well-image that Max doesn’t very much enjoy that sort of thing

So… make sure you do the bare minimum inside that function / write a separate function for the changeHandler that only handles the situation of the original object getting moved/rotated.

I took apart that function into various logical components and started adding them back in one by one. The problem seems to be a result of trying to make the handler create a reference copy of an object.

Running through all of this stuff every time the screen is redrawn is really quite excessive. I just need an update to occur when the user has let go after moving the object. Is there any way to do that???

The latest code is below. I have highlighted the change handler:


rollout RO_CSecSlider "Cross Section"
(
  local target, xDim, yDim, zDim, minPos, maxPos, dimBox, crsSec, refTgt
  local csAmt = 0
  
  fn deleteOld =
  (
	-- delete the previous dimension box, cross section box, and reference
	
	--if (isValidNode refTgt) and (refTgt != undefined) do delete refTgt
 --if (isValidNode crsSec) and (crsSec != undefined) do delete crsSec
	--if (isValidNode dimBox) and (dimBox != undefined) do delete dimBox
 delete $*_reference
 delete $Dimension_Box*
 delete $Cross_Section*
  )
  
  fn setDimensions =
  (
	-- get the x, y, and z dimensions of the target object
 if (isValidNode target) and (target != undefined) do
 (
	  xDim = (target.max.X - target.min.X)
	  yDim = (target.max.Y - target.min.Y)
	  zDim = (target.max.Z - target.min.Z)
	  minPos = target.center
   maxPos = target.center
	)
  )
  
  fn setupCSec =
  (
	if (isValidNode target) and (target != undefined) do
 (
	  -- set the box and cross section dimensions and transforms based on the direction of the cross section
   case RO_CSecSlider.rad_Direction.state of
   (
	 1: (
	   minPos.z = target.min.z; maxPos.z = target.max.z
			 dimBox = box length:yDim width:xDim height:(zDim - (zDim * csAmt)) transform:(matrix3 [1,0,0] [0,1,0]  [0,0,1]  minPos)
			 crsSec = box length:yDim width:xDim height:(zDim * csAmt)   transform:(matrix3 [1,0,0] [0,-1,0] [0,0,-1] maxPos)
	 )
   
	   2: (
	   minPos.z = target.min.z; maxPos.z = target.max.z
			 dimBox = box length:yDim width:xDim height:(zDim - (zDim * csAmt)) transform:(matrix3 [1,0,0] [0,-1,0] [0,0,-1] maxPos)
			 crsSec = box length:yDim width:xDim height:(zDim * csAmt)   transform:(matrix3 [1,0,0] [0,1,0]  [0,0,1]  minPos)
	 )
   
	 3: (
	   minPos.x = target.min.x; maxPos.x = target.max.x
			 dimBox = box length:zDim width:yDim height:(xDim - (xDim * csAmt)) transform:(matrix3 [0,1,0] [0,0,1]  [1,0,0]  minPos)
			 crsSec = box length:zDim width:yDim height:(xDim * csAmt)   transform:(matrix3 [0,1,0] [0,0,-1] [-1,0,0] maxPos)
	 )
   
	 4: (
	   minPos.x = target.min.x; maxPos.x = target.max.x
			 dimBox = box length:zDim width:yDim height:(xDim - (xDim * csAmt)) transform:(matrix3 [0,1,0] [0,0,-1] [-1,0,0] maxPos)
			 crsSec = box length:zDim width:yDim height:(xDim * csAmt)   transform:(matrix3 [0,1,0] [0,0,1]  [1,0,0]  minPos)
	 )
   
	 5: (
	   minPos.y = target.min.y; maxPos.y = target.max.y
			 dimBox = box length:zDim width:xDim height:(yDim - (yDim * csAmt)) transform:(matrix3 [1,0,0] [0,0,-1] [0,-1,0] maxPos)
			 crsSec = box length:zDim width:xDim height:(yDim * csAmt)   transform:(matrix3 [1,0,0] [0,0,1]  [0,1,0]  minPos)
	 )
   
	 6: (
	   minPos.y = target.min.y; maxPos.y = target.max.y
			 dimBox = box length:zDim width:xDim height:(yDim - (yDim * csAmt)) transform:(matrix3 [1,0,0] [0,0,1]  [0,1,0]  minPos)
			 crsSec = box length:zDim width:xDim height:(yDim * csAmt)   transform:(matrix3 [1,0,0] [0,0,-1] [0,-1,0] maxPos)
	 )
   ) -- end case
   
   dimBox.name = "Dimension_Box"
   crsSec.name = "Cross_Section"
 ) -- end if
  ) -- end fn
  
  fn greenWire =
  (
	-- display the dimension box in green wire
	dimBox.wirecolor = color 0 255 0
	dimBox.material = standard()
 dimBox.material.diffuse = color 0 255 0
 dimBox.material.wire = on
  )
  
  fn createCSec =
  (
	-- create target reference, hide original
	refTgt = reference target
	refTgt.wirecolor = target.wirecolor
 refTgt.name = target.name + "_reference"
	
 -- run a boolean operation using the target reference and the cross section box
	boolObj.createBooleanObject refTgt
	boolObj.SetOperandB refTgt crsSec 4 2
  )
  
  fn setDisplay =
  (
	if (isValidNode target and target != undefined) do
 (
	  case RO_CSecSlider.rad_Original.state of
	  (
		1: (target.xray = true;  target.isHidden = false)
	 2: (target.xray = false; target.isHidden = true)
	  ) -- end case
	) -- end if
 
 if (isValidNode dimBox and dimBox != undefined) do
 (
	  case RO_CSecSlider.rad_dimBox.state of
   (
	 1: dimBox.isHidden = false
	 2: dimBox.isHidden = true
   ) -- end case
	) -- end if
  )
  
  fn clearCSec =
  (
	deleteOld()
 
	RO_CSecSlider.pck_Select.text = "(Select an object)"
 if (isValidNode target and target != undefined) do (target.xray = false; target.isHidden = false)
 target = undefined
  )
  
  pickbutton pck_Select "(Select an object)"
  
  label lbl_Direction "Direction:"
  radioButtons rad_Direction labels:#("top", "bottom", "left", "right", "front", "back")
  
  slider cSecSlider "Amount"
  button btn_Clear "Clear"
  label lbl_Original "Original:"
  radioButtons rad_Original labels:#("xray","hidden")
  
  label lbl_DimBox "Dimension Box:"
  radioButtons rad_DimBox labels:#("wire","hidden")
  
  on pck_Select picked obj do
  (
	deleteOld()
	pck_Select.text = obj.name
	target = obj
	target.xray = true
 
	deleteOld()
	setDimensions()
 setupCSec()
 greenWire()
 createCSec()
 setDisplay()
 
 
 -- problem isolated but apparently not fixable
 
 when transform target changes handleAt:#redrawViews do
 (
   --RO_CSecSlider.deleteOld()
	  --RO_CSecSlider.setDimensions()
   --RO_CSecSlider.setupCSec()
   --RO_CSecSlider.greenWire()
   -- --RO_CSecSlider.createCSec()
   
   RO_CSecSlider.refTgt = reference RO_CSecSlider.target
   
   --setDisplay()
 )
  )
  -- end of problem area
  
  on rad_Direction changed val do
  (
	if (isValidNode target and target != undefined) do
 (
	  deleteOld()
	  setDimensions()
   setupCSec()
   greenWire()
   createCSec()
   setDisplay()
 )
  )
  
  on cSecSlider changed val do
  (
	csAmt = cSecSlider.value / 100
 if (isValidNode target and target != undefined) do
 (
	  deleteOld()
	  setDimensions()
   setupCSec()
   greenWire()
   createCSec()
   setDisplay()
 )
  )
  
  on btn_Clear pressed do clearCSec()
  
  on rad_Original changed val do setDisplay()
  on Rad_DimBox changed val do setDisplay()
  on RO_CSecSlider close do
  (
	--deleteAllChangeHandlers()
 callbacks.removeScripts
 clearCSec()
  )
) -- end rollout
createDialog RO_CSecSlider

I don’t think there’s a callback that does a kind of “when <object> stops being moved/rotated/scaled” sort of thing; you’d have to write your own work-around by, for example, using a timer and checking between two ticks of the timer whether the object’s transform has changed – if it hasn’t, assume the user is done transforming the object and then call the ‘heavy’ function.


myDotNetTimer = dotNetObject "System.Windows.Forms.Timer" 

myDotNetTimer.interval = 1000

lastTickTransform = undefined

fn helloWorld = ( format "% - Hello World!
" (timeStamp()) )

myObject = sphere()

fn doDelay = (
	format "% - Tick
" (timeStamp())
	if (lastTickTransform == undefined) then ( lastTickTransform = myObject.transform)
	else (
		if ((myObject.pos == lastTickTransform.pos) AND (myObject.rotation == lastTickTransform.rotation) AND (myObject.scale == lastTickTransform.scale)) then (
			myDotNetTimer.stop()
			helloWorld()
		)
		else ( lastTickTransform = myObject.transform	)
	)
)
doDelay()

dotnet.addEventHandler myDotNetTimer "tick" doDelay

when transform myObject changes handleAt:#redrawViews do (
	format "% - transform changed
" (timeStamp())
	lastTickTransform = myObject.transform
	myDotNetTimer.stop(); myDotNetTimer.start()
)

Thanks for all the help, Z…

I’m not very good with dotNet – I tried learning it once but it confuses the heck out of me. Anyway, I tried to incorporate what you showed me into the script, and have included the latest version below.

Right now it sometimes works when you move an object. The moment you try to rotate or scale anything, the code stops working, and the ticks keep going until you shut them up with the clear button or by closing the rollout. Once this has happened, the code will not work again without restarting MAX.

Also, trying to move anything around too erratically crashes the program.

I can tell I’m not doing this right, what should I be doing differently?

global RO_CSecSlider
myDotNetTimer = dotNetObject "System.Windows.Forms.Timer" 
myDotNetTimer.interval = 1000

lastTickTransform = undefined

fn doDelay =
(
  format "% - Tick
" (timeStamp())
  
  if (isValidNode RO_CSecSlider.target and RO_CSecSlider.target != undefined) do
  (
	if lastTickTransform == undefined
	  then lastTickTransform = RO_CSecSlider.target.transform
   else
   (
	 if (RO_CSecSlider.target.pos == lastTickTransform.pos) AND (RO_CSecSlider.target.rotation == lastTickTransform.rotation) AND (RO_CSecSlider.target.scale == lastTickTransform.scale)
	then
	(
	  myDotNetTimer.stop()
			RO_CSecSlider.createCSec()
	)
	else lastTickTransform = RO_CSecSlider.target.transform
 ) -- end if/then/else
 
  ) -- end if
) -- end fn

dotNet.addEventHandler myDotNetTimer "tick" doDelay


rollout RO_CSecSlider "Cross Section"
(
  local target, xDim, yDim, zDim, minPos, maxPos, dimBox, crsSec, refTgt
  local csAmt = 0

  fn deleteOld =
  (
	-- delete the previous dimension box, cross section box, and reference

 delete $*_reference
 delete $Dimension_Box*
 delete $Cross_Section*
  )
  
  fn setDisplay =
  (
	if (isValidNode target and target != undefined) do
 (
	  case RO_CSecSlider.rad_Original.state of
	  (
		1: (target.xray = true;  target.isHidden = false)
	 2: (target.xray = false; target.isHidden = true)
	  ) -- end case
	) -- end if
 
 if (isValidNode dimBox and dimBox != undefined) do
 (
	  case RO_CSecSlider.rad_dimBox.state of
   (
	 1: dimBox.isHidden = false
	 2: dimBox.isHidden = true
   ) -- end case
	) -- end if
  )
  
  fn createCSec =
  (
	deleteOld()
	setDisplay()
  
	-- get the x, y, and z dimensions of the target object
 if (isValidNode target) and (target != undefined) do
 (
	  xDim = (target.max.X - target.min.X)
	  yDim = (target.max.Y - target.min.Y)
	  zDim = (target.max.Z - target.min.Z)

	  minPos = target.center
   maxPos = target.center
	)

	if (isValidNode target) and (target != undefined) do
 (
	  -- set the box and cross section dimensions and transforms based on the direction of the cross section
   case RO_CSecSlider.rad_Direction.state of
   (
	 1: (
	   minPos.z = target.min.z; maxPos.z = target.max.z
			 dimBox = box length:yDim width:xDim height:(zDim - (zDim * csAmt)) transform:(matrix3 [1,0,0] [0,1,0]  [0,0,1]  minPos)
			 crsSec = box length:yDim width:xDim height:(zDim * csAmt)   transform:(matrix3 [1,0,0] [0,-1,0] [0,0,-1] maxPos)
	 )
   
	   2: (
	   minPos.z = target.min.z; maxPos.z = target.max.z
			 dimBox = box length:yDim width:xDim height:(zDim - (zDim * csAmt)) transform:(matrix3 [1,0,0] [0,-1,0] [0,0,-1] maxPos)
			 crsSec = box length:yDim width:xDim height:(zDim * csAmt)   transform:(matrix3 [1,0,0] [0,1,0]  [0,0,1]  minPos)
	 )
   
	 3: (
	   minPos.x = target.min.x; maxPos.x = target.max.x
			 dimBox = box length:zDim width:yDim height:(xDim - (xDim * csAmt)) transform:(matrix3 [0,1,0] [0,0,1]  [1,0,0]  minPos)
			 crsSec = box length:zDim width:yDim height:(xDim * csAmt)   transform:(matrix3 [0,1,0] [0,0,-1] [-1,0,0] maxPos)
	 )
   
	 4: (
	   minPos.x = target.min.x; maxPos.x = target.max.x
			 dimBox = box length:zDim width:yDim height:(xDim - (xDim * csAmt)) transform:(matrix3 [0,1,0] [0,0,-1] [-1,0,0] maxPos)
			 crsSec = box length:zDim width:yDim height:(xDim * csAmt)   transform:(matrix3 [0,1,0] [0,0,1]  [1,0,0]  minPos)
	 )
   
	 5: (
	   minPos.y = target.min.y; maxPos.y = target.max.y
			 dimBox = box length:zDim width:xDim height:(yDim - (yDim * csAmt)) transform:(matrix3 [1,0,0] [0,0,-1] [0,-1,0] maxPos)
			 crsSec = box length:zDim width:xDim height:(yDim * csAmt)   transform:(matrix3 [1,0,0] [0,0,1]  [0,1,0]  minPos)
	 )
   
	 6: (
	   minPos.y = target.min.y; maxPos.y = target.max.y
			 dimBox = box length:zDim width:xDim height:(yDim - (yDim * csAmt)) transform:(matrix3 [1,0,0] [0,0,1]  [0,1,0]  minPos)
			 crsSec = box length:zDim width:xDim height:(yDim * csAmt)   transform:(matrix3 [1,0,0] [0,0,-1] [0,-1,0] maxPos)
	 )
   )
   
   dimBox.name = "Dimension_Box"
   crsSec.name = "Cross_Section"
 )

	-- display the dimension box in green wire
	dimBox.wirecolor = color 0 255 0
	dimBox.material = standard()
 dimBox.material.diffuse = color 0 255 0
 dimBox.material.wire = on

	-- create target reference, hide original
	refTgt = reference target
	refTgt.wirecolor = target.wirecolor
 refTgt.name = target.name + "_reference"
	
 -- run a boolean operation using the target reference and the cross section box
	boolObj.createBooleanObject refTgt
	boolObj.SetOperandB refTgt crsSec 4 2
  )
  
  fn clearCSec =
  (
	deleteOld()
 
	RO_CSecSlider.pck_Select.text = "(Select an object)"
 if (isValidNode target and target != undefined) do (target.xray = false; target.isHidden = false)
 target = undefined
  )
  
  pickbutton pck_Select "(Select an object)"
  
  label lbl_Direction "Direction:"
  radioButtons rad_Direction labels:#("top", "bottom", "left", "right", "front", "back")
  
  slider cSecSlider "Amount"
  button btn_Clear "Clear"

  label lbl_Original "Original:"
  radioButtons rad_Original labels:#("xray","hidden")
  
  label lbl_DimBox "Dimension Box:"
  radioButtons rad_DimBox labels:#("wire","hidden")
  
  on pck_Select picked obj do
  (
	deleteOld()
	pck_Select.text = obj.name
	target = obj

	target.xray = true

 createCSec()
 doDelay()
 
 when transform RO_CSecSlider.target changes handleAt:#redrawViews do
 (
   --RO_CSecSlider.deleteOld()

   format "% - transform changed
" (timeStamp())
   lastTickTransform = RO_CSecSlider.target.transform
   myDotNetTimer.stop(); myDotNetTimer.start()
 )
  )
  
  on rad_Direction changed val do
  (
	if (isValidNode target and target != undefined) do createCSec()
  )
  
  on cSecSlider changed val do
  (
	csAmt = cSecSlider.value / 100
 if (isValidNode target and target != undefined) do createCSec()
  )

  on btn_Clear pressed do
  (
	myDotNetTimer.stop()
	clearCSec()
  )
  
  on rad_Original changed val do setDisplay()
  on Rad_DimBox changed val do setDisplay()

  on RO_CSecSlider close do
  (
	--deleteAllChangeHandlers()
 myDotNetTimer.stop()
 clearCSec()
 RO_CSecSlider = undefined
  )
) -- end rollout

createDialog RO_CSecSlider
1 Reply
(@zeboxx2)
Joined: 1 year ago

Posts: 0

Any situation in which it doesn’t work?

Whoops. The line…

 if (RO_CSecSlider.target.pos == lastTickTransform.pos) AND (RO_CSecSlider.target.rotation == lastTickTransform.rotation) AND (RO_CSecSlider.target.scale == lastTickTransform.scale)

…SHOULD be…

 if (RO_CSecSlider.target.transform.pos == lastTickTransform.pos) AND (RO_CSecSlider.target.transform.rotation == lastTickTransform.rotation) AND (RO_CSecSlider.target.transform.scale == lastTickTransform.scale)

Not seeing that happening, and my mouse is getting a pretty good workout 😮 Does this happen while you move, or after the delay?

Okay, it’s definitely working for all three now, but I am still having the crashing problem. After a lot of experimentation, I have figured out that it occurs when I drag an object, hold it still without letting go (long enough for the function to execute) and then move it again. That crashes Max every time I do it.

The problem does not occur with the sample timer/delay script you gave me, but then as you pointed out, I am calling a rather “heavy” function with mine.

I’m betting there are some conditionals I could be putting in, or maybe a try/catch statement, that would prevent the crashing, but I would need to know where the problem is originating, and Max is crashing without giving me a useful error message.

It would be really nice to have something like:

if (run_the_function != crash_the_program) do (run_the_function)

built into every scripting language. XD

Page 1 / 2