Add button to run a script
-
Hi everyone, I am trying to learn how to code python by modifying the maxon scripts.
I have this basic script working as a plugin. It makes the object editable and moves the anchor point. I am trying to figure out a way to make this work with a button click instead of just clicking on the script to run it.
Can you please help me get one or two buttons to work with this code? Thank you for any guidance!
import c4d
import osdef load_bitmap(path):
path = os.path.join(os.path.dirname(file), path)
bmp = c4d.bitmaps.BaseBitmap()
if bmp.InitWith(path)[0] != c4d.IMAGERESULT_OK:
bmp = None
return bmpimport c4d
from c4d import guiclass MakeEditableAnchor(c4d.plugins.CommandData):
PLUGIN_ID = 1058020 PLUGIN_NAME = 'MakeEditableAnchor' PLUGIN_INFO = 0 PLUGIN_ICON = load_bitmap('res/icons/makeeditableanchor.jpg') PLUGIN_HELP = '' def Register(self): return c4d.plugins.RegisterCommandPlugin( self.PLUGIN_ID, self.PLUGIN_NAME, self.PLUGIN_INFO, self.PLUGIN_ICON, self.PLUGIN_HELP, self) def Execute(self, doc): gui.MessageDialog('Hello World!') c4d.CallCommand(12236) # Make Editable op = doc.GetActiveObject() #op = doc.SearchObject("obj1") ps = op.GetAllPoints() #Points in Local coordinates List m = op.GetMg() #Object Global Matrix center = op.GetMp() #Local coordinates center: https://tinyurl.com/wed88cn rad = op.GetRad() # Geometry radius: https://tinyurl.com/tb6vn64 center -= c4d.Vector(0,rad.y,0) #Move down axis to lowest point. Comment this line to keep axis in a center center *= m #Convert to Global coordinates center new_m = c4d.Matrix(m) #Copy matrix new_m.off = center #Change its center loc_m = ~new_m * m #Get local matrix op.SetAllPoints([loc_m.Mul(p) for p in ps]) op.SetMg(new_m) op.Message(c4d.MSG_UPDATE) c4d.EventAdd() # Execute main() return True
if name == 'main':
MakeEditableAnchor().Register() -
I am actually not sure what you mean by
with a button click instead of just clicking on the script
A Command Plugin is essentially one command that is triggered by clicking the plugin, as the name says. If you want to embed a button in your GUI, you can just drag and drop the icon (I see that you have one) in any toolbar you like.
You can of course load a dialog instead of directly executing the command. In that case you need to derive a new dialog from the class GeDialog and open the dialog asynchronously (see the other threads recently), then you can do tricks with it like embedding it into the layout and have C4D open it automatically on start. However, this makes only sense if you have more than one functionality to execute, or input fields for the user to enter data in, otherwise you end up with just one button in the dialog, which is a bit wasteful.
Also, isn't that what you just asked in the other thread, where you already have a GeDialog created?
-
Thank you. Yes, the goal is to be able to execute multiple commands in one plugin so I was trying to find a way to connect the GeDialog with some commands.
I am new to programming and I have read through the GeDialog documentation and I can not figure out a way to connect the two concepts.
Is there some example code that creates a few buttons that run some commands?
Thank you for your help, I appreciate it.
-
Hello @stereo_stan,
thank you for reaching out to us. I am not one hundred percent sure how your question is meant.
Scripts in the Python Scripting Manger in Cinema 4D can have their own button out of the box, you can even define the icon of that button. For details, please refer to the Icon section the Script Manger User Manual.
In case your question was more about execution, @Cairyn already gave you most of the info (thanks!). In principle you would do this with a
CommandData
or aNodeData
plugin, depending on what you have in mind exactly. Define there aSTRING
element in their interface definition and flag it as beingMULTISTRING
and Python. Also add a button to the interface. Then, in your implementation, listen for the button being clicked and load the Python code from the code window you did create. You could of course also add logic to populate that code window from a file or cut out the code window altogether and always execute files. Executing arbitrary modules is more a Python than a Python SDK question and therefore out of scope of support, but I have shown it here. There are multiple libraries in the standard Python library you can use for this,runpy
is the least complicated of them.Cheers,
ferdinand -
@stereo_stan I suppose you don't mean to run an external script (Python allows this through Execute and Evaluate functions) since you're a beginner. You just want to call a functionality from your script (the same). So, you just need the dialog.
The Github example (you found these, yes?) Memory Viewer contains a dialog that is called as part of the CommandData plugin. Let me quote this partially here (as it is (C)Maxon stuff anyway )
class MemoryViewerCommandData(c4d.plugins.CommandData): """ Command Data class that holds the MemoryViewerDialog instance. """ dialog = None def Execute(self, doc): """ Called when the user Execute the command (CallCommand or a clicks on the Command from the plugin menu) :param doc: the current active document :type doc: c4d.documents.BaseDocument :return: True if the command success """ # Creates the dialog if its not already exists if self.dialog is None: self.dialog = MemoryViewerDialog() # Opens the dialog return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaulth=400, defaultw=400) def RestoreLayout(self, sec_ref): """ Used to restore an asynchronous dialog that has been placed in the users layout. :param sec_ref: The data that needs to be passed to the dlg (almost no use of it). :type sec_ref: PyCObject :return: True if the restore success """ # Creates the dialog if its not already exists if self.dialog is None: self.dialog = MemoryViewerDialog() # Restores the layout return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)
This CommandData is relatively simple, it just opens your dialog. Noteworthy are that you need to open the dialog as DLG_TYPE_ASYNC, which will allow you to work in C4D while the dialog is open; and the implementation of a RestoreLayout function which C4D uses to open a dialog that is embedded in the layout.
The example also contains the MemoryViewerDialog class which is used in this CommandData, but you may not want to study that as it shows a number of different techniques (like GeUserArea) that you don't want yet, so I don't quote it. However, I saw in your other thread that you can construct a dialog class already, so I guess you do not need further pointers.
To get a button into your dialog, use AddButton in your dialog's CreateLayout method as often as you like, like that:
self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code")
then evaluate the commands from that button in the dialog's Command methoddef Command(self, id, msg): if id==ID_BUTTON3: print ("Button clicked") return True
-
Thank you Cairyn and Ferdinand, this is all very helpful and I have a better understanding of different ways to approach this type of project.
Yes, I do not think I want to run an external script, just have one script that a user can perform a few different commands from. Essentially so they can just have one command icon ( script ) in their interface and when they click on that it opens up a GeDialog with a bunch of options they can run by clicking on buttons inside of the GeDialog vs having to click on a bunch of different scripts. I assume those buttons are essentially other scripts that are all bundled together that they would drop in their scripts folder.
I am so excited about all of the possibilities of adding programming into my daily work as a c4d animator-it is challenging to learn from a non-programming background but this forum has helped a lot.
Thanks again.
-
Hello @stereo_stan
unless I am overlooking something here, you can then simply drag and drop the script into one of Cinema's palettes as described in my previous posting. No extra steps required.
Cheers,
Ferdinand -
One more question on this topic. I have tried to combine Cairyn's note on creating a button with the py-commanddata_dialogr13 script from Maxon github to create an async dialog.
For some reason, it will not add my button to the dialog-basically just trying to add buttons to the py-commanddata script. Do you have any possible suggestions on making something like this work? Basically would like an async dialog with buttons. Thank you for any advice.
""" Copyright: MAXON Computer GmbH Author: Maxime Adam Description: - Creates a Dialog which display 2 buttons OK and Cancel. Class/method highlighted: - c4d.plugins.CommandData - CommandData.Execute() - c4d.gui.GeDialog - GeDialog.CreateLayout() - GeDialog.Command() """ import c4d from c4d import gui # Be sure to use a unique ID obtained from www.plugincafe.com PLUGIN_ID = 1057171 class ExampleDialog(c4d.gui.GeDialog): def CreateLayout(self): """This Method is called automatically when Cinema 4D Create the Layout (display) of the Dialog.""" # Defines the title of the Dialog self.SetTitle("This is an example Dialog") # Creates a Ok and Cancel Button self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL) return True def Command(self, messageId, bc): """This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called. It is also called when a string menu item is selected. self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code") Args: messageId (int): The ID of the gadget that triggered the event. bc (c4d.BaseContainer): The original message container. Returns: bool: False if there was an error, otherwise True. """ # User click on Ok buttonG if messageId == c4d.DLG_OK: print("User Click on yep") return True if ID == ID_BUTTON3: print ("Button clicked") return True # User click on Cancel button elif messageId == c4d.DLG_CANCEL: print("User Click on Cancel") # Close the Dialog self.Close() return True return True def Command(self, id, msg): if id == ID_BUTTON3: print ("Button clicked") return True class ExampleDialogCommand(c4d.plugins.CommandData): """Command Data class that holds the ExampleDialog instance.""" dialog = None def Execute(self, doc): """Called when the user executes a command via either CallCommand() or a click on the Command from the extension menu. Args: doc (c4d.documents.BaseDocument): The current active document. Returns: bool: True if the command success. """ # Creates the dialog if its not already exists if self.dialog is None: self.dialog = ExampleDialog() # Opens the dialog return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=400, defaulth=32) def RestoreLayout(self, sec_ref): """Used to restore an asynchronous dialog that has been placed in the users layout. Args: sec_ref (PyCObject): The data that needs to be passed to the dialog. Returns: bool: True if the restore success """ # Creates the dialog if its not already exists if self.dialog is None: self.dialog = ExampleDialog() # Restores the layout return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref) # main if __name__ == "__main__": # Registers the plugin c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID, str="Py-CommandData Dialog", info=0, help="Display a basic GUI", dat=ExampleDialogCommand(), icon=None)
-
Heelo @stereo_stan,
@stereo_stan said in Add button to run a script:
or some reason, it will not add my button to the dialog-basically just trying to add buttons to the py-commanddata script.
The posted code does not add any button. This would have to happen in
ExampleDialog.CreateLayout()
which does only set the title of the dialog and adds a cancle group. Is that what you mean with "button"?Cheers,
Ferdinand -
As Ferdinand already said, you forgot to add the AddButton call, or rather (as I can see my line
self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code")
in the script), you inserted it in the wrong place (it's now inside a comment section, where it doesn't do anything, and also in the wrong method.Additionally, I need to tell you that you must define
ID_BUTTON3
somewhere, or it will raise an error. And the snippetif ID == ID_BUTTON3:
will not work since the parameterID
that I used is actually calledmessageId
in yourcommand
method. Oops, and you duplicated the wholecommand
method so now you have two of them and Python will only consider the latter and ignore the first.Okay, I guess Maxon hates me for self promoting here, but this is not so much an API question now but a matter of plain Python understanding, so you may perhaps consider my Python/C4D API course under
https://www.patreon.com/cairyn
In section 10 I have multiple examples for dialog setup with layout, buttons, command method, text fields, radio buttons, separators, columns, etc.; too much to replicate here.I have corrected the script for you a final time, removing all the comments and reducing the sample to the most necessary stuff:
import c4d from c4d import gui PLUGIN_ID = 1057171 ID_BUTTON3 = 1001 class ExampleDialog(c4d.gui.GeDialog): def CreateLayout(self): self.SetTitle("This is an example Dialog") self.AddButton(ID_BUTTON3, c4d.BFH_SCALEFIT, name="Jump into code") self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL) return True def Command(self, messageId, bc): if messageId == c4d.DLG_OK: print("User Click on yep") return True elif messageId == ID_BUTTON3: print ("Button clicked") return True elif messageId == c4d.DLG_CANCEL: print("User Click on Cancel") self.Close() return True return True class ExampleDialogCommand(c4d.plugins.CommandData): dialog = None def Execute(self, doc): if self.dialog is None: self.dialog = ExampleDialog() return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=400, defaulth=32) def RestoreLayout(self, sec_ref): if self.dialog is None: self.dialog = ExampleDialog() return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref) if __name__ == "__main__": c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID, str="Py-CommandData Dialog", info=0, help="Display a basic GUI", dat=ExampleDialogCommand(), icon=None)
-
Thank you Cairyn and Ferdinand again. This is all making much more sense now, I appreciate you helping me understand how to correctly set up a simple interface. I do need to spend more time just learning the fundamentals of Python.
-
Hello @stereo_stan,
without further questions or replies, we will consider this topic as solved by Monday, the 30th and flag it accordingly.
Thank you for your understanding,
Ferdinand