Example #1
0
def create_skeleton(character_name='Character1', attrs_dict=None):
    """
    Creates a new HumanIk skeleton
    """

    if attrs_dict is None:
        attrs_dict = dict()

    sync_skeleton_generator_from_ui()

    create_character(character_name=character_name, lock=False)
    current_name = get_current_character()
    if not current_name:
        return False

    skeleton_generator_node = create_skeleton_generator_node(current_name)
    if not skeleton_generator_node:
        logger.warning('Was not possible to create HIK Skeleton Generator node.')
        return False

    load_default_human_ik_pose_onto_skeleton_generator_node(skeleton_generator_node)
    set_skeleton_generator_defaults(skeleton_generator_node)
    set_skeleton_generator_attrs(skeleton_generator_node, attrs_dict)

    reset_current_source()

    # If we have no characters yet, select the newly created character to refresh both the character and sources list
    dcc.select_node(current_name)
    update_current_character_from_scene()
    update_definition_ui()
    select_skeleton_tab()

    return True
Example #2
0
    def load(self, *args, **kwargs):

        objects = kwargs.get('objects', None)
        namespaces = kwargs.get('namespaces', None)

        valid_nodes = list()
        target_objects = objects
        source_objects = self.objects()

        self.validate(namespaces=namespaces)

        matches = utils.match_names(source_objects,
                                    target_objects=target_objects,
                                    target_namespaces=namespaces)
        for source_node, target_node in matches:
            if '*' in target_node.name():
                valid_nodes.append(target_node.name())
            else:
                target_node.strip_first_pipe()
                try:
                    target_node = target_node.to_short_name()
                except exceptions.NoObjectFoundError as exc:
                    logger.warning(exc)
                    continue
                except exceptions.MoreThanOneObjectFoundError as exc:
                    logger.warning(exc)
                valid_nodes.append(target_node.name())

        if valid_nodes:
            dcc.select_node(valid_nodes, **kwargs)
            dcc.focus_ui_panel('MayaWindow')
        else:
            raise exceptions.NoMatchFoundError(
                'No objects match when loading selection set data')
Example #3
0
def match_rotation(source_transform=None, target_transform=None):
    """
    Matches rotation of the source node to the rotation of the given target node(s)
    """

    out_dict = {'success': False, 'result': list()}

    selection = dcc.selected_nodes_of_type(node_type='transform')
    source_transform = source_transform or selection[
        0] if python.index_exists_in_list(selection, 0) else None
    if not source_transform:
        out_dict[
            'msg'] = 'No source transform given to match against target rotation.'
        return out_dict
    target_transform = target_transform or selection[1:] if len(
        selection) > 1 else None
    if not source_transform:
        out_dict[
            'msg'] = 'No target transform(s) given to match source rotation against.'
        return out_dict
    source_transform = python.force_list(source_transform)
    target_transform = python.force_list(target_transform)

    percentage = 100.0 / len(source_transform)

    for i, source in enumerate(source_transform):
        library.Command.progressCommand.emit(
            percentage * (i + 1), 'Matching rotation: {}'.format(source))
        try:
            maya.cmds.delete(
                maya.cmds.orientConstraint(target_transform,
                                           source,
                                           maintainOffset=False))

            # For joints, we store now rotation data in jointOrient attribute
            if dcc.node_type(source) == 'joint':
                for axis in 'XYZ':
                    joint_orient_attr = 'jointOrient{}'.format(axis)
                    joint_rotation_attr = 'rotate{}'.format(axis)
                    dcc.set_attribute_value(source, joint_orient_attr, 0.0)
                    joint_rotation = dcc.get_attribute_value(
                        source, joint_rotation_attr)
                    dcc.set_attribute_value(source, joint_orient_attr,
                                            joint_rotation)
                    dcc.set_attribute_value(source, joint_rotation_attr, 0.0)

            out_dict['result'].append(source)
        except Exception as exc:
            out_dict[
                'msg'] = 'Was not possible to match node "{}" rotation to "{}" : {}'.format(
                    source_transform, target_transform, exc)
            return out_dict

    matched_nodes = out_dict.get('result', None)
    if matched_nodes:
        dcc.select_node(matched_nodes)

    out_dict['success'] = True

    return out_dict
