Note: this commit includes new functionality to save and restore scripts configure...
authorWillian Padovani Germano <wpgermano@gmail.com>
Sat, 16 Apr 2005 05:25:42 +0000 (05:25 +0000)
committerWillian Padovani Germano <wpgermano@gmail.com>
Sat, 16 Apr 2005 05:25:42 +0000 (05:25 +0000)
Scripts:
- Thanks Jean-Michel Soler (jms) for updated versions of dispaint, fixfromarmature and unweld (also renamed to remove version part).
- Thanks Bart for the upgraded VRML exporter (great doc webpage!).  It is available as VRML 97 and the original VRML 2 is for now still there, to help users testing the new version.  For the next release the old one should be removed, of course.
- New script: Scripts Config Editor (Scripts win -> Scripts -> System).  Scripts with config options (simple data that is to be set according to user needs or preferences) can use this facility instead of providing a gui and writing config files to disk themselves.
- Added new menu: System, available in the Scripts win.
- Updated sys_info.py, help_browse.py and the AC3D importer and exporter.
- Removed use of the Scrollbar and added arrow keys and mouse wheel support instead in Daniel Dunbar's old doc_browser.py. The scrollbar events handling doesn't exist, Ton suggested removing the scrollbar from the API months ago.  For now its ref doc is gone and no bundled script uses it, until we get time to implement it properly.
- Added module BPyRegistry.py with functions to handle reading / writing config files automatically to the scripts/bpydata/config dir.
- Removing dir release/bpydata and its contents (moved earlier to release/scripts/bpydata/)
- Bug #2379: made small changes to bevel_center's ui to fix a problem reported by Alexander Ewering (intrr):
http://projects.blender.org/tracker/?func=detail&atid=125&aid=2379&group_id=9

BPython:
- Thanks Campbell Barton for new functionality: Blender.Get() now can also return all the paths from the user prefs -> file paths win and there is a new function: Blender.sys.expandpath() to transform Blender paths (those starting with '//' and ending with '#') to absolute paths.
- Added function Blender.ShowHelp(), to open the Scripts Help Browser with a given help page -- just a time saver for scripts.
- Improved function Blender.Run() to also work with gui and file select scripts.
- Found a (new?) crash related to NMesh.PutRaw when creating a new object while in edit mode.  Leaving / entering edit mode fixes the problem, so a check for obj created, edit mode and leaving / re-entering it were added to the code for now (gdb didn't help much, no backtrace)
- doc updates, including splitting intro page in two, with bpython related stuff (registering / documenting / configuring scripts and command line mode (thanks Chris Want for "use system variables to pass parameters to scripts" idea).
- Registry: functions have been updated to support writing to / reading from disk, for the config editor -- only simple config data supported, for large amounts coders should write to a file themselves.  This is done with a new parameter: Registry.GetKey(keyname, True) will also search for the key on the config dir, if not already loaded; equiv. for Registry.SetKey(keyname, dict, True).  Data is only written to / read from disk when needed and only scripts already used (assuming they support this functionality) will have config data saved.

30 files changed:
release/bpydata/KUlang.txt [deleted file]
release/bpydata/readme.txt [deleted file]
release/scripts/ac3d_export.py
release/scripts/ac3d_import.py
release/scripts/bevel_center.py
release/scripts/bpymodules/BPyBlender.py
release/scripts/bpymodules/BPyRegistry.py [new file with mode: 0644]
release/scripts/config.py [new file with mode: 0644]
release/scripts/disp_paint.py
release/scripts/doc_browser.py
release/scripts/fixfromarmature.py
release/scripts/help_browser.py
release/scripts/sysinfo.py
release/scripts/unweld.py [new file with mode: 0644]
release/scripts/vrml97_export.py [new file with mode: 0644]
source/blender/python/BPY_menus.c
source/blender/python/BPY_menus.h
source/blender/python/api2_2x/Blender.c
source/blender/python/api2_2x/EXPP_interface.c
source/blender/python/api2_2x/NMesh.c
source/blender/python/api2_2x/Registry.c
source/blender/python/api2_2x/Sys.c
source/blender/python/api2_2x/doc/API_intro.py
source/blender/python/api2_2x/doc/API_related.py [new file with mode: 0644]
source/blender/python/api2_2x/doc/Blender.py
source/blender/python/api2_2x/doc/Draw.py
source/blender/python/api2_2x/doc/Object.py
source/blender/python/api2_2x/doc/Registry.py
source/blender/python/api2_2x/doc/Render.py
source/blender/python/api2_2x/doc/Sys.py

diff --git a/release/bpydata/KUlang.txt b/release/bpydata/KUlang.txt
deleted file mode 100644 (file)
index 38605d6..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-Version 3.233-2004
-******************
-Espanol
-Sale del programa
-Utilidades de...%t|Alinea objetos%x1|Creacion%x2|Edita mallas%x3|Edita objetos%x4
-11
-Mov
-Esc
-Encaja
-Abarca
-Separa
-Alinea
-Rota
-Incr.
-Crea nuevos objetos
-Es+
-Es*
-Separar entre:%t|Origenes%x1|Centros geometricos%x2|Minimos%x3|Maximos%x4|Baricentro%x5|Objetos%x6
-Crear%t|Arco (3 ptos.)%x1|Arco (interactivo)%x2|Circunferencia (3 ptos.)%x3
-12
-Puntos
-Centro
-Orden
-Objeto
-AngIni:
-AngFin:
-Angulo:
-Radio:
-Puntos:
-Centro
-Nombre:
-Puntos
-Modifica vertices%t|Subdivide%x1|Envia a un plano%x2|Aplica LocRotSize%x3
-Partes
-Proyectar en el plano:%t|Coordenado global...%x1|Coordenado local...%x2
-Actuar sobre el plano%t|Yz%x1|Zx%x2|Xy%x3
-En la dirección%t|X%x1|Y%x2|Z%x3|Ortogonal al plano%x4
-Captura
-Buffer%t|Copia vector diferencia%x1|Copia distancia%x2|Copia diferencia de rotacion%x3|Copia media LocRotSiz%x4|Ver buffer en consola%x5
-Transformar LocRotSize%t|Hacia el obj. activo%x1|Aleatoriamente%x2
-Poner a distancia fija%x1|Sumar (desp. absoluto)%x2|Multiplicar (desp. relativo)%x3
-********************
-English
-Exit program
-Utils about:%t|Align Objects%x1|Create%x2|Edit Meshes%x3|Edit Objects%x4
-11
-Mov
-Sca
-Fit
-Embrace
-Separate
-Align
-Rota
-Incr.
-Create new objects
-Sc+
-Sc*
-Separate between:%t|Origins%x1|Geometric centers%x2|Minimum%x3|Maximum%x4|Baricenter%x5|Objects%x6
-Create what%t|Arc (3 pts.)%x1|Arc (interactive)%x2|Circunference (3 pts.)%x3
-12
-Points
-Centre
-Sort
-Object
-AngIni:
-AngEnd:
-Angle:
-Radius:
-Points:
-Centre
-ObjName:
-Points
-Modify vertices%t|Subdivide edges%x1|Send to a plane%x2|Set LocRotSize%x3
-Parts
-Project onto the plane:%t|Global coordinated...%x1|Local coordinated...%x2
-Act on plane%t|Yz%x1|Zx%x2|Xy%x3
-In direction%t|X%x1|Y%x2|Z%x3|Ortogonal to plane%x4
-Get
-Buffer%t|Copy diference vector%x1|Copy distance%x2|Copy rot diference%x3|Copy LocRotSiz average%x4|Show Buffer in Console%x5
-Transform LocRotSize%t|Close to active%x1|Randomly%x2
-Set at fixed distance%x1|Add (absolute displ.)%x2|Multiply (relative displ.)%x3
-********************
-Catala
-Surt del programa
-Utilitats de...%t|Alinea objectes%x1|Creacio%x2|Edita malles%x3|Edita objetes%x4
-11
-Mov
-Esc
-Encaixa
-Abarca
-Separa
-Alinea
-Rotacio
-Incr.
-Crea objectes nous
-Es+
-Es*
-Separa entra:%t|Origens%x1|Centres geometrics%x2|Minims%x3|Maxims%x4|Baricentre%x5|Objectes%x6
-Crear%t|Arc (3 pts.)%x1|Arc (interactiu)%x2|Circumferencia  (3 pts.)%x3
-12
-Punts
-Centre
-Ordre
-Objecte
-AngIni:
-AngFi:
-Angle:
-Radi:
-Punts:
-Centre
-Nom:
-Punts
-Modifica vertex%t|Subdivideix%x1|Envia a un pla%x2|Aplica LocRotSize%x3
-Parts
-Projectar en el pla:%t|Coordenacio global...%x1|Coordenacio local...%x2
-Actuar sobre el pla%t|Yz%x1|Zx%x2|Xy%x3
-En la direccio%t|X%x1|Y%x2|Z%x3|Ortogonal al pla%x4
-Captura
-Buffer%t|Copia vector diferencia%x1|Copia distancia%x2|Copia diferencia de rotacio%x3|Copia mitjana LocRotSiz%x4|Veure buffer en consola%x5
-Transformar LocRotSize%t|Cap al obj. actiu%x1|Aleatoriamente%x2
-Posar a distancia fixa%x1|Sumar (desp. absolut)%x2|Multiplicar (desp. relatiu)%x3
diff --git a/release/bpydata/readme.txt b/release/bpydata/readme.txt
deleted file mode 100644 (file)
index 3e640e2..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-This directory is the default place for scripts to put their data,
-like internal files needed by the script and its saved configuration.
-
-Scripts can find the path to this dir using Blender.Get("datadir").
-Ex:
-
-import Blender
-print Blender.Get("datadir")
-
index 2f44c0910c4884ce4cda1dee4977751d4bcc0921..85d1ac5ceff83d30f64fc6c72696f24ec38bf2d7 100644 (file)
@@ -2,52 +2,75 @@
 
 """ Registration info for Blender menus:
 Name: 'AC3D (.ac)...'
-Blender: 233
+Blender: 236
 Group: 'Export'
-Submenu: 'All meshes...' all
-Submenu: 'Only selected...' sel
-Submenu: 'Configure +' config
-Tip: 'Export to AC3D (.ac) format.'
+Tip: 'Export selected meshes to AC3D (.ac) format'
 """
 
 __author__ = "Willian P. Germano"
 __url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org",
        "PLib 3d gaming lib, http://plib.sf.net")
-__version__ = "2.34 09/20/04"
+__version__ = "2.36 2005-04-14"
 
 __bpydoc__ = """\
-This script exports Blender meshes to AC3D's .ac file format.
+This script exports selected Blender meshes to AC3D's .ac file format.
 
 AC3D is a simple commercial 3d modeller also built with OpenGL.
 The .ac file format is an easy to parse text format well supported,
-for example, by the PLib 3d gaming library (AC3D v3.x).
+for example, by the PLib 3d gaming library (AC3D 3.x).
 
 Supported:<br>
     UV-textured meshes with hierarchy (grouping) information.
 
 Missing:<br>
-    Support for AC3D 4's crease tag (simple, will be added as option for
-the next version of this exporter).
+    The 'url' tag, specific to AC3D.  It is easy to add by hand to the exported
+file, if needed.
 
 Known issues:<br>
-    Models textured with more than one image do not work -- for the
-moment you can separate them in Blender such that each mesh only has one
-image assigned (also see notes below);<br>
-    The exporter is slow for large meshes -- faster code was written for the
-TuxKart (http://tuxkart.sf.net, wiki at http://netpanzer.berlios.de/tuxkart)
-game exporter and will be integrated on a future version of this exporter.
+    The ambient and emit data we can retrieve from Blender are single values,
+that this script copies to R, G, B, giving shades of gray.<br>
+    In AC3D 4 "compatibility mode":<br>
+    - shininess of materials is taken from the shader specularity value in Blender, mapped from [0.0, 2.0] to [0, 128];<br>
+    - crease angle is exported, but in Blender it is limited to [1, 80], since there are other more powerful ways to control surface smoothing.  In AC3D 4.0 crease's range is [0.0, 180.0];
+
+Config Options:<br>
+    toggle:
+    - AC3D 4 mode: unset it to export without the 'crease' tag that was
+introduced with AC3D 4.0 and with the old material handling;<br>
+    - skip data: set it if you don't want mesh names (ME:, not OB: field)
+to be exported as strings for AC's "data" tags (19 chars max);<br>
+    - rgb mirror color can be exported as ambient and/or emissive if needed,
+since Blender handles these differently;<br>
+    - default mat: a default (white) material is added if some mesh was
+left without mats -- it's better to always add your own materials;<br>
+    - no split: don't split meshes (see above);<br>
+    - set texture dir: override the actual textures path with a given default
+path (or simply export the texture names, without dir info, if the path is
+empty);<br>
+    strings:
+    - export dir: default dir to export to;<br>
+    - texture dir: override textures path with this path if 'set texture dir'
+toggle is "on".
 
 Notes:<br>
-    There is a version of this script, by Ingo Ruhnke, that accepts meshes with
-more than one texture image assigned, check TuxKart's wiki.
+    This version is considerably faster than previous ones for large meshes;<br>
+    Multiple textures per mesh are supported (mesh gets split);<br>
+    Parenting with meshes or empties as parents is converted to AC3D group
+information;<br>
+    Start mesh object names (OB: field) with "!" or "#" if you don't want them to be exported;<br>
+    Start mesh object names (OB: field) with "=" or "$" to prevent them from being split (meshes with multiple textures or both textured and non textured faces are split unless this trick is used or the "no split" option is set.
 """
 
 # $Id$
 #
 # --------------------------------------------------------------------------
-# AC3DExport version 2.34
-# Program versions: Blender 2.34 and AC3Db files (means version 0xb)
-# new: minor cosmetic tweaks, exporter itself didn't change
+# AC3DExport version 2.36+
+# Program versions: Blender 2.36+ and AC3Db files (means version 0xb)
+# new: faster, supports multiple textures per object and parenting is
+# properly exported as group info, adapted to work with the Config Editor
+# --------------------------------------------------------------------------
+# Thanks: Steve Baker for discussions and inspiration; for testing, bug
+# reports, suggestions: David Megginson, Filippo di Natale, Franz Melchior
 # --------------------------------------------------------------------------
 # ***** BEGIN GPL LICENSE BLOCK *****
 #
@@ -60,413 +83,602 @@ more than one texture image assigned, check TuxKart's wiki.
 #
 # 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
+# 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.
-#
-# ***** END GPL LICENCE BLOCK *****
 # --------------------------------------------------------------------------
 
 import Blender
+from Blender import sys as bsys
+from Blender import Mathutils
+
+# Globals
+ERROR_MSG = '' # popup error msg
+REPORT_DATA = {
+       'main': [],
+       'errors': [],
+       'warns': [],
+       'nosplit': [],
+       'noexport': []
+}
+TOKENS_DONT_EXPORT = ['!', '#']
+TOKENS_DONT_SPLIT  = ['=', '$']
+MATIDX_ERROR = False
+
+REG_KEY = 'ac3d_export'
+
+# config options:
+SKIP_DATA = False
+MIRCOL_AS_AMB = False
+MIRCOL_AS_EMIS = False
+ADD_DEFAULT_MAT = True
+SET_TEX_DIR = True
+TEX_DIR = ''
+AC3D_4 = True # export crease value, compatible with AC3D 4 loaders
+NO_SPLIT = False
+EXPORT_DIR = ''
+
+tooltips = {
+       'SKIP_DATA': "don't export mesh names as data fields",
+       'MIRCOL_AS_AMB': "export mirror color as ambient color",
+       'MIRCOL_AS_EMIS': "export mirror color as emissive color",
+       'ADD_DEFAULT_MAT': "always add a default white material",
+       'SET_TEX_DIR': "don't export default texture paths (edit also \"tex dir\")",
+       'EXPORT_DIR': "default / last folder used to export .ac files to",
+       'TEX_DIR': "(see \"set tex dir\") dir to prepend to all exported texture names (leave empty for no dir)",
+       'AC3D_4': "compatibility mode, adds 'crease' tag and slightly better material support",
+       'NO_SPLIT': "don't split meshes with multiple textures (or both textured and non textured polygons)",
+}
 
-ARG = __script__['arg'] # user selected argument
+def update_RegistryInfo():
+       d = {}
+       d['SKIP_DATA'] = SKIP_DATA
+       d['MIRCOL_AS_AMB'] = MIRCOL_AS_AMB
+       d['MIRCOL_AS_EMIS'] = MIRCOL_AS_EMIS
+       d['ADD_DEFAULT_MAT'] = ADD_DEFAULT_MAT
+       d['SET_TEX_DIR'] = SET_TEX_DIR
+       d['TEX_DIR'] = TEX_DIR
+       d['AC3D_4'] = AC3D_4
+       d['NO_SPLIT'] = NO_SPLIT
+       d['EXPORT_DIR'] = EXPORT_DIR
+       d['tooltips'] = tooltips
+       Blender.Registry.SetKey(REG_KEY, d, True)
 
-HELPME = 0 # help window
+# Looking for a saved key in Blender.Registry dict:
+rd = Blender.Registry.GetKey(REG_KEY, True)
 
-SKIP_DATA = 1
-MIRCOL_AS_AMB = 0
-MIRCOL_AS_EMIS = 0
-ADD_DEFAULT_MAT = 1
+if rd:
+       try:
+               AC3D_4 = rd['AC3D_4']
+               SKIP_DATA = rd['SKIP_DATA']
+               MIRCOL_AS_AMB = rd['MIRCOL_AS_AMB']
+               MIRCOL_AS_EMIS = rd['MIRCOL_AS_EMIS']
+               ADD_DEFAULT_MAT = rd['ADD_DEFAULT_MAT']
+               SET_TEX_DIR = rd['SET_TEX_DIR']
+               TEX_DIR = rd['TEX_DIR']
+               EXPORT_DIR = rd['EXPORT_DIR']
+               NO_SPLIT = rd['NO_SPLIT']
+       except KeyError: update_RegistryInfo()
 
-# Looking for a saved key in Blender.Registry dict:
-rd = Blender.Registry.GetKey('AC3DExport')
+else:
+       update_RegistryInfo()
+
+VERBOSE = True
+CONFIRM_OVERWRITE = True
+
+# check General scripts config key for default behaviors
+rd = Blender.Registry.GetKey('General', True)
 if rd:
-  SKIP_DATA = rd['SKIP_DATA']
-  MIRCOL_AS_AMB = rd['MIRCOL_AS_AMB']
-  MIRCOL_AS_EMIS = rd['MIRCOL_AS_EMIS']
-  ADD_DEFAULT_MAT = rd['ADD_DEFAULT_MAT']
+       try:
+               VERBOSE = rd['verbose']
+               CONFIRM_OVERWRITE = rd['confirm_overwrite']
+       except: pass
 
-def update_RegistryInfo():
-  d = {}
-  d['SKIP_DATA'] = SKIP_DATA
-  d['MIRCOL_AS_AMB'] = MIRCOL_AS_AMB
-  d['MIRCOL_AS_EMIS'] = MIRCOL_AS_EMIS
-  d['ADD_DEFAULT_MAT'] = ADD_DEFAULT_MAT
-  Blender.Registry.SetKey('AC3DExport', d)
 
 # The default material to be used when necessary (see ADD_DEFAULT_MAT)
 DEFAULT_MAT = \
-'MATERIAL "DefaultWhite" rgb 1 1 1  amb 1 1 1  emis 0 0 0  spec 0.5 0.5 0.5 shi 64  trans 0'
+'MATERIAL "DefaultWhite" rgb 1 1 1  amb 1 1 1  emis 0 0 0  \
+spec 0.5 0.5 0.5  shi 64  trans 0'
 
 # This transformation aligns Blender and AC3D coordinate systems:
-acmatrix = [[1,0,0,0],[0,0,-1,0],[0,1,0,0],[0,0,0,1]]
+acmatrix = Mathutils.Matrix([1,0,0,0], [0,0,-1,0], [0,1,0,0], [0,0,0,1])
 
 def Round(f):
-    r = round(f,6) # precision set to 10e-06
-    if r == int(r):
-        return str(int(r))
-    else:
-        return str(r)
-    
+       r = round(f,6) # precision set to 10e-06
+       if r == int(r):
+               return str(int(r))
+       else:
+               return str(r)
 def transform_verts(verts, m):
-    r = []
-    for v in verts:
-        t = [0,0,0]
-        t[0] = m[0][0]*v[0] + m[1][0]*v[1] + m[2][0]*v[2] + m[3][0]
-        t[1] = m[0][1]*v[0] + m[1][1]*v[1] + m[2][1]*v[2] + m[3][1]
-        t[2] = m[0][2]*v[0] + m[1][2]*v[1] + m[2][2]*v[2] + m[3][2]
-        r.append(t)
-    return r
-
-def matrix_mul(m, n = acmatrix):
-    indices = [0,1,2,3]
-    t = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
-    for i in indices:
-        for j in indices:
-            for k in indices:
-                t[i][j] += m[i][k]*n[k][j]
-    return t
+       vecs = []
+       for v in verts:
+               vec = Mathutils.Vector([v[0],v[1],v[2], 1])
+               vecs.append(Mathutils.VecMultMat(vec, m))
+       return vecs
 
 # ---
 
-class AC3DExport:
+# meshes with more than one texture assigned
+# are split and saved as these foomeshes
+class FooMesh:
+
+       class FooVert:
+               def __init__(self, v):
+                       self.v = v
+                       self.index = 0
+
+       class FooFace:
+               def __init__(self, foomesh, f):
+                       self.f = f
+                       foov = foomesh.FooVert
+                       self.v = [foov(f.v[0]), foov(f.v[1])]
+                       len_fv = len(f.v)
+                       if len_fv > 2 and f.v[2]:
+                               self.v.append(foov(f.v[2]))
+                               if len_fv > 3 and f.v[3]: self.v.append(foov(f.v[3]))
+
+               def __getattr__(self, attr):
+                       if attr == 'v': return self.v
+                       return getattr(self.f, attr)
+
+               def __len__(self):
+                       return len(self.f)
+
+       def __init__(self, tex, faces, mesh):
+               self.name = mesh.name
+               self.mesh = mesh
+               self.faces = []
+               self.verts = verts = []
+               vidxs = [0]*len(mesh.verts)
+               faces2 = [0]*len(faces)
+               for f in faces:
+                       self.faces.append(self.FooFace(self, f))
+                       for v in f.v:
+                               if v: vidxs[v.index] = 1
+               i = 0
+               for v in mesh.verts:
+                       if vidxs[v.index]:
+                               verts.append(v)
+                               vidxs[v.index] = i
+                               i += 1
+               for f in self.faces:
+                       for v in f.v:
+                               if v: v.index = vidxs[v.v.index]
+
+       def hasFaceUV(self):
+               return self.mesh.hasFaceUV()
+
+       def getMaxSmoothAngle(self):
+               return self.mesh.getMaxSmoothAngle()
+
+
+class AC3DExport: # the ac3d exporter part
+
+       def __init__(self, scene_objects, filename):
+
+               global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT
+               global ERROR_MSG, MATIDX_ERROR
+
+               MATIDX_ERROR = 0
+
+               header = 'AC3Db'
+               self.buf = ''
+               self.mbuf = ''
+               self.mlist = []
+               world_kids = 0
+               kids_dict = self.kids_dict = {}
+               objs = []
+               exp_objs = self.exp_objs = []
+               tree = {}
+
+               try:
+                       file = self.file = open(filename, 'w')
+               except IOError, (errno, strerror):
+                       error = "IOError #%s: %s" % (errno, strerror)
+                       REPORT_DATA['errors'].append("Saving failed - %s." % error)
+                       ERROR_MSG = "Couldn't save file!%%t|%s" % error
+                       return None
+
+               file.write(header+'\n')
+
+               objs = \
+                       [o for o in scene_objects if o.getType() in ['Mesh', 'Empty']]
+
+               for obj in objs[:]:
+                       parent = obj.getParent()
+                       list = [obj]
+
+                       while parent:
+                               obj = parent
+                               parent = parent.getParent()
+                               list.insert(0, obj)
+
+                       dict = tree
+                       for i in range(len(list)):
+                               lname = list[i].getType()[:2] + list[i].name
+                               if lname not in dict.keys():
+                                       dict[lname] = {}
+                               dict = dict[lname]
+
+               self.traverse_dict(tree)
+
+               world_kids = len(tree.keys())
+
+               objlist = [Blender.Object.Get(name) for name in exp_objs]
+
+               meshlist = [o for o in objlist if o.getType() == 'Mesh']
+
+               self.MATERIALS(meshlist)
+               if not self.mbuf or ADD_DEFAULT_MAT:
+                       self.mbuf = DEFAULT_MAT + '\n' + self.mbuf
+               file.write(self.mbuf)
+
+               file.write('OBJECT world\nkids %s\n' % world_kids)
+
+               for obj in objlist:
+                       self.obj = obj
+
+                       objtype = obj.getType()
+                       objname = obj.name
+                       kidsnum = kids_dict[objname]
+
+                       if kidsnum:
+                               self.OBJECT('group')
+                               parent_is_mesh = 0
+                               if objtype == 'Mesh':
+                                       kidsnum += 1
+                                       parent_is_mesh = 1
+                               self.name(objname)
+                               self.kids(kidsnum)
+
+                       if objtype == 'Mesh':
+                               mesh = self.mesh = obj.getData()
+                               meshes = self.split_mesh(mesh)
+                               if len(meshes) > 1:
+                                       if NO_SPLIT or self.dont_split(objname):
+                                               self.export_mesh(mesh, obj)
+                                               REPORT_DATA['nosplit'].append(objname)
+                                       else:
+                                               self.OBJECT('group')
+                                               self.name(objname)
+                                               self.kids(len(meshes))
+                                               counter = 0
+                                               for me in meshes:
+                                                       self.export_mesh(me, obj,
+                                                               name = '%s_%s' % (obj.name, counter), foomesh = True)
+                                                       self.kids()
+                                                       counter += 1
+                               else:
+                                       self.export_mesh(mesh, obj)
+                                       self.kids()
+
+               file.close()
+               REPORT_DATA['main'].append("Done. Saved to: %s" % filename)
+
+       def traverse_dict(self, dict):
+               kids_dict = self.kids_dict
+               exp_objs = self.exp_objs
+               keys = dict.keys()
+               for k in keys:
+                       objname = k[2:]
+                       klen = len(dict[k])
+                       kids_dict[objname] = klen
+                       if self.dont_export(objname):
+                               dict.pop(k)
+                               parent = Blender.Object.Get(objname).getParent()
+                               if parent: kids_dict[parent.name] -= 1
+                               REPORT_DATA['noexport'].append(objname)
+                               continue
+                       if klen:
+                               self.traverse_dict(dict[k])
+                               exp_objs.insert(0, objname)
+                       else:
+                               if k.find('Em', 0) == 0: # Empty w/o children
+                                       dict.pop(k)
+                                       parent = Blender.Object.Get(objname).getParent()
+                                       if parent: kids_dict[parent.name] -= 1
+                               else:
+                                       exp_objs.insert(0, objname)
+
+       def dont_export(self, name): # if name starts with '!' or '#'
+               length = len(name)
+               if length >= 1:
+                       if name[0] in TOKENS_DONT_EXPORT: # '!' or '#' doubled (escaped): export
+                               if length > 1 and name[1] == name[0]:
+                                       return 0
+                               return 1
+
+       def dont_split(self, name): # if name starts with '=' or '$'
+               length = len(name)
+               if length >= 1:
+                       if name[0] in TOKENS_DONT_SPLIT: # '=' or '$' doubled (escaped): split
+                               if length > 1 and name[1] == name[0]:
+                                       return 0
+                               return 1
+
+       def split_mesh(self, mesh):
+               tex_dict = {0:[]}
+               for f in mesh.faces:
+                       if f.image:
+                               if not f.image.name in tex_dict: tex_dict[f.image.name] = []
+                               tex_dict[f.image.name].append(f)
+                       else: tex_dict[0].append(f)
+               keys = tex_dict.keys()
+               len_keys = len(keys)
+               if not tex_dict[0]:
+                       len_keys -= 1
+                       tex_dict.pop(0)
+                       keys.remove(0)
+               elif len_keys > 1:
+                       lines = []
+                       anyimgkey = [k for k in keys if k != 0][0]
+                       for f in tex_dict[0]:
+                               if len(f.v) < 3:
+                                       lines.append(f)
+                       if len(tex_dict[0]) == len(lines):
+                               for l in lines:
+                                       tex_dict[anyimgkey].append(l)
+                               len_keys -= 1
+                               tex_dict.pop(0)
+               if len_keys > 1:
+                       foo_meshes = []
+                       for k in keys:
+                               faces = tex_dict[k]
+                               foo_meshes.append(FooMesh(k, faces, mesh))
+                       return foo_meshes
+               return [mesh]
+
+       def export_mesh(self, mesh, obj, name = None, foomesh = False):
+               file = self.file
+               self.OBJECT('poly')
+               if not name: name = obj.name
+               self.name(name)
+               if not SKIP_DATA:
+                       self.data(len(mesh.name), mesh.name)
+               texline = self.texture(mesh.faces)
+               if texline: file.write(texline)
+               if AC3D_4:
+                       self.crease(mesh.getMaxSmoothAngle())
+               self.numvert(mesh.verts, obj.getMatrix())
+               self.numsurf(mesh.faces, mesh.hasFaceUV(), foomesh)
+
+       def MATERIALS(self, meshlist):
+               for meobj in meshlist:
+                       me = meobj.getData()
+                       mat = me.materials
+                       mbuf = []
+                       mlist = self.mlist
+                       for m in xrange(len(mat)):
+                               name = mat[m].name
+                               try:
+                                       mlist.index(name)
+                               except ValueError:
+                                       mlist.append(name)
+                                       M = Blender.Material.Get(name)
+                                       material = 'MATERIAL "%s"' % name
+                                       mirCol = "%s %s %s" % (Round(M.mirCol[0]), Round(M.mirCol[1]),
+                                               Round(M.mirCol[2]))
+                                       rgb = "rgb %s %s %s" % (Round(M.R), Round(M.G), Round(M.B))
+                                       amb = "amb %s %s %s" % (Round(M.amb), Round(M.amb), Round(M.amb))
+                                       spec = "spec %s %s %s" % (Round(M.specCol[0]),
+                                                Round(M.specCol[1]), Round(M.specCol[2]))
+                                       if AC3D_4:
+                                               emit = Round(M.emit)
+                                               emis = "emis %s %s %s" % (emit, emit, emit)
+                                               shival = int(M.spec * 64)
+                                       else:
+                                               emis = "emis 0 0 0"
+                                               shival = 72
+                                       shi = "shi %s" % shival
+                                       trans = "trans %s" % (Round(1 - M.alpha))
+                                       if MIRCOL_AS_AMB:
+                                               amb = "amb %s" % mirCol 
+                                       if MIRCOL_AS_EMIS:
+                                               emis = "emis %s" % mirCol
+                                       mbuf.append("%s %s %s %s %s %s %s\n" \
+                                               % (material, rgb, amb, emis, spec, shi, trans))
+                       self.mlist = mlist
+                       self.mbuf += "".join(mbuf)
+
+       def OBJECT(self, type):
+               self.file.write('OBJECT %s\n' % type)
+
+       def name(self, name):
+               if name[0] in TOKENS_DONT_EXPORT or name[0] in TOKENS_DONT_SPLIT:
+                       if len(name) > 1: name = name[1:]
+               self.file.write('name "%s"\n' % name)
+
+       def kids(self, num = 0):
+               self.file.write('kids %s\n' % num)
+
+       def data(self, num, str):
+               self.file.write('data %s\n%s\n' % (num, str))
+
+       def texture(self, faces):
+               tex = ""
+               for f in faces:
+                       if f.image:
+                               tex = f.image.name
+                               break
+               if tex:
+                       image = Blender.Image.Get(tex)
+                       texfname = image.filename
+                       if SET_TEX_DIR:
+                               texfname = Blender.sys.basename(texfname)
+                               if TEX_DIR:
+                                       texfname = Blender.sys.join(TEX_DIR, texfname)
+                       buf = 'texture "%s"\n' % texfname
+                       xrep = image.xrep
+                       yrep = image.yrep
+                       buf += 'texrep %s %s\n' % (xrep, yrep)
+                       self.file.write(buf)
+
+       def rot(self, matrix):
+               rot = ''
+               not_I = 0
+               for i in [0, 1, 2]:
+                       r = map(Round, matrix[i])
+                       not_I += (r[0] != '0.0')+(r[1] != '0.0')+(r[2] != '0.0')
+                       not_I -= (r[i] == '1.0')
+                       for j in [0, 1, 2]:
+                               rot = "%s %s" % (rot, r[j])
+               if not_I:
+                       self.file.write('rot %s\n' % rot.strip())
+                               
+       def loc(self, loc):
+               loc = map(Round, loc)
+               if loc[0] or loc[1] or loc[2]:
+                       self.file.write('loc %s %s %s\n' % (loc[0], loc[1], loc[2]))
+
+       def crease(self, crease):
+               self.file.write('crease %s\n' % crease)
+
+       def numvert(self, verts, matrix):
+               file = self.file
+               file.write("numvert %s\n" % len(verts))
+               m = matrix * acmatrix
+               verts = transform_verts(verts, m)
+               for v in verts:
+                       v0, v1, v2 = Round(v[0]), Round(v[1]), Round(v[2])
+                       file.write("%s %s %s\n" % (v0, v1, v2))
+
+       def numsurf(self, faces, hasFaceUV, foomesh = False):
+
+               global ADD_DEFAULT_MAT, MATIDX_ERROR
+               file = self.file
+               file.write("numsurf %s\n" % len(faces))
+
+               if not foomesh: verts = self.mesh.verts
+
+               mlist = self.mlist
+               omlist = {}
+               objmats = self.mesh.materials[:]
+               matidx_error_told = 0
+               for i in range(len(objmats)):
+                       objmats[i] = objmats[i].name
+               for f in faces:
+                       m_idx = f.materialIndex
+                       try:
+                               m_idx = mlist.index(objmats[m_idx])
+                       except IndexError:
+                               if not MATIDX_ERROR:
+                                       rdat = REPORT_DATA['warns']
+                                       rdat.append("Object %s" % self.obj.name)
+                                       rdat.append("has at least one material *index* assigned but not")
+                                       rdat.append("defined (not linked to an existing material).")
+                                       rdat.append("Result: some faces may be exported with a wrong color.")
+                                       rdat.append("You can link materials in the Edit Buttons window (F9).")
+                               elif not matidx_error_told:
+                                       midxmsg = "- Same for object %s." % self.obj.name
+                                       REPORT_DATA['warns'].append(midxmsg)
+                               MATIDX_ERROR += 1
+                               matidx_error_told = 1
+                               m_idx = 0
+                       refs = len(f)
+                       flaglow = (refs == 2) << 1
+                       two_side = f.mode & Blender.NMesh.FaceModes['TWOSIDE']
+                       two_side = (two_side > 0) << 1
+                       flaghigh = f.smooth | two_side
+                       surfstr = "SURF 0x%d%d\n" % (flaghigh, flaglow)
+                       if ADD_DEFAULT_MAT and objmats: m_idx += 1
+                       matstr = "mat %s\n" % m_idx
+                       refstr = "refs %s\n" % refs
+                       u, v, vi = 0, 0, 0
+                       fvstr = []
+                       if foomesh:
+                               for vert in f.v:
+                                       fvstr.append(str(vert.index))
+                                       if hasFaceUV:
+                                               u = f.uv[vi][0]
+                                               v = f.uv[vi][1]
+                                               vi += 1
+                                       fvstr.append(" %s %s\n" % (u, v))
+                       else:
+                               for vert in f.v:
+                                       fvstr.append(str(verts.index(vert)))
+                                       if hasFaceUV:
+                                               u = f.uv[vi][0]
+                                               v = f.uv[vi][1]
+                                               vi += 1
+                                       fvstr.append(" %s %s\n" % (u, v))
+
+                       fvstr = "".join(fvstr)
+
+                       file.write("%s%s%s%s" % (surfstr, matstr, refstr, fvstr))
 
-    def __init__(self, scene, filename):
+# End of Class AC3DExport
 
-        global ARG, SKIP_DATA, ADD_DEFAULT_MAT, DEFAULT_MAT
+from Blender.Window import FileSelector
+
+def report_data():
+       global VERBOSE
+
+       if not VERBOSE: return
+
+       d = REPORT_DATA
+       msgs = {
+               '0main': '%s\nExporting meshes to AC3D format' % str(19*'-'),
+               '1warns': 'Warnings',
+               '2errors': 'Errors',
+               '3nosplit': 'Not split (because name starts with "=" or "$")',
+               '4noexport': 'Not exported (because name starts with "!" or "#")'
+       }
+       if NO_SPLIT:
+               l = msgs['3nosplit']
+               l = "%s (because OPTION NO_SPLIT is set)" % l.split('(')[0] 
+               msgs['3nosplit'] = l
+       keys = msgs.keys()
+       keys.sort()
+       for k in keys:
+               msgk = msgs[k]
+               msg = '\n'.join(d[k[1:]])
+               if msg:
+                       print '\n-%s:' % msgk
+                       print msg
+
+# File Selector callback:
+def fs_callback(filename):
+       global ERROR_MSG, EXPORT_DIR, OBJS, CONFIRM_OVERWRITE, VERBOSE
 
-        print 'Trying AC3DExport...'
+       if not filename.endswith('.ac'): filename = '%s.ac' % filename
 
-        header = 'AC3Db'
-        self.buf = ''
-        self.mbuf = ''
-        world_kids = 0
-        self.mlist = []
-        kids_dict = {}
-        objlist = []
-        bl_objlist2 = []
-        if ARG == 'all': bl_objlist = scene.getChildren()
-        elif ARG == 'sel': bl_objlist = Blender.Object.GetSelected()
-
-        for obj in bl_objlist:
-            if obj.getType() != 'Mesh' and obj.getType() != 'Empty':
-                continue
-            else: kids_dict[obj.name] = 0
-            if obj.getParent() == None:
-                objlist.append(obj.name)
-            else:
-                bl_objlist2.append(obj)
-
-        bl_objlist = bl_objlist2[:]
-        world_kids = len(objlist)
-
-        while bl_objlist2:
-            for obj in bl_objlist:
-                obj2 = obj
-                dad = obj.getParent()
-                kids_dict[dad.name] += 1
-                while dad.name not in objlist:
-                    obj2 = dad
-                    dad = dad.getParent()
-                    kids_dict[dad.name] += 1
-                objlist.insert(objlist.index(dad.name)+1, obj2.name)
-                bl_objlist2.remove(obj2)
-
-        for object in objlist:
-            obj = Blender.Object.Get(object)
-            self.obj = obj
-
-            if obj.getType() == 'Empty':
-                self.OBJECT("group")
-                self.name(obj.name)
-                #self.rot(obj.rot)
-                #self.loc(obj.loc)
-            else:
-                mesh = self.mesh = obj.getData()
-                self.MATERIAL(mesh.materials)
-                self.OBJECT("poly")
-                self.name(obj.name)
-                if not SKIP_DATA: self.data(mesh.name)
-                self.texture(mesh.faces)
-                self.numvert(mesh.verts, obj.getMatrix())
-                self.numsurf(mesh.faces, mesh.hasFaceUV())
-
-            self.kids(kids_dict[object])
-
-        if not self.mbuf or ADD_DEFAULT_MAT:
-            self.mbuf = DEFAULT_MAT + '\n' + self.mbuf
-            print "\nNo materials: a default (white) has been assigned.\n"
-        self.mbuf = self.mbuf + "%s\n%s %s\n" \
-                    % ('OBJECT world', 'kids', world_kids)
-        buf = "%s\n%s%s" % (header, self.mbuf, self.buf)
-
-        if filename.find('.ac', -3) <= 0: filename += '.ac'
-
-        try:
-            file = open(filename, 'w')
-        except IOError, (errno, strerror):
-            errmsg = "IOError #%s" % errno
-            errmsg = errmsg + "%t|" + strerror
-            Blender.Draw.PupMenu(errmsg)
-            return None
-        file.write(buf)
-        file.close()
-
-        print "Done. Saved to %s\n" % filename
-
-    def MATERIAL(self, mat):
-        if mat == [None]:
-            print "Notice -- object %s has no material linked to it:" % self.name
-            print "\tThe first entry in the .ac file will be used."
-            return
-        mbuf = ''
-        mlist = self.mlist
-        for m in xrange(len(mat)):
-            name = mat[m].name
-            try:
-                mlist.index(name)
-            except ValueError:
-                mlist.append(name)
-                M = Blender.Material.Get(name)
-                material = 'MATERIAL "%s"' % name
-                mirCol = "%s %s %s" % (Round(M.mirCol[0]),
-                                       Round(M.mirCol[1]), Round(M.mirCol[2]))
-                rgb = "rgb %s %s %s" % (Round(M.R), Round(M.G), Round(M.B))
-                amb = "amb %s %s %s" % (Round(M.amb), Round(M.amb), Round(M.amb))
-                if MIRCOL_AS_AMB:
-                    amb = "amb %s" % mirCol 
-                emis = "emis 0 0 0"
-                if MIRCOL_AS_EMIS:
-                    emis = "emis %s" % mirCol
-                spec = "spec %s %s %s" % (Round(M.specCol[0]),
-                                          Round(M.specCol[1]), Round(M.specCol[2]))
-                shi = "shi 72"
-                trans = "trans %s" % (Round(1 - M.alpha))
-                mbuf = mbuf + "%s %s %s %s %s %s %s\n" \
-                       % (material, rgb, amb, emis, spec, shi, trans)
-        self.mlist = mlist
-        self.mbuf = self.mbuf + mbuf
-
-    def OBJECT(self, type):
-        self.buf = self.buf + "OBJECT %s\n" % type
-
-    def name(self, name):
-        self.buf = self.buf + 'name "%s"\n' % name
-
-    def data(self, name):
-        self.buf = self.buf + 'data %s\n%s\n' % (len(name), name)
-
-    def texture(self, faces):
-        tex = []
-        for f in faces:
-            if f.image and f.image.name not in tex:
-                tex.append(f.image.name)
-        if tex:
-            if len(tex) > 1:
-                print "\nAC3Db format supports only one texture per object."
-                print "Object %s -- using only the first one: %s\n" % (self.obj.name, tex[0])
-            image = Blender.Image.Get(tex[0])
-            buf = 'texture "%s"\n' % image.filename
-            xrep = image.xrep
-            yrep = image.yrep
-            buf += 'texrep %s %s\n' % (xrep, yrep)
-            self.buf = self.buf + buf
-
-    def rot(self, matrix):
-        rot = ''
-        not_I = 0
-        for i in [0, 1, 2]:
-            r = map(Round, matrix[i])
-            not_I += (r[0] != '0.0')+(r[1] != '0.0')+(r[2] != '0.0')
-            not_I -= (r[i] == '1.0')
-            for j in [0, 1, 2]:
-                rot = "%s %s" % (rot, r[j])
-        if not_I:
-            rot = rot.strip()
-            buf = 'rot %s\n' % rot
-            self.buf = self.buf + buf
-        
-    def loc(self, loc):
-        loc = map(Round, loc)
-        if loc[0] or loc[1] or loc[2]:
-            buf = 'loc %s %s %s\n' % (loc[0], loc[1], loc[2])
-            self.buf = self.buf + buf
-
-    def numvert(self, verts, matrix):
-        buf = "numvert %s\n" % len(verts)
-        m = matrix_mul(matrix)
-        verts = transform_verts(verts, m)
-        for v in verts:
-            v = map(Round, v)
-            buf = buf + "%s %s %s\n" % (v[0], v[1], v[2])
-        self.buf = self.buf + buf
-
-    def numsurf(self, faces, hasFaceUV):
-
-        global ADD_DEFAULT_MAT
-        
-        buf = "numsurf %s\n" % len(faces)
-        
-        mlist = self.mlist
-        indexerror = 0
-        omlist = {}
-        objmats = self.mesh.materials
-        for i in range(len(objmats)):
-            objmats[i] = objmats[i].name
-        for f in faces:
-            m_idx = f.materialIndex
-            try:
-                m_idx = mlist.index(objmats[m_idx])
-            except IndexError:
-                if not indexerror:
-                    print "\nNotice: object " + self.obj.name + \
-                          " has at least one material *index* assigned"
-                    print "\tbut not defined (not linked to an existing material)."
-                    print "\tThis can cause some of its faces to be exported with a wrong color."
-                    print "\tYou can fix the problem in the Blender Edit Buttons Window (F9).\n"
-                    indexerror = 1
-                m_idx = 0
-            refs = len(f)
-            flaglow = (refs == 2) << 1
-            two_side = f.mode & Blender.NMesh.FaceModes['TWOSIDE']
-            two_side = (two_side > 0) << 1
-            flaghigh = f.smooth | two_side
-            buf = buf + "SURF 0x%d%d\n" % (flaghigh, flaglow)
-            if ADD_DEFAULT_MAT and objmats: m_idx += 1
-            buf = buf + "mat %s\n" % m_idx
-            buf = buf + "refs %s\n" % refs
-            u, v, vi = 0, 0, 0
-            for vert in f.v:
-                vindex = self.mesh.verts.index(vert)
-                if hasFaceUV:
-                    u = f.uv[vi][0]
-                    v = f.uv[vi][1]
-                    vi += 1
-                buf = buf + "%s %s %s\n" % (vindex, u, v)
-        self.buf = self.buf + buf
-
-    def kids(self, kids = 0):
-        self.buf = self.buf + "kids %s\n" % kids
+       if bsys.exists(filename) and CONFIRM_OVERWRITE:
+               if Blender.Draw.PupMenu('OVERWRITE?%t|File exists') != 1:
+                       return
 
-# End of Class AC3DExport
+       Blender.Window.WaitCursor(1)
+       starttime = bsys.time()
 
-from Blender import Draw, BGL
-
-def gui():
-  global SKIP_DATA, MIRCOL_AS_AMB, MIRCOL_AS_EMIS, ADD_DEFAULT_MAT, HELPME
-  global HELPME
-
-  if HELPME:
-    BGL.glClearColor(0.6,0.6,0.9,1)
-    BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
-    BGL.glColor3f(1,1,1)
-    BGL.glRasterPos2i(18, 150)
-    Draw.Text("AC3D is a simple, affordable commercial 3d modeller that can "
-      "be found at www.ac3d.org .")
-    BGL.glRasterPos2i(18, 130)
-    Draw.Text("It uses a nice text file format (extension .ac) which supports "
-      "uv-textured meshes")
-    BGL.glRasterPos2i(18, 110)
-    Draw.Text("with parenting (grouping) information.")
-    BGL.glRasterPos2i(18, 90)
-    Draw.Text("Notes: AC3D has a 'data' token that assigns a string to each "
-      "mesh, useful for games,")
-    BGL.glRasterPos2i(55, 70)
-    Draw.Text("for example. You can use Blender's mesh 'ME:' field for that.")
-    BGL.glRasterPos2i(55, 50)
-    Draw.Text("The .ac format is well supported by the PLib 3d gaming library.")
-    Draw.Button("Ok", 21, 285, 10, 45, 20,
-      "Click to return to previous screen.")
-  else:
-    BGL.glClearColor(0,0,1,1)
-    BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
-    BGL.glColor3f(1,1,1)
-    BGL.glRasterPos2i(20, 150)
-    Draw.Text("AC3D Exporter")
-    Draw.Toggle("Default mat", 1, 15, 100, 90, 20, ADD_DEFAULT_MAT,
-      "Objects without materials assigned get a default (white) one"
-      " automatically.")
-    Draw.Toggle("Skip data", 2, 15, 80, 90, 20, SKIP_DATA,
-      "Don't export mesh names as 'data' info.")
-    Draw.Toggle("Mir2Amb", 3, 15, 50, 90, 20, MIRCOL_AS_AMB,
-      "Get AC3D's ambient RGB color for each object from its mirror color "
-      "in Blender.")
-    Draw.Toggle("Mir2Emis", 4, 15, 30, 90, 20, MIRCOL_AS_EMIS,
-      "Get AC3D's emissive RGB color for each object from its mirror color "
-      "in Blender.")
-    Draw.Button("Export All...", 10, 140, 80, 110, 30,
-      "Export all meshes to an AC3D file.")
-    Draw.Button("Export Selected...", 11, 140, 40, 110, 30,
-      "Export selected meshes to an AC3D file.")
-    Draw.Button("HELP", 20, 285, 80, 100, 40, "Click for additional info.")
-    Draw.Button("EXIT", 22, 285, 30, 100, 40, "Click to leave.")
-
-def event(evt, val):
-  global HELPME
-
-  if not val: return
-
-  if HELPME:
-    if evt == Draw.ESCKEY:
-      HELPME = 0
-      Draw.Register(gui, event, b_event)
-      return
-    else: return
-
-  if evt == Draw.ESCKEY:
-    update_RegistryInfo()
-    Draw.Exit()
-    return
-  else: return
-
-  Draw.Register(gui, event, b_event)
-
-def b_event(evt):
-  global ARG, SKIP_DATA, MIRCOL_AS_AMB, MIRCOL_AS_EMIS, ADD_DEFAULT_MAT
-  global HELPME
-
-  if evt == 1:
-    ADD_DEFAULT_MAT = 1 - ADD_DEFAULT_MAT
-    Draw.Redraw(1)
-  elif evt == 2:
-    SKIP_DATA = 1 - SKIP_DATA
-    Draw.Redraw(1)
-  elif evt == 3:
-    MIRCOL_AS_AMB = 1 - MIRCOL_AS_AMB
-    Draw.Redraw(1)
-  elif evt == 4:
-    MIRCOL_AS_EMIS = 1 - MIRCOL_AS_EMIS
-    Draw.Redraw(1)
-  elif evt == 10:
-    ARG = 'all'
-    fname = Blender.sys.makename(ext=".ac")
-    Blender.Window.FileSelector(fs_callback, "Export AC3D", fname)
-  elif evt == 11:
-    ARG = 'sel'
-    fname = Blender.sys.makename(ext=".ac")
-    Blender.Window.FileSelector(fs_callback, "Export AC3D", fname)
-  elif evt == 20:
-    HELPME = 1 - HELPME
-    Draw.Redraw(1)
-  elif evt == 21: # leave Help screen
-    HELPME = 0
-    Draw.Register(gui, event, b_event)
-  elif evt == 22:
-    update_RegistryInfo()
-    Draw.Exit()
-  else:
-    Draw.Register(gui, event, b_event)
+       export_dir = bsys.dirname(filename)
+       if export_dir != EXPORT_DIR:
+               EXPORT_DIR = export_dir
+               update_RegistryInfo()
 
-def fs_callback(filename):
-  scene = Blender.Scene.GetCurrent()
-  test = AC3DExport(scene, filename)
+       test = AC3DExport(OBJS, filename)
+       if ERROR_MSG:
+               Blender.Draw.PupMenu(ERROR_MSG)
+               ERROR_MSG = ''
+       else:
+               endtime = bsys.time() - starttime
+               REPORT_DATA['main'].append("Data exported in %.3f seconds." % endtime)
+
+       if VERBOSE: report_data()
+       Blender.Window.WaitCursor(0)
+       return
+
+
+# -- End of definitions
+
+OBJS = Blender.Object.GetSelected()
 
-if __script__['arg'] == 'config':
-  Draw.Register(gui, event, b_event)
+if not OBJS:
+       Blender.Draw.PupMenu('ERROR: No objects selected')
 else:
-  fname = Blender.sys.makename(ext=".ac")
-  Blender.Window.FileSelector(fs_callback, "Export AC3D", fname)
+       fname = bsys.makename(ext=".ac")
+       if EXPORT_DIR:
+               fname = bsys.join(EXPORT_DIR, bsys.basename(fname))
+       FileSelector(fs_callback, "Export AC3D", fname)
index 07e8540798ac742261b4c370fb0ba5ffc89959e9..d2505022adf88a44af67b28e834cf1f042ff7e9f 100644 (file)
@@ -2,7 +2,7 @@
 
 """ Registration info for Blender menus:
 Name: 'AC3D (.ac)...'
-Blender: 232
+Blender: 236
 Group: 'Import'
 Tip: 'Import an AC3D (.ac) file.'
 """
@@ -10,40 +10,46 @@ Tip: 'Import an AC3D (.ac) file.'
 __author__ = "Willian P. Germano"
 __url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org",
        "PLib 3d gaming lib, http://plib.sf.net")
