Undo redo on RhinoCommon

Hi,

I am developing a Rhinocommon.dll 5.14 and Rhinocommon.dll 7.5 .Net 4.8 plugin in Windows 10 using the geometries contained in the RhinoDoc.ActiveDoc.

I reproduced the issues reported as 1’ and 2’ on Rhino 5.14. On Rhino 7.5, 2’ seems to work correctly.
3’ is only a feature explanation.

1’

With the sequence

  • a1 start an undo block from an UI event outside a command
  • b1 call RhinoObject.GeometryBase.SetUserString(“test1Key”, “test1Value”) on an existing RhinoObject inside the document retrieving it with the RhinoDoc.Objects.FindById (obtaining a Rhino.DocObjects.PolyCurve from a Guid)
  • c1 change the color of the object blocking the display using the colorsource.ColorSourceFromObject
  • d1 close the undo block keeping a1/b1/c1/d1 executed synchronously in the same stack started by a1
  • a2 start an undo block from a UI event outside a command
  • b2 call RhinoObject.GeometryBase.SetUSerString(“test1Key”, “”) on an existing RhinoObject inside the document (a polycurve)
  • c2 change the color of the object blocking the display using the colorsource.ColorSourceFromObject
  • d2 close the undo block

If I try to redo running the RhinoScript “Undo”, the color changes with the setting associated to the undo block, but the KeyValuePair string does not reappear as setted in b1

If I enclose a/b/c/d in a command and access the RhinoDoc from the command parameter instead of the static pointer Rhino.RhinoDoc.ActiveDoc, I reproduce the same problem

If I traslate the geometry for example (100,0,0) and then (-100,0,0) before setting the user string, the userstring is handled correctly by the Rhinoceros UndoRedo manager, as in
id1 = BeginUndoRecord()
rhinoObject.Geometry.Transform(Rhino.Geometry.Transform.Translation(new Vector3d(100, 0, 0))); *
rhinoObject.Geometry.Transform(Rhino.Geometry.Transform.Translation(new Vector3d(-100, 0, 0))); *
rhinoObject.Geometry.SetUserString(key, value);
rhinoObject.Attributes.ObjectColor = oColor;
rhinoObject.Attributes.ColorSource = objectColorSource;
rhinoObject.Attributes.PlotColor = oColor;
rhinoObject.Attributes.PlotColorSource = objectPlotColorSource;
rhinoObject.CommitChanges();
EndUndoRecord(id1)

If I
a’ move the object from Rhinoceros Viewport UI
b’ set the string without applying a tranform as workaround (creating an undo block as reported above)
c’ move the object from Rhinoceros Viewport UI
d’ try to redo all the steps
the string sent in b’ is committed in the undo redo queue by c’: when I undo to b’ I find the string, but also when I undo to the first move a’ before setting the string.

Is there a way to avoid the dummy geometry change * in rhino5.14 and rhino7.5 to allow the undo redo on ‘rhinoObject.Geometry.SetUserString(key, value)’ ?

2’

If I import a 3dm with RhinoCommon.dll as in
var fro = new Rhino.FileIO.FileReadOptions();
fro.ScaleGeometry = false;
fro.BatchMode = true;
fro.NewMode = true;
Rhino.RhinoDoc.ReadFile(fullpath, fro);
and then try to use the undo redo feature, the undo from the script “_-Undo” does not happen.

If I import the 3dm as in
var script1 = “_-New None";
var script2 = "
-Import " + fullpath + " _Enter”;
rc = true;
rhinoDocIn.Modified = false;
if (rc)
rc = Rhino.RhinoApp.RunScript(script1, true);
if (rc)
rc = Rhino.RhinoApp.RunScript(script2, true);
rhinoDocIn.Modified = false;
the undo redo feature works correctly.

On rhino7.5, also Rhino.RhinoDoc.ReadFile(fullpath, fro) works correctly
On rhino5.14 am I missing something?

3’

With the sequence

  • a add an event handler to Rhino.Command.UndoRedo
  • b create 5 undoredo blocks, receiving correctly 5 increments of UndoRedoEventArgs.UndoSerialNumber declaring the undoredo blocks just created.
  • c call “Undo” script, receiving from Rhino.Command.UndoRedo a new UndoRedoEventArgs.UndoSerialNumber for the undo script treated as a new command and inside the events of the undo block recovered with UndoRedoEventArgs.UndoSerialNumber corresponding to the fourth block recovered

