
import re
from functools import partial
import maya.cmds as cmds
import maya.mel as mel

def get_connection_or_value(node_layer, attribute):
    connections = cmds.listConnections('%s.%s' % (node_layer, attribute), s=True, d=False)
    if connections:
        return connections[0]
    else:
        attr_value = cmds.getAttr('%s.%s' % (node_layer, attribute))
        if type(attr_value) == list:  # color (eg, tint) is returned in list of tuple, [(r,g,b)]
            attr_value = attr_value[0]
        return attr_value

def get_connection(node_layer, attribute):
    connections = cmds.listConnections('%s.%s' % (node_layer, attribute), s=True, d=False)
    if connections:
        return connections[0]
    else:
        return ''

def disconnect_layer_attr(node_layer, attribute):
    attr = '%s.%s' % (node_layer, attribute)
    if cmds.objExists(attr):
        connections = cmds.listConnections(attr, s=True, d=False)
        if connections[0]:
            cmds.disconnectAttr(connections[0], attr)

def set_connection_or_value(attr_value, node_layer, attribute):
    attr = str('%s.%s' % (node_layer, attribute))
    if type(attr_value) in (str, unicode) and cmds.objExists(attr):
        connection = cmds.connectionInfo(attr_value, sourceFromDestination=True)
        if connection:
            cmds.connectAttr(connection, attr, f=True)
        else:
            print('MILA warning: cannot set %s to string %s' % (attr, attr_value))
    else:
        attr_type = cmds.attributeQuery(attribute, node=node_layer, attributeType=True)
        if attr_type in ('double3', 'float3'): # value returned is a list of one tuple, ie [(r,g,b)]
            cmds.setAttr(attr, attr_value[0], attr_value[1], attr_value[2], type='double3')
        else:
            cmds.setAttr(attr, attr_value)

def component_attr_index (component_attr):
    match = re.search('.*\[([0-9]*)\]', component_attr)
    if match:
        return int(match.groups()[0])

class mix():
    def __init__(self, node, component_attr):
        self.node = node
        self.attr = component_attr  # indexed mix attr, eg component[0]
        node_mix_component = '%s.%s' % (node, component_attr)
        self.on = get_connection_or_value(node_mix_component, 'on')
        self.background_color = [0.3,0.3,0.3]
                
        self.shader = get_connection(node_mix_component, 'shader')
                
        self.weight = get_connection_or_value(node_mix_component, 'weight')
        self.weight_tint = get_connection_or_value(node_mix_component, 'weight_tint')

    def index(self):
        return component_attr_index(self.attr)

    def move_up(self, material_node, *args):
        self.move(True)
        if material_node:
            refresh_material_layout(material_node)
        
    def move_down(self, material_node, *args):
        self.move(False)
        if material_node:
            refresh_material_layout(material_node)
        
    def move(self, up):
        node = self.node
        attr = self.attr
        mixes = collect_mixes(node)
        
        current_mix = None
        swap_mix = None
        for i in range(len(mixes)):
            if mixes[i].attr == self.attr:
                current_mix = mixes[i]
                if up:
                    swap_mix = mixes[i-1]
                else:
                    swap_mix = mixes[i+1]
        
        node_current_mix_attr = '%s.%s' % (node, current_mix.attr)
        node_swap_mix_attr = '%s.%s' % (node, swap_mix.attr)

        cmds.connectAttr ('%s.message' % swap_mix.shader, '%s.shader' % node_current_mix_attr, f=True)
        cmds.connectAttr ('%s.message' % current_mix.shader, '%s.shader' % node_swap_mix_attr, f=True)
        
        set_connection_or_value(swap_mix.on, node_current_mix_attr, 'on')
        set_connection_or_value(current_mix.on, node_swap_mix_attr, 'on')
        
        set_connection_or_value(swap_mix.weight, node_current_mix_attr, 'weight')
        set_connection_or_value(current_mix.weight, node_swap_mix_attr, 'weight')

        set_connection_or_value(swap_mix.weight_tint, node_current_mix_attr, 'weight_tint')
        set_connection_or_value(current_mix.weight_tint, node_swap_mix_attr, 'weight_tint')

    def __str__(self):
        return ('Attr: %s\nEnabled: %s, Shader: %s, Weight: %s, WeightTint: %s' % (self.attr, str(self.on), str(self.shader), str(self.weight), str(self.weight_tint)))

def collect_mixes(node):
    mixes = []
    component_attrs = cmds.listAttr(node, m=True, v=True, c=True, st='components')
    if component_attrs:
        for component_attr in component_attrs:
            component = mix(node, component_attr)
            mixes.append(component)
    return mixes