-__version__ = "2.34 07/26/04"
+__version__ = "2.36 2005-04-14"
 
 __bpydoc__ = """\
 This script imports AC3D models into Blender.
 
 AC3D is a simple and affordable commercial 3d modeller also built with OpenGL.
 The .ac file format is an easy to parse text format well supported,
-for example, by the PLib 3d gaming library (AC3D v3.x).
+for example, by the PLib 3d gaming library.
 
 Supported:<br>
     UV-textured meshes with hierarchy (grouping) information.
 
 Missing:<br>
-    Support for AC3D 4's crease tag (simple, will be added soon).
+    The url tag is irrelevant for Blender.
 
 Known issues:<br>
     None.
 
+Config Options:<br>
+    - group (toggle): if "on", grouped objects in the .ac file are parented to
+Empties.
+    - textures dir (string): if non blank, when imported texture paths are
+wrong in the .ac file, Blender will also look for them at this dir.
+
 Notes:<br>
-    Check script's source code for options that can be tweaked (using Blender
-empties to emulate AC3D's grouping info and setting a default folder for
-textures, for the case of wrong paths in the .ac file(s).
+    - when looking for assigned textures, Blender tries in order: the actual
+paths from the .ac file, the .ac file's dir and the default textures dir path
+users can configure (see config options above).
 """
 
 # $Id$
 #
 # --------------------------------------------------------------------------
-# AC3DImport version 2.34 Jul 26, 2004
-# Program versions: Blender 2.32+ and AC3Db files (means version 0xb)
-# small update to allow a default path for textures, see TEXDIR below.
+# AC3DImport version 2.36 Apr 14, 2005
+# Program versions: Blender 2.36+ and AC3Db files (means version 0xb)
+# changed: updated to use the Scripts Config Editor facilities
 # --------------------------------------------------------------------------
 # ***** BEGIN GPL LICENSE BLOCK *****
 #
-# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
+# Copyright (C) 2005: Willian P. Germano, wgermano _at_ ig.com.br
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -52,7 +58,7 @@ textures, for the case of wrong paths in the .ac file(s).
 #
 # 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
+# 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
@@ -64,394 +70,454 @@ textures, for the case of wrong paths in the .ac file(s).
 
 # Note:
 # Blender doesn't handle n-gons (polygons with more than 4 vertices):
-#   The script triangulates them, but concave polygons come out wrong and need
-#   fixing. Avoiding or triangulating concave n-gons in AC3D is a simple way to
-#   avoid problems.
+#  The script triangulates them, but concave polygons come out wrong and need
+#  fixing. Avoiding or triangulating concave n-gons in AC3D is a simple way to
+#  avoid problems.
+
+import Blender
+from Blender import Registry
+from Blender.sys import dirsep
 
 # Default folder for AC3D textures, to override wrong paths, change to your
 # liking or leave as "":
-TEXDIR = ""
+TEXTURES_DIR = ""
 
-# Set 'GROUP' to 1 to make Blender group imported objects using Empties,
+# Set 'GROUP' to True to make Blender group imported objects using Empties,
 # to reproduce the object hierarchy in the .ac file
-GROUP = 0
+GROUP = False
 
-import Blender
+tooltips = {
+       'TEXTURES_DIR': 'additional dir to look for missing textures',
+       'GROUP': 'mimick grouping information by parenting grouped meshes to empties'
+}
+
+def update_registry():
+       global GROUP, TEXTURES_DIR
+       rd = dict([('GROUP', GROUP), ('TEXTURES_DIR', TEXTURES_DIR)])
+       Registry.SetKey('ac3d_import', rd, True)
+
+rd = Registry.GetKey('ac3d_import', True)
+
+if rd:
+       TEXTURES_DIR = rd['TEXTURES_DIR']
+       GROUP = rd['GROUP']
+else: update_registry()
 
-if TEXDIR:
-    TEXDIR = TEXDIR.replace('\\','/')
-    if TEXDIR[-1] != '/': TEXDIR += '/'
+if TEXTURES_DIR:
+       oldtexdir = TEXTURES_DIR
+       if dirsep == '/': TEXTURES_DIR = TEXTURES_DIR.replace('\\', '/')
+       if TEXTURES_DIR[-1] != dirsep: TEXTURES_DIR = "%s%s" % (TEXTURES_DIR, dirsep)
+       if oldtexdir != TEXTURES_DIR: update_registry()
 
+
+VERBOSE = True
+rd = Registry.GetKey('General', True)
+if rd:
+       if rd.has_key('verbose'):
+               VERBOSE = rd['verbose']
+
+       
 errmsg = ""
 
+
+def inform(msg):
+       global VERBOSE
+       if VERBOSE: print msg
+
+
 class Obj:
-    
-    def __init__(self, type):
-        self.type = type
-        self.dad = None
-        self.name = ''
-        self.data = ''
-        self.tex = ''
-        self.texrep = [1,1]
-        self.texoff = None
-        self.loc = [0, 0, 0]
-        self.rot = []
-        self.vlist = []
-        self.flist = []
-        self.matlist = []
-        self.kids = 0
+       
+       def __init__(self, type):
+               self.type = type
+               self.dad = None
+               self.name = ''
+               self.data = ''
+               self.tex = ''
+               self.texrep = [1,1]
+               self.texoff = None
+               self.loc = [0, 0, 0]
+               self.rot = []
+               self.crease = 30
+               self.vlist = []
+               self.flist = []
+               self.matlist = []
+               self.kids = 0
 
 class AC3DImport:
 
