Navigation

    • Register
    • Login
    • Search
    1. Home
    2. Manuel
    Manuel

    Manuel

    @Manuel

    337
    Reputation
    1272
    Posts
    437
    Profile views
    2
    Followers
    0
    Following
    Joined Last Online
    Website developers.maxon.net/ Location France

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

    Best posts made by Manuel

    RE: Add/Remove Groups of User Data

    Hello,

    After some research in the forum, i've found this post that show how to react to a button pressed.

    With that, it juste a matter of using AddUserData and his friend RemoveUserData

    Let me try to explain a bit more what i've done.
    In the UserData tab, i've a got a group that will be used as a container, so i can add or remove thing inside without touching other userdata. I just browse through all user data using GetUserDataContainer and check the parent of the fields with DESC_PARENTGROUP

    I'm pretty sure you will go through this code easily but if you got any questions, feel free to ask.

    i'm adding the file so you can play with the code directly
    AddRemoveGroup.c4d

    And the code. At the top, i've got the ID of both button and the group that will serve as a container.

    import c4d
    
    
    # the group id where all the fiels will be created.
    addButtonID = 1
    removeButtonID = 2
    mainGroupID  = 3
    
    
    
    def AddGroupOfUserData():
        """
        Creating the group with differents field.
        Called by button Add
        """
        obj = op.GetObject() # get the object where we want the user data
        parentGroup = obj.GetUserDataContainer()[mainGroupID][0]
        newGroup = CreateGroup(parentGroup)
    
        nameString = AddString(newGroup, "Name")
        check = AddBool (newGroup, "Use Sound")
        timeField = AddTime(newGroup, "StartTime")
        AddSoundPath(newGroup, "Sound")
        
        #as function are returning the id of the userdata we can change their value
        obj[nameString] = "Default value for the string"
        #Set the bool to tru by default
        obj[check] = True
        #set the time to 0 
        obj[timeField] = c4d.BaseTime(0)
    
    def RemoveGroupOfUserData():
        """
        this will iterate trough all group and remove the last one
        including his children
        """
        obj = op.GetObject() # get the object where we want the user data
        lastGroupID, count = GetLastGroupID()
    
    
        if lastGroupID > -1:
            # we find a group so let remove all the child of that group we found
            for descID, bc in op.GetObject().GetUserDataContainer():
    
                if IsChildOfGroup(bc, lastGroupID):
                   obj.RemoveUserData(descID)
                   del(obj[descID]) #remove the information in the basecontainer
    
            obj.RemoveUserData(lastGroupID)
            del(obj[lastGroupID])
            obj.Message(c4d.MSG_UPDATE)
    
    
    def IsChildOfGroup(bc, groupID = mainGroupID):
        """
        Check if the parent is the same id of groupID
        """
        if bc[c4d.DESC_PARENTGROUP].GetDepth() > 1:
            if bc[c4d.DESC_PARENTGROUP][1].id == groupID:
                return True
        return False
    
    
    def GetLastGroupID(parent = None):
        """
        get the last group in the user data
        """
        groupID = -1
        count = 0
        for descID, bc in op.GetObject().GetUserDataContainer():
            if descID[1].dtype == c4d.DTYPE_GROUP:
                if IsChildOfGroup(bc):
                        count += 1
                        groupID = descID[1].id
    
        return groupID, count
    
    
    def CreateGroup(parent, trackNumber = -1):
        """
        this will create a group in the user data below "myGroup" with all the different data needed
        """
        if trackNumber < 0:
            # we must calculate the track number
            lastGroupID, trackNumber = GetLastGroupID()
    
        #Get the default container for the group type
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_GROUP)
        longName  = "Track " + str(trackNumber)
        shortName = "Track"  + str(trackNumber)
    
        bc[c4d.DESC_NAME] = longName
        bc[c4d.DESC_SHORT_NAME] = shortName
        bc[c4d.DESC_TITLEBAR] = True
        bc[c4d.DESC_GUIOPEN] = False
        bc[c4d.DESC_PARENTGROUP] = parent
        #return the group id added so we can add other element to it
        return op.GetObject().AddUserData(bc)
    
    
    
    def AddBool (group, name):
        """
        this will create a boolean gadget in the group
        """
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_BOOL)
        bc[c4d.DESC_NAME] = name
        bc[c4d.DESC_SHORT_NAME] = name
        bc[c4d.DESC_PARENTGROUP] = group
        return op.GetObject().AddUserData(bc)
    
    
    def AddTime(group, name):
        """
        this will add the field start time for exemple
        """
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_TIME)
        bc[c4d.DESC_NAME] = name
        bc[c4d.DESC_SHORT_NAME] = name
        #bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_TIME
        bc[c4d.DESC_PARENTGROUP] = group
        return op.GetObject().AddUserData(bc)
    
    def AddSoundPath(group, name):
        """
        this will add the field for file
        """
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_FILENAME)
        bc[c4d.DESC_NAME] = name
        bc[c4d.DESC_SHORT_NAME] = name
        bc[c4d.DESC_PARENTGROUP] = group
        return op.GetObject().AddUserData(bc)
    
    
    def AddString(group, name):
        """
        this will add a static string to a group
        """
        bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_STATICTEXT)
        bc[c4d.DESC_NAME] = name
        bc[c4d.DESC_SHORT_NAME] = name
        #bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_STATICTEXT
        bc[c4d.DESC_PARENTGROUP] = group
        return op.GetObject().AddUserData(bc)
    
    
    
    def message(msg_type, data) :
        if msg_type == c4d.MSG_NOTIFY_EVENT:
            event_data = data['event_data']
            if event_data['msg_id'] == c4d.MSG_DESCRIPTION_COMMAND:
                desc_id = event_data['msg_data']['id']
                if desc_id[1].id == addButtonID:
                    AddGroupOfUserData()
                elif desc_id[1].id == removeButtonID:
                    RemoveGroupOfUserData()
                    
    
    
    def main() :
        pass
    
    posted in Cinema 4D SDK •
    RE: Move just the axis of a polygonal object

    Hello @rui_mac

    you don't really need to deal with hierarchy as long as you are using global space.
    you have to come back to local space with the new matrix, by multiplying the points position with the inverse matrix.
    Be sure to also move the axis of the object as @mp5gosu stated and to ask the object to update itself.

            # Retrieves selected object
            op = doc.GetActiveObject()
            if op is None:
                raise TypeError("There's no object selected.")
        
            # Retrieves the matrix of the object
            opmg = op.GetMg()
        
            # Retrieves the point in global space
            pointsGlob = [p * opmg for p in op.GetAllPoints()]
        
            # Starts the undo process (the initial scene to be restored after an undo action)
            doc.StartUndo()
        
            # Notifies a change on the obj
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
        
            # Updates the matrix
            opmg.off = c4d.Vector(0)
            # Updates the axis of the object
            op.SetMg(opmg)
        
            # Notifies the object, that internal data changed and it needs to be updated
            op.Message(c4d.MSG_UPDATE)
        
            # Inverses the matrix to come back to local space
            invopmg = ~opmg
        
            # Retrieves points from global to local space
            newPoints = [p * invopmg for p in pointsGlob]
        
            # Updates the points
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
            op.SetAllPoints(newPoints)
        
            # Ends the undo process (the final scene state)
            doc.EndUndo()
        
            # Updates Cinema 4D
            c4d.EventAdd()
        
    

    Cheers
    Manuel

    posted in General Talk •
    RE: Retrieve the World Rotation of a Selected Edge?

    hello,

    To answer this question if someone want to do it by code.

    import c4d
    from c4d import gui
    
    # Welcome to the world of Python
    
    
    # Main function
    def main():
        #get the selected object
        op = doc.GetActiveObject()
        if not op:
            raise TypeError("there's no object selected")
        if not op.IsInstanceOf(c4d.Opolygon):
            raise TypeError("obj should be a polygon object")
    
        #setup neighbor
        nbr = c4d.utils.Neighbor()
        nbr.Init(op)
    
        #get the selected edge
        bs = op.GetSelectedEdges(nbr,c4d.EDGESELECTIONTYPE_SELECTION)
        
    
        #maximum number of edge should be number of polygon * 4
        sel = bs.GetAll(op.GetPolygonCount() * 4)
        
        #get all polygon
        vadr = op.GetAllPolygons()
        #init point a and b index to -1 for error check
        a = b = -1
        #initialise a counter to check in sel array.
        countEdge = 0
        
        #we can check now every edge to find the corresponding points. So for each polygon
        for i in xrange(op.GetPolygonCount()):
            #get the polygon information
            pli = nbr.GetPolyInfo(i)
    
            for side in xrange(4): # Test all 4 sides of a polygon
                # Only proceed if edge is marked
                # and edge really exists (for triangles side 2 from c..d does not exist as c==d)
    
                if pli["mark"][side] or (side==2 and vadr[i].c == vadr[i].d): 
                    continue 
                
                #if the edge is marked as selected in our sel array
                if sel[countEdge]:
                    if side==0:
                        a=vadr[i].a; b=vadr[i].b
                    elif side==1:
                        a=vadr[i].b; b=vadr[i].c
                    elif side==2:
                        a=vadr[i].c; b=vadr[i].d
                    elif side==3:
                        a=vadr[i].d; b=vadr[i].a
                countEdge +=1
    
        if a == -1 or b == -1:
            raise ValueError("points index can't be negatives")
        
        #get all points array 
        points = op.GetAllPoints()
    
        
        #Get the direction of the edge in global coordinate (multiply the points'vector by the matrix of the object)
        opmg = op.GetMg()
        #i've picked a direction from point a to point b so the angle could need to be reverted.
        dirVector = points[b]*opmg - points[a]*opmg
        #normalize a vector is often a good idea.
        dirVector.Normalize()
        
        #transform the vector to hpb note the result is in radian
        hpb = c4d.utils.VectorToHPB(dirVector)
        
        #transform radian to degree
        hpb.x = c4d.utils.RadToDeg(hpb.x)    
        hpb.y = c4d.utils.RadToDeg(hpb.y)
        hpb.z = c4d.utils.RadToDeg(hpb.z)
    
        print "the edge is poiting to",dirVector,"the corresponding angle is", hpb
    
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Cheers
    Manuel

    posted in Cinema 4D SDK •
    RE: Modifying or Adding Script Directory?

    Hello,

    On windows, you can create an environment variable named C4D_SCRIPTS_DIR more informations in the doc
    The doc say : Multiple paths can be separated using a semicolon (;) but it's seem buggy at the moment, i have to investigate a bit further and i'll be back if i found something.

    On OSX you don't have environment variables but you can create a Symbolic Link (not an alias). You have to use the Terminal to do so (ln -s /<source>/myscript.py /<preferences path>/library/scripts) This will create a link in your scripts directory.
    I've tried to link from directory to directory but it doesn't work.

    Cheers
    Manuel

    posted in Cinema 4D SDK •
    RE: LoadFile(Filename file) does not work for layouts.

    Several things here,

    your path on windows should use backslash (\) and not slash (/)
    and you should have two.

    "C:\\Program Files\\MAXON\\Cinema 4D R20 Education\\library\\layout\\myLayout.l4d"
    

    A better way to do this is to use operator so your path will be compatible with all OS.

    // Create a filename to point to "Application directory/library/layout/Animate.l4d"
    const Filename file = GeGetStartupPath() + Filename("library") + Filename("layout") + Filename("Animate.l4d");
    // load the layout.
    Bool result = LoadFile(file);
    // check the result.
    if (!result)
    	DiagnosticOutput("can't load the layout");
    		
    

    I've tested this code in PluginMessage() using C4DPL_COMMANDLINEARGS and C4DPL_PROGRAM_STARTED

    Cheers
    Manuel

    posted in Cinema 4D SDK •
    RE: Set Use As Render View

    hello,

    For your next threads, please help us keeping things organised and clean.

    • Q&A New Functionality.
    • How to Post Questions especially the tagging part.

    I've added the tags and marked this thread as a question so when you considered it as solved, please change the state 🙂

    There's no function to set the viewport used for rendering.

    Why can't you use CallCommand ? (even if in this case the command is the same what ever the active viewport is)

    you can change it in the BaseContainer of the document. The symbol is not exposed so you have to use the ID itself.

        #DOCUMENT_RVIEW 10001
        bc = doc.GetDataInstance()
        print bc[10001]
        bc[10001] = 1
    

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: Use a list or array for data?

    Hello,

    The first thing that come to mind is a for loop. But you need to get informations in userData that are stored in your obj in a chaotic way.

    What i suggest here, is to create those userdata in a more organized way (maybe with a script) so can easily create a loop in that case.

    in pseudo code it could be something like this at the end.

    tracks = obj.GetCTracks()
    
    for index, track in enumerate(tracks):
    	obj[c4d.ID_USERDATA, index * 4  + 0] = track.GetName()
    	track[c4d.CID_SOUND_ONOFF] = obj[index*4 + 1]
            track[c4d.CID_SOUND_START] = obj[index*4 + 2]
            track[c4d.CID_SOUND_NAME]  = obj[index*4 + 3]
    
    

    Let me know what do you think about it ?

    Cheers

    posted in General Talk •
    RE: Buttons ids

    Hello,

    there's no real list where you can find easily those IDs.

    @m_adam came with this idea :
    open the Script log (menu script > script log) and click the button you want to call.
    in the console you will see something like

     c4d.CallButton(tool(), c4d.MDATA_NEWTRANSFORM)
    

    so you know that the ID is c4d.MDATA_NEWTRANSFORM

    Another place to look for, is the folder ressource/modules where you can find the files .res that build the ui. (using your OS search function will help a lot) Openning those file you will find the information

    BUTTON MDATA_NEWTRANSFORM	{ FIT_H; }
    

    and you can maybe open the file with the same name with the extention .h where you will find the id

    MDATA_NEWTRANSFORM							= 701,   // Button
    

    Other than that, there are the enums on the documentation like this page but it's not really "user friendly"

    Let me know.

    Cheers
    Manuel

    posted in Cinema 4D SDK •
    RE: is there a market to sell my plugin?

    Hello,

    There's no official market for Cinema 4D's plugins own by Maxon.

    But, as an old plugins seller myself, i can try to give you some hints and share my experience.

    If you want to sell your plugins, you have several options and some legal part to consider.

    You must collect VAT for example. As a European, things can be different depending on witch country your client is from. I'm far to be a specialist, and there is some tools that can handle those things for you. Don't be afraid of legal part.

    1 - You can run your own shop by having your own provider, using "simple tools" like
    Wordpress + WooCommerce. If you add plugins to handle VAT and other legal stuff, this can be a breeze. You have your own Design, you can handle serial numbers the way you want, you have total control.

    2 - Using a market place ( Aescripts or Gumroad for exemple) have some advantages :

    • they will manage the legal stuff for you,
    • they help you if you got any question
    • you can focus on your development
    • they will generate a lot more traffic and your tools will be more visible (people can come for another plugins and check what they have in the catalog)

    You also have to consider the tax they will charge of course. They will varie form plateform to plateform. Don't hesitate to contact them.

    Final point, DO IT, it's fun, you will have some nice feedback, having people, sending work they have done with your tools, from all over the world is just a blast.

    Cheers
    Manuel

    posted in General Talk •
    RE: Make Objects Editable (Streamlined)?

    hello,

    seem that you forgot brackets on your list of object.

    list = [op]
    

    watch out where you are running those command, some operation have to be executed in the main thread
    on a script it should look like this

    import c4d
    from c4d import gui
    from c4d import utils
    # Welcome to the world of Python
    
    
    # Script state in the menu or the command palette
    # Return True or c4d.CMD_ENABLED to enable, False or 0 to disable
    # Alternatively return c4d.CMD_ENABLED|c4d.CMD_VALUE to enable and check/mark
    #def state():
    #    return True
    
    # Main function
    def main():
    
        op = doc.GetActiveObject()
    
        if not op: 
            raise TypeError("there's no object selected")
        res = utils.SendModelingCommand(
                                  command = c4d.MCOMMAND_MAKEEDITABLE,
                                  list = [op])
        #check if res is a list 
        if res is False:
            raise TypeError("make editable didn't worked")
        elif res is True:
            print "make editable should not return true"
        elif isinstance(res, list):
            doc.InsertObject(res[0])
        
        
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Cheers
    Manuel

    posted in Cinema 4D SDK •

    Latest posts made by Manuel

    RE: Python Tag how to hide a mogragh object

    hi,

    well the main problem is not where but when to change that MoData. Specially in python i see no solution.
    With c++ you could find a hacky way of doing it but i did not tried.

    One way of doing it would be to create a setup inside the message function of the tag (so you are on the main thread). Creating a formula effector, hide it inside the object manager and use the formula to hide the object you want. something like id!=2&id!=3 will work. Of course, you need to include that effector to the cloner's effector list. I would not call that a "good solution", but it will work.

    Cheers,
    Manuel.

    posted in Cinema 4D SDK •
    RE: How to get animation tracks of Redshift material using python in Cinema 4D 2023

    hi,

    this should not be different than any node inside c4d (including scene node).
    Tracks are stored inside a Baselist2D object created and linked to any node.
    You must get the path of the node and retrieve the object using GetBaseListForNode.

    Once you got the object, it will work as expected with GetCTracks for example. I included in the example below how to get directly the descID of a port and the corresponding track.

    from typing import Optional
    import c4d
    import maxon
    
    # for this example we are creating keys for the color and the intensity parameter of the BSDF node inside
    # a standard/physical material
    
    
    doc: c4d.documents.BaseDocument  # The active document
    op: Optional[c4d.BaseObject]  # The active object, None if unselected
    
    def main() -> None:
        # Get the active material
        mat = doc.GetActiveMaterial()
        if mat is None:
            raise ValueError("There is no selected BaseMaterial")
    
        # Retrieve the reference of the material as a node Material.
        nodeMaterial = mat.GetNodeMaterialReference()
        if nodeMaterial is None:
            raise ValueError("can't retrieve nodeMaterial reference")
    
        # Retrieve the current node space Id
        nodespaceId = c4d.GetActiveNodeSpaceId()
        # Get the nimbusref, this is useful to retrieve the DescID of a parameter.
        nimbusRef =  nodeMaterial.GetNimbusRef(nodespaceId)
    
    
        graph = nodeMaterial.GetGraph(nodespaceId)
        if graph is None:
            raise ValueError("can't retrieve the graph of this nimbus ref")
    
        root = graph.GetRoot()
    
        # Delegate function that will retrieve the tracks for a port.
        def PrintInfo(port):
            # Get the path of the node. We are looking for port that must have "color" somewhere in their path.
            portPath = port.GetPath()
            if "color" in portPath:
                # Retrieve information of the True node this port is part of
                # For each GraphNode (node and port) a BaseList2D is created.
                parentNode = port.GetAncestor(maxon.NODE_KIND.NODE)
                parentNodePath = parentNode.GetPath()
    
                # Retrieve the descID corresponding to this port.
                portDescID =  nimbusRef.GetDescID(portPath)          
                # Retrieve the BaseList2D object corresponding to the True Node and NOT the port.
                obj = nodeMaterial.GetBaseListForNode(nodespaceId, parentNodePath)
                if obj is None:
                    return False
                # As this parameter is a color, the parameter will have one track per sub channel.
                # The last level of the descID must be added.
                portDescID.PushId(c4d.DescLevel(c4d.COLORA_R))
                # Find the right track with this DescID on the True Node Baselist2D object
                track = obj.FindCTrack(portDescID)
                print (track)
    
            return True
    
        nodesList = []
        root.GetChildren(nodesList, maxon.NODE_KIND.NODE)
        for node in nodesList:
            # Another way of printing all the track a node have. Retrieve the object corresponding to the node.
            # Once the right object is founded, track works the same as any regular c4d object.
            # nodePath = node.GetPath()
            # obj = nodeMaterial.GetBaseListForNode(nodespaceId, nodePath)
            # for track in obj.GetCTracks():
            #     print (track)
            node.GetInputs().GetChildren(PrintInfo, maxon.NODE_KIND.PORT_MASK)
    
    """
    def state():
        # Defines the state of the command in a menu. Similar to CommandData.GetState.
        return c4d.CMD_ENABLED
    """
    
    if __name__ == '__main__':
        main()
    

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: Save Project with Assets not updating the maxon::Url of node textures in R26 and 2023

    Hi,

    sorry i am a bit lost with your questions and i need a bit of clarification on some aspect.

    If i understand correctly, you are implementing your own node space with your own texture node and the url is note updated when you use "save project with asset' i need more investigation time on that sorry.

    About the Asset Inspector, if i am correct, this was a bug but fixed for a coming release. What i do not understand is that you are saying that the bitmap related code is the same but working in r21-25. If i am correct, the Node Api was not available for public with r21. So i am confused here, what code are you talking about?

    g_developerNodeEditorFunctions have been removed and nothing is replacing it. The Edit Resource... command will be available only for groups. It will also be displayed when you right click on the node editor itself (the root) but will do nothing as the root is not supposed to be edited.

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: Python Tag how to hide a mogragh object

    hi,

    I got the feeling it will not work but i need to investigate a lot more. Either you are modifying the MoData too early, and the cloner will erase/initialise them after you modified them, or you are modifying them after the cloner have generated the clones.

    That is why we have the effectors to modify the Modata before the cloner have generated the clones.

    Is there any reason you want to use a tag instead of an effector?

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: Object Generator as Particles

    Hi,

    sorry, the display of this tab is hardcoded an only available for a couple of builtin objects.

    What you can do is have a Matrix object in object mode that point to your generator, the rs tag being applied to the matrix object.

    You can also create that setup inside your generator. Below, i am using a cube but that could be your PointObject instead.

    Keep in mind that python is slow, using it for point cloud or anything related to particles might be super slow.

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument # The document evaluating this python generator
    op: c4d.BaseObject # The python generator
    hh: Optional["PyCapsule"] # A HierarchyHelp object, only defined when main is executed
    
    def main() -> c4d.BaseObject:
    .
        cube =  c4d.BaseObject(c4d.Ocube)
        parent = c4d.BaseObject(c4d.Onull)
        cube.InsertUnder(parent)
        matrix = c4d.BaseObject(c4d.Omgmatrix)
        matrix.InsertUnder(parent)
        matrix[c4d.ID_MG_MOTIONGENERATOR_MODE] = c4d.ID_MG_MOTIONGENERATOR_MODE_OBJECT
        matrix[c4d.MG_OBJECT_LINK] = cube
        matrix[c4d.MG_POLY_MODE_] = c4d.MG_POLY_MODE_VERTEX
        rsTag = matrix.MakeTag(1036222)
        rsTag[c4d.REDSHIFT_OBJECT_PARTICLE_MODE] = 2
    
        
        
        return parent
    
    

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: FieldList Enable Clamping

    hi,

    for this, you must use the function SetFlags on the Field data and update the data on the object. The flag that must be set is FIELDLIST_FLAGS::CLAMPOUTPUT

    Unfortunately, the UI do not update, you must deselect and reselect the object. I did not find anything yet to address this issue.
    I used c++ this time to be sure nothing was wrong with python.

    BaseObject* op = doc->GetActiveObject();
    if (!op)
    	return maxon::NullptrError(MAXON_SOURCE_LOCATION);
    
    GeData data;
    op->GetParameter(FIELDS, data, DESCFLAGS_GET::NONE);
    FieldList* fl = static_cast<FieldList*>(data.GetCustomDataType(CUSTOMDATATYPE_FIELDLIST));
    fl->SetFlags(FIELDLIST_FLAGS::CLAMPOUTPUT, false);
    op->SetParameter(FIELDS, GeData(CUSTOMDATATYPE_FIELDLIST, *fl), DESCFLAGS_SET::NONE);
    

    python code just in case

        fl = op[c4d.FIELDS]
        toggle = fl.CheckFlag(c4d.FIELDLIST_FLAGS_CLAMPOUTPUT)
        fl.SetFlags(c4d.FIELDLIST_FLAGS_CLAMPOUTPUT, not toggle)
        op[c4d.FIELDS] = fl
    

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: FieldList Enable Clamping

    hi,

    It is not clear what parameter you want to modify. The code you are sharing is inside GetDDescription i suppose.
    You need to user SetParameter on your node itself. The tricky part is to build the DescID to target the right parameter.
    You need the ID of your fieldList, the ID of the layer on that list and the parameter ID on that layer. All level must be defined as CUSTOMDATATYPE_FIELDLIST.

    You must search for the layer inside your field list and call GetUniqueID on it. This will return the correct ID you must use in your DescLevel. Below a script that will flip/flop the parameter "enable/value" for the first layer in the list.
    7be77196-b02c-4830-8ea6-6fbf8fef22bd-image.png

    I created a python script for r20 and for more recent version

    R20 version

    
    import c4d
    
    def main():
        # Called when the plugin is selected by the user. Similar to CommandData.Execute.
        effector = op
        if effector is None:
            raise ValueError("there is no active objet")
        
        fieldList  = effector[c4d.FIELDS]
        root = fieldList.GetLayersRoot()
        clampLayer = root.GetDown()
        
        # Retriving the unique ID allows to construct the DescID to target the right parameter.
        clampUniqueID = clampLayer.GetUniqueID()
        
        # Building the DescID
        # This ID is composed of three level:
        # the first one define the field list parameter,
        # the second define  the layer, that is why we need its UniqueID,
        # the last level define the parameter in this layer.
        # Note that all DescLevel are of DataType CUSTOMDATATYPE_FIELDLIST
        enableID = c4d.DescID(c4d.DescLevel(c4d.FIELDS, c4d.CUSTOMDATATYPE_FIELDLIST), c4d.DescLevel(clampUniqueID,  c4d.CUSTOMDATATYPE_FIELDLIST),  c4d.DescLevel(c4d.ID_FIELDLAYER_ENABLE_VALUE, c4d.CUSTOMDATATYPE_FIELDLIST))
    
        # Retreving the value using GetParameter on the effector itself.
        value  = effector.GetParameter(enableID, c4d.DESCFLAGS_GET_NONE)
        # Define the oposite value
        effector.SetParameter(enableID, not value, c4d.DESCFLAGS_SET_NONE)
    
    
        c4d.EventAdd()
    if __name__ == '__main__':
        main()
    

    Same example but for more recent version of c4d.

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument  # The active document
    op: Optional[c4d.BaseObject]  # The active object, None if unselected
    
    def main() -> None:
        # Called when the plugin is selected by the user. Similar to CommandData.Execute.
        effector :c4d.BaseObject = op
        if effector is None:
            raise ValueError("there is no active objet")
        
        fieldList :c4d.FieldList = effector[c4d.FIELDS]
        root :c4d.GeListHead = fieldList.GetLayersRoot()
        clampLayer :c4d.modules.mograph.FieldLayer = root.GetDown()
        
        # Retriving the unique ID allows to construct the DescID to target the right parameter.
        clampUniqueID = clampLayer.GetUniqueID()
        
        # Building the DescID
        # This ID is composed of three level:
        # the first one define the field list parameter,
        # the second define  the layer, that is why we need its UniqueID,
        # the last level define the parameter in this layer.
        # Note that all DescLevel are of DataType CUSTOMDATATYPE_FIELDLIST
        enableID :c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.FIELDS, c4d.CUSTOMDATATYPE_FIELDLIST), c4d.DescLevel(clampUniqueID,  c4d.CUSTOMDATATYPE_FIELDLIST),  c4d.DescLevel(c4d.ID_FIELDLAYER_ENABLE_VALUE, c4d.CUSTOMDATATYPE_FIELDLIST))
    
        # Retreving the value using GetParameter on the effector itself.
        value : bool  = effector.GetParameter(enableID, c4d.DESCFLAGS_GET_NONE)
        # Define the oposite value
        effector.SetParameter(enableID, not value, c4d.DESCFLAGS_SET_NONE)
    
    
        c4d.EventAdd()
    if __name__ == '__main__':
        main()
    

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: Displaying tag properties as part of object's properties

    hi,

    if you defined TAG_MULTIPLE when you register your tag, the tag will not be displayed in the attribut manager when you select the object.

    Other than that, i did not found anything special to do.

    With the latest version (i think it was introduced in r25) you have a parameter on each tag to expose the tag on the object ID_EXPOSETAB that must be set to true.

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: select edge points with python

    hi,

    In c4d there is no edge, edges are just defined by being connected between two points.

    After trying a lot of things i finally found something that looks to work.
    It is a combinaison of many ideas. The idea is to count the number of times a point is present on a polygon and the number of times this point is present on a ngon's edge. Make the difference and just keep the point that have a difference of 2 so this point is connected to only two edges.

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument  # The active document
    op: Optional[c4d.BaseObject]  # The active object, None if unselected
    
    # Tries to get the points that are only connected to two edge and part of NGons
    
    
    
    def main() -> None:
        polys = op.GetAllPolygons()
        # GetNgonEdgesCompact will give a list of value for each polygon.
        # Those correspond to a binary value where 1 mean the edge is part of an ngon
        # and 0 mean no. Be careful that for Triangle edge 2 must not be taken into account.
    
        # edge                3   2   1   0
        #                     -   -   -   -   =   0
        #                     -   -   -   x   =   1
        #                     -   -   x   -   =   2
        #                     -   -   x   x   =   3
        #                     -   x   -   -   =   4
        #                     -   x   -   x   =   5
        #                     -   x   x   -   =   6
        #                     -   x   x   x   =   7
        #                     x   -   -   -   =   8
        #                     x   -   -   x   =   9    
        #                     x   -   x   -   =   10
        #                     x   -   x   x   =   11
        #                     x   x   -   -   =   12
        #                     x   x   -   x   =   13
        #                     x   x   x   -   =   14
        #                     x   x   x   x   =   15
    
        ngonEC = op.GetNgonEdgesCompact()
        pointCount = op.GetPointCount()
        
    
        # Array allowing to count, for each point,  the number of time this point is present in an polygon
        # and the number of time this point is present in an ngon edge.
    
        pointNgonDict = {}
        pointPolyDict = {}
        answer = []
        
        for index in range(0, pointCount):
            pointNgonDict[index] = 0
            pointPolyDict[index] = 0
        
        # Prepare a neighbor object so we can check if the edge is marked or not avoiding
        # to count mutiple time an edge
        nb = c4d.utils.Neighbor()
        nb.Init(op)
        
        # For each polygon
        for polyIndex, cPoly in enumerate(polys):
            pli = nb.GetPolyInfo(polyIndex)
            
            # mark those points as part of the polygon
            pointPolyDict[cPoly.a] += 1
            pointPolyDict[cPoly.b] += 1
            pointPolyDict[cPoly.c] += 1
            
            
            if cPoly.IsTriangle():
                # here the edge are really 0, 1, 3 we do not use 2.
                for edgeIndex in [0, 1, 3]:
                    # To avoid counting twice an edge only check those that are marked as false.
                    if pli["mark"][edgeIndex] == False:
                        # If the bit of this edge is set to 1, that mean this edge is on a ngon
                        if ngonEC[polyIndex] & (1 << edgeIndex):
                            p1, p2 = cPoly.EdgePoints(edgeIndex)
                            pointNgonDict[p1] += 1
                            pointNgonDict[p2] += 1
            else:
                # we include the fourth point.
                pointPolyDict[cPoly.d] +=1
                # same as the triangle but with all index 0, 1, 2, 3
                for edgeIndex in [0, 1, 2, 3]:
                    if pli["mark"][edgeIndex] == False:
                        if ngonEC[polyIndex] & (1 << edgeIndex):
                            p1, p2 = cPoly.EdgePoints(edgeIndex)
                            pointNgonDict[p1] += 1
                            pointNgonDict[p2] += 1
    
        # We calculate the difference between those two array and put in the result 
        # array only points index that are
        # in 2 more polygons than the number of edge present on a ngon this point is part of.
        
        for i, (j, k) in enumerate(zip(pointPolyDict.values(), pointNgonDict.values())):
            # Check i the point is at least in one ngon with k > 0
            if k > 0 andj - k == 2:
                answer.append(i)
    
        print (pointNgonDict)
        print (pointPolyDict)
        print(answer)
        
    
    if __name__ == '__main__':
        main()
    

    Cheers,
    Manuel

    posted in Cinema 4D SDK •
    RE: Fieldlist HasContent() GetCount() bug workaround

    hi,

    unfortunately, there is nothing in the SDK that will help you in this regard. The only solution i see is to check the type of the layer and hardcode if that layer can have a link or not. Could be just an array containing the IDs of layers that do not have this link.

    Cheers,
    Manuel

    posted in Cinema 4D SDK •