Group Details Private

administrators

RE: Directly create Redshift standard surface for RS node space

Hi,

When you call AddGraph, the delegate function registered for the nodespace to create a material is executed. That is why it create a default redshift node material with the default endNode and the default RS Material node.

The only way to do it for now is exactly what you are doing.

One remark i should have comment in our examples,

addedGraph = nodeMaterial.AddGraph(nodespaceId)
nimbusRef = mat.GetNimbusRef(nodespaceId)
graph = nimbusRef.GetGraph()

addedGraph is the same as graph, nimbusRef are sometimes useful, but if you are just manipulating existing graph, they are not.

Cheers,
Manuel

posted in Cinema 4D SDK
RE: Tool plugin gui

Hello @chuanzhen,

in the above example also use function CreateLayout(), self.AddButton(),

I am not sure to which example you refer, but if it is my mock-up, you will see that it does not use any of the methods for adding gadgets manually. But you of course still have to implement the dialog and CreateLayout.

def CreateLayout(self) -> bool:
    """Adds gadgets to the dialog.

    Either populate the dialog manually or load a dialog resource.
    """
    return self.LoadDialogResource(*data)

The example is a bit optimistic, usually you still must do some stuff in the dialog, like for example adjusting the title or populating its menu, but in principle you just have to call self.LoadDialogResource(*data) to load a dialog resource into a dialog. Note that you can also invoke the method in succession to load in multiple dialog fragements.

There is no dedicated Python documentation for this, but it all works in Python too, I have used it myself recently. I would recommend having a look at:

  • The C++ Resource Files Manual to understand how resources work in general (and that there are two types of resources: one for descriptions and one for dialogs),
  • the C++ Dialog Layout Manual to understand the fundamental design principle of a dialog (which you probably already do), and
  • the C++ Dialog Resource Manual which describes the syntax of dialog resource files. Which is very similar to the syntax of description resources but not identical to it.

As far as I am aware of, we have no Python examples for using GeDialog.LoadDialogResource but there are plenty of C++ examples. The Asset API Examples Dialog is for example partially being loaded from a dialog resource (in line 80).

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Rotation for polygon

Hello @kantronin,

Thank you for reaching out to us. I am not quite sure how your last sentence is meant,

I manage to do this type of rotation, with matrices and with a change of Cartesian coordinate system, but I know that there is a much simpler method with C4D python functions

but you can define a rotation matrix from an axis of rotation v and an angle w with the function c4d.utils.RotAxisToMatrix(v, w). I.e., to transform vertices of an object, you would do something like this:

axis: c4d.Vector = c4d.Vector(1, 2, 3)
theta: float = c4d.utils.DegToRad(45)
transform: c4d.Matrix = c4d.utils.RotAxisToMatrix(axis, theta)

transformedPoints: list[c4d.Vector] = [transform * p for p in somePointObject.GetAllPoints()]

This transformation would however here happen in local object space, i.e., the space vertices/points are being expressed in. If you want to do the rotation in a space relative to the polygon, you will have to construct a frame for that polygon first, then convert the points out of local object space into your custom polygon space, carry out your rotation, and then convert the points back. I am just mentioning this because you talk about '[..] do[ing] a rotation for a polyon' which somewhat implies that you want to rotate around the origin of the polygon and some frame implied by the polygon.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: It seems that the scroll group has a bug in the R26 version. When the doc is refreshed, it will automatically change its position?

Hello @jack0319,

Yes, this is a bug. I had to do some digging first (debug against c4d) to find out what is going wrong here. It seems like this is a bug in the scroll group gadget. I was unable to pinpoint what is going exactly wrong, but when a new document is created, the scroll group position is set again, because Cinema 4D is reinitializing its layout. You currently cannot do anything about this. I have filed a bug report on our bug tracker and will update this thread when the bug has been fixed.

We cannot make any guarantees regarding an ETA and considering that this is a negligible impact bug, it could take multiple revisions until the bug is fixed.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Tool plugin gui

Hello @chuanzhen,

I unfortunately do not fully understand what you want to say.

My last version used dialog to implement gui, but due to some data transmission methods, I want to use res method to re-implement

Both ToolData and DescriptionToolData allow defining their GUI using resources, one uses dialog resources, the other description resources.

it, it seems that this is a limitation, I can only use dialog to achieve!

I am not quite sure what you mean by limitation here. Not being able to implement a DescriptionToolData plugin in Python is a limitation, but that does not stop you from defining your tool plugin GUI in a resource.

If you really want a description-based tool, you must move to C++ there you would also have (more) direct viewport image buffer access, which would further incentivize doing this.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Tool plugin gui

Hello @chuanzhen,

