Traversing a layer shader with python
- 
Hi guys,
Got a question regarding traversing a layer shader with Python.
For an internal plugin I need to go through shaders and localize/globalize the file paths in the bitmaps.
When I run into a layer shader I want to get into said shader and traverse all layers, checking if it's a bitmap and if so localize/globalize the path.
However, the SDK documentation is very sparse for the layer shader itself, specifically the "LayerShaderLayer.GetParameter(self, id)", it says to look at the C++ documentation for ID's, which I did.
For instance "For shaders (TypeShader): LAYER_S_PARAM_SHADER"
Neither:- 2
 - LAYER_S_PARAM_SHADER
 - [LAYER_S_PARAM_SHADER]
 - [c4d.LAYER_S_PARAM_SHADER]
Give me any result. 
2 comes back as "none" and the rest gives an attribute error.
0 also comes back as "none".I imagine that the GetParameter is used to get the file path of a layer if it's a bitmap, if not, what should I use?
The LayerShaderLayer only seems to have: GetNext, GetType, GetName, GetPreview, GetParameter, SetParameter.When I run a GetType on the layer, it gives me back "2" which is "TypeShader" and with GetName it returns the name of the Bitmap in that layer.
But how do I then get to the actual file path inside that layer to change it?Kind regards,
Joep
 - 
Hello @Peek,
Thank you for reaching out to us. Please share your code, as things otherwise tend to become one giant guessing game for us. In general, I would recommend having a look at BaseShader: Access and Structure, as the hierarchy and branching structure of shaders is a bit dicey. I would also recommend having a look at this thread as it lines out some details about the layer shader.
Other than that, I am not quite sure what you want to do with
LAYER_S_PARAM_SHADER. It is just the stride for a group of parameters, namely these here:/// @addtogroup LAYER_S_PARAM_SHADER /// @ingroup group_containerid /// @{ /// Parameters for @ref TypeShader layers. #define LAYER_S_PARAM_SHADER_MODE 2000 ///< ::Int32 Blend mode: @enumerateEnum{BlendMode} #define LAYER_S_PARAM_SHADER_BLEND 2001 ///< ::Float Blend parameter. #define LAYER_S_PARAM_SHADER_LINK 2002 ///< @c void* Pointer to a BaseLink that contains the shader, read-only. /// @}Unless you want to ignore the whole
c4d.LayerShaderLayerinterface, you will not have to deal with these strides, which divide the data container of the actualLayerShaderinto bins. Without your code I can only guess what you are trying to do. Find an example below.Cheers,
FerdinandResult:

shader = <c4d.LayerShader object called Layer/Layer with ID 1011123 at 2670729460672> layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_MODE) = 2 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_BLEND) = 1.0 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK) = <c4d.LayerShader object ... layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_MODE) = 3 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_BLEND) = 0.75 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK) = <c4d.BaseShader object ... layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_MODE) = 1 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_BLEND) = 0.5 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK) = <c4d.BaseShader object ... shader = <c4d.LayerShader object called Layer/Layer with ID 1011123 at 2670729454400> layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_MODE) = 2 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_BLEND) = 0.5 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK) = <c4d.BaseShader object ... layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_MODE) = 1 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_BLEND) = 1.0 layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK) = <c4d.BaseShader object ... shader = <c4d.BaseShader object called Color/Color with ID 5832 at 2670659243072> shader = <c4d.BaseShader object called Fusion/Fusion with ID 1011109 at 2670659205440> shader = <c4d.BaseShader object called Noise/Noise with ID 1011116 at 2670659241152> shader = <c4d.BaseShader object called Noise/Noise with ID 1011116 at 2670659215552> shader = <c4d.BaseShader object called Gradient/Gradient with ID 1011100 at 2670729457088> shader = <c4d.BaseShader object called Noise/Noise with ID 1011116 at 2670729455424>Code:
"""Demonstrates how to reach layer shader information. Run this script with at least one material in a scene, with a shader tree in its color channel. See also: https://developers.maxon.net/forum/topic/14059/ """ import c4d import typing doc: c4d.documents.BaseDocument # The active document def iter_shader( shader: c4d.BaseShader, level: int = 0) -> typing.Iterator[tuple[c4d.BaseShader, int]]: """Yields all shader, level tuples in the shader tree #shader. """ if not isinstance(shader, c4d.BaseShader): return yield (shader, level) # Because hierarchical relations between shaders are not standardized, we must traverse both # their hierarchical relations and the shader branching relation. for child in shader.GetChildren(): # hierarchy for item in iter_shader(child, level + 1): yield item for item in iter_shader(shader.GetFirstShader(), level + 1): # branching yield item def main() -> None: """Runs the example. """ # Get the color channel sahder for the first material in the scene. material: c4d.BaseMaterial | None = doc.GetFirstMaterial() if not material: raise RuntimeError("Please add at least one material to the document.") colorShader: c4d.BaseShader | None = material[c4d.MATERIAL_COLOR_SHADER] if not colorShader: raise RuntimeError("Please add a color shader to the first material.") # And traverse the whole tree. for shader, level in iter_shader(colorShader): print (f"{' ' * level}{shader = }") # When it is a layer shader, inspect its layers. if isinstance(shader, c4d.LayerShader): layer: c4d.LayerShaderLayer | None = shader.GetFirstLayer() if not layer: continue li: int = level + 1 while layer: print (f"{' ' * li}{layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_MODE) = }") print (f"{' ' * li}{layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_BLEND) = }") print (f"{' ' * li}{layer.GetParameter(c4d.LAYER_S_PARAM_SHADER_LINK) = }") layer = layer.GetNext() if __name__ == '__main__': main() - 
Hi @ferdinand
First of all thank you very much for the extensive reply, I haven't had the time yet to put the code intro practice but I wanted to answer your question on what I wanted to do with my code first.
I haven't had much effective code since I can't get to the actual bitmap's filepath in the layer shader, but this is what I have, sorry for the ton of print statements but I need to figure out what the code was doing:
if (i.GetType() == 1011123): print ("Layershader found!") print("i inside layershader is ", i) layerShader = i layer = layerShader.GetFirstLayer() while layer.GetNext(): print("Layer is: ", layer) print("Layer name: ", layer.GetName(doc)) print("Layer type is: ", layer.GetType()) print("Layer parameter is: ", layer.GetParameter(0)) print("Next layer is: ", layer.GetNext()) nextLayer = layer.GetNext() print("Layer name: ", nextLayer.GetName(doc)) print("Layer type is: ", nextLayer.GetType()) print("Layer parameter is: ", nextLayer.GetParameter(0)) print("Next layer is: ", nextLayer.GetNext()) print("-----------------------------------------------------") if (layer.GetType() == 5833) or (layer.GetType() == 1036473): print("Bitmap found in layer!") file = os.path.split(shader(i)) print("file is: ", file[1]) if layer.GetNext(): layer = layer.GetNext() else: pass else: print("No bitmap found in layer!") if layer.GetNext(): layer = layer.GetNext() else: passWhat I want to do in code is this:
- Go through all shaders in the scene.
 - Go into each shader and into each "slot" in each shader to see if a bitmap is present.
 - If a bitmap is present, I either want to make the texture path local, or make the texture path global to our server location.
 - If I run into a layer shader in a slot, I want to go into each layer of said layer shader to check if there is a bitmap loaded, and if so to change the texture path to local or global.
 
So far I've tackled the first 3 things but I'm running into issues with the 4th as I can't read out the texture path from a layer in the layer shader it seems.
 - 
Hello @Peek,
well, as my example and the C++ Manual I linked to above demonstrate, you do not really need the
LayerShaderLayerinterface for that. A layer shader carries its layered shaders as children. So, with theiter_shaderfrom above, you could do something like this:for shader in iter_shader(channelRoot): if shader.CheckType(c4d.Xlayer): for nestedShader in iter_shader(shader): if nestedShader.CheckType(c4d.Xbitmap): # This is a bitmap shader inside a layer shader, either as direct child or burried # much deeper. ...Your code is lacking that recursive aspect, a layer shader can contain a layer shader, which can contain a layer shader, ...
But in general, it looks a bit like that you just want to use c4d.documents.GetAllAssetsNew anyway. It will yield all bitmap assets in a scene including their owners. It will also work for everything that supports
MSG_GETALLASSETSand not just materials for the standard renderer. We talked about that method multiple times, for example here.Cheers,
Ferdinand - 
@ferdinand : The GetAllAssetsNew is a far more elegant solution for the problem I am trying to solve and I've rewritten the code so that it works now. We use Corona render and Redshift so it's a bonus that this works with pretty much all materials.
Thank you very much for the help, I will put your information about the layer shader in my database for future reference as I'm sure it will become helpful in another project!
Kind regards,
Joep - 
I i_mazlov referenced this topic on 
 - 
F ferdinand forked this topic on 
 - 
P Peek referenced this topic on