Control whether a redshift shader node port is collapsed or not.

Hi,

We create Redshift shader nodes programmatically in C++ and would like to control whether groups like Subsurface are collapsed or not:

7a234e8c-36ac-4bfb-b9dc-d7ca2798af3c-image.png a605bd97-7e1f-4d77-9068-06709a956d08-image.png

Setting up the node space, getting its graph, creating the nodes and their connections works fine.
I however don't know how to find the groups like Base, Reflection, Subsurface, ... and how to set whether those are collapsed or not.

Based on GraphModelInterface Class Reference I thought that Base, Reflection, Subsurface, ... might be NODE_KIND::INPORT with the other ports as children, however when I iterate over the ports of the node I can only find their child nodes.

So for the node in the below reduced code example, how do I get e.g. the Subsurface "port-group" and how to I control whether it is collapsed or not?

maxon::Id nodeSpaceID = maxon::Id("com.redshift3d.redshift4c4d.class.nodespace");
const maxon::nodes::NodesGraphModelRef& graph = nodeMaterial->GetGraph(nodeSpaceID) iferr_return;

maxon::GraphNode node = graph.AddChild(maxon::Id(), maxon::Id("com.redshift3d.redshift4c4d.nodes.core.standardmaterial"), maxon::DataDictionary()) iferr_return;

Best
Till Niese

Hello @till-niese,

Thank you for reaching out to us.

I however don't know how to find the groups like Base, Reflection, Subsurface, ... and how to set whether those are collapsed or not.

These things are metadata associated with ports; I think they dubbed these layers. You should be able to get hold of the GraphNode instances representing them when you traverse the children of a node manually, but they are just 'fluff' nodes which do not have any higher meaning aside from structuring things for the user. In 2023.1.3 they still have an indication of a port when unfolded, this will be changed in an upcoming version. There is nothing to connect to them, they are not ports. What do you want to do with them?

d43d6d17-b141-401e-9f2e-ed0ed0c5dd27-image.png

Just to be clear here. Image in the RS Texture node above is not a port or port bundle, but a layer because its name is printed in bold. It has no ID (at least none we can see easily) and we cannot connect things to it. Filename on the other hand is not only a port, but also port bundle. Which has the child ports Path and Layer. Accessing bundled ports is just means accessing them hierarchically, .e.g, for the Path port (taken from the Redshift Renderer Manual) :

// Here we encounter something new, a port which has ports itself is a "port-bundle" in terms
// of the Nodes API. The "Filename" port of a texture node is a port bundle which consists out
// of two sub-ports, the "path" and the "layer" port of the texture. The texture URL must be 
// written into the "path" sub-port.
maxon::GraphNode pathPortRustTexNode = rustTexNode.GetInputs().FindChild(
    maxon::Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0")).FindChild(
        maxon::Id("path")) iferr_return;
pathPortRustTexNode.SetDefaultValue(rustTextureUrl) iferr_return;

// PS: This is actually taken from an for 2023.1.3 changes updated and not yet published version.

... and how to set whether those are collapsed or not.

That is not possible/intended just as for example you are not supposed to set directly the position of a node. The Nodes API follows the Model-View-Presenter data model, and with GraphModelInterface and GraphNode you are operating on the model of the graph, its logical data. There can be multiple presenters for the same model and these interfaces are not public, nor are the maxon attributes with which you could access that data.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hello @ferdinand

Thank you for your detailed answer.

What do you want to do with them?

If we create a Standard Material Node, and connect Texture nodes to "Subsurface > Color" and "Subsurface > Weight" the resulting Node looks like this:

1068a261-66d6-4787-a3ea-c2f5fb46e04c-grafik.png

We however want that all ports that have a connection are visible. So that the node looks this way instead:
e697500a-6536-4290-9ea2-d1df60f7b7ca-grafik.png

That's the reason why we would like to control wheter the a layer like "Subsurface" is collapsed/closed or not.

But if I understand this part of your answer correctly:

That is not possible/intended just as for example you are not supposed to set directly the position of a node.

it is at least not possible/intended to control those layers directly.

Is there a way to in general tell a node - or the whole graph - that all connected ports should be shown, so that we have the desired result without controlling the layers manually?

Best,
Till Niese

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());
    }
  }
}

MAXON SDK Specialist
developers.maxon.net