Tape tool as floater UI (Script Wish)


#5

Can the pin feature be automatically called when the UI is there and I press the tape-tool?
Even better: when I execute the code, the UI is built, followed by running the tape-tool for me. Then I could create a button out of your script.


#6

Regarding the idea of automatically creating a Tape when you run the script, although a Tape can be created from a script, I’ve seen issues when trying to select it. The best would be to create a custom Tape plugin.

Updated code below.

#7

This time, nothing happens.
The UI is there, but everything is grayed out.
I tried both creating UI first, tape after, and tape first and UI after, with same result


#8

Don’t know what it could be. Tested it in different Max versions and it works. Just have the UI opened and then manually create a Tape and it should update the values.

EDIT 1: I’ve updated the code, it should now work with any amount of Tapes and keep the values of the latest selected one.
EDIT 2: Improved it a bit. Should have better response and work better when changing selections.
EDIT 3: Fixed bugs and converted to MacroScript.

macroScript Floater_Tape
category:"Tools"
tooltip:"Floater Tape"
buttontext:"Tape"
icon:#("Helpers",5)
(
	global RO_TAPE_FLOATER
	
	on execute do
	(
		rollout RO_TAPE_FLOATER "Tape Floater" width:140 height:194
		(
			label   lbl      "Length:" pos:[12, 4]
			spinner spn1     ""        pos:[ 8,20] fieldwidth:112 range:[0,1e9,0] type:#worldunits enabled:false
			label   lbl_node ""        pos:[12,40] width:104
			
			label lbl1 "To X Axis:"   pos:[20, 64]
			label lbl2 "To Y Axis:"   pos:[20, 84]
			label lbl3 "To Z Axis:"   pos:[20,104]
			label lbl4 "To XY Plane:" pos:[ 8,134]
			label lbl5 "To YZ Plane:" pos:[ 8,154]
			label lbl6 "To ZX Plane:" pos:[ 8,174]
			
			label edt1 "" pos:[74, 64] width:104
			label edt2 "" pos:[74, 84] width:104
			label edt3 "" pos:[74,104] width:104
			label edt4 "" pos:[74,134] width:104
			label edt5 "" pos:[74,154] width:104
			label edt6 "" pos:[74,174] width:104
			
			local plane_xy = [0,0,1]
			local plane_yz = [1,0,0]
			local plane_zx = [0,1,0]
			local node     = undefined
			local ctrl     = float_script()
			
			fn DeleteHandlers =
			(
				deleteallchangehandlers id:#ID_0X5D5F4A2C
				gc light:on
			)
				
			fn UpdateUI =
			(
				if node == undefined or isdeleted node or node.target == undefined or isdeleted node.target then
				(
					spn1.value = 0
					lbl_node.text = edt1.text = edt2.text = edt3.text = edt4.text = edt5.text = edt6.text = ""
					DeleteHandlers()
				)else(
					p1 = node.pos
					p2 = node.target.pos
					
					dir = normalize (p1-p2)
					
					angleToAxis_X = acos dir.x
					angleToAxis_Y = acos dir.y
					angleToAxis_Z = acos dir.z
					
					angleToPlane_XY = abs ((acos (dot dir plane_xy))-90)
					angleToPlane_YZ = abs ((acos (dot dir plane_yz))-90)
					angleToPlane_ZX = abs ((acos (dot dir plane_zx))-90)
					
					spn1.value = distance p1 p2
					
					edt1.text = angleToAxis_X as string
					edt2.text = angleToAxis_Y as string
					edt3.text = angleToAxis_Z as string
					
					edt4.text = angleToPlane_XY as string
					edt5.text = angleToPlane_YZ as string
					edt6.text = angleToPlane_ZX as string
				)
			)
			
			fn GetSelection =
			(
				sel = selection[1]
				
				if sel == undefined do return()
				
				if iskindof sel tape then
				(
					node = sel
				)else if iskindof (refs.dependentnodes sel)[1] tape then
				(
					node = (refs.dependentnodes sel)[1]
				)else(
					node = undefined
				)
				
				if node != undefined and node.target != undefined do
				(
					DeleteHandlers()
					if ctrl.VariableExists "TAPE" then ctrl.SetObject "TAPE" node.controller else ctrl.AddObject "TAPE" node.controller
					
					when parameters ctrl changes id:#ID_0X5D5F4A2C do UpdateUI()
					when node deleted id:#ID_0X5D5F4A2C do
					(
						node = undefined
						UpdateUI()
					)
					
					lbl_node.text = node.name
					
					ctrl.update()
				)
			)
			
			on RO_TAPE_FLOATER open do
			(
				callbacks.removescripts id:#ID_0X350B32A9
				callbacks.addscript #selectionSetChanged "::RO_TAPE_FLOATER.GetSelection()" id:#ID_0X350B32A9
				GetSelection()
			)
			
			on RO_TAPE_FLOATER close do
			(
				callbacks.removescripts id:#ID_0X350B32A9
				DeleteHandlers()
			)
		)
		
		createdialog RO_TAPE_FLOATER style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)

		startobjectcreation Tape
	
	) -- End Excecute
  
	on isChecked return (mcrUtils.IsCreating Tape)
  
)

