@brucek5
Well, first of all you wouldn't need Event Notification if the button is on the Python tag itself, instead of the null. For its own controls, the message function already receives all events:
import c4d
def message(msg_type, data):
if msg_type == c4d.MSG_DESCRIPTION_COMMAND:
print (data)
desc_id = data['id']
print (desc_id, " received")
def main():
pass
(to be used in a Python tag with one button)
This is still called very often but at least doesn't require the whole notification shebang which is listed as private in the documentation, and as long as you use early return methods, it's not bad.
There may be reasons why you'd want the GUI attached to the null but in this case I would readily put it on the Python tag.
As far as Python scripts go: Yes, the advantage of a Python tag is that the code is bound to the document/project/file, which is not the case with a script, which resides separately in the script folder, or a full Python plugin, which goes in the plugin folder. There are arguments for both ways to store Python code - the script or plugin is available for all projects (if properly generalized); the plugin even allows setting up a GUI that can be embedded in the layout.
In your case, I am honestly not sure whether I recommend a script or plugin; I guess I personally would realize it as such (being a script fan ) but if the code is tightly bound to your project, doing it within the tag is just as fine, as long as you execute the code as reaction to a button (in the message
function) and not as part of the scene evaluation (in the main
function).
Setting up your own dialog (or even plugin, for which you'd need a unique plugin ID to be obtained from Maxon) indeed requires a bit more work than the bare user data. Here's a sample script (not plugin) that opens a very simple dialog with two buttons:
import c4d
from c4d import gui, utils
ID_EDIT_BUTTONGROUP = 1007
ID_EDIT_OKBUTTON = 1008
ID_EDIT_CANCELBUTTON = 1009
class settingsDialog(c4d.gui.GeDialog):
def __init__(self):
c4d.gui.GeDialog.__init__(self)
self.exitok = False
def CreateLayout(self):
self.SetTitle("Several text fields")
if self.GroupBegin(ID_EDIT_BUTTONGROUP, c4d.BFH_CENTER, 2, 0):
self.AddButton(ID_EDIT_OKBUTTON, c4d.BFH_LEFT, name="OK")
self.AddButton(ID_EDIT_CANCELBUTTON, c4d.BFH_LEFT, name="Cancel")
self.GroupEnd()
return True
def Command(self, id, msg):
if id==ID_EDIT_OKBUTTON:
print ("OK clicked")
self.exitok = True
self.Close()
if id==ID_EDIT_CANCELBUTTON:
print ("Cancel clicked")
self.exitok = False
self.Close()
return True
def main():
sDialog = settingsDialog()
if sDialog.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE):
print ("Exit:", sDialog.exitok)
if __name__=='__main__':
main()
I do have lessons for script dialogs galore here (section 10):
https://www.patreon.com/cairyn
and also simple command plugins that allow you to embed the dialog in the GUI, but that is not really the core of the question. (There are also examples here on the forum if you search for GeDialog, I assume.)
Back to the original topic:
I have set up a simple example with a cube falling on a plane through dynamics.
The Python tag contains a button that, if clicked, executes the scene for frames 0-80 and creates keyframes for rotation and translation of the cube. The tag's code looks like this:
import c4d
def SetKey (currentTime, obj, parameterDesc, value):
track = obj.FindCTrack(parameterDesc)
if track == None:
# create track for this parameter
track = c4d.CTrack(obj, parameterDesc)
obj.InsertTrackSorted(track)
curve = track.GetCurve() # always present
currentKey = curve.FindKey(currentTime) # returns dictionary "key" (Key), "idx" (Index)
if currentKey:
key = currentKey['key'] # key reference to change
doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, key)
key.SetValue(curve, value)
curve.SetKeyDirty()
else:
defkey, dub = doc.GetDefaultKey() # new key to insert
key = defkey.GetClone() # needed?
key.SetValue(curve, value)
key.SetTime(curve, currentTime)
key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)
curve.InsertKey(key, True)
doc.AddUndo(c4d.UNDOTYPE_NEW, key)
def message(msg_type, data):
if msg_type == c4d.MSG_DESCRIPTION_COMMAND:
print (data) # irrelevant since there is only one button
fps = doc.GetFps()
theCube = doc.GetFirstObject()
for frame in range(81):
doc.SetTime(c4d.BaseTime(frame, fps))
doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_INTERNALRENDERER)
print ("Rot:", theCube[c4d.ID_BASEOBJECT_REL_ROTATION])
print ("Pos:", theCube[c4d.ID_BASEOBJECT_REL_POSITION])
descX = c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
)
descY = c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
)
descZ = c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
)
val = theCube[descX]
SetKey (doc.GetTime(), theCube, descX, val)
val = theCube[descY]
SetKey (doc.GetTime(), theCube, descY, val)
val = theCube[descZ]
SetKey (doc.GetTime(), theCube, descZ, val)
descX = c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
)
descY = c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
)
descZ = c4d.DescID(
c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
)
val = theCube[descX]
SetKey (doc.GetTime(), theCube, descX, val)
val = theCube[descY]
SetKey (doc.GetTime(), theCube, descY, val)
val = theCube[descZ]
SetKey (doc.GetTime(), theCube, descZ, val)
def main():
pass
As you see, the scene evaluation is triggered in the aforementioned way by setting the frame and then calling ExecutePasses, which will realize the dynamics. Then the keyframes are generated for the new cube position. You can then switch off dynamics and use only the keyframes. (I am using BUILDFLAGS_INTERNALRENDERER here but I am not sure whether this makes a difference.)
You can play with that; I suppose without knowing your exact code I can't help much more.
EDIT: Here's the scene for your convenience:
Python tag triggers scene evaluation.c4d