Navigation

    • Register
    • Login
    • Search
    1. Home
    2. HerrMay
    H

    HerrMay

    @HerrMay

    5
    Reputation
    52
    Posts
    51
    Profile views
    0
    Followers
    0
    Following
    Joined Last Online

    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups
    HerrMay Follow

    Best posts made by HerrMay

    RE: Blank appearing dropdown cycle

    Hi @m_adam ,

    of course, stupid me. :face_palm: I knew it was something simple I’m missing here. Seems I didn’t see the elephant in the room. :D

    This works like a charm now. Thank you!

    Allow one follow up question though. If I now want to translate this code to a TagData Plugin I’m pretty sure I don’t wanna use a global variable, do I? Would it be sufficient to make that variable a member variable inside of def __init__(self) of the Tag Plugin class?

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Unique Material per Object Plugin instance?

    Hi Guys,

    as I'm pretty sure I found a way to achieve what I'm after I thought I update this thread. Maybe this will help others as well. :wink:

    After taking some time to make NodeData.CopyTo() work, I ended up not getting it to work at all.

    So I thought about how I could achieve what I'm after a different way. Long story short, I ended up implementing a MessageData plugin as some kind of watchdog for a document. Since its CoreMessage runs on the main thread I can happily insert and delete materials as much as I wish to. (At least I'm hoping so :grimacing: :grin:)

    Tl; dr
    The idea behind this goes as follows. I have a timer running and in addition to that I listen for c4d.EVMSG_CHANGE and do some checking to see if the scene needs to update. In my case it's comparing the amount of a specific object against the amount of "specific" materials. If there's a difference I use that difference to delete or insert materials until there's no difference. Once there's no difference I can assign the materials to the objects and let each object control its own material.

    To distinguish between materials responsible for my plugin and the ones that aren't I make sure to put a unique plugin id inside the base container of the material I can then check for.

    Here's a code snippet of that MessageData:

    class Watchdog(c4d.plugins.MessageData):
    
        PLUGIN_ID = "Use your own unique one"
        PLUGIN_NAME = "A MessageData plugin."
        PLUGIN_INFO = 0
    
        def __init__(self):
            self._time = 1000
    
        def GetTimer(self):
            return self._time
    
        def SetTimer(self, time):
            self._time = time
    
    
        @property
        def should_execute(self):
            is_mainthread = c4d.threading.GeIsMainThread()
            check_running = (
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_EDITORRENDERING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_EXTERNALRENDERING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_INTERACTIVERENDERING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_ANIMATIONRUNNING)),
                bool(c4d.CheckIsRunning(c4d.CHECKISRUNNING_VIEWDRAWING))
            )
            is_running = any(item is True for item in check_running)
            return is_mainthread and not is_running
    
    
        def CoreMessage(self, mid, mdata):
    
            if not self.should_execute:
                return False
    
            doc = c4d.documents.GetActiveDocument()
            # SceneHandler is a custom class I delegate the whole creation and comparing stuff to.
            objs, mats = ..., ...
            scene = SceneHandler(objs, mats)
    
            # Check for a change and start the timer again. But only if the scene should update. Otherwise the timer would run all the time.
            if mid == c4d.EVMSG_CHANGE:
                if scene.should_update:
                    self.SetTimer(1000)
    
            # If we get a timer event we update the scene as long as it shouldn't update anymore. We can then stop the timer.
            if mid == c4d.MSG_TIMER:
                if not scene.should_update:
                    self.SetTimer(0)
                scene.update(doc)
    
            return True
    

    Maybe this will help others. Since I found a solution for my problem this thread can be marked solved.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Run a GUI Dialog AFTER C4D Launches not BEFORE?

    Hi @bentraje,

    I‘m not sure but maybe checking for c4d. C4DPL_PROGRAM_STARTED in def PluginMessage(id, data) could be of any help here.

    Something like below.

    import c4d
    import sys
    
    def PluginMessage(id, data):
        if id == c4d.C4DPL_PROGRAM_STARTED:
            # Do your messaging here. 
            return True
    
        return False
    
    

    Cheers,
    Sebastian!

    posted in Cinema 4D SDK •
    RE: How to Add Child Shaders to a Fusion Shader?

    Hello @ferdinand,

    when trying out your function to traverse shaders I noticed that for two materials in the scene it yields for the first material all the shaders of itself but also all the shaders of the second material. For second material it works like expected and yields only the shaders of itself.

    After fiddling around a bit with some other code of you from this post I think I ended up with a function that is yielding all the shaders from a material iteratively.

    Find the code below. Maybe this will help others. :)

    Cheers,
    Sebastian

    def iter_shaders(node):
        """Credit belongs to Ferdinand from the Plugincafe. I added only the part with the material and First Shader checking.
    
    Yields all descendants of ``node`` in a truly iterative fashion.
    
        The passed node itself is yielded as the first node and the node graph is
        being traversed in depth first fashion.
    
        This will not fail even on the most complex scenes due to truly
        hierarchical iteration. The lookup table to do this, is here solved with
        a dictionary which yields favorable look-up times in especially larger
        scenes but results in a more convoluted code. The look-up could
        also be solved with a list and then searching in the form ``if node in
        lookupTable`` in it, resulting in cleaner code but worse runtime metrics
        due to the difference in lookup times between list and dict collections.
        """
        if not node:
            return
    
        # The lookup dictionary and a terminal node which is required due to the
        # fact that this is truly iterative, and we otherwise would leak into the
        # ancestors and siblings of the input node. The terminal node could be
        # set to a different node, for example ``node.GetUp()`` to also include
        # siblings of the passed in node.
        visisted = {}
        terminator = node
    
        while node:
    
            if isinstance(node, c4d.Material) and not node.GetFirstShader():
               break
            
            if isinstance(node, c4d.Material) and node.GetFirstShader():
                node = node.GetFirstShader()
            
            # C4DAtom is not natively hashable, i.e., cannot be stored as a key
            # in a dict, so we have to hash them by their unique id.
            node_uuid = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
            if not node_uuid:
                raise RuntimeError("Could not retrieve UUID for {}.".format(node))
    
            # Yield the node when it has not been encountered before.
            if not visisted.get(bytes(node_uuid)):
                yield node
                visisted[bytes(node_uuid)] = True
    
            # Attempt to get the first child of the node and hash it.
            child = node.GetDown()
    
            if child:
                child_uuid = child.FindUniqueID(c4d.MAXON_CREATOR_ID)
                if not child_uuid:
                    raise RuntimeError("Could not retrieve UUID for {}.".format(child))
    
            # Walk the graph in a depth first fashion.
            if child and not visisted.get(bytes(child_uuid)):
                node = child
    
            elif node == terminator:
                break
    
            elif node.GetNext():
                node = node.GetNext()
    
            else:
                node = node.GetUp()
    
    posted in Cinema 4D SDK •
    RE: Script: Connect + Delete all subdivision surface objects

    Hi @derekheisler,

    there are multiple reasons why your script isn't catching all of you SDS objects. Number one is that you only take the top level of objects into account. doc.GetObjects() does not account for nested objects that live as children under some other objects. So one solution here is to write a custom function that traverses the complete document i.e. not only the top level objects but every child, sibling, grandchild and so on.

    Number two is in Cinema there is no symbol c4d.Osubdiv. The correct symbol is c4d.Osds. ChatGPT knows and can do a lot, but not everything. ;-)

    One additional suggestion. While it can be comfortable to simply use c4d.CallCommand, for proper undo handling you should avoid it and instead use e.g. node.Remove() if you want to delete some object.

    Having said all of that, find below a script that finds all SDS objects gets a copy and of them, bakes them down and inserts the new polygon objects into your document.

    I left out the part to set back the objects position back for you to find out.

    Cheers,
    Sebastian

    import c4d
    
    def get_next(node):
        """Return the next node from a tree-like hierarchy."""
        
        if node.GetDown():
            return node.GetDown()
        
        while not node.GetNext() and node.GetUp():
            node = node.GetUp()
            
        return node.GetNext()
    
    def iter_tree(node):
        """Iterate a tree-like hierarchy yielding every object starting at *node*."""
        
        while node:
            yield node
            node = get_next(node)
            
    def iter_sds_objects(doc):
        """Iterate a tree-like hierarchy yielding every +
        SDS object starting at the first object in the document.
        """
        
        is_sds = lambda node: node.CheckType(c4d.Osds)
        node = doc.GetFirstObject()
        
        for obj in filter(is_sds, iter_tree(node)):
            yield obj
    
    
    def join_objects(op, doc):
        """Join a hierarchy of objects and return them as a single polygon."""
        
        settings = c4d.BaseContainer()
    
        res = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_JOIN,
            list=[op],
            mode=c4d.MODELINGCOMMANDMODE_ALL,
            bc=settings,
            doc=doc
        )
        
        if not isinstance(res, list) or not res:
            return
        
        res = res[0]
        return res.GetClone()
    
    
    # Main function
    def main():
        
        doc.StartUndo()
        
        null = c4d.BaseObject(c4d.Onull)
        
        tempdoc = c4d.documents.BaseDocument()
        tempdoc.InsertObject(null)
        
        for sds in iter_sds_objects(doc):
            clone = sds.GetClone()
            clone.InsertUnderLast(null)
            
        joined = join_objects(null, tempdoc)
        
        doc.InsertObject(joined)
        doc.AddUndo(c4d.UNDOTYPE_NEW, joined)
        
        doc.EndUndo()
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    
    posted in Cinema 4D SDK •

    Latest posts made by HerrMay

    RE: Dynamically changing custom icon of TagData Plugin

    Hi @ferdinand,

    alrighty, understood. I know you don't have either the time nor the permission to go on deep bug hunting tours. So no worries there. :)

    I stripped down the code to the bare minimum. That way it should be easier.

    Please find below the new project.

    DoNothingTag.zip

    To mimic what I've down please follow these steps. Tested in 2023.2.1

    1. Install the "Do Nothing Tag" Plugin from the ZIP-Archive.

    2. Create some objects. Doesn't matter what kind.

    3. Select all of these objects and apply the "Do Nothing Tag" from the extensions submenu inside the tag menu.

    4. You see the topmost object of the selection gets the red icon while the others get the green one.

    I hope I could be more helpful with these informations. If not, don't hesitate to tell me what I could provide additionally.

    Thank you,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Dynamically changing custom icon of TagData Plugin

    Hi @ferdinand,

    ah stupid me. I forgot to remove the GetDEnabling part. Didn't mean to bring that along.

    My problem is not so much about enabling or disabling the node(s) but more the fact that some nodes are simply getting the "wrong" icon status when the tag is apllied from the menu.

    Try to comment def CoreMessage(self, id, bc) in the TagTestMessageHandler class and then reload the plugin and apply the tag to your object selection again. You should see that the topmost object gets the red icon while the others get the expected green one.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    Dynamically changing custom icon of TagData Plugin

    Hi guys,

    I have a tag plugin which makes use of c4d.MSG_GETCUSTOMICON stole the code from here to dynamically change its icon based on a parameter of the tag.

    While this is all fine and dandy, I encountered the problem that this only works if I add the tag to a single object. The minute I have multiple objects selected and add my tag plugin to them, the first (or last, depending how you want to see it) always loads the wrong icon while the other objects get the correct one. See pictures below.

    Wrong behaviour:
    Not_Working.jpg

    Expected behaviour:
    Working.jpg

    My feeling is, that c4d.MSG_MENUPREPARE is the culprit here and only gets called for a single instance of the tag. Not sure though.

    To circumvent this I implemented a c4d.plugins.MessageData plugin which reacts to a c4d.SpecialEventAdd() call I send every time c4d.MSG_MENUPREPARE is called received in the tag plugin. So in essence I delegate the work to the MessageData plugin which is now responsible to set the parameter on the tag plugin instances.

    Long story short - it works. :D
    But I wonder if this is "the right" way to do it. As it feels a little bit clunky and also somehow overkill for such a small feature. Imagine having a couple of plugins which all implement some similar feature. All of these plugins would then need a MessageData plugin which communicates with them.

    So, to finish this of. Could someone tell me if this is the intended approach or if I#m missing something here and doing things unnecessarily complicated?

    I attached the project so that its easier to follow.

    TagTest.zip

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Nesting Pivot object causes parent to shoot up into space

    Hi @ferdinand,

    Eureka! Of course, how simple. :D
    This bevaiour is just what I wanted all long. Man, sometimes you don't see the hand in front of your face.

    Thanks for helping me @ferdinand. You're simply golden.

    P. S. I should really internalize these damn matrices already. :D

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Nesting Pivot object causes parent to shoot up into space

    Hi @ferdinand,

    as always - thanks for your explanation. Thats what I thought, I got myself in a race condition there. I feel more and more stupid with these questions I ask. :D

    However, I wonder what could be a viable solution then? I mean, there must be a better way, right? As one simply can not stop a user from nesting the pivot object under the camera.

    I hacked that rig together because I thought it would be nice to have a tag based solution for an orbit camera. Instead of always nesting various null objects to drive rotations and what not.

    I thought about cloning that pivot into a virtual document and reading its matrix from there and use that as a pivot matrix. Didn't make much difference. The race condition was still there, though not as hefty.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    Nesting Pivot object causes parent to shoot up into space

    Hello guys,

    I have a python tag that sits on a plane old camera object. The tag has some user data controls for position, rotation etc. along with the option to use a custom pivot object (e.g. c4d.BaseObject(c4d.Onull) ) to rotate the camera around.

    If no custom pivot object is supllied I use the world origin i.e. a unit matrix c4d.Matrix() as the pivot.

    While this all works I noticed some odd behaviour when I nest the custom pivot object as a child object of the camera. Things start to get really weird then. I move the nested pivot object and the camera object starts flying all over the place.

    I suspect that I either ran into some kind of logical limitation or that my math is not correct.

    While I know that my question is probably way beyond support I still wonder if someone could be so kind and spare some time to help me out here.

    Please find below the code I'm using along with the scene file.

    Cheers,
    Sebastian

    Python_OrbitCamera.c4d

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument # The document evaluating this tag
    op: c4d.BaseTag # The Python scripting tag
    flags: int # c4d.EXECUTIONFLAGS
    priority: int # c4d.EXECUTIONPRIORITY
    tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system
    thread: Optional[c4d.threading.BaseThread] # The thread executing this tag
    
    def main() -> None:
        # Called when the tag is executed. It can be called multiple time per frame. Similar to TagData.Execute.
        # Write your code here
    
        pivot = op[c4d.ID_USERDATA,1]
    
        pivot_rotation_heading = op[c4d.ID_USERDATA,3]
        pivot_rotation_pitch = op[c4d.ID_USERDATA,4]
        pivot_rotation_banking = op[c4d.ID_USERDATA,5]
    
        camera_position_x = op[c4d.ID_USERDATA,13]
        camera_position_y = op[c4d.ID_USERDATA,17]
        camera_position_z = op[c4d.ID_USERDATA,18]
        camera_rotation_heading = op[c4d.ID_USERDATA,6]
        camera_rotation_pitch = op[c4d.ID_USERDATA,7]
        camera_rotation_banking = op[c4d.ID_USERDATA,8]
    
        camera = op.GetObject()
    
        if not camera or not camera.CheckType(c4d.Ocamera):
            return
    
        # Get the global matrix of the pivot.
        # If there is no pivot object supplied use the unit matrix.
        pivot_matrix = pivot.GetMg() if pivot is not None else c4d.Matrix()
    
        # Construct a matrix to set our camera object to.
        camera_matrix = (
    
            pivot_matrix *
            c4d.utils.MatrixRotY(pivot_rotation_heading) *
            c4d.utils.MatrixRotX(pivot_rotation_pitch) *
            c4d.utils.MatrixRotZ(pivot_rotation_banking) *
    
            c4d.utils.MatrixMove(
                c4d.Vector(camera_position_x, camera_position_y, camera_position_z)
            ) *
    
            c4d.utils.MatrixRotY(camera_rotation_heading) *
            c4d.utils.MatrixRotX(camera_rotation_pitch) *
            c4d.utils.MatrixRotZ(camera_rotation_banking)
        )
    
        camera.SetMg(camera_matrix)
    posted in Cinema 4D SDK •
    RE: How to add custom menu to take manager

    Hi @i_mazlov,

    thank you for your reply. :)

    Yes, I thought as much. Too bad the take manager menu is not publicy exposed.

    And while I understand that Maxon as well as the community are not big fans of every plugin messing around with menu insertions, it still is a pitty.

    One can e.g. insert plugins into the menu of the material manager as done so by "CV-Swim".

    IMHO it should pose no problem when done carefully with UX in mind while doing so. Especially when one "only" appends entries to a menu.

    Nethertheless thank you for confirmation.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: How to get the Interface language of c4d

    Hi @chuanzhen,

    to get a specific installed language via python you can use c4d.GeGetLanguage(index).

    When you want to retrieve all installed langauges you must iterate them yourself.

    See the code example below. Meant to be executed from the script manager.

    Cheers,
    Sebastian

    from typing import Optional, Generator
    import c4d
    
    doc: c4d.documents.BaseDocument  # The active document
    op: Optional[c4d.BaseObject]  # The active object, None if unselected
    
    
    def iter_language(start: int=0) -> Generator[dict, None, None]:
        """Yield all installed languages.
        
        Paramameters 
        ------------
        start
            int: The index to start with
            
        Returns
        -------
            Generator[dict]
            
        Example
        -------
            >>> for language in iter_language():
                    print(language)
                    
            {'extensions': 'en-US', 'name': 'English', 'default_language': True}
        """
    
        while True:
            
            lang = c4d.GeGetLanguage(start)
            
            if lang is None:
                break
    
            yield lang
            start += 1
    
    
    def main() -> None:
        # Called when the plugin is selected by the user. Similar to CommandData.Execute.
        
        for language in iter_language():
            print(language)
    
    """
    def state():
        # Defines the state of the command in a menu. Similar to CommandData.GetState.
        return c4d.CMD_ENABLED
    """
    
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK •
    How to add custom menu to take manager

    Hi guys,

    I wonder if it is possible to add a command plugin via a custom menu to the menu of the take manager? Similar to how one can add a custom menu to the material manager for example.

    Cheers,
    Sebastian

    posted in Cinema 4D SDK •
    RE: Script: Connect + Delete all subdivision surface objects

    Hi @derekheisler,

    there are multiple reasons why your script isn't catching all of you SDS objects. Number one is that you only take the top level of objects into account. doc.GetObjects() does not account for nested objects that live as children under some other objects. So one solution here is to write a custom function that traverses the complete document i.e. not only the top level objects but every child, sibling, grandchild and so on.

    Number two is in Cinema there is no symbol c4d.Osubdiv. The correct symbol is c4d.Osds. ChatGPT knows and can do a lot, but not everything. ;-)

    One additional suggestion. While it can be comfortable to simply use c4d.CallCommand, for proper undo handling you should avoid it and instead use e.g. node.Remove() if you want to delete some object.

    Having said all of that, find below a script that finds all SDS objects gets a copy and of them, bakes them down and inserts the new polygon objects into your document.

    I left out the part to set back the objects position back for you to find out.

    Cheers,
    Sebastian

    import c4d
    
    def get_next(node):
        """Return the next node from a tree-like hierarchy."""
        
        if node.GetDown():
            return node.GetDown()
        
        while not node.GetNext() and node.GetUp():
            node = node.GetUp()
            
        return node.GetNext()
    
    def iter_tree(node):
        """Iterate a tree-like hierarchy yielding every object starting at *node*."""
        
        while node:
            yield node
            node = get_next(node)
            
    def iter_sds_objects(doc):
        """Iterate a tree-like hierarchy yielding every +
        SDS object starting at the first object in the document.
        """
        
        is_sds = lambda node: node.CheckType(c4d.Osds)
        node = doc.GetFirstObject()
        
        for obj in filter(is_sds, iter_tree(node)):
            yield obj
    
    
    def join_objects(op, doc):
        """Join a hierarchy of objects and return them as a single polygon."""
        
        settings = c4d.BaseContainer()
    
        res = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_JOIN,
            list=[op],
            mode=c4d.MODELINGCOMMANDMODE_ALL,
            bc=settings,
            doc=doc
        )
        
        if not isinstance(res, list) or not res:
            return
        
        res = res[0]
        return res.GetClone()
    
    
    # Main function
    def main():
        
        doc.StartUndo()
        
        null = c4d.BaseObject(c4d.Onull)
        
        tempdoc = c4d.documents.BaseDocument()
        tempdoc.InsertObject(null)
        
        for sds in iter_sds_objects(doc):
            clone = sds.GetClone()
            clone.InsertUnderLast(null)
            
        joined = join_objects(null, tempdoc)
        
        doc.InsertObject(joined)
        doc.AddUndo(c4d.UNDOTYPE_NEW, joined)
        
        doc.EndUndo()
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    
    posted in Cinema 4D SDK •