Group Details Private

administrators

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: Export fbx of only selected objects

Hello @Aleksey,

I am glad to hear that you found your solution. And as lined out above, we are of course aware that ChatGPT is also an excellent learning tool, and it is therefore in our interest to allow it. But there is also the other side to it, and that is why we are a bit undecided on how we will deal with it in the future.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Crash when apply a Tag plugin.

Hello @Dunhou,

Thank you for reaching out to us and thank you at @HerrMay for providing what I would think is the correct answer.

At least on my machine, nothing is crashing here, it is just that the description of the tag is malformed on line five.

6ff23335-b6c4-4d15-9ef4-47f3a84ca4b2-image.png

In this case, it is such a severe error, that Cinema 4D fails to load the description at all because it is cyclically referencing itself. The keyword INCLUDE in a resource allows us to include another resource in that resource. And when we try to reference the resource, we are defining, Cinema 4D is understandably confused. For less severe descriptions errors, one usually is able to then lick OK a few times to get Cinema 4D to load what it does understand. But that does not work here, and one is caught in an endless loop of self-reference.

In practice, INCLUDE is usually used to load in the base description of something. So, when we take the simple description of the opydoublecircle example plugin which only defines one parameter named PYCIRCLEOBJECT_RAD,

CONTAINER Opydoublecircle
{
	NAME Opydoublecircle;
	INCLUDE Obase; // Loads 

	GROUP ID_OBJECTPROPERTIES
	{
		REAL PYCIRCLEOBJECT_RAD { UNIT METER; MIN 0.0; }
	}
	INCLUDE Osplineprimitive;
}

we can see that Obase loaded the Basic and Coordinates tabs into that object description that every object must have. And Osplineprimitive loaded in the parameters which are shared by all spline primitives, as for example Plane, Reverse, etc.

c2a597a9-df44-439e-aa0c-62908933dbeb-image.png

When we remove Osplineprimitive, all these parameters will not be added to our object.

CONTAINER Opydoublecircle
{
	NAME Opydoublecircle;
	INCLUDE Obase; // Loads 

	GROUP ID_OBJECTPROPERTIES
	{
		REAL PYCIRCLEOBJECT_RAD { UNIT METER; MIN 0.0; }
	}
}

7a26bad0-db17-4ad5-ba7e-0de7490bc743-image.png

What slightly complicates this, is that (allmost) all NodeData derived plugin types must include at least their base description, e.g., Obase for objects, Tbase for tags, Mbase for materials, Xbase for shaders, and so on.

In some cases speccializations are allowed, as for example including Texpression instead of Tbase. Texpression extends Tbase and while Tbase is used for generic tags which usually do not modify the scene, e.g., the "Display" tag Tdisplay is based on Tbase:

CONTAINER Tdisplay
{
	NAME Tdisplay;
	INCLUDE Tbase;

	GROUP ID_TAGPROPERTIES
	{
		COLUMNS 2;
		BOOL DISPLAYTAG_AFFECT_DISPLAYMODE { }
		LONG DISPLAYTAG_SDISPLAYMODE
		{

Texpression is usually used by tags which do modify a scene in some shape or form, e.g, the "Look at Camera" tag Tlookatcamera:

CONTAINER Tlookatcamera
{
	NAME Tlookatcamera;
	INCLUDE Texpression;

	GROUP ID_TAGPROPERTIES
	{
		BOOL LOOKATCAMERA_PITCH { }
	}
}

The difference is then simply that such tags whill have the expression paramaters in their 'Basic' tab.

a91e446d-4b0d-4fcc-a0a4-72179491d19c-image.png

As said before, I already have updating the GUI manuals on my desk, it will be one of the next things I do, as they are factually non-existent for Python and a bit outdated and assumptious regarding what users will easily understand for C++. But I would still recommend Python users having a look at the C++ docs, as the information provided there is directly transferable to Python.

As lined out in other threads, greping the Cinema 4D resource folder, e.g., C:\Program Files\Maxon\2023.1.3\resource\ is a good way to gather knowledge on your own right now. I like using Agent Ransack for doing things like this, but any grep-like tool, including these built into text editors, will do:

afde649f-398b-439c-b596-12ffc6b12967-image.png

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: How to be sure if an object is a generator/modifier?

Hello @HerrMay,

Thank you for reaching out to us. Well, what c4d.BaseList2D.GetInfo() is returning are the plugin flags the plugin has been registered with, e.g., c4d.OBJECT_GENERATOR. What one then considers "bullet proof" is a bit of a question of taste.

c4d.BaseList2D.GetInfo returns the plugin flags mask without any filtering, i.e., it is in this sense bullet proof, and in the sense that it will correctly identify plugins that exhibit the behavior that comes with for example with OBJECT_GENERATOR. What you or the common user associate with these terms might be rightfully something different, and in the end these things are just that: flags. So nothing prevents a developer from registering something as a OBJECT_GENERATOR and then writing something that behaves very differently. But things like Olight and Ocamera rightfully do not classify as generators because they have no cache, the core characteristic of a generator, as also indicated by the documentation:

OBJECT_GENERATOR: Generator object. Produces a polygonal or spline representation on its own. (E.g. primitive cube.)

Noteworthy is in this context that SplineObject instances are generators because they generate their LineObject cache. the Modelling - Geometry Model Python code examples expand a bit more on that concept.

306a3d9d-1b55-4788-bbcc-afde27d29a13-image.png

As an alternative, you could test for objects being a PointObject instance, as all things which are not generators in your world view should be a point object (again, it is a bit ambiguous what you would consider a generator and what not). You can also check if something is a BaseObject but not a PointObject and has no cache to decide if it is something like Olight. What is a modifier or not in this interpretative/analytical sense is probably impossible, as there is already room for interpretation for the native objects of Cinema 4D, deformers and MoGraph effectors are modifiers, field objects are not. This likely extends to many plugin objects which might have modifier qualities but do not necessarily flag themselves as such with OBJECT_MODIFIER.

Regarding ID_BASEOBJECT_GENERATOR_FLAG, all objects have this parameter, even the ones which do not display it. So, calling C4DAtom.GetParameter with the flag DESCFLAGS_GET_NO_GEDATADEFAULTVALUE will not yield None for objects which seem to not have this parameter. But you can inspect the description of an object to find out, see the code example at the end.

Cheers,
Ferdinand

Result:

Cube.1(Polygon) cannot be enabled or disabled.
Group(Group) enabled-state is: 0.
Linear Field(Linear Field) enabled-state is: 1.
Camera(Camera) cannot be enabled or disabled.
Light(Light) enabled-state is: 1.

Code:

"""Demonstrates how to check if a BaseObject has the green enabled check-mark or not.

Will print out the state for the selected object when run.
"""

import c4d
import typing

doc: c4d.documents.BaseDocument  # The active document
op: typing.Optional[c4d.BaseObject]  # The active object, None if unselected


def main() -> None:
    """Runs the example.
    """
    if not op:
        return

    # Something like this will not work, because all BaseObject types carry this parameter, it is 
    # just that some of them are hiding it. So, we will never retrieve None here.
    state: typing.Optional[bool] = op.GetParameter(
        c4d.ID_BASEOBJECT_GENERATOR_FLAG, c4d.DESCFLAGS_GET_NO_GEDATADEFAULTVALUE)
    
    # But we can look up if this parameter is hidden in the description of the object.
    description: c4d.Description = op.GetDescription(c4d.DESCFLAGS_DESC_NONE)
    param: typing.Optional[c4d.BaseContainer] = description.GetParameter(
        c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_GENERATOR_FLAG, c4d.DTYPE_BOOL, 0)))
    if not param:
        raise RuntimeError("Could not retrieve parameter container.")

    isHidden: bool = param[c4d.DESC_HIDE]

    if isHidden:
        print (f"{op.GetName()}({op.GetTypeName()}) cannot be enabled or disabled.")
    else:
        # We could also reuse #state here, but the fancier GetParameter() access is not required.
        print (f"{op.GetName()}({op.GetTypeName()}) enabled-state is: "
               f"{op[c4d.ID_BASEOBJECT_GENERATOR_FLAG]}.")


if __name__ == '__main__':
    main()
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: 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: How to draw on spline points from spline returned by Object Data

Hello @HerrMay,

Funny enough DrawPoints seems to internally use the "same points" to draw, one gets to see when one edits the points of a polygon object. 😄 In S26 the points are round (thats probably why I was thinking of circles) while in R20 they are squares. No question here, just an observation.

As you can see, in the script I retrieve the cache of the object we are going to draw.

# Get the cache of the object representing the plugin in the object manager, i.e., more or
# less what we returned in GetContour, Cinema 4D has however exhausted the cache for us, and
# the cache is not a SplineObject but a LineObject (i.e, the cache of a SplineObject).
cache: c4d.LineObject = op.GetDeformCache() or op.GetCache()
if not isinstance(cache, c4d.LineObject):
    return c4d.DRAWRESULT_O

This not only enables us to deal with splines affected by a deformer correctly, but also and more importantly with spline generators as for example the Circle spline generator.

Such generators do not have any control points one could retrieve, their cache will always contain LineObject instances and not SplineObject instances (when they have been implemented correctly). This is also what I was referring to in my comment, because OffsetYSpline returns a SplineObject as its cache in GetContour, but Cinema 4D will 'exhaust' that cache for us by returning the cache of the cache, the LineObject.

A LineObject represents a SplineObject over its current interpolation settings.

What is actually weird though is that the color gradient you used here to color the points doesn't work in R20. The points are simply drawn black there. Possibly a Python 2/3 issue caused by integer/float division differences? 🤷

Yes, this is Python 3 code. In Python 2 integers divide by default as integers. Simply wrap one of the numbers into a float, e.g., f: float = float(i)/float(count).

Allow one follow up question. How can I draw only the control points of the spline? In your example you use a circle primitive which has - when made editable - exactly four points making up the shape via tangents. Where as the drawn points using the intermediate points of the circle as well.

You can use BaseObject.GetRealSpline to get the underlying editable spline object for a spline. So, when you would replace the section quoted above with this:

cache: c4d.SplineObject = op.GetRealSpline()
if not isinstance(cache, c4d.SplineObject):
    return c4d.DRAWRESULT_OK

You would now draw the control points of the SplineObject representing the spline generator rather than the vertices of its underlying LineObject. The problem in this case is that the plugin you have chosen is calling 'Current State to Object' (CSTO) on the input object which it is cloning and returning offset-ed (see line 130 in your code listing). CSTO is of course just replacing an object with its cache state. So, when you put a circle spline below the offset spline, it will still be drawn as in the LineObject cache version because the cache and the BaseObject.GetRealSpline representation are in this case identical.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Export fbx of only selected objects

Hello @Aleksey ,

Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!

Getting Started

Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

About your First Question

The problem with ChatGPT, as amazing as its principal abilities are, is that it seems to struggle with how im- and exporters work in our API. And in true ChatGPT fashion it then starts making up things which simply do not exist, as for example c4d.FBXEXPORT_SELECTION_ONLY = True or the fifth SaveDocument argument.

The problem which arises here for us is that this whole workflow, not writing code manually and serving here that automatically generated for others to fix, is in essence a violation our guidelines as lined out in Forum Guidelines: Scope of Support:

  • If necessary, we will provide code examples, but we will not provide full solutions or design applications.
  • "It doesn't work" is not a support request and ultimately, we will not debug code, but instead provide answers to specific problems.

Or in short, the rule of every coding-community in the world also does apply here: "we will not do your homework for you". There are of course nuances here, and ChatGPT differs at least a little from straight up asking for a solution. But in the end, we cannot fix the code for everyone who is able to use ChatGPT but does not know our APIs or how to use Python/C++ in general. We would be simply overwhelmed by this, our support has always the goal to help users being able to help themselves in the future by understanding something fundamentally.

We had a brief talk about this subject this morning and haven't come to a full conclusion on how we will deal with this in the future. To be clear here, the fault does not lie with you, @Aleksey, as nothing explicitly forbids this being done at the moment. But we might restrict support requests which are based on code that clearly has been automatically generated. We can of course see the advantages in allowing this for the community, having an easy entry point for them. But at the same time, the sheer amount of support requests this could entail is a problem.


The Python example export_obj_r13.py demonstrates how to modify the settings of an im- or exporter plugin at the case of the OBJ exporter. I also provided an example specific to this case below.

Cheers,
Ferdinand

Code:

"""Provides an example for setting the parameters of an im- or exporter plugin.

See also:
    https://github.com/PluginCafe/cinema4d_py_sdk_extended/blob/master/scripts/
    03_application_development/files_media/export_obj_r13.py
"""

import c4d
import os

doc: c4d.documents.BaseDocument # The active document.

def main():
    """
    """
    # Check that there is document path to infer from the active document.
    if doc.GetDocumentPath() == "":
        c4d.gui.MessageDialog("Cannot infer save location from unsaved document.")
        return

    # Get the selected objects
    selectedObjects: list[c4d.BaseObject] = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
    if not selectedObjects:
        c4d.gui.MessageDialog("No objects selected. Please select at least one object and try again.")
        return
    
    # Get the FBX exporter plugin.
    plugin: c4d.plugins.BasePlugin = c4d.plugins.FindPlugin(
        c4d.FORMAT_FBX_EXPORT, c4d.PLUGINTYPE_SCENESAVER)
    if not plugin:
        raise RuntimeError("Could not find FBX exporter plugin.")

    # Get the internal data so that we write the setting we want to write, handling the settings
    # of im- and exporter plugins is a bit fiddly as the plugin node itself does not hold these
    # parameters.
    privateData: dict = {}
    if not plugin.Message(c4d.MSG_RETRIEVEPRIVATEDATA, privateData):
        raise RuntimeError("Failed to retrieve private FBX exporter plugin data.")

    settings: c4d.BaseList2D = privateData.get("imexporter", None)
    if settings is None:
        raise RuntimeError("Failed to retrieve settings node for FBX exporter plugin.")

    # The is just normal parameter access.
    settings[c4d.FBXEXPORT_SELECTION_ONLY] = True

    # Now we can invoke the exporter with the settings we have defined.
    documentName: str = f"{doc.GetDocumentName()}_{selectedObjects[0].GetName()}"
    filePath: str = os.path.join(doc.GetDocumentPath(), documentName)
    res: bool = c4d.documents.SaveDocument(
        doc, filePath, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_FBX_EXPORT)
    
    if res:
        c4d.gui.MessageDialog(f"FBX export successful: {filePath}")
    else:
        c4d.gui.MessageDialog("FBX export failed. Please check the file path and try again.")

if __name__=='__main__':
    main()
posted in Cinema 4D SDK