CRhinoUndoEventHandler troubleshooting


I need help to handle a custom CRhinoUndoEventHandler.

My plugin adds custom mesh objects into the document, each of them having an instance of custom ON_UserData attached. The commands receive some user interaction as input, update the user data and replace the object with an updated mesh.

From Dale’s SampleCustomUndo I see that my CRhinoUndoEventHandler should have as member an instance of my data (which will store its “old” version before the user does Undo), and inside the virtual Undo I should code the way of doing Undo operation. Inside my plugin, I’d use AddCustomUndoEvent with the current data before running any command.

However, I have three crucial questions:

  1. When I write data, I don’t know whether it’s my ON_UserData or the linked CRhinoMeshObject that “lives” in the document, or both.

  2. For me, my custom user data and the mesh object are the same: when an instance of user data is updated, its corresponding mesh is updated and replaces the old one. In the undo event handler, where do I perform the operation of replacing the last mesh with the old one?

In the header I read “Never change any setting in the Rhino document or application”, which leads me to confusion (whether the operation of replacing a mesh is a change of settings, etc).

  1. In Dale’s sample I see that the operations coded in Undo modify an instance of data (a double value) that is a private member of CRhinoUtilityPlugIn. Why is this necessary? I assume that it is for easy tracking of the data to be “undone”, but what’s the way to do it with multiple objects that are in the document?

For example, I have a command that takes as input 2 custom meshes A and B and as output gives two new meshes C and D, being C the updated version of A and D the updated version of B. If the user does Undo and I want to recover A/B from C/D, I don’t know how to track them properly from the members in the undo handler and the document.

Any idea or hint will be very appreciated!

I don’t think you need to do anything with undo, because each object has its own copy of user data. The default handling of undo will re-instate the previous object with its user data attached.
Let’s give objects letters and user data numbers:

A+1 and B+2 go into the command, and out come C+3 and D+4. Because the replacement is done inside the command the Rhino runtime will track any added and removed objects in the document and put them on the undo stack.

So, before command the undo stack is empty and after the command A+1 and B+2 are on the undo stack. Then when you undo, A+1 and B+2 are put back into the document and C+3 and D+4 are put on the redo stack. Then when you redo, C+3 and D+4 are put back into the document and A+1 and B+2 are put back onto the undo stack.

So, as long as you do not share the same user data object between objects (that I would call bad practice) I don’t think you need any custom undo handling.

One other thing you need to think about (and test): you can attach user data to the Rhino object, or to the geometry that is wrapped by the object. I’m not sure what is the right approach for your case.

Hi Menno, thanks for your reply!

Indeed, the default handling of undo correctly re-instates the mesh, but not the user data. Here you see an example: I have my mesh object A with user data 1 (which are the coordinates of the 4 corner grips). I make an operation of dragging and end up with a different object B with user data 2 having the 4 new grips (in this case the in-command replacement is handled by CRhinoObjectGrips::NewObject()). Then, I undo and the mesh seems to go back to A, but the user data does not, as the grips remain like 2, with those two selected. Now if I move them, the mesh will update and be like B.

It’s strange though, cause you see those black parameter lines that remain like B, which means that the mesh object keeps being B, but the display mesh goes back to A.

I thought that maybe this was due to some mistake when coding my custom grips, but this behavior also happens with commands that don’t involve grips but just object replacement, like this:

I always attach my user data to an ON_Mesh object which is the geometry of a CRhinoMeshObject (this is attaching to geometry, isn’t it?). For example, in a typical replacement command transforming A+1 into B+2, I code:

const ON_Geometry* geometryA = objectA->Geometry();
CCustomUserData* userData1 = CCustomUserData::Cast( geometryA->GetUserData(CCustomUserData::Id()) );
// in between there's some code updating userData1 (which then becomes userData2)
ON_Mesh* meshB = new ON_Mesh();
// update meshB as a function of updated userData1
CCustomMeshObject* meshObjectB = new CCustomMeshObject();
meshB ->CopyUserData( geometryA  );
meshObjectB ->SetMesh( meshB );
doc.ReplaceObject( objectA, meshObjectB );

I just realise when writing this that maybe I should create a copy of userData1, modify it and attach it to B, instead of modifying userData1 and making B copy the modified user data in A?


This was definitely the issue! Working perfectly now :grinning:

Many thanks Menno!


Ok cool