[PythonCAD] more undo

Eric Wilhelm ewilhelm at sbcglobal.net
Sat Sep 4 15:57:36 CEST 2004

I'm poking at your undo system to see how it works.  If I draw a line, 
change the color, and then try ctrl+Z, I get this:


saveUndoData: ('add', ('point', (4, 1), 159.0, 516.0))
saveUndoData: ('add', ('point', (5, 1), 408.0, 326.0))
saveUndoData: ('add', ('segment', (6, 1), (None, None, None, None), 4, 
saveUndoData: ('attr_changed', 'color', (255, 255, 255))
saveUndoData: ('mod', 6)
Traceback (most recent call last):
  File "./trunk/Interface/Gtk/gtkmenus.py", line 313, in edit_undo_cb
  File "./trunk/Generic/image.py", line 916, in undo
  File "./trunk/Generic/entity.py", line 256, in undo
  File "./trunk/Generic/logger.py", line 67, in undo
    self.execute(True, *_data)
  File "./trunk/Generic/layer.py", line 2388, in execute
  File "./trunk/Generic/entity.py", line 256, in undo
  File "./trunk/Generic/logger.py", line 67, in undo
    self.execute(True, *_data)
  File "./trunk/Generic/segment.py", line 991, in execute
    raise ValueError, "Unexpected operation: %s" % _op
ValueError: Unexpected operation: attr_changed


I've turned on some of your debugging statements in Generic/logger.py.  
Digging around a little further, it seems that each class has an 
execute() function, but this is only involved in undo/redo (and then 
only with attributes?)

I've looked back through the archives, but can't seem to find an 
"executive summary" of how undo/redo works.  Is there some 
documentation that I'm missing?

From my poking around, it looks like the undo/redo is managed on a 
fairly low level (where each change is logged and there must be a 
specific action defined for undo/redo of that kind of change.)  Is 
this correct?  If so, how well does this scale as new types of 
entities and actions are added?  Is there a more generic system that 
would work?

Consider this.  What if you wanted to create an animation of a 
session's editing changes?  What if you wanted to create it 
backwards?  Ignoring the animation issues, how would the code be able 
to create the data at each change point?  What if you want to undo 
the last thing done before the file was last saved?

Global snapshots at each change would be the most generic way, but 
also the most expensive.  Differences of these snapshots might be 
less expensive, but that could be tricky to do efficiently (unless 
you work with the persistent directory idea.)

With the logging system, you have to have a record of each change and 
a function that is able to undo each particular change.  This gives a 
foo() and unfoo() pair of functions for each particular action.  
Efficient, but hard to code and maintain.

So, global the snapshot/rollback undo is really simple and hard to get 
wrong.  The local change-specific undo is really CPU efficient and 
hard to get right.

Assuming that you don't want to explore a global diff/patch undo 
system, what would make the local scheme more robust?

Maybe a scripting/unscripting system?  This gives you the added 
benefit of a scripting system while simultaneously abstracting the 
undo mechanism into a more robust scheme and allowing the undo 
history to be persistent across a reboot.

If you build this into the existing logger, a syntax like the 
following might work.

  entity CHANGE property TO value

And to make it easily unscriptable, you could pass something like this 
to the logger.

  entity CHANGE property FROM value1 TO value2

So, undo becomes a matter of munging the TO and FROM around.

What about create/delete?

  entity ADD type WITH property=value AND property2=value2
  entity REMOVE type LOG property=value AND property2=value2

Of course, I'm just messing around with this SQL-like syntax, but you 
get the idea.  It's verbose and very structured.  Designed with 
inversion in mind.  The point is, if every action is scriptable and 
unscriptable, the undo/redo becomes a matter of parsing and running a 

Other than the undo system uses, I'm not sure that this sort of macro 
scripting is a good idea.  After all, you could simply evaluate some 
Python, or even have an embedded Perl interpreter.  The problem with 
these languages is that parsing for the sake of inversion is HARD.

"But as to modern architecture, let us drop it and let us take 
modernistic out and shoot it at sunrise."
                                        --F.L. Wright

More information about the PythonCAD mailing list