Navigation

    • Register
    • Login
    • Search
    1. Home
    2. m_adam
    M

    m_adam

    @m_adam

    644
    Reputation
    1194
    Posts
    857
    Profile views
    7
    Followers
    0
    Following
    Joined Last Online

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

    Best posts made by m_adam

    RE: Sampling the falloff of a mograph effector? (python/R19)

    Hi @jenandesign, first of all, welcome in the plugincafe community.

    No worry at all, since it's your first post, but I would like to point you about few rules in order to properly ask questions in this forum for future ones (I've set up this one).

    • How to Post Questions (Especially the tagging system).
    • Q&A New Functionality.

    With that's said. Regarding your question. I get it to work (a bit hacky) but it's working.
    When C4D render a scene, actually the whole scene is cloned in memory and then it renders this one. (It allow a user to still work on C4D while the other scene is rendering).
    So this render scene is read-only, you shouldn't modify at anytime anything in this scene, or you can screw up the render.
    Moreover in a shaderData, in order to optimize memory, only relevant information (aka polygon, UV) are copied. So you are losing your UserData.
    This means you have to find another way to find your effector.
    So the way I find to achieve what you want is to get the document from the object passed to the shader(the rendered document).
    Then in this document, I search for an effector called "Plain" (Not the most robust way but it's working).

    Then here the final code I put on the python color channel of a sketch and toon

    import c4d
    
    def main():
        doc = op.GetDocument()
        effector = doc.SearchObject("Plain")
        if not effector: return c4d.Vector()
        
        eff =  c4d.modules.mograph.C4D_Falloff()
        if not eff: return c4d.Vector()
        eff.InitFalloff(bc=effector.GetData())
    
        eff.SetMg(effector.GetMg())
        eff.SetMode(effector[c4d.FALLOFF_MODE])
    
         # World position currently sampled
        value = eff.Sample(wPnt, False)
        return c4d.Vector(value)
    
    

    And the attached scene where a plain effector drives a mograph effector and the sketch and toon shader.
    0_1542626486830_SketchAndToon.c4d
    0_1542626902040_sketchtoon.jpg

    If you have any questions, please let me know!
    Cheers,
    Maxime!

    posted in Cinema 4D SDK •
    RE: 3rd party APIs into pyp/pypv file

    Hi @merkvilson first of all pip and Niklas localimport module is completely two different things.

    • pip is a python module manager (similar to package manager in unix). This means once it's installed to a python binary, it will be able to manage other modules, download them, install them with all their dependencies and update them and so on.
    • localimport is a python module that let you manage import in a local way while in Cinema 4D by default is global. For more information please read how the import function works in python.
      Meaning if you add a module named xyz to the sys.path from your plugin A and from plugin B you try to import another module named xyz, it will load the one from plugin A (the first one register).

    Now how to install pip?

    1. Downloads https://bootstrap.pypa.io/get-pip.py.
    2. Downloads the c4dpy version matching your Cinema 4D version. (Already included in R21)
    3. Moves c4dpy and get-pip.py in the Cinema 4D folder.
    4. Opens a shell to the Cinema 4D folder:
      • Window: Type cmd in the top path and press Enter.
      • Mac: Open a new shell, then with cd navigate to your Cinema 4D folder. (You can drag and drop the path).
    5. Runs this command line c4dpy get-pip.py. This will execute the get-pip script which will, download and install the pip module.
    6. Now you can start to play with pip c4dpy -m pip install numpy. c4dpy allow to runs any module with the -m argument see c4dpy commandline. in this case I asked to pip to download and install numpy but any other 3rd party compatible with pip can be downloaded and used.

    With that's said we do not provide support for 3rd party modules, so if it does not work with Cinema 4D Python environment, we can't help you.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Setting `coordinate system` using `MCOMMAND_MIRROR`

    Hi @kisaf,

    MCOMMAND_MIRROR is indeed a bit special because when you click on the Apply button from the GUI, it does send a message to some c4d elements, then these elements read MDATA_MIRROR_VALUE, MDATA_MIRROR_SYSTEM and MDATA_MIRROR_PLANE calculates values for MDATA_MIRROR_POINT and MDATA_MIRROR_VECTOR and then call the SendModelingCommand. Unfortunately, there is no way for you to send this message, and the best way to do it is to recalculate the stuff as bellow.

    import c4d
    
    
    def main():
        # Checks if selected object is valid
        if op is None:
            raise ValueError("op is none, please select one object.")
    
        # Defines some general settings, take care MDATA_MIRROR_VALUE, MDATA_MIRROR_SYSTEM and MDATA_MIRROR_PLANE
        settings = c4d.BaseContainer()
        settings[c4d.MDATA_MIRROR_DUPLICATE] = True
        settings[c4d.MDATA_MIRROR_SELECTIONS] = True
        settings[c4d.MDATA_MIRROR_ONPLANE] = True
        settings[c4d.MDATA_MIRROR_WELD] = True
        settings[c4d.MDATA_MIRROR_TOLERANCE] = 0.01
    
        # Corresponds to MDATA_MIRROR_VALUE, MDATA_MIRROR_SYSTEM and MDATA_MIRROR_PLANE
        value = 1000.0
        system = 0 # 0 = Object, 1 = World
        plane = 1 # 0 = XY, 1 = ZY, 2 = XZ
    
        if not 0 <= system < 2:
            raise ValueError("System can only be 0 or 1")
    
        # Buffer position vector
        pos = c4d.Vector()
    
        # Calculates Local (Object) coordinates
        if system == 0:
            globalMatrix = op.GetMg()
            if plane == 0:
                pos = globalMatrix.v3
            elif plane == 1:
                pos = globalMatrix.v1
            elif plane == 2:
                pos = globalMatrix.v2
    
            settings[c4d.MDATA_MIRROR_POINT] = globalMatrix.off + pos * value
            settings[c4d.MDATA_MIRROR_VECTOR] = pos
    
        # Calculates World coordinates
        elif system == 1:
            if plane == 0:
                pos = c4d.Vector(0.0, 0.0, 1.0)
            elif plane == 1:
                pos = c4d.Vector(1.0, 0.0, 0.0)
            elif plane == 2:
                pos = c4d.Vector(0.0, 1.0, 0.0)
    
            settings[c4d.MDATA_MIRROR_POINT] = pos * value
            settings[c4d.MDATA_MIRROR_VECTOR] = pos
    
        # Send the Modeling Operation
        res = c4d.utils.SendModelingCommand(c4d.MCOMMAND_MIRROR,
                                            [op],
                                            c4d.MODELINGCOMMANDMODE_ALL,
                                            settings,
                                            doc)
    
        c4d.EventAdd()
    
    
    if __name__ == "__main__":
        main()
    
    

    Hope it solves your issue, if you have any question, please let me know.
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Get World Position of Obj A's position and Set it to Obj B's position

    So regarding your script why it crashes, it's simply because you make an infinite loop with this code since you add each time something more to iterate.

    for idx in newPos:
        newPos.append(GlobalToLocal(oldObject, idx))
    

    Just to make it cleaner I've called objA and objB.
    Then the needed steps are:

    1. Get World Position from points of objB.
    2. Convert these World Position in the local space of objA.

    So as you may be already aware regarding our Matrix Fundamentals where is how to achieve both operations.

    • Local Position to World Position corresponds to the obj global matrix where the position is locals to multiplied by the local position.
    • World Position to Local Position corresponds to the inverse obj global matrix where the position will be locals multiplied by the world position.

    Finally, before providing with the solution I would like to remind you to please always execute your code in the main function. The main reason is that if you transform your script as a button in the Dialog, every code that is in the global scope of your script is executed for each redraw of the Cinema 4D GUI (which is pretty intensive!). Moreover please always check for objects, if they exist or not try to adopt a defensive programming style in order to avoid any issues.

    import c4d
    
    def main():
        # Declares object variables
        ObjA = doc.SearchObject("A")
        ObjB = doc.SearchObject("B")
    
        # Checks if objects are found
        if ObjA is None or ObjB is None:
            raise ValueError("Can't found a and b object")
    
        # Checks if they are polygon object
        if not isinstance(ObjA, c4d.PolygonObject) or not isinstance(ObjB, c4d.PolygonObject):
            raise TypeError("Objects are not PolygonObject")
    
        # Checks Point count is the same for both obj
        allPtsObjA = ObjA.GetAllPoints()
        allPtsObjB = ObjB.GetAllPoints()
        if len(allPtsObjA) != len(allPtsObjB):
            raise ValueError("Object does not get the same pount count")
    
        # Retrieves all points of B in World Position, by multipling each local position of ObjB.GetAllPoints() by ObjB.GetMg()
        allPtsObjBWorld = [pt * ObjB.GetMg() for pt in allPtsObjB]
    
        # Retrieves All points of B from World to Local Position of ObjA, by multipling each world position of ObjB by the inverse objA.GetMg()
        allPtsObjANewLocal = [pt * ~ObjA.GetMg() for pt in allPtsObjBWorld]
        
        # Sets New points position
        ObjA.SetAllPoints(allPtsObjANewLocal)
        ObjA.Message(c4d.MSG_UPDATE)
        
        # Updates Cinema 4D
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    If you have any questions, please let me know.
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: How to deepcopy DescID?

    Hi @mikeudin, due to how our Classic Python API is built deepcopy does not work since our python objects only store pointer to our internal C++ object and not directly to other Python objects, so deepcopy fail in this regards since, the deepcopy operation, free and try to recreate these data, which is not possible since they are not PyObject but real C++ object but copy.copy does works, but since it's a shallow copy operation it only works for one level (e.g. a dict of dict of DescID will fail).

    For more information about the difference please read Shallow vs Deep Copying of Python Objects.

    However here a couple methods to copy a dict.

    import copy
    dict1 = {'1':dId_one,'2':dId_two}
    dict2 =  copy.copy(dict1)
    
    dict1 = {'1':dId_one,'2':dId_two}
    dict2 =  dict1.copy()
    
    dict1 = {'1':dId_one,'2':dId_two}
    dict2 =  dict(dict1)
    

    Cheers,
    Maxime.

    posted in General Talk •
    RE: Python Script Gui: Use EditSlider Get Float

    Hi @mike, first of all I would like to remind you to read and use the Q&A functionnality.

    Regarding your question, here is an example of a basic GeDialog, which make use to SetFloat to define a range.

    import c4d
    
    class MonDlg( c4d.gui.GeDialog):
        idSlider = 1000
        idButton = 1001
    
        # Create the Layout
        def CreateLayout(self):
            self.AddEditSlider(self.idSlider, c4d.BFH_SCALE|c4d.BFV_SCALE, initw=100, inith=20)
            self.AddButton(self.idButton, c4d.BFH_SCALE|c4d.BFV_SCALE, initw=100, inith=20,name = 'Get Value')	
            return True
    
        # Called after CreateLayout
        def InitValues(self):
            self.SetFloat(self.idSlider, 0.25, min=0.0, max=1.0, step=0.01, min2=0.0, max2=0.0)
            return True
    
        # Called for each interaction from a widget
        def Command(self, id, msg):
            if id == self.idButton:
                print self.GetFloat(self.idSlider)
            
            return True
    
    def main():
        dlg = MonDlg()
        dlg.Open(c4d.DLG_TYPE_MODAL)
    
    if __name__=='__main__':
        main()
    

    If you have any questions please let me know.
    Cheers,
    Maxime!

    posted in Cinema 4D SDK •
    RE: restore weight to BaseContainer and get it Vs Direct get weight from weight Tag

    Hi @chuanzhen actually the problem interested me and I took some personal time on my weekend to do some tests.

    I did 2 test scenes, which was a cube with 163970 points driven by 3 joints
    With your original source code 0.53 fps
    optimize python: 1.09 fps (x2.05)
    C++: 19.36 fps (x36.52)

    And the other one was the same cube but driven by 11 joints.
    With your original source code 0.37 fps
    optimize python: 0.64 fps (x1.73)
    C++: 12.08 (x32.65)

    So here is the optimized python code. As you can see I try to avoid as much as possible doing operation in the nested loop.

    class newskin(plugins.ObjectData):
     
        lasttagdirty = None   #for weight change update
        jointdir = None       #for joint change update
        cachedir = None       #for other bindmesh check
     
        def CheckDirty(self, op, doc):
            #check first run
            if op[c4d.NEWSKINTAG] is None:
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
     
            #check weight change
            if op[c4d.NEWSKINTAG].GetDirty(c4d.DIRTYFLAGS_DATA) != self.lasttagdirty:
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
     
            #check matrix change
            checktime = op[c4d.NEWSKINTAG].GetJointCount()
            cdirty = 0
     
            for i in xrange(checktime):
                cdirty += op[c4d.NEWSKINTAG].GetJoint(i).GetDirty(c4d.DIRTYFLAGS_MATRIX)
     
            if cdirty != self.jointdir or self.cachedir != op[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE):
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
     
            return
     
       
        def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread):
     
            if mod[c4d.NEWSKINTAG] is None or mod[c4d.NEWSKINCHECK] is None:
                return True
     
            tag = mod[c4d.NEWSKINTAG]
            self.lasttagdirty = tag.GetDirty(c4d.DIRTYFLAGS_DATA)  #get current weight tag dirty
            self.cachedir = mod[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE)    #get bindmesh check
            plist = [pos * op_mg for pos in op.GetAllPoints()]
            pcount = op.GetPointCount()  
            jcount = tag.GetJointCount()
            self.jointdir = 0
     
            for m in xrange(jcount):
                joint = tag.GetJoint(m)
                self.jointdir += joint.GetDirty(c4d.DIRTYFLAGS_MATRIX)   #all joint current matrix dirtycount
     
            temp = c4d.Vector()
            for n in xrange(pcount):    #n:point index  
                temp %= temp
     
                for m in xrange(jcount):
                    joint = tag.GetJoint(m)
                    weight = tag.GetWeight(m, n)
     
                    if not weight :
                        continue  
     
                    cjmg = joint.GetMg()
                    jdict = tag.GetJointRestState(m)
                    jmg = jdict["m_bMg"]
                    jmi = jdict["m_bMi"]
     
                    temp += weight * cjmg * jmi * plist[n]  #defrmer global pos
                plist[n] = temp
           
            plist = [~op_mg * pos for pos in plist]
            op.SetAllPoints(plist)
            op.Message(c4d.MSG_UPDATE)
     
            return True
    

    And here the C++ version (compatible R20 only). It uses paralellFor, not sure it's really worth didn't do proper profiling about it but I wanted to try them. Moreover please do not consider my C++ code has fully optimized since you could probably improve it depending on the situation.

    #include "c4d_symbols.h"
    #include "main.h"
    #include "c4d_objectdata.h"
    #include "lib_ca.h"
    
    // Local resources
    #include "oskinmodifier.h"
    #include "maxon/parallelfor.h"
    #include "maxon/basearray.h"
    
    /**A unique plugin ID. You must obtain this from http://www.plugincafe.com. Use this ID to create new instances of this object.*/
    static const Int32 ID_OBJECTDATA_SKINMODIFIER = 1000002;
    
    class SkinModifier : public ObjectData
    {
    	INSTANCEOF(SkinModifier, ObjectData)
    
    public:
    	static NodeData* Alloc() { return NewObj(SkinModifier) iferr_ignore("SkinModifier plugin not instanced"); }
    
    	virtual void CheckDirty(BaseObject *op, BaseDocument *doc);
    	virtual Bool ModifyObject(BaseObject* mod, BaseDocument* doc, BaseObject* op, const Matrix& op_mg, const Matrix& mod_mg, Float lod, Int32 flags, BaseThread* thread);
    
    private:
    
    	BaseList2D* GetBaseLink(BaseObject* op, DescID id, Int excepted);
    	Int lasttagdirty; /// get current weight tag dirty
    	Int jointdir;
    	Int cachedir;
    };
    
    void SkinModifier::CheckDirty(BaseObject *op, BaseDocument *doc)
    {
    	BaseList2D* t = GetBaseLink(op, DescID(NEWSKINTAG), Tweights);
    	BaseList2D* l = GetBaseLink(op, DescID(NEWSKINCHECK), Opolygon);
    	if (!t || !l)
    		return;
    
    	PointObject* linkOp = static_cast<PointObject*>(l);
    	CAWeightTag* tag = static_cast<CAWeightTag*>(t);
    
    	// check first run
    	if (!tag)
    	{
    		op->SetDirty(DIRTYFLAGS::DATA);
    		return;
    	}
    
    	// check weight change
    	if (tag->GetDirty(DIRTYFLAGS::DATA) != lasttagdirty)
    	{
    		op->SetDirty(DIRTYFLAGS::DATA);
    		return;
    	}
    
    	// check matrix change
    	Int cdirty = 0;
    	for (Int i = 0; i < tag->GetJointCount(); i++)
    	{
    		cdirty += tag->GetJoint(i, tag->GetDocument())->GetDirty(DIRTYFLAGS::MATRIX);
    		if (cdirty != jointdir || cachedir != linkOp->GetDirty(DIRTYFLAGS::CACHE))
    		{
    			op->SetDirty(DIRTYFLAGS::DATA);
    			return;
    		}
    	}
    
    	return;
    }
    
    BaseList2D* SkinModifier::GetBaseLink(BaseObject* op, DescID id, Int excepted)
    {
    	GeData data;
    	if (!op->GetParameter(id, data, DESCFLAGS_GET::NONE))
    		return nullptr;
    
    	return data.GetLink(op->GetDocument(), excepted);
    }
    
    
    Bool SkinModifier::ModifyObject(BaseObject* mod, BaseDocument* doc, BaseObject* op, const Matrix& op_mg, const Matrix& mod_mg, Float lod, Int32 flags, BaseThread* thread)
    {
    	if (!mod || !op || !doc || !thread)
    		return false;
    
    	BaseList2D* t = GetBaseLink(mod, DescID(NEWSKINTAG), Tweights);
    	BaseList2D* l = GetBaseLink(mod, DescID(NEWSKINCHECK), Opolygon);
    	if (!t || !l)
    		return false;
    
    	PointObject* linkOp = static_cast<PointObject*>(l);
    	CAWeightTag* tag = static_cast<CAWeightTag*>(t);
    
    	lasttagdirty = tag->GetDirty(DIRTYFLAGS::DATA);
    	cachedir = linkOp->GetDirty(DIRTYFLAGS::CACHE);
    
    	PointObject* pObj = ToPoint(op);
    	const Vector * pListR = pObj->GetPointR();
    	Vector * pListW = pObj->GetPointW();
    
    	const Int pcount = pObj->GetPointCount();
    	const Int jcount = tag->GetJointCount();
    	jointdir = 0;
    
    	for (Int i = 0; i < jcount; i++)
    	{
    		BaseObject* joint = tag->GetJoint(i, doc);
    		jointdir += joint->GetDirty(DIRTYFLAGS::MATRIX);
    	}
    
    
    	auto worker = [jcount, op_mg, &pListR, &pListW, &doc, &tag](maxon::Int i)
    	{
    		Vector temp;
    		Vector pos = op_mg * pListR[i];
    
    		for (Int m = 0; m < jcount; m++)
    		{
    			Vector pos = op_mg * pListR[i];
    			BaseObject* joint = tag->GetJoint(m, doc);
    			Float weight = tag->GetWeight(m, i);
    
    			if (weight == 0.0)
    				continue;
    
    			Matrix cjmg = joint->GetMg();
    			JointRestState jrest = tag->GetJointRestState(m);
    			Matrix jmg = jrest.m_bMg;
    			Matrix jmi = jrest.m_bMi;
    
    			temp += weight * cjmg * jmi * pos;
    		}
    		pListW[i] = ~op_mg * temp;
    	};
    
    	maxon::ParallelFor::Dynamic(0, pcount, worker);
    
    	// Notify Cinema about the internal data update.
    	op->Message(MSG_UPDATE);
    
    	return true;
    }
    
    Bool RegisterSkinModifier()
    {
    	return RegisterObjectPlugin(ID_OBJECTDATA_SKINMODIFIER, "C++ oskinmodifier"_s, OBJECT_MODIFIER, SkinModifier::Alloc, "oskinmodifier"_s, nullptr, 0);
    }
    

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Adding items to a LONG/CYCLE description container.

    Hi @FlavioDiniz, first of all, welcome in the plugincafe community!

    Don't worry, since it's your first topic, I've set up your topic correctly. But please for your next topics, use the Q&A functionnaly, make sure to also read How to Post Questions.

    With that's said you can find the manual about SetDParameter in the C++ docs which will be trigger each time a user changes a value in the description of your tag. And also the C++ manual about GetDDescription.
    Here a quick example of the implementation in python

        extensions = ["exr","hdr","jpg","png"]
    
        # Called by C4D when loading description
        def GetDDescription(self, node, description, flags):
            data = node.GetDataInstance()
            if data is None:
                return False
    
            # Load the description
            if not description.LoadDescription(node.GetType()):
                return False
    
            # If there is no description for c4d.TESTSCALE, we create it. Otherwise we override it and set DESC_CYCLE with our extensions list.
            singleID = description.GetSingleDescID()
            paramID = c4d.DescID(c4d.TESTSCALE)
            if singleID is None or paramID.IsPartOf(singleID)[0]:
                bcParam = description.GetParameterI(c4d.TESTSCALE)
    
                items = c4d.BaseContainer()
                for i, extension in enumerate(self.extensions):
                    items.SetString(i, extension)
    
                bcParam[c4d.DESC_CYCLE] = items
    
            return (True, flags | c4d.DESCFLAGS_DESC_LOADED)
    
        # Called when a parameter is Set
        def SetDParameter(self, node, id, t_data, flags):
            if not node:
                return
    
            # called when the filename is changed. We add the path to our list and we return c4d.DESCFLAGS_PARAM_SET to inform c4d the paramaters is just sets
            if id[0].id == c4d.STRINGTEST:
                self.extensions.append(t_data)
                return (True, flags | c4d.DESCFLAGS_PARAM_SET)
    
            return False
    

    If you have any questions, please let me know!
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Get all texture tags & show texture paths,but reflection channel throw error? :(

    Hi @Ashton_FCS_PluginDev, we are glad to see you back in the new plugincafe community!
    No worries at all since it's your first post here, but please make sure to read and apply the rules defined on the following topics:

    • How to Post Questions.
    • Q&A Functionality.

    Regarding your issue, you read m_spec, but you never check if there is a bitmashader in it or not. And in your case, there is no, so m_spec is set to None, and you are then trying to access value.
    So please always check for None.
    Then since you directly read the BITMAPSHADER_FILENAME parameter from this shader, please also consider to check the shader type you retrieve, maybe a user used a noise and not a bitmap.

    m_spec = userMat[c4d.MATERIAL_REFLECTION_SHADER]
    if not m_spec or m_spec.CheckType(c4d.Xbitmap):
        continue
    

    Finally, as you may already be aware, reflection gets multiple layers. You get some information in this thread, how to deal with it.

    If you have any others question, please let me know!
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: deformer object priority

    Hi @mike,
    No worries at all since, but please make sure to read and apply the rules defined on the following topics:

    • Q&A Functionality.

    Regarding your question, you can make use of the Shift Priority Tags. You can find more information on the priority in the c4d help.

    But C4D also offer the Shift Priority Tag.
    Finally, note in some case it can be a limitation of the viewport which not refresh properly while data are correctly set.
    And you need to manually refresh the viewport.

    Hope it solves your issue, if you have any question please let me know!
    Cheers,
    Maxime!

    posted in Cinema 4D SDK •

    Latest posts made by m_adam

    RE: Quicktab SDK Example

    While rethinking about it last night, it may not be that slow if on user interaction you just flush the main group and re-attach already existing dialog.

    So you need to modify your `def _DrawQuickTabGroup(self)`` method like so to only display visible dialog:

        def _DrawQuickTabGroup(self, onlyVisible=True):
            """ Creates and draws all the SubDialog for each tab, 
                take care it does not hide these according to a selection state.
    
            Returns: 
                True if success otherwise False.
            """
    
            # Checks if the quicktab is defined
            if self._quickTab is None:
                return False
    
            activeIds, activeNames = self.GetActiveTabs()
    
            # Flush the content of the group that holds all ours SubDialogs
            self.LayoutFlushGroup(ID_MAINGROUP)
            #self.GroupBorderSpace(left=5, top=5, right=5, bottom=5)
    
            # Iterates over the number of tab to create and attach the correct SubDialog
            for tabId, (tabName, tabGui) in enumerate(self._tabList.items()):
                toDisplay = tabId in activeIds
                if not toDisplay:
                    continue
                self.AddSubDialog(ID_QUICKTAB_BASE_GROUP + tabId, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0)
                self.AttachSubDialog(tabGui, ID_QUICKTAB_BASE_GROUP + tabId)
    
            self.LayoutChanged(ID_MAINGROUP)
    
            return True
    

    Then in the Command message instead of calling DisplayCorrectGroup you should call _DrawQuickTabGroup like so:

        def Command(self, id, msg):
            if id == ID_QUICKTAB_BAR and self._quickTab:
                self._DrawQuickTabGroup()
                return True
    

    Regarding your question and classic Tab, find an example in Remove a GeDialog Tab Group but as you can see this is also not possible to remove a tab without redrawing everything .

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Quicktab SDK Example

    Hi @mogh sorry for the delay, I can indeed confirm the bug and reported it so far I don't have any other workaround than flushing the whole group and re-add everything, which can be very slow.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Checking for ALT GetInputState() issue

    Just checked with development team, and this is a bug as this is something we don't really support and the slider having a default behavior for when alt is currently executed. So far there is no workaround, so the best way to proceed would be to create your own alert windows like so:

    import c4d
    
    
    PLUGINSTRING = "Alt test"
    UI_SPACING = 15048
    
    class AlertDialog(c4d.gui.GeDialog):
        
        @staticmethod
        def Create(msg):
            alertDlg = AlertDialog(msg)
            
            alertDlg.Open(c4d.DLG_TYPE_MODAL, defaultw=0, defaulth=0)
            return alertDlg
        
        def __init__(self, msg):
            self.message = msg
            
        def CreateLayout(self):
            self.AddStaticText(id=1000, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, initw=150, name=self.message)
            self.AddDlgGroup(c4d.DLG_OK)
            return True
            
        def Command(self, messageId, bc):
            if messageId == c4d.DLG_OK:
                self.Close()
            return True
    
    class AltTestDialog(c4d.gui.GeDialog):
    
        def CreateLayout(self):
            self.SetTitle(PLUGINSTRING)
    
            self.AddStaticText(id=1001, flags=c4d.BFH_MASK, initw=150, name="Spacing", borderstyle=c4d.BORDER_NONE)
            self.AddEditSlider(UI_SPACING, c4d.BFH_SCALEFIT, initw=80, inith=0)
            return True
    
    
        def Command(self, gadgetId, msg):
            doc = c4d.documents.GetActiveDocument()
    
            if gadgetId == UI_SPACING:
                self.Spacing  = self.GetInt32(UI_SPACING)
    
                newMsg = c4d.BaseContainer()
                if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD,c4d.KEY_ALT, newMsg):
    
                    if newMsg[c4d.BFM_INPUT_VALUE]:    # only dynamic update if Alt is pressed
                        parent = doc.GetActiveObject()
                        if not parent:
                            AlertDialog.Create('Please select a cloner!')
                            return True
    
            return True
    
    
    def main():
        global dlg
        # Creates a new instance of the GeDialog
        dlg = AltTestDialog()
    
        # Opens the GeDialog, since it's open it as Modal, it block Cinema 4D
        dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=300, defaulth=50)
    
    
    if __name__ == "__main__":
        main()
    

    Cheers,
    Maxime

    posted in Cinema 4D SDK •
    RE: Checking for ALT GetInputState() issue

    Hi thanks a lot, I'm now able to reproduce the issue, this may take some time (if this is even possible) to provide a workaround. But will for sure find a way to achieve that within this week.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: How to find out if document is rendering from a python tag

    Hi @HerrMay sadly having something working within the main will be a bit tricky, but this can be easily tested with message and more especially the MSG_MULTI_RENDERNOTIFICATION

    Find below a script run from a Python Tag.

    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
        pass
    
    
    def message(id: int, data: object) -> bool:
        if id == c4d.MSG_MULTI_RENDERNOTIFICATION and data["start"]:
            print(data)
        return True
    

    Note that the MSG_MULTI_RENDERNOTIFICATION in Python does not contain the context from which a render was started via the flag argument as it is in C++. I added it, so it will be in one of the next version of Cinema 4D.
    Finally note that some IPR from external render engine may not work, as they bypass completely the Cinema 4D rendering pipeline.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Un-commenting Message function of python tag results in RuntimeError

    Hi the issue is that normally when you implement a tag you are within a class and more especially a TagData which inherit from a NodeData therefor you can call the method "super" without issue because there is indeed a class. But in this case there is no TagData instance, so for the moment there is no good way to propagate the message and you should return True.

    And I know this is the default code, this will be looked at and fixed within a future version.
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Checking for ALT GetInputState() issue

    Hi @pim sorry I was busy the last days, regarding your question keep in mind that returning true cancel all others messages so please forward the message at the end so all operations like drag operation are performed correctly.

    1 week ago the question on How to detect CTRL being pressed in a GeDialog was asked, and the correct way is to check for BFM_INPUT_QUALIFIER and not BFM_INPUT_VALUE

    If it does not work, then I will ask you to provide a complete example.
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Quicktab SDK Example

    Hi can you illustrate what "GUI disintegration" means with a picture. as with the latest 2023.1 I can't spot any issue.

    May I ask which version of Cinema 4D are you using and most important which Python version? You taged this topic as R20 and 2023 and R20 would mean Python 2.7, but you need to be careful since dictionary in python are guarantee to be ordered only since Python 3.6.

    Which I think is what cause your issue since with Cinema 4D 2023.1 I wasn't able to reproduce the "switch between CCCC and DDDD". So if you want to support version as old as R20 you will need a different data structure like tuple. For more information see Default ordered dictionaries in Python 2.7.x.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: Quicktab SDK Example

    Hi there is nothing preventing for more "tabs", since your code is far from being complete it will be very hard for us to help you, so please share your code.

    As a hint I would check for Ids, as it is most likely the cause.
    Cheers,
    Maxime.

    posted in Cinema 4D SDK •
    RE: PointObject.CalcVertexMap(self, modifier)

    Hi @Abetred, just to let you now that baca is right here. If you are still in the design phase and not yet sure how to developer something feel free to ask us so we can guide you to achieve what you want to do.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK •