Group Details Private

administrators

RE: GeClipMap and init(BaseBitmap)

Hello @wickedp,

Okay, I will keep an eye on this thread.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Replace "Shape Node"?

Hello @bentraje,

Thank you for reaching out to us. This is a tricky question to answer, especially since you do not clarify what you would expect that function to do exactly.

When I take here all hints and assume a bit, then the answer is: No, in a classic API scene graph this is not possible in the way you probably think about it, but in a maxon API scene graph it is.

Scene Graphs

The classic API scene graph, i.e., things you see in the Object, Material, etc. managers, is organized as a monohierarchical structure (with a twist), while the Nodes API and Maya are inherently polyhierarchical. The common examples for a monohierarchy would be a classical file system. Every node in a classical file system can have exactly one parent, it cannot appear twice in two separate places. The common example of a polyhierarchy is a human family. Here a child usually has more than one parent.

motion_keyframes.gif

Technically speaking, polyhierarchies, are not really hierarchies, but directed acyclic graphs, a.k.a., 'a scene graph'. A monohierarchy is what we usually call 'a tree'. The classic API scene graph puts a twist on the tree concept, branches, but it would be more aptly named as a scene tree. At its core it is however bound to this 1:N relation, opposed to Nodes API scene graphs or Maya, which express relations in the complexity of N:N.

With this fundamental difference also follow many other changes, ranging from a more granular node model to high level things as dependency relations. In the end, this means that you can drive two geometries with the same transform in a Scene Nodes scene and Maya, but you cannot in a classic API scene. Switching out the geometry of a node, replacing a cube with a circle spline, is only a variation of the same problem.

What Can I Do?

Okay, that was a lot of tech blah-blah for a simple question. But I explained this to highlight how deeply rooted the problem is. You can of course just replace data:

  1. By using the Replace With Command. It does some heavy lifting for you, but it will also break links and will not copy over many forms of data. Transforms are however preserved.
  2. By writing something yourself in Python as indicated by yourself. Here you could be more specific about what you want to happen, but this can be a lot of work. You cannot really escape the fact that a classic API scene is organized as a tree, and your idea of 'replacing just one aspect of an entity' requires conceptually a true scene graph.

You could also simply use Scene Nodes, as there this all no problem:

motion_keyframes.gif

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Show "Hidden" Points?

Hi,

your segment tag do not include those points. You must update it.

op.SetSegment(0, 16, False)

Of course, if you want more than one segments, you must provide the right code.

This might happen if you changed the point count without updating the segments or defining them.

Cheers,
Manuel

posted in Cinema 4D SDK
RE: Add Additional Script Path?

Hi @bentraje, for the moment there is only the environment variable C4D_SCRIPTS_DIR that you can define. However this can't be set for a specific Cinema 4D version and this will be global. You can also add multiple path by separating them with a ;.

Cheers,
Maxime.

posted in General Talk
RE: Compiling for R20... linker error in maxon::String

hi,

As Ferdinand said, you should regenerate the project, that mean start from nothing except the source code and use the projectTools corresponding to the R20 that you can find on this page.

First thing to do is to compile the r20 sdk to be sure that it is working, then add your source code.
I would remove as much code i can and isolate the issue.

As you said R20 is not supported anymore, there is not too much we can do for you.

Cheers,
Manuel

posted in Cinema 4D SDK
RE: Unpacking Animation Data Wrapped by the Motion System (NLA)

Hey @kng_ito,

Thank you for your reply. I still must do some interpretation here, and your initial core question,

Is there any way to get correct information of key frames on Motion Manager layers?

seems to have been slightly misleading. As you do not seem to want to retrieve the key frames for the layers in your Motion System (e.g., an animation for their strength value), but the key frames of the animation the layer is wrapping. I have changed the title of the topic to better reflect that.

You cannot just iterate over the key frames of the object for that, as they are wrapped by the motion layers. What you see in the in the Timeline Manager is just smoke and mirrors, or more specifically the data from the motion sources. This is evident by the fact that switching the active layer will also change the displayed tracks and curves.

motion_keyframes.gif

Your Cube object is actually just carrying two tracks with one frame each, which serve as dummies to be dynamically filled by the Motion System when the document is animated. So, this output of yours was correct:

Position . X: 1 Key
Position . Y: 1 Key

Branching and the Motion System

The Motion System is more or less undocumented, even in the C++ API, with no dedicated interfaces for it being publicly provided. But you can access the data you want which is stored in the branches of the Motion System tag. I am not going to expand on the concepts of branches here in all detail, but you can think of them as alternative hierarchy paths. A BaseObject carries a tag branch for all its tags, a document an object branch for all its object, etc. So, instead of just a pure up, down, previous, and next tree, there are things attached to nodes which then contain a hierarchy of their own, the branches.

The data you are looking for is stored in the c4d.NLAbase branches of the Motion System tag controlling the animation.

To find out more about the concept of branches, you can read these two answers of mine on Plugin Café and read the C++ Docs Manual for GeListHead.

  1. Simple Branch Traversal (Python): Demonstrates how to traverse a node and all its descendants in all branches and printing them in a makeshift tree. The output shown below has been created with the code from this example.
  2. Complex Branch Traversal with Branch Content Mapping (Python): Demonstrates basically the same, only that the code provided here also predicts where it must branch into to find certain kinds of nodes.
  3. GeListHead Manual: GeListHead and GeListNode::GetBranchInfo implement the branching logic. This is the C++ Manual for that, but the manual is of questionable quality as it fails to explain the core concept and instead dabbles in technical details.

Accessing the Keyframes of Animations Wrapped by a Motion System

A Motion System tag has an c4d.NLAbase == 5349 branch called 'Motion System'. It contains all the animation layers managed by the system as BaseList2D instances. These are the nodes which are represented by the layer items with sliders in the tag. Each of these layers also has a c4d.NLAbase branch called Animation Layer which holds the 'Motion Sources' for the animation wrapped by the layer. In your case the cube objects on which the original animation has been created. Here you will find the key-frames which are wrapped by the animation layer. To clarify the somewhat wordy explanation, here is a cropped branching tree for the Motion System tag in your scene.

<c4d.BaseTag object called Motion System/Motion System ...          // The Motion System tag.
  Branching into 'Motion System (5349):'                            // Its NLA branch. 
    <c4d.GeListHead>
      Direct Children:'                                             // The nodes inside this branch
        <c4d.BaseList2D object called Layer 0/Motion Layer>         // The first animation layer.
          Branching into 'Animation Layer (5349):'                  // Its NLA branch. 
            <c4d.GeListHead>
              Direct Children:'                                     // The nodes inside this branch.
                <c4d.BaseObject object called Cube/Cube>            // A node holding animation data.
                  Branching into 'Tracks (5350):'                   // Here starts the animation data.
                    ...
        <c4d.BaseList2D object called Layer 1/Motion Layer>         // The second layer.
          Branching into 'Animation Layer (5349):'                  // The same game again ...
            <c4d.GeListHead>
              Direct Children:'
                <c4d.BaseObject object called Cube/Cube>
                  Branching into 'Tracks (5350):'
                    ...

When we put this information together, we can reach into the Motion System data of a Motion System tag to retrieve the nodes which hold the original information, two Ocube objects in your example scene. It is not possible to retrieve the same data from tracks of the Ocube object hosting the Motion System tag, as it is simply not stored there. It is the nodes stored deep inside the tag which drive the animation of the node hosting the animation (and its direct hierarchical descendants).

Find an example which puts all these things together below.

Cheers,
Ferdinand

Result:

The full node graph for <c4d.BaseTag object called Motion System/Motion System with ID 465003000 at 3006362080256>:
----------------------------------------------------------------------------------------------------

<c4d.BaseTag object called Motion System/Motion System with ID 465003000 at 3006362080256>
  Branching into 'Motion System (5349):'
    <c4d.GeListHead object at 0x000002BBF9250DC0>
      Direct Children:'
        <c4d.BaseList2D object called Layer 0/Motion Layer with ID 465003001 at 3006362071744>
          Branching into 'Animation Layer (5349):'
            <c4d.GeListHead object at 0x000002BBF924AF40>
              Direct Children:'
                <c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362212608>
                  Branching into 'Tracks (5350):'
                    <c4d.GeListHead object at 0x000002BBF926CAC0>
                      Direct Children:'
                        <c4d.CTrack object called Position . X/Track with ID 5350 at 3006362078784>
                          Branching into 'Sequences (5351):'
                            <c4d.GeListHead object at 0x000002BBF9244140>
                              Direct Children:'
                                <c4d.CCurve object called  with ID 5351 at 3006362070592>
        <c4d.BaseList2D object called Layer 1/Motion Layer with ID 465003001 at 3006362062912>
          Branching into 'Animation Layer (5349):'
            <c4d.GeListHead object at 0x000002BBF927CE80>
              Direct Children:'
                <c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362173952>
                  Branching into 'Tracks (5350):'
                    <c4d.GeListHead object at 0x000002BBF9273840>
                      Direct Children:'
                        <c4d.CTrack object called Position . Y/Track with ID 5350 at 3006362111168>
                          Branching into 'Sequences (5351):'
                            <c4d.GeListHead object at 0x000002BBF927E700>
                              Direct Children:'
                                <c4d.CCurve object called  with ID 5351 at 3006362199424>
<c4d.BaseTag object called Phong/Phong with ID 5612 at 3006362101184>
----------------------------------------------------------------------------------------------------

Traversing the NLA branches of <c4d.BaseTag object called Motion System/Motion System with ID 465003000 at 3006362080256> for animation data:

<c4d.BaseList2D object called Layer 0/Motion Layer with ID 465003001 at 3006362235840>
<c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362091456>
	Position . X
		Key 0: Value: 0.0, Data: None
		Key 1: Value: 100.0, Data: None
<c4d.BaseList2D object called Layer 1/Motion Layer with ID 465003001 at 3006362238144>
<c4d.BaseObject object called Cube/Cube with ID 5159 at 3006362083840>
	Position . Y
		Key 0: Value: 0.0, Data: None
		Key 1: Value: 34.0, Data: None
		Key 2: Value: 122.0, Data: None

Code:

"""Demonstrates how to unpack animation data stored inside a Motion System tag.

To run this Script Manager script, one must select an object which holds a Motion System tag. It 
will print out all keyframes which are wrapped by the animation layers of the tag. The animation
data data is stored in the #NLAbase branch relations of the tag. 

See https://plugincafe.maxon.net/topic/14298 for a more complete overview of the subject.

Note:
    This example uses two symbols exposed with `2023.0.0`. To run the script in prior versions, one
    must replace them with their integer values or attach attributes of these names and values to
    the c4d module.

    c4d.Tmotionsystem: int = 465003000
    c4d.NLAbase: int = 5349
"""
__version__ = "2023.0.0"

import c4d
import typing

op: typing.Optional[c4d.BaseObject]

def PrintRelatedNodes(node: c4d.GeListNode, indent: int=0):
    """Traverses all node relations of #node and prints them out in a tree-like manner.

    This functions is only used to demonstrate the structure of a motion tag. See 
    https://plugincafe.maxon.net/topic/14056/copy-layershader-to-a-new-material/6 for the original
    and more densely commented code.
    """
    def RepresentNode(node: c4d.GeListNode) -> None:
        """Represents a node over its string representation and branches.
        """
        print(f"{'  ' * indent}{node}")
        if node.GetBranchInfo() == None:
            return

        for item in node.GetBranchInfo():
            branchHead = item["head"]
            name = item["name"]
            tid = item["id"]

            if branchHead.GetDown() == None:
                continue
            
            print (f"{'  ' * (indent + 1)}Branching into '{name} ({tid}):'")
            PrintRelatedNodes(branchHead, indent + 2)

    if not isinstance(node, c4d.GeListNode):
        return

    if indent == 0:
        print (f"{'-' * 100}\n")

    while node:
        RepresentNode(node)
        if node.GetDown():
            print (f"{'  ' * (indent + 1)}Direct Children:'")
            PrintRelatedNodes(node.GetDown(), indent + 2)

        node = node.GetNext()

    if indent == 0:
        print (f"{'-' * 100}\n")

# --- Actual code starts here ----------------------------------------------------------------------

# A type alias for a generator which yields BaseList2D instances.
BaseList2DGenerator: typing.Type = typing.Generator[c4d.BaseList2D, None, None]

def GetMotionData(node: c4d.BaseList2D) -> BaseList2DGenerator:
    """Yields the relevant nodes of a motion system stored in #node by unpacking the branched data.
    """
    def branch(node: c4d.GeListNode, branchTypes: list[int]) -> BaseList2DGenerator:
        """Yields the branches of #node whose ID is contained in #branchTypes.

        Yielded will not be the branch head but the first node in it. Branches with a matching type
        but no nodes will be ignored.
        """
        branchCollection: typing.Optional[list[dict]] = node.GetBranchInfo()
        if branchCollection is None:
            return

        for branch in branchCollection:
            if branch["head"] and branch["id"] in branchTypes and branch["head"].GetDown():
                yield branch["head"].GetDown()

    def iter(node: c4d.GeListNode, branchTypes: list[int]) -> BaseList2DGenerator:
        """Yields the descendants of #node, including its branches and the branches of descendants.
        
        Traversed will only be branches whose ID is contained in #branchTypes.
        """
        if not node:
            return
        
        while (node):
            yield node
            for branchNode in branch(node, branchTypes):
                for child in iter(branchNode, branchTypes):
                    yield child

            for child in node.GetChildren():
                for item in iter(child, branchTypes):
                    yield item
            node = node.GetNext()

    # Iterate over all descendants of #node, branching into all branches that are of type NLAbase.
    # Both motion systems and animation layers live under this branch ID.
    motionTypes: list[int] = [c4d.NLAbase]
    for head in branch(node, motionTypes):
        for node in iter(head, motionTypes):
            yield node

def main() -> None:
    """Runs the example.
    """
    # Check if there is a selected object and that it has a Motion System tag.
    if not op:
        print ("Please select an object.")
        return
    
    tag: c4d.BaseTag = op.GetTag(c4d.Tmotionsystem)
    if not isinstance(tag, c4d.BaseTag):
        print ("Please select an object that has a motion system tag.")
        return

    # To understand what is going on here, we should look at the node data stored in the tag, i.e.,
    # unpack its branches. The tag stores an object for each animation layer it holds under the
    # animation layer branches of its motion system branch.
    print ("The full node graph for {tag}:")
    PrintRelatedNodes(tag)

    # Traverse #tag for the key frames contained within it. When you would have animated the layer
    # strength of the Animation layers, this would also be printed here. Adjust to your liking to
    # retrieve only the data you need.
    print ("Traversing the NLA branches of {tag} for animation data:")
    for node in GetMotionData(tag):
        print (f"Node in an NLA branch: {node}")

        # Iterate over the tracks and curves of the NLA node.
        for track in node.GetCTracks():
            print (f"\t{track.GetName()}")
            curve: c4d.CCurve = track.GetCurve()

            for kid in range(curve.GetKeyCount()):
                key: c4d.CKey = curve.GetKey(kid)
                print(f"Key {kid}: Value: {key.GetValue()}, Data: {key.GetGeData()}")


if __name__ == '__main__':
    main()
posted in Cinema 4D SDK
RE: GeClipMap and init(BaseBitmap)

Hey @wickedp,

Yeah, that could be the case, there is some weird hacking going on in GeClipMap regarding the bit depth of the managed bitmap and I remember a comment warning about stuff being buggy or slow for the bit depth X (I do not recall the exact number).

But I do not think that this is the case here, I assume you are using .TextAt() and drawing into an empty bitmap canvas. I recognized this before myself too: TextAt() seems to need pixels it can blend into, i.e., you cannot have text over a transparent background. When I stumbled upon this a while ago, I briefly play around with SetDrawMode with little luck and then simply assumed that you cannot have text over a transparent background. I did not dig any deeper then due to lack of time, so my assumption could have been false.

It depends on what you want to do here. If this is just a question out of curiosity, I would leave it at this hint of mine. If you need text over a transparent background, I could have a deeper look, but I will probably only happen next week due to time restrictions and this probably being quite time consuming to find out (GeClipMap is itself only a frontend interface which operates on older and more low-level types).

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Unpacking Animation Data Wrapped by the Motion System (NLA)

Hey @kng_ito,

Thank you for reaching out to us. Could you please provide an example scene file and the code you have written?

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Compiling for R20... linker error in maxon::String

Hey @fwilleke80,

Thank you for reaching out to us. We will talk about this tomorrow in the SDK Group, and Maxime and Manuel might know here more than I do, but one thing jumps to mind here for me:

  1. Are you sure there are no build fragments in your build directory, so that the linker/compiler cannot get confused? Visual Studio has sometimes the tendency to make a mess.
  2. From which follows, have you stripped the solution from all compilation results? The 'Clean Solution/Build Folder' commands in VS/XCode or unfortunately not very dependable, I would strip the solution by hand or copy over the source files to a new solution.
  3. Regenerating/checking your solution could also solve the problem.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: GeClipMap and init(BaseBitmap)

Hello @wickedp,

Thank you for reaching out to us. For GeClipMap::Init(BaseBitmap* bm):

  1. Is this copying the bmp into the clip map?
  2. Is the clip map now working on the original bitmap?
  1. No
  2. Yes

This also means that you cannot deallocate the bitmap for the lifetime of the GeClipmap, or more specifically, as long as you intend to call methods on it. This is what is meant by 'the caller owns the pointed bitmap'; it is a polite way of saying 'make sure that this thing stays alive'.

Regarding the casting subject. Inheritance is not always necessary for casting to work, I assume this is where your question came from, and the relevant factor is memory layout. Sometimes we construct types to be layout compliant to other types to allow for casting (without an inheritance relation), the pairs (VPBuffer, MultiPassBitmap) and (Filename, maxon::Url) would be two examples which can be cast without an inheritance relation, for the latter it is however not advisable, you should use MaxonConvert. For GeClipmap this is not true, it just carries a private field for the managed bitmap.

Find below an example which demonstrates the behavior.

Cheers,
Ferdinand

Result (shown is here the first bitmap, i.e., source, it also contains the red square):
Screenshot 2022-12-07 at 14.21.36.png

source == result = true

Code:

/// @brief Demonstrates the ownership of bitmaps associated with a GeClipMap drawing canvas and the 
/// bitmap object lifetime guarantees a caller must make.
static maxon::Result<void> RunGeClipmapTest(BaseDocument* doc)
{
  // The bitmap we are going to use as a source. We are not using scope based memory management, 
  // AutoAlloc, but handle the bitmap ourselves, to make a point about ownership.
  BaseBitmap* source;
  
  iferr_scope;
  finally
  {
    // Free #source when we exit the function normally or through an error.
    BaseBitmap::Free(source);
  };
  
  // Allocate memory for #source and load an image into it.
  Filename file {"/Users/f_hoppe/Documents/matcopy.gif"_s};
  source = BaseBitmap::Alloc();
  if (!source)
    return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
   
  if (source->Init(file) != IMAGERESULT::OK)
    return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not load image file into bitmap."_s);
  
  // Allocate a GeClipMap canvas to draw into and init it with #source, it will copy the pointer
  // and not the object/memory itself.
  AutoAlloc<GeClipMap> canvas;
  if (!canvas)
    return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
  
  if (canvas->Init(source) != IMAGERESULT::OK)
    return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);
  
  // Doing something like this will lead to crashes in release mode or stops in debug mode, #canvas
  // relies on the object, the memory region, pointed to by #source for the calls made below.
  // BaseBitmap::Free(source);
  // ---> Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
   
  canvas->BeginDraw();
  canvas->SetColor(255, 0, 0);
  canvas->FillRect(0, 0, 100, 100);
  canvas->EndDraw();
 
  BaseBitmap* const result = canvas->GetBitmap();
  if (!result)
    return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);
  
  // Both source and result will contain the red square in the top left corner because they are the 
  // same object.
  ApplicationOutput("source == result = @"_s, source == result);
  ShowBitmap(source);
  ShowBitmap(result);
  
  return maxon::OK;
}
posted in Cinema 4D SDK