I use the tape quite often, but infortunately, the default UI is really narrow and I cant read the values.
I would like to see a floater UI for the tape tool.
I use the tape quite often, but infortunately, the default UI is really narrow and I cant read the values.
I would like to see a floater UI for the tape tool.
It has great potential. The UI is larger, so I can see more.
Is it possible to keep the focus on the tape object, so that you can read the values all the time without the need to first select the tape object?
We would need to keep focus both when the tape is been created (eg if I just drag and then right click to cancel the tape) or when I select the target.
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.
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.
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
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)
)
Works wonderful now! Fantastic work 
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 
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.
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
. Well, let me know what would you like to do from my previous question and Iāll see if I can finish it.
i suggest to replace all value outputs with readonly textbox controls to be able copy values in the clipboard.
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?
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()
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.
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.
Thank you very much !
The new code works way better than the original tape 
I love the copy-button
I suggest you publish it on Scriptspot as well 
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.)
Thanks Denis,
Personally, I appreciate different approaches, because it reveals different possibilities.
Here is my feedback
MAXScript Change Handler Exception: Unable to convert: undefined to type: <node> when I click the Tape ToolIssues: 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.