• Treeview does not refresh

    r20 python
    5
    0 Votes
    5 Posts
    1k Views
    H
    Hello @ferdinand, sorry for coming back this late. Work kept me quite busy. @ferdinand so, I had a play with your code example. It works like a charm and also seems to have a lot of potential for all kinds of update/dirty checking issues. Thanks again @ferdinand for your help, the effort you always put into answering our questions and the eloborate examples you come up with! Cheers, Sebastian
  • How to implement a license check on a python plugin?

    python
    5
    0 Votes
    5 Posts
    1k Views
    ThomasBT
    @manuel Hi Manuel, I just wanted to let you know that I have decided on your suggestion and have decided on a license system with serial numbers that are automatically created and delivered upon purchase and in combination with a kind of license server that controls the licenses..... thank you for the hints
  • How to understand symbols of some spcial case ?

    2023 python windows
    5
    1
    0 Votes
    5 Posts
    584 Views
    DunhouD
    @ferdinand Thanks for your help. I did search on web and find the Unicode string, and Chinese characters is so much complicated ,when most user use Chinese for the GUI language, Maybe sometime the translation of the world "在队列中" witch means "in the queue" in English has changed ( I belive now Chinese translation is response to IHDT so it won't randomly changed). That is not a big problem but maybe a little "uniform" with the "ID" And the "brief moment" is I don't sure why does it happend. In another word ,I think the initializing render process can be also called "rendering" . so it is a bit of counterintuitive for me , maybe I should add an additional check to make sure the spying will not break while the brief. Cheers~
  • C4D Threading in python doesn't work as expect .

    sdk python 2023
    9
    0 Votes
    9 Posts
    2k Views
    DunhouD
    @ferdinand thanks for your generoso help. I already send you an email with an zip file , and some notes in the front of the codes. Have a good day!
  • Catch - 'c4d.BaseDraw' is not alive

    2
    0 Votes
    2 Posts
    609 Views
    ferdinandF
    Hey @mogh, Thank you for reaching out to us. The idea of a node, a c4d.C4DAtom not being alive means that the Python layer cannot find the C++ C4DAtom which the Python layer c4d.C4DAtom was referencing, usually because something has been reallocated in the background while someone was long-term storing a c4d.C4DAtom instance. We talked about it here in more detail. Sometimes you then must write elaborate interfaces which recapture the same thing over their UUID, e.g., get hold of the same shader once its Python reference has gone bad. But that does not seem necessary here since you are interested only in the active viewport of the active document. It is in this case also not only the case that the viewport could have been reallocated, but also what is the active viewport could have changed (although that will likely cause viewports to be reallocated). So, when you want to modify the active base draw, in fact any node, make sure to get a fresh reference when possible. doc: c4d.document.BaseDocument = c4d.documents.GetActiveDocument() bd: c4d.BaseDraw = doc.GetActiveBaseDraw() self.AddCheckbox(ID_SAVEFRAME, flags=c4d.BFH_LEFT, initw=270, inith=0, name="Show Save Frame") self.SetBool(ID_SAVEFRAME, bd[c4d.BASEDRAW_DATA_SHOWSAFEFRAME]) If you need access to the active viewport of the active document very often, you could make it a property with some mild caching. import c4d import typing class Foo: """Provides cached access to the active document and active viewport. """ def __init__(self) -> None: self._activeDocument: typing.Optional[c4d.documents.BaseDocument] = None self._activeViewport: typing.Optional[c4d.BaseDraw] = None @property def ActiveDocument(self) -> c4d.documents.BaseDocument: """Returns the active document. """ if self._activeDocument is None or not self._activeDocument.IsAlive(): self._activeDocument = c4d.documents.GetActiveDocument() return self._activeDocument @property def ActiveBaseDraw(self) -> c4d.BaseDraw: """Returns the active viewport in the active document. """ if self._activeViewport is None or not self._activeViewport.IsAlive(): self._activeViewport = self.ActiveDocument.GetActiveBaseDraw() return self._activeViewport def Bar(self) -> None: """Makes use of the #ActiveBaseDraw property. """ self.ActiveBaseDraw[c4d.BASEDRAW_DATA_TEXTURES] = True Although this also suffers from the problem that what is the active viewport could have changed. Caching anything that is "active" is a bad idea as what is active can change. Active entities should always be retrieved when required unless one can guarantee that they cannot change (by being in a modal dialog for example). Cheers, Ferdinand
  • How to get the bounding box for the whole scene

    python
    5
    0 Votes
    5 Posts
    3k Views
    ferdinandF
    Hey @mogh, Thank you for pointing out that problem. No, there is no Vector(0, 0, 0) being added, there was simply a small bug in my code. self.min: c4d.Vector = c4d.Vector(sys.float_info.max) self.max: c4d.Vector = c4d.Vector(sys.float_info.min) float_info.min is 2.2250738585072014e-308, i.e., the smallest representable increment of the float type, not the lower representable boundary. So, the code did initialize the max-value boundary as ~(0, 0, 0) which then caused values to be discarded which should not be discared. I of course meant here the following: self.min: c4d.Vector = c4d.Vector(sys.float_info.max) self.max: c4d.Vector = c4d.Vector(-sys.float_info.max) Fixing this should fix your problems. I have updated my original posting to not mislead future readers. Cheers, Ferdinand
  • This topic is deleted!

    1
    0 Votes
    1 Posts
    5 Views
    No one has replied
  • How to get and create redshift light groups with python?

    2023 python
    3
    0 Votes
    3 Posts
    538 Views
    gheyretG
    @manuel Cool ! Thank you ~
  • Button Hover -> Helptext - searching for example

    Moved
    5
    0 Votes
    5 Posts
    888 Views
    M
    I use CreateLayout ... no description file ... but as I wrote probably a little bit to cryptic I am fine for now. Thanks for the reply Manuel. Cheers mogh
  • TreeView for ObjectData plugin

    python r23
    2
    0 Votes
    2 Posts
    507 Views
    M
    Hi Mike this is unfortunately not possible, even in C++ for more info please look at Info for ITEMTREE/customgui_itemtree. Cheers, Maxime.
  • Purpose of different preference folders

    3
    0 Votes
    3 Posts
    998 Views
    A
    Hi @ferdinand, Thank you for the quick reply. This answers all my questions! Cheers, Arttu
  • 0 Votes
    4 Posts
    849 Views
    ferdinandF
    Hey @mogh, just to clarify: I found that bit of the documentation before you pointed it out, but in my mind I could not link "needs a document" to "the object has to be present" ... Well, the documentation clearly states it: doc (Optional[c4d.documents.BaseDocument]) – The document for the operation. Should be set if possible. Must be set for MCOMMAND_JOIN, MCOMMAND_MAKEEDITABLE, MCOMMAND_CURRENTSTATETOOBJECT and MCOMMAND_SPLINE_PROJECT. If you set the document, the objects which you pass to this function have to be in the same document. So pay attention that you use one send_modeling_command per document for objects. But that could be more visible. I'll see if I can rework the function documentation a bit to make that important information more prominent. Cheers, Ferdinand
  • This topic is deleted!

    1
    0 Votes
    1 Posts
    26 Views
    No one has replied
  • List All Nodes With No Filter?

    python 2023
    3
    0 Votes
    3 Posts
    721 Views
    B
    RE: Yes, GraphModelHelper.ListAllNodes requires at least one attribute to be set in the data dictionary. Gotcha. Thanks for the clarification. Anyway, the suggested alternative (i.e. write a separate iterator) still works as expected. Same as before.
  • Trying to programmatically create vertex color tag

    2
    0 Votes
    2 Posts
    793 Views
    ferdinandF
    Hello @zauhar, thank you for reaching out to us. Your code does look mostly fine and is working for me. The line op = Default makes little sense and you are of course missing things like imports, a context guard and a main function. Not all of them are absolutely necessary, but it is strongly advised to use them all, especially for novice users. Find a code example below. Vertex colors are also not automatically rendered as the diffuse color in Cinema 4D, one must use a VertexMap shader for that. Please consult the user manual or end-user support for end-user questions, we cannot deliver such support here. Cheers, Ferdinand The result for two polygon objects, the color of a vertex depends on its position in global space. [image: 1676539052471-0e9b3117-e70a-4495-8001-cbe0a6b0a2e4-image.png] Code """Creates vertex-color tag on the currently selected polygon object and makes it the active tag. Must be run as a Script Manager scrip with a polygon object selected. The color of each vertex will depend on its position in global space. """ import c4d import typing GRADIENT_MIN: float = -200.0 # The lower boundary of the gradient. GRADIENT_MAX: float = 200.0 # The upper boundary of the gradient. op: typing.Optional[c4d.BaseObject] # The active object, can be `None`. def main() -> None: """Runs the example. """ # Check that there is indeed a selected object and that it is a polygon object. if not isinstance(op, c4d.PolygonObject): raise TypeError("Please select a polygon object.") # Get its point count and allocate a vertex color tag with that count. count: int = op.GetPointCount() tag: c4d.VertexColorTag = c4d.VertexColorTag(count) if not isinstance(tag, c4d.VertexColorTag): raise MemoryError("Could not allocate tag.") # We are going to make it a little bit more exciting and write a gradient based on the global # coordinate y-component of a vertex. # Set the tag to defining colors only once per vertex instead of having N colors per vertex, # where N is the number of polygons attached to it. tag.SetPerPointMode(True) # Get the global matrix of the polygon object and all its points in global space. mg: c4d.Matrix = op.GetMg() globalPoints: list[c4d.Vector] = [mg * p for p in op.GetAllPoints()] # Define a callable with which we can get the gradient color of a point over its index. GetGradientColor = lambda i : c4d.Vector4d( c4d.utils.RangeMap(globalPoints[i].y, GRADIENT_MIN, GRADIENT_MAX, 0, 1, True), 0, 0, 1) # Get the data pointer of the tag and start writing data. dataW: object = tag.GetDataAddressW() for i in range(count): c4d.VertexColorTag.SetPoint(dataW, None, None, i, GetGradientColor(i)) # Insert the tag into the selected object, make it the active tag, and push an update event. op.InsertTag(tag) tag.SetBit(c4d.BIT_ACTIVE) c4d.EventAdd() if __name__ == "__main__": main()
  • Control whether a redshift shader node port is collapsed or not.

    2023 c++
    4
    2
    0 Votes
    4 Posts
    752 Views
    ferdinandF
    Hello @till-niese, No, that is not possible for public API users. As I tried to indicate before, you are operating here on the data model of the graph with GraphModelInterface. There is also a GraphModelPresenterInterface which is the presenter and provides some GUI logic as selecting nodes and wires, but that interface is non-public. The toggle state of a widget is buried deep in the presenter and not even exposed over the interface. There are also the non-public attributes about which I talked before. I went ahead and wrote some code trying to emulate access in the public API [1], but unsurprisingly, there is nothing to be found in the public graphs, the code will not print anything. It is simply not possible to do what you want to do here, and it is quite common for our APIs to not expose GUI functionalities. In the case of the Node API, the complexities of the MVP application model are added on top of that. There can be multiple presenters coordinating multiple views on a singular model. Or in less fancy: One can have multiple node editors opened on the same graph. The toggle state of a port bundle/group is shared between all editors at the moment. And aside from a principal stance of the feasibility of exposing GUIs, there might be substantial problems with exposing the toggle state despite the external perception that is just "this one thing"; I do not know for sure since I am not familiar with the implementation details here, but I would not be surprised. In the Asset API we had a similar case with people wanting to know "the selected asset" and our answer then being "that is an ambiguous question because there can be more than one Asset Browser". In the Asset API the solution is to open your own Asset Browser where you can then get that state. There is AFAIK currently no alternative solution as such for your problem, e.g., a node command which would toggle the folding state of a port. We could think about adding such command, but I doubt that I will find many supporters for this idea. Cheers, Ferdinand [1] // graph is a maxon::NodesGraphModelRef/GraphModelRef instance // rustTexNode is a RS Texture GraphNode in #graph. maxon::GraphNode root = graph.GetRoot(); maxon::GraphNode portBundle = rustTexNode.GetInputs().FindChild( maxon::Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0")) iferr_return; // Iterate over candidates where this widget data is internally to be found, a graph root, a true // node, and a port (bundle). for (const maxon::GraphNode& node : { root, rustTexNode, portBundle }) { // Iterate over some attribute identifiers where GUI widget data is stored internally. for (const maxon::String& attr : { "widgetDataBlackBox"_s, "widgetDataBlackBoxSM"_s, "widgetDataBlackBoxOut"_s }) { maxon::InternedId attrId; attrId.Init("widgetDataBlackBoxOut") iferr_return; // Get the widget data stored at the maxon attribute #attr and iterate over its entries, the // data will always be empty/null. const maxon::DataDictionary attrData = node.GetValue<maxon::DataDictionary>( attrId).GetValueOrNull() iferr_return; for (const auto& entry : attrData) { const maxon::Data key = entry.first.GetCopy() iferr_return; const maxon::Data value = entry.second.GetCopy() iferr_return; ApplicationOutput("node: @, attr: @, key: @, value: @, type: @", node, attr, key, value, value.GetType()); } } }
  • How to Add Child Shaders to a Fusion Shader?

    python r20
    6
    1
    0 Votes
    6 Posts
    2k Views
    H
    Hello @ferdinand, when trying out your function to traverse shaders I noticed that for two materials in the scene it yields for the first material all the shaders of itself but also all the shaders of the second material. For second material it works like expected and yields only the shaders of itself. After fiddling around a bit with some other code of you from this post I think I ended up with a function that is yielding all the shaders from a material iteratively. Find the code below. Maybe this will help others. Cheers, Sebastian def iter_shaders(node): """Credit belongs to Ferdinand from the Plugincafe. I added only the part with the material and First Shader checking. Yields all descendants of ``node`` in a truly iterative fashion. The passed node itself is yielded as the first node and the node graph is being traversed in depth first fashion. This will not fail even on the most complex scenes due to truly hierarchical iteration. The lookup table to do this, is here solved with a dictionary which yields favorable look-up times in especially larger scenes but results in a more convoluted code. The look-up could also be solved with a list and then searching in the form ``if node in lookupTable`` in it, resulting in cleaner code but worse runtime metrics due to the difference in lookup times between list and dict collections. """ if not node: return # The lookup dictionary and a terminal node which is required due to the # fact that this is truly iterative, and we otherwise would leak into the # ancestors and siblings of the input node. The terminal node could be # set to a different node, for example ``node.GetUp()`` to also include # siblings of the passed in node. visisted = {} terminator = node while node: if isinstance(node, c4d.Material) and not node.GetFirstShader(): break if isinstance(node, c4d.Material) and node.GetFirstShader(): node = node.GetFirstShader() # C4DAtom is not natively hashable, i.e., cannot be stored as a key # in a dict, so we have to hash them by their unique id. node_uuid = node.FindUniqueID(c4d.MAXON_CREATOR_ID) if not node_uuid: raise RuntimeError("Could not retrieve UUID for {}.".format(node)) # Yield the node when it has not been encountered before. if not visisted.get(bytes(node_uuid)): yield node visisted[bytes(node_uuid)] = True # Attempt to get the first child of the node and hash it. child = node.GetDown() if child: child_uuid = child.FindUniqueID(c4d.MAXON_CREATOR_ID) if not child_uuid: raise RuntimeError("Could not retrieve UUID for {}.".format(child)) # Walk the graph in a depth first fashion. if child and not visisted.get(bytes(child_uuid)): node = child elif node == terminator: break elif node.GetNext(): node = node.GetNext() else: node = node.GetUp()
  • Relative File Path Not Recognize by Plug-in?

    2023 python
    5
    0 Votes
    5 Posts
    1k Views
    ferdinandF
    Hello @bentraje, Yes, the Python variant of GeGetPluginPath is not that useful as it just calls the C++ implementation which will then return the Python module path because the Python module is the C++ plugin which is calling GeGetPluginPath in that case. I will make the function description a bit more clear about that and also add a little example snippet for the module attribute __file__. And as what I would consider a fun fact: The __file__ attribute of modules is one of the many things where the language Python took inspiration from C/C++. In C++, __FILE__ is the macro which refers to the source file it is referenced in. I used this for example recently in the C++ Color Management examples to load an ICC file which is located next to a source file. But since users are usually not in the habit of compiling their C++ code themselves, or if they do, then want to move the resulting binary to a different place, __FILE__ is much less useful than __file__ as C++ there is a difference between the source and the executable. Cheers, Ferdinand
  • Run a GUI Dialog AFTER C4D Launches not BEFORE?

    python 2023
    7
    1
    0 Votes
    7 Posts
    2k Views
    B
    @ferdinand Thanks for the response. With the illustration code you provided, I misunderstood the documentation. Thanks for the clarification. Basically, I thought the PluginMessage/RegisterPlugin functions should be method (i.e. they should be under the class of the plugin data, command data etc). They actually live outside the classes. It now works as expected. Thanks!
  • InExclude detect 'Remove All' context menu command

    python
    3
    1
    0 Votes
    3 Posts
    628 Views
    mikeudinM
    Thank you @ferdinand ! Will chek it!