class layer():
    def __init__(self, node, layer_attr):
        self.node = node
        self.attr = layer_attr  # indexed layer id, eg layer[0]
        node_layer = '%s.%s' % (node, layer_attr)
        self.on = get_connection_or_value(node_layer, 'on')
        self.background_color = [0.3,0.3,0.3]
                
        self.shader = get_connection(node_layer, 'shader')
                
        self.weight = get_connection_or_value(node_layer, 'weight')
        self.weight_tint = get_connection_or_value(node_layer, 'weight_tint')

        self.use_directional_weight = get_connection_or_value(node_layer, 'use_directional_weight')
        self.directional_weight_mode = get_connection_or_value(node_layer, 'directional_weight_mode')
        self.ior = get_connection_or_value(node_layer, 'ior')
        self.normal_reflectivity = get_connection_or_value(node_layer, 'normal_reflectivity')
        self.grazing_reflectivity = get_connection_or_value(node_layer, 'grazing_reflectivity')
        self.exponent = get_connection_or_value(node_layer, 'exponent')

    def index(self):
        return component_attr_index(self.attr)

    def move_up(self, material_node, *args):
        self.move(True)
        if material_node:
            refresh_material_layout(material_node)
        
    def move_down(self, material_node, *args):
        self.move(False)
        if material_node:
            refresh_material_layout(material_node)
        
    def move(self, up):
        node = self.node
        attr = self.attr
        layers = collect_layers(node)
        
        current_layer = None
        swap_layer = None
        for i in range(len(layers)):
            if layers[i].attr == self.attr:
                current_layer = layers[i]
                if up:
                    swap_layer = layers[i-1]
                else:
                    swap_layer = layers[i+1]
        
        node_current_layer_attr = '%s.%s' % (node, current_layer.attr)
        node_swap_layer_attr = '%s.%s' % (node, swap_layer.attr)

        cmds.connectAttr ('%s.message' % swap_layer.shader, '%s.shader' % node_current_layer_attr, f=True)
        cmds.connectAttr ('%s.message' % current_layer.shader, '%s.shader' % node_swap_layer_attr, f=True)

        set_connection_or_value(swap_layer.on, node_current_layer_attr, 'on')
        set_connection_or_value(current_layer.on, node_swap_layer_attr, 'on')
        
        set_connection_or_value(swap_layer.weight, node_current_layer_attr, 'weight')
        set_connection_or_value(current_layer.weight, node_swap_layer_attr, 'weight')

        set_connection_or_value(swap_layer.weight_tint, node_current_layer_attr, 'weight_tint')
        set_connection_or_value(current_layer.weight_tint, node_swap_layer_attr, 'weight_tint')

        set_connection_or_value(swap_layer.use_directional_weight, node_current_layer_attr, 'use_directional_weight')
        set_connection_or_value(current_layer.use_directional_weight, node_swap_layer_attr, 'use_directional_weight')

        set_connection_or_value(swap_layer.directional_weight_mode, node_current_layer_attr, 'directional_weight_mode')
        set_connection_or_value(current_layer.directional_weight_mode, node_swap_layer_attr, 'directional_weight_mode')
            
        set_connection_or_value(swap_layer.ior, node_current_layer_attr, 'ior')
        set_connection_or_value(current_layer.ior, node_swap_layer_attr, 'ior')

        set_connection_or_value(swap_layer.normal_reflectivity, node_current_layer_attr, 'normal_reflectivity')
        set_connection_or_value(current_layer.normal_reflectivity, node_swap_layer_attr, 'normal_reflectivity')

        set_connection_or_value(swap_layer.grazing_reflectivity, node_current_layer_attr, 'grazing_reflectivity')
        set_connection_or_value(current_layer.grazing_reflectivity, node_swap_layer_attr, 'grazing_reflectivity')

        set_connection_or_value(swap_layer.exponent, node_current_layer_attr, 'exponent')
        set_connection_or_value(current_layer.exponent, node_swap_layer_attr, 'exponent')

    def __str__(self):
        return ('Attr: %s\nEnabled: %s, Shader: %s, Weight: %s, WeightTint: %s, UseDirectionalWeight: %s, DirectionalWeightMode: %s, IOR: %s, NormalReflectivity: %s, GrazingReflectivity: %s, Exponent: %s' % (self.attr, str(self.on), str(self.shader), str(self.weight), str(self.weight_tint), str(self.use_directional_weight), str(self.directional_weight_mode), str(self.ior), str(self.normal_reflectivity),  str(self.grazing_reflectivity),  str(self.exponent)))

def collect_layers(node):
    layers = []
    component_attrs = cmds.listAttr(node, m=True, v=True, c=True, st='layers')
    if component_attrs:
        for layer_name in component_attrs:
            component = layer(node, layer_name)
            layers.append(component)
    return layers


def create_base_layout(material_node):
    
    cmds.columnLayout('AEmila_material_base', adj=True)
    refresh_base_layout(material_node)
    cmds.setParent('..')


base_component_labels = {'mila_diffuse_reflection':"Diffuse",
                    'mila_mix_subsurface_scatter':"Diffuse (Scatter)",
                    'mila_mix_reflective':"Reflective",
                    'mila_mix_reflective_aniso':"Reflective (Anisotropic)",
                    'mila_layer_reflective':"Reflective (Fresnel)",
                    'mila_layer_reflective_aniso':"Reflective (Anisotropic, Fresnel)",
                    'mila_layer_reflective_plastic':"Reflective (Plastic)",
                    'mila_layer_reflective_paint':"Reflective (Paint)",
                    'mila_layer_transmissive':"Transmissive",
                    'mila_layer_transmissive_clear':"Transmissive (Clear)",
                    'mila_layer_transmissive_distance':"Transmissive (Distance)",
                    'mila_mix_translucent':"Diffuse (Translucent)",
                    'mila_hair':"Hair",
                    'mila_skin':"Skin"}

def refresh_base_layout(material_node):
    
    to_del = cmds.layout('AEmila_material_base', q=True, childArray=True)
    if to_del:
        for ui in to_del:
            cmds.deleteUI(ui)
    cmds.setParent('AEmila_material_base')
    base_select_control=cmds.optionMenuGrp(l='Base Component', changeCommand=partial(change_base_component, material_node))
    cmds.menuItem(label="Diffuse")
    cmds.menuItem(label="Diffuse (Scatter)")
    cmds.menuItem(label="Diffuse (Translucent)")
    cmds.menuItem(label="Reflective")
    cmds.menuItem(label="Reflective (Anisotropic)")
    cmds.menuItem(label="Reflective (Plastic)")
    cmds.menuItem(label="Reflective (Paint)")
    cmds.menuItem(label="Transmissive")
    cmds.menuItem(label="Transmissive (Clear)")
    cmds.menuItem(label="Transmissive (Distance)")
    #ensure the current base component is indicated in the UI
    components = collect_components(get_components_node(material_node, create_when_empty=True))
    base_component_node, parent_components = get_base_component_node(components)  #recurses through connection hierarchy
    selected_type_label = base_component_labels[cmds.nodeType(base_component_node)]
    cmds.optionMenuGrp(base_select_control, edit=True, value=selected_type_label)
    refresh_component_layout(material_node, base_component_node, collapsable=False)

def refresh_base_layout_after_change(material_node):
    
    to_del = cmds.layout('AEmila_material_base', q=True, childArray=True)
    base_select_control=to_del[0]
    if to_del:
        for ui in to_del:
            if not (ui == base_select_control): # first ui
                cmds.deleteUI(ui)
    
    cmds.setParent('AEmila_material_base')

    #ensure the current base component is indicated in the UI
    components = collect_components(get_components_node(material_node, create_when_empty=True))
    base_component_node, parent_components = get_base_component_node(components)  #recurses through connection hierarchy

    refresh_component_layout(material_node, base_component_node, collapsable=False)

def get_base_component_node (components):
    if len(components) >0:
        last_component_node = get_connection('%s.%s' % (components[-1].node, components[-1].attr), 'shader')
        if last_component_node:
            if cmds.nodeType(last_component_node) in ('mila_layer', 'mila_mix'):
                components = collect_components(last_component_node)
                last_component_node, components = get_base_component_node(components)
            return (last_component_node, components)

