diff options
-rw-r--r-- | source/blender/python/BPY_extern.h | 2 | ||||
-rw-r--r-- | source/blender/python/BPY_interface.c | 175 | ||||
-rw-r--r-- | source/blender/python/BPY_menus.c | 589 | ||||
-rw-r--r-- | source/blender/python/BPY_menus.h | 98 | ||||
-rw-r--r-- | source/blender/python/SConscript | 1 | ||||
-rw-r--r-- | source/blender/src/header_info.c | 51 | ||||
-rw-r--r-- | source/blender/src/header_script.c | 103 | ||||
-rw-r--r-- | source/creator/creator.c | 4 |
8 files changed, 977 insertions, 46 deletions
diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index a791788cd9b..3feb5606059 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -49,7 +49,7 @@ struct _object; // forward declaration for PyObject ! void BPY_start_python(void); void BPY_end_python(void); -void BPY_syspath_append_pythondir(void); +void BPY_post_start_python(void); int BPY_Err_getLinenumber(void); const char *BPY_Err_getFilename(void); /* void BPY_Err_Handle(struct Text *text); */ diff --git a/source/blender/python/BPY_interface.c b/source/blender/python/BPY_interface.c index 8f6e813a9e2..c8f8e825fa3 100644 --- a/source/blender/python/BPY_interface.c +++ b/source/blender/python/BPY_interface.c @@ -41,12 +41,14 @@ #include <MEM_guardedalloc.h> #include <BLI_blenlib.h> /* for BLI_last_slash() */ +#include <BIF_interface.h> /* for pupmenu */ #include <BIF_space.h> #include <BIF_screen.h> #include <BKE_global.h> #include <BKE_library.h> #include <BKE_main.h> #include <BKE_text.h> +#include <BKE_utildefines.h> #include <DNA_camera_types.h> #include <DNA_ID.h> #include <DNA_lamp_types.h> @@ -62,6 +64,7 @@ #include <DNA_userdef_types.h> /* for U.pythondir */ #include "BPY_extern.h" +#include "BPY_menus.h" #include "api2_2x/EXPP_interface.h" #include "api2_2x/constant.h" @@ -135,6 +138,9 @@ void BPY_end_python(void) } Py_Finalize(); + + BPyMenu_RemoveAllEntries(); /* freeing bpymenu mem */ + return; } @@ -198,7 +204,7 @@ void init_syspath(void) else printf ("Warning: could not determine argv[0] path\n"); - if (U.pythondir) { + if (U.pythondir && strcmp(U.pythondir, "")) { p = Py_BuildValue("s", U.pythondir); syspath_append(p); /* append to module search path */ } @@ -256,15 +262,17 @@ void init_syspath(void) } /*****************************************************************************/ -/* Description: This function adds the user defined folder for Python */ -/* scripts to sys.path. This is done in init_syspath, too, but */ -/* when Blender's main() runs BPY_start_python(), U.pythondir */ -/* isn't set yet, so we provide this function to be executed */ -/* after U.pythondir is defined. */ +/* Description: This function finishes Python initialization in Blender. */ +/* Because U.pythondir (user defined dir for scripts) isn't */ +/* initialized when BPY_start_Python needs to be executed, we */ +/* postpone adding U.pythondir to sys.path and also BPyMenus */ +/* (mechanism to register scripts in Blender menus) for when */ +/* that dir info is available. */ /*****************************************************************************/ -void BPY_syspath_append_pythondir(void) +void BPY_post_start_python(void) { syspath_append(Py_BuildValue("s", U.pythondir)); + BPyMenu_Init(); /* get dynamic menus (registered scripts) data */ } /*****************************************************************************/ @@ -304,10 +312,15 @@ PyObject *traceback_getFilename(PyObject *tb) /* Description: Blender Python error handler. This catches the error and */ /* stores filename and line number in a global */ /*****************************************************************************/ -void BPY_Err_Handle(Text *text) +void BPY_Err_Handle(char *script_name) { PyObject *exception, *err, *tb, *v; + if (!script_name) { + printf("Error: script has NULL name\n"); + return; + } + PyErr_Fetch(&exception, &err, &tb); if (!exception && !tb) { @@ -315,7 +328,7 @@ void BPY_Err_Handle(Text *text) return; } - strcpy(g_script_error.filename, GetName(text)); + strcpy(g_script_error.filename, script_name); if (exception && PyErr_GivenExceptionMatches(exception, PyExc_SyntaxError)) { /* no traceback available when SyntaxError */ @@ -350,7 +363,7 @@ void BPY_Err_Handle(Text *text) v = PyObject_GetAttrString(tb, "tb_next"); if (v == Py_None || strcmp(PyString_AsString(traceback_getFilename(v)), - GetName(text))) { + script_name)) { break; } @@ -378,7 +391,7 @@ void BPY_Err_Handle(Text *text) int BPY_txt_do_python(struct SpaceText* st) { PyObject *py_dict, *py_result; - BPy_constant *tracer; + BPy_constant *info; Script *script = G.main->script.first; if (!st->text) return 0; @@ -415,26 +428,144 @@ int BPY_txt_do_python(struct SpaceText* st) script->py_globaldict = py_dict; -/* We will insert a constant dict at this script's namespace, with the name - * of the script. Later more info can be added, if necessary. */ - tracer = (BPy_constant *)M_constant_New(); /*create a constant object*/ - if (tracer) { - constant_insert(tracer, "name", PyString_FromString(script->id.name+2)); + info = (BPy_constant *)M_constant_New(); + if (info) { + constant_insert(info, "name", PyString_FromString(script->id.name+2)); + Py_INCREF (Py_None); + constant_insert(info, "arg", Py_None); + PyDict_SetItemString(py_dict, "__script__", (PyObject *)info); } - PyDict_SetItemString(py_dict, "__script__", (PyObject *)tracer); - clearScriptLinks (); py_result = RunPython (st->text, py_dict); /* Run the script */ if (!py_result) { /* Failed execution of the script */ - BPY_Err_Handle(st->text); + BPY_Err_Handle(GetName(st->text)); + ReleaseGlobalDictionary(py_dict); + free_libblock(&G.main->script, script); + //BPY_end_python(); + //BPY_start_python(); + + return 0; + } + else { + Py_DECREF (py_result); + script->flags &=~SCRIPT_RUNNING; + if (!script->flags) { + ReleaseGlobalDictionary(py_dict); + script->py_globaldict = NULL; + free_libblock(&G.main->script, script); + } + } + + return 1; /* normal return */ +} + +/*****************************************************************************/ +/* Description: This function executes the script chosen from a menu. */ +/* Notes: It is called by the ui code in src/header_???.c when a user */ +/* clicks on a menu entry that refers to a script. */ +/* Scripts are searched in the BPyMenuTable, using the given */ +/* menutype and event values to know which one was chosen. */ +/*****************************************************************************/ +int BPY_menu_do_python(short menutype, int event) +{ + PyObject *py_dict, *py_result, *pyarg = NULL; + BPy_constant *info; + BPyMenu *pym; + BPySubMenu *pysm; + FILE *fp = NULL; + char filestr[FILE_MAXDIR+FILE_MAXFILE]; + Script *script = G.main->script.first; + + if ((menutype < 0) || (menutype > PYMENU_TOTAL) || (event < 0)) + return 0; + + pym = BPyMenuTable[menutype]; + + while (event--) { + if (pym) pym = pym->next; + else break; + } + + if (!pym) return 0; + +/* if there are submenus, let the user choose one from a pupmenu that we + * create here.*/ + pysm = pym->submenus; + if (pysm) { + char *pupstr; + int arg; + + pupstr = BPyMenu_CreatePupmenuStr(pym, menutype); + + if (pupstr) { + arg = pupmenu(pupstr); + MEM_freeN(pupstr); + + if (arg >= 0) { + while (arg--) pysm = pysm->next; + pyarg = PyString_FromString(pysm->arg); + } + else return 0; + } + } + + if (!pyarg) {/* no submenus */ + Py_INCREF (Py_None); + pyarg = Py_None; + } + + BLI_make_file_string(NULL, filestr, U.pythondir, pym->filename); + fp = fopen(filestr, "r"); + if (!fp) { /* later also support userhome/.blender/scripts/ or whatever */ + printf("Error loading script: couldn't open file %s\n", filestr); + return 0; + } + + /* Create a new script structure and initialize it: */ + script = alloc_libblock(&G.main->script, ID_SCRIPT, pym->name); + + if (!script) { + printf("couldn't allocate memory for Script struct!"); + fclose(fp); + return 0; + } + + script->id.us = 1; + script->filename = NULL; /* it's a Blender Text script */ + script->flags = SCRIPT_RUNNING; + script->py_draw = NULL; + script->py_event = NULL; + script->py_button = NULL; + + py_dict = CreateGlobalDictionary(); + + script->py_globaldict = py_dict; + + info = (BPy_constant *)M_constant_New(); + if (info) { + constant_insert(info, "name", PyString_FromString(script->id.name+2)); + constant_insert(info, "arg", pyarg); + PyDict_SetItemString(py_dict, "__script__", (PyObject *)info); + } + + clearScriptLinks (); + + py_result = PyRun_File(fp, pym->filename, Py_file_input, py_dict, py_dict); + + fclose(fp); + + if (!py_result) { /* Failed execution of the script */ + + BPY_Err_Handle(script->id.name+2); + PyErr_Print(); ReleaseGlobalDictionary(py_dict); free_libblock(&G.main->script, script); - BPY_end_python(); - BPY_start_python(); + // BPY_end_python(); + // BPY_start_python(); return 0; } @@ -595,7 +726,7 @@ void BPY_do_pyscript(struct ID *id, short event) if (!ret) { /* Failed execution of the script */ - BPY_Err_Handle ((Text*) scriptlink->scripts[index]); + BPY_Err_Handle (scriptlink->scripts[index]->name+2); BPY_end_python (); BPY_start_python (); } diff --git a/source/blender/python/BPY_menus.c b/source/blender/python/BPY_menus.c new file mode 100644 index 00000000000..b5d9610d3d8 --- /dev/null +++ b/source/blender/python/BPY_menus.c @@ -0,0 +1,589 @@ +/* + * + * ***** BEGIN GPL/BL DUAL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. The Blender + * Foundation also sells licenses for use in proprietary software under + * the Blender License. See http://www.blender.org/BL/ for information + * about this. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * This is a new part of Blender. + * + * Contributor(s): Willian P. Germano + * + * ***** END GPL/BL DUAL LICENSE BLOCK ***** +*/ + +/* This is the main file responsible for having bpython scripts accessible + * from Blender menus. To know more, please start with its header file. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifndef WIN32 +#include <dirent.h> +#else +#include "BLI_winstuff.h" +#include <io.h> +#include <direct.h> +#endif + +#include "BKE_utildefines.h" +#include "BLI_blenlib.h" +#include "MEM_guardedalloc.h" + +#include <DNA_userdef_types.h> /* for U.pythondir */ + +#include "BPY_extern.h" +#include "BPY_menus.h" + +#include <errno.h> + +#define BPYMENU_DATAFILE ".Bpymenus" + +static int bpymenu_group_atoi (char *str) +{ + if (!strcmp(str, "Import")) return PYMENU_IMPORT; + else if (!strcmp(str, "Export")) return PYMENU_EXPORT; + else if (!strcmp(str, "Misc")) return PYMENU_MISC; + else return -1; +} + +char *BPyMenu_group_itoa (short menugroup) +{ + switch (menugroup) { + case PYMENU_IMPORT: + return "Import"; + break; + case PYMENU_EXPORT: + return "Export"; + break; + case PYMENU_MISC: + return "Misc"; + break; + } + return NULL; +} + +/* BPyMenu_CreatePupmenuStr: + * build and return a meaninful string to be used by pupmenu(). The + * string is made of a bpymenu name as title and its submenus as possible + * choices for the user. +*/ +char *BPyMenu_CreatePupmenuStr(BPyMenu *pym, short menugroup) +{ + BPySubMenu *pysm = pym->submenus; + char str[1024], str2[100]; + int i = 0, rlen; + + if (!pym || !pysm) return NULL; + + str[0] = '\0'; + + snprintf(str2, sizeof(str2), "%s: %s%%t", + BPyMenu_group_itoa(menugroup), pym->name); + strcat(str, str2); + + while (pysm) { + snprintf(str2, sizeof(str2), "|%s%%x%d", pysm->name, i); + rlen = sizeof(str) - strlen(str); + strncat(str, str2, rlen); + i++; + pysm = pysm->next; + } + + return BLI_strdup(str); +} + +static void bpymenu_RemoveAllSubEntries (BPySubMenu *smenu) +{ + BPySubMenu *tmp; + + while (smenu) { + tmp = smenu->next; + if (smenu->name) MEM_freeN(smenu->name); + if (smenu->arg) MEM_freeN(smenu->arg); + MEM_freeN(smenu); + smenu = tmp; + } + return; +} + +void BPyMenu_RemoveAllEntries (void) +{ + BPyMenu *tmp, *pymenu; + int i; + + for (i = 0; i < PYMENU_TOTAL; i++) { + pymenu = BPyMenuTable[i]; + while (pymenu) { + tmp = pymenu->next; + if (pymenu->name) MEM_freeN(pymenu->name); + if (pymenu->filename) MEM_freeN(pymenu->filename); + if (pymenu->tooltip) MEM_freeN(pymenu->tooltip); + if (pymenu->submenus) bpymenu_RemoveAllSubEntries(pymenu->submenus); + MEM_freeN(pymenu); + pymenu = tmp; + } + BPyMenuTable[i] = NULL; + } + return; +} + +static BPyMenu *bpymenu_FindEntry (short group, char *name) +{ + BPyMenu *pymenu; + + if ((group <0) || (group >= PYMENU_TOTAL)) return NULL; + + pymenu = BPyMenuTable[group]; + + while (pymenu) { + if (!strcmp(pymenu->name, name)) return pymenu; + pymenu = pymenu->next; + } + + return NULL; +} + +static void bpymenu_set_tooltip (BPyMenu *pymenu, char *tip) +{ + if (!pymenu) return; + + if (pymenu->tooltip) MEM_freeN(pymenu->tooltip); + pymenu->tooltip = BLI_strdup(tip); + + return; +} + +/* bpymenu_AddEntry: + * try to find an existing pymenu entry with the given type and name; + * if found, update it with new info, otherwise create a new one and fill it. + */ +static BPyMenu *bpymenu_AddEntry (short group, char *name, char *fname, char *tooltip) +{ + BPyMenu *menu, **iter; + + if ((group < 0) || (group >= PYMENU_TOTAL)) return NULL; + if (!name || !fname) return NULL; + + menu = bpymenu_FindEntry (group, name); /* already exists? */ + + if (menu) { + printf("\nWarning: script %s's menu name is already in use.\n", fname); + printf("Edit the script and change its Name: '%s' field, please.\n", name); + return NULL; + } + + menu = MEM_mallocN(sizeof(BPyMenu), "pymenu"); + + if (!menu) return NULL; + + menu->name = BLI_strdup(name); + menu->filename = BLI_strdup(fname); + menu->tooltip = NULL; + if (tooltip) menu->tooltip = BLI_strdup(tooltip); + menu->submenus = NULL; + menu->next = NULL; + + iter = &BPyMenuTable[group]; + while (*iter) iter = &((*iter)->next); + + *iter = menu; + + return menu; +} + +/* bpymenu_AddSubEntry: + * add a submenu to an existing python menu. + */ +static int bpymenu_AddSubEntry (BPyMenu *mentry, char *name, char *arg) +{ + BPySubMenu *smenu, **iter; + + smenu = MEM_mallocN(sizeof(BPySubMenu), "pysubmenu"); + if (!smenu) return -1; + + smenu->name = BLI_strdup(name); + smenu->arg = BLI_strdup(arg); + smenu->next = NULL; + + if (!smenu->name || !smenu->arg) return -1; + + iter = &(mentry->submenus); + while (*iter) iter = &((*iter)->next); + + *iter = smenu; + + return 0; +} + +/* bpymenu_CreateFromFile: + * parse the bpymenus data file where Python menu data is stored; + * based on this data, create and fill the pymenu structs. + */ +static void bpymenu_CreateFromFile (void) +{ + FILE *fp; + char line[255], w1[255], w2[255], tooltip[255], *tip; + int parsing; + short group; + BPyMenu *pymenu = NULL; + + /* init global bpymenu table (it is a list of pointers to struct BPyMenus + * for each available cathegory: import, export, etc.) */ + for (group = 0; group < PYMENU_TOTAL; group++) + BPyMenuTable[group] = NULL; + + /* let's try to open the file with bpymenu data */ + BLI_make_file_string (NULL, line, BLI_gethome(), BPYMENU_DATAFILE); + + fp = fopen(line, "rb"); + + if (!fp) { + printf("BPyMenus error: couldn't open config file %s.\n", line); + return; + } + + while (fgets(line, 255, fp)) { /* parsing file lines */ + + switch (line[0]) { /* check first char */ + case '#': /* comment */ + continue; + break; + case '\n': + continue; + break; + default: + parsing = sscanf(line, "%s {\n", w1); /* menu group */ + break; + } + + if (parsing == 1) { /* got menu group string */ + group = bpymenu_group_atoi(w1); + if (group < 0) { /* invalid type */ + printf("BPyMenus error parsing config file: wrong group: %s.\n", w1); + continue; + } + } + else continue; + + while (1) { + tip = NULL; /* optional tooltip */ + fgets(line, 255, fp); + if (line[0] == '}') break; + else if (line[0] == '\n') continue; + else if (line[0] == '\'') { /* menu entry */ + parsing = sscanf(line, "'%[^']' %s '%[^']'\n", w1, w2, tooltip); + + if (parsing <= 0) { /* invalid line, get rid of it */ + fgets(line, 255, fp); /* add error report later */ + } + else if (parsing == 3) tip = tooltip; /* has tooltip */ + + pymenu = bpymenu_AddEntry(group, w1, w2, tip); + if (!pymenu) { + puts("BpyMenus error: couldn't create bpymenu entry.\n"); + return; + } + } + else if (line[0] == '|' && line[1] == '_') { /* menu sub-entry */ + if (!pymenu) continue; /* no menu yet, skip this line */ + sscanf(line, "|_%[^:]: %s\n", w1, w2); + bpymenu_AddSubEntry(pymenu, w1, w2); + } + } + } + return; +} + +/* bpymenu_WriteDataFile: + * writes the registered scripts info to the user's home dir, for faster + * access when the scripts dir hasn't changed. +*/ +static void bpymenu_WriteDataFile(void) +{ + BPyMenu *pymenu; + BPySubMenu *smenu; + FILE *fp; + char fname[FILE_MAXDIR+FILE_MAXFILE]; + int i; + + BLI_make_file_string(NULL, fname, BLI_gethome(), BPYMENU_DATAFILE); + + fp = fopen(fname, "w"); + if (!fp) { + printf("BPyMenus error: couldn't write %s file.", fname); + return; + } + + fprintf(fp, "# Blender: registered menu entries for bpython scripts\n"); + + for (i = 0; i < PYMENU_TOTAL; i++) { + pymenu = BPyMenuTable[i]; + if (!pymenu) continue; + fprintf(fp, "\n%s {\n", BPyMenu_group_itoa(i)); + while (pymenu) { + fprintf(fp, "'%s' %s", pymenu->name, pymenu->filename); + if (pymenu->tooltip) fprintf(fp, " '%s'\n", pymenu->tooltip); + else fprintf(fp, "\n"); + smenu = pymenu->submenus; + while (smenu) { + fprintf(fp, "|_%s: %s\n", smenu->name, smenu->arg); + smenu = smenu->next; + } + pymenu = pymenu->next; + } + fprintf(fp, "}\n"); + } +} + +/* BPyMenu_PrintAllEntries: + * useful for debugging. +*/ +void BPyMenu_PrintAllEntries(void) +{ + BPyMenu *pymenu; + BPySubMenu *smenu; + int i; + + printf("# Blender: registered menu entries for bpython scripts\n"); + + for (i = 0; i < PYMENU_TOTAL; i++) { + pymenu = BPyMenuTable[i]; + printf("\n%s {\n", BPyMenu_group_itoa(i)); + while (pymenu) { + printf("'%s' %s", pymenu->name, pymenu->filename); + if (pymenu->tooltip) printf(" '%s'\n", pymenu->tooltip); + else printf("\n"); + smenu = pymenu->submenus; + while (smenu) { + printf("|_%s: %s\n", smenu->name, smenu->arg); + smenu = smenu->next; + } + pymenu = pymenu->next; + } + printf("}\n"); + } +} + +/* bpymenu_CreateFromDir: + * this function scans the scripts dir looking for .py files with the + * right header and menu info, using that to fill the bpymenu structs. + * Speed is important. +*/ +static void bpymenu_CreateFromDir (void) +{ + DIR *dir; + FILE *fp; + struct dirent *dir_entry; + BPyMenu *pymenu; + char *s, *fname, str[FILE_MAXFILE+FILE_MAXDIR]; + char line[100], w[100]; + char name[100], submenu[100], subarg[100], tooltip[100]; + int res = 0; + + dir = opendir(U.pythondir); + + if (!dir) { + printf("BPyMenus error: couldn't open python dir %s.\n", U.pythondir); + return; + } + + /* init global bpymenu table (it is a list of pointers to struct BPyMenus + * for each available group: import, export, etc.) */ + for (res = 0; res < PYMENU_TOTAL; res++) + BPyMenuTable[res] = NULL; + +/* we scan the dir for filenames ending with .py and starting with the + * right 'magic number': '#!BPY'. All others are ignored. */ + + while ((dir_entry = readdir(dir)) != NULL) { + fname = dir_entry->d_name; + /* ignore anything starting with a dot */ + if (fname[0] == '.') continue; /* like . and .. */ + + /* also skip filenames whose extension isn't '.py' */ + s = strstr(fname, ".py"); + if (!s || *(s+3) != '\0') continue; + + BLI_make_file_string(NULL, str, U.pythondir, fname); + + fp = fopen(str, "rb"); + + if (!fp) { + printf("BPyMenus error: couldn't open %s.\n", str); + return; + } + + /* finally, look for the start string '#!BPY', with + * or w/o white space(s) between #! and BPY */ + fgets(line, 100, fp); + if (line[0] != '#' || line[1] != '!') goto discard; + + if (!strstr (line, "BPY")) goto discard; + + /* file passed the tests, look for the three double-quotes */ + while (fgets(line, 100, fp)) { + if (line[0] == '"' && line[1] == '"' && line[2] == '"') { + res = 1; /* found */ + break; + } + } + + if (!res) goto discard; + + /* Now we're ready to get the registration info. A little more structure + * was imposed to their format, for speed. The registration + * lines must appear between the first pair of triple double-quotes and + * follow this order (the single-quotes are part of the format): + * + * Name: 'script name for the menu' + * Group: 'group name' (defines menu) + * Submenu: 'submenu name' related_1word_arg + * Tooltip: 'tooltip for the menu' + * + * notes: + * - there may be more than one submenu line, or none: + * submenus and the tooltip are optional; + * - only the first letter of each token is checked, both lower + * and upper cases, so that's all that matters for recognition: + * n 'script name' is enough for the name line, for example. */ + + /* first the name: */ + res = fscanf(fp, "%[^']'%[^'\r\n]'\n", w, name); + if ((res != 2) || (w[0] != 'n' && w[0] != 'N')) { + printf("BPyMenus error: wrong 'name' line in %s.\n", str); + printf("%s | %s\n", w, name); + goto discard; + } + + line[0] = '\0'; /* used as group for this part */ + + /* the group: */ + res = fscanf(fp, "%[^']'%[^'\r\n]'\n", w, line); + if ((res != 2) || (w[0] != 'g' && w[0] != 'G')) { + printf("BPyMenus error: wrong 'group' line in %s.\n", str); + printf("'%s' | '%s'\n", w, name); + goto discard; + } + + res = bpymenu_group_atoi(line); + if (res < 0) { + printf("BPyMenus error: unknown 'group' %s in %s.\n", line, str); + goto discard; + } + + pymenu = bpymenu_AddEntry(res, name, fname, NULL); + if (!pymenu) { + printf("BPyMenus error: couldn't create entry for %s.\n", str); + fclose(fp); + closedir(dir); + return; + } + + /* the (optional) submenu(s): */ + while (fgets (line, 100, fp)) { + res = sscanf(line, "%[^']'%[^'\r\n]'%s\n", w, submenu, subarg); + if ((res != 3) || (w[0] != 's' && w[0] != 'S')) break; + bpymenu_AddSubEntry(pymenu, submenu, subarg); + } + + /* the (optional) tooltip: */ + res = sscanf(line, "%[^']'%[^'\r\n]'\n", w, tooltip); + if ((res == 2) && (w[0] == 't' || w[0] == 'T')) { + bpymenu_set_tooltip (pymenu, tooltip); + } + +discard: + fclose (fp); + continue; + } + + closedir(dir); + return; +} + +/* BPyMenu_Init: + * import the bpython menus data to Blender, either from: + * - the BPYMENU_DATAFILE file (~/.Bpymenus) or + * - the scripts dir, case it's newer than the datafile (then update the file). + * then fill the bpymenu table with this data. +*/ +void BPyMenu_Init(void) +{ + char fname[FILE_MAXDIR+FILE_MAXFILE]; + struct stat st; + time_t tdir, tfile; + int result = 0; + + result = stat(U.pythondir, &st); + + if (result == -1) { + /* + printf ("\nScripts dir: %s\nError: %s\n", U.pythondir, strerror(errno)); + printf ("Please go to 'Info window -> File Paths tab' and set a valid " + "path for\nthe Blender Python scripts dir.\n"); + */ + return; + } + + if (!S_ISDIR(st.st_mode)) { + /* + printf ("\nScripts dir: %s is not a directory!", U.pythondir); + printf ("Please go to 'Info window -> File Paths tab' and set a valid " + "path for\nthe Blender Python scripts dir.\n"); + */ + return; + } + + printf("Registering scripts in Blender menus:\n"); + + tdir = st.st_mtime; + + BLI_make_file_string(NULL, fname, BLI_gethome(), BPYMENU_DATAFILE); + + result = stat(fname, &st); + if ((result == -1) || !S_ISREG(st.st_mode)) tfile = 0; + else tfile = st.st_mtime; + + /* comparing dates */ + + if (tdir > tfile) { /* if so, dir is newer */ + printf("Getting menu data from dir: %s\n", U.pythondir); + bpymenu_CreateFromDir(); + bpymenu_WriteDataFile(); /* recreate the file */ + return; + } + else { /* file is newer */ + printf("Getting menu data from file: %s\n", fname); + bpymenu_CreateFromFile(); + } + + return; +} + + diff --git a/source/blender/python/BPY_menus.h b/source/blender/python/BPY_menus.h new file mode 100644 index 00000000000..8451a122f06 --- /dev/null +++ b/source/blender/python/BPY_menus.h @@ -0,0 +1,98 @@ +/* + * + * ***** BEGIN GPL/BL DUAL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. The Blender + * Foundation also sells licenses for use in proprietary software under + * the Blender License. See http://www.blender.org/BL/ for information + * about this. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * This is a new part of Blender. + * + * Contributor(s): Willian P. Germano + * + * ***** END GPL/BL DUAL LICENSE BLOCK ***** +*/ + +#ifndef BPY_MENUS_H +#define BPY_MENUS_H + +/* This header exposes BPyMenu related public declarations. The implementation + * adds 'dynamic' menus to Blender, letting scripts register themselves in any + * of a few pre-defined (trivial to upgrade) places in menus. These places or + * slots are called groups here (Import, Export, etc). This is how it works: + * - scripts at some specific folder (only the user pref U.pythondir, right + * now) are scanned for registration info. + * - this data is also saved to a .Bpymenus file at the user's home dir and + * only re-created when the scripts folder gets modified. + * - on start-up Blender uses this info to fill a table, which is used to + * create the menu entries when they are needed (see header_info.c or + * header_script.c, under source/blender/src/, for examples). +*/ + +/* These two structs hold py menu/submenu info. + * BPyMenu holds a script's name (as should appear in the menu) and filename, + * plus an optional list of submenus. Each submenu is related to a string + * (arg) that the script can get from the __script__ pydict, to know which + * submenu was chosen. */ + +typedef struct BPySubMenu { + char *name; + char *arg; + struct BPySubMenu *next; +} BPySubMenu; + +typedef struct BPyMenu { + char *name; + char *filename; + char *tooltip; + struct BPySubMenu *submenus; + struct BPyMenu *next; +} BPyMenu; + +/* Scripts can be added to only a few pre-defined places in menus, like + * File->Import, File->Export, etc. (for speed and better control). + * To make a new menu 'slot' available for scripts: + * - add an entry to the enum below, right before PYMENU_TOTAL, of course; + * - update the bpymenu_group_atoi() and BPyMenu_group_itoa() functions in + * BPY_menus.c; + * - add the necessary code to the header_***.c file in + * source/blender/src/, like done in header_info.c for import/export; + * - update the bpython registering function and its documentation to include + * the new group. +*/ +typedef enum { + PYMENU_IMPORT, + PYMENU_EXPORT, + PYMENU_MISC, + PYMENU_TOTAL +} PYMENUHOOKS; + +/* BPyMenuTable holds all registered pymenus, as linked lists for each menu + * where they can appear (see PYMENUHOOKS enum above). +*/ +BPyMenu *BPyMenuTable[PYMENU_TOTAL]; + +/* public functions: */ +void BPyMenu_Init(void); +void BPyMenu_RemoveAllEntries(void); +void BPyMenu_PrintAllEntries(void); +char *BPyMenu_CreatePupmenuStr(BPyMenu *pym, short group); +char *BPyMenu_group_itoa (short group); + +#endif /* BPY_MENUS_H */ diff --git a/source/blender/python/SConscript b/source/blender/python/SConscript index 212af83a235..02a9444b128 100644 --- a/source/blender/python/SConscript +++ b/source/blender/python/SConscript @@ -11,6 +11,7 @@ python_env.Append (CXXFLAGS = cxxflags) python_env.Append (CPPDEFINES = defines) source_files = ['BPY_interface.c', + 'BPY_menus.c', 'api2_2x/Blender.c', 'api2_2x/Sys.c', 'api2_2x/Registry.c', diff --git a/source/blender/src/header_info.c b/source/blender/src/header_info.c index 834c2613ea3..08b0a934f59 100644 --- a/source/blender/src/header_info.c +++ b/source/blender/src/header_info.c @@ -108,6 +108,7 @@ #include "MEM_guardedalloc.h" #include "BPY_extern.h" +#include "BPY_menus.h" #include "render.h" // for R - should use BKE_bad_level_calls.h instead? @@ -661,16 +662,13 @@ static void do_info_file_importmenu(void *arg, int event) ScrArea *sa; if(curarea->spacetype==SPACE_INFO) { - sa= closest_bigger_area(); + sa= find_biggest_area_of_type(SPACE_SCRIPT); + if (!sa) sa= closest_bigger_area(); areawinset(sa->win); } - /* these are no defines, easier this way, the codes are in the function below */ - switch(event) { - - case 0: - break; - } + BPY_menu_do_python(PYMENU_IMPORT, event); + allqueue(REDRAWINFO, 0); } @@ -678,14 +676,19 @@ static uiBlock *info_file_importmenu(void *arg_unused) { uiBlock *block; short yco = 20, menuwidth = 120; + BPyMenu *pym; + int i = 0; block= uiNewBlock(&curarea->uiblocks, "importmenu", UI_EMBOSSP, UI_HELV, G.curscreen->mainwin); uiBlockSetButmFunc(block, do_info_file_importmenu, NULL); //uiBlockSetXOfs(block, -50); // offset to parent button - - // uiDefBut(block, BUTM, 1, "Python scripts go here somehow!", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 0, ""); - uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, ""); - + + for (pym = BPyMenuTable[PYMENU_IMPORT]; pym; pym = pym->next, i++) { + uiDefBut(block, BUTM, 1, pym->name, 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, i, pym->tooltip?pym->tooltip:pym->filename); + } + + //uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, ""); + uiBlockSetDirection(block, UI_RIGHT); uiTextBoundsBlock(block, 60); @@ -701,8 +704,12 @@ static void do_info_file_exportmenu(void *arg, int event) areawinset(sa->win); } + /* events >=3 are registered bpython scripts */ + if (event >= 3) BPY_menu_do_python(PYMENU_EXPORT, event - 3); + /* these are no defines, easier this way, the codes are in the function below */ - switch(event) { + + else switch(event) { case 0: write_vrml_fs(); @@ -720,15 +727,24 @@ static void do_info_file_exportmenu(void *arg, int event) static uiBlock *info_file_exportmenu(void *arg_unused) { uiBlock *block; - short yco = 20; + short yco = 20, menuwidth = 120; + BPyMenu *pym; + int i = 0; block= uiNewBlock(&curarea->uiblocks, "exportmenu", UI_EMBOSSP, UI_HELV, G.curscreen->mainwin); uiBlockSetButmFunc(block, do_info_file_exportmenu, NULL); //uiBlockSetXOfs(block, -50); // offset to parent button - uiDefBut(block, BUTM, 1, "VRML 1.0...|Ctrl F2", 0, yco-=20, 120, 19, NULL, 0.0, 0.0, 1, 0, ""); - uiDefBut(block, BUTM, 1, "DXF...|Shift F2", 0, yco-=20, 120, 19, NULL, 0.0, 0.0, 1, 1, ""); - uiDefBut(block, BUTM, 1, "Videoscape...|Alt W", 0, yco-=20, 120, 19, NULL, 0.0, 0.0, 1, 2, ""); + uiDefBut(block, BUTM, 1, "VRML 1.0...|Ctrl F2", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 0, ""); + uiDefBut(block, BUTM, 1, "DXF...|Shift F2", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 1, ""); + uiDefBut(block, BUTM, 1, "Videoscape...|Alt W", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 2, ""); + + uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, ""); + +/* note that we acount for the 3 previous entries with i+3: */ + for (pym = BPyMenuTable[PYMENU_EXPORT]; pym; pym = pym->next, i++) { + uiDefBut(block, BUTM, 1, pym->name, 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, i+3, pym->tooltip?pym->tooltip:pym->filename); + } uiBlockSetDirection(block, UI_RIGHT); uiTextBoundsBlock(block, 60); @@ -736,7 +752,6 @@ static uiBlock *info_file_exportmenu(void *arg_unused) return block; } - static void do_info_filemenu(void *arg, int event) { ScrArea *sa; @@ -857,7 +872,7 @@ static uiBlock *info_filemenu(void *arg_unused) uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, ""); uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Append...|Shift F1", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 3, ""); -// uiDefIconTextBlockBut(block, info_file_importmenu, NULL, ICON_RIGHTARROW_THIN, "Import", 0, yco-=20, menuwidth, 19, ""); + uiDefIconTextBlockBut(block, info_file_importmenu, NULL, ICON_RIGHTARROW_THIN, "Import", 0, yco-=20, menuwidth, 19, ""); uiDefIconTextBlockBut(block, info_file_exportmenu, NULL, ICON_RIGHTARROW_THIN, "Export", 0, yco-=20, menuwidth, 19, ""); uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, ""); diff --git a/source/blender/src/header_script.c b/source/blender/src/header_script.c index e66fea68cf4..a56cede0746 100644 --- a/source/blender/src/header_script.c +++ b/source/blender/src/header_script.c @@ -66,13 +66,82 @@ #include "BKE_library.h" #include "BKE_main.h" #include "BKE_sca.h" -#include "BPY_extern.h" #include "BSE_filesel.h" +#include "BPY_extern.h" +#include "BPY_menus.h" + #include "blendef.h" #include "mydevice.h" /* ********************** SCRIPT ****************************** */ + +/* action executed after clicking in Scripts menu */ +static void do_scripts_submenus(void *int_arg, int event) +{ + SpaceScript *sc= curarea->spacedata.first; + Script *script= sc->script; + ScrArea *sa; + int menutype = (int)int_arg; + + BPY_menu_do_python (menutype, event); + + //allqueue(REDRAWSCRIPT, 0); +} + +static uiBlock *script_scripts_submenus(void *int_menutype) +{ + uiBlock *block; + short yco = 20, menuwidth = 120; + BPyMenu *pym; + int i = 0, menutype = (int)int_menutype; + + if ((menutype < 0) || (menutype > PYMENU_TOTAL)) return; + + block= uiNewBlock(&curarea->uiblocks, "importmenu", UI_EMBOSSP, UI_HELV, G.curscreen->mainwin); + uiBlockSetButmFunc(block, do_scripts_submenus, int_menutype); + //uiBlockSetXOfs(block, -50); // offset to parent button + + for (pym = BPyMenuTable[menutype]; pym; pym = pym->next, i++) { + uiDefBut(block, BUTM, 1, pym->name, 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, i, pym->tooltip?pym->tooltip:pym->filename); + } + + uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, ""); + + uiBlockSetDirection(block, UI_RIGHT); + uiTextBoundsBlock(block, 60); + + return block; +} + +/* Scripts menu */ +static uiBlock *script_scriptsmenu(void *arg_unused) +{ + //SpaceScript *sc= curarea->spacedata.first; + //Script *script= sc->script; + uiBlock *block; + short yco = 0, menuwidth = 120; + int i; + + block= uiNewBlock(&curarea->uiblocks, "script_scriptsmenu", UI_EMBOSSP, UI_HELV, curarea->headwin); + //uiBlockSetButmFunc(block, do_script_scriptsmenu, NULL); + + for (i = 0; i < PYMENU_TOTAL; i++) { + uiDefIconTextBlockBut(block, script_scripts_submenus, (void *)i, ICON_RIGHTARROW_THIN, BPyMenu_group_itoa(i), 0, yco-=20, menuwidth, 19, ""); + } + + if(curarea->headertype==HEADERTOP) { + uiBlockSetDirection(block, UI_DOWN); + } + else { + uiBlockSetDirection(block, UI_TOP); + uiBlockFlipOrder(block); + } + + uiTextBoundsBlock(block, 50); + return block; +} + void do_script_buttons(unsigned short event) { SpaceScript *sc= curarea->spacedata.first; @@ -121,7 +190,7 @@ void script_buttons(void) { uiBlock *block; SpaceScript *sc= curarea->spacedata.first; - short xco = 8; + short xco = 8, xmax; char naam[256]; if (!sc || sc->spacetype != SPACE_SCRIPT) return; @@ -138,9 +207,34 @@ void script_buttons(void) windowtype_pup(), xco,0,XIC+10,YIC, &(curarea->butspacetype), 1.0, SPACEICONMAX, 0, 0, "Displays Current Window Type. Click for menu" " of available types."); + xco += XIC+14; + + uiBlockSetEmboss(block, UI_EMBOSSN); + if(curarea->flag & HEADER_NO_PULLDOWN) { + uiDefIconButS(block, TOG|BIT|0, B_FLIPINFOMENU, ICON_DISCLOSURE_TRI_RIGHT, + xco,2,XIC,YIC-2, + &(curarea->flag), 0, 0, 0, 0, "Enables display of pulldown menus"); + } else { + uiDefIconButS(block, TOG|BIT|0, B_FLIPINFOMENU, ICON_DISCLOSURE_TRI_DOWN, + xco,2,XIC,YIC-2, + &(curarea->flag), 0, 0, 0, 0, "Hides pulldown menus"); + } + uiBlockSetEmboss(block, UI_EMBOSS); + xco+=XIC; + + /* pull down menus */ + if((curarea->flag & HEADER_NO_PULLDOWN)==0) { + uiBlockSetEmboss(block, UI_EMBOSSP); + + xmax= GetButStringLength("Scripts"); + uiDefBlockBut(block,script_scriptsmenu, NULL, "Scripts", xco, 0, xmax, 20, ""); + xco+=xmax; + } + + uiBlockSetEmboss(block, UI_EMBOSSX); + xco += 10; /* FULL WINDOW */ - xco= 25; if(curarea->full) uiDefIconBut(block, BUT,B_FULL, ICON_SPLITSCREEN, xco+=XIC,0,XIC,YIC, 0, 0, 0, 0, 0, "Returns to multiple views window (CTRL+Up arrow)"); @@ -149,7 +243,7 @@ void script_buttons(void) 0, 0, 0, "Makes current window full screen (CTRL+Down arrow)"); /* STD SCRIPT BUTTONS */ - xco+= 2*XIC; + xco += 2*XIC; xco= std_libbuttons(block, xco, 0, 0, NULL, B_SCRIPTBROWSE, (ID*)sc->script, 0, &(sc->menunr), 0, 0, 0, 0, 0); /* always as last */ @@ -158,5 +252,6 @@ void script_buttons(void) uiDrawBlock(block); } + /* ********************** SCRIPT ****************************** */ diff --git a/source/creator/creator.c b/source/creator/creator.c index c208666f06d..6b536f8e91a 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -429,9 +429,11 @@ int main(int argc, char **argv) * so we provide the BPY_ function below to append the user defined * pythondir to Python's sys.path at this point. Simply putting * BIF_init() before BPY_start_python() crashes Blender at startup. + * Update: now this function also inits the bpymenus, which also depend + * on U.pythondir. */ - BPY_syspath_append_pythondir(); + BPY_post_start_python(); } else { BPY_start_python(); |