Ejemplo n.º 1
0
    def attach_to_plane(self, constraint_rot=True):
        """
        Create follicle attached to the place for each input joint
        :param constraint_rot: Are the joints will be constraint in rotation on the follicle
        :return: Nothing
        """
        nomenclature_rig = self.get_nomenclature_rig()
        fol_v = 0.5  # Always in the center

        #split_value = 1.0 / (len(self.chain_jnt) - 1)

        for i, jnt in enumerate(self.chain_jnt):
            #fol_u = split_value * i
            # TODO: Validate that we don't need to inverse the rotation separately.
            jnt_pos = jnt.getMatrix(worldSpace=True).translate
            pos, fol_u, fol_v = libRigging.get_closest_point_on_surface(self._ribbon_shape, jnt_pos)
            fol_name = nomenclature_rig.resolve("ribbonFollicle{0:02d}".format(i))
            fol_shape = libRigging.create_follicle2(self._ribbon_shape, u=fol_u, v=fol_v)
            fol = fol_shape.getParent()
            fol.rename(fol_name)
            if constraint_rot:
                pymel.parentConstraint(fol, jnt, mo=True)
            else:
                pymel.pointConstraint(fol, jnt, mo=True)

            self._follicles.append(fol)
    def _create_follicle(self, ctrl_tm, influence, obj_mesh=None, u_coord=None, v_coord=None):
        nomenclature_rig = self.get_nomenclature_rig()
        # Create a follicle, this will be used for callibration purpose.
        # If this affect performance we can create it only when necessary, however being able to
        # see it help with debugging.

        # Resolve u and v coordinates
        if obj_mesh is None:
            # We'll scan all available geometries and use the one with the shortest distance.
            meshes = libHistory.get_affected_shapes(influence)
            meshes = list(set(meshes) & set(self.rig.get_shapes()))
            if not meshes:
                meshes = set(self.rig.get_shapes())
            obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes(meshes, ctrl_tm.translate)

        else:
            _, out_u, out_v = libRigging.get_closest_point_on_shape(obj_mesh, ctrl_tm.translate)

        # Resolve u and v coordinates if necesary.
        if u_coord is None:
            u_coord = out_u
        if v_coord is None:
            v_coord = out_v

        fol_name = nomenclature_rig.resolve('follicle')
        fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord)
        fol_transform = fol_shape.getParent()
        fol_transform.rename(fol_name)
        fol_transform.setParent(self.grp_rig)

        return fol_transform, fol_shape
    def build(self, avar, ref=None, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None,
              flip_lr=False, follow_mesh=True, ctrl_tm=None, ctrl_size=1.0, parent_pos=None,
              parent_rot=None, parent_scl=None, constraint=False, cancel_t=True, cancel_r=True, **kwargs):
        super(ModelInteractiveCtrl, self).build(avar, ctrl_size=ctrl_size, **kwargs)

        nomenclature_rig = self.get_nomenclature_rig()

        #
        # Resolve necessary informations
        #

        # Resolve which object will the InteractiveCtrl track.
        # If we don't want to follow a particular geometry, we'll use the end of the stack.
        # Otherwise the influence will be used (to also resolve the geometry).
        # todo: it could be better to resolve the geometry ourself
        if ref is None:
            ref = self.jnt

        # Resolve ctrl matrix
        # It can differ from the influence to prevent the controller to appear in the geometry.
        if ctrl_tm is None:
            ctrl_tm = self.get_default_tm_ctrl()

        if ctrl_tm is None and ref_tm:
            ctrl_tm = ref_tm

        if ctrl_tm is None and self.jnt:
            ctrl_tm = self.jnt.getMatrix(worldSpace=True)

        if ctrl_tm is None:
            raise Exception("Cannot resolve ctrl transformation matrix!")

        pos_ref = self.project_pos_on_face(ctrl_tm.translate, geos=self.get_meshes())

        # Resolve u and v coordinates
        # todo: check if we really want to resolve the u and v ourself since it's now connected.
        if obj_mesh is None:
            # We'll scan all available geometries and use the one with the shortest distance.
            meshes = libHistory.get_affected_shapes(ref)
            meshes = list(set(meshes) & set(self.rig.get_shapes()))
            if not meshes:
                meshes = set(self.rig.get_shapes())
            obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes(meshes, pos_ref)

            if obj_mesh is None and follow_mesh:
                raise Exception("Can't find mesh affected by {0}. ".format(self.jnt))

        else:
            _, out_u, out_v = libRigging.get_closest_point_on_shape(obj_mesh, pos_ref)

        # Resolve u and v coordinates if necesary.
        if u_coord is None:
            u_coord = out_u
        if v_coord is None:
            v_coord = out_v

        if self.jnt:
            self.debug('Creating doritos on {0} using {1} as reference'.format(obj_mesh, self.jnt))
        else:
            self.debug('Creating doritos on {0}'.format(obj_mesh))

        # Hack: Since there's scaling on the ctrl so the left and right side ctrl channels matches, we need to flip the ctrl shapes.
        if flip_lr:
            self.ctrl.scaleX.set(-1)
            libPymel.makeIdentity_safe(self.ctrl, rotate=True, scale=True, apply=True)

        #
        # Create the follicle setup
        #

        # Initialize external stack
        # Normally this would be hidden from animators.
        stack_name = nomenclature_rig.resolve('doritosStack')
        self._stack = Node(self)
        self._stack.build(name=stack_name)
        self._stack.setTranslation(pos_ref)

        # Create the layer_fol that will follow the geometry
        layer_fol_name = nomenclature_rig.resolve('doritosFol')
        layer_fol = self._stack.append_layer()
        layer_fol.rename(layer_fol_name)
        layer_fol.setParent(self.grp_rig)

        #
        # Constraint grp_rig
        #

        # Constraint position
        # TODO: Validate that we don't need to inverse the rotation separately.
        if parent_pos is None:
            fol_mesh = None
            if follow_mesh:
                fol_name = nomenclature_rig.resolve('follicle')
                fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord)
                fol_mesh = fol_shape.getParent()
                self.follicle = fol_mesh
                fol_mesh.rename(fol_name)
                fol_mesh.setParent(self.grp_rig)
                parent_pos = fol_mesh
            elif ref:
                parent_pos = ref

        if parent_pos:
            pymel.parentConstraint(parent_pos, layer_fol, maintainOffset=True, skipRotate=['x', 'y', 'z'])

        # Constraint rotation
        # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since
        # any deformation can affect the normal of the faces.
        if parent_rot:
            pymel.orientConstraint(parent_rot, layer_fol, maintainOffset=True)

        #
        # Constraint a specic controller to the avar doritos stack.
        # Call this method after connecting the ctrl to the necessary avars.
        # The sensibility of the doritos will be automatically computed in this step if necessary.
        #

        # Create inverted attributes for sensibility
        util_sensitivity_inv = libRigging.create_utility_node('multiplyDivide', operation=2,
                                                              input1X=1.0, input1Y=1.0, input1Z=1.0,
                                                              input2X=self.attr_sensitivity_tx,
                                                              input2Y=self.attr_sensitivity_ty,
                                                              input2Z=self.attr_sensitivity_tz
                                                              )
        attr_sensibility_lr_inv = util_sensitivity_inv.outputX
        attr_sensibility_ud_inv = util_sensitivity_inv.outputY
        attr_sensibility_fb_inv = util_sensitivity_inv.outputZ

        #
        # Inverse translation
        #
        if cancel_t:
            attr_ctrl_inv_t = libRigging.create_utility_node(
                'multiplyDivide', input1=self.ctrl.node.t,
                input2=[-1, -1, -1]
            ).output

            attr_ctrl_inv_t = libRigging.create_utility_node(
                'multiplyDivide',
                input1=attr_ctrl_inv_t,
                input2X=self.attr_sensitivity_tx,
                input2Y=self.attr_sensitivity_ty,
                input2Z=self.attr_sensitivity_tz
            ).output

            layer_inv_t = self._stack.append_layer(name='inverseT')

            if flip_lr:
                attr_doritos_tx = libRigging.create_utility_node(
                    'multiplyDivide',
                    input1X=attr_ctrl_inv_t.outputX,
                    input2X=-1
                ).outputX
            else:
                attr_doritos_tx = attr_ctrl_inv_t.outputX
            attr_doritos_ty = attr_ctrl_inv_t.outputY
            attr_doritos_tz = attr_ctrl_inv_t.outputZ

            pymel.connectAttr(attr_doritos_tx, layer_inv_t.tx)
            pymel.connectAttr(attr_doritos_ty, layer_inv_t.ty)
            pymel.connectAttr(attr_doritos_tz, layer_inv_t.tz)

        #
        # Inverse rotation
        # Add an inverse node that will counter animate the position of the ctrl.
        # TODO: Rename
        #
        if cancel_r:
            layer_inv_r = self._stack.append_layer(name='inverseR')
            # layer_doritos = pymel.createNode('transform', name=layer_doritos_name)
            # layer_doritos.setParent(self._stack.node)

            # Create inverse attributes for the ctrl

            attr_ctrl_inv_r = libRigging.create_utility_node(
                'multiplyDivide',
                input1=self.ctrl.node.r,
                input2=[-1, -1, -1]
            ).output

            pymel.connectAttr(attr_ctrl_inv_r, layer_inv_r.r)

        #
        # Apply scaling on the ctrl parent.
        # This is were the 'black magic' happen.
        #
        if flip_lr:
            attr_ctrl_offset_sx_inn = libRigging.create_utility_node(
                'multiplyDivide',
                input1X=self.attr_sensitivity_tx,
                input2X=-1
            ).outputX
        else:
            attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx
        attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty
        attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz

        # Connect any additionel scale source.
        if parent_scl:
            u = libRigging.create_utility_node(
                'multiplyDivide',
                name=nomenclature_rig.resolve('getAbsoluteCtrlOffsetScale'),
                input1X=attr_ctrl_offset_sx_inn,
                input1Y=attr_ctrl_offset_sy_inn,
                input1Z=attr_ctrl_offset_sz_inn,
                input2X=parent_scl.scaleX,
                input2Y=parent_scl.scaleY,
                input2Z=parent_scl.scaleZ
            )
            attr_ctrl_offset_sx_inn, attr_ctrl_offset_sy_inn, attr_ctrl_offset_sz_inn = u.outputX, u.outputY, u.outputZ

        pymel.connectAttr(attr_ctrl_offset_sx_inn, self.ctrl.offset.scaleX)
        pymel.connectAttr(attr_ctrl_offset_sy_inn, self.ctrl.offset.scaleY)
        pymel.connectAttr(attr_ctrl_offset_sz_inn, self.ctrl.offset.scaleZ)

        # Apply sensibility on the ctrl shape
        ctrl_shape = self.ctrl.node.getShape()
        tmp = pymel.duplicate(self.ctrl.node.getShape())[0]
        ctrl_shape_orig = tmp.getShape()
        ctrl_shape_orig.setParent(self.ctrl.node, relative=True, shape=True)
        ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name()))
        pymel.delete(tmp)
        ctrl_shape_orig.intermediateObject.set(True)

        for cp in ctrl_shape.cp:
            cp.set(0, 0, 0)

        # Counter-scale the shape
        attr_adjustement_sx_inn = attr_sensibility_lr_inv
        attr_adjustement_sy_inn = attr_sensibility_ud_inv
        attr_adjustement_sz_inn = attr_sensibility_fb_inv
        attr_adjustement_scale = libRigging.create_utility_node(
            'composeMatrix',
            inputScaleX=attr_adjustement_sx_inn,
            inputScaleY=attr_adjustement_sy_inn,
            inputScaleZ=attr_adjustement_sz_inn
        ).outputMatrix

        attr_adjustement_rot = libRigging.create_utility_node(
            'composeMatrix',
            inputRotateX=self.ctrl.node.rotateX,
            inputRotateY=self.ctrl.node.rotateY,
            inputRotateZ=self.ctrl.node.rotateZ
        ).outputMatrix

        attr_adjustement_rot_inv = libRigging.create_utility_node(
            'inverseMatrix',
            inputMatrix=attr_adjustement_rot
        ).outputMatrix

        attr_adjustement_tm = libRigging.create_utility_node(
            'multMatrix', matrixIn=[
                attr_adjustement_rot,
                attr_adjustement_scale,
                attr_adjustement_rot_inv
            ]
        ).matrixSum

        attr_transform_geometry = libRigging.create_utility_node(
            'transformGeometry',
            transform=attr_adjustement_tm,
            inputGeometry=ctrl_shape_orig.local
        ).outputGeometry
        pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True)

        #
        # Constraint grp_anm
        #

        # Create an output object that will hold the world position of the ctrl offset.
        # We'll use direct connections to the animation controller to simplify the dag tree and support
        # non-uniform scaling (which is really hard to do using constraints).

        # Since the the model's ctrl will still be influenced by the root ctrl, we'll need to extract the offset
        # relative to the root ctrl.
        grp_output = pymel.createNode(
            'transform',
            name=nomenclature_rig.resolve('output'),
            parent=self.grp_rig
        )

        # Position
        stack_end = self._stack.get_stack_end()
        pymel.parentConstraint(stack_end, grp_output, maintainOffset=False, skipRotate=['x', 'y', 'z'])

        # Rotation
        if parent_rot is None:
            parent_rot = stack_end
            # parent_rot = layer_inv_r.getParent()
        pymel.orientConstraint(parent_rot, grp_output, maintainOffset=True)

        # Direct-connect the output group to the ctrl offset grp.
        # Since the ctrl is a child of the master ctrl, we'll need to take it's parent in consideration.
        util_get_ctrl_offset_local_trs = libRigging.create_utility_node(
            'decomposeMatrix',
            inputMatrix=libRigging.create_utility_node(
                'multMatrix',
                matrixIn=(
                    grp_output.worldMatrix,
                    self.rig.grp_anm.worldInverseMatrix
                )
            ).matrixSum
        )
        pymel.connectAttr(util_get_ctrl_offset_local_trs.outputTranslate, self.ctrl.offset.translate)
        pymel.connectAttr(util_get_ctrl_offset_local_trs.outputRotate, self.ctrl.offset.rotate)

        # Clean dag junk
        if grp_rig:
            self._stack.setParent(grp_rig)
            if fol_mesh:
                fol_mesh.setParent(grp_rig)

        if constraint and self.jnt:
            pymel.parentConstraint(self.ctrl.node, self.jnt, maintainOffset=True)