base_component_nodes = {"Diffuse":'mila_diffuse_reflection',
                        "Diffuse (Scatter)":'mila_mix_subsurface_scatter',
                        "Diffuse (Translucent)":'mila_mix_translucent',
                        "Reflective":'mila_mix_reflective',
                        "Reflective (Anisotropic)":'mila_mix_reflective_aniso',
                        "Reflective (Fresnel)":'mila_layer_reflective',
                        "Reflective (Anisotropic, Fresnel)":'mila_layer_reflective_aniso',
                        "Reflective (Plastic)":'mila_layer_reflective_plastic',
                        "Reflective (Paint)":'mila_layer_reflective_paint',
                        "Transmissive":'mila_layer_transmissive',
                        "Transmissive (Clear)":'mila_layer_transmissive_clear',
                        "Transmissive (Distance)":'mila_layer_transmissive_distance'
                       }

def change_base_component(material_node, value):
    components_node = get_components_node(material_node, create_when_empty=False)
    if components_node:
        components = collect_components(components_node)
        current_base_component_node, parent_components = get_base_component_node(components)  #recurses through connection hierarchy
        if current_base_component_node:
            current_base_component_node_type = cmds.nodeType(current_base_component_node)
            new_node_type = base_component_nodes[value]
            if (new_node_type and (new_node_type != current_base_component_node_type)):
                cmds.disconnectAttr("%s.message" % current_base_component_node, '%s.%s.shader' % (parent_components[-1].node, parent_components[-1].attr))
                new_shader_node = cmds.createNode(new_node_type, skipSelect=True) # skipSelect allows us to stay on this node
                cmds.connectAttr ('%s.message' % new_shader_node, '%s.%s.shader' % (parent_components[-1].node, parent_components[-1].attr))
                current_color = get_connection_or_value(current_base_component_node, 'tint')
                set_connection_or_value(current_color, new_shader_node, 'tint')
                update_material_swatch(material_node)
                refresh_base_layout_after_change(material_node)
    else:
        print('MILA warning: no components')

def create_material_layout(material_node):
    components_node = get_components_node(material_node, create_when_empty=True)

    cmds.setUITemplate('attributeEditorTemplate', pushTemplate=True)
    
    cmds.columnLayout(adj=True)
    
    cmds.rowLayout(nc=3, cw3=[100, 100, 100], cat=[1, 'left', 20])
    cmds.button('mila_create_new_mix', w=120, rs=False, label='+ Mix', command=lambda *args: create_new_mix(components_node, material_node))
    cmds.button('mila_create_new_layer', w=120, rs=False, label='+ Layer', command=lambda *args: create_new_layer(components_node, material_node))
    cmds.setParent('..')
    cmds.setParent('..')
    cmds.setUITemplate(popTemplate=True)

    cmds.columnLayout('AEmila_material_components', adj=True)
    refresh_material_layout(material_node)
    cmds.setParent('..')  # matching column layout for initial creation    


def get_components_node(material_node, create_when_empty=True):
    connection = cmds.connectionInfo('%s.shader' % material_node, sourceFromDestination=True)
    if ((not connection) and create_when_empty):
        layer_node = cmds.createNode('mila_layer', skipSelect=True) # skipSelect allows us to stay on this node
        cmds.connectAttr ('%s.message' % (layer_node), '%s.shader' % material_node)
        shader_node = cmds.createNode('mila_diffuse_reflection', skipSelect=True) # skipSelect allows us to stay on this node
        cmds.connectAttr ('%s.message' % (shader_node), '%s.layers[0].shader' % (layer_node))
        connection = cmds.connectionInfo('%s.shader' % material_node, sourceFromDestination=True)  
        cmds.setAttr('%s.tint' % shader_node, 0.7, 0.7, 0.7, type='double3')
    return connection.split(".")[-2]  # not the last which should be "message", but the second to last layer or mix

def get_components_node_type(components_node):
    return cmds.nodeType(components_node)

def components_node_is_layer(components_node):
   return cmds.nodeType(components_node) ==  'mila_layer'

def components_node_is_mix(components_node):
   return cmds.nodeType(components_node) ==  'mila_mix'

def collect_components(components_node):
    components_node_type = cmds.nodeType(components_node)
    components_info = []
    if components_node_type == 'mila_layer':
        components_info = collect_layers(components_node)
    elif components_node_type == 'mila_mix':
        components_info = collect_mixes(components_node)
    else:
        print('MILA warning: Unknown components type %s' % components_node_type)
    return components_info

def refresh_material_layout(material_node):
    components_node = get_components_node(material_node)
    cmds.button('mila_create_new_mix', e=True, en=len(collect_components(components_node)) > 1, command=lambda *args: create_new_mix(components_node, material_node))
    cmds.button('mila_create_new_layer', e=True, command=lambda *args: create_new_layer(components_node, material_node))

    to_del = cmds.layout('AEmila_material_components', q=True, childArray=True)
    if to_del:
        for ui in to_del:
            cmds.deleteUI(ui)

    refresh_components_layout(components_node, material_node, hierarchy_level=0) #level 0 is top level

component_titles = {'mila_diffuse_reflection':"Diffuse Reflection",
                    'mila_glossy_reflection':"Glossy Reflection",
                    'mila_specular_reflection':"Specular Reflection",
                    'mila_diffuse_transmission':"Diffuse Transmission",
                    'mila_glossy_transmission':"Glossy Transmission",
                    'mila_specular_transmission':"Specular Transmission",
                    'mila_transparency':"Transparency",
                    'mila_scatter':"Scatter",
                    'mila_emission':"Emission",
                    'mila_mix_reflective':"Reflective",
                    'mila_mix_reflective_aniso':"Reflective (Anisotropic)",
                    'mila_layer_reflective':"Reflective (Fresnel)",
                    'mila_layer_reflective_aniso':"Reflective (Anisotropic, Fresnel)",
                    'mila_layer_reflective_plastic':"Reflective (Plastic)",
                    'mila_layer_reflective_paint':"Reflective (Paint)",
                    'mila_layer_transmissive':"Transmissive",
                    'mila_layer_transmissive_clear':"Transmissive (Clear)",
                    'mila_layer_transmissive_distance':"Transmissive (Distance)",
                    'mila_mix_translucent':"Translucent",
                    'mila_mix_glossy_reflection':"Glossy Reflection Mix",
                    'weighted_layer':"Weighted Layer",
                    'fresnel_layer':"Fresnel Layer",
                    'custom_layer':"Custom Layer",
                    'mila_mix':"Mix",
                    'mila_layer':"Layer"}

def get_component_title(component_node):
    component_node_type = cmds.nodeType(component_node)
    title = component_titles[component_node_type]
    if title:
        return title
    else:
        return component_node_type