-    def __init__(self, filename):
-
-        global errmsg
-
-        print "Trying to import AC3D model(s) from %s ..." % filename
-
-        self.i = 0
-        errmsg = ''
-        self.importdir = Blender.sys.dirname(filename)
-        try:
-            file = open(filename, 'r')
-        except IOError, (errno, strerror):
-            errmsg = "IOError #%s: %s" % (errno, strerror)
-            print errmsg
-            return None
-        header = file.read(5)
-        header, version = header[:4], header[-1]
-        if header != 'AC3D':
-            file.close()
-            errmsg = 'Invalid file -- AC3D header not found.'
-            print errmsg
-            return None
-        elif version != 'b':
-            print 'AC3D file version 0x%s.' % version
-            print 'This importer is for version 0xb, so it may fail.' 
-
-        self.token = {'OBJECT':     self.parse_obj,
-                      'numvert':    self.parse_vert,
-                      'numsurf':    self.parse_surf,
-                      'name':       self.parse_name,
-                      'data':       self.parse_data,
-                      'kids':       self.parse_kids,
-                      'loc':        self.parse_loc,
-                      'rot':        self.parse_rot,
-                      'MATERIAL':   self.parse_mat,
-                      'texture':    self.parse_tex,
-                      'texrep':     self.parse_texrep,
-                      'texoff':     self.parse_texoff}
-
-        self.objlist = []
-        self.mlist = []
-        self.dads = []
-        self.kids = []
-        self.dad = None
-
-        self.lines = file.readlines()
-        self.lines.append('')
-        self.parse_file()
-        file.close()
-        
-        self.testAC3DImport()
-                
-    def parse_obj(self, value):
-        if self.kids:
-            while not self.kids[-1]:
-                self.kids.pop()
-                self.dad = self.dad.dad
-            self.kids[-1] -= 1
-        new = Obj(value)
-        new.dad = self.dad
-        new.name = value
-        self.objlist.append(new)
-
-    def parse_kids(self, value):
-        kids = int(value)
-        if kids:
-            self.kids.append(kids)
-            self.dad = self.objlist[-1]
-        self.objlist[-1].kids = kids
-
-    def parse_name(self, value):
-        name = value.split('"')[1]
-        self.objlist[-1].name = name
-
-    def parse_data(self, value):
-        data = self.lines[self.i].strip()
-        self.objlist[-1].data = data
-
-    def parse_tex(self, value):
-        texture = value.split('"')[1]
-        self.objlist[-1].tex = texture
-
-    def parse_texrep(self, trash):
-        trep = self.lines[self.i - 1]
-        trep = trep.split()
-        trep = [float(trep[1]), float(trep[2])]
-        self.objlist[-1].texrep = trep
-        self.objlist[-1].texoff = [0, 0]
-
-    def parse_texoff(self, trash):
-        toff = self.lines[self.i - 1]
-        toff = toff.split()
-        toff = [float(toff[1]), float(toff[2])]
-        self.objlist[-1].texoff = toff
-        
-    def parse_mat(self, value):
-        i = self.i - 1
-        lines = self.lines
-        line = lines[i].split()
-        mat_name = ''
-        mat_col = mat_spec_col = [0,0,0]
-        mat_alpha = 1
-
-        while line[0] == 'MATERIAL':
-            mat_name = line[1].split('"')[1]
-            mat_col = map(float,[line[3],line[4],line[5]])
-            mat_spec_col = map(float,[line[15],line[16],line[17]])
-            mat_alpha = float(line[-1])
-            mat_alpha = 1 - mat_alpha
-            self.mlist.append([mat_name, mat_col, mat_spec_col, mat_alpha])
-            i += 1
-            line = lines[i].split()
-
-        self.i = i
-
-    def parse_rot(self, trash):
-        i = self.i - 1
-        rot = self.lines[i].split(' ', 1)[1]
-        rot = map(float, rot.split())
-        self.objlist[-1].rot = rot
-
-    def parse_loc(self, trash):
-        i = self.i - 1
-        loc = self.lines[i].split(' ', 1)[1]
-        loc = map(float, loc.split())
-        self.objlist[-1].loc = loc
-
-    def parse_vert(self, value):
-        i = self.i
-        lines = self.lines
-        obj = self.objlist[-1]
-        vlist = obj.vlist
-        n = int(value)
-
-        while n:
-            line = lines[i].split()
-            line = map(float, line)
-            vlist.append(line)
-            n -= 1
-            i += 1
-
-        self.i = i
-
-        rot = obj.rot
-        if rot:
-            nv = len(vlist)
-            for j in range(nv):
-                v = vlist[j]
-                t = [0,0,0]
-                t[0] = rot[0]*v[0] + rot[3]*v[1] + rot[6]*v[2]
-                t[1] = rot[1]*v[0] + rot[4]*v[1] + rot[7]*v[2]
-                t[2] = rot[2]*v[0] + rot[5]*v[1] + rot[8]*v[2]
-                vlist[j] = t
-
-        loc = obj.loc
-        dad = obj.dad
-        while dad:
-            for j in [0, 1, 2]:
-                loc[j] += dad.loc[j]
-            dad = dad.dad
-
-        for v in vlist:
-            for j in [0, 1, 2]:
-                v[j] += loc[j]
-
-    def parse_surf(self, value):
-        i = self.i
-        is_smooth = 0
-        double_sided = 0
-        lines = self.lines
-        obj = self.objlist[-1]
-        matlist = obj.matlist
-        numsurf = int(value)
-
-        while numsurf:
-            flags = lines[i].split()
-            flaglow = 0
-            if len(flags[1]) > 3: flaglow = int(flags[1][3])
-            flaghigh = int(flags[1][2])
-            is_smooth = flaghigh & 1
-            twoside = flaghigh & 2
-            mat = lines[i+1].split()
-            mat = int(mat[1])
-            if not mat in matlist: matlist.append(mat)
-            refs = lines[i+2].split()
-            refs = int(refs[1])
-            i += 3
-            face = []
-            faces = []
-            fuv = []
-            rfs = refs
-
-            while rfs:
-                line = lines[i].split()
-                v = int(line[0])
-                uv = [float(line[1]), float(line[2])]
-                face.append([v, uv])
-                rfs -= 1
-                i += 1
-                
-            if flaglow:
-                while len(face) >= 2:
-                    cut = face[:2]
-                    faces.append(cut)
-                    face = face[1:]
-
-                if flaglow == 1:
-                    face = [faces[-1][-1], faces[0][0]]
-                    faces.append(face)
-
-            else:
-                while len(face) > 4:
-                    cut = face[:4]
-                    face = face[3:]
-                    face.insert(0, cut[0])
-                    faces.append(cut)        
-
-                faces.append(face)
-
-            for f in faces:
-                f.append(mat)
-                f.append(is_smooth)
-                f.append(twoside)
-                self.objlist[-1].flist.append(f)
-
-            numsurf -= 1      
-
-                            
-        self.i = i
-
-    def parse_file(self):
-        i = 1
-        lines = self.lines
-        line = lines[i].split()
-
-        while line:
-            kw = ''
-            for k in self.token.keys():
-                if line[0] == k:
-                    kw = k
-                    break
-            i += 1
-            if kw:
-                self.i = i
-                self.token[kw](line[1])
-                i = self.i
-            line = lines[i].split()
-
-    def testAC3DImport(self):
-        global GROUP
-        scene = Blender.Scene.GetCurrent()
-
-        bmat = []
-        for mat in self.mlist:
-            name = mat[0]
-            m = Blender.Material.New(name)
-            m.rgbCol = (mat[1][0], mat[1][1], mat[1][2])
-            m.specCol = (mat[2][0], mat[2][1], mat[2][2])
-            m.alpha = mat[3]
-            bmat.append(m)
-
-        for obj in self.objlist:
-            if obj.type == 'world':
-                continue
-            elif obj.type == 'group':
-                if not GROUP: continue
-                empty = Blender.Object.New('Empty')
-                empty.name = obj.name
-                scene.link(empty)
-                if self.dads:
-                    dadobj = Blender.Object.get(self.dads.pop())
-                    dadobj.makeParent([empty])
-                while obj.kids:
-                    self.dads.append(empty.name)
-                    obj.kids -= 1
-                continue
-            mesh = Blender.NMesh.New()
-            if (obj.data): mesh.name = obj.data
-            mesh.hasFaceUV(1)
-
-            tex = None
-            if obj.tex != '':
-                try:
-                    tex = Blender.Image.Load(obj.tex)
-                    # Commented because it's unnecessary:
-                    #tex.xrep = int(obj.texrep[0])
-                    #tex.yrep = int(obj.texrep[1])
-                except:
-                    basetexname = Blender.sys.basename(obj.tex)
-                    try:
-                        obj.tex = self.importdir + '/' + basetexname
-                        tex = Blender.Image.Load(obj.tex)
-                    except:
-                        try:
-                            obj.tex = TEXDIR + basetexname
-                            tex = Blender.Image.Load(obj.tex)
-                        except:
-                            print "Couldn't load texture: %s" % basetexname
-
-            for v in obj.vlist:
-                bvert = Blender.NMesh.Vert(v[0],v[1],v[2])
-                mesh.verts.append(bvert)
-
-            objmat_indices = []
-            for mat in bmat:
-                if bmat.index(mat) in obj.matlist:
-                    objmat_indices.append(bmat.index(mat))
-                    mesh.materials.append(mat)
-            for f in obj.flist:
-                twoside = f[-1]
-                is_smooth = f[-2]
-                fmat = f[-3]
-                f=f[:-3]
-                bface = Blender.NMesh.Face()
-                bface.smooth = is_smooth
-                if twoside: bface.mode |= Blender.NMesh.FaceModes['TWOSIDE']
-                if tex:
-                    bface.mode |= Blender.NMesh.FaceModes['TEX']
-                    bface.image = tex
-                bface.materialIndex = objmat_indices.index(fmat)
-                if obj.texoff:
-                    uoff = obj.texoff[0]
-                    voff = obj.texoff[1]
-                    urep = obj.texrep[0]
-                    vrep = obj.texrep[1]
-                    for vi in range(len(f)):
-                        f[vi][1][0] *= urep
-                        f[vi][1][1] *= vrep
-                        f[vi][1][0] += uoff
-                        f[vi][1][1] += voff
-
-                for vi in range(len(f)):
-                    bface.v.append(mesh.verts[f[vi][0]])
-                    bface.uv.append((f[vi][1][0], f[vi][1][1]))
-                mesh.faces.append(bface)
-
-            mesh.mode = 0
-            object = Blender.NMesh.PutRaw(mesh)
-            object.setName(obj.name)
-            object.setEuler([1.5707963,0,0]) # align ac3d w/ Blender
-            if self.dads:
-                dadobj = Blender.Object.get(self.dads.pop())
-                dadobj.makeParent([object])
-
-        print '...done!'
+       def __init__(self, filename):
+
+               global errmsg
+
+               self.i = 0
+               errmsg = ''
+               self.importdir = Blender.sys.dirname(filename)
+               try:
+                       file = open(filename, 'r')
+               except IOError, (errno, strerror):
+                       errmsg = "IOError #%s: %s" % (errno, strerror)
+                       Blender.Draw.PupMenu('ERROR: %s' % errmsg)
+                       inform(errmsg)
+                       return None
+               header = file.read(5)
+               header, version = header[:4], header[-1]
+               if header != 'AC3D':
+                       file.close()
+                       errmsg = 'AC3D header not found (invalid file)'
+                       Blender.Draw.PupMenu('ERROR: %s' % errmsg)
+                       inform(errmsg)
+                       return None
+               elif version != 'b':
+                       inform('AC3D file version 0x%s.' % version)
+                       inform('This importer is for version 0xb, so it may fail.')
+
+               self.token = {'OBJECT':         self.parse_obj,
+                                         'numvert':    self.parse_vert,
+                                         'numsurf':    self.parse_surf,
+                                         'name':               self.parse_name,
+                                         'data':               self.parse_data,
+                                         'kids':               self.parse_kids,
+                                         'loc':                self.parse_loc,
+                                         'rot':                self.parse_rot,
+                                         'MATERIAL':   self.parse_mat,
+                                         'texture':    self.parse_tex,
+                                         'texrep':             self.parse_texrep,
+                                         'texoff':             self.parse_texoff,
+                                         'crease':             self.parse_crease}
+
+               self.objlist = []
+               self.mlist = []
+               self.dads = []
+               self.kids = []
+               self.dad = None
+
+               self.lines = file.readlines()
+               self.lines.append('')
+               self.parse_file()
+               file.close()
+               
+               self.testAC3DImport()
+                               
+       def parse_obj(self, value):
+               if self.kids:
+                       while not self.kids[-1]:
+                               self.kids.pop()
+                               self.dad = self.dad.dad
+                       self.kids[-1] -= 1
+               new = Obj(value)
+               new.dad = self.dad
+               new.name = value
+               self.objlist.append(new)
+
+       def parse_kids(self, value):
+               kids = int(value)
+               if kids:
+                       self.kids.append(kids)
+                       self.dad = self.objlist[-1]
+               self.objlist[-1].kids = kids
+
+       def parse_name(self, value):
+               name = value.split('"')[1]
+               self.objlist[-1].name = name
+
+       def parse_data(self, value):
+               data = self.lines[self.i].strip()
+               self.objlist[-1].data = data
+
+       def parse_tex(self, value):
+               texture = value.split('"')[1]
+               self.objlist[-1].tex = texture
+
+       def parse_texrep(self, trash):
+               trep = self.lines[self.i - 1]
+               trep = trep.split()
+               trep = [float(trep[1]), float(trep[2])]
+               self.objlist[-1].texrep = trep
+               self.objlist[-1].texoff = [0, 0]
+
+       def parse_texoff(self, trash):
+               toff = self.lines[self.i - 1]
+               toff = toff.split()
+               toff = [float(toff[1]), float(toff[2])]
+               self.objlist[-1].texoff = toff
+               
+       def parse_mat(self, value):
+               i = self.i - 1
+               lines = self.lines
+               line = lines[i].split()
+               mat_name = ''
+               mat_col = mat_amb = mat_emit = mat_spec_col = [0,0,0]
+               mat_alpha = 1
+               mat_spec = 1.0
+
+               while line[0] == 'MATERIAL':
+                       mat_name = line[1].split('"')[1]
+                       mat_col = map(float,[line[3],line[4],line[5]])
+                       v = map(float,[line[7],line[8],line[9]])
+                       mat_amb = (v[0]+v[1]+v[2]) / 3.0
+                       v = map(float,[line[11],line[12],line[13]])
+                       mat_emit = (v[0]+v[1]+v[2]) / 3.0
+                       mat_spec_col = map(float,[line[15],line[16],line[17]])
+                       mat_spec = float(line[19]) / 64.0
+                       mat_alpha = float(line[-1])
+                       mat_alpha = 1 - mat_alpha
+                       self.mlist.append([mat_name, mat_col, mat_amb, mat_emit, mat_spec_col, mat_spec, mat_alpha])
+                       i += 1
+                       line = lines[i].split()
+
+               self.i = i
+
+       def parse_rot(self, trash):
+               i = self.i - 1
+               rot = self.lines[i].split(' ', 1)[1]
+               rot = map(float, rot.split())
+               self.objlist[-1].rot = rot
+
+       def parse_loc(self, trash):
+               i = self.i - 1
+               loc = self.lines[i].split(' ', 1)[1]
+               loc = map(float, loc.split())
+               self.objlist[-1].loc = loc
+
+       def parse_crease(self, value):
+               # AC3D: range is [0.0, 180.0]; Blender: [1, 80]
+               try:
+                       value = int(value)
+               except ValueError:
+                       value = int(float(value)) # duh
+               self.objlist[-1].crease = value
+
+       def parse_vert(self, value):
+               i = self.i
+               lines = self.lines
+               obj = self.objlist[-1]
+               vlist = obj.vlist
+               n = int(value)
+
+               while n:
+                       line = lines[i].split()
+                       line = map(float, line)
+                       vlist.append(line)
+                       n -= 1
+                       i += 1
+
+               self.i = i
+
+               rot = obj.rot
+               if rot:
+                       nv = len(vlist)
+                       for j in range(nv):
+                               v = vlist[j]
+                               t = [0,0,0]
+                               t[0] = rot[0]*v[0] + rot[3]*v[1] + rot[6]*v[2]
+                               t[1] = rot[1]*v[0] + rot[4]*v[1] + rot[7]*v[2]
+                               t[2] = rot[2]*v[0] + rot[5]*v[1] + rot[8]*v[2]
+                               vlist[j] = t
+
+               loc = obj.loc
+               dad = obj.dad
+               while dad:
+                       for j in [0, 1, 2]:
+                               loc[j] += dad.loc[j]
+                       dad = dad.dad
+
+               for v in vlist:
+                       for j in [0, 1, 2]:
+                               v[j] += loc[j]
+
+       def parse_surf(self, value):
+               i = self.i
+               is_smooth = 0
+               double_sided = 0
+               lines = self.lines
+               obj = self.objlist[-1]
+               matlist = obj.matlist
+               numsurf = int(value)
+
+               while numsurf:
+                       flags = lines[i].split()
+                       flaglow = 0
+                       if len(flags[1]) > 3: flaglow = int(flags[1][3])
+                       flaghigh = int(flags[1][2])
+                       is_smooth = flaghigh & 1
+                       twoside = flaghigh & 2
+                       mat = lines[i+1].split()
+                       mat = int(mat[1])
+                       if not mat in matlist: matlist.append(mat)
+                       refs = lines[i+2].split()
+                       refs = int(refs[1])
+                       i += 3
+                       face = []
+                       faces = []
+                       fuv = []
+                       rfs = refs
+
+                       while rfs:
+                               line = lines[i].split()
+                               v = int(line[0])
+                               uv = [float(line[1]), float(line[2])]
+                               face.append([v, uv])
+                               rfs -= 1
+                               i += 1
+                               
+                       if flaglow:
+                               while len(face) >= 2:
+                                       cut = face[:2]
+                                       faces.append(cut)
+                                       face = face[1:]
+
+                               if flaglow == 1:
+                                       face = [faces[-1][-1], faces[0][0]]
+                                       faces.append(face)
+
+                       else:
+                               while len(face) > 4:
+                                       cut = face[:4]
+                                       face = face[3:]
+                                       face.insert(0, cut[0])
+                                       faces.append(cut)        
+
+                               faces.append(face)
+
+                       for f in faces:
+                               f.append(mat)
+                               f.append(is_smooth)
+                               f.append(twoside)
+                               self.objlist[-1].flist.append(f)
+
+                       numsurf -= 1      
+
+                                                       
+               self.i = i
+
+       def parse_file(self):
+               i = 1
+               lines = self.lines
+               line = lines[i].split()
+
+               while line:
+                       kw = ''
+                       for k in self.token.keys():
+                               if line[0] == k:
+                                       kw = k
+                                       break
+                       i += 1
+                       if kw:
+                               self.i = i
+                               self.token[kw](line[1])
+                               i = self.i
+                       line = lines[i].split()
+
+       def testAC3DImport(self):
+               global GROUP
+               scene = Blender.Scene.GetCurrent()
+
+               bmat = []
+               for mat in self.mlist:
+                       name = mat[0]
+                       m = Blender.Material.New(name)
+                       m.rgbCol = (mat[1][0], mat[1][1], mat[1][2])
+                       m.amb = mat[2]
+                       m.emit = mat[3]
+                       m.specCol = (mat[4][0], mat[4][1], mat[4][2])
+                       m.spec = mat[5]
+                       m.alpha = mat[6]
+                       bmat.append(m)
+
+               for obj in self.objlist:
+                       if obj.type == 'world':
+                               continue
+                       elif obj.type == 'group':
+                               if not GROUP: continue
+                               empty = Blender.Object.New('Empty')
+                               empty.name = obj.name
+                               scene.link(empty)
+                               if self.dads:
+                                       dadobj = Blender.Object.get(self.dads.pop())
+                                       dadobj.makeParent([empty])
+                               while obj.kids:
+                                       self.dads.append(empty.name)
+                                       obj.kids -= 1
+                               continue
+                       mesh = Blender.NMesh.New()
+                       if obj.data: mesh.name = obj.data
+                       mesh.setMaxSmoothAngle(obj.crease) # will clamp to [1, 80]
+                       mesh.hasFaceUV(1)
+
+                       tex = None
+                       if obj.tex != '':
+                               try:
+                                       tex = Blender.Image.Load(obj.tex)
+                                       # Commented because it's unnecessary:
+                                       #tex.xrep = int(obj.texrep[0])
+                                       #tex.yrep = int(obj.texrep[1])
+                               except:
+                                       basetexname = Blender.sys.basename(obj.tex)
+                                       try:
+                                               obj.tex = self.importdir + '/' + basetexname
+                                               tex = Blender.Image.Load(obj.tex)
+                                       except:
+                                               try:
+                                                       obj.tex = TEXTURES_DIR + basetexname
+                                                       tex = Blender.Image.Load(obj.tex)
+                                               except:
+                                                       inform("Couldn't load texture: %s" % basetexname)
+
+                       for v in obj.vlist:
+                               bvert = Blender.NMesh.Vert(v[0],v[1],v[2])
+                               mesh.verts.append(bvert)
+
+                       objmat_indices = []
+                       for mat in bmat:
+                               if bmat.index(mat) in obj.matlist:
+                                       objmat_indices.append(bmat.index(mat))
+                                       mesh.materials.append(mat)
+                       for f in obj.flist:
+                               twoside = f[-1]
+                               is_smooth = f[-2]
+                               fmat = f[-3]
+                               f=f[:-3]
+                               bface = Blender.NMesh.Face()
+                               bface.smooth = is_smooth
+                               if twoside: bface.mode |= Blender.NMesh.FaceModes['TWOSIDE']
+                               if tex:
+                                       bface.mode |= Blender.NMesh.FaceModes['TEX']
+                                       bface.image = tex
+                               bface.materialIndex = objmat_indices.index(fmat)
+                               if obj.texoff:
+                                       uoff = obj.texoff[0]
+                                       voff = obj.texoff[1]
+                                       urep = obj.texrep[0]
+                                       vrep = obj.texrep[1]
+                                       for vi in range(len(f)):
+                                               f[vi][1][0] *= urep
+                                               f[vi][1][1] *= vrep
+                                               f[vi][1][0] += uoff
+                                               f[vi][1][1] += voff
+
+                               for vi in range(len(f)):
+                                       bface.v.append(mesh.verts[f[vi][0]])
+                                       bface.uv.append((f[vi][1][0], f[vi][1][1]))
+                               mesh.faces.append(bface)
+
+                       mesh.mode = 0
+                       object = Blender.NMesh.PutRaw(mesh)
+                       object.setName(obj.name)
+                       object.setEuler([1.5707963,0,0]) # align ac3d w/ Blender
+                       if self.dads:
+                               dadobj = Blender.Object.get(self.dads.pop())
+                               dadobj.makeParent([object])
 
 # End of class AC3DImport
 
 def filesel_callback(filename):
-  test = AC3DImport(filename)
+
+       inform("Trying to import AC3D model(s) from %s ..." % filename)
+       Blender.Window.WaitCursor(1)
+       starttime = Blender.sys.time()
+       test = AC3DImport(filename)
+       Blender.Window.WaitCursor(0)
+       endtime = Blender.sys.time() - starttime
+       inform('... done!  Data imported in %.3f seconds.\n' % endtime)
 
 Blender.Window.FileSelector(filesel_callback, "Import AC3D", "*.ac")
index ededf397ef7003a339f7d55f129c0b3d4566ba56..6025cef7f0e11f1e41da9a25e143542dbb005928 100644 (file)
@@ -2,7 +2,7 @@
 
 """ Registration info for Blender menus
 Name: 'Bevel Center'
-Blender: 234
+Blender: 236
 Group: 'Mesh'
 Tip: 'Bevel selected vertices'
 """
@@ -375,6 +375,7 @@ def clear_old():
 # Interface
 #
 global dist
+NV = {}
 dist = Create(0.2)
 left = Create(0.0)
 right = Create(1.0)
@@ -392,15 +393,15 @@ def draw():
        global EVENT_NOEVENT, EVENT_BEVEL, EVENT_UPDATE, EVENT_RECURS, EVENT_EXIT
 
        glClear(GL_COLOR_BUFFER_BIT)
-       Button("Bevel",EVENT_BEVEL,10,100,280,25)
-       left=Number('',  EVENT_NOEVENT,10,70,45, 20,left.val,0,right.val,'Set the minimum of the slider')
-       right = Number("",EVENT_NOEVENT,245,70,45,20,right.val,left.val,200,"Set the maximum of the slider")
-       dist=Slider("Thickness  ",EVENT_UPDATE,60,70,180,20,dist.val,left.val,right.val,0,"Thickness of the bevel, can be changed even after bevelling")
-       glRasterPos2d(8,40)
+       Button("Bevel",EVENT_BEVEL,10,100,300,25)
+       left=Number('',  EVENT_NOEVENT,10,70,50, 20,left.val,0,right.val,'Set the minimum of the slider')
+       right = Number("",EVENT_NOEVENT,260,70,50,20,right.val,left.val,200,"Set the maximum of the slider")
+       dist=Slider("Thickness ",EVENT_UPDATE,65,70,190,20,dist.val,left.val,right.val,0,"Thickness of the bevel, can be changed even after bevelling")
+       glRasterPos2d(10,40)
        Text('To finish, you can use recursive bevel to smooth it')
-       num=Number('',  EVENT_NOEVENT,10,10,40, 16,num.val,1,100,'Recursion level')
-       Button("Recursive",EVENT_RECURS,55,10,100,16)
-       Button("Exit",EVENT_EXIT,210,10,80,20)
+       num=Number('',  EVENT_NOEVENT,10,10,50, 16,num.val,1,100,'Recursion level')
+       Button("Recursive",EVENT_RECURS,65,10,100,16)
+       Button("Exit",EVENT_EXIT,230,10,80,20)
 
 def event(evt, val):
        if ((evt == QKEY or evt == ESCKEY) and not val):
@@ -432,7 +433,11 @@ def bevel():
        is_editmode = Window.EditMode()
        if is_editmode: Window.EditMode(0)
        objects = Blender.Object.GetSelected() 
-       me = NMesh.GetRaw(objects[0].data.name)
+       bev_obj = objects[0]
+       if bev_obj.getType() != "Mesh":
+               PupMenu("ERROR: active object must be a mesh")
+               return
+       me = NMesh.GetRaw(bev_obj.getData(name_only = True))
        #
        NF = []
        NV = {}
@@ -453,7 +458,8 @@ def bevel():
 
 def bevel_update():
        """ Use NV to update the bevel """
-       global dist, old_dist
+       global dist, old_dist, NV
+       if not NV: return
        is_editmode = Window.EditMode()
        if is_editmode: Window.EditMode(0)
        fac = dist.val - old_dist
index dda491e3af9f44e8eb365fa0c65893b86f563122..70a2324d0ff27d99fa8566a8fc444e0ce4ee78d1 100644 (file)
@@ -33,5 +33,5 @@ basic_modules = [
 'chunk','colorsys','copy','copy_reg','gzip','os','random','repr','stat',
 'string','StringIO','types','UserDict','webbrowser','whrandom',
 'zlib', 'math',
-'BPyBlender'
+'BPyBlender', 'BPyRegistry'
 ]
