<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2012-2018, <NAME>
#
# This module is part of anima-tools and is released under the BSD 2
# License: http://www.opensource.org/licenses/BSD-2-Clause

import pymel.core as pm

import re
FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)')
ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')


def kill_all_torn_off_panels():
    """deletes all torn off panels
    """
    panel_list = pm.getPanel(type='modelPanel')

    # remove all torn off panels
    for panel in panel_list:
        if panel.getTearOff():
            panel.delete(pnl=1)


def maximize_first_model_panel():
    """maximizes the first model panel it can find

    :return:
    """
    panel_list = pm.getPanel(type='modelPanel')
    if len(panel_list) == 0:
        return

    # maximize one panel in default layout
    g_main_pane = pm.melGlobals['gMainPane']

    pane_config = pm.paneLayout(g_main_pane, q=1, configuration=1)
    if pane_config != 'single':
        # call mel here
        pm.mel.eval('doSwitchPanes(1, { "single", "%s"})' % panel_list[0])
        pm.mel.eval('updateToolbox();')


def get_valid_dag_node(node):
    """returns a valid dag node even the input is string
    """
    try:
        dag_node = pm.nodetypes.DagNode(node)
    except pm.MayaNodeError:
        print('Error: no node named : %s' % node)
        return None

    return dag_node


def get_valid_node(node):
    """returns a valid PyNode even the input is string
    """
    try:
        PyNode = pm.PyNode(node)
    except pm.MayaNodeError:
        print('Error: no node named : %s' % node)
        return None

    return PyNode


def get_anim_curves(node):
    """returns all the animation curves connected to the
    given node
    """
    # list all connections to the node
    connected_nodes = pm.listConnections(node)

    anim_curve = "animCurve"

    return_list = []
    for cNode in connected_nodes:
        if pm.nodeType(cNode)[0:len(anim_curve)] == anim_curve:
            return_list.append(cNode)

    return return_list


def set_anim_curve_color(anim_curve, color):
    """sets animCurve color to color
    """
    anim_curve = get_valid_node(anim_curve)
    anim_curve.setAttr("useCurveColor", True)
    anim_curve.setAttr("curveColor", color, type="double3")


def axial_correction_group(obj,
                           to_parents_origin=False,
                           name_prefix="",
                           name_postfix="_ACGroup#"):
    """creates a new parent to zero out the transformations

    if to_parents_origin is set to True, it doesn't zero outs the
    transformations but creates a new parent at the same place of the original
    parent

    :returns: pymel.core.nodeTypes.Transform
    """
    obj = get_valid_dag_node(obj)

    if name_postfix == "":
        name_postfix = "_ACGroup#"

    ac_group = pm.group(
        em=True,
        n=(name_prefix + obj.name() + name_postfix)
    )

    ac_group = pm.parent(ac_group, obj)[0]

    pm.setAttr(ac_group + ".t", [0, 0, 0])
    pm.setAttr(ac_group + ".r", [0, 0, 0])
    pm.setAttr(ac_group + ".s", [1, 1, 1])

    parent = pm.listRelatives(obj, p=True)
    if len(parent) != 0:
        pm.parent(ac_group, parent[0], a=True)
    else:
        pm.parent(ac_group, w=True)

    if to_parents_origin:
        pm.setAttr(ac_group + ".t", [0, 0, 0])
        pm.setAttr(ac_group + ".r", [0, 0, 0])
        pm.setAttr(ac_group + ".s", [1, 1, 1])

    pm.parent(obj, ac_group, a=True)

    # for joints also set the joint orient to zero
    if isinstance(obj, pm.nodetypes.Joint):
        # set the joint rotation and joint orient to zero
        obj.setAttr('r', (0, 0, 0))
        obj.setAttr('jo', (0, 0, 0))

    return ac_group


def go_home(node):
    """sets all the transformations to zero
    """
    if node.attr('t').isSettable():
        node.setAttr('t', (0, 0, 0))
    if node.attr('r').isSettable():
        node.setAttr('r', (0, 0, 0))
    if node.attr('s').isSettable():
        node.setAttr('s', (1, 1, 1))


def rivet():
    """the python version of the famous rivet setup from Bazhutkin
    """
    selection_list = pm.filterExpand(sm=32)

    if selection_list is not None and len(selection_list) > 0:
        size = len(selection_list)
        if size != 2:
            raise pm.MayaObjectError('No two edges selected')

        edge1 = pm.PyNode(selection_list[0])
        edge2 = pm.PyNode(selection_list[1])

        edge1Index = edge1.indices()[0]
        edge2Index = edge2.indices()[0]

        shape = edge1.node()

        cFME1 = pm.createNode('curveFromMeshEdge', n='rivetCurveFromMeshEdge#')
        cFME1.setAttr('ihi', 1)
        cFME1.setAttr('ei[0]', edge1Index)

        cFME2 = pm.createNode('curveFromMeshEdge', n='rivetCurveFromMeshEdge#')
        cFME2.setAttr('ihi', 1)
        cFME2.setAttr('ei[0]', edge2Index)

        loft = pm.createNode('loft', n='rivetLoft#')
        loft.setAttr('ic', s=2)
        loft.setAttr('u', 1)
        loft.setAttr('rsn', 1)

        pOSI = pm.createNode('pointOnSurfaceInfo',
                             n='rivetPointOnSurfaceInfo#')
        pOSI.setAttr('turnOnPercentage', 1)
        pOSI.setAttr('parameterU', 0.5)
        pOSI.setAttr('parameterV', 0.5)

        loft.attr('os') >> pOSI.attr('is')
        cFME1.attr('oc') >> loft.attr('ic[0]')
        cFME2.attr('oc') >> loft.attr('ic[1]')
        shape.attr('w') >> cFME1.attr('im')
        shape.attr('w') >> cFME2.attr('im')
    else:
        selection_list = pm.filterExpand(sm=41)

        if selection_list is not None and len(selection_list) > 0:
            size = len(selection_list)
            if size != 1:
                raise pm.MayaObjectError('No one point selected')

            point = pm.PyNode(selection_list[0])
            shape = point.node()
            u = float(point.name().split('][')[0].split('[')[1])
            v = float(point.name().split('][')[1].split(']')[0])

            pOSI = pm.createNode('pointOnSurfaceInfo',
                                 n='rivetPointOnSurfaceInfo#')
            pOSI.setAttr('turnOnPercentage', 0)
            pOSI.setAttr('parameterU', u)
            pOSI.setAttr('parameterV', v)
            shape.attr('ws') >> pOSI.attr('is')
        else:
            raise pm.MayaObjectError('No edges or point selected')

    locator = pm.spaceLocator(n='rivet#')
    aimCons = pm.createNode('aimConstraint',
                            p=locator,
                            n=locator.name() + '_rivetAimConstraint#')
    aimCons.setAttr('tg[0].tw', 1)
    aimCons.setAttr('a', (0, 1, 0))
    aimCons.setAttr('u', (0, 0, 1))
    aimCons.setAttr('v', k=0)
    aimCons.setAttr('tx', k=0)
    aimCons.setAttr('ty', k=0)
    aimCons.setAttr('tz', k=0)
    aimCons.setAttr('rx', k=0)
    aimCons.setAttr('ry', k=0)
    aimCons.setAttr('rz', k=0)
    aimCons.setAttr('sx', k=0)
    aimCons.setAttr('sy', k=0)
    aimCons.setAttr('sz', k=0)

    pOSI.attr('position') >> locator.attr('translate')
    pOSI.attr('n') >> aimCons.attr('tg[0].tt')
    pOSI.attr('tv') >> aimCons.attr('wu')
    aimCons.attr('crx') >> locator.attr('rx')
    aimCons.attr('cry') >> locator.attr('ry')
    aimCons.attr('crz') >> locator.attr('rz')

    pm.select(locator)
    return locator


def auto_rivet():
    """creates hair follicles around selection
    """
    sel_list = pm.ls(sl=1)

    # the last selection is the mesh
    objects = sel_list[:-1]
    geo = sel_list[-1]

    # get the closest point to the surface
    geo_shape = geo.getShape()

    follicles = []

    for obj in objects:
        # pivot point of the obj
        pivot = obj.getRotatePivot(space='world')
        uv = geo_shape.getUVAtPoint(pivot, space='world')

        # create a hair follicle
        follicle = pm.nt.Follicle()
        follicles.append(follicle)
        follicle.simulationMethod.set(0)
        geo_shape.worldMatrix >> follicle.inputWorldMatrix
        geo_shape.outMesh >> follicle.inputMesh
        follicle.parameterU.set(uv[0])
        follicle.parameterV.set(uv[1])

        # parent the object to the follicles transform node
        follicle_transform = follicle.getParent()

        follicle.outTranslate >> follicle_transform.translate
        follicle.outRotate >> follicle_transform.rotate

        pm.parent(obj, follicle_transform)

    return follicles