Example #4
0
    def selection_mirror(self, data, reply):

        selected_geo = data['geo']
        selected_vertices = data['selected_vertices']
        symmetry_table = data['symmetry_table']

        mirror_vertices = list()

        dcc.enable_wait_cursor()
        try:
            for selected_vertex in selected_vertices:
                vert_num = int(
                    re.search(r'\[(\w+)\]', selected_vertex).group(1))
                mirror_vert_num = consts.get_mirror_vertex_index(
                    symmetry_table, vert_num)
                if mirror_vert_num != -1:
                    mirror_vertices.append(
                        dcc.node_vertex_name(selected_geo, mirror_vert_num))
                else:
                    mirror_vertices.append(
                        dcc.node_vertex_name(selected_geo, vert_num))

            if mirror_vertices:
                dcc.select_node(mirror_vertices)

            reply['success'] = True

        except Exception as exc:
            logger.error('Error while selecting mirror: {} | {}'.format(
                exc, traceback.format_exc()))
            reply['success'] = False
        finally:
            dcc.disable_wait_cursor()

        reply['result'] = mirror_vertices
def select_controls(**kwargs):
    """
    Select all controls in current scene
    """

    namespace = kwargs.get('namespace', '')
    all_controls = get_controls(namespace=namespace, **kwargs)
    if not all_controls:
        return

    dcc.select_node(all_controls)
def freeze_skinned_mesh(skinned_mesh, **kwargs):

    out_dict = {'success': False, 'result': list()}

    meshes = skinned_mesh or dcc.selected_nodes_of_type('transform')
    meshes = python.force_list(meshes)
    if not meshes:
        return False

    if kwargs.pop('auto_assign_labels', False):
        joint_utils.auto_assign_labels_to_mesh_influences(
            meshes,
            input_left=kwargs.pop('left_side_label', None),
            input_right=kwargs.pop('right_side_label', None),
            check_labels=True)

    percentage = 100.0 / len(meshes)

    for i, mesh in enumerate(meshes):
        library.Command.progressCommand.emit(
            percentage * (i + 1), 'Cleaning Skinned Mesh: {}'.format(mesh))
        try:
            skin_cluster_name = skin_utils.find_related_skin_cluster(mesh)
            if not skin_cluster_name:
                continue
            attached_joints = maya.cmds.skinCluster(skin_cluster_name,
                                                    query=True,
                                                    inf=True)
            mesh_shape_name = maya.cmds.listRelatives(mesh, shapes=True)[0]
            out_influences_array = api_skin.get_skin_weights(
                skin_cluster_name, mesh_shape_name)
            maya.cmds.skinCluster(mesh_shape_name, edit=True, unbind=True)
            maya.cmds.delete(mesh, ch=True)
            maya.cmds.makeIdentity(mesh, apply=True)
            new_skin_cluster_name = maya.cmds.skinCluster(
                attached_joints,
                mesh,
                toSelectedBones=True,
                bindMethod=0,
                normalizeWeights=True)[0]
            api_skin.set_skin_weights(new_skin_cluster_name, mesh_shape_name,
                                      out_influences_array)
            out_dict['result'].append(mesh)
        except Exception as exc:
            out_dict[
                'msg'] = 'Was not possible to freeze skinned meshes: "{}" | {}'.format(
                    meshes, exc)
            return out_dict

    dcc.select_node(meshes)

    out_dict['success'] = True

    return out_dict
