From BlenderWiki
Script Constraint
Mode: Object and Pose modes
Panel: Constraints (Object context and sub-context, F7, or Editing context, F9, when in Pose mode)
Description
The Script constraint allows you to write a new constraint, all in Python. A so called “Pyconstraint” can have multiple targets (as much as the script requires), and obviously their own options.
Note that it seems that the script containing the Pyconstraint must be loaded in Blender’s Text Editor to be listed in the Script list of available Pyconstraints…
Below is a simple example of Pyconstraint, based on the template featured with Blender 2.49. If you want to learn more about Blender and Python, see this chapter, and the Python API of Blender 2.49.
Options
- Target
- This constraint uses as many targets as defined by the script, and is not functional (red state) when it has none.
- Script
- This drop-down list allows you to select a text datablock containing the Pyconstraint to use.
- If you add a Script constraint through the CtrlAltC shortcut (in a 3D view), a small additional pop-up menu will ask you to select the script to be used.
- Options
- This button shows the constraint’s settings, in a pop-up dialog.
- Refresh
- This button reloads the script code.
- CSpace
- This constraint allows you to chose in which space evaluate its owner’s and targets’ transform properties.
Example
We will create a simple Pyconstraint, based on the template featured with Blender (in a Text Editor, select the Text » Script Templates » Script Constraint menu entry to create a new text datablock initialized with a dummy Pyconstraint code, doing nothing).
Our constraint will be a sort of concatenation of the standard Copy Location, Copy Rotation and Copy Scale ones. It will use three targets, and allow the user to copy the location, rotation, and/or scale of one or more of these targets.
The script
You can File:ManConstraintsScriptDemo.zip the original code produced by the constraint template, and the script file of our example.
Intro
First, the intro of the script.
- 1: The
#BPYCONSTRAINTcomment indicates to Blender that this script is a Pyconstraint. - 2: The UTF8 encoding declaration was added, as there are some non-ansi chars in the file…
- 3 – 8: Then, we have a docstring quickly describing what that constraint does.
- 10 – 13: The import statements are the same as the ones in the template.
- 18: Finally, we specify that we want three targets for our constraint.
#BPYCONSTRAINT# -*- coding: utf-8 -*-'''
This demo Pyconstraint allows you to copy the location, rotation and/or scalefrom one or more of its three targets.The same effect could obviously be achieved with up to threeCopy Location/Rotation/Scale constraints in the owner’s stack…'''
import Blenderfrom Blender import Draw
from Blender import Mathutils
import math
'''
This variable specifies the number of targets that this constraint uses.'''
NUM_TARGETS = 3
doConstraint()
Next, we have the main doConstraint() function, i.e. the one called to evaluate the constraint.
Basically, it:
- 32 – 41: Computes owner’s and targets’ locations, rotations and scales.
- 45 – 53: Defines, if needed, the “idproperties” of the constraint, i.e. its settings.
- 58 – 68: Based on these idproperties values, it copies the location, rotation and/or scale of a given target to the owner’s ones.
- 71 – 79: Computes back the return matrix of the owner, based on its new location/rotation/scale, and return it.
'''
This function is called to evaluate the constraint.obmatrix: (Matrix) copy of owner’s “ownerspace” matrix.targetmatrices: (List) list of copies of the “targetspace” matrices of thetargets (where applicable).idprop: (IDProperties) wrapped data referring to thisconstraint instance’s idproperties.Here, it just copies the user-selected transform properties of each target tothe owner’s ones.'''
def doConstraint(
obmatrix,
targetmatrices,
idprop):# Separate out the transformation components for easy access.obloc = obmatrix.translationPart() # Translation
obrot = obmatrix.toEuler() # Rotation
obsca = obmatrix.scalePart() # Scale
trg = []
for trgm in targetmatrices:
trgloc = trgm.translationPart() # Translation
trgrot = trgm.toEuler() # Rotation
trgsca = trgm.scalePart() # Scale
trg.append((trgloc, trgrot, trgsca,))
# Define user-settable parameters…# Must also be defined in getSettings().if not idprop.has_key('user_trg1_loc'): idprop['user_trg1_loc'] = 0
if not idprop.has_key('user_trg1_rot'): idprop['user_trg1_rot'] = 0
if not idprop.has_key('user_trg1_sca'): idprop['user_trg1_sca'] = 0
if not idprop.has_key('user_trg2_loc'): idprop['user_trg2_loc'] = 0
if not idprop.has_key('user_trg2_rot'): idprop['user_trg2_rot'] = 0
if not idprop.has_key('user_trg2_sca'): idprop['user_trg2_sca'] = 0
if not idprop.has_key('user_trg3_loc'): idprop['user_trg3_loc'] = 0
if not idprop.has_key('user_trg3_rot'): idprop['user_trg3_rot'] = 0
if not idprop.has_key('user_trg3_sca'): idprop['user_trg3_sca'] = 0
# Code actually doing something: it copies the targets’s selected transform# properties to the object ones…# Locationif idprop['user_trg1_loc']: obloc = trg[0][0]
elif idprop['user_trg2_loc']: obloc = trg[1][0]
elif idprop['user_trg3_loc']: obloc = trg[2][0]
# Rotationif idprop['user_trg1_rot']: obrot = trg[0][1]
elif idprop['user_trg2_rot']: obrot = trg[1][1]
elif idprop['user_trg3_rot']: obrot = trg[2][1]
# Scaleif idprop['user_trg1_sca']: obsca = trg[0][2]
elif idprop['user_trg2_sca']: obsca = trg[1][2]
elif idprop['user_trg3_sca']: obsca = trg[2][2]
# Convert back into a matrix for loc, scale, rotation…mtxloc = Mathutils.TranslationMatrix(obloc)
mtxrot = obrot.toMatrix().resize4x4()
mtxsca = Mathutils.Matrix(
[obsca[0], 0, 0, 0],
[0, obsca[1], 0, 0],
[0, 0, obsca[2], 0],
[0, 0, 0, 1])
# ...and recombine the separate elements into a transform matrix.outputmatrix = mtxsca * mtxrot * mtxloc
# Return the new (owner) matrix.return outputmatrix
doTarget()
The next doTarget() function is used to “prepare” the targets’ matrices before sending them to doConstraint(). We do not need it here, so we just return the unmodified matrix.
'''
This function manipulates the matrix of a target prior to sending it todoConstraint().target_object: wrapped data, representing the target object.subtarget_bone: wrapped data, representing the subtargetpose-bone/vertex-group (where applicable).target_matrix: (Matrix) the transformation matrix of thetarget.id_properties_of_constraint: (IDProperties) wrapped idproperties.'''
def doTarget(
target_object,
subtarget_bone,
target_matrix,
id_properties_of_constraint):# We do not modify the targets’ matrices here…return target_matrix
getSettings()
Finally, we have the “GUI” getSettings() function, the one called when you click on the Options button. It’s up to you to choose how to get these settings from the user, but the simplest way is to use a pop-up block.
Our function:
- 107 – 115: Creates the constraints properties, if needed (yes, the same code as in
doConstraint(), that’s not DRY…). - 118 – 141: Prepares the data for the GUI.
- 143 – 187: Draws the pop-up dialog, and stores the returned values in the idproperties of the constraint.
'''
This function draws a pupblock that lets the user set the values of customsettings the constraint defines.This function is called when the user presses the “Options” button.idprop: (IDProperties) wrapped data referring to this constraint instance’sidproperties.'''
def getSettings(idprop):
# Define user-settable parameters.# Must also be defined in doConstraint().if not idprop.has_key('user_trg1_loc'): idprop['user_trg1_loc'] = 0
if not idprop.has_key('user_trg1_rot'): idprop['user_trg1_rot'] = 0
if not idprop.has_key('user_trg1_sca'): idprop['user_trg1_sca'] = 0
if not idprop.has_key('user_trg2_loc'): idprop['user_trg2_loc'] = 0
if not idprop.has_key('user_trg2_rot'): idprop['user_trg2_rot'] = 0
if not idprop.has_key('user_trg2_sca'): idprop['user_trg2_sca'] = 0
if not idprop.has_key('user_trg3_loc'): idprop['user_trg3_loc'] = 0
if not idprop.has_key('user_trg3_rot'): idprop['user_trg3_rot'] = 0
if not idprop.has_key('user_trg3_sca'): idprop['user_trg3_sca'] = 0
# Create temporary vars for interface…ubutt_trg1_loc = Draw.Create(idprop['user_trg1_loc'])
ubutt_trg1_rot = Draw.Create(idprop['user_trg1_rot'])
ubutt_trg1_sca = Draw.Create(idprop['user_trg1_sca'])
ubutt_trg2_loc = Draw.Create(idprop['user_trg2_loc'])
ubutt_trg2_rot = Draw.Create(idprop['user_trg2_rot'])
ubutt_trg2_sca = Draw.Create(idprop['user_trg2_sca'])
ubutt_trg3_loc = Draw.Create(idprop['user_trg3_loc'])
ubutt_trg3_rot = Draw.Create(idprop['user_trg3_rot'])
ubutt_trg3_sca = Draw.Create(idprop['user_trg3_sca'])
# Define and draw pupblock.block = []
block.append("Target 1: ")
block.append(("Loc", ubutt_trg1_loc, "Copy Location from Target 1."))
block.append(("Rot", ubutt_trg1_rot, "Copy Rotation from Target 1."))
block.append(("Scale", ubutt_trg1_sca, "Copy Scale from Target 1."))
block.append("Target 2: ")
block.append(("Loc", ubutt_trg2_loc, "Copy Location from Target 2."))
block.append(("Rot", ubutt_trg2_rot, "Copy Rotation from Target 2."))
block.append(("Scale", ubutt_trg2_sca, "Copy Scale from Target 2."))
block.append("Target 3: ")
block.append(("Loc", ubutt_trg3_loc, "Copy Location from Target 3."))
block.append(("Rot", ubutt_trg3_rot, "Copy Rotation from Target 3."))
block.append(("Scale", ubutt_trg3_sca, "Copy Scale from Target 3."))
retval = Draw.PupBlock("Multi Targets Copy Constraint", block)
# Update id-property values after user changed settings.# For each transform prop type, target 1 settings have precedence on# target 2, etc.if (retval):
# Locationif ubutt_trg1_loc.val:
idprop['user_trg1_loc'] = 1
idprop['user_trg2_loc'] = 0
idprop['user_trg3_loc'] = 0
elif ubutt_trg2_loc.val:
idprop['user_trg1_loc'] = 0
idprop['user_trg2_loc'] = 1
idprop['user_trg3_loc'] = 0
elif ubutt_trg3_loc.val:
idprop['user_trg1_loc'] = 0
idprop['user_trg2_loc'] = 0
idprop['user_trg3_loc'] = 1
# Rotationif ubutt_trg1_rot.val:
idprop['user_trg1_rot'] = 1
idprop['user_trg2_rot'] = 0
idprop['user_trg3_rot'] = 0
elif ubutt_trg2_rot.val:
idprop['user_trg1_rot'] = 0
idprop['user_trg2_rot'] = 1
idprop['user_trg3_rot'] = 0
elif ubutt_trg3_rot.val:
idprop['user_trg1_rot'] = 0
idprop['user_trg2_rot'] = 0
idprop['user_trg3_rot'] = 1
# Scaleif ubutt_trg1_sca.val:
idprop['user_trg1_sca'] = 1
idprop['user_trg2_sca'] = 0
idprop['user_trg3_sca'] = 0
elif ubutt_trg2_sca.val:
idprop['user_trg1_sca'] = 0
idprop['user_trg2_sca'] = 1
idprop['user_trg3_sca'] = 0
elif ubutt_trg3_sca.val:
idprop['user_trg1_sca'] = 0
idprop['user_trg2_sca'] = 0
idprop['user_trg3_sca'] = 1
Usage
Now, with the above code loaded in the Text Editor, select an object, add to it a Script constraint, and in the Script drop-down list, select the pyconstraint (named after its text datablock). Fill in the three targets’ names (you can use a same object for the three, if you want).
The constraint is now working (no more in red state), but it does nothing by default.
Click on the Options button, and in the dialog that pops-up, enable the toggle buttons corresponding to the transform property you want to copy. Note that in (The options pop-up dialog), we selected the location of both Target_1 and Target_2. However, as explained in the code above, the first target’s settings supersede the second ones, etc., hence it will be the first target’s location that is used…
As you can see in (Demo Pyconstraint example), the Owner empty now copies the location of Target_1, the rotation of Target_2, and the scale of Target_3!




