Group Details Private

Global Moderators

Forum wide moderators

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
RE: how to get active viewport effect open/close state

Hello @chuanzhen,

thank you for reaching out to us. It is good to see that you found a solution yourself and thank you for sharing your solution. In case future readers stumble upon this, here is a bit more extensive posting which leads up to the point where @chuanzhen was, already having a BaseDraw instance to write parameter values to.

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,

welcome to the Plugin Café and thank you for reaching out to us. For future postings I would recommend having a look at our Forum Guidelines as they will line out important aspects of the support provided here. One of the important points is to provide executable code and clear instructions how to reproduce a problem.

Your problem seems to be that the scroll position of a scroll group is being reset unintentionally. You mention as the cause 'the doc being refreshed'. Since refreshing documents is not established terminology in the Cinema 4D API, and your code is not executable, the problem is not reproducible for me as I simply do not know what you mean by that. In place of refreshing the documents , I tried:

  • Creating a new document
  • Adding an event to a document
  • Pushing a GUI update event to the scroll group gadget

because it either seemed sensible or you were doing something like this in your code.

None of these have any effect on the scroll position of the scroll group in S26.1. Find the example code I produced and the results at the end of the posting. Please provide executable example code of your problem where all unnecessary elements have been removed. At the moment, I simply cannot reproduce your problem.

Cheers,
Ferdinand

The result:
blah_scroll.gif

The code:

"""Example for scroll groups in S26.1 and possible bugs in it.

The example can be run from the script manager.
"""

import c4d
import typing

doc: c4d.documents.BaseDocument
op: typing.Optional[c4d.BaseObject]

class MyDialog (c4d.gui.GeDialog):
    """Provides the dialog containing the scroll group.
    """
    ID_MENU: int = 1000

    ID_GRP_SCROLL: int = 2000
    ID_GRP_CONTAINER: int = 2001
    ID_GDG_BASE: int = 3000

    GADGET_COUNT: int = 100

    def CreateLayout(self) -> bool:
        """Adds a menu with an item to refresh the scroll group, the scroll group and multiple
        gadgets to the dialog.
        """
        self.MenuSubBegin("Menu")
        self.MenuAddString(MyDialog.ID_MENU, "Refresh")
        self.MenuSubEnd()

        defaultFlags: int = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT

        self.ScrollGroupBegin(MyDialog.ID_GRP_SCROLL, defaultFlags, c4d.SCROLLGROUP_VERT | c4d.SCROLLGROUP_HORIZ)
        self.GroupBegin(MyDialog.ID_GRP_CONTAINER, defaultFlags, 1)
        self.GroupBorderSpace(5, 5, 5, 5)
        self.GroupSpace(3, 3)

        for i in range(MyDialog.GADGET_COUNT):
            self.AddStaticText(MyDialog.ID_GDG_BASE + i, c4d.BFH_SCALEFIT, name=f"Gadget {i}")

        self.GroupEnd() # ID_GRP_CONTAINER
        self.GroupEnd() # ID_GRP_SCROLL

        return super().CreateLayout()

    def Command(self, cid: int, msg: c4d.BaseContainer) -> bool:
        """Refreshes ID_GRP_SCROLL and also sets off EventAdd when ID_MENU is invoked.
        """
        if cid == MyDialog.ID_MENU:
            print ("Refresh Layout & Event Add")
            self.LayoutChanged(MyDialog.ID_GRP_SCROLL)
            c4d.EventAdd()
        return super().Command(cid, msg)

    def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> int:
        """From your code, I commented it out, since I do not understand its purpose.
        """
        if msg.GetId() == c4d.BFM_SCROLLGROUP_SCROLLED :
            # print("scroll group change when document refresh")
            pass
        return super().Message(msg, result)

def main() -> None:
    """Opens the dialog.
    """
    global dlg
    dlg = MyDialog()
    dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=500, defaulth=250)



if __name__ == '__main__':
    main()
