On 08/05/2014 at 14:23, xxxxxxxx wrote:
@Niklas: Thanks for the SceneHook suggestion and code example - that definitely points me in the right direction.
@Scott: I think the reason your undo isn't working is because you're saving it to the document's container directly. The doc object doesn't support undo. Try saving it to the hook's container.
I've created a slightly more complex example based on yours which seems to be working:
"""This is a simple example of how to add undo support for changes saved into a document's container.
This is best used when you have a dialog plugin that needs to save it's state/data with a scene,
and not individual objects/tags within that scene.
TO DO:
------------
[] Simple test with preset text
"""
#IMPORTS
import c4d
from c4d import gui, plugins
#LOGGING
import logging
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.INFO) #Logging levels are: DEBUG, INFO, WARNING, ERROR, CRITICAL
#GLOBALS
PLUGIN_ID = 1032093
#DIALOG IDS
DLG_EDIT_TEXT = 10001
DLG_SAVE = 10002
DLG_LOAD = 10003
#CONTAINER IDs
BC_ID_NAME = 0
#CLASS DEFINITIONS
class DocDataDialog(gui.GeDialog) :
"""A two-button dialog that stores data in the document's settings _hook."""
def __init__(self) :
"""Initializes variables needed by funtions in this class."""
logger.debug("DocDataDialog()")
self._last_doc = None #Last document seen by the dialog, can be used to detect new documents
self._hook = None #The most-recent document settings object, so that we have easy access to it
self._update_dialog = True #A flag that can be set which dictates whether the dialog should update on refresh
self.Refresh()
def CreateLayout(self) :
"""Adds buttons to your layout."""
logger.debug("DocDataDialog.CreateLayout()")
self.AddStaticText(DLG_EDIT_TEXT, c4d.BFH_SCALEFIT)
self.AddButton(DLG_SAVE, c4d.BFH_CENTER, 0, 10, name="Save Text to Container")
self.AddButton(DLG_LOAD, c4d.BFH_CENTER, 0, 10, name="Load Text from Container")
#Fill the dialog with data
self.Refresh()
return True
def InitValues(self) :
"""No data needs to be initialized."""
logger.debug("DocDataDialog.InitValues()")
return True
def Command(self, id, msg) :
"""Respond to user button presses."""
logger.debug("DocDataDialog.Command(id=%s, msg=%s)" % (id, msg))
#Store value in a container when this button is pressed
if id == DLG_SAVE:
logger.debug("DLG_SAVE")
self._last_doc.StartUndo()
#Store the data from the dialog
self._last_doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, self._hook)
plugin_bc = c4d.BaseContainer()
plugin_bc[BC_ID_NAME] = "Saved Data"
self._hook.GetDataInstance().SetContainer(PLUGIN_ID, plugin_bc)
self._last_doc.EndUndo()
self._update_dialog = False #Prevent loading data just in case the user presses undo
c4d.EventAdd()
#Read the container when this button is pressed
if id == DLG_LOAD:
logger.debug("DLG_LOAD")
self._update_dialog = True
self.Refresh()
return True
def Refresh(self) :
"""Reload the data from the BaseSettings _hook. Call when there's a new scene or a significant change."""
logger.debug("DocDataDialog.Refresh()")
self._last_doc = c4d.documents.GetActiveDocument()
self._hook = self._last_doc.FindSceneHook(c4d.ID_BS_HOOK)
hook_bc = self._hook.GetDataInstance()
plugin_bc = hook_bc.GetContainer(PLUGIN_ID)
if self._update_dialog:
saved_string = plugin_bc[BC_ID_NAME]
if saved_string is None:
saved_string = ""
#Take the stored data and show it in the dialog
self.SetString(DLG_EDIT_TEXT, saved_string)
def CoreMessage(self, kind, bc) :
"""Respond to messages from Cinema 4D"""
logger.debug("DocDataDialog.CoreMessage(kind=%s, bc=%s)" % (kind, bc))
#Something has changed in the document
if kind == c4d.EVMSG_DOCUMENTRECALCULATED:
logger.debug("c4d.EVMSG_DOCUMENTRECALCULATED")
#Refresh the dialog to account for whatever these changes may be.
self.Refresh()
return True
#REGISTRATION
class DocDataCommand(plugins.CommandData) :
"""A command plugin which calls up our dialog."""
dialog = None
def Execute(self, doc) :
"""When the user calls this command, open an instance of our dialog."""
logger.debug("DocDataCommand.Execute(_last_doc=%s)" % (doc,))
#Create the dialog
if self.dialog is None:
self.dialog = DocDataDialog()
return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID,
defaultw=200, defaulth=150, xpos=-1, ypos=-1)
#Register our plugin
if __name__ == "__main__":
plugins.RegisterCommandPlugin(PLUGIN_ID, "UndoTest", 0, None, "", DocDataCommand())