From BlenderWiki

Jump to: navigation, search

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

The Script constraint panel.
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 #BPYCONSTRAINT comment 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.
  1. #BPYCONSTRAINT
  2. # -*- coding: utf-8 -*-
  3. '''
  4. This demo Pyconstraint allows you to copy the location, rotation and/or scale
  5. from one or more of its three targets.
  6. The same effect could obviously be achieved with up to three
  7. Copy Location/Rotation/Scale constraints in the owner’s stack…
  8. '''
  9.  
  10. import Blender
  11. from Blender import Draw
  12. from Blender import Mathutils
  13. import math
  14.  
  15. '''
  16. This variable specifies the number of targets that this constraint uses.
  17. '''
  18. 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.
  1. '''
  2. This function is called to evaluate the constraint.
  3.     obmatrix:       (Matrix) copy of owner’s “ownerspace” matrix.
  4.     targetmatrices: (List) list of copies of the “targetspace” matrices of the
  5.                     targets (where applicable).
  6.     idprop:         (IDProperties) wrapped data referring to this 
  7.                     constraint instance’s idproperties.
  8. Here, it just copies the user-selected transform properties of each target to
  9. the owner’s ones.
  10. '''
  11. def doConstraint(
  12.         obmatrix,
  13.         targetmatrices,
  14.         idprop):
  15.     # Separate out the transformation components for easy access.
  16.     obloc = obmatrix.translationPart() # Translation
  17.     obrot = obmatrix.toEuler()         # Rotation
  18.     obsca = obmatrix.scalePart()       # Scale
  19.  
  20.     trg = []
  21.     for trgm in targetmatrices:
  22.         trgloc = trgm.translationPart() # Translation
  23.         trgrot = trgm.toEuler()         # Rotation
  24.         trgsca = trgm.scalePart()       # Scale
  25.         trg.append((trgloc, trgrot, trgsca,))
  26.  
  27.     # Define user-settable parameters…
  28.     # Must also be defined in getSettings().
  29.     if not idprop.has_key('user_trg1_loc'): idprop['user_trg1_loc'] = 0
  30.     if not idprop.has_key('user_trg1_rot'): idprop['user_trg1_rot'] = 0
  31.     if not idprop.has_key('user_trg1_sca'): idprop['user_trg1_sca'] = 0
  32.     if not idprop.has_key('user_trg2_loc'): idprop['user_trg2_loc'] = 0
  33.     if not idprop.has_key('user_trg2_rot'): idprop['user_trg2_rot'] = 0
  34.     if not idprop.has_key('user_trg2_sca'): idprop['user_trg2_sca'] = 0
  35.     if not idprop.has_key('user_trg3_loc'): idprop['user_trg3_loc'] = 0
  36.     if not idprop.has_key('user_trg3_rot'): idprop['user_trg3_rot'] = 0
  37.     if not idprop.has_key('user_trg3_sca'): idprop['user_trg3_sca'] = 0
  38.  
  39.     # Code actually doing something: it copies the targets’s selected transform
  40.     # properties to the object ones…
  41.     # Location
  42.     if   idprop['user_trg1_loc']: obloc = trg[0][0]
  43.     elif idprop['user_trg2_loc']: obloc = trg[1][0]
  44.     elif idprop['user_trg3_loc']: obloc = trg[2][0]
  45.     # Rotation
  46.     if   idprop['user_trg1_rot']: obrot = trg[0][1]
  47.     elif idprop['user_trg2_rot']: obrot = trg[1][1]
  48.     elif idprop['user_trg3_rot']: obrot = trg[2][1]
  49.     # Scale
  50.     if   idprop['user_trg1_sca']: obsca = trg[0][2]
  51.     elif idprop['user_trg2_sca']: obsca = trg[1][2]
  52.     elif idprop['user_trg3_sca']: obsca = trg[2][2]
  53.  
  54.     # Convert back into a matrix for loc, scale, rotation…
  55.     mtxloc = Mathutils.TranslationMatrix(obloc)
  56.     mtxrot = obrot.toMatrix().resize4x4()
  57.     mtxsca = Mathutils.Matrix(
  58.         [obsca[0], 0,        0,        0],
  59.         [0,        obsca[1], 0,        0],
  60.         [0,        0,        obsca[2], 0],
  61.         [0,        0,        0,        1])
  62.  
  63.     # ...and recombine the separate elements into a transform matrix.
  64.     outputmatrix = mtxsca * mtxrot * mtxloc
  65.  
  66.     # Return the new (owner) matrix.
  67.     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.

  1. '''
  2. This function manipulates the matrix of a target prior to sending it to
  3. doConstraint().
  4.     target_object:               wrapped data, representing the target object.
  5.     subtarget_bone:              wrapped data, representing the subtarget
  6.                                  pose-bone/vertex-group (where applicable).
  7.     target_matrix:               (Matrix) the transformation matrix of the
  8.                                  target.
  9.     id_properties_of_constraint: (IDProperties) wrapped idproperties.
  10. '''
  11. def doTarget(
  12.         target_object,
  13.         subtarget_bone,
  14.         target_matrix,
  15.         id_properties_of_constraint):
  16.     # We do not modify the targets’ matrices here…
  17.     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.
  1. '''
  2. This function draws a pupblock that lets the user set the values of custom
  3. settings the constraint defines.
  4. This function is called when the user presses the “Options” button.
  5.     idprop: (IDProperties) wrapped data referring to this constraint instance’s
  6.             idproperties.
  7. '''
  8. def getSettings(idprop):
  9.     # Define user-settable parameters.
  10.     # Must also be defined in doConstraint().
  11.     if not idprop.has_key('user_trg1_loc'): idprop['user_trg1_loc'] = 0
  12.     if not idprop.has_key('user_trg1_rot'): idprop['user_trg1_rot'] = 0
  13.     if not idprop.has_key('user_trg1_sca'): idprop['user_trg1_sca'] = 0
  14.     if not idprop.has_key('user_trg2_loc'): idprop['user_trg2_loc'] = 0
  15.     if not idprop.has_key('user_trg2_rot'): idprop['user_trg2_rot'] = 0
  16.     if not idprop.has_key('user_trg2_sca'): idprop['user_trg2_sca'] = 0
  17.     if not idprop.has_key('user_trg3_loc'): idprop['user_trg3_loc'] = 0
  18.     if not idprop.has_key('user_trg3_rot'): idprop['user_trg3_rot'] = 0
  19.     if not idprop.has_key('user_trg3_sca'): idprop['user_trg3_sca'] = 0
  20.  
  21.     # Create temporary vars for interface…
  22.     ubutt_trg1_loc = Draw.Create(idprop['user_trg1_loc'])
  23.     ubutt_trg1_rot = Draw.Create(idprop['user_trg1_rot'])
  24.     ubutt_trg1_sca = Draw.Create(idprop['user_trg1_sca'])
  25.     ubutt_trg2_loc = Draw.Create(idprop['user_trg2_loc'])
  26.     ubutt_trg2_rot = Draw.Create(idprop['user_trg2_rot'])
  27.     ubutt_trg2_sca = Draw.Create(idprop['user_trg2_sca'])
  28.     ubutt_trg3_loc = Draw.Create(idprop['user_trg3_loc'])
  29.     ubutt_trg3_rot = Draw.Create(idprop['user_trg3_rot'])
  30.     ubutt_trg3_sca = Draw.Create(idprop['user_trg3_sca'])
  31.  
  32.     # Define and draw pupblock.
  33.     block = []
  34.     block.append("Target 1: ")
  35.     block.append(("Loc",   ubutt_trg1_loc, "Copy Location from Target 1."))
  36.     block.append(("Rot",   ubutt_trg1_rot, "Copy Rotation from Target 1."))
  37.     block.append(("Scale", ubutt_trg1_sca, "Copy Scale from Target 1."))
  38.     block.append("Target 2: ")
  39.     block.append(("Loc",   ubutt_trg2_loc, "Copy Location from Target 2."))
  40.     block.append(("Rot",   ubutt_trg2_rot, "Copy Rotation from Target 2."))
  41.     block.append(("Scale", ubutt_trg2_sca, "Copy Scale from Target 2."))
  42.     block.append("Target 3: ")
  43.     block.append(("Loc",   ubutt_trg3_loc, "Copy Location from Target 3."))
  44.     block.append(("Rot",   ubutt_trg3_rot, "Copy Rotation from Target 3."))
  45.     block.append(("Scale", ubutt_trg3_sca, "Copy Scale from Target 3."))
  46.  
  47.     retval = Draw.PupBlock("Multi Targets Copy Constraint", block)
  48.  
  49.     # Update id-property values after user changed settings.
  50.     # For each transform prop type, target 1 settings have precedence on
  51.     # target 2, etc.
  52.     if (retval):
  53.         # Location
  54.         if ubutt_trg1_loc.val:
  55.             idprop['user_trg1_loc'] = 1
  56.             idprop['user_trg2_loc'] = 0
  57.             idprop['user_trg3_loc'] = 0
  58.         elif ubutt_trg2_loc.val:
  59.             idprop['user_trg1_loc'] = 0
  60.             idprop['user_trg2_loc'] = 1
  61.             idprop['user_trg3_loc'] = 0
  62.         elif ubutt_trg3_loc.val:
  63.             idprop['user_trg1_loc'] = 0
  64.             idprop['user_trg2_loc'] = 0
  65.             idprop['user_trg3_loc'] = 1
  66.         # Rotation
  67.         if ubutt_trg1_rot.val:
  68.             idprop['user_trg1_rot'] = 1
  69.             idprop['user_trg2_rot'] = 0
  70.             idprop['user_trg3_rot'] = 0
  71.         elif ubutt_trg2_rot.val:
  72.             idprop['user_trg1_rot'] = 0
  73.             idprop['user_trg2_rot'] = 1
  74.             idprop['user_trg3_rot'] = 0
  75.         elif ubutt_trg3_rot.val:
  76.             idprop['user_trg1_rot'] = 0
  77.             idprop['user_trg2_rot'] = 0
  78.             idprop['user_trg3_rot'] = 1
  79.         # Scale
  80.         if ubutt_trg1_sca.val:
  81.             idprop['user_trg1_sca'] = 1
  82.             idprop['user_trg2_sca'] = 0
  83.             idprop['user_trg3_sca'] = 0
  84.         elif ubutt_trg2_sca.val:
  85.             idprop['user_trg1_sca'] = 0
  86.             idprop['user_trg2_sca'] = 1
  87.             idprop['user_trg3_sca'] = 0
  88.         elif ubutt_trg3_sca.val:
  89.             idprop['user_trg1_sca'] = 0
  90.             idprop['user_trg2_sca'] = 0
  91.             idprop['user_trg3_sca'] = 1

Usage

The constraint panel.

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.

The options pop-up dialog.

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…

Demo Pyconstraint example.
The base set, without any constraint.
The Script constraint added, using the demo Pyconstraint.

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!