diff --git a/release/scripts/bpymodules/BPyRegistry.py b/release/scripts/bpymodules/BPyRegistry.py
new file mode 100644 (file)
index 0000000..33e438b
--- /dev/null
@@ -0,0 +1,248 @@
+# --------------------------------------------------------------------------
+# Module BPyRegistry version 0.1
+#   Helper functions to store / restore configuration data.
+# --------------------------------------------------------------------------
+# $Id$
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
+#
+# 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.
+#
+# 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,
+# --------------------------------------------------------------------------
+
+# The Registry is a Python dictionary that is kept in Blender for as long as
+# the program is running, where scripts can store / restore persistent data
+# (data that is not lost when the script exits).  This module provides
+# functions to save and restore Registry entries as config data in the
+# bpydata/config folder.  Scripts just need to give an extra parameter to
+# the Blender.Registry.Get/Set() functions to have their data automatically
+# saved and restored when needed.
+#
+# Note: entries starting with an underscore are not saved, so script authors
+# can use that fact to define data that is not meant to be stored in a
+# config file.  Example: data to be passed to another script and references to
+# invalid data, like Blender objects and any function or method.
+#
+# Check the Blender.Registry documentation for more information.
+
+import Blender
+from Blender import Registry, sys as bsys
+
+_EXT = '.cfg' # file extension for saved config data
+
+# limits:
+MAX_ITEMS_NUM = 50 # max number of keys per dict and itens per list and tuple
+MAX_STR_LEN = 300 # max string length (remember this is just for config data)
+
+_CFG_DIR = ''
+if Blender.Get('udatadir'):
+       _CFG_DIR = Blender.sys.join(Blender.Get('udatadir'), 'config')
+if not _CFG_DIR or not bsys.exists(_CFG_DIR):
+       _CFG_DIR = Blender.sys.join(Blender.Get('datadir'), 'config')
+if not bsys.exists(_CFG_DIR):
+       _CFG_DIR = ''
+
+# to compare against, so we don't write to a cvs tree:
+_CVS_SUBPATH = 'release/scripts/bpydata/config/'
+if bsys.dirsep == '\\':
+       _CVS_SUBPATH = _CVS_SUBPATH.replace('/', '\\')
+
+_KEYS = [k for k in Registry.Keys() if k[0] != '_']
+
+def _sanitize(o):
+       "Check recursively that all objects are valid, set invalid ones to None"
+
+       global MAX_ITEMS_NUM, MAX_STR_LEN
+
+       valid_types = [int, float, bool, long, type]
+       valid_checked_types = [str, unicode]
+       # Only very simple types are considered valid for configuration data,
+  # functions, methods and Blender objects (use their names instead) aren't.
+
+       t = type(o)
+
+       if t == dict:
+               keys = o.keys()
+               if len(keys) > MAX_ITEMS_NUM:
+                       return None
+               for k in keys:
+                       o[k] = _sanitize(o[k])
+       elif t in [list, tuple]:
+               if len(o) > MAX_ITEMS_NUM:
+                       return None
+               result = []
+               for i in o: result.append(_sanitize(i))
+               return result
+       elif t in valid_types:
+               return o
+       elif t in valid_checked_types:
+               if len(o) > MAX_STR_LEN:
+                       o = o[:MAX_STR_LEN]
+               return o
+       else: return None
+
+       return o
+
+
+def _dict_to_str(name, d):
+       "Return a pretty-print version of the passed dictionary"
+
+       if name: l = ['%s = {' % name]
+       else: l = ['{']
+       keys = d.keys()
+       for k in keys:
+               if type(d[k]) == dict:
+                       l.append("'%s': %s" % (k, _dict_to_str(None, d[k])))
+               else:
+                       l.append("'%s': %s," % (k, repr(d[k])))
+       if name: l.append('}')
+       else: l.append('},')
+       return "\n".join(l)
+
+_HELP_MSG = """
+Please create a valid scripts config dir tree either by
+copying release/scripts/ tree to your <blenderhome> dir
+or by copying release/scripts/bpydata/ tree to a user
+defined scripts dir that you can set in the 
+User Preferences -> Paths tab -> Python path input box.
+"""
+
+def _check_dir():
+       global _CFG_DIR, _CVS_SUBPATH, _HELP_MSG
+
+       if not _CFG_DIR:
+               errmsg = "scripts config dir not found!\n%s" % _HELP_MSG
+               raise IOError, errmsg
+       elif _CFG_DIR.find(_CVS_SUBPATH) > 0:
+               errmsg = """
+Your scripts config dir:\n%s
+seems to reside in your local Blender's cvs tree.\n%s""" % (_CFG_DIR, _HELP_MSG)
+               raise SystemError, errmsg
+       else: return
+
+
+# API:
+
+BPY_KEY_MISSING = 0
+BPY_KEY_IN_REGISTRY = 1
+BPY_KEY_IN_FILE = 2
+
+def HasConfigData (key):
+       """
+       Check if the given key exists, either already loaded in the Registry dict or
+       as a file in the script data config dir.
+       @type key: string
+       @param key: a given key name.
+       @returns:
+               - 0: key does not exist;
+               - 1: key exists in the Registry dict only;
+               - 2: key exists as a file only;
+               - 3: key exists in the Registry dict and also as a file.
+       @note: for readability it's better to check against the constant bitmasks
+               BPY_KEY_MISSING = 0, BPY_KEY_IN_REGISTRY = 1 and BPY_KEY_IN_FILE = 2.
+       """
+
+       fname = bsys.join(_CFG_DIR, "%s%s" % (key, _EXT))
+
+       result = BPY_KEY_MISSING
+       if key in Registry.Keys(): result |= BPY_KEY_IN_REGISTRY
+       if bsys.exists(fname): result |= BPY_KEY_IN_FILE
+
+       return result
+
+
+def LoadConfigData (key = None):
+       """
+       Load config data from file(s) to the Registry dictionary.
+       @type key: string
+       @param key: a given key name.  If None (default), all available keys are
+               loaded.
+       @returns: None
+       """
+
+       _check_dir()
+
+       import os
+
+       if not key:
+               files = \
+                       [bsys.join(_CFG_DIR, f) for f in os.listdir(_CFG_DIR) if f[-4:] == _EXT]
+       else:
+               files = []
+               fname = bsys.join(_CFG_DIR, "%s%s" % (key, _EXT))
+               if bsys.exists(fname): files.append(fname)
+
+       for p in files:
+               f = file(p, 'r')
+               lines = f.readlines()
+               f.close()
+               mainkey = lines[0].split('=')[0].strip()
+               pysrc = "\n".join(lines)
+               exec(pysrc)
+               exec("Registry.SetKey('%s', %s)" % (str(mainkey), mainkey))
+
+
+def RemoveConfigData (key = None):
+       """
+       Remove this key's config file from the <(u)datadir>/config/ folder.
+       @type key: string
+       @param key: the name of the key to be removed.  If None (default) all
+               available config files are deleted.
+       """
+
+       _check_dir()
+
+       if not key:
+               files = \
+                       [bsys.join(_CFG_DIR, f) for f in os.listdir(_CFG_DIR) if f[-4:] == _EXT]
+       else:
+               files = []
+               fname = bsys.join(_CFG_DIR, "%s%s" % (key, _EXT))
+               if bsys.exists(fname): files.append(fname)
+
+       import os
+
+       for p in files:
+               os.remove(p) # remove the file(s)
+
+
+def SaveConfigData (key = None):
+       """
+       Save Registry key(s) as file(s) in the <(u)datadir>/config/ folder.
+       @type key: string
+       @param key: the name of the key to be saved.  If None (default) all
+               available keys are saved.
+       """
+
+       global _KEYS, _CFG_DIR
+
+       _check_dir()
+
+       if key: keys = [key]
+       else: keys = _KEYS
+
+       for mainkey in keys:
+               cfgdict = Registry.GetKey(mainkey).copy()
+               for k in cfgdict.keys():
+                       if k[0] == '_': cfgdict.pop(k)
+
+               if not cfgdict: continue
+
+               filename = bsys.join(_CFG_DIR, "%s%s" % (mainkey, _EXT))
+               f = file(filename, 'w')
+               output = _dict_to_str(mainkey, _sanitize(cfgdict))
+               f.write(output)
+               f.close()
+
diff --git a/release/scripts/config.py b/release/scripts/config.py
new file mode 100644 (file)
index 0000000..ea91d5e
--- /dev/null
@@ -0,0 +1,792 @@
+#!BPY
+
+"""
+Name: 'Scripts Config Editor'
+Blender: 236
+Group: 'System'
+Tooltip: 'View and edit available scripts configuration data'
+"""
+
+__author__ = "Willian P. Germano"
+__version__ = "0.1 2005/04/14"
+__email__ = ('scripts', 'Author, wgermano:ig*com*br')
+__url__ = ('blender', 'elysiun')
+
+__bpydoc__ ="""\
+This script can be used to view and edit configuration data stored
+by other scripts.
+
+Technical: this data is saved as dictionary keys with the
+Blender.Registry module functions.  It is persistent while Blender is
+running and, if the script's author chose to, is also saved to a file
+in the scripts config data dir.
+
+Usage:
+
+- Start Screen:
+
+To access any available key, select it from (one of) the menu(s).
+
+Hotkeys:<br>
+   ESC or Q: [Q]uit<br>
+   H: [H]elp
+
+- Keys Config Screen:
+
+This screen exposes the configuration data for the chosen script key.  If the
+buttons don't fit completely on the screen, you can scroll up or down with
+arrow keys or a mouse wheel.  Leave the mouse pointer over any button to get
+a tooltip about that option.
+
+Any change can be reverted -- unless you have already applied it.
+
+If the key is already stored in a config file, there will be a toggle button
+(called 'file') that controls whether the changes will be written back to
+the file or not.  If you just want to change the configuration for the current
+session, simply unset that button.  Note, though, that data from files has
+precedence over those keys already loaded in Blender, so if you re-run this
+config editor, unsaved changes will not be seen.
+
+Hotkeys:<br>
+   ESC: back to Start Screen<br>
+   Q: [Q]uit<br>
+   U: [U]ndo changes<br>
+   ENTER: apply changes (can't be reverted, then)<br>
+   UP, DOWN Arrows and mouse wheel: scroll text up / down
+
+Notes:
+
+a) Available keys are determined by which scripts you use.  If the key you
+expect isn't available (or maybe there are none or too few keys), either the
+related script doesn't need or still doesn't support this feature or the key
+has not been stored yet, in which case you just need to run that script once
+to make its config data available.
+
+b) There are two places where config data files can be saved: the
+bpydata/config/ dir (1) inside the default scripts dir or (2) inside the user
+defined Python scripts dir
+(User Preferences window -> File Paths tab -> Python path).  If available,
+(2) is the default and also the recommended option, because then fresh Blender
+installations won't delete your config data.  To use this option, simply set a
+dir for Python scripts at the User Preferences window and make sure this dir
+has the subdirs bpydata/ and bpydata/config/ inside it.
+
+c) The key called "General" in the "Other" menu has general config options.
+All scripts where that data is relevant are recommended to access it and set
+behaviors accordingly.
+"""
+
+# $Id$
+#
+# --------------------------------------------------------------------------
+# config.py version 0.1 2005/04/08
+# --------------------------------------------------------------------------
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
+#
+# 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.
+#
+# 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.
+#
+# ***** END GPL LICENCE BLOCK *****
+# --------------------------------------------------------------------------
+
+import Blender
+from Blender import Draw, BGL, Registry, Window, sys as bsys
+from Blender.Window import Theme
+from BPyRegistry import LoadConfigData, SaveConfigData, HasConfigData,\
+       BPY_KEY_IN_FILE, MAX_STR_LEN, MAX_ITEMS_NUM
+
+# ---
+# The "General" configure options key is managed from this script.
+verbose = True
+confirm_overwrite = True
+
+tooltips = {
+       'verbose': 'print script messages (info, warnings, errors) to the console',
+       'confirm_overwrite': 'scripts should always confirm before overwriting files'
+}
+
+CFG_LIST = ['verbose', 'confirm_overwrite', 'tooltips']
+KEY_NAME = 'General'
+
+def update_registry():
+       rd = {}
+       for var in CFG_LIST:
+               exec("rd['%s']=%s" % (var, var))
+       Registry.SetKey(KEY_NAME, rd, True)
+
+rd = Registry.GetKey('General', True)
+if rd:
+       try:
+               for var in CFG_LIST[:-1]: # no need to update tooltips
+                       exec("%s=rd['%s']" % (var, var))
+       except: update_registry()
+
+else:
+       update_registry()
+# ---
+
+# script globals:
+CFGKEY = ''
+LABELS = []
+GD = {} # groups dict (includes "Other" for unmapped keys)
+INDEX = 0 # to pass button indices to fs callbacks
+FREEKEY_IDX = 0 # index of set of keys not mapped to a script name
+KEYMENUS = []
+ALL_SCRIPTS = {}
+ALL_GROUPS = []
+START_SCREEN  = 0
+CONFIG_SCREEN = 1
+DISK_UPDATE = True # write changed data to its config file
+
+ACCEPTED_TYPES = [bool, int, float, str, unicode]
+
+SCREEN = START_SCREEN
+
+SCROLL_DOWN = 0
+
+# events:
+BEVT_START = 50
+BEVT_EXIT = 0 + BEVT_START
+BEVT_BACK = 1 + BEVT_START
+BEVT_DISK = 2 + BEVT_START
+BEVT_CANCEL = 3 + BEVT_START
+BEVT_APPLY = 4 + BEVT_START
+BEVT_HELP = 5 + BEVT_START
+BEVT_DEL = 6 + BEVT_START
+BEVT_KEYMENU = []
+BUT_KEYMENU = []
+BEVT_BOOL = 100
+BEVT_INT = BEVT_BOOL + MAX_ITEMS_NUM
+BEVT_FLOAT = BEVT_BOOL + 2*MAX_ITEMS_NUM
+BEVT_STR = BEVT_BOOL + 3*MAX_ITEMS_NUM
+BEVT_BROWSEDIR = BEVT_BOOL + 4*MAX_ITEMS_NUM
+BEVT_BROWSEFILE = BEVT_BOOL + 5*MAX_ITEMS_NUM
+BUT_TYPES = {
+       bool: 0,
+       int: 0,
+       float: 0,
+       str: 0
+}
+
+# Function definitions:
+
+def get_keys():
+       LoadConfigData() # loads all data from files in (u)scripts/bpydata/config/
+       return [k for k in Registry.Keys() if k[0] != "_"]
+
+
+def show_help(script = 'config.py'):
+       Blender.ShowHelp(script)
+
+
+def fs_dir_callback(pathname):
+       global CFGKEY, INDEX
+
+       pathname = bsys.dirname(pathname)
+       datatypes = CFGKEY.sorteddata
+       datatypes[str][INDEX][1] = pathname
+
+
+def fs_file_callback(pathname):
+       global CFGKEY, INDEX
+
+       datatypes = CFGKEY.sorteddata
+       datatypes[str][INDEX][1] = pathname
+
+
+# parse Bpymenus file to get all script filenames
+# (used to show help for a given key)
+def fill_scripts_dict():
+       global ALL_SCRIPTS, ALL_GROUPS
+
+       group = ''
+       group_len = 0
+       sep = bsys.sep
+       home = Blender.Get('homedir')
+       if not home:
+               errmsg = """
+Can't find Blender's home dir and so can't find the
+Bpymenus file automatically stored inside it, which
+is needed by this script.  Please run the
+Help -> System -> System Information script to get
+information about how to fix this.
+"""
+               raise SystemError, errmsg
+       fname = bsys.join(home, 'Bpymenus')
+       if not bsys.exists(fname): return False
+       f = file(fname, 'r')
+       lines = f.readlines()
+       f.close()
+       for l in lines:
+               if l.rfind('{') > 0:
+                       group = l.split()[0]
+                       ALL_GROUPS.append(group)
+                       group_len += 1
+                       continue
+               elif l[0] != "'": continue
+               fields = l.split("'")
+               if len(fields) > 2:
+                       menuname = fields[1].replace('...','')
+                       fields = fields[2].split()
+                       if len(fields) > 1:
+                               fname = fields[1].split(sep)[-1]
+                               ALL_SCRIPTS[fname] = (menuname, group_len - 1)
+       return True
+
+
+def map_to_registered_script(name):
+       global ALL_SCRIPTS
+
+       if not name.endswith('.py'):
+               name = "%s.py" % name
+       if ALL_SCRIPTS.has_key(name):
+               return ALL_SCRIPTS[name] # == (menuname, group index)
+       return None
+
+
+def reset():
+       global LABELS, GD, KEYMENUS, KEYS
+
+       # init_data is recalled when a key is deleted, so:
+       LABELS = []
+       GD = {}
+       KEYMENUS = []
+       KEYS = get_keys()
+
+
+# gather all script info, fill gui menus
+def init_data():
+       global KEYS, GD, ALL_GROUPS, ALL_SCRIPTS, KEYMENUS, LABELS
+       global BUT_KEYMENU, BEVT_KEYMENU, FREEKEY_IDX
+
+       for k in ALL_GROUPS:
+               GD[k] = []
+       GD[None] = []
+
+       for k in KEYS:
+               res = map_to_registered_script(k)
+               if res:
+                       GD[ALL_GROUPS[res[1]]].append((k, res[0]))
+               else: GD[None].append((k, k))
+
+       for k in GD.keys():
+               if not GD[k]: GD.pop(k)
+
+       if GD.has_key(None):
+               GD['Other'] = GD[None]
+               GD.pop(None)
+               FREEKEY_IDX = -1
+
+       BUT_KEYMENU = range(len(GD))
+
+       for k in GD.keys():
+               kmenu = ['Configuration Keys: %s%%t' % k]
+               for j in GD[k]:
+                       kmenu.append(j[1])
+               kmenu = "|".join(kmenu)
+               KEYMENUS.append(kmenu)
+               LABELS.append(k)
+
+       if FREEKEY_IDX < 0:
+               FREEKEY_IDX = LABELS.index('Other')
+
+       length = len(KEYMENUS)
+       BEVT_KEYMENU = range(1, length + 1)
+       BUT_KEYMENU = range(length)
+
+
+# for theme colors:
+def float_colors(cols):
+       return map(lambda x: x / 255.0, cols)
+
+
+
+class Config:
+
+       def __init__(self, key, has_group = True):
+               global DISK_UPDATE
+
+               self.key = key
+               self.has_group = has_group
+               self.name = key
+               self.fromdisk = HasConfigData(key) & BPY_KEY_IN_FILE
+               if not self.fromdisk: DISK_UPDATE = False
+               else: DISK_UPDATE = True
+
+               self.origdata = Registry.GetKey(key, True)
+               data = self.data = self.origdata.copy()
+
+               if not data:
+                       Draw.PupMenu('ERROR: couldn\'t find requested data')
+                       self.data = None
+                       return
+
+               keys = data.keys()
+               nd = {}
+               for k in keys:
+                       nd[k.lower()] = k
+
+               if nd.has_key('tooltips'):
+                       ndval = nd['tooltips']
+                       self.tips = data[ndval]
+                       data.pop(ndval)
+               else: self.tips = 0
+
+               if nd.has_key('limits'):
+                       ndval = nd['limits']
+                       self.limits = data[ndval]
+                       data.pop(ndval)
+               else: self.limits = 0
+
+               if self.has_group:
+                       scriptname = key
+                       if not scriptname.endswith('.py'):
+                               scriptname = "%s.py" % scriptname
+               elif nd.has_key('script'):
+                               ndval = nd['script']
+                               scriptname = data[ndval]
+                               data.pop(ndval)
+                               if not scriptname.endswith('.py'):
+                                       scriptname = "%s.py" % scriptname
+               else: scriptname = None
+
+               self.scriptname = scriptname
+
+               self.sort()
+
+
+       def needs_update(self): # check if user changed data
+               data = self.data
+               new = self.sorteddata
+
+               for vartype in new.keys():
+                       for i in new[vartype]:
+                               if data[i[0]] != i[1]: return 1
+
+               return 0 # no changes
+
+
+       def update(self): # update original key
+               global DISK_UPDATE
+
+               data = self.data
+               odata = self.origdata
+               new = self.sorteddata
+               for vartype in new.keys():
+                       for i in new[vartype]:
+                               if data[i[0]] != i[1]: data[i[0]] = i[1]
+                               if odata[i[0]] != i[1]: odata[i[0]] = i[1]
+
+               if DISK_UPDATE: Registry.SetKey(self.key, odata, True)
+
+       def delete(self):
+               global DISK_UPDATE
+
+               delmsg = 'OK?%t|Delete key from memory'
+               if DISK_UPDATE:
+                       delmsg = "%s and from disk" % delmsg
+               if Draw.PupMenu(delmsg) == 1:
+                       Registry.RemoveKey(self.key, DISK_UPDATE)
+                       return True
+
+               return False
+
+
+       def revert(self): # revert to original key
+               data = self.data
+               new = self.sorteddata
+               for vartype in new.keys():
+                       for i in new[vartype]:
+                               if data[i[0]] != i[1]: i[1] = data[i[0]]
+
+
+       def sort(self): # create a new dict with types as keys
+               global ACCEPTED_TYPES, BUT_TYPES
+
+               data = self.data
+               datatypes = {}
+               keys = [k for k in data.keys() if k[0] != '_']
+               for k in keys:
+                       val = data[k]
+                       tval = type(val)
+                       if tval not in ACCEPTED_TYPES: continue
+                       if not datatypes.has_key(tval):
+                               datatypes[tval] = []
+                       datatypes[type(val)].append([k, val])
+               if datatypes.has_key(unicode):
+                       if not datatypes.has_key(str): datatypes[str] = datatypes[unicode]
+                       else:
+                               for i in datatypes[unicode]: datatypes[str].append(i)
+                       datatypes.pop(unicode)
+               for k in datatypes.keys():
+                       dk = datatypes[k]
+                       dk.sort()
+                       dk.reverse()
+                       BUT_TYPES[k] = range(len(dk))
+               self.sorteddata = datatypes
+
+
+# GUI:
+
+# gui callbacks:
+
+def gui(): # drawing the screen
+
+       global SCREEN, START_SCREEN, CONFIG_SCREEN, KEYMENUS, LABELS
+       global BEVT_KEYMENU, BUT_KEYMENU, CFGKEY
+       global BUT_TYPES, SCROLL_DOWN, VARS_NUM
+
+       WIDTH, HEIGHT = Window.GetAreaSize()
+
+       theme = Theme.Get()[0]
+       tui = theme.get('ui')
+       ttxt = theme.get('text')
+
+       COL_BG = float_colors(ttxt.back)
+       COL_TXT = ttxt.text
+       COL_TXTHI = ttxt.text_hi
+
+       BGL.glClearColor(COL_BG[0],COL_BG[1],COL_BG[2],COL_BG[3])
+       BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
+       BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
+
+       if SCREEN == START_SCREEN:
+               x = 10
+               y = 10
+               h = 20
+               w = 90
+               BGL.glRasterPos2i(x, y)
+               Draw.Text('Select a configuration key to access it.  Press Q or ESC to leave.')
+               km_len = len(KEYMENUS)
+               km_columns = (WIDTH - x) / w
+               if km_columns == 0: km_rows = km_len
+               else:
+                       km_rows = km_len / km_columns
+                       if (km_len % km_columns): km_rows += 1
+               if km_rows == 0: km_rows = 1
+               ystart = y + 2*h*km_rows
+               if ystart > (HEIGHT - 70): ystart = HEIGHT - 70
+               y = ystart
+               column = 1
+               for i, km in enumerate(KEYMENUS):
+                       column += 1
+                       BGL.glRasterPos2i(x + 2, y + h + 5)
+                       Draw.Text(LABELS[i])
+                       BUT_KEYMENU[i] = Draw.Menu(km, BEVT_KEYMENU[i],
+                               x, y, w - 10, h, 0, 'Choose a key to access its configuration data')
+                       if column > km_columns:
+                               column = 1
+                               y -= 2*h
+                               if y < 35: break
+                               x = 10
+                       else: x += w
+               x = 10
+               y = 50 + ystart
+               BGL.glColor3ub(COL_TXTHI[0], COL_TXTHI[1], COL_TXTHI[2])
+               BGL.glRasterPos2i(x, y)
+               Draw.Text('Scripts Configuration Editor')
+               Draw.PushButton('help', BEVT_HELP, x, 22, 45, 16,
+                       'View help information about this script (hotkey: H)')
+
+       elif SCREEN == CONFIG_SCREEN:
+               x = y = 10
+               h = 18
+               data = CFGKEY.sorteddata
+               tips = CFGKEY.tips
+               fromdisk = CFGKEY.fromdisk
+               limits = CFGKEY.limits
+               VARS_NUM = 0
+               for k in data.keys():
+                       VARS_NUM += len(data[k])
+               lines = VARS_NUM + 5 # to account for header and footer
+               y = lines*h
+               if y > HEIGHT - 20: y = HEIGHT - 20
+               BGL.glColor3ub(COL_TXTHI[0],COL_TXTHI[1], COL_TXTHI[2])
+               BGL.glRasterPos2i(x, y)
+               Draw.Text('Scripts Configuration Editor')
+               y -= 20
+               BGL.glColor3ub(COL_TXT[0],COL_TXT[1], COL_TXT[2])
+               txtsize = 10
+               if HEIGHT < lines*h:
+                       BGL.glRasterPos2i(10, 5)
+                       txtsize += Draw.Text('Arrow keys or mouse wheel to scroll, ')
+               BGL.glRasterPos2i(txtsize, 5)
+               Draw.Text('Q or ESC to return.')
+               BGL.glRasterPos2i(x, y)
+               Draw.Text('Key: "%s"' % CFGKEY.name)
+               bh = 16
+               bw = 45
+               by = 16
+               i = -1
+               if CFGKEY.scriptname:
+                       i = 0
+                       Draw.PushButton('help', BEVT_HELP, x, by, bw, bh,
+                               'Show documentation for the script that owns this key (hotkey: H)')
+               Draw.PushButton('back', BEVT_BACK, x + (1+i)*bw, by, bw, bh,
+                       'Back to config keys selection screen (hotkey: ESC)')
+               Draw.PushButton('exit', BEVT_EXIT, x + (2+i)*bw, by, bw, bh,
+                       'Exit from Scripts Config Editor (hotkey: Q)')
+               Draw.PushButton('revert', BEVT_CANCEL, x + (3+i)*bw, by, bw, bh,
+                       'Revert data to original values (hotkey: U)')
+               Draw.PushButton('apply', BEVT_APPLY, x + (4+i)*bw, by, bw, bh,
+                       'Apply changes, if any (hotkey: ENTER)')
+               delmsg = 'Delete this data key from memory'
+               if fromdisk: delmsg = "%s and from disk" % delmsg
+               Draw.PushButton('delete', BEVT_DEL, x + (5+i)*bw, by, bw, bh,
+                       '%s (hotkey: DELETE)' % delmsg)
+               if fromdisk:
+                       Draw.Toggle("file", BEVT_DISK, x + 3 + (6+i)*bw, by, bw, bh, DISK_UPDATE,
+                               'Update also the file where this config key is stored')
+               i = -1
+               top = -1
+               y -= 20
+               yend = 30
+               if data.has_key(bool) and y > 0:
+                       lst = data[bool]
+                       for l in lst:
+                               top += 1
+                               i += 1
+                               if top < SCROLL_DOWN: continue
+                               y -= h
+                               if y < yend: break
+                               w = 20
+                               tog = data[bool][i][1]
+                               if tips and tips.has_key(l[0]): tooltip = tips[l[0]]
+                               else: tooltip = "click to toggle"
+                               BUT_TYPES[bool][i] = Draw.Toggle("", BEVT_BOOL + i,
+                                       x, y, w, h, tog, tooltip)
+                               BGL.glRasterPos2i(x + w + 3, y + 5)
+                               Draw.Text(l[0].lower().replace('_', ' '))
+                       i = -1
+                       y -= 5
+               if data.has_key(int) and y > 0:
+                       lst = data[int]
+                       for l in lst:
+                               w = 70
+                               top += 1
+                               i += 1
+                               if top < SCROLL_DOWN: continue
+                               y -= h
+                               if y < yend: break
+                               val = data[int][i][1]
+                               if limits: min, max = limits[l[0]]
+                               else: min, max = 0, 10
+                               if tips and tips.has_key(l[0]): tooltip = tips[l[0]]
+                               else: tooltip = "click / drag to change"
+                               BUT_TYPES[int][i] = Draw.Number("", BEVT_INT + i,
+                                       x, y, w, h, val, min, max, tooltip)
+                               BGL.glRasterPos2i(x + w + 3, y + 3)
+                               Draw.Text(l[0].lower().replace('_', ' '))
+                       i = -1
+                       y -= 5
+               if data.has_key(float) and y > 0:
+                       lst = data[float]
+                       for l in lst:
+                               w = 70
+                               top += 1
+                               i += 1
+                               if top < SCROLL_DOWN: continue
+                               y -= h
+                               if y < yend: break
+                               val = data[float][i][1]
+                               if limits: min, max = limits[l[0]]
+                               else: min, max = 0.0, 1.0
+                               if tips and tips.has_key(l[0]): tooltip = tips[l[0]]
+                               else: tooltip = "click and drag to change"
+                               BUT_TYPES[float][i] = Draw.Number("", BEVT_FLOAT + i,
+                                       x, y, w, h, val, min, max, tooltip)
+                               BGL.glRasterPos2i(x + w + 3, y + 3)
+                               Draw.Text(l[0].lower().replace('_', ' '))
+                       i = -1
+                       y -= 5
+               if data.has_key(str) and y > 0:
+                       lst = data[str]
+                       for l in lst:
+                               top += 1
+                               i += 1
+                               if top < SCROLL_DOWN: continue
+                               y -= h
+                               if y < yend: break
+                               name = l[0].lower()
+                               is_dir = is_file = False
+                               if name.find('_dir', -4) > 0:   is_dir = True
+                               elif name.find('_file', -5) > 0: is_file = True
+                               w = WIDTH - 20
+                               wbrowse = 50
+                               if is_dir and w > wbrowse: w -= wbrowse
+                               if tips and tips.has_key(l[0]): tooltip = tips[l[0]]
+                               else: tooltip = "click to write a new string"
+                               name = name.replace('_',' ') + ': '
+                               BUT_TYPES[str][i] = Draw.String(name, BEVT_STR + i,
+                                       x, y, w, h, l[1], MAX_STR_LEN, tooltip)
+                               if is_dir:
+                                       Draw.PushButton('browse', BEVT_BROWSEDIR + i, x+w+1, y, wbrowse, h,
+                                               'click to open a file selector (pick any file in the desired dir)')
+                               elif is_file:
+                                       Draw.PushButton('browse', BEVT_BROWSEFILE + i, x + w + 1, y, 50, h,
+                                               'click to open a file selector')
+
+
+def fit_scroll():
+       global SCROLL_DOWN, VARS_NUM
+       max = VARS_NUM - 1 # so last item is always visible
+       if SCROLL_DOWN > max:
+               SCROLL_DOWN = max
+       elif SCROLL_DOWN < 0:
+               SCROLL_DOWN = 0
+
+
+def event(evt, val): # input events
+
+       global SCREEN, START_SCREEN, CONFIG_SCREEN
+       global SCROLL_DOWN, CFGKEY
+
+       if not val: return
+
+       if evt == Draw.ESCKEY:
+               if SCREEN == START_SCREEN: Draw.Exit()
+               else:
+                       if CFGKEY.needs_update():
+                               if Draw.PupMenu('UPDATE?%t|Data was changed') == 1:
+                                       CFGKEY.update()
+                       SCREEN = START_SCREEN
+                       SCROLL_DOWN = 0
+                       Draw.Redraw()
+               return
+       elif evt == Draw.QKEY:
+               if SCREEN == CONFIG_SCREEN and CFGKEY.needs_update():
+                       if Draw.PupMenu('UPDATE?%t|Data was changed') == 1:
+                               CFGKEY.update()
+               Draw.Exit()
+               return
+       elif evt == Draw.HKEY:
+               if SCREEN == START_SCREEN: show_help()
+               elif CFGKEY.scriptname: show_help(CFGKEY.scriptname)
+               return
+
+       elif SCREEN == CONFIG_SCREEN:
+               if evt in [Draw.DOWNARROWKEY, Draw.WHEELDOWNMOUSE]:
+                       SCROLL_DOWN += 1
+                       fit_scroll()
+               elif evt in [Draw.UPARROWKEY, Draw.WHEELUPMOUSE]:
+                       SCROLL_DOWN -= 1
+                       fit_scroll()
+               elif evt == Draw.UKEY:
+                       if CFGKEY.needs_update():
+                               CFGKEY.revert()
+               elif evt == Draw.RETKEY or evt == Draw.PADENTER:
+                       if CFGKEY.needs_update():
+                               CFGKEY.update()
+               elif evt == Draw.DELKEY:
+                       if CFGKEY.delete():
+                               reset()
+                               init_data()
+                               SCREEN = START_SCREEN
+                               SCROLL_DOWN = 0
+               else: return
+               Draw.Redraw()
+
+
+def button_event(evt): # gui button events
+
+       global SCREEN, START_SCREEN, CONFIG_SCREEN, CFGKEY, DISK_UPDATE
+       global BEVT_KEYMENU, BUT_KEYMENU, BUT_TYPES, SCROLL_DOWN, GD, INDEX
+       global BEVT_EXIT, BEVT_BACK, BEVT_APPLY, BEVT_CANCEL, BEVT_HELP, FREEKEY_IDX
+
+       if SCREEN == START_SCREEN:
+               for e in BEVT_KEYMENU:
+                       if evt == e:
+                               index = e - 1
+                               k = BUT_KEYMENU[index].val - 1
+                               CFGKEY = Config(GD[LABELS[index]][k][0], index != FREEKEY_IDX)
+                               if CFGKEY.data:
+                                       SCREEN = CONFIG_SCREEN
+                                       Draw.Redraw()
+                                       return
+               if evt == BEVT_EXIT:
+                       Draw.Exit()
+               elif evt == BEVT_HELP:
+                       show_help()
+               return
+
+       elif SCREEN == CONFIG_SCREEN:
+               datatypes = CFGKEY.sorteddata
+               if evt >= BEVT_BROWSEFILE:
+                       INDEX = evt - BEVT_BROWSEFILE
+                       Window.FileSelector(fs_file_callback, 'Choose file')
+               elif evt >= BEVT_BROWSEDIR:
+                       INDEX = evt - BEVT_BROWSEDIR
+                       Window.FileSelector(fs_dir_callback, 'Choose any file')
+               elif evt >= BEVT_STR:
+                       var = BUT_TYPES[str][evt - BEVT_STR].val
+                       datatypes[str][evt - BEVT_STR][1] = var
+               elif evt >= BEVT_FLOAT:
+                       var = BUT_TYPES[float][evt - BEVT_FLOAT].val
+                       datatypes[float][evt - BEVT_FLOAT][1] = var
+               elif evt >= BEVT_INT:
+                       var = BUT_TYPES[int][evt - BEVT_INT].val
+                       datatypes[int][evt - BEVT_INT][1] = var
+               elif evt >= BEVT_BOOL:
+                       var = datatypes[bool][evt - BEVT_BOOL][1]
+                       if var == True: var = False
+                       else: var = True
+                       datatypes[bool][evt - BEVT_BOOL][1] = var
+
+               elif evt == BEVT_BACK:
+                       if SCREEN == CONFIG_SCREEN:
+                               SCREEN = START_SCREEN
+                               SCROLL_DOWN = 0
+                               Draw.Redraw()
+               elif evt == BEVT_EXIT:
+                       if CFGKEY.needs_update():
+                               if Draw.PupMenu('UPDATE?%t|Data was changed') == 1:
+                                       CFGKEY.update()
+                       Draw.Exit()
+                       return
+               elif evt == BEVT_APPLY:
+                       if CFGKEY.needs_update():
+                               CFGKEY.update()
+               elif evt == BEVT_CANCEL:
+                       if CFGKEY.needs_update():
+                               CFGKEY.revert()
+               elif evt == BEVT_DEL:
+                       if CFGKEY.delete():
+                               reset()
+                               init_data()
+                               SCREEN = START_SCREEN
+                               SCROLL_DOWN = 0
+               elif evt == BEVT_DISK:
+                       if DISK_UPDATE: DISK_UPDATE = False
+                       else: DISK_UPDATE = True
+               elif evt == BEVT_HELP:
+                       show_help(CFGKEY.scriptname)
+                       return
+               else:
+                       return
+       Draw.Redraw()
+
+# End of definitions
+
+
+KEYS = get_keys()
+
+if not KEYS:
+       Draw.PupMenu("NO DATA: please read this help screen")
+       Blender.ShowHelp('config.py')
+else:
+       fill_scripts_dict()
+       init_data()
+       Draw.Register(gui, event, button_event)
index 01a6da8cc98295c2a065159442b4dd8bf03ddac2..e7628a00cae85851bf92dd7a8aa4712288671f7c 100644 (file)
@@ -1,7 +1,7 @@
 #!BPY
 
 """ Registration info for Blender menus: <- these words are ignored
-Name: 'dispaint'
+Name: 'Dispaint'
 Blender: 233
 Group: 'Mesh'
 Tip: 'use vertex paint color value to modify shape displacing vertices along normal'
@@ -126,6 +126,9 @@ E_NOISEBAS = 54
 E_NOISEVAL=[E_NOISEH,E_NOISELAC,E_NOISEOCT,E_NOISEOFF,E_NOISEBAS]
 E_NOISEDIM = 55
 
+ExitTIP="Exit from this script session "
+CreateTIP="Create a new copy of the selected shape"
+ActionTIP="Do the current selected actions"
 
 
 def copy_transform(ozero,Obis):
@@ -241,7 +244,8 @@ def paint():
                           traite_face(f) 
                      else:
                           traite_face(f)
-                   Me[0].link(me)
+                   #Me[0].link(me)
+                   me.update()
                    Me[0].makeDisplayList()
                except:
                   ERROR=2
@@ -354,7 +358,7 @@ def draw():
     global mat, ORIName, NEWName, ORIENTMenu 
     global NRepeat, ERROR, TextERROR , NOISE, NOISEMenu, NOISEDIMbout,NOISEDIM
     global HBout,lacunarityBout,octavesBout,offsetBout,basisBout
-    global noiseTYPE
+    global noiseTYPE, ExitTIP, CreateTIP, ActionTIP
     
     size=Buffer(GL_FLOAT, 4)
     glGetFloatv(GL_SCISSOR_BOX, size)
@@ -378,9 +382,9 @@ def draw():
     n0=70
     n1=55
 
-    Button("Create"                ,E_CREATE  ,5  ,size[3]-n0+16  ,60 ,20)
-    Button("Action"                ,E_ACTION  ,5  ,size[3]-n0-4  ,60 ,20)
-    Button("Exit"                  ,E_EXIT   ,5  ,size[3]-n0-24  ,60 ,20)
+    Button("Create"                ,E_CREATE  ,5  ,size[3]-n0+16  ,60 ,20,CreateTIP)
+    Button("Action"                ,E_ACTION  ,5  ,size[3]-n0-4  ,60 ,20,ActionTIP)
+    Button("Exit"                  ,E_EXIT   ,5  ,size[3]-n0-24  ,60 ,20,ExitTIP)
     
     NRepeat=Number("repeat"        ,E_REPEAT   ,5  ,size[3]-n0-50     ,75 ,20, NRepeat.val,1,10)    
     
index 9c8d77caca6d1cb9c9fb7d1758b19bb8435206f5..2a6273f15c73ffcb4b4d8a3f98bcd5c50367eae8 100644 (file)
@@ -3,7 +3,7 @@
 """
 Name: 'BPy Doc Browser'
 Blender: 232
-Group: 'Misc'
+Group: 'System'
 Tip: 'Browse BPython (scripting API) modules doc strings.'
 """
 
@@ -15,8 +15,11 @@ The "Doc Browser" lets users navigate the documentation strings of part of
 the Blender Python API.
 
 It doesn't give access yet to object method functions and variables, only to
-module functions, but still it is a handy reference.  Specially for quick
-access, for example to Blender.BGL: the module that wraps OpenGL calls.
+module functions, but still it is a handy reference.
+
+Hotkeys:<br>
+    Page Up / Page Down: scroll 5 lines at a time;<br>
+    Up / Down arrow keys or mouse wheel: scroll one line at a time.
 
 Notes:<br>
     Everyone interested in the bpython api is also invited to read "The Blender
@@ -199,7 +202,7 @@ BACK_MODULE= 7
 CLOSE_VIEW= 8
 FILTER_DISPLAY= 9
 
-SCROLLBAR= 10
+#SCROLLBAR= 10
 
 VIEW_DOC= 100
 BROWSE_MODULE= 20000
@@ -325,8 +328,8 @@ def draw():
        if (items>len(browselist)): items= len(browselist)
 
        end= len(browselist)-items
-       if (end>0):
-               scr= Scrollbar(SCROLLBAR, table[2]+5, table[1], 20, table[3]-table[1], scr.val, 0.0, end, 0, "Page Up/Down scrolls list.")
+       #if (end>0):
+       #       scr= Scrollbar(SCROLLBAR, table[2]+5, table[1], 20, table[3]-table[1], scr.val, 0.0, end, 0, "Page Up/Down scrolls list.")
 
        row= table
        row[1]= row[3]-row_height
@@ -404,17 +407,27 @@ def draw():
                if (eindex<doc_lines): Button("Page down", DOC_PAGE_DOWN, table[2]-100, table[1]+5, 90, 18)
 
 lmouse= [0, 0]
+
+def fit_scroll():
+       global browse_scrollstart, browselist
+       if (browse_scrollstart<0): browse_scrollstart= 0
+       elif (browse_scrollstart>=len(browselist)): browse_scrollstart= len(browselist)-1
+
 def event(evt, val):
        global browse_scrollstart
 
        if (evt==QKEY or evt==ESCKEY): Exit()
-       elif (evt in [PAGEUPKEY, PAGEDOWNKEY] and val): 
-               if (evt==PAGEUPKEY): browse_scrollstart= browse_scrollstart-5
-               else: browse_scrollstart= browse_scrollstart+5
-               
-               if (browse_scrollstart<0): browse_scrollstart= 0
-               elif (browse_scrollstart>=len(browselist)): browse_scrollstart= len(browselist)-1
-
+       elif val:
+               if (evt in [PAGEUPKEY, PAGEDOWNKEY]):
+                       if (evt==PAGEUPKEY): browse_scrollstart= browse_scrollstart-5
+                       else: browse_scrollstart= browse_scrollstart+5
+               elif (evt in [UPARROWKEY, WHEELUPMOUSE]):
+                       browse_scrollstart -= 1
+               elif (evt in [DOWNARROWKEY, WHEELDOWNMOUSE]):
+                       browse_scrollstart += 1
+               else: return
+
+               fit_scroll()
                Redraw()
 
 def bevent(evt):
@@ -431,9 +444,9 @@ def bevent(evt):
        elif (evt==CLOSE_VIEW): view_doc(-1)
        elif (evt==FILTER_DISPLAY): toggle_function_filter()
 
-       elif (evt==SCROLLBAR):
-               global browse_scrollstart
-               browse_scrollstart= int(scr.val)
+       #elif (evt==SCROLLBAR):
+       #       global browse_scrollstart
+       #       browse_scrollstart= int(scr.val)
 
        elif (evt>=BROWSE_MODULE): browse_module(evt-BROWSE_MODULE)
        elif (evt>=VIEW_DOC): view_doc(evt-VIEW_DOC)    
index c60978831fcfd060abd0f50806e68829d75f36f7..9e11702c0beec2ec08b21c3a5a1578783e23c3b2 100644 (file)
@@ -64,8 +64,19 @@ Select the mesh and run this script.  A fixed copy of it will be created.
 # --------------------------------------------------------------------------
 
 import Blender
-try:
- Ozero=Blender.Object.GetSelected()[0]
+
+Ozero=Blender.Object.GetSelected()[0]
+
+errormsg = ''
+if not Ozero:
+ errormsg = "no mesh object selected"
+elif Ozero.getType() != "Mesh":
+ errormsg = "selected (active) object must be a mesh"
+
+if errormsg:
+ Blender.Draw.PupMenu("ERROR: %s" % errormsg)
+
+else:
  nomdelobjet=Ozero.getName()
  Mesh=Blender.NMesh.GetRawFromObject(nomdelobjet)
  Obis = Blender.Object.New ('Mesh')
@@ -73,5 +84,17 @@ try:
  Obis.setMatrix(Ozero.getMatrix())
  scene = Blender.Scene.getCurrent()
  scene.link (Obis)
-except:
- Blender.Draw.PupMenu("Error%t|Not a mesh or no object selected")
+
+ Mesh2=Obis.getData()
+ Mesh1=Ozero.getData()
+
+ if len(Mesh2.verts)==len(Mesh1.verts): 
+    for VertGroupName in Mesh1.getVertGroupNames():
+       VertexList = Mesh1.getVertsFromGroup(VertGroupName, True)
+       Mesh2.addVertGroup(VertGroupName)
+       for Vertex in VertexList:
+           Mesh2.assignVertsToGroup(VertGroupName, [Vertex[0]], Vertex[1], 'add')
+ else:
+     for vgroupname in Ozero.getVertGroupNames():
+        Mesh2.addVertGroup(vgroupname)
+ Mesh2.update()
index bcac4b791d2b0f9ff41ae507789876d74c25f016..aa05c988207644b369bd468264e917682b8ae6ff 100644 (file)
@@ -179,6 +179,16 @@ PATHS = {
        'uscripts': Blender.Get('uscriptsdir')
 }
 
+if not PATHS['home']:
+       errmsg = """
+Can't find Blender's home dir and so can't find the
+Bpymenus file automatically stored inside it, which
+is needed by this script.  Please run the
+Help -> System -> System Information script to get
+information about how to fix this.
+"""
+       raise SystemError, errmsg
+
 BPYMENUS_FILE = bsys.join(PATHS['home'], 'Bpymenus')
 
 f = file(BPYMENUS_FILE, 'r')
index daa6867e1a89e26606ab46a3e20c66d00833881c..01cff8ae57f990f7df72ea903660e3c84f8fe237 100644 (file)
@@ -12,13 +12,13 @@ __url__ = ("blender", "elysiun")
 __version__ = "1.1"
 __bpydoc__ = """\
 This script creates a text in Blender's Text Editor with information
-about your OS, video card, OpenGL driver, Blender and Python versions and
-more.
+about your OS, video card, OpenGL driver, Blender and Python versions,
+script related paths and more.
 