Example #7
0
    def select_moved_vertices(self, data, reply):

        obj = data['geo']
        base_obj = data['base_geo']
        tolerance = data['tolerance']

        moved_vertices = list()

        total_vertices = dcc.total_vertices(obj)

        dcc_progress_bar = progressbar.ProgressBar(title='Working',
                                                   count=total_vertices)
        dcc_progress_bar.status('Checking Verts')
        mod = math.ceil(total_vertices / 50)

        dcc.enable_wait_cursor()
        try:
            for i in range(total_vertices):
                if i % mod == 0:
                    prog_num = i
                    prog = (prog_num / total_vertices) * 100.0
                    dcc_progress_bar.inc(prog)

                vtx = dcc.node_vertex_name(obj, i)
                vec1 = mathlib.Vector(
                    *dcc.node_vertex_object_space_translation(base_obj, i))
                vec2 = mathlib.Vector(
                    *dcc.node_vertex_object_space_translation(obj, i))

                if mathlib.get_distance_between_vectors(vec1,
                                                        vec2) > tolerance:
                    moved_vertices.append(vtx)

            if len(moved_vertices) > 0:
                dcc.select_node(obj)
                dcc.enable_component_selection()
                dcc.select_node(moved_vertices, replace_selection=False)

            reply['success'] = True

        except Exception as exc:
            logger.error(
                'Error while selecting moving vertices: {} | {}'.format(
                    exc, traceback.format_exc()))
            reply['success'] = False
        finally:
            dcc.disable_wait_cursor()
            dcc_progress_bar.end()

        reply['result'] = moved_vertices
def set_shape(crv,
              crv_shape_list,
              size=None,
              select_new_shape=False,
              keep_color=False):
    """
    Creates a new shape on the given curve
    :param crv:
    :param crv_shape_list:
    :param size:
    :param select_new_shape: bool
    :param keep_color: bool
    """

    crv_shapes = controlutils.validate_curve(crv)

    orig_size = None
    orig_color = None
    if crv_shapes:
        orig_size = dcc.node_bounding_box_size(crv)

        # If there are multiple shapes, we only take into account the color of the first shape
        orig_color = dcc.node_color(crv_shapes[0])

    if crv_shapes:
        dcc.delete_node(crv_shapes)

    for i, c in enumerate(crv_shape_list):
        new_shape = dcc.list_shapes(c)[0]
        new_shape = dcc.rename_node(
            new_shape,
            dcc.node_short_name(crv) + 'Shape' + str(i + 1).zfill(2))
        dcc.enable_overrides(new_shape)
        if orig_color is not None and keep_color:
            dcc.set_node_color(new_shape, orig_color)
        dcc.combine_shapes(crv, new_shape, delete_after_combine=True)

    new_size = dcc.node_bounding_box_size(crv)

    if orig_size and new_size:
        scale_size = orig_size / new_size
        dcc.scale_shapes(crv, scale_size, relative=False)

    if size:
        dcc.scale_shapes(crv, size, relative=True)

    if select_new_shape:
        dcc.select_node(crv)

    return crv