def hair_from_curves():
    """creates hairs from curves
    """
    selection_list = pm.ls(sl=1)

    curves = []
    curve_shapes = []

    mesh = ""
    mesh_shape = ""

    for i in range(0, len(selection_list)):
        shapes = pm.listRelatives(selection_list[i], s=True)
        node_type = pm.nodeType(shapes[0])

        if node_type == 'nurbsCurve':
            curves.append(selection_list[i])
            curve_shapes.append(shapes[0])
        elif node_type == 'mesh':
            mesh = selection_list[i]
            mesh_shape = shapes[0]

    do_output_curve = 1
    hide_output_curve = 0

    #create hair
    hair_system = pm.createNode('hairSystem')
    pm.connectAttr('time1.outTime', (hair_system + '.currentTime'))

    hair_system_group = ""
    hair_system_out_hair_group = ""
    hair_system_parent = pm.listTransforms(hair_system)
    if len(hair_system_parent) > 0:
        hair_system_group = hair_system_parent[0] + "Follicles"
        if not pm.objExists(hair_system_group):
            hair_system_group = pm.group(em=1, name='hsysGroup')
        if do_output_curve:
            hair_system_out_hair_group = hair_system_parent[0] + "OutputCurves"
            if not pm.objExists(hair_system_out_hair_group):
                hair_system_out_hair_group = \
                    pm.group(em=1, name='hsysOutHairGroup')
            if hide_output_curve:
                pm.setAttr(hair_system_out_hair_group + '.visibility', False)

    #create closestPointOnMesh to read the closest point parameter
    cpom = pm.createNode('closestPointOnMesh')
    pm.connectAttr((mesh_shape + '.worldMesh[0]'), (cpom + '.inMesh'))

    num_of_curves = len(curves)

    for i in range(0, num_of_curves):
        dup_name = pm.duplicate(curves[i])
        dup_shape = pm.listRelatives(dup_name[0], s=True)

        first_cv_tra = pm.xform(q=True, ws=True, t=(curves[i] + '.cv[0'))

        pm.setAttr(
            (cpom + '.ip'),
            (first_cv_tra[0], first_cv_tra[1], first_cv_tra[2]),
            type="double3"
        )

        pu = pm.getAttr(cpom + '.r.u')
        pv = pm.getAttr(cpom + '.r.v')

        hair_curve_name_prefix = mesh + "Follicle"
        naming_index = \
            num_of_curves * int(pu * float(num_of_curves - 1) + 0.5) + \
            int(pv * float(num_of_curves - 1) + 0.5)

        new_name = hair_curve_name_prefix + str(naming_index)

        #create follicle
        hair = pm.createNode('follicle')
        pm.setAttr(pu, hair + '.parameterU')
        pm.setAttr(pv, hair + '.parameterV')

        pm.connectAttr((curve_shapes[i] + '.worldSpace[0]'), (hair + '.sp'))

        transforms = pm.listTransforms(hair)
        hair_dag = transforms[0]

        pm.connectAttr((mesh_shape + '.worldMatrix[0]'), (hair + '.inputWorldMatrix'))

        pm.connectAttr((mesh_shape + '.outMesh'), (hair + '.inputMesh'))
        current_uv_set = pm.polyUVSet(q=True, currentUVSet=mesh_shape)
        pm.setAttr(current_uv_set[0], (hair + '.mapSetName'), type="string")

        pm.connectAttr((hair + '.outTranslate'), (hair_dag + '.translate'))
        pm.connectAttr((hair + '.outRotate'), (hair_dag + '.rotate'))
        pm.setAttr((hair_dag + '.translate'), lock=True)
        pm.setAttr((hair_dag + '.rotate'), lock=True)

        pm.setAttr(hair + '.degree', 3)
        pm.setAttr(hair + '.startDirection', 1)
        pm.setAttr(hair + '.restPose', 3)

        pm.parent(hair_system_group, relative=hair_dag)

        pm.parent(hair_dag, absolute=curves[i])

        pm.setAttr(hair + '.simulationMethod', 2)

        #initHairCurveDisplay(curves[i], "start")

        hair_index = i
        pm.connectAttr(
            (hair + '.outHair'),
            (hair_system + '.inputHair[%f]' % hair_index)
        )
        pm.connectAttr(
            (hair_system + '.inputHair[%f]' % hair_index),
            (hair + '.currentPosition')
        )

        crv = dup_shape[0]
        pm.connectAttr((hair + '.outCurve'), (crv + '.create'))
        #initHairCurveDisplay(crv, "current")

        transforms = pm.listTransforms(crv)
        pm.parent(transforms[0], hair_system_out_hair_group, r=True)

        pm.rename(hair_dag, new_name)

    pm.select(hair_system, r=True)

    import maya.mel as mel
    mel.eval('displayHairCurves("current", true')

    pm.delete(cpom)


def align_to_pole_vector():
    """aligns the object to the pole vector of the selected ikHandle
    """
    selection_list = pm.ls(sl=1)

    ik_handle = ""
    control_object = ""

    for obj in selection_list:
        if pm.nodeType(obj) == 'ikHandle':
            ik_handle = obj
        else:
            control_object = obj

    temp = pm.listConnections((ik_handle + '.startJoint'), s=1)
    start_joint = temp[0]
    start_joint_pos = pm.xform(start_joint, q=True, ws=True, t=True)

    temp = pm.listConnections((ik_handle + '.endEffector'), s=1)
    end_effector = temp[0]
    pm.xform(
        control_object,
        ws=True,
        t=(start_joint_pos[0], start_joint_pos[1], start_joint_pos[2])
    )

    pm.parent(control_object, end_effector)
    pm.setAttr(control_object + '.r', 0, 0, 0)

    pm.parent(control_object, w=True)


def export_blend_connections():
    """Exports the connection commands from selected objects to the blendShape
    of another object. The resulted text file contains all the MEL scripts to
    reconnect the objects to the blendShape node. So after exporting the
    connection commands you can export the blendShape targets as another maya
    file and delete them from the scene, thus your scene gets lite and loads
    much more quickly.
    """
    selection_list = pm.ls(tr=1, sl=1, l=1)

    dialog_return = pm.fileDialog2(cap="Save As", fm=0, ff='Text Files(*.txt)')

    filename = dialog_return[0]
    print(filename)

    print("\n\nFiles written:\n--------------------------------------------\n")

    with open(filename, 'w') as fileId:
        for i in range(0, len(selection_list)):
            shapes = pm.listRelatives(selection_list[i], s=True, f=True)

            main_shape = ""
            for j in range(0, len(shapes)):
                if pm.getAttr(shapes[j] + '.intermediateObject') == 0:
                    main_shape = shapes
                    break
            if main_shape == "":
                main_shape = shapes[0]

            con = pm.listConnections(main_shape, t="blendShape", c=1, s=1, p=1)

            cmd = "connectAttr -f %s.worldMesh[0] %s;" % (
                ''.join(map(str, main_shape)),
                ''.join(map(str, con[0].name()))
            )
            print (cmd + "\n")
            fileId.write("%s\n" % cmd)

    print("\n------------------------------------------------------\n")
    print("filename: %s     ...done\n" % filename)


def transfer_shaders(source, target):
    """transfers shader from source to target
    """
    if isinstance(source, pm.nt.Transform):
        source_shape = source.getShape()
    else:
        source_shape = source

    # get the shadingEngines
    shading_engines = source_shape.outputs(type=pm.nt.ShadingEngine)

    if len(shading_engines):
        pm.sets(shading_engines[0], fe=target)
        # also assign instances to the same shader
        if target.instanceCount() > 1:
            for i in range(1, target.instanceCount()):
                target.attr('instObjGroups[%s]' % i).disconnect()
                target.attr('instObjGroups[%s]' % i) >> \
                shading_engines[0].attr('dagSetMembers').next_available


def benchmark(iter_cnt):
    """benchmarks playback rate

    :param iter_cnt: Count of iteration
    """
    import time

    start = pm.playbackOptions(q=1, min=1)
    stop = pm.playbackOptions(q=1, max=1)

    start_time = time.time()

    for j in range(iter_cnt):
        for i in range(start, stop + 1):
            pm.currentTime(e=i)

    total_time = time.time() - start_time
    print("------------------------------")
    print("BenchmarkTime : %s" % total_time)
    print("Total iterCnt : %s" % iter_cnt)
    print("Average FPS   : %s" % ((stop - start) * iter_cnt / total_time))


def load_shelf_tab(shelf_path):
    """loads the given shelf tab
    """
    # look in to the shelf mel file from user folders
    import os
    if os.path.exists(shelf_path):
        try:
            pm.mel.eval('loadNewShelf "%s"' % shelf_path)
        except Exception:
            # probably not in GUI mode
            return


