A dynamically rebuilding Custom Attribute UI

Hi,

I am trying to build a dynamically rebuilding Custom Attribute UI in MAXScript, similar to how the standard Array modifier works.

More specifically, I want behavior like the Distribution rollout in Array: when the top dropdown changes, the whole visible set of controls below it changes immediately, the rollout height updates, and the rollouts below move accordingly — without leaving an empty vertical area, and without looking like a visibly nested subrollout.
I attached an example of the 3ds Max Array modifier UI showing the desired behavior.

In my own CA UI, the usual approaches do not seem to match that behavior:

hiding controls with .visible leaves layout/spacing problems
subRollout still creates a kind of fixed embedded area and does not feel like the whole rollout is rebuilt
separate rollouts become separate sections in the Modify panel, which is not what I want

So my question is:

Is there a way in pure MAXScript to make a Custom Attribute rollout rebuild itself in place like the standard Array modifier’s Distribution rollout?

Or is that behavior only possible through an internal / SDK-based UI mechanism that is not available to scripted rollouts?

Any pointers, examples, or documentation references would be very helpful.

Thanks.

AttachmentSize
native_array_modifier.png103.27 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
SimonBourgeois's picture

It is possible in rollout,but i didn't test in Custom Attribute

Hello Vadim,
i think you can do that by using some position coordinates that aren't visible and use event to move them, here is an example for classic rollout:

(
	try(DestroyDialog ::SB_testRoll)catch()
 
	rollout SB_testRoll "Test"
	(
		dropdownlist testList items:#("1","2","3")
 
		spinner Spn1 "spinner1" pos:[5,30]
		spinner Spn2 "spinner2" pos:[5,50]
		spinner Spn3 "spinner3" pos:[2000,0]
		spinner Spn4 "spinner4" pos:[2000,0]
		spinner Spn5 "spinner5" pos:[2000,0]
		spinner Spn6 "spinner6" pos:[2000,0]
 
		on testList selected state do
		(
			case state of
			(
				1: (Spn1.pos = [105,30];Spn2.pos = [105,50];Spn3.pos = [2000,0];Spn4.pos =[2000,0];Spn5.pos = [2000,0];Spn6.pos = [2000,0])
				2: (Spn1.pos = [2000,0];Spn2.pos = [2000,0];Spn3.pos = [105,30];Spn4.pos =[105,50];Spn5.pos = [2000,0];Spn6.pos = [2000,0])
				3: (Spn1.pos = [2000,0];Spn2.pos = [2000,0];Spn3.pos = [2000,0];Spn4.pos =[2000,0];Spn5.pos = [105,30];Spn6.pos = [105,50])
			)
		)
 
	)
	CreateDialog SB_testRoll
)

additionally you can update rollout size by using:

(
	try(closeRolloutFloater ::SB_testUI)catch()
 
	rollout testRoll1 "Rollout 1"
	(
		spinner Roll1Spn1
		spinner Roll1Spn2
 
		on testRoll1 rolledUp state do 
		(
			if state then SB_testUI.size += [0,testRoll1.height] else SB_testUI.size -= [0,testRoll1.height]
		)
	)
	rollout testRoll2 "Rollout 2"
	(
		spinner Roll2Spn1
		spinner Roll2Spn2
 
		on testRoll2 rolledUp state do 
		(
			if state then SB_testUI.size += [0,testRoll2.height] else SB_testUI.size -= [0,testRoll2.height]
		)
	)
 
 
SB_testUI = NewRolloutFloater "Test UI" 320 100
AddRollout testRoll1 SB_testUI
AddRollout testRoll2 SB_testUI rolledup:true
 
)

in this example it is the closeup state that triggers the Rollout size change but it can be used in the first example to change the current rollout size if the amount of UI elements doesn't use the same amount of UI space between different states, here is an example:

(
	try(DestroyDialog ::SB_testRoll)catch()
 
	rollout SB_testRoll "Test"
	(
		dropdownlist testList items:#("1","2","3")
 
		spinner Spn1 "spinner1" pos:[5,30]
		spinner Spn2 "spinner2" pos:[2000,0]
		spinner Spn3 "spinner3" pos:[2000,0]
		spinner Spn4 "spinner4" pos:[2000,0]
		spinner Spn5 "spinner5" pos:[2000,0]
		spinner Spn6 "spinner6" pos:[2000,0]
 
		on testList selected state do
		(
			case state of
			(
				1: (Spn1.pos = [125,30];Spn2.pos = [2000,0];Spn3.pos = [2000,0];Spn4.pos =[2000,0];Spn5.pos = [2000,0];Spn6.pos = [2000,0];SB_testRoll.height = 50)
				2: (Spn1.pos = [2000,0];Spn2.pos = [125,30];Spn3.pos = [125,50];Spn4.pos =[2000,0];Spn5.pos = [2000,0];Spn6.pos = [2000,0];SB_testRoll.height = 70)
				3: (Spn1.pos = [2000,0];Spn2.pos = [2000,0];Spn3.pos = [2000,0];Spn4.pos =[125,30];Spn5.pos = [125,50];Spn6.pos = [125,70];SB_testRoll.height = 90)
			)
		)
 
	)
	CreateDialog SB_testRoll 200 50
)