Example #9
0
def match_scale(source_transform=None, target_transform=None):
    """
    Matches scale of the source node to the scale of the given target node(s)
    """

    out_dict = {'success': False, 'result': list()}

    selection = dcc.selected_nodes_of_type(node_type='transform')
    source_transform = source_transform or selection[
        0] if python.index_exists_in_list(selection, 0) else None
    if not source_transform:
        out_dict[
            'msg'] = 'No source transform given to match against target scale.'
        return out_dict
    target_transform = target_transform or selection[1:] if len(
        selection) > 1 else None
    if not source_transform:
        out_dict[
            'msg'] = 'No target transform(s) given to match source scale against.'
        return out_dict
    source_transform = python.force_list(source_transform)
    target_transform = python.force_list(target_transform)

    percentage = 100.0 / len(source_transform)

    for i, source in enumerate(source_transform):
        library.Command.progressCommand.emit(
            percentage * (i + 1), 'Matching scale: {}'.format(source))
        try:
            maya.cmds.delete(
                maya.cmds.scaleConstraint(target_transform,
                                          source,
                                          maintainOffset=False))
            out_dict['result'].append(source)
        except Exception as exc:
            out_dict[
                'msg'] = 'Was not possible to match node "{}" scale to "{}" : {}'.format(
                    source_transform, target_transform, exc)
            return out_dict

    matched_nodes = out_dict.get('result', None)
    if matched_nodes:
        dcc.select_node(matched_nodes)

    out_dict['success'] = True

    return out_dict
    def manual_orient_joints(self, data, reply):

        orient_type = data.get('orient_type', 'add')
        x_axis = data.get('x_axis', 0.0)
        y_axis = data.get('y_axis', 0.0)
        z_axis = data.get('z_axis', 0.0)
        affect_children = data.get('affect_children', False)

        if orient_type == 'add':
            tweak = 1.0
        else:
            tweak = -1.0

        tweak_rot = [x_axis * tweak, y_axis * tweak, z_axis * tweak]

        joints = dcc.selected_nodes_of_type(node_type='joint')
        if not joints:
            return

        for jnt in joints:
            dcc.set_node_rotation_axis_in_object_space(jnt, tweak_rot[0],
                                                       tweak_rot[1],
                                                       tweak_rot[2])
            dcc.zero_scale_joint(jnt)
            dcc.freeze_transforms(jnt, preserve_pivot_transforms=True)

            if affect_children:
                childs = dcc.list_children(
                    jnt,
                    children_type=['transform', 'joint'],
                    full_path=False,
                    all_hierarchy=True) or list()
                for child in childs:
                    parent = dcc.node_parent(child)
                    dcc.set_parent_to_world(child)
                    dcc.set_node_rotation_axis_in_object_space(
                        child, tweak_rot[0], tweak_rot[1], tweak_rot[2])
                    dcc.zero_scale_joint(child)
                    dcc.freeze_transforms(child,
                                          preserve_pivot_transforms=True)
                    dcc.set_parent(child, parent)

        dcc.select_node(joints, replace_selection=True)

        reply['success'] = True
    def reset_joints_orient_to_world(self, data, reply):
        apply_to_hierarchy = data.get('apply_to_hierarchy', False)

        if apply_to_hierarchy:
            dcc.select_hierarchy()

        joints = dcc.selected_nodes_of_type(node_type='joint', full_path=False)
        if not joints:
            reply['msg'] = 'No joints selected'
            reply['success'] = False
            return

        for jnt in reversed(joints):
            childs = dcc.list_children(jnt,
                                       all_hierarchy=False,
                                       children_type=['transform', 'joint'])

            # If the joints has direct childs, unparent that childs and store names
            if childs:
                if len(childs) > 0:
                    childs = dcc.set_parent_to_world(childs)

            # Get parent of this joints for later use
            parent = dcc.node_parent(jnt, full_path=False) or ''

            if parent:
                dcc.set_parent_to_world(jnt)

            # Clear joint axis
            dcc.zero_scale_joint(jnt)
            dcc.freeze_transforms(jnt, preserve_pivot_transforms=True)
            dcc.zero_orient_joint(jnt)

            # Reparent
            if parent:
                dcc.set_parent(jnt, parent)

            # Reparent child
            if childs:
                if len(childs) > 0:
                    dcc.set_parent(childs, jnt)

        dcc.select_node(joints, replace_selection=True)

        reply['success'] = True
Example #12
0
    def _after_load(self):
        """
        Internal function that is called after loading the pose
        """

        if not self._is_loading:
            return

        logger.debug('After Load Pose "{}"'.format(self.path))

        self._is_loading = False
        if self._selection:
            dcc.select_node(self._selection)
            self._selection = None
        dcc.set_auto_keyframe_enabled(self._auto_key_frame)
        dcc.disable_undo()

        logger.info('Loaded Pose "{}"'.format(self.path))
    def set_manual_orient_joints(self, data, reply):
        x_axis = data.get('x_axis', 0.0)
        y_axis = data.get('y_axis', 0.0)
        z_axis = data.get('z_axis', 0.0)
        affect_children = data.get('affect_children', False)

        childs = list()

        tweak_rot = [x_axis, y_axis, z_axis]

        joints = dcc.selected_nodes_of_type(node_type='joint', full_path=False)
        if not joints:
            return

        for jnt in joints:
            if not affect_children:
                childs = dcc.list_children(
                    jnt,
                    children_type=['transform', 'joint'],
                    full_path=False,
                    all_hierarchy=False) or list()
                for child in childs:
                    dcc.set_parent_to_world(child)

            # Set the rotation axis
            for i, axis in enumerate(['x', 'y', 'z']):
                dcc.set_attribute_value(jnt,
                                        'jointOrient{}'.format(axis.upper()),
                                        tweak_rot[i])

            # Clear joint axis
            dcc.zero_scale_joint(jnt)
            dcc.freeze_transforms(jnt, preserve_pivot_transforms=True)

            if childs:
                for child in childs:
                    dcc.set_parent(child, jnt)

        dcc.select_node(joints, replace_selection=True)

        reply['success'] = True
