1091 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1091 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 
 | |
|  * $Id$
 | |
|  *
 | |
|  * ***** 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, Michael Reimpell
 | |
|  *
 | |
|  * ***** 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.
 | |
|  */
 | |
| 
 | |
| #include "BPY_menus.h"
 | |
| 
 | |
| #include <Python.h>
 | |
| #ifndef WIN32
 | |
|   #include <dirent.h>
 | |
| #else
 | |
|   #include "BLI_winstuff.h"
 | |
| #endif
 | |
| #include "BKE_global.h"
 | |
| #include "BKE_utildefines.h"
 | |
| #include "BLI_blenlib.h"
 | |
| #include "MEM_guardedalloc.h"
 | |
| #include "DNA_userdef_types.h"	/* for U.pythondir */
 | |
| #include "api2_2x/EXPP_interface.h" /* for bpy_gethome() */
 | |
| 
 | |
| #define BPYMENU_DATAFILE "Bpymenus"
 | |
| #define MAX_DIR_DEPTH 4 /* max depth for traversing scripts dirs */
 | |
| #define MAX_DIR_NUMBER 30 /* max number of dirs in scripts dirs trees */
 | |
| 
 | |
| static int DEBUG;
 | |
| static int Dir_Depth;
 | |
| static int Dirs_Number;
 | |
| 
 | |
| /* BPyMenuTable holds all registered pymenus, as linked lists for each menu
 | |
|  * where they can appear (see PYMENUHOOKS enum in BPY_menus.h).
 | |
| */
 | |
| BPyMenu *BPyMenuTable[PYMENU_TOTAL];
 | |
| 
 | |
| static int bpymenu_group_atoi( char *str )
 | |
| {
 | |
| 	if( !strcmp( str, "Export" ) )
 | |
| 		return PYMENU_EXPORT;
 | |
| 	else if( !strcmp( str, "Import" ) )
 | |
| 		return PYMENU_IMPORT;
 | |
| 	else if( !strcmp( str, "Help" ) )
 | |
| 		return PYMENU_HELP;
 | |
| 	else if( !strcmp( str, "HelpWebsites" ) )
 | |
| 		return PYMENU_HELPWEBSITES;
 | |
| 	else if( !strcmp( str, "HelpSystem" ) )
 | |
| 		return PYMENU_HELPSYSTEM;
 | |
| 	else if( !strcmp( str, "Render" ) )
 | |
| 		return PYMENU_RENDER;
 | |
| 	else if( !strcmp( str, "System" ) )
 | |
| 		return PYMENU_SYSTEM;
 | |
| 	else if( !strcmp( str, "Object" ) )
 | |
| 		return PYMENU_OBJECT;
 | |
| 	else if( !strcmp( str, "Mesh" ) )
 | |
| 		return PYMENU_MESH;
 | |
| 	else if( !strncmp( str, "Theme", 5 ) )
 | |
| 		return PYMENU_THEMES;
 | |
| 	else if( !strcmp( str, "Add" ) )
 | |
| 		return PYMENU_ADD;
 | |
| 	else if( !strcmp( str, "Wizards" ) )
 | |
| 		return PYMENU_WIZARDS;
 | |
| 	else if( !strcmp( str, "Animation" ) )
 | |
| 		return PYMENU_ANIMATION;
 | |
| 	else if( !strcmp( str, "Materials" ) )
 | |
| 		return PYMENU_MATERIALS;
 | |
| 	else if( !strcmp( str, "UV" ) )
 | |
| 		return PYMENU_UV;
 | |
| 	else if( !strcmp( str, "Image" ) )
 | |
| 		return PYMENU_IMAGE;
 | |
| 	else if( !strcmp( str, "FaceSelect" ) )
 | |
| 		return PYMENU_FACESELECT;
 | |
| 	else if( !strcmp( str, "WeightPaint" ) )
 | |
| 		return PYMENU_WEIGHTPAINT;
 | |
| 	else if( !strcmp( str, "VertexPaint" ) )
 | |
| 		return PYMENU_VERTEXPAINT;
 | |
| 	/* "Misc" or an inexistent group name: use misc */
 | |
| 	else
 | |
| 		return PYMENU_MISC;
 | |
| }
 | |
| 
 | |
| char *BPyMenu_group_itoa( short menugroup )
 | |