If I embed in the user RhinoDoc a RhinoObject with a userstring that holds the id given by RhinoApp.BeginUndoRecord, on the undo and redo operations I retrieve the original id that was declared from Rhinocommon on the creation of the undoredo block.

Is it possible to disable the creation of a new UndoRedoEventArgs.UndoSerialNumber when I recall a previous undoredo block with the scripts “Undo” and “Redo”?
Or being able to recover the original UndoSerialNumber after executing multiple undo and redo?
If not, I have to measure the sequence of the 4 callbacks declaring a recalled undoredo block and recall the dataentry history in the plugin I am developing.

Hi @cavalli.stefano,

It’s probably best that you post some source code, what we can run here, that demonstrates what is working or not working.

– Dale

  1. For the first problem, I have prepared a R7 W10 .Net plugin with a command that creates a line, and on multiple calls it sets a different color and user string to allow a check on the RhinoDoc recovered from the undo calls.

R7TestPlugin002UndoRedoOnGeometryBaseUserStringDictionary.7z (41.8 KB)

After loading it and calling

R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand

with additional calls to Rhinoceros native Undo Command I retrieve the following log:

Command: '_Paste
Command: R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:51 AM ClearGeometry
Command: R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:52 AM CreateGeometryWithoutUserStrings
Command: R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:52 AM SetColor: objectColor=‘Color [Blue]’,userStringValue=‘Blue’
Command: R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:52 AM SetColor: objectColor=‘Color [Cyan]’,userStringValue=‘Cyan’
Command: R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:52 AM SetColor: objectColor=‘Color [Magenta]’,userStringValue=‘Magenta’
Command: R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:52 AM SetColor: objectColor=‘Color [Yellow]’,userStringValue=‘Yellow’
Command: Undo
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:18:59 AM undoredo recover: objectColor=‘Color [Fuchsia]’,userStringValue=‘Yellow’
Undoing R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
Command: Undo
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:19:13 AM undoredo recover: objectColor=‘Color [Aqua]’,userStringValue=‘Yellow’
Undoing R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
Command: Undo
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:19:37 AM undoredo recover: objectColor=‘Color [Blue]’,userStringValue=‘Cyan’
Undoing R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
Command: Undo
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:19:40 AM undoredo recover: objectColor=‘Color [Black]’,userStringValue=‘Cyan’
Undoing R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand
Command: Undo
R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand 11:19:44 AM no object in document
Undoing R7TestPlugin002UndoREdoOnGeometryBaseUserStringDictionaryCommand

In the code I set the translation workaround only in the cyan step, and the last step is yellow. The user string on undo operations displays only these values.

  1. The second problem is detected only in rhino5, if you have an hint to because it is happening let me know. In rhino7 the same VS2019 solution works correctly (redirecting to the corresponding rhinocommon.dll). If there is a known issue on rhino5 for me is enough to know how to work around it, in other case I will consider it obsolete from adopting Rhino7

version1 that precludes native undo script

var fro = new Rhino.FileIO.FileReadOptions();
fro.ScaleGeometry = false;
fro.BatchMode = true;
fro.NewMode = true;
Rhino.RhinoDoc.ReadFile(fullpath, fro);

version2 that allows native undo script

var script1 = “_-New None";
var script2 = "-Import " + fullpath + " _Enter”;
rc = true;
rhinoDocIn.Modified = false;
if (rc)
rc = Rhino.RhinoApp.RunScript(script1, true);
if (rc)
rc = Rhino.RhinoApp.RunScript(script2, true);
rhinoDocIn.Modified = false;

  1. The third problem is only a feature explanation request, it could be rephrased as “is there a way to know the original undo block when the user recalls multiple undo and redo without having to manage an event queue”?

Hi @cavalli.stefano,

If you are modifying document objects then you shouldn’t be implementing custom undo handling. Let Rhino handle this instead.

TestCavalli.cs (6.6 KB)

– Dale

Ok, summarizing

  • I was changing a RhinoObject per property, hoping that a RhinoObject.CommitChanges() at the end of my settings to the RhinoDoc.ActiveDoc passed by Command.RunCommand could write to actual Rhinoceros memory allocations including the undoredo feature.
  • Editing per GeometryBase or per ObjectAttribute as you suggested resolves my problem without having to apply dummy translations to RhinoObjects.

