Rename shader in specific channel



  • On 20/01/2018 at 07:46, xxxxxxxx wrote:

    Hey guys,

    I'm currently writing a script which later on I want to turn into a Commanddata plugin.
    The script is quite straightforward. I want to rename a shader only if it is in a specific material channel.
    So if the user executes the script from the Script Manager a gui popup appears where the user can choose which material channel should be searched for the specific shader, e.g. the c4d color shader.

    To check if the desired shader is in a material at all I use a code example from cineversity where a material gets checked recursively. So if the first shader in for example the color channel is not a color shader but let's say a layer shader or any other shader where the color shader could be potentially under, I can still access it and don't be limited to the very first level of hierarchy.

    However, the problem here is that I noticed some strange behaviour with this approach.
    I set up a basic scene where I have only one material with the color and the luminance channel both active and in each of these channels I loaded a color shader.
    If I choose in the script that only the color channel should be searched for any color shaders and run the renaming process, the color shader in the luminance channel gets also renamed.

    I did a lot of testing and tried to come up with different code approaches but all I got was an unpredictable behaviour where one time only the color shader in the color channel was renamed (like desired) and the other time it was quite off as the color shader in the luminance channel got renamed as well.

    I don't know if I'm running into some kind of limitations here. And although I already created some python scripts or expressions in the past, I wouldn't consider myself a professional programmer. 
    It's more a skillset based on a self taught basis i guess. ;-)

    Well, however, heres the code of the script. 
    As I don't have any ideas anymore how to tackle this, I deeply appreciate if someone can help me here.

    Thanks,
    Sebastian

    import c4d
    from c4d import gui

    def popupMenu() :

    # ------------------------------------------------------------
        # Declare menu items ID
        # ------------------------------------------------------------
        IDM_MENU1 = c4d.FIRST_POPUP_ID
        IDM_MENU2 = c4d.FIRST_POPUP_ID + 1
        
        # ------------------------------------------------------------
        # Main menu
        # ------------------------------------------------------------
        menu = c4d.BaseContainer()
        menu.InsData(IDM_MENU1, 'Color')        # Color channel
        menu.InsData(IDM_MENU2, 'Luminance')    # Luminance channel
        
        # ------------------------------------------------------------
        # Finally show popup dialog
        # ------------------------------------------------------------
        result = gui.ShowPopupDialog(cd=None, bc=menu, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS)
        return result

    def renameShader(shader, shader_type) :
        """
        Loop through the BaseList.
        
        """
        testname = 'My shader'

    while(shader) :

    if shader.GetType() == shader_type:
                name = shader[c4d.ID_BASELIST_NAME]
                if name is not testname:
                    shader[c4d.ID_BASELIST_NAME] = '{}'.format(testname)

    # Check for child shaders & recurse
            if shader.GetDown() :
                renameShader(shader.GetDown(), shader_type)

    # Get the Next Shader
            shader = shader.GetNext()

    def main() :
        
        doc.StartUndo()
        bc = c4d.BaseContainer()
        result = popupMenu()
        if result == 0:
            return

    mats = doc.GetActiveMaterials()
        if not mats:
            gui.MessageDialog('No material selected.')

    for mat in mats:
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, mat)

    if result == 900000:
                shader = mat[c4d.MATERIAL_COLOR_SHADER]     # Color channel
            elif result == 900001:
                shader = mat[c4d.MATERIAL_LUMINANCE_SHADER] # Luminance channel
                
            renameShader(shader, c4d.Xcolor)        
            mat = mat.GetNext()
                        
        doc.EndUndo()
        c4d.EventAdd()

    if __name__=='__main__':
        main()



  • On 22/01/2018 at 09:15, xxxxxxxx wrote:

    Hi and Welcome to the Plugin Cafe forums!

    Shaders are handled by a material with a tree. This means all shaders and their sub-shaders are stored inside such tree for each material.
    So when the next shader is retrieved with shader.GetNext() then a shader in another channel might be retrieved.

    Also the order in the tree is not the order that can be seen in the GUI/Attribute Manager. For instance the color channel shader might be after the luminance channel shader.

    If you are only interested in a specific shader and its sub-shaders then you should not call GetNext() on this one. You should only loop through the children sub-shaders given by GetDown().
    In the code you posted, renameShader() loops through all shaders under and after the initial channel shader.

    A solution would be to move the actual renaming to a dedicated function and perform the recursive loop only starting with the first sub-shader given by the channel shader.GetDown().

    Note it is recommended to use BaseList2D.GetName()/BaseList2D.SetName() instead of c4d.ID_BASELIST_NAME.



  • On 25/01/2018 at 01:58, xxxxxxxx wrote:

    Hi Yannick,

    thanks for pointing me in the right direction and to shed some light on this one.

    After a little hassle i finally got it working. I went the route you suggested only checking if a specific shader is at the topmost level of a "channels shader" or if a shader gets down. If so I recurse over it's children or siblings.

    Using BaseList2D.GetName()/BaseList2D.SetName() instead of c4d.ID_BASELIST_NAME is because it's more pythonic and faster, isn't it? So in general one should consider to always use equivalent functions instead of subscripting?

    However I have another question and don't know if I should open another thread or if I can post it here. 
    For a CommandData Plugin I'm quite unsure where to actually put my functions. By that I mean my custom functions I need for the plugin. Some say you can do everything in the Command() function of the c4d.gui.GeDialog() class, while others create a main() function with their custom code inside and put that in the execute() function of the plugin.CommandData() class.

    I ask this because I want to avoid calling things like for example doc = c4d.documents.GetActiveDocument() over and over again inside of every function i need the active document.

    It would be nice if you could help me here.

    Thanks,
    Sebastian



  • On 25/01/2018 at 09:11, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    Using BaseList2D.GetName()/BaseList2D.SetName() instead of c4d.ID_BASELIST_NAME is because it's more pythonic and faster, isn't it? So in general one should consider to always use equivalent functions instead of subscripting?

    Internally getting/setting c4d.ID_BASELIST_NAME calls BaseList2D.GetName()/SetName() so it is better to call directly the functions. (I also find the code nicer with the functions name.)
    I can say c4d.ID_BASELIST_NAME is a special parameter ID and BaseList2D.GetName()/SetName() are provided by the API to get/set the name of a baselist.

    Usually subscripting is better to access normal parameters.

    Originally posted by xxxxxxxx

    For a CommandData Plugin I'm quite unsure where to actually put my functions. By that I mean my custom functions I need for the plugin. Some say you can do everything in the Command() function of the c4d.gui.GeDialog() class, while others create a main() function with their custom code inside and put that in the execute() function of the plugin.CommandData() class.

    I don't follow here. Once you are in the GeDialog.Command() the only way to return to the CommandData.Execute() is to invoke the command again.

    Originally posted by xxxxxxxx

    I ask this because I want to avoid calling things like for example doc = c4d.documents.GetActiveDocument() over and over again inside of every function i need the active document.

    You can simply pass the active document over to the called functions once retrieved in GeDialog.Command().


Log in to reply