| {
 | |
| 	switch ( menugroup ) {
 | |
| 	case PYMENU_EXPORT:
 | |
| 		return "Export";
 | |
| 		break;
 | |
| 	case PYMENU_IMPORT:
 | |
| 		return "Import";
 | |
| 		break;
 | |
| 	case PYMENU_ADD:
 | |
| 		return "Add";
 | |
| 		break;
 | |
| 	case PYMENU_HELP:
 | |
| 		return "Help";
 | |
| 		break;
 | |
| 	case PYMENU_HELPWEBSITES:
 | |
| 		return "HelpWebsites";
 | |
| 		break;
 | |
| 	case PYMENU_HELPSYSTEM:
 | |
| 		return "HelpSystem";
 | |
| 		break;
 | |
| 	case PYMENU_RENDER:
 | |
| 		return "Render";
 | |
| 		break;
 | |
| 	case PYMENU_SYSTEM:
 | |
| 		return "System";
 | |
| 		break;
 | |
| 	case PYMENU_OBJECT:
 | |
| 		return "Object";
 | |
| 		break;
 | |
| 	case PYMENU_MESH:
 | |
| 		return "Mesh";
 | |
| 		break;
 | |
| 	case PYMENU_THEMES:
 | |
| 		return "Themes";
 | |
| 		break;
 | |
| 	case PYMENU_WIZARDS:
 | |
| 		return "Wizards";
 | |
| 		break;
 | |
| 	case PYMENU_ANIMATION:
 | |
| 		return "Animation";
 | |
| 		break;
 | |
| 	case PYMENU_MATERIALS:
 | |
| 		return "Materials";
 | |
| 		break;
 | |
| 	case PYMENU_UV:
 | |
| 		return "UV";
 | |
| 		break;
 | |
| 	case PYMENU_IMAGE:
 | |
| 		return "Image";
 | |
| 		break;
 | |
| 	case PYMENU_FACESELECT:
 | |
| 		return "FaceSelect";
 | |
| 		break;
 | |
| 	case PYMENU_WEIGHTPAINT:
 | |
| 		return "WeightPaint";
 | |
| 		break;
 | |
| 	case PYMENU_VERTEXPAINT:
 | |
| 		return "VertexPaint";
 | |
| 		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;
 | |
| 	}
 | |
| 
 | |
| 	Dirs_Number = 0;
 | |
| 	Dir_Depth = 0;
 | |
| 
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| /* BPyMenu_GetEntry:
 | |
|  * given a group and a position, return the entry in that position from
 | |
|  * that group.
 | |
| */
 | |
| BPyMenu *BPyMenu_GetEntry( short group, short pos )
 | |
| {
 | |
| 	BPyMenu *pym = NULL;
 | |
| 
 | |
| 	if( ( group < 0 ) || ( group >= PYMENU_TOTAL ) )
 | |
| 		return NULL;
 | |
| 
 | |
| 	pym = BPyMenuTable[group];
 | |
| 
 | |
| 	while( pos-- ) {
 | |
| 		if( pym )
 | |
| 			pym = pym->next;
 | |
| 		else
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	return pym;		/* found entry or 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, int is_userdir, char *tooltip )
 | |
| {
 | |
| 	BPyMenu *menu, *next = NULL, **iter;
 | |
| 	int nameclash = 0;
 | |
| 
 | |
| 	if( ( group < 0 ) || ( group >= PYMENU_TOTAL ) )
 | |
| 		return NULL;
 | |
| 	if( !name || !fname )
 | |
| 		return NULL;
 | |
| 
 | |
| 	menu = bpymenu_FindEntry( group, name );	/* already exists? */
 | |
| 
 | |
| 	/* if a menu with this name already exists in the same group:
 | |
| 	 * - if one script is in the default dir and the other in U.pythondir,
 | |
| 	 *   accept and let the new one override the other.
 | |
| 	 * - otherwise, report the error and return NULL. */
 | |
| 	if( menu ) {
 | |
| 		if( menu->dir < is_userdir ) {	/* new one is in U.pythondir */
 | |
| 			nameclash = 1;
 | |
| 			if( menu->name )
 | |
| 				MEM_freeN( menu->name );
 | |
| 			if( menu->filename )
 | |
| 				MEM_freeN( menu->filename );
 | |
| 			if( menu->tooltip )
 | |
| 				MEM_freeN( menu->tooltip );
 | |
| 			if( menu->submenus )
 | |
| 				bpymenu_RemoveAllSubEntries( menu->submenus );
 | |
| 			next = menu->next;
 | |
| 		} else {	/* they are in the same dir */
 | |
| 			if (DEBUG) {
 | |
| 				fprintf(stderr, "\n\
 | |
| Warning: script %s's menu name is already in use.\n\
 | |
| Edit the script and change its \n\
 | |
| Name: '%s'\n\
 | |
| field, please.\n\
 | |
| Note: if you really want to have two scripts for the same menu with\n\
 | |
| the same name, keep one in the default dir and the other in\n\
 | |
| the user defined dir (only the later will be registered).\n", fname, name);
 | |
| 			}
 | |
| 			return NULL;
 | |
| 		}
 | |
| 	} else
 | |
| 		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->dir = is_userdir;
 | |
| 	menu->submenus = NULL;
 | |
| 	menu->next = next;	/* non-NULL if menu already existed */
 | |
| 
 | |
| 	if( nameclash )
 | |
| 		return menu;	/* no need to place it, it's already at the list */
 | |
| 	else {	/* insert the new entry in its correct position at the table */
 | |
| 		BPyMenu *prev = NULL;
 | |
| 		char *s = NULL;
 | |
| 
 | |
| 		iter = &BPyMenuTable[group];
 | |
| 		while( *iter ) {
 | |
| 			s = ( *iter )->name;
 | |
| 			if( s )
 | |
| 				if( strcmp( menu->name, s ) < 0 )
 | |
| 					break;	/* sort by names */
 | |
| 			prev = *iter;
 | |
| 			iter = &( ( *iter )->next );
 | |
| 		}
 | |
| 
 | |
| 		if( *iter ) {	/* prepend */
 | |
| 			menu->next = *iter;
 | |
| 			if( prev )
 | |
| 				prev->next = menu;
 | |
| 			else
 | |
| 				BPyMenuTable[group] = menu;	/* is first entry */
 | |
| 		} else
 | |
| 			*iter = menu;	/* append */
 | |
| 	}
 | |
| 
 | |
| 	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 int bpymenu_CreateFromFile( void )
 | |
| {
 | |
| 	FILE *fp;
 | |
| 	char line[255], w1[255], w2[255], tooltip[255], *tip;
 | |
| 	char *homedir = NULL;
 | |
| 	int parsing, version, is_userdir;
 | |
| 	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 */
 | |
| 	homedir = bpy_gethome(0);
 | |
| 	if (!homedir) {
 | |
| 		if( DEBUG )
 | |
| 			fprintf(stderr,
 | |
| 				"BPyMenus error: couldn't open config file Bpymenus: no home dir.\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	BLI_make_file_string( "/", line, homedir, BPYMENU_DATAFILE );
 | |
| 
 | |
| 	fp = fopen( line, "rb" );
 | |
| 
 | |
| 	if( !fp ) {
 | |
| 		if( DEBUG )
 | |
| 			fprintf(stderr, "BPyMenus error: couldn't open config file %s.\n", line );
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	fgets( line, 255, fp );	/* header */
 | |
| 
 | |
| 	/* check if the U.pythondir we saved at the file is different from the
 | |
| 	 * current one.  If so, return to force updating from dirs */
 | |
| 	w1[0] = '\0';
 | |
| 	fscanf( fp, "# User defined scripts dir: %[^\n]\n", w1 );
 | |
| 	if( w1 ) {
 | |
| 		char upythondir[FILE_MAXDIR];
 | |
| 
 | |
| 		BLI_strncpy(upythondir, U.pythondir, FILE_MAXDIR);
 | |
| 		BLI_convertstringcode(upythondir, G.sce, 0);
 | |
| 		if( strcmp( w1, upythondir ) != 0 )
 | |
| 			return -1;
 | |
| 		w1[0] = '\0';
 | |
| 	}
 | |
| 
 | |
| 	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 = (short)bpymenu_group_atoi( w1 );
 | |
| 			if( group < 0 && DEBUG ) {	/* invalid type */
 | |
| 				fprintf(stderr,
 | |
| 					"BPyMenus error parsing config file: wrong group: %s,\n\
 | |
| will use 'Misc'.\n", w1 );
 | |
| 			}
 | |
| 		} else
 | |
| 			continue;
 | |
| 
 | |
| 		for(;;) {
 | |
| 			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 %d '%[^']'\n",
 | |
| 						w1, &version, w2, &is_userdir,
 | |
| 						tooltip );
 | |
| 
 | |
| 				if( parsing <= 0 ) {	/* invalid line, get rid of it */
 | |
| 					fgets( line, 255, fp );
 | |
| 				} else if( parsing == 5 )
 | |
| 					tip = tooltip;	/* has tooltip */
 | |
| 
 | |
| 				pymenu = bpymenu_AddEntry( group,
 | |
| 							   ( short ) version,
 | |
| 							   w1, w2, is_userdir,
 | |
| 							   tip );
 | |
| 				if( !pymenu ) {
 | |
| 					puts( "BPyMenus error: couldn't create bpymenu entry.\n" );
 | |
| 					fclose( fp );
 | |
| 					return -1;
 | |
| 				}
 | |
| 			} 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 0;
 | |
| }
 | |
| 
 | |
| /* 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], *homedir;
 | |
| 	int i;
 | |
| 
 | |
| 	homedir = bpy_gethome(0);
 | |
| 
 | |
| 	if (!homedir) {
 | |
| 		if( DEBUG )
 | |
| 			fprintf(stderr,
 | |
| 				"BPyMenus error: couldn't write Bpymenus file: no home dir.\n\n");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	BLI_make_file_string( "/", fname, homedir, BPYMENU_DATAFILE );
 | |
| 
 | |
| 	fp = fopen( fname, "w" );
 | |
| 	if( !fp ) {
 | |
| 		if( DEBUG )
 | |
| 			fprintf(stderr, "BPyMenus error: couldn't write %s file.\n\n",
 | |
| 				fname );
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	fprintf( fp,
 | |
| 		 "# Blender: registered menu entries for bpython scripts\n" );
 | |
| 
 | |
| 	if (U.pythondir[0] != '\0' &&
 | |
| 			strcmp(U.pythondir, "/") != 0 && strcmp(U.pythondir, "//") != 0)
 | |
| 	{
 | |
| 		char upythondir[FILE_MAXDIR];
 | |
| 
 | |
| 		BLI_strncpy(upythondir, U.pythondir, FILE_MAXDIR);
 | |
| 		BLI_convertstringcode(upythondir, G.sce, 0);
 | |
| 		fprintf( fp, "# User defined scripts dir: %s\n", upythondir );
 | |
| 	}
 | |
| 
 | |
| 	for( i = 0; i < PYMENU_TOTAL; i++ ) {
 | |
| 		pymenu = BPyMenuTable[i];
 | |
| 		if( !pymenu )
 | |
| 			continue;
 | |
| 		fprintf( fp, "\n%s {\n", BPyMenu_group_itoa( (short)i ) );
 | |
| 		while( pymenu ) {
 | |
| 			fprintf( fp, "'%s' %d %s %d", pymenu->name,
 | |
| 				 pymenu->version, pymenu->filename,
 | |
| 				 pymenu->dir );
 | |
| 			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( (short)i ) );
 | |
| 		while( pymenu ) {
 | |
| 			printf( "'%s' %d %s %d", pymenu->name, pymenu->version,
 | |
| 				pymenu->filename, pymenu->dir );
 | |
| 			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_ParseFile:
 | |
|  * recursively scans folders looking for scripts to register.
 | |
|  *
 | |
|  * This function scans the scripts directory looking for .py files with the
 | |
|  * right header and menu info, using that to fill the bpymenu structs.
 | |
|  * is_userdir defines if the script is in the default scripts dir or the
 | |
|  * user defined one (U.pythondir: is_userdir == 1).
 | |
|  * Speed is important.
 | |
|  *
 | |
|  * The first line of the script must be '#!BPY'.
 | |
|  * The header registration lines must appear between the first pair of
 | |
|  * '\"\"\"' and follow this order (the single-quotes are part of
 | |
|  * the format):
 | |
|  *
 | |
|  * # \"\"\"<br>
 | |
|  * # Name: 'script name for the menu'
 | |
|  * # Blender: <code>short int</code> (minimal Blender version)
 | |
|  * # Group: 'group name' (defines menu)
 | |
|  * # Submenu: 'submenu name' related_1word_arg
 | |
|  * # Tooltip: 'tooltip for the menu'
 | |
|  * # \"\"\"
 | |
|  *
 | |
|  * Notes:
 | |
|  *
 | |
|  * - Commenting out header lines with "#" is optional, but recommended.
 | |
|  * - 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;
 | |
|  * - Line length must be less than 99.
 | |
|  */
 | |
| static int bpymenu_ParseFile(FILE *file, char *fname, int is_userdir)
 | |
| {
 | |
| 	char line[100];
 | |
| 	char head[100];
 | |
| 	char middle[100];
 | |
| 	char tail[100];
 | |
| 	int matches;
 | |
| 	int parser_state;
 | |
| 
 | |
| 	char script_name[100];
 | |
| 	int script_version = 1;
 | |
| 	int script_group;
 | |
| 
 | |
| 	BPyMenu *scriptMenu = NULL;
 | |
| 
 | |
| 	if (file != NULL) {
 | |
| 		parser_state = 1; /* state of parser, 0 to terminate */
 | |
| 
 | |
| 		while ((parser_state != 0) && (fgets(line, 100, file) != NULL)) {
 | |
| 
 | |
| 			switch (parser_state) {
 | |
| 
 | |
| 				case 1: /* !BPY */
 | |
| 					if (strncmp(line, "#!BPY", 5) == 0) {
 | |
| 						parser_state++;
 | |
| 					} else {
 | |
| 						parser_state = 0;
 | |
| 					}
 | |
| 					break;
 | |
| 
 | |
| 				case 2: /* \"\"\" */
 | |
| 					if ((strstr(line, "\"\"\""))) {
 | |
| 						parser_state++;
 | |
| 					}
 | |
| 					break;
 | |
| 
 | |
| 				case 3: /* Name: 'script name for the menu' */
 | |
| 					matches = sscanf(line, "%[^']'%[^']'%c", head, script_name, tail);
 | |
| 					if ((matches == 3) && (strstr(head, "Name:") != NULL)) {
 | |
| 						parser_state++;
 | |
| 					} else {
 | |
| 						if (DEBUG)
 | |
| 							fprintf(stderr, "BPyMenus error: Wrong 'Name' line: %s\n", fname);
 | |
| 						parser_state = 0;
 | |
| 					}
 | |
| 					break;
 | |
| 
 | |
| 				case 4: /* Blender: <short int> */
 | |
| 					matches = sscanf(line, "%[^1234567890]%i%c", head, &script_version,
 | |
| 						tail);
 | |
| 					if (matches == 3) {
 | |
| 						parser_state++;
 | |
| 					} else {
 | |
| 						if (DEBUG)
 | |
| 							fprintf(stderr,"BPyMenus error: Wrong 'Blender' line: %s\n",fname);
 | |
| 						parser_state = 0;
 | |
| 					}
 | |
| 					break;
 | |
| 
 | |
| 				case 5: /* Group: 'group name' */
 | |
| 					matches = sscanf(line, "%[^']'%[^']'%c", head, middle, tail);
 | |
| 					if ((matches == 3) && (strstr(head, "Group:") != NULL)) {
 | |
| 						script_group = bpymenu_group_atoi(middle);
 | |
| 						if (script_group < 0) {
 | |
| 							if (DEBUG)
 | |
| 								fprintf(stderr, "BPyMenus error: Unknown group \"%s\": %s\n",
 | |
| 									middle, fname);
 | |
| 							parser_state = 0;
 | |
| 						}
 | |
| 						
 | |
| 						else { /* register script */
 | |
| 							scriptMenu = bpymenu_AddEntry((short)script_group,
 | |
| 								(short int)script_version, script_name, fname, is_userdir,NULL);
 | |
| 							if (scriptMenu == NULL) {
 | |
| 								if (DEBUG)
 | |
| 									fprintf(stderr,
 | |
| 										"BPyMenus error: Couldn't create entry for: %s\n", fname);
 | |
| 								parser_state = 0;
 | |
| 							} else {
 | |
| 								parser_state++;
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 					} else {
 | |
| 						if (DEBUG)
 | |
| 							fprintf(stderr, "BPyMenus error: Wrong 'Group' line: %s\n",fname);
 | |
| 						parser_state = 0;
 | |
| 					}
 | |
| 					break;
 | |
| 
 | |
| 				case 6: /* optional elements */
 | |
| 					/* Submenu: 'submenu name' related_1word_arg */
 | |
| 					matches = sscanf(line, "%[^']'%[^']'%s\n", head, middle, tail);
 | |
| 					if ((matches == 3) && (strstr(head, "Submenu:") != NULL)) {
 | |
| 						bpymenu_AddSubEntry(scriptMenu, middle, tail);
 | |
| 					} else {
 | |
| 						/* Tooltip: 'tooltip for the menu */
 | |
| 						matches = sscanf(line, "%[^']'%[^']'%c", head, middle, tail);
 | |
| 						if ((matches == 3) && ((strstr(head, "Tooltip:") != NULL) ||
 | |
| 							(strstr(head, "Tip:") != NULL))) {
 | |
| 							bpymenu_set_tooltip(scriptMenu, middle);
 | |
| 						}
 | |
| 						parser_state = 0;
 | |
| 					}
 | |
| 					break;
 | |
| 
 | |
| 				default:
 | |
| 					parser_state = 0;
 | |
| 					break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	else { /* shouldn't happen, it's checked in bpymenus_ParseDir */
 | |
| 		if (DEBUG)
 | |
| 			fprintf(stderr, "BPyMenus error: Couldn't open %s.\n", fname);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* bpymenu_ParseDir:
 | |
|  * recursively scans folders looking for scripts to register.
 | |
|  *
 | |
|  * This function scans the scripts directory looking for .py files with the
 | |
|  * right header and menu info.
 | |
|  * - is_userdir defines if the script is in the default scripts dir or the
 | |
|  * user defined one (U.pythondir: is_userdir == 1);
 | |
|  * - parentdir is the parent dir name to store as part of the script filename,
 | |
|  * if we're down a subdir.
 | |
|  * Speed is important.
 | |
|  */
 | |
| static int bpymenu_ParseDir(char *dirname, char *parentdir, int is_userdir )
 | |
| {
 | |
| 	DIR *dir; 
 | |
| 	FILE *file = NULL;
 | |
| 	struct dirent *de;
 | |
| 	struct stat status;
 | |
| 	char *file_extension;
 | |
| 	char path[FILE_MAXDIR];
 | |
| 	char subdir[FILE_MAXDIR];
 | |
| 	char *s = NULL;
 | |
| 
 | |
| 	dir = opendir(dirname);
 | |
| 
 | |
| 	if (dir != NULL) {
 | |
| 		while ((de = readdir(dir)) != NULL) {
 | |
| 
 | |
| 			/* skip files and dirs starting with '.' or 'bpy' */
 | |
| 			if ((de->d_name[0] == '.') || !strncmp(de->d_name, "bpy", 3)) {
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			BLI_make_file_string("/", path, dirname, de->d_name);
 | |
| 
 | |
| 			if (stat(path, &status) != 0) {
 | |
| 				if (DEBUG)
 | |
| 					fprintf(stderr, "stat %s failed: %s\n", path, strerror(errno));
 | |
| 			}
 | |
| 
 | |
| 			if (S_ISREG(status.st_mode)) { /* is file */
 | |
| 
 | |
| 				file_extension = strstr(de->d_name, ".py");
 | |
| 
 | |
| 				if (file_extension && *(file_extension + 3) == '\0') {
 | |
| 					file = fopen(path, "rb");
 | |
| 
 | |
| 					if (file) {
 | |
| 						s = de->d_name;
 | |
| 						if (parentdir) {
 | |
| 							BLI_make_file_string(NULL, subdir, parentdir, de->d_name);
 | |
| 							s = subdir;
 | |
| 						}
 | |
| 						bpymenu_ParseFile(file, s, is_userdir);
 | |
| 						fclose(file);
 | |
| 					}
 | |
| 
 | |
| 					else {
 | |
| 						if (DEBUG)
 | |
| 							fprintf(stderr, "BPyMenus error: Couldn't open %s.\n", path);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			else if (S_ISDIR(status.st_mode)) { /* is subdir */
 | |
| 				Dirs_Number++;
 | |
| 				Dir_Depth++;
 | |
| 				if (Dirs_Number > MAX_DIR_NUMBER) {
 | |
| 					if (DEBUG) {
 | |
| 						fprintf(stderr, "BPyMenus error: too many subdirs.\n");
 | |
| 					}
 | |
| 					closedir(dir);
 | |
| 					return -1;
 | |
| 				}
 | |
| 				else if (Dir_Depth > MAX_DIR_DEPTH) {
 | |
| 					if (DEBUG)
 | |
| 						fprintf(stderr,
 | |
| 							"BPyMenus error: max depth reached traversing dir tree.\n");
 | |
| 					closedir(dir);
 | |
| 					return -1;
 | |
| 				}
 | |
| 				s = de->d_name;
 | |
| 				if (parentdir) {
 | |
| 					BLI_make_file_string(NULL, subdir, parentdir, de->d_name);
 | |
| 					s = subdir;
 | |
| 				}
 | |
| 				if (bpymenu_ParseDir(path, s, is_userdir) == -1) {
 | |
| 					closedir(dir);
 | |
| 					return -1;
 | |
| 				}
 | |
| 				Dir_Depth--;
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 		closedir(dir);
 | |
| 	}
 | |
| 
 | |
| 	else { /* open directory stream failed */
 | |
| 		if (DEBUG)
 | |
| 			fprintf(stderr, "opendir %s failed: %s\n", dirname, strerror(errno));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int bpymenu_GetStatMTime( char *name, int is_file, time_t * mtime )
 | |
| {
 | |
| 	struct stat st;
 | |
| 	int result;
 | |
| 
 | |
| 	result = stat( name, &st );
 | |
| 
 | |
| 	if( result == -1 )
 | |
| 		return -1;
 | |
| 
 | |
| 	if( is_file ) {
 | |
| 		if( !S_ISREG( st.st_mode ) )
 | |
| 			return -2;
 | |
| 	} else if( !S_ISDIR( st.st_mode ) )
 | |
| 		return -2;
 | |
| 
 | |
| 	*mtime = st.st_mtime;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* BPyMenu_Init:
 | |
|  * import the bpython menus data to Blender, either from:
 | |
|  * - the BPYMENU_DATAFILE file (?/.blender/Bpymenus) or
 | |
|  * - the scripts dir(s), case 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(s) anyway.
 | |
| */
 | |
| int BPyMenu_Init( int usedir )
 | |
| {
 | |
| 	char fname[FILE_MAXDIR];
 | |
| 	char dirname[FILE_MAXDIR];
 | |
| 	char upythondir[FILE_MAXDIR];
 | |
| 	char *upydir = U.pythondir, *sdir = NULL;
 | |
| 	time_t time_dir1 = 0, time_dir2 = 0, time_file = 0;
 | |
| 	int stat_dir1 = 0, stat_dir2 = 0, stat_file = 0;
 | |
| 	int i;
 | |
| 
 | |
| 	DEBUG = G.f & G_DEBUG;	/* is Blender in debug mode (started with -d) ? */
 | |
| 
 | |
| 	/* init global bpymenu table (it is a list of pointers to struct BPyMenus
 | |
| 	 * for each available group: import, export, etc.) */
 | |
| 	for( i = 0; i < PYMENU_TOTAL; i++ )
 | |
| 		BPyMenuTable[i] = NULL;
 | |
| 
 | |
| 	if( DEBUG )
 | |
| 		fprintf(stdout, "\nRegistering scripts in Blender menus ...\n\n" );
 | |
| 
 | |
| 	if( U.pythondir[0] == '\0') {
 | |
| 		upydir = NULL;
 | |
| 	}
 | |
| 	else if (strcmp(U.pythondir, "/") == 0 || strcmp(U.pythondir, "//") == 0) {
 | |
| 		/* these are not accepted to prevent possible slight slowdowns on startup;
 | |
| 		 * they should not be used as user defined scripts dir, anyway, also from
 | |
| 		 * speed considerations, since they'd not be dedicated scripts dirs */
 | |
| 		if (DEBUG) fprintf(stderr,
 | |
| 			"BPyMenus: invalid user defined Python scripts dir: \"/\" or \"//\".\n");
 | |
| 		upydir = NULL;
 | |
| 	}
 | |
| 	else {
 | |
| 		BLI_strncpy(upythondir, upydir, FILE_MAXDIR);
 | |
| 		BLI_convertstringcode(upythondir, G.sce, 0);
 | |
| 	}
 | |
| 
 | |
| 	sdir = bpy_gethome(1);
 | |
| 
 | |
| 	if (sdir) {
 | |
| 		BLI_strncpy(dirname, sdir, FILE_MAXDIR);
 | |
| 		stat_dir1 = bpymenu_GetStatMTime( dirname, 0, &time_dir1 );
 | |
| 
 | |
| 		if( stat_dir1 < 0 ) {
 | |
| 			time_dir1 = 0;
 | |
| 			if( DEBUG ) {
 | |
| 				fprintf(stderr,
 | |
| 					"\nDefault scripts dir: %s:\n%s\n", dirname, strerror(errno));
 | |
| 				if( upydir )
 | |
| 					fprintf(stdout,
 | |
| 						"Getting scripts menu data from user defined dir: %s.\n",
 | |
| 						upythondir );
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	else stat_dir1 = -1;
 | |
| 
 | |
| 	if( upydir ) {
 | |
| 		stat_dir2 = bpymenu_GetStatMTime( upythondir, 0, &time_dir2 );
 | |
| 
 | |
| 		if( stat_dir2 < 0 ) {
 | |
| 			time_dir2 = 0;
 | |
| 			upydir = NULL;
 | |
| 			if( DEBUG )
 | |
| 				fprintf(stderr, "\nUser defined scripts dir: %s:\n%s.\n",
 | |
| 					upythondir, strerror( errno ) );
 | |
| 			if( stat_dir1 < 0 ) {
 | |
| 				if( DEBUG )
 | |
| 					fprintf(stderr, "\
 | |
| To have scripts in menus, please add them to the default scripts dir:\n\
 | |
| %s\n\
 | |
| and / or go to 'Info window -> File Paths tab' and set a valid path for\n\
 | |
| the user defined Python scripts dir.\n", dirname );
 | |
| 				return -1;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	else stat_dir2 = -1;
 | |
| 
 | |
| 	if( ( stat_dir1 < 0 ) && ( stat_dir2 < 0 ) ) {
 | |
| 		if( DEBUG ) {
 | |
| 			fprintf(stderr, "\nCannot register scripts in menus, no scripts dir"
 | |
| 							" available.\nExpected default dir at: %s \n", dirname );
 | |
| 		}
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (usedir) stat_file = -1;
 | |
| 	else { /* if we're not forced to use the dir */
 | |
| 		char *homedir = bpy_gethome(0);
 | |
| 
 | |
| 		if (homedir) {
 | |
| 			BLI_make_file_string( "/", fname, homedir, BPYMENU_DATAFILE );
 | |
| 			stat_file = bpymenu_GetStatMTime( fname, 1, &time_file );
 | |
| 			if( stat_file < 0 )
 | |
| 				time_file = 0;
 | |
| 
 | |
| 		/* comparing dates */
 | |
| 
 | |
| 			if((stat_file == 0)
 | |
| 				&& (time_file > time_dir1) && (time_file > time_dir2))
 | |
| 			{	/* file is newer */
 | |
| 				stat_file = bpymenu_CreateFromFile(  );	/* -1 if an error occurred */
 | |
| 				if( !stat_file && DEBUG )
 | |
| 					fprintf(stdout,
 | |
| 						"Getting menu data for scripts from file:\n%s\n\n", fname );
 | |
| 			}
 | |
| 			else stat_file = -1;
 | |
| 		}
 | |
| 		else stat_file = -1;	/* -1 to use dirs: didn't use file or it was corrupted */
 | |
| 	}
 | |
| 
 | |
| 	if( stat_file == -1 ) {	/* use dirs */
 | |
| 		if( DEBUG ) {
 | |
| 			fprintf(stdout,
 | |
| 				"Getting menu data for scripts from dir(s):\ndefault: %s\n", dirname );
 | |
| 			if( upydir )
 | |
| 				fprintf(stdout, "user defined: %s\n", upythondir );
 | |
| 			fprintf(stdout, "\n");
 | |
| 		}
 | |
| 		if( stat_dir1 == 0 ) {
 | |
| 			i = bpymenu_ParseDir( dirname, NULL, 0 );
 | |
| 			if (i == -1 && DEBUG)
 | |
| 				fprintf(stderr, "Default scripts dir does not seem valid.\n\n");
 | |
| 		}
 | |
| 		if( stat_dir2 == 0 ) {
 | |
| 			BLI_strncpy(dirname, U.pythondir, FILE_MAXDIR);
 | |
| 			BLI_convertstringcode(dirname, G.sce, 0);
 | |
| 			i = bpymenu_ParseDir( dirname, NULL, 1 );
 | |
| 			if (i == -1 && DEBUG)
 | |
| 				fprintf(stderr, "User defined scripts dir does not seem valid.\n\n");
 | |
| 		}
 | |
| 
 | |
| 		/* check if we got any data */
 | |
| 		for( i = 0; i < PYMENU_TOTAL; i++ )
 | |
| 			if( BPyMenuTable[i] )
 | |
| 				break;
 | |
| 
 | |
| 		/* if we got, recreate the file */
 | |
| 		if( i < PYMENU_TOTAL )
 | |
| 			bpymenu_WriteDataFile(  );
 | |
| 		else if( DEBUG ) {
 | |
| 			fprintf(stderr, "\n\
 | |
| Warning: Registering scripts in menus -- no info found.\n\
 | |
| Either your scripts dirs have no .py scripts or the scripts\n\
 | |
| don't have a header with registration data.\n\
 | |
| Default scripts dir is:\n\
 | |
| %s\n", dirname );
 | |
| 			if( upydir )
 | |
| 				fprintf(stderr, "User defined scripts dir is: %s\n",
 | |
| 					upythondir );
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 |