-If you are experiencing trouble running Blender or its scripts in general,
-this information can be useful for online searches (like checking if there
-are known issues related to your video card) or to get help from other users
-or the program's developers.
+If you are experiencing trouble running Blender itself or any Blender Python
+script, this information can be useful to fix any problems or at least for
+online searches (like checking if there are known issues related to your
+video card) or to get help from other users or the program's developers.
 """
 
 # $Id$
@@ -85,7 +85,8 @@ def textWrap(text, length = 70):
 #  msg = sys.exc_info()[1].__str__().split()[3]
 #  Blender.Draw.PupMenu("Python error:|This script requires the %s module" %msg)
 
-header = "=  Blender %s System Information     =" % Blender.Get("version")
+version = Blender.Get('version') / 100.0
+header = "=  Blender %s System Information  =" % version
 lilies = len(header)*"="+"\n"
 header = lilies + header + "\n" + lilies
 
@@ -97,40 +98,12 @@ output.write("Platform: %s\n========\n\n" % sys.platform)
 
 output.write("Python:\n======\n\n")
 output.write("- Version: %s\n\n" % sys.version)
-output.write("- Path:\n\n")
+output.write("- Paths:\n\n")
 for p in sys.path:
        output.write(p + '\n')
 
 output.write("\n- Directories:")
 
-if not Blender.Get('homedir'):
-       outmsg = """
-
-<WARNING> - Blender home dir not found!
-  This should probably be "<path>/.blender/"
-  where <path> is usually the user's home dir.
-
-  Blender's home dir is where entries like:
-    folders scripts/, plugins/ and locale/ and
-    files .Blanguages and .bfont.ttf
-  are located.
-
-  It's also where Blender stores the Bpymenus file
-  with information about registered scripts, so it
-  only needs to scan scripts dir(s) when they are
-  modified.
-"""
-       output.write(outmsg)
-       if Blender.Get('scriptsdir').find('release') > 0:
-               output.write("""
-It seems this Blender binary is located at its cvs source tree:
-that's ok, but the scripts registration data will be recreated
-from dir(s) whenever you start the program, instead of only
-when those dirs are modified.
-
-Adding a .blender/ subdir to e. g. your home dir can prevent that.
-""")
-
 dirlist = [
        ['homedir', 'Blender home dir', 1],
        ['scriptsdir', 'Default dir for scripts', 1],
@@ -139,11 +112,14 @@ dirlist = [
        ['udatadir', 'Data dir "bpydata/" inside user defined dir', 0]
 ]
 
+has_dir = {}
+
 for dir in dirlist:
        dirname, dirstr, is_critical = dir
        dirpath = Blender.Get(dirname)
        output.write("\n\n %s:\n" % dirstr)
        if not dirpath:
+               has_dir[dirname] = False
                if is_critical:
                        warnings += 1
                        output.write("  <WARNING> -- not found")
@@ -152,24 +128,34 @@ for dir in dirlist:
                        output.write("  <NOTICE> -- not found")
        else:
                output.write("  %s" % dirpath)
+               has_dir[dirname] = True
 
-configdir = bsys.join(Blender.Get('datadir'), 'config')
-output.write('\n\n Default config data "bpydata/config/" dir:')
-if bsys.exists(configdir):
-       output.write("  %s" % configdir)
-else:
-       warnings += 1
-       output.write("""
-  <WARNING> -- not found.
-  config/ should be inside the default scripts *data dir*.
-  It's used by Blender to store scripts configuration data.
-""")
+if not has_dir['homedir']:
+       outmsg = """
+
+<WARNING> - Blender home dir not found!
+  This should probably be "<path>/.blender/"
+  where <path> is usually the user's home dir.
 
-if Blender.Get('udatadir'):
+  Blender's home dir is where entries like:
+    folders scripts/, plugins/ and locale/ and
+    files .Blanguages and .bfont.ttf
+  are located.
+
+  It's also where Blender stores the Bpymenus file
+  with information about registered scripts, so it
+  only needs to scan scripts dir(s) when they are
+  modified.
+"""
+       output.write(outmsg)
+
+has_uconfdir = False
+if has_dir['udatadir']:
        uconfigdir = bsys.join(Blender.Get('udatadir'), 'config')
        output.write("\n\n User defined config data dir:")
-       if bsys.exists(configdir):
-               output.write("  %s" % configdir)
+       if bsys.exists(uconfigdir):
+               has_uconfdir = True
+               output.write("  %s" % uconfigdir)
        else:
                notices += 1
                output.write("""