Here is a modified version, as suggested by Denis, with TextBox controls so you can copy the values.

macroScript Floater_Tape
category:"Tools"
tooltip:"Floater Tape"
buttontext:"Tape"
icon:#("Helpers",5)
(
	global RO_TAPE_FLOATER
	
	on execute do
	(
		rollout RO_TAPE_FLOATER "Tape Floater" width:140 height:194
		(
			label   lbl      "Length:" pos:[12, 4]
			spinner spn1     ""        pos:[ 8,20] fieldwidth:112 range:[0,1e9,0] type:#worldunits enabled:false
			label   lbl_node ""        pos:[12,40] width:104
			
			label lbl1 "To X Axis:"   pos:[20, 64]
			label lbl2 "To Y Axis:"   pos:[20, 84]
			label lbl3 "To Z Axis:"   pos:[20,104]
			label lbl4 "To XY Plane:" pos:[ 8,134]
			label lbl5 "To YZ Plane:" pos:[ 8,154]
			label lbl6 "To ZX Plane:" pos:[ 8,174]
			
			dotnetcontrol edt1 "TextBox" pos:[70, 60] width:62
			dotnetcontrol edt2 "TextBox" pos:[70, 80] width:62
			dotnetcontrol edt3 "TextBox" pos:[70,100] width:62
			dotnetcontrol edt4 "TextBox" pos:[70,130] width:62
			dotnetcontrol edt5 "TextBox" pos:[70,150] width:62
			dotnetcontrol edt6 "TextBox" pos:[70,170] width:62
			
			local plane_xy = [0,0,1]
			local plane_yz = [1,0,0]
			local plane_zx = [0,1,0]
			local node     = undefined
			local ctrl     = float_script()
			
			fn DeleteHandlers =
			(
				deleteallchangehandlers id:#ID_0X5D5F4A2C
				gc light:on
			)
				
			fn UpdateUI =
			(
				if node == undefined or isdeleted node or node.target == undefined or isdeleted node.target then
				(
					spn1.value = 0
					lbl_node.text = edt1.text = edt2.text = edt3.text = edt4.text = edt5.text = edt6.text = ""
					DeleteHandlers()
				)else(
					p1 = node.pos
					p2 = node.target.pos
					
					dir = normalize (p1-p2)
					
					angleToAxis_X = acos dir.x
					angleToAxis_Y = acos dir.y
					angleToAxis_Z = acos dir.z
					
					angleToPlane_XY = abs ((acos (dot dir plane_xy))-90)
					angleToPlane_YZ = abs ((acos (dot dir plane_yz))-90)
					angleToPlane_ZX = abs ((acos (dot dir plane_zx))-90)
					
					spn1.value = distance p1 p2
					
					edt1.text = angleToAxis_X as string
					edt2.text = angleToAxis_Y as string
					edt3.text = angleToAxis_Z as string
					
					edt4.text = angleToPlane_XY as string
					edt5.text = angleToPlane_YZ as string
					edt6.text = angleToPlane_ZX as string

					edt1.update()
					edt2.update()
					edt3.update()
					edt4.update()
					edt5.update()
					edt6.update()
				)
			)
			
			fn GetSelection =
			(
				sel = selection[1]
				
				if sel == undefined do return()
				
				if iskindof sel tape then
				(
					node = sel
				)else if iskindof (refs.dependentnodes sel)[1] tape then
				(
					node = (refs.dependentnodes sel)[1]
				)else(
					node = undefined
				)
				
				if node != undefined and node.target != undefined do
				(
					DeleteHandlers()
					if ctrl.VariableExists "TAPE" then ctrl.SetObject "TAPE" node.controller else ctrl.AddObject "TAPE" node.controller
					
					when parameters ctrl changes id:#ID_0X5D5F4A2C do UpdateUI()
					when node deleted id:#ID_0X5D5F4A2C do
					(
						node = undefined
						UpdateUI()
					)
					
					lbl_node.text = node.name
					
					ctrl.update()
				)
			)
			
			fn SetUpControls =
			(
				for j in RO_TAPE_FLOATER.controls where iskindof j dotNetControl do j.ReadOnly = true
			)
			
			on RO_TAPE_FLOATER open do
			(
				SetUpControls()
				callbacks.removescripts id:#ID_0X350B32A9
				callbacks.addscript #selectionSetChanged "::RO_TAPE_FLOATER.GetSelection()" id:#ID_0X350B32A9
				GetSelection()
			)
			
			on RO_TAPE_FLOATER close do
			(
				callbacks.removescripts id:#ID_0X350B32A9
				DeleteHandlers()
			)
			
		)
		createdialog RO_TAPE_FLOATER style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
		
		startobjectcreation Tape
		
	) -- End Excecute
  
	on isChecked return (mcrUtils.IsCreating Tape)
	
)

