Example #1
0
    def __init__(self, parent, partClass, builderUI):
        MelForm.__init__(self, parent)

        self.partClass = partClass
        self.builderUI = builderUI
        self.UI_create = cmd.button(
            l=self.BUTTON_LBL_TEMPLATE %
            names.camelCaseToNice(partClass.GetPartName()),
            c=self.on_create,
            w=160)

        #now populate the ui for the part's args
        self.kwarg_UIs = {}  #keyed by arg name
        kwargList = partClass.GetDefaultBuildKwargList()
        self.UI_argsLayout = MelForm(self)

        #everything has a parent attribute, so build it first
        prevUI = None
        for arg, default in kwargList:
            #skip UI for parent - assume selection always
            if arg == 'parent':
                continue

            setParent(self.UI_argsLayout)
            lbl = cmd.text(l=names.camelCaseToNice(arg, self.ABBRVS_TO_EXPAND))

            #determine the function to use for building the UI for the arg
            buildMethodFromName = UI_FOR_NAMED_RIG_ARGS.get(arg, None)
            buildMethodFromType = getBuildUIMethodForObject(
                default) or MelTextField

            buildMethod = buildMethodFromName or buildMethodFromType

            self.kwarg_UIs[arg] = argUI = buildMethod(self.UI_argsLayout)
            argUI.setValue(default)

            #perform layout
            if prevUI is None:
                self.UI_argsLayout(e=True, af=((lbl, 'left', 15)))
            else:
                self.UI_argsLayout(e=True, ac=((lbl, 'left', 15, prevUI)))

            if isinstance(argUI, MelCheckBox):
                self.UI_argsLayout(e=True, af=((argUI, 'top', 3)))

            self.UI_argsLayout(e=True,
                               af=((lbl, 'top', 3)),
                               ac=((argUI, 'left', 5, lbl)))
            prevUI = argUI

        setParent(self)

        self(e=True,
             af=((self.UI_create, 'left', 0), (self.UI_argsLayout, 'right',
                                               0)),
             ac=((self.UI_argsLayout, 'left', 0, self.UI_create)))
	def __init__( self, parent, partClass, builderUI ):
		MelForm.__init__( self, parent )

		self.partClass = partClass
		self.builderUI = builderUI
		self.UI_create = cmd.button( l=self.BUTTON_LBL_TEMPLATE % names.camelCaseToNice( partClass.GetPartName() ), c=self.on_create, w=160 )

		#now populate the ui for the part's args
		self.kwarg_UIs = {}  #keyed by arg name
		kwargList = partClass.GetDefaultBuildKwargList()
		self.UI_argsLayout = MelForm( self )

		#everything has a parent attribute, so build it first
		prevUI = None
		for arg, default in kwargList:
			#skip UI for parent - assume selection always
			if arg == 'parent':
				continue

			setParent( self.UI_argsLayout )
			lbl = cmd.text( l=names.camelCaseToNice( arg, self.ABBRVS_TO_EXPAND ) )

			#determine the function to use for building the UI for the arg
			buildMethodFromName = UI_FOR_NAMED_RIG_ARGS.get( arg, None )
			buildMethodFromType = getBuildUIMethodForObject( default ) or MelTextField

			buildMethod = buildMethodFromName or buildMethodFromType

			self.kwarg_UIs[ arg ] = argUI = buildMethod( self.UI_argsLayout )
			argUI.setValue( default )

			#perform layout
			if prevUI is None:
				self.UI_argsLayout( e=True, af=((lbl, 'left', 15)) )
			else:
				self.UI_argsLayout( e=True, ac=((lbl, 'left', 15, prevUI)) )

			if isinstance( argUI, MelCheckBox ):
				self.UI_argsLayout( e=True, af=((argUI, 'top', 3)) )

			self.UI_argsLayout( e=True, af=((lbl, 'top', 3)), ac=((argUI, 'left', 5, lbl)) )
			prevUI = argUI

		setParent( self )

		self( e=True, af=((self.UI_create, 'left', 0),
		                  (self.UI_argsLayout, 'right', 0)),
		              ac=((self.UI_argsLayout, 'left', 0, self.UI_create)) )
	def GetPartName( cls ):
		'''
		can be used to get a "nice" name for the part class
		'''
		if cls.DISPLAY_NAME is None:
			return camelCaseToNice( cls.__name__ )

		return cls.DISPLAY_NAME