def delete_shelf_tab(shelf_name, confirm=True):
    """The python version of the original mel script of Maya
    :param shelf_name: The name of the shelf to delete
    """
    try:
        shelf_top_level_path = pm.melGlobals['gShelfTopLevel']
    except KeyError:
        # not in GUI mode
        return

    shelf_top_level = pm.windows.tabLayout(shelf_top_level_path, e=1)

    if len(shelf_top_level.children()) < 0:
        return

    if confirm:
        # before doing anything ask it
        response = pm.confirmDialog(
            title='Delete Shelf?',
            message='Delete %s?' % shelf_name,
            button=['Yes', 'No'],
            defaultButton='No',
            cancelButton='No',
            dismissString='No'
        )
        if response == 'No':
            return

    # update the preferences
    shelf_number = -1
    number_of_shelves = pm.optionVar['numShelves']
    for i in range(1, number_of_shelves + 1):
        if pm.optionVar['shelfName%s' % i] == shelf_name:
            shelf_number = i
            break

    if shelf_number == -1:
        # there should be no shelf with this name
        return

    # offset shelves
    for i in range(shelf_number, number_of_shelves):
        pm.optionVar['shelfLoad%s' % i] = pm.optionVar['shelfLoad%s' % (i + 1)]
        pm.optionVar['shelfName%s' % i] = pm.optionVar['shelfName%s' % (i + 1)]
        pm.optionVar['shelfFile%s' % i] = pm.optionVar['shelfFile%s' % (i + 1)]

    pm.optionVar.pop('shelfLoad%s' % number_of_shelves)
    number_of_shelves -= 1
    pm.optionVar['numShelves'] = number_of_shelves

    pm.windows.deleteUI('%s|%s' % (shelf_top_level_path, shelf_name), layout=1)

    # remove the shelf mel file from user folders
    import os
    for path in pm.internalVar(userShelfDir=1).split(os.path.pathsep):
        shelf_file_name = 'shelf_%s.mel' % shelf_name
        shelf_file_full_path = os.path.join(path, shelf_file_name)

        deleted_file_name = '%s.deleted' % shelf_file_name
        deleted_file_full_path = os.path.join(path, deleted_file_name)

        try:
            os.remove(deleted_file_full_path)
        except OSError:
            pass

        try:
            os.remove(shelf_file_full_path)
            break
        except OSError:
            pass

    # Make sure the new active shelf tab has buttons
    pm.mel.eval('shelfTabChange();')


def cube_from_bbox(bbox):
    """creates a polyCube from the given bbox

    :param bbox: pymel.core.dt.BoundingBox instance
    """
    cube = pm.polyCube(
        width=bbox.width(),
        height=bbox.height(),
        depth=bbox.depth(),
        ch=False
    )
    cube[0].setAttr('t', bbox.center())
    return cube[0]


def create_bbox(nodes, per_selection=False):
    """creates bounding boxes for the selected objects

    :param bool per_selection: If True will create a BBox for each
      given object
    """

    if per_selection:
        for node in nodes:
            return cube_from_bbox(node.boundingBox())
    else:
        bbox = pm.dt.BoundingBox()
        for node in nodes:
            bbox.expand(node.boundingBox().min())
            bbox.expand(node.boundingBox().max())
        return cube_from_bbox(bbox)


def replace_with_bbox(nodes):
    """replaces the given nodes with a bbox object
    """
    node_names = []
    bboxes = []
    processed_nodes = []
    for node in nodes:
        # create a bbox and parent it to the parent of
        # the original node

        # check if it is a transform node
        if not isinstance(node, pm.nt.Transform):
            continue

        # check the shape
        # check if it has at least one shape under it
        if not has_shape(node):
            continue

        bbox = cube_from_bbox(node.boundingBox())
        bbox.setParent(node.getParent())

        # set pivots
        rp = pm.xform(node, q=1, ws=1, rp=1)
        sp = pm.xform(node, q=1, ws=1, sp=1)
        pm.xform(bbox, ws=1, rp=rp)
        pm.xform(bbox, ws=1, sp=sp)

        node_name = node.name()
        node_shape = node.getShape()
        node_shape_name = None
        if node_shape is not None:
            node_shape_name = node_shape.name()

        node_names.append((node_name, node_shape_name))
        bboxes.append(bbox)
        processed_nodes.append(node)

    # delete the nodes
    if len(processed_nodes):
        pm.delete(processed_nodes)

        # rename the bboxes
        for name, bbox in zip(node_names, bboxes):
            bbox.rename(name[0])
            if name[1]:
                bbox.getShape().rename(name[1])

    return bboxes


def get_root_nodes():
    """returns the root DAG nodes
    """
    root_transform_nodes = []
    for node in pm.ls(dag=1, transforms=1):
        if node.getParent() is None:
            shape = node.getShape()
            if shape:
                if shape.type() not in ['camera']:
                    root_transform_nodes.append(node)
            else:
                root_transform_nodes.append(node)

    return root_transform_nodes


def create_arnold_stand_in(path=None):
    """A fixed version of original arnold script of SolidAngle Arnold core API
    """
    if not pm.objExists('ArnoldStandInDefaultLightSet'):
        pm.createNode(
            "objectSet",
            name="ArnoldStandInDefaultLightSet",
            shared=True
        )
        pm.lightlink(
            object='ArnoldStandInDefaultLightSet',
            light='defaultLightSet'
        )

    stand_in = pm.createNode('aiStandIn', n='ArnoldStandInShape')
    # temp fix until we can correct in c++ plugin
    stand_in.setAttr('visibleInReflections', True)
    stand_in.setAttr('visibleInRefractions', True)

    pm.sets('ArnoldStandInDefaultLightSet', add=stand_in)
    if path:
        stand_in.setAttr('dso', path)

    return stand_in


def create_rs_proxy_node(path=None):
    """Creates Redshift Proxies showing a proxy object
    """
    proxy_mesh_node = pm.createNode('RedshiftProxyMesh')
    proxy_mesh_node.fileName.set(path)
    proxy_mesh_shape = pm.createNode('mesh')
    proxy_mesh_node.outMesh >> proxy_mesh_shape.inMesh

    # assign default material
    pm.sets('initialShadingGroup', fe=proxy_mesh_shape)

    return proxy_mesh_node, proxy_mesh_shape


def run_pre_publishers():
    """runs pre publishers if the current scene is a published version

    This is written to prevent users to save on top of a Published version and
    create a back door to skip un publishable scene to publish
    """
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()

    version = m_env.get_current_version()

    # check if we have a proper version
    if not version:
        return

    # check if it is a Representation
    from anima.representation import Representation
    if Representation.repr_separator in version.take_name:
        return

    if version.is_published:
        from anima.publish import (run_publishers, staging, PRE_PUBLISHER_TYPE,
                                   POST_PUBLISHER_TYPE)
        # before doing anything run all publishers
        type_name = ''
        if version.task.type:
            type_name = version.task.type.name

        # before running use the staging area to store the current version
        staging['version'] = version
        run_publishers(type_name)
        # do not forget to clean up the staging area
        staging.clear()
    else:
        # run some of the publishers
        from anima.env.mayaEnv.publish import PublishError
        from anima.env.mayaEnv import publish as publish_scripts
        try:
            publish_scripts.check_node_names_with_bad_characters()
        except PublishError as e:
            # pop up a message box with the error
            pm.confirmDialog(
                title='SaveError',
                message=str(''.join([i for i in unicode(e) if ord(i) < 128])),
                button=['Ok']
            )
            raise e

        # update updated_by field of the current version
        from stalker import LocalSession
        ls = LocalSession()
        logged_in_user = ls.logged_in_user
        if logged_in_user:
            version.updated_by = logged_in_user
            from stalker.db.session import DBSession
            DBSession.commit()


def run_post_publishers():
    """runs post publishers if the current scene is a published version

    This is written to prevent users to save on top of a Published version and
    create a back door to skip un publishable scene to publish
    """
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()

    version = m_env.get_current_version()

    # check if we have a proper version
    if not version:
        return

    # check if it is a Representation
    from anima.representation import Representation
    if Representation.repr_separator in version.take_name:
        return

    if version.is_published:
        from anima.publish import (run_publishers, staging, PRE_PUBLISHER_TYPE,
                                   POST_PUBLISHER_TYPE)
        # before doing anything run all publishers
        type_name = ''
        if version.task.type:
            type_name = version.task.type.name

        # before running use the staging area to store the current version
        staging['version'] = version
        run_publishers(type_name, publisher_type=POST_PUBLISHER_TYPE)
        # do not forget to clean up the staging area
        staging.clear()


def get_default_render_layer():
    """Returns the default render layer
    :return:
    """
    return pm.ls(type='renderLayer')[0].defaultRenderLayer()


def switch_to_default_render_layer():
    """sets the current layer to defaultRenderLayer
    """
    try:
        default_render_layer = get_default_render_layer()
        current_layer = get_current_render_layer()
        if current_layer != default_render_layer:
            default_render_layer.setCurrent()
    except (NameError, RuntimeError):
        pass


def get_current_render_layer():
    """Returns the current render layer

    :return:
    """
    default_render_layer = get_default_render_layer()
    return default_render_layer.currentLayer()


def fix_external_paths():
    """fixes external paths in a maya scene
    """
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()
    if m_env.get_current_version():
        m_env.replace_external_paths()


def has_shape(node):
    """checks if the given node has at least one child that has a shape
    """
    allowed_shapes = (
        pm.nt.Mesh,
        pm.nt.NurbsCurve,
        pm.nt.NurbsSurface
    )

    has_it = False

    children = node.getChildren()
    while len(children) and not has_it:
        child = children.pop(0)
        if isinstance(child, allowed_shapes):
            has_it = True
            break
        children += child.getChildren()

    return has_it


def generate_thumbnail():
    """generates thumbnail for current scene
    """
    import tempfile
    import glob
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()
    v = m_env.get_current_version()

    if not v:
        return

    # do not generate a thumbnail from a Repr
    if '@' in v.take_name:
        return

    task = v.task
    project = task.project
    # repo = project.repository
    imf = project.image_format
    width = int(imf.width * 0.5)
    height = int(imf.height * 0.5)

    temp_output = tempfile.mktemp()

    current_frame = pm.currentTime(q=1)
    output_file = pm.playblast(
        fmt='image',
        startTime=current_frame,
        endTime=current_frame,
        sequenceTime=1,
        forceOverwrite=1,
        filename=temp_output,
        clearCache=1,
        showOrnaments=1,
        percent=100,
        wh=(width, height),
        offScreen=1,
        viewer=0,
        compression='PNG',
        quality=70,
        framePadding=0
    )
    pm.currentTime(current_frame)

    output_file = output_file.replace('####', '*')
    found_output_file = glob.glob(output_file)
    if found_output_file:
        output_file = found_output_file[0]

        from anima.ui import utils
        utils.upload_thumbnail(task, output_file)

    return found_output_file


