Solved Copy the texture in the Reflection channel to the Color channel

As the title suggests, I'm trying to bulk copy the layer texture in the Reflection channel to the Color channel in all the selected materials, then enabling the Color channel and disabling the Reflection channel. Here's my script:

import c4d
from c4d import gui


def main():
    mat = doc.GetActiveMaterials()
    if len(mat) == 0: return

    for i, m in enumerate(mat):
        try:
            layerIndex = m.GetReflectionLayerIndex(0).GetDataID()

            reftex = m[layerIndex + c4d.REFLECTION_LAYER_COLOR_TEXTURE]
            m[c4d.MATERIAL_USE_COLOR] = True

            colt = c4d.BaseList2D(c4d.Xbitmap)
            colt[c4d.BITMAPSHADER_FILENAME] = reftex
            m.InsertShader(colt)
            m[c4d.MATERIAL_COLOR_SHADER]  = colt

            m[c4d.MATERIAL_USE_REFLECTION] = False

            m.Message(c4d.MSG_UPDATE)

        except:
            pass


    c4d.EventAdd()

if __name__=='__main__':
    main()

The layer can be the first one so that's not a problem, the script doesn't need to parse all the reflection layers, but somehow this doesn't work. I have no issues copying the texture shader from different channels but not from the Reflection channel. It's probably because Reflection channel can have many layers. But it seems I'm making a simple mistake somewhere.

Thanks in advance.

Hello @tankun,

Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!

Getting Started

Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

About your First Question

You got it almost right, good job for your first question on the tricky subject of reflection layers! Your main fault was that you treated your variable reftex as if it were a str or maxon.Url, although it is in reality a BaseShader or None. There were also some minor bits and bobs, but they are not so important. Find an example for how I would write such thing below.

Cheers,
Ferdinand

Result:
Before:
e6dc1bea-efd8-4450-8e52-8a870db3354c-image.png
After:
09984c18-40e9-43e4-a781-49875356bc01-image.png

Code:

"""Demonstrates how to move the color shader of the first reflection layer 
which has such shader to the color channel of the material. 
"""

import c4d

doc: c4d.documents.BaseDocument

def main():
    """Runs the example.
    """
    # For every material in the document, ...
    for material in doc.GetMaterials():
        # but skip over everything that does not have reflectance channels, ...
        if not material.CheckType(c4d.Mmaterial):
            continue

        # iterate over all reflection layers to find the first layer that has a color shader.
        i: int = 0
        layer: c4d.ReflectionLayer | None = material.GetReflectionLayerIndex(i)
        while(layer):
            # Get the ID of the reflection layer color shader and retrieve the shader.
            pid: int = layer.GetDataID() + c4d.REFLECTION_LAYER_COLOR_TEXTURE
            colorShader: c4d.BaseShader | None = material[pid]

            # Increment our counter and get the next layer.
            i += 1
            layer = material.GetReflectionLayerIndex(i)

            # When the last layer did not have color shader, continue the inner iteration.
            if not colorShader:
                continue
            
            # One of the flaws of your code was that you were reallocating a shader and also
            # were trying to treat your #reftex like a str/Url, although it was of type 
            # c4d.BaseShader or None. But we can also just reuse the existing shader, no reallocation
            # required. When you want to explicitly reallocate the shader, you should also remove
            # the old shader, i.e., call #colorShader.Remove() in this case.

            # Toggle the channels and move the shader reference. Also wrap the whole operation into
            # an undo step. If you want the whole material conversion to be one undo, you would
            # have to move the #StartUndo and #EndUndo outside of the outmost loop.
            doc.StartUndo()
            doc.AddUndo(c4d.UNDO_CHANGE, material)

            material[c4d.MATERIAL_USE_COLOR] = True
            material[c4d.MATERIAL_USE_REFLECTION] = False
            material[pid] = None # This is important, a shader can only be refed once.
            material[c4d.MATERIAL_COLOR_SHADER] = colorShader

            doc.EndUndo()

            # Break the inner iteration since we have set/found a color shader.
            break
    
    # Push an update event to Cinema 4D so that our changes are reflected in the UI.
    c4d.EventAdd()

if __name__=='__main__':
    main()

MAXON SDK Specialist
developers.maxon.net

Hello @tankun,

Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!

Getting Started

Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

About your First Question

You got it almost right, good job for your first question on the tricky subject of reflection layers! Your main fault was that you treated your variable reftex as if it were a str or maxon.Url, although it is in reality a BaseShader or None. There were also some minor bits and bobs, but they are not so important. Find an example for how I would write such thing below.

Cheers,
Ferdinand

Result:
Before:
e6dc1bea-efd8-4450-8e52-8a870db3354c-image.png
After:
09984c18-40e9-43e4-a781-49875356bc01-image.png

Code:

"""Demonstrates how to move the color shader of the first reflection layer 
which has such shader to the color channel of the material. 
"""

import c4d

doc: c4d.documents.BaseDocument

def main():
    """Runs the example.
    """
    # For every material in the document, ...
    for material in doc.GetMaterials():
        # but skip over everything that does not have reflectance channels, ...
        if not material.CheckType(c4d.Mmaterial):
            continue

        # iterate over all reflection layers to find the first layer that has a color shader.
        i: int = 0
        layer: c4d.ReflectionLayer | None = material.GetReflectionLayerIndex(i)
        while(layer):
            # Get the ID of the reflection layer color shader and retrieve the shader.
            pid: int = layer.GetDataID() + c4d.REFLECTION_LAYER_COLOR_TEXTURE
            colorShader: c4d.BaseShader | None = material[pid]

            # Increment our counter and get the next layer.
            i += 1
            layer = material.GetReflectionLayerIndex(i)

            # When the last layer did not have color shader, continue the inner iteration.
            if not colorShader:
                continue
            
            # One of the flaws of your code was that you were reallocating a shader and also
            # were trying to treat your #reftex like a str/Url, although it was of type 
            # c4d.BaseShader or None. But we can also just reuse the existing shader, no reallocation
            # required. When you want to explicitly reallocate the shader, you should also remove
            # the old shader, i.e., call #colorShader.Remove() in this case.

            # Toggle the channels and move the shader reference. Also wrap the whole operation into
            # an undo step. If you want the whole material conversion to be one undo, you would
            # have to move the #StartUndo and #EndUndo outside of the outmost loop.
            doc.StartUndo()
            doc.AddUndo(c4d.UNDO_CHANGE, material)

            material[c4d.MATERIAL_USE_COLOR] = True
            material[c4d.MATERIAL_USE_REFLECTION] = False
            material[pid] = None # This is important, a shader can only be refed once.
            material[c4d.MATERIAL_COLOR_SHADER] = colorShader

            doc.EndUndo()

            # Break the inner iteration since we have set/found a color shader.
            break
    
    # Push an update event to Cinema 4D so that our changes are reflected in the UI.
    c4d.EventAdd()

if __name__=='__main__':
    main()

MAXON SDK Specialist
developers.maxon.net

Yes, thank you so much. This works perfectly. And thanks a million for the in depth explanation.