diff options
author | Joshua Leung <aligorith@gmail.com> | 2009-04-20 13:17:43 +0400 |
---|---|---|
committer | Joshua Leung <aligorith@gmail.com> | 2009-04-20 13:17:43 +0400 |
commit | 332e00198914bb6cd34199fffb3832e170066959 (patch) | |
tree | f0f46d21fe9ee0ffecfa41eadd72f24eb10adda7 | |
parent | 64822060783deab359160e669515417787516164 (diff) |
Drivers: "Scripted Expression" drivers (i.e. PyDrivers) work again
Now it is possible to write a Python Expression using the variable names for driver targets (see mockup from initial commit) to substitute the appropriate values into the expression.
In the __global__ namespace for PyDriver evaluation, the following modules are available:
* __builtins__ - i.e. the builtin Python functions
* bpy - new Python API
* math or m - math module
For example:
Consider a driver with three targets, named: A, B, C
Now, you could write an expression like:
C if A < 5 else B
or even:
2*C if A < 5 or A > 20 else m.PI*B
Of course, you don't have to have three targets, the above was just an example.
TODO:
* Bring back way to load pydrivers.py
* Blender.Noise equivalent would be nice to have
P.S. I hope I haven't made any terrible Python API coding errors here (i.e. mem leaks, etc.)
-rw-r--r-- | source/blender/blenkernel/BKE_fcurve.h | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/anim_sys.c | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/depsgraph.c | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/fcurve.c | 2 | ||||
-rw-r--r-- | source/blender/python/BPY_extern.h | 1 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_interface.c | 199 | ||||
-rw-r--r-- | source/blender/python/intern/stubs.c | 2 |
7 files changed, 208 insertions, 6 deletions
diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index f973f86972d..3dd083476ce 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -33,6 +33,8 @@ struct ChannelDriver *fcurve_copy_driver(struct ChannelDriver *driver); void driver_free_target(struct ChannelDriver *driver, struct DriverTarget *dtar); struct DriverTarget *driver_add_new_target(struct ChannelDriver *driver); +float driver_get_target_value(struct ChannelDriver *driver, struct DriverTarget *dtar); + /* ************** F-Curve Modifiers *************** */ /* F-Curve Modifier Type-Info (fmi): diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 5b10f921d7d..30dcb383ef6 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -401,12 +401,12 @@ static short animsys_write_rna_setting (PointerRNA *ptr, char *path, int array_i RNA_property_enum_set(&new_ptr, prop, (int)value); break; default: - break; + /* nothing can be done here... so it is unsuccessful? */ + return 0; } } /* successful */ - // XXX should the unhandled case also be successful? return 1; } else { diff --git a/source/blender/blenkernel/intern/depsgraph.c b/source/blender/blenkernel/intern/depsgraph.c index a34f99d253f..8bd848ea9e6 100644 --- a/source/blender/blenkernel/intern/depsgraph.c +++ b/source/blender/blenkernel/intern/depsgraph.c @@ -326,6 +326,10 @@ static void dag_add_driver_relation(AnimData *adt, DagForest *dag, DagNode *node /* check if bone... */ if ((ob->type==OB_ARMATURE) && dtar->rna_path && strstr(dtar->rna_path, "pose.pose_channels[")) dag_add_relation(dag, node1, node, isdata?DAG_RL_DATA_DATA:DAG_RL_DATA_OB, "Driver"); + /* check if ob data */ + else if (dtar->rna_path && strstr(dtar->rna_path, "data.")) + dag_add_relation(dag, node1, node, isdata?DAG_RL_DATA_DATA:DAG_RL_DATA_OB, "Driver"); + /* normal */ else dag_add_relation(dag, node1, node, isdata?DAG_RL_OB_DATA:DAG_RL_OB_OB, "Driver"); } diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index cce4f3a1217..49100be5216 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -650,7 +650,7 @@ ChannelDriver *fcurve_copy_driver (ChannelDriver *driver) /* Driver Evaluation -------------------------- */ /* Helper function to obtain a value using RNA from the specified source (for evaluating drivers) */ -static float driver_get_target_value (ChannelDriver *driver, DriverTarget *dtar) +float driver_get_target_value (ChannelDriver *driver, DriverTarget *dtar) { PointerRNA id_ptr, ptr; PropertyRNA *prop; diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index 6039ad05962..d8e4611d9df 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -126,7 +126,6 @@ extern "C" { void BPY_pydriver_update(void); float BPY_pydriver_eval(struct ChannelDriver *driver); - struct Object **BPY_pydriver_get_objects(struct ChannelDriver *driver); int BPY_button_eval(char *expr, double *value); diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index ecf1cc828f3..86406134253 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -20,6 +20,7 @@ #include "bpy_operator.h" #include "bpy_ui.h" +#include "DNA_anim_types.h" #include "DNA_space_types.h" #include "DNA_text_types.h" @@ -29,6 +30,7 @@ #include "BLI_string.h" #include "BKE_context.h" +#include "BKE_fcurve.h" #include "BKE_text.h" #include "BPY_extern.h" @@ -415,3 +417,200 @@ void BPY_run_ui_scripts(bContext *C) #endif } +/* ****************************************** */ +/* Drivers - PyExpression Evaluation */ +// XXX Hopefully I haven't committed any PyAPI coding sins here ;) - Aligorith, 2009Apr20 + +/* for pydrivers (drivers using one-line Python expressions to express relationships between targets) */ +PyObject *bpy_pydriver_Dict = NULL; + +/* For faster execution we keep a special dictionary for pydrivers, with + * the needed modules and aliases. + */ +static int bpy_pydriver_create_dict(void) +{ + PyObject *d, *mod; + + /* validate namespace for driver evaluation */ + if (bpy_pydriver_Dict) return -1; + + d = PyDict_New(); + if (d == NULL) + return -1; + else + bpy_pydriver_Dict = d; + + /* import some modules: builtins, bpy, math, (Blender.noise )*/ + PyDict_SetItemString(d, "__builtins__", PyEval_GetBuiltins()); + + mod = PyImport_ImportModule("math"); + if (mod) { + PyDict_Merge(d, PyModule_GetDict(mod), 0); /* 0 - dont overwrite existing values */ + + /* Only keep for backwards compat! - just import all math into root, they are standard */ + PyDict_SetItemString(d, "math", mod); + PyDict_SetItemString(d, "m", mod); + Py_DECREF(mod); + } + + /* add bpy to global namespace */ + mod= PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0); + if (mod) { + PyDict_SetItemString(bpy_pydriver_Dict, "bpy", mod); + Py_DECREF(mod); + } + + +#if 0 // non existant yet + mod = PyImport_ImportModule("Blender.Noise"); + if (mod) { + PyDict_SetItemString(d, "noise", mod); + PyDict_SetItemString(d, "n", mod); + Py_DECREF(mod); + } else { + PyErr_Clear(); + } + + /* If there's a Blender text called pydrivers.py, import it. + * Users can add their own functions to this module. + */ + if (G.f & G_DOSCRIPTLINKS) { + mod = importText("pydrivers"); /* can also use PyImport_Import() */ + if (mod) { + PyDict_SetItemString(d, "pydrivers", mod); + PyDict_SetItemString(d, "p", mod); + Py_DECREF(mod); + } else { + PyErr_Clear(); + } + } +#endif // non existant yet + + return 0; +} + +/* 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 + * updates in it reach pydriver evaluation. + */ +void BPY_pydriver_update(void) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + + if (bpy_pydriver_Dict) { /* free the global dict used by pydrivers */ + PyDict_Clear(bpy_pydriver_Dict); + Py_DECREF(bpy_pydriver_Dict); + bpy_pydriver_Dict = NULL; + } + + PyGILState_Release(gilstate); + + return; +} + +/* error return function for BPY_eval_pydriver */ +static float pydriver_error(ChannelDriver *driver) +{ + if (bpy_pydriver_Dict) { /* free the global dict used by pydrivers */ + PyDict_Clear(bpy_pydriver_Dict); + Py_DECREF(bpy_pydriver_Dict); + bpy_pydriver_Dict = NULL; + } + + driver->flag |= DRIVER_FLAG_INVALID; /* py expression failed */ + fprintf(stderr, "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n", driver->expression); + + PyErr_Print(); + + return 0.0f; +} + +/* This evals py driver expressions, 'expr' is a Python expression that + * should evaluate to a float number, which is returned. + */ +float BPY_pydriver_eval (ChannelDriver *driver) +{ + PyObject *driver_vars=NULL; + PyObject *retval; + PyGILState_STATE gilstate; + + DriverTarget *dtar; + float result = 0.0f; /* default return */ + char *expr = NULL; + short targets_ok= 1; + + /* sanity checks - should driver be executed? */ + if ((driver == NULL) /*|| (G.f & G_DOSCRIPTLINKS)==0*/) + return result; + + /* get the py expression to be evaluated */ + expr = driver->expression; + if ((expr == NULL) || (expr[0]=='\0')) + return result; + + gilstate = PyGILState_Ensure(); + + /* init global dictionary for py-driver evaluation settings */ + if (!bpy_pydriver_Dict) { + if (bpy_pydriver_create_dict() != 0) { + fprintf(stderr, "Pydriver error: couldn't create Python dictionary"); + PyGILState_Release(gilstate); + return result; + } + } + + /* add target values to a py dictionary that we add to the drivers dict as 'd' */ + driver_vars = PyDict_New(); // XXX do we need to decref this? + for (dtar= driver->targets.first; dtar; dtar= dtar->next) { + PyObject *driver_arg = NULL; + float tval = 0.0f; + + /* try to get variable value */ + tval= driver_get_target_value(driver, dtar); + driver_arg= PyFloat_FromDouble((double)tval); + if (driver_arg == NULL) continue; + + /* try to add to dictionary */ + if (PyDict_SetItemString(driver_vars, dtar->name, driver_arg)) { + /* this target failed */ + if (targets_ok) { + /* first one - print some extra info for easier identification */ + fprintf(stderr, "\nBPY_pydriver_eval() - Error while evaluating PyDriver:\n"); + targets_ok= 0; + } + + fprintf(stderr, "\tBPY_pydriver_eval() - couldn't add variable '%s' to namespace \n", dtar->name); + PyErr_Print(); + } + } + + /* execute expression to get a value */ + retval = PyRun_String(expr, Py_eval_input, bpy_pydriver_Dict, driver_vars); + + /* decref the driver vars first... */ + Py_DECREF(driver_vars); + + /* process the result */ + if (retval == NULL) { + result = pydriver_error(driver); + PyGILState_Release(gilstate); + return result; + } + + result = (float)PyFloat_AsDouble(retval); + Py_DECREF(retval); + + if ((result == -1) && PyErr_Occurred()) { + result = pydriver_error(driver); + PyGILState_Release(gilstate); + return result; + } + + /* all fine, make sure the "invalid expression" flag is cleared */ + driver->flag &= ~DRIVER_FLAG_INVALID; + + PyGILState_Release(gilstate); + + return result; +} diff --git a/source/blender/python/intern/stubs.c b/source/blender/python/intern/stubs.c index e438966f324..8d6b5ad9f19 100644 --- a/source/blender/python/intern/stubs.c +++ b/source/blender/python/intern/stubs.c @@ -33,8 +33,6 @@ void BPY_post_start_python() {} void BPY_do_all_scripts() {} void BPY_call_importloader() {} void BPY_do_pyscript() {} -void BPY_pydriver_eval() {} -void BPY_pydriver_get_objects() {} void BPY_clear_script() {} //void BPY_free_compiled_text() {} void BPY_pyconstraint_eval() {} |