@@ -180,6 +166,49 @@ if Blender.Get('udatadir'):
   won't overwrite the data.)
 """)
 
+configdir = bsys.join(Blender.Get('datadir'), 'config')
+output.write('\n\n Default config data "bpydata/config/" dir:\n')
+if bsys.exists(configdir):
+       output.write("  %s" % configdir)
+else:
+       warnings += 1
+       output.write("""<WARNING> -- not found.
+  config/ should be inside the default scripts *data dir*.
+  It's used by Blender to store scripts configuration data
+  when <user defined scripts dir>/bpydata/config/ dir is
+  not available.
+""")
+
+if has_uconfdir:
+       output.write("""
+
+The user defined config dir will be used.
+""")
+
+cvsdir = 'release/scripts'
+if bsys.dirsep == '\\': cvsdir = cvsdir.replace('/','\\')
+sdir = Blender.Get('scriptsdir')
+if sdir and sdir.find(cvsdir) >= 0:
+       if has_uconfdir:
+               notices += 1
+               output.write("\n\n<NOTICE>:\n")
+       else:
+               warnings += 1
+               output.write("\n\n<WARNING>:\n")
+       output.write("""
+It seems this Blender binary is located in its cvs source tree.
+
+It's recommended that the release/scripts/ dir tree is copied
+to your blender home dir.
+""")
+       if not has_uconfdir:
+               output.write("""
+Since you also don't have a user defined scripts dir with the
+bpydata/config dir inside it, it will not be possible to save
+and restore scripts configuration data files, since writing
+to a dir inside a cvs tree is not a good idea and is avoided. 
+""")
+
 missing_mods = [] # missing basic modules
 
 try:
@@ -197,11 +226,19 @@ Some expected modules were not found.
 Because of that some scripts bundled with Blender may not work.
 Please read the FAQ in the Readme.html file shipped with Blender
 for information about how to fix the problem.
-Missing modules:"""
+Missing modules:
+"""
                output.write(outmsg)
                warnings += 1
                for m in missing_mods:
                        output.write('-> ' + m + '\n')
+               if 'BPyRegistry' in missing_mods:
+                       output.write("""
+Module BPyRegistry.py is missing!
+Without this module it's not possible to save and restore
+scripts configuration data files.
+""")
+
        else:
                output.write("\n\n- Modules: all basic ones were found.\n")
 
diff --git a/release/scripts/unweld.py b/release/scripts/unweld.py
new file mode 100644 (file)
index 0000000..7212574
--- /dev/null
@@ -0,0 +1,439 @@
+#!BPY
+""" Registration info for Blender menus: <- these words are ignored
+Name: 'Unweld'
+Blender: 234
+Group: 'Mesh'
+Tip: 'Unweld all faces from a (or several) selected and common vertex. Made vertex bevelling'
+"""
+
+__author__ = "Jean-Michel Soler (jms)"
+__url__ = ("blender", "elysiun",
+"Script's homepage, http://jmsoler.free.fr/didacticiel/blender/tutor/cpl_faces2vertex.htm#exemple",
+"Communicate problems and errors, http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender")
+__version__ = "0.4.5 beta"
+
+__bpydoc__ = """\
+This script unwelds faces from a selected vertex.
+
+There's also experimental support for static or dynamic (move mouse in the
+Scripts window) vertex bevel.
+
+Usage:
+
+Select a vertex, then run this script.  Its options are:
+
+- unbind points;<br>
+- with noise;<br>
+- middle face;<br>
+- static bevel vertex;<br>
+- moving bevel vertex;
+"""
+
+# ------------------------------------------
+# Un-Weld script 0.4.5 beta
+name="UnWeld"
+Tip= 'Unweld all faces from a selected and common vertex. Made vertex bevelling'
+#
+# split all faces from one selected vertex
+# (c) 2004 J-M Soler released under Blender Artistic License
+#----------------------------------------------
+# Official Page :
+website = 'http://jmsoler.free.fr/didacticiel/blender/tutor/cpl_faces2vertex.htm#exemple'
+# Communicate problems and errors on:
+community = 'http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender'
+#----------------------------------------------
+# Blender Artistic License
+# http://download.blender.org/documentation/html/x21254.html
+#---------------------------------------------
+# Changelog
+#----------------------------------------------
+# 25/05 :
+# -- separate choise, normal (same place) or spread at random, middle of the face
+# -- works on several vertices too
+# -- Quite vertex bevelling on <<lone>> vertex : create hole in faces around this
+# vertex
+# 03/06 :
+# -- a sort of "bevelled vertex" extrusion controled by horizontal mouse
+# displacement. just a beta test to the mouse control.
+# 08/08 :
+# -- minor correction to completely disconnect face.
+#----------------------------------------------
+# Page officielle :
+#   http://jmsoler.free.fr/didacticiel/blender/tutor/cpl_faces2vertex.htm#exemple
+# Communiquer les problemes et erreurs sur:
+#   http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender
+# Blender Artistic License
+#    http://download.blender.org/documentation/html/x21254.html
+#--------------------------------------------- 
+# Changelog
+#----------------------------------------------
+#      25/05 :
+#           -- separation du choix, normal ou dispersion hasardeuse, 
+#              milieu de face
+#           -- sommets multiples / 
+#           -- presque  unvertex bevelling sur un vertex solitaire : cree
+#              un trou dans la facette autour du sommet
+#     03/06 :
+#           -- une sorte de vertex extruder en biseau, controle par
+#              deplacement horizontal de la souris 
+#     08/08 :
+#           -- correction mineure pour s'assurer que les faces soient 
+#              entierment deconnectees
+#----------------------------------------------
+
+import Blender
+from Blender import Noise
+from Blender.Draw import *
+from Blender.BGL import *
+
+# $Id$
+Blender.Window.EditMode(0)
+
+
+def autoDocHelp_script(name,Tip,website):
+    try:
+       dir = Blender.Get('datadir')
+       scriptdir=dir[:dir.find(dir.split(Blender.sys.sep)[-2])]+'scripts'+Blender.sys.sep
+       ttt="""#!BPY\n\"\"\"\nName: '%s'\nBlender: 234
+Group: 'HelpWebsites'\nTooltip: '%s'\n\"\"\"
+import Blender, webbrowser\nwebbrowser.open('%s')\n"""%(name,Tip,website)
+       fil=open(scriptdir+'Help_%s.py'%name.replace(' ','_'),'w')
+       fil.write(ttt)
+       fil.close()
+       ttt="""#!BPY\n\"\"\"\nName: 'A french speaking users community'\nBlender: 234\nGroup: 'HelpWebsites'\nTooltip:  'aA french community News Portal, zoo-Blender.'\n\"\"\"
+import Blender,webbrowser
+webbrowser.open('http://www.zoo-logique.org/3D.Blender/newsportal/thread.php?group=3D.Blender')\n"""
+       fil=open(scriptdir+'Help_frenchusers.py','w')
+       fil.write(ttt)
+       fil.close()
+    except:
+       pass
+
+autoDocHelp_script(name,Tip,website)
+
+Nr=Noise.random
+decal=0.03
+t=[0.0,0.0,0.0]
+pl=[]
+orig=[]
+
+DEBUG = 0
+SUBSURF=0
+DIM=Create(1.0)
+
+def  Tampon(v,t):
+      for n in range(len(v)): t[n]=t[n]+v[n]
+      return t
+   
+def  freeTampon(t):
+      for n in range(3): t[n]=0.0
+      return t
+
+def  TamponMoyen(t,f):      
+      for n in range(3): t[n]/=len(f)
+      return t
+
+def  appliqueMoyenne(v,t):
+      for n in range(len(v)): v[n]=t[n]
+      return v
+
+def docF(f0,f):
+      try:
+          f0.mat=f.mat
+          f0.uv=f.uv 
+          f0.col=f.col
+          f0.image=f.image
+          f0.smooth=f.smooth
+          f0.mode=f.mode
+          f0.flag=f.flag
+          return f0
+      except:
+          pass    
+
+def listConnecterFace(me,lebon):
+               listf2v={}
+               #tri des faces connectees aux sommets selectionnes                           
+               for f in me.faces:
+                 for v in f.v:
+                   if v==lebon:
+                      if v.index not in listf2v.keys():
+                          listf2v[me.verts.index(v)]=[f]
+                      elif f not in listf2v[me.verts.index(v)]:
+                         listf2v[me.verts.index(v)].append(f)
+               return listf2v
+
+
+def creerFaceSupplementaire(me,lebon,listf2v):
+               global t
+               for f in listf2v[lebon.index]:
+
+                  f0=Blender.NMesh.Face()
+                  if result==3: t=freeTampon(t)
+
+                  for v in f.v:
+                     if result==3: t=Tampon(v,t)
+
+                     if v!=lebon:
+                        f0.append(v)
+                     else:
+                        if result==2:                           
+                           nv=Blender.NMesh.Vert(lebon.co[0]+Nr()*decal,
+                                                 lebon.co[1]+Nr()*decal,
+                                                 lebon.co[2]+Nr()*decal)
+                        else:
+                           nv=Blender.NMesh.Vert(lebon.co[0],
+                                                 lebon.co[1],
+                                                 lebon.co[2])
+                        nv.sel=1
+                        me.verts.append(nv)
+
+                        f0.append(me.verts[me.verts.index(nv)])
+                        localise=me.verts.index(nv)                          
+                     docF(f0,f)   
+                     
+                  if result==3:
+                         t=TamponMoyen(t,f0.v)
+                         me.verts[localise]=appliqueMoyenne(me.verts[localise],t)
+                  me.faces.append(f0)                  
+
+               del me.verts[me.verts.index(lebon)]
+
+               for f in listf2v[lebon.index]:
+                  del me.faces[me.faces.index(f)]
+               return me
+
+def collecte_edge(listf2v,me,lebon):
+      back=0
+      edgelist = []
+      vertlist = []
+      if DEBUG : print listf2v    
+      for face in listf2v[lebon.index]:
+          if len(face.v) == 4:
+              vlist = [0,1,2,3,0]
+          elif len(face.v) == 3:
+              vlist = [0,1,2,0]
+          else:
+              vlist = [0,1]
+          for i in range(len(vlist)-1):              
+              vert0 = min(face.v[vlist[i]].index,face.v[vlist[i+1]].index)
+              vert1 = max(face.v[vlist[i]].index,face.v[vlist[i+1]].index)              
+              edgeinlist = 0
+              if vert0==lebon.index or vert1==lebon.index:                 
+                 for edge in edgelist:
+                    if ((edge[0]==vert0) and (edge[1]==vert1)):
+                        edgeinlist = 1
+                        edge[2] = edge[2]+1
+                        edge.append(me.faces.index(face))
+                        break                  
+                 if edgeinlist==0:
+                    edge = [vert0,vert1,1,me.faces.index(face)]
+                    edgelist.append(edge)
+                    
+      for  edge in edgelist:
+        #print edge
+        if len(edge)==4:
+                   del edgelist[edgelist.index(edge)]
+                           
+      edges=len(edgelist)
+      if DEBUG : print 'number of edges : ',edges," Edge list : " ,edgelist    
+      return edges, edgelist     
+
+MouseClickG= Blender.Draw.LEFTMOUSE
+MouseClickD= Blender.Draw.RIGHTMOUSE
+MouseClickM= Blender.Draw.MIDDLEMOUSE
+
+mouse_x=1
+mouse_y=1
+
+x=1
+y=1
+
+debut=0
+
+def D():
+  size=Buffer(GL_FLOAT, 4)
+  glGetFloatv(GL_SCISSOR_BOX, size)
+  size= size.list 
+  glColor3f(0.1, 0.1, 0.15)
+  glRasterPos2f(10, size[3]-16)
+  Text("Quit = Q Key")
+  glRasterPos2f(10, size[3]-36)
+  Text("Mouse to the Right = Increase")
+  glRasterPos2f(10, size[3]-56)
+  Text("Mouse to the Left = Decrease")
+
+def E(evt,val):
+ global mouse_x,x,pl,orig,me,debut
+ global mouse_y,y, MouseClickG,MouseClickD,MouseClickM
+ if (evt== QKEY): Exit()
+ if (evt == MOUSEX): 
+       mouse_x = val
+       pos=x-mouse_x
+       x=mouse_x
+       if pos==0:
+          pos=1
+       deplace(pl,orig,abs(pos)/pos)
+ if (evt == MOUSEY): mouse_y = val
+
+def BE(evt):
+ pass
+
+
+def deplace(pl,orig,n):
+    global me, OBJECT
+    for p in pl:
+       #print p, orig,len(me.verts) 
+       me.verts[p].co[0]+=n*orig[0]
+       me.verts[p].co[1]+=n*orig[1]
+       me.verts[p].co[2]+=n*orig[2]
+    me.update()
+    Blender.Redraw()
+
+
+def VertexBevel(result):
+              global t,pl, orig,me,  SUBSURF
+              unique=0
+              for v in me.verts:
+                  if v.sel==1:
+                     lebon=v   
+                     unique+=1
+
+              if  unique==1:
+                    edges=0
+                    edgelist=[]
+                    vertlist=[]
+                    orig=lebon.no[:]
+                    listf2v=listConnecterFace(me,lebon)
+                    edges, edgelist = collecte_edge(listf2v,me,lebon)
+                    for f in listf2v[lebon.index]:
+                       f0=Blender.NMesh.Face()
+                       for v in f.v:
+                          if v!=lebon:
+                             f0.append(v)
+                          else:
+                              nv=Blender.NMesh.Vert(lebon.co[0],lebon.co[1],lebon.co[2])                           
+                              nv.sel=1 
+                              me.verts.append(nv)
+                              f0.append(me.verts[me.verts.index(nv)])
+                              for e in edgelist:
+                                 if e[-1]==me.faces.index(f) or e[-2]==me.faces.index(f):
+                                     if me.verts.index(nv) not in e:
+                                        e.insert(0,me.verts.index(nv))
+                       docF(f0,f)               
+                       me.faces.append(f0)                       
+                       vertlist.append([me.verts.index(nv),me.faces.index(f)])
+                    for e in  edgelist :
+                        del e[e.index(lebon.index)]
+                        f0=Blender.NMesh.Face()
+                        for n in range(3): 
+                           f0.v.append(me.verts[e[n]])
+                        me.faces.append(f0);
+                     
+                    for ve in vertlist:
+                         t=freeTampon(t)
+                         for v in me.faces[ve[1]].v:
+                              t=Tampon(v,t)                     
+                         t=TamponMoyen(t,me.faces[ve[1]].v)
+                         ve.append(t[:])       
+                         me.verts[ve[0]]=appliqueMoyenne(me.verts[ve[0]],t)
+
+                    def swap(x,y):
+                        return y,x
+
+                    p=[[edgelist[0][0],edgelist[0][1]]] 
+                    while len(p)!=len(edgelist): 
+                      for n in range(1,len(edgelist)) :
+                           if p[-1][1]== edgelist[n][0]:
+                              p.append([edgelist[n][0],edgelist[n][1]])
+                              n+=1
+                           elif p[-1][1]== edgelist[n][1]:
+                              edgelist[n][0],edgelist[n][1]=swap(edgelist[n][0],edgelist[n][1])
+                              p.append([edgelist[n][0],edgelist[n][1]])
+                              n+=1
+                    if len(p)%2==0:
+                          P0=p[:(len(p))/2] ; P1=p[len(p)/2:]; P1.reverse()
+                          for s in range(len(P0)-1):
+                             f0=Blender.NMesh.Face()
+                             table=[P0[s][0],P0[s][1],P1[s+1][0],P1[s+1][1]]
+                             for t in table:f0.v.append(me.verts[t])
+                             me.faces.append(f0) 
+                    elif len(p) >3 :   
+                          P0=p[:(len(p)-1)/2];P1=p[(len(p)-1)/2:-1]; P1.reverse()
+                          for s in range(len(P0)-1):
+                             f0=Blender.NMesh.Face()
+                             table=[P0[s][0],P0[s][1],P1[s+1][0],P1[s+1][1]]
+                             for t in table:f0.v.append(me.verts[t])
+                             me.faces.append(f0) 
+                          f0=Blender.NMesh.Face()
+                          table=[p[-1][0],P0[0][0],P1[-1][1]]
+                          for t in table:f0.v.append(me.verts[t])
+                          me.faces.append(f0) 
+
+                    elif len(p) ==3 :   
+                          if DEBUG :print P0,P1      
+                          f0=Blender.NMesh.Face()
+                          table=[p[0][0],p[0][1],p[1][1]]
+                          for t in table:f0.v.append(me.verts[t])
+                          me.faces.append(f0) 
+
+                    for f in listf2v[lebon.index]:
+                       del me.faces[me.faces.index(f)]  
+                    del me.verts[me.verts.index(lebon)]
+                    me.update()
+
+                    if me.mode&Blender.NMesh.Modes['SUBSURF']:
+                       me.mode-=Blender.NMesh.Modes['SUBSURF']
+                       SUBSURF=1 
+                       me.update()
+                       OBJECT[0].makeDisplayList() 
+
+                    if result==5:
+                       pl=[]
+                       for s in me.verts:
+                           if s.sel==1:
+                              pl.append(s.index)
+                       Blender.Draw.Register(D,E,BE)
+
+                    """
+                    if SUBSURF==1 :
+                       me.mode+=Blender.NMesh.Modes['SUBSURF']
+                       me.update()
+                       OBJECT[0].makeDisplayList() 
+                    """  
+              else:
+                  name = " It could leave only one selected vertex %t | ok %x1 ?"
+                  result = Blender.Draw.PupMenu(name)
+
+OBJECT=Blender.Object.GetSelected()
+
+if len(OBJECT)!=0:
+   if  OBJECT[0].getType()=='Mesh':
+       name = "Unweld %t|Unbind Points %x1|With Noise %x2|Middle Face %x3|Static Bevel Vertex %x4|Moving Bevel Vertex %x5|"
+       result = Blender.Draw.PupMenu(name)
+       if result:
+           me=OBJECT[0].getData()
+           unique=0
+           if result<4:
+              vSelection=[]
+              for v in me.verts:
+                if v.sel==1:
+                    vSelection.append(v)
+              for v in  vSelection:
+                    lebon=v    
+                    if DEBUG : print lebon
+                    listf2v=listConnecterFace(me,lebon)
+                    me=creerFaceSupplementaire(me,lebon,listf2v)
+                    #OBJECT[0].link(me)
+                    me.update()
+                    OBJECT[0].makeDisplayList()
+           else:
+               VertexBevel(result)
+               OBJECT[0].makeDisplayList()
+                    
+   else:
+       name = "Nothing to do! Are you sure ?"
+       result = Blender.Draw.PupMenu(name)
+
+                                       
diff --git a/release/scripts/vrml97_export.py b/release/scripts/vrml97_export.py
new file mode 100644 (file)
index 0000000..7fb036f
--- /dev/null
@@ -0,0 +1,1065 @@
+#!BPY
+""" Registration info for Blender menus:
+Name: 'VRML97 (.wrl)...'
+Blender: 235
+Group: 'Export'
+Submenu: 'All Objects...' all
+Submenu: 'Selected Objects...' selected
+Tooltip: 'Export to VRML97 file format (.wrl)'
+"""
+
+__author__ = ("Rick Kimball", "Ken Miller", "Steve Matthews", "Bart")
+__url__ = ["blender", "elysiun",
+"Author's (Rick) homepage, http://kimballsoftware.com/blender",
+"Author's (Bart) homepage, http://www.neeneenee.de/vrml",
+"Complete online documentation, http://www.neeneenee.de/blender/x3d/exporting_web3d.html"]
+__version__ = "2005/04/09"
+
+__bpydoc__ = """\
+This script exports to VRML97 format, which used to be called VRML2.
+
+Usage:
+
+Run this script from "File->Export" menu.  A pop-up will ask whether you
+want to export only selected or all relevant objects.
+
+Known issues:<br>
+    Doesn't handle multiple materials (don't use material indices);<br>
+    Doesn't handle multiple UV textures on a single mesh (create a mesh
+for each texture);<br>
+    Can't get the texture array associated with material * not the UV ones;
+"""
+
+
+# $Id$
+#
+#------------------------------------------------------------------------
+# VRML97 exporter for blender 2.33 or above
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Copyright (C) 2003,2004: Rick Kimball rick@vrmlworld.net
+#
+# 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.
+#
+# 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.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+
+####################################
+# Library dependancies
+####################################
+
+import Blender
+from Blender import Object, NMesh, Lamp, Draw, BGL, Image, Text
+from Blender.Scene import Render
+from os.path import exists, join
+pytinst = 1
+import math
+
+####################################
+# Global Variables
+####################################
+
+scene = Blender.Scene.getCurrent()
+world = Blender.World.Get() 
+worldmat = Blender.Texture.Get()
+_safeOverwrite = True
+radD=math.pi/180.0
+ARG=''
+
+def rad2deg(v):
+    return round(v*180.0/math.pi,4)
+
+def deg2rad(v):
+    return (v*math.pi)/180.0;
+
+class DrawTypes:
+    """Object DrawTypes enum values
+    BOUNDS - draw only the bounding box of the object
+    WIRE - draw object as a wire frame
+    SOLID - draw object with flat shading
+    SHADED - draw object with OpenGL shading
+"""
+    BOUNDBOX  = 1
+    WIRE      = 2
+    SOLID     = 3
+    SHADED    = 4
+    TEXTURE   = 5
+
+if not hasattr(Blender.Object,'DrawTypes'):
+    Blender.Object.DrawTypes = DrawTypes()
+
+##########################################################
+# Functions for writing output file
+##########################################################
+
+class VRML2Export:
+
+    def __init__(self, filename):
+        #--- public you can change these ---
+        self.matonly = 0
+        self.share = 0
+        self.billnode = 0
+        self.halonode = 0
+        self.collnode = 0
+        self.tilenode = 0
+        self.verbose=2     # level of verbosity in console 0-none, 1-some, 2-most
+        self.cp=3          # decimals for material color values     0.000 - 1.000
+        self.vp=3          # decimals for vertex coordinate values  0.000 - n.000
+        self.tp=3          # decimals for texture coordinate values 0.000 - 1.000
+        self.it=3
+        
+        #--- class private don't touch ---
+        self.texNames={}   # dictionary of textureNames
+        self.matNames={}   # dictionary of materiaNames
+        self.indentLevel=0 # keeps track of current indenting
+        self.filename=filename
+        self.file = open(filename, "w")
+        self.bNav=0
+        self.nodeID=0
+        self.namesReserved=[ "Anchor", "Appearance", "AudioClip",
+                             "Background","Billboard", "Box", 
+                             "Collision", "Color", "ColorInterpolator", "Cone", "Coordinate", "CoordinateInterpolator", "Cylinder", "CylinderSensor",
+                             "DirectionalLight", 
+                             "ElevationGrid", "Extrustion", 
+                             "Fog", "FontStyle", "Group", 
+                             "ImageTexture", "IndexedFaceSet", "IndexedLineSet", "Inline", 
+                             "LOD", "Material", "MovieTexture", 
+                             "NavigationInfo", "Normal", "NormalInterpolator","OrientationInterpolator", 
+                             "PixelTexture", "PlaneSensor", "PointLight", "PointSet", "PositionInterpolator", "ProxmimitySensor", 
+                             "ScalarInterpolator", "Script", "Shape", "Sound", "Sphere", "SphereSensor", "SpotLight", "Switch",
+                             "Text", "TextureCoordinate", "TextureTransform", "TimeSensor", "TouchSensor", "Transform", 
+                             "Viewpoint", "VisibilitySensor", "WorldInfo" ]
+        self.namesStandard=[ "Empty","Empty.000","Empty.001","Empty.002","Empty.003","Empty.004","Empty.005",
+                             "Empty.006","Empty.007","Empty.008","Empty.009","Empty.010","Empty.011","Empty.012",
+                             "Scene.001","Scene.002","Scene.003","Scene.004","Scene.005","Scene.06","Scene.013",
+                             "Scene.006","Scene.007","Scene.008","Scene.009","Scene.010","Scene.011","Scene.012",
+                             "World","World.000","World.001","World.002","World.003","World.004","World.005" ]
+
+##########################################################
+# Writing nodes routines
+##########################################################
+
+    def writeHeader(self):
+        self.file.write("#VRML V2.0 utf8\n\n")
+        self.file.write("# This file was authored with Blender (http://www.blender.org/)\n")
+        self.file.write("# Exported using VRML97 exporter v1.50\n\n")
+
+    def writeInline(self):
+        inlines = Blender.Scene.Get()
+        allinlines = len(inlines)
+        if scene != inlines[0]:
+            return
+        else:
+            for i in range(allinlines):
+                nameinline=inlines[i].getName()
+                if (nameinline not in self.namesStandard) and (i > 0):
+                    self.writeIndented("DEF %s Inline {\n" % (self.cleanStr(nameinline)), 1)
+                    nameinline = nameinline+".wrl"
+                    self.writeIndented("url \"%s\" \n" % nameinline)
+                    self.writeIndented("}\n", -1)
+                    self.writeIndented("\n")
+
+    def writeScript(self):
+        textEditor = Blender.Text.Get() 
+        alltext = len(textEditor)
+        for i in range(alltext):
+            nametext = textEditor[i].getName()
+            nlines = textEditor[i].getNLines() 
+            if (nametext == "web3d" or nametext == "web3d.js" or nametext == "web3d.txt") and (nlines != None):
+                nalllines = len(textEditor[i].asLines())
+                alllines = textEditor[i].asLines()
+                for j in range(nalllines):
+                    self.writeIndented(alllines[j] + "\n")
+        self.writeIndented("\n")
+
+    def writeViewpoint(self, thisObj):
+        context = scene.getRenderingContext()
+        ratio = float(context.imageSizeY())/float(context.imageSizeX())
+        lens = (360* (math.atan(ratio *16 / thisObj.data.getLens()) / 3.141593))*(3.141593/180)
+        if lens > 3.14:
+            lens = 3.14
+        self.writeIndented("DEF %s Viewpoint {\n" % (self.cleanStr(thisObj.name)), 1)
+        self.writeIndented("description \"%s\" \n" % (thisObj.name))
+        # get the camera location, subtract 90 degress from X to orient like VRML does
+        loc = self.rotatePointForVRML(thisObj.loc)
+        rot = [thisObj.RotX - 1.57, thisObj.RotY, thisObj.RotZ]
+        nRot = self.rotatePointForVRML(rot)
+        # convert to Quaternion and to Angle Axis
+        Q  = self.eulerToQuaternions(nRot[0], nRot[1], nRot[2])
+        Q1 = self.multiplyQuaternions(Q[0], Q[1])
+        Qf = self.multiplyQuaternions(Q1, Q[2])
+        angleAxis = self.quaternionToAngleAxis(Qf)
+        # write orientation statement
+        self.writeIndented("orientation %3.2f %3.2f %3.2f %3.2f\n" % (angleAxis[0], angleAxis[1], -angleAxis[2], angleAxis[3]))
+        # write position statement
+        self.writeIndented("position %3.2f %3.2f %3.2f\n" % (loc[0], loc[1], loc[2]))
+        self.writeIndented("fieldOfView %.3f\n" % (lens))
+        self.writeIndented("}\n", -1)
+        self.writeIndented("\n")
+
+    def writeFog(self):
+        if len(world) > 0:
+            mtype = world[0].getMistype()
+            mparam = world[0].getMist()
+            grd = world[0].getHor()
+            grd0, grd1, grd2 = grd[0], grd[1], grd[2]
+        else:
+            return
+        if (mtype == 1):
+            self.writeIndented("Fog {\n",1)
+            self.writeIndented("fogType \"LINEAR\"\n")                                         
+            self.writeIndented("color %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + "\n")
+            self.writeIndented("visibilityRange " + str(round(mparam[2],self.cp)) + "\n")  
+            self.writeIndented("}\n",-1)
+            self.writeIndented("\n")   
+        elif (mtype == 2):
+            self.writeIndented("Fog {\n",1)
+            self.writeIndented("fogType \"EXPONENTIAL\"\n")                                            
+            self.writeIndented("color %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + "\n")
+            self.writeIndented("visibilityRange " + str(round(mparam[2],self.cp)) + "\n")  
+            self.writeIndented("}\n",-1) 
+            self.writeIndented("\n")  
+        else:
+            return
+
+    def writeNavigationInfo(self, scene):
+        allObj = []
+        allObj = scene.getChildren()
+        headlight = "TRUE"
+        vislimit = 0.0
+        for thisObj in allObj:
+            objType=thisObj.getType()
+            if objType == "Camera":
+                vislimit = thisObj.data.getClipEnd()
+            elif objType == "Lamp":
+                headlight = "FALSE"
+        self.writeIndented("NavigationInfo {\n",1)
+        self.writeIndented("headlight %s" % headlight + "\n")
+        self.writeIndented("visibilityLimit %s\n" % (round(vislimit,self.cp)))
+        self.writeIndented("type [\"EXAMINE\", \"ANY\"]\n")            
+        self.writeIndented("avatarSize [0.25, 1.75, 0.75]\n")
+        self.writeIndented("} \n",-1)
+        self.writeIndented(" \n")
+
+    def writeSpotLight(self, object, lamp):
+        safeName = self.cleanStr(object.name)
+        if len(world) > 0:
+            ambi = world[0].getAmb()
+            ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
+        else:
+            ambi = 0
+            ambientIntensity = 0
+
+        # compute cutoff and beamwidth
+        intensity=min(lamp.energy/1.5,1.0)
+        beamWidth=deg2rad(lamp.spotSize)*.37;
+        cutOffAngle=beamWidth*1.3
+
+        (dx,dy,dz)=self.computeDirection(object)
+        # note -dx seems to equal om[3][0]
+        # note -dz seems to equal om[3][1]
+        # note  dy seems to equal om[3][2]
+        om = object.getMatrix()
+            
+        location=self.rotVertex(om, (0,0,0));
+        radius = lamp.dist*math.cos(beamWidth)
+        self.writeIndented("DEF %s SpotLight {\n" % safeName,1)
+        self.writeIndented("radius %s\n" % (round(radius,self.cp)))
+        self.writeIndented("ambientIntensity %s\n" % (round(ambientIntensity,self.cp)))
+        self.writeIndented("intensity %s\n" % (round(intensity,self.cp)))
+        self.writeIndented("color %s %s %s\n" % (round(lamp.col[0],self.cp),
+                                                    round(lamp.col[1],self.cp),
+                                                    round(lamp.col[2],self.cp)))
+        self.writeIndented("beamWidth %s\n" % (round(beamWidth,self.cp)))
+        self.writeIndented("cutOffAngle %s\n" % (round(cutOffAngle,self.cp)))
+        self.writeIndented("direction %s %s %s\n" % (round(dx,3),round(dy,3),round(dz,3)))
+        self.writeIndented("location %s %s %s\n" % (round(location[0],3),
+                                                    round(location[1],3),
+                                                    round(location[2],3)))
+        self.writeIndented("}\n",-1)
+        self.writeIndented("\n")
+        
+    def writeDirectionalLight(self, object, lamp):
+        safeName = self.cleanStr(object.name)
+        if len(world) > 0:
+            ambi = world[0].getAmb()
+            ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
+        else:
+            ambi = 0
+            ambientIntensity = 0
+
+        intensity=min(lamp.energy/1.5, 1.0) 
+        (dx,dy,dz)=self.computeDirection(object)
+        self.writeIndented("DEF %s DirectionalLight {\n" % safeName,1)
+        self.writeIndented("ambientIntensity %s\n" % (round(ambientIntensity,self.cp)))
+        self.writeIndented("color %s %s %s\n" % (round(lamp.col[0],self.cp),
+                                                    round(lamp.col[1],self.cp),
+                                                    round(lamp.col[2],self.cp)))
+        self.writeIndented("intensity %s\n" % (round(intensity,self.cp)))
+        self.writeIndented("direction %s %s %s\n" % (round(dx,4),round(dy,4),round(dz,4)))
+        self.writeIndented("}\n",-1)
+        self.writeIndented("\n")
+
+    def writePointLight(self, object, lamp):
+        safeName = self.cleanStr(object.name)
+        if len(world) > 0:
+            ambi = world[0].getAmb()
+            ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
+        else:
+            ambi = 0
+            ambientIntensity = 0
+        om = object.getMatrix()
+        location=self.rotVertex(om, (0,0,0));
+        intensity=min(lamp.energy/1.5,1.0)
+        radius = lamp.dist
+        self.writeIndented("DEF %s PointLight {\n" % safeName,1)
+        self.writeIndented("ambientIntensity %s\n" % (round(ambientIntensity,self.cp)))
+        self.writeIndented("color %s %s %s\n" % (round(lamp.col[0],self.cp),
+                                                    round(lamp.col[1],self.cp),
+                                                    round(lamp.col[2],self.cp)))
+        self.writeIndented("intensity %s\n" % (round(intensity,self.cp)))
+        self.writeIndented("location %s %s %s\n" % (round(location[0],3),
+                                                    round(location[1],3),
+                                                    round(location[2],3)))
+        self.writeIndented("radius %s\n" % radius )
+        self.writeIndented("}\n",-1)
+        self.writeIndented("\n")
+
+    def writeNode(self, thisObj):
+        objectname=str(thisObj.getName())
+        if objectname in self.namesStandard:
+            return
+        else:
+            (dx,dy,dz)=self.computeDirection(thisObj)
+            om = thisObj.getMatrix()
+            location=self.rotVertex(om, (0,0,0));
+            self.writeIndented("%s {\n" % objectname,1)
+            self.writeIndented("# direction %s %s %s\n" % (round(dx,3),round(dy,3),round(dz,3)))
+            self.writeIndented("# location %s %s %s\n" % (round(location[0],3),
+                                                        round(location[1],3),
+                                                        round(location[2],3)))
+            self.writeIndented("}\n",-1)
+            self.writeIndented("\n")
+    def createDef(self, name):
+        name = name + str(self.nodeID)
+        self.nodeID=self.nodeID+1
+        if len(name) <= 3:
+            newname = "_" + str(self.nodeID)
+            return "%s" % (newname)
+        else:
+            for bad in [' ','"','#',"'",',','.','[','\\',']','{','}']:
+                name=name.replace(bad,'_')
+            if name in self.namesReserved:
+                newname = name[0:3] + "_" + str(self.nodeID)
+                return "%s" % (newname)
+            elif name[0].isdigit():
+                newname = "_" + name + str(self.nodeID)
+                return "%s" % (newname)
+            else:
+                newname = name
+                return "%s" % (newname)
+
+    def secureName(self, name):
+        name = name + str(self.nodeID)
+        self.nodeID=self.nodeID+1
+        if len(name) <= 3:
+            newname = "_" + str(self.nodeID)
+            return "%s" % (newname)
+        else:
+            for bad in ['"','#',"'",',','.','[','\\',']','{','}']:
+                name=name.replace(bad,'_')
+            if name in self.namesReserved:
+                newname = name[0:3] + "_" + str(self.nodeID)
+                return "%s" % (newname)
+            elif name[0].isdigit():
+                newname = "_" + name + str(self.nodeID)
+                return "%s" % (newname)
+            else:
+                newname = name
+                return "%s" % (newname)
+
+    def writeIndexedFaceSet(self, object, normals = 0):
+
+        imageMap={}   # set of used images
+        sided={}      # 'one':cnt , 'two':cnt
+        vColors={}    # 'multi':1
+        meshName = self.cleanStr(object.name)
+        mesh=object.getData()
+        for face in mesh.faces:
+            if face.mode & Blender.NMesh.FaceModes['HALO'] and self.halonode == 0:
+                self.writeIndented("Billboard {\n",1)
+                self.writeIndented("axisOfRotation 0 0 0\n")
+                self.writeIndented("children [\n")
+                self.halonode = 1
+            elif face.mode & Blender.NMesh.FaceModes['BILLBOARD'] and self.billnode == 0:
+                self.writeIndented("Billboard {\n",1)
+                self.writeIndented("axisOfRotation 0 1 0\n")
+                self.writeIndented("children [\n")
+                self.billnode = 1
+            elif face.mode & Blender.NMesh.FaceModes['OBCOL'] and self.matonly == 0:
+                self.matonly = 1
+            elif face.mode & Blender.NMesh.FaceModes['SHAREDCOL'] and self.share == 0:
+                self.share = 1
+            elif face.mode & Blender.NMesh.FaceModes['TILES'] and self.tilenode == 0:
+                self.tilenode = 1
+            elif not face.mode & Blender.NMesh.FaceModes['DYNAMIC'] and self.collnode == 0:
+                self.writeIndented("Collision {\n",1)
+                self.writeIndented("collide FALSE\n")
+                self.writeIndented("children [\n")
+                self.collnode = 1
+
+        nIFSCnt=self.countIFSSetsNeeded(mesh, imageMap, sided, vColors)
+        
+        if nIFSCnt > 1:
+            self.writeIndented("DEF %s%s Group {\n" % ("G_", meshName),1)
+            self.writeIndented("children [\n",1)
+        
+        if sided.has_key('two') and sided['two'] > 0:
+            bTwoSided=1
+        else:
+            bTwoSided=0
+        om = object.getMatrix();
+        location=self.rotVertex(om, (0,0,0));
+        self.writeIndented("Transform {\n",1)
+        self.writeIndented("translation %s %s %s\n" % (round(location[0],3),
+                                                    round(location[1],3),
+                                                    round(location[2],3)),1)
+        self.writeIndented("children [\n")
+        self.writeIndented("DEF %s Shape {\n" % meshName,1)
+            
+        maters=mesh.materials
+        hasImageTexture=0
+        issmooth=0
+
+        if len(maters) > 0 or mesh.hasFaceUV():
+            self.writeIndented("appearance Appearance {\n", 1)
+            
+            # right now this script can only handle a single material per mesh.
+            if len(maters) >= 1:
+                mat=Blender.Material.Get(maters[0].name)
+                self.writeMaterial(mat, self.createDef(maters[0].name))
+                if len(maters) > 1:
+                    print "Warning: mesh named %s has multiple materials" % meshName
+                    print "Warning: only one material per object handled"
+            else:
+                self.writeIndented("material NULL\n")
+        
+            #-- textures
+            if mesh.hasFaceUV():
+                for face in mesh.faces:
+                    if (hasImageTexture == 0) and (face.image):
+                        self.writeImageTexture(face.image.name)
+                        hasImageTexture=1  # keep track of face texture
+            if self.tilenode == 1:
+                self.writeIndented("textureTransform TextureTransform  { scale %s %s }\n" % (face.image.xrep, face.image.yrep))
+                self.tilenode = 0
+            self.writeIndented("}\n", -1)
+
+        #-- IndexedFaceSet or IndexedLineSet
+
+        # check if object is wireframe only
+        if object.drawType == Blender.Object.DrawTypes.WIRE:
+            # user selected WIRE=2 on the Drawtype=Wire on (F9) Edit page
+            ifStyle="IndexedLineSet"
+        else:
+            # user selected BOUNDS=1, SOLID=3, SHARED=4, or TEXTURE=5
+            ifStyle="IndexedFaceSet"
+
+        
+        self.writeIndented("geometry %s {\n" % ifStyle, 1)
+        if object.drawType != Blender.Object.DrawTypes.WIRE:
+            if bTwoSided == 1:
+                self.writeIndented("solid FALSE\n")
+            else:
+                self.writeIndented("solid TRUE\n")
+
+        #--- output coordinates
+        self.writeCoordinates(object, mesh, meshName)
+        
+        if object.drawType != Blender.Object.DrawTypes.WIRE:
+            #--- output textureCoordinates if UV texture used
+            if mesh.hasFaceUV():
+                if hasImageTexture == 1:
+                    self.writeTextureCoordinates(mesh)
+                elif self.matonly == 1 and self.share == 1:
+                    self.writeFaceColors(mesh)
+
+        for face in mesh.faces:
+            if face.smooth:
+                 issmooth=1
+        if issmooth==1:
+            creaseAngle=(mesh.getMaxSmoothAngle())*radD
+            self.writeIndented("creaseAngle %s\n" % (round(creaseAngle,self.cp)))
+
+        #--- output vertexColors
+        if self.share == 1 and self.matonly == 0:
+            self.writeVertexColors(mesh)
+        self.matonly = 0
+        self.share = 0
+        #--- output closing braces
+        self.writeIndented("}\n", -1)
+        self.writeIndented("}\n", -1)
+        self.writeIndented("]\n", -1)
+        self.writeIndented("}\n", -1)
+
+        if self.halonode == 1:
+            self.writeIndented("]\n", -1)
+            self.writeIndented("}\n", -1)
+            self.halonode = 0
+
+        if self.billnode == 1:
+            self.writeIndented("]\n", -1)
+            self.writeIndented("}\n", -1)
+            self.billnode = 0
+
+        if self.collnode == 1:
+            self.writeIndented("]\n", -1)
+            self.writeIndented("}\n", -1)
+            self.collnode = 0
+
+        if nIFSCnt > 1:
+            self.writeIndented("]\n", -1)
+            self.writeIndented("}\n", -1)
+
+        self.writeIndented("\n")
+
+    def writeCoordinates(self, object, mesh, meshName):
+        #-- vertices
+        self.writeIndented("coord DEF %s%s Coordinate {\n" % ("coord_",meshName), 1)
+        self.writeIndented("point [\n\t\t\t\t\t\t", 1)
+        meshVertexList = mesh.verts
+
+        # create vertex list and pre rotate -90 degrees X for VRML
+        mm=object.getMatrix()
+        location=self.rotVertex(mm, (0,0,0));
+        for vertex in meshVertexList:
+            v=self.rotVertex(mm, vertex);
+            self.file.write("%s %s %s, " %
+                               (round((v[0]-location[0]),self.vp),
+                                round((v[1]-location[1]),self.vp),
+                                round((v[2]-location[2]),self.vp) ))
+        self.writeIndented("\n", 0)
+        self.writeIndented("]\n", -1)
+        self.writeIndented("}\n", -1)
+
+        self.writeIndented("coordIndex [\n\t\t\t\t\t", 1)
+        coordIndexList=[]  
+        for face in mesh.faces:
+            cordStr=""
+            for i in range(len(face)):
+                indx=meshVertexList.index(face[i])
+                cordStr = cordStr + "%s " % indx
+            self.file.write(cordStr + "-1, ")
+        self.writeIndented("\n", 0)
+        self.writeIndented("]\n", -1)
+
+    def writeTextureCoordinates(self, mesh):
+        texCoordList=[] 
+        texIndexList=[]
+        j=0
+
+        for face in mesh.faces:
+            for i in range(len(face)):
+                texIndexList.append(j)
+                texCoordList.append(face.uv[i])
+                j=j+1
+            texIndexList.append(-1)
+
+        self.writeIndented("texCoord TextureCoordinate {\n", 1)
+        self.writeIndented("point [\n\t\t\t\t\t\t", 1)
+        for i in range(len(texCoordList)):
+            self.file.write("%s %s, " %
+                               (round(texCoordList[i][0],self.tp), 
+                                round(texCoordList[i][1],self.tp)))
+        self.writeIndented("\n", 0)
+        self.writeIndented("]\n", -1)
+        self.writeIndented("}\n", -1)
+
+        self.writeIndented("texCoordIndex [\n\t\t\t\t\t\t", 1)
+        texIndxStr=""
+        for i in range(len(texIndexList)):
+            texIndxStr = texIndxStr + "%d, " % texIndexList[i]
+            if texIndexList[i]==-1:
+                self.file.write(texIndxStr)
+                texIndxStr=""
+        self.writeIndented("\n", 0)
+        self.writeIndented("]\n", -1)
+
+    def writeFaceColors(self, mesh):
+        self.writeIndented("colorPerVertex FALSE\n")
+        self.writeIndented("color Color {\n",1)
+        self.writeIndented("color [\n\t\t\t\t\t\t", 1)
+
+        for face in mesh.faces:
+            if face.col:
+                c=face.col[0]
+                if self.verbose > 2:
+                    print "Debug: face.col r=%d g=%d b=%d" % (c.r, c.g, c.b)
+
+                aColor = self.rgbToFS(c)
+                self.file.write("%s, " % aColor)
+        self.writeIndented("\n", 0)
+        self.writeIndented("]\n",-1)
+        self.writeIndented("}\n",-1)
+
+    def writeVertexColors(self, mesh):
+        self.writeIndented("colorPerVertex TRUE\n")
+        self.writeIndented("color Color {\n",1)
+        self.writeIndented("color [\n\t\t\t\t\t\t", 1)
+
+        for i in range(len(mesh.verts)):
+            c=self.getVertexColorByIndx(mesh,i)
+            if self.verbose > 2:
+                print "Debug: vertex[%d].col r=%d g=%d b=%d" % (i, c.r, c.g, c.b)
+
+            aColor = self.rgbToFS(c)
+            self.file.write("%s, " % aColor)
+        self.writeIndented("\n", 0)
+        self.writeIndented("]\n",-1)
+        self.writeIndented("}\n",-1)
+
+    def writeMaterial(self, mat, matName):
+        # look up material name, use it if available
+        if self.matNames.has_key(matName):
+            self.writeIndented("material USE %s\n" % matName)
+            self.matNames[matName]+=1
+            return;
+
+        self.matNames[matName]=1
+
+        ambient = mat.amb/2
+        diffuseR, diffuseG, diffuseB = mat.rgbCol[0], mat.rgbCol[1],mat.rgbCol[2]
+        if len(world) > 0:
+            ambi = world[0].getAmb()
+            ambi0, ambi1, ambi2 = ambi[0], ambi[1], ambi[2]
+        else:
+            ambi = 0
+            ambi0, ambi1, ambi2 = 0, 0, 0
+        emisR, emisG, emisB = (diffuseR*mat.emit+ambi0)/4, (diffuseG*mat.emit+ambi1)/4, (diffuseB*mat.emit+ambi2)/4
+
+        shininess = mat.hard/255.0
+        specR = (mat.specCol[0]+0.001)/(1.05/(mat.getSpec()+0.001))
+        specG = (mat.specCol[1]+0.001)/(1.05/(mat.getSpec()+0.001))
+        specB = (mat.specCol[2]+0.001)/(1.05/(mat.getSpec()+0.001))
+        transp = 1-mat.alpha
+
+        self.writeIndented("material DEF %s Material {\n" % matName, 1)
+        self.writeIndented("diffuseColor %s %s %s" %
+                           (round(diffuseR,self.cp), round(diffuseG,self.cp), round(diffuseB,self.cp)) +
+                           "\n")
+        self.writeIndented("ambientIntensity %s" %
+                           (round(ambient,self.cp))+
+                           "\n")
+        self.writeIndented("specularColor %s %s %s" %
+                           (round(specR,self.cp), round(specG,self.cp), round(specB,self.cp)) +
+                           "\n" )
+        self.writeIndented("emissiveColor  %s %s %s" %
+                           (round(emisR,self.cp), round(emisG,self.cp), round(emisB,self.cp)) +
+                           "\n" )
+        self.writeIndented("shininess %s" %
+                           (round(shininess,self.cp)) +
+                           "\n" )
+        self.writeIndented("transparency %s" %
+                           (round(transp,self.cp)) +
+                           "\n")
+        self.writeIndented("}\n",-1)
+
+    def writeImageTexture(self, name):
+        if self.texNames.has_key(name):
+            self.writeIndented("texture USE %s\n" % self.cleanStr(name))
+            self.texNames[name] += 1
+            return
+        else:
+            self.writeIndented("texture DEF %s ImageTexture {\n" % self.cleanStr(name), 1)
+            self.writeIndented("url \"%s\"\n" % name)
+            self.writeIndented("}\n",-1)
+            self.texNames[name] = 1
+
+    def writeBackground(self):
+        if len(world) > 0:
+            worldname = world[0].getName()
+        else:
+            return
+        blending = world[0].getSkytype()       
+        grd = world[0].getHor()
+        grd0, grd1, grd2 = grd[0], grd[1], grd[2]
+        sky = world[0].getZen()
+        sky0, sky1, sky2 = sky[0], sky[1], sky[2]
+        mix0, mix1, mix2 = grd[0]+sky[0], grd[1]+sky[1], grd[2]+sky[2]
+        mix0, mix1, mix2 = mix0/2, mix1/2, mix2/2
+        if worldname in self.namesStandard:
+            self.writeIndented("Background {\n",1)
+        else:
+            self.writeIndented("DEF %s Background {\n" % self.createDef(worldname),1)
+        # No Skytype - just Hor color
+        if blending == 0:
+            self.writeIndented("groundColor %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + "\n")
+            self.writeIndented("skyColor %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + "\n")
+        # Blend Gradient
+        elif blending == 1:
+            self.writeIndented("groundColor [ %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + ",")
+            self.writeIndented("%s %s %s" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)) + " ]\n")
+            self.writeIndented("groundAngle [ 1.57, 1.57 ]\n")
+            self.writeIndented("skyColor [ %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + ",")
+            self.writeIndented("%s %s %s" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)) + " ]\n")
+            self.writeIndented("skyAngle [ 1.57, 1.57 ]\n")
+        # Blend+Real Gradient Inverse
+        elif blending == 3:
+            self.writeIndented("groundColor [ %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + ", ")
+            self.writeIndented("%s %s %s" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)) + " ]\n")
+            self.writeIndented("groundAngle [ 1.57, 1.57 ]\n")
+            self.writeIndented("skyColor [ %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + ", ")
+            self.writeIndented("%s %s %s" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)) + " ]\n")
+            self.writeIndented("skyAngle [ 1.57, 1.57 ]\n")
+        # Paper - just Zen Color
+        elif blending == 4:
+            self.writeIndented("groundColor %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + "\n")
+            self.writeIndented("skyColor %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + "\n")
+        # Blend+Real+Paper - komplex gradient
+        elif blending == 7:
+            self.writeIndented("groundColor [ %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + ", ")
+            self.writeIndented("%s %s %s" %(round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + " ]\n")
+            self.writeIndented("groundAngle [ 1.57, 1.57 ]\n")
+            self.writeIndented("skyColor [ %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + ",")
+            self.writeIndented("%s %s %s" %(round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + " ]\n")
+            self.writeIndented("skyAngle [ 1.57, 1.57 ]\n")
+        # Any Other two colors
+        else:
+            self.writeIndented("groundColor %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + "\n")
+            self.writeIndented("skyColor %s %s %s" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)) + "\n")
+        alltexture = len(worldmat)
+        for i in range(alltexture):
+            namemat = worldmat[i].getName()
+            pic = worldmat[i].getImage()       
+            if (namemat == "back") and (pic != None):
+                self.writeIndented("backUrl \"" + str(pic.getName()) + "\"\n")
+            elif (namemat == "bottom") and (pic != None):
+                self.writeIndented("bottomUrl \"" + str(pic.getName()) + "\"\n")
+            elif (namemat == "front") and (pic != None):
+                self.writeIndented("frontUrl \"" + str(pic.getName()) + "\"\n")
+            elif (namemat == "left") and (pic != None):
+                self.writeIndented("leftUrl \"" + str(pic.getName()) + "\"\n")
+            elif (namemat == "right") and (pic != None):
+                self.writeIndented("rightUrl \"" + str(pic.getName()) + "\"\n")
+            elif (namemat == "top") and (pic != None):
+                self.writeIndented("topUrl \"" + str(pic.getName()) + "\"\n")
+        self.writeIndented("}",-1)
+        self.writeIndented("\n\n")
+
+##########################################################
+# export routine
+##########################################################
+
+    def export(self, scene, world, worldmat):
+        print "Info: starting VRML97 export to " + self.filename + "..."
+        self.writeHeader()
+        self.writeNavigationInfo(scene)
+        self.writeBackground()
+        self.writeFog()
+        allObj = []
+        if ARG == 'selected':
+            allObj = Blender.Object.GetSelected()
+        else:
+            allObj = scene.getChildren()
+            self.writeInline()
+        for thisObj in allObj:
+            try:
+                objType=thisObj.getType()
+                objName=thisObj.getName()
+                self.matonly = 0
+                if objType == "Camera":
+                    self.writeViewpoint(thisObj)
+                elif objType == "Mesh":
+                    self.writeIndexedFaceSet(thisObj, normals = 0)
+                elif objType == "Lamp":
+                    lmpName=Lamp.Get(thisObj.data.getName())
+                    lmpType=lmpName.getType()
+                    if lmpType == Lamp.Types.Lamp:
+                        self.writePointLight(thisObj, lmpName)
+                    elif lmpType == Lamp.Types.Spot:
+                        self.writeSpotLight(thisObj, lmpName)
+                    elif lmpType == Lamp.Types.Sun:
+                        self.writeDirectionalLight(thisObj, lmpName)
+                    else:
+                        self.writeDirectionalLight(thisObj, lmpName)
+                elif objType == "Empty" and objName != "Empty":
+                    self.writeNode(thisObj)
+                else:
+                    #print "Info: Ignoring [%s], object type [%s] not handle yet" % (object.name,object.getType())
+                    print ""
+            except AttributeError:
+                print "Error: Unable to get type info for %s" % thisObj.getName()
+        if ARG != 'selected':
+            self.writeScript()
+        self.cleanup()
+        
+##########################################################
+# Utility methods
+##########################################################
+
+    def cleanup(self):
+        self.file.close()
+        self.texNames={}
+        self.matNames={}
+        self.indentLevel=0
+        print "Info: finished VRML97 export to %s\n" % self.filename
+
+    def cleanStr(self, name, prefix='rsvd_'):
+        """cleanStr(name,prefix) - try to create a valid VRML DEF name from object name"""
+
+        newName=name[:]
+        if len(newName) == 0:
+            self.nNodeID+=1
+            return "%s%d" % (prefix, self.nNodeID)
+        
+        if newName in self.namesReserved:
+            newName='%s%s' % (prefix,newName)
+        
+        if newName[0].isdigit():
+            newName='%s%s' % ('_',newName)
+
+        for bad in [' ','"','#',"'",',','.','[','\\',']','{','}']:
+            newName=newName.replace(bad,'_')
+        return newName
+
+    def countIFSSetsNeeded(self, mesh, imageMap, sided, vColors):
+        """
+        countIFFSetsNeeded() - should look at a blender mesh to determine
+        how many VRML IndexFaceSets or IndexLineSets are needed.  A
+        new mesh created under the following conditions:
+        
+         o - split by UV Textures / one per mesh
+         o - split by face, one sided and two sided
+         o - split by smooth and flat faces
+         o - split when faces only have 2 vertices * needs to be an IndexLineSet
+        """
+        
+        imageNameMap={}
+        faceMap={}
+        nFaceIndx=0
+        
+        for face in mesh.faces:
+            sidename='';
+            if (face.mode & NMesh.FaceModes.TWOSIDE) == NMesh.FaceModes.TWOSIDE:
+                sidename='two'
+            else:
+                sidename='one'
+
+            if not vColors.has_key('multi'):
+                for face in mesh.faces:
+                    if face.col:
+                        c=face.col[0]
+                        if c.r != 255 and c.g != 255 and c.b !=255:
+                            vColors['multi']=1
+
+            if sided.has_key(sidename):
+                sided[sidename]+=1
+            else:
+                sided[sidename]=1
+
+            if face.image:
+                faceName="%s_%s" % (face.image.name, sidename);
+
+                if imageMap.has_key(faceName):
+                    imageMap[faceName].append(face)
+                else:
+                    imageMap[faceName]=[face.image.name,sidename,face]
+
+        if self.verbose > 2:
+            for faceName in imageMap.keys():
+                ifs=imageMap[faceName]
+                print "Debug: faceName=%s image=%s, solid=%s facecnt=%d" % \
+                      (faceName, ifs[0], ifs[1], len(ifs)-2)
+
+        return len(imageMap.keys())
+    
+    def faceToString(self,face):
+
+        print "Debug: face.flag=0x%x (bitflags)" % face.flag
+        if face.flag & NMesh.FaceFlags.SELECT == NMesh.FaceFlags.SELECT:
+            print "Debug: face.flag.SELECT=true"
+
+        print "Debug: face.mode=0x%x (bitflags)" % face.mode
+        if (face.mode & NMesh.FaceModes.TWOSIDE) == NMesh.FaceModes.TWOSIDE:
+            print "Debug: face.mode twosided"
+
+        print "Debug: face.transp=0x%x (enum)" % face.transp
+        if face.transp == NMesh.FaceTranspModes.SOLID:
+            print "Debug: face.transp.SOLID"
+
+        if face.image:
+            print "Debug: face.image=%s" % face.image.name
+        print "Debug: face.materialIndex=%d" % face.materialIndex 
+
+    def getVertexColorByIndx(self, mesh, indx):
+        for face in mesh.faces:
+            j=0
+            for vertex in face.v:
+                if vertex.index == indx:
+                    c=face.col[j]
+                j=j+1
+        return c
+
+    def meshToString(self,mesh):
+        print "Debug: mesh.hasVertexUV=%d" % mesh.hasVertexUV()
+        print "Debug: mesh.hasFaceUV=%d" % mesh.hasFaceUV()
+        print "Debug: mesh.hasVertexColours=%d" % mesh.hasVertexColours()
+        print "Debug: mesh.verts=%d" % len(mesh.verts)
+        print "Debug: mesh.faces=%d" % len(mesh.faces)
+        print "Debug: mesh.materials=%d" % len(mesh.materials)
+
+    def rgbToFS(self, c):
+        s="%s %s %s" % (
+            round(c.r/255.0,self.cp),
+            round(c.g/255.0,self.cp),
+            round(c.b/255.0,self.cp))
+        return s
+
+    def computeDirection(self, object):
+        x,y,z=(0,-1.0,0) # point down
+        ax,ay,az = (object.RotX,object.RotZ,object.RotY)
+
+        # rot X
+        x1=x
+        y1=y*math.cos(ax)-z*math.sin(ax)
+        z1=y*math.sin(ax)+z*math.cos(ax)
+
+        # rot Y
+        x2=x1*math.cos(ay)+z1*math.sin(ay)
+        y2=y1
+        z2=z1*math.cos(ay)-x1*math.sin(ay)
+
+        # rot Z
+        x3=x2*math.cos(az)-y2*math.sin(az)
+        y3=x2*math.sin(az)+y2*math.cos(az)
+        z3=z2
+
+        return [x3,y3,z3]
+        
+
+    # swap Y and Z to handle axis difference between Blender and VRML
+    #------------------------------------------------------------------------
+    def rotatePointForVRML(self, v):
+        x = v[0]
+        y = v[2]
+        z = -v[1]
+        
+        vrmlPoint=[x, y, z]
+        return vrmlPoint
+    
+    def rotVertex(self, mm, v):
+        lx,ly,lz=v[0],v[1],v[2]
+        gx=(mm[0][0]*lx + mm[1][0]*ly + mm[2][0]*lz) + mm[3][0]
+        gy=((mm[0][2]*lx + mm[1][2]*ly+ mm[2][2]*lz) + mm[3][2])
+        gz=-((mm[0][1]*lx + mm[1][1]*ly + mm[2][1]*lz) + mm[3][1])
+        rotatedv=[gx,gy,gz]
+        return rotatedv
+
+    # For writing well formed VRML code
+    #------------------------------------------------------------------------
+    def writeIndented(self, s, inc=0):
+        if inc < 1:
+            self.indentLevel = self.indentLevel + inc
+
+        spaces=""
+        for x in xrange(self.indentLevel):
+            spaces = spaces + "\t"
+        self.file.write(spaces + s)
+
+        if inc > 0:
+            self.indentLevel = self.indentLevel + inc
+
+    # Converts a Euler to three new Quaternions
+    # Angles of Euler are passed in as radians
+    #------------------------------------------------------------------------
+    def eulerToQuaternions(self, x, y, z):
+        Qx = [math.cos(x/2), math.sin(x/2), 0, 0]
+        Qy = [math.cos(y/2), 0, math.sin(y/2), 0]
+        Qz = [math.cos(z/2), 0, 0, math.sin(z/2)]
+        
+        quaternionVec=[Qx,Qy,Qz]
+        return quaternionVec
+    
+    # Multiply two Quaternions together to get a new Quaternion
+    #------------------------------------------------------------------------
+    def multiplyQuaternions(self, Q1, Q2):
+        result = [((Q1[0] * Q2[0]) - (Q1[1] * Q2[1]) - (Q1[2] * Q2[2]) - (Q1[3] * Q2[3])),
+                  ((Q1[0] * Q2[1]) + (Q1[1] * Q2[0]) + (Q1[2] * Q2[3]) - (Q1[3] * Q2[2])),
+                  ((Q1[0] * Q2[2]) + (Q1[2] * Q2[0]) + (Q1[3] * Q2[1]) - (Q1[1] * Q2[3])),
+                  ((Q1[0] * Q2[3]) + (Q1[3] * Q2[0]) + (Q1[1] * Q2[2]) - (Q1[2] * Q2[1]))]
+        
+        return result
+    
+    # Convert a Quaternion to an Angle Axis (ax, ay, az, angle)
+    # angle is in radians
+    #------------------------------------------------------------------------
+    def quaternionToAngleAxis(self, Qf):
+        scale = math.pow(Qf[1],2) + math.pow(Qf[2],2) + math.pow(Qf[3],2)
+        ax = Qf[1]
+        ay = Qf[2]
+        az = Qf[3]
+
+        if scale > .0001:
+            ax/=scale
+            ay/=scale
+            az/=scale
+        
+        angle = 2 * math.acos(Qf[0])
+        
+        result = [ax, ay, az, angle]
+        return result
+
+##########################################################
+# Callbacks, needed before Main
+##########################################################
+
+def select_file(filename):
+  if pytinst == 1:
+    if exists(filename) and _safeOverwrite:
+      result = Draw.PupMenu("File Already Exists, Overwrite?%t|Yes%x1|No%x0")
+      if(result != 1):
+        return
+
+  if not filename.endswith('.wrl'): filename += '.wrl'
+  wrlexport=VRML2Export(filename)
+  wrlexport.export(scene, world, worldmat)
+
+def createWRLPath():
+  filename = Blender.Get('filename')
+  #print filename
+  
+  if filename.find('.') != -1:
+    filename = filename.split('.')[0]
+    filename += ".wrl"
+    #print filename
+
+  return filename
+
+#########################################################
+# main routine
+#########################################################
+
+try:
+    ARG = __script__['arg'] # user selected argument
+except:
+    print "older version"
+
+if Blender.Get('version') < 235:
+    print "Warning: VRML97 export failed, wrong blender version!"
+    print " You aren't running blender version 2.35 or greater"
+    print " download a newer version from http://blender.org/"
+else:
+    Blender.Window.FileSelector(select_file,"Export VRML97",createWRLPath())
index 40dd8543762c1449467cda13fca3f2b8de91d4c8..e71d06b41415aa046899ee486c27f0663a7d300e 100644 (file)
@@ -94,6 +94,8 @@ static int bpymenu_group_atoi( char *str )
                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" ) )
@@ -141,6 +143,9 @@ char *BPyMenu_group_itoa( short menugroup )
        case PYMENU_RENDER:
                return "Render";
                break;
+       case PYMENU_SYSTEM:
+               return "System";
+               break;
        case PYMENU_OBJECT:
                return "Object";
                break;
index 52ab5e85e4ae217d77919bc638b1ccadf6c7f27a..8ef94b1ce7906b9e92099cdee7f586f7a75083fd 100644 (file)
@@ -25,7 +25,7 @@
  *
  * This is a new part of Blender.
  *
- * Contributor(s): Willian P. Germano
+ * Contributor(s): Willian P. Germano, Matt Ebb
  *
  * ***** END GPL/BL DUAL LICENSE BLOCK *****
 */
@@ -87,6 +87,7 @@ typedef enum {
        PYMENU_MISC,
        PYMENU_OBJECT,
        PYMENU_RENDER,/* exporters to external renderers */
+       PYMENU_SYSTEM,
        PYMENU_THEMES,
        PYMENU_UV,/* UV editing tools, to go in UV/Image editor space, 'UV' menu */
        PYMENU_WIZARDS,/* complex 'app' scripts */
index 988e9ac67bd4b3f18c277aeb6686330bfea5cfd5..e2f68a9a6f98572cd5ce1eab3196c38f2b89f1e7 100644 (file)
@@ -24,7 +24,8 @@
  *
  * This is a new part of Blender.
  *
- * Contributor(s): Michel Selten, Willian P. Germano, Joseph Gilbert
+ * Contributor(s): Michel Selten, Willian P. Germano, Joseph Gilbert,
+ * Campbell Barton
  *
  * ***** END GPL/BL DUAL LICENSE BLOCK *****
 */
@@ -66,6 +67,8 @@
 #include "../BPY_extern.h" /* BPY_txt_do_python_Text */
 #include "../BPY_menus.h"      /* to update menus */
 
+extern PyObject *bpy_registryDict; /* defined in ../BPY_interface.c */
+
 /**********************************************************/
 /* Python API function prototypes for the Blender module.      */
 /**********************************************************/
@@ -76,6 +79,7 @@ static PyObject *Blender_Quit( PyObject * self );
 static PyObject *Blender_Load( PyObject * self, PyObject * args );
 static PyObject *Blender_Save( PyObject * self, PyObject * args );
 static PyObject *Blender_Run( PyObject * self, PyObject * args );
+static PyObject *Blender_ShowHelp( PyObject * self, PyObject * args );
 static PyObject *Blender_UpdateMenus( PyObject * self);
 
 extern PyObject *Text3d_Init( void ); /* missing in some include */
@@ -137,6 +141,15 @@ static char Blender_Run_doc[] =
        "(script) - Run the given Python script.\n\
 (script) - the path to a file or the name of an available Blender Text.";
 
+static char Blender_ShowHelp_doc[] =
+"(script) - Show help for the given Python script.\n\
+  This will try to open the 'Scripts Help Browser' script, so to have\n\
+any help displayed the passed 'script' must be properly documented\n\
+with the expected strings (check API ref docs or any bundled script\n\
+for examples).\n\n\
+(script) - the filename of a script in the default or user defined\n\
+           scripts dir (no need to supply the full path name)."; 
+
 static char Blender_UpdateMenus_doc[] =
        "() - Update the menus where scripts are registered.  Only needed for\n\
 scripts that save other new scripts in the default or user defined folders.";
@@ -152,6 +165,7 @@ static struct PyMethodDef Blender_methods[] = {
        {"Load", Blender_Load, METH_VARARGS, Blender_Load_doc},
        {"Save", Blender_Save, METH_VARARGS, Blender_Save_doc},
        {"Run", Blender_Run, METH_VARARGS, Blender_Run_doc},
+       {"ShowHelp", Blender_ShowHelp, METH_VARARGS, Blender_ShowHelp_doc},
        {"UpdateMenus", ( PyCFunction ) Blender_UpdateMenus, METH_NOARGS,
         Blender_UpdateMenus_doc},
        {NULL, NULL, 0, NULL}
@@ -272,6 +286,110 @@ static PyObject *Blender_Get( PyObject * self, PyObject * args )
                }
                if (!ret) ret = EXPP_incr_ret(Py_None);
        }
+       /* USER PREFS: */
+       else if( StringEqual( str, "yfexportdir" ) ) {
+               if (U.yfexportdir[0] != '\0') {
+                       char yfexportdir[FILE_MAXDIR];
+
+                       BLI_strncpy(yfexportdir, U.yfexportdir, FILE_MAXDIR);
+                       BLI_convertstringcode(yfexportdir, G.sce, 0);
+
+                       if( BLI_exists( yfexportdir ) )
+                               ret = PyString_FromString( yfexportdir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }
+       /* fontsdir */
+       else if( StringEqual( str, "fontsdir" ) ) {
+               if (U.fontdir[0] != '\0') {
+                       char fontdir[FILE_MAXDIR];
+
+                       BLI_strncpy(fontdir, U.fontdir, FILE_MAXDIR);
+                       BLI_convertstringcode(fontdir, G.sce, 0);
+
+                       if( BLI_exists( fontdir ) )
+                               ret = PyString_FromString( fontdir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }       
+       /* texturesdir */
+       else if( StringEqual( str, "texturesdir" ) ) {
+               if (U.textudir[0] != '\0') {
+                       char textudir[FILE_MAXDIR];
+
+                       BLI_strncpy(textudir, U.textudir, FILE_MAXDIR);
+                       BLI_convertstringcode(textudir, G.sce, 0);
+
+                       if( BLI_exists( textudir ) )
+                               ret = PyString_FromString( textudir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }               
+       /* texpluginsdir */
+       else if( StringEqual( str, "texpluginsdir" ) ) {
+               if (U.plugtexdir[0] != '\0') {
+                       char plugtexdir[FILE_MAXDIR];
+
+                       BLI_strncpy(plugtexdir, U.plugtexdir, FILE_MAXDIR);
+                       BLI_convertstringcode(plugtexdir, G.sce, 0);
+
+                       if( BLI_exists( plugtexdir ) )
+                               ret = PyString_FromString( plugtexdir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }                       
+       /* seqpluginsdir */
+       else if( StringEqual( str, "seqpluginsdir" ) ) {
+               if (U.plugseqdir[0] != '\0') {
+                       char plugseqdir[FILE_MAXDIR];
+
+                       BLI_strncpy(plugseqdir, U.plugseqdir, FILE_MAXDIR);
+                       BLI_convertstringcode(plugseqdir, G.sce, 0);
+
+                       if( BLI_exists( plugseqdir ) )
+                               ret = PyString_FromString( plugseqdir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }                       
+       /* renderdir */
+       else if( StringEqual( str, "renderdir" ) ) {
+               if (U.renderdir[0] != '\0') {
+                       char renderdir[FILE_MAXDIR];
+
+                       BLI_strncpy(renderdir, U.renderdir, FILE_MAXDIR);
+                       BLI_convertstringcode(renderdir, G.sce, 0);
+
+                       if( BLI_exists( renderdir ) )
+                               ret = PyString_FromString( renderdir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }               
+       /* soundsdir */
+       else if( StringEqual( str, "soundsdir" ) ) {
+               if (U.sounddir[0] != '\0') {
+                       char sounddir[FILE_MAXDIR];
+
+                       BLI_strncpy(sounddir, U.sounddir, FILE_MAXDIR);
+                       BLI_convertstringcode(sounddir, G.sce, 0);
+
+                       if( BLI_exists( sounddir ) )
+                               ret = PyString_FromString( sounddir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }               
+       /* tempdir */
+       else if( StringEqual( str, "tempdir" ) ) {
+               if (U.tempdir[0] != '\0') {
+                       char tempdir[FILE_MAXDIR];
+
+                       BLI_strncpy(tempdir, U.tempdir, FILE_MAXDIR);
+                       BLI_convertstringcode(tempdir, G.sce, 0);
+
+                       if( BLI_exists( tempdir ) )
+                               ret = PyString_FromString( tempdir );
+               }
+               if (!ret) ret = EXPP_incr_ret(Py_None);
+       }       
        /* According to the old file (opy_blender.c), the following if
           statement is a quick hack and needs some clean up. */
        else if( StringEqual( str, "vrmloptions" ) ) {
@@ -493,11 +611,56 @@ static PyObject *Blender_Save( PyObject * self, PyObject * args )
        return Py_None;
 }
 
+static PyObject *Blender_ShowHelp(PyObject *self, PyObject *args)
+{
+       PyObject *script = NULL;
+       char hspath[FILE_MAXDIR + FILE_MAXFILE]; /* path to help_browser.py */
+       char *sdir = bpy_gethome(1);
+       PyObject *rkeyd = NULL, *arglist = NULL;
+
+       if (!PyArg_ParseTuple(args, "O!", &PyString_Type, &script))
+               return EXPP_ReturnPyObjError(PyExc_TypeError,
+                       "expected a script filename as argument");
+
+       /* first we try to find the help_browser script */
+
+       if (sdir) BLI_make_file_string("/", hspath, sdir, "help_browser.py");
+
+       if (!sdir || (!BLI_exists(hspath) && (U.pythondir[0] != '\0'))) {
+                       char upydir[FILE_MAXDIR];
+
+                       BLI_strncpy(upydir, U.pythondir, FILE_MAXDIR);
+                       BLI_convertstringcode(upydir, G.sce, 0);
+                       BLI_make_file_string("/", hspath, upydir, "help_browser.py");
+
+                       if (!BLI_exists(hspath))
+                               return EXPP_ReturnPyObjError(PyExc_RuntimeError,
+                                       "can't find script help_browser.py");
+       }
+
+       /* now we store the passed script in the registry dict and call the
+        * help_browser to show help info for it */
+       rkeyd = PyDict_New();
+       if (!rkeyd)
+               return EXPP_ReturnPyObjError(PyExc_MemoryError,
+                       "can't create py dictionary!");
+
+       PyDict_SetItemString(rkeyd, "script", script);
+       PyDict_SetItemString(bpy_registryDict, "__help_browser", rkeyd);
+
+       arglist = Py_BuildValue("(s)", hspath);
+       Blender_Run(self, arglist);
+       Py_DECREF(arglist);
+
+       return EXPP_incr_ret(Py_None);
+}
+
 static PyObject *Blender_Run(PyObject *self, PyObject *args)
 {
        char *fname = NULL;
        Text *text = NULL;
        int is_blender_text = 0;
+       Script *script = NULL;
 
        if (!PyArg_ParseTuple(args, "s", &fname))
                return EXPP_ReturnPyObjError(PyExc_TypeError,
@@ -527,7 +690,28 @@ static PyObject *Blender_Run(PyObject *self, PyObject *args)
                }
        }
 
-       BPY_txt_do_python_Text(text);
+       /* (this is messy, check Draw.c's Method_Register and Window.c's file
+        * selector for more info)
+        * - caller script is the one that called this Blender_Run function;
+        * - called script is the argument to this function: fname;
+        * To mark scripts whose global dicts can't be freed right after
+        * the script execution (or better, 'first pass', since these scripts
+        * leave callbacks for gui or file/image selectors) we flag them.  But to
+        * get a pointer to them we need to check which one is currently
+        * running (if none we're already at a spacescript).  To make sure only
+        * the called script will have the SCRIPT_RUNNING flag on, we unset it
+        * for the caller script here: */
+       script = G.main->script.first;
+       while (script) {
+               if (script->flags & SCRIPT_RUNNING) break;
+               script = script->id.next;
+       }
+
+       if (script) script->flags &= ~SCRIPT_RUNNING; /* unset */
+
+       BPY_txt_do_python_Text(text); /* call new script */
+
+       if (script) script->flags |= SCRIPT_RUNNING; /* set */
 
        if (!is_blender_text) free_libblock(&G.main->text, text);
 
index 4be155db94458be9dd50bb7be30eebe41960e960..62d1f4e799f4e8b3e090829d587da5d83ce04117 100644 (file)
@@ -65,6 +65,7 @@ char *bpy_gethome(int append_scriptsdir)
 {
        static char homedir[FILE_MAXDIR];
        static char scriptsdir[FILE_MAXDIR];
+       char tmpdir[FILE_MAXDIR];
        char bprogdir[FILE_MAXDIR];
        char *s;
        int i;
@@ -91,20 +92,37 @@ char *bpy_gethome(int append_scriptsdir)
                }
                else return homedir;
        }
+       else homedir[0] = '\0';
 
-       /* otherwise, use argv[0] (bprogname) to get .blender/ in
+       /* if either:
+        * no homedir was found or
+        * append_scriptsdir = 1 but there's no scripts/ inside homedir,
+        * use argv[0] (bprogname) to get .blender/ in
         * Blender's installation dir */
        s = BLI_last_slash( bprogname );
 
        i = s - bprogname + 1;
 
-       PyOS_snprintf( bprogdir, i, bprogname );
-       BLI_make_file_string( "/", homedir, bprogdir, ".blender" );
+       PyOS_snprintf( bprogdir, i, "%s", bprogname );
 
-       if (BLI_exists(homedir)) {
+       /* using tmpdir to preserve homedir (if) found above:
+        * the ideal is to have a home dir with scripts dir inside
+        * it, but if that isn't available, it's possible to
+        * have a 'broken' home dir somewhere and a scripts dir in the
+        * cvs sources */
+       BLI_make_file_string( "/", tmpdir, bprogdir, ".blender" );
+
+       if (BLI_exists(tmpdir)) {
                if (append_scriptsdir) {
-                       BLI_make_file_string("/", scriptsdir, homedir, "scripts");
-                       if (BLI_exists(scriptsdir)) return scriptsdir;
+                       BLI_make_file_string("/", scriptsdir, tmpdir, "scripts");
+                       if (BLI_exists(scriptsdir)) {
+                               PyOS_snprintf(homedir, FILE_MAXDIR, "%s", tmpdir);
+                               return scriptsdir;
+                       }
+                       else {
+                               homedir[0] = '\0';
+                               scriptsdir[0] = '\0';
+                       }
                }
                else return homedir;
        }
@@ -113,6 +131,7 @@ char *bpy_gethome(int append_scriptsdir)
        if (append_scriptsdir) {
                BLI_make_file_string("/", scriptsdir, bprogdir, "release/scripts");
                if (BLI_exists(scriptsdir)) return scriptsdir;
+               else scriptsdir[0] = '\0';
        }
 
        return NULL;
index f2bf3aa31e417efdc4ad9ae43e77acf06293fbab..2e6f2ca2601ba121d4e676ebf50630ca21e3043f 100644 (file)
@@ -2796,7 +2796,7 @@ static PyObject *M_NMesh_PutRaw( PyObject * self, PyObject * args )
                ob = add_object( OB_MESH );
                if( !ob ) {
                        PyErr_SetString( PyExc_RuntimeError,
-                                        "Fatal: could not create mesh object" );
+                               "Fatal: could not create mesh object" );
                        return 0;
                }
 
@@ -2824,6 +2824,11 @@ static PyObject *M_NMesh_PutRaw( PyObject * self, PyObject * args )
        if( !during_script(  ) )
                EXPP_allqueue( REDRAWVIEW3D, 0 );
 
+       if (ob && G.obedit) { /* prevents a crash when a new object is created */
+               exit_editmode(1);
+               enter_editmode();
+       }
+
        // @OK...this requires some explanation:
        // Materials can be assigned two ways:
        // a) to the object data (in this case, the mesh)
index e0ba81b1029d2e872d28ecde7ef66d2431c3735e..bf839630aa2925fc22215a46a2e4d619af718ac1 100644 (file)
 #include "Registry.h"
 
 #include <stdio.h>
+#include <BKE_global.h> /* G.f & G_DEBUG */
 
 #include "gen_utils.h"
 
 
-
 /* the Registry dictionary */
 PyObject *bpy_registryDict = NULL;
 
@@ -64,17 +64,23 @@ char M_Registry_Keys_doc[] =
     Each key references another dict with saved data from a specific script.\n";
 
 char M_Registry_GetKey_doc[] =
-       "(name) - Get a specific entry (dict) from the Registry dictionary\n\
- (name) - a string that references a specific script.\n";
+       "(name, disk = False) - Get an entry (a dict) from the Registry dictionary\n\
+ (name) - a string that references a specific script;\n\
+ (disk = False) - search on the user (if available) or default scripts config\n\
+data dir.\n";
 
 char M_Registry_SetKey_doc[] =
-       "(key, dict) - Store an entry in the Registry dictionary.\n\
+       "(key, dict, disk = False) - Store an entry in the Registry dictionary.\n\
     If an entry with the same 'key' already exists, it is substituted.\n\
  (key) - the string to use as a key for the dict being saved.\n\
- (dict) - a dictionary with the data to be stored.\n";
+ (dict) - a dictionary with the data to be stored.\n\
+ (disk = False) - also write data as a config file inside the user (if\n\
+available) or default scripts config data dir.\n";
 
 char M_Registry_RemoveKey_doc[] =
-       "(key) - Remove the dict with key 'key' from the Registry.\n";
+       "(key, disk = False) - Remove the dict with key 'key' from the Registry.\n\
+ (key) - the name of the key to delete;\n\
+ (disk = False) - if True the respective config file is also deleted.\n";
 
 /*****************************************************************************/
 /* Python method structure definition for Blender.Registry module:           */
@@ -118,25 +124,35 @@ static PyObject *M_Registry_GetKey( PyObject * self, PyObject * args )
 {
        PyObject *pyentry = NULL;
        PyObject *pydict = NULL;
+       int disk = 0;
 
        if( !bpy_registryDict )
                return EXPP_ReturnPyObjError( PyExc_RuntimeError,
-                                             "No Registry dictionary found!" );
+                       "No Registry dictionary found!" );
 
-       if( !PyArg_ParseTuple( args, "O!", &PyString_Type, &pyentry ) )
+       if( !PyArg_ParseTuple( args, "O!|i", &PyString_Type, &pyentry, &disk ) )
                return EXPP_ReturnPyObjError( PyExc_AttributeError,
-                                             "expected a string" );
+                       "expected a string and optionally a bool" );
 
        pydict = PyDict_GetItem( bpy_registryDict, pyentry );   /* borrowed ... */
 
-       if( !pydict )
-/*    return EXPP_ReturnPyObjError (PyExc_KeyError,
-            "no such key in the Registry"); */
-               pydict = Py_None;       /* better to return None than an error */
-
-       Py_INCREF( pydict );    /* ... so we incref it */
-       /* should we copy the dict instead? */
-       return pydict;
+       if (!pydict) {
+               if (disk > 0) {
+                       /* try to get data from disk */
+                       char buf[256];
+                       PyOS_snprintf(buf, sizeof(buf),
+                               "import Blender, BPyRegistry; BPyRegistry.LoadConfigData('%s')",
+                               PyString_AsString(pyentry));
+                       if (!PyRun_SimpleString(buf))
+                               pydict = PyDict_GetItem(bpy_registryDict, pyentry);
+                       else PyErr_Clear();
+               }
+
+               if (!pydict) /* no need to return a KeyError, since without doubt */
+                       pydict = Py_None; /* Py_None means no key (all valid keys are dicts) */
+       }
+
+       return EXPP_incr_ret (pydict); /* ... so we incref it */
 }
 
 /*****************************************************************************/
@@ -147,14 +163,15 @@ static PyObject *M_Registry_SetKey( PyObject * self, PyObject * args )
 {
        PyObject *pystr = NULL;
        PyObject *pydict = NULL;
+       int disk = 0;
 
        if( !bpy_registryDict )
                return EXPP_ReturnPyObjError( PyExc_RuntimeError,
                                              "No Registry dictionary found!" );
 
-       if( !PyArg_ParseTuple( args, "O!O!",
+       if( !PyArg_ParseTuple( args, "O!O!|i",
                               &PyString_Type, &pystr, &PyDict_Type,
-                              &pydict ) )
+                              &pydict, &disk ) )
                return EXPP_ReturnPyObjError( PyExc_AttributeError,
                                              "expected a string and a dictionary" );
 
@@ -162,6 +179,19 @@ static PyObject *M_Registry_SetKey( PyObject * self, PyObject * args )
                return EXPP_ReturnPyObjError( PyExc_RuntimeError,
                                              "Registry_SetKey: couldn't update the Registry dict" );
 
+       if (disk) {
+               /* try to save data to disk */
+               char buf[256];
+               PyOS_snprintf(buf, sizeof(buf),
+                       "import Blender, BPyRegistry; BPyRegistry.SaveConfigData('%s')",
+                       PyString_AsString(pystr));
+               if (PyRun_SimpleString(buf) != 0) {
+                       PyErr_Clear();
+                       if (G.f & G_DEBUG)
+                               fprintf(stderr, "\nCan't save script configuration data!\n");
+               }
+       }
+
        Py_INCREF( Py_None );
        return Py_None;
 }
@@ -173,18 +203,31 @@ static PyObject *M_Registry_SetKey( PyObject * self, PyObject * args )
 static PyObject *M_Registry_RemoveKey( PyObject * self, PyObject * args )
 {
        PyObject *pystr = NULL;
+       int disk = 0;
 
        if( !bpy_registryDict )
                return EXPP_ReturnPyObjError( PyExc_RuntimeError,
                                              "No Registry dictionary found!" );
 
-       if( !PyArg_ParseTuple( args, "O!", &PyString_Type, &pystr ) )
+       if( !PyArg_ParseTuple( args, "O!|i", &PyString_Type, &pystr, &disk ) )
                return EXPP_ReturnPyObjError( PyExc_AttributeError,
-                                             "expected a string" );
+                                             "expected a string and optionally a bool" );
 
        if( PyDict_DelItem( bpy_registryDict, pystr ) ) /* returns 0 on success */
                return EXPP_ReturnPyObjError( PyExc_KeyError,
                                              "no such key in the Registry" );
+       else if (disk) {
+               /* try to delete from disk too */
+               char buf[256];
+               PyOS_snprintf(buf, sizeof(buf),
+                       "import Blender, BPyRegistry; BPyRegistry.RemoveConfigData('%s')",
+                       PyString_AsString(pystr));
+               if (PyRun_SimpleString(buf) != 0) {
+                       PyErr_Clear();
+                       if (G.f & G_DEBUG)
+                               fprintf(stderr, "\nCan't remove script configuration data file!\n");
+               }
+       }
 
        Py_INCREF( Py_None );
        return Py_None;
index 5af539ac881fabfec880a503d17076224fb9a020..64bdc49bf79b6bd6274454ec09d91cd751a957e4 100644 (file)
  *
  * This is a new part of Blender.
  *
- * Contributor(s): Willian P. Germano
+ * Contributor(s): Willian P. Germano, Campbell Barton
  *
  * ***** END GPL/BL DUAL LICENSE BLOCK *****
 */
 
 #include <BKE_utildefines.h>
 #include <BLI_blenlib.h>
+#include <DNA_scene_types.h> /* G.scene->r.cfra */
 #include <PIL_time.h>
 #include <Python.h>
 #include <sys/stat.h>
@@ -47,10 +48,11 @@ static PyObject *M_sys_dirname( PyObject * self, PyObject * args );
 static PyObject *M_sys_join( PyObject * self, PyObject * args );
 static PyObject *M_sys_splitext( PyObject * self, PyObject * args );
 static PyObject *M_sys_makename( PyObject * self, PyObject * args,
-                                PyObject * kw );
+       PyObject * kw );
 static PyObject *M_sys_exists( PyObject * self, PyObject * args );
 static PyObject *M_sys_time( PyObject * self );
 static PyObject *M_sys_sleep( PyObject * self, PyObject * args );
+static PyObject *M_sys_expandpath( PyObject *self, PyObject *args);
 
 /*****************************************************************************/
 /* The following string definitions are used for documentation strings.      */
@@ -108,6 +110,15 @@ The return value is as follows:\n\
 \t 2: path is an existing dirname;\n\
 \t-1: path exists but is neither a regular file nor a dir.";
 
+static char M_sys_expandpath_doc[] =
+"(path) - Expand this Blender internal path to a proper file system path.\n\
+(path) - the string path to convert.\n\n\
+Note: internally Blender paths can contain two special character sequences:\n\
+- '//' (at start) for base path directory (the current .blend's dir path);\n\
+- '#' (at ending) for current frame number.\n\n\
+This function expands these to their actual content, returning a valid path.\n\
+If the special chars are not found in the given path, it is simply returned.";
+
 /*****************************************************************************/
 /* Python method structure definition for Blender.sys module:                */
 /*****************************************************************************/
@@ -122,6 +133,7 @@ struct PyMethodDef M_sys_methods[] = {
        {"exists", M_sys_exists, METH_VARARGS, M_sys_exists_doc},
        {"sleep", M_sys_sleep, METH_VARARGS, M_sys_sleep_doc},
        {"time", ( PyCFunction ) M_sys_time, METH_NOARGS, M_sys_time_doc},
+       {"expandpath", M_sys_expandpath, METH_VARARGS, M_sys_expandpath_doc},
        {NULL, NULL, 0, NULL}
 };
 
@@ -395,3 +407,18 @@ static PyObject *M_sys_exists( PyObject * self, PyObject * args )
 
        return Py_BuildValue( "i", i );
 }
+
+static PyObject *M_sys_expandpath( PyObject * self, PyObject * args )
+{
+       char *path = NULL;
+       char expanded[FILE_MAXDIR + FILE_MAXFILE];
+
+       if (!PyArg_ParseTuple( args, "s", &path))
+               return EXPP_ReturnPyObjError( PyExc_TypeError,
+                       "expected string argument" );
+
+       BLI_strncpy(expanded, path, FILE_MAXDIR + FILE_MAXFILE);
+       BLI_convertstringcode(expanded, G.sce, G.scene->r.cfra);
+
+       return PyString_FromString(expanded);
+}
index 8f4031756f20c3dec3fa1b5e87d67ccae4f875eb..ce2b7d8fc9340edee45d98be60e3e1049c7f5efd 100644 (file)
@@ -40,9 +40,16 @@ The Blender Python API Reference
   - L{Texture}
   - L{Types}
   - L{Window}
-     - L{Theme} (new)
+     - L{Theme}
   - L{World}
-  - L{sys<Sys>}
+  - L{sys<Sys>} (*)
+
+ Additional information:
+ -----------------------
+
+  - L{Misc facilities<API_related>}:
+    - scripts: registering in menus, documenting, configuring (new);
+    - command line examples (new).
 
  (*) - marks updated.
 
@@ -91,57 +98,6 @@ These are the basic ways to execute scripts in Blender:
  6. A script can call another script (that will run in its own context, with
  its own global dictionary) with the L{Blender.Run} module function.
 
-Registering scripts:
---------------------
- To be registered a script needs two things:
-   - be either in the default scripts dir or in the user defined scripts path
-     (see Info window, paths tab);
-   - have a proper header.
-
- Try 'blender -d' to know where your default dir for scripts is, it will
- inform either the dir or the file with that info already parsed, which is
- in the same dir of the scripts folder.
-
- The header should be like this one (all double and single apostrophes below
- are required)::
-  #!BPY
-
-  # \"\"\"
-  # Name: 'Script Name'
-  # Blender: 233
-  # Group: 'Export'
-  # Submenu: 'All' all
-  # Submenu: 'Selected' sel
-  # Submenu: 'Configure (gui)' gui
-  # Tooltip: 'Export to some format.'
-  # \"\"\"
-
- where:
-  - B{Name} is the string that will appear in the menu;
-  - B{Blender} is the minimum program version required to run the script;
-  - B{Group} defines where the script will be put, see all groups in the
-    Scripts Window's header, menu "Scripts";
-  - B{Submenu} adds optional submenus for further control;
-  - B{Tooltip} is the (short) tooltip string for the menu entry.
-
- note:
-  - all double and single apostrophes above are required;
-  - B{*NEW*}: you can "comment out" the header above, by starting lines with
-    '#', like we did.  This is not required (except for the first line, #!BPY,
-    of course), but this way the header won't conflict with Python tools that
-    you can use to generate documentation for your script code.  Just
-    remember to keep this header above any other line with triple
-    double-quotes (\"\"\") in your script.
-
- Submenu lines are not required, use them if you want to provide extra
- options.  To see which submenu the user chose, check the "__script__"
- dictionary in your code: __script__['arg'] has the defined keyword (the word
- after the submenu string name: all, sel or gui in the example above) of the
- chosen submenu.  For example, if the user clicked on submenu 'Selected' above,
- __script__['arg'] will be "sel".
-
- If your script requires extra data or configuration files, there is a special
- folder where they can be saved: see 'datadir' in L{Blender.Get}.
 
 Interaction with users:
 -----------------------
@@ -171,53 +127,22 @@ Command line mode:
  run scripts from the program itself: you can't import the Blender module
  into an external Python interpreter.
 
- But with "OnLoad" script links, the "-b" background mode and additions like
- the "-P" command line switch, L{Blender.Save}, L{Blender.Load},
- L{Blender.Quit} and the L{Library} module, for many tasks it's possible to
- control Blender via some automated process using scripts.  Note that command
- line scripts are run before Blender initializes its windows (and in '-b' mode
- no window will be initialized), so many functions that get or set window
- related attributes (like most in L{Window}) don't work here.  If you need
- those, use an ONLOAD script link (see L{Scene.Scene.addScriptLink}) instead --
- it's also possible to use a command line script to write or set an ONLOAD
- script link.  Check the L{Blender.mode} module var to know if Blender is being
- executed in "background" or "interactive" mode.
-
- Background mode examples::
-
-  # Open Blender in background mode with file 'myfile.blend'
-  # and run the script 'script.py':
-
-  blender -b myfile.blend -P script.py
-
-  # Note: a .blend file is always required.  'script.py' can be a file
-  # in the file system or a Blender Text stored in 'myfile.blend'.
+ On the other hand, for many tasks it's possible to control Blender via
+ some automated process using scripts.  Interested readers should learn about
+ features like "OnLoad" script links, the "-b <blendfile>" (background mode)
+ and "-P <script>" (run script) command line options and API calls like
+ L{Blender.Save}, L{Blender.Load}, L{Blender.Quit} and the L{Library} and
+ L{Render} modules. 
 
-  # Let's assume 'script.py' has code to render the current frame;
-  # this line will set the [s]tart and [e]nd (and so the current) frame to
-  # frame 44 and call the script:
+ Note that command line scripts are run before Blender initializes its windows
+ (and in '-b' mode no window will be initialized), so many functions that get
+ or set window related attributes (like most in L{Window}) don't work here.  If
+ you need those, use an ONLOAD script link (see L{Scene.Scene.addScriptLink})
+ instead -- it's also possible to use a command line script to write or set an
+ ONLOAD script link.  Check the L{Blender.mode} module var to know if Blender
+ is being executed in "background" or "interactive" mode.
 
-  blender -b myfile.blend -s 44 -e 44 -P script.py
-
-  # Using now a script written to render animations, we set different
-  # start and end frames and then execute this line:
-
-  blender -b myfile.blend -s 1 -e 10 -P script.py
-
-  # Note: we can also set frames and define if we want a single image or
-  # an animation in the script body itself, naturally.
-
- The rendered pictures will be written to the default render folder, that can
- also be set via bpython (take a look at L{Render.RenderData}).  Their
- names will be the equivalent frame number followed by the extension of the
- chosen image type: 0001.png, for example.  To rename them to something else,
- coders can use the C{rename} function in the standard 'os' Python module.
-
- Reminder: if you just need to render, it's not necessary to have a script.
- Blender can create stills and animations with its own command line arguments.
- Example:
-  - a single image at frame 44: blender -b myfile.blend -f 44
-  - an animation from frame 1 to 10: blender -b myfile.blend -s 1 -e 10 -a
+ L{Click here for command line and background mode examples<API_related>}.
 
 
 Demo mode:
@@ -246,7 +171,7 @@ Blender Data Structures:
  Programs manipulate data structures.  Blender python scripts are no exception.
  Blender uses an Object Oriented architecture.  The bpython interface tries to
  present Blender objects and their attributes in the same way you see them
- through the User Interface ( the GUI ).  One key to bpython programming is
+ through the User Interface (the GUI).  One key to bpython programming is
  understanding the information presented in Blender's OOPS window where Blender
  objects and their relationships are displayed.
 
@@ -268,74 +193,6 @@ Blender Data Structures:
  Blender works the way it does, see the U{Blender Architecture document
  <http://www.blender3d.org/cms/Blender_Architecture.336.0.html>}.
 
-Documenting scripts:
---------------------
-
- The "Scripts Help Browser" script in the Help menu can parse special variables
- from registered scripts and display help information for users.  For that,
- authors only need to add proper information to their scripts, after the
- registration header.
-
- The expected variables:
-
-  - __bpydoc__ (or __doc__) (type: string):
-    - The main help text.  Write a first short paragraph explaining what the
-      script does, then add the rest of the help text, leaving a blank line
-      between each new paragraph.  To force line breaks you can use <br> tags.
-
-  - __author__ (type: string or list of strings):
-    - Author name(s).
-
-  - __version__ (type: string):
-    - Script version.
-
-  - __url__ (type: string or list of strings):
-    - Internet links that are shown as buttons in the help screen.  Clicking
-      them opens the user's default browser at the specified location.  The
-      expected format for each url entry is e.g.
-      "Author's site, http://www.somewhere.com".  The first part, before the
-      comma (','), is used as the button's tooltip.  There are two preset
-      options: "blender" and "elysiun", which link to the Python forums at
-      blender.org and elysiun.com, respectively.
-
-  - __email__ (optional, type: string or list of strings):
-    - Equivalent to __url__, but opens the user's default email client.  You
-      can write the email as someone:somewhere*com and the help script will
-      substitute accordingly: someone@somewhere.com.  This is only a minor help
-      to hide emails from spammers, since your script may be available at some
-      site.  "scripts" is the available preset, with the email address of the
-      mailing list devoted to scripting in Blender, bf-scripts-dev@blender.org.
-      You should only use this one if you are subscribed to the list:
-      http://projects.blender.org/mailman/listinfo/bf-scripts-dev for more
-      information.
-
- Example::
-   __author__ = 'Mr. Author'
-   __version__ = '1.0 2005/06/06'
-   __url__ = ["Author's site, http://somewhere.com",
-       "Support forum, http://somewhere.com/forum/", "blender", "elysiun"]
-   __email__ = ["Mr. Author, mrauthor:somewhere*com", "scripts"]
-   __bpydoc__ = \"\"\"\\
-   This script does this and that.
-
-   Explaining better, this script helps you create ...
-
-   You can write as many paragraphs as needed.
-
-   Shortcuts:<br>
-     Esc or Q: quit.<br>
-     etc.
-
-   Supported:<br>
-     Meshes, metaballs.
-
-   Known issues:<br>
-     This is just an example, there's no actual script.
-
-   Notes:<br>
-     You can check scripts bundled with Blender to see more examples of how to
-    add documentation to your own works.
- \"\"\"
 
 A note to newbie script writers:
 --------------------------------
@@ -348,8 +205,8 @@ A note to newbie script writers:
  to get an idea of what can be done, you may be surprised.
 
 @author: The Blender Python Team
-@requires: Blender 2.35 or newer.
-@version: 2.35 - 2.36
+@requires: Blender 2.36 cvs or newer.
+@version: 2.36 cvs
 @see: U{www.blender3d.org<http://www.blender3d.org>}: main site
 @see: U{www.blender.org<http://www.blender.org>}: documentation and forum
 @see: U{www.elysiun.com<http://www.elysiun.com>}: user forum
diff --git a/source/blender/python/api2_2x/doc/API_related.py b/source/blender/python/api2_2x/doc/API_related.py
new file mode 100644 (file)
index 0000000..103a6c3
--- /dev/null
@@ -0,0 +1,377 @@
+# This is not a real module, it's simply an introductory text.
+
+"""
+Blender Python related features
+===============================
+
+ L{Back to Main Page<API_intro>}
+
+
+Introduction:
+=============
+
+ This page describes special features available to BPython scripts:
+
+   - Command line mode is accessible with the '-P' and '-b' Blender options.
+   - Registration allows scripts to become available from some pre-defined menus
+     in Blender, like Import, Export, Wizards and so on.
+   - Proper documentation data is used by the 'Scripts Help Browser' script to
+     show help information for any registered script.  Your own GUI can use
+     this facility with the L{Blender.ShowHelp} function.
+   - Configuration is for data in your script that can be tweaked according to
+     user taste or needs.  Like documentation, this is another helper
+     functionality -- you don't need to provide a GUI yourself to edit config
+     data.
+
+
+ Command line usage:
+ -------------------
+
+ B{Specifying scripts}:
+
+ The '-P' option followed either by:
+   - a script filename (full pathname if not in the same folder where you run
+     the command);
+   - the name of a Text in a .blend file (that must also be specified)
+ will open Blender and immediately run the given script.
+
+ Example::
+
+  # open Blender and execute the given script:
+  blender -P script.py
+
+ B{Passing parameters}:
+
+ To pass parameters to the script you can:
+       - write them to a file before running Blender, then make your script parse that file;
+       - set environment variables and access them with the 'os' module:
+
+ Examples with parameters being passed to the script via command line::
+
+  # execute a command like:
+
+  myvar=value blender -P script.py
+
+  # and in script.py access myvar with os.getenv
+  # (os.environ and os.setenv are also useful):
+
+  # script.py:
+  import os
+  val = os.getenv('myvar')
+
+  # To pass multiple parameters, simply write them in sequence,
+  # separated by spaces:
+
+  myvar1=value1 myvar2=value2 mystr="some string data" blender -P script.py
+
+ B{Background mode}:
+
+ In '-b' mode no windows will be opened: the program will run as a command
+ line tool able to render stills and animations and execute any working Python
+ script with complete access to loaded .blend's file contents.  Once the task
+ is completed, the program will exit.
+
+ Background mode examples::
+
+  # Open Blender in background mode with file 'myfile.blend'
+  # and run the script 'script.py':
+
+  blender -b myfile.blend -P script.py
+
+  # Note: a .blend file is always required.  'script.py' can be a file
+  # in the file system or a Blender Text stored in 'myfile.blend'.
+
+  # Let's assume 'script.py' has code to render the current frame;
+  # this line will set the [s]tart and [e]nd (and so the current) frame to
+  # frame 44 and call the script:
+
+  blender -b myfile.blend -s 44 -e 44 -P script.py
+
+  # Using now a script written to render animations, we set different
+  # start and end frames and then execute this line:
+
+  blender -b myfile.blend -s 1 -e 10 -P script.py
+
+  # Note: we can also set frames and define if we want a single image or
+  # an animation in the script body itself, naturally.
+
+ The rendered pictures will be written to the default render folder, that can
+ also be set via bpython (take a look at L{Render.RenderData}).  Their
+ names will be the equivalent frame number followed by the extension of the
+ chosen image type: 0001.png, for example.  To rename them to something else,
+ coders can use the C{rename} function in the standard 'os' Python module.
+
+ Reminder: if you just need to render, it's not necessary to have a script.
+ Blender can create stills and animations with its own command line arguments.
+ Example:
+  - a single image at frame 44: blender -b myfile.blend -f 44
+  - an animation from frame 1 to 10: blender -b myfile.blend -s 1 -e 10 -a
+
+
+ Registering scripts:
+ --------------------
+
+ To be registered a script needs two things:
+   - to be either in the default scripts dir or in the user defined scripts
+     path (see User Preferences window -> File Paths tab -> Python path);
+   - to have a proper header.
+
+ Try 'blender -d' to know where your default dir for scripts is, it will
+ inform either the dir or the file with that info already parsed, which is
+ in the same dir of the scripts folder.
+
+ The header should be like this one (all double and single apostrophes below
+ are required)::
+  #!BPY
+
+  # \"\"\"
+  # Name: 'Script Name'
+  # Blender: 233
+  # Group: 'Export'
+  # Submenu: 'All' all
+  # Submenu: 'Selected' sel
+  # Submenu: 'Configure (gui)' gui
+  # Tooltip: 'Export to some format.'
+  # \"\"\"
+
+ where:
+  - B{Name} is the string that will appear in the menu;
+  - B{Blender} is the minimum program version required to run the script;
+  - B{Group} defines where the script will be put, see all groups in the
+    Scripts Window's header, menu "Scripts";
+  - B{Submenu} adds optional submenus for further control;
+  - B{Tooltip} is the (short) tooltip string for the menu entry.
+
+ note:
+  - all double and single apostrophes above are required;
+  - you can "comment out" the header above, by starting lines with
+    '#', like we did.  This is not required (except for the first line, #!BPY,
+    of course), but this way the header won't conflict with Python tools that
+    you can use to generate documentation for your script code.  Just
+    remember to keep this header above any other line with triple
+    double-quotes (\"\"\") in your script.
+
+ Submenu lines are not required, use them if you want to provide extra
+ options.  To see which submenu the user chose, check the "__script__"
+ dictionary in your code: __script__['arg'] has the defined keyword (the word
+ after the submenu string name: all, sel or gui in the example above) of the
+ chosen submenu.  For example, if the user clicked on submenu 'Selected' above,
+ __script__['arg'] will be "sel".
+
+ If your script requires extra data or configuration files, there is a special
+ folder where they can be saved: see 'datadir' in L{Blender.Get}.
+
+
+ Documenting scripts:
+ --------------------
+
+ The "Scripts Help Browser" script in the Help menu can parse special variables
+ from registered scripts and display help information for users.  For that,
+ authors only need to add proper information to their scripts, after the
+ registration header.
+
+ The expected variables:
+
+  - __bpydoc__ (or __doc__) (type: string):
+    - The main help text.  Write a first short paragraph explaining what the
+      script does, then add the rest of the help text, leaving a blank line
+      between each new paragraph.  To force line breaks you can use <br> tags.
+
+  - __author__ (type: string or list of strings):
+    - Author name(s).
+
+  - __version__ (type: string):
+    - Script version.  A good recommendation is using a version number followed
+      by the date in the format YYYY/MM/DD: "1.0 2005/12/31".
+
+  - __url__ (type: string or list of strings):
+    - Internet links that are shown as buttons in the help screen.  Clicking
+      them opens the user's default browser at the specified location.  The
+      expected format for each url entry is e.g.
+      "Author's site, http://www.somewhere.com".  The first part, before the
+      comma (','), is used as the button's tooltip.  There are two preset
+      options: "blender" and "elysiun", which link to the Python forums at
+      blender.org and elysiun.com, respectively.
+
+  - __email__ (optional, type: string or list of strings):
+    - Equivalent to __url__, but opens the user's default email client.  You
+      can write the email as someone:somewhere*com and the help script will
+      substitute accordingly: someone@somewhere.com.  This is only a minor help
+      to hide emails from spammers, since your script may be available at some
+      site.  "scripts" is the available preset, with the email address of the
+      mailing list devoted to scripting in Blender, bf-scripts-dev@blender.org.
+      You should only use this one if you are subscribed to the list:
+      http://projects.blender.org/mailman/listinfo/bf-scripts-dev for more
+      information.
+
+ Example::
+   __author__ = 'Mr. Author'
+   __version__ = '1.0 2005/01/01'
+   __url__ = ["Author's site, http://somewhere.com",
+       "Support forum, http://somewhere.com/forum/", "blender", "elysiun"]
+   __email__ = ["Mr. Author, mrauthor:somewhere*com", "scripts"]
+   __bpydoc__ = \"\"\"\\
+   This script does this and that.
+
+   Explaining better, this script helps you create ...
+
+   You can write as many paragraphs as needed.
+
+   Shortcuts:<br>
+     Esc or Q: quit.<br>
+     etc.
+
+   Supported:<br>
+     Meshes, metaballs.
+
+   Known issues:<br>
+     This is just an example, there's no actual script.
+
+   Notes:<br>
+     You can check scripts bundled with Blender to see more examples of how to
+    add documentation to your own works.
+ \"\"\"
+
+ B{Note}: your own GUI or menu code can display documentation by calling the
+ help browser with the L{Blender.ShowHelp} function.
+
+ Configuring scripts:
+ --------------------
+
+ Configuration data is simple data used by your script (bools, ints, floats,
+ strings) to define default behaviors.
+
+ For example, an exporter might have:
+   - EXPORT_LIGHTS = False: a bool variable (True / False) to determine if it
+     should also export lights setup information;
+   - VERSION = 2.0: an int to define an specific version of the export format;
+   - TEX_DIR = "/path/to/textures": a default texture dir to prepend to all
+     exported texture filenames instead of their actual paths.
+
+ To properly handle this, script writers had to keep this information in a
+ separate config file (at L{Blender.Get}('udatadir') or, if not available,
+ L{Blender.Get}('datadir')), provide a GUI to edit it and update the file
+ whenever needed.
+
+ There are facilities in BPython now to take care of this in a simplified (and
+ much recommended) way.
+
+ The L{Registry} module functions L{GetKey<Registry.GetKey>} and
+ L{SetKey<Registry.SetKey>} take care of both keeping the data in Blender
+ and (new) storing it in config files at the proper dir.  And the 'Scripts
+ Configuration Editor' script provides a GUI for users to view and edit
+ configuration data.
+
+ Here's how it works::
+
+  # sample_exporter.py
+  import Blender
+  from Blender import Registry
+
+  # First define all config variables with their default values:
+  EXPORT_LIGHTS = True
+  VERBOSE = True
+  EXPORT_DIR = ''
+
+  # Then define a function to update the Registry:
+  def registry_update():
+    # populate a dict with current config values:
+    d = {
+      'EXPORT_LIGHTS': EXPORT_LIGHTS,
+      'VERBOSE': VERBOSE,
+      'EXPORT_DIR': EXPORT_DIR
+    }
+    # store the key (optional 3rd arg tells if
+    # the data should also be written to a file):
+    Registry.SetKey('sample_exporter', d, True)
+
+  # (A good convention is to use the script name as Registry key)
+
+  # Now we check if our key is available in the Registry or file system:
+  regdict = Registry.GetKey('sample_exporter', True)
+
+  # If this key already exists, update config variables with its values:
+  if regdict:
+    EXPORT_LIGHTS = regdict['EXPORT_LIGHTS']
+    VERBOSE = regdict['VERBOSE']
+    EXPORT_DIR = regdict['EXPORT_DIR']
+  else: # if the key doesn't exist yet, use our function to create it:
+    update_registry()
+
+  # ...
+
+ Hint: nicer code than the simplistic example above can be written by keeping
+ config var names in a list of strings and using the exec function. 
+
+ B{Note}: if you have a gui and the user uses it to change config vars,
+ call the registry_update() function to save the changes.
+ On the other hand, you don't need to handle configuration
+ in your own gui, it can be left for the 'Scripts Config Editor',
+ which should have access to your script's config key as soon as the
+ above code is executed once.
+
+ As written above, config vars can be bools, ints, floats or strings.  This is
+ what the Config Editor supports, with sensible but generous limits for the
+ number of vars and the size of each string.  Restrictions were suggested or
+ imposed to these facilities related to the Registry module because it's meant
+ for configuration info, not for large volumes of data.  For that you can
+ trivially store it in a file or Blender Text yourself -- and tell the user
+ about it, specially if your script keeps megabytes of data in the Registry
+ memory.
+
+ B{Scripts Configuration Editor}:
+
+ This script should be available from the Help menu and provides a GUI to
+ view and edit saved configuration data, both from the Registry dictionary in
+ memory and the scripts config data dir.
+
+ The example above already gives a good idea of how the information can be
+ prepared to be accessible from this editor, but there is more worth knowing:
+
+  1. String vars that end with '_dir' or '_file' (can be upper case, too) are
+  recognized as input boxes for dirs or files and a 'browse' button is added to
+  their right side, to call the file selector.
+
+  2. Both key names and configuration variables names starting with an
+  underscore ('_') are ignored by the editor.  Programmers can use this feature
+  for any key or config var that is not meant to be configured by this editor.
+
+  3. The following information refers to extra config variables that may be
+  added specifically to aid the configuration editor script.  To clarify, in the
+  example code above these variables (the string 'script' and the dictionaries
+  'tooltips' and 'limits') would appear along with EXPORT_LIGHTS, VERBOSE and
+  EXPORT_DIR, wherever they are written.
+
+  Minor note: these names are case insensitive: tooltips, TOOLTIPS, etc. are all
+  recognized.
+
+  3.1 The config editor will try to display a 'help' button for a key, to show
+  documentation for the script that owns it. To find this "owner script", it
+  will first look for a config variable called 'script', a string containing
+  the name of the owner Python file (with or without '.py' extension)::
+
+   script = 'sample_exporter.py'
+
+  If there is no such variable, the editor will check if the file formed by the
+  key name and the '.py' extension exists. If both alternatives fail, no help
+  button will be displayed.
+
+  3.2 You can define tooltips for the buttons that the editor creates for your
+  config data (string input, toggle, number sliders).  Simply create a dict
+  called 'tooltips', where config var names are keys and their tooltips,
+  values::
+
+   tooltips = {
+     'EXPORT_DIR': 'default folder where exported files should be saved',
+     'VERBOSE': 'print info and warning messages to the console',
+     'EXPORT_LIGHTS': 'export scene lighting setup'
+   }
+
+  3.3 Int and float button sliders need min and max limits.  This can be passed
+  to the editor via a dict called 'limits' (ivar1, ivar2 and fvar are meant as
+  extra config vars that might have been in the example code above)::
+
+   limits = {'ivar1': [-10, 10], 'ivar2': [0, 100], 'fvar1': [-12.3, 15.4]}
+
+ L{Back to Main Page<API_intro>}
+"""
index d22536c24a8041407a45faa8bf0959e5941616ca..1aa7f81c7038613e54ec08771b128c63fe876909 100644 (file)
@@ -10,7 +10,7 @@
 """
 The main Blender module.
 
-B{New}: L{Run}, L{UpdateMenus}, new options to L{Get}.
+B{New}: L{Run}, L{UpdateMenus}, new options to L{Get}, L{ShowHelp}.
 
 Blender
 =======
@@ -61,13 +61,23 @@ def Get (request):
             available (is None if not found), but users that define uscriptsdir
             have a place for their own scripts and script data that won't be
             erased when a new version of Blender is installed.
-      - 'scriptsdir': the path to the main dir where scripts are stored
-            (can be None, if not found).
-      - 'uscriptsdir': the path to the user defined dir for scripts, see
-            the paths tab in the User Preferences window in Blender
-            (can be None, if not found).
+      - 'scriptsdir': the path to the main dir where scripts are stored.
+      - 'uscriptsdir': the path to the user defined dir for scripts. (*)
+      - 'yfexportdir': the path to the user defined dir for yafray export. (*)
+      - 'fontsdir': the path to the user defined dir for fonts. (*)
+      - 'texturesdir': the path to the user defined dir for textures. (*)
+      - 'texpluginsdir': the path to the user defined dir for texture plugins. (*)
+      - 'seqpluginsdir': the path to the user defined dir for sequence plugins. (*)
+      - 'renderdir': the path to the user defined dir for render output. (*)
+      - 'soundsdir': the path to the user defined dir for sound files. (*)
+      - 'tempdir': the path to the user defined dir for storage of Blender
+            temporary files. (*)
       - 'version' : the Blender version number.
-  @return: The requested data.
+  @note: (*) these can be set in Blender at the User Preferences window -> File
+      Paths tab.
+  @warn: this function returns None for requested dir paths that have not been
+      set or do not exist in the user's file system.
+  @return: The requested data or None if not found.
   """
 
 def Redraw ():
@@ -122,8 +132,8 @@ def Save (filename, overwrite = 0):
       of the supported extensions or an error will be returned.
   @type overwrite: int (bool)
   @param overwrite: if non-zero, file 'filename' will be overwritten if it
-      already exists.  By default existing files are not overwritten (an error
-      is returned).
+      already exists (can be checked with L{Blender.sys.exists<Sys.exists>}.
+      By default existing files are not overwritten (an error is returned).
 
   @note: The substring ".B.blend" is not accepted inside 'filename'.
   @note: DXF, STL and Videoscape export only B{selected} meshes.
@@ -136,8 +146,28 @@ def Run (script):
   @param script: the name of an available Blender Text (use L{Text.Get}() to
       get a complete list) or the full pathname to a Python script file in the
       system.
-  @note: the script is executed in its own context (with its own global
-      dictionary), as if you had called it with ALT+P or chosen from a menu.
+  @note: the script is executed in its own context -- with its own global
+      dictionary -- as if it had been executed from the Text Editor or chosen
+      from a menu.
+  """
+
+def ShowHelp (script):
+  """
+  Show help for the given script.  This is a time-saver ("code-saver") for
+  scripts that need to feature a 'help' button in their GUI's or a 'help'
+  submenu option.  With proper documentation strings, calling this function is
+  enough to present a screen with help information plus link and email buttons.
+  @type script: string
+  @param script: the filename of a registered Python script.
+  @note: this function uses L{Run} and the "Scripts Help Browser" script.  This
+     means that it expects proper doc strings in the script to be able to show
+     help for it (otherwise it offers to load the script source code as text).
+     The necessary information about doc strings is in the
+     L{Intro page<API_intro>} of this API Reference documentation you're
+     reading.
+  @note: 'script' doesn't need to be a full path name: "filename.py" is enough.
+     Note, though, that this function only works for properly registered
+     scripts (those that appear in menus).
   """
 
 def UpdateMenus ():
index 67d9203b5f5181935d4472371aa705f35b370a97..a3c42c7cdc21d8a483d2baaca9ffe5ee31922ebe 100644 (file)
@@ -192,7 +192,23 @@ All available events:
   - ZEROKEY
   - ZKEY
 
-@note: function Button has a new alias: L{PushButton}.
+@note: function Button has an alias: L{PushButton}.
+
+@warn: B{very important}: if using your script causes "Error totblock"
+messages when Blender exits (meaning that memory has been leaked), this may
+have been caused by an ignored return value from one of the button types.  To
+avoid this, assign created buttons return values to B{global} variables,
+instead of ignoring them.  Examples::
+
+  # avoid this, it can cause memory leaks:
+  Draw.Toggle(...)
+  Draw.Number(...)
+  Draw.String(...)
+  # this is correct -- assuming the variables are globals:
+  my_toggle_button = Draw.Toggle(...)
+  my_int_button = Draw.Number(...)
+  my_str_button = Draw.String(...)
+
 
 @warn: Inside the windowing loop (after Draw.Register() has been executed and
 before Draw.Exit() is called), don't use the redraw functions from other
@@ -484,36 +500,36 @@ def Slider(name, event, x, y, width, height, initial, min, max, realtime = 1,
   @return: The Button created.
   """
 
-def Scrollbar(event, x, y, width, height, initial, min, max, realtime = 1,
-           tooltip = None):
-  """
-  Create a new Scrollbar Button object.
-  @type event: int
-  @param event: The event number to pass to the button event function when
-      activated.
-  @type x: int
-  @type y: int
-  @param x: The lower left x (horizontal) coordinate of the button.
-  @param y: The lower left y (vertical) coordinate of the button.
-  @type width: int
-  @type height: int
-  @param width: The button width.
-  @param height: The button height.
-  @type initial: int or float
-  @type min: int or float
-  @type max: int or float
-  @param initial:  The initial value.
-  @param min:  The minimum value.
-  @param max:  The maximum value.
-  @type realtime: int
-  @param realtime: If non-zero (the default), the slider will emit events as
-      it is edited.
-  @type tooltip: string
-  @param tooltip: The button's tooltip (the string that appears when the mouse
-      is kept over the button).
-  @rtype: Blender Button
-  @return: The Button created.
-  """
+#def Scrollbar(event, x, y, width, height, initial, min, max, realtime = 1,
+#           tooltip = None):
+#  """
+#  Create a new Scrollbar Button object.
+#  @type event: int
+#  @param event: The event number to pass to the button event function when
+#      activated.
+#  @type x: int
+#  @type y: int
+#  @param x: The lower left x (horizontal) coordinate of the button.
+#  @param y: The lower left y (vertical) coordinate of the button.
+#  @type width: int
+#  @type height: int
+#  @param width: The button width.
+#  @param height: The button height.
+#  @type initial: int or float
+#  @type min: int or float
+#  @type max: int or float
+#  @param initial:  The initial value.
+#  @param min:  The minimum value.
+#  @param max:  The maximum value.
+#  @type realtime: int
+#  @param realtime: If non-zero (the default), the slider will emit events as
+#      it is edited.
+#  @type tooltip: string
+#  @param tooltip: The button's tooltip (the string that appears when the mouse
+#      is kept over the button).
+#  @rtype: Blender Button
+#  @return: The Button created.
+#  """
 
 def Number(name, event, x, y, width, height, initial, min, max, tooltip = None):
   """
index cdb5beb1f197d6bdb45f40a590f0120ddbe3511a..1dc8ee4413e29d5b4e276c147688f5b80b641bb8 100644 (file)
@@ -804,11 +804,11 @@ class Property:
     """
     Set the the Object's Particle Interaction type.
     Use Module Constants
-        NONE
-        WIND
-        FORCE
-        VORTEX
-        MAGNET
+      - NONE
+      - WIND
+      - FORCE
+      - VORTEX
+      - MAGNET
     @rtype: PyNone
     @type type: int
     @param type: the Object's Particle Interaction Type.
@@ -902,7 +902,7 @@ class Property:
     Values between 0 to 50.0
     @rtype: PyNone
     @type mass: float
-    @param damp: the Object's SB New mass.
+    @param mass: the Object's SB New mass.
     """  
   
   def getSBGravity():
@@ -917,7 +917,7 @@ class Property:
     Values between 0 to 10.0
     @rtype: PyNone
     @type grav: float
-    @param damp: the Object's SB New Gravity.
+    @param grav: the Object's SB New Gravity.
     """ 
     
   def getSBFriction():
@@ -932,7 +932,7 @@ class Property:
     Values between 0 to 10.0
     @rtype: PyNone
     @type frict: float
-    @param damp: the Object's SB New Friction.
+    @param frict: the Object's SB New Friction.
     """ 
 
   def getSBErrorLimit():
@@ -947,7 +947,7 @@ class Property:
     Values between 0 to 1.0
     @rtype: PyNone
     @type err: float
-    @param damp: the Object's SB New ErrorLimit.
+    @param err: the Object's SB New ErrorLimit.
     """ 
     
   def getSBGoalSpring():
@@ -962,7 +962,7 @@ class Property:
     Values between 0 to 0.999
     @rtype: PyNone
     @type gs: float
-    @param damp: the Object's SB New GoalSpring.
+    @param gs: the Object's SB New GoalSpring.
     """ 
     
   def getSBGoalFriction():
@@ -977,7 +977,7 @@ class Property:
     Values between 0 to 10.0
     @rtype: PyNone
     @type gf: float
-    @param damp: the Object's SB New GoalFriction.
+    @param gf: the Object's SB New GoalFriction.
     """ 
     
   def getSBMinGoal():
@@ -992,7 +992,7 @@ class Property:
     Values between 0 to 1.0
     @rtype: PyNone
     @type mg: float
-    @param damp: the Object's SB New MinGoal.
+    @param mg: the Object's SB New MinGoal.
     """ 
     
   def getSBMaxGoal():
@@ -1007,7 +1007,7 @@ class Property:
     Values between 0 to 1.0
     @rtype: PyNone
     @type mg: float
-    @param damp: the Object's SB New MaxGoal.
+    @param mg: the Object's SB New MaxGoal.
     """ 
     
   def getSBInnerSpring():
@@ -1021,8 +1021,8 @@ class Property:
     Set the the Object's SB InnerSpring.
     Values between 0 to 0.999
     @rtype: PyNone
-    @type spr: float
-    @param damp: the Object's SB New InnerSpring.
+    @type sprr: float
+    @param sprr: the Object's SB New InnerSpring.
     """ 
     
   def getSBInnerSpringFriction():
@@ -1037,7 +1037,7 @@ class Property:
     Values between 0 to 10.0
     @rtype: PyNone
     @type sprf: float
-    @param damp: the Object's SB New InnerSpringFriction.
+    @param sprf: the Object's SB New InnerSpringFriction.
     """ 
     
   def getSBDefaultGoal():
@@ -1052,7 +1052,7 @@ class Property:
     Values between 0 to 1.0
     @rtype: PyNone
     @type goal: float
-    @param damp: the Object's SB New DefaultGoal.
+    @param goal: the Object's SB New DefaultGoal.
     """   
 
   def getSBEnable():
@@ -1068,7 +1068,7 @@ class Property:
     0: off
     @rtype: PyNone
     @type switch: int
-    @param damp: the Object's SB New Enable Value.
+    @param switch: the Object's SB New Enable Value.
     """  
 
   def getSBPostDef():
@@ -1084,7 +1084,7 @@ class Property:
     0: off
     @rtype: PyNone
     @type switch: int
-    @param damp: the Object's SB New PostDef Value.
+    @param switch: the Object's SB New PostDef Value.
     """ 
 
   def getSBUseGoal():
@@ -1100,7 +1100,7 @@ class Property:
     0: off
     @rtype: PyNone
     @type switch: int
-    @param damp: the Object's SB New UseGoal Value.
+    @param switch: the Object's SB New UseGoal Value.
     """ 
   def getSBUseEdges():
     """
@@ -1115,7 +1115,7 @@ class Property:
     0: off
     @rtype: PyNone
     @type switch: int
-    @param damp: the Object's SB New UseEdges Value.
+    @param switch: the Object's SB New UseEdges Value.
     """ 
     
   def getSBStiffQuads():
@@ -1131,5 +1131,5 @@ class Property:
     0: off
     @rtype: PyNone
     @type switch: int
-    @param damp: the Object's SB New StiffQuads Value.
+    @param switch: the Object's SB New StiffQuads Value.
     """     
index 390669eb37da83fc8f846f509014209527622bf9..5913505f4e42af257199399f3067848494fe21f7 100644 (file)
@@ -3,6 +3,8 @@
 """
 The Blender.Registry submodule.
 
+B{New}: L{GetKey} and L{SetKey} can respectively load and save data to disk now.
+
 Registry
 ========
 
@@ -17,19 +19,19 @@ give script authors a way around this limitation.
 In Python terms, the Registry holds a dictionary of dictionaries.
 You should use it to save Python objects only, not BPython (Blender Python)
 objects -- but you can save BPython object names, since those are strings.
-Also, if you need to save a considerable amount of data, please save to a
-file instead. There's no need to keep huge blocks of memory around when they
-can simply be read from a file.
+Also, if you need to save a considerable amount of data, we recommend saving
+it to a file instead. There's no need to keep huge blocks of memory around when
+they can simply be read from a file.
+
+Examples of what this module can be used for:
 
-Two uses for this module:
+a) Saving data from a script that another script will need to access later.
 
