Notifications
Clear all

[Closed] Custom Attributes – How to make dynamic Rollouts persistent?

Hi everyone,

new to MaxScript, so I have probably missed something very obvious. It’s a rather general question, so I won’t provide a code sample just yet.

I am currently working on a Scripted Custom Attribute (CA) that I put on an empty Attribute Holder. The CA has an “Add State” button which dynamically adds SubRollouts ( using AddSubRollout()) to the CA UI with the RolloutCreator. The problem is that the dynamically generated SubRollouts disappear when I deselect and then reselect the object.

What do I have to do to make the SubRollouts persistent? Do I have to redefine my CA? Or is the described behavior above unexpected? In that case, I will provide some code samples next.

Thank you very much!

ConSeannery

10 Replies

Without any code i’ts difficult to help you.
Surely you could solve it with a local variable in your rollout and checking it in your ‘open rollout handler’.

aaandreas, thank you for your response. I think it’s easiest if I attach the entire macro script so you can all see the problem first hand.

Open the CPI Manager and just select any object like a Sphere and hit the Button “Make Selected a CPI”. This will add the custom attributes to an Attribute Holder called “CP Interactable”. Now click the “Add State” button one or more times to create the aforementioned dynamic Rollouts created with the RolloutCreator. Then deselect and reselect the object. The SubRollouts have disappeared. I need to make thesepersistent, ideally without Rollout file templates or other crazy string based coding.

The code for creating the dynamic Rollouts is located in the CreateState() function starting line 129.

