How to call other object's button attribute by a tag and simulate clicking?
-
Hello everyone, I want to simulate clicking buttons on other objects through a custom button. Is this possible?
Thank you! -
Hey @kangddan,
Thank you for reaching out to us. The answer depends on where the button is located. Button in dialogs cannot be clicked programmatically, unless you own the implementation. Buttons in descriptions can be clicked programmatically, i.e., descriptions means things that live in the Attribute Manager. An easy way to do this is c4d.CallButton.
Where this rule is somewhat broken, is when a button embedded in the UI of a paramater. E.g., a button in the flyout of a Color field. The button must be its own entity with its own ID, so that you can call it.
Cheers,
Ferdinand -
Thanks @ferdinand
I've found that c4d.CallButton doesn't seem to simulate clicks on buttons created via custom user data. Or, when I click a user data button, can it send a global signal? I don't quite understand the Message function of the Python tag. If I can send a global signal, then the Message functions inside all objects with Python tags could receive the signal, allowing for unified batch triggering. This seems more elegant than simulating clicks on buttons of different objects to trigger them. -
@ferdinand
Just like this, I can manually click each object's user data button to make it do its work. Then I also have a button that can trigger the content inside each object's Python tag in a batch~ -
Hey @kangddan
I am not quite sure what that last message is meant to convey, the screen cast of the QT dialog. But I guess you have troubles with our message system? Please post your code in the future, it is not fun to write boilerplate code for user issues.
Cheers,
FerdinandExplanation
File: test.c4d
Code
Script Manager Script:
import c4d doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. # ID of the first user data element (a button). ID_BTN_1: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)) # ID and fields for our custom message. ID_CUSTOM_MESSAGE: int = 1065587 # Registered as a plugin ID under developers.maxon.net ID_CUSTOM_MESSAGE_TARGET: int = 0 # Made up IDs for the container fields. ID_CUSTOM_MESSAGE_DATA: int = 1 # Made up IDs for the container fields. def main() -> None: """Called by Cinema 4D when the script is being executed. """ # Send a message to a singular node. op.Message(c4d.MSG_DESCRIPTION_COMMAND, {"id": ID_BTN_1}) # Broadcast a message to multiple nodes, here we broadcast into the document node, i.e., # all nodes in the document in this case. When we would send this to an object, it would be # broadcasted to all children, tags, tracks, curves, etc of that object. doc.MultiMessage(c4d.MULTIMSG_ROUTE_BROADCAST, c4d.MSG_DESCRIPTION_COMMAND, {"id": ID_BTN_1}) # But for buttons we implement ourself it is a bit nonsensical to simulate a button click, as # we then also can just implement any other message. Here we send a quasi-custom message type # via #MSG_BASECONTAINER as we cannot implement truly custom messages in Python. # Create a container and mark it with our plugin ID so recipients can identify it. Then mark # the message as targeted at tags, add a payload, and broadcast it into the document. bc: c4d.BaseContainer = c4d.BaseContainer(ID_CUSTOM_MESSAGE) bc.SetInt32(ID_CUSTOM_MESSAGE_TARGET, c4d.Tbase) # Could also pass Tphong, to only target phong tags for example. bc.SetString(ID_CUSTOM_MESSAGE_DATA, "Hello World") doc.MultiMessage(c4d.MULTIMSG_ROUTE_BROADCAST, c4d.MSG_BASECONTAINER, bc) if __name__ == '__main__': main()
Python Tag:
import c4d import typing doc: c4d.documents.BaseDocument # The document containing this field object. op: c4d.BaseTag # The Python tag containing this code. # ID of the first user data element (a button). ID_BTN_1: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA), c4d.DescLevel(1)) # ID and fields for our custom message. ID_CUSTOM_MESSAGE: int = 1065587 # Registered as a plugin ID under developers.maxon.net ID_CUSTOM_MESSAGE_TARGET: int = 0 # Made up IDs for the container fields. ID_CUSTOM_MESSAGE_DATA: int = 1 # Made up IDs for the container fields. def main() -> None: """Called by Cinema 4D to execute the tag. """ pass def message(mid: int, data: typing.Any) -> bool: """Called by Cinema 4D to handle messages sent to the Python Generator object. """ # Handle a direct button click message. What you did in your code, just testing for the ID 1 in # the second desc level, is not so good, as your code could would then also trigger for buttons # located in other sub-containers with the ID 1. You should test for level 0 being of value # c4d.ID_USERDATA and level 1 being of value 1. You also do not test for the sent DescID having # two levels in the first place, which would lead to errors when there is a button in the top # level of the description and the user clicks it. if mid == c4d.MSG_DESCRIPTION_COMMAND: isBtn1: bool = isinstance(data, dict) and data.get("id", c4d.DescID()) == ID_BTN_1 if isBtn1: print (f"Button 1 pressed on node: {op.GetName()}") # We could also write it in this form, I find my variant a bit cleaner. # did: c4d.DescID = data.get("id", c4d.DescID()) # if did.GetDepth() == 2 and did[0].id == c4d.ID_USERDATA and did[1].id == 1: # print (f"Button 1 pressed on node: {op.GetName()}") # Handle a container message with which we can send a broad range of data, we check that the # container is marked with our custom ID. if mid == c4d.MSG_BASECONTAINER and isinstance(data, c4d.BaseContainer) and data.GetId() == ID_CUSTOM_MESSAGE: # Check that the type of this node is an instance of the target set in the sent container. if op.IsInstanceOf(data.GetInt32(ID_CUSTOM_MESSAGE_TARGET)): print(f"Custom message received with data: {data.GetString(ID_CUSTOM_MESSAGE_DATA)} in node: {op.GetName()}") return True ```
-
Hi @ferdinand
Thanks for your help! Apologies for not providing code earlier, and also for the confusion with the PySide GIF – it was just an analogy to explain the batch triggering concept I was trying to achieve in C4D.
I'm happy to report that I've successfully resolved the issue with the global signal for user data buttons. Your guidance on the message system was very useful~
Cheers
DanDanKang
-
Good to hear!