def perform_playblast(action):
    """the patched version of the original perform playblast
    """
    # check if the current scene is a Stalker related version
    print('called anima.env.mayaEnv.auxiliary.perform_playblast(%s)' % action)

    # if not call the default playblast
    # if it is call out ShotPlayblaster
    from anima.env.mayaEnv import Maya
    m = Maya()
    v = m.get_current_version()

    if v:
        # do playblaster
        pb = Playblaster()
        extra_playblast_options = {
            'viewer': 1
        }

        # ask playblast format
        playblast_format = ask_playblast_format()
        if playblast_format == 'image':
            extra_playblast_options['fmt'] = 'image'
            extra_playblast_options['compression'] = 'png'

        # ask resolution
        resolution = ask_playblast_resolution()
        if resolution is None:
            return

        extra_playblast_options['percent'] = resolution

        outputs = pb.playblast(
            extra_playblast_options=extra_playblast_options
        )

        response = pm.confirmDialog(
            title='Upload To Server?',
            message='Upload To Server?',
            button=['Yes', 'No'],
            defaultButton='No',
            cancelButton='No',
            dismissString='No'
        )
        if response == 'Yes':
            for output in outputs:
                pb.upload_output(pb.version, output)

    else:
        # call the original playblast
        return pm.mel.eval('performPlayblast_orig(%s);' % action)


def set_range_from_shot(shot):
    """sets the playback range from a shot node in the scene

    :param shot: Maya Shot
    """
    min_frame = shot.getAttr('startFrame')
    max_frame = shot.getAttr('endFrame')

    pm.playbackOptions(
        ast=min_frame,
        aet=max_frame,
        min=min_frame,
        max=max_frame
    )


def ask_playblast_resolution():
    """Asks the user the playblast resolution
    """
    # ask resolution
    response = pm.confirmDialog(
        title='Resolution?',
        message='Resolution?',
        button=['Default', 'Full', 'Half', 'Quarter', 'Cancel'],
        defaultButton='Default',
        cancelButton='Default',
        dismissString='Default'
    )
    if response == 'Default':
        return 50
    elif response == 'Full':
        return 100
    elif response == 'Half':
        return 50
    elif response == 'Quarter':
        return 25
    elif response == 'Cancel':
        return None

    return 50


def ask_playblast_format():
    """asks the user the playblast format
    """
    # ask resolution
    response = pm.confirmDialog(
        title='Format?',
        message='Format?',
        button=['QuickTime', 'PNG'],
        defaultButton='QuickTime',
        cancelButton='QuickTime',
        dismissString='QuickTime'
    )

    if response == 'QuickTime':
        return 'qt'
    elif response == 'PNG':
        return 'image'


def perform_playblast_shot(shot_name):
    """Performs shot playblast, this is written to replace the menu action in
    Camera Sequencer.

    :param shot_name: Shot name
    :return:
    """

    response = pm.confirmDialog(
        title='Perform Playblast?',
        message='Perform Playblast?',
        button=['Yes', 'No'],
        defaultButton='No',
        cancelButton='No',
        dismissString='No'
    )
    if response == 'No':
        return

    # ask resolution
    resolution = ask_playblast_resolution()
    if resolution is None:
        return

    extra_playblast_options = {
        'viewer': 1,
        'percent': resolution
    }

    shot = pm.PyNode(shot_name)

    pb = Playblaster()
    video_file_output = pb.playblast_shot(
        shot,
        extra_playblast_options=extra_playblast_options
    )

    response = pm.confirmDialog(
        title='Upload To Server?',
        message='Upload To Server?',
        button=['Yes', 'No'],
        defaultButton='No',
        cancelButton='No',
        dismissString='No'
    )
    if response == 'Yes':
        pb.upload_output(pb.version, video_file_output)


class Playblaster(object):
    """Generates playblasts.
    If there are shots in the current scene then it generates playblasts for
    each of them and uploads it to the server
    """

    display_flags = [
        'nurbsCurves', 'nurbsSurfaces', 'cv', 'hulls', 'polymeshes',
        'subdivSurfaces', 'planes', 'lights', 'cameras', 'imagePlane',
        'joints', 'ikHandles', 'deformers', 'dynamics', 'particleInstancers',
        'fluids', 'hairSystems', 'follicles', 'nCloths', 'nParticles', 'nRigids',
        'dynamicConstraints', 'locators', 'dimensions', 'pivots',
        'handles', 'textures', 'strokes', 'motionTrails', 'pluginShapes',
        'clipGhosts', 'greasePencils', 'manipulators', 'grid',
    ]

    cam_attribute_names = [
        'overscan',
        'filmFit',
        'displayFilmGate',
        'displayResolution',
        'displayGateMask',
        'displayFieldChart',
        'displaySafeAction',
        'displaySafeTitle',
        'displayFilmPivot',
        'displayFilmOrigin'
    ]

    global_playblast_options = {
        # 'fmt': 'image',
        'fmt': 'qt',
        'forceOverwrite': 1,
        'clearCache': 1,
        'showOrnaments': 1,
        'percent': 100,
        'offScreen': 1,
        'viewer': 0,
        # 'compression': 'png',
        'compression': 'MPEG-4 Video',
        'quality': 85,
        'sequenceTime': 1
    }

    hud_name = 'PlayblasterHUD'

    def __init__(self):
        from stalker import LocalSession
        local_session = LocalSession()
        self.logged_in_user = local_session.logged_in_user

        if not self.logged_in_user:
            raise RuntimeError('Please login first!')

        self.version = None
        from anima.env.mayaEnv import Maya
        self.m_env = Maya()
        self.version = self.m_env.get_current_version()

        self.ui_window_name = 'playblaster_window'
        self.ui_window = None

        self.user_view_options = {}
        self.reset_user_view_options_storage()

        self.batch_mode = False

    def check_sequence_name(self):
        """checks sequence name and asks the user to set one if maya is in UI
        mode and there is no sequence name set
        """
        sequencer = pm.ls(type='sequencer')[0]
        sequence_name = sequencer.getAttr('sequence_name')
        if sequence_name == '' and not pm.general.about(batch=1) \
           and not self.batch_mode:
            result = pm.promptDialog(
                title='Please enter a Sequence Name',
                message='Sequence Name:',
                button=['OK', 'Cancel'],
                defaultButton='OK',
                cancelButton='Cancel',
                dismissString='Cancel'
            )

            if result == 'OK':
                sequencer.setAttr(
                    'sequence_name',
                    pm.promptDialog(query=True, text=True)
                )

    def get_hud_data(self):
        """
        """
        # try to get the shot from sequencer
        current_shot = pm.sequenceManager(q=1, currentShot=1)

        if current_shot:
            shot_name = pm.getAttr('%s.shotName' % current_shot)
            current_cam_name = pm.shot(current_shot, q=1, cc=1)
            current_cam = pm.PyNode(current_cam_name)
        else:
            # then try to get the shot name from the file name
            import os
            shot_name = os.path.split(pm.sceneName())[1].split('_')[0]
            # use the active panel camera
            active_panel = self.get_active_panel()
            current_cam = pm.modelEditor(active_panel, q=1, cam=1)
            current_cam_name = current_cam.name()

        if isinstance(current_cam, pm.nt.Transform):
            current_cam = current_cam.getShape()

        focal_length = current_cam.getAttr('focalLength')

        sequencers = pm.ls(type='sequencer')
        if sequencers:
            sequencer = sequencers[0]
            if sequencer.getAttr('sequence_name') != '':
                shot_info = sequencer.getAttr('sequence_name')
            else:
                shot_info = 'INVALID'
        else:
            shot_info = shot_name

        cf = pm.currentTime(q=1) + 1

        import timecode
        # TODO: This should use project frame rate
        tc = timecode.Timecode('24', frames=cf)

        if current_shot:
            start_time = pm.shot(current_shot, q=1, st=1)
            end_time = pm.shot(current_shot, q=1, et=1)
        else:
            # no shot node use the current playback range
            start_time = pm.playbackOptions(q=1, min=1)
            end_time = pm.playbackOptions(q=1, max=1)

        cs_frame = int(cf - start_time)

        length = int(end_time - start_time) + 1

        if self.version:
            user_name = self.version.updated_by.name
        else:
            # get the user name from the login info
            if self.logged_in_user:
                user_name = self.logged_in_user.name
            else:
                # ok try to use the filename
                user_name = pm.sceneName().split('_')[-1]<commit_msg>* [Maya] Fixed HUD data to correctly read the ``Project.fps`` value instead of using 24 by default.<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2012-2018, <NAME>
#
# This module is part of anima-tools and is released under the BSD 2
# License: http://www.opensource.org/licenses/BSD-2-Clause

import pymel.core as pm

import re
FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)')
ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')