A last version with buttons for copying the values. A bit cluttered UI but does the job.
Three flavors, for all tastes.

macroScript Floater_Tape
category:"Tools"
tooltip:"Floater Tape"
buttontext:"Tape"
icon:#("Helpers",5)
(
	global RO_TAPE_FLOATER
	
	on execute do
	(
		rollout RO_TAPE_FLOATER "Tape Floater" width:156 height:194
		(
			label   lbl      "Length:" pos:[12, 4]
			spinner spn1     ""        pos:[ 8,20] fieldwidth:108 range:[0,1e9,0] type:#worldunits enabled:false
			label   lbl_node ""        pos:[12,40] width:104
			
			label lbl1 "To X Axis:"   pos:[20, 64]
			label lbl2 "To Y Axis:"   pos:[20, 84]
			label lbl3 "To Z Axis:"   pos:[20,104]
			label lbl4 "To XY Plane:" pos:[ 8,134]
			label lbl5 "To YZ Plane:" pos:[ 8,154]
			label lbl6 "To ZX Plane:" pos:[ 8,174]
			
			label edt1 "0.0" pos:[74, 64] width:58
			label edt2 "0.0" pos:[74, 84] width:58
			label edt3 "0.0" pos:[74,104] width:58
			label edt4 "0.0" pos:[74,134] width:58
			label edt5 "0.0" pos:[74,154] width:58
			label edt6 "0.0" pos:[74,174] width:58
			
			button bt0 "C" pos:[132, 18] width:18 height:18 tooltip:"Copy Value"
			button bt1 "C" pos:[132, 62] width:18 height:18 tooltip:"Copy Value"
			button bt2 "C" pos:[132, 82] width:18 height:18 tooltip:"Copy Value"
			button bt3 "C" pos:[132,102] width:18 height:18 tooltip:"Copy Value"
			button bt4 "C" pos:[132,132] width:18 height:18 tooltip:"Copy Value"
			button bt5 "C" pos:[132,152] width:18 height:18 tooltip:"Copy Value"
			button bt6 "C" pos:[132,172] width:18 height:18 tooltip:"Copy Value"
			
			local plane_xy = [0,0,1]
			local plane_yz = [1,0,0]
			local plane_zx = [0,1,0]
			local node     = undefined
			local ctrl     = float_script()
			
			fn DeleteHandlers =
			(
				deleteallchangehandlers id:#ID_0X5D5F4A2C
				gc light:on
			)
				
			fn UpdateUI =
			(
				if node == undefined or isdeleted node or node.target == undefined or isdeleted node.target then
				(
					spn1.value    = 0
					lbl_node.text = ""
					edt1.text     = edt2.text = edt3.text = edt4.text = edt5.text = edt6.text = "0.0"
					DeleteHandlers()
				)else(
					p1 = node.pos
					p2 = node.target.pos
					
					dir = normalize (p1-p2)
					
					angleToAxis_X = acos dir.x
					angleToAxis_Y = acos dir.y
					angleToAxis_Z = acos dir.z
					
					angleToPlane_XY = abs ((acos (dot dir plane_xy))-90)
					angleToPlane_YZ = abs ((acos (dot dir plane_yz))-90)
					angleToPlane_ZX = abs ((acos (dot dir plane_zx))-90)
					
					spn1.value = distance p1 p2
					
					edt1.text = angleToAxis_X as string
					edt2.text = angleToAxis_Y as string
					edt3.text = angleToAxis_Z as string
					
					edt4.text = angleToPlane_XY as string
					edt5.text = angleToPlane_YZ as string
					edt6.text = angleToPlane_ZX as string
				)
			)
			
			fn GetSelection =
			(
				sel = selection[1]
				
				if sel == undefined do return()
				
				if iskindof sel tape then
				(
					node = sel
				)else if iskindof (refs.dependentnodes sel)[1] tape then
				(
					node = (refs.dependentnodes sel)[1]
				)else(
					node = undefined
				)
				
				if node != undefined and node.target != undefined do
				(
					DeleteHandlers()
					if ctrl.VariableExists "TAPE" then ctrl.SetObject "TAPE" node.controller else ctrl.AddObject "TAPE" node.controller
					
					when parameters ctrl changes id:#ID_0X5D5F4A2C do UpdateUI()
					when node deleted id:#ID_0X5D5F4A2C do
					(
						node = undefined
						UpdateUI()
					)
					
					lbl_node.text = node.name
					
					ctrl.update()
				)
			)
			
			fn SetUpControls =
			(
				for j in RO_TAPE_FLOATER.controls where iskindof j dotNetControl do j.ReadOnly = true
			)
			
			on RO_TAPE_FLOATER open do
			(
				SetUpControls()
				callbacks.removescripts id:#ID_0X350B32A9
				callbacks.addscript #selectionSetChanged "::RO_TAPE_FLOATER.GetSelection()" id:#ID_0X350B32A9
				GetSelection()
			)
			
			on RO_TAPE_FLOATER close do
			(
				callbacks.removescripts id:#ID_0X350B32A9
				DeleteHandlers()
			)
			
			on bt0 pressed do setclipboardtext (spn1.value as string)
			on bt1 pressed do setclipboardtext edt1.text
			on bt2 pressed do setclipboardtext edt2.text
			on bt3 pressed do setclipboardtext edt3.text
			on bt4 pressed do setclipboardtext edt4.text
			on bt5 pressed do setclipboardtext edt5.text
			on bt6 pressed do setclipboardtext edt6.text
			
		)
		createdialog RO_TAPE_FLOATER style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
		
		startobjectcreation Tape
		
	) -- End Excecute
  
	on isChecked return (mcrUtils.IsCreating Tape)
	
)