Edit: in custom attribute it isn't as easy as .height isn't exposed, the only way i found is to use subrollout, and to rebuild them each time, here is an example:

(
	testCA = attributes DynamicUI_SubRoll
	(
		parameters main
		(
			mode type:#integer default:1
			val1 type:#float default:0
			val2 type:#float default:0
			val3 type:#float default:0
			val4 type:#float default:0
			val5 type:#float default:0
			val6 type:#float default:0
		)
 
		rollout params "Test Dynamic UI"
		(
			dropdownlist ddlMode "Mode:" items:#("Mode 1", "Mode 2", "Mode 3")
			subRollout subContainer "sub" pos:[2,50] width:170 height:10
 
			fn getCADef obj =
			(
				for i = 1 to custAttributes.count obj do
				(
					local ca = custAttributes.get obj i
					if isProperty ca #mode and isProperty ca #val1 then return ca
				)
				undefined
			)
 
			fn getObj =
			(
				local obj = modPanel.getCurrentObject()
				if obj == undefined do obj = selection[1]
				obj
			)
 
			fn getMode1Roll =
			(
				local rc = rolloutCreator "mode1Roll" "Mode 1"
				rc.begin()
				rc.addControl #spinner #spnVal1 "Value A:" paramStr:"fieldWidth:60"
				rc.end()
			)
 
			fn getMode2Roll =
			(
				local rc = rolloutCreator "mode2Roll" "Mode 2"
				rc.begin()
				rc.addControl #spinner #spnVal2 "Value B:" paramStr:"fieldWidth:60"
				rc.addControl #spinner #spnVal3 "Value C:" paramStr:"fieldWidth:60"
				rc.end()
			)
 
			fn getMode3Roll =
			(
				local rc = rolloutCreator "mode3Roll" "Mode 3"
				rc.begin()
				rc.addControl #spinner #spnVal4 "Value D:" paramStr:"fieldWidth:60"
				rc.addControl #spinner #spnVal5 "Value E:" paramStr:"fieldWidth:60"
				rc.addControl #spinner #spnVal6 "Value F:" paramStr:"fieldWidth:60"
				rc.end()
			)
 
			fn clearSub =
			(
				while subContainer.rollouts.count > 0 do
					removeSubRollout subContainer subContainer.rollouts[1]
			)
 
			fn updateUI =
			(
				clearSub()
				local obj = getObj()
				if obj == undefined do return()
				local theCA = getCADef obj
				if theCA == undefined do return()
 
				local r = undefined
				local numSpinners = 0
				case ddlMode.selection of
				(
					1:
					(
						r = getMode1Roll()
						addSubRollout subContainer r
						r.spnVal1.value = theCA.val1
						numSpinners = 1
					)
					2:
					(
						r = getMode2Roll()
						addSubRollout subContainer r
						r.spnVal2.value = theCA.val2
						r.spnVal3.value = theCA.val3
						numSpinners = 2
					)
					3:
					(
						r = getMode3Roll()
						addSubRollout subContainer r
						r.spnVal4.value = theCA.val4
						r.spnVal5.value = theCA.val5
						r.spnVal6.value = theCA.val6
						numSpinners = 3
					)
				)
 
				local subH = 25 + (numSpinners * 22) + 8
				subContainer.height = subH
 
				params.height = 42 + subH + 4
 
				theCA.mode = ddlMode.selection
			)
 
			on ddlMode selected state do updateUI()
 
			on params open do
			(
				local obj = getObj()
				if obj != undefined do
				(
					local theCA = getCADef obj
					if theCA != undefined do
					(
						ddlMode.selection = theCA.mode
						updateUI()
					)
				)
			)
		)
	)
 
	if selection.count > 0 then
	(
		for obj in selection do
		(
			for i = custAttributes.count obj to 1 by -1 do
			(
				local ca = custAttributes.get obj i
				if isProperty ca #mode and isProperty ca #val1 do
					custAttributes.delete obj i
			)
			custAttributes.add obj testCA
		)
	)
)

to test, select an object and execute the script

Vadim Vdovenko's picture

Thank you so much for the

Thank you so much for the detailed response, this is very helpful!

SimonBourgeois's picture

You're welcome :)

In latest example, since the UI elements are rebuilt at each dropdownlist change, you'll have to store UI elements values , to be able to rebuild with accurate values, the current version "forget" the already set values.Here is a modified version that keep already set values:

(
	fn getStoreCode paramName =
	(
		local s = "( local obj = modPanel.getCurrentObject();"
		s += " if obj == undefined do obj = selection[1];"
		s += " if obj != undefined do ("
		s += "   for i = 1 to custAttributes.count obj do ("
		s += "     local ca = custAttributes.get obj i;"
		s += "     if isProperty ca #mode and isProperty ca #val1 do"
		s += "       ( ca." + paramName + " = val; exit )"
		s += "   )"
		s += " ))"
		s
	)
 
	testCA = attributes DynamicUI_SubRoll
	(
		parameters main
		(
			mode type:#integer default:1
			val1 type:#float default:0
			val2 type:#float default:0
			val3 type:#float default:0
			val4 type:#float default:0
			val5 type:#float default:0
			val6 type:#float default:0
		)
 
		rollout params "Test Dynamic UI"
		(
			dropdownlist ddlMode "Mode:" items:#("Mode 1", "Mode 2", "Mode 3")
			subRollout subContainer "sub" pos:[2,50] width:170 height:10
 
			fn getCADef obj =
			(
				for i = 1 to custAttributes.count obj do
				(
					local ca = custAttributes.get obj i
					if isProperty ca #mode and isProperty ca #val1 then return ca
				)
				undefined
			)
 
			fn getObj =
			(
				local obj = modPanel.getCurrentObject()
				if obj == undefined do obj = selection[1]
				obj
			)
 
			fn getMode1Roll =
			(
				local rc = rolloutCreator "mode1Roll" "Mode 1"
				rc.begin()
				rc.addControl #spinner #spnVal1 "Value A:" paramStr:"fieldWidth:60"
				rc.addHandler #spnVal1 #changed paramStr:"val" 	codeStr:(getStoreCode "val1")
				rc.end()
			)
 
			fn getMode2Roll =
			(
				local rc = rolloutCreator "mode2Roll" "Mode 2"
				rc.begin()
				rc.addControl #spinner #spnVal2 "Value B:" paramStr:"fieldWidth:60"
				rc.addControl #spinner #spnVal3 "Value C:" paramStr:"fieldWidth:60"
				rc.addHandler #spnVal2 #changed paramStr:"val" codeStr:(getStoreCode "val2")
				rc.addHandler #spnVal3 #changed paramStr:"val" codeStr:(getStoreCode "val3")
				rc.end()
			)
 
			fn getMode3Roll =
			(
				local rc = rolloutCreator "mode3Roll" "Mode 3"
				rc.begin()
				rc.addControl #spinner #spnVal4 "Value D:" paramStr:"fieldWidth:60"
				rc.addControl #spinner #spnVal5 "Value E:" paramStr:"fieldWidth:60"
				rc.addControl #spinner #spnVal6 "Value F:" paramStr:"fieldWidth:60"
				rc.addHandler #spnVal4 #changed paramStr:"val" codeStr:(getStoreCode "val4")
				rc.addHandler #spnVal5 #changed paramStr:"val" codeStr:(getStoreCode "val5")
				rc.addHandler #spnVal6 #changed paramStr:"val" codeStr:(getStoreCode "val6")
				rc.end()
			)
 
			fn clearSub =
			(
				while subContainer.rollouts.count > 0 do removeSubRollout subContainer subContainer.rollouts[1]
			)
 
			fn updateUI =
			(
				clearSub()
				local obj = getObj()
				if obj == undefined do return()
				local theCA = getCADef obj
				if theCA == undefined do return()
 
				local r = undefined
				local numSpinners = 0
				case ddlMode.selection of
				(
					1:
					(
						r = getMode1Roll()
						addSubRollout subContainer r
						r.spnVal1.value = theCA.val1
						numSpinners = 1
					)
					2:
					(
						r = getMode2Roll()
						addSubRollout subContainer r
						r.spnVal2.value = theCA.val2
						r.spnVal3.value = theCA.val3
						numSpinners = 2
					)
					3:
					(
						r = getMode3Roll()
						addSubRollout subContainer r
						r.spnVal4.value = theCA.val4
						r.spnVal5.value = theCA.val5
						r.spnVal6.value = theCA.val6
						numSpinners = 3
					)
				)
 
				local subH = 25 + (numSpinners * 22) + 8
				subContainer.height = subH
				params.height = 42 + subH + 4
				theCA.mode = ddlMode.selection
			)
 
			on ddlMode selected state do updateUI()
 
			on params open do
			(
				local obj = getObj()
				if obj != undefined do
				(
					local theCA = getCADef obj
					if theCA != undefined do
					(
						ddlMode.selection = theCA.mode
						updateUI()
					)
				)
			)
		)
	)
 
	if selection.count > 0 then
	(
		for obj in selection do
		(
			for i = custAttributes.count obj to 1 by -1 do
			(
				local ca = custAttributes.get obj i
				if isProperty ca #mode and isProperty ca #val1 do
					custAttributes.delete obj i
			)
			custAttributes.add obj testCA
		)
	)
)
Vadim Vdovenko's picture

Thank you so much for the

Thank you so much for the detailed response, this is very helpful!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.