Ejemplo n.º 4
0
    def build(self, module, ref, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None, flip_lr=False, follow_mesh=True, **kwargs):
        """
        Create an Interactive controller that follow a geometry.
        :param module: ???
        :param ref:
        :param ref_tm:
        :param grp_rig:
        :param obj_mesh:
        :param u_coord:
        :param v_coord:
        :param kwargs:
        :return:
        """

        # HACK: Ensure flipped shapes are correctly restaured...
        # This is necessary since when holded, the scale of the ctrl is set to identity.
        # However ctrl from the right side have an inverted scale on the x axis. -_-
        if flip_lr and libPymel.is_valid_PyNode(self.shapes):
            self.shapes.sx.set(-1)
            pymel.makeIdentity(self.shapes, rotate=True, scale=True, apply=True)

        # todo: Simplify the setup, too many nodes
        super(InteractiveCtrl, self).build(**kwargs)

        #nomenclature_anm = self.get_nomenclature_anm(parent)
        nomenclature_rig = module.rig.nomenclature(suffix=module.rig.nomenclature.type_rig)
        #nomenclature_rig = self.get_nomenclature_rig(parent)

        # TODO: Only use position instead of PyNode or Matrix?
        if ref_tm is None:
            ref_tm = ref.getMatrix(worldSpace=True)
        pos_ref = ref_tm.translate

        # Resolve u and v coordinates
        # todo: check if we really want to resolve the u and v ourself since it's now connected.
        if obj_mesh is None:
            # We'll scan all available geometries and use the one with the shortest distance.
            meshes = libRigging.get_affected_geometries(ref)
            meshes = list(set(meshes) & set(module.rig.get_meshes()))
            obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes(meshes, pos_ref)
        else:
            _, out_u, out_v = libRigging.get_closest_point_on_shape(obj_mesh, pos_ref)

        if u_coord is None:
            u_coord = out_u
        if v_coord is None:
            v_coord = out_v

        if obj_mesh is None:
            raise Exception("Can't find mesh affected by {0}. Skipping doritos ctrl setup.")

        if self.jnt:
            module.debug('Creating doritos on {0} using {1} as reference'.format(obj_mesh, self.jnt))
        else:
            module.debug('Creating doritos on {0}'.format(obj_mesh))


        # Initialize external stack
        # Normally this would be hidden from animators.
        stack_name = nomenclature_rig.resolve('doritosStack')
        stack = classNode.Node(self)
        stack.build(name=stack_name)
        stack.setTranslation(pos_ref)

        # Add sensibility attributes
        # The values will be computed when attach_ctrl will be called
        libAttr.addAttr_separator(
            module.grp_rig,
            "ctrlCalibration"
        )
        self.attr_sensitivity_tx = libAttr.addAttr(
            module.grp_rig,
            longName=self._ATTR_NAME_SENSITIVITY_TX,
            defaultValue=1.0
        )
        self.attr_sensitivity_ty = libAttr.addAttr(
            module.grp_rig,
            longName=self._ATTR_NAME_SENSITIVITY_TY,
            defaultValue=1.0
        )
        self.attr_sensitivity_tz = libAttr.addAttr(
            module.grp_rig,
            longName=self._ATTR_NAME_SENSITIVITY_TZ,
            defaultValue=1.0
        )
        self.attr_sensitivity_tx.set(channelBox=True)
        self.attr_sensitivity_ty.set(channelBox=True)
        self.attr_sensitivity_tz.set(channelBox=True)


        # Note that to only check in the Z axis, we'll do a raycast first.
        # If we success this will become our reference position.
        '''
        pos = pos_ref
        pos.z = 999
        dir = pymel.datatypes.Point(0,0,-1)
        result = next(iter(libRigging.ray_cast(pos, dir, [obj_mesh])), None)
        if result:
            pos_ref = result
            ctrl_tm.translate = result
        '''

        # Create the layer_fol that will follow the geometry
        layer_fol_name = nomenclature_rig.resolve('doritosFol')
        layer_fol = stack.append_layer()
        layer_fol.rename(layer_fol_name)
        #layer_fol.setParent(self.grp_rig)

        # TODO: Validate that we don't need to inverse the rotation separately.
        fol_mesh = None
        if follow_mesh:
            fol_name = nomenclature_rig.resolve('doritosFollicle')
            fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord)
            fol_mesh = fol_shape.getParent()
            self.follicle = fol_mesh
            fol_mesh.rename(fol_name)
            pymel.parentConstraint(fol_mesh, layer_fol, maintainOffset=True)
            fol_mesh.setParent(self.grp_rig)

            # HACK: Fix rotation issues.
            # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since
            # any deformation can affect the normal of the faces.
            jnt_head = module.rig.get_head_jnt()
            if jnt_head:
                pymel.disconnectAttr(layer_fol.rx)
                pymel.disconnectAttr(layer_fol.ry)
                pymel.disconnectAttr(layer_fol.rz)
                pymel.orientConstraint(jnt_head, layer_fol, maintainOffset=True)
        else:
            self.follicle = layer_fol
            pymel.parentConstraint(ref, layer_fol, maintainOffset=True)

        #
        # Constraint a specic controller to the avar doritos stack.
        # Call this method after connecting the ctrl to the necessary avars.
        # The sensibility of the doritos will be automatically computed in this step if necessary.
        #



        # Create inverted attributes for sensibility
        util_sensitivity_inv = libRigging.create_utility_node('multiplyDivide', operation=2,
                                                              input1X=1.0, input1Y=1.0, input1Z=1.0,
                                                              input2X=self.attr_sensitivity_tx,
                                                              input2Y=self.attr_sensitivity_ty,
                                                              input2Z=self.attr_sensitivity_tz
                                                              )
        attr_sensibility_lr_inv = util_sensitivity_inv.outputX
        attr_sensibility_ud_inv = util_sensitivity_inv.outputY
        attr_sensibility_fb_inv = util_sensitivity_inv.outputZ

        # Add an inverse node that will counter animate the position of the ctrl.
        # TODO: Rename
        layer_doritos_name = nomenclature_rig.resolve('doritosInv')
        layer_doritos = pymel.createNode('transform', name=layer_doritos_name)
        layer_doritos.setParent(stack.node)

        # Create inverse attributes for the ctrl
        attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide', input1=self.node.t, input2=[-1, -1, -1]).output
        attr_ctrl_inv_r = libRigging.create_utility_node('multiplyDivide', input1=self.node.r, input2=[-1, -1, -1]).output
        attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide',
                                                         input1=attr_ctrl_inv_t,
                                                         input2X=self.attr_sensitivity_tx,
                                                         input2Y=self.attr_sensitivity_ty,
                                                         input2Z=self.attr_sensitivity_tz
                                                         ).output

        if flip_lr:
            attr_doritos_tx = libRigging.create_utility_node('multiplyDivide',
                                                             input1X=attr_ctrl_inv_t.outputX,
                                                             input2X=-1
                                                             ).outputX
        else:
            attr_doritos_tx = attr_ctrl_inv_t.outputX
        attr_doritos_ty = attr_ctrl_inv_t.outputY
        attr_doritos_tz = attr_ctrl_inv_t.outputZ

        pymel.connectAttr(attr_doritos_tx, layer_doritos.tx)
        pymel.connectAttr(attr_doritos_ty, layer_doritos.ty)
        pymel.connectAttr(attr_doritos_tz, layer_doritos.tz)
        pymel.connectAttr(attr_ctrl_inv_r, layer_doritos.r)

        # Apply scaling on the ctrl parent.
        # This is were the 'black magic' happen.
        if flip_lr:
            attr_ctrl_offset_sx_inn = libRigging.create_utility_node('multiplyDivide',
                                                                     input1X=self.attr_sensitivity_tx,
                                                                     input2X=-1
                                                                     ).outputX
        else:
            attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx
        attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty
        attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz

        pymel.connectAttr(attr_ctrl_offset_sx_inn, self.offset.scaleX)
        pymel.connectAttr(attr_ctrl_offset_sy_inn, self.offset.scaleY)
        pymel.connectAttr(attr_ctrl_offset_sz_inn, self.offset.scaleZ)

        # Apply sensibility on the ctrl shape
        ctrl_shape = self.node.getShape()
        tmp = pymel.duplicate(self.node.getShape())[0]
        ctrl_shape_orig = tmp.getShape()
        ctrl_shape_orig.setParent(self.node, relative=True, shape=True)
        ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name()))
        pymel.delete(tmp)
        ctrl_shape_orig.intermediateObject.set(True)

        for cp in ctrl_shape.cp:
            cp.set(0,0,0)

        # Counter-scale the shape
        attr_adjustement_sx_inn = attr_sensibility_lr_inv
        attr_adjustement_sy_inn = attr_sensibility_ud_inv
        attr_adjustement_sz_inn = attr_sensibility_fb_inv
        attr_adjustement_scale = libRigging.create_utility_node('composeMatrix',
                                                             inputScaleX=attr_adjustement_sx_inn,
                                                             inputScaleY=attr_adjustement_sy_inn,
                                                             inputScaleZ=attr_adjustement_sz_inn
                                                             ).outputMatrix

        attr_adjustement_rot = libRigging.create_utility_node('composeMatrix',
                                                              inputRotateX=self.node.rotateX,
                                                              inputRotateY=self.node.rotateY,
                                                              inputRotateZ=self.node.rotateZ
                                                              ).outputMatrix

        attr_adjustement_rot_inv = libRigging.create_utility_node('inverseMatrix', inputMatrix=attr_adjustement_rot).outputMatrix

        attr_adjustement_tm = libRigging.create_utility_node('multMatrix', matrixIn=[
            attr_adjustement_rot,
            attr_adjustement_scale,
            attr_adjustement_rot_inv
        ]).matrixSum

        attr_transform_geometry = libRigging.create_utility_node('transformGeometry', transform=attr_adjustement_tm,
                                                                 inputGeometry=ctrl_shape_orig.local).outputGeometry
        pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True)

        # Constraint ctrl
        pymel.parentConstraint(layer_doritos, self.offset, maintainOffset=False, skipRotate=['x', 'y', 'z'])
        pymel.orientConstraint(layer_doritos.getParent(), self.offset, maintainOffset=True)

        # Clean dag junk
        if grp_rig:
            stack.setParent(grp_rig)
            if fol_mesh:
                fol_mesh.setParent(grp_rig)