level_colors = [[0.38, 0.38, 0.38],[0.32, 0.32, 0.32],[0.26, 0.26, 0.26],[0.20, 0.20, 0.20],[0.14, 0.14, 0.14]]

def refresh_components_layout(components_node, material_node, hierarchy_level=0):
    components_node_type = get_components_node_type(components_node) 
    components = collect_components(components_node)
    base_component_node, parent_components = get_base_component_node(collect_components(get_components_node(material_node)))
    if components:
        length_components_even = len(components) % 2 == 0
        if hierarchy_level == 0:
            cmds.setParent('AEmila_material_components')
        label_title_prefix = "%s" % get_component_title(components_node)
        label_title = ""
        for component in components:
            component.background_color = level_colors[hierarchy_level]

            last_component = (component == components[-1]) # last (base) component

            base_component = base_component_node == component.shader

            has_sub_components = (cmds.nodeType(component.shader) in ('mila_mix','mila_layer'))

            if not base_component:
                if has_sub_components:
                    label_title = "%s: %s Sub-Components" % (label_title_prefix, get_component_title(component.shader))
                else:
                    label_title = "%s: %s" % (label_title_prefix, get_component_title(component.shader))
                indent_margin = hierarchy_level * 40
                layout = cmds.frameLayout(label='%s' % (label_title), labelIndent=indent_margin, collapsable=True, collapse=0, backgroundColor=component.background_color)
                cmds.setUITemplate('attributeEditorTemplate', pushTemplate=True)
            
                cmds.columnLayout(rowSpacing=1, adjustableColumn=True, backgroundColor=component.background_color, columnOffset=['left', indent_margin])
            
                node_component_attr = '%s.%s' % (component.node, component.attr)

                if not (last_component and has_sub_components):
                    cmds.rowLayout(nc=5, cw5=[30, 25, 25, 270,30], cat=[1, 'left', 5])
            
                    enabled = cmds.checkBox(l='', cc=partial(update_material_swatch, material_node))
                    cmds.connectControl(enabled, '%s.on' % node_component_attr)

                    # move up not enabled for first component, nor last
                    up_enabled = not (component == components[0])
                    if last_component:
                        up_enabled = False
                    # move down not enabled for last component, nor for second to last
                    down_enabled = not last_component
                    if (len(components) > 1) and (component == components[-2]): 
                        down_enabled = False
            
                    cmds.symbolButton(image='arrowDown.png', vis=down_enabled, c=partial(component.move_down, material_node))
                    cmds.symbolButton(image='arrowUp.png', vis=up_enabled, c=partial(component.move_up, material_node))
           
                    cmds.attrFieldSliderGrp( l='Weight', min=0, max=1, at='%s.weight' % node_component_attr, cw=[1,60], vis=(not last_component))
                    cmds.scriptJob( attributeChange=['%s.weight' % node_component_attr, partial(update_material_swatch, material_node)] )

                    cmds.iconTextButton(style='iconOnly', h=25, mw=0, mh=0, image='smallTrash.png', c=partial(delete_component, node_component_attr, material_node), vis=(not last_component))

                    cmds.setParent('..') # row layout for top row of component containing icons and weight slider
                    
                is_layer = components_node_is_layer(components_node)
                directional_enable = is_layer and cmds.getAttr('%s.use_directional_weight' % node_component_attr)
                directional_mode = is_layer and cmds.getAttr('%s.directional_weight_mode' % node_component_attr)
                fresnel_enable = (directional_mode == 0) and directional_enable
                if is_layer:
                    cmds.attrFieldSliderGrp( l='Fresnel IOR', min=1.0, smx=2.0, at='%s.ior' % node_component_attr, vis=fresnel_enable)
                    cmds.scriptJob( attributeChange=['%s.ior' % node_component_attr, partial(update_material_swatch, material_node)] )
                    custom_enable = directional_mode == 1
                    cmds.attrFieldSliderGrp( l='Normal Reflectivity', min=0, max=1, at='%s.normal_reflectivity' % node_component_attr, en=cmds.getAttr('%s.directional_weight_mode' % node_component_attr)==1, vis=custom_enable)
                    cmds.scriptJob( attributeChange=['%s.normal_reflectivity' % node_component_attr, partial(update_material_swatch, material_node)] )
                    cmds.attrFieldSliderGrp( l='Grazing Reflectivity', min=0, max=1, at='%s.grazing_reflectivity' % node_component_attr, en=cmds.getAttr('%s.directional_weight_mode' % node_component_attr)==1, vis=custom_enable )
                    cmds.scriptJob( attributeChange=['%s.grazing_reflectivity' % node_component_attr, partial(update_material_swatch, material_node)] )

                    cmds.attrFieldSliderGrp( l='Exponent', min=0.1, smx=20, at='%s.exponent' % node_component_attr, en=cmds.getAttr('%s.directional_weight_mode' % node_component_attr)==1, vis=custom_enable)
                    cmds.scriptJob( attributeChange=['%s.exponent' % node_component_attr, partial(update_material_swatch, material_node)] )

                cmds.attrNavigationControlGrp(label='Bump', attribute='%s.bump' % node_component_attr, 
                                              disconnect='cmds.setAttr(%s.bump, 0, 0, 0)' % node_component_attr)
                cmds.separator()
                refresh_component_layout(material_node, component.shader, hierarchy_level=hierarchy_level, background_color=component.background_color, collapsable=(not last_component))

                cmds.setParent('..')  # matched with column layout for icons and weight controls, ie layer/mix controls
            
                cmds.setUITemplate(popTemplate=True)
                cmds.setParent('..')  # matched with frame layout for this component

