Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Forum wide moderators
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
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
None
rd.GetName()
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()
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.
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.
BaseDraw
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:
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.
The result:
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()
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.
c4d.documents.BatchRender
BaseDocument
c4d.documents.LoadDocument
c4d.documents.SaveDocument
c4d.documents.RenderDocument
c4d.modules.net.NetRenderService
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.
RenderDocument
NetRenderService
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.
plugin A
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.
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.
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).
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.
language\dialog
string_us/dialogs/myDialog.str
dialogs/myDialog.res
NAME IDS_MY_GADGET
myDialog.res
myDialog.str
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.
c4d_strings.str
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.