Ejemplo n.º 5
0
    def build_stack(self, stack, mult_u=1.0, mult_v=1.0):
        """
        The dag stack is a chain of transform nodes daisy chained together that computer the final transformation of the influence.
        The decision of using transforms instead of multMatrix nodes is for clarity.
        Note also that because of it's parent (the offset node) the stack relative to the influence original translation.
        """
        # TODO: Maybe use sub-classing to differenciate when we need to use a surface or not.
        nomenclature_rig = self.get_nomenclature_rig()

        #
        # Extract the base U and V of the base influence using the stack parent. (the 'offset' node)
        #
        surface_shape = self.surface.getShape()

        util_get_base_uv_absolute = libRigging.create_utility_node(
            'closestPointOnSurface',
            inPosition=self._grp_offset.t,
            inputSurface=surface_shape.worldSpace)

        util_get_base_uv_normalized = libRigging.create_utility_node(
            'setRange',
            oldMinX=surface_shape.minValueU,
            oldMaxX=surface_shape.maxValueU,
            oldMinY=surface_shape.minValueV,
            oldMaxY=surface_shape.maxValueV,
            minX=0,
            maxX=1,
            minY=0,
            maxY=1,
            valueX=util_get_base_uv_absolute.parameterU,
            valueY=util_get_base_uv_absolute.parameterV)
        attr_base_u_normalized = util_get_base_uv_normalized.outValueX
        attr_base_v_normalized = util_get_base_uv_normalized.outValueY

        self._attr_u_base = libAttr.addAttr(
            self.grp_rig,
            longName=self._ATTR_NAME_U_BASE,
            defaultValue=attr_base_u_normalized.get())
        self._attr_v_base = libAttr.addAttr(
            self.grp_rig,
            longName=self._ATTR_NAME_V_BASE,
            defaultValue=attr_base_v_normalized.get())

        pymel.connectAttr(attr_base_u_normalized,
                          self.grp_rig.attr(self._ATTR_NAME_U_BASE))
        pymel.connectAttr(attr_base_v_normalized,
                          self.grp_rig.attr(self._ATTR_NAME_V_BASE))

        #
        # Create follicle setup
        # The setup is composed of two follicles.
        # One for the "bind pose" and one "driven" by the avars..
        # The delta between the "bind pose" and the "driven" follicles is then applied to the influence.
        #

        # Determine the follicle U and V on the reference nurbsSurface.
        # jnt_pos = self.jnt.getTranslation(space='world')
        # fol_pos, fol_u, fol_v = libRigging.get_closest_point_on_surface(self.surface, jnt_pos)
        base_u_val = self._attr_u_base.get()
        base_v_val = self._attr_v_base.get()

        # Resolve the length of each axis of the surface
        self._attr_length_u, self._attr_length_v, arcdimension_shape = libRigging.create_arclengthdimension_for_nurbsplane(
            self.surface)
        arcdimension_transform = arcdimension_shape.getParent()
        arcdimension_transform.rename(nomenclature_rig.resolve('arcdimension'))
        arcdimension_transform.setParent(self.grp_rig)

        #
        # Create two follicle.
        # - influenceFollicle: Affected by the ud and lr Avar
        # - bindPoseFollicle: A follicle that stay in place and keep track of the original position.
        # We'll then compute the delta of the position of the two follicles.
        # This allow us to move or resize the plane without affecting the built rig. (if the rig is in neutral pose)
        #
        offset_name = nomenclature_rig.resolve('bindPoseRef')
        obj_offset = pymel.createNode('transform', name=offset_name)
        obj_offset.setParent(self._grp_offset)

        fol_offset_name = nomenclature_rig.resolve('bindPoseFollicle')
        # fol_offset = libRigging.create_follicle(obj_offset, self.surface, name=fol_offset_name)
        fol_offset_shape = libRigging.create_follicle2(self.surface,
                                                       u=base_u_val,
                                                       v=base_v_val)
        fol_offset = fol_offset_shape.getParent()
        fol_offset.rename(fol_offset_name)
        pymel.parentConstraint(fol_offset, obj_offset, maintainOffset=False)
        fol_offset.setParent(self.grp_rig)

        # Create the influence follicle
        influence_name = nomenclature_rig.resolve('influenceRef')
        influence = pymel.createNode('transform', name=influence_name)
        influence.setParent(self._grp_offset)

        fol_influence_name = nomenclature_rig.resolve('influenceFollicle')
        fol_influence_shape = libRigging.create_follicle2(self.surface,
                                                          u=base_u_val,
                                                          v=base_v_val)
        fol_influence = fol_influence_shape.getParent()
        fol_influence.rename(fol_influence_name)
        pymel.parentConstraint(fol_influence, influence, maintainOffset=False)
        fol_influence.setParent(self.grp_rig)

        #
        # Extract the delta of the influence follicle and it's initial pose follicle
        #
        attr_localTM = libRigging.create_utility_node(
            'multMatrix',
            matrixIn=[influence.worldMatrix,
                      obj_offset.worldInverseMatrix]).matrixSum

        # Since we are extracting the delta between the influence and the bindpose matrix, the rotation of the surface
        # is not taken in consideration wich make things less intuitive for the rigger.
        # So we'll add an adjustement matrix so the rotation of the surface is taken in consideration.
        util_decomposeTM_bindPose = libRigging.create_utility_node(
            'decomposeMatrix', inputMatrix=obj_offset.worldMatrix)
        attr_translateTM = libRigging.create_utility_node(
            'composeMatrix',
            inputTranslate=util_decomposeTM_bindPose.outputTranslate
        ).outputMatrix
        attr_translateTM_inv = libRigging.create_utility_node(
            'inverseMatrix',
            inputMatrix=attr_translateTM,
        ).outputMatrix
        attr_rotateTM = libRigging.create_utility_node(
            'multMatrix',
            matrixIn=[obj_offset.worldMatrix, attr_translateTM_inv]).matrixSum
        attr_rotateTM_inv = libRigging.create_utility_node(
            'inverseMatrix', inputMatrix=attr_rotateTM).outputMatrix
        attr_finalTM = libRigging.create_utility_node(
            'multMatrix',
            matrixIn=[attr_rotateTM_inv, attr_localTM,
                      attr_rotateTM]).matrixSum

        util_decomposeTM = libRigging.create_utility_node(
            'decomposeMatrix', inputMatrix=attr_finalTM)

        #
        # Resolve the parameterU and parameterV
        #
        self._attr_u_mult_inn = libAttr.addAttr(
            self.grp_rig,
            longName=self._ATTR_NAME_U_MULT,
            defaultValue=self._AVAR_DEFAULT_MULTIPLIER_U)
        self._attr_v_mult_inn = libAttr.addAttr(
            self.grp_rig,
            longName=self._ATTR_NAME_V_MULT,
            defaultValue=self._AVAR_DEFAULT_MULTIPLIER_V)
        attr_u_inn, attr_v_inn = self._get_follicle_absolute_uv_attr()

        #
        # Create the 1st (follicleLayer) that will contain the extracted position from the ud and lr Avar.
        #
        layer_follicle = stack.append_layer('follicleLayer')
        pymel.connectAttr(util_decomposeTM.outputTranslate,
                          layer_follicle.translate)

        pymel.connectAttr(attr_u_inn, fol_influence.parameterU)
        pymel.connectAttr(attr_v_inn, fol_influence.parameterV)
        pymel.connectAttr(self._attr_u_base, fol_offset.parameterU)
        pymel.connectAttr(self._attr_v_base, fol_offset.parameterV)

        #
        # The second layer (oobLayer for out-of-bound) that allow the follicle to go outside it's original plane.
        # If the UD value is out the nurbsPlane UV range (0-1), ie 1.1, we'll want to still offset the follicle.
        # For that we'll compute a delta between a small increment (0.99 and 1.0) and multiply it.
        #
        nomenclature_rig = self.get_nomenclature_rig()
        oob_step_size = 0.001  # TODO: Expose a Maya attribute?

        fol_clamped_v_name = nomenclature_rig.resolve('influenceClampedV')
        fol_clamped_v_shape = libRigging.create_follicle2(self.surface,
                                                          u=base_u_val,
                                                          v=base_v_val)
        fol_clamped_v = fol_clamped_v_shape.getParent()
        fol_clamped_v.rename(fol_clamped_v_name)
        fol_clamped_v.setParent(self.grp_rig)

        fol_clamped_u_name = nomenclature_rig.resolve('influenceClampedU')
        fol_clamped_u_shape = libRigging.create_follicle2(self.surface,
                                                          u=base_u_val,
                                                          v=base_v_val)
        fol_clamped_u = fol_clamped_u_shape.getParent()
        fol_clamped_u.rename(fol_clamped_u_name)
        fol_clamped_u.setParent(self.grp_rig)

        # Clamp the values so they never fully reach 0 or 1 for U and V.
        util_clamp_uv = libRigging.create_utility_node(
            'clamp',
            inputR=attr_u_inn,
            inputG=attr_v_inn,
            minR=oob_step_size,
            minG=oob_step_size,
            maxR=1.0 - oob_step_size,
            maxG=1.0 - oob_step_size)
        clamped_u = util_clamp_uv.outputR
        clamped_v = util_clamp_uv.outputG

        pymel.connectAttr(clamped_v, fol_clamped_v.parameterV)
        pymel.connectAttr(attr_u_inn, fol_clamped_v.parameterU)

        pymel.connectAttr(attr_v_inn, fol_clamped_u.parameterV)
        pymel.connectAttr(clamped_u, fol_clamped_u.parameterU)

        # Compute the direction to add for U and V if we are out-of-bound.
        dir_oob_u = libRigging.create_utility_node(
            'plusMinusAverage',
            operation=2,
            input3D=[fol_influence.translate,
                     fol_clamped_u.translate]).output3D
        dir_oob_v = libRigging.create_utility_node(
            'plusMinusAverage',
            operation=2,
            input3D=[fol_influence.translate,
                     fol_clamped_v.translate]).output3D

        # Compute the offset to add for U and V

        condition_oob_u_neg = libRigging.create_utility_node(
            'condition',
            operation=4,  # less than
            firstTerm=attr_u_inn,
            secondTerm=0.0,
            colorIfTrueR=1.0,
            colorIfFalseR=0.0,
        ).outColorR
        condition_oob_u_pos = libRigging.create_utility_node(
            'condition',  # greater than
            operation=2,
            firstTerm=attr_u_inn,
            secondTerm=1.0,
            colorIfTrueR=1.0,
            colorIfFalseR=0.0,
        ).outColorR
        condition_oob_v_neg = libRigging.create_utility_node(
            'condition',
            operation=4,  # less than
            firstTerm=attr_v_inn,
            secondTerm=0.0,
            colorIfTrueR=1.0,
            colorIfFalseR=0.0,
        ).outColorR
        condition_oob_v_pos = libRigging.create_utility_node(
            'condition',  # greater than
            operation=2,
            firstTerm=attr_v_inn,
            secondTerm=1.0,
            colorIfTrueR=1.0,
            colorIfFalseR=0.0,
        ).outColorR

        # Compute the amount of oob
        oob_val_u_pos = libRigging.create_utility_node(
            'plusMinusAverage', operation=2, input1D=[attr_u_inn,
                                                      1.0]).output1D
        oob_val_u_neg = libRigging.create_utility_node('multiplyDivide',
                                                       input1X=attr_u_inn,
                                                       input2X=-1.0).outputX
        oob_val_v_pos = libRigging.create_utility_node(
            'plusMinusAverage', operation=2, input1D=[attr_v_inn,
                                                      1.0]).output1D
        oob_val_v_neg = libRigging.create_utility_node('multiplyDivide',
                                                       input1X=attr_v_inn,
                                                       input2X=-1.0).outputX
        oob_val_u = libRigging.create_utility_node(
            'condition',
            operation=0,
            firstTerm=condition_oob_u_pos,
            secondTerm=1.0,
            colorIfTrueR=oob_val_u_pos,
            colorIfFalseR=oob_val_u_neg).outColorR
        oob_val_v = libRigging.create_utility_node(
            'condition',
            operation=0,
            firstTerm=condition_oob_v_pos,
            secondTerm=1.0,
            colorIfTrueR=oob_val_v_pos,
            colorIfFalseR=oob_val_v_neg).outColorR

        oob_amount_u = libRigging.create_utility_node(
            'multiplyDivide',
            operation=2,
            input1X=oob_val_u,
            input2X=oob_step_size).outputX
        oob_amount_v = libRigging.create_utility_node(
            'multiplyDivide',
            operation=2,
            input1X=oob_val_v,
            input2X=oob_step_size).outputX

        oob_offset_u = libRigging.create_utility_node('multiplyDivide',
                                                      input1X=oob_amount_u,
                                                      input1Y=oob_amount_u,
                                                      input1Z=oob_amount_u,
                                                      input2=dir_oob_u).output
        oob_offset_v = libRigging.create_utility_node('multiplyDivide',
                                                      input1X=oob_amount_v,
                                                      input1Y=oob_amount_v,
                                                      input1Z=oob_amount_v,
                                                      input2=dir_oob_v).output

        # Add the U out-of-bound-offset only if the U is between 0.0 and 1.0
        oob_u_condition_1 = condition_oob_u_neg
        oob_u_condition_2 = condition_oob_u_pos
        oob_u_condition_added = libRigging.create_utility_node(
            'addDoubleLinear',
            input1=oob_u_condition_1,
            input2=oob_u_condition_2).output
        oob_u_condition_out = libRigging.create_utility_node(
            'condition',
            operation=0,  # equal
            firstTerm=oob_u_condition_added,
            secondTerm=1.0,
            colorIfTrue=oob_offset_u,
            colorIfFalse=[0, 0, 0]).outColor

        # Add the V out-of-bound-offset only if the V is between 0.0 and 1.0
        oob_v_condition_1 = condition_oob_v_neg
        oob_v_condition_2 = condition_oob_v_pos
        oob_v_condition_added = libRigging.create_utility_node(
            'addDoubleLinear',
            input1=oob_v_condition_1,
            input2=oob_v_condition_2).output
        oob_v_condition_out = libRigging.create_utility_node(
            'condition',
            operation=0,  # equal
            firstTerm=oob_v_condition_added,
            secondTerm=1.0,
            colorIfTrue=oob_offset_v,
            colorIfFalse=[0, 0, 0]).outColor

        oob_offset = libRigging.create_utility_node(
            'plusMinusAverage',
            input3D=[oob_u_condition_out, oob_v_condition_out]).output3D

        layer_oob = stack.append_layer('oobLayer')
        pymel.connectAttr(oob_offset, layer_oob.t)

        #
        # Create the third layer that apply the translation provided by the fb Avar.
        #

        layer_fb = stack.append_layer('fbLayer')
        attr_get_fb = libRigging.create_utility_node(
            'multiplyDivide',
            input1X=self.attr_fb,
            input2X=self._attr_length_u).outputX
        attr_get_fb_adjusted = libRigging.create_utility_node(
            'multiplyDivide', input1X=attr_get_fb, input2X=0.1).outputX
        pymel.connectAttr(attr_get_fb_adjusted, layer_fb.translateZ)

        #
        # Create the 4th layer (folRot) that apply the rotation provided by the follicle controlled by the ud and lr Avar.
        # This is necessary since we don't want to rotation to affect the oobLayer and fbLayer.
        #
        layer_follicle_rot = stack.append_layer('folRot')
        pymel.connectAttr(util_decomposeTM.outputRotate,
                          layer_follicle_rot.rotate)

        #
        # Create a 5th layer that apply the yw, pt, rl and Avar.
        #
        layer_rot = stack.append_layer('rotLayer')
        pymel.connectAttr(self.attr_yw, layer_rot.rotateY)
        pymel.connectAttr(self.attr_pt, layer_rot.rotateX)
        pymel.connectAttr(self.attr_rl, layer_rot.rotateZ)

        return stack
    def build(self, ctrl_size_max=None, ctrl_size_min=None, parent=True, **kwargs):
        """
        :param ctrl_size_max: Used to automatically size layer ctrls. Define the maximum size (applied on first layer)
        :param ctrl_size_min: Used to automatically size layer ctrls. Define the minimum size (applied on last layer)
        :param parent: Redefined to compensate for bad design. Identical implementation than base class.
        :param kwargs: Any keyword argument will be forwarded to the base method.
        """
        super(InteractiveFK, self).build(parent=None, **kwargs)

        nomenclature_rig_grp = self.get_nomenclature_rig_grp()
        nomenclature_jnt = self.get_nomenclature_jnt()

        # Create a group that we will parent all surfaces to.
        self._grp_surfaces = pymel.createNode(
            'transform',
            name=nomenclature_rig_grp.resolve('surfaces'),
            parent=self.grp_rig
        )

        self._init_layers()

        # Resolve default ctrl_size
        if ctrl_size_min is None or ctrl_size_max is None:
            val = self._get_default_ctrl_size()
            ctrl_size_max = val * 0.25
            ctrl_size_min = ctrl_size_max / float(len(self.layers))
            self.info('Default ctrl size is adjusted from bettwen {} at {}'.format(ctrl_size_min, ctrl_size_max))

        self._build_layers(
            ctrl_size_max=ctrl_size_max,
            ctrl_size_min=ctrl_size_min
        )
        # Create a group that represent the original parent of everything.
        # This allow use to supported non-uniform scaling by using direct connections instead of parent/scaleConstraint.
        parent_obj = self.get_parent_obj()
        self._grp_parent = pymel.createNode(
            'transform',
            name=nomenclature_rig_grp.resolve('parent'),
            parent=self.grp_rig
        )
        # Rig parenting
        if parent_obj:
            self._grp_parent.setMatrix(parent_obj.getMatrix(worldSpace=True))

        # For each influence, create a follicle that will follow the final mesh.
        unassigned_influences = self._get_unassigned_influences()
        last_surface = self.layers[-1].get_surface()

        if unassigned_influences and last_surface:
            grp_follicles = pymel.createNode(
                'transform',
                name=nomenclature_rig_grp.resolve('follicles'),
                parent=self.grp_rig,
            )

            for i, jnt in enumerate(unassigned_influences):
                nomenclature = nomenclature_jnt + self.rig.nomenclature(jnt.stripNamespace().nodeName())

                # Get the final LOCAL transformation of the influence.
                # If we have a parent, we'll want to convert it to WORLD transformation.
                pos = jnt.getTranslation(space='world')
                _, u, v = libRigging.get_closest_point_on_surface(last_surface, pos)
                fol_shape = libRigging.create_follicle2(last_surface, u, v, connect_transform=True)
                fol_transform = fol_shape.getParent()
                fol_transform.rename(nomenclature.resolve())
                fol_transform.setParent(grp_follicles)

                # Connect the influence.
                # Note that we don't apply any scale constraining since we assume that all influence have
                # the same common parent that drive the scale.
                if parent_obj:
                    # Use an extra object to match original influence transform.
                    grp_output = pymel.createNode(
                        'transform',
                        name=nomenclature_jnt.resolve('output{}'.format(i)),
                        parent=self._grp_parent
                    )
                    grp_output.setMatrix(jnt.getMatrix(worldSpace=True), worldSpace=True)
                    pymel.parentConstraint(fol_transform, grp_output, maintainOffset=True)

                    # Hack: Reset joint orient so our direct connection work...
                    # todo: use compose matrix?
                    if isinstance(jnt, pymel.nodetypes.Joint):
                        jnt.jointOrientX.set(0.0)
                        jnt.jointOrientY.set(0.0)
                        jnt.jointOrientZ.set(0.0)

                    libAttr.connect_transform_attrs(grp_output, jnt, sx=False, sy=False, sz=False)

                else:
                    pymel.parentConstraint(fol_transform, jnt, maintainOffset=True)

        # Manually parent the module with support for scaling.
        if parent_obj and parent_obj != self.grp_anm:
            pymel.parentConstraint(parent_obj, self.grp_anm, maintainOffset=True)
            pymel.scaleConstraint(parent_obj, self.grp_anm, maintainOffset=True)
    def build(self, module, create_follicle=True, pos=None, shape=None, shape_next=None, u_coord=None, v_coord=None,
              constraint=True, **kwargs):
        """
        :param pos: The position to use when seeking where to create the follicle. Can be resolved automatically if the module have an influence.
        :param shape: The shape to create the follicle on. Can be resolved automatically if the module have an influence.
        :param u_coord: The U coordinate to use for the follicle. Can be resolved automatically if the module have an influence.
        :param v_coord: The V coordinate to use for the follicle. Can be resolved automatically if the module have an influence.
        :param constraint: If True, the ctrl will drive the influence via direct connect.
        :param kwargs: Any additional keyword argument will be passed to the parent method.
        """
        super(InteractiveFKCtrlModel, self).build(
            module,
            parent=None,  # We handle the parenting ourself!
            **kwargs
        )

        nomenclature_rig = self.get_nomenclature_rig()

        # Resolve bind position.
        if pos is None:
            pos = self.get_bind_pos()

        #
        # Create the 'bind' node that will follow the follicle in translation and something else in rotation.
        #

        # Create the a group containing the local offset in case we have an hyerarchy to preserve.
        self._grp_offset = pymel.createNode(
            'transform',
            name=nomenclature_rig.resolve('offset'),
            parent=self.grp_rig
        )
        attr_offset_tm = self._grp_offset.matrix

        # Create a reference to the previous deformation
        self._grp_bind = pymel.createNode(
            'transform',
            name=nomenclature_rig.resolve('follicle'),
            parent=self.grp_rig
        )
        # self._layer_bind = self._stack.append_layer()
        # self._layer_bind.rename(layer_fol_name)
        self._grp_bind.setMatrix(self.get_bind_tm())
        # self._layer_bind.setParent(self.grp_rig)
        attr_bind_tm = self._grp_bind.matrix
        attr_bind_tm_inv = self._grp_bind.inverseMatrix

        # Compute the parent offset and the deformation offset toguether.
        attr_total_offset = libRigging.create_utility_node(
            'multMatrix',
            name=nomenclature_rig.resolve('getOffset'),
            matrixIn=(
                attr_offset_tm,
                attr_bind_tm,
            )
        ).matrixSum

        # Create follicle to track the transform BEFORE the ctrl.
        if create_follicle:
            # Resolve mesh if necessary.
            if not shape:
                shape = self.get_default_shape()
            if not shape:
                raise Exception("Can't resolve mesh to attach to!")

            # Resolve uv coords if necessary
            if u_coord is None or v_coord is None:
                _, u_coord, v_coord = libRigging.get_closest_point_on_shape(shape, pos)
            if u_coord is None or v_coord is None:
                raise Exception("Can't resolve uv coordinates to use!")

            fol_shape = libRigging.create_follicle2(shape, u=u_coord, v=v_coord)
            ref_before = fol_shape.getParent()
            ref_before.rename(nomenclature_rig.resolve('preCtrl'))
            ref_before.setParent(self.grp_rig)
        else:
            ref_before = pymel.createNode(
                'transform',
                name=nomenclature_rig.resolve('preCtrl'),
                parent=self.grp_rig
            )
            ref_before.setMatrix(self.get_bind_tm())

        pymel.parentConstraint(ref_before, self._grp_bind, maintainOffset=True)

        # Create follicle to track the transfort AFTER the ctrl.
        # This will be used to position the controller correctly.
        if shape_next:
            # Resolve uv coords if necessary
            _, u_coord, v_coord = libRigging.get_closest_point_on_shape(shape_next, pos)
            if u_coord is None or v_coord is None:
                raise Exception("Can't resolve uv coordinates to use!")

            fol_shape = libRigging.create_follicle2(shape_next, u=u_coord, v=v_coord)
            ref_after = fol_shape.getParent()
            ref_after.rename(nomenclature_rig.resolve('postCtrl'))
            ref_after.setParent(self.grp_rig)

            self.follicle = ref_after

        #
        # Constraint grp_anm
        #

        # Create an output object that will hold the world position of the ctrl offset.
        # This allow us to create direct connection which simplify the dag tree for the animator
        # and allow us to easily scale the whole setup to support non-uniform scaling.

        util_decompose_offset = libRigging.create_utility_node(
            'decomposeMatrix',
            inputMatrix=attr_total_offset
        )

        pymel.connectAttr(util_decompose_offset.outputTranslate, self.ctrl.offset.translate)
        pymel.connectAttr(util_decompose_offset.outputRotate, self.ctrl.offset.rotate)

        #
        # Create an output group that contain the new joint position
        #
        grp_scale = pymel.createNode(
            'transform',
            name=nomenclature_rig.resolve('parent'),
            parent=self.grp_rig
        )
        self._grp_output = pymel.createNode(
            'transform',
            name=nomenclature_rig.resolve('output'),
            parent=grp_scale
        )

        attr_get_local_tm = libRigging.create_utility_node(
            'multMatrix',
            matrixIn=(
                self.ctrl.matrix,
                attr_total_offset
            )
        ).matrixSum

        util_decompose_local_tm = libRigging.create_utility_node(
            'decomposeMatrix',
            inputMatrix=attr_get_local_tm
        )

        pymel.connectAttr(util_decompose_local_tm.outputTranslate, self._grp_output.translate)
        pymel.connectAttr(util_decompose_local_tm.outputRotate, self._grp_output.rotate)
        pymel.connectAttr(util_decompose_local_tm.outputScale, self._grp_output.scale)

        pymel.parentConstraint(self._grp_output, self.jnt, maintainOffset=True)
        pymel.scaleConstraint(self._grp_output, self.jnt, maintainOffset=True)

        surface = self.get_surface()
        skincluster = _get_immediate_skincluster(surface)
        index = libSkinning.get_skin_cluster_influence_objects(skincluster).index(self.jnt)
        pymel.connectAttr(attr_bind_tm_inv, skincluster.bindPreMatrix[index], force=True)
