== PyConstraints ==

At last! The ability to code constraints in Python. This opens up many interesting rigging possibilities, as well as making prototyping constraints easier.

* A PyConstraint script must begin with the line 
#BPYCONSTRAINT
* It must also define a doConstraint function, which performs the core actions of the constraint.
* PyConstraints use IDProperties to store custom properties for each PyConstraint instance. The scripter can choose which of these ID-Properties to expose to a user to control the behaviour of the constraint. This must be done using the Draw.PupBlock method.

Credits to Joe Eager (joeedh) for coding the original patch on which this is based. I've made heavy revisions to large parts of the patch.

For more detailed information, and some demo scripts, see the following page:
http://aligorith.googlepages.com/pyconstraints2
This commit is contained in:
Joshua Leung 2007-06-18 07:41:21 +00:00
parent 770291b9ea
commit 01e8789f3f
13 changed files with 862 additions and 37 deletions

@ -54,6 +54,7 @@ struct IpoCurve;
struct FluidsimSettings;
struct Render;
struct RenderResult;
struct bPythonConstraint;
char *getIpoCurveName( struct IpoCurve * icu );
void insert_vert_ipo(struct IpoCurve *icu, float x, float y);
@ -124,6 +125,16 @@ int BPY_button_eval(char *expr, double *value)
return 0;
}
/* constraint.c */
void BPY_pyconstraint_eval(struct bPythonConstraint *con, float obmat[][4], short ownertype, void *ownerdata, float targetmat[][4])
{
}
int BPY_pyconstraint_targets(struct bPythonConstraint *con, float targetmat[][4])
{
return 0;
}
/* writefile.c */
/* struct Oops; */
void free_oops(struct Oops *oops){}

