[Closed] How can I write a Undo/Redo using C#
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(); } }
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");
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!
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
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
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
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
oh indeed, according to shape class hierarchy it is not the case 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.
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.