Batch Parameter Changer — A Rewrite for Fusion 9

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Batch Parameter Changer — A Rewrite for Fusion 9

#1

Post by Midgardsormr » Wed Jan 10, 2018 5:44 pm

UPDATE #2: The completed script is attached to this post. It will be available in Reactor shortly.

UPDATE: This thread will now document my process as I rewrite this relatively complex script for Fusion9 and to use UI Manager in place of the old IUP interface. I'll leave this first post here for historical purposes; the adventure begins at Post #6!

============================

I am working on updating SlayerK's Batch Parameter Changer script for Fusion 9, and I'm running into an issue that's truly vexing me.

Everything works except for Point controls. When attempting to alter a Center control, for instance, I can't get the script to assign a user-selected value. Any attempt to use a variable in the assignment results in the control being set to default of {0.5, 0.5}. If I hard code it, it works. If I use the variable, it doesn't.

The troublesome line is 527. There is a large swath of disabled code around there—that's stripping it down to the minimum required functionality (none of the math operations were ever implemented in this script, anyway). The behavior is exactly the same with all of that turned on.
Code: [Select all] [Expand/Collapse] [Download] (Batch_Parmeters_Changer_IUP.lua)
  1. --- ////////////////////////////////////////////////////
  2. ---
  3. --- Script for simultaneous adjusting multiple tools' parameters
  4. ---
  5. --- Non-default names are supported
  6. ---
  7. --- Written by SlayerK 2007/02/22
  8. --- Concept by Gringo
  9. --- for bugs and reports: slayerk@mail.ru
  10. ---
  11. --- ////////////////////////////////////////////////////
  12. function isin(elem)
  13.     for i, j in ipairs(allToolsNames) do
  14.         if elem==j then
  15.             return true
  16.         end
  17.     end
  18.     return false
  19. end
  20.  
  21.  
  22. function clearModifiers(tbl)
  23.     local tmp={}
  24.     for i=1, table.getn(tbl) do
  25.         if isin(tbl[i]:GetAttrs().TOOLS_RegID) then
  26.             table.insert(tmp, tbl[i])
  27.         end
  28.      end
  29.      return tmp;
  30. end
  31.  
  32. function getMin(tbl)
  33.      if table.getn(tbl)==0 then
  34.           return(-1)
  35.      end
  36.      
  37.      local minTl=0
  38.      local minInps=1000000
  39.      numInps=0
  40.    
  41.      for i=1, table.getn(tbl) do
  42.           numInps=table.getn(tbl[i]:GetInputList())
  43.           if numInps<minInps then
  44.                 minTl=i
  45.                 minInps=numInps
  46.           end
  47.      end
  48.      return minTl
  49. end
  50.  
  51. function getInpsNames(tl)
  52.     local tbl=tl:GetInputList()
  53.     local tmp={}
  54.     local attrs={}
  55.     local name=""
  56.     local iID=""
  57.     local iType=""
  58.     for i, j in ipairs(tbl) do
  59.           attrs = j:GetAttrs()
  60.           name  = attrs.INPS_Name
  61.           iID   = attrs.INPS_ID
  62.           iType = attrs.INPS_DataType
  63.           if (attrs.INPB_Passive == false) and (eyeon.isin(supp_types, iType) == true) then
  64.                 table.insert(tmp, {tp=iType, id=iID, nm=name})
  65.           end
  66.     end
  67.     return tmp
  68. end
  69.  
  70. function getInpNum(inpInfo, tl)
  71.     local inputs={}
  72.     inputs=tl:GetInputList();
  73.     local inpAttrs
  74.     local i
  75.     local j
  76.     for i, j in ipairs(inputs) do
  77.         inpAttrs=j:GetAttrs()
  78.         if  (inpAttrs.INPS_Name==inpInfo.nm) and
  79.             (inpAttrs.INPS_ID==inpInfo.id) and
  80.             (inpAttrs.INPS_DataType==inpInfo.tp) then
  81.             return i
  82.         end
  83.     end
  84.     return -1
  85. end
  86.  
  87. function cTime()
  88.     return comp:GetAttrs().COMPN_CurrentTime
  89. end
  90.  
  91. function getCurrVal(tl, inpN)
  92.    
  93.     return tl:GetInputList()[inpN][cTime()];
  94. end
  95.  
  96. function mulInps(tAll, tNew)
  97.      
  98.      if type(tAll)~="table" then
  99.           return nil
  100.      end
  101.  
  102.      local tmp={}
  103.      for i=1, table.getn(tNew) do
  104.           fl=false
  105.           for j=1, table.getn(tAll) do
  106.                 if (tNew[i].id==tAll[j].id) and (tNew[i].nm==tAll[j].nm) and (tNew[i].tp==tAll[j].tp) then
  107. --~                 if (tNew[i].id==tAll[j].id) then
  108.                      fl=true
  109.                 end
  110.           end
  111.           if fl==true then
  112.                 table.insert(tmp, tNew[i])
  113.           end
  114.      end
  115.      
  116.      return tmp
  117. end
  118.  
  119. function isSeen(tlID)
  120.      for i=1, table.getn(seen) do
  121.           if tlID == seen[i] then
  122.                 return true
  123.           end
  124.      end
  125.      return false
  126. end
  127.  
  128. function getInpType(inpName)
  129.      for i, j in sheets do
  130.           if j.nm==inpName then
  131.                 return j.tp
  132.           end
  133.      end
  134.      return supp_types[1]
  135. end
  136.  
  137. function dumpNames(tbl)
  138.      for i=1, table.getn(tbl) do
  139.           print(tbl[i].tp .. "  " .. tbl[i].id .. " " .. tbl[i].nm)
  140.      end
  141. end
  142.  
  143. function isChange(t1, t2)
  144.      if ((t1=="Number") or (t1=="FuID") or (t1=="Text")) and (t2=="Point") then
  145.           return true
  146.      end
  147.      if ((t2=="Number") or (t2=="FuID") or (t1=="Text")) and (t1=="Point") then
  148.           return true
  149.      end
  150.      return false
  151. end
  152.  
  153. function cmpVals(v1, v2)
  154.  
  155.      if type(v1)~=type(v2)  then
  156.           return false
  157.      end
  158.      
  159.      if (type(v1)=="number") or (type(v1)=="string") then
  160.           return (v1==v2)
  161.      end
  162.      
  163.      if type(v1)=="table" then
  164.           for i=1, table.getn(v1) do
  165.                 if v1[i]~=v2[i] then
  166.                      return false
  167.                 end
  168.           end
  169.           return true
  170.      end
  171.      
  172.      return false
  173. end
  174.  
  175. function getParameter(pID)
  176.     local new=0
  177.     local inpNum=getInpNum(pID, tools[1])
  178.     local parVal=getCurrVal(tools[1], inpNum)
  179.      
  180.     for i=2, table.getn(tools) do
  181.         inpNum=getInpNum(pID, tools[i])
  182.         new=getCurrVal(tools[i], inpNum)
  183.         if cmpVals(new, parVal)==false then
  184.             return "?"
  185.         end
  186.      end
  187.      
  188.      if type(parVal)=="number" then
  189.           return tostring(parVal)
  190.      end
  191.      
  192.      if type(parVal)=="table" then
  193.           return parVal
  194.      end
  195.      
  196.      if type(parVal)=="string" then
  197.           return parVal
  198.      end
  199.      
  200.      return "?"
  201. end
  202.  
  203. function tblToString(tbl, div, lastDiv)
  204.      local n=table.getn(tbl)
  205.      local str=""
  206.      for i=1, n do
  207.           if type(tbl[i])~="table" then
  208.                 if i~=n then
  209.                     str=str .. tbl[i] .. div
  210.                 else
  211.                      str=str .. tbl[i] .. lastDiv
  212.                 end
  213.           else
  214.                 return ""
  215.           end
  216.      end
  217.      return str
  218. end
  219. --/////////////////////////////////////////////////                     IUP Functions
  220. function getList(s_tbl, vName)
  221.      n=table.getn(s_tbl)
  222.      str=""
  223.      for i=1, n do
  224.           if i~=n then
  225.                 str=str ..'"'.. s_tbl[i].nm .. '", '
  226.           else
  227.                 str=str ..'"'.. s_tbl[i].nm .. '"; '
  228.           end
  229.      end
  230.      str = vName .. "=iup.list{" .. str .. 'dropdown="yes", size="250x" }'
  231.      dostring(str)
  232. end
  233.  
  234.  
  235. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  236. --                                                                                          MAIN CODE
  237. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  238.  
  239. -- LUA
  240. --///////////////////////////////////////////////
  241.  
  242. allTools={}
  243. allToolsNames={}
  244.  
  245.  
  246. if globals.__addtool_data then
  247.     allTools=globals.__addtool_data
  248. else
  249.     allTools = fu:GetRegSummary(CT_Tool)
  250.     globals.__addtool_data = allTools
  251. end
  252.  
  253. for i,v in ipairs(allTools) do
  254.     if v.REGS_Name~=nil and v.REGS_OpIconString~=nil and v.REGS_ID~=nil then
  255.         table.insert(allToolsNames, v.REGS_ID)
  256.     end
  257. end
  258.  
  259. supp_types={"Number", "FuID", "Point", "Text"}  -- Supproted for this script input data types
  260. seen={}                                                     -- List of seed tools in tools table
  261. tools=comp:GetToolList(true)
  262. tools=clearModifiers(tools)
  263. --~ dump(tools)
  264.  
  265. if  table.getn(tools)<2 then                                            --
  266.      print("you must select more then one tool for sheet")  -- Selected less then two tools
  267.      return 0                                                               --
  268. end
  269.  
  270. minEntry=getMin(tools)
  271. --~ print("minEntry " .. minEntry .. "  " .. tools[minEntry]:GetAttrs().TOOLS_Name)
  272. sheets=getInpsNames(tools[minEntry])                                -- getting number of tool in tools table with minimal number of inputs
  273. table.insert(seen, tools[minEntry]:GetAttrs().TOOLS_RegID)          -- getting ID of this tool. for sample "Loader"
  274. table.remove(tools, minEntry)                                       -- remove this tool form tools table
  275.  
  276. while (table.getn(tools)>0) do
  277.      nowMin=1
  278.      if isSeen(tools[nowMin]:GetAttrs().TOOLS_RegID)==false then
  279.           table.insert(seen, tools[nowMin]:GetAttrs().TOOLS_RegID)
  280.           iInfo=getInpsNames(tools[nowMin])
  281.           sheets=mulInps(sheets, iInfo)
  282.      end
  283.      table.remove(tools, nowMin)
  284. end
  285.  
  286. --~ print("ok")
  287. if table.getn(sheets)==0 then
  288.     print("No sheet Inputs")
  289.     return 0
  290. end
  291.  
  292. table.sort(sheets, function (a,b) return (b.nm > a.nm) end)
  293.  
  294. tools=comp:GetToolList(true)
  295. tools=clearModifiers(tools)
  296.  
  297. -- IUP
  298. --///////////////////////////////////////////////
  299. message="Here palced values\nof FuID to copy\nin TextField"
  300. nowAc=1
  301. selTp=sheets[1].tp
  302. nowTp=sheets[1].tp
  303. xVal=""
  304. yVal=""
  305. tVal=""
  306. fVal=""
  307.  
  308. active  ="255 255 255"
  309. passive ="64 64 64"
  310. textCol ="0 0 0"
  311.  
  312. getList(sheets, "lst") -- sets up lst = iup...
  313.  
  314. infoFuID=iup.multiline{size="10x10", expand="YES", fgcolor=active, bgcolor=passive, value=message}
  315.  
  316. listFuID=iup.list{expand="YES";fgcolor=active}
  317.  
  318. --iup.list{"Item 1 Text","Item 2 Text","Item 3 Text"; expand="YES",value="1"}
  319.  
  320.  
  321. cordX=iup.text{size="50x"}
  322. cordY=iup.text{size="50x"}
  323. cordS=iup.hbox{iup.label{title="X:"}, cordX, iup.label{title="Y:"}, cordY}
  324.  
  325. inputType=iup.label{title=sheets[nowAc].tp}
  326. textFld=iup.text{size="200x"}
  327. textInp=iup.hbox{inputType, textFld}
  328.  
  329. btn_set=iup.button{title="Apply", size="100x20"}
  330.  
  331. setList=iup.vbox{textInp, cordS, btn_set; gap=10, alignment="ACENTER"}
  332.  
  333. iup.SetAttribute(cordX, "FGCOLOR", textCol)
  334. iup.SetAttribute(cordY, "FGCOLOR", textCol)
  335. iup.SetAttribute(textFld, "FGCOLOR", textCol)
  336. iup.SetAttribute(btn_set, "FGCOLOR", textCol)
  337.  
  338. currentVal=getParameter(sheets[1])
  339. if nowTp~="Point" then
  340.      tVal=currentVal
  341.      iup.SetAttribute(textFld, "VALUE", currentVal)
  342.      iup.SetAttribute(cordS, "ACTIVE", "NO")
  343.      iup.SetAttribute(textFld, "BGCOLOR", active)
  344.      iup.SetAttribute(cordX, "BGCOLOR", passive)
  345.      iup.SetAttribute(cordY, "BGCOLOR", passive)
  346. else
  347.      iup.SetAttribute(cordX, "VALUE", tostring(currentVal[1]))
  348.      iup.SetAttribute(cordY, "VALUE", tostring(currentVal[2]))
  349.      xVal=currentVal[1]
  350.      yVal=currentVal[2]
  351.      iup.SetAttribute(textFld, "ACTIVE", "NO")
  352.      iup.SetAttribute(textFld, "BGCOLOR", passive)
  353.      iup.SetAttribute(cordX, "BGCOLOR", active)
  354.      iup.SetAttribute(cordY, "BGCOLOR", active)
  355. end
  356.  
  357. --                                                                                      Event functions
  358. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  359. function clearFuIDlist()
  360.     for i=1, 10 do listFuID[i]=nil end
  361. end
  362.  
  363. function listFuID:action(tVar, i, v)
  364.     if selTp=="FuID" then
  365.         iup.SetAttribute(textFld, "VALUE", tVar)
  366.         tVal=tVar
  367.     end
  368. end
  369.  
  370. -- idx = i,
  371. function lst:action(t, i, v)
  372.     nowAc=i
  373.  
  374.     selTp=sheets[i].tp
  375.     iup.SetAttribute(inputType, "TITLE", sheets[i].tp)
  376.     currentVal=getParameter(sheets[i])
  377.     clearFuIDlist()
  378.  
  379.     if (sheets[i].tp=="Point") then
  380.         if currentVal=="?"  then
  381.             currentVal={"?", "?"}
  382.         else
  383.             xVal=currentVal[1]
  384.             yVal=currentVal[2]
  385.         end
  386.         iup.SetAttribute(cordX, "VALUE", tostring(currentVal[1]))
  387.         iup.SetAttribute(cordY, "VALUE", tostring(currentVal[2]))
  388.     else
  389.         tVal=currentVal
  390.         iup.SetAttribute(textFld, "VALUE", currentVal)
  391.     end
  392.      
  393.     if (sheets[i].tp=="FuID") then
  394.         dostring("fuIDAttrs=composition." .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs()")
  395.         inpCtrlType=string.gsub(fuIDAttrs.INPID_InputControl, "ID", "")
  396.         dostring("idMat=composition." .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs().INPIDT_" .. inpCtrlType .. "_ID")
  397. --      iup.SetAttribute(infoFuID, "VALUE", tblToString(idMat, "\n", ""))
  398.         clearFuIDlist()
  399.         for i, j in ipairs(idMat) do listFuID[i]=tostring(j) end
  400.     else
  401.         iup.SetAttribute(infoFuID, "VALUE", message)
  402.     end
  403.      
  404.      if isChange(nowTp, sheets[i].tp) then
  405.           if nowTp~="Point" then
  406.                 iup.SetAttribute(textFld, "ACTIVE", "NO")
  407.                 iup.SetAttribute(textFld, "BGCOLOR", passive)
  408.                 iup.SetAttribute(cordS, "ACTIVE", "YES")
  409.                 iup.SetAttribute(cordX, "BGCOLOR", active)
  410.                 iup.SetAttribute(cordY, "BGCOLOR", active)
  411.           else
  412.                 iup.SetAttribute(cordS, "ACTIVE", "NO")
  413.                 iup.SetAttribute(cordX, "BGCOLOR", passive)
  414.                 iup.SetAttribute(cordY, "BGCOLOR", passive)
  415.                 iup.SetAttribute(textFld, "ACTIVE", "YES")
  416.                 iup.SetAttribute(textFld, "BGCOLOR", active)
  417.           end
  418.           nowTp=sheets[i].tp
  419.           iup.Refresh(setList)
  420.      end
  421. end
  422.  
  423. function btn_set:action()
  424.      
  425.     local do_s=""
  426.     local operation=""
  427.     local incX=""
  428.     local incY=""
  429.     local _tVal=tVal
  430.     local newx=xVal
  431.     local newy=yVal
  432.     print("At button press, \nnewx = "..newx..", newy = "..newy)
  433.     local newVal=0
  434.     local chX=1
  435.     local chY=1
  436.      
  437.     for i=1, table.getn(tools) do
  438.        
  439.         tlName=tools[i]:GetAttrs().TOOLS_Name
  440.          
  441.         if sheets[nowAc].tp=="Number" then
  442.             if eyeon.isin( {"+","-","/","*"}, string.sub(_tVal,1,1)) then
  443.                 operation=string.sub(_tVal, 1, 1)
  444.                 _tVal=string.sub(_tVal, 2, string.len(tVal))
  445.             end
  446.             if tonumber(_tVal)==nil then
  447.                      print("Entered data isn't Number")
  448.                      return
  449.                 end
  450.                 if operation=="" then
  451.                     local inpNum=getInpNum(sheets[nowAc], tools[i])
  452.                     if inpNum>0 then
  453.                         tools[i]:GetInputList()[inpNum][cTime()]=tonumber(_tVal)
  454.                     end
  455.                 else
  456.                     local inpNum=getInpNum(sheets[nowAc], tools[i])
  457.                     if inpNum>0 then
  458.                         local cValue=getCurrVal(tools[i], inpNum)
  459.                         if operation=="+" then
  460.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue+tonumber(_tVal)
  461.                         end
  462.                         if operation=="-" then
  463.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue-tonumber(_tVal)
  464.                         end
  465.                         if operation=="*" then
  466.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue*tonumber(_tVal)
  467.                         end
  468.                         if operation=="/" then
  469.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue/tonumber(_tVal)
  470.                         end
  471.                     end
  472.                 end
  473.         end
  474.          
  475.         if sheets[nowAc].tp=="Point" then
  476.            
  477.             -- if eyeon.isin( {"+","-","/","*"}, string.sub(xVal,1,1)) then
  478.                 -- incX=string.sub(xVal, 1, 1)
  479.                 -- newx=string.sub(xVal, 2, string.len(xVal))
  480.             -- end
  481.                
  482.             -- if eyeon.isin( {"+","-","/","*"}, string.sub(yVal,1,1)) then
  483.                 -- incY=string.sub(yVal, 1, 1)
  484.                 -- newy=string.sub(yVal, 2, string.len(yVal))
  485.             -- end
  486.            
  487.             local inpNum=getInpNum(sheets[nowAc], tools[i])
  488.             if inpNum>0 then
  489.                 --tmpVar=getCurrVal(tools[i], inpNum)
  490.                
  491.                 -- if (tonumber(newx)==nil) then
  492.                     -- chX=0
  493.                 -- end
  494.            
  495.                 -- if (tonumber(newy)==nil) then
  496.                     -- chY=0
  497.                 -- end
  498.            
  499.                 -- INCREMENT FOR ELEMENTS
  500.                 -------------------------------------------------------------------------------------------
  501.                 --newVal=0
  502.            
  503.                 -- if chX==1 then
  504.                     -- if incX~="" then
  505.                         -- if incX=="+" then newVal=tmpVar[1]+tonumber(newx) end
  506.                         -- if incX=="-" then newVal=tmpVar[1]-tonumber(newx) end
  507.                         -- if incX=="*" then newVal=tmpVar[1]*tonumber(newx) end
  508.                         -- if incX=="/" then newVal=tmpVar[1]/tonumber(newx) end
  509.                         -- newx=newVal
  510.                     -- end
  511.                     -- tools[i]:GetInputList()[inpNum][cTime()]={newx, newy}
  512.                 -- else
  513.                     -- newx=tmpVar[1]
  514.                 -- end
  515.            
  516.                 -- if chY==1 then
  517.                     -- if incY~="" then
  518.                         -- if incY=="+" then newVal=tmpVar[2]+tonumber(newy) end
  519.                         -- if incY=="-" then newVal=tmpVar[2]-tonumber(newy) end
  520.                         -- if incY=="*" then newVal=tmpVar[2]*tonumber(newy) end
  521.                         -- if incY=="/" then newVal=tmpVar[2]/tonumber(newy) end
  522.                         -- newy=newVal
  523.                     -- end
  524.                     newx = newx
  525.                     newy = newy
  526.                     print("Before assignment,\nnewx = "..newx..", newy = "..newy)
  527.                     tools[i]:GetInputList()[inpNum][cTime()]={newx, newy}
  528.                     print("After assignment,\nnewx = "..newx..", newy = "..newy)
  529.                     print("New control values: x = "..tools[i]:GetInputList()[inpNum][cTime()][1]..", y = "..tools[i]:GetInputList()[inpNum][cTime()][2])
  530.                 -- end
  531.                 -------------------------------------------------------------------------------------------
  532.             end
  533.         end
  534.          
  535.           if (sheets[nowAc].tp=="Text") or (sheets[nowAc].tp=="FuID")then
  536.                 if tVal~="?" then
  537.                     do_s="composition."..tlName .. "." .. sheets[nowAc].id .. "[" .. comp:GetAttrs().COMPN_CurrentTime .. ']="' .. tVal .. '"'
  538.                     dostring(do_s)
  539.                 end
  540.           end
  541.    
  542.      end
  543. end
  544.  
  545. function cordX:action(c, after)
  546.      xVal=after
  547. end
  548.  
  549. function cordY:action(c, after)
  550.      yVal=after
  551. end
  552.  
  553. function textFld:action(c, after)
  554.      tVal=after
  555. end
  556. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  557.  
  558. dlg=iup.dialog{
  559.      iup.hbox{
  560.           iup.vbox{
  561.                 iup.hbox{
  562.                      iup.frame{
  563.                           iup.hbox{
  564.                                 lst;
  565.                                 gap=5
  566.                           };
  567.                           title="Choose Parameter",
  568.                           size="300x50",
  569.                           fgcolor=active
  570.                      }
  571.                 },
  572.                 iup.hbox{
  573.                      iup.frame{
  574.                           setList;
  575.                           title="Set To:",
  576.                           size="300x110",
  577.                           fgcolor=active
  578.                      }
  579.                 },
  580.  
  581.           },
  582.           iup.vbox{
  583.                 iup.frame{
  584.                      iup.vbox{
  585.                           listFuID;
  586.                           gap=5
  587.                      };
  588.                      size="100x150"
  589.                 }
  590.           }
  591.      };
  592.      margin="10x10",
  593.      title="© 2007 SlayerK Multiple Parameter Changer",
  594.      resize="no",
  595.      bgcolor="64 64 64"
  596. }
  597.  
  598. dlg:show()
  599. iup.MainLoop()
  600.  
I am also working on making it use the UI Manager instead of IUP, but I need the logic to work before I finish implementing that.

I've built some test code to see if I could replicate the problem outside of the Batch Changer script, but it works as expected:

Code: Select all

tools = comp:GetToolList(true)
myx = 0.2
myy = 0.8
for i, j in ipairs(tools) do
	newx = myx
	newy = myy
	tools[i]:GetInputList()[33][1005] = {newx, newy}

end
Any ideas? I'm about ready to just rewrite the routine from the ground up.
You do not have the required permissions to view the files attached to this post.
Last edited by Midgardsormr on Mon Jan 22, 2018 4:44 pm, edited 2 times in total.

User avatar
SirEdric
Fusionator
Posts: 1963
Joined: Tue Aug 05, 2014 10:04 am
Answers: 4
Real name: Eric Westphal
Been thanked: 136 times
Contact:

Re: Batch Parameter Changer -- Baffling problem

#2

Post by SirEdric » Wed Jan 10, 2018 8:56 pm

Just a wild guess.....
instead of calling the cTime() function of the script,
have you tried to use Fusions built-in 'CurrentTime' ?

Code: Select all

-- start of script
ct = comp.CurrentTime

-- code etc.....

tools[i]:GetInputList()[inpNum][ct]={newx, newy}

reinier
Posts: 12
Joined: Tue Aug 05, 2014 2:57 pm

Re: Batch Parameter Changer -- Baffling problem

#3

Post by reinier » Wed Jan 10, 2018 10:09 pm

Hi!
When/if you get it working, could you share it?
This is one I used all the time in Fusion6...
Would be very much appreciated!

Reinier

User avatar
SecondMan
Site Admin
Posts: 3586
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 5
Location: Vancouver, Canada
Been thanked: 104 times
Contact:

Re: Batch Parameter Changer -- Baffling problem

#4

Post by SecondMan » Thu Jan 11, 2018 7:22 am

I tried updating that script on a couple of occasions. All false starts. As soon as I start digging I get utterly confused. It's large, not well documented and some parts of it feel very messy to me.

Not saying it's a bad script - I'm not in a position to judge - but if I were you (or rather it would be my personal wish ;)) I'd start from scratch and document my progress and step by step approach in a lovely topic here :D

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer -- Baffling problem

#5

Post by Midgardsormr » Thu Jan 11, 2018 7:56 am

Yes, I'll definitely share it! And I'm doing my best to document and reorganize it as I go along. Writing a case study as I work on it is certainly a good idea, too.

The most confusing bit is the large quantity of code that was meant to perform arithmetic on the original value—it was never implemented in the UI, and it should have been broken out into functions instead of cluttering the Apply button's function. That thing should be 20-30 lines instead of 100.

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#6

Post by Midgardsormr » Thu Jan 11, 2018 1:15 pm

First Pass: Compatibility Updates

Let's start with the original v01 script published in 2007:
Code: [Select all] [Expand/Collapse] [Download] (Batch_Parameters_Changer_IUP.eyeonscript)
  1. --- ////////////////////////////////////////////////////
  2. ---
  3. --- Script for simultaneous adjusting multiple tools' parameters
  4. ---
  5. --- Non-default names are supported
  6. ---
  7. --- Written by SlayerK 2007/02/22
  8. --- Concept by Gringo
  9. --- for bugs and reports: slayerk@mail.ru
  10. ---
  11. --- ////////////////////////////////////////////////////
  12. function isin(elem)
  13.     for i, j in allToolsNames do
  14.         if elem==j then
  15.             return true
  16.         end
  17.     end
  18.     return false
  19. end
  20.  
  21.  
  22. function clearModifiers(tbl)
  23.     local tmp={}
  24.     for i=1, table.getn(tbl) do
  25.         if isin(tbl[i]:GetAttrs().TOOLS_RegID) then
  26.             table.insert(tmp, tbl[i])
  27.         end
  28.      end
  29.      return tmp;
  30. end
  31.  
  32. function getMin(tbl)
  33.      if table.getn(tbl)==0 then
  34.           return(-1)
  35.      end
  36.      
  37.      local minTl=0
  38.      local minInps=1000000
  39.      numInps=0
  40.    
  41.      for i=1, table.getn(tbl) do
  42.           numInps=table.getn(tbl[i]:GetInputList())
  43.           if numInps<minInps then
  44.                 minTl=i
  45.                 minInps=numInps
  46.           end
  47.      end
  48.      return minTl
  49. end
  50.  
  51. function getInpsNames(tl)
  52.     local tbl=tl:GetInputList()
  53.     local tmp={}
  54.     local attrs={}
  55.     local name=""
  56.     local iID=""
  57.     local iType=""
  58.     for i, j in tbl do
  59.           attrs = j:GetAttrs()
  60.           name  = attrs.INPS_Name
  61.           iID   = attrs.INPS_ID
  62.           iType = attrs.INPS_DataType
  63.           if (attrs.INPB_Passive == false) and (eyeon.isin(supp_types, iType) == true) then
  64.                 table.insert(tmp, {tp=iType, id=iID, nm=name})
  65.           end
  66.     end
  67.     return tmp
  68. end
  69.  
  70. function getInpNum(inpInfo, tl)
  71.     local inputs={}
  72.     inputs=tl:GetInputList();
  73.     local inpAttrs
  74.     local i
  75.     local j
  76.     for i, j in inputs do
  77.         inpAttrs=j:GetAttrs()
  78.         if  (inpAttrs.INPS_Name==inpInfo.nm) and
  79.             (inpAttrs.INPS_ID==inpInfo.id) and
  80.             (inpAttrs.INPS_DataType==inpInfo.tp) then
  81.             return i
  82.         end
  83.     end
  84.     return -1
  85. end
  86.  
  87. function cTime()
  88.     return comp:GetAttrs().COMPN_CurrentTime
  89. end
  90.  
  91. function getCurrVal(tl, inpN)
  92.    
  93.     return tl:GetInputList()[inpN][cTime()];
  94. end
  95.  
  96. function mulInps(tAll, tNew)
  97.      
  98.      if type(tAll)~="table" then
  99.           return nil
  100.      end
  101.  
  102.      local tmp={}
  103.      for i=1, table.getn(tNew) do
  104.           fl=false
  105.           for j=1, table.getn(tAll) do
  106.                 if (tNew[i].id==tAll[j].id) and (tNew[i].nm==tAll[j].nm) and (tNew[i].tp==tAll[j].tp) then
  107. --~                 if (tNew[i].id==tAll[j].id) then
  108.                      fl=true
  109.                 end
  110.           end
  111.           if fl==true then
  112.                 table.insert(tmp, tNew[i])
  113.           end
  114.      end
  115.      
  116.      return tmp
  117. end
  118.  
  119. function isSeen(tlID)
  120.      for i=1, table.getn(seen) do
  121.           if tlID == seen[i] then
  122.                 return true
  123.           end
  124.      end
  125.      return false
  126. end
  127.  
  128. function getInpType(inpName)
  129.      for i, j in sheets do
  130.           if j.nm==inpName then
  131.                 return j.tp
  132.           end
  133.      end
  134.      return supp_types[1]
  135. end
  136.  
  137. function dumpNames(tbl)
  138.      for i=1, table.getn(tbl) do
  139.           print(tbl[i].tp .. "  " .. tbl[i].id .. " " .. tbl[i].nm)
  140.      end
  141. end
  142.  
  143. function isChange(t1, t2)
  144.      if ((t1=="Number") or (t1=="FuID") or (t1=="Text")) and (t2=="Point") then
  145.           return true
  146.      end
  147.      if ((t2=="Number") or (t2=="FuID") or (t1=="Text")) and (t1=="Point") then
  148.           return true
  149.      end
  150.      return false
  151. end
  152.  
  153. function cmpVals(v1, v2)
  154.  
  155.      if type(v1)~=type(v2)  then
  156.           return false
  157.      end
  158.      
  159.      if (type(v1)=="number") or (type(v1)=="string") then
  160.           return (v1==v2)
  161.      end
  162.      
  163.      if type(v1)=="table" then
  164.           for i=1, table.getn(v1) do
  165.                 if v1[i]~=v2[i] then
  166.                      return false
  167.                 end
  168.           end
  169.           return true
  170.      end
  171.      
  172.      return false
  173. end
  174.  
  175. function getParameter(pID)
  176.     local new=0
  177.     local inpNum=getInpNum(pID, tools[1])
  178.     local parVal=getCurrVal(tools[1], inpNum)
  179.      
  180.     for i=2, table.getn(tools) do
  181.         inpNum=getInpNum(pID, tools[i])
  182.         new=getCurrVal(tools[i], inpNum)
  183.         if cmpVals(new, parVal)==false then
  184.             return "?"
  185.         end
  186.      end
  187.      
  188.      if type(parVal)=="number" then
  189.           return tostring(parVal)
  190.      end
  191.      
  192.      if type(parVal)=="table" then
  193.           return parVal
  194.      end
  195.      
  196.      if type(parVal)=="string" then
  197.           return parVal
  198.      end
  199.      
  200.      return "?"
  201. end
  202.  
  203. function tblToString(tbl, div, lastDiv)
  204.      local n=table.getn(tbl)
  205.      local str=""
  206.      for i=1, n do
  207.           if type(tbl[i])~="table" then
  208.                 if i~=n then
  209.                     str=str .. tbl[i] .. div
  210.                 else
  211.                      str=str .. tbl[i] .. lastDiv
  212.                 end
  213.           else
  214.                 return ""
  215.           end
  216.      end
  217.      return str
  218. end
  219. --/////////////////////////////////////////////////                     IUP Functions
  220. function getList(s_tbl, vName)
  221.      n=table.getn(s_tbl)
  222.      str=""
  223.      for i=1, n do
  224.           if i~=n then
  225.                 str=str ..'"'.. s_tbl[i].nm .. '", '
  226.           else
  227.                 str=str ..'"'.. s_tbl[i].nm .. '"; '
  228.           end
  229.      end
  230.      str = vName .. "=iup.list{" .. str .. 'dropdown="yes", size="250x" }'
  231.      dostring(str)
  232. end
  233.  
  234.  
  235. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  236. --                                                                                          MAIN CODE
  237. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  238.  
  239. -- LUA
  240. --///////////////////////////////////////////////
  241.  
  242. allTools={}
  243. allToolsNames={}
  244.  
  245.  
  246. if globals.__addtool_data then
  247.     allTools=globals.__addtool_data
  248. else
  249.     allTools = fu:GetRegSummary(CT_Tool)
  250.     globals.__addtool_data = allTools
  251. end
  252.  
  253. for i,v in ipairs(allTools) do
  254.     if v.REGS_Name~=nil and v.REGS_OpIconString~=nil and v.REGS_ID~=nil then
  255.         table.insert(allToolsNames, v.REGS_ID)
  256.     end
  257. end
  258.  
  259. supp_types={"Number", "FuID", "Point", "Text"}  -- Supproted for this script input data types
  260. seen={}                                                     -- List of seed tools in tools table
  261. tools=comp:GetToolList(true)
  262. tools=clearModifiers(tools)
  263. --~ dump(tools)
  264.  
  265. if  table.getn(tools)<2 then                                            --
  266.      print("you must select more then one tool for sheet")  -- Selected less then two tools
  267.      return 0                                                               --
  268. end
  269.  
  270. minEntry=getMin(tools)
  271. --~ print("minEntry " .. minEntry .. "  " .. tools[minEntry]:GetAttrs().TOOLS_Name)
  272. sheets=getInpsNames(tools[minEntry])                                -- getting number of tool in tools table with minimal number of inputs
  273. table.insert(seen, tools[minEntry]:GetAttrs().TOOLS_RegID)          -- getting ID of this tool. for sample "Loader"
  274. table.remove(tools, minEntry)                                       -- remove this tool form tools table
  275.  
  276. while (table.getn(tools)>0) do
  277.      nowMin=1
  278.      if isSeen(tools[nowMin]:GetAttrs().TOOLS_RegID)==false then
  279.           table.insert(seen, tools[nowMin]:GetAttrs().TOOLS_RegID)
  280.           iInfo=getInpsNames(tools[nowMin])
  281.           sheets=mulInps(sheets, iInfo)
  282.      end
  283.      table.remove(tools, nowMin)
  284. end
  285.  
  286. --~ print("ok")
  287. if table.getn(sheets)==0 then
  288.     print("No sheet Inputs")
  289.     return 0
  290. end
  291.  
  292. table.sort(sheets, function (a,b) return (b.nm > a.nm) end)
  293.  
  294. tools=comp:GetToolList(true)
  295. tools=clearModifiers(tools)
  296.  
  297. -- IUP
  298. --///////////////////////////////////////////////
  299. message="Here palced values\nof FuID to copy\nin TextField"
  300. nowAc=1
  301. selTp=sheets[1].tp
  302. nowTp=sheets[1].tp
  303. xVal=""
  304. yVal=""
  305. tVal=""
  306. fVal=""
  307.  
  308. active  ="255 255 255"
  309. passive ="64 64 64"
  310. textCol ="0 0 0"
  311.  
  312. getList(sheets, "lst")
  313.  
  314. infoFuID=iup.multiline{size="10x10", expand="YES", fgcolor=active, bgcolor=passive, value=message}
  315.  
  316. listFuID=iup.list{expand="YES";fgcolor=active}
  317.  
  318. --iup.list{"Item 1 Text","Item 2 Text","Item 3 Text"; expand="YES",value="1"}
  319.  
  320.  
  321. cordX=iup.text{size="50x"}
  322. cordY=iup.text{size="50x"}
  323. cordS=iup.hbox{iup.label{title="X:"}, cordX, iup.label{title="Y:"}, cordY}
  324.  
  325. inputType=iup.label{title=sheets[nowAc].tp}
  326. textFld=iup.text{size="200x"}
  327. textInp=iup.hbox{inputType, textFld}
  328.  
  329. btn_set=iup.button{title="Apply", size="100x20"}
  330.  
  331. setList=iup.vbox{textInp, cordS, btn_set; gap=10, alignment="ACENTER"}
  332.  
  333. iup.SetAttribute(cordX, "FGCOLOR", textCol)
  334. iup.SetAttribute(cordY, "FGCOLOR", textCol)
  335. iup.SetAttribute(textFld, "FGCOLOR", textCol)
  336.  
  337. currentVal=getParameter(sheets[1])
  338. if nowTp~="Point" then
  339.      tVal=currentVal
  340.      iup.SetAttribute(textFld, "VALUE", currentVal)
  341.      iup.SetAttribute(cordS, "ACTIVE", "NO")
  342.      iup.SetAttribute(textFld, "BGCOLOR", active)
  343.      iup.SetAttribute(cordX, "BGCOLOR", passive)
  344.      iup.SetAttribute(cordY, "BGCOLOR", passive)
  345. else
  346.      iup.SetAttribute(cordX, "VALUE", tostring(currentVal[1]))
  347.      iup.SetAttribute(cordY, "VALUE", tostring(currentVal[2]))
  348.      xVal=currentVal[1]
  349.      yVal=currentVal[2]
  350.      iup.SetAttribute(textFld, "ACTIVE", "NO")
  351.      iup.SetAttribute(textFld, "BGCOLOR", passive)
  352.      iup.SetAttribute(cordX, "BGCOLOR", active)
  353.      iup.SetAttribute(cordY, "BGCOLOR", active)
  354. end
  355.  
  356. --                                                                                      Event functions
  357. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  358. function clearFuIDlist()
  359.     for i=1, 10 do listFuID[i]=nil end
  360. end
  361.  
  362. function listFuID:action(tVar, i, v)
  363.     if selTp=="FuID" then
  364.         iup.SetAttribute(textFld, "VALUE", tVar)
  365.         tVal=tVar
  366.     end
  367. end
  368.  
  369. function lst:action(t, i, v)
  370.     nowAc=i
  371.  
  372.     selTp=sheets[i].tp
  373.     iup.SetAttribute(inputType, "TITLE", sheets[i].tp)
  374.     currentVal=getParameter(sheets[i])
  375.     clearFuIDlist()
  376.  
  377.     if (sheets[i].tp=="Point") then
  378.         if currentVal=="?"  then
  379.             currentVal={"?", "?"}
  380.         else
  381.             xVal=currentVal[1]
  382.             yVal=currentVal[2]
  383.         end
  384.         iup.SetAttribute(cordX, "VALUE", tostring(currentVal[1]))
  385.         iup.SetAttribute(cordY, "VALUE", tostring(currentVal[2]))
  386.     else
  387.         tVal=currentVal
  388.         iup.SetAttribute(textFld, "VALUE", currentVal)
  389.     end
  390.      
  391.     if (sheets[i].tp=="FuID") then
  392.         dostring("fuIDAttrs=" .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs()")
  393.         inpCtrlType=string.gsub(fuIDAttrs.INPID_InputControl, "ID", "")
  394.         dostring("idMat=" .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs().INPIDT_" .. inpCtrlType .. "_ID")
  395. --      iup.SetAttribute(infoFuID, "VALUE", tblToString(idMat, "\n", ""))
  396.         clearFuIDlist()
  397.         for i, j in idMat do listFuID[i]=tostring(j) end
  398.     else
  399.         iup.SetAttribute(infoFuID, "VALUE", message)
  400.     end
  401.      
  402.      if isChange(nowTp, sheets[i].tp) then
  403.           if nowTp~="Point" then
  404.                 iup.SetAttribute(textFld, "ACTIVE", "NO")
  405.                 iup.SetAttribute(textFld, "BGCOLOR", passive)
  406.                 iup.SetAttribute(cordS, "ACTIVE", "YES")
  407.                 iup.SetAttribute(cordX, "BGCOLOR", active)
  408.                 iup.SetAttribute(cordY, "BGCOLOR", active)
  409.           else
  410.                 iup.SetAttribute(cordS, "ACTIVE", "NO")
  411.                 iup.SetAttribute(cordX, "BGCOLOR", passive)
  412.                 iup.SetAttribute(cordY, "BGCOLOR", passive)
  413.                 iup.SetAttribute(textFld, "ACTIVE", "YES")
  414.                 iup.SetAttribute(textFld, "BGCOLOR", active)
  415.           end
  416.           nowTp=sheets[i].tp
  417.           iup.Refresh(setList)
  418.      end
  419. end
  420.  
  421. function btn_set:action()
  422.      
  423.     local do_s=""
  424.     local operation=""
  425.     local incX=""
  426.     local incY=""
  427.     local _tVal=tVal
  428.     local _xVal=xVal
  429.     local _yVal=yVal
  430.     local newVal=0
  431.     local chX=1
  432.     local chY=1
  433.      
  434.     for i=1, table.getn(tools) do
  435.        
  436.         tlName=tools[i]:GetAttrs().TOOLS_Name
  437.          
  438.         if sheets[nowAc].tp=="Number" then
  439.             if eyeon.isin( {"+","-","/","*"}, string.sub(_tVal,1,1)) then
  440.                 operation=string.sub(_tVal, 1, 1)
  441.                 _tVal=string.sub(_tVal, 2, string.len(tVal))
  442.             end
  443.             if tonumber(_tVal)==nil then
  444.                      print("Entered data isn't Number")
  445.                      return
  446.                 end
  447.                 if operation=="" then
  448.                     local inpNum=getInpNum(sheets[nowAc], tools[i])
  449.                     if inpNum>0 then
  450.                         tools[i]:GetInputList()[inpNum][cTime()]=tonumber(_tVal)
  451.                     end
  452.                 else
  453.                     local inpNum=getInpNum(sheets[nowAc], tools[i])
  454.                     if inpNum>0 then
  455.                         local cValue=getCurrVal(tools[i], inpNum)
  456.                         if operation=="+" then
  457.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue+tonumber(_tVal)
  458.                         end
  459.                         if operation=="-" then
  460.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue-tonumber(_tVal)
  461.                         end
  462.                         if operation=="*" then
  463.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue*tonumber(_tVal)
  464.                         end
  465.                         if operation=="/" then
  466.                             tools[i]:GetInputList()[inpNum][cTime()]=cValue/tonumber(_tVal)
  467.                         end
  468.                     end
  469.                 end
  470.         end
  471.          
  472.         if sheets[nowAc].tp=="Point" then
  473.            
  474.             if eyeon.isin( {"+","-","/","*"}, string.sub(xVal,1,1)) then
  475.                 incX=string.sub(xVal, 1, 1)
  476.                 _xVal=string.sub(xVal, 2, string.len(xVal))
  477.             end
  478.                
  479.             if eyeon.isin( {"+","-","/","*"}, string.sub(yVal,1,1)) then
  480.                 incY=string.sub(yVal, 1, 1)
  481.                 _yVal=string.sub(yVal, 2, string.len(yVal))
  482.             end
  483.            
  484.             local inpNum=getInpNum(sheets[nowAc], tools[i])
  485.             if inpNum>0 then
  486.                 tmpVar=getCurrVal(tools[i], inpNum)
  487.                
  488.                 if (tonumber(_xVal)==nil) then
  489.                     chX=0
  490.                 end
  491.            
  492.                 if (tonumber(_yVal)==nil) then
  493.                     chY=0
  494.                 end
  495.            
  496.                 -- INCREMENT FOR ELEMENTS
  497.                 -------------------------------------------------------------------------------------------
  498.                 newVal=0
  499.            
  500.                 if chX==1 then
  501.                     if incX~="" then
  502.                         if incX=="+" then newVal=tmpVar[1]+tonumber(_xVal) end
  503.                         if incX=="-" then newVal=tmpVar[1]-tonumber(_xVal) end
  504.                         if incX=="*" then newVal=tmpVar[1]*tonumber(_xVal) end
  505.                         if incX=="/" then newVal=tmpVar[1]/tonumber(_xVal) end
  506.                         _xVal=newVal
  507.                     end
  508.                     tools[i]:GetInputList()[inpNum][cTime()]={_xVal, tmpVar[2], 0}
  509.                 else
  510.                     _xVal=tmpVar[1]
  511.                 end
  512.            
  513.                 if chY==1 then
  514.                     if incY~="" then
  515.                         if incY=="+" then newVal=tmpVar[2]+tonumber(_yVal) end
  516.                         if incY=="-" then newVal=tmpVar[2]-tonumber(_yVal) end
  517.                         if incY=="*" then newVal=tmpVar[2]*tonumber(_yVal) end
  518.                         if incY=="/" then newVal=tmpVar[2]/tonumber(_yVal) end
  519.                         _yVal=newVal
  520.                     end
  521.                     tools[i]:GetInputList()[inpNum][cTime()]={_xVal, _yVal, 0}
  522.                 end
  523.                 -------------------------------------------------------------------------------------------
  524.             end
  525.         end
  526.          
  527.           if (sheets[nowAc].tp=="Text") or (sheets[nowAc].tp=="FuID")then
  528.                 if tVal~="?" then
  529.                     do_s=tlName .. "." .. sheets[nowAc].id .. "[" .. comp:GetAttrs().COMPN_CurrentTime .. ']="' .. tVal .. '"'
  530.                     dostring(do_s)
  531.                 end
  532.           end
  533.    
  534.      end
  535. end
  536.  
  537. function cordX:action(c, after)
  538.      xVal=after
  539. end
  540.  
  541. function cordY:action(c, after)
  542.      yVal=after
  543. end
  544.  
  545. function textFld:action(c, after)
  546.      tVal=after
  547. end
  548. --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  549.  
  550. dlg=iup.dialog{
  551.      iup.hbox{
  552.           iup.vbox{
  553.                 iup.hbox{
  554.                      iup.frame{
  555.                           iup.hbox{
  556.                                 lst;
  557.                                 gap=5
  558.                           };
  559.                           title="Choose Parameter",
  560.                           size="300x50",
  561.                           fgcolor=active
  562.                      }
  563.                 },
  564.                 iup.hbox{
  565.                      iup.frame{
  566.                           setList;
  567.                           title="Set To:",
  568.                           size="300x110",
  569.                           fgcolor=active
  570.                      }
  571.                 },
  572.  
  573.           },
  574.           iup.vbox{
  575.                 iup.frame{
  576.                      iup.vbox{
  577.                           listFuID;
  578.                           gap=5
  579.                      };
  580.                      size="100x150"
  581.                 }
  582.           }
  583.      };
  584.      margin="10x10",
  585.      title="© 2007 SlayerK Multiple Parameter Changer",
  586.      resize="no",
  587.      bgcolor="64 64 64"
  588. }
  589.  
  590. dlg:show()
  591. iup.MainLoop()
Pretty much the first thing Blackmagic did when they purchased Fusion was to destroy all references to Eyeon branding, including the scripts' file extension of .eyeonscript. So the first thing to do when updating an old script for Fusion 9 is to change the extension to .lua. Once that's done, Fusion will be able to display the script in your Script menu.

Next, run the script and see what errors appear in the Console View:

...ts\Comp\z_RnD\Batch_Parameters_Changer_IUP_casestudy.lua:13: attempt to call a table value

This error gives us two pieces of information—the line number, 13, where the script failed, and a hint about what went wrong—an improper call to a table. So let's look at line 13:

for i, j in allToolsNames do

In earlier versions of Lua, this for string worked. Now, though, a table cannot be called in this manner. Instead, an iterator function, ipairs(), was added. The correct syntax is this:

for i, j in ipairs(allToolsNames) do

By updating all of the for loops that reference tables in this manner, we fix most of the problems in updating to Fusion9. You can find the first few by saving the changes, running the script again, and looking at the lines referenced in the error messages. After fixing three lines, the script reaches the point where it presents its GUI:
001_squashing-ipairs-errors.png
I certainly haven't found all of the ipairs() bugs yet, but at least portions of the script now work. I can put a new value in the Number field, click Apply, and the value in my selected nodes updates as expected. Let's take a look at something other than a simple number field. Every node has some text fields in their script tab. Let's try updating one of those:
001_squashing-ipairs-errors.png
This set of errors is a little less useful—it references line 1, which is a comment. We'll have to do some sleuthing to figure out what the problem is. We'll start with the error message itself: attempt to index global 'Polygon3' (a nil value). In previous versions of Fusion, you could reference a tool by its name alone. The script interpreter would make an assumption that the context was the current composition. In Fusion 9, you have to supply that context yourself. If you want to set a parameter on Polygon3, you have to reference it as composition.Polygon3.

What's puzzling here is that setting a number parameter worked just fine, but the text field didn't. So there must be something different between the way the two datatypes are assigned. The error doesn't occur until the Apply button is clicked, so it stands to reason that the bug is probably in the routine that handles that button click. At almost 600 lines, this is a relatively long script to just read through in search of a routine that I am not familiar with. I use Notepad++ as my editor, though, and it gives me the ability to collapse all of the functions and control structures to simplify looking at the code. Not all of the function names offer clarity, but I happen to know that in IUP, interactions with the interface require a structure that looks like this: control:action(arguments). Scanning through all those collapsed items, I see this:
003_button-action.png
There is only one button in the interface, so it seems pretty likely that this is the function I should look at to find out what happens when I click that button. There's quite a lot of code in this function. In fact, it's the biggest function in this script, so it takes a little time and effort to understand what's going on. I don't need to comprehend everything—right now I'm looking at the overview of its logic. Let's break that down a bit, documenting as we go.

The first ten or so lines are setting up some local variables. Some of them import the values of some global variables. I could go back and trace those if necessary, but for now we'll just assume that they're probably the user-supplied new values for the parameter we're changing. Next we have a for loop. This one is another approach to running the loop on a table. The function table.getn() returns the number of entries in a table. It uses an iterator i and sets the upper bound on the loop as the size of the table tools. Presumably, tools is the list of selected nodes that will be changed. I'll just put in some comments with my guess here:

Code: Select all

-- Executes when the Apply button is pressed.
function btn_set:action()
	 
	local do_s=""
	local operation=""
	local incX=""
	local incY=""
	local _tVal=tVal
	local _xVal=xVal
	local _yVal=yVal
	local newVal=0
	local chX=1
	local chY=1
	 
	for i=1, table.getn(tools) do -- For each selected node, do some stuff
Next we've got a line that puts the name of the tool under consideration into a variable. Pretty straightforward. Following that, we have a selection control:
if sheets[nowAc].tp=="Number" then
That looks like what I'm after! There's a lot of code in there, and to be honest, much of it isn't actually doing anything because it's related to an Operation control that was never implemented. The relevant line that I'm looking for, though, is 450:
tools[i]:GetInputList()[inpNum][cTime()]=tonumber(_tVal)
This line works—if we're dealing with a Number datatype, the script does what it's supposed to do. Let's look for the Text routine now and see how it differs.

Code: Select all

		  if (sheets[nowAc].tp=="Text") or (sheets[nowAc].tp=="FuID")then
				if tVal~="?" then
					do_s=tlName .. "." .. sheets[nowAc].id .. "[" .. comp:GetAttrs().COMPN_CurrentTime .. ']="' .. tVal .. '"' 
					dostring(do_s)
				end
		  end
Clearly, this is quite different from what happens with Numbers. I had to do a bit of research to learn what dostring() is all about. It takes a string as input and then executes it as though it were a line of code. So now we look at what do_s will actually contain when the code is executed.

tlName is the name of our tool, so for instance, Polygon3. Then the .. operator concatenates a "." character to the next bit: sheets[nowAc].id will take a little bit of research to figure out what it contains. More concatenation to put the current frame number in brackets, and then "= tVal". I've been working with this script for a little while already, so I'd already determined that sheets[nowAc].id will be the REGS_ID value for the Input being changed. Therefore, if we assume that we're changing Polygon3's EndRenderScript Input, do_s contains this:
"Polygon3.EndRenderScript[0] = print("foo")"

Also assuming that we're currently on frame 0 and the value put into the Text field is print("foo")

So why does one line work while the other doesn't? In the case of the Number routine, we have a handle to the tool itself in the tools table. We don't need to provide a context because that's implicit in the direct link we have to the tool. In the case of the Text routine, we haven't told Fusion what we mean by Polygon3. From the script's perspective, that's a variable that has never been declared and is therefore nil. We need to prefix it with composition., which is something that Fusion already knows about. We can therefore update the line like so:

do_s="composition." .. tlName .. "." .. sheets[nowAc].id .. "[" .. comp:GetAttrs().COMPN_CurrentTime .. ']="' .. tVal .. '"'

Running the script again reveals that it doesn't handle some bits of text very well:
004_character-handling-error.png
This is actually a bug that existed from the original conception of this script. It doesn't have the ability to deal with reserved characters, which need to be escaped. At this point, it is up to the user to know about that and escape the quotation marks themselves: print(\"foo\") yields correct results.

Fixing that problem is a task for another day. Right now, all I am concerned with is getting the script to the point where it runs in Fusion 9 just like it did in Fusion 6, legacy bugs included. Next data type! Since we've got this function open already, we can see that there are two more datatypes to examine: Points and FuIDs. Points are a two-dimensional number, with values for both X and Y. FuIDs are lists, such as you might find in a combo box or multi-button. Not all such lists use an FuID. For instance, a Polygon's Fill Method just uses Numbers, but the Polygon's Filter input uses FuID.

Selecting Filter from the Parameter list immediately gives me some new errors, and an unusually helpful stack traceback display:
004_fuID-errors.png
Let's start at the top: Line 393 contains a reference to fuIDAttrs, which apparently doesn't exist. That's accompanied by a note in the console, also about fuIDAttrs, claiming that Polygon3 doesn't exist. That looks very similar to the error I just finished troubleshooting. In fact, it has the very same cause—Line 392 has another dostring() command that attempts to create fuIDAttrs:
dostring("fuIDAttrs=" .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs()")

We can solve the problem the same way, by adding "composition." to the variable declaration like so:
dostring("fuIDAttrs=composition." .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs()")

Line 394 also has a dostring() with a very similar construction, so let's be proactive and fix that one, too since we're already here:
dostring("idMat=composition." .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs().INPIDT_" .. inpCtrlType .. "_ID")

Running the script generates yet another error. This one's on line 397—another for loop that needs ipairs(). The next run of the script gives me what I expect to see—the FuID options appear in the right-hand box in the GUI. If I select one and hit Apply, my Polygon nodes update as expected.

That leaves the Point datatype. This leads to the original problem that started this whole thing—no matter what I do, I can't seem to get a Point control to update. I spent most of a day tearing the script apart to try to solve this one without really getting anywhere, so I'm going to leave it alone for now and hope that I can solve it during the rewrite.

At this point, the script works in Fusion 9 for any datatype other than Points. The process of updating any other legacy script is very similar—run it, find the place where it errors, and update the code. The three primary problems are these:
For loops on tables. Fix them by putting the table in a call to ipairs()
Node names with no context. Fix them by prefixing them with composition..
A file extension of .eyeonscript, which should be changed to .lua.

In my next post, I'll start walking through this code in more detail, explaining and commenting it.
You do not have the required permissions to view the files attached to this post.
Last edited by Midgardsormr on Sat Oct 12, 2019 8:17 am, edited 1 time in total.

User avatar
SecondMan
Site Admin
Posts: 3586
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 5
Location: Vancouver, Canada
Been thanked: 104 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#7

Post by SecondMan » Thu Jan 11, 2018 2:29 pm

This script is more than 10 years old?!

Gah. Grandad me.

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#8

Post by Midgardsormr » Thu Jan 11, 2018 6:16 pm

Second Pass: Analysis and Documentation

Anyone who's opened this script up and tried to understand it has been in for a world of confusion. It's very poorly documented, not well organized, and some of the techniques it uses to solve problems are unorthodox. I'm going to just start at the top and work my way down, attempting to explain what I can.

Code: Select all

function isin(elem)
	for i, j in ipairs(allToolsNames) do
		if elem==j then
			return true
		end
	end
	return false
end
This function searches a global table called allToolsNames for a specific tool type. If that tool is found, the function returns true.

Whenever you encounter a global variable in a function (one that isn't declared inside the function and doesn't come in as an argument), it's useful to find where that variable came from elsewhere in the code. A simple search for allToolsNames turns it up on lines 243 and 255. In the first, it's simply declared as a table, and the second shows that it's being populated with the REGS_IDs of every tool in another table allTools. allTools itself is populated a few lines up, where it gets the contents of Fusion's tool Registry.

So the name of the table allToolsNames is a little deceptive because the table doesn't actually contain tool names, but the REGS_ID. The function doesn't care if your tool is "Loader5" or "Loader_Starscream_v02". All it's looking for is the tool type of "Loader." This function, in my opinion, is superfluous since the bmd scriptlib already contains an isin function that requires the table to be supplied as an argument as well as the desired element. Furthermore, that function is used later on in this very script, so I can't fathom why this function was even written.

Code: Select all

function clearModifiers(tbl)
	local tmp={}
	for i=1, table.getn(tbl) do
		if isin(tbl[i]:GetAttrs().TOOLS_RegID) then 
			table.insert(tmp, tbl[i])
		end
	 end
	 return tmp;
end
clearModifiers() takes a list of objects as input and returns a new list composed only of items that have a property TOOLS_RegID that matches one of the entries in allToolsNames.

Since allToolsNames contains only objects of type CT_Tool, anything that doesn't conform, ie Modifiers, will be stripped from the table. This function uses the previous function, isin(), to match entries against the allToolsNames list.

The function table.getn() used here returns the size of a table. This is useful for tables that might have nil values in the middle of their data. The ipairs() iterator function will abort when it finds a nil during the loop, so it cannot always be relied upon. table.insert() places a new entry at the end of a table. We don't just use the iterator variable to insert records using tmp[i] = tbl[i] because that will also put gaps in the data.

Code: Select all

function getMin(tbl)
	if table.getn(tbl)==0 then
		return(-1)
	end
	
	local minTl=0
	local minInps=1000000
	numInps = 0
	
	for i=1, table.getn(tbl) do
		numInps = table.getn(tbl[i]:GetInputList())
		if numInps<minInps then
			minTl=i
			minInps=numInps
		end
	end
	return minTl
end
This function takes the selected tool list as input and returns the index of the tool with the smallest number of inputs. This reduces the burden on a later function that reduces the input list to those inputs common to all selected tools.

The function table.getn() is once more in use. This time, in addition to being used to control the for loop, it is also used to count how many Inputs the tool under consideration has by supplying it with the output of GetInputList().

Code: Select all

function getInpsNames(tl)
	local tbl=tl:GetInputList()
	local tmp={}
	local attrs={}
	local name=""
	local iID=""
	local iType=""
	for i, j in ipairs(tbl) do
		  attrs	= j:GetAttrs()
		  name	= attrs.INPS_Name
		  iID	= attrs.INPS_ID
		  iType	= attrs.INPS_DataType
		  if (attrs.INPB_Passive == false) and (eyeon.isin(supp_types, iType) == true) then
				table.insert(tmp, {tp=iType, id=iID, nm=name})
		  end
	end
	return tmp
end
Although the function implies that it retrieves Input names, it actually returns a table containing the names, ids, and datatypes of all of a tool's inputs. It rejects any input marked as "Passive" and any that has a datatype that is not in a short list of types supported by this script. The list of supported types, supp_types, is a global variable set up elsewhere in the script.

Code: Select all

function getInpNum(inpInfo, tl)
	local inputs={}
	inputs=tl:GetInputList();
	local inpAttrs
	local i
	local j
	for i, j in ipairs(inputs) do
		inpAttrs=j:GetAttrs()
		if 	(inpAttrs.INPS_Name==inpInfo.nm) and
			(inpAttrs.INPS_ID==inpInfo.id) and
			(inpAttrs.INPS_DataType==inpInfo.tp) then
			return i
		end
	end
	return -1
end
Finds the index of an Input with specific parameters in a list of a tool's Inputs.

Code: Select all

function cTime()
	return comp:GetAttrs().COMPN_CurrentTime 
end
Gets the current frame number from the comp. As SirEdric points out, comp.CurrentTime is much simpler.

Code: Select all

function getCurrVal(tl, inpN)
	
	return tl:GetInputList()[inpN][cTime()];
end
Retrieves the current value of the specified input from the specified tool. Used to pre-fill the value field in the GUI.

Code: Select all

function mulInps(tAll, tNew)
	 
	 if type(tAll)~="table" then
		  return nil
	 end

	 local tmp={}
	 for i=1, table.getn(tNew) do
		  fl=false
		  for j=1, table.getn(tAll) do
				if (tNew[i].id==tAll[j].id) and (tNew[i].nm==tAll[j].nm) and (tNew[i].tp==tAll[j].tp) then
					 fl=true
				end
		  end
		  if fl==true then
				table.insert(tmp, tNew[i])
		  end
	 end
	 
	 return tmp
end
This function compares the contents of two tables. Each entry that is in both tables (matches all three of name, type, and ID) is inserted into a new table. This function is called in a loop to pare away inputs that are not present in all selected tools. The result is used to populate the Parameter combo box in the GUI.

Code: Select all

function isSeen(tlID)
	 for i=1, table.getn(seen) do
		  if tlID == seen[i] then
				return true
		  end
	 end
	 return false
end
seen is a global table that contains a list of tool types. The function returns a Boolean indicating whether or not the toolID provided is in that table.

Code: Select all

function getInpType(inpName)
	 for i, j in sheets do
		  if j.nm==inpName then
				return j.tp
		  end
	 end
	 return supp_types[1]
end
Given an input name, the function searches the global table sheets and returns the datatype of the first entry that matches the name. If the input is not found, it returns "Number". This is used to set the label for the entry field in the UI.

Code: Select all

function dumpNames(tbl)
	 for i=1, table.getn(tbl) do
		  print(tbl[i].tp .. "	" .. tbl[i].id .. "	" .. tbl[i].nm)
	 end
end
Apparently a debugging function. Prints a list of Inputs's datatype, id, and names from a table.

Code: Select all

function isChange(t1, t2)
	 if ((t1=="Number") or (t1=="FuID") or (t1=="Text")) and (t2=="Point") then
		  return true
	 end
	 if ((t2=="Number") or (t2=="FuID") or (t1=="Text")) and (t1=="Point") then
		  return true
	 end
	 return false
end
This checks to see if one argument is a Point and the other isn't. Its only purpose appears to be to determine whether or not to disable fields in the GUI.

Code: Select all

function cmpVals(v1, v2)

	 if type(v1)~=type(v2)  then
		  return false
	 end
	 
	 if (type(v1)=="number") or (type(v1)=="string") then
		  return (v1==v2)
	 end
	 
	 if type(v1)=="table" then
		  for i=1, table.getn(v1) do
				if v1[i]~=v2[i] then
					 return false
				end
		  end
		  return true
	 end
	 
	 return false
end
Compares two values, or all of the corresponding value pairs in two tables, for equivalence. Returns false if they are not identical. Used to determine whether to prefill the value field in the UI with "?".

Code: Select all

function getParameter(pID)
	local new=0
	local inpNum=getInpNum(pID, tools[1])
	local parVal=getCurrVal(tools[1], inpNum)
	 
	for i=2, table.getn(tools) do
		inpNum=getInpNum(pID, tools[i])
		new=getCurrVal(tools[i], inpNum)
		if cmpVals(new, parVal)==false then
			return "?"
		end
	 end
	 
	 if type(parVal)=="number" then
		  return tostring(parVal)
	 end
	 
	 if type(parVal)=="table" then
		  return parVal
	 end
	 
	 if type(parVal)=="string" then
		  return parVal
	 end
	 
	 return "?"
end
Returns, in string form, the current value of the chosen parameter. If the current value of all tools is not identical, it instead returns "?". Used to pre-fill the Value text entry field. It does not handle the FuID datatype.

Code: Select all

function tblToString(tbl, div, lastDiv)
	 local n=table.getn(tbl)
	 local str=""
	 for i=1, n do
		  if type(tbl[i])~="table" then
				if i~=n then
					str=str .. tbl[i] .. div 
				else
					 str=str .. tbl[i] .. lastDiv 
				end
		  else
				return ""
		  end
	 end
	 return str
end
Creates a string containing all entries from the specified table, separated by a programmer-specified delimiter. Unused in the current form of the script, although there's a disabled line that would call it.

Code: Select all

--/////////////////////////////////////////////////						IUP Functions
function getList(s_tbl, vName)
	 n=table.getn(s_tbl)
	 str=""
	 for i=1, n do
		  if i~=n then
				str=str ..'"'.. s_tbl[i].nm .. '", '
		  else
				str=str ..'"'.. s_tbl[i].nm .. '"; '
		  end
	 end
	 str = vName .. "=iup.list{" .. str .. 'dropdown="yes", size="250x" }'
	 dostring(str)
end
In this section of functions dedicated to IUP, this is the sole entry. Yet there are several other event-handlers later on that I think should probably have been up here, too. In any case, this function adds entries to the Parameter combo box. It again uses that esoteric dostring() function.

Now we begin what is designated as the "Main Code," where execution of the script will actually begin.

Code: Select all

allTools={}
allToolsNames={}


if globals.__addtool_data then
	allTools=globals.__addtool_data
else
	allTools = fu:GetRegSummary(CT_Tool)
	globals.__addtool_data = allTools
end

for i,v in ipairs(allTools) do
	if v.REGS_Name~=nil and v.REGS_OpIconString~=nil and v.REGS_ID~=nil then
		table.insert(allToolsNames, v.REGS_ID)
	end
end

supp_types={"Number", "FuID", "Point", "Text"}	-- Supproted for this script input data types
seen={}														-- List of seed tools in tools table
tools=comp:GetToolList(true)
tools=clearModifiers(tools)
--~ dump(tools)

if  table.getn(tools)<2 then											--
	 print("you must select more then one tool for sheet")	-- Selected less then two tools 
	 return 0																--
end
This first bit of code is fairly simple. the allTools, allToolsNames, and tools tables are initialized and populated. All of this ultimately serves the purpose of creating a list of selected Tools and excluding Modifiers. Then the script checks to be certain that at least two Tools were selected.

Code: Select all

minEntry=getMin(tools)
--~ print("minEntry " .. minEntry .. "  " .. tools[minEntry]:GetAttrs().TOOLS_Name)
sheets=getInpsNames(tools[minEntry])								-- getting number of tool in tools table with minimal number of inputs
table.insert(seen, tools[minEntry]:GetAttrs().TOOLS_RegID)			-- getting ID of this tool. for sample "Loader"
table.remove(tools, minEntry)										-- remove this tool form tools table

while (table.getn(tools)>0) do
	 nowMin=1
	 if isSeen(tools[nowMin]:GetAttrs().TOOLS_RegID)==false then
		  table.insert(seen, tools[nowMin]:GetAttrs().TOOLS_RegID)
		  iInfo=getInpsNames(tools[nowMin])
		  sheets=mulInps(sheets, iInfo)
	 end
	 table.remove(tools, nowMin)
end

--~ print("ok")
if table.getn(sheets)==0 then
	print("No sheet Inputs")
	return 0
end

table.sort(sheets, function (a,b) return (b.nm > a.nm) end)
This, to me, is the interesting part of this script. First, the selected tool with the smallest number if Inputs is found. Reference to that tool is removed from the tools list (possibly creating a nil in the middle of the table, which is why we can't always rely on ipairs()). A list of that tool's inputs is placed in the sheets table.

In the while loop, we look at each remaining entry in tools. If a tool of that type has already been processed (exists in the seen table), the tool is removed from tools. Otherwise, the tool's type is inserted into seen, a list of that tool's Inputs is acquired, and mulInps is called to remove any inputs that are not in common between the two lists. The tool is removed from the table, and the loop repeats. The while loop breaks when getn() reports that there are no more entries in the tools table.

If there are no inputs in the sheets table at the end of this process, then the selected tools have no inputs in common, and the script exits with an error. The table is alphabetized in preparation for presenting it to the user in the GUI.

Code: Select all

tools = comp:GetToolList(true)
tools = clearModifiers(tools)
Since the tools list was depopulated by the previous routine, these lines restore it.

Code: Select all

-- IUP
--///////////////////////////////////////////////
message="Here palced values\nof FuID to copy\nin TextField"
nowAc=1
selTp=sheets[1].tp
nowTp=sheets[1].tp
xVal=""
yVal=""
tVal=""
fVal=""

active	="255 255 255"
passive	="64 64 64"
textCol	="0 0 0"

getList(sheets, "lst")

infoFuID=iup.multiline{size="10x10", expand="YES", fgcolor=active, bgcolor=passive, value=message}

listFuID=iup.list{expand="YES";fgcolor=active}

--iup.list{"Item 1 Text","Item 2 Text","Item 3 Text"; expand="YES",value="1"}


cordX=iup.text{size="50x"}
cordY=iup.text{size="50x"}
cordS=iup.hbox{iup.label{title="X:"}, cordX, iup.label{title="Y:"}, cordY}

inputType=iup.label{title=sheets[nowAc].tp}
textFld=iup.text{size="200x"}
textInp=iup.hbox{inputType, textFld}

btn_set=iup.button{title="Apply", size="100x20"}

setList=iup.vbox{textInp, cordS, btn_set; gap=10, alignment="ACENTER"}

iup.SetAttribute(cordX, "FGCOLOR", textCol)
iup.SetAttribute(cordY, "FGCOLOR", textCol)
iup.SetAttribute(textFld, "FGCOLOR", textCol)

currentVal=getParameter(sheets[1])
if nowTp~="Point" then
	 tVal=currentVal
	 iup.SetAttribute(textFld, "VALUE", currentVal)
	 iup.SetAttribute(cordS, "ACTIVE", "NO")
	 iup.SetAttribute(textFld, "BGCOLOR", active)
	 iup.SetAttribute(cordX, "BGCOLOR", passive)
	 iup.SetAttribute(cordY, "BGCOLOR", passive)
else
	 iup.SetAttribute(cordX, "VALUE", tostring(currentVal[1]))
	 iup.SetAttribute(cordY, "VALUE", tostring(currentVal[2]))
	 xVal=currentVal[1]
	 yVal=currentVal[2]
	 iup.SetAttribute(textFld, "ACTIVE", "NO")
	 iup.SetAttribute(textFld, "BGCOLOR", passive)
	 iup.SetAttribute(cordX, "BGCOLOR", active)
	 iup.SetAttribute(cordY, "BGCOLOR", active)
end
I am not going to spend much time at all analyzing the IUP code. The only things I really need to understand here are the relationships between variable names and UI fields. In IUP, a field is addressed by assigning a handle for it to a variable. For instance, cordX=iup.text{size="50x"} creates a text field with a width of 50 pixels, but its identity for scripting purposes is the variable cordX. When we then later see a line like iup.SetAttribute(cordX, "FGCOLOR", textCol), we understand that it is targeting that text field called cordX.

We'll see many such references in the event functions below, so it's important to figure out how they relate to the GUI.

Code: Select all

-- 																						Event functions
--/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function clearFuIDlist()
	for i=1, 10 do listFuID[i]=nil end
end
Here we start looking at what happens when the user interacts with the GUI. This first function isn't actually an event function, but it is called exclusively by the events, so I suppose I can see what it's doing here. The purpose of the function is to remove all entries from a global table called listFuID, which is itself a reference to a field in the UI. The script assumes that no list of FuIDs will ever exceed 10, which is dangerous since an input can theoretically have any number of FuID entries. A Merge's Apply Mode, for instance, has 17. The script appears to work as expected, though, so I suspect that this function may not even be necessary.

Code: Select all

function listFuID:action(tVar, i, v)
	if selTp=="FuID" then
		iup.SetAttribute(textFld, "VALUE", tVar)
		tVal=tVar
	end
end
This function activates when the user changes the listFuID field, which is the box on the right-hand side of the interface where lists appear for parameters such as Merge's Apply Mode or Blur's Filter. When the user selects a new FuID from the list, the contents of textFld and tVal are updated with that information. tVal is the new value that will go into the input when the Apply button is clicked.

Code: Select all

function lst:action(t, i, v)
	nowAc=i

	selTp=sheets[i].tp
	iup.SetAttribute(inputType, "TITLE", sheets[i].tp)
	currentVal=getParameter(sheets[i])
	clearFuIDlist()

	if (sheets[i].tp=="Point") then
		if currentVal=="?"  then 
			currentVal={"?", "?"}
		else
			xVal=currentVal[1]
			yVal=currentVal[2]
		end
		iup.SetAttribute(cordX, "VALUE", tostring(currentVal[1]))
		iup.SetAttribute(cordY, "VALUE", tostring(currentVal[2]))
	else
		tVal=currentVal
		iup.SetAttribute(textFld, "VALUE", currentVal)
	end
	 
	if (sheets[i].tp=="FuID") then
 		dostring("fuIDAttrs=composition." .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs()")
		inpCtrlType=string.gsub(fuIDAttrs.INPID_InputControl, "ID", "")
		dostring("idMat=composition." .. tools[1]:GetAttrs().TOOLS_Name .. "." .. sheets[i].id .. ":GetAttrs().INPIDT_" .. inpCtrlType .. "_ID")
--		iup.SetAttribute(infoFuID, "VALUE", tblToString(idMat, "\n", ""))
		clearFuIDlist()
		for i, j in ipairs(idMat) do listFuID[i]=tostring(j) end
	else
		iup.SetAttribute(infoFuID, "VALUE", message)
	end
	 
	 if isChange(nowTp, sheets[i].tp) then
		  if nowTp~="Point" then
				iup.SetAttribute(textFld, "ACTIVE", "NO")
				iup.SetAttribute(textFld, "BGCOLOR", passive)
				iup.SetAttribute(cordS, "ACTIVE", "YES")
				iup.SetAttribute(cordX, "BGCOLOR", active)
				iup.SetAttribute(cordY, "BGCOLOR", active)
		  else
				iup.SetAttribute(cordS, "ACTIVE", "NO")
				iup.SetAttribute(cordX, "BGCOLOR", passive)
				iup.SetAttribute(cordY, "BGCOLOR", passive)
				iup.SetAttribute(textFld, "ACTIVE", "YES")
				iup.SetAttribute(textFld, "BGCOLOR", active)
		  end
		  nowTp=sheets[i].tp
		  iup.Refresh(setList)
	 end
end
This one's pretty long, and it's got some weird stuff in it. It is called when the field called lst is changed. Tracing back through the code, I can see that lst is created by a call to getList. Line 312 has that call: getList(sheets, "lst"). It's not obvious that this creates the variable lst, though, because the actual variable assignment is handled in one of those dostring() calls. getList() creates the combo box and populates it with the contents of the sheets table, which as we saw earlier, contains the inputs that all of the selected tools have in common.

In short, this function is called when the user chooses an input that they want to change.

I am not 100% certain what the arguments on these event handlers are for, but i appears to be the index of the item chosen from the combo box. Since that combo box essentially contains the sheets table, this i is also the index of the associated input in sheets. nowAc is a global variable that was declared at the beginning of the IUP section of the code. By changing it here, the other functions will be able to see which input is being acted upon, even if they are not passed that information explicitly.

The datatype of the chosen input is determined, and the inputType label on the GUI is updated to suit. The current value of the input is also obtained, and then the FuIDList is cleared (probably unnecessarily).

Then we move into a selection structure, where we check the datatype of the input and act accordingly, transferring the information obtained from getParameter() into the appropriate field in the GUI. The convoluted dostring() logic is the only part that's really difficult to understand.

Finally, some fields are grayed-out and disabled depending on if they are needed based on the datatype.

Code: Select all

function btn_set:action()
	 
	local do_s=""
	local operation=""
	local incX=""
	local incY=""
	local _tVal=tVal
	local _xVal=xVal
	local _yVal=yVal
	local newVal=0
	local chX=1
	local chY=1
	 
	for i=1, table.getn(tools) do
		
		tlName=tools[i]:GetAttrs().TOOLS_Name
		  
		if sheets[nowAc].tp=="Number" then
			if eyeon.isin( {"+","-","/","*"}, string.sub(_tVal,1,1)) then
				operation=string.sub(_tVal, 1, 1)
				_tVal=string.sub(_tVal, 2, string.len(tVal))
			end
			if tonumber(_tVal)==nil then
					 print("Entered data isn't Number")
					 return
				end
				if operation=="" then
					local inpNum=getInpNum(sheets[nowAc], tools[i])
					if inpNum>0 then
						tools[i]:GetInputList()[inpNum][cTime()]=tonumber(_tVal)
					end
				else
					local inpNum=getInpNum(sheets[nowAc], tools[i])
					if inpNum>0 then
						local cValue=getCurrVal(tools[i], inpNum)
						if operation=="+" then 
							tools[i]:GetInputList()[inpNum][cTime()]=cValue+tonumber(_tVal)
						end
						if operation=="-" then 
							tools[i]:GetInputList()[inpNum][cTime()]=cValue-tonumber(_tVal)
						end
						if operation=="*" then 
							tools[i]:GetInputList()[inpNum][cTime()]=cValue*tonumber(_tVal)
						end
						if operation=="/" then 
							tools[i]:GetInputList()[inpNum][cTime()]=cValue/tonumber(_tVal)
						end
					end
				end
		end
		  
  		if sheets[nowAc].tp=="Point" then
			
			if eyeon.isin( {"+","-","/","*"}, string.sub(xVal,1,1)) then
				incX=string.sub(xVal, 1, 1)
				_xVal=string.sub(xVal, 2, string.len(xVal))
			end
				
			if eyeon.isin( {"+","-","/","*"}, string.sub(yVal,1,1)) then
				incY=string.sub(yVal, 1, 1)
				_yVal=string.sub(yVal, 2, string.len(yVal))
			end
			
			local inpNum=getInpNum(sheets[nowAc], tools[i])
			if inpNum>0 then
				tmpVar=getCurrVal(tools[i], inpNum)
				
				if (tonumber(_xVal)==nil) then
					chX=0
				end
			
				if (tonumber(_yVal)==nil) then
					chY=0
				end
			
				-- INCREMENT FOR ELEMENTS
				-------------------------------------------------------------------------------------------
				newVal=0
			
				if chX==1 then
					if incX~="" then
						if incX=="+" then newVal=tmpVar[1]+tonumber(_xVal) end
						if incX=="-" then newVal=tmpVar[1]-tonumber(_xVal) end
						if incX=="*" then newVal=tmpVar[1]*tonumber(_xVal) end
						if incX=="/" then newVal=tmpVar[1]/tonumber(_xVal) end
						_xVal=newVal
					end
					tools[i]:GetInputList()[inpNum][cTime()]={_xVal, tmpVar[2], 0}
				else
					_xVal=tmpVar[1]
				end
			
				if chY==1 then
					if incY~="" then
						if incY=="+" then newVal=tmpVar[2]+tonumber(_yVal) end
						if incY=="-" then newVal=tmpVar[2]-tonumber(_yVal) end
						if incY=="*" then newVal=tmpVar[2]*tonumber(_yVal) end
						if incY=="/" then newVal=tmpVar[2]/tonumber(_yVal) end
						_yVal=newVal
					end
					tools[i]:GetInputList()[inpNum][cTime()]={_xVal, _yVal, 0}
				end
				-------------------------------------------------------------------------------------------
			end
		end
		  
		  if (sheets[nowAc].tp=="Text") or (sheets[nowAc].tp=="FuID")then
				if tVal~="?" then
					do_s="composition." .. tlName .. "." .. sheets[nowAc].id .. "[" .. comp:GetAttrs().COMPN_CurrentTime .. ']="' .. tVal .. '"' 
					dostring(do_s)
				end
		  end
	
	 end
end
This ridiculously long piece of code executes when the Apply button is clicked. There is an awful lot in here, but the good news is that most of it doesn't actually do anything. Apparently there was an intention to add a feature where simple math operations could be performed on the inputs. For instance, the user could elect to add 5 to the current value of each one. That feature was never finished, but part of the code sticks around, anyway, and the bulk of this function is made up of that code. Ideally, the math operations should have been removed to functions of their own instead of cluttering up the button action. Probably that would have happened if it had ever been finished.

I won't bother analyzing all of the dead code—I'll only talk about the portions that actually work. First, we have a for loop that considers each selected tool. Inside the loop, we have a selection structure that determines what will happen based on the datatype of the input being changed. Let's look at each of those in turn:

If the datatype is "Number," we simply assign the user-designated value to the input. Here is the code reduced to only the essential lines:

Code: Select all

if sheets[nowAc].tp=="Number" then
	if tonumber(_tVal)==nil then
		 print("Entered data isn't Number")
		 return
	end
	local inpNum=getInpNum(sheets[nowAc], tools[i])
	if inpNum>0 then
		tools[i]:GetInputList()[inpNum][cTime()]=tonumber(_tVal)
	end	
end
Everything else in that section was nonsense. If the datatype is "Point," we first check to be sure the user-supplied data is numeric, although unlike the Number above there is no error handling here. Instead, the script will just quietly refuse to do anything. Next, we assign new x and y values individually. Once again, I'll strip the code down for better comprehension:

Code: Select all

if sheets[nowAc].tp=="Point" then
	local inpNum=getInpNum(sheets[nowAc], tools[i])
	if inpNum>0 then
		tmpVar=getCurrVal(tools[i], inpNum)
		if (tonumber(_xVal)==nil) then
			chX=0
		end
		if (tonumber(_yVal)==nil) then
			chY=0
		end
		if chX==1 then
			tools[i]:GetInputList()[inpNum][cTime()]={_xVal, tmpVar[2], 0}
		else
			_xVal=tmpVar[1]
		end
		if chY==1 then
			tools[i]:GetInputList()[inpNum][cTime()]={_xVal, _yVal, 0}
		end
	end
end
The datatypes "Text" and "FuID" are both handled in the same selection, and although the code is shorter, it does use that bizarre dostring() that we fixed in the first pass.

That's it for the button action. That's probably the most difficult piece of the script to comprehend, but once the garbage been cleared away, hopefully you can see that it's not as bad as it looks.

Code: Select all

function cordX:action(c, after)
	 xVal=after
end

function cordY:action(c, after)
	 yVal=after
end

function textFld:action(c, after)
	 tVal=after
end
I'm bundling these last three functions together because they're short, simple, and all do the same thing. When the user has put a new value in any of these fields, these functions simply transfer the new data into the appropriate global variables that will be used elsewhere.

Code: Select all

--/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

dlg=iup.dialog{
	 iup.hbox{
		  iup.vbox{
				iup.hbox{
					 iup.frame{
						  iup.hbox{
								lst;
								gap=5
						  };
						  title="Choose Parameter",
						  size="300x50",
						  fgcolor=active
					 }
				},
				iup.hbox{
					 iup.frame{
						  setList;
						  title="Set To:",
						  size="300x110",
						  fgcolor=active
					 }
				},

		  },
		  iup.vbox{
				iup.frame{
					 iup.vbox{
						  listFuID;
						  gap=5
					 };
					 size="100x150"
				}
		  }
	 };
	 margin="10x10",
	 title="© 2007 SlayerK Multiple Parameter Changer",
	 resize="no",
	 bgcolor="64 64 64"
}

dlg:show()
iup.MainLoop()
Finally, this last bit creates the GUI itself. This is where the layout is created. The last two lines invoke the window and then keep it active until the user closes it.

In my next post, I'll wireframe the new GUI and determine what functions I'll need to replicate the features of the old script.

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#9

Post by Midgardsormr » Tue Jan 16, 2018 2:44 pm

Third Pass: The Rewrite

During my first attempt to adapt this code, I tried to just bolt on a new UI Manager interface and strap the old code into it with duct tape and baling wire. That didn't work so well. A more organized approach is called for. This time I decided to design the UI first, then bring in the functionality a little bit at a time. To that end, let's start by examining the new interface:
005_gui-compare.png
Here you can see the original IUP window on the right and my new UI Manager window on the left. In this image, everything is visible, but when the script is actually working, some of those fields will be hidden based on the datatype of the input being adjusted. The label "dataType" will also update interactively to indicate which kind of data the active field is expecting.

Setting the window up took a little bit of time and experimentation, but fortunately Andrew's fantastic guide and example scripts made most of the work easy. I imagine you can learn a lot more there than you can from me, so I'll just show the code and leave it at that:
Code: [Select all] [Expand/Collapse] [Download] (UIMan_BatchChg.lua)
  1. -- Set up UI Manager
  2. local ui = fu.UIManager
  3. local disp = bmd.UIDispatcher(ui)
  4. local width,height = 400,200
  5.  
  6. -- Define the Window
  7. win = disp:AddWindow({
  8.     ID = 'BCWin',
  9.     WindowTitle = 'Batch Parameter Changer',
  10.     Geometry = {800,200,600,340},
  11.     Spacing = 10,
  12.    
  13.     ui:VGroup{
  14.         ID = 'root',
  15.         Weight = 1.0,
  16.         ui:HGroup{
  17.             Weight = 0,
  18.             ui:Label{
  19.                 ID = 'paramLabel',
  20.                 Text = 'Choose Parameter:',
  21.                 Weight = 0,
  22.             },
  23.         },
  24.         ui:VGap(3),
  25.         ui:HGroup{
  26.             Weight = 0,
  27.             ui:HGap(53),
  28.             ui:ComboBox{
  29.                 ID = 'Parameter',
  30.                 Text = 'Choose Parameter',
  31.                 Weight = 1,
  32.             },
  33.         },
  34.         ui:VGap(30),
  35.         ui:HGroup{
  36.             Weight = 0,
  37.             ui:Label{
  38.                 ID = 'setLabel',
  39.                 Text = 'Set To:',
  40.             },
  41.         },
  42.         ui:VGap(10),
  43.         ui:HGroup{
  44.             Weight = 0,
  45.             ui:Label{
  46.                 ID = 'dataTypeLabel',
  47.                 Text = 'dataType',
  48.                 Weight = 0,
  49.                 Visible = true,
  50.             },
  51.             ui:LineEdit{
  52.                 ID = 'textFld',
  53.                 Text = 0,
  54.                 Visible = true,
  55.             },
  56.         },
  57.         ui:HGroup{
  58.             Weight = 0,
  59.             ui:HGap(53),
  60.             ui:ComboBox{
  61.                 ID = 'listFuID',
  62.                 Text = '',
  63.                 Weight = 1,
  64.                 Visible = true,
  65.             },
  66.         },
  67.         ui:HGroup{
  68.             Weight = 0,
  69.             ui:HGap(36),
  70.             ui:Label{
  71.                 ID = 'xlabel',
  72.                 Text = 'X:',
  73.                 Visible = true,
  74.                 Weight = 0.05,
  75.             },
  76.             ui:LineEdit{
  77.                 ID = 'cordX',
  78.                 Text = '0.5',
  79.                 Visible = true,
  80.                 Weight = 1,
  81.             },
  82.             ui:HGap(20),
  83.             ui:Label{
  84.                 ID = 'ylabel',
  85.                 Text = 'Y:',
  86.                 Visible = true,
  87.                 Weight = 0.05,
  88.             },
  89.             ui:LineEdit{
  90.                 ID = 'cordY',
  91.                 Text = '0.5',
  92.                 Visible = true,
  93.                 Weight = 1,
  94.             },
  95.         },
  96.         ui:VGap(30),
  97.         ui:HGroup{
  98.             ui:Button{
  99.                 ID = 'btn_set',
  100.                 Text = 'Apply',
  101.                 Weight = 1,
  102.             },
  103.         },
  104.     },
  105.    
  106. })
  107.  
  108. -- The window was closed
  109. function win.On.BCWin.Close(ev)
  110.   disp:ExitLoop()
  111. end
  112.  
  113. -- Add your GUI element based event functions here:
  114. itm = win:GetItems()
  115.  
  116. -- Activate the window
  117. win:Show()
  118. disp:RunLoop()
  119. win:Hide()
  120.  
The GUI will be responsive to user input. Choosing a Parameter will cause it to display the expected datatype and reveal only the appropriate entry fields: One for text and numbers, a combo box for FuIDs, and a pair of X and Y fields for Points. Unlike the original script, I don't consider it necessary to copy the selected FuID into the main entry box; the combo box itself will be sufficient. And, of course, clicking "Apply" will cause the execution of a routine that sets the inputs to the value(s) entered.

Showing and hiding fields with UI Manager is as simple as changing the value of their Visible attributes. I was doing some nonsense with graying out disabled fields and making them Read Only, but simply hiding them works better. I could probably do the same with the labels, but for now I'll leave it be and see how much it bothers me to have the label not be in line with the entry fields.

On to the process of actually making the interface work. I'm going to go with a user-centric approach—rather than trying to port over all of the code and shoehorn it into my new interface, I'll work on each piece of the window, copying over only such code as I need to solve specific problems. As I go along, I'll clean things up for readability and better logical flow.

General functions will go at the top of the script, followed by the UI Manager code. After that will come the event manager functions, then the main() function, and finally the four function calls that will invoke everything.

So the first thing that needs to happen is the Choose Parameter box needs to be filled with a list of Inputs common to all of the selected Tools. The bulk of the code in the section of the original script marked MAIN CODE seems to be used to perform this task. For the most part, the logic seems pretty sound, but some of the variable names are abbreviated to the point of being obtuse. I'll be honest in saying that I'm not 100% sure what's up with the globals.__addtool_data bit. I don't know if it's actually doing something or not, but operating on the assumption that it was there for a reason, I've left it intact. It doesn't seem to interfere with anything that I can see.

Mostly in this section I've just added comments to document the code. In a few places I've changed the variable and function names to improve readability. The most critical change is that the table sheets is now controls. I'll need to remember that as I continue the rewrite, as sheets was used in many places. I replaced the function isSeen() with a call to bmd.isin(), and I moved the getList() function into main() since it didn't seem to warrant being a separate routine. I may change my mind about that later when I look into handling FuIDs, but even if I decide to include the function after all, it will need to be generalized to work for more than just one UI field. Here is my new main() function:

Code: Select all

-- //////////////////////////////////
-- /           MAIN CODE            /
-- //////////////////////////////////
function main()
	allTools = {}
	allToolsNames = {}

	-- Get a list of all the tools in Fusion's registry
	if globals.__addtool_data then
		allTools = globals.__addtool_data
	else
		allTools = fu:GetRegSummary(CT_Tool)
		globals.__addtool_data = allTools
	end

	-- Make a new list containing the REGS_ID of all tools that have all three of name, OpIcon and ID.
	for i,v in ipairs(allTools) do
		if v.REGS_Name~=nil and v.REGS_OpIconString~=nil and v.REGS_ID~=nil then
			table.insert(allToolsNames, v.REGS_ID)
		end
	end

	supp_types = {"Number", "FuID", "Point", "Text",}	-- DataTypes supported by this script.
	tools = comp:GetToolList(true)						-- List of user-selected tools
	tools = clearModifiers(tools)						-- Removes Modifiers from the tool list.
	seen={}												-- Holds a list of tool REGIDs detected in the tools list
														--		Filters the list for efficiency when building 
														--		the Parameters table.

	-- If fewer than two tools are selected, throw an error.
	if table.getn(tools) < 2 then
		comp:AskUser("You must select more than one tool.\nAborting.", {})
		print("You must select more than one tool.")
		return 0
	end

	-- Get the index of the tool with the smallest number of inputs
	minEntry = getMin(tools)

	-- Get a list of the inputs in that tool. 
	controls = getInputInfo(tools[minEntry])

	-- Add the tool type to the seen table
	table.insert(seen, tools[minEntry]:GetAttrs().TOOLS_RegID)

	-- Remove the tool from the tool list so it won't be reprocessed
	table.remove(tools, minEntry)

	-- Pare down the inputs in the controls table to the inputs common to all selected tools.
	while (table.getn(tools)>0) do 	-- Loop as long as at least one tool remains in the table. 
									-- This loop always processes the tool at index 1 of the tools table.
		-- Check to see if the current tool's RegID has already been processed.
		if bmd.isin(seen, tools[1]:GetAttrs().TOOLS_RegID) == false then
			-- Add the RegID to the seen table
			table.insert(seen, tools[1]:GetAttrs().TOOLS_RegID)
			-- Get info about the tool's Inputs
			iInfo = getInputInfo(tools[1])
			-- Remove any inputs that are not already present in controls
			controls = intersect(controls, iInfo)
		end
		-- Remove the current tool from the list. getn() will be reduced by 1. When no tools remain,
		-- the loop will break.
		table.remove(tools, 1)
	end

	-- If no inputs remain in the controls table, the selected tools have no controls in common.
	-- Throw an error.
	if table.getn(controls) == 0 then
		composition:AskUser("No Common Inputs. Aborting.", {})
		print("No common inputs.")
		return 0
	end

	-- Alphabetize the controls table.
	table.sort(controls, function(a,b) return (b.nm > a.nm) end)

	-- Populate the Parameter combo box
	for i = 1, table.getn(controls) do
		itm.Parameter:AddItem(controls[i].nm)
	end

	-- Repopulate the tool list
	tools = comp:GetToolList(true)
	tools = clearModifiers(tools)
	
	return 1

end
Since the bmd library provides an isin() function of its own, I just used that instead of bringing over the local isin(). I changed getInpsNames() to getInputInfo(), making it a little clearer what that function does. Likewise, mulInps() became intersect(). I'd like to generalize intersect() later on, as I think it would be a useful function to keep on hand, and depending on how it's implemented, it might be an interesting exercise in recursion. As in main(), I expanded abbreviated variable and argument names to make it clearer what they are for.

Here they are:

Code: Select all

-- /////////////////////
-- / Utility Functions /
-- /////////////////////

-- Clears Modifiers from a list of tools. allToolsNames is a global table containing a list of
-- all Tools available in Fusion.
function clearModifiers(tbl)
	local out = {}
	for i = 1, table.getn(tbl) do
		if bmd.isin(allToolsNames, tbl[i]:GetAttrs().TOOLS_RegID) then
			table.insert(out, tbl[i])
		end
	end
	return out
end

-- Finds the tool in the supplied list with the smallest number of Inputs and returns that tool's index.
function getMin(tbl)
	if table.getn(tbl)==0 then
		return(-1) -- If there are no items in the table, throw an error.
	end
	
	local minTool = 0
	local minInputs = math.huge
	numInputs = 0
	
	for i=1, table.getn(tbl) do
		numInputs = table.getn(tbl[i]:GetInputList())
		if numInputs<minInputs then
			minTool=i
			minInputs=numInputs
		end
	end
	return minTool
end

-- Returns a table containing a list of a tool's inputs' types, ids and names.
function getInputInfo(tool)
	local tbl = tool:GetInputList()
	local out = {}
	local attrs = {}
	local name = ""
	local iID = ""
	local iType = ""
	for i, j in ipairs(tbl) do
		attrs = j:GetAttrs()
		name = attrs.INPS_Name
		iID = attrs.INPS_ID
		iType = attrs.INPS_DataType
		if (attrs.INPB_Passive == false) and (bmd.isin(supp_types, iType) == true) then
			table.insert(out, {tp=iType, id=iID, nm=name})
		end
	end
	return out
end


-- Compares the entries in two tables and returns a table containing all entries that are in both.
-- Look into generalizing this function.
function intersect(tblA, tblB)
	if type(tblA)~="table" then
		return nil
	end
	
	local out = {}
	for i = 1, table.getn(tblB) do
		flag = false
		for j = 1, table.getn(tblA) do
			if(tblB[i].id == tblA[j].id) and (tblB[i].nm == tblA[j].nm) and (tblB[i].tp == tblA[j].tp) then
				flag = true
			end
		end
		if flag == true then
			table.insert(out, tblB[i])
		end
	end
		
	return out
end
Testing shows that the interface is working perfectly so far. The Choose Parameter combo box populates with only the control names that are common between all selected tools. Curiously, it doesn't display hidden controls, such as those that are revealed by ticking the Motion Blur box. That might be a problem that would require restoring that getList() function. But I'll burn that bridge when I come to it.

The next thing I want to happen is to determine the dataType of the selected control, update the dataType label, and hide any unnecessary data entry fields. For that, we'll look at the first of our event handling functions: win.On.Parameter.CurrentIndexChanged(ev).

The first thing that is important to know is that UI Manager doesn't count the same was as Lua does. Combo boxes in UI Manager start at index 0, so in order to correlate the selected index reported by the event manager to the associated table index, we need to add 1 to it:
local idx = itm.Parameter.CurrentIndex + 1

I bring over a couple more functions from the original script, touching them up as I did the others. Given that the controls table already contains tool types, I don't see any reason to use a function to retrieve that information. dataType = controls[index].tp is much more direct, and it removes the need for the paramname variable. Retrieving an input's current value, though, does require the getParameter() function. And that, in turn, has dependencies on getInputNumber() and compareValues().

There is a tricky bit in getting the FuID combo box working, so I'm going to put that off for a few moments. Instead, I will first do my UI adjustments. A simple if-then-else selection structure is used to hide and reveal fields using their Visible attribute. Pre-filling the data entry fields for Numbers, Points, and Text is also very simple: itm.textFld.Text = currentValue.

Now, that FuID thing was one of the places where SlayerK used dostring(). I'd like to avoid such a clumsy approach, so let's see if I can't break down what I need to get the same results without it. First, let's suppose that the tool we're looking at is Polygon1, and we want the list associated with the Filter control, which has an index of 8 and an id of "Filter." The dostring() commands would result in this code:

Code: Select all

fuIDAttrs = composition.Polygon1.Filter:GetAttrs()
inputControlType = string.gsub(fuIDAttrs.INPID_InputControl, "ID", "")
idMat = composition.Polygon1.Filter:GetAttrs().INPIDT_MultiButtonControl_ID
The end result is a table called idMat that contains a list of FuIDs. In this case, {1 = Box, 2 = Bartlett, 3 = Multi-box, 4 = Gaussian,}.

I think the reason for the dostring() was because SlayerK didn't know the other way you can address a table's contents in Lua. It's not always necessary to use the sub-table notation of Polygon1.Filter. If we instead use the sub-table as an index, we can do it this way:

Code: Select all

id = controls[index].id
fuIDAttrs = tools[1][id]:GetAttrs()
inputControlType = string.gsub(fuIDAttrs.INPID_Inputcontrol, "ID", "")
controlID = "INPIDT_"..inputControlType.."_ID"
fuIDlist = tools[1][id]:GetAttrs()[controlID]
fuIDlist now contains a table with a list of FuIDs, ready to be inserted into the combo box:

Code: Select all

for i=1, table.getn(fuIDlist) do
	itm.listFuID:AddItem(fuIDlist[i])
end
Trouble is, we're still going to need to clear out the list whenever we run this routine or it's just going to keep filling up with more entries. A quick check with Andrew's FusionScript Help Browser (a must-have script!) shows me that UIComboBox has a method called Clear(), which turns out to do exactly as I expect: It clears out any existing items in the combo box.

The win.On.Parameter.CurrentIndexChanged() function is now complete:

Code: Select all

-- When an entry is chosen in the Parameter combo box, change the data type label and pre-fill
-- the appropriate data entry field with the current value of the chosen Input, if it's identical
-- on all controls. If it is not, insert "?". Toggle Visible attributes to configure the UI.
function win.On.Parameter.CurrentIndexChanged(ev)
	local index = itm.Parameter.CurrentIndex + 1
	local currentValue = getParameter(controls[index])
	local dataType = controls[index].tp
	local xVal, yVal
	local id = controls[index].id
	
	itm.dataTypeLabel.Text = dataType
	
	if dataType == "Point" then
		if currentValue == "?" then
			currentValue = {"?", "?"}
		else
			xVal = currentValue[1]
			yVal = currentValue[2]
		end
		
		itm.textFld.Visible = false
		itm.listFuID.Visible = false
		itm.xlabel.Visible = true
		itm.ylabel.Visible = true
		itm.cordX.Visible = true
		itm.cordY.Visible = true
		itm.cordX.Text = tostring(xVal)
		itm.cordY.Text = tostring(yVal)
		
	elseif dataType == "FuID" then
		itm.textFld.Visible = false
		itm.listFuID.Visible = true
		itm.xlabel.Visible = false
		itm.ylabel.Visible = false
		itm.cordX.Visible = false
		itm.cordY.Visible = false
		
		itm.listFuID:Clear()
		
		fuIDAttrs = tools[1][id]:GetAttrs()
		inputControlType = string.gsub(fuIDAttrs.INPID_InputControl, "ID", "")
		controlID = "INPIDT_"..inputControlType.."_ID"
		fuIDlist = tools[1][id]:GetAttrs()[controlID]
		
		for i = 1, table.getn(fuIDlist) do
			itm.listFuID:AddItem(fuIDlist[i])
		end
		
	else
		itm.textFld.Visible = true
		itm.listFuID.Visible = false
		itm.xlabel.Visible = false
		itm.ylabel.Visible = false
		itm.cordX.Visible = false
		itm.cordY.Visible = false
		
		itm.textFld.Text = currentValue
	end
	
end
There is only one function remaining, which executes upon clicking the "Apply" button. Unfortunately, I just got slammed with work, so I am not going to have time to dig into that one in this post. Hopefully I'll be able to get to it within the week, but we'll see. For now, rest assured that this script is working pretty well so far, and I am very confident that a working version will be available to the community very soon!

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#10

Post by Midgardsormr » Mon Jan 22, 2018 4:42 pm

Back to it!

As I said in the last post, the only thing that remains is to write the function that actually sets the new value to each selected tool. In comparison to the original script, this is fairly straightforward. And I did finally figure out what I was doing wrong in my original conversion—I needed to force a conversion from the text field to a numerical value with tonumber(). It was staring me right in the face in all of that code I'd bypassed; I should have taken care to make sure I knew everything that was going on in there. Anyway…

This function goes off when the Apply button is clicked, so it is declared with function win.On.btn_set.Clicked(ev). I query the Parameter field for its index and use that to get the dataType and id of the control I want to set, just like in win.On.Parameter.CurrentIndexChanged(). Then a simple loop through each tool and a selection structure based on dataType. The parameter is set with the simple line j[id][comp.CurrentTime] = tonumber(newValue), with minor variations depending on the dataType.

The completed function:

Code: Select all

-- When the Apply button is clicked, set the chosen Parameter on each selected tool. This button
-- does not close the GUI.
function win.On.btn_set.Clicked(ev)
	-- Identify the parameter to be changed, get its datatype and ID.
	local index = itm.Parameter.CurrentIndex + 1
	local dataType = controls[index].tp
	local id = controls[index].id
	
	-- Loop through each entry in the selected tools list.
	for i, j in ipairs(tools) do
	
		--Selection based on dataType
		if dataType == "Number" then
			--Get the user-supplied new value
			local newValue = itm.textFld.Text
			--Check it for valid type
			if tonumber(newValue)==nil then
				print("Entered data is not a Number")
				return
			end
			--Set the chosen input
			j[id][comp.CurrentTime] = tonumber(newValue)
		end
		
		--Second verse, same as the first, except we don't need to validate the data type.
		if dataType == "Text" then
			local newValue = itm.textFld.Text
			j[id][comp.CurrentTime] = newValue
		end
		
		--Points are a 3 dimensional value (though the Z value is 
		--very rarely addressed), so we use a table to hold it.
		if dataType == "Point" then
			local newValue = {}
			newValue[1] = tonumber(itm.cordX.Text)
			newValue[2] = tonumber(itm.cordY.Text)
			newValue[3] = 0
			local errFlag = 0
			if tonumber(itm.cordX.Text) == nil then
				print("X Coordinate data is not a Number")
				errFlag = 1
			end
			if tonumber(itm.cordY.Text) == nil then
				print("Y Coordinate data is not a Number")
				errFlag = 1
			end
			if errFlag == 1 then
				return
			end
			j[id][comp.CurrentTime] = newValue
		end
		
		--The combo box uses a different attribute to hold its current
		--value, but it's otherwise just like the others. No need to test
		--for valid input since that's enforced by the box itself.
		if dataType == "FuID" then
			local newValue = itm.listFuID.CurrentText
			j[id][comp.CurrentTime] = newValue
		end
	end
end
After the really long previous post, it seems a bit anti-climactic! The complete script is available in the first post of this thread, and it will be available in Reactor soon.

User avatar
AndrewHazelden
Fusionator
Posts: 1391
Joined: Fri Apr 03, 2015 3:20 pm
Answers: 4
Location: West Dover, Nova Scotia, Canada
Been thanked: 59 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#11

Post by AndrewHazelden » Mon Jan 22, 2018 4:50 pm

Hi.

Great work Midgardsormr!

I wanted to mention that Lua can be a pain sometimes when you want to explicitly convert Boolean values since there is no premade purpose built toboolean() function built into the language by default. it might not be an issue in this case but anytime I start using tostring() and tonumber() I start to think about boolean handling as well. :)

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#12

Post by Midgardsormr » Mon Jan 22, 2018 5:02 pm

That's a good thing to watch for. I don't think I've yet run across a tool that has an actual Boolean Input, so I won't worry about it at this time.

User avatar
edenexposito
Posts: 29
Joined: Fri Mar 23, 2018 2:36 am

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#13

Post by edenexposito » Fri Apr 27, 2018 5:23 am

Seems that In Loader I can not change channels in OpenEXR files with this batch. I want add "alpha" to all this 3 loaders at same time. All loaders load same file, but change channels RGB (it's a merged AOV exr).

Image


It's normal?

User avatar
Midgardsormr
Fusionator
Posts: 1216
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 95 times
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#14

Post by Midgardsormr » Fri Apr 27, 2018 7:29 am

Hmmm... Well, the Loader's channel assignment is two levels deeper than a typical property. It's a sub-table of a sub-table of the Clip property. I don't recall if this script recurses over the inputs. I don't think it does, so it probably won't find nested inputs like that.

Here's a script snippet that will do what you need, though:

  1. for i, tool in ipairs(comp:GetToolList(true, "Loader")) do tool.Clip1.OpenEXRFormat.AlphaName = "A" end

Select all the Loaders you want to update, and run that line in the Console. If the channel name you want to put in there is not "A", just change it to whatever you need it to be.

User avatar
emill
Posts: 5
Joined: Thu Nov 02, 2017 7:31 am
Contact:

Re: Batch Parameter Changer — A Rewrite for Fusion 9

#15

Post by emill » Fri May 25, 2018 1:10 am

Tried to use this. Install via Reactor went smoothly (well, apart from having to hunt for the installed .lua file, which I ended up finding with Reactor > Tools > Show Reactor Folder in C:\ProgramData\Blackmagic Design\Fusion\Reactor\Deploy\Scripts\Comp).

But when I selected a few nodes and tried running the script (drag n drop into Console) I just got:
...\Reactor\Deploy\Scripts\Comp\Batch_Change_Parameters.lua:28: attempt to call field 'isin' (a nil value)

Am I even using it right, is it supposed to be dragged in or am I missing some menu item / hotkey? (I hope this can be bound to a hotkey, file/folderhunting & dragging is slow.)

Not sure why your code isn't seeing bmd.isin(). I'm on Windows here.