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

    Detect new project in Python plugin

    Cinema 4D SDK
    python
    2
    4
    582
    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.
    • M
      mheberlein
      last edited by

      Hi all, is there a callback or MessageData ID that my Python plugin can attach to detect new projects? I would like to execute some code every time a user executes File > New Project.
      Thanks! Michael

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

        Hello @mheberlein,

        thank you for reaching out to us. For what you are trying to do there is unfortunately no convenient path for implementation in Python, but it is certainly possible to do.

        There is the message family MSG_DOCUMENTINFO being broadcasted to some atoms of in a scene graph. It is related to the sub messages MSG_DOCUMENTINFO_TYPE which will tell you if a document has been loaded, saved and more. To make use of that message, you will need a NodeData plugin that receives that message in its Message() method. There are unfortunately no good candidate in Python which you could use in your case. In C++ you would use a SceneHookData plugin, but that is not available in Python.

        You could use other NodeData derived plugin interfaces like ObjectData or MaterialData which will also retrieve that message, but that probably does not work very well in your setup. So, the best route is probably a timer. Note that not all NodeData derived plugins will receive MSG_DOCUMENTINFO, e.g., you cannot setup shop in a PreferenceData plugin for example, which is also a node, as it won't receive that message.

        To take the timer route, implement a GeDialog or MessageData plugin and overwrite their GetTimer() method to receive periodic MSG_TIMER core messages for that timer interval in their CoreMessage() methods. E.g., returning 1000 in GetTimer() will result in a MSG_TIMER core message being sent to that interface in an interval of roughly one second. There you could call a function which manually evaluates if the document has changed by comparing the current document against an identifier for a cached document. Note that I wrote here 'identifier' and not object, as objects get reallocated quite often in Cinema 4D. So, attempting to store an object itself as a reference won't work. Since BaseDocument is a type of node itself, you should use C4DAtom.FindUniqueID() in conjunction with the MAXON_CREATOR_ID id. The snippet below demonstrates the approach in a pseudo-code fashion.

        import c4d
        
        class MyMessageData (c4d.plugins.MessageData):
            """A message data implementation.
        
            I will not show the actual implementation of the message data here, but
            how to securely store a reference to a node (a document) via its UUID.
            """
        
            def __init__(self):
                """Initializes the attribute storing the UUID of the active document.
                """
                # The UUID of the document this message data has seen last.
                self._cachedDocumentUuid = None
        
            @staticmethod
            def GetActiveDocumentUuid() -> bytes:
                """Returns the MAXON_CREATOR_ID UUID for the active document.
                """
                doc = c4d.GetActiveDocument()
                if doc is None:
                    raise RuntimeError("Could not access active document.")
        
                uuid = doc.FindUniqueID(c4d.MAXON_CREATOR_ID)
                if uuid is None:
                    raise RuntimeError(f"{doc} has no maxon creation marker.")
        
                return bytes(uuid)
        
            def CompareCachedUuid(self, uuid: bytes) -> bool:
                """Checks if the passed UUID is different than the cached one.
        
                Returns #True when they match and #False when they do not. When there
                is a mismatch, the cached UUID will also be updated to the passed on.  
                """
                self._cachedDocumentUuid != uuid:
                    self._cachedDocumentUuid = uuid
                    return False
                return True
        
            def CompareDocuments(self) -> None:
                """Called by CoreMessage() on a MSG_TIMER event.
                """
                currentUuid = MyMessageData.GetActiveDocumentUuid()
                if not self.CompareCachedUuid(currentUuid):
                    print ("Active document has changed.")
                else:
                    print ("Active document is the same.")
        

        You could also mix the MSG_DOCUMENTINFO with the timer approach by injecting a dummy node, a hidden object into the active document via a controlling MessageData to offload the checking work to it and get access to the more elaborate events in the context MSG_DOCUMENTINFO. For a simple usage with a moderate timer value the simple approach should be fine. Note that when you set the timer callback to a low value like 40ms, that your code will then really run all the time, possibly impacting the performance of Cinema 4D.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • M
          mheberlein
          last edited by

          Thank you for your reply, Ferdinand.
          I had a similar timer workaround in mind when I decided that there must be a better, official way to do this.
          Will implement it this way now, running every 3-4 seconds should be enough.

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

            Hello @mheberlein,

            without any further questions or postings, we will consider this thread as solved by Friday the 4th, February 2022.

            Thank you for your understanding,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

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