Getting an effective value of an enum from a GraphNode
-
I am trying to wrap my mind around the GraphNode api, and starting to get it a bit (i think)
However, im trying to retrieve the type of a RS Bump Map, however im having a bit of trouble.
I can get the bump map node itself, but I cannot seem to get the effective value that is in its port.
This is my latest attempt, it fails at typeNode.GetValue(maxon::EffectivePortValue,ifnoerr(auto & bumpRes = inputs.FindChild(maxon::Id("com.redshift3d.redshift4c4d.nodes.core.standardmaterial.bump_input"))) { maxon::GraphNode bumpNode = getConnectedNode(bumpRes, maxon::NODE_KIND::NODE); auto assetId = bumpNode.GetValue(maxon::nodes::AssetId).GetValueOrDefault().GetValue(); const maxon::Id& nodeId = assetId.Get<0>(); if (nodeId == maxon::Id("com.redshift3d.redshift4c4d.nodes.core.bumpmap")) { ifnoerr(auto & typeNode = bumpNode.GetInputs().FindChild(maxon::Id("com.redshift3d.redshift4c4d.nodes.core.bumpmap.inputType"))) { maxon::ConstDataPtr inputData2 = typeNode.GetValue(maxon::EffectivePortValue, maxon::DataType::DefaultValue()) iferr_return; auto type = inputData2.GetType(); auto typeId = type.GetId(); } } }
-
Hey @ECHekman,
The value of a port should be accessed with GraphNodeFunctions.GetPortValue or
GraphNodeFunctions::GetEffectivePortValue
. Usually, the former is fine, and only for some specialty cases where the actual port data does not reflect what the user sees, and you want to access the user facing value, you have to use the latter.What you are doing there, is the old access via
GetValue
which is primarily meant for attribute access these days. But it should still work. As always for non-executable code examples, it is hard to judge what is going wrong there for you. But note that checks like these are not doing what you probably think they are doing:ifnoerr(auto & typeNode = bumpNode.GetInputs().FindChild(maxon::Id("..."))) { ... }
I personally would use
auto
only sparingly and especially with the added line-break and the unfortunate placement of the reference operator had to look twice what you are doing here. But that is just personal taste, even if we wrote it like this in a more Maxonic way, it would not do what you probably think it does.void SomeFunction() { iferr_scope_handler { // DiagnosticOutput("@ failed with error: @", MAXON_FUNCTIONNAME, err); return; }; const maxon::GraphNode& someInput = bumpNode.GetInputs().FindChild(maxon::Id("...")) iferr_return; if (!someInput) return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Input node not found"_s); }
The return value of
FindChild
will always be amaxon::GraphNode
, unless an internal error occurs. A non-valid node path will not raise an error but return an empty and with that invalid node.if (someInput.IsValid()) { // Do something with someInput } else { // Handle the case where the input node is not found }
Cheers,
Ferdinandedit: It is good to see that you are moving towards the Maxon API. But when I see things like this:
maxon::GraphNode bumpNode = getConnectedNode(bumpRes, maxon::NODE_KIND::NODE);
I am not so sure you are on the right track. When you write functions within the Maxon API, you should use our error handling. When you terminate the error handling within your functions, you will probably not have a good time. I.e., the signature should be this
getConnectedNode(const GraphNode& input, const NODE_KIND kind) -> Result<GraphNode>
and not
-> GraphNode&
or-> GraphNode
. Note that there is alsoGraphModelHelperInterface
which likely already implements what you are implementing there. See the GraphModelInterface Manual for an overview. -
Hey @ferdinand,
Sorry to interject here, I'm just curious why it's recommended to put the return type at the end (of course, I prefer this Python-like writing style), but as far as I know, this is a relatively old-fashioned writing style (c++11), and the code I observed during my learning process was all of the predecessor types, this is quite confusing for a C++ beginner like me.
Cheers~
DunHouResult<GraphNode> getConnectedNode(const GraphNode& input, const NODE_KIND kind);```
-
Hey @Dunhou ,
That is a good question, although slightly off-topic. What I used there is called trailing return type. It was introduced in C++11, specifically in the context of lambda expressions. The syntax allows you to specify the return type after the function parameters, which can be useful in certain situations, especially when dealing with complex types or when the return type depends on template parameters. It is functionally identical to the leading style. Python is deeply related to C and C++, and many of its conventions, concepts, and features are rooted in C/C++ programming practices.
I just went here instinctively for this notation, as it is somewhat common to use it when you talk about a function in terms of its signature, rather than its concrete implementation. It is not a requirement, and you can write the function both ways, we in fact - except for lambda expressions - do not use this notation in our codebase.
// This is the same as Sum1(const int a, const int b) -> int { return a + b; } // as this (except for the name so that they can coexist). int Sum2(const int a, const int b) { return a + b; } // But we need the trailing notation for lambdas, of which modern Maxon API Cinema 4D makes heavy use. void Foo() { auto add = [](const int a, const int b) -> int { return a + b; }; cout << add(1, 2) << std::endl; }
So, to recap, we recommend to use
Result<T>
as the return type for everything that comes into contact with Maxon API error handling, so that you can propagate errors. We do not make any strong recommendations regarding return type conventions. We slightly recommend the traditional leading return type notation, but it is not a requirement.Cheers
Ferdinand -
I wrote a whole a4 reply about your suggestions about maxon style, error handling and my opinions about the sparse documentation and the new api in general, but i deleted it because im just so baffled by how hard it is to just get a connected node, and then get a single enum value from a node that I literally stopped caring about code architecture and style. I have tried so many different thing now im at a loss at what is supposed to be the way to do this?
All I want to do:
1: Get the bumpmap that is connected to the standard material node. (com.redshift3d.redshift4c4d.nodes.core.standardmaterial.bump_input)
2: Get the inputType enum value of the bumpmap node (com.redshift3d.redshift4c4d.nodes.core.bumpmap.inputType)I have tried so many different things im not even going to post code. I have read the sdk manual. I have rooted through the examples projects. I have read all the code comments in the headers. Never in my life have i spent so much effort doing something this conceptually simple. What am i supposed to do here?
I just want this value
-
Hey @ECHekman,
I sense there is some frustration, but I am not sure telling us how bad our API is will get us anywhere. Yes, the Nodes API ist not trivial, but you are only on the using part (which is not that hard to understand) not the implementation part (which is the trickly one). There are multiple render engine vendors who took that hurdle. I already answered your questions, and as always you will not see source code from us, unless you give us executable code in the first place upon which we can build, or we deem a subject new.
I often bend these rules a bit where it makes sense to meet our customers and third parties halfway. But you cannot just throw a snippet at us and then expect us to invent everything around it and then fix that for you. Executable code makes a difference as lined out in our support procedures. My hunch would be that your
getConnectedNode
does not work because you do not check if your nodes are valid.You can get the value of a port with
GetPortValue
orGetEffectivePortValue
. What you are doing withGetValue
andEffectivePortValue
is the old way but will still work.// Redshift expresses a lot of its enums as strings and not as ints (did not check if that is here the case). const String value = myPort.GetEffectivePortValue<String>().GetOrDefault() iferr_return;
And as lined out before, what your function is trying to do, can likely be done via
GraphModelHelper
too, e.g., with GraphModelHelper::GetDirectPredecessors. An alternative and more manual approach would be usingGraphNode.GetInnerNodes
andGraphNode.GetConnections
.And as always, I am not really looking for a discussion about what you or I would consider a good API. I am telling you that you will be in a world of hurt when you terminate your errors everywhere as you did in your code. Your code will then just silently fail without you knowing why. So, I gave you an example on how to use our error handling.
Cheers,
Ferdinand