Thank you for reaching out to us. In short, the answer is that you are looking at the wrong example and therefore do not load your GUI, resulting in nothing being displayed. You must overwrite ToolData.AllocSubDialog() to show your tool dialog.

There are two ways to implement tools in the Cinema 4D API: ToolData plugins which base their GUI on dialog resources, and DescriptionToolData plugins which base their GUI on description resources, i.e., work for example similar to an ObjectData plugin.

In Python you can only implement directly ToolData plugins, i.e., plugins which are based on dialogs. There is however one caveat, as there is SculptBrushToolData, which is a specialization of DescriptionToolData for sculpting brushes. And SculptBrushToolData has been wrapped for Python, sculpt_grab_brush_r16.pyp is an example for that.

Python examples for ToolData plugins can be found in py-liquid_painter_r12.pyp and py-tooldata_ui_r15.pyp. Your class S_ScreenShot is not implementing AllocSubDialog and therefore cannot open that dialog. It must look something like this:

class MyToolDialog(c4d.gui.SubDialog):
    """Realizes a dialog for a tool hook.
    """
    def __init__(self, toolData: c4d.BaseContainer) -> None:
        """Not necessary, but IMHO advantageous to grab the tool data container.
        """
        self._toolData = toolData

    def CreateLayout(self) -> bool:
        """Adds gadgets to the dialog.

        Either populate the dialog manually or load a dialog resource.
        """
        return self.LoadDialogResource(*data)


class S_ScreenShot(plugins.ToolData):
    """Realizes a tool hook.
    """

    def AllocSubDialog(self, bc: c4d.BaseContainer) -> c4d.gui.SubDialog:  
        """Called by Cinema 4D to allocate the tool dialog.
        """
        return MyToolDialog(bc)

    ...

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: How can I print something in a new console items in python?

you must use the pluginStart function (usually in main.cpp)

I edited the main.cpp file that you can find in our cinema4dSDK example. Those examples are available in all c4d installation inside the sdk.zip.

You must run the project tool on the directory to generate the project files for xcode or visual studio. You can doznload the project tools here.
Your sdk.zip should compile out of the box if you respect those IDE version.

24a42e87-f6ca-4f75-bab8-00b2bac27682-image.png

Cheers,
Manuel

posted in Cinema 4D SDK
RE: How can I print something in a new console items in python?

hi,

To use a logger, you need to retrieve the reference to it. For now, you can only check if its name is the same as the one you expect.
Or you can write to one of the loggers you can find on this page

from typing import Optional
import c4d
import maxon

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.

    txt = "Printing in the {} console my text"
    # Write to a specific logger
    defaultLogger = maxon.Loggers.Default()
    defaultLogger.Write(maxon.TARGETAUDIENCE.ALL, txt.format("default"), maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.DEFAULT)
    # Write to all loggers.
    for log in maxon.Loggers.GetEntries():
        log.Write(maxon.TARGETAUDIENCE.ALL, txt.format(log.GetName()), maxon.MAXON_SOURCE_LOCATION(1), maxon.WRITEMETA.DEFAULT)


if __name__ == '__main__':
    main()

Cheers,
Manuel

posted in Cinema 4D SDK
RE: How to change render setting before add BatchRender

Hey @tash,

you got the right idea and your code

rd = doc.GetFirstRenderData()

while True:    
    if rd.GetName() == "typeB":
        break
    rd = rd.GetNext()

is not a loop without an exit condition, it is only that it is a bit ugly one 😉 At some point rd will be None, and then rd.GetName() will raise an AttributeError because the type None has no such method. So, you could write instead:

rd = doc.GetFirstRenderData()

while rd:   # or a bit more verbose as: while isinstance(rd, c4d.documents.RenderData):    
    if rd.GetName() == "typeB":
        break
    rd = rd.GetNext()

The problem would then however be, that this code cannot reach nested render data, e.g., RenderData_1.1 and RenderData_1.2 in the example below.

RenderData_1
    RenderData_1.1
    RenderData_1.2
RenderData_2

Your code also does not have an answer to what happens when there is no "typeB" to be found at all. Find an example of how I would write something like this below. Please keeo in mind that this is still an example that must be finished by yourself.

Cheers,
Ferdinand

"""Provides an example for discovering, loading, modifying, and then saving documents, to be loaded
by the batch renderer.
"""

import c4d
import os
import typing

def Iterate(node: c4d.GeListNode) -> c4d.GeListNode:
    """Walks #node depth first.
    """
    if node is None:
        return

    while node:
        yield node
        for descendant in Iterate(node.GetDown()):
            yield descendant
        node = node.GetNext()

def ModifyDocument(doc: c4d.documents.BaseDocument, renderDataTargetName: str,
                   fallbackRenderData: typing.Optional[c4d.documents.RenderData] = None) -> None:
    """Modifies a document according to what you want to do.

    This is just an example, the function could do anything.

    Args:
        doc: The document to modify.
        renderDataTargetName: The name of the render data to load in the document.
        fallbackRenderData: The render data to use when #renderDataTargetName is not present. Will
        be ignored when None. Defaults to None.
    """
    def _createNewRenderData():
        """Little helper function which inserts #fallbackRenderData into #doc.
        """
        # Cloning is technically not necessary in this case, since we know #fallbackRenderData to
        # be unique for each passed #doc, but if we weren't, we must clone the render data since a
        # node can only be attached to one document (RenderData is also a BaseList2D, i.e., node).
        clone: c4d.documents.RenderData = fallbackRenderData.GetClone(c4d.COPYFLAGS_0)
        doc.InsertRenderDataLast(clone)
        doc.SetActiveRenderData(clone)

    if not isinstance(doc, c4d.documents.BaseDocument):
        raise TypeError(f"Not a document: {doc}")

    # Get the first render data in the document. If there is None, either create the default data 
    # and bail, or just bail when there is no default data.
    rData: c4d.documents.RenderData = doc.GetFirstRenderData()
    if rData is None:
        if fallbackRenderData:
            _createNewRenderData()
        return

    # Loop over all render data in the document.
    for rData in Iterate(rData):
        # We found a match, set the data as the active one and bail.
        if rData.GetName() == renderDataTargetName:
            doc.SetActiveRenderData(rData)
            return

    # When we reach this point it means that #renderDataTargetName was not contained in #doc.
    if fallbackRenderData:
        _createNewRenderData()


def main() -> None:
    """
    """
    # Let the user select a directory to scan for documents to load.
    directory: str = c4d.storage.LoadDialog(
        title="Please select the scene directory", flags=c4d.FILESELECT_DIRECTORY)
    if directory is None:
        return

    # Your render settings to look for and a list to put files into for the Batch render.
    targetRenderSettings: str = "typeB"
    batchRenderFiles: list[str] = []

    # Walk that directory and find everything that is a Cinema 4D file.
    for root, _, files in os.walk(directory):
        for f in files:
            if not f.endswith("c4d"):
                continue
            
            # Load the file into a Cinema 4D document and make up a new path for the modified 
            # document.
            filePath: str = os.path.join(root, f)
            doc: c4d.BaseDocument = c4d.documents.LoadDocument(filePath, c4d.SCENEFILTER_NONE)
            if not isinstance(doc, c4d.documents.BaseDocument):
                raise OSError(f"Failed loading into a c4d document: '{filePath}'")

            name: str = os.path.splitext(doc.GetDocumentName())[0]
            newPath: str = os.path.join(doc.GetDocumentPath(), f"modified_{name}.c4d")

            # doc could not contain #targetRenderSettings, so we build some fallback data to use
            # instead. They could also be loaded from another document, etc.
            fallback: c4d.documents.RenderData = c4d.documents.RenderData()
            fallback[c4d.RDATA_RENDERENGINE] = c4d.RDATA_RENDERENGINE_PREVIEWHARDWARE
            fallback[c4d.RDATA_PATH] = f"render_{name}"

            # Modify #doc to whatever you want to do.
            ModifyDocument(doc, targetRenderSettings, fallback)

            # Save the document and append the file to the to be rendered files.
            if not c4d.documents.SaveDocument(doc, newPath, c4d.SAVEDOCUMENTFLAGS_NONE, 
                                              c4d.FORMAT_C4DEXPORT):
                raise OSError(f"Could not save {doc} to '{newPath}'.")
            batchRenderFiles.append(newPath)


    # Get the batch renderer.
    batchRender: c4d.documents.BatchRender = c4d.documents.GetBatchRender()

    # Start adding the newly crated files as render jobs and then open the batch render dialog.
    for i, file in enumerate(batchRenderFiles):
        batchRender.AddFile(file, 0)

    batchRender.Open()


if __name__ == '__main__':
    main()
posted in Cinema 4D SDK
RE: Node port Vector2 value is not recognized by Python

Hi,

not all maxon datatype are available in python. 2d vector (or vec2) is one of them. Of course, c4d is not able to convert it. with the default values, c4d is converting the data to a float and that is why you only retrieve the first value X.

You can select all the nodes and use the copy command; this will export to clipboard the selected nodes as a json formatted text. Unfortunately, only the modified values will be exported, not the default ones. That should still work for other dcc using redshift if the default values are the same.

Cheers,
Manuel

posted in Cinema 4D SDK