Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    How to call other object's button attribute by a tag and simulate clicking?

    Cinema 4D SDK
    2024 windows python
    2
    7
    1.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • kangddanK
      kangddan
      last edited by

      Hello everyone, I want to simulate clicking buttons on other objects through a custom button. Is this possible?
      Thank you!c981a2679efc788cb33ee5312fb5f3a.png

      https://github.com/kangddan

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @kangddan
        last edited by ferdinand

        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

        MAXON SDK Specialist
        developers.maxon.net

        kangddanK 2 Replies Last reply Reply Quote 0
        • kangddanK
          kangddan @ferdinand
          last edited by kangddan

          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.

          https://github.com/kangddan

          1 Reply Last reply Reply Quote 0
          • kangddanK
            kangddan @ferdinand
            last edited by kangddan

            @ferdinand
            giff.gif
            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~

            https://github.com/kangddan

            ferdinandF 1 Reply Last reply Reply Quote 0
            • ferdinandF
              ferdinand @kangddan
              last edited by ferdinand

              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,
              Ferdinand

              Explanation

              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
              ```

              MAXON SDK Specialist
              developers.maxon.net

              kangddanK 1 Reply Last reply Reply Quote 1
              • kangddanK
                kangddan @ferdinand
                last edited by kangddan

                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
                bandicam 2025-05-23 19-37-11-991 00_00_00-00_00_30.gif

                https://github.com/kangddan

                1 Reply Last reply Reply Quote 1
                • ferdinandF
                  ferdinand
                  last edited by

                  Good to hear!

                  MAXON SDK Specialist
                  developers.maxon.net

                  1 Reply Last reply Reply Quote 1
                  • First post
                    Last post