#9

Works wonderful now! Fantastic work :smiley:

The only tiny bug, nothing critical, is when you drag a tape and right-click to cancel on the fly. I get an error message
"No ""isDeleted"" function for undefined"

Beside than that, works great. Finally a Tape Floater :smiley:

edit: I tried to add this so that the tool works as one:
on execute do StartObjectCreation Tape
on isChecked return mcrUtils.IsCreating Tape

I tried to put it in the beginning and in the end. It worked once, and then it broke. I think it might be because of the right-click -error message I mentioned above.


#10

Glad it worked.

Regarding the error you get, same as the previous error, I don’t get any error when the script is used as is and you create the Tape from the Max UI (not even if I start the tool from the listener), but haven’t tried to put it in a Macro.

What Max version are you using?

What would be best for you, to create a Macro out of the script or to add a button to the script to create a new Tape?

EDIT: Got the error when running from a Macro :slight_smile:. Well, let me know what would you like to do from my previous question and I’ll see if I can finish it.


#11

Code updated. I think everything works fine now as a MacroScript.


#12

i suggest to replace all value outputs with readonly textbox controls to be able copy values in the clipboard.


#13

Thanks Denis.

The fields where previously Edittext controls, but changed them to labels because they perform a lot better. There is a lag in other controls while moving the tape fast. The other native control that performs well are the spinners. Tried some .Net control but they all have a little lag. Perhaps they redraw or update when the system is idle, I don’t know.