def refresh_component_layout(material_node, component_node, hierarchy_level=0, background_color=[.267,.267,.267], collapsable=True):
    component_node_type = cmds.nodeType(component_node)
    has_sub_components = component_node_type in ('mila_mix', 'mila_layer')
    
    margin = hierarchy_level * 40
    
    if has_sub_components:
        components_node = component_node
        refresh_components_layout(components_node, material_node, hierarchy_level=hierarchy_level+1)
    elif component_node_type == 'mila_transparency':
        cmds.attrColorSliderGrp(l='Transparency Color', at='%s.transparency' % component_node)
        cmds.scriptJob( attributeChange=['%s.transparency' % component_node, partial(update_material_swatch, material_node)] )
    elif component_node_type == 'mila_scatter':
        scatter_layout(component_node, material_node, background_color)
    else:
        cmds.attrColorSliderGrp( l='Color', at='%s.tint' % component_node)
        cmds.scriptJob( attributeChange=['%s.tint' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type in ('mila_diffuse_reflection', 'mila_glossy_reflection', 'mila_glossy_transmission', 'mila_mix_subsurface_scatter', 'mila_mix_translucent'):
            cmds.attrFieldSliderGrp( l='Roughness', min=0, max=1, at='%s.roughness' % component_node)
            cmds.scriptJob( attributeChange=['%s.roughness' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type == 'mila_mix_glossy_reflection':
            cmds.attrFieldSliderGrp( l='Roughness 1', min=0, max=1, at='%s.roughness1' % component_node)
            cmds.scriptJob( attributeChange=['%s.roughness1' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Weight 1', min=0, max=1, at='%s.weight1' % component_node)
            cmds.scriptJob( attributeChange=['%s.weight1' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Roughness 2', min=0, max=1, at='%s.roughness2' % component_node)
            cmds.scriptJob( attributeChange=['%s.roughness2' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Weight 2', min=0, max=1, at='%s.weight2' % component_node)
            cmds.scriptJob( attributeChange=['%s.weight2' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Roughness 3', min=0, max=1, at='%s.roughness3' % component_node)
            cmds.scriptJob( attributeChange=['%s.roughness3' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Weight 3', min=0, max=1, at='%s.weight3' % component_node)
            cmds.scriptJob( attributeChange=['%s.weight3' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type in ('mila_mix_subsurface_scatter', 'mila_mix_translucent'):
            cmds.attrFieldSliderGrp( l='Diffuse Weight', min=0, max=1, at='%s.diffuse_weight' % component_node)
            cmds.scriptJob( attributeChange=['%s.diffuse_weight' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type == 'mila_mix_subsurface_scatter':
            diffuse_scatter_layout(component_node, material_node, background_color)
        if component_node_type in ('mila_glossy_transmission', 'mila_specular_transmission'):
            cmds.attrFieldSliderGrp( l='IOR', min=1, smx=2, fmx=10, at='%s.ior' % component_node)
            cmds.scriptJob( attributeChange=['%s.ior' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type in ('mila_mix_reflective', 'mila_mix_reflective_aniso', 'mila_layer_reflective', 'mila_layer_reflective_plastic'):
            cmds.attrFieldSliderGrp( l='Glossy Blend', min=0, max=1, at='%s.glossy_weight' % component_node)
            cmds.scriptJob( attributeChange=['%s.glossy_weight' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Glossy Roughness', min=0, max=1, at='%s.glossy_roughness' % component_node)
            cmds.scriptJob( attributeChange=['%s.glossy_roughness' % component_node, partial(update_material_swatch, material_node)] )
            if component_node_type in ('mila_mix_reflective_aniso', 'mila_layer_reflective_aniso'):
                anisotropy_layout(component_node, material_node, background_color)
                cmds.attrFieldSliderGrp( l='Diffuse Roughness', min=0, max=1, at='%s.diffuse_roughness' % component_node)
                cmds.scriptJob( attributeChange=['%s.diffuse_roughness' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type in ('mila_layer_reflective', 'mila_layer_reflective_plastic'):
            cmds.attrFieldSliderGrp( l='Fresnel IOR', min=1, smx=2, fmx=10, at='%s.ior' % component_node)
            cmds.scriptJob( attributeChange=['%s.ior' % component_node, partial(update_material_swatch, material_node)] )
        if component_node_type == 'mila_layer_reflective_paint':
            cmds.attrFieldSliderGrp( l='Edge Weight', min=0, max=1, at='%s.edge_weight' % component_node)
            cmds.scriptJob( attributeChange=['%s.edge_weight' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrColorSliderGrp( l='Edge Color', at='%s.edge_color' % component_node)
            cmds.scriptJob( attributeChange=['%s.edge_color' % component_node, partial(update_material_swatch, material_node)] )

            cmds.attrFieldSliderGrp( l='Edge Color Bias', min=1, smx=2, fmx=10, at='%s.edge_color_bias' % component_node)
            cmds.scriptJob( attributeChange=['%s.edge_color_bias' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Glossy Blend', min=0, max=1, at='%s.glossy_weight' % component_node)
            cmds.scriptJob( attributeChange=['%s.glossy_weight' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrColorSliderGrp( l='Glossy Color', at='%s.glossy_color' % component_node)
            cmds.scriptJob( attributeChange=['%s.glossy_color' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Glossy Roughness', min=0, max=1, at='%s.glossy_roughness' % component_node)
            cmds.scriptJob( attributeChange=['%s.glossy_roughness' % component_node, partial(update_material_swatch, material_node)] )
            flakes_layout(component_node, material_node, background_color)
        if component_node_type in ('mila_layer_transmissive','mila_layer_transmissive_clear','mila_layer_transmissive_distance'):
            if (component_node_type == 'mila_layer_transmissive_distance'):
                cmds.attrFieldSliderGrp( l='At Distance', min=0, smx=10, at='%s.max_dist' % component_node)
                cmds.scriptJob( attributeChange=['%s.max_dist' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='Reflective Weight', min=0, max=1, at='%s.reflective_weight' % component_node)
            cmds.scriptJob( attributeChange=['%s.reflective_weight' % component_node, partial(update_material_swatch, material_node)] )
            if component_node_type in ('mila_layer_transmissive','mila_layer_transmissive_distance'):
                cmds.attrFieldSliderGrp( l='Roughness', min=0, max=1, at='%s.roughness' % component_node)
                cmds.scriptJob( attributeChange=['%s.roughness' % component_node, partial(update_material_swatch, material_node)] )
            cmds.attrFieldSliderGrp( l='IOR', min=1, smx=2, fmx=10, at='%s.ior' % component_node)
            cmds.scriptJob( attributeChange=['%s.ior' % component_node, partial(update_material_swatch, material_node)] )
            if (component_node_type == 'mila_layer_transmissive'):
                anisotropy_layout(component_node, material_node, background_color)
        if (component_node_type == 'mila_emission'):
            cmds.attrFieldSliderGrp( l='Intensity', min=0, smx=20, at='%s.intensity' % component_node)
            cmds.scriptJob( attributeChange=['%s.intensity' % component_node, partial(update_material_swatch, material_node)] )
        else:
            contribution_layout(component_node, material_node, background_color)

def scatter_layout(component_node, material_node, background_color):
    cmds.attrColorSliderGrp( l='Front Tint', at='%s.front_tint' % component_node)
    cmds.scriptJob( attributeChange=['%s.front_tint' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Front Weight', min=0, max=1, at='%s.front_weight' % component_node)
    cmds.scriptJob( attributeChange=['%s.front_weight' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldGrp(label='Front Radius', at='%s.front_radius' % component_node)
    cmds.scriptJob( attributeChange=['%s.front_radius' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrColorSliderGrp( l='Front Radius Mod', at='%s.front_radius_mod' % component_node)
    cmds.scriptJob( attributeChange=['%s.front_radius_mod' % component_node, partial(update_material_swatch, material_node)] )
    cmds.separator()
    cmds.attrColorSliderGrp( l='Back Tint', at='%s.back_tint' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_tint' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Back Weight', min=0, max=1, at='%s.back_weight' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_weight' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldGrp(label='Back Radius', at='%s.back_radius' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_radius' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrColorSliderGrp( l='Back Radius Mod', at='%s.back_radius_mod' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_radius_mod' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Back Depth', min=0, smx=10, ann='Back depth distance scale, if 0, uses radius',
                             at='%s.back_depth' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_depth' % component_node, partial(update_material_swatch, material_node)] )
    cmds.separator()
    cmds.attrFieldSliderGrp( l='Scale Conversion', min=0, smx=10, at='%s.scale_conversion' % component_node)
    cmds.scriptJob( attributeChange=['%s.scale_conversion' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Sampling Radius Factor', min=0, smx=10, at='%s.sampling_radius_mult' % component_node)
    cmds.scriptJob( attributeChange=['%s.sampling_radius_mult' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrEnumOptionMenuGrp(l='Resolution',
                               at='%s.resolution' % component_node,
                               ei = [(0, "2 x Image"),
                                     (1, "1 x Image"),
                                     (2, "1/2 x Image"),
                                     (3, "1/3 x Image"),
                                     (4, "1/4 x Image"),
                                     (5, "1/5 x Image")])
    cmds.attrFieldSliderGrp( l='Light Storage Gamma', min=0, smx=2, at='%s.light_storage_gamma' % component_node)
    cmds.scriptJob( attributeChange=['%s.light_storage_gamma' % component_node, partial(update_material_swatch, material_node)] )

def diffuse_scatter_layout(component_node, material_node, background_color):
    cmds.separator()
    cmds.attrColorSliderGrp( l='Front Scatter Color', at='%s.front_color' % component_node)
    cmds.scriptJob( attributeChange=['%s.front_color' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Front Weight', min=0, max=1, at='%s.front_weight' % component_node)
    cmds.scriptJob( attributeChange=['%s.front_weight' % component_node, partial(update_material_swatch, material_node)] )
    cmds.separator()
    cmds.attrColorSliderGrp( l='Back Scatter Color', at='%s.back_color' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_color' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Back Weight', min=0, max=1, at='%s.back_weight' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_weight' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Back Scatter Depth', min=0, smx=10, ann='Back depth distance scale, if 0, uses radius',
                             at='%s.back_depth' % component_node)
    cmds.scriptJob( attributeChange=['%s.back_depth' % component_node, partial(update_material_swatch, material_node)] )
    cmds.separator()
    cmds.attrFieldSliderGrp( l='Scale Conversion', min=0, smx=10, at='%s.scale_conversion' % component_node,
                             ann='Divide scatter distance of 20 units by this conversion factor; higher values scatter less')
    cmds.scriptJob( attributeChange=['%s.scale_conversion' % component_node, partial(update_material_swatch, material_node)] )

flake_types = {"Distortion 1":0,
               "Distortion 2":1,
               "Distortion 3":2,
               "Cellular 1":3,
               "Cellular 2":4,
               "Cellular 3":5}

def change_flake_type(material_node, paint_node, value):
    set_connection_or_value(flake_types[value], paint_node, 'flake_type')
    cell_enable = flake_types[value] > 2
    cmds.optionMenuGrp("AEmila_flake_cell_style", e=True, en=cell_enable)
    circle_size_enable = cell_enable and (cmds.getAttr('%s.flake_cell_style' % paint_node) == 1)
    cmds.attrFieldSliderGrp("AEmila_flake_circle_size", e=True, en=circle_size_enable)
    update_material_swatch(material_node)

cell_styles = {"Edgy":0,
               "Circular":1}

def change_cell_style(material_node, paint_node, value):
    set_connection_or_value(cell_styles[value], paint_node, 'flake_cell_style')
    circle_size_enable = cell_styles[value] == 1
    cmds.attrFieldSliderGrp("AEmila_flake_circle_size", e=True, en=circle_size_enable)
    update_material_swatch(material_node)

def flakes_layout(component_node, material_node, background_color):
    cmds.frameLayout(collapsable=True, collapse=True, label="Flakes", labelIndent=80, font="tinyBoldLabelFont", backgroundColor=background_color)
    cmds.columnLayout(rowSpacing=1, adjustableColumn=True, backgroundColor=background_color)
    cmds.attrFieldSliderGrp( l='Flake Weight', min=0, max=1, at='%s.flake_weight' % component_node)
    cmds.scriptJob( attributeChange=['%s.flake_weight' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrColorSliderGrp( l='Flake Color', at='%s.flake_color' % component_node)
    cmds.scriptJob( attributeChange=['%s.flake_color' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Flake Roughness', min=0, max=1, at='%s.flake_roughness' % component_node)
    cmds.scriptJob( attributeChange=['%s.flake_roughness' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Flake Scale', min=0, smx=2, fmx=10, at='%s.flake_scale' % component_node)
    cmds.scriptJob( attributeChange=['%s.flake_scale' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Flake Strength', min=0, smx=1, at='%s.flake_strength' % component_node)
    cmds.scriptJob( attributeChange=['%s.flake_strength' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Flake Density', min=0, smx=2, at='%s.flake_density' % component_node)
    cmds.scriptJob( attributeChange=['%s.flake_density' % component_node, partial(update_material_swatch, material_node)] )
    cmds.optionMenuGrp("AEmila_flake_type", l='Flake Type')
    cmds.menuItem("Distortion 1")
    cmds.menuItem("Distortion 2")
    cmds.menuItem("Distortion 3")
    cmds.menuItem("Cellular 1")
    cmds.menuItem("Cellular 2")
    cmds.menuItem("Cellular 3")
    cmds.optionMenuGrp("AEmila_flake_type", e=True, select=cmds.getAttr("%s.flake_type"%component_node)+1, 
                       cc=partial(change_flake_type, material_node, component_node))
    cell_enable = cmds.getAttr('%s.flake_type' % component_node) > 2
    cmds.optionMenuGrp("AEmila_flake_cell_style", l='Cell Style', en=cell_enable)
    cmds.menuItem("Edgy")
    cmds.menuItem("Circular")
    flake_cell_style = cmds.getAttr('%s.flake_cell_style' % component_node)
    cmds.optionMenuGrp("AEmila_flake_cell_style", e=True, select = flake_cell_style + 1,
                       cc=partial(change_cell_style, material_node, component_node))              
    circle_size_enable = cell_enable and (flake_cell_style == 1)
    cmds.attrFieldSliderGrp("AEmila_flake_circle_size",
                             l='Circle Size', min=0, smx=2, fmx=10,
                             at='%s.flake_circle_size' % component_node,
                             en=circle_size_enable )
    cmds.scriptJob( attributeChange=['%s.flake_circle_size' % component_node, partial(update_material_swatch, material_node)] )
    cmds.setParent('..') # matched with column layout
    cmds.setParent('..') # matched with Flakes frame layout

def anisotropy_layout(component_node, material_node, background_color):
    cmds.frameLayout(collapsable=True, collapse=True, label="Anisotropy", labelIndent=80, font="tinyBoldLabelFont", backgroundColor=background_color)
    cmds.columnLayout(rowSpacing=1, adjustableColumn=True, backgroundColor=background_color)
    cmds.attrFieldSliderGrp( l='Anisotropy', min=0.1, smx=10, at='%s.anisotropy' % component_node)
    cmds.scriptJob( attributeChange=['%s.anisotropy' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Angle', smn=0, smx=1, at='%s.aniso_angle' % component_node)
    cmds.scriptJob( attributeChange=['%s.aniso_angle' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrEnumOptionMenuGrp(l='Channel',
                               at='%s.aniso_channel' % component_node,
                               ei = [(-1, "World Point"),
                                     (0, "Texture space 0"),
                                     (1, "Texture space 1"),
                                     (2, "Texture space 2"),
                                     (3, "Texture space 3"),
                                     (4, "Texture space 4"),
                                     (5, "Texture space 5")])
    cmds.scriptJob( attributeChange=['%s.aniso_channel' % component_node, partial(update_material_swatch, material_node)] )
    cmds.setParent('..') # matched with column layout
    cmds.setParent('..') # matched with Anisotropy frame layout
    
def contribution_layout(component_node, material_node, background_color):
    cmds.frameLayout(collapsable=True, collapse=True, label="Contribution", labelIndent=80, font="tinyBoldLabelFont", backgroundColor=background_color)
    cmds.columnLayout(rowSpacing=1, adjustableColumn=True, backgroundColor=background_color)
    cmds.attrFieldSliderGrp( l='Direct', min=0, smx=1, at='%s.direct' % component_node)
    cmds.scriptJob( attributeChange=['%s.direct' % component_node, partial(update_material_swatch, material_node)] )
    cmds.attrFieldSliderGrp( l='Indirect', min=0, smx=1, at='%s.indirect' % component_node)
    cmds.scriptJob( attributeChange=['%s.indirect' % component_node, partial(update_material_swatch, material_node)] )
    cmds.setParent('..') # matched with column layout
    cmds.setParent('..') # matched with Contribution frame layout


def choose_form_layout(components, title_label="Choose"):
    form = cmds.setParent(q=True)
    cmds.formLayout(form, e=True, width=210) #hardcode width, cause not resizable
    title = cmds.text(l=title_label)
    buttons = []
    for component in components:
        if component in component_titles.keys():
            component_title = component_titles[component]
            button = cmds.button(component, label=component_title, width=200, command='import maya.cmds\nmaya.cmds.layoutDialog(dismiss="' + component + '")' )
            buttons.append(button)
        elif component == 'separator':
            buttons.append(cmds.separator(st='in'))
        else:
            buttons.append(cmds.text(label=component))
    cancel = cmds.button(l='Cancel', c='import maya.cmds\ncmds.layoutDialog(dismiss="Cancel")')
    spacer = 4
    top = 8
    edge = 5
    attach_form_arg = [(title, 'top', top), (title, 'left', edge), (title, 'right', edge), (cancel, 'bottom', edge)]
    attach_none_arg = [(title, 'bottom')]
    attach_control_arg = []
    i = 0
    for button in buttons:
        if i == 0:
            above_button = title
            spacer = 10
        else:
            above_button = buttons[i-1]
            spacer = 4
        attach_form_arg.append((button, 'left', edge))
        attach_form_arg.append((button, 'right', edge))
        attach_none_arg.append((button, 'bottom'))
        attach_control_arg.append((button, 'top', spacer, above_button))
        i += 1
    attach_form_arg.append((cancel, 'left', 65))
    attach_form_arg.append((cancel, 'right', 65))
    attach_control_arg.append((cancel, 'top', 12, buttons[-1]))
    cmds.formLayout(form, edit=True,
                    attachForm=attach_form_arg,
                    attachNone=attach_none_arg,
                    attachControl=attach_control_arg)

def elemental_component_form():
    choose_form_layout(['separator',
                        'Reflection',
                        'mila_specular_reflection',
                        'mila_glossy_reflection',
                        'mila_mix_glossy_reflection',
                        'mila_diffuse_reflection', 
                        'separator',
                        'Transmission',
                        'mila_specular_transmission',
                        'mila_glossy_transmission',
                        'mila_diffuse_transmission',
                        'separator',
                        'mila_transparency',
                        'separator',
                        'Subsurface Scattering',
                        'mila_scatter',
                        'separator',
                        'Emission',
                        'mila_emission'],
                       title_label="Choose elemental component")

def choose_elemental_component():
    component = cmds.layoutDialog(ui=elemental_component_form)
    if component in ('Cancel','dismiss'):
        return 'None'
    else:
        return component

def layer_type_form():
    choose_form_layout(['weighted_layer','fresnel_layer','custom_layer'],title_label="Choose layer type")

def choose_layer_type():
    layer_type = cmds.layoutDialog(ui=layer_type_form)
    if layer_type in ('Cancel','dismiss'):
        return 'None'
    else:
        return layer_type

def create_new_layer(node, material_node):
    index = 0
    component_attrs = []
    if components_node_is_mix(node):
        component_attrs = cmds.listAttr(node, m=True, v=True, c=True, st='components')
        if len(component_attrs) > 1:
	    bottom_component = node
        elif len(component_attrs) == 0:
            print ("MILA Error: expected mila_mix with 1 layer, got %i instead" % len(component_attrs))
        else:
            print ("MILA: Adding layer, single mix component, so replace mix with layer")
            bottom_component = collect_mixes(node)[0].shader
        layer_node = cmds.createNode('mila_layer', skipSelect=True)
        cmds.connectAttr ('%s.message' % bottom_component, '%s.layers[0].shader' % layer_node, f=True)
        cmds.connectAttr ('%s.message' % layer_node, '%s.shader' % material_node, f=True)
        node = layer_node # was mix node
    component_attrs = cmds.listAttr(node, m=True, v=True, c=True, st='layers')
    if component_attrs:
        index = component_attr_index(component_attrs[-1])
        index += 1
    layer_type = choose_layer_type()
    if (layer_type != 'None'):
        shader_name = choose_elemental_component()
        if (shader_name != 'None'):
            shader_node = cmds.createNode(shader_name, skipSelect=True) # skipSelect allows us to stay on this node
            cmds.connectAttr ('%s.message' % (shader_node), '%s.layers[%i].shader' % (node, index))
            if layer_type in ('fresnel_layer', 'custom_layer'):
                cmds.setAttr('%s.layers[%i].use_directional_weight' % (node, index), 1)
                if (layer_type == 'custom_layer'):
                    cmds.setAttr('%s.layers[%i].directional_weight_mode' % (node, index), 1)
            layers = collect_layers(node)
            if (len(layers) == index+1):
                while (index > 0):
                    layers = collect_layers(node)
                    layers[index].move_up(material_node)
                    index -= 1
    refresh_material_layout(material_node)
    update_material_swatch(material_node)
   
def mix_over_layers_form():
    choose_form_layout(['Mix into top layer','Mix with all layers'], title_label="Choose mix placement")

def mix_with_components_form():
    choose_form_layout(['Mix with all components','Mix'], title_label="Choose mix type")

def choose_mix_component_type():
    mix_type = cmds.layoutDialog(ui=mix_over_layers_form)
    if mix_type in ('Cancel','dismiss'):
        return 'None'
    else:
        return mix_type

def create_new_mix(node, material_node):
    index = 0
    component_attrs = []
    shader_name = choose_elemental_component()
    if (shader_name == 'None'):
        return
    node_is_layer = components_node_is_layer(node)
    if node_is_layer:
        mix_into_top_layer = False
        create_new_mix_node = True
        component_attrs = cmds.listAttr(node, m=True, v=True, c=True, st='layers')
        if len(component_attrs) > 1:
            mix_into_top_layer = True
            if mix_into_top_layer:
                bottom_component = collect_layers(node)[0].shader
                if components_node_is_mix(bottom_component):
                   create_new_mix_node = False
                print ("MILA: Adding mix to top layer %s" % bottom_component)
            else:
                bottom_component = node # mix with current mila_layer as bottom(base)component
        else:
            print ("MILA: Adding mix, single layer component, so replace layer with mix")
            bottom_component = collect_layers(node)[0].shader
            if components_node_is_mix(bottom_component):
               create_new_mix_node = False

        if create_new_mix_node:
            mix_node = cmds.createNode('mila_mix', skipSelect=True)
            cmds.connectAttr ('%s.message' % bottom_component, '%s.components[0].shader' % mix_node, f=True)
            cmds.setAttr('%s.clamp' % (mix_node), 1)
            if mix_into_top_layer:
                cmds.connectAttr ('%s.message' % mix_node, '%s.layers[0].shader' % node, f=True)
            else: # mix into material         
                cmds.connectAttr ('%s.message' % mix_node, '%s.shader' % material_node, f=True)
            node = mix_node # node was layer node
        else:
            node = bottom_component # an existing mix node
    # node ensured to be a mix node, ready for adding a top mix component over one or more others
    component_attrs = cmds.listAttr(node, m=True, v=True, c=True, st='components')
    if component_attrs:
        index = component_attr_index(component_attrs[-1])
        index += 1
        shader_node = cmds.createNode(shader_name, skipSelect=True) # skipSelect allows us to stay on this node
        cmds.connectAttr ('%s.message' % (shader_node), '%s.components[%i].shader' % (node, index))
        mixes = collect_mixes(node)
        # move new last component to component 0 slot by moving it up, index from high to low
        if (len(mixes) == index+1):
            while (index > 0):
                mixes = collect_mixes(node)
                mixes[index].move_up(material_node)
                index -= 1
        cmds.setAttr('%s.components[0].weight' % (node), 0.5)
    refresh_material_layout(material_node)
    update_material_swatch(material_node)

def check_component_indices(components):
    i = 0
    for component in components:
        if (i != component.index()):
            return False
        i += 1
    return True

def delete_component(attr, material_node, *args):
    print('MILA: Deleting %s' % attr)
    index = component_attr_index(attr)
    node = attr.partition('.')[0]
    if node:
        components = collect_components(node)
        if check_component_indices(components):
            i = 0
            while (i < len(components)-1): # moving layer-to-delete to bottom, last move is second-to-last layer
                if (i == index):
                    components[index].move_down(None) # None is no material_node, so refresh doesn't happen til end
                    index += 1
                i += 1
                components = collect_components(node)
            if (components[-1].index() == (len(components) - 1)): # check that last layer is correctly numbered
                cmds.removeMultiInstance('%s.%s' % (components[-1].node, components[-1].attr), b=True)
            remaining_node = components[-2].shader
            if ((len(components) == 2) and (node == get_components_node(material_node))): 
                if cmds.nodeType(remaining_node) in ('mila_mix', 'mila_layer'): 
                    cmds.connectAttr ('%s.message' % (remaining_node), '%s.shader' % (material_node), f=True)
            refresh_material_layout(material_node)
            update_material_swatch(material_node)

def update_material_swatch(material_node, *args):
    fb = cmds.getAttr('%s.show_framebuffer' % material_node)
    cmds.setAttr('%s.show_framebuffer' % material_node, fb)

# Copyright (C) 1997-2014 Autodesk, Inc., and/or its licensors.
# All rights reserved.
#
# The coded instructions, statements, computer programs, and/or related
# material (collectively the "Data") in these files contain unpublished
# information proprietary to Autodesk, Inc. ("Autodesk") and/or its licensors,
# which is protected by U.S. and Canadian federal copyright law and by
# international treaties.
#
# The Data is provided for use exclusively by You. You have the right to use,
# modify, and incorporate this Data into other products for purposes authorized 
# by the Autodesk software license agreement, without fee.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. AUTODESK
# DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTIES
# INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF NON-INFRINGEMENT,
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ARISING FROM A COURSE 
# OF DEALING, USAGE, OR TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS
# LICENSORS BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
# DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK AND/OR ITS
# LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY OR PROBABILITY OF SUCH DAMAGES.

