How can I write a Undo/Redo using C#


#1

I want to write a 3dsmax DLL entirely in c#, but I wonder if there is an example to guide me if I write undo/redo.

 public class SRYRest : IRestoreObj
        {
            public IINode curNode;
            public IBaseInterface curid;
            public IntPtr pt;
            private SRYRest() { curNode = null; }
            public SRYRest(IINode node)
            {
                curNode = node;
            }
            int IRestoreObj.Size => Marshal.SizeOf<IINode>(curNode);

            public string Description => "SRY_Undo";
            public IntPtr NativePointer => throw new NotImplementedException();
            public void Redo()
            {
                //throw new NotImplementedException();
            }
            public void Restore(bool isUndo)
            {
                //throw new NotImplementedException();
            }
            public void EndHold()
            {
                curNode.ClearAFlag(AnimatableFlags.Held);
            }
            public IntPtr Execute(int cmd, UIntPtr arg1, UIntPtr arg2, UIntPtr arg3)
            {
                throw new NotImplementedException();
            }

            public IBaseInterface GetInterface(IInterface_ID id)
            {
                //throw new NotImplementedException();
                curid = id as IBaseInterface;
                return curid;
            }
            public bool Equals(IInterfaceServer other)
            {
                return false;
            }
            public void Dispose()
            {
                //throw new NotImplementedException();
            }
        }

#2

see “\3ds Max … SDK\maxsdk\samples\utilities\rescale.cpp”


    public class Tool_RestoreObj : Autodesk.Max.Plugins.RestoreObj {
        
        uint nHandle;

        public Tool_RestoreObj(uint hnd) { nHandle = hnd; }


        public override void Redo() {
            // use nHandle here

        }


        public override void Restore(bool isUndo) {
            // use nHandle here

        }



        public override string Description {
            get { return "Tool_undoName"; }
        }

    }



//use like this


    IHold theHold = Autodesk.Max.GlobalInterface.Instance.TheHold;
    theHold.Resume();
    theHold.Begin();

    // undo action



    theHold.Put(new Tool_RestoreObj(handle));
    theHold.Accept("Tool_undoName");
    theHold.Suspend();
    theHold.Resume();
    theHold.Begin();


    theHold.Accept("Tool_undoName");
    


#3

public class SRYRest : RestoreObj
{
uint nHandle;
IINode oldNode;
IGlobal g;
public SRYRest(uint hnd, IINode node,IGlobal gl)
{
nHandle = hnd;
oldNode = node;
g = gl;
}
public override void Redo()
{

    }

    public override void Restore(bool isUndo)
    {
        IInterface14 core = g.COREInterface14;
        if (isUndo)
        {
            IINode s = core.GetINodeByHandle(nHandle);
            s = oldNode;
        }
    }
    public override string Description
    {
        get { return "SRYundo"; }
    }
}

public void SRYTestShape()
{

        IGlobal g = Autodesk.Max.GlobalInterface.Instance;
        IInterface14 core = g.COREInterface14;
        var t = core.Time;
        int num = core.SelNodeCount;
        List<IINode> spls = new List<IINode>();
        if (num != 0)
        {
            for (int i = 0; i < num; i++) 
            {
                IINode nod = core.GetSelNode(i);
                if (nod.ObjectRef.GetType().Name == "SplineShape") 
                {
                    spls.Add(nod);
                }
            }

            foreach (IINode s in spls)
            {
                ISplineKnot k = g.SplineKnot.Create();
                IPoint3 p = g.Point3.Create(0.0f, 0.0f, 0.0f);
                IPoint3 iv = g.Point3.Create(1.0f, 0.0f, 0.0f);
                IPoint3 ov = g.Point3.Create(0.0f, 1.0f, 0.0f);
                k.Ktype = 2;
                k.Ltype = 0;
                k.Knot = p;
                k.InVec = iv;
                k.OutVec = ov;
                var os = s.EvalWorldState(t, true);
                var obj = os.Obj.ConvertToType(t, g.SplineShapeClassID);
                s.ObjectRef = obj;
                ISplineShape mySpline = obj as ISplineShape;
                //----------------Restore----------------
                uint handle = s.Handle;
                IHold theHold = g.TheHold;
                theHold.Resume();
                theHold.Begin(); 
                theHold.Put(new SRYShape.SRYRest(handle,s,g));
                theHold.Accept("SRYundo");
                //-----------------
                mySpline.AddKnot(0, k, 1);
                ISpline3D sp = mySpline.Shape.GetSpline(0);
                sp.ComputeBezPoints();
                mySpline.Shape.UpdateSels(true);
                mySpline.Shape.InvalidateGeomCache();
                mySpline.UpdateSelectDisplay();
                
            }
            
        }
    }

