Change object order in Scene View
As outlined in https://www.daz3d.com/forums/discussion/140571/order-objects-in-scene-tab-drag-drop, I would love to have a way to change the order of items in your scene view/structure tab.
I'm a visually oriented guy, and I like to have things that are close in meaning visually close as well.
So I bit the bullet and looked at how scripts in DAZ work.
Surprisingly, it's a rather easy JS based simply API system that doesn't require a ton of getting used to. (Except for the GUI part - they're quite ugly to set up IMO (Although I haven't spent much time with it yet)) - and I wrote a script that lets you move nodes around, AS LONG AS THEY HAVE PARENTS.
Because the API exposes methods to insert nodes into a specific index of other nodes, I can move items around when there is a parent to "move" the item into.
When you've selected an item that does not have a parent, you should be presented with the option to move all scene contents in a group which I simply call "Scene Root".
- Usage should be pretty easy: Move the selected item up and down past its siblings under a parent by clicking "up" and "down"
- Move the selected item out of its parent node by clicking "Unparent"
- Make the upper sibling the parent of the currently selected node (i.e. move selected node into the one above it)
And navigate the selection via the corresponding buttons below.
Video:
// V0.1var selSize = 0;var hasParent = false;var numChildren = 0;var isFirst = false;var isLast = false;var fos = Scene.getSelectedNode(0);var los = Scene.getSelectedNode(selSize-1);var par = fos.getNodeParent();var startSelect = null;var statAbsPos;var statAbsRot;var statAbsScale;function moveInView(a){ var selnums = Scene.getNumSelectedNodes(); for(var i = 0; i<selnums; i++){ var selnode = Scene.getSelectedNode(i); if(selnode.getNodeParent()){ var parnode = selnode.getNodeParent(); var cind = parnode.findChildIndex(selnode); // msg(selnode.getLabel() + " = " + cind + " + " + a + " /" + parnode.getNumNodeChildren()); if(cind + a>=0 && cind + a < parnode.getNumNodeChildren()){ parnode.moveNodeChildToIndex(selnode, cind + a, true); } } else{ if(ask("No parent node available.\nShould all scene Nodes be moved into a Root Node (Group)?", "No Parent", "Cancel", "OK")){ rootAll(); moveInView(a); } } } updateButtons();}function unparent(){ var selnums = Scene.getNumSelectedNodes(); for(var i = 0; i<selnums; i++){ var selnode = Scene.getSelectedNode(i); var parentnode = selnode.getNodeParent(); if(parentnode){ if(selnode.getNodeParent().getNodeParent()){ saveTrans(selnode); selnode.getNodeParent().getNodeParent().addNodeChild(selnode); restoreTrans(selnode); } else{ parentnode.removeNodeChild(selnode, true); } } } updateButtons();}function doparent(){ var selnums = Scene.getNumSelectedNodes(); for(var i = 0; i<selnums; i++){ var selnode = Scene.getSelectedNode(i); var parentnode = selnode.getNodeParent(); if(parentnode){ var selfid = parentnode.findChildIndex(selnode); var newpar = parentnode.getNodeChild(selfid-1); newpar.addNodeChild(selnode, true); } } updateButtons();}function selectSibling(i){ getStates(); if(selSize<=0){ Scene.getNodeChild(0).selected = true; return; } var sib = getSibling(fos,i); sib.select(true); fos.select(false); updateButtons();}function selectParent(){ getStates(); if(hasParent && fos.getNodeParent().getNodeParent()){ selectNone(); fos.getNodeParent().select(true); } updateButtons();}function selectChild(){ getStates(); if(numChildren>0){ selectNone(); fos.getNodeChild(0).select(true); } updateButtons();}var wDlg = new DzDialog;wDlg.caption = "Re-Order Scene Object Tree View";var wDlgLayout = new DzGridLayout( wDlg );wDlgLayout.margin = 5;wDlgLayout.spacing = 5;var wBtnGrp = new DzGroupBox( wDlg );wBtnGrp.title = "Move in List";wBtnGrp.columns = 3;var bugrid = new DzGridLayout(wBtnGrp);bugrid.margin = 10;bugrid.spacing = 4;var wMoveUp = new DzPushButton(wBtnGrp);wMoveUp.text = "Up";wMoveUp.minWidth = 80;wMoveUp.maxHeight = 20;wMoveUp.clicked.connect(function(){moveInView(-1)});bugrid.addWidget(wMoveUp, 0, 1);var wUnparent = new DzPushButton(wBtnGrp);wUnparent.text = "Unparent";wUnparent.minWidth = 80;wUnparent.maxHeight = 20;wUnparent.clicked.connect(unparent);bugrid.addWidget(wUnparent, 1, 0);var wParent = new DzPushButton(wBtnGrp);wParent.text = "Parent";wParent.minWidth = 80;wParent.maxHeight = 20;wParent.clicked.connect(doparent);bugrid.addWidget(wParent, 1, 3);var wMoveDown = new DzPushButton(wBtnGrp);wMoveDown.text = "Down";wMoveDown.minWidth = 80;wMoveDown.maxHeight = 20;wMoveDown.clicked.connect(function(){moveInView(1)});bugrid.addWidget(wMoveDown, 3, 1);/////////////// Navigation. I'm lazy, so I basically just copied the above buttonsvar wNavGrp = new DzGroupBox( wDlg );wNavGrp.title = "Navigate Selection in List";wNavGrp.columns = 3;var nagrid = new DzGridLayout(wNavGrp);nagrid.margin = 10;nagrid.spacing = 4;var wnMoveUp = new DzPushButton(wNavGrp);wnMoveUp.text = "Up";wnMoveUp.minWidth = 80;wnMoveUp.maxHeight = 20;wnMoveUp.clicked.connect(function(){selectSibling(-1)});nagrid.addWidget(wnMoveUp, 0, 1);var wnUnparent = new DzPushButton(wNavGrp);wnUnparent.text = "Up Parent";wnUnparent.minWidth = 80;wnUnparent.maxHeight = 20;wnUnparent.clicked.connect(selectParent);nagrid.addWidget(wnUnparent, 1, 0);var wnParent = new DzPushButton(wNavGrp);wnParent.text = "Into Children";wnParent.minWidth = 80;wnParent.maxHeight = 20;wnParent.clicked.connect(selectChild);nagrid.addWidget(wnParent, 1, 3);var wnMoveDown = new DzPushButton(wNavGrp);wnMoveDown.text = "Down";wnMoveDown.minWidth = 80;wnMoveDown.maxHeight = 20;wnMoveDown.clicked.connect(function(){selectSibling(1)});nagrid.addWidget(wnMoveDown, 3, 1);wDlgLayout.addMultiCellWidget( wBtnGrp, 0, 0, 0, 1 );wDlgLayout.addMultiCellWidget( wNavGrp, 1, 1, 0, 1 );var wDlgBtnsGB = new DzGroupBox( wDlg );wDlgBtnsGB.flat = true;var wDlgBtnsLyt = new DzGridLayout( wDlgBtnsGB );wDlgBtnsLyt.margin = 5;wDlgBtnsLyt.spacing = 5;var wAcceptBtn = new DzPushButton( wDlgBtnsGB );wAcceptBtn.text = "OK";wAcceptBtn.minWidth = 80;wAcceptBtn.maxHeight = 20;wDlg.setAcceptButton( wAcceptBtn );wDlgBtnsLyt.addWidget( wAcceptBtn, 1, 0 );wDlgLayout.addMultiCellWidget( wDlgBtnsGB, 2, 2, 0, 1 );wDlg.maxWidth = wDlg.minWidth;wDlg.maxHeight = wDlg.minHeight;updateButtons();if(selSize<=0){ Scene.getNodeChild(0).select(true); updateButtons();}if(!hasParent && ask("No parent node available.\nShould all scene Nodes be moved into a Root Node (Group)?", "No Parent", "Cancel", "OK")){ startSelect = Scene.getSelectedNode(0); rootAll();}var rn = Scene.findNode("Scene Root");if(!hasParent && rn && rn.getNumNodeChildren()>0){ selectNone(); if(startSelect!=null){ startSelect.select(true); } else{ rn.getNodeChild(0).select(true); } updateButtons();}wDlg.exec()function msg(s,t,c1,c2,c3){ t = t==undefined?"Message":t; c1 = c1==undefined?"OK":c1; c2 = c2==undefined?"":c2; c3 = c3==undefined?"":c3; if(c2!=""){ if(c3!=""){ return MessageBox.information(s,t,c1,c2,c3); } return MessageBox.information(s,t,c1,c2); } return MessageBox.information(s,t,c1);}function warn(s,t,c1,c2,c3){ t = t==undefined?"Warning":t; c1 = c1==undefined?"OK":c1; c2 = c2==undefined?"Cancel":c2; c3 = c3==undefined?"":c3; if(c2!=""){ if(c3!=""){ return MessageBox.warning(s,t,c1,c2,c3); } return MessageBox.warning(s,t,c1,c2); } return MessageBox.warning(s,t,c1);}function ask(s,t,c1,c2,c3){ t = t==undefined?"Message":t; c1 = c1==undefined?"No":c1; c2 = c2==undefined?"Yes":c2; c3 = c3==undefined?"":c3; if(c2!=""){ if(c3!=""){ return MessageBox.question(s,t,c1,c2,c3); } return MessageBox.question(s,t,c1,c2); } return MessageBox.question(s,t,c1);}function rootAll(){ var baseNode = new DzGroupNode(); baseNode.name = "Scene Root"; baseNode.label = "Scene Root"; Scene.addNode(baseNode); var allnodes = Scene.getNodeList(); var j = 0; for(var i = 0; i<allnodes.length; i++){ if(allnodes[i].name == "Scene Root" || allnodes[i].getNodeParent()) continue; baseNode.addNodeChild(allnodes[i], true); baseNode.moveNodeChildToIndex(allnodes[i],j); j++; }}function getSibling(n, i){ var parnode = n.getNodeParent(); var cur = parnode.findChildIndex(n); if(cur + i < 0){ return parnode; } if(cur + i >= parnode.getNumNodeChildren()){ return nextOrParent(n, i); } return parnode.getNodeChild(cur + i);}function nextOrParent(c, i){ var n = c; var parnode = n.getNodeParent(); var cur = parnode.findChildIndex(n); if(cur + i >= parnode.getNumNodeChildren()){ if(n.getNodeParent()){ return nextOrParent(n.getNodeParent(), i); } else{return null;} } return parnode.getNodeChild(cur + i);}function getStates(){ selSize = Scene.getNumSelectedNodes(); hasParent = false; isFirst = true; isLast = true; fos = Scene.getSelectedNode(0); los = Scene.getSelectedNode(selSize-1); par = fos.getNodeParent(); numChildren = fos.getNumNodeChildren(); if(selSize>0 && par) hasParent = true; if(hasParent && par.findChildIndex(fos)>0) isFirst = false; if(hasParent && par.findChildIndex(los) < par.getNumNodeChildren()-1) isLast = false;}function setButtons(){ wMoveUp.enabled = !isFirst; wParent.enabled = !isFirst; wMoveDown.enabled = !isLast; wUnparent.enabled = hasParent; wnMoveDown.enabled = !isLast; wnMoveUp.enabled = hasParent; wnParent.enabled = numChildren>0; wnUnparent.enabled = hasParent && fos.getNodeParent().getNodeParent();}function updateButtons(){ getStates(); setButtons();}function selectNone(){ for(var i = 0; i<Scene.getNumSelectedNodes(); i++){ Scene.getSelectedNode(i).select(false); }}function saveTrans(n){ statAbsPos = n.getWSPos(); statAbsRot = n.getWSRot(); statAbsScale = n.getWSScale();}function restoreTrans(n){ n.setWSPos(statAbsPos); n.setWSRot(statAbsRot); n.setWSScale(statAbsScale);}
I hope this is useful to at least one other person :)
Make it available via the main menu by right clicking -> Create Custom Action.
If the attachment doesn't work, try: https://mega.nz/file/orx10KqY#LbQL0C3Bm1Rw1yk1Cp9KdYHt8MNoHaqC_pj4q_UjqwQ
Comments
Thank you so much for this -- it's an absolute mind and time saver!
Thank You.
cool! thank you!
j
This is awesome. No way to thank you enough!
Thanks!
Thanks a lot, very useful!
Wow. Thank you so much for this script.
Saving the above in a script and executing it results in a An error occurred while reading the file, see the log for more details message when the scene is empty or no selection was made. The log states:
2019-12-04 11:32:19.041 Loading script: D:/Library/Scripts/Utilities/Scene Object.dsa
2019-12-04 11:32:19.043 WARNING: ..\..\..\..\..\src\sdksource\general\dzscene.cpp(5693): Index out of range in DzScene::getSelectedNode()
2019-12-04 11:32:19.043 WARNING: ..\..\..\..\..\src\sdksource\general\dzscene.cpp(5693): Index out of range in DzScene::getSelectedNode()
2019-12-04 11:32:19.043 WARNING: Script Error: Line 13
2019-12-04 11:32:19.043 WARNING: TypeError: Result of expression 'fos' [null] is not an object.
2019-12-04 11:32:19.043 WARNING: Stack Trace:
<anonymous>()@D:/Library/Scripts/Utilities/Scene Object.dsa:13
2019-12-04 11:32:19.047 Error in script execution: D:/Library/Scripts/Utilities/Scene Object.dsa
Otherwise it seems to work well, thank you.
How do you get the icon in the list (Which list is the Main?) so I could click it? What do I do with the .dsa file? It shows a dialog as if I should paste it in.
Put the .dsa file in the Scripts folder found in My Library. Then from the Content Library [in D/S], browse to that folder and create the Custom Action to add it to the top bar.
Thank you, looks awesome :-)
Brilliant! Kisses for all!!!
THATS brill thank you
Just what I was looking for, thanks !
Brilliant. Thank you!
Oh my, it seems I'm not getting any comment notifications
If I take too long to respond to something, let me know in the comments of the above YouTube video.
I just got a comment that brought me back here and I think I never got notified of the comments :/
But disclaimer: I haven't fiddled with DAZ in years and I'm really not up-to-date or even able to check anything, I don't have it currently installed
Woo Hoo. You're a star Beared-P. Thanks
Thank you :)
Thank you so much. This is such a weird omission, I thought something was wrong with my DAZ installation.
Installed and still working fine on DAZ 4.22