@ -58,6 +58,9 @@
#include "BKE_ipo.h"
#include "BKE_global.h"
#include "BKE_library.h"
#include "BKE_idprop.h"
#include "BPY_extern.h"
#include "blendef.h"
@ -80,6 +83,15 @@ void free_constraint_data (bConstraint *con)
{
if (con->data) {
/* any constraint-type specific stuff here */
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data= con->data;
IDP_FreeProperty(data->prop);
MEM_freeN(data->prop);
}
break;
}
MEM_freeN(con->data);
}
@ -117,6 +129,14 @@ void relink_constraints (struct ListBase *list)
for (con = list->first; con; con=con->next) {
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data;
data = con->data;
ID_NEW(data->tar);
}
break;
case CONSTRAINT_TYPE_KINEMATIC:
{
bKinematicConstraint *data;
@ -260,6 +280,13 @@ void copy_constraints (ListBase *dst, ListBase *src)
char constraint_has_target (bConstraint *con)
{
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = con->data;
if (data->tar)
return 1;
}
break;
case CONSTRAINT_TYPE_TRACKTO:
{
bTrackToConstraint *data = con->data;
@ -354,6 +381,13 @@ Object *get_constraint_target(bConstraint *con, char **subtarget)
* to the name for this constraints subtarget ... NULL otherwise
*/
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data=con->data;
*subtarget = data->subtarget;
return data->tar;
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data = con->data;
@ -450,6 +484,14 @@ void set_constraint_target(bConstraint *con, Object *ob, char *subtarget)
{
/* Set the target for this constraint */
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = con->data;
data->tar= ob;
if(subtarget) BLI_strncpy(data->subtarget, subtarget, 32);
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data = con->data;
@ -590,6 +632,18 @@ void *new_constraint_data (short type)
void *result;
switch (type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data;
data = MEM_callocN(sizeof(bPythonConstraint), "pythonConstraint");
/* everything should be set correctly by calloc, except for the prop->type constant.*/
data->prop = MEM_callocN(sizeof(IDProperty), "PyConstraintProps");
data->prop->type = IDP_GROUP;
result = data;
}
break;
case CONSTRAINT_TYPE_KINEMATIC:
{
bKinematicConstraint *data;
@ -1272,6 +1326,31 @@ short get_constraint_target_matrix (bConstraint *con, short ownertype, void* own
Mat4One (mat);
}
break;
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data;
data = (bPythonConstraint*)con->data;
/* special exception for curves - depsgraph issues */
if (data->tar && data->tar->type == OB_CURVE) {
Curve *cu= data->tar->data;
/* this check is to make sure curve objects get updated on file load correctly.*/
if(cu->path==NULL || cu->path->data==NULL) /* only happens on reload file, but violates depsgraph still... fix! */
makeDispListCurveTypes(data->tar, 0);
}
/* if the script doesn't set the target matrix for any reason, fall back to standard methods */
if (BPY_pyconstraint_targets(data, mat) < 1) {
if (data->tar) {
constraint_target_to_mat4(data->tar, data->subtarget, mat, size);
valid = 1;
}
else
Mat4One (mat);
}
}
break;
case CONSTRAINT_TYPE_CLAMPTO:
{
bClampToConstraint *data;
@ -1316,7 +1395,14 @@ void evaluate_constraint (bConstraint *constraint, Object *ob, short ownertype,
case CONSTRAINT_TYPE_NULL:
case CONSTRAINT_TYPE_KINEMATIC: /* removed */
break;
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data;
data= constraint->data;
BPY_pyconstraint_eval(data, ob->obmat, ownertype, ownerdata, targetmat);
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data;

@ -274,6 +274,7 @@ void IDP_FreeGroup(IDProperty *prop)
for (loop=prop->data.group.first; loop; loop=next)
{
next = loop->next;
BLI_remlink(&prop->data.group, loop);
IDP_FreeProperty(loop);
MEM_freeN(loop);
}

@ -1611,6 +1611,15 @@ static void lib_link_constraints(FileData *fd, ID *id, ListBase *conlist)
}
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data;
data= (bPythonConstraint*)con->data;
data->tar = newlibadr(fd, id->lib, data->tar);
data->text = newlibadr(fd, id->lib, data->text);
//IDP_LibLinkProperty(data->prop, (fd->flags & FD_FLAGS_SWITCH_ENDIAN), fd);
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data;
@ -1717,6 +1726,11 @@ static void direct_link_constraints(FileData *fd, ListBase *lb)
link_list(fd, lb);
for (cons=lb->first; cons; cons=cons->next) {
cons->data = newdataadr(fd, cons->data);
if (cons->type == CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data= cons->data;
data->prop = newdataadr(fd, data->prop);
IDP_DirectLinkProperty(data->prop, (fd->flags & FD_FLAGS_SWITCH_ENDIAN), fd);
}
}
}
@ -6885,6 +6899,13 @@ static void expand_constraints(FileData *fd, Main *mainvar, ListBase *lb)
for (curcon=lb->first; curcon; curcon=curcon->next) {
switch (curcon->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = (bPythonConstraint*)curcon->data;
expand_doit(fd, mainvar, data->tar);
expand_doit(fd, mainvar, data->text);
break;
}
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data = (bActionConstraint*)curcon->data;

@ -702,6 +702,16 @@ static void write_constraints(WriteData *wd, ListBase *conlist)
switch (con->type) {
case CONSTRAINT_TYPE_NULL:
break;
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = (bPythonConstraint*) con->data;
writestruct(wd, DATA, "bPythonConstraint", 1, data);
/*Write ID Properties -- and copy this comment EXACTLY for easy finding
of library blocks that implement this.*/
IDP_WriteProperty(data->prop, wd);
}
break;
case CONSTRAINT_TYPE_TRACKTO:
writestruct(wd, DATA, "bTrackToConstraint", 1, con->data);
break;

@ -38,6 +38,9 @@ struct ListBase;
struct Object;
struct bConstraint;
struct bConstraintChannel;
struct Text;
/* generic constraint editing functions */
struct bConstraint *add_new_constraint(short type);
@ -58,5 +61,9 @@ char *get_con_subtarget_name(struct bConstraint *con, struct Object *target);
void rename_constraint(struct Object *ob, struct bConstraint *con, char *newname);
/* two special functions for PyConstraints */
char *buildmenu_pyconstraints(struct Text *con_text, int *pyconindex);
void validate_pyconstraint_cb(void *arg1, void *arg2);
#endif

@ -39,6 +39,10 @@
#include "DNA_object_types.h"
struct Action;
struct Text;
#ifndef __cplusplus
struct PyObject;
#endif
/* channels reside in Object or Action (ListBase) constraintChannels */
typedef struct bConstraintChannel{
@ -59,6 +63,18 @@ typedef struct bConstraint{
float enforce;
} bConstraint;
/* Python Script Constraint */
typedef struct bPythonConstraint {
Object *tar; /* object to use as target (if required) */
char subtarget[32]; /* bone to use as subtarget (if required) */
struct Text *text; /* text-buffer (containing script) to execute */
IDProperty *prop; /* 'id-properties' used to store custom properties for constraint */
int flag; /* general settings/state indicators accessed by bitmapping */
int pad;
} bPythonConstraint;
/* Single-target subobject constraints */
typedef struct bKinematicConstraint{
Object *tar;
@ -350,6 +366,10 @@ typedef struct bClampToConstraint {
#define LIMIT_NOPARENT 0x01
/* python constraint -> flag */
#define PYCON_USETARGETS 0x01
#define PYCON_SCRIPTERROR 0x02
#define CONSTRAINT_DRAW_PIVOT 0x40
/* important: these defines need to match up with PHY_DynamicTypes headerfile */

@ -46,11 +46,16 @@ struct SpaceScript; /* DNA_space_types.h */
struct Script; /* BPI_script.h */
struct ScrArea; /* DNA_screen_types.h */
struct bScreen; /* DNA_screen_types.h */
struct bPythonConstraint; /* DNA_constraint_types.h */
#ifdef __cplusplus
extern "C" {
#endif
void BPY_pyconstraint_eval(struct bPythonConstraint *con, float obmat[][4], short ownertype, void *ownerdata, float targetmat[][4]);
void BPY_pyconstraint_settings(void *arg1, void *arg2);
int BPY_pyconstraint_targets(struct bPythonConstraint *con, float targetmat[][4]);
int BPY_is_pyconstraint(struct Text *text);
void BPY_start_python( int argc, char **argv );
void BPY_end_python( void );
void BPY_post_start_python( void );

@ -40,6 +40,7 @@
#include "BIF_space.h"
#include "BIF_screen.h"
#include "BIF_toolbox.h"
#include "BKE_action.h" /* for get_pose_channel() */
#include "BKE_library.h"
#include "BKE_object.h" /* during_scriptlink() */
#include "BKE_text.h"
@ -47,6 +48,7 @@
#include "DNA_curve_types.h" /* for struct IpoDriver */
#include "DNA_ID.h" /* ipo driver */
#include "DNA_object_types.h" /* ipo driver */
#include "DNA_constraint_types.h" /* for pyconstraint */
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h" /* for U.pythondir */
@ -66,8 +68,13 @@
#include "api2_2x/Draw.h"
#include "api2_2x/Object.h"
#include "api2_2x/Registry.h"
#include "api2_2x/Pose.h"
#include "api2_2x/bpy.h" /* for the new "bpy" module */
/*these next two are for pyconstraints*/
#include "api2_2x/IDProp.h"
#include "api2_2x/matrix.h"
/* for scriptlinks */
#include "DNA_lamp_types.h"
#include "DNA_camera_types.h"
@ -86,7 +93,6 @@
/* for pydrivers (ipo drivers defined by one-line Python expressions) */
PyObject *bpy_pydriver_Dict = NULL;
/*
* set up a weakref list for Armatures
* creates list in __main__ module dict
@ -160,6 +166,7 @@ PyObject *importText( char *name );
void init_ourImport( void );
void init_ourReload( void );
PyObject *blender_import( PyObject * self, PyObject * args );
PyObject *RunPython2( Text * text, PyObject * globaldict, PyObject *localdict );
void BPY_Err_Handle( char *script_name );
@ -462,10 +469,11 @@ void BPY_Err_Handle( char *script_name )
PyErr_Fetch( &exception, &err, &tb );
if( !exception && !tb ) {
printf( "FATAL: spurious exception\n" );
return;
}
if (!script_name) script_name = "untitled";
//if( !exception && !tb ) {
// printf( "FATAL: spurious exception\n" );
// return;
//}
strcpy( g_script_error.filename, script_name );
@ -1138,6 +1146,376 @@ static float pydriver_error(IpoDriver *driver) {
return 0.0f;
}
/********PyConstraints*********/
int BPY_is_pyconstraint(Text *text)
{
TextLine *tline = text->lines.first;
if (tline && (tline->len > 10)) {
char *line = tline->line;
/* Expected format: #BPYCONSTRAINT
* The actual checks are forgiving, so slight variations also work. */
if (line && line[0] == '#' && strstr(line, "BPYCONSTRAINT")) return 1;
}
return 0;
}
/* This evals py constraints. It is passed all the arguments the normal constraints recieve */
void BPY_pyconstraint_eval(bPythonConstraint *con, float obmat[][4], short ownertype, void *ownerdata, float targetmat[][4])
{
PyObject *srcmat, *tarmat, *idprop;
PyObject *globals;
PyObject *gval;
PyObject *pyargs, *retval;
MatrixObject *retmat;
int row, col;
if ( !con->text ) return;
if ( con->flag & PYCON_SCRIPTERROR) return;
globals = CreateGlobalDictionary();
srcmat = newMatrixObject( (float*)obmat, 4, 4, Py_NEW );
tarmat = newMatrixObject( (float*)targetmat, 4, 4, Py_NEW );
idprop = BPy_Wrap_IDProperty( NULL, con->prop, NULL);
/* since I can't remember what the armature weakrefs do, I'll just leave this here
commented out. This function was based on pydrivers, and it might still be relevent.
if( !setup_armature_weakrefs()){
fprintf( stderr, "Oops - weakref dict setup\n");
return result;
}
*/
retval = RunPython( con->text, globals );
if ( retval == NULL ) {
BPY_Err_Handle(con->text->id.name);
ReleaseGlobalDictionary( globals );
con->flag |= PYCON_SCRIPTERROR;
/* free temp objects */
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
return;
}
if (retval) {Py_XDECREF( retval );}
retval = NULL;
gval = PyDict_GetItemString(globals, "doConstraint");
if (!gval) {
ReleaseGlobalDictionary( globals );
/* free temp objects */
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
printf("ERROR: no doConstraint function in constraint!\n");
return;
}
/* Now for the fun part! Try and find the functions we need. */
if (PyFunction_Check(gval) ) {
pyargs = Py_BuildValue("OOO", srcmat, tarmat, idprop);
retval = PyObject_CallObject(gval, pyargs);
Py_XDECREF( pyargs );
} else {
printf("ERROR: doConstraint is supposed to be a function!\n");
con->flag |= PYCON_SCRIPTERROR;
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
return;
}
if (!retval) {
BPY_Err_Handle(con->text->id.name);
con->flag |= PYCON_SCRIPTERROR;
/* free temp objects */
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
return;
}
if (!PyObject_TypeCheck(retval, &matrix_Type)) {
printf("Error in PyConstraint - doConstraint: Function not returning a matrix!\n");
con->flag |= PYCON_SCRIPTERROR;
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
Py_XDECREF( retval );
return;
}
retmat = (MatrixObject*) retval;
if (retmat->rowSize != 4 || retmat->colSize != 4) {
printf("Error in PyConstraint - doConstraint: Matrix returned is the wrong size!\n");
con->flag |= PYCON_SCRIPTERROR;
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
Py_XDECREF( retval );
return;
}
/* this is the reverse of code taken from newMatrix() */
for(row = 0; row < 4; row++) {
for(col = 0; col < 4; col++) {
obmat[row][col] = retmat->contigPtr[row*4+col];
}
}
/* clear globals */
ReleaseGlobalDictionary( globals );
/* free temp objects */
Py_XDECREF( idprop );
Py_XDECREF( srcmat );
Py_XDECREF( tarmat );
Py_XDECREF( retval );
}
/* This evaluates whether constraint uses targets, and also the target matrix
* Return code of 0 = doesn't use targets, 1 = uses targets + matrix set, -1 = uses targets + matrix not set
*/
int BPY_pyconstraint_targets(bPythonConstraint *con, float targetmat[][4])
{
PyObject *tar, *subtar;
PyObject *tarmat, *idprop;
PyObject *globals;
PyObject *gval, *gval2;
PyObject *pyargs, *retval;
MatrixObject *retmat;
bPoseChannel *pchan;
int row, col;
if ( !con->text ) return 0;
if ( con->flag & PYCON_SCRIPTERROR) return 0;
globals = CreateGlobalDictionary();
tar = Object_CreatePyObject( con->tar );
if ( con->tar )
pchan = get_pose_channel( con->tar->pose, con->subtarget );
else
pchan = NULL;
subtar = PyPoseBone_FromPosechannel( pchan );
tarmat = newMatrixObject( (float*)targetmat, 4, 4, Py_NEW );
idprop = BPy_Wrap_IDProperty( NULL, con->prop, NULL);
/* since I can't remember what the armature weakrefs do, I'll just leave this here
commented out. This function was based on pydrivers, and it might still be relevent.
if( !setup_armature_weakrefs()){
fprintf( stderr, "Oops - weakref dict setup\n");
return result;
}
*/
retval = RunPython( con->text, globals );
if ( retval == NULL ) {
BPY_Err_Handle(con->text->id.name);
ReleaseGlobalDictionary( globals );
con->flag |= PYCON_SCRIPTERROR;
/* free temp objects */
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
return 0;
}
if (retval) {Py_XDECREF( retval );}
retval = NULL;
/* try to find USE_TARGET global constant */
gval = PyDict_GetItemString(globals, "USE_TARGET");
if (!gval) {
ReleaseGlobalDictionary( globals );
/* free temp objects */
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
return 0;
}
/* try to find doTarget function to set the target matrix */
gval2 = PyDict_GetItemString(globals, "doTarget");
if (!gval2) {
ReleaseGlobalDictionary( globals );
/* free temp objects */
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
return -1;
}
/* Now for the fun part! Try and find the functions we need.*/
if (PyFunction_Check(gval2) ) {
pyargs = Py_BuildValue("OOOO", tar, subtar, tarmat, idprop);
retval = PyObject_CallObject(gval2, pyargs);
Py_XDECREF( pyargs );
} else {
printf("ERROR: doTarget is supposed to be a function!\n");
con->flag |= PYCON_SCRIPTERROR;
ReleaseGlobalDictionary( globals );
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
return -1;
}
if (!retval) {
BPY_Err_Handle(con->text->id.name);
con->flag |= PYCON_SCRIPTERROR;
/* free temp objects */
ReleaseGlobalDictionary( globals );
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
return -1;
}
if (!PyObject_TypeCheck(retval, &matrix_Type)) {
ReleaseGlobalDictionary( globals );
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
Py_XDECREF( retval );
return -1;
}
retmat = (MatrixObject*) retval;
if (retmat->rowSize != 4 || retmat->colSize != 4) {
printf("Error in PyConstraint - doTarget: Matrix returned is the wrong size!\n");
con->flag |= PYCON_SCRIPTERROR;
ReleaseGlobalDictionary( globals );
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
Py_XDECREF( retval );
return -1;
}
/* this is the reverse of code taken from newMatrix() */
for(row = 0; row < 4; row++) {
for(col = 0; col < 4; col++) {
targetmat[row][col] = retmat->contigPtr[row*4+col];
}
}
/* clear globals */
ReleaseGlobalDictionary( globals );
/* free temp objects */
Py_XDECREF( tar );
Py_XDECREF( subtar );
Py_XDECREF( idprop );
Py_XDECREF( tarmat );
Py_XDECREF( retval );
return 1;
}
/* This draws+handles the user-defined interface for editing pyconstraints idprops */
void BPY_pyconstraint_settings(void *arg1, void *arg2)
{
bPythonConstraint *con= (bPythonConstraint *)arg1;
PyObject *idprop;
PyObject *globals;
PyObject *gval;
PyObject *retval;
if ( !con->text ) return;
if ( con->flag & PYCON_SCRIPTERROR) return;
globals = CreateGlobalDictionary();
idprop = BPy_Wrap_IDProperty( NULL, con->prop, NULL);
retval = RunPython( con->text, globals );
if ( retval == NULL ) {
BPY_Err_Handle(con->text->id.name);
ReleaseGlobalDictionary( globals );
con->flag |= PYCON_SCRIPTERROR;
/* free temp objects */
Py_XDECREF( idprop );
return;
}
if (retval) {Py_XDECREF( retval );}
retval = NULL;
gval = PyDict_GetItemString(globals, "getSettings");
if (!gval) {
printf("ERROR: no getSettings function in constraint!");
/* free temp objects */
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
return;
}
/* Now for the fun part! Try and find the functions we need. */
if (PyFunction_Check(gval) ) {
retval = PyObject_CallFunction(gval, "O", idprop);
} else {
printf("ERROR: getSettings is supposed to be a function!\n");
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
return;
}
if (!retval) {
BPY_Err_Handle(con->text->id.name);
con->flag |= PYCON_SCRIPTERROR;
/* free temp objects */
ReleaseGlobalDictionary( globals );
Py_XDECREF( idprop );
return;
}
else {
/* clear globals */
ReleaseGlobalDictionary( globals );
/* free temp objects */
Py_XDECREF( idprop );
return;
}
}
/* Update function, it gets rid of pydrivers global dictionary, forcing
* BPY_pydriver_eval to recreate it. This function is used to force
* reloading the Blender text module "pydrivers.py", if available, so

@ -1918,6 +1918,8 @@ static PyObject *M_Constraint_TypeDict( void )
PyInt_FromLong( CONSTRAINT_TYPE_RIGIDBODYJOINT ) );
PyConstant_Insert( d, "CLAMPTO",
PyInt_FromLong( CONSTRAINT_TYPE_CLAMPTO ) );
PyConstant_Insert( d, "PYTHON",
PyInt_FromLong( CONSTRAINT_TYPE_PYTHON ) );
}
return S;
}

@ -109,6 +109,7 @@
#include "DNA_vfont_types.h"
#include "DNA_view3d_types.h"
#include "DNA_world_types.h"
#include "DNA_text_types.h"
#include "BKE_anim.h"
#include "BKE_armature.h"
@ -143,6 +144,7 @@
#include "BSE_edit.h"
#include "BDR_editobject.h"
#include "BPY_extern.h"
#include "butspace.h" // own module
@ -328,6 +330,9 @@ void get_constraint_typestring (char *str, void *con_v)
bConstraint *con= con_v;
switch (con->type){
case CONSTRAINT_TYPE_PYTHON:
strcpy(str, "Script");
return;
case CONSTRAINT_TYPE_CHILDOF:
strcpy (str, "Child Of");
return;
@ -508,6 +513,7 @@ void autocomplete_vgroup(char *str, void *arg_v)
}
}
/* draw panel showing settings for a constraint */
static void draw_constraint (uiBlock *block, ListBase *list, bConstraint *con, short *xco, short *yco)
{
Object *ob= OBACT, *target;
@ -599,10 +605,60 @@ static void draw_constraint (uiBlock *block, ListBase *list, bConstraint *con, s
}
else {
switch (con->type){
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = con->data;
uiBut *but2;
static int pyconindex=0;
char *menustr;
height = 90;
uiDefBut(block, ROUNDBOX, B_DIFF, "", *xco-10, *yco-height, width+40, height-1, NULL, 5.0, 0.0, 12, rb_col, "");
uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Script:", *xco+60, *yco-24, 55, 18, NULL, 0.0, 0.0, 0.0, 0.0, "");
/* do the scripts menu */
menustr = buildmenu_pyconstraints(data->text, &pyconindex);
but2 = uiDefButI(block, MENU, B_CONSTRAINT_TEST, menustr,
*xco+120, *yco-24, 150, 20, &pyconindex,
0.0, 1.0, 0, 0, "Set the Script Constraint to use");
uiButSetFunc(but2, validate_pyconstraint_cb, data, &pyconindex);
MEM_freeN(menustr);
uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Target:", *xco+60, *yco-48, 55, 18, NULL, 0.0, 0.0, 0.0, 0.0, "");
if (data->flag & PYCON_USETARGETS) {
/* Draw target parameters */
uiBlockBeginAlign(block);
uiDefIDPoinBut(block, test_obpoin_but, ID_OB, B_CONSTRAINT_CHANGETARGET, "OB:", *xco+120, *yco-48, 150, 18, &data->tar, "Target Object");
if (is_armature_target) {
but= uiDefBut(block, TEX, B_CONSTRAINT_CHANGETARGET, "BO:", *xco+120, *yco-66,150,18, &data->subtarget, 0, 24, 0, 0, "Subtarget Bone");
uiButSetCompleteFunc(but, autocomplete_bone, (void *)data->tar);
}
else {
strcpy (data->subtarget, "");
}
uiBlockEndAlign(block);
}
else {
/* Draw indication that no target needed */
uiDefBut(block, LABEL, B_CONSTRAINT_TEST, "Not Applicable", *xco+120, *yco-48, 150, 18, NULL, 0.0, 0.0, 0.0, 0.0, "");
}
/* settings */
uiBlockBeginAlign(block);
but=uiDefBut(block, BUT, B_CONSTRAINT_TEST, "Options", *xco, *yco-88, (width/2),18, NULL, 0, 24, 0, 0, "Change some of the constraint's settings.");
uiButSetFunc(but, BPY_pyconstraint_settings, data, NULL);
uiDefBut(block, BUT, B_CONSTRAINT_TEST, "Refresh", *xco+((width/2)+10), *yco-88, (width/2),18, NULL, 0, 24, 0, 0, "Force constraint to refresh it's settings");
uiBlockEndAlign(block);
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data = con->data;
height = 88;
uiDefBut(block, ROUNDBOX, B_DIFF, "", *xco-10, *yco-height, width+40,height-1, NULL, 5.0, 0.0, 12, rb_col, "");
@ -1336,8 +1392,13 @@ static uiBlock *add_constraintmenu(void *arg_unused)
}
uiDefBut(block, SEPR, 0, "", 0, yco-=6, 120, 6, NULL, 0.0, 0.0, 0, 0, "");
uiDefBut(block, BUTM, B_CONSTRAINT_ADD_PYTHON, "Script", 0, yco-=20, 160, 19, NULL, 0.0, 0.0, 1, 0, "");
uiDefBut(block, SEPR, 0, "", 0, yco-=6, 120, 6, NULL, 0.0, 0.0, 0, 0, "");
uiDefBut(block, BUTM, B_CONSTRAINT_ADD_NULL,"Null", 0, yco-=20, 160, 19, NULL, 0.0, 0.0, 1, 0, "");
uiTextBoundsBlock(block, 50);
uiBlockSetDirection(block, UI_DOWN);
@ -1360,7 +1421,17 @@ void do_constraintbuts(unsigned short event)
if(ob->pose) ob->pose->flag |= POSE_RECALC; // checks & sorts pose channels
DAG_scene_sort(G.scene);
break;
case B_CONSTRAINT_ADD_PYTHON:
{
bConstraint *con;
con = add_new_constraint(CONSTRAINT_TYPE_PYTHON);
add_constraint_to_active(ob, con);
BIF_undo_push("Add constraint");
}
break;
case B_CONSTRAINT_ADD_NULL:
{
bConstraint *con;

@ -45,6 +45,7 @@
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_text_types.h"
#include "DNA_view3d_types.h"
#include "BKE_action.h"
@ -52,6 +53,7 @@
#include "BKE_constraint.h"
#include "BKE_depsgraph.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_ipo.h"
#include "BKE_object.h"
#include "BKE_utildefines.h"
@ -65,6 +67,8 @@
#include "BIF_space.h"
#include "BIF_toolbox.h"
#include "BPY_extern.h"
#include "blendef.h"
#include "nla.h"
#include "mydevice.h"
@ -247,7 +251,14 @@ char *get_con_subtarget_name(bConstraint *con, Object *target)
* to the name for this constraints subtarget ... NULL otherwise
*/
switch (con->type) {
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = con->data;
if (data->flag & PYCON_USETARGETS) {
if (data->tar==target) return data->subtarget;
}
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data = con->data;
@ -376,6 +387,51 @@ static void test_constraints (Object *owner, const char* substring)
curcon->flag &= ~CONSTRAINT_DISABLE;
switch (curcon->type){
case CONSTRAINT_TYPE_PYTHON:
{
bPythonConstraint *data = curcon->data;
float dummy_matrix[4][4];
/* is there are valid script? */
if (!data->text) {
curcon->flag |= CONSTRAINT_DISABLE;
break;
}
else if (!BPY_is_pyconstraint(data->text)) {
curcon->flag |= CONSTRAINT_DISABLE;
break;
}
data->flag &= ~PYCON_SCRIPTERROR;
/* does the constraint require target input? */
if (BPY_pyconstraint_targets(data, dummy_matrix))
data->flag |= PYCON_USETARGETS;
else
data->flag &= ~PYCON_USETARGETS;
/* check whether we have a valid target */
if (data->flag & PYCON_USETARGETS) {
/* validate target */
if (!exist_object(data->tar)) {
data->tar = NULL;
curcon->flag |= CONSTRAINT_DISABLE;
break;
}
if ( (data->tar == owner) &&
(!get_named_bone(get_armature(owner),
data->subtarget))) {
curcon->flag |= CONSTRAINT_DISABLE;
break;
}
}
else {
/* don't hold onto target */
data->tar = NULL;
BLI_strncpy(data->subtarget, "", 32);
}
}
break;
case CONSTRAINT_TYPE_ACTION:
{
bActionConstraint *data = curcon->data;
@ -688,21 +744,21 @@ void add_constraint(int only_IK)
else {
if(pchanact) {
if(pchansel)
nr= pupmenu("Add Constraint to Active Bone%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5|Stretch To%x7|Action%x16");
nr= pupmenu("Add Constraint to Active Bone%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|%l|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|Stretch To%x7|Action%x16|%l|Script%x18");
else if(obsel && obsel->type==OB_CURVE)
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5|Follow Path%x6|Clamp To%x17|Stretch To%x7|Action%x16");
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|%l|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|Follow Path%x6|Clamp To%x17|Stretch To%x7|Action%x16|%l|Script%x18");
else if(obsel)
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5|Stretch To%x7|Action%x16");
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|%l|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|Stretch To%x7|Action%x16|%l|Script%x18");
else
nr= pupmenu("Add Constraint to New Empty Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5|Stretch To%x7");
nr= pupmenu("Add Constraint to New Empty Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|%l|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|Stretch To%x7|%l|Script%x18");
}
else {
if(obsel && obsel->type==OB_CURVE)
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5|Follow Path%x6|Clamp To%x17");
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|Follow Path%x6|Clamp To%x17|%l|Script%x18");
else if(obsel)
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5");
nr= pupmenu("Add Constraint to Active Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|%l|Script%x18");
else
nr= pupmenu("Add Constraint to New Empty Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|Track To%x3|Floor%x4|Locked Track%x5");
nr= pupmenu("Add Constraint to New Empty Object%t|Copy Location%x1|Copy Rotation%x2|Copy Scale%x8|Limit Location%x13|Limit Rotation%x14|Limit Scale%x15|%l|Track To%x3|Floor%x4|Locked Track%x5|%l|Script%x18");
}
}
@ -762,6 +818,21 @@ void add_constraint(int only_IK)
cu->flag |= CU_PATH;
con = add_new_constraint(CONSTRAINT_TYPE_CLAMPTO);
}
else if(nr==18) {
char *menustr;
int scriptint= 0, dummy_active=0;
/* popup a list of usable scripts */
menustr = buildmenu_pyconstraints(NULL, &dummy_active);
scriptint = pupmenu(menustr);
MEM_freeN(menustr);
/* only add constraint if a script was chosen */
if (scriptint) {
con = add_new_constraint(CONSTRAINT_TYPE_PYTHON);
validate_pyconstraint_cb(con->data, &scriptint);
}
}
if(con==NULL) return; /* paranoia */
@ -921,3 +992,53 @@ void rename_constraint(Object *ob, bConstraint *con, char *oldname)
}
/* ********************** CONSTRAINT-SPECIFIC STUFF ********************* */
/* ------------- PyConstraints ------------------ */
/* this callback sets the text-file to be used for selected menu item */
void validate_pyconstraint_cb(void *arg1, void *arg2)
{
bPythonConstraint *data = arg1;
Text *text;
int index = *((int *)arg2);
int i;
/* innovative use of a for loop to search */
for (text=G.main->text.first, i=1; text && index!=i; i++, text=text->id.next);
data->text = text;
}
/* this returns a string for the list of usable pyconstraint script names */
char *buildmenu_pyconstraints(Text *con_text, int *pyconindex)
{
Text *text;
char *menustr = MEM_callocN(128, "menustr pyconstraints");
char *name, stmp[128];
int buf = 128;
int used = strlen("Scripts: %t") + 1;
int i;
sprintf(menustr, "%s", "Scripts: %t");
for (text=G.main->text.first, i=1; text; i++, text=text->id.next) {
/* this is important to ensure that right script is shown as active */
if (text == con_text) *pyconindex = i;
/* menu entry is length of name + 3(len(|%X)) + 6 characters for the int.*/
if (BPY_is_pyconstraint(text)) {
name= text->id.name;
if (strlen(name)+used+10 >= buf) {
char *newbuf = MEM_callocN(buf+128, "menustr pyconstraints 2");
memcpy(newbuf, menustr, used);
MEM_freeN(menustr);
menustr = newbuf;
buf += 128;
}
sprintf(stmp, "|%s%%x%d", name, i);
strcat(menustr, stmp);
used += strlen(name)+10;
}
}
return menustr;
}

@ -52,6 +52,8 @@
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_text_types.h"
#include "DNA_constraint_types.h"
#include "DNA_action_types.h"
#include "BIF_drawtext.h"
#include "BIF_interface.h"
@ -59,11 +61,14 @@
#include "BIF_screen.h"
#include "BIF_space.h"
#include "BIF_toolbox.h"
#include "BKE_global.h"
#include "BKE_library.h"
#include "BKE_main.h"
#include "BKE_sca.h"
#include "BKE_text.h"
#include "BKE_depsgraph.h"
#include "BSE_filesel.h"
#include "BPY_extern.h"
@ -135,27 +140,68 @@ void do_text_buttons(unsigned short event)
break;
case B_TEXTDELETE:
text= st->text;
if (!text) return;
/* make the previous text active, if its not there make the next text active */
if (st->text->id.prev) {
st->text = st->text->id.prev;
pop_space_text(st);
} else if (st->text->id.next) {
st->text = st->text->id.next;
pop_space_text(st);
}
{
Object *obt;
bConstraint *con;
int update;
BPY_clear_bad_scriptlinks(text);
free_text_controllers(text);
unlink_text(text);
free_libblock(&G.main->text, text);
allqueue(REDRAWTEXT, 0);
allqueue(REDRAWHEADERS, 0);
text= st->text;
if (!text) return;
/* make the previous text active, if its not there make the next text active */
if (st->text->id.prev) {
st->text = st->text->id.prev;
pop_space_text(st);
} else if (st->text->id.next) {
st->text = st->text->id.next;
pop_space_text(st);
}
/*check all pyconstraints*/
for (obt=G.main->object.first; obt; obt=obt->id.next) {
update = 0;
if(obt->type==OB_ARMATURE && obt->pose) {
bPoseChannel *pchan;
for(pchan= obt->pose->chanbase.first; pchan; pchan= pchan->next) {
for (con = pchan->constraints.first; con; con=con->next) {
if (con->type==CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data = con->data;
if (data->text==text) data->text = NULL;
update = 1;
}
}
}
}
for (con = obt->constraints.first; con; con=con->next) {
if (con->type==CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data = con->data;
if (data->text==text) data->text = NULL;
update = 1;
}
}
if (update) {
DAG_object_flush_update(G.scene, obt, OB_RECALC_DATA);
}
}
BPY_clear_bad_scriptlinks(text);
free_text_controllers(text);
unlink_text(text);
free_libblock(&G.main->text, text);
allqueue(REDRAWTEXT, 0);
allqueue(REDRAWHEADERS, 0);
/*for if any object constraints were changed.*/
allqueue(REDRAWVIEW3D, 0);
allqueue(REDRAWBUTSOBJECT, 0);
allqueue(REDRAWBUTSEDIT, 0);
BIF_undo_push("Delete Text");
}
break;
/*
@ -265,6 +311,42 @@ static void do_text_filemenu(void *arg, int event)
case 6:
run_python_script(st);
break;
case 7:
{
Object *obt;
bConstraint *con;
short update;
/* check all pyconstraints */
for (obt=G.main->object.first; obt; obt=obt->id.next) {
update = 0;
if(obt->type==OB_ARMATURE && obt->pose) {
bPoseChannel *pchan;
for(pchan= obt->pose->chanbase.first; pchan; pchan= pchan->next) {
for (con = pchan->constraints.first; con; con=con->next) {
if (con->type==CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data = con->data;
if (data->text==text) data->flag = 0;
update = 1;
}
}
}
}
for (con = obt->constraints.first; con; con=con->next) {
if (con->type==CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data = con->data;
if (data->text==text) data->flag = 0;
update = 1;
}
}
if (update) {
DAG_object_flush_update(G.scene, obt, OB_RECALC_DATA);
}
}
}
break;
default:
break;
}
@ -608,13 +690,23 @@ static uiBlock *text_filemenu(void *arg_unused)
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "New|Alt N", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 1, "");
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Open...|Alt O", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 2, "");
if(text) {
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Reopen|Alt R", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 3, "");
uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Save|Alt S", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 4, "");
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Save As...", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 5, "");
uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Run Python Script|Alt P", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 6, "");
if (BPY_is_pyconstraint(text))
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Refresh All PyConstraints", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 7, "");
uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
}
uiDefIconTextBlockBut(block, text_template_scriptsmenu, NULL, ICON_RIGHTARROW_THIN, "Script Templates", 0, yco-=20, 120, 19, "");