Ejemplo n.º 8
0
    def build_stack(self, stack, mult_u=1.0, mult_v=1.0):
        """
        The dag stack is a chain of transform nodes daisy chained together that computer the final transformation of the influence.
        The decision of using transforms instead of multMatrix nodes is for clarity.
        Note also that because of it's parent (the offset node) the stack relative to the influence original translation.
        """
        # TODO: Maybe use sub-classing to differenciate when we need to use a surface or not.
        nomenclature_rig = self.get_nomenclature_rig()

        #
        # Extract the base U and V of the base influence using the stack parent. (the 'offset' node)
        #
        surface_shape = self.surface.getShape()

        util_get_base_uv_absolute = libRigging.create_utility_node(
            'closestPointOnSurface',
            inPosition=self._grp_offset.t,
            inputSurface=surface_shape.worldSpace
        )

        util_get_base_uv_normalized = libRigging.create_utility_node(
            'setRange',
            oldMinX=surface_shape.minValueU,
            oldMaxX=surface_shape.maxValueU,
            oldMinY=surface_shape.minValueV,
            oldMaxY=surface_shape.maxValueV,
            minX=0,
            maxX=1,
            minY=0,
            maxY=1,
            valueX=util_get_base_uv_absolute.parameterU,
            valueY=util_get_base_uv_absolute.parameterV
        )
        attr_base_u_normalized = util_get_base_uv_normalized.outValueX
        attr_base_v_normalized = util_get_base_uv_normalized.outValueY

        self._attr_u_base = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U_BASE, defaultValue=attr_base_u_normalized.get())
        self._attr_v_base = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V_BASE, defaultValue=attr_base_v_normalized.get())

        pymel.connectAttr(attr_base_u_normalized, self.grp_rig.attr(self._ATTR_NAME_U_BASE))
        pymel.connectAttr(attr_base_v_normalized, self.grp_rig.attr(self._ATTR_NAME_V_BASE))

        #
        # Create follicle setup
        # The setup is composed of two follicles.
        # One for the "bind pose" and one "driven" by the avars..
        # The delta between the "bind pose" and the "driven" follicles is then applied to the influence.
        #

        # Determine the follicle U and V on the reference nurbsSurface.
        # jnt_pos = self.jnt.getTranslation(space='world')
        # fol_pos, fol_u, fol_v = libRigging.get_closest_point_on_surface(self.surface, jnt_pos)
        base_u_val = self._attr_u_base.get()
        base_v_val = self._attr_v_base.get()

        # Resolve the length of each axis of the surface
        self._attr_length_u, self._attr_length_v, arcdimension_shape = libRigging.create_arclengthdimension_for_nurbsplane(self.surface)
        arcdimension_transform = arcdimension_shape.getParent()
        arcdimension_transform.rename(nomenclature_rig.resolve('arcdimension'))
        arcdimension_transform.setParent(self.grp_rig)

        #
        # Create two follicle.
        # - influenceFollicle: Affected by the ud and lr Avar
        # - bindPoseFollicle: A follicle that stay in place and keep track of the original position.
        # We'll then compute the delta of the position of the two follicles.
        # This allow us to move or resize the plane without affecting the built rig. (if the rig is in neutral pose)
        #
        offset_name = nomenclature_rig.resolve('bindPoseRef')
        obj_offset = pymel.createNode('transform', name=offset_name)
        obj_offset.setParent(self._grp_offset)

        fol_offset_name = nomenclature_rig.resolve('bindPoseFollicle')
        # fol_offset = libRigging.create_follicle(obj_offset, self.surface, name=fol_offset_name)
        fol_offset_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val)
        fol_offset = fol_offset_shape.getParent()
        fol_offset.rename(fol_offset_name)
        pymel.parentConstraint(fol_offset, obj_offset, maintainOffset=False)
        fol_offset.setParent(self.grp_rig)

        # Create the influence follicle
        influence_name = nomenclature_rig.resolve('influenceRef')
        influence = pymel.createNode('transform', name=influence_name)
        influence.setParent(self._grp_offset)

        fol_influence_name = nomenclature_rig.resolve('influenceFollicle')
        fol_influence_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val)
        fol_influence = fol_influence_shape.getParent()
        fol_influence.rename(fol_influence_name)
        pymel.parentConstraint(fol_influence, influence, maintainOffset=False)
        fol_influence.setParent(self.grp_rig)

        #
        # Extract the delta of the influence follicle and it's initial pose follicle
        #
        attr_localTM = libRigging.create_utility_node('multMatrix', matrixIn=[
            influence.worldMatrix,
            obj_offset.worldInverseMatrix
        ]).matrixSum

        # Since we are extracting the delta between the influence and the bindpose matrix, the rotation of the surface
        # is not taken in consideration wich make things less intuitive for the rigger.
        # So we'll add an adjustement matrix so the rotation of the surface is taken in consideration.
        util_decomposeTM_bindPose = libRigging.create_utility_node('decomposeMatrix',
                                                                   inputMatrix=obj_offset.worldMatrix
                                                                   )
        attr_translateTM = libRigging.create_utility_node('composeMatrix',
                                                          inputTranslate=util_decomposeTM_bindPose.outputTranslate
                                                          ).outputMatrix
        attr_translateTM_inv = libRigging.create_utility_node('inverseMatrix',
                                                              inputMatrix=attr_translateTM,
                                                              ).outputMatrix
        attr_rotateTM = libRigging.create_utility_node('multMatrix',
                                                       matrixIn=[obj_offset.worldMatrix, attr_translateTM_inv]
                                                       ).matrixSum
        attr_rotateTM_inv = libRigging.create_utility_node('inverseMatrix',
                                                           inputMatrix=attr_rotateTM
                                                           ).outputMatrix
        attr_finalTM = libRigging.create_utility_node('multMatrix',
                                                      matrixIn=[attr_rotateTM_inv,
                                                                attr_localTM,
                                                                attr_rotateTM]
                                                      ).matrixSum

        util_decomposeTM = libRigging.create_utility_node('decomposeMatrix',
                                                          inputMatrix=attr_finalTM
                                                          )

        #
        # Resolve the parameterU and parameterV
        #
        self._attr_u_mult_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U_MULT, defaultValue=self._AVAR_DEFAULT_MULTIPLIER_U)
        self._attr_v_mult_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V_MULT, defaultValue=self._AVAR_DEFAULT_MULTIPLIER_V)
        attr_u_inn, attr_v_inn = self._get_follicle_absolute_uv_attr()

        #
        # Create the 1st (follicleLayer) that will contain the extracted position from the ud and lr Avar.
        #
        layer_follicle = stack.append_layer('follicleLayer')
        pymel.connectAttr(util_decomposeTM.outputTranslate, layer_follicle.translate)


        pymel.connectAttr(attr_u_inn, fol_influence.parameterU)
        pymel.connectAttr(attr_v_inn, fol_influence.parameterV)
        pymel.connectAttr(self._attr_u_base, fol_offset.parameterU)
        pymel.connectAttr(self._attr_v_base, fol_offset.parameterV)

        #
        # The second layer (oobLayer for out-of-bound) that allow the follicle to go outside it's original plane.
        # If the UD value is out the nurbsPlane UV range (0-1), ie 1.1, we'll want to still offset the follicle.
        # For that we'll compute a delta between a small increment (0.99 and 1.0) and multiply it.
        #
        nomenclature_rig = self.get_nomenclature_rig()
        oob_step_size = 0.001  # TODO: Expose a Maya attribute?

        fol_clamped_v_name = nomenclature_rig.resolve('influenceClampedV')
        fol_clamped_v_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val)
        fol_clamped_v = fol_clamped_v_shape.getParent()
        fol_clamped_v.rename(fol_clamped_v_name)
        fol_clamped_v.setParent(self.grp_rig)

        fol_clamped_u_name = nomenclature_rig.resolve('influenceClampedU')
        fol_clamped_u_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val)
        fol_clamped_u = fol_clamped_u_shape.getParent()
        fol_clamped_u.rename(fol_clamped_u_name)
        fol_clamped_u.setParent(self.grp_rig)

        # Clamp the values so they never fully reach 0 or 1 for U and V.
        util_clamp_uv = libRigging.create_utility_node('clamp',
                                                       inputR=attr_u_inn,
                                                       inputG=attr_v_inn,
                                                       minR=oob_step_size,
                                                       minG=oob_step_size,
                                                       maxR=1.0 - oob_step_size,
                                                       maxG=1.0 - oob_step_size)
        clamped_u = util_clamp_uv.outputR
        clamped_v = util_clamp_uv.outputG

        pymel.connectAttr(clamped_v, fol_clamped_v.parameterV)
        pymel.connectAttr(attr_u_inn, fol_clamped_v.parameterU)

        pymel.connectAttr(attr_v_inn, fol_clamped_u.parameterV)
        pymel.connectAttr(clamped_u, fol_clamped_u.parameterU)

        # Compute the direction to add for U and V if we are out-of-bound.
        dir_oob_u = libRigging.create_utility_node('plusMinusAverage',
                                                   operation=2,
                                                   input3D=[
                                                       fol_influence.translate,
                                                       fol_clamped_u.translate
                                                   ]).output3D
        dir_oob_v = libRigging.create_utility_node('plusMinusAverage',
                                                   operation=2,
                                                   input3D=[
                                                       fol_influence.translate,
                                                       fol_clamped_v.translate
                                                   ]).output3D

        # Compute the offset to add for U and V

        condition_oob_u_neg = libRigging.create_utility_node('condition',
                                                             operation=4,  # less than
                                                             firstTerm=attr_u_inn,
                                                             secondTerm=0.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR
        condition_oob_u_pos = libRigging.create_utility_node('condition',  # greater than
                                                             operation=2,
                                                             firstTerm=attr_u_inn,
                                                             secondTerm=1.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR
        condition_oob_v_neg = libRigging.create_utility_node('condition',
                                                             operation=4,  # less than
                                                             firstTerm=attr_v_inn,
                                                             secondTerm=0.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR
        condition_oob_v_pos = libRigging.create_utility_node('condition',  # greater than
                                                             operation=2,
                                                             firstTerm=attr_v_inn,
                                                             secondTerm=1.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR

        # Compute the amount of oob
        oob_val_u_pos = libRigging.create_utility_node('plusMinusAverage', operation=2,
                                                       input1D=[attr_u_inn, 1.0]).output1D
        oob_val_u_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_u_inn, input2X=-1.0).outputX
        oob_val_v_pos = libRigging.create_utility_node('plusMinusAverage', operation=2,
                                                       input1D=[attr_v_inn, 1.0]).output1D
        oob_val_v_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_v_inn, input2X=-1.0).outputX
        oob_val_u = libRigging.create_utility_node('condition', operation=0, firstTerm=condition_oob_u_pos,
                                                   secondTerm=1.0, colorIfTrueR=oob_val_u_pos,
                                                   colorIfFalseR=oob_val_u_neg).outColorR
        oob_val_v = libRigging.create_utility_node('condition', operation=0, firstTerm=condition_oob_v_pos,
                                                   secondTerm=1.0, colorIfTrueR=oob_val_v_pos,
                                                   colorIfFalseR=oob_val_v_neg).outColorR

        oob_amount_u = libRigging.create_utility_node('multiplyDivide', operation=2, input1X=oob_val_u,
                                                      input2X=oob_step_size).outputX
        oob_amount_v = libRigging.create_utility_node('multiplyDivide', operation=2, input1X=oob_val_v,
                                                      input2X=oob_step_size).outputX

        oob_offset_u = libRigging.create_utility_node('multiplyDivide', input1X=oob_amount_u, input1Y=oob_amount_u,
                                                      input1Z=oob_amount_u, input2=dir_oob_u).output
        oob_offset_v = libRigging.create_utility_node('multiplyDivide', input1X=oob_amount_v, input1Y=oob_amount_v,
                                                      input1Z=oob_amount_v, input2=dir_oob_v).output

        # Add the U out-of-bound-offset only if the U is between 0.0 and 1.0
        oob_u_condition_1 = condition_oob_u_neg
        oob_u_condition_2 = condition_oob_u_pos
        oob_u_condition_added = libRigging.create_utility_node('addDoubleLinear',
                                                               input1=oob_u_condition_1,
                                                               input2=oob_u_condition_2
                                                               ).output
        oob_u_condition_out = libRigging.create_utility_node('condition',
                                                             operation=0,  # equal
                                                             firstTerm=oob_u_condition_added,
                                                             secondTerm=1.0,
                                                             colorIfTrue=oob_offset_u,
                                                             colorIfFalse=[0, 0, 0]
                                                             ).outColor

        # Add the V out-of-bound-offset only if the V is between 0.0 and 1.0
        oob_v_condition_1 = condition_oob_v_neg
        oob_v_condition_2 = condition_oob_v_pos
        oob_v_condition_added = libRigging.create_utility_node('addDoubleLinear',
                                                               input1=oob_v_condition_1,
                                                               input2=oob_v_condition_2
                                                               ).output
        oob_v_condition_out = libRigging.create_utility_node('condition',
                                                             operation=0,  # equal
                                                             firstTerm=oob_v_condition_added,
                                                             secondTerm=1.0,
                                                             colorIfTrue=oob_offset_v,
                                                             colorIfFalse=[0, 0, 0]
                                                             ).outColor

        oob_offset = libRigging.create_utility_node('plusMinusAverage',
                                                    input3D=[oob_u_condition_out, oob_v_condition_out]).output3D

        layer_oob = stack.append_layer('oobLayer')
        pymel.connectAttr(oob_offset, layer_oob.t)

        #
        # Create the third layer that apply the translation provided by the fb Avar.
        #

        layer_fb = stack.append_layer('fbLayer')
        attr_get_fb = libRigging.create_utility_node('multiplyDivide',
                                                     input1X=self.attr_fb,
                                                     input2X=self._attr_length_u).outputX
        attr_get_fb_adjusted = libRigging.create_utility_node('multiplyDivide',
                                                              input1X=attr_get_fb,
                                                              input2X=0.1).outputX
        pymel.connectAttr(attr_get_fb_adjusted, layer_fb.translateZ)

        #
        # Create the 4th layer (folRot) that apply the rotation provided by the follicle controlled by the ud and lr Avar.
        # This is necessary since we don't want to rotation to affect the oobLayer and fbLayer.
        #
        layer_follicle_rot = stack.append_layer('folRot')
        pymel.connectAttr(util_decomposeTM.outputRotate, layer_follicle_rot.rotate)

        #
        # Create a 5th layer that apply the yw, pt, rl and Avar.
        #
        layer_rot = stack.append_layer('rotLayer')
        pymel.connectAttr(self.attr_yw, layer_rot.rotateY)
        pymel.connectAttr(self.attr_pt, layer_rot.rotateX)
        pymel.connectAttr(self.attr_rl, layer_rot.rotateZ)

        return stack
Ejemplo n.º 9
0
    def build(self,
              module,
              ref,
              ref_tm=None,
              grp_rig=None,
              obj_mesh=None,
              u_coord=None,
              v_coord=None,
              flip_lr=False,
              follow_mesh=True,
              **kwargs):
        """
        Create an Interactive controller that follow a geometry.
        :param module: ???
        :param ref:
        :param ref_tm:
        :param grp_rig:
        :param obj_mesh:
        :param u_coord:
        :param v_coord:
        :param kwargs:
        :return:
        """

        # HACK: Ensure flipped shapes are correctly restaured...
        # This is necessary since when holded, the scale of the ctrl is set to identity.
        # However ctrl from the right side have an inverted scale on the x axis. -_-
        if flip_lr and libPymel.is_valid_PyNode(self.shapes):
            self.shapes.sx.set(-1)
            pymel.makeIdentity(self.shapes,
                               rotate=True,
                               scale=True,
                               apply=True)

        # todo: Simplify the setup, too many nodes
        super(InteractiveCtrl, self).build(**kwargs)

        #nomenclature_anm = self.get_nomenclature_anm(parent)
        nomenclature_rig = module.rig.nomenclature(
            suffix=module.rig.nomenclature.type_rig)
        #nomenclature_rig = self.get_nomenclature_rig(parent)

        # TODO: Only use position instead of PyNode or Matrix?
        if ref_tm is None:
            ref_tm = ref.getMatrix(worldSpace=True)
        pos_ref = ref_tm.translate

        # Resolve u and v coordinates
        # todo: check if we really want to resolve the u and v ourself since it's now connected.
        if obj_mesh is None:
            # We'll scan all available geometries and use the one with the shortest distance.
            meshes = libRigging.get_affected_geometries(ref)
            meshes = list(set(meshes) & set(module.rig.get_meshes()))
            obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes(
                meshes, pos_ref)
        else:
            _, out_u, out_v = libRigging.get_closest_point_on_shape(
                obj_mesh, pos_ref)

        if u_coord is None:
            u_coord = out_u
        if v_coord is None:
            v_coord = out_v

        if obj_mesh is None:
            raise Exception(
                "Can't find mesh affected by {0}. Skipping doritos ctrl setup."
            )

        if self.jnt:
            module.debug(
                'Creating doritos on {0} using {1} as reference'.format(
                    obj_mesh, self.jnt))
        else:
            module.debug('Creating doritos on {0}'.format(obj_mesh))

        # Initialize external stack
        # Normally this would be hidden from animators.
        stack_name = nomenclature_rig.resolve('doritosStack')
        stack = classNode.Node(self)
        stack.build(name=stack_name)
        stack.setTranslation(pos_ref)

        # Add sensibility attributes
        # The values will be computed when attach_ctrl will be called
        libAttr.addAttr_separator(module.grp_rig, "ctrlCalibration")
        self.attr_sensitivity_tx = libAttr.addAttr(
            module.grp_rig,
            longName=self._ATTR_NAME_SENSITIVITY_TX,
            defaultValue=1.0)
        self.attr_sensitivity_ty = libAttr.addAttr(
            module.grp_rig,
            longName=self._ATTR_NAME_SENSITIVITY_TY,
            defaultValue=1.0)
        self.attr_sensitivity_tz = libAttr.addAttr(
            module.grp_rig,
            longName=self._ATTR_NAME_SENSITIVITY_TZ,
            defaultValue=1.0)
        self.attr_sensitivity_tx.set(channelBox=True)
        self.attr_sensitivity_ty.set(channelBox=True)
        self.attr_sensitivity_tz.set(channelBox=True)

        # Note that to only check in the Z axis, we'll do a raycast first.
        # If we success this will become our reference position.
        '''
        pos = pos_ref
        pos.z = 999
        dir = pymel.datatypes.Point(0,0,-1)
        result = next(iter(libRigging.ray_cast(pos, dir, [obj_mesh])), None)
        if result:
            pos_ref = result
            ctrl_tm.translate = result
        '''

        # Create the layer_fol that will follow the geometry
        layer_fol_name = nomenclature_rig.resolve('doritosFol')
        layer_fol = stack.append_layer()
        layer_fol.rename(layer_fol_name)
        #layer_fol.setParent(self.grp_rig)

        # TODO: Validate that we don't need to inverse the rotation separately.
        fol_mesh = None
        if follow_mesh:
            fol_name = nomenclature_rig.resolve('doritosFollicle')
            fol_shape = libRigging.create_follicle2(obj_mesh,
                                                    u=u_coord,
                                                    v=v_coord)
            fol_mesh = fol_shape.getParent()
            self.follicle = fol_mesh
            fol_mesh.rename(fol_name)
            pymel.parentConstraint(fol_mesh, layer_fol, maintainOffset=True)
            fol_mesh.setParent(self.grp_rig)

            # HACK: Fix rotation issues.
            # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since
            # any deformation can affect the normal of the faces.
            jnt_head = module.rig.get_head_jnt()
            if jnt_head:
                pymel.disconnectAttr(layer_fol.rx)
                pymel.disconnectAttr(layer_fol.ry)
                pymel.disconnectAttr(layer_fol.rz)
                pymel.orientConstraint(jnt_head,
                                       layer_fol,
                                       maintainOffset=True)
        else:
            self.follicle = layer_fol
            pymel.parentConstraint(ref, layer_fol, maintainOffset=True)

        #
        # Constraint a specic controller to the avar doritos stack.
        # Call this method after connecting the ctrl to the necessary avars.
        # The sensibility of the doritos will be automatically computed in this step if necessary.
        #

        # Create inverted attributes for sensibility
        util_sensitivity_inv = libRigging.create_utility_node(
            'multiplyDivide',
            operation=2,
            input1X=1.0,
            input1Y=1.0,
            input1Z=1.0,
            input2X=self.attr_sensitivity_tx,
            input2Y=self.attr_sensitivity_ty,
            input2Z=self.attr_sensitivity_tz)
        attr_sensibility_lr_inv = util_sensitivity_inv.outputX
        attr_sensibility_ud_inv = util_sensitivity_inv.outputY
        attr_sensibility_fb_inv = util_sensitivity_inv.outputZ

        # Add an inverse node that will counter animate the position of the ctrl.
        # TODO: Rename
        layer_doritos_name = nomenclature_rig.resolve('doritosInv')
        layer_doritos = pymel.createNode('transform', name=layer_doritos_name)
        layer_doritos.setParent(stack.node)

        # Create inverse attributes for the ctrl
        attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide',
                                                         input1=self.node.t,
                                                         input2=[-1, -1,
                                                                 -1]).output
        attr_ctrl_inv_r = libRigging.create_utility_node('multiplyDivide',
                                                         input1=self.node.r,
                                                         input2=[-1, -1,
                                                                 -1]).output
        attr_ctrl_inv_t = libRigging.create_utility_node(
            'multiplyDivide',
            input1=attr_ctrl_inv_t,
            input2X=self.attr_sensitivity_tx,
            input2Y=self.attr_sensitivity_ty,
            input2Z=self.attr_sensitivity_tz).output

        if flip_lr:
            attr_doritos_tx = libRigging.create_utility_node(
                'multiplyDivide', input1X=attr_ctrl_inv_t.outputX,
                input2X=-1).outputX
        else:
            attr_doritos_tx = attr_ctrl_inv_t.outputX
        attr_doritos_ty = attr_ctrl_inv_t.outputY
        attr_doritos_tz = attr_ctrl_inv_t.outputZ

        pymel.connectAttr(attr_doritos_tx, layer_doritos.tx)
        pymel.connectAttr(attr_doritos_ty, layer_doritos.ty)
        pymel.connectAttr(attr_doritos_tz, layer_doritos.tz)
        pymel.connectAttr(attr_ctrl_inv_r, layer_doritos.r)

        # Apply scaling on the ctrl parent.
        # This is were the 'black magic' happen.
        if flip_lr:
            attr_ctrl_offset_sx_inn = libRigging.create_utility_node(
                'multiplyDivide', input1X=self.attr_sensitivity_tx,
                input2X=-1).outputX
        else:
            attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx
        attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty
        attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz

        pymel.connectAttr(attr_ctrl_offset_sx_inn, self.offset.scaleX)
        pymel.connectAttr(attr_ctrl_offset_sy_inn, self.offset.scaleY)
        pymel.connectAttr(attr_ctrl_offset_sz_inn, self.offset.scaleZ)

        # Apply sensibility on the ctrl shape
        ctrl_shape = self.node.getShape()
        tmp = pymel.duplicate(self.node.getShape())[0]
        ctrl_shape_orig = tmp.getShape()
        ctrl_shape_orig.setParent(self.node, relative=True, shape=True)
        ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name()))
        pymel.delete(tmp)
        ctrl_shape_orig.intermediateObject.set(True)

        for cp in ctrl_shape.cp:
            cp.set(0, 0, 0)

        # Counter-scale the shape
        attr_adjustement_sx_inn = attr_sensibility_lr_inv
        attr_adjustement_sy_inn = attr_sensibility_ud_inv
        attr_adjustement_sz_inn = attr_sensibility_fb_inv
        attr_adjustement_scale = libRigging.create_utility_node(
            'composeMatrix',
            inputScaleX=attr_adjustement_sx_inn,
            inputScaleY=attr_adjustement_sy_inn,
            inputScaleZ=attr_adjustement_sz_inn).outputMatrix

        attr_adjustement_rot = libRigging.create_utility_node(
            'composeMatrix',
            inputRotateX=self.node.rotateX,
            inputRotateY=self.node.rotateY,
            inputRotateZ=self.node.rotateZ).outputMatrix

        attr_adjustement_rot_inv = libRigging.create_utility_node(
            'inverseMatrix', inputMatrix=attr_adjustement_rot).outputMatrix

        attr_adjustement_tm = libRigging.create_utility_node(
            'multMatrix',
            matrixIn=[
                attr_adjustement_rot, attr_adjustement_scale,
                attr_adjustement_rot_inv
            ]).matrixSum

        attr_transform_geometry = libRigging.create_utility_node(
            'transformGeometry',
            transform=attr_adjustement_tm,
            inputGeometry=ctrl_shape_orig.local).outputGeometry
        pymel.connectAttr(attr_transform_geometry,
                          ctrl_shape.create,
                          force=True)

        # Constraint ctrl
        pymel.parentConstraint(layer_doritos,
                               self.offset,
                               maintainOffset=False,
                               skipRotate=['x', 'y', 'z'])
        pymel.orientConstraint(layer_doritos.getParent(),
                               self.offset,
                               maintainOffset=True)

        # Clean dag junk
        if grp_rig:
            stack.setParent(grp_rig)
            if fol_mesh:
                fol_mesh.setParent(grp_rig)
