1119 lines
28 KiB
C
1119 lines
28 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;
|
|
else if( !strcmp( str, "UVCalculation" ) )
|
|
return PYMENU_UVCALCULATION;
|
|
else if( !strcmp( str, "Armature" ) )
|
|
return PYMENU_ARMATURE;
|
|
else if( !strcmp( str, "ScriptTemplate" ) )
|
|
return PYMENU_SCRIPTTEMPLATE;
|
|
else if( !strcmp( str, "MeshFaceKey" ) )
|
|
return PYMENU_MESHFACEKEY;
|
|
else if( !strcmp( str, "AddMesh" ) )
|
|
return PYMENU_ADDMESH;
|
|
/* "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_UVCALCULATION:
|
|
return "UVCalculation";
|
|
break;
|
|
case PYMENU_ARMATURE:
|
|
return "Armature";
|
|
break;
|
|
case PYMENU_SCRIPTTEMPLATE:
|
|
return "ScriptTemplate";
|
|
break;
|
|
case PYMENU_MESHFACEKEY:
|
|
return "MeshFaceKey";
|
|
break;
|
|
case PYMENU_ADDMESH:
|
|
return "AddMesh";
|
|
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_MAX];
|
|
char subdir[FILE_MAX];
|
|
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) {
|
|
/* Join parentdir and de->d_name */
|
|
BLI_join_dirfile(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) {
|
|
/* Join parentdir and de->d_name */
|
|
BLI_join_dirfile(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;
|
|
}
|