Hello @lingza,
Thank you for reaching out to us. The likely reason why your script is not working is the position of your AddUndo
call. As the Python documentation states,
always has to be called before a change is made. In the case of the creation of a new object the call is done afterwards,
There is also a slight ambiguity with how to treat hierarchy modifications: as a UNDOTYPE_CHANGE
event or as an UNDOTYPE_DELETEOBJ
and UNDOTYPE_NEWOBJ
event. You can do both, but as hinted at in the UNDOTYPE_CHANGE
description, hierarchy modifications also count as changes. Find a script example below which works fine for me, i.e., undo does revert to the state before running the script.
Cheers,
Ferdinand
Scene state before the script ran (and also the state after an undo):

Scene state after the script ran:

The script:
"""Provides an example for creating undo items for hierarchy modifications.
"""
import c4d
doc: c4d.documents.BaseDocument # The active document
def main() -> None:
"""
"""
# Get the active selection as you did.
selection: list[c4d.BaseObject] = doc.GetActiveObjects(
c4d.GETACTIVEOBJECTFLAGS_CHILDREN | c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
if len(selection) < 2:
return
# We declare the first item in the list our root to which we are going to parent things.
root: c4d.BaseObject = selection.pop(0)
# Start an undo stack.
doc.StartUndo()
for item in selection:
# Here you got things mixed up a bit. An undo item must be added before the action is
# carried out, e.g., UNDO_CHANGE, and only after when a new item is being added, i.e.,
# UNDOTYPE_NEWOBJ. While the situation might be ambiguous here - is moving an item adding
# an item or not, your symbol UNDO_CHANGE did not line up with place where you called
# AddUndo.
# I decided to go the easy route and treat this just as a change event and not as a removal
# and new object event.
doc.AddUndo(c4d.UNDO_CHANGE, item)
# A node can only be attached to one thing. In the Python layer, GeListNode.Remove() is
# called automatically when one calls one of the insertion methods, in C++ not. So, this line
# is only for verbosity.
item.Remove()
# Insert the item under the root.
item.InsertUnderLast(root)
# Close the undo stack and push an update event to Cinema 4D.
doc.EndUndo()
c4d.EventAdd()
if __name__ == '__main__':
main()