def kill_all_torn_off_panels():
    """deletes all torn off panels
    """
    panel_list = pm.getPanel(type='modelPanel')

    # remove all torn off panels
    for panel in panel_list:
        if panel.getTearOff():
            panel.delete(pnl=1)


def maximize_first_model_panel():
    """maximizes the first model panel it can find

    :return:
    """
    panel_list = pm.getPanel(type='modelPanel')
    if len(panel_list) == 0:
        return

    # maximize one panel in default layout
    g_main_pane = pm.melGlobals['gMainPane']

    pane_config = pm.paneLayout(g_main_pane, q=1, configuration=1)
    if pane_config != 'single':
        # call mel here
        pm.mel.eval('doSwitchPanes(1, { "single", "%s"})' % panel_list[0])
        pm.mel.eval('updateToolbox();')


def get_valid_dag_node(node):
    """returns a valid dag node even the input is string
    """
    try:
        dag_node = pm.nodetypes.DagNode(node)
    except pm.MayaNodeError:
        print('Error: no node named : %s' % node)
        return None

    return dag_node


def get_valid_node(node):
    """returns a valid PyNode even the input is string
    """
    try:
        PyNode = pm.PyNode(node)
    except pm.MayaNodeError:
        print('Error: no node named : %s' % node)
        return None

    return PyNode


def get_anim_curves(node):
    """returns all the animation curves connected to the
    given node
    """
    # list all connections to the node
    connected_nodes = pm.listConnections(node)

    anim_curve = "animCurve"

    return_list = []
    for cNode in connected_nodes:
        if pm.nodeType(cNode)[0:len(anim_curve)] == anim_curve:
            return_list.append(cNode)

    return return_list


def set_anim_curve_color(anim_curve, color):
    """sets animCurve color to color
    """
    anim_curve = get_valid_node(anim_curve)
    anim_curve.setAttr("useCurveColor", True)
    anim_curve.setAttr("curveColor", color, type="double3")


def axial_correction_group(obj,
                           to_parents_origin=False,
                           name_prefix="",
                           name_postfix="_ACGroup#"):
    """creates a new parent to zero out the transformations

    if to_parents_origin is set to True, it doesn't zero outs the
    transformations but creates a new parent at the same place of the original
    parent

    :returns: pymel.core.nodeTypes.Transform
    """
    obj = get_valid_dag_node(obj)

    if name_postfix == "":
        name_postfix = "_ACGroup#"

    ac_group = pm.group(
        em=True,
        n=(name_prefix + obj.name() + name_postfix)
    )

    ac_group = pm.parent(ac_group, obj)[0]

    pm.setAttr(ac_group + ".t", [0, 0, 0])
    pm.setAttr(ac_group + ".r", [0, 0, 0])
    pm.setAttr(ac_group + ".s", [1, 1, 1])

    parent = pm.listRelatives(obj, p=True)
    if len(parent) != 0:
        pm.parent(ac_group, parent[0], a=True)
    else:
        pm.parent(ac_group, w=True)

    if to_parents_origin:
        pm.setAttr(ac_group + ".t", [0, 0, 0])
        pm.setAttr(ac_group + ".r", [0, 0, 0])
        pm.setAttr(ac_group + ".s", [1, 1, 1])

    pm.parent(obj, ac_group, a=True)

    # for joints also set the joint orient to zero
    if isinstance(obj, pm.nodetypes.Joint):
        # set the joint rotation and joint orient to zero
        obj.setAttr('r', (0, 0, 0))
        obj.setAttr('jo', (0, 0, 0))

    return ac_group


def go_home(node):
    """sets all the transformations to zero
    """
    if node.attr('t').isSettable():
        node.setAttr('t', (0, 0, 0))
    if node.attr('r').isSettable():
        node.setAttr('r', (0, 0, 0))
    if node.attr('s').isSettable():
        node.setAttr('s', (1, 1, 1))


def rivet():
    """the python version of the famous rivet setup from Bazhutkin
    """
    selection_list = pm.filterExpand(sm=32)

    if selection_list is not None and len(selection_list) > 0:
        size = len(selection_list)
        if size != 2:
            raise pm.MayaObjectError('No two edges selected')

        edge1 = pm.PyNode(selection_list[0])
        edge2 = pm.PyNode(selection_list[1])

        edge1Index = edge1.indices()[0]
        edge2Index = edge2.indices()[0]

        shape = edge1.node()

        cFME1 = pm.createNode('curveFromMeshEdge', n='rivetCurveFromMeshEdge#')
        cFME1.setAttr('ihi', 1)
        cFME1.setAttr('ei[0]', edge1Index)

        cFME2 = pm.createNode('curveFromMeshEdge', n='rivetCurveFromMeshEdge#')
        cFME2.setAttr('ihi', 1)
        cFME2.setAttr('ei[0]', edge2Index)

        loft = pm.createNode('loft', n='rivetLoft#')
        loft.setAttr('ic', s=2)
        loft.setAttr('u', 1)
        loft.setAttr('rsn', 1)

        pOSI = pm.createNode('pointOnSurfaceInfo',
                             n='rivetPointOnSurfaceInfo#')
        pOSI.setAttr('turnOnPercentage', 1)
        pOSI.setAttr('parameterU', 0.5)
        pOSI.setAttr('parameterV', 0.5)

        loft.attr('os') >> pOSI.attr('is')
        cFME1.attr('oc') >> loft.attr('ic[0]')
        cFME2.attr('oc') >> loft.attr('ic[1]')
        shape.attr('w') >> cFME1.attr('im')
        shape.attr('w') >> cFME2.attr('im')
    else:
        selection_list = pm.filterExpand(sm=41)

        if selection_list is not None and len(selection_list) > 0:
            size = len(selection_list)
            if size != 1:
                raise pm.MayaObjectError('No one point selected')

            point = pm.PyNode(selection_list[0])
            shape = point.node()
            u = float(point.name().split('][')[0].split('[')[1])
            v = float(point.name().split('][')[1].split(']')[0])

            pOSI = pm.createNode('pointOnSurfaceInfo',
                                 n='rivetPointOnSurfaceInfo#')
            pOSI.setAttr('turnOnPercentage', 0)
            pOSI.setAttr('parameterU', u)
            pOSI.setAttr('parameterV', v)
            shape.attr('ws') >> pOSI.attr('is')
        else:
            raise pm.MayaObjectError('No edges or point selected')

    locator = pm.spaceLocator(n='rivet#')
    aimCons = pm.createNode('aimConstraint',
                            p=locator,
                            n=locator.name() + '_rivetAimConstraint#')
    aimCons.setAttr('tg[0].tw', 1)
    aimCons.setAttr('a', (0, 1, 0))
    aimCons.setAttr('u', (0, 0, 1))
    aimCons.setAttr('v', k=0)
    aimCons.setAttr('tx', k=0)
    aimCons.setAttr('ty', k=0)
    aimCons.setAttr('tz', k=0)
    aimCons.setAttr('rx', k=0)
    aimCons.setAttr('ry', k=0)
    aimCons.setAttr('rz', k=0)
    aimCons.setAttr('sx', k=0)
    aimCons.setAttr('sy', k=0)
    aimCons.setAttr('sz', k=0)

    pOSI.attr('position') >> locator.attr('translate')
    pOSI.attr('n') >> aimCons.attr('tg[0].tt')
    pOSI.attr('tv') >> aimCons.attr('wu')
    aimCons.attr('crx') >> locator.attr('rx')
    aimCons.attr('cry') >> locator.attr('ry')
    aimCons.attr('crz') >> locator.attr('rz')

    pm.select(locator)
    return locator


def auto_rivet():
    """creates hair follicles around selection
    """
    sel_list = pm.ls(sl=1)

    # the last selection is the mesh
    objects = sel_list[:-1]
    geo = sel_list[-1]

    # get the closest point to the surface
    geo_shape = geo.getShape()

    follicles = []

    for obj in objects:
        # pivot point of the obj
        pivot = obj.getRotatePivot(space='world')
        uv = geo_shape.getUVAtPoint(pivot, space='world')

        # create a hair follicle
        follicle = pm.nt.Follicle()
        follicles.append(follicle)
        follicle.simulationMethod.set(0)
        geo_shape.worldMatrix >> follicle.inputWorldMatrix
        geo_shape.outMesh >> follicle.inputMesh
        follicle.parameterU.set(uv[0])
        follicle.parameterV.set(uv[1])

        # parent the object to the follicles transform node
        follicle_transform = follicle.getParent()

        follicle.outTranslate >> follicle_transform.translate
        follicle.outRotate >> follicle_transform.rotate

        pm.parent(obj, follicle_transform)

    return follicles