Ejemplo n.º 10
0
    def build_stack(self, rig, stack, mult_u=1.0, mult_v=1.0):
        """
        The dag stack is a stock of dagnode that act as additive deformer to controler the final position of
        the drived joint.
        """
        # TODO: Maybe use sub-classing to differenciate when we need to use a surface or not.
        nomenclature_rig = self.get_nomenclature_rig(rig)

        #
        # Create follicle setup
        # The setup is composed of two follicles.
        # One for the "bind pose" and one "driven" by the avars..
        # The delta between the "bind pose" and the "driven" follicles is then applied to the influence.
        #

        # Determine the follicle U and V on the reference nurbsSurface.
        # jnt_pos = self.jnt.getTranslation(space='world')
        # fol_pos, fol_u, fol_v = libRigging.get_closest_point_on_surface(self.surface, jnt_pos)
        fol_u, fol_v = self.get_base_uv()

        # Create and connect follicle-related parameters
        u_base = fol_u  # fol_influence.parameterU.get()
        v_base = 0.5  # fol_influence.parameterV.get()

        # Resolve the length of each axis of the surface
        self._attr_length_u, self._attr_length_v, arcdimension_shape = libRigging.create_arclengthdimension_for_nurbsplane(self.surface)
        arcdimension_transform = arcdimension_shape.getParent()
        arcdimension_transform.rename(nomenclature_rig.resolve('arcdimension'))
        arcdimension_transform.setParent(self.grp_rig)

        # Create the bind pose follicle
        offset_name = nomenclature_rig.resolve('bindPoseRef')
        obj_offset = pymel.createNode('transform', name=offset_name)
        obj_offset.setParent(stack._layers[0])

        fol_offset_name = nomenclature_rig.resolve('bindPoseFollicle')
        # fol_offset = libRigging.create_follicle(obj_offset, self.surface, name=fol_offset_name)
        fol_offset_shape = libRigging.create_follicle2(self.surface, u=fol_u, v=fol_v)
        fol_offset = fol_offset_shape.getParent()
        fol_offset.rename(fol_offset_name)
        pymel.parentConstraint(fol_offset, obj_offset, maintainOffset=False)
        fol_offset.setParent(self.grp_rig)

        # Create the influence follicle
        influence_name = nomenclature_rig.resolve('influenceRef')
        influence = pymel.createNode('transform', name=influence_name)
        influence.setParent(stack._layers[0])

        fol_influence_name = nomenclature_rig.resolve('influenceFollicle')
        fol_influence_shape = libRigging.create_follicle2(self.surface, u=fol_u, v=fol_v)
        fol_influence = fol_influence_shape.getParent()
        fol_influence.rename(fol_influence_name)
        pymel.parentConstraint(fol_influence, influence, maintainOffset=False)
        fol_influence.setParent(self.grp_rig)

        #
        # Extract the delta of the influence follicle and it's initial pose follicle
        #
        attr_localTM = libRigging.create_utility_node('multMatrix', matrixIn=[
            influence.worldMatrix,
            obj_offset.worldInverseMatrix
        ]).matrixSum

        # Since we are extracting the delta between the influence and the bindpose matrix, the rotation of the surface
        # is not taken in consideration wich make things less intuitive for the rigger.
        # So we'll add an adjustement matrix so the rotation of the surface is taken in consideration.
        util_decomposeTM_bindPose = libRigging.create_utility_node('decomposeMatrix',
                                                                   inputMatrix=obj_offset.worldMatrix
                                                                   )
        attr_translateTM = libRigging.create_utility_node('composeMatrix',
                                                          inputTranslate=util_decomposeTM_bindPose.outputTranslate
                                                          ).outputMatrix
        attr_translateTM_inv = libRigging.create_utility_node('inverseMatrix',
                                                              inputMatrix=attr_translateTM,
                                                              ).outputMatrix
        attr_rotateTM = libRigging.create_utility_node('multMatrix',
                                                       matrixIn=[obj_offset.worldMatrix, attr_translateTM_inv]
                                                       ).matrixSum
        attr_rotateTM_inv = libRigging.create_utility_node('inverseMatrix',
                                                           inputMatrix=attr_rotateTM
                                                           ).outputMatrix
        attr_finalTM = libRigging.create_utility_node('multMatrix',
                                                      matrixIn=[attr_rotateTM_inv,
                                                                attr_localTM,
                                                                attr_rotateTM]
                                                      ).matrixSum

        util_decomposeTM = libRigging.create_utility_node('decomposeMatrix',
                                                          inputMatrix=attr_finalTM
                                                          )

        layer_follicle = stack.add_layer('follicleLayer')
        pymel.connectAttr(util_decomposeTM.outputTranslate, layer_follicle.translate)
        pymel.connectAttr(util_decomposeTM.outputRotate, layer_follicle.rotate)

        self._attr_u_base = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U_BASE, defaultValue=u_base)
        self._attr_v_base = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V_BASE, defaultValue=v_base)

        attr_u_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U)
        attr_v_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V)

        self._attr_u_mult_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U_MULT, defaultValue=mult_u)
        self._attr_v_mult_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V_MULT, defaultValue=mult_v)

        # Connect UD to V
        attr_get_v_offset = libRigging.create_utility_node('multiplyDivide',
                                                           input1X=self.attr_ud,
                                                           input2X=0.5
                                                           ).outputX
        attr_get_v_multiplied = libRigging.create_utility_node('multiplyDivide',
                                                               input1X=attr_get_v_offset,
                                                               input2X=self._attr_v_mult_inn).outputX
        attr_v_cur = libRigging.create_utility_node('addDoubleLinear',
                                                    input1=self._attr_v_base,
                                                    input2=attr_get_v_multiplied
                                                    ).output
        pymel.connectAttr(attr_v_cur, attr_v_inn)

        # Connect LR to U
        attr_get_u_offset = libRigging.create_utility_node('multiplyDivide',
                                                           input1X=self.attr_lr,
                                                           input2X=0.5
                                                           ).outputX
        attr_get_u_multiplied = libRigging.create_utility_node('multiplyDivide',
                                                               input1X=attr_get_u_offset,
                                                               input2X=self._attr_u_mult_inn).outputX
        attr_u_cur = libRigging.create_utility_node('addDoubleLinear',
                                                    input1=self._attr_u_base,
                                                    input2=attr_get_u_multiplied
                                                    ).output
        pymel.connectAttr(attr_u_cur, attr_u_inn)

        pymel.connectAttr(attr_u_inn, fol_influence.parameterU)
        pymel.connectAttr(attr_v_inn, fol_influence.parameterV)
        pymel.connectAttr(self._attr_u_base, fol_offset.parameterU)
        pymel.connectAttr(self._attr_v_base, fol_offset.parameterV)

        #
        # The OOB layer (out-of-bound) allow the follicle to go outside it's original plane.
        # HACK: If the UD value is out the nurbsPlane UV range (0-1), ie 1.1, we'll want to still offset the follicle.
        # For that we'll compute a delta between a small increment (0.99 and 1.0) and multiply it.
        #
        nomenclature_rig = self.get_nomenclature_rig(rig)
        oob_step_size = 0.001  # TODO: Expose a Maya attribute?
        jnt_tm = self.jnt.getMatrix(worldSpace=True)

        '''
        inf_clamped_v_name= nomenclature_rig.resolve('influenceClampedVRef')
        inf_clamped_v = pymel.createNode('transform', name=inf_clamped_v_name)
        inf_clamped_v.setParent(stack._layers[0])

        inf_clamped_u_name= nomenclature_rig.resolve('influenceClampedURef')
        inf_clamped_u = pymel.createNode('transform', name=inf_clamped_u_name)
        inf_clamped_u.setParent(stack._layers[0])
        '''

        fol_clamped_v_name = nomenclature_rig.resolve('influenceClampedV')
        fol_clamped_v_shape = libRigging.create_follicle2(self.surface, u=fol_u, v=fol_v)
        fol_clamped_v = fol_clamped_v_shape.getParent()
        fol_clamped_v.rename(fol_clamped_v_name)
        fol_clamped_v.setParent(self.grp_rig)

        fol_clamped_u_name = nomenclature_rig.resolve('influenceClampedU')
        fol_clamped_u_shape = libRigging.create_follicle2(self.surface, u=fol_u, v=fol_v)
        fol_clamped_u = fol_clamped_u_shape.getParent()
        fol_clamped_u.rename(fol_clamped_u_name)
        fol_clamped_u.setParent(self.grp_rig)

        # Clamp the values so they never fully reach 0 or 1 for U and V.
        util_clamp_uv = libRigging.create_utility_node('clamp',
                                                       inputR=attr_u_cur,
                                                       inputG=attr_v_cur,
                                                       minR=oob_step_size,
                                                       minG=oob_step_size,
                                                       maxR=1.0 - oob_step_size,
                                                       maxG=1.0 - oob_step_size)
        clamped_u = util_clamp_uv.outputR
        clamped_v = util_clamp_uv.outputG

        pymel.connectAttr(clamped_v, fol_clamped_v.parameterV)
        pymel.connectAttr(attr_u_cur, fol_clamped_v.parameterU)

        pymel.connectAttr(attr_v_cur, fol_clamped_u.parameterV)
        pymel.connectAttr(clamped_u, fol_clamped_u.parameterU)

        # Compute the direction to add for U and V if we are out-of-bound.
        dir_oob_u = libRigging.create_utility_node('plusMinusAverage',
                                                   operation=2,
                                                   input3D=[
                                                       fol_influence.translate,
                                                       fol_clamped_u.translate
                                                   ]).output3D
        dir_oob_v = libRigging.create_utility_node('plusMinusAverage',
                                                   operation=2,
                                                   input3D=[
                                                       fol_influence.translate,
                                                       fol_clamped_v.translate
                                                   ]).output3D

        # Compute the offset to add for U and V

        condition_oob_u_neg = libRigging.create_utility_node('condition',
                                                             operation=4,  # less than
                                                             firstTerm=attr_u_cur,
                                                             secondTerm=0.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR
        condition_oob_u_pos = libRigging.create_utility_node('condition',  # greater than
                                                             operation=2,
                                                             firstTerm=attr_u_cur,
                                                             secondTerm=1.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR
        condition_oob_v_neg = libRigging.create_utility_node('condition',
                                                             operation=4,  # less than
                                                             firstTerm=attr_v_cur,
                                                             secondTerm=0.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR
        condition_oob_v_pos = libRigging.create_utility_node('condition',  # greater than
                                                             operation=2,
                                                             firstTerm=attr_v_cur,
                                                             secondTerm=1.0,
                                                             colorIfTrueR=1.0,
                                                             colorIfFalseR=0.0,
                                                             ).outColorR

        # Compute the amount of oob
        oob_val_u_pos = libRigging.create_utility_node('plusMinusAverage', operation=2,
                                                       input1D=[attr_u_cur, 1.0]).output1D
        oob_val_u_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_u_cur, input2X=-1.0).outputX
        oob_val_v_pos = libRigging.create_utility_node('plusMinusAverage', operation=2,
                                                       input1D=[attr_v_cur, 1.0]).output1D
        oob_val_v_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_v_cur, input2X=-1.0).outputX
        oob_val_u = libRigging.create_utility_node('condition', operation=0, firstTerm=condition_oob_u_pos,
                                                   secondTerm=1.0, colorIfTrueR=oob_val_u_pos,
                                                   colorIfFalseR=oob_val_u_neg).outColorR
        oob_val_v = libRigging.create_utility_node('condition', operation=0, firstTerm=condition_oob_v_pos,
                                                   secondTerm=1.0, colorIfTrueR=oob_val_v_pos,
                                                   colorIfFalseR=oob_val_v_neg).outColorR

        oob_amount_u = libRigging.create_utility_node('multiplyDivide', operation=2, input1X=oob_val_u,
                                                      input2X=oob_step_size).outputX
        oob_amount_v = libRigging.create_utility_node('multiplyDivide', operation=2, input1X=oob_val_v,
                                                      input2X=oob_step_size).outputX

        oob_offset_u = libRigging.create_utility_node('multiplyDivide', input1X=oob_amount_u, input1Y=oob_amount_u,
                                                      input1Z=oob_amount_u, input2=dir_oob_u).output
        oob_offset_v = libRigging.create_utility_node('multiplyDivide', input1X=oob_amount_v, input1Y=oob_amount_v,
                                                      input1Z=oob_amount_v, input2=dir_oob_v).output

        # Add the U out-of-bound-offset only if the U is between 0.0 and 1.0
        oob_u_condition_1 = condition_oob_u_neg
        oob_u_condition_2 = condition_oob_u_pos
        oob_u_condition_added = libRigging.create_utility_node('addDoubleLinear',
                                                               input1=oob_u_condition_1,
                                                               input2=oob_u_condition_2
                                                               ).output
        oob_u_condition_out = libRigging.create_utility_node('condition',
                                                             operation=0,  # equal
                                                             firstTerm=oob_u_condition_added,
                                                             secondTerm=1.0,
                                                             colorIfTrue=oob_offset_u,
                                                             colorIfFalse=[0, 0, 0]
                                                             ).outColor

        # Add the V out-of-bound-offset only if the V is between 0.0 and 1.0
        oob_v_condition_1 = condition_oob_v_neg
        oob_v_condition_2 = condition_oob_v_pos
        oob_v_condition_added = libRigging.create_utility_node('addDoubleLinear',
                                                               input1=oob_v_condition_1,
                                                               input2=oob_v_condition_2
                                                               ).output
        oob_v_condition_out = libRigging.create_utility_node('condition',
                                                             operation=0,  # equal
                                                             firstTerm=oob_v_condition_added,
                                                             secondTerm=1.0,
                                                             colorIfTrue=oob_offset_v,
                                                             colorIfFalse=[0, 0, 0]
                                                             ).outColor

        oob_offset = libRigging.create_utility_node('plusMinusAverage',
                                                    input3D=[oob_u_condition_out, oob_v_condition_out]).output3D

        layer_oob = stack.add_layer('oobLayer')
        pymel.connectAttr(oob_offset, layer_oob.t)

        #
        # Build Front/Back setup
        #

        layer_fb = stack.add_layer('fbLayer')
        attr_get_fb = libRigging.create_utility_node('multiplyDivide',
                                                     input1X=self.attr_fb,
                                                     input2X=self._attr_length_u).outputX
        attr_get_fb_adjusted = libRigging.create_utility_node('multiplyDivide',
                                                              input1X=attr_get_fb,
                                                              input2X=0.1).outputX
        pymel.connectAttr(attr_get_fb_adjusted, layer_fb.translateZ)

        #
        #  Create a layer before the ctrl to apply the YW, PT and RL avar.
        #
        nomenclature_rig = self.get_nomenclature_rig(rig)
        layer_rot = stack.add_layer('rotLayer')

        pymel.connectAttr(self.attr_yw, layer_rot.rotateY)
        pymel.connectAttr(self.attr_pt, layer_rot.rotateX)
        pymel.connectAttr(self.attr_rl, layer_rot.rotateZ)

        return stack
Ejemplo n.º 11
0
    def build(self, parent, ref, ref_tm=None, grp_rig=None, obj_mesh=None, **kwargs):
        # todo: Simplify the setup, too many nodes

        # Resolve geometry to attach to
        if obj_mesh is None:
            obj_mesh = parent.get_farest_affected_mesh(ref)

        if obj_mesh is None:
            raise Exception("Can't find mesh affected by {0}. Skipping doritos ctrl setup.")

        super(InteractiveCtrl, self).build(parent, **kwargs)

        #nomenclature_anm = self.get_nomenclature_anm(parent)
        nomenclature_rig = parent.nomenclature(suffix=parent.nomenclature.type_rig)
        #nomenclature_rig = self.get_nomenclature_rig(parent)

        # TODO: Only use position instead of PyNode or Matrix?
        if ref_tm is None:
            ref_tm = ref.getMatrix(worldSpace=True)

        need_flip = ref_tm.translate.x < 0

        # Initialize external stack
        # Normally this would be hidden from animators.
        stack_name = nomenclature_rig.resolve('doritosStack')
        stack = classNode.Node(self)
        stack.build(name=stack_name)
        stack.setMatrix(ref_tm )

        # Add sensibility attributes
        # The values will be computed when attach_ctrl will be called
        self.attr_sensitivity_tx = libAttr.addAttr(stack, longName=self._ATTR_NAME_SENSITIVITY_TX,
                                                             defaultValue=1.0)
        self.attr_sensitivity_ty = libAttr.addAttr(stack, longName=self._ATTR_NAME_SENSITIVITY_TY,
                                                             defaultValue=1.0)
        self.attr_sensitivity_tz = libAttr.addAttr(stack, longName=self._ATTR_NAME_SENSITIVITY_TZ,
                                                             defaultValue=1.0)

        log.info('Creating doritos setup from {0} to {1}'.format(self.jnt, obj_mesh))

        # Resolve the doritos location
        '''
        if ctrl_tm is None:
            ctrl_tm = self.jnt.getMatrix(worldSpace=True)
        '''

        # Find the closest point on the surface.
        pos_ref = ref_tm.translate



        # Note that to only check in the Z axis, we'll do a raycast first.
        # If we success this will become our reference position.
        '''
        pos = pos_ref
        pos.z = 999
        dir = pymel.datatypes.Point(0,0,-1)
        result = next(iter(libRigging.ray_cast(pos, dir, [obj_mesh])), None)
        if result:
            pos_ref = result
            ctrl_tm.translate = result
        '''

        # Create the layer_fol that will follow the geometry
        layer_fol_name = nomenclature_rig.resolve('doritosFol')
        layer_fol = stack.add_layer()
        layer_fol.rename(layer_fol_name)
        #layer_fol.setParent(self.grp_rig)

        fol_pos, fol_u, fol_v = libRigging.get_closest_point_on_mesh(obj_mesh, pos_ref)

        # TODO: Validate that we don't need to inverse the rotation separately.
        fol_name = nomenclature_rig.resolve('doritosFollicle')
        fol_shape = libRigging.create_follicle2(obj_mesh, u=fol_u, v=fol_v)
        fol = fol_shape.getParent()
        fol.rename(fol_name)
        pymel.parentConstraint(fol, layer_fol, maintainOffset=True)
        fol = fol_shape.getParent()
        fol.setParent(self.grp_rig)

        # HACK: Fix rotation issues.
        # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since
        # any deformation can affect the normal of the faces.
        jnt_head = parent.get_head_jnt()
        if jnt_head:
            pymel.disconnectAttr(layer_fol.rx)
            pymel.disconnectAttr(layer_fol.ry)
            pymel.disconnectAttr(layer_fol.rz)
            pymel.orientConstraint(jnt_head, layer_fol, maintainOffset=True)

        #
        # Constraint a specic controller to the avar doritos stack.
        # Call this method after connecting the ctrl to the necessary avars.
        # The sensibility of the doritos will be automatically computed in this step if necessary.
        #



        # Create inverted attributes for sensibility
        util_sensitivity_inv = libRigging.create_utility_node('multiplyDivide', operation=2,
                                                              input1X=1.0, input1Y=1.0, input1Z=1.0,
                                                              input2X=self.attr_sensitivity_tx,
                                                              input2Y=self.attr_sensitivity_ty,
                                                              input2Z=self.attr_sensitivity_tz
                                                              )
        attr_sensibility_lr_inv = util_sensitivity_inv.outputX
        attr_sensibility_ud_inv = util_sensitivity_inv.outputY
        attr_sensibility_fb_inv = util_sensitivity_inv.outputZ

        # Add an inverse node that will counter animate the position of the ctrl.
        # TODO: Rename
        layer_doritos_name = nomenclature_rig.resolve('doritosInv')
        layer_doritos = pymel.createNode('transform', name=layer_doritos_name)
        layer_doritos.setParent(stack.node)

        # Create inverse attributes for the ctrl
        attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide', input1=self.node.t, input2=[-1, -1, -1]).output
        attr_ctrl_inv_r = libRigging.create_utility_node('multiplyDivide', input1=self.node.r, input2=[-1, -1, -1]).output
        attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide',
                                                         input1=attr_ctrl_inv_t,
                                                         input2X=self.attr_sensitivity_tx,
                                                         input2Y=self.attr_sensitivity_ty,
                                                         input2Z=self.attr_sensitivity_tz
                                                         ).output

        if need_flip:
            attr_doritos_tx = libRigging.create_utility_node('multiplyDivide',
                                                             input1X=attr_ctrl_inv_t.outputX,
                                                             input2X=-1
                                                             ).outputX
        else:
            attr_doritos_tx = attr_ctrl_inv_t.outputX
        attr_doritos_ty = attr_ctrl_inv_t.outputY
        attr_doritos_tz = attr_ctrl_inv_t.outputZ

        pymel.connectAttr(attr_doritos_tx, layer_doritos.tx)
        pymel.connectAttr(attr_doritos_ty, layer_doritos.ty)
        pymel.connectAttr(attr_doritos_tz, layer_doritos.tz)
        pymel.connectAttr(attr_ctrl_inv_r, layer_doritos.r)

        # Apply scaling on the ctrl parent.
        # This is were the 'black magic' happen.
        if need_flip:
            attr_ctrl_offset_sx_inn = libRigging.create_utility_node('multiplyDivide',
                                                                     input1X=self.attr_sensitivity_tx,
                                                                     input2X=-1
                                                                     ).outputX
        else:
            attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx
        attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty
        attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz

        pymel.connectAttr(attr_ctrl_offset_sx_inn, self.offset.scaleX)
        pymel.connectAttr(attr_ctrl_offset_sy_inn, self.offset.scaleY)
        pymel.connectAttr(attr_ctrl_offset_sz_inn, self.offset.scaleZ)

        # Apply sensibility on the ctrl shape
        ctrl_shape = self.node.getShape()
        tmp = pymel.duplicate(self.node.getShape())[0]
        ctrl_shape_orig = tmp.getShape()
        ctrl_shape_orig.setParent(self.node, relative=True, shape=True)
        ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name()))
        pymel.delete(tmp)
        ctrl_shape_orig.intermediateObject.set(True)

        for cp in ctrl_shape.cp:
            cp.set(0,0,0)

        # Counter-scale the shape
        '''
        if need_flip:
            attr_adjustement_sx_inn = libRigging.create_utility_node('multiplyDivide', input1X=attr_sensibility_lr_inv, input2X=-1).outputX
        else:
            attr_adjustement_sx_inn = attr_sensibility_lr_inv
        '''
        attr_adjustement_sx_inn = attr_sensibility_lr_inv
        attr_adjustement_sy_inn = attr_sensibility_ud_inv
        attr_adjustement_sz_inn = attr_sensibility_fb_inv
        attr_adjustement_scale = libRigging.create_utility_node('composeMatrix',
                                                             inputScaleX=attr_adjustement_sx_inn,
                                                             inputScaleY=attr_adjustement_sy_inn,
                                                             inputScaleZ=attr_adjustement_sz_inn
                                                             ).outputMatrix

        attr_adjustement_rot = libRigging.create_utility_node('composeMatrix',
                                                              inputRotateX=self.node.rotateX,
                                                              inputRotateY=self.node.rotateY,
                                                              inputRotateZ=self.node.rotateZ
                                                              ).outputMatrix

        attr_adjustement_rot_inv = libRigging.create_utility_node('inverseMatrix', inputMatrix=attr_adjustement_rot).outputMatrix

        attr_adjustement_tm = libRigging.create_utility_node('multMatrix', matrixIn=[
            attr_adjustement_rot,
            attr_adjustement_scale,
            attr_adjustement_rot_inv
        ]).matrixSum

        attr_transform_geometry = libRigging.create_utility_node('transformGeometry', transform=attr_adjustement_tm,
                                                                 inputGeometry=ctrl_shape_orig.local).outputGeometry
        pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True)

        # Constraint ctrl
        pymel.parentConstraint(layer_doritos, self.offset, maintainOffset=False, skipRotate=['x', 'y', 'z'])
        pymel.orientConstraint(layer_doritos.getParent(), self.offset, maintainOffset=True)

        # Clean dag junk
        if grp_rig:
            stack.setParent(grp_rig)
            fol.setParent(grp_rig)