Undo BaseDocument Container Changes?
-
On 05/05/2014 at 17:34, xxxxxxxx wrote:
Is it possible to add changes to a BaseDocument's container to that document's undo buffer? Based on some preliminary searches of the forum, it doesn't seem like there's a method.
So I guess my question is, where should my dialog plugin store its data so that the user can undo/redo changes in the dialog and have the dialog state get stored with the document?
I've tried storing data in the LayerObjectRoot but that results in a hard-crash as it seems to only support GetUp/Down/etc. and not other BaseList2D functions.
All other locations: objects, materials, render settings, layers - can be accessed and deleted by the user. Something I definitely don't want to happen.
Thanks!Donovan
-
On 06/05/2014 at 08:09, xxxxxxxx wrote:
Hi Donovan,
Waiting for a developer reply (see my answer to your reply on another thread here). But I
don't think its possible.I do think however that it is absolutely acceptable to exploit something that is always available
in a document, such as the basic SceneHooks. "BaseSettings Hooks" sounds pretty good, too.
I included this technique in your task-list example in the Python SDK.# Save to the document. doc = self._last_doc hook = GetBaseSettingsHook(doc) if hook: # No matter what happens, the undo step must be # closed after it was started. doc.StartUndo() try: doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, hook) hook.GetDataInstance().SetContainer(PLUGIN_ID, bc) finally: doc.EndUndo()
-Niklas
-
On 06/05/2014 at 13:25, xxxxxxxx wrote:
Can you please post a simpler example?
You've got methods calling other methods. And it's very hard to follow what's going on with the SceneHook code.Here's a much simpler example of storing values in a container.
But the container does not undo when the undo is used:PLUGIN_ID = 1000009 # Testing id ONLY!!!!!!! MY_BTN1 = 10001 MY_BTN2 = 10002 class MyDialog(gui.GeDialog) : mybc = c4d.BaseContainer() def CreateLayout(self) : self.AddButton(MY_BTN1, c4d.BFH_CENTER, 100, 10, name="Set Container") self.AddButton(MY_BTN2, c4d.BFH_CENTER, 100, 10, name="Read Container") return True def InitValues(self) : return True def Command(self, id, msg) : doc = c4d.documents.GetActiveDocument() #Store value in a container when this button is pressed if id == MY_BTN1: hook = doc.FindSceneHook(c4d.ID_BS_HOOK) bc = hook.GetDataInstance() doc.StartUndo() doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, hook) doc[123456] = self.mybc self.mybc.SetString(1, "hello") hook.GetDataInstance().SetContainer(PLUGIN_ID, bc) doc.EndUndo() #Read the container when this button is pressed if id == MY_BTN2: print len(self.mybc) print self.mybc.GetString(1) return True class myDialog_Main(plugins.CommandData) : dialog = None def Execute(self, doc) : #Create the dialog if self.dialog is None: self.dialog = MyDialog() return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=200, defaulth=150, xpos=-1, ypos=-1) if __name__ == "__main__": plugins.RegisterCommandPlugin(PLUGIN_ID, "myPythonDialog",0,None,"", myDialog_Main())
-ScottA
-
On 08/05/2014 at 02:37, xxxxxxxx wrote:
The container you got there won't change. You have to read it back from the hook.
BaseContainer.SetContainer(sub_bc) will make a copy of sub_bc. You could use
.GetContainerInstance() to get the actually stored container afterwards but the undo-process
will create a new copy as well.import c4d from c4d import gui, plugins PLUGIN_ID = 1000009 # Testing id ONLY!!!!!!! MY_BTN1 = 10001 MY_BTN2 = 10002 class MyDialog(gui.GeDialog) : mybc = c4d.BaseContainer() def CreateLayout(self) : self.AddButton(MY_BTN1, c4d.BFH_CENTER, 100, 10, name="Set Container") self.AddButton(MY_BTN2, c4d.BFH_CENTER, 100, 10, name="Read Container") return True def InitValues(self) : return True def Command(self, id, msg) : doc = c4d.documents.GetActiveDocument() #Store value in a container when this button is pressed if id == MY_BTN1: hook = doc.FindSceneHook(c4d.ID_BS_HOOK) bc = hook.GetDataInstance() doc.StartUndo() doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, hook) doc[123456] = self.mybc self.mybc.SetString(1, "hello") hook.GetDataInstance().SetContainer(PLUGIN_ID, bc) doc.EndUndo() #Read the container when this button is pressed if id == MY_BTN2: hook = doc.FindSceneHook(c4d.ID_BS_HOOK) self.mybc = hook.GetDataInstance().GetContainer(PLUGIN_ID) print len(self.mybc) print self.mybc.GetString(1) return True class myDialog_Main(plugins.CommandData) : dialog = None def Execute(self, doc) : #Create the dialog if self.dialog is None: self.dialog = MyDialog() return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=200, defaulth=150, xpos=-1, ypos=-1) if __name__ == "__main__": plugins.RegisterCommandPlugin(PLUGIN_ID, "myPythonDialog",0,None,"", myDialog_Main())
-Niklas
-
On 08/05/2014 at 08:21, xxxxxxxx wrote:
Thanks Niklas.
But the code you posted does not undo the container when the document is undone.
However...It does undo the container when the "Read Container" button is pressed. Because you're setting the custom container mybc = the document's container like this:
self.mybc = hook.GetDataInstance().GetContainer(PLUGIN_ID)What you have created is a container undo button.
But undoing the actual document still doesn't undo the container.Is there a way to make this happen: self.mybc = hook.GetDataInstance().GetContainer(PLUGIN_ID)
When the document is undone (Ctrl+Z)?-ScottA
-
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())
-
On 08/05/2014 at 17:00, xxxxxxxx wrote:
Thanks Donovan.
It's a bit confusing. But I think I see what is happening.You're still using a button to make the undo happen. In this case the DLG_LOAD button.
You just go through an extra step and make it happen when the _update_dialog variable is set to True. But it's still only occurring when the user physically changes a gizmo.If you click the DLG_SAVE button. The dialog does not show "Saved Data" in it.
But the container "plugin_bc" was created and is there in the SceenHook.
And if you undo the document. It does not undo that container.In short.
What you're doing is undoing the dialog...but not undoing the the document (the SceenHook).
It's a bit confusing to follow. But I think I understand what's going on.-ScottA