def hair_from_curves():
    """creates hairs from curves
    """
    selection_list = pm.ls(sl=1)

    curves = []
    curve_shapes = []

    mesh = ""
    mesh_shape = ""

    for i in range(0, len(selection_list)):
        shapes = pm.listRelatives(selection_list[i], s=True)
        node_type = pm.nodeType(shapes[0])

        if node_type == 'nurbsCurve':
            curves.append(selection_list[i])
            curve_shapes.append(shapes[0])
        elif node_type == 'mesh':
            mesh = selection_list[i]
            mesh_shape = shapes[0]

    do_output_curve = 1
    hide_output_curve = 0

    #create hair
    hair_system = pm.createNode('hairSystem')
    pm.connectAttr('time1.outTime', (hair_system + '.currentTime'))

    hair_system_group = ""
    hair_system_out_hair_group = ""
    hair_system_parent = pm.listTransforms(hair_system)
    if len(hair_system_parent) > 0:
        hair_system_group = hair_system_parent[0] + "Follicles"
        if not pm.objExists(hair_system_group):
            hair_system_group = pm.group(em=1, name='hsysGroup')
        if do_output_curve:
            hair_system_out_hair_group = hair_system_parent[0] + "OutputCurves"
            if not pm.objExists(hair_system_out_hair_group):
                hair_system_out_hair_group = \
                    pm.group(em=1, name='hsysOutHairGroup')
            if hide_output_curve:
                pm.setAttr(hair_system_out_hair_group + '.visibility', False)

    #create closestPointOnMesh to read the closest point parameter
    cpom = pm.createNode('closestPointOnMesh')
    pm.connectAttr((mesh_shape + '.worldMesh[0]'), (cpom + '.inMesh'))

    num_of_curves = len(curves)

    for i in range(0, num_of_curves):
        dup_name = pm.duplicate(curves[i])
        dup_shape = pm.listRelatives(dup_name[0], s=True)

        first_cv_tra = pm.xform(q=True, ws=True, t=(curves[i] + '.cv[0'))

        pm.setAttr(
            (cpom + '.ip'),
            (first_cv_tra[0], first_cv_tra[1], first_cv_tra[2]),
            type="double3"
        )

        pu = pm.getAttr(cpom + '.r.u')
        pv = pm.getAttr(cpom + '.r.v')

        hair_curve_name_prefix = mesh + "Follicle"
        naming_index = \
            num_of_curves * int(pu * float(num_of_curves - 1) + 0.5) + \
            int(pv * float(num_of_curves - 1) + 0.5)

        new_name = hair_curve_name_prefix + str(naming_index)

        #create follicle
        hair = pm.createNode('follicle')
        pm.setAttr(pu, hair + '.parameterU')
        pm.setAttr(pv, hair + '.parameterV')

        pm.connectAttr((curve_shapes[i] + '.worldSpace[0]'), (hair + '.sp'))

        transforms = pm.listTransforms(hair)
        hair_dag = transforms[0]

        pm.connectAttr((mesh_shape + '.worldMatrix[0]'), (hair + '.inputWorldMatrix'))

        pm.connectAttr((mesh_shape + '.outMesh'), (hair + '.inputMesh'))
        current_uv_set = pm.polyUVSet(q=True, currentUVSet=mesh_shape)
        pm.setAttr(current_uv_set[0], (hair + '.mapSetName'), type="string")

        pm.connectAttr((hair + '.outTranslate'), (hair_dag + '.translate'))
        pm.connectAttr((hair + '.outRotate'), (hair_dag + '.rotate'))
        pm.setAttr((hair_dag + '.translate'), lock=True)
        pm.setAttr((hair_dag + '.rotate'), lock=True)

        pm.setAttr(hair + '.degree', 3)
        pm.setAttr(hair + '.startDirection', 1)
        pm.setAttr(hair + '.restPose', 3)

        pm.parent(hair_system_group, relative=hair_dag)

        pm.parent(hair_dag, absolute=curves[i])

        pm.setAttr(hair + '.simulationMethod', 2)

        #initHairCurveDisplay(curves[i], "start")

        hair_index = i
        pm.connectAttr(
            (hair + '.outHair'),
            (hair_system + '.inputHair[%f]' % hair_index)
        )
        pm.connectAttr(
            (hair_system + '.inputHair[%f]' % hair_index),
            (hair + '.currentPosition')
        )

        crv = dup_shape[0]
        pm.connectAttr((hair + '.outCurve'), (crv + '.create'))
        #initHairCurveDisplay(crv, "current")

        transforms = pm.listTransforms(crv)
        pm.parent(transforms[0], hair_system_out_hair_group, r=True)

        pm.rename(hair_dag, new_name)

    pm.select(hair_system, r=True)

    import maya.mel as mel
    mel.eval('displayHairCurves("current", true')

    pm.delete(cpom)


def align_to_pole_vector():
    """aligns the object to the pole vector of the selected ikHandle
    """
    selection_list = pm.ls(sl=1)

    ik_handle = ""
    control_object = ""

    for obj in selection_list:
        if pm.nodeType(obj) == 'ikHandle':
            ik_handle = obj
        else:
            control_object = obj

    temp = pm.listConnections((ik_handle + '.startJoint'), s=1)
    start_joint = temp[0]
    start_joint_pos = pm.xform(start_joint, q=True, ws=True, t=True)

    temp = pm.listConnections((ik_handle + '.endEffector'), s=1)
    end_effector = temp[0]
    pm.xform(
        control_object,
        ws=True,
        t=(start_joint_pos[0], start_joint_pos[1], start_joint_pos[2])
    )

    pm.parent(control_object, end_effector)
    pm.setAttr(control_object + '.r', 0, 0, 0)

    pm.parent(control_object, w=True)


def export_blend_connections():
    """Exports the connection commands from selected objects to the blendShape
    of another object. The resulted text file contains all the MEL scripts to
    reconnect the objects to the blendShape node. So after exporting the
    connection commands you can export the blendShape targets as another maya
    file and delete them from the scene, thus your scene gets lite and loads
    much more quickly.
    """
    selection_list = pm.ls(tr=1, sl=1, l=1)

    dialog_return = pm.fileDialog2(cap="Save As", fm=0, ff='Text Files(*.txt)')

    filename = dialog_return[0]
    print(filename)

    print("\n\nFiles written:\n--------------------------------------------\n")

    with open(filename, 'w') as fileId:
        for i in range(0, len(selection_list)):
            shapes = pm.listRelatives(selection_list[i], s=True, f=True)

            main_shape = ""
            for j in range(0, len(shapes)):
                if pm.getAttr(shapes[j] + '.intermediateObject') == 0:
                    main_shape = shapes
                    break
            if main_shape == "":
                main_shape = shapes[0]

            con = pm.listConnections(main_shape, t="blendShape", c=1, s=1, p=1)

            cmd = "connectAttr -f %s.worldMesh[0] %s;" % (
                ''.join(map(str, main_shape)),
                ''.join(map(str, con[0].name()))
            )
            print (cmd + "\n")
            fileId.write("%s\n" % cmd)

    print("\n------------------------------------------------------\n")
    print("filename: %s     ...done\n" % filename)


def transfer_shaders(source, target):
    """transfers shader from source to target
    """
    if isinstance(source, pm.nt.Transform):
        source_shape = source.getShape()
    else:
        source_shape = source

    # get the shadingEngines
    shading_engines = source_shape.outputs(type=pm.nt.ShadingEngine)

    if len(shading_engines):
        pm.sets(shading_engines[0], fe=target)
        # also assign instances to the same shader
        if target.instanceCount() > 1:
            for i in range(1, target.instanceCount()):
                target.attr('instObjGroups[%s]' % i).disconnect()
                target.attr('instObjGroups[%s]' % i) >> \
                shading_engines[0].attr('dagSetMembers').next_available


def benchmark(iter_cnt):
    """benchmarks playback rate

    :param iter_cnt: Count of iteration
    """
    import time

    start = pm.playbackOptions(q=1, min=1)
    stop = pm.playbackOptions(q=1, max=1)

    start_time = time.time()

    for j in range(iter_cnt):
        for i in range(start, stop + 1):
            pm.currentTime(e=i)

    total_time = time.time() - start_time
    print("------------------------------")
    print("BenchmarkTime : %s" % total_time)
    print("Total iterCnt : %s" % iter_cnt)
    print("Average FPS   : %s" % ((stop - start) * iter_cnt / total_time))


def load_shelf_tab(shelf_path):
    """loads the given shelf tab
    """
    # look in to the shelf mel file from user folders
    import os
    if os.path.exists(shelf_path):
        try:
            pm.mel.eval('loadNewShelf "%s"' % shelf_path)
        except Exception:
            # probably not in GUI mode
            return


def delete_shelf_tab(shelf_name, confirm=True):
    """The python version of the original mel script of Maya
    :param shelf_name: The name of the shelf to delete
    """
    try:
        shelf_top_level_path = pm.melGlobals['gShelfTopLevel']
    except KeyError:
        # not in GUI mode
        return

    shelf_top_level = pm.windows.tabLayout(shelf_top_level_path, e=1)

    if len(shelf_top_level.children()) < 0:
        return

    if confirm:
        # before doing anything ask it
        response = pm.confirmDialog(
            title='Delete Shelf?',
            message='Delete %s?' % shelf_name,
            button=['Yes', 'No'],
            defaultButton='No',
            cancelButton='No',
            dismissString='No'
        )
        if response == 'No':
            return

    # update the preferences
    shelf_number = -1
    number_of_shelves = pm.optionVar['numShelves']
    for i in range(1, number_of_shelves + 1):
        if pm.optionVar['shelfName%s' % i] == shelf_name:
            shelf_number = i
            break

    if shelf_number == -1:
        # there should be no shelf with this name
        return

    # offset shelves
    for i in range(shelf_number, number_of_shelves):
        pm.optionVar['shelfLoad%s' % i] = pm.optionVar['shelfLoad%s' % (i + 1)]
        pm.optionVar['shelfName%s' % i] = pm.optionVar['shelfName%s' % (i + 1)]
        pm.optionVar['shelfFile%s' % i] = pm.optionVar['shelfFile%s' % (i + 1)]

    pm.optionVar.pop('shelfLoad%s' % number_of_shelves)
    number_of_shelves -= 1
    pm.optionVar['numShelves'] = number_of_shelves

    pm.windows.deleteUI('%s|%s' % (shelf_top_level_path, shelf_name), layout=1)

    # remove the shelf mel file from user folders
    import os
    for path in pm.internalVar(userShelfDir=1).split(os.path.pathsep):
        shelf_file_name = 'shelf_%s.mel' % shelf_name
        shelf_file_full_path = os.path.join(path, shelf_file_name)

        deleted_file_name = '%s.deleted' % shelf_file_name
        deleted_file_full_path = os.path.join(path, deleted_file_name)

        try:
            os.remove(deleted_file_full_path)
        except OSError:
            pass

        try:
            os.remove(shelf_file_full_path)
            break
        except OSError:
            pass

    # Make sure the new active shelf tab has buttons
    pm.mel.eval('shelfTabChange();')


