diff options
-rw-r--r-- | source/blender/blenkernel/bad_level_call_stubs/stubs.c | 11 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/constraint.c | 88 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/idprop.c | 1 | ||||
-rw-r--r-- | source/blender/blenloader/intern/readfile.c | 21 | ||||
-rw-r--r-- | source/blender/blenloader/intern/writefile.c | 10 | ||||
-rw-r--r-- | source/blender/include/BIF_editconstraint.h | 7 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_constraint_types.h | 20 | ||||
-rw-r--r-- | source/blender/python/BPY_extern.h | 7 | ||||
-rw-r--r-- | source/blender/python/BPY_interface.c | 388 | ||||
-rw-r--r-- | source/blender/python/api2_2x/Constraint.c | 2 | ||||
-rw-r--r-- | source/blender/src/buttons_object.c | 75 | ||||
-rw-r--r-- | source/blender/src/editconstraint.c | 137 | ||||
-rw-r--r-- | source/blender/src/header_text.c | 132 |
13 files changed, 862 insertions, 37 deletions
diff --git a/source/blender/blenkernel/bad_level_call_stubs/stubs.c b/source/blender/blenkernel/bad_level_call_stubs/stubs.c index 344f763608c..e76c53fc72c 100644 --- a/source/blender/blenkernel/bad_level_call_stubs/stubs.c +++ b/source/blender/blenkernel/bad_level_call_stubs/stubs.c @@ -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){} diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 97f5ed18cbe..9c03db824af 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -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; diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c index 4ff4073fdbe..bb4c66da0b1 100644 --- a/source/blender/blenkernel/intern/idprop.c +++ b/source/blender/blenkernel/intern/idprop.c @@ -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); } diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 10f9be743ea..7cfef325f63 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -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; diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index 226561ab97b..2ac3063f248 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -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; diff --git a/source/blender/include/BIF_editconstraint.h b/source/blender/include/BIF_editconstraint.h index db55bc5c7d7..68327bdd684 100644 --- a/source/blender/include/BIF_editconstraint.h +++ b/source/blender/include/BIF_editconstraint.h @@ -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 diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index 74307454187..3179d1035ff 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -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 */ diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index d7db680a458..34271efc4f1 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -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 ); diff --git a/source/blender/python/BPY_interface.c b/source/blender/python/BPY_interface.c index f8a3c269292..6ee06c64cc7 100644 --- a/source/blender/python/BPY_interface.c +++ b/source/blender/python/BPY_interface.c @@ -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 diff --git a/source/blender/python/api2_2x/Constraint.c b/source/blender/python/api2_2x/Constraint.c index 55e7e480d6b..71dbef5f2d5 100644 --- a/source/blender/python/api2_2x/Constraint.c +++ b/source/blender/python/api2_2x/Constraint.c @@ -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; } diff --git a/source/blender/src/buttons_object.c b/source/blender/src/buttons_object.c index bb5ac2ba682..7d2c5dd5f00 100644 --- a/source/blender/src/buttons_object.c +++ b/source/blender/src/buttons_object.c @@ -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; diff --git a/source/blender/src/editconstraint.c b/source/blender/src/editconstraint.c index 2eef0f55978..32cfde2eb86 100644 --- a/source/blender/src/editconstraint.c +++ b/source/blender/src/editconstraint.c @@ -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; +} diff --git a/source/blender/src/header_text.c b/source/blender/src/header_text.c index 92a5a49a681..32268c35210 100644 --- a/source/blender/src/header_text.c +++ b/source/blender/src/header_text.c @@ -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, ""); |