macroScript CPI_Manager 
category:"DEV"
buttonText:"CPI Manager"
toolTip:"Manager tool to set Control Panel Interactables"
--[icon:#(<string>, <index>)] 
silentErrors:false 
autoUndoEnabled:true 
(
  struct StateData (name = "State_", pos = [0.0, 0.0, 0.0], rot = [0.0, 0.0, 0.0, 1.0])

  -- // functions --
  local MakeSelectedCPI
  local AddCPIModifierToSelected
  local RemoveCPIFromSelected
  local IsCPI
  local HasBaseCPIAttributes
  
  -- // custom attributes --
  local Interactable

  rollout AssignCPI_RO "Assign CPI" width:250 height:300
  (
      button AssignCPI_Btn "Make Object a CPI" width:200 height:45 pos:[25,100]
      button RemoveCPIFromSelected_Btn "Remove CPI from Object" width:200 height:45 pos:[25,180]

      on AssignCPI_Btn pressed do
      (
        MakeSelectedCPI()
      )

      on RemoveCPIFromSelected_Btn pressed do
      (
        RemoveCPIFromSelected()
      )
  )

  on Execute do 
  (
    createDialog AssignCPI_RO
  )

  fn MakeSelectedCPI =
  (
    if($ == undefined) then
    (
      return (messageBox "Nothing selected!")
    )

    Interactable = attributes "Interactable"
    (
      -- // ui controls --
      local SteppedInteractable_RO
      local CPI_RO
      local SmoothInteractable_RO
      local CPI_Type_RBtn

      -- // functions --
      local CreateState
      local UpdateControls
      local ResizeStateList
      local SetRolloutState
      local IsStepped
      local IsSmooth

      -- // data variables --
      local StateRollouts = #()
      local StateArray = #()
      local CreatedStatesCount = 0

      fn UpdateControls = 
      (
        SteppedInteractable_RO.controls.enabled = CPI_RO.IsStepped()
        SteppedInteractable_RO.States_SRO.visible = CPI_RO.IsStepped()
        SmoothInteractable_RO.controls.enabled = CPI_RO.IsSmooth()
      )

      parameters ParamsCPI rollout:CPI_RO
      (
        IsInteractable type:#boolean default:true
        CPIType type:#integer ui:CPI_Type_RBtn default:1
      )

      rollout CPI_RO "CP Interactable" rolledUP:false
      (
        group "CP Interactable Type"
        (
             radiobuttons CPI_Type_RBtn labels:#("Stepped", "Smooth") align:#center height:30 offset:[0, 15] default:1
        )

        fn IsStepped = 
        (
          if(CPI_Type_RBtn.state == 1) then
          (
            return true
          )
          else
          (
            return false
          )
        )

        fn IsSmooth = 
        (
          return not IsStepped()
        )

        on CPI_Type_RBtn changed state do
        (
          UpdateControls()
        )
      )

      parameters ParamsStepped rollout:SteppedInteractable_RO
      (
        StateListBaseHeight type:#integer default:200
        SteppedRolloutBaseHeight type:#integer default:300
        SteppedRolloutMaxHeight type:#integer default:450
        IsSequential type:#boolean ui:IsSequential_CBox
      )

      rollout SteppedInteractable_RO "Stepped Interactable" height:300 rolledUP:false
      (
        checkbox IsSequential_CBox "		Is Sequential" align:#center
        button AddState_Btn "Add New State" height:35 width:100 align:#center
        --dotNetControl aList "system.windows.forms.listview"

        subRollout States_SRO "States" height:200

        fn CreateState = 
        (
            newState = StateData()

            append StateArray newState
            CreatedStatesCount += 1

            stateName = "State_" + (CreatedStatesCount as string)
            stateTest = stateName as name
            localPos = coordsys #parent $.position
            localRot = coordsys #parent $.rotation as eulerAngles

            newState.name = stateName
            newState.pos = localPos
            newState.rot = localRot

            PosX = newState.pos.x as string
            PosY = newState.pos.y as string
            PosZ = newState.pos.z as string

            RotX = newState.rot.x as string
            RotY = newState.rot.y as string
            RotZ = newState.rot.z as string

            rci = rolloutCreator name:stateName caption:stateName

            rci.begin()
            rci.addControl #label #Pos_Lbl "Pos:" paramStr:"width:30 height:25 alight:#right offset:[0, 8] across:4"
            rci.addControl #label #PosX_Lbl PosX paramStr:"width:30 height:25 alight:#right style_sunkenedge:true"
            rci.addControl #label #PosY_Lbl PosY paramStr:"width:30 height:25 alight:#right style_sunkenedge:true"
            rci.addControl #label #PosZ_Lbl PosZ paramStr:"width:30 height:25 alight:#right style_sunkenedge:true"

            rci.addControl #label #Rot_Lbl "Rot:" paramStr:"width:30 height:25 alight:#right offset:[0, -2] across:4"
            rci.addControl #label #RotX_Lbl RotX paramStr:"width:30 height:25 alight:#right offset:[0, -10] style_sunkenedge:true"
            rci.addControl #label #RotY_Lbl RotY paramStr:"width:30 height:25 alight:#right offset:[0, -10] style_sunkenedge:true"
            rci.addControl #label #RotZ_Lbl RotZ paramStr:"width:30 height:25 alight:#right offset:[0, -10] style_sunkenedge:true"

            rci.addControl #button #SetState_Btn "Set" paramStr:"width:40 alight:#right across:2"
            rci.addControl #button #ClearState_Btn "Clear" paramStr:"width:40 alight:#right"
            rci.addControl #button #DeleteState_Btn "Delete" paramStr:"width:94 alight:#right"

            rci.addControl #checkbox #TestCheck_CBox "Do It" paramStr:"alight:#right"
            rci.end()

            append StateRollouts rci.def

            AddSubRollout States_SRO rci.def rolledUp:false
        )

        fn ResizeStateList stateCount = 
        (
          if(stateCount < 1) then
          (
            stateCount = 1
          )

          if(CPI_RO.IsStepped()) then
          (
            newHeight = stateCount * StateListBaseHeight

            if(newHeight > SteppedRolloutMaxHeight) then
            (
              newHeight = SteppedRolloutMaxHeight
            )

            if(newHeight < SteppedRolloutBaseHeight) then
            (
              newHeight = SteppedRolloutBaseHeight
            )
            
            SteppedInteractable_RO.height = newHeight
            States_SRO.height = newHeight - (SteppedRolloutBaseHeight - StateListBaseHeight)
          )
          else
          (
            SteppedInteractable_RO.height = SteppedRolloutBaseHeight
            States_SRO.height = StateListBaseHeight
          )
        )

        fn UpdateStateList stateCount =
        (
          if(stateCount < 1) then
          (
            stateCount = 1
          )

          ResizeStateList stateCount

--           for s in StateRollouts do
--           (
--             AddSubRollout States_SRO s
--           )

          -- update rollout
          --max create mode
          --max modify mode
        )

--         on States_SRO open do
--         (
--           UpdateStateList StateArray.count
--         )

--         on States_SRO close do
--         (
--           UpdateStateList StateArray.count
--         )

        on SteppedInteractable_RO open do
        (
          UpdateStateList StateArray.count
        )

        on SteppedInteractable_RO close do
        (
          UpdateStateList StateArray.count
        )

        on AddState_Btn pressed do 
        (
          CreateState()
          UpdateStateList StateArray.count
        )
      )

      parameters ParamsSmooth rollout:SmoothInteractable_RO
      (
        SmoothType type:#integer ui:CPI_SmoothType_RBtn
      )

      rollout SmoothInteractable_RO "Smooth Interactable" rolledUP:false
      (
        radiobuttons CPI_SmoothType_RBtn labels:#("Use Limits", "Use Animation Track") align:#center height:30 offset:[0, 15] default:1

        on SmoothInteractable_RO open do
        (
          UpdateControls()
        )
      )
    ) -- End Interactable

    AddCPIModifierToSelected()
  ) -- End fn MakeSelectedCPI


  fn RemoveCPIFromSelected =
  (
    if($ == undefined) then
    (
      return "Nothing selected!"
    )

    for s in selection do
    (
      if(IsCPI(s)) then
      (
        if(s.modifiers["CP Interactable"] != undefined) then
        (
          deleteModifier s s.modifiers["CP Interactable"]
        )
        else
        (
          thisDef = custAttributes.getDef s.Interactable baseObject:true
          custAttributes.delete s thisDef baseObject:true
        )
      )
    )
  )
  
  fn IsCPI s = 
  (
    if(s.modifiers["CP Interactable"] != undefined or HasBaseCPIAttributes(s)) then
    (
      return true
    )
    else
    (
      return false
    )
  )

  fn HasBaseCPIAttributes s =
  (
    try
    (
      local thisInteractable = custAttributes.getDef s.Interactable baseObject:true

      if(thisInteractable == undefined) then
      (
        return false
      )
      else
      (
        thisInteractable = undefined
        return true
      )
    )
    catch
    (
      thisInteractable = undefined
      return false
    )
  )

  fn AddCPIModifierToSelected =
  (
    for s in selection do
    (
      if(not IsCPI(s)) then
      (
        local CPI_AttrHolderMod = EmptyModifier()
        CPI_AttrHolderMod.name = "CP Interactable"
        addModifier s CPI_AttrHolderMod
        custAttributes.add s.modifiers["CP Interactable"] Interactable
        custAttributes.makeUnique s.modifiers["CP Interactable"] Interactable
      )
    )
  )
)

I really have no time to see your code.
In a first look, what i’ll do is:
Line 121: define a new parameter to store the rollout state

createStateRollout type:#boolean  default:false

in line 252: after creating the subrollout, set the above parameter to true

createStateRollout = true

in line 243: in the open rollout event, check for this parameter and open it if true

if createStateRollout do CreateState()

Sorry but I really have no time to test it.

aaandreas,

not sure I understand you correctly, but how is this boolean supposed to solve the lack of persistence of my dynamically created subrollouts? The subrollouts get deleted as soon as I deselect my object. Maybe you find time to give my script a test run in the coming days. That should clarify my issue.

Thanks for you response!

ConSeannery

Another quick view…

Add these lines in the ‘on SteppedInteractable_RO open do’ (line 240 aprox)


          for s in StateRollouts do
           (
             AddSubRollout States_SRO s
           )

As you store the subrollouts in the StateRollouts local array, they are already there and you just have to use them when opening the main rollout (i.e. when you enter the modify pannel).
Tell me if it works for you.

EDIT: of course, as you store all this in local variables, it won’t be saved with your scene.

I already tried this piece of code before and it didn’t work The StateRollouts array properly retains the state of the destroyed UI elements upon deselection, but as soon as I reselect the object and the AddSubRollouts method is called, the values are reset again. I tried to workaround this by making a deep copy of the StatesRollouts array beforehand and then read in those preserved values, but it’s complicated and so far no success.

I guess the reason for the lack of persistence is that, unlike the fixed rollouts, the dynamic rollouts don’t have an associated parameters block with variables which would store the state of the UI elements between selections. Does this assumption sound about right?

I am currently trying to implement a workaround by using the RolloutCreator’s addLocal(), addText() and addHandlers methods. It’s messy.
Alternatively, I could try to dynamically create a parameters block for the dynamic rollouts and add it into the custom attributes definition (if that is possible). Or maybe the functions custAttributes.[i]setDefData/i and custAttributes.getDefData() can be of help. I really don’t like either path as it is all very string-heavy and thus prone to errors and tedious debugging.

I would really prefer a simpler solution if one exists.

Thanks again!

Have you tried these lines where I’ve told you? It works for me.
But, in any case and as I told you, it won’t be saved with your scene, the same with all your attribute locals.
You should save values of each rollout somewhere, for example as parameter arrays with an item value for each rollout value (and of course your locals too).
And, if possible, give an ID to your attributes.

aaandreas, yes it works in that it rebuilds all the created dynamic rollouts when I de- and reselect an object. But that was not the primary issue (sorry if I communicated this badly). The issue was that the UI values/state (checkbox enabled/disabled, label texts etc.) won’t be preserved after de- and reselect. Or does that work for you, too?
I find it odd that I have to save all UI values into a custom data structure and then reassign them upon reselection. I thought there had to be an easier way to do it, but apparently this is not the case

What would be the benefit of assigning an attributeID? Btw, all my custom attributes are unique from using custAttributon.makeUnique().

ConS.

1 Reply
(@aaandres)
Joined: 11 months ago

Posts: 0

Really curious!!

Your post #1:

Your post #2:

Your pos #3:

So I have spent the few time I have to solve something that you already knew how to solve. Perfect! :banghead:

That is just the problem. They are unique. So if you change your CA definition, you won’t update your objects with this new definition (but probably you already knew it and that wasn’t the question).

aaandres, I’m really sorry for my bad communication. I considered the solution of saving the subrollouts in an array and then reading it in during reselection a poor fix and rather a workaround than anything else. I honestly couldn’t believe that this was the way to go to make this work in MaxScript.
So I was indeed looking for a way to make the subrollouts persistent without me having to create an array. Once it became apparent that no elegant solution existed, I shifted focus to retaining the UI state. I think that’s when we started to talk past each other.

Sorry again and thanks for your important help! I really appreciate it.