def cube_from_bbox(bbox):
    """creates a polyCube from the given bbox

    :param bbox: pymel.core.dt.BoundingBox instance
    """
    cube = pm.polyCube(
        width=bbox.width(),
        height=bbox.height(),
        depth=bbox.depth(),
        ch=False
    )
    cube[0].setAttr('t', bbox.center())
    return cube[0]


def create_bbox(nodes, per_selection=False):
    """creates bounding boxes for the selected objects

    :param bool per_selection: If True will create a BBox for each
      given object
    """

    if per_selection:
        for node in nodes:
            return cube_from_bbox(node.boundingBox())
    else:
        bbox = pm.dt.BoundingBox()
        for node in nodes:
            bbox.expand(node.boundingBox().min())
            bbox.expand(node.boundingBox().max())
        return cube_from_bbox(bbox)


def replace_with_bbox(nodes):
    """replaces the given nodes with a bbox object
    """
    node_names = []
    bboxes = []
    processed_nodes = []
    for node in nodes:
        # create a bbox and parent it to the parent of
        # the original node

        # check if it is a transform node
        if not isinstance(node, pm.nt.Transform):
            continue

        # check the shape
        # check if it has at least one shape under it
        if not has_shape(node):
            continue

        bbox = cube_from_bbox(node.boundingBox())
        bbox.setParent(node.getParent())

        # set pivots
        rp = pm.xform(node, q=1, ws=1, rp=1)
        sp = pm.xform(node, q=1, ws=1, sp=1)
        pm.xform(bbox, ws=1, rp=rp)
        pm.xform(bbox, ws=1, sp=sp)

        node_name = node.name()
        node_shape = node.getShape()
        node_shape_name = None
        if node_shape is not None:
            node_shape_name = node_shape.name()

        node_names.append((node_name, node_shape_name))
        bboxes.append(bbox)
        processed_nodes.append(node)

    # delete the nodes
    if len(processed_nodes):
        pm.delete(processed_nodes)

        # rename the bboxes
        for name, bbox in zip(node_names, bboxes):
            bbox.rename(name[0])
            if name[1]:
                bbox.getShape().rename(name[1])

    return bboxes


def get_root_nodes():
    """returns the root DAG nodes
    """
    root_transform_nodes = []
    for node in pm.ls(dag=1, transforms=1):
        if node.getParent() is None:
            shape = node.getShape()
            if shape:
                if shape.type() not in ['camera']:
                    root_transform_nodes.append(node)
            else:
                root_transform_nodes.append(node)

    return root_transform_nodes


def create_arnold_stand_in(path=None):
    """A fixed version of original arnold script of SolidAngle Arnold core API
    """
    if not pm.objExists('ArnoldStandInDefaultLightSet'):
        pm.createNode(
            "objectSet",
            name="ArnoldStandInDefaultLightSet",
            shared=True
        )
        pm.lightlink(
            object='ArnoldStandInDefaultLightSet',
            light='defaultLightSet'
        )

    stand_in = pm.createNode('aiStandIn', n='ArnoldStandInShape')
    # temp fix until we can correct in c++ plugin
    stand_in.setAttr('visibleInReflections', True)
    stand_in.setAttr('visibleInRefractions', True)

    pm.sets('ArnoldStandInDefaultLightSet', add=stand_in)
    if path:
        stand_in.setAttr('dso', path)

    return stand_in


def create_rs_proxy_node(path=None):
    """Creates Redshift Proxies showing a proxy object
    """
    proxy_mesh_node = pm.createNode('RedshiftProxyMesh')
    proxy_mesh_node.fileName.set(path)
    proxy_mesh_shape = pm.createNode('mesh')
    proxy_mesh_node.outMesh >> proxy_mesh_shape.inMesh

    # assign default material
    pm.sets('initialShadingGroup', fe=proxy_mesh_shape)

    return proxy_mesh_node, proxy_mesh_shape


def run_pre_publishers():
    """runs pre publishers if the current scene is a published version

    This is written to prevent users to save on top of a Published version and
    create a back door to skip un publishable scene to publish
    """
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()

    version = m_env.get_current_version()

    # check if we have a proper version
    if not version:
        return

    # check if it is a Representation
    from anima.representation import Representation
    if Representation.repr_separator in version.take_name:
        return

    if version.is_published:
        from anima.publish import (run_publishers, staging, PRE_PUBLISHER_TYPE,
                                   POST_PUBLISHER_TYPE)
        # before doing anything run all publishers
        type_name = ''
        if version.task.type:
            type_name = version.task.type.name

        # before running use the staging area to store the current version
        staging['version'] = version
        run_publishers(type_name)
        # do not forget to clean up the staging area
        staging.clear()
    else:
        # run some of the publishers
        from anima.env.mayaEnv.publish import PublishError
        from anima.env.mayaEnv import publish as publish_scripts
        try:
            publish_scripts.check_node_names_with_bad_characters()
        except PublishError as e:
            # pop up a message box with the error
            pm.confirmDialog(
                title='SaveError',
                message=str(''.join([i for i in unicode(e) if ord(i) < 128])),
                button=['Ok']
            )
            raise e

        # update updated_by field of the current version
        from stalker import LocalSession
        ls = LocalSession()
        logged_in_user = ls.logged_in_user
        if logged_in_user:
            version.updated_by = logged_in_user
            from stalker.db.session import DBSession
            DBSession.commit()


def run_post_publishers():
    """runs post publishers if the current scene is a published version

    This is written to prevent users to save on top of a Published version and
    create a back door to skip un publishable scene to publish
    """
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()

    version = m_env.get_current_version()

    # check if we have a proper version
    if not version:
        return

    # check if it is a Representation
    from anima.representation import Representation
    if Representation.repr_separator in version.take_name:
        return

    if version.is_published:
        from anima.publish import (run_publishers, staging, PRE_PUBLISHER_TYPE,
                                   POST_PUBLISHER_TYPE)
        # before doing anything run all publishers
        type_name = ''
        if version.task.type:
            type_name = version.task.type.name

        # before running use the staging area to store the current version
        staging['version'] = version
        run_publishers(type_name, publisher_type=POST_PUBLISHER_TYPE)
        # do not forget to clean up the staging area
        staging.clear()


def get_default_render_layer():
    """Returns the default render layer
    :return:
    """
    return pm.ls(type='renderLayer')[0].defaultRenderLayer()


def switch_to_default_render_layer():
    """sets the current layer to defaultRenderLayer
    """
    try:
        default_render_layer = get_default_render_layer()
        current_layer = get_current_render_layer()
        if current_layer != default_render_layer:
            default_render_layer.setCurrent()
    except (NameError, RuntimeError):
        pass


def get_current_render_layer():
    """Returns the current render layer

    :return:
    """
    default_render_layer = get_default_render_layer()
    return default_render_layer.currentLayer()


def fix_external_paths():
    """fixes external paths in a maya scene
    """
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()
    if m_env.get_current_version():
        m_env.replace_external_paths()


def has_shape(node):
    """checks if the given node has at least one child that has a shape
    """
    allowed_shapes = (
        pm.nt.Mesh,
        pm.nt.NurbsCurve,
        pm.nt.NurbsSurface
    )

    has_it = False

    children = node.getChildren()
    while len(children) and not has_it:
        child = children.pop(0)
        if isinstance(child, allowed_shapes):
            has_it = True
            break
        children += child.getChildren()

    return has_it


def generate_thumbnail():
    """generates thumbnail for current scene
    """
    import tempfile
    import glob
    from anima.env import mayaEnv
    m_env = mayaEnv.Maya()
    v = m_env.get_current_version()

    if not v:
        return

    # do not generate a thumbnail from a Repr
    if '@' in v.take_name:
        return

    task = v.task
    project = task.project
    # repo = project.repository
    imf = project.image_format
    width = int(imf.width * 0.5)
    height = int(imf.height * 0.5)

    temp_output = tempfile.mktemp()

    current_frame = pm.currentTime(q=1)
    output_file = pm.playblast(
        fmt='image',
        startTime=current_frame,
        endTime=current_frame,
        sequenceTime=1,
        forceOverwrite=1,
        filename=temp_output,
        clearCache=1,
        showOrnaments=1,
        percent=100,
        wh=(width, height),
        offScreen=1,
        viewer=0,
        compression='PNG',
        quality=70,
        framePadding=0
    )
    pm.currentTime(current_frame)

    output_file = output_file.replace('####', '*')
    found_output_file = glob.glob(output_file)
    if found_output_file:
        output_file = found_output_file[0]

        from anima.ui import utils
        utils.upload_thumbnail(task, output_file)

    return found_output_file


