Solved How to Solo a Layer

Hi,

I just spent a bit of time debugging a couple issues when trying to Solo a Layer in the Layer Manager, and figured I would post my findings to save someone else some trouble:

Q: How do you solo a layer?

layer_root = doc.GetLayerObjectRoot()
if not layer_root: return

# Get the first layer in the scene
layer = layer_root.GetDown()
if not layer: return

# Be sure to use the `rawdata` flag so that you get the stored layer state, not the "effective" layer state
layer_data = layer.GetLayerData(doc, rawdata=True)

start_solo_state = layer_data["solo"]
solo_state = True

layer_data["solo"] = True

# SetLayerData needs a complete LayerData dict, it doesn't work with something like `{"solo": True}`
# doing that will force all other layer state flags to be False
layer.SetLayerData(doc, layer_data)

# This isn't documented near SetLayerData, but you must call this method or C4D won't know to update the
# solo state of layers in the scene.
set_or_clear = 
doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_SET)

# Let C4D know something has changed to trigger a redraw
c4d.EventAdd()

Documentation Request:
Please update the docs for SetLayerData to include an example like the above, and make a note that if you plan to SetLayerData you should call GetLayerData with rawdata=True.

Thanks,

Donovan

-- Edit 2020/03/20 --

Actually this isn't working well in all circumstances. If all layers are unsoloed I'm not getting the expected refresh.

Okay, I think I figured out the issue (but haven't updated the logic above to account for it):

If you turn off solo on all layers, you need to use doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_CLEAR), but if even one layer has the solo flag on, you need to use doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_SET).

...I think.

Hi @dskeith thanks for reaching out us.

As pointed out by @PluginStudent, the NBIT_SOLO_LAYER behavior is explained in the Layer Manuel although it make sense to add a note to the Python API as well.

With regard to a complete example, I've partially reworked your code to make sure that by executing the script multiple times you can run across all the solo configurations that the scene can handle.

import c4d

# Main function
def main():
    layer_root = doc.GetLayerObjectRoot()
    if not layer_root: 
        return

    # Get the first layer in the scene
    layer = layer_root.GetDown()
    if not layer: 
        return

    soloSet = False

    while layer is not None:
        # Set `rawdata` to True flag so to get the original layer values without any additional global changes
        layer_data = layer.GetLayerData(doc, rawdata=True)

        # check the current layer solo state:
        if layer_data["solo"] == False:
            # if not "solo" then set to True
            layer_data["solo"] = True
            # update the layer
            layer.SetLayerData(doc, layer_data)
            # update the flag
            soloSet = True
            break
        
        # deactivate if layer solo was True
        layer_data["solo"] = False
        layer.SetLayerData(doc, layer_data)

        # go to next layer
        layer = layer.GetNext()
    
    # if one (or more) solo was set the set NBIT_SOLO_LAYER otherwise clear
    if soloSet == True:
        doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_SET)
    else:
        doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_CLEAR)

    # Add an event 
    c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

Cheers

Did you look into the C++ docs on this issue? Because there you find a Layer Manual.

Hi @dskeith thanks for reaching out us.

As pointed out by @PluginStudent, the NBIT_SOLO_LAYER behavior is explained in the Layer Manuel although it make sense to add a note to the Python API as well.

With regard to a complete example, I've partially reworked your code to make sure that by executing the script multiple times you can run across all the solo configurations that the scene can handle.

import c4d

# Main function
def main():
    layer_root = doc.GetLayerObjectRoot()
    if not layer_root: 
        return

    # Get the first layer in the scene
    layer = layer_root.GetDown()
    if not layer: 
        return

    soloSet = False

    while layer is not None:
        # Set `rawdata` to True flag so to get the original layer values without any additional global changes
        layer_data = layer.GetLayerData(doc, rawdata=True)

        # check the current layer solo state:
        if layer_data["solo"] == False:
            # if not "solo" then set to True
            layer_data["solo"] = True
            # update the layer
            layer.SetLayerData(doc, layer_data)
            # update the flag
            soloSet = True
            break
        
        # deactivate if layer solo was True
        layer_data["solo"] = False
        layer.SetLayerData(doc, layer_data)

        # go to next layer
        layer = layer.GetNext()
    
    # if one (or more) solo was set the set NBIT_SOLO_LAYER otherwise clear
    if soloSet == True:
        doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_SET)
    else:
        doc.ChangeNBit(c4d.NBIT_SOLO_LAYER, c4d.NBITCONTROL_CLEAR)

    # Add an event 
    c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

Cheers