Group Details Private

Global Moderators

Forum wide moderators

  • RE: Volume Builder GetInputObject returns something different?

    Hi Dan,

    Please check out the "How to ask Questions" section of our Support Procedures. It's very difficult to reproduce the behavior you're talking about without any piece of code!

    The GetInputObject() function works in the domain of input objects of the volume builder (pink arrow). The GetDown() function operates on the object hierarchy (blue arrow). I personally would 'a priori' not expect them to behave the same way. I'd assume it's quite the opposite: there're some circumstances, when they behave the same way.

    8a547552-bc45-44a2-aa1d-569f086f79a9-image.png

    Regarding getting the cache issue it's hard to tell without seeing what's actually going on in your code. Did you compare that GetInputObject() and GetDown() functions actually return the exact same object, and then executing GetCache() on this object doesn't return you anything?

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: c4dpy -g_encryptPypFile fails: cannot find pyp file - plugin registration failed

    Hello @ezeuz,

    Thank you for reaching out to us. I can reproduce this, and what is even more odd, I can reproduce this back to R25, where it for sure once worked on my machine.

    26e0385d-c1ce-4288-8f1b-b3234392fc70-image.png

    I am quite frankly a bit perplexed by what is going wrong here. This is the code which is failing here:

    //Check if a pyp file is currently loaded. Used to check if a user registers a plugin from the pluginmanager. Requires GIL, sets the exception text.
    static Bool CheckIfLoadedFromPypFile()
    {
    	if (!current_plugin)
    	{
    		CPyErr_Format(PyExc_EnvironmentError, "cannot find pyp file - plugin registration failed");
    		return false;
    	}
    	return true;
    }
    

    Since this is failing so far back for me, there are two options:

    1. We overlook something. That seems a bit unlikely, I run my shell as admin and I tried this with multiple plugin files to encrypt. And there is the screen in my old posting where it for sure worked. I also wrote the command line arguments part in the c4dpy manual and I tested there each command I documented.
    2. Something in Windows changed so that now the plugin loading is failing (but only in this context), i.e., current_plugin is null. This sounds even more unlikely.

    At the end of the day, this is more @m_adam's domain, as he owns c4dpy. Let me sleep a night over this and also try the same on macOS (or have a look with a debugger when I find the time). When I then cannot find a solution, I will flag this thread as a bug, and Maxime will have to take a look.

    I am pretty sure we are overlooking something here ...

    Cheers,
    Ferdinand

    edit: There could be a bug in recent versions of Cinema 4D which does not unload binaries correctly. Try rebooting, I currently cannot do this as I am compiling, will try on Monday.

    posted in Cinema 4D SDK
  • RE: How to scale all objects in a scene at once?

    Hello @j_vogel,

    Welcome to the Maxon developers forum and its community, it is great to have you with us!

    Getting Started

    Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.

    • Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
    • Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
    • Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.

    It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.

    About your First Question

    As lined out above, please follow our support procedures. Thread necromancy usually leads to problems, at least when that much time has passed, I have forked this thread. Please also follow our rules about Support Procedures: Asking Questions, in short, we do not answer "please implement this or that for me".

    The parameter DOCUMENT_DOCUNIT in a document is just something that is added to things like the coordinate manager, to set the unit and a multiplier for the numbers shown there. It will not scale the actual geometries (as it otherwise would be destructive) and therefore does not affect the view port.

    To scale all elements in the scene, you must scale all elements 🙂 . You can use the command which we implemented for that, what is talked about above and which can be invoked with the button in the document scale settings. But you then have to use its UI, which is I guess not what you want. So, you would have write something like that yourself. But there is currently no premade scene traversal in Python which can be a bit daunting for beginners (will change with the next major release). Carrying out the scaling itself is not that hard, although this complexity can also vary based on the content. Since I know you have beta access: You can also pick the latest beta build, the traversal stuff is already in there.

    Cheers,
    Ferdinand

    Code

    """Demonstrates how to scale all objects in a scene by a given factor.
    """
    
    import c4d
    
    SCALE_FACTOR: c4d.Vector = c4d.Vector(2) # Scale factor for the objects, we pick a uniform scale of 2.
    doc: c4d.documents.BaseDocument          # The currently active document.
    
    def IterateTree(node: c4d.GeListNode, yieldSiblings: bool = False) -> c4d.GeListNode:
        """Iterates over all descendants of the given #node and optionally its next siblings.
    
        In the next major release, there will be (finally) built-in scene traversal functions in Python. The one
        to pick for this case would be `mxutils.IterateTree`. Its signature is compatible with what I did here.
        """
        def iterate(node: c4d.GeListNode) -> c4d.GeListNode:
            if not node:
                return
            
            yield node
            for child in node.GetChildren():
                yield from iterate(child)
    
        while node:
            yield from iterate(node)
            node = node.GetNext() if yieldSiblings else None
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        # Iterate over all objects in the document and scale them.
        for node in IterateTree(doc.GetFirstObject(), yieldSiblings=True):
            node.SetMl(node.GetMl() * c4d.utils.MatrixScale(SCALE_FACTOR))
            
        c4d.EventAdd()
    
    
    if __name__ == '__main__':
        main()
    

    edit: you should of course use the local and not global matrix, as you otherwise will scale things multiple times.

    posted in Cinema 4D SDK
  • RE: How do I create a Plugin Identifier?

    Hello @shir,

    Thank you for reaching out to us. It should be self-explanatory that we cannot say much about 'the maxon site is broken sending me from broken link to broken link'. What is 'the' maxon site for you? And there are not countless broken links on our sites, that all sounds very hyperbolic.

    The old site which also hosts the plugin IDs is still operational: https://plugincafe.maxon.net/, you can also access it as embedded content via https://developers.maxon.net/forum/pid/, but you will have to login with your old plugin cafe account in both cases or create a new one.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Importing pythonapi from ctypes freezes C4D

    Hey @lasselauch,

    We are not able to reproduce this crash on an Intel, M1, or M3 MacBook with 2024.4.0. Please provide and submit a crash report when this is still a problem for you. I would also recommend reinstalling Cinema 4D to rule out that your installation was damaged.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Xpresso & Pyhton - Convert Link List (Hierarchy Node) to In-/Exclusion

    Hi @hoffwerr ,

    Welcome to the Maxon developers forum and its community, it is great to have you with us!

    Getting Started

    Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.

    • Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
    • Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
    • Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.

    It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.

    About your First Question

    Please elaborate on what exactly you're trying to achieve? Why do you need a python node here, wouldn't it be enough using ObjectList node instead? What exactly do you mean by "convert to In-Exclusion list"?

    Please don't be shy to attach a sample project file that shows where you are currently at.

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: Importing pythonapi from ctypes freezes C4D

    Hey @lasselauch,

    we will have a look at the crash, I will move the thread here into bugs when we can confirm it.

    About your problem at hand: I have written a code example which should get you going. In short, the message data is stored under BFM_CORE_PAR1 for p1 and _PAR2 for p2. For the rest, please read the example. But I personally would still advise against taking such route unless one really has to.

    Make sure to set USE_CTYPES to FALSE in the script when you still encounter issues importing it.

    Cheers,
    Ferdinand

    Result

    54e58394-fefd-4c73-ab54-987268bd4bb7-image.png

    Code

    """Demonstrates sending messages using both SpecialEventAdd() and a custom message stream
     implementation.
    
    The general idea of the MessageStream type is to have centralized place to store data as key-value
    pairs. In most cases, we can just define and expose such data container in a module which is imported
    by all plugins which have to share the data. I went here the slightly more complex route of the case
    where plugins are not able to do this, and all come with their own implementation and the data then
    being share via a "standard" module like sys, os, or what I picked here, c4d.
    
    This also demonstrates the most simple use case of SpeicalEventAdd(), CoreMessage() and ctypes, 
    where we send an integer as the message data. As expressed in the thread above, I am not a big fan
    of this approach, this is all very un-pythonic and error prone.
    
    We technically COULD do all what MessageStream does and more here with ctypes alone, but the more 
    complex our data gets, the more we will have to do to unpack our data from the wrapping capsule, and
    when we want to send anything else than ints, we will also have to start packing up data when
    sending them. This is all just a huge pain in the *** unless you are someone who is very comfortable
    with the Python C API and the memory layout of your sent data anyway.
    
    Note:
        What I have not done here, is made this thread safe. If we have multiple threads sending messages
        to the message stream, we will have to make sure that we do not run into access conflicts. This
        could either be done via calling c4d.threading.GeThreadLock() before accessing the message stream
        and .GeThreadUnlock() after, but that will really lock up everything which is not so great. An
        alternative would be to use a Python's threading.Lock(), threading.RLock() or threading.Semaphore()
        to lock the access to a message stream. The problem is there that our threads, c4d.threading.
        C4DThread, are not fully compatible with Python's threading, so be careful when using them 
        together. But I have used threading.Semaphore() in the past without encountering any issues.
    """
    USE_CTYPES: bool = True # Toggle for using ctypes and with it SpecialEventAdd() or not. Disable this
                            # when you experience crashes when importing ctypes.
    IS_DEBUG: bool = True   # Toggle for debug behavior.
    PID_MY_CORE_MESSAGE: int = 1063958 # A registered plugin ID to uniquely identify our core message.
    
    import c4d
    import time
    import inspect
    
    if USE_CTYPES:
        import ctypes
    
    from mxutils import CheckType
    
    class MessageStream:
        """Realizes a message stream that can be observed by others.
    
        An observer is just a delegate, i.e., a function that is called when 'something' happens in the
        message stream. Or in other words, more or less what the classic API message functions do in 
        Cinema 4D. The main difference between the observer/delegate pattern and old school messages is 
        that one has to manually register with the observable, here via AddObserver().
    
        Can also be used as a plain message list without any observers, i.e., in conjunction with
        SpecialEventAdd() and CoreMessage().
    
        I used here ints as message IDs, but we could also use strings or any other hashable type. I 
        have done this solely so that the type works better in conjunction with SpecialEventAdd() which
        is int focused.
    
        See also:
            https://en.wikipedia.org/wiki/Observer_pattern
    
        Example:
            # Define a function that will be called when something happens in the message stream,
            # the observable in Maxon terms, we are interested in.
    
            def OnMessageStreamEvent(event: str, mid: int, data: any) -> bool:
                if event == MessageStream.EVENT_ADD:
                    print (f"An item with ID {mid} was added to the message stream.")
                    return True # Consume the event.
    
                return False # Otherwise do not consume the event.
    
            # Get the message stream with the handle 'main' and add our observer to it.
            stream: MessageStream = MessageStream.GetStream("main")
            stream.AddObserver(OnMessageStreamEvent)
    
            # Somewhere else and some time later we can now send messages to the stream.
            MessageStream.GetStream("main").Put(1, "Hello world!")
    
            # Will print:
            # An item with ID 1 was added to the message stream.
        """
        EVENT_ADD: str = "ADD" # And item was added to the message stream.
        EVENT_REMOVE: str = "REMOVE" # An item was removed from the message stream.
        EVENT_FLUSH: str = "FLUSH" # The message stream was flushed.
        EVENT_UPDATE: str = "UPDATE" # An item in the message stream was updated.
    
        ATTACHMENT_POINT: object = c4d # The object/module to attach the global message streams to.
        STREAM_ATTRIBUTE: str = "__MESSAGE_STREAM_" # The prefix for the global message stream attributes.
    
        # --- Object model -----------------------------------------------------------------------------
    
        def __init__(self, handle: str = ""):
            """Initializes the message stream with an optional handle.
            """
            self._handle: str = CheckType(handle, str)
            self._data: dict[int, any] = {}
            self._observers: list[callable] = []
    
        def __str__(self) -> str:
            """Returns a string representation of the message stream.
            """
            return (f"<MessageStream('{self._handle}') at {hex(id(self)).upper()} with "
                    f"{len(self._observers)} observers>")
        
        def __repr__(self) -> str:
            """Returns a string representation of the message stream.
            """
            return str(self)
        
        def __len__(self) -> int:
            """Returns the number of messages in the message stream.
            """
            return len(self._data)
        
        def __contains__(self, mid: int) -> bool:
            """Checks if a message ID is in the message stream.
            """
            return mid in self._data
        
        def __iter__(self) -> iter:
            """Returns an iterator over the message stream.
            """
            return iter(self._data)
        
        def __notify__(self, event: str, mid: int, data: any) -> None:
            """Notifies all observers of the message stream about an event until it is being consumed
            or until all observers have been notified.
            """
            for observer in self._observers:
                if observer(event, mid, data):
                    break
        
        def __getitem__(self, mid: int) -> any:
            """Returns the data of a message in the message stream.
            """
            if mid not in self._data:
                raise KeyError(f"The message ID {mid} is not in the message stream.")
            return self._data[mid]
        
        def __setitem__(self, mid: int, data: any) -> None:
            """Puts a message into the message stream.
            """
            isUpdate: bool = mid in self._data
            self._data[mid] = data
            self.__notify__(self.EVENT_UPDATE if isUpdate else self.EVENT_ADD, mid, data)
    
        def __delitem__(self, mid: int) -> None:
            """Removes a message from the message stream.
            """
            if mid not in self._data:
                raise KeyError(f"The message ID {mid} is not in the message stream.")
            data: any = self._data[mid]
            del self._data[mid]
            self.__notify__(self.EVENT_REMOVE, mid, data)
    
        def __flush__(self) -> None:
            """Flushes the message stream.
            """
            self._data.clear()
            self.__notify__(self.EVENT_FLUSH, 0, None)
    
        # --- Properties --------------------------------------------------------------------------------
    
        @property
        def Handle(self) -> str:
            """Returns the handle of the message stream.
            """
            return self._handle
        
        @property
        def Observers(self) -> tuple[callable]:
            """Returns a shallow copy of the observer list of the message stream.
            """
            return tuple(self._observers)
        
        @property
        def Messages(self) -> dict[int, any]:
            """Returns a shallow copy of the messages of the message stream.
            """
            return dict(self._data)
        
        # --- Observer pattern -------------------------------------------------------------------------
        
        def AddObserver(self, observer: callable) -> None:
            """Adds a delegate to the message stream that is called when the message stream is modified.
    
            The delegate must have the signature observer(event: str, mid: int, data: any) -> bool. 
            Returning true will consume the event, returning false will propagate it to the next
            observer. The event can be one of the EVENT_* symbols. The mid is the message ID and the data
            is the message data.
            """
            if not callable(observer):
                raise TypeError("The observer must be a callable.")
            if observer in self._observers:
                raise ValueError("The observer is already registered.")
            
            # Make sure that the observer can in general deal with the required signature.
            try:
                sig: inspect.Signature = inspect.signature(observer)
                sig.bind(self.EVENT_ADD, 0, 0)
            except Exception as e:
                raise ValueError("The observer must have the signature observer(event: str, mid: int, "
                                 "data: any) -> bool.") from e
    
            self._observers.append(observer)
    
        def RemoveObserver(self, observer: callable) -> None:
            """Removes a delegate from the message stream.
            """
            if observer not in self._observers:
                raise ValueError(f"The observer {observer} is not registered in the message "
                                 f"stream {self}.")
            
            self._observers.remove(observer)
    
        # --- Message stream ---------------------------------------------------------------------------
    
        def Flush(self) -> None:
            """Flushes the message stream.
            """
            self.__flush__()
    
        def Put(self, mid: int, data: any) -> None:
            """Puts a message into the message stream.
            """
            self[mid] = data
        
        def Get(self, mid: int, default: any = None) -> any:
            """Gets a message from the message stream.
            """
            return self[mid] if mid in self else default
        
        def Remove(self, mid: int) -> None:
            """Removes a message from the message stream.
            """
            del self[mid]
    
        # --- Static methods ---------------------------------------------------------------------------
    
        @staticmethod
        def GetStream(handle: str = "main") -> "MessageStream":
            """Returns the globally accessible message stream which has been attached to 
            #ATTACHMENT_POINT under the given #handle.
    
            Doing it in this way is only necessary when we want to have a globally accessible message
            stream with multiple users which cannot share a module, otherwise we can just put this
            class in a file `stream.py`, declare there module level instance of this class and import
            this module in all plugins/modules which need it.
    
                ```stream.py
                class MessageStream:
                    ...
                
                MY_STREAM: MessageStream = MessageStream("my_stream")
                ```
    
                ```plugin.py
                from stream import MY_STREAM
    
                class MyPlugin (...):
                    def __init__(self):
                        MY_STREAM.AddObserver(self.OnMessageStreamEvent)
                        ...
                ```
                ```other_plugin.py
                from stream import MY_STREAM
    
                class OtherPlugin (...):
                        def __init__(self):
                            MY_STREAM.AddObserver(self.OnMessageStreamEvent)
                            ...
                ```
            """
            attribute: str = f"{MessageStream.STREAM_ATTRIBUTE}_{handle.upper()}__"
            if not hasattr(MessageStream.ATTACHMENT_POINT, attribute):
                setattr(MessageStream.ATTACHMENT_POINT, attribute, MessageStream(handle))
    
            stream: MessageStream = getattr(MessageStream.ATTACHMENT_POINT, attribute, None)
            if ((stream.__class__.__qualname__ != MessageStream.__qualname__) or 
                (stream.Handle != handle)):
                raise ValueError(f"Could not get the message stream for the handle '{handle}'.")
    
            return stream
        
        @staticmethod
        def RemoveAllStreams() -> None:
            """Removes all globally accessible streams which have been attached to #ATTACHMENT_POINT.
    
            Doing this can become necessary in development, as we otherwise cannot reload/update the 
            type when it is still in memory.
            """
            for attr in dir(MessageStream.ATTACHMENT_POINT):
                if attr.startswith(MessageStream.STREAM_ATTRIBUTE):
                    delattr(MessageStream.ATTACHMENT_POINT, attr)
    
    if IS_DEBUG:
        MessageStream.RemoveAllStreams()
    
    # --- Implementation of a message dialog which makes use of all this -------------------------------
    
    class MessageDialog(c4d.gui.GeDialog):
        """Realizes a dialog that sends messages using both SpecialEventAdd() and the custom message
        stream implementation.
        """
        # The IDs of the dialog elements.
        ID_BUTTON: int = 1000
        ID_MESSAGE: int = 1001
    
        def __init__(self):
            """Initializes the dialog by attaching an observer to the default custom message stream.
            """
            MessageStream.GetStream().AddObserver(self.OnMessageStreamEvent)
        
        def CreateLayout(self) -> bool:
            """Sets up a UI to send a string message via the custom message stream.
            """
            self.SetTitle("Message Dialog")
            self.GroupBorderSpace(10, 10, 10, 10)
            self.GroupSpace(5, 5)
            self.AddEditText(self.ID_MESSAGE, c4d.BFH_SCALEFIT, initw=200)
            self.AddButton(self.ID_BUTTON, c4d.BFH_CENTER, name="Send Message")
            self.SetString(self.ID_MESSAGE, "Hello world!")
    
            return True
        
        def Command(self, cid: int, msg: c4d.BaseContainer) -> bool:
            """Sets of both a message via the custom message stream and a core message when the user
            presses the "Send Message" button.
            """
            if cid == self.ID_BUTTON:
                # Define the message ID as the current time in seconds and the message as the text in
                # the edit field.
                mid: int = int(time.time())
                msg: str = self.GetString(self.ID_MESSAGE)
    
                # Send a message via the our custom message stream implementation.
                stream: MessageStream = MessageStream.GetStream()
                stream.Put(mid, msg)
                print (f"Sent message with ID {mid} to {stream}.")
    
                # And also invoke a core message using SpecialEventAdd() and ctypes, we just send here
                # the message ID as p1 while we rely on storing the actual message in the message stream.
                # We could also send more complex data here, but this can all get quite complex quite 
                # quickly.
                if USE_CTYPES:
                    c4d.SpecialEventAdd(PID_MY_CORE_MESSAGE, p1=mid, p2=0)
                    print (f"Sent core message with the ID {PID_MY_CORE_MESSAGE} and data {mid}.")
            
            return True
        
        def OnMessageStreamEvent(self, event: str, mid: int, data: any) -> bool:
            """Called when a message is sent to the custom message stream implementation.
            """
            print (f"An '{event}' event occurred for message with ID '{mid}' and data '{data}'.")
            return False # We do not consume the event.
        
        def CoreMessage(self, cid: int, msg: c4d.BaseContainer) -> bool:
            """Called by Cinema 4D when a core message is sent.
            """
            # We receive a core message which notifies us that something has put something into our 
            # custom message stream, the data which is being sent to us via the core message is just the
            # identifier of the message in our custom message stream. The core message data contains the
            # p1 and p2 values of the SpecialEventAdd() call which we must unpack here (just p1 in our
            # case).
            if cid == PID_MY_CORE_MESSAGE and USE_CTYPES:
                capsule: any = msg.GetVoid(c4d.BFM_CORE_PAR1)
                ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_int
                ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
                p1: int = ctypes.pythonapi.PyCapsule_GetPointer(capsule, None)
    
                # Do something with the message.
                print (f"Received core message with packed p1 data: {p1}")
                data: str | None = MessageStream.GetStream().Get(p1, None)
                print (f"Resolved to actual payload '{data}' for code message p1 payload '{p1}'.")
            
            return True
    
    if __name__ == "__main__":
        dlg: MessageDialog = MessageDialog()
        dlg.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE)
    
    posted in Cinema 4D SDK
  • RE: Adding image to GeDialog - Python Plugin/Script

    Hey @Dunhou,

    I do not think that this is possible. At least not in a sane way, as there is no way to control the opacity of the text drawing background color alone. What you probably could do is:

    1. Scale your image to draw down to the size of your GeUserArea drawing region, using something like BaseBitmap.ScaleBicubic.
    2. Now init a GeClipMap canvas with that bitmap.
    3. Draw the text.
    4. Finalize and get the bitmap for the canvas and draw that result into your GeUserArea.

    Which is a bit "ehh" performance-wise (could be mitigated by caching the underlying GeClipMap result) but will then also force you to do really everything in the GeClipMap, loosing access to things like GeUserArea.DrawCustomButton.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Transform coordinates of a polygon's points

    Hi @SmetK,

    Please note that we have a great Matrix Manual with code samples. You can also check our github repository for the geometry examples, e.g. the Polygon Object example.

    For your further questions I kindly encourage you to create new thread and shape your posting according to how it's described in our Support Procedures: Asking Question.

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: Importing/loading sound file to scene via Python

    Hi @ezeuz,

    Great to hear you've figured it out and thanks for sharing the solution with the community!

    Cheers,
    Ilia

    posted in Cinema 4D SDK