def perform_playblast(action):
    """the patched version of the original perform playblast
    """
    # check if the current scene is a Stalker related version
    print('called anima.env.mayaEnv.auxiliary.perform_playblast(%s)' % action)

    # if not call the default playblast
    # if it is call out ShotPlayblaster
    from anima.env.mayaEnv import Maya
    m = Maya()
    v = m.get_current_version()

    if v:
        # do playblaster
        pb = Playblaster()
        extra_playblast_options = {
            'viewer': 1
        }

        # ask playblast format
        playblast_format = ask_playblast_format()
        if playblast_format == 'image':
            extra_playblast_options['fmt'] = 'image'
            extra_playblast_options['compression'] = 'png'

        # ask resolution
        resolution = ask_playblast_resolution()
        if resolution is None:
            return

        extra_playblast_options['percent'] = resolution

        outputs = pb.playblast(
            extra_playblast_options=extra_playblast_options
        )

        response = pm.confirmDialog(
            title='Upload To Server?',
            message='Upload To Server?',
            button=['Yes', 'No'],
            defaultButton='No',
            cancelButton='No',
            dismissString='No'
        )
        if response == 'Yes':
            for output in outputs:
                pb.upload_output(pb.version, output)

    else:
        # call the original playblast
        return pm.mel.eval('performPlayblast_orig(%s);' % action)


def set_range_from_shot(shot):
    """sets the playback range from a shot node in the scene

    :param shot: Maya Shot
    """
    min_frame = shot.getAttr('startFrame')
    max_frame = shot.getAttr('endFrame')

    pm.playbackOptions(
        ast=min_frame,
        aet=max_frame,
        min=min_frame,
        max=max_frame
    )


def ask_playblast_resolution():
    """Asks the user the playblast resolution
    """
    # ask resolution
    response = pm.confirmDialog(
        title='Resolution?',
        message='Resolution?',
        button=['Default', 'Full', 'Half', 'Quarter', 'Cancel'],
        defaultButton='Default',
        cancelButton='Default',
        dismissString='Default'
    )
    if response == 'Default':
        return 50
    elif response == 'Full':
        return 100
    elif response == 'Half':
        return 50
    elif response == 'Quarter':
        return 25
    elif response == 'Cancel':
        return None

    return 50


def ask_playblast_format():
    """asks the user the playblast format
    """
    # ask resolution
    response = pm.confirmDialog(
        title='Format?',
        message='Format?',
        button=['QuickTime', 'PNG'],
        defaultButton='QuickTime',
        cancelButton='QuickTime',
        dismissString='QuickTime'
    )

    if response == 'QuickTime':
        return 'qt'
    elif response == 'PNG':
        return 'image'


def perform_playblast_shot(shot_name):
    """Performs shot playblast, this is written to replace the menu action in
    Camera Sequencer.

    :param shot_name: Shot name
    :return:
    """

    response = pm.confirmDialog(
        title='Perform Playblast?',
        message='Perform Playblast?',
        button=['Yes', 'No'],
        defaultButton='No',
        cancelButton='No',
        dismissString='No'
    )
    if response == 'No':
        return

    # ask resolution
    resolution = ask_playblast_resolution()
    if resolution is None:
        return

    extra_playblast_options = {
        'viewer': 1,
        'percent': resolution
    }

    shot = pm.PyNode(shot_name)

    pb = Playblaster()
    video_file_output = pb.playblast_shot(
        shot,
        extra_playblast_options=extra_playblast_options
    )

    response = pm.confirmDialog(
        title='Upload To Server?',
        message='Upload To Server?',
        button=['Yes', 'No'],
        defaultButton='No',
        cancelButton='No',
        dismissString='No'
    )
    if response == 'Yes':
        pb.upload_output(pb.version, video_file_output)


class Playblaster(object):
    """Generates playblasts.
    If there are shots in the current scene then it generates playblasts for
    each of them and uploads it to the server
    """

    display_flags = [
        'nurbsCurves', 'nurbsSurfaces', 'cv', 'hulls', 'polymeshes',
        'subdivSurfaces', 'planes', 'lights', 'cameras', 'imagePlane',
        'joints', 'ikHandles', 'deformers', 'dynamics', 'particleInstancers',
        'fluids', 'hairSystems', 'follicles', 'nCloths', 'nParticles', 'nRigids',
        'dynamicConstraints', 'locators', 'dimensions', 'pivots',
        'handles', 'textures', 'strokes', 'motionTrails', 'pluginShapes',
        'clipGhosts', 'greasePencils', 'manipulators', 'grid',
    ]

    cam_attribute_names = [
        'overscan',
        'filmFit',
        'displayFilmGate',
        'displayResolution',
        'displayGateMask',
        'displayFieldChart',
        'displaySafeAction',
        'displaySafeTitle',
        'displayFilmPivot',
        'displayFilmOrigin'
    ]

    global_playblast_options = {
        # 'fmt': 'image',
        'fmt': 'qt',
        'forceOverwrite': 1,
        'clearCache': 1,
        'showOrnaments': 1,
        'percent': 100,
        'offScreen': 1,
        'viewer': 0,
        # 'compression': 'png',
        'compression': 'MPEG-4 Video',
        'quality': 85,
        'sequenceTime': 1
    }

    hud_name = 'PlayblasterHUD'

    def __init__(self):
        from stalker import LocalSession
        local_session = LocalSession()
        self.logged_in_user = local_session.logged_in_user

        if not self.logged_in_user:
            raise RuntimeError('Please login first!')

        self.version = None
        from anima.env.mayaEnv import Maya
        self.m_env = Maya()
        self.version = self.m_env.get_current_version()

        self.ui_window_name = 'playblaster_window'
        self.ui_window = None

        self.user_view_options = {}
        self.reset_user_view_options_storage()

        self.batch_mode = False

    def check_sequence_name(self):
        """checks sequence name and asks the user to set one if maya is in UI
        mode and there is no sequence name set
        """
        sequencer = pm.ls(type='sequencer')[0]
        sequence_name = sequencer.getAttr('sequence_name')
        if sequence_name == '' and not pm.general.about(batch=1) \
           and not self.batch_mode:
            result = pm.promptDialog(
                title='Please enter a Sequence Name',
                message='Sequence Name:',
                button=['OK', 'Cancel'],
                defaultButton='OK',
                cancelButton='Cancel',
                dismissString='Cancel'
            )

            if result == 'OK':
                sequencer.setAttr(
                    'sequence_name',
                    pm.promptDialog(query=True, text=True)
                )

    def get_hud_data(self):
        """
        """
        # try to get the shot from sequencer
        current_shot = pm.sequenceManager(q=1, currentShot=1)

        if current_shot:
            shot_name = pm.getAttr('%s.shotName' % current_shot)
            current_cam_name = pm.shot(current_shot, q=1, cc=1)
            current_cam = pm.PyNode(current_cam_name)
        else:
            # then try to get the shot name from the file name
            import os
            shot_name = os.path.split(pm.sceneName())[1].split('_')[0]
            # use the active panel camera
            active_panel = self.get_active_panel()
            current_cam = pm.modelEditor(active_panel, q=1, cam=1)
            current_cam_name = current_cam.name()

        if isinstance(current_cam, pm.nt.Transform):
            current_cam = current_cam.getShape()

        focal_length = current_cam.getAttr('focalLength')

        sequencers = pm.ls(type='sequencer')
        if sequencers:
            sequencer = sequencers[0]
            if sequencer.getAttr('sequence_name') != '':
                shot_info = sequencer.getAttr('sequence_name')
            else:
                shot_info = 'INVALID'
        else:
            shot_info = shot_name

        cf = pm.currentTime(q=1) + 1

        import timecode
        frame_rate = 25
        from anima.env import mayaEnv
        maya_env = mayaEnv.Maya()
        v = maya_env.get_current_version()
        if v:
            frame_rate = v.task.project.fps
        tc = timecode.Timecode(frame_rate, frames=cf)

        if current_shot:
            start_time = pm.shot(current_shot, q=1, st=1)
            end_time = pm.shot(current_shot, q=1, et=1)
        else:
            # no shot node use the current playback range
            start_time = pm.playbackOptions(q=1, min=1)
            end_time = pm.playbackOptions(q=1, max=1)

        cs_frame = int(cf - start_time)

        length = int(end_time - start_time) + 1

        if self.version:
            user_name = self.version.updated_by.name
        else:
            # get the user name from the login info
            if self.logged_in_user:
                user_name = self.logged_in_user.name
            else:
                # ok try to use the filename
                user_name = pm.sceneName().split('_')[-1]
{'fraction_non_alphanumeric': 0.11709258483452031, 'fraction_numerical': 0.0080121491411814, 'mean_word_length': 2.3065668152893815, 'pattern_counts': {'":': 4, '<': 9, '<?xml version=': 0, '>': 85, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '99156', 'n_tokens_mistral': 24850, 'n_tokens_neox': 23629, 'n_words': 5534}