Example #14
0
    def check_symmetry(self, data, reply):
        obj = data['geo']
        axis = data['axis']
        tolerance = data['tolerance']
        table = data['table']
        use_pivot = data['use_pivot']
        select_asymmetric_vertices = data['select_asymmetric_vertices']

        pos_verts = list()
        neg_verts = list()
        pos_verts_int = list()
        neg_verts_int = list()
        pos_verts_trans = list()
        neg_verts_trans = list()
        vert_counter = 0
        non_symm_verts = list()
        is_symmetric = False

        axis_ind = axis
        axis_2_ind = (axis_ind + 1) % 3
        axis_3_ind = (axis_ind + 2) % 3

        if use_pivot:
            vtx_trans = dcc.node_world_space_translation(obj)
            mid = vtx_trans[axis_ind]
        else:
            if table:
                bounding_box = dcc.node_world_bounding_box(obj)
                mid = bounding_box[axis_ind] + (
                    (bounding_box[axis_ind + 3] - bounding_box[axis_ind]) / 2)
            else:
                mid = 0

        symmetry_table = list()

        total_vertices = dcc.total_vertices(obj)

        dcc.enable_wait_cursor()
        dcc_progress_bar = progressbar.ProgressBar(title='Working',
                                                   count=total_vertices)
        dcc_progress_bar.status('Sorting')
        mod = math.ceil(total_vertices / 50)

        try:
            for i in range(total_vertices):
                if i % mod == 0:
                    prog_num = i
                    prog = (prog_num / total_vertices) * 100.0
                    dcc_progress_bar.inc(prog)

                vtx = dcc.node_vertex_name(obj, i)
                vtx_trans = dcc.node_vertex_world_space_translation(obj, i)
                mid_offset = vtx_trans[axis_ind] - mid
                if mid_offset >= consts.MID_OFFSET_TOLERANCE:
                    pos_verts.append(vtx)
                    if table:
                        pos_verts_int.append(i)
                    pos_verts_trans.append(vtx_trans[axis_ind])
                else:
                    if mid_offset < consts.MID_OFFSET_TOLERANCE:
                        neg_verts.append(vtx)
                        if table:
                            neg_verts_int.append(i)
                        neg_verts_trans.append(vtx_trans[axis_ind])

            msg = 'Building Symmetry Table' if table else 'Checking for Symmetry'
            dcc_progress_bar.set_progress(0)
            dcc_progress_bar.status(msg)
            for i in range(len(pos_verts)):
                # if i % mod == 0:
                #     prog_num = i
                #     prog = (prog_num / total_vertices) * 100.0
                #     dcc_progress_bar.inc(prog)

                vtx = pos_verts[i]
                pos_offset = pos_verts_trans[i] - mid
                if pos_offset < tolerance:
                    pos_verts[i] = consts.MATCH_STR
                    vert_counter += 1
                    continue

                for j in range(len(neg_verts)):
                    if neg_verts[j] == consts.MATCH_STR:
                        continue
                    neg_offset = mid - neg_verts_trans[j]
                    if neg_offset < tolerance:
                        neg_verts[j] = consts.MATCH_STR
                        vert_counter += 1
                        continue

                    if abs(pos_offset - neg_offset) <= tolerance:
                        vtx_trans = dcc.node_vertex_world_space_translation(
                            vtx)
                        vtx2_trans = dcc.node_vertex_world_space_translation(
                            neg_verts[j])
                        test1 = vtx_trans[axis_2_ind] - vtx2_trans[axis_2_ind]
                        test2 = vtx_trans[axis_3_ind] - vtx2_trans[axis_3_ind]
                        if abs(test1) < tolerance and abs(test2) < tolerance:
                            if table:
                                symmetry_table.append(pos_verts_int[i])
                                symmetry_table.append(neg_verts_int[j])
                                vert_counter += 2

                            pos_verts[i] = neg_verts[j] = consts.MATCH_STR
                            break

            pos_verts = [x for x in pos_verts if x not in [consts.MATCH_STR]]
            neg_verts = [x for x in neg_verts if x not in [consts.MATCH_STR]]
            non_symm_verts = pos_verts + neg_verts

            if table:
                if vert_counter != total_vertices:
                    logger.warning(
                        'Base geometry is not symmetrical, not all vertices can be mirrored'
                    )
                else:
                    logger.info('Base geometry is symmetrical')
                    is_symmetric = True

            reply['success'] = True
        except Exception as exc:
            logger.error('Error while checking symmetry: {} | {}'.format(
                exc, traceback.format_exc()))
            reply['success'] = False
        finally:
            dcc.disable_wait_cursor()
            dcc_progress_bar.end()

        if select_asymmetric_vertices:
            total_vertices_to_select = len(non_symm_verts)
            if total_vertices_to_select > 0:
                dcc.enable_component_selection()
                dcc.select_node(non_symm_verts)
                logger.info(
                    '{} asymmetric vert(s)'.format(total_vertices_to_select))
            else:
                dcc.select_node(obj)

        reply['result'] = non_symm_verts, symmetry_table, is_symmetric
    def orient_joints(self, data, reply):
        aim_axis_index = data.get('aim_axis_index', 0.0)
        aim_axis_reverse = data.get('aim_axis_reverse', False)
        up_axis_index = data.get('up_axis_index', 0.0)
        up_axis_reverse = data.get('up_axis_reverse', False)
        up_world_axis_x = data.get('up_world_axis_x', 0.0)
        up_world_axis_y = data.get('up_world_axis_y', 0.0)
        up_world_axis_z = data.get('up_world_axis_z', 0.0)
        apply_to_hierarchy = data.get('apply_to_hierarchy', False)

        reset_joints = list()

        # Get up and aim axis
        aim_axis = [0, 0, 0]
        up_axis = [0, 0, 0]
        world_up_axis = [up_world_axis_x, up_world_axis_y, up_world_axis_z]

        if aim_axis_index == up_axis_index:
            LOGGER.warning(
                'aim and up axis are the same, maybe orientation wont work correctly!'
            )

        aim_axis_reverse_value = 1.0 if not aim_axis_reverse else -1.0
        up_axis_reverse_value = 1.0 if not up_axis_reverse else -1.0

        aim_axis[aim_axis_index] = aim_axis_reverse_value
        up_axis[up_axis_index] = up_axis_reverse_value

        # Get selected joints
        if apply_to_hierarchy:
            dcc.select_hierarchy()

        joints = dcc.selected_nodes_of_type(node_type='joint', full_path=False)
        if not joints:
            reply['msg'] = 'No joints selected'
            reply['success'] = False
            return

        for jnt in reversed(joints):
            childs = dcc.list_children(jnt,
                                       all_hierarchy=False,
                                       children_type=['transform', 'joint'])

            # If the joints has direct childs, unparent that childs and store names
            if childs:
                if len(childs) > 0:
                    childs = dcc.set_parent_to_world(childs)
            childs = python.force_list(childs)

            # Get parent of this joints for later use
            parent = ''
            parents = dcc.node_parent(jnt)
            if parents:
                parent = parents[0]

            # Aim to the child
            aim_target = ''
            if childs:
                for child in childs:
                    if dcc.node_type(child) == 'joint':
                        aim_target = child
                        break

            if aim_target != '':

                # Apply an aim constraint from the joint to its child (target)
                dcc.delete_node(
                    dcc.create_aim_constraint(jnt,
                                              aim_target,
                                              aim_axis=aim_axis,
                                              up_axis=up_axis,
                                              world_up_axis=world_up_axis,
                                              world_up_type='vector',
                                              weight=1.0))

                # Clear joint axis
                dcc.zero_scale_joint(jnt)
                dcc.freeze_transforms(jnt, preserve_pivot_transforms=True)

            elif parent != '':
                reset_joints.append(jnt)

            # Reparent child
            if childs:
                if len(childs) > 0:
                    dcc.set_parent(childs, jnt)

        for jnt in reset_joints:
            # If there is no target, the joint will take its parent orientation
            for axis in ['x', 'y', 'z']:
                dcc.set_attribute_value(
                    jnt, 'jointOrient{}'.format(axis.upper()),
                    dcc.get_attribute_value(jnt, 'r{}'.format(axis)))
                dcc.set_attribute_value(jnt, 'r{}'.format(axis), 0)

        dcc.select_node(joints, replace_selection=True)

        reply['success'] = True