Do you know how to make the TextBox control perform better, with no redrawing lag?


#14

it’s not really ‘lagging’… it’s not updating UI controls during some other scripts running.

the way to update ui controls is to use windows.processPostedMessages after setting ui controls values

the SDK enough solution is MAXScript_interface->CheckMAXMessages()


#15

I think the word “lag” isn’t completly wrong, we use it often in programming to describe some sort of delay between events, you know what I mean.

“Lag: A period of time between one event and another.”

As you said, windows.processPostedMessages() should dispatch all the messages, although it may not always ensures a UI redraw or update. If I am not wrong, it would also dispatch other messages, which may cause troubles, wouldn’t it?

In this case it updates the UI, but due to the amount of calls the controller makes, I am unsure of whether or not it is good to put it there.

If someone would like to test its stability you can just add the following line to the code below the “edt6.text = angleToPlane_ZX as string” line:

windows.processPostedMessages()

Just needed for the version that uses the .Net controls.


#16

windows.processPostedMessages() is about window messages. it doesn’t effect any system messages (like system notifications or events).

in your case it’s safe to use… slowing down is possible, but very little

.net controls will meet the same problem with update. or might be worth.


#17

Thank you very much !

The new code works way better than the original tape :smiley:
I love the copy-button

I suggest you publish it on Scriptspot as well :+1:


#18
macroScript Popup_Tape
	category:"Tools"
	tooltip:"Popup Tape Params"
	buttontext:"POPUP TAPE"
