Insert a shader into a shader hierarchy
-
Hi...
I know how to add shaders to materials or other shaders, set their parameters etc.
but i can't wrap my head around on how to insert a shader into an unknown tree.explanation ..
lets take Mat[c4d.MATERIAL_COLOR_SHADER] with an unknown content.
now lets use a walker to process the complete shader hierarchy
and for any Layer Shader, create a Filter Shader in place, with the Layer Shader in the link.creating the Filter Shader and adding the Layer Shader inside is easy,
but how can you add the Filter Shader to the (former) parent of the Layer Shader ?
Is there a possibility to know which slot the Layer Shader was in?
Eg. even if you'd type check and know the parent is a Fusion Shader,
this shader would have 3 possible slots (Blend, Mask and Channel)
and you'd not know which slot the Layer Shader has been in.
Is this possible somehow?hope this is explained in an understandable way .-)
-
ok, I've come up with this and it kind of works...
in the GUI it looks correct, but the shader tree is broken.
only the filter shader is in the tree, the layer shader is missing
i'd be thankful for a hint what i'm missing here.import c4d ''' before running the script, create a material with a layer shader in the color slot ''' def main(): li = doc.GetActiveMaterials() if not li: li = doc.GetMaterials() mat = li[0] layerShaders = [] sh = mat.GetFirstShader() while sh is not None: if sh.GetType() == c4d.Xlayer: layerShaders.append(sh) sh = walker(sh) for shLayer in layerShaders: parent = shLayer.GetUp() if parent is None: parent = mat # index = findIndex(parent, shLayer) # old index = findDescId(parent, shLayer) # new :) if index: shLayer = shLayer.GetClone() parent[index].Remove() # the original != the clone shFilter = c4d.BaseShader(c4d.Xfilter) shFilter.InsertShader(shLayer) shFilter[c4d.SLA_FILTER_TEXTURE] = shLayer parent.InsertShader(shFilter) parent[index] = shFilter #def findIndex(parent, shader): # description = parent.GetDescription(c4d.DESCFLAGS_DESC_0) # for bc, paramid, groupid in description: # if paramid: # index = paramid[0].id + 0 # try: # if parent[index] == shader: # return index # except AttributeError: # pass # return None def findDescId(parent, shader): description = parent.GetDescription(c4d.DESCFLAGS_DESC_NONE) for bc, descId, _ in description: if bc[c4d.DESC_SHADERLINKFLAG] == True: if parent[descId] == shader: return descId def walker(op): if not op: return None if op.GetDown(): return op.GetDown() while op.GetUp() and not op.GetNext(): op = op.GetUp() return op.GetNext() if __name__=='__main__': main()
-
Hello @indexofrefraction,
Thank you for reaching out to us. I am having a bit trouble understanding what you want to do, but from what I understood, you have a shader graph something like this:
MyMaterial M +- BranchHead 'Shaders' +- LayerShader L
and want to go to this:
MyMaterial M +- BranchHead 'Shaders' +- FilterShader F +- LayerShader L
while also replacing all base links in the Material M that formerly pointed to the layer shader L with a link to the filter shader F. And your actual question is then (which you did not explicitly ask):
How do I find out which parameters of a node point to another node?
Answer
First of all, I would point out this thread as we recently dealt there with shader hierarchies, which can be anything else than trivial. I will not deal with complex hierarchies here, but when your layer shader is nested inside some other shader (and not directly attached to a material), you will have to.
To find out which parameter types a node has, and by extension if the node has a certain value as one of its parameter values, one must traverse the description of the node. The description of a node defines among other things the structure of its parameters. For a material we can look for all shader links (or a specific value) by testing for
DESC_SHADERLINKFLAG
in the description data container. See the example at the end of this posting for details.Cheers,
FerdinandThe result for a material with a noise shader in its color channel:
Found a shader link parameter: (8000, 133, 5703). Its value <c4d.BaseShader object called Noise/Noise with ID 1011116 at 1636161331392> is a shader of the material. Found a shader link parameter: (8001, 133, 5703). Found a shader link parameter: (8002, 133, 5703). Found a shader link parameter: (8003, 133, 5703). Found a shader link parameter: (8004, 133, 5703). Found a shader link parameter: (526376, 133, 5703). Found a shader link parameter: (526377, 133, 5703). Found a shader link parameter: (526378, 133, 5703). Found a shader link parameter: (526381, 133, 5703). Found a shader link parameter: (526394, 133, 5703). Found a shader link parameter: (526403, 133, 5703). Found a shader link parameter: (526419, 133, 5703). Found a shader link parameter: (8005, 133, 5703). Found a shader link parameter: (8006, 133, 5703). Found a shader link parameter: (8012, 133, 5703). Found a shader link parameter: (8007, 133, 5703). Found a shader link parameter: (8008, 133, 5703). Found a shader link parameter: (8009, 133, 5703). Found a shader link parameter: (3225, 133, 5703).
The code:
"""Traverses the description of a material to find shader link parameters with a specific value. Run this in the script manager with at least one material in the scene. """ import c4d doc: c4d.documents.BaseDocument def main() -> None: """ """ # Get the first material in the document. material = doc.GetFirstMaterial() # I am not going to deal with nested shaders here, and instead look at the simple case of all # shaders being attached to the material directly and us only wanting to inspect such shaders. # Get the shaders directly used by the material. currentShader, shaders = material.GetFirstShader(), [] while currentShader: shaders.append(currentShader) currentShader = currentShader.GetNext() # Now we are going to traverse the description of the material. Iterating over a description # will yield a data container for the current parameter, its DescId, and a third parameter # which is here irrelevant. for bc, descId, _ in material.GetDescription(c4d.DESCFLAGS_DESC_NONE): # We are looking for shader links which is a special case of base links. If a base link # is a shader link, that information is stored in its description data container. The nice # side effect is that this field is only written for base link parameters, so we do not # have to check the parameter type anymore. if bc[c4d.DESC_SHADERLINKFLAG] == True: print (f"Found a shader link parameter: {descId}.") # Get the value of the parameter. value = material[descId] if value in shaders: print (f"\tIts value {value} is a shader of the material.") if __name__ == '__main__': main()
-
hi ferdinand,
our posts probably crossed each other ...
thanks a lot, and yes you understood it rightcould you maybe have a look at the code above ?
i replaced my findIndex() by a new findDescId() using your example
but inserting the Layer Shader into the Filter Shader still doesn't work correctlybest, index
-
Hey,
Well, your code looks mostly okay at first glance, but you know that we state in our forum guidelines that we cannot debug code for users. What jumps to the eye however is that your
walker
function only takes hierarchies into account. At least for the layer shader this will work fine, but to be more robust, I would also traverse the shader branch of each node. The subject has been dealt with in the topic I linked to in my first posting.When your script is not working, please specify what is not working, and where your code fails.
Cheers,
Ferdinand -
hm, actually i think the script does work correctly.
i was additionally checking if the shader tree was correct
and i was doing this using my own shader walker
and it failed, it stopps walking after the Filter Shaderif i use your PrintRelatedNodes() everything looks ok
i will post a follow up question in the mentioned thread :
https://developers.maxon.net/forum/topic/14056/copy-layershader-to-a-new-material/5 -
ok, i think i figured out my issue mentioned above ..
it would be nice if this could be confirmed:materials and shaders can only have shader type nodes,
there must be a shader gelist head at the beginning and followed by normal nodes.
i guess this is was InsertShader() actually does.now if you InsertShader again in the middle of a tree, you get a second shader gelist head
and as expected this the point is where my simple shader traversing failed.
but.. if you insert the filter shader manually in the material manager,
there is no second shader gelist head, just the one at the beginning.so i guess that c4d accepts both ways,
but the proper way would be already to use InsertUnder if we're already in a shader type node list (?)
using InsertShader in the middle of a shader list is not necessary and bad practice (?)
a list with multiple shader gelist heads could (should?) be pruned of excessive heads (?) -
Hey @indexofrefraction,
but the proper way would be already to use InsertUnder if we're already in a shader type node list (?) using InsertShader in the middle of a shader list is not necessary and bad practice (?) a list with multiple shader gelist heads could (should?) be pruned of excessive heads (?)
Well, the answer to that all is sort of yesn't. I personally would for example consider the layer shader carrying its shader dependencies as direct children to be at least a regression of the classic API scene architecture, as the proper place would be the shader branch of the layer shader.
The layer shader engineers might have (somewhat) rightfully thought that it does not really matter, or even had some technical reason to store the shaders as direct children. There is nothing special about branches, they are just contextualized hierarchies. For an object and its tags, they are necessary, because if we would also store tags as direct children of objects, it would become very cumbersome to sort out what is what when tags and child objects would be mixed. For shaders this is not the case, as there is nothing which is naturally a child of shaders as child objects are of objects. But in the end, the 'right' place to store shaders attached to a
GeListNode
is its shader branch. And other shader developers, both internal external, might have solved this differently and let their shaders store their shader dependencies in the 'Shader' branch of their shader. Which at end forces other developers to understand what each shader type does and imitate it when it must be handled in code.There is no 'satisfying' answer, when a shader type stores its shader dependencies as direct children, you must use
InsertUnder
, and when it does store its shader dependencies in the shader branch, you must useInsertShader
(which is just a shortcut for callingInsertUnder
on the shader branchGeListHead
of that node).Cheers,
Ferdinand -
hi Ferdinand,
i noticed that there is actually an issue with excessive gelist heads....
if you have this :<c4d.BaseShader object called 'Filter/Filter' with ID 1011128 at 0x7fcebe74eeb0> + <c4d.GeListHead object at 0x7fcebe787a70> branch 'Shaders' + + <c4d.LayerShader object called 'Layer/Layer' with ID 1011123 at 0x7fcebe787ab0> + + + <c4d.BaseShader object called 'Noise/Noise' with ID 1011116 at 0x7fcebe787b90>
GetUp() on the LayerShader seems to return None,
which is a problem if - like in this case - you need to know if a shader is in the middle inside a shader tree,
or if it is at the top / a direct child of the materialthis makes something like this necessary to properly "insert" a shader ....
if isinstance(target, c4d.BaseShader): shader.InsertUnder(target) else: target.InsertShader(shader)
-
Hey @indexofrefraction,
well, this is not surprising, as the
LayerShader
is not in a hierarchical relation with theGeListHead
but a branch relation. You must call GetListHead on aGeListNode
to retrieve its head, i.e., the branch which is containing the node.Cheers,
Ferdinand -
I also stumbled on this just today, and had the suspicion that BaseList2D::InsertShader() isn't the right function for this. Thank you @ferdinand for verifying that.
While it totally makes sense to use GeListNode::InsertUnder() when inserting shaders under shaders inside the shaders branch, I think it should totally be mentioned in the SDK docs. Ideally, as a big fat
@note
for the Doxygen description of BaseList2D::InsertShader(), as well as in the Shader section of the BaseList2D Manual.Cheers,
Frank -
Hey @fwilleke80,
Thank you for reaching out to us. If I remember correctly, we briefly talked about this internally when this thread was created. The problem is, as lined out above,
GeListNode::InsertUnder()
is not the unambiguously correct function to insert a shader below a shader, and by conventionBaseList2D::InsertShader()
is neither. It depends on the implementation of the shader what is correct and what not.So, flat out stating something like 'shaders which have other shaders as dependencies will carry them as direct children' as a warning in
BaseList2D::InsertShader()
can simply not be guaranteed to be correct. When the question was recent, I checked a couple of other shader types as the fusion shader, the filter shader, etc., and they all behaved the same as the layer shader, i.e., they carried their dependencies as direct children. But this only came by convention and not by definition, and there could certainly be shaders out there which handle this differently.However, and long story short, I understand your concerns and will add a small note, although a bit more defensively stated. In the end you must check yourself what each shader type does.
Cheers,
Ferdinand -
Then maybe it should be made an official guideline to insert shaders into materials with InsertShader(), and into a hierarchy of shaders with InsertUnder()
You're right, I think we did talk about this before. But I totally forgot, since it's not in the docs.
-
And, as a tip for other developers, I want to add that the "Active Object Dialog" example plugin from the C++ SDK does a fabulous job exposing things like this, and visualizing the document's node hierarchy.
Whoever wrote that back in the days still deserves a medal
-
this "Active Object Dialog" plugin looks very interesting
if it would be understandably described how to compile it / the c++ sdk, we could check it out
but sadly this got so complicated that you need a degree in IT to do it. .-) -
@indexofrefraction said in Insert a shader into a shader hierarchy:
this "Active Object Dialog" plugin looks very interesting
if it would be understandably described how to compile it / the c++ sdk, we could check it out
but sadly this got so complicated that you need a degree in IT to do it. .-)The first video in my tutorial series shows you how to compile the SDK example plugins.
-
Thanks a lot kbar.. i'll watch and learn!
still... the hurdles to compile a c++ plugin are extremely high nowadays.
it would be great if the sdk plugins would be downloadable ready compiled as well