/* * * ***** 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 #endif #include #include #include #include #include #include #ifndef WIN32 #include #else #include "BLI_winstuff.h" #include #include #endif #include "BKE_utildefines.h" #include "BLI_blenlib.h" #include "MEM_guardedalloc.h" #include /* for U.pythondir */ #include "BPY_extern.h" #include "BPY_menus.h" #include #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'; PyOS_snprintf(str2, sizeof(str2), "%s: %s%%t", BPyMenu_group_itoa(menugroup), pym->name); strcat(str, str2); while (pysm) { PyOS_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, short version, 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->version = version; 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, version; 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, "'%[^']' %d %s '%[^']'\n", w1, &version, 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, (short)version, w1, w2, tip); if (!pymenu) { puts("BPyMenus error: couldn't create bpymenu entry.\n"); fclose(fp); 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); } } } fclose(fp); 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' %d %s", pymenu->name, pymenu->version, 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"); } fclose(fp); return; } /* 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' %d %s", pymenu->name, pymenu->version, 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, version = 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) * Blender: (minimal Blender version) * 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; * - the Blender version is the same number reported by * Blender.Get('version') in BPython or G.version in C; * - 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); goto discard; } line[0] = '\0'; /* used as group for this part */ /* minimal Blender version: */ res = fscanf(fp, "%s %d\n", w, &version); if ((res != 2) || (w[0] != 'b' && w[0] != 'B')) { printf("BPyMenus error: wrong 'blender' line in %s.\n", str); goto discard; } /* 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); 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, (short)version, 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. * if param usedir != 0, then the data is recreated from the dir anyway. */ int BPyMenu_Init(int usedir) { char fname[FILE_MAXDIR+FILE_MAXFILE]; struct stat st; time_t tdir, tfile = 0; int result = 0; result = stat(U.pythondir, &st); if (result == -1) { printf ("\n# Scripts dir: %s\n# Error: %s\n", U.pythondir, strerror(errno)); printf ("# Please go to 'Info window -> File Paths tab' and set a valid\n" "# path for the Blender Python scripts dir.\n"); return -1; } if (!S_ISDIR(st.st_mode)) { printf ("\n# Scripts dir: %s is not a directory!", U.pythondir); printf ("# Please go to 'Info window -> File Paths tab' and set a valid\n" "# path for\nthe Blender Python scripts dir.\n"); return -1; } printf("Registering scripts in Blender menus:\n"); tdir = st.st_mtime; if (!usedir) { /* if we're not forced to use the dir */ BLI_make_file_string(NULL, fname, BLI_gethome(), BPYMENU_DATAFILE); result = stat(fname, &st); if ((result != -1) && S_ISREG(st.st_mode)) 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(); /* check if we got any data */ for (result = 0; result < PYMENU_TOTAL; result++) if (BPyMenuTable[result]) break; /* if we got, recreate the file */ if (result < PYMENU_TOTAL) bpymenu_WriteDataFile(); else printf ("# Warning: Registering scripts in menus -- no info found.\n" "# Either your scripts dir has no .py scripts or they don't\n" "# have registration data.\n"); return 0; } else { /* file is newer */ printf("Getting menu data from file: %s\n", fname); bpymenu_CreateFromFile(); } return 0; }