It doesn’t work!


#4

I doubt max can magically restore the previous state of a node given only the handle to that node. It would require it to store a full copy of the node and all of its dependents.
Check sdk sources, for example class XFormVertsRestore : public RestoreObj in editspl.cpp


#5

Yes, I just wanted the node to return to its original state, but I failed. The example you gave is about Bezier Shape . Actually, I was wondering if I could do nodes a little easier


#6

Perhaps you should store the original Obj (IObject) pre converted to SplineShape in order to later restore it from inside RestoreObj::Restore method. Another good example of implementation is class SSRestore : public RestoreObj in avg_dlx.cpp
Never happened to use these restoreobj myself niether in c# nor c++ so it just a guess


#7

@Serejah , I haven’t succeeded yet, but thank you! I’ll keep trying. Another problem is that I can’t refresh the line, In the Max script, I can use updatedshape.


#8

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Max;
using UiViewModels.Actions;

namespace Test.test.UndoRedo {


    public class SRYRest_CUIActionAdapter : CuiActionCommandAdapter {
        public override string ActionText => "SRYTestShape_command";

        public override string Category => "SRYTestShape_command";

        public override string InternalActionText => "SRYTestShape_command";

        public override string InternalCategory => "SRYTestShape_command";

        public override void Execute(object parameter) {
            Test.SRYTestShape();
        }
    }

    public static class Test {
        public static void SRYTestShape() {
            IGlobal g = Autodesk.Max.GlobalInterface.Instance;
            IInterface14 core = g.COREInterface14;
            var t = core.Time;
            int num = core.SelNodeCount;
            List<IINode> spls = new List<IINode>();


            IHold theHo = g.TheHold;
            theHo.SuperBegin();           
            theHo.Suspend();

            if(num != 0) {
                for(int i = 0; i < num; i++) {
                    IINode nod = core.GetSelNode(i);

                    if(nod.ObjectRef.IsShapeObject) {
                        if((nod.ObjectRef as ISplineShape) == null) {
                            IModifier edspl_mod = (IModifier)g.COREInterface.CreateInstance(SClass_ID.Osm, g.Class_ID.Create(96, 0));
                            var dObj = g.CreateDerivedObject(nod.ObjectRef);
                            dObj.AddModifier(edspl_mod, null, 0);
                            nod.ObjectRef = dObj;
                            g.COREInterface.CollapseNode(nod, true);
                        }
                        spls.Add(nod);
                    }
                }                

                foreach(IINode s in spls) {
                    //saving original
                    List<ISpline3D> all3dLines = new List<ISpline3D>();
                    var origSs = (s.ObjectRef as ISplineShape);
                    var shp = origSs.Shape;
                    for(int spl = 0; spl < shp.SplineCount; spl++) {
                        ISpline3D spn = shp.GetSpline(spl);
                        int clo = spn.Closed();
                        int endVert = spn.Verts / 3;
                        ISpline3D pSpline = g.Spline3D.Create(1, 2, 0);
                        for(int i = 0; i < endVert; i++) {
                            pSpline.AddKnot(spn.GetKnot(i), -1);
                        }
                        pSpline.SetClosed(clo);
                        pSpline.ComputeBezPoints();
                        all3dLines.Add(pSpline);
                    }
                    //saving original

                    uint handle = s.Handle;
                    IPoint3 p = g.Point3.Create(0.0f, 0.0f, 0.0f);
                    IPoint3 iv = g.Point3.Create(1.0f, 0.0f, 0.0f);
                    IPoint3 ov = g.Point3.Create(0.0f, 1.0f, 0.0f);
                    ISplineKnot k = g.SplineKnot.Create();
                    k.Ktype = 2;
                    k.Ltype = 0;
                    k.Knot = p;
                    k.InVec = iv;
                    k.OutVec = ov;
                    var os = s.EvalWorldState(t, true);
                    var obj = os.Obj.ConvertToType(t, g.SplineShapeClassID);
                    s.ObjectRef = obj;
                    ISplineShape mySpline = obj as ISplineShape;
                    mySpline.AddKnot(0, k, 1);
                    ISpline3D sp = mySpline.Shape.GetSpline(0);
                    sp.ComputeBezPoints();
                    mySpline.Shape.UpdateSels(true);
                    mySpline.Shape.InvalidateGeomCache();
                    mySpline.UpdateSelectDisplay();
                    ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand($"Lin=maxOps.getNodeByHandle {handle}; updateshape Lin;");

                    theHo.Resume();
                    theHo.Begin();
                    theHo.Put(new SRYRest(handle, all3dLines));
                    theHo.Accept("SRYundo");
                    theHo.Suspend();
                }
            }

            theHo.Resume();
            theHo.SuperAccept("SRYundo");



        }
    }

