Constraints allow you to animate one object via another object's animation. You constrain different properties, such as position or direction, of one object to another object. You cannot create your own constraints, but Softimage provides many diverse types that you can apply and manipulate through the SDK.
A constraint recognizes one or more objects as the constraining object(s) and one object as the constrained object. It is the constrained object which "owns" the constraint: you can access the constraint via the constrained object's kinematics. From the constraint, you can then access both the constraining and constrained objects (see Accessing Constraints through the SDK for more information).
A constraint is a piece of data that mediates between the constraining and constrained objects. Depending on its type, the constraint expects a specific set of connection points, for example a surface constraint expects the constrainer to be a NURBS surface whereas the pose constraint expects the constraining object to be any 3D object.
You can apply a constraint using scripting commands, the OM, or the C++ API:
Scripting command: SIApplyCns
Alternatively, you can use the ApplyCns command, which performs cycle checking; however, cycle checking can create problems because it can log a lot of warnings that can be safely ignored in some cases (some cycles are on purpose). Another problem is that a popup message appears if there's a cycle, which halt automated tools and requires manual intervention for each cycle warning message.
Object Model:Kinematics.AddConstraint method on the object to be constrained
C++ API:Kinematics::AddConstraint member on the object to be constrained
To remove a constraint through the SDK, you can only use one of these scripting commands:
RemoveCns: removes the constraint identified by name
DeleteObj: this is a general purpose command that deletes the specified object
You can get the constraints applied to an object through its Kinematics property for both the object model (JScript Example: Accessing the Constraint object) and the C++ API (C++ API Example: Accessing the Constraint object). For scripting commands, you can get the constrained objects using the GetConstrainedObjects command.
From there, the Constraint object gives you access to the collection of constraining objects as well as the constrained object, and the UpVector reference (the object that determines in which direction the constrained object should point). The following examples demonstrate how to access the Constraint object and its properties.
// "obj" is a pointer to a constrained object var cnslist = obj.Kinematics.Constraints; // Enumerate over the ConstraintCollection for ( var i=0; i<cnslist.Count; i++ ) { var cns = cnslist(0); var myself = cns.Constrained; // this should point to the original 'obj' var stuckonyou = cns.Constraining; // everyone constraining 'obj' var upvect = cns.UpVectorReference; // object used as my upvector reference Application.LogMessage( "Constraint type: " + cns.Type ); Application.LogMessage( "Constrained object: " + myself ); Application.LogMessage( "# of Constraining objects: " + stuckonyou.Count ); Application.LogMessage( "UpVectorRef: " + upvect ); } // So if you had a sphere pose-constrained by a null (with no up-vector // reference), you would get something like this: //INFO : Constraint type: posecns //INFO : Constrained object: sphere //INFO : # of Constraining objects: 1 //INFO : UpVectorRef: null
// "obj" is a pointer to a constrained object Kinematics kine = obj.GetKinematics(); CRefArray cnslist = kine.GetConstraints(); // Enumerate over the CRefArray of Constraints for ( LONG i=0; i<cnslist.GetCount(); ++i ) { CRef ref = cnslist.GetItem(i); if ( ref.IsA(siConstraintID) ) { Constraint cns(ref); X3DObject myself = cns.GetConstrained(); // this should point to the original 'obj' CRefArray stuckonyou = cns.GetConstraining(); // everyone constraining 'obj' X3DObject upvect = cns.GetUpVectorReference(); // object used as my upvector reference app.LogMessage( L"Constraint type: " + cns.GetType() ); app.LogMessage( L"Constrained object: " + myself.GetName() ); app.LogMessage( L" # of Constraining objects: " + stuckonyou.GetCount() ); app.LogMessage( L"UpVectorRef: " + upvect.GetName() ); } } // So if you had a sphere pose-constrained by a null (with no up-vector // reference), you would get something like this: //INFO : Constraint type: posecns //INFO : Constrained object: sphere //INFO : # of Constraining objects: //INFO : UpVectorRef:
You can also get animation information from clips and sources that are based on constraints. For more information, see Constraints and the Mixer.
The Constraint Stack is the collection of constraints nested under a constrained object's Kinematics property. The order that you see in the UI mirrors the typical operator stack, that is the last constraint applied is at the top. When you access the collection of constraints through the SDK, you get the constraints in the same order they were applied.
If an object has more than one constraint applied and you want to blend the constraint weighting, you can access the blendweight parameter from the Constraint object.
The blendweight parameter can be identified with shortcut notation or standard (full) notation (getting the parameter by name from the ParameterCollection:
// "cns" is a pointer to a constraint cns.blendweight.Value = .25;// shortcut notation cns.Parameters("blendweight").Value = .25;// full notation
You can change the blendweight parameter's value with the convenience ProjectItem::PutParameterValue function or by using the standard (full) notation (getting the parameter by name from the CParameterRefArray):
// "cns" is a pointer to a constraint cns.PutParameterValue(L"blendweight", 0.75);// use convenience method CParameterRefArray paramlist = cns.GetParameters();// full notation Parameter param = paramlist.GetItem(L"blendweight"); param.PutValue(0.75);
Constraints can be used in the animation mixer as a type of animation within an ActionSource or ActionSource. However, when a constraint is used as the underlying DataSource for an action, the constraint is no longer accessible from the clip or the source, unlike some other types of data sources (such as FCurves or FCurves or ShapeKeys or ShapeKeys). This is because the full constraint is not stored inside the action (for performance reasons). Constraint clips maintain their own data independent from the source, so when you update the constraint (the source), the clip in the mixer does not reflect the new values.
The only way to create an action source in the SDK is to use the StoreAction command. The StoreAction command stores any kind of action, so you need to make sure you set its parameters correctly. Here are few guidelines to follow:
For the InputObjs parameter, specify the affected parameters, not the constraint itself. And remember that constraints are always defined in global space, so these will always be the global transformation parameters.
Another important things to remember is that you need to specify the affected parameters with Relative Names. Otherwise, you may have connection problems later on instantiating this source as a clip in the mixer.
For the Mode parameter, specify one of the following values:
For the RemoveAnim parameter, if you specify true (the default), the constraint is removed from the animated parameters.
For the AddClip parameter, if you specify false (the default) you can run the AddClip command later on with this source to instantiate it in the mixer.
Action sources and clips based on constraints can be accessed like any other action source or mixer clip (see Sources and Clips for more information). But because a constraint-based source does not store the full constraint, the underlying constraint object is not accessible using either of the following:
AnimationSourceItem.Source or AnimationSourceItem::GetSource
MappedItem.Source2 or MappedItem::GetAnimationSource
You always have to be careful about creating cycles with constraints (see the Animation (user) guide for details), but when you are working with constraint-based animation in the mixer it is easy to lose track of potential cycles, especially if you are copying sources from one object to another.
The CycleChecking command can help detect these cycles, but there's no way to work around them. The only fix is to modify that part of the scene so there is no circular dependency.
At first glance, constraints may seem similar to custom (scripted) operators. However, there are several basic differences in the way they are implemented in Softimage which lead to these differences in behavior and appearance:
In the UI, constraints appear as a collection of properties under the kinematics property of the constrained object while custom operators are nested under their output parameters.
Constraints are generally applied to an entire object unlike custom operators, which are always applied directly to a specific parameter. Constraints affect the object's global transform, not its parameters.
Both constraints and custom operators can be muted and even deleted; however, objects that were altered via constraints remain as they were at the time the constraint was removed, whereas objects affected by custom operators snap back to their pre-operator condition.
Operators and expressions are always evaluated before constraints on the same object. If an operator is reading from a parameter that is driven by a constraint, the data pulled by the operator may be stale. You can work around this problem by applying the constraint to a null and reading the data from there instead.
Except where otherwise noted, this work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License