Author Topic: How to create materials with shared shaders via Python?  (Read 2520 times)

2024-07-15, 11:24:15

GeMeMe

  • Active Users
  • **
  • Posts: 11
    • View Profile
Hi everyone,

I literally started looking into C4D and Python yesterday. So please forgive if I don’t quite hit the right terminology here and there.

I’m setting out on integrating C4D into an existing toolchain via Python. Part of that is the need to create materials with complex shader trees from scratch via code only. I’m already at a stage where I can import the material config via JSON, create and connect material and shader nodes, as well as set all their parameters. But I’ve run into a problem that I didn’t foresee.

As I’ve learned, every shader node MUST be a child of the material/shader it is linked to and CANNOT be used as input by other materials/shaders that are not its parent. I wish to create material setups where the output of some shaders is reused by more than one other shader or material. Corona’s node editor solves this problem by automatically introducing invisible “shared nodes” between a shader output and its multiple users. When I probe a simple material I created via the node editor, I see something like this (A “Corona Physical” material with a single “Color” shader assigned to two different inputs):
Code: [Select]
BaseMaterial <c4d.BaseMaterial object called Test material/Corona Physical with ID 1056306 at 2107176123328> {
  Slot 20209: # Roughness
    Node <c4d.BaseShader object called Color1 (shared)/Corona Shared with ID 1040749 at 2107176157248> {
      Slot 11901:
        Node <c4d.BaseShader object called Color1/Color with ID 5832 at 2107176140352> {
        }
    }
  Slot 20218: # Bump
    Node <c4d.BaseShader object called Color1 (shared)/Corona Shared with ID 1040749 at 2107176148800> {
      Slot 11901:
        Node <c4d.BaseShader object called Color1/Color with ID 5832 at 2107176159296> {
        }
    }
}

This seems to indicate that the node editor internally actually does create duplicates of a reused shader node with a “Corona Shared” node for each (note the different memory addresses), and then uses some mechanism to keep these duplicates in sync, while presenting only one shader node to the user in the node editor. This makes sense as even a Corona Shared is subject to the one-parent-per-child restriction of c4d.BaseList2D().

So how does this all work under the hood? How can I create a material using this sharing mechanism when creating materials via Python? I looked far and wide for some sample code or explanation but found nothing. So, I really hope some Dev or other knowledgeable person will shed some light on this or at least point me in the right direction.

Any help is greatly appreciated!

2024-07-15, 12:57:43
Reply #1

James Vella

  • Active Users
  • **
  • Posts: 670
    • View Profile
Having trouble understanding your question but can you not just assign the color node to a variable then use that throughout? color_shader variable in this case

For example:
Code: [Select]
import c4d

def main():
    doc = c4d.documents.GetActiveDocument()  # Get the active document
   
    # ID for Corona Physical Material
    corona_mat_id = 1056306
   
    # Create a new Corona Physical Material
    new_corona_mat = c4d.BaseMaterial(corona_mat_id)
   
    # Insert the material into the document
    doc.InsertMaterial(new_corona_mat)
   
    # Create a new Color Shader node
    color_shader = c4d.BaseShader(c4d.Xcolor)
   
    # Insert the shader into the material's node system
    new_corona_mat.InsertShader(color_shader)
   
    # Assign the color shader to the Diffuse channel
    new_corona_mat[c4d.CORONA_PHYSICAL_MATERIAL_BASE_COLOR_TEXTURE] = color_shader
   
    # Assign the same color shader to the Roughness channel
    new_corona_mat[c4d.CORONA_PHYSICAL_MATERIAL_BASE_ROUGHNESS_TEXTURE] = color_shader
   
    # Update the material and the document
    new_corona_mat.Update(True, True)
    c4d.EventAdd()
   
if __name__ == '__main__':
    main()

Then the next material you can just instance that color node/variable:
Code: [Select]
    # .... previous code below c4d.EventAdd()
    # Create a new Corona Physical Material
    new_corona_mat2 = c4d.BaseMaterial(corona_mat_id)
   
    # Insert the material into the document
    doc.InsertMaterial(new_corona_mat2)
   
    # Assign the color shader to the Diffuse channel
    new_corona_mat2[c4d.CORONA_PHYSICAL_MATERIAL_BASE_COLOR_TEXTURE] = color_shader

    # Update the material and the document
    new_corona_mat2.Update(True, True)
    c4d.EventAdd()

