[SOLVED] Editing Hierarchy Order Via Code - in editor (not in game)

entity.reparent(newParent) works as expected in Play Mode.

I need it to work in edit mode. It does work, but it reverts when you reload the scene.

The following steps work as expected, and the object is reparented in both the world space, and in the hierarchy. BUT, when I reload the editor, the OLD hierarchy is restored, and the object is not parented any more.

How can I implement the following code so that it DOES NOT REVERT to the old code?
Is there some kind of undo/redo stack I have to interact with, or a deeper “truth” than the visible hierarchy that the editor has saved?

Thanks in advance for any insight! @Leonidas looking at you! :-]

/* 
SIMPLE IN-EDITOR REPARENTING EXAMPLE 
you can easily reproduce this in chrome console with an 
empty project with 2 items of different names.
It's easier to see the effects if the 2 items are cubes.
*/

// Step 0 - Setup function
function FindObjectsByName(name){
        return pc.app.root.findByName("Root").find(
            function(node){ 
                return  node.name == name;
            }
        );
    }
// Step 1 - reparent object "physically" so it follows its new parent.
// Working as expected. Drag obj2 and see that obj1 follows.
    obj1 = FindObjectsByName('obj1')[0]; 
    obj2 = FindObjectsByName('obj2')[0];
    obj1.reparent(obj2);
// Step 2 - reparent object "hierarchically" so that it shows up as a 
// child of the new parent in the hierarchy
// Working as expected. See obj1 as a child of obj2 in the hierarchy.
    obj1treeItem = editor.call('entities:hierarchy').getTreeItemForEntity(obj1.getGuid()); 
    obj2treeItem = editor.call('entities:hierarchy').getTreeItemForEntity(obj2.getGuid()); 

    var oldParent = obj1treeItem.parent;    
    oldParent.remove(obj1treeItem); 
    obj2treeItem.append(obj1treeItem); 
// Step 3: Reload the scene. Observe that the OLD parent order is 
// preserved in both the world space and in the hierarchy, and 
// obj 1 no longer follows obj 2.

// What I've tried, to no effect:

var items= [{entity:obj1,parent:obj2,index:0}];
editor.call('entities:reparent',items, true);

var treeItems = [{entity:obj1treeItem,parent:obj2treeItem,index:0}];
editor.call('entities:reparent',treeItems, true);

obj1treeItem.emit('reparent',items);
editor.call('viewport:render'); 

// The above throw an error with reference to lines 1795 of app.js. I'm investigating this now, as I've only just learned of app.js (I was only working with editor.js before). I'll update this thread as I progress. 
// app.js
// starting from line 1785
const doReparent = (entity, parent, indNew, position, rotation) => {
        const history = {
          parent: parent.history.enabled,
          entity: entity.history.enabled
        };
        parent.history.enabled = false;

        if (indNew !== -1 && indNew <= parent.get('children').length) {
          parent.insert('children', entity.get('resource_id'), indNew);
        } else {
          parent.insert('children', entity.get('resource_id'));
        }

        parent.history.enabled = history.parent;

        if (!globals.entities.get(entity.get('resource_id'))) {
          console.error(`BUG TRACKING: reparenting missing child guid ${entity.get('resource_id')} to parent ${parent.get('resource_id')}`);
        }

        entity.history.enabled = false;
        entity.set('parent', parent.get('resource_id'));

        if (options.preserveTransform && position && entity.viewportEntity) {
          entity.viewportEntity.setPosition(position);
          entity.viewportEntity.setRotation(rotation);
          var localPosition = entity.viewportEntity.getLocalPosition();
          var localRotation = entity.viewportEntity.getLocalEulerAngles();
          entity.set('position', [localPosition.x, localPosition.y, localPosition.z]);
          entity.set('rotation', [localRotation.x, localRotation.y, localRotation.z]);
        }

        entity.history.enabled = history.entity;
      };

      const redo = () => {
        sortRecords('indNew');

        const latest = record => {
          const entity = record.entity.latest();
          if (!entity) return;
          const parent = record.parent.latest();
          const parentOld = entity.parent;
          if (!parentOld || !parent) return;
          return {
            entity,
            parent,
            parentOld
          };
        };

        const validRecords = [];
        records.forEach((record, i) => {
          const data = latest(record);
          if (!data) return;

          if (isValidRecord(data.entity, data.parentOld, data.parent)) {
            validRecords.push(record);
          }
        });
        if (!validRecords.length) return false;
        let selection;
        let selectionHistory;

        if (globals.selection) {
          selection = globals.selection.items;
          selectionHistory = globals.selection.history.enabled;
          globals.selection.history.enabled = false;
        }

        validRecords.forEach(record => {
          const parentOld = record.entity.latest().parent;
          const history = parentOld.history.enabled;
          parentOld.history.enabled = false;
          parentOld.removeValue('children', record.resourceId);
          parentOld.history.enabled = history;
        });
        validRecords.forEach(record => {
          const data = latest(record);
          doReparent(data.entity, data.parent, record.indNew, record.position, record.rotation);
        });

        if (selection) {
          globals.selection.set(selection, {
            history: false
          });
          globals.selection.history.enabled = selectionHistory;
        }

        return true;
      };

      const dirty = redo();

      if (dirty && options.history && globals.history) {
        const undo = () => {
          sortRecords('indOld');

          const latest = record => {
            const entity = record.entity.latest();
            if (!entity) return;
            const parent = entity.parent;
            if (!parent) return;
            const parentOld = record.parentOld.latest();
            if (!parentOld) return;
            return {
              entity,
              parent,
              parentOld
            };
          };

          const validRecords = [];
          records.forEach(record => {
            const data = latest(record);
            if (!data) return;

            if (isValidRecord(data.entity, data.parent, data.parentOld)) {
              validRecords.push(record);
            }
          });
          if (!validRecords.length) return;
          let selection;
          let selectionHistory;

          if (globals.selection) {
            selection = globals.selection.items;
            selectionHistory = globals.selection.history.enabled;
            globals.selection.history.enabled = false;
          }

          validRecords.forEach(record => {
            const parent = record.entity.latest().parent;
            const history = parent.history.enabled;
            parent.history.enabled = false;
            parent.removeValue('children', record.resourceId);
            parent.history.enabled = history;
          });
          validRecords.forEach(record => {
            const data = latest(record);
            doReparent(data.entity, data.parentOld, record.indOld, record.position, record.rotation);
          });

          if (selection) {
            globals.selection.set(selection, {
              history: false
            });
            globals.selection.history.enabled = selectionHistory;
          }
        };

        globals.history.add({
          name: 'reparent entities',
          undo: undo,
          redo: redo
        });
      }
    }

Hi @Charlie_Van_Norman,

I think you can easily do that using the new editor API, specifically using the editor entity class:

2 Likes

LOL. After banging my head up against this wall for the better part of the day, turns out there was a door all along. Thanks @Leonidas for flipping on the light as always. :smiley:

Working as expected, including persisting after reloading.

Thank you!!

1 Like