diff --git a/release/scripts/scripttemplate_pyconstraint.py b/release/scripts/scripttemplate_pyconstraint.py index 12bd4bcb73d..9ea00a2a2b1 100644 --- a/release/scripts/scripttemplate_pyconstraint.py +++ b/release/scripts/scripttemplate_pyconstraint.py @@ -16,8 +16,8 @@ script_data = \ PyConstraints are text buffers that start with #BPYCONSTRAINT. They must define a doConstraint function. The doConstraint -function is called with the world-space matrix of the parent object/posebone -as the first argument, the world-space matrix of the target object/posebone as +function is called with the matrix of the parent object/posebone +as the first argument, the matrix of the target object/posebone as the second, and an ID property that's attached to the current constraint instance. The function then must return a 4x4 Mathutils.Matrix() object. @@ -30,6 +30,13 @@ When a constraint needs to have a Target Object/Bone, the USE_TARGET line below must be present. Also, if any special matrix creation needs to be performed for the target, a doTarget function must also be defined. +Optionally, a doDriver function may be defined. This function is used +to get and/or modify settings of the owner and target, and as such, should +be used with caution. Under no circumstances, should you modify the transforms +of either the owner or the target in this function, as they will either have +no effect, or will result in other things not being updated correctly. Therefore, +it should be used sparringly. + <------- End removable description section -----------> """ # Add a licence here if you wish to re-distribute, we recommend the GPL @@ -64,12 +71,37 @@ def getSettings(idproperty): # this optional function performs special actions that only require # access to the target data - calculation of special information +# targetobject: (Object) wrapped data referring to the target object +# subtarget: (String/PoseChannel) +# - If the target is a PoseChannel in an armature, then this +# is a wrapped copy of that PoseChannel. +# - Otherwise, this field will either be an empty string or the +# name of the vertex group +# targetmatrix: (Matrix) matrix that will be used as the target matrix +# idprop: (IDProperties) wrapped data referring to this +# constraint instance's idproperties """ def doTarget (targetobject, subtarget, targetmatix, idproperty): # return a 4x4 matrix (which acts as the matrix of the target) return targetmatrix; """ +# This optional function is used to modify/get values on the owner and the +# target for creating certain setups. It should be used sparingly +# ownerobject: (Object) wrapped data referring to the owning object +# subowner: (PoseChannel) wrapped data referring to the PoseChannel that +# owns the constraint (where applicable) +# target: (Object) wrapped data referring to the target +# subtarget: (String/PoseChannel) +# - If the target is a PoseChannel in an armature, then this +# is a wrapped copy of that PoseChannel. +# - Otherwise, this field will either be an empty string or the +# name of the vertex group +""" +def doDriver (ownerobject, subowner, targetobject, subtarget, idproperty): + pass; +""" + ''' new_text = bpy.data.texts.new('pyconstraint_template.py') diff --git a/source/blender/blenkernel/bad_level_call_stubs/stubs.c b/source/blender/blenkernel/bad_level_call_stubs/stubs.c index 340ac1ed693..0c81cdfa245 100644 --- a/source/blender/blenkernel/bad_level_call_stubs/stubs.c +++ b/source/blender/blenkernel/bad_level_call_stubs/stubs.c @@ -125,10 +125,13 @@ int BPY_button_eval(char *expr, double *value) return 0; } -/* constraint.c */ +/* PyConstraints - BPY_interface.c */ void BPY_pyconstraint_eval(struct bPythonConstraint *con, float ownermat[][4], float targetmat[][4]) { } +void BPY_pyconstraint_driver(struct bPythonConstraint *con, struct bConstraintOb *cob, struct Object *target, char subtarget[]) +{ +} int BPY_pyconstraint_targets(struct bPythonConstraint *con, float targetmat[][4]) { return 0; diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 7f736a644f9..5ed729fbd37 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -2815,16 +2815,29 @@ void solve_constraints (ListBase *conlist, bConstraintOb *cob, float ctime) /* value should have been set from IPO's/Constraint Channels already */ enf = con->enforce; - /* move owner into right space */ + /* move owner matrix into right space */ constraint_mat_convertspace(cob->ob, cob->pchan, cob->matrix, CONSTRAINT_SPACE_WORLD, con->ownspace); + Mat4CpyMat4(oldmat, cob->matrix); - /* Get the target matrix - in right space to be used */ + /* get the target matrix - in right space to be used */ ownerdata= ((cob->pchan)? (void *)cob->pchan : (void *)cob->ob); get_constraint_target_matrix(con, cob->type, ownerdata, tarmat, ctime); - Mat4CpyMat4(oldmat, cob->matrix); - /* solve the constraint */ + /* Special Hack for PyConstraints to be able to set settings on the owner and/or + * target. Technically, this violates the design of constraints (as constraints should + * only act on matrices to alter the final transform of an owner), but on the other + * hand, this makes PyConstraints more powerful as it enables certain setups to be created + * and work reliably. + */ + if (con->type == CONSTRAINT_TYPE_PYTHON) { + bPythonConstraint *pycon= (bPythonConstraint *)con->data; + + /* as usual, the function for this is defined in BPY_interface.c */ + BPY_pyconstraint_driver(pycon, cob, pycon->tar, pycon->subtarget); + } + + /* Solve the constraint */ evaluate_constraint(con, cob->matrix, tarmat); /* Interpolate the enforcement, to blend result of constraint into final owner transform */ diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index ee7afe7dcf6..0e662c38d31 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -47,6 +47,7 @@ struct Script; /* BPI_script.h */ struct ScrArea; /* DNA_screen_types.h */ struct bScreen; /* DNA_screen_types.h */ struct bPythonConstraint; /* DNA_constraint_types.h */ +struct bConstraintOb; /* BKE_constraint.h */ #ifdef __cplusplus extern "C" { #endif @@ -71,6 +72,7 @@ extern "C" { void BPy_Free_DrawButtonsList(void); void BPY_pyconstraint_eval(struct bPythonConstraint *con, float ownermat[][4], float targetmat[][4]); + void BPY_pyconstraint_driver(struct bPythonConstraint *con, struct bConstraintOb *cob, struct Object *target, char subtarget[]); 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); diff --git a/source/blender/python/BPY_interface.c b/source/blender/python/BPY_interface.c index bcdf5ecee9c..67ad88359a8 100644 --- a/source/blender/python/BPY_interface.c +++ b/source/blender/python/BPY_interface.c @@ -44,6 +44,7 @@ #include "BKE_library.h" #include "BKE_object.h" /* during_scriptlink() */ #include "BKE_text.h" +#include "BKE_constraint.h" /* for bConstraintOb */ #include "DNA_curve_types.h" /* for struct IpoDriver */ #include "DNA_ID.h" /* ipo driver */ @@ -1163,7 +1164,9 @@ int BPY_is_pyconstraint(Text *text) return 0; } -/* This evals py constraints. It is passed all the arguments the normal constraints recieve */ +/* PyConstraints Evaluation Function (only called from evaluate_constraint) + * This function is responsible for modifying the ownermat that it is passed. + */ void BPY_pyconstraint_eval(bPythonConstraint *con, float ownermat[][4], float targetmat[][4]) { PyObject *srcmat, *tarmat, *idprop; @@ -1290,6 +1293,110 @@ void BPY_pyconstraint_eval(bPythonConstraint *con, float ownermat[][4], float ta Py_XDECREF( retval ); } +/* PyConstraints 'Driver' Function + * This function is responsible for running any code that requires full access to the owner and the target + * It should be used sparringly, and only for doing 'hacks' which are not possible any other way. + */ +void BPY_pyconstraint_driver(bPythonConstraint *con, bConstraintOb *cob, Object *target, char subtarget[]) +{ + PyObject *owner, *subowner, *tar, *subtar; + PyObject *idprop; + PyObject *globals, *gval; + PyObject *pyargs, *retval; + + if ( !con->text ) return; + if ( con->flag & PYCON_SCRIPTERROR) return; + + globals = CreateGlobalDictionary(); + + owner = Object_CreatePyObject( cob->ob ); + subowner = PyPoseBone_FromPosechannel( cob->pchan ); + + tar = Object_CreatePyObject( target ); + if ( (target) && (target->type==OB_ARMATURE) ) { + bPoseChannel *pchan; + pchan = get_pose_channel( target->pose, subtarget ); + subtar = PyPoseBone_FromPosechannel( pchan ); + } + else + subtar = PyString_FromString(subtarget); + + 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( owner ); + Py_XDECREF( subowner ); + Py_XDECREF( tar ); + Py_XDECREF( subtar ); + return; + } + + if (retval) {Py_XDECREF( retval );} + retval = NULL; + + gval = PyDict_GetItemString(globals, "doDriver"); + if (!gval) { + ReleaseGlobalDictionary( globals ); + + /* free temp objects */ + Py_XDECREF( idprop ); + Py_XDECREF( owner ); + Py_XDECREF( subowner ); + Py_XDECREF( tar ); + Py_XDECREF( subtar ); + return; + } + + /* Now for the fun part! Try and find the functions we need. */ + if (PyFunction_Check(gval) ) { + pyargs = Py_BuildValue("OOOOO", owner, subowner, tar, subtar, idprop); + retval = PyObject_CallObject(gval, pyargs); + Py_XDECREF( pyargs ); + } else { + printf("ERROR: doDriver is supposed to be a function!\n"); + con->flag |= PYCON_SCRIPTERROR; + ReleaseGlobalDictionary( globals ); + + Py_XDECREF( idprop ); + Py_XDECREF( owner ); + Py_XDECREF( subowner ); + Py_XDECREF( tar ); + Py_XDECREF( subtar ); + return; + } + + /* an error occurred while running the function? */ + if (!retval) { + BPY_Err_Handle(con->text->id.name); + con->flag |= PYCON_SCRIPTERROR; + } + + /* clear globals */ + ReleaseGlobalDictionary( globals ); + + /* free temp objects */ + Py_XDECREF( idprop ); + Py_XDECREF( owner ); + Py_XDECREF( subowner ); + Py_XDECREF( tar ); + Py_XDECREF( subtar ); +} + /* 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 */ @@ -1310,10 +1417,13 @@ int BPY_pyconstraint_targets(bPythonConstraint *con, float targetmat[][4]) globals = CreateGlobalDictionary(); tar = Object_CreatePyObject( con->tar ); - if ( con->tar ) + if ( (con->tar) && (con->tar->type==OB_ARMATURE) ) { + bPoseChannel *pchan; pchan = get_pose_channel( con->tar->pose, con->subtarget ); + subtar = PyPoseBone_FromPosechannel( pchan ); + } else - pchan = NULL; + subtar = PyString_FromString(subtarget); subtar = PyPoseBone_FromPosechannel( pchan ); tarmat = newMatrixObject( (float*)targetmat, 4, 4, Py_NEW );