« Last Edit: 2024-07-15, 14:05:33 by James Vella »

2024-07-15, 16:25:15
Reply #2

GeMeMe

  • Active Users
  • **
  • Posts: 11
    • View Profile
Thanks for taking the time, James.
Quote
can you not just assign the color node to a variable then use that throughout

You cannot, unfortunately. Although, it seems like it does work at first. The node editor shows the shader correctly connected to both materials, but only first material (the one that is the parent of the shader) is able to actually use its output. new_corona_mat2 behaves like the shader is not connected at all.

The c4d.BaseList2d documentation states:
Quote
  • If you are programming shader links you need to make sure that every shader is referenced only once; it is not allowed that a shader is referenced multiple times.
  • If the referencing object is a shader the referenced shader must be a child of it, otherwise it must be inserted via InsertShader().
  • Example 1: A tag references a shader. The shader must be inserted into the tag using tag.InsertShader().
  • Example 2: A shader (A) references another shader (B): shader B must be a child of shader A.

I assume that's why Corona's node editor uses an intermediary node, along with some under-the-hood workings to pull off shared shaders. How this actually works is what I'd like to find out.

2024-07-15, 16:31:54
Reply #3

James Vella

  • Active Users
  • **
  • Posts: 670
    • View Profile
Even though the material ball is grey it looks like its working, cube is using left material, sphere using right

When I have time ill look into it further (the material ball being grey)

edit:
Ok now I understand your question better, the (shared) component, when I query this setup it doesnt have "shared". Ok cool ill think about it, we are currently using InsertShader but I'm guessing theres a small part thats missing. Ill take a look later on.
« Last Edit: 2024-07-15, 16:52:27 by James Vella »

2024-07-16, 10:50:58
Reply #4

GeMeMe

  • Active Users
  • **
  • Posts: 11
    • View Profile
Thanks for looking into this, James. Let me know what you find out! I'll continue my own experiments in the meantime.

2024-07-16, 19:01:43
Reply #5

James Vella

  • Active Users
  • **
  • Posts: 670
    • View Profile
I wasnt able to work it out GeMeMe. Im better with maxscript, python for c4d is bit out of my comfort zone. Ive made a few basics scripts but this is a head scratcher. My only thoughts is that it could be related to a node material instead a standard material which apparently standard materials can be elevated to. Maybe this in the doc will help:
https://developers.maxon.net/docs/py/2024_3_0/manuals/manual_graphdescription.html

2024-07-16, 20:07:30
Reply #6

GeMeMe

  • Active Users
  • **
  • Posts: 11
    • View Profile
Thanks, James. Coming myself from 3ds Max, I'm also more familiar with maxscript. I definitely have my gripes with maxscript here and there, but looking into C4D scripting, I now come to a whole new appreciation regarding the ease with which something like building material and shader trees works in 3ds Max...

I was under the impression that the Corona plugin bypasses C4D's node graph engine, hence the need for their own node editor (not sure which came first). I think, unless one of the Devs chimes in, we won't be able to get to the bottom of this.

I guess I have to pivot towards a template-based system for batch creation of materials.

2024-07-16, 20:23:22
Reply #7

James Vella

  • Active Users
  • **
  • Posts: 670
    • View Profile
I definitely have my gripes with maxscript here and there, but looking into C4D scripting, I now come to a whole new appreciation regarding the ease with which something like building material and shader trees works in 3ds Max...

Ha yeah I completely understand, sometimes the devil you know is better than the one you dont right :D

I have a feeling its not a complex solution, but working out missing piece of the puzzle is indeed not fun lol. I tried many times today and I've hit the wall. Sorry mate but this one I'm skipping out on.

2024-08-22, 11:08:30
Reply #8

John_Do

  • Active Users
  • **
  • Posts: 253
    • View Profile
Bump !

Could we have a feedback from the team ? I tried many things on my side without any success.

Is linking shaders with Shared shaders possible at all through C4D Python API ?

Thanks

2024-08-23, 18:46:42
Reply #9

John_Do

  • Active Users
  • **
  • Posts: 253
    • View Profile
I wrote something that somewhat works. The shared shaders are synced but the links aren't drawn in the node editor until the source shader is edited.