(
	global PopupTapeRollout = if PopupTapeRollout != undefined do PopupTapeRollout

	--local dialog_width = 220 
	
	rollout PopupTapeRollout "Popup Tape Params" width:220
	(
		local opened = off
		local placed = if placed != undefined do placed
		local use_units = if use_units != undefined then use_units else off
		
		local w_dl = 220
		
		local w_sh = 54
		local w_st = w_dl - w_sh - 10
		local w_tx = w_st/3
		local w_sp = 2*w_tx
		local w_bx = 3*w_tx
		
		local w_im = w_sh + 4
		local h_im = 18
		
		fn isValidTape node = (iskindof node Tape and not isdeleted node and isvalidnode node.target)
		local tape_node = if tape_node != undefined do tape_node
			
		group "Current Tape: "
		(
			label length_lb "Length:" align:#right offset:[-w_st,4]
			edittext length_ed pos:[w_sh,length_lb.pos.y-1] width:w_sp
			checkbox units_ch "Units" checked:use_units pos:([w_sh + w_sp + 6,length_lb.pos.y])

			label tape_lb "Name:" align:#right offset:[-w_st,6]
			edittext tape_name_tx pos:[w_sh,tape_lb.pos.y-1] width:w_bx
		)
		
		group "World Space Angles: "
		(
			label axis_lb "Axis:" align:#right offset:[-w_st,4]
			edittext angle_xx_ed pos:[w_sh + 0*w_tx,axis_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_yy_ed pos:[w_sh + 1*w_tx,axis_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_zz_ed pos:[w_sh + 2*w_tx,axis_lb.pos.y-1] width:w_tx readonly:on
			imgtag axis_im pos:[0,axis_lb.pos.y-2] width:w_im height:h_im transparent:black tooltip:"Axis X Y Z"

			label plane_lb "Plane:" align:#right offset:[-w_st,4]
			edittext angle_xy_ed pos:[w_sh + 0*w_tx,plane_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_yz_ed pos:[w_sh + 1*w_tx,plane_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_zx_ed pos:[w_sh + 2*w_tx,plane_lb.pos.y-1] width:w_tx readonly:on
			imgtag plane_im pos:[0,plane_lb.pos.y-2] width:w_im height:h_im transparent:black tooltip:"Plane XY YZ ZX"
		)
				
		fn updateNameUI = if isValidTape tape_node do
		(
			tape_name_tx.text = tape_node.name
		)
		
		fn roundFloat d pre:0.01 =
		(
			d = (d as float)/pre
			v = if (d - (v1 = floor d)) > ((v2 = ceil d) - d) then v2 else v1 
			v*pre
		)
		fn axisAngle dir axis calc: control: =
		(
			a = roundFloat (abs (calc (dot dir axis)))
			if control != unsupplied then
			(
				if control.text as float != a do 
				(
					control.text = (a as string)
				)
				ok
			)
			else a
		)
		fn updateDataUI = if isValidTape tape_node do
		(			
			d = distance tape_node tape_node.target
			length_ed.text = if not use_units then d as string else units.formatValue d 

			dir = tape_node.dir
			
			axisAngle dir x_axis calc:acos control:angle_xx_ed
			axisAngle dir y_axis calc:acos control:angle_yy_ed
			axisAngle dir z_axis calc:acos control:angle_zz_ed
			
			axisAngle dir z_axis calc:asin control:angle_xy_ed
			axisAngle dir x_axis calc:asin control:angle_yz_ed
			axisAngle dir y_axis calc:asin control:angle_zx_ed

			windows.processPostedMessages()
		)
		
		on units_ch changed state do 
		(
			use_units = state
			updateDataUI()
		)
		
		fn deleteHandlers =
		(
			deleteallchangehandlers id:#popup_tape
			gc light:on
		)
		fn setupHandlers =
		(
			deleteHandlers()
			
			when transform tape_node changes id:#popup_tape do updateDataUI()
			when name tape_node change id:#popup_tape do updateNameUI()
		)
		
		fn getTapeNode = if isvalidnode (node = selection[1]) do
		(
			if iskindof node Targetobject do node = refs.dependentnodes node firstonly:on
			
			if isValidTape node do
			(
				tape_node = node	
				setupHandlers() 

				updateDataUI()
				updateNameUI()
			)
		)
		
		on PopupTapeRollout close do
		(
			opened = off
			placed = getdialogpos PopupTapeRollout
			
			callbacks.removescripts id:#popup_tape
			deleteHandlers()
			updateToolbarButtons()
		)

		on PopupTapeRollout open do
		(
			opened = on
			placed = getdialogpos PopupTapeRollout
			
			callbacks.removescripts id:#popup_tape
			callbacks.addscript #selectionSetChanged "::PopupTapeRollout.getTapeNode()" id:#popup_tape
			callbacks.addscript #unitsChange "::PopupTapeRollout.updateDataUI()" id:#popup_tape
			getTapeNode()
			updateToolbarButtons()
		)
	)
	
	
	fn isOpened = (iskindof ::PopupTapeRollout RolloutClass and ::PopupTapeRollout.opened)
	on isChecked do isOpened()

	on execute do
	(
		if isOpened() then
		(
			try (::PopupTapeRollout.placed = getdialogpos ::PopupTapeRollout) catch()
			try (destroydialog ::PopupTapeRollout) catch()
		)
		else
		(
			pos = if iskindof PopupTapeRollout RolloutClass do PopupTapeRollout.placed
			pos = if iskindof pos Point2 then pos else unsupplied
			createdialog PopupTapeRollout style:#(#style_titlebar, #style_toolwindow, #style_sysmenu) pos:pos
		)
		updateToolbarButtons()
	)
)
try (destroydialog ::PopupTapeRollout) catch()

this is my version… i changed a little callbacks logic, and UI. the UI might look OFF in normal display because i tested it for 4K only (what i have at home)

also couple features added (tape name update, units option, etc.)


#19

Thanks Denis,

Personally, I appreciate different approaches, because it reveals different possibilities.
Here is my feedback

  • I like the UI, it offers many feature. Interresting that once you click and move the target, it sticks to the mouse pointer - what was your motivation for that feature?
  • I like that the toolbar button toggles the UI, however, it does not turn on the tape for me. The toggle could include the tape tool as well. But then we would need a tape-button in the UI as well, because in Jorge’s approach, pressing the toolbar button toggles the tape alone (like in the native tape tool). The way I use the tape tool is mostly creating it and RMB click to cancel it on the fly once I got what I needed, which is measuring. After cancelling the tool, I might need it again - so pressing the toolbar button would in this case turn off the UI instead of giving me a new tape. Optionally, RMB-cancelling the tool could simply turn of the UI as well, so that I would need to press the toolbar button again to get the tool plus UI. This could work :slight_smile:
  • Issues: I get two MAXScript Change Handler Exception: Unable to convert: undefined to type: <node> when I click the Tape Tool

#20

Issues: I get two MAXScript Change Handler Exception: Unable to convert: undefined to type: when I click the Tape Tool

that was a bug. fixed.

about combining the pop-up tape params dialog and tape creation tool… i don’t want to combine it one tool.
the dialog binds to any newly created tape object or to any selected tape or its target.


#21

I see the weakness of using windows.processPostedMessages () …
Unfortunately, this affects internal system messages too.

actually the most interesting part of the task is the finding a ‘ui lagging’ free solution.


#22

Great!
There is one popup left - it occurs when I cancel a tape by left clicking


#23

fixed. Thanks for debugging


#24
macroScript Popup_Tape
	category:"Tools"
	tooltip:"Popup Tape Params"
	buttontext:"POPUP TAPE"
(
	global PopupTapeRollout = if PopupTapeRollout != undefined do PopupTapeRollout

	--local dialog_width = 220 
	
	rollout PopupTapeRollout "Popup Tape Params" width:220
	(
		local opened = off
		local placed = if placed != undefined do placed
		
		local use_units = on
		
		local dialog_width = 220
		
		local w_sh = 54
		local w_st = dialog_width - w_sh - 10
		local w_tx = w_st/3
		local w_sp = 2*w_tx - 12
		local w_bx = 3*w_tx
		
		local w_im = w_sh + 4
		local h_im = 18
		
		fn isValidTape node = (iskindof node Tape and not isdeleted node and isvalidnode node.target)
		local tape_node = if tape_node != undefined do tape_node
			
		group "Current Tape: "
		(
			label length_lb "Length:" align:#right offset:[-w_st,4]
			spinner length_sp pos:[w_sh,length_lb.pos.y] fieldwidth:w_sp type:(if use_units then #worldunits else #float) range:[0.0001,1e9,0] --scale:(universeScale 1)
			--checkbox units_ch "Units" checked:use_units pos:([w_sh + w_sp + 18,length_lb.pos.y]) enabled:off -- built-in mxs doesn't support changing spinner type after creation 

			label tape_lb "Name:" align:#right offset:[-w_st,6]
			edittext tape_name_tx pos:[w_sh,tape_lb.pos.y-1] width:w_bx
		)
		
		local null = "0.0"
		group "World Space Angles: "
		(
			label axis_lb "Axis:" align:#right offset:[-w_st,4]
			edittext angle_xx_ed text:null pos:[w_sh + 0*w_tx,axis_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_yy_ed text:null pos:[w_sh + 1*w_tx,axis_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_zz_ed text:null pos:[w_sh + 2*w_tx,axis_lb.pos.y-1] width:w_tx readonly:on
			imgtag axis_im pos:[0,axis_lb.pos.y-2] width:w_im height:h_im transparent:black tooltip:"Axis X Y Z"

			label plane_lb "Plane:" align:#right offset:[-w_st,4]
			edittext angle_xy_ed text:null pos:[w_sh + 0*w_tx,plane_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_yz_ed text:null pos:[w_sh + 1*w_tx,plane_lb.pos.y-1] width:w_tx readonly:on
			edittext angle_zx_ed text:null pos:[w_sh + 2*w_tx,plane_lb.pos.y-1] width:w_tx readonly:on
			imgtag plane_im pos:[0,plane_lb.pos.y-2] width:w_im height:h_im transparent:black tooltip:"Plane XY YZ ZX"
		)
				
		fn roundFloat d pre:0.01 =
		(
			d = (d as float)/pre
			v = if (d - (v1 = floor d)) > ((v2 = ceil d) - d) then v2 else v1 
			v*pre
		)
		fn axisAngle dir axis func: control: =
		(
			local ang = roundFloat (abs (func (dot dir axis)))
			if iskindof control RolloutControl then
			(
				if control.text as float != ang do control.text = (ang as string)
				ok
			)
			else ang
		)

		fn updateNameUI = if isValidTape tape_node do
		(
			tape_name_tx.text = tape_node.name
			--tape_name_tx.text = tape_node.name + " (" + tape_node.target.name + ")" -- if you want to show tape's target name 
		)
		fn updateDataUI = if isValidTape tape_node do
		(
			--notifydependents tape_node.target partid:#tm -- DON'T uncomment! it just proves problems with processPostedMessages
			
			length_sp.value = distance tape_node tape_node.target 

			local dir = tape_node.dir
			
			axisAngle dir x_axis func:acos control:angle_xx_ed
			axisAngle dir y_axis func:acos control:angle_yy_ed
			axisAngle dir z_axis func:acos control:angle_zz_ed
			
			axisAngle dir z_axis func:asin control:angle_xy_ed
			axisAngle dir x_axis func:asin control:angle_yz_ed
			axisAngle dir y_axis func:asin control:angle_zx_ed

			windows.processPostedMessages()
		)
		fn updateLength value: =
		(
			if value == unsupplied do value = length_sp.value
			if tape_node.isselected then
			(
				pos = tape_node.pos - tape_node.dir * value
				tape_node.target.pos = pos 
			)
			else
			(
				pos = tape_node.target.pos + tape_node.dir * value
				tape_node.pos = pos
			)
		)
		
		on length_sp buttondown do if isValidTape tape_node do
		(
			theHold.Begin()
			updateLength()
		)
		on length_sp entered spin can do if isValidTape tape_node do
		(
			if spin and theHold.Holding() do 
			(
				if can then theHold.Cancel() else theHold.Accept "Set Length"
			)
		)
		on length_sp changed val do if isValidTape tape_node do
		(
			local hold = theHold.Holding()
			undo "Enter Length" (not hold) updateLength()
		)
		
		fn deleteHandlers = (deleteallchangehandlers id:#popup_tape)
		fn setupHandlers node = 
		(
			deleteHandlers()
			
			tape_node = node	
			
			when transform tape_node changes id:#popup_tape handleAt:#redrawViews do updateDataUI()
			when transform tape_node.target changes id:#popup_tape handleAt:#redrawViews do updateDataUI()
			when name tape_node change id:#popup_tape do updateNameUI()
			--when name tape_node.target change id:#popup_tape do updateNameUI() -- if you want to show tape's target name 
		)
		
		fn getTapeNode = if isvalidnode (node = selection[1]) do
		(
			if iskindof node Targetobject do node = refs.dependentnodes node firstonly:on
			
			if isValidTape node do
			(
				setupHandlers node

				updateDataUI()
				updateNameUI()
			)
		)
		
		on PopupTapeRollout close do
		(
			opened = off
			placed = getdialogpos PopupTapeRollout
			
			callbacks.removescripts id:#popup_tape
			deleteHandlers()
			updateToolbarButtons()
		)

		on PopupTapeRollout open do
		(
			opened = on
			placed = getdialogpos PopupTapeRollout
			
			callbacks.removescripts id:#popup_tape
			callbacks.addscript #selectionSetChanged "::PopupTapeRollout.getTapeNode()" id:#popup_tape
			
			getTapeNode()
			updateToolbarButtons()
		)
	)
	
	
	fn isOpened = (iskindof ::PopupTapeRollout RolloutClass and ::PopupTapeRollout.opened)
	on isChecked do isOpened()

	on execute do
	(
		if isOpened() then
		(
			try (::PopupTapeRollout.placed = getdialogpos ::PopupTapeRollout) catch()
			try (destroydialog ::PopupTapeRollout) catch()
		)
		else
		(
			pos = if iskindof PopupTapeRollout RolloutClass do PopupTapeRollout.placed
			pos = if iskindof pos Point2 then pos else unsupplied
			createdialog PopupTapeRollout style:#(#style_titlebar, #style_toolwindow, #style_sysmenu) pos:pos
		)
		updateToolbarButtons()
	)
)
try (destroydialog ::PopupTapeRollout) catch()

this is new version… i’ve cleaned up node monitoring, and add editable tape length.
the code about ‘undoable editing’ of tape length might be interesting for mxs developing beginners.

the logic of ‘length editing’:
– change the length spinner’s value using arrows or by typing
– if tape node (or both tape and target) is selected the target moves
– if tape target node (or nothing) is selected the tape node moves

PS. if you would find a bug, please, tell me about and let me fix it