    public class SRYRest : Autodesk.Max.Plugins.RestoreObj {
        uint nHandle;
        List<ISpline3D> lineData;

        public SRYRest(uint hnd, List<ISpline3D> lines) {
            nHandle = hnd;
            lineData = lines;
        }
        public override void Redo() {
            throw new NotImplementedException();
        }

        public override void Restore(bool isUndo) {
            

            IINode activeLine = GlobalInterface.Instance.COREInterface.GetINodeByHandle(nHandle);

            IObject ob = activeLine.ObjectRef;
            ISplineShape origSS = (ob as ISplineShape);
            IBezierShape origBS = origSS.Shape;
            origBS.NewShape();
            for(int s = 0; s < lineData.Count; s++) {
                origBS.InsertSpline(lineData[s], s);
            }
            origBS.UpdateSels(true);
            origBS.InvalidateGeomCache();
            ManagedServices.MaxscriptSDK.ExecuteMaxscriptCommand($"Lin=maxOps.getNodeByHandle {activeLine.Handle}; updateshape Lin;");

        }

        public override string Description {
            get { return "SRYundo"; }
        }

    }



}




You can similarly save updated 3dSplines for redo, I also did not find maxscript updatedshape replacement in SDK


#9

there’s UpdateShape method in SimpleSpline class


#10

Is it useful for any Shape?


#11

oh indeed, according to shape class hierarchy it is not the case :neutral_face: seems like it isn’t very useful in your case

seems like updateShape method calls some method of an IObject class and perhaps invalidates validity interval.
%D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5


#12

if((nod.ObjectRef as ISplineShape) == null) {
IModifier edspl_mod = (IModifier)g.COREInterface.CreateInstance(SClass_ID.Osm, g.Class_ID.Create(96, 0));
var dObj = g.CreateDerivedObject(nod.ObjectRef);
dObj.AddModifier(edspl_mod, null, 0);
nod.ObjectRef = dObj;
g.COREInterface.CollapseNode(nod, true);
}
This paragraph has no effect on line and cannot change line into splineshape. Is there any better way? How do you handle special shape: line. For example, add a dot, delete a segment.Does line belong to simplespline or Linearshape? I feel it is a monster. Why does Line exist.