PSA, disable undo recording when procedurally generating subobjects

Dropping this here, hoping this helps others that run into a similar problem

I’m working on code that allows me to procedurally generate other rhino objects from a source object. I was having a lot of trouble getting undo working correctly until I found the UndoRecordingEnabled flag on the document. Now it’s working great. If you don’t disable recording events, then you get into situation where undo/redo also adds and removes the dependent objects and because you can’t control the ordering of that, you occasionally get duplicate objects:

But in the meantime working dependent objects:

Some general psuedo code for how it’s built, note that it glosses over handling a changing under of dependent objects, preserving selected geometric modifications, and dissociating a dependent object from the source object in order to keep it when the source is deleted:

Event Listeners:

OnRhinoObjectAdded()
{
    GenerateObjectDependentGeometry()
}

OnRhinoObjectRemoved()
{
    if(ObjectBeingReplaced) return;
    RemoveDependentGeometry()
}

Main Generation

List<ObjRef> CurrentDependentObjects;

GenerateObjectDependentGeometry()
{
    StartDependentObjectModification();
    List<GeometryBase> newGeometry = GenerateGeometry();
    int index = 0;
    for(; index < newGeometry.Count; index++)
    {
          if(index < CurrentDependentObjects.Count)
          {
               UpdateObject(CurrentDependentObjects[index], newGeometry[index]);   
          }
          else
          {
               CreateNewDependentObject(newGeometry[index]);
          }
    }
    RemoveDependentGeometry(index);
    CompleteDependentObjectModification();
}

Helper Functions

CreateNewDependentObject(GeometryBase newGeo)
{
       var id = Doc.Objects.Add(newGeo);
       var objectRef = new ObjRef(Doc, id);
       CurrentDependentObjects.Add(objectRef);       
}
UpdateObject(ObjRef dependentRef, GeometryBase newGeo)
{
       var obj = dependentRef.Object();
       //if a user modified the subobject by deleting it, respect that
       if(obj is null || obj.IsDeleted) return;
       /* I'm using replace instead of just delete and add to preserve other data 
        * that may be attached to the dependent objects in between modifications 
        * to the source object.
       */
       Doc.Objects.Replace(obj, newGeo, true);
}
RemoveDependentGeometry(int startIndex = 0)
{
       StartDependentObjectModification();
       for(int i = startIndex; i<CurrentDependentObjects.Count; i++)
       {
              Doc.Objects.Delete(CurrentDependentObjects[i]);
       }
       CompleteDependentObjectModification();
}

And Finally Undo Recording Gaurds

int _dependentObjectGaurd = 0;
bool _cachedUndoRecordingState = false;
StartDependentObjectModification()
{
      if(_dependentObjectGaurd == 0)
     {
          _cachedUndoRecordingState = Doc.UndoRecordingEnabled;
          Doc.UndoRecordingEnabled = false;
     }
      _dependentObjectGaurd++;
}
CompleteDependentObjectModification()
{
    _dependentObjectGaurd--;
    if(dependentObjectGaurd > 0) return;
    _dependentObjectGaurd = 0;
    Doc.UndoRecordingEnabled = _cachedUndoRecordingState;
}
1 Like