Maybe in Rhino8 you could add also the edit mode I was trying at the beginning of this post. I don’t know if this goes against the setting of the entire geometry or objectattributes, but it could be more standard if every possible set to a given data structure tree gives the same result (with the CommitChanges call at the end).

.

Also, recalling the third point of my starting post

If I embed in the user RhinoDoc a RhinoObject with a userstring that holds the id given by RhinoApp.BeginUndoRecord, on the undo and redo operations I retrieve the original id that was declared from Rhinocommon on the creation of the undoredo block.

If I execute the sample you sended back with additional logs and then try to do some undo redo recalls,
with repeated undo redo I lose the id of a recovered block as reported by the following sequence where
a created 004 becomes a recovered 009 on the first undo recall, and then with an immediate redo and undo becomes a recovered 011:

Command: R7TestPlugin003From002Command
______________________________________BeginRecording 001
R7TestPlugin003From002Command.ClearGeometry: 10:16:06 PM
______________________________________EndRecording 001
Command: R7TestPlugin003From002Command
______________________________________BeginRecording 002
R7TestPlugin003From002Command.ActionCreateGeometry: 10:16:08 PM
______________________________________EndRecording 002
Command: R7TestPlugin003From002Command
______________________________________BeginRecording 003
R7TestPlugin003From002Command.ActionSetColor: color = Color [Blue], value = “Color [Blue]”
______________________________________EndRecording 003
Command: R7TestPlugin003From002Command
______________________________________BeginRecording 004
R7TestPlugin003From002Command.ActionSetColor: color = Color [Cyan], value = “Color [Cyan]”
______________________________________EndRecording 004
Command: R7TestPlugin003From002Command
______________________________________BeginRecording 005
R7TestPlugin003From002Command.ActionSetColor: color = Color [Magenta], value = “Color [Magenta]”
______________________________________EndRecording 005
Command: R7TestPlugin003From002Command
______________________________________BeginRecording 006
R7TestPlugin003From002Command.ActionSetColor: color = Color [Yellow], value = “Color [Yellow]”
______________________________________EndRecording 006
Command: _Undo
______________________________________BeginRecording 007
______________________________________BeginUndo ‘006’ tagFromTable ‘007’
______________________________________EndUndo ‘006’ tagFromTable ‘007’
Undoing R7TestPlugin003From002Command
______________________________________EndRecording 007
Command: _Undo
______________________________________BeginRecording 008
______________________________________BeginUndo ‘005’ tagFromTable ‘008’
______________________________________EndUndo ‘005’ tagFromTable ‘008’
Undoing R7TestPlugin003From002Command
______________________________________EndRecording 008
Command: _Undo
______________________________________BeginRecording 009
______________________________________BeginUndo ‘004’ tagFromTable ‘009’
______________________________________EndUndo ‘004’ tagFromTable ‘009’
Undoing R7TestPlugin003From002Command
______________________________________EndRecording 009
Command: _Redo
______________________________________BeginRecording 010
______________________________________BeginRedo ‘009’ tagFromTable ‘010’
______________________________________EndRedo ‘009’ tagFromTable ‘010’
Redoing R7TestPlugin003From002Command
______________________________________EndRecording 010
Command: _Undo
______________________________________BeginRecording 011
______________________________________BeginUndo ‘010’ tagFromTable ‘011’
______________________________________EndUndo ‘010’ tagFromTable ‘011’
Undoing R7TestPlugin003From002Command
______________________________________EndRecording 011

The id ‘004’ of the state of memory described above on the recover 009 and 011 (that should give the same viewport of 004) could be passed by the undoredo event,

  • because it could be useful to synchronize memory states of a client plugin
  • given that, if the client plugin changes its internal memory state just after a recover, then it has to declare another undoredo block.

This could be useful if the client plugin has a local layer handled only by himself in a dedicated dialog sequence, considering the link to the undoredo feature.

question 1:
The RhinoDoc.StringTable could handle this feature, writing only on undo blocks created outside the calls from the undo redo new blocks creations?.
question 2:
Without using the RhinoDoc.StringTable, if I want to relink to the memory state 4, I have to create a table of similar undoRedoBlock ids?

R7TestPlugin003From002Command.cs (8.9 KB)