-a) To save data from a script that another script will need to access later.
+b) Saving configuration data for a script.  Users can view and edit this data
+using the "Scripts Configuration Editor" script, then.
 
-b) To save configuration data from your script's gui (button values) so that the
-next time the user runs your script, the changes will still be there. Later we
-can make Blender save the Registry so that its data won't be lost after users
-quit the program. And also add an option to save as a Text that can be kept in
-a .blend file, letting users keep script data there.
+c) Saving configuration data from your script's gui (button values) so that the
+next time the user runs your script, the changes will still be there.  
 
 Example::
 
@@ -42,12 +44,12 @@ Example::
   mystr = "hello"
 
   # then check if they are already at the Registry (saved on a
-  # previous execution of this script):
-  dict = Registry.GetKey('MyScript')
-  if dict: # if found, get the values saved there
-    myvar1 = dict['myvar1']
-    myvar2 = dict['myvar2']
-    mystr = dict['mystr']
+  # previous execution of this script) or on disk:
+  rdict = Registry.GetKey('MyScript', True)
+  if rdict: # if found, get the values saved there
+    myvar1 = rdict['myvar1']
+    myvar2 = rdict['myvar2']
+    mystr = rdict['mystr']
 
   # let's create a function to update the Registry when we need to:
   def update_Registry():