Example #4
0
	def populate( self ):
		#remove any existing children
		self.clear()
		part = self.part
		assert isinstance( part, SkeletonPart )

		#pimp out the UI
		lbl = MelButton( self, l=part.getPartName(), w=140, c=self.on_select )

		#grab the args the rigging method takes
		argsForm = MelForm( self )
		argsUIs = []

		buildKwargs = part.getBuildKwargs()
		for arg in self.ARGS_TO_HIDE:
			buildKwargs.pop( arg, None )

		for arg, argValue in buildKwargs.iteritems():
			argLbl = MelLabel( argsForm, label=names.camelCaseToNice( arg ) )

			#determine the function to use for building the UI for the arg
			buildMethodFromName = UI_FOR_NAMED_RIG_ARGS.get( arg, None )
			buildMethodFromType = getBuildUIMethodForObject( argValue ) or MelTextField

			buildMethod = buildMethodFromName or buildMethodFromType  #prioritize the method from name over the method from type

			argWidget = buildMethod( argsForm )
			argWidget.setValue( argValue )

			argsUIs.append( argLbl )
			argsUIs.append( argWidget )
			self.argUIs[ arg ] = argWidget

		try:
			inc = 1.0 / len( argsUIs )
		except ZeroDivisionError: inc = 1.0

		for n, ui in enumerate( argsUIs ):
			p = n*inc
			if n:
				argsForm( e=True, ac=((ui, 'left', 5, argsUIs[ n-1 ])) )
			else:
				argsForm( e=True, af=((ui, 'left', 0)) )

		#finally build the "rebuild" button
		reButt = MelButton( self, l="rebuild", c=self.on_rebuild, w=100 )

		#perform layout...
		self.setWidth( 50 )
		argsForm.setWidth( 50 )

		self( e=True,
		      af=((lbl, 'left', 0),
		          (reButt, 'right', 0)),
		      ac=((argsForm, 'left', 5, lbl),
		          (argsForm, 'right', 0, reButt)) )
    def populate(self):
        if not bool(self.rigTypes):
            self.setVisibility(False)
            return

        #remove any existing children
        self.clearArgs()
        part = self.part

        rigKwargs = part.getRigKwargs()

        #build the disable and optionbox for the rig method
        disableState = rigKwargs.get('disable', False)

        #grab the args the rigging method takes
        argsForm = self.UI_argsForm
        argsUIs = []

        rigMethodName = rigKwargs.get('rigMethodName',
                                      self.rigTypes[0].__name__)
        rigClass = rigPrimitives.RigPart.GetNamedSubclass(rigMethodName)

        if rigClass is None:
            rigClass = part.RigTypes[0]

        zeroWeightTypes = MelCheckBox, MelOptionMenu

        argNamesAndDefaults = rigClass.GetDefaultBuildKwargList()
        for arg, default in argNamesAndDefaults:
            argValue = rigKwargs.get(arg, default)
            argLbl = MelLabel(argsForm, label=names.camelCaseToNice(arg))

            #determine the function to use for building the UI for the arg
            buildMethodFromName = UI_FOR_NAMED_RIG_ARGS.get(arg, None)
            buildMethodFromType = getBuildUIMethodForObject(
                default) or MelTextField

            buildMethod = buildMethodFromName or buildMethodFromType

            argWidget = buildMethod(argsForm)
            argWidget.setValue(argValue, False)
            argWidget.setChangeCB(self.on_argCB)

            argLbl.enable(not disableState)
            argWidget.enable(not disableState)

            argsUIs.append(argLbl)
            argsUIs.append(argWidget)
            self.argUIs[arg] = argWidget

        #if there are no args - create an empty text widget otherwise maya will crash.  yay!
        argsUIs.append(MelLabel(argsForm, label=''))

        argsForm.layout()
	def populate( self ):
		if not bool( self.rigTypes ):
			self.setVisibility( False )
			return

		#remove any existing children
		self.clearArgs()
		part = self.part

		rigKwargs = part.getRigKwargs()

		#build the disable and optionbox for the rig method
		disableState = rigKwargs.get( 'disable', False )

		#grab the args the rigging method takes
		argsForm = self.UI_argsForm
		argsUIs = []

		rigMethodName = rigKwargs.get( 'rigMethodName', self.rigTypes[ 0 ].__name__ )
		rigClass = rigPrimitives.RigPart.GetNamedSubclass( rigMethodName )

		if rigClass is None:
			rigClass = part.RigTypes[ 0 ]

		zeroWeightTypes = MelCheckBox, MelOptionMenu

		argNamesAndDefaults = rigClass.GetDefaultBuildKwargList()
		for arg, default in argNamesAndDefaults:
			argValue = rigKwargs.get( arg, default )
			argLbl = MelLabel( argsForm, label=names.camelCaseToNice( arg ) )

			#determine the function to use for building the UI for the arg
			buildMethodFromName = UI_FOR_NAMED_RIG_ARGS.get( arg, None )
			buildMethodFromType = getBuildUIMethodForObject( default ) or MelTextField

			buildMethod = buildMethodFromName or buildMethodFromType

			argWidget = buildMethod( argsForm )
			argWidget.setValue( argValue, False )
			argWidget.setChangeCB( self.on_argCB )

			argLbl.enable( not disableState )
			argWidget.enable( not disableState )

			argsUIs.append( argLbl )
			argsUIs.append( argWidget )
			self.argUIs[ arg ] = argWidget

		#if there are no args - create an empty text widget otherwise maya will crash.  yay!
		argsUIs.append( MelLabel( argsForm, label='' ) )

		argsForm.layout()
    def addParent(self, parent, name=None):
        #sanity check - make sure the parent isn't either the control, the space, already in the list or a child of either the control or space
        if parent == self.getControl():
            return
        elif parent == self.getSpace():
            return

        if name is None:
            name = spaceSwitching.getSpaceName(self.getControl(), parent)
            if name is None:
                name = names.camelCaseToNice(str(parent))

        self.UI_parents.append([parent, name])
	def addParent( self, parent, name=None ):
		#sanity check - make sure the parent isn't either the control, the space, already in the list or a child of either the control or space
		if parent == self.getControl():
			return
		elif parent == self.getSpace():
			return

		if name is None:
			name = spaceSwitching.getSpaceName( self.getControl(), parent )
			if name is None:
				name = names.camelCaseToNice( str( parent ) )

		self.UI_parents.append( [parent, name] )
Example #9
0
	def __init__( self, parent, preset ):
		MelHSingleStretchLayout.__init__( self, parent )
		self.preset = preset

		MelButton( self, l=names.camelCaseToNice( preset.name() ), c=self.on_load, w=160 )
		spac = MelSpacer( self )
		MelButton( self, l='Overwrite', c=self.on_overwrite, w=100 )
		if preset.locale == presetsUI.LOCAL:
			self.UI_publish = MelButton( self, l='Publish', c=self.on_publish, w=100 )

		#MelButton( self, l='Delete', c=self.on_remove, w=100 )

		self.setStretchWidget( spac )
		self.layout()
Example #10
0
    def setChain(self, dynamicChain):
        self.clear()
        self._chain = dynamicChain

        if dynamicChain is None:
            return

        dynChainNode = dynamicChain.getNode()
        MelLabel(self, l='Editing Dynamic Chain: %s' % dynChainNode)
        MelSeparator(self, h=15)

        attrs = listAttr(dynChainNode, k=True) or []
        for attr in attrs:
            attrpath = '%s.%s' % (dynChainNode, attr)

            niceAttrName = camelCaseToNice(attr)

            #query the attribute type and build UI for the types we care about presenting to the user
            attrType = getAttr(attrpath, type=True)
            ui = None
            if attrType == 'bool':
                ui = MelCheckBox(self, l=niceAttrName)
            elif attrType == 'double':
                min, max = addAttr(attrpath, q=True,
                                   min=True), addAttr(attrpath,
                                                      q=True,
                                                      max=True)
                ui = LabelledFloatSlider(self,
                                         min,
                                         max,
                                         ll=niceAttrName,
                                         llw=65).getWidget()

            if ui is None:
                continue

            connectControl(ui, attrpath)

        MelSeparator(self, h=15)

        hLayout = MelHSingleStretchLayout(self)
        lbl = MelLabel(hLayout, l='Key Every N Frames')
        self.UI_nFrame = MelIntField(hLayout, v=4, min=1, max=10, step=1)
        self.UI_bake = MelButton(hLayout, l='Bake To Keys', c=self.on_bake)

        hLayout(e=True, af=((lbl, 'top', 0), (lbl, 'bottom', 0)))
        hLayout.padding = 10
        hLayout.setStretchWidget(self.UI_bake)
        hLayout.layout()
Example #11
0
	def setChain( self, dynamicChain ):
		self.clear()
		self._chain = dynamicChain

		if dynamicChain is None:
			return

		dynChainNode = dynamicChain.getNode()
		MelLabel( self, l='Editing Dynamic Chain: %s' % dynChainNode )
		MelSeparator( self, h=15 )

		attrs = listAttr( dynChainNode, k=True ) or []
		for attr in attrs:
			attrpath = '%s.%s' % (dynChainNode, attr)

			niceAttrName = camelCaseToNice( attr )

			#query the attribute type and build UI for the types we care about presenting to the user
			attrType = getAttr( attrpath, type=True )
			ui = None
			if attrType == 'bool':
				ui = MelCheckBox( self, l=niceAttrName )
			elif attrType == 'double':
				min, max = addAttr( attrpath, q=True, min=True ), addAttr( attrpath, q=True, max=True )
				ui = LabelledFloatSlider( self, min, max, ll=niceAttrName, llw=65 ).getWidget()

			if ui is None:
				continue

			connectControl( ui, attrpath )

		MelSeparator( self, h=15 )

		hLayout = MelHSingleStretchLayout( self )
		lbl = MelLabel( hLayout, l='Key Every N Frames' )
		self.UI_nFrame = MelIntField( hLayout, v=4, min=1, max=10, step=1 )
		self.UI_bake = MelButton( hLayout, l='Bake To Keys', c=self.on_bake )

		hLayout( e=True, af=((lbl, 'top', 0), (lbl, 'bottom', 0)) )
		hLayout.padding = 10
		hLayout.setStretchWidget( self.UI_bake )
		hLayout.layout()
def generateNiceControlName( control ):
	niceName = getNiceName( control )
	if niceName is not None:
		return niceName

	try:
		rigPart = RigPart.InitFromItem( control )
		if rigPart is None: raise RigPartError( "null" )
		controlName = rigPart.getControlName( control )
	except RigPartError:
		controlName = str( control )

	controlName = Name( controlName )
	parity = controlName.get_parity()

	if parity == Parity.LEFT:
		controlName = 'Left '+ str( stripParity( controlName )  )
	if parity == Parity.RIGHT:
		controlName = 'Right '+ str( stripParity( controlName )  )
	else:
		controlName = str( controlName )

	return camelCaseToNice( controlName )
def add(src,
        tgt,
        name=None,
        space=None,
        maintainOffset=True,
        nodeWithParentAttr=None,
        skipTranslationAxes=(),
        skipRotationAxes=(),
        constraintType=CONSTRAINT_PARENT):

    global AXES
    AXES = list(AXES)

    if space is None:
        space = listRelatives(src, p=True, pa=True)[0]

    if nodeWithParentAttr is None:
        nodeWithParentAttr = src

    if not name:
        name = getNiceName(tgt)
        if name is None:
            name = camelCaseToNice(str(tgt))

    #if there is an existing constraint, check to see if the target already exists in its target list - if it does, return the condition used it uses
    attrState(space, ('t', 'r'), lock=False)
    existingConstraint = findConstraint(src)
    if existingConstraint:
        constraintType = nodeType(existingConstraint)
        constraintFunc = getattr(cmd, constraintType)
        targetsOnConstraint = constraintFunc(existingConstraint,
                                             q=True,
                                             tl=True)
        if tgt in targetsOnConstraint:
            idx = targetsOnConstraint.index(tgt)
            aliases = constraintFunc(existingConstraint,
                                     q=True,
                                     weightAliasList=True)
            cons = listConnections('%s.%s' %
                                   (existingConstraint, aliases[idx]),
                                   type='condition',
                                   d=False)

            return cons[0]

    #when skip axes are specified maya doesn't handle things properly - so make sure
    #ALL transform channels are connected, and remove unwanted channels at the end...
    preT, preR = getAttr('%s.t' % space)[0], getAttr('%s.r' % space)[0]
    if existingConstraint:
        chans = CONSTRAINT_CHANNELS[constraintType]
        for channel, constraintAttr in zip(*chans):
            for axis in AXES:
                spaceAttr = '%s.%s%s' % (space, channel, axis)
                conAttr = '%s.%s%s' % (existingConstraint, constraintAttr,
                                       axis)
                if not isConnected(conAttr, spaceAttr):
                    connectAttr(conAttr, spaceAttr)

    #get the names for the parents from the parent enum attribute
    cmdOptionKw = {'mo': True} if maintainOffset else {}
    if objExists('%s.parent' % nodeWithParentAttr):
        srcs, names = getSpaceTargetsNames(src)
        addAttr('%s.parent' % nodeWithParentAttr,
                e=True,
                enumName=':'.join(names + [name]))

        #if we're building a pointConstraint instead of a parent constraint AND we already
        #have spaces on the object, we need to turn the -mo flag off regardless of what the
        #user set it to, as the pointConstraint maintain offset has different behaviour to
        #the parent constraint
        if constraintType in (CONSTRAINT_POINT, CONSTRAINT_ORIENT):
            cmdOptionKw = {}
    else:
        addAttr(nodeWithParentAttr, ln='parent', at="enum", en=name)
        setAttr('%s.parent' % nodeWithParentAttr, keyable=True)

    #now build the constraint
    constraintFunction = getattr(cmd, constraintType)
    constraint = constraintFunction(tgt, space, **cmdOptionKw)[0]

    weightAliasList = constraintFunction(constraint,
                                         q=True,
                                         weightAliasList=True)
    targetCount = len(weightAliasList)
    constraintAttr = weightAliasList[-1]
    condition = shadingNode('condition',
                            asUtility=True,
                            n='%s_to_space_%s#' %
                            (getShortName(src), getShortName(tgt)))

    setAttr('%s.secondTerm' % condition, targetCount - 1)
    setAttr('%s.colorIfTrue' % condition, 1, 1, 1)
    setAttr('%s.colorIfFalse' % condition, 0, 0, 0)
    connectAttr('%s.parent' % nodeWithParentAttr, '%s.firstTerm' % condition)
    connectAttr('%s.outColorR' % condition,
                '%s.%s' % (constraint, constraintAttr))

    #find out what symbol to use to find the parent attribute
    parentAttrIdx = 0
    if not apiExtensions.cmpNodes(space, src):
        parentAttrIdx = triggered.addConnect(src, nodeWithParentAttr)

    #add the zooObjMenu commands to the object for easy space switching
    Trigger.CreateMenu(src, "parent to %s" % name,
                       ChangeSpaceCmd.Create(targetCount - 1, parentAttrIdx))

    #when skip axes are specified maya doesn't handle things properly - so make sure
    #ALL transform channels are connected, and remove unwanted channels at the end...
    for axis, value in zip(AXES, preT):
        if axis in skipTranslationAxes:
            attr = '%s.t%s' % (space, axis)
            delete(attr, icn=True)
            setAttr(attr, value)

    for axis, value in zip(AXES, preR):
        if axis in skipRotationAxes:
            attr = '%s.r%s' % (space, axis)
            delete(attr, icn=True)
            setAttr(attr, value)

    #make the space node non-keyable and lock visibility
    attrState(space, ['t', 'r', 's'], lock=True)
    attrState(space, 'v', *control.HIDE)

    return condition
Example #14
0
	def DISPLAY_NAME( cls ):
		return camelCaseToNice( cls.__name__ )
Example #15
0
	def GetPartName( cls ):
		'''
		can be used to get a "nice" name for the part class
		'''
		return camelCaseToNice( cls.__name__ )
def add( src, tgt,
         name=None,
         space=None,
         maintainOffset=True,
         nodeWithParentAttr=None,
         skipTranslationAxes=(),
         skipRotationAxes=(),
         constraintType=CONSTRAINT_PARENT ):

	global AXES
	AXES = list( AXES )

	if space is None:
		space = listRelatives( src, p=True, pa=True )[ 0 ]

	if nodeWithParentAttr is None:
		nodeWithParentAttr = src

	if not name:
		name = getNiceName( tgt )
		if name is None:
			name = camelCaseToNice( str( tgt ) )


	#if there is an existing constraint, check to see if the target already exists in its target list - if it does, return the condition used it uses
	attrState( space, ('t', 'r'), lock=False )
	existingConstraint = findConstraint( src )
	if existingConstraint:
		constraintType = nodeType( existingConstraint )
		constraintFunc = getattr( cmd, constraintType )
		targetsOnConstraint = constraintFunc( existingConstraint, q=True, tl=True )
		if tgt in targetsOnConstraint:
			idx = targetsOnConstraint.index( tgt )
			aliases = constraintFunc( existingConstraint, q=True, weightAliasList=True )
			cons = listConnections( '%s.%s' % (existingConstraint, aliases[ idx ]), type='condition', d=False )

			return cons[ 0 ]


	#when skip axes are specified maya doesn't handle things properly - so make sure
	#ALL transform channels are connected, and remove unwanted channels at the end...
	preT, preR = getAttr( '%s.t' % space )[0], getAttr( '%s.r' % space )[0]
	if existingConstraint:
		chans = CONSTRAINT_CHANNELS[ constraintType ]
		for channel, constraintAttr in zip( *chans ):
			for axis in AXES:
				spaceAttr = '%s.%s%s' %( space, channel, axis)
				conAttr = '%s.%s%s' % (existingConstraint, constraintAttr, axis)
				if not isConnected( conAttr, spaceAttr ):
					connectAttr( conAttr, spaceAttr )


	#get the names for the parents from the parent enum attribute
	cmdOptionKw = { 'mo': True } if maintainOffset else {}
	if objExists( '%s.parent' % nodeWithParentAttr ):
		srcs, names = getSpaceTargetsNames( src )
		addAttr( '%s.parent' % nodeWithParentAttr, e=True, enumName=':'.join( names + [name] ) )

		#if we're building a pointConstraint instead of a parent constraint AND we already
		#have spaces on the object, we need to turn the -mo flag off regardless of what the
		#user set it to, as the pointConstraint maintain offset has different behaviour to
		#the parent constraint
		if constraintType in ( CONSTRAINT_POINT, CONSTRAINT_ORIENT ):
			cmdOptionKw = {}
	else:
		addAttr( nodeWithParentAttr, ln='parent', at="enum", en=name )
		setAttr( '%s.parent' % nodeWithParentAttr, keyable=True )


	#now build the constraint
	constraintFunction = getattr( cmd, constraintType )
	constraint = constraintFunction( tgt, space, **cmdOptionKw )[ 0 ]


	weightAliasList = constraintFunction( constraint, q=True, weightAliasList=True )
	targetCount = len( weightAliasList )
	constraintAttr = weightAliasList[ -1 ]
	condition = shadingNode( 'condition', asUtility=True, n='%s_to_space_%s#' % (getShortName( src ), getShortName( tgt )) )

	setAttr( '%s.secondTerm' % condition, targetCount-1 )
	setAttr( '%s.colorIfTrue' % condition, 1, 1, 1 )
	setAttr( '%s.colorIfFalse' % condition, 0, 0, 0 )
	connectAttr( '%s.parent' % nodeWithParentAttr, '%s.firstTerm' % condition )
	connectAttr( '%s.outColorR' % condition, '%s.%s' % (constraint, constraintAttr) )


	#find out what symbol to use to find the parent attribute
	parentAttrIdx = 0
	if not apiExtensions.cmpNodes( space, src ):
		parentAttrIdx = triggered.addConnect( src, nodeWithParentAttr )


	#add the zooObjMenu commands to the object for easy space switching
	Trigger.CreateMenu( src,
	                    "parent to %s" % name,
	                    ChangeSpaceCmd.Create( targetCount-1, parentAttrIdx ) )


	#when skip axes are specified maya doesn't handle things properly - so make sure
	#ALL transform channels are connected, and remove unwanted channels at the end...
	for axis, value in zip( AXES, preT ):
		if axis in skipTranslationAxes:
			attr = '%s.t%s' % (space, axis)
			delete( attr, icn=True )
			setAttr( attr, value )

	for axis, value in zip( AXES, preR ):
		if axis in skipRotationAxes:
			attr = '%s.r%s' % (space, axis)
			delete( attr, icn=True )
			setAttr( attr, value )


	#make the space node non-keyable and lock visibility
	attrState( space, [ 't', 'r', 's' ], lock=True )
	attrState( space, 'v', *control.HIDE )


	return condition