Example #16
0
    def select(self):
        """
        Selects wrapped meta node in current scene
        """

        dcc.select_node(self.poser.meta_node)
def replace_control_curves(control_name,
                           control_type='circle',
                           controls_path=None,
                           auto_scale=True,
                           maintain_line_width=True,
                           keep_color=True,
                           **kwargs):
    """
    Replaces the given control with the given control type deleting the existing control curve shape nodes
    :param control_name:
    :param control_type:
    :param controls_path:
    :param auto_scale: bool
    :param maintain_line_width: bool
    :param keep_color: bool
    :return:
    """

    orig_sel = dcc.selected_nodes()

    line_width = -1
    orig_size = None
    orig_color = kwargs.pop('color', None)
    if auto_scale:
        orig_size = get_control_size(control_name)
    if maintain_line_width:
        line_width = curve.get_curve_line_thickness(control_name)
    if keep_color:
        orig_color = get_control_color(control_name)

    new_control = create_control_curve(control_name='new_ctrl',
                                       control_type=control_type,
                                       controls_path=controls_path,
                                       color=orig_color,
                                       **kwargs)[0]
    if auto_scale and orig_size is not None:
        new_scale = get_control_size(new_control)
        scale_factor = orig_size / new_scale
        dcc.scale_shapes(new_control, scale_factor, relative=False)

    # We need to make sure that transforms are reset in all scenarios
    # Previous implementation was failing if the target hierarchy had a mirror behaviour (group scaled negative in
    # one of the axises)
    # maya.cmds.matchTransform([new_control, target], pos=True, rot=True, scl=True, piv=True)
    target = dcc.list_nodes(control_name, node_type='transform')[0]
    dcc.delete_node(
        dcc.create_parent_constraint(new_control,
                                     target,
                                     maintain_offset=False))
    dcc.delete_node(
        dcc.create_scale_constraint(new_control, target,
                                    maintain_offset=False))
    target_parent = dcc.node_parent(target)
    if target_parent:
        new_control = dcc.set_parent(new_control, target_parent)
        for axis in 'XYZ':
            dcc.set_attribute_value(new_control, 'translate{}'.format(axis),
                                    0.0)
            dcc.set_attribute_value(new_control, 'rotate{}'.format(axis), 0.0)
            dcc.set_attribute_value(new_control, 'scale{}'.format(axis), 1.0)
        new_control = dcc.set_parent_to_world(new_control)

    if target != new_control:
        xform_utils.parent_transforms_shapes(target, [new_control],
                                             delete_original=True,
                                             delete_shape_type='nurbsCurve')

    if maintain_line_width:
        curve.set_curve_line_thickness(target, line_width=line_width)

    dcc.select_node(orig_sel)

    return target