Solved Creating shaders from PSD files

Hi,

Newbie here so forgive any breach of protocol etc!

I'm trying to use python to assign PSD layers to individual materials, then apply these materials to a texture tag on individual planes (this can be used to create a pop-up book effect for example). The material previews look fine in the material window, and perfect when rendered with the standard renderer, but in the C4d view panel they all appear as the PSD layers all merged together (see pic attached of viewport vs rendered, with the material previews below). I suspect I'm missing a command to create a specific viewport or OpenGL texture from the material, or from the shader... Does anyone have any obvious suggestions or pointers?

opengl texture is merged layers.jpg

Thanks, Yan.

First of all welcome in the plugincafe community :wink:

May I ask you for a psd file because here I don't have any issue with a file I made so maybe there is something wrong.
Does the issue also appear if you setup manually the file? If it's the case it's most likely a limitation of Cinema 4D viewport.

And if it's not the case please share your code.
Cheers,
Maxime.

Hi Maxime,

The PSD file is just a test file, so at the moment it's a very simple layered file. Four layers, everything completely rasterised, no text or effects or anything special. Here's the code I'm using - you'll need to replace "/MyPath/test.psd" with a path to your PSD image.

Than you so much for helping!

import c4d
from c4d import bitmaps

def CreateMaterial(matName, fileName, layerName, i):
    # Create a new material from scratch
    mat = c4d.Material()
    mat.SetName(matName)
    doc = c4d.documents.GetActiveDocument()
    doc.InsertMaterial(mat)

    # create a texture shader and assign it to the color channel
    
    # enable the color channel
    mat[c4d.MATERIAL_USE_COLOR] = True
    # create a bitmap shader
    texshader = c4d.BaseShader(c4d.Xbitmap)
    texshader[c4d.BITMAPSHADER_FILENAME] = fileName
    # provide the layer set information
    layerSet = c4d.LayerSet()
    layerSet.SetMode(c4d.LAYERSETMODE_LAYERS)
    layerSet.AddLayer(layerName)
    texshader[c4d.BITMAPSHADER_LAYERSET] = layerSet
    # insert the bitmap shader into the material
    mat.InsertShader(texshader)
    # assign the bitmap shader to the color channel
    mat[c4d.MATERIAL_COLOR_SHADER] = texshader
    
    # enable the alpha channel
    mat[c4d.MATERIAL_USE_ALPHA] = True
    # create a bitmap shader
    texshader = c4d.BaseShader(c4d.Xbitmap)
    texshader[c4d.BITMAPSHADER_FILENAME] = fileName
    # provide the layer set information
    layerSet = c4d.LayerSet()
    layerSet.SetMode(c4d.LAYERSETMODE_LAYERALPHA)
    layerSet.AddLayer(layerName)
    texshader[c4d.BITMAPSHADER_LAYERSET] = layerSet
    layerSet.SetPreviewMode(0)
    # insert the bitmap shader into the material
    mat.InsertShader(texshader)
    # assign the bitmap shader to the color channel
    mat[c4d.MATERIAL_ALPHA_SHADER] = texshader
    
    # by default the reflectance channel is enabled,
    # let's disable it
    mat[c4d.MATERIAL_USE_REFLECTION] = False
   
   
    # Create a new null and plane from scratch
    nl = c4d.BaseObject(c4d.Onull)
    nl.SetName(matName)
    nl.SetRelScale(c4d.Vector(i,i,i))
    doc.InsertObject(nl)
    pl = c4d.BaseObject(c4d.Oplane)
    pl.SetName(matName)
    pl[c4d.PRIM_PLANE_WIDTH] = 192
    pl[c4d.PRIM_PLANE_HEIGHT] = 108
    pl[c4d.PRIM_PLANE_SUBW] = 1
    pl[c4d.PRIM_PLANE_SUBH] = 1
    pl[c4d.PRIM_AXIS] = 5
    pl.SetRelPos(c4d.Vector(0,0, 200))
    doc.InsertObject(pl,nl)
    
    
    # Add a texture tag to the plane
    tt = c4d.TextureTag()
    tt.SetName(matName)
    tt.SetMaterial(mat)
    
    pl.InsertTag(tt)
     
    return




def main():

    # load a multi layer psd file
    path = "/MyPath/test2.psd"

    bc = c4d.BaseContainer()
    bc.SetFilename(c4d.LOADTEXTURE_FILENAME, path.encode('utf-8'))
    tex = c4d.modules.bodypaint.SendPainterCommand(c4d.PAINTER_LOADTEXTURE, doc=doc, tex=None, bc=bc)
    if tex is None:
        print "Failed loading file"
        return
    
    # iterate over the layers and create the materials
    layer = tex.GetFirstLayer()
    i = 0
    while layer:
        i += 1
        layerName = layer.GetName()
        materialName = layerName
        CreateMaterial(materialName, path, layerName, i)
        
        # up to the next layer in the PSD
        layer = layer.GetNext()
    
        c4d.EventAdd()
      
    

if __name__=='__main__':
    main()

Hi, @yanmasterson I'm afraid there is currently a bug in PAINTER_LOADTEXTURE, which change the OpenGL cache of the picture, and there is actually no way to properly set it back once PAINTER_LOADTEXTURE have screwed it for a given Bitmap, you can't restore it and you have to close Cinema 4D.

So as a workaround you can use external libraries to read layer name of your psd file
And then use the next stuff.

    # load a multi layer psd file
    path = r"C:\Users\graphos\Desktop\test.psd"

    # Data retrieved from the psd file.
    layerData = {"Layer2":1, "Layer4":2, "Layer3":3, "Layer1":4}

    for layerName, i in layerData.iteritems():
        materialName = layerName
        CreateMaterial(materialName, path, layerName, i)

    c4d.EventAdd()

While I know this workaround is not perfect I still investigate the bug.
Cheers,
Maxime.

Thanks Maxime! I'm relieved it's a bug, I was looking everywhere for functions to manually fix the OpenGL preview. I will try with this approach.

Y.

Gah, this bug has me hung up too. I'll attempt to implement the external library approach. @yanmasterson @m_adam did you find a workaround that doesn't use an external library?

Also, is there any other way to get the layers from a photoshop file other than using:

c4d.modules.bodypaint.SendPainterCommand(c4d.PAINTER_LOADTEXTURE, doc=doc, tex=None, bc=bc)

Hello,

the C++ API includes the MultipassBitmap class, that allows to handle multi-layer images. While this class is also available in Python, the Python version is somewhat limited. So might have to switch to C++.

best wishes,
Sebastian

Here is a thread having similar issues. https://plugincafe.maxon.net/topic/3209/2592_parsing-psd-layers-in-multipassbitmap/11

Looks like Pythons MultiPassBitmap class is not working at all for loading bitmaps from layered PSD files. I get a layer count of 0 and no other data from the PSD file. I can't write C++ so I think I'll have to live with the openGL bug.

Screen Shot 2019-11-20 at 2.45.24 PM.png
image above is editor view vs standard renderer picture view.

I want to be able to just select these layers here somehow without using SendPainterCommand:
Screen Shot 2019-11-20 at 8.15.30 PM.png

You can use LayerSet.AddLayer('myPSD_LayerName') which will select the layer of the layerSet. It works, However, you can't retrieve the name of the PSD layers using LayerSet so I'm still stuck with the SendPainterCommand openGL bug and MultiPassBitmap not functioning with Python. :confused:

Hi Been a few years now, any changes on this, I would love a script that iterated through a PSD file, create a material for each layer and set the Alphas for each layer

This post is deleted!