@@ -55,7 +57,8 @@ Example::
     d['myvar1'] = myvar1
     d['myvar2'] = myvar2
     d['mystr'] = mystr
-    Blender.Registry.SetKey('MyScript', d)
+    # cache = True: data is also saved to a file
+    Blender.Registry.SetKey('MyScript', d, True)
 
   # ...
   # here goes the main part of the script ...
@@ -72,21 +75,29 @@ def Keys ():
   Get all keys currently in the Registry's dictionary.
   """
 
-def GetKey (key):
+def GetKey (key, cached = False):
   """
   Get key 'key' from the Registry.
   @type key: string
   @param key: a key from the Registry dictionary.
+  @type cached: bool
+  @param cached: if True and the requested key isn't already loaded in the
+      Registry, it will also be searched on the user or default scripts config
+      data dir (config subdir in L{Blender.Get}('datadir')).
   @return: the dictionary called 'key'.
   """
 
-def SetKey (key, dict):
+def SetKey (key, dict, cache = False):
   """
   Store a new entry in the Registry.
   @type key: string
   @param key: the name of the new entry, tipically your script's name.
   @type dict: dictionary
   @param dict: a dict with all data you want to save in the Registry.
+  @type cache: bool
+  @param cache: if True the given key data will also be saved as a file
+      in the config subdir of the scripts user or default data dir (see
+      L{Blender.Get}.
   """
 
 def RemoveKey (key):
index 61454cfab53019476aab684f808c60408db18ba2..fc1d2e5326628156ab8e8f5d57da8a6214274a81 100644 (file)
@@ -3,8 +3,8 @@
 """
 The Blender.Scene.Render submodule.
 
-Scene
-=====
+Scene.Render
+============
 
 This module provides access to B{Scene Rendering Contexts} in Blender.
 
index 7536296d0ff6e22ff0c58550361ffd48654a20e8..8d9ecf0eb46c2e1d0df2c3facb579ffd9bd52d04 100644 (file)
@@ -6,7 +6,7 @@ The Blender.sys submodule.
 sys
 ===
 
-B{New}: L{exists}, L{makename}, L{join}, L{sleep}.
+B{New}: L{expandpath}.
 
 This module provides a minimal set of helper functions and data.  Its purpose
 is to avoid the need for the standard Python module 'os', in special 'os.path',
@@ -137,3 +137,22 @@ def sleep (millisecs = 10):
   @param millisecs: the amount of time in milliseconds to sleep.  The default
       is 10 which is 0.1 seconds.
   """
+
+def expandpath (path):
+  """
+  Expand the given Blender 'path' into an absolute and valid path.
+  Internally, Blender recognizes two special character sequences in paths:
+    - '//' (used at the beginning): means base path -- the current .blend file's
+      dir;
+    - '#' (used at the end): means current frame number.
+  The expanded string can be passed to generic python functions that don't
+  understand Blender's internal relative paths.
+  @note: this function is also useful for obtaining the name of the image
+      that will be saved when rendered.
+  @note: if the passed string doesn't contain the special characters it is
+    returned unchanged.
+  @type path: string
+  @param path: a path name.
+  @rtype: string
+  @return: the expanded (if necessary) path.
+  """