posted in Cinema 4D SDK
RE: How to change render setting before add BatchRender

Hello @tash,

welcome to the forum and thank you for reaching out to us.

Is it possible to change the rendering settings before AddFile to BatchRender?

It depends a bit on how you mean that. c4d.documents.BatchRender is inherently file based.

  1. You can certainly change something about a file, e.g., its render settings, but you must then save these changes to disk (to the old or a new location) before you can render them with the batch renderer. So, you would have to iterate over your file paths, load each of them into a BaseDocument with c4d.documents.LoadDocument, carry out your modifications of the document, and then save the document to disk with c4d.documents.SaveDocument. Then you would have to add the file path fm for the modified document to the Batch Renderer. Once the rendering is done, you could optionally remove these documents from disk again.
  2. You can also render BaseDocument instances directly with c4d.documents.RenderDocument. If you would want any sort of queue/batch thing, you would have to implement it yourself in this case though.
  3. Or you hook directly into c4d.modules.net.NetRenderService, the Teams Renderer backend. There you can also add BaseDocument instances as renders.

I personally would go with option 1, as it is the most economical solution. RenderDocument is quite limited and NetRenderService is quite powerful but also nontrivial to use.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: How can I add a menu to .res file for muti-language support?

Is that means if I have a plugin A with unique ID A , the dialog ID 100 likes A-100 inside , and plugin B with unique ID B , the dialog ID 100 likes B-100 , so that they can exist at same time ?

Yes, other than plugin IDs, resource IDs are scoped within the resource they are being used for. plugin A is simply not aware of the resources of plugin B.

And those A-100 like IDs will dispear when close c4d , so I don't have to unreg them :eg.: bitmapbutton IDs manually? ( c4d.gui.RegisterIcon / c4d.gui.UnregisterIcon)

You do not have to register or unregister anything in this context.

Cheers,
Ferdinand

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

hi,

this is not possible in python. But possible in c++, you can have a look at our manual

Once the logger is created in c++ you can print there with python.

Cheers,
Manuel

posted in Cinema 4D SDK
RE: How can I add a menu to .res file for muti-language support?

Hey,

but if I have mutiple dialogs or sub dialogs from the main dialog(even another plugins), what is happend if those dialog's ID contain some same numbers?

You cannot do that, all integer identifiers for dialogs within a project (i.e., stuff the is located in the same resource directory) must be unique. Except for cycle values, they only do have to be unique within the scope of the cycle/dropdown they are being defined for.

If you really want to have two dialogs which share ID values for gadgets, then you must define them in different projects, each with their own resource directory. Which will also mean that the two dialogs cannot access each other's implementation (in an easy manner, you could of course cook something up with messages for example).

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: How can I add a menu to .res file for muti-language support?

Hello @dunhou,

You are right, my little diagram was not correct, the string folders must be part of the resource, I forgot to indent them one level. I have fixed the diagram.

The string definitions in language\dialog, e.g., string_us/dialogs/myDialog.str, define the strings referenced by a dialog definition, e.g., dialogs/myDialog.res. E.g., when you have some dialog gadget which is defined to have NAME IDS_MY_GADGET in myDialog.res, the myDialog.str files for all languages must define a string IDS_MY_GADGET.

The file c4d_strings.str on the other hand is used to store generic string, and it is not specifically made for menus, it is just one way you can use it. There is no (public) mechanism to define menus in resources, so, all you can do is to define strings in the c4d_strings.str files and then load strings for the currently active language inside Cinema 4D to build a localized menu out of them.

You can see this multipurpose nature at the example of the strings_en-US/c4d_strings.str file of the Asset API Examples Plugin. I stored there all kinds of string information, ranging from tooltips, over the names of the examples and their short descriptions, to some URLs. c4d_strings.str does exactly what its name implies, it stores generic strings.

I hope this clarifies things.

Cheers,
Ferdinand

posted in Cinema 4D SDK