== PyConstraints ==
authorJoshua Leung <aligorith@gmail.com>
Sat, 18 Aug 2007 06:17:50 +0000 (06:17 +0000)
committerJoshua Leung <aligorith@gmail.com>
Sat, 18 Aug 2007 06:17:50 +0000 (06:17 +0000)
I've added the ability for PyConstraints to define a function (doDriver) that is able to directly modify values of the owner/target, so that certain setups can be created reliably. Users should take note that this is against the basic concept of what a constraint does, and that under no circumstances may they set the values of any variables controlling the transforms. For more details, check out the information in the PyConstraint template script.

I've also updated PyConstraints to be aware of geometry targets. The script template has been updated with this information.

release/scripts/scripttemplate_pyconstraint.py
source/blender/blenkernel/bad_level_call_stubs/stubs.c
source/blender/blenkernel/intern/constraint.c
source/blender/python/BPY_extern.h
source/blender/python/BPY_interface.c

index 12bd4bcb73df804bab714cfd34c8cdeb01ea72de..9ea00a2a2b116eb525d61ede2381608d6680820b 100644 (file)
@@ -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')
index 340ac1ed6930b966925e36033603936b75fd5c69..0c81cdfa2456318af5465b91dfa777a14473207b 100644 (file)
@@ -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;
index 7f736a644f947f51350d45bd0dc8505cb386b37f..5ed729fbd3799b9c37c9c03469bc8b63ae485bda 100644 (file)
@@ -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 */
index ee7afe7dcf62a413f0d6c0a520301f832438fc6e..0e662c38d3152cda884f6aeac2700014034b81f6 100644 (file)
@@ -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);
index bcdf5ecee9c5623018e1fdee46b62e212990768a..67ad88359a80c8296497493c53a7cf325d7172da 100644 (file)
@@ -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 );