* Edited and consistent-ified the File->Import menu item labels and fileselect button...
[blender.git] / release / scripts / wrl2export.py
1 #!BPY
2 """ Registration info for Blender menus:
3 Name: 'VRML 2.0 (.wrl)...'
4 Blender: 232
5 Group: 'Export'
6 Submenu: 'All Objects...' all
7 Submenu: 'Selected Objects...' selected
8 Tooltip: 'Export to VRML2 (.wrl) file.'
9 """
10
11 # $Id$
12 #
13 #------------------------------------------------------------------------
14 # VRML2 exporter for blender 2.28a or above
15 #
16 # Source: http://blender.kimballsoftware.com/
17 #
18 # Authors: Rick Kimball with much inspiration
19 #         from the forum at www.elysiun.com
20 #         and irc://irc.freenode.net/blenderchat
21 #         Ken Miller and Steve Matthews (Added Camera Support)
22 #
23 # ***** BEGIN GPL LICENSE BLOCK *****
24 #
25 # Copyright (C) 2003,2004: Rick Kimball rick@vrmlworld.net
26 #
27 # This program is free software; you can redistribute it and/or
28 # modify it under the terms of the GNU General Public License
29 # as published by the Free Software Foundation; either version 2
30 # of the License, or (at your option) any later version.
31 #
32 # This program is distributed in the hope that it will be useful,
33 # but WITHOUT ANY WARRANTY; without even the implied warranty of
34 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35 # GNU General Public License for more details.
36 #
37 # You should have received a copy of the GNU General Public License
38 # along with this program; if not, write to the Free Software Foundation,
39 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
40 #
41 # ***** END GPL LICENCE BLOCK *****
42 #
43 # To use script:
44 # 1.) load this file in the text window.
45 #     (press SHIFT+F11, Open New via Datablock button)
46 # 2.) make sure your mouse is over the text edit window and
47 #     run this script. (press ALT+P)
48 # Or:
49 #   copy to the scripts directory and it will appear in the
50 #   export list. (Needs 2.32 or higher)
51 #
52 # Notes:
53 #  a.) output filename is same as current blender file with .wrl extension
54 #  b.) error messages go to the Blender DOS console window
55 #
56 # The latest version of this python export script:
57 #   http://blender.kimballsoftware.com/
58 #
59 # If you like this script, try using http://vrmlworld.net/
60 # to show off your VRML world.
61 #
62 # 2004-01-19 by Rick Kimball <rick@vrmlworld.net>
63 #  o added sub menus and file selector dialog
64 #
65 # 2004-01-17 by Rick Kimball <rick@vrmlworld.net>
66 #  o add meta comments so script will appear in export menu list
67 #
68 # 2003-11-01 by Rick Kimball <rick@vrmlworld.net>
69 #  o fixed issues related to Lamp object and 2.28a API.
70 #
71 # 2003-07-19 by Rick Kimball <rick@vrmlworld.net>
72 #  o made compatible with new Python API in 2.28
73 #
74 # 2003-01-16 by Ken Miller - with math help from Steve Matthews :)
75 #   o Added support for exporting cameras out of Blender
76 #   o Sets the name of the camera as the object name
77 #   o sets the description of the camera as the object name,
78 #     which should be modified to something meaningful
79 #   o sets the position and orientation
80 #
81 # 2003-01-19 Rick Kimball <rick@vrmlworld.net>
82 #   o Added Support For PointLight, SpotLight and DirectionalLight using Lamps
83 #   o Creates multi singlesided or doublesided IFS
84 #   o Creates IndexedLineSets if DrawTypes is WIRE instead of Shaded
85 #
86 # 2003-02-03 Rick Kimball <rick@vrmlworld.net>
87 #   o attempts to catch exceptions for empty objects
88 #
89 # 2003-02-04 Rick Kimball <rick@vrmlworld.net>
90 #   o fixed file overwrite problem when blender filename is all uppercase
91 #
92 # 2003-02-08 Rick Kimball <rick@vrmlworld.net>
93 #   o cleanStr() creates valid VRML DEF names even if object.name
94 #     is zero length or uses VRML reserved names or characters
95 #
96 #------------------------------------------------------------------------
97 # Known Issue:
98 #  o doesn't handle multiple materials (don't use material indices)
99 #  o doesn't handle multiple UV textures on a single mesh. (create a mesh for each texture)
100 #  o material colors need work
101 #  o spotlight softness needs work
102 #  o can't get the texture array associated with material * not the UV ones
103 #  o can't set smoothing, crease angle and mesh smoothing * setting not accesible
104 #
105 # Still Todo:
106 #
107 #  - Support for material indexes
108 #  - Automatically Split IFS when multiple UV textures found * warning only now
109 #  - Automatically Split IFS when combination of single vs double sided
110 #  - Automatically Split IFS when face with only 2 vertices is found should be an ILS
111 #  - Export common coordinate map for split IFS
112 #  - Intelligent color array vs color index
113 #  - Support more blender objects: World
114 #  - Figure out how to output Animation
115 #  - Add GUI to control the following:
116 #    o All/Layer/Object output radio button
117 #    o Color per vertex toggle yes/no
118 #    o Complex/Simple VRML output radio button
119 #    o Compressed/Uncompressed output radio button
120 #    o Decimal precision dropdown 1,2,3,4,5,6
121 #    o IFS/Elevation Grid output radio button
122 #    o Normals output toggle yes/no
123 #    o Proto output toggle yes/no
124 #    o Verbose console progress
125
126 import Blender
127 from Blender import NMesh, Lamp
128 import math
129
130
131 #-- module constants
132 radD=math.pi/180.0
133 rad90=90.0*radD      # for rotation
134 rad30=30.0*radD      # default crease angle
135 ARG=''
136
137 #------------------------------------------------------------------------
138 #-- utility functions and classes --
139 #------------------------------------------------------------------------
140 def rad2deg(v):
141     return round(v*180.0/math.pi,4)
142
143 def deg2rad(v):
144     return (v*math.pi)/180.0;
145
146 class DrawTypes:
147     """Object DrawTypes enum values
148     BOUNDS - draw only the bounding box of the object
149     WIRE - draw object as a wire frame
150     SOLID - draw object with flat shading
151     SHADED - draw object with OpenGL shading
152 """
153     BOUNDBOX  = 1
154     WIRE      = 2
155     SOLID     = 3
156     SHADED    = 4
157     TEXTURE   = 5
158
159 #------------------------------------------------------
160 # the Blender.Object class seems to be missing this...
161 #------------------------------------------------------
162 if not hasattr(Blender.Object,'DrawTypes'):
163     Blender.Object.DrawTypes = DrawTypes()
164
165 #------------------------------------------------------------------------
166 #-- VRML2Export --
167 #------------------------------------------------------------------------
168 class VRML2Export:
169     #------------------------------------------------------------------------
170     def __init__(self, filename):
171         #--- public you can change these ---
172         self.verbose=1     # level of verbosity in console 0-none, 1-some, 2-most
173         self.cp=3          # decimals for material color values     0.000 - 1.000
174         self.vp=3          # decimals for vertex coordinate values  0.000 - n.000
175         self.tp=3          # decimals for texture coordinate values 0.000 - 1.000
176         self.ambientIntensity=.2
177         self.defCreaseAngle=rad30
178         self.smooth=0
179         
180         #--- class private don't touch ---
181         self.texNames={}   # dictionary of textureNames
182         self.matNames={}   # dictionary of materiaNames
183         self.indentLevel=0 # keeps track of current indenting
184         self.filename=filename
185         self.file = open(filename, "w")
186         self.bNav=0
187         self.nNodeID=0
188         self.VRMLReserved=[ "Anchor","Appearance","Anchor","AudioClip","Background",
189                             "Billboard", "Box", "Collision", "Color", "ColorInterpolator",
190                             "Cone", "Coordinate", "CoordinateInterpolator", "Cylinder",
191                             "CylinderSensor", "DirectionalLight", "ElevationGrid",
192                             "Extrustion", "Fog", "FontStyle", "Group", "ImageTexture",
193                             "IndexedFaceSet", "IndexedLineSet", "Inline", "LOD",
194                             "Material", "MovieTexture", "NavigationInfo", "Normal",
195                             "NormalInterpolator","OrientationInterpolator", "PixelTexture",
196                             "PlaneSensor", "PointLight", "PointSet", "PositionInterpolator",
197                             "ProxmimitySensor", "ScalarInterpolator", "Script", "Shape",
198                             "Sound", "Sphere", "SphereSensor", "SpotLight", "Switch",
199                             "Text", "TextureCoordinate", "TextureTransform", "TimeSensor",
200                             "TouchSensor", "Transform", "Viewpoint", "VisibilitySensor",
201                             "WorldInfo"
202                             ]
203
204     #------------------------------------------------------------------------
205     # writeHeader, export file, cleanup
206     #------------------------------------------------------------------------
207     def writeHeader(self):
208         self.file.write("#VRML V2.0 utf8\n")
209         self.file.write("# modeled using blender3d http://blender.org/$\n")
210         self.file.write("# exported using wrl2export.py version $Revision$\n")
211         self.file.write("# get latest exporter at http://kimballsoftware.com/blender/\n\n")
212     
213     def export(self, scene):
214         print "Info: starting VRML2 export to " + self.filename + "..."
215     
216         self.writeHeader()
217         theObjects = []
218         if ARG == 'selected':
219             theObjects = Blender.Object.GetSelected()
220         else:
221             theObjects = scene.getChildren()
222             
223         for object in theObjects:
224             try:
225                 objType=object.getType()
226
227                 if objType == "Mesh":
228                     self.writeIndexedFaceSet(object, normals = 0)
229                 elif objType == "Camera":
230                     self.writeCameraInfo(object)
231                 elif objType == "Lamp":
232                     # if there is a lamp then we probably want to turn off the headlight
233                     if self.bNav == 0:
234                         self.writeNavigationInfo()
235                         self.bNav=1
236                     #endif
237                 
238                     lamp=Lamp.Get(object.data.getName())
239                     try:
240                         lampType=lamp.getType()
241
242                         if lampType == Lamp.Types.Lamp:
243                             self.writePointLight(object, lamp)
244                         elif lampType == Lamp.Types.Spot:
245                             self.writeSpotLight(object, lamp)
246                         elif lampType == Lamp.Types.Sun:
247                             self.writeDirectionalLight(object, lamp)
248                         else:
249                             self.writeDirectionalLight(object, lamp)
250                         #endif
251                     except AttributeError:
252                         print "Error: Unable to get type info for %s" % object.name
253                 else:
254                     print "Info: Ignoring [%s], object type [%s] not handle yet" % \
255                           (object.name,object.getType())
256                 #endif
257             except ValueError:
258                 print "Error: object named %s has problem with accessing an attribute" % object.name
259             #end try
260         #endfor
261         self.cleanup()
262
263     def cleanup(self):
264         self.file.close()
265         self.texNames={}
266         self.matNames={}
267         self.indentLevel=0
268         print "Info: finished VRML2 export to %s\n" % self.filename
269
270     #------------------------------------------------------------------------
271     # Writes out camera info as a viewpoint
272     # Handles orientation, position
273     # Use camera object name to set description
274     #------------------------------------------------------------------------
275     def writeCameraInfo(self, object):
276         if self.verbose > 0:
277             print "Info: exporting camera named="+object.name
278         #endif
279         
280         self.writeIndented("DEF %s Viewpoint {\n" % (self.cleanStr(object.name)), 1)
281         
282         self.writeIndented("description \"%s\" \n" % (object.name))
283         
284         # get the camera location, subtract 90 degress from X to orient like VRML does
285         loc = self.rotatePointForVRML(object.loc)
286         rot = [object.RotX - 1.57, object.RotY, object.RotZ]
287         nRot = self.rotatePointForVRML(rot)
288         
289         # convert to Quaternion and to Angle Axis
290         Q  = self.eulerToQuaternions(nRot[0], nRot[1], nRot[2])
291         Q1 = self.multiplyQuaternions(Q[0], Q[1])
292         Qf = self.multiplyQuaternions(Q1, Q[2])
293         angleAxis = self.quaternionToAngleAxis(Qf)
294         
295         # write orientation statement
296         self.writeIndented("orientation %3.2f %3.2f %3.2f %3.2f\n" %
297                            (angleAxis[0], angleAxis[1], -angleAxis[2], angleAxis[3]))
298         
299         # write position statement
300         self.writeIndented("position %3.2f %3.2f %3.2f\n" %
301                            (loc[0], loc[1], loc[2]))
302         
303         self.writeIndented("} # Viewpoint\n", -1)
304         
305         self.writeIndented("\n")
306
307     #------------------------------------------------------------------------
308     def writeIndexedFaceSet(self, object, normals = 0):
309         if self.verbose > 0:
310             print "Info: exporting mesh named=["+object.name+"]"
311         #endif
312
313         imageMap={}   # set of used images
314         sided={}      # 'one':cnt , 'two':cnt
315         vColors={}    # 'multi':1
316
317         mesh=object.getData()
318
319         nIFSCnt=self.countIFSSetsNeeded(mesh, imageMap, sided, vColors)
320
321         meshName = self.cleanStr(object.name)
322         
323         if nIFSCnt > 1:
324             self.writeIndented("DEF %s%s Group {\n" % ("G_", meshName),1)
325             self.writeIndented("children [\n",1)
326         #endif
327         
328         if self.verbose > 0:
329             print "Debug: [%s] has %d UV Textures" % (object.name, nIFSCnt)
330         #endif
331
332         if sided.has_key('two') and sided['two'] > 0:
333             bTwoSided=1
334         else:
335             bTwoSided=0
336
337         self.writeIndented("DEF %s Shape {\n" % meshName,1)
338     
339         # show script debugging info
340         if self.verbose > 1:
341             self.meshToString(mesh)
342             print "Debug: mesh.faces["
343             for face in mesh.faces:
344                 self.faceToString(face)
345             #endfor
346             print "Debug: ]"
347         #endif
348         
349         maters=mesh.materials
350         hasImageTexture=0
351
352         if len(maters) > 0 or mesh.hasFaceUV():
353             self.writeIndented("appearance Appearance {\n", 1)
354             
355             # right now this script can only handle a single material per mesh.
356             if len(maters) >= 1:
357                 mat=Blender.Material.Get(maters[0].name)
358                 self.writeMaterial(mat, self.cleanStr(maters[0].name,'mat_'))
359                 if len(maters) > 1:
360                     print "Warning: mesh named %s has multiple materials" % meshName
361                     print "Warning: only one material per object handled"
362                 #endif
363             else:
364                 self.writeIndented("material NULL\n")
365             #endif
366         
367             #-- textures
368             if mesh.hasFaceUV():
369                 for face in mesh.faces:
370                     if (hasImageTexture == 0) and (face.image):
371                         self.writeImageTexture(face.image.name)
372                         hasImageTexture=1  # keep track of face texture
373                     #endif
374                 #endfor
375             #endif
376
377             self.writeIndented("} # Appearance\n", -1)
378         #endif
379
380         #-------------------------------------------------------------------
381         #--
382         #-- IndexedFaceSet or IndexedLineSet
383         #
384
385         # check if object is wireframe only
386         if object.drawType == Blender.Object.DrawTypes.WIRE:
387             # user selected WIRE=2 on the Drawtype=Wire on (F9) Edit page
388             ifStyle="IndexedLineSet"
389         else:
390             # user selected BOUNDS=1, SOLID=3, SHARED=4, or TEXTURE=5
391             ifStyle="IndexedFaceSet"
392         #endif
393         
394         self.writeIndented("geometry %s {\n" % ifStyle, 1)
395         if object.drawType != Blender.Object.DrawTypes.WIRE:
396             if bTwoSided == 1:
397                 self.writeIndented("solid FALSE # two sided\n")
398             else:
399                 self.writeIndented("solid TRUE # one sided\n")
400             #endif
401         #endif
402         
403         #---
404         #--- output coordinates
405         self.writeCoordinates(object, mesh, meshName)
406         
407         if object.drawType != Blender.Object.DrawTypes.WIRE:
408             #---
409             #--- output textureCoordinates if UV texture used
410             if mesh.hasFaceUV():
411                 if hasImageTexture == 1:
412                     self.writeTextureCoordinates(mesh)
413                     if vColors.has_key('multi'):
414                         self.writeVertexColors(mesh) # experiment
415                     #endif
416                 else:
417                     self.writeFaceColors(mesh)
418                 #endif hasImageTexture
419             #endif hasFaceUV
420
421             # TBD: figure out how to get this properly
422             if self.smooth:
423                 creaseAngle=self.defCreaseAngle;
424                 self.writeIndented("creaseAngle %s\n" % creaseAngle)
425             else:
426                 self.writeIndented("creaseAngle 0.0 # in radians\n")
427             #endif mesh.smooth
428         #endif WIRE
429
430         #--- output vertexColors
431         if mesh.hasVertexColours() and vColors.has_key('multi'):
432             self.writeVertexColors(mesh)
433         #endif
434
435         #--- output closing braces
436         self.writeIndented("} # %s\n" % ifStyle, -1)
437         self.writeIndented("} # Shape\n", -1)
438
439         if nIFSCnt > 1:
440             self.writeIndented("] # children\n", -1)
441             self.writeIndented("} # Group\n", -1)
442         #endif
443
444         self.writeIndented("\n")
445
446     #------------------------------------------------------------------------
447     def writeCoordinates(self, object, mesh, meshName):
448         #-- vertices
449         self.writeIndented("coord DEF %s%s Coordinate {\n" % ("coord_",meshName), 1)
450         self.writeIndented("point [\n", 1)
451         meshVertexList = mesh.verts
452
453         # create vertex list and pre rotate -90 degrees X for VRML
454         mm=object.getMatrix()
455         for vertex in meshVertexList:
456             v=self.rotVertex(mm, vertex);
457             self.writeIndented("%s %s %s,\n" %
458                                (round(v[0],self.vp),
459                                 round(v[1],self.vp),
460                                 round(v[2],self.vp) ))
461         #endfor
462         self.writeIndented("] # point\n", -1)
463         self.writeIndented("} # Coordinate\n", -1)
464
465         self.writeIndented("coordIndex [\n", 1)
466         coordIndexList=[]  
467         for face in mesh.faces:
468             cordStr=""
469             for i in range(len(face)):
470                 indx=meshVertexList.index(face[i])
471                 cordStr = cordStr + "%s, " % indx
472             #endfor
473             self.writeIndented(cordStr + "-1,\n")
474         #endfor
475         self.writeIndented("] # coordIndex\n", -1)
476
477     #------------------------------------------------------------------------
478     def writeTextureCoordinates(self, mesh):
479         texCoordList=[] 
480         texIndexList=[]
481         j=0
482
483         for face in mesh.faces:
484             for i in range(len(face)):
485                 texIndexList.append(j)
486                 texCoordList.append(face.uv[i])
487                 j=j+1
488             #endfor
489             texIndexList.append(-1)
490         #endfor
491
492         self.writeIndented("texCoord TextureCoordinate {\n", 1)
493         self.writeIndented("point [\n", 1)
494         for i in range(len(texCoordList)):
495             self.writeIndented("%s %s," %
496                                (round(texCoordList[i][0],self.tp), 
497                                 round(texCoordList[i][1],self.tp))+"\n")
498         #endfor
499         self.writeIndented("] # point\n", -1)
500         self.writeIndented("} # texCoord\n", -1)
501
502         self.writeIndented("texCoordIndex [\n", 1)
503         texIndxStr=""
504         for i in range(len(texIndexList)):
505             texIndxStr = texIndxStr + "%d, " % texIndexList[i]
506             if texIndexList[i]==-1:
507                 self.writeIndented(texIndxStr + "\n")
508                 texIndxStr=""
509             #endif
510         #endfor
511         self.writeIndented("] # texCoordIndex\n", -1)
512
513     #------------------------------------------------------------------------
514     def writeFaceColors(self, mesh):
515         self.writeIndented("colorPerVertex FALSE\n")
516         self.writeIndented("color Color {\n",1)
517         self.writeIndented("color [\n",1)
518
519         for face in mesh.faces:
520             if face.col:
521                 c=face.col[0]
522                 if self.verbose > 1:
523                     print "Debug: face.col r=%d g=%d b=%d" % (c.r, c.g, c.b)
524                 #endif
525
526                 aColor = self.rgbToFS(c)
527                 self.writeIndented("%s,\n" % aColor)
528         #endfor
529
530         self.writeIndented("] # color\n",-1)
531         self.writeIndented("} # Color\n",-1)
532
533     #------------------------------------------------------------------------
534     def writeVertexColors(self, mesh):
535         self.writeIndented("colorPerVertex TRUE\n")
536         self.writeIndented("color Color {\n",1)
537         self.writeIndented("color [\n",1)
538
539         for i in range(len(mesh.verts)):
540             c=self.getVertexColorByIndx(mesh,i)
541             if self.verbose > 1:
542                 print "Debug: vertex[%d].col r=%d g=%d b=%d" % (i, c.r, c.g, c.b)
543             #endif
544
545             aColor = self.rgbToFS(c)
546             self.writeIndented("%s,\n" % aColor)
547         #endfor
548
549         self.writeIndented("] # color\n",-1)
550         self.writeIndented("} # Color\n",-1)
551
552     #------------------------------------------------------------------------
553     def writeMaterial(self, mat, matName):
554         # look up material name, use it if available
555         if self.matNames.has_key(matName):
556             self.writeIndented("material USE %s\n" % matName)
557             self.matNames[matName]+=1
558             return;
559         #endif
560
561         self.matNames[matName]=1
562
563         ambient = mat.amb
564         diffuseR, diffuseG, diffuseB = mat.rgbCol[0], mat.rgbCol[1],mat.rgbCol[2]
565         emisR, emisG, emisB = diffuseR*mat.emit, diffuseG*mat.emit, diffuseB*mat.emit
566         shininess = mat.hard/255.0
567         specR = mat.specCol[0]
568         specG = mat.specCol[1]
569         specB = mat.specCol[2]
570         transp = 1-mat.alpha
571
572         self.writeIndented("material DEF %s Material {\n" % matName, 1)
573         self.writeIndented("diffuseColor %s %s %s" %
574                            (round(diffuseR,self.cp), round(diffuseG,self.cp), round(diffuseB,self.cp)) +
575                            "\n")
576         self.writeIndented("ambientIntensity %s" %
577                            (round(ambient,self.cp))+
578                            "\n")
579         self.writeIndented("specularColor %s %s %s" %
580                            (round(specR,self.cp), round(specG,self.cp), round(specB,self.cp)) +
581                            "\n" )
582         self.writeIndented("emissiveColor  %s %s %s" %
583                            (round(emisR,self.cp), round(emisG,self.cp), round(emisB,self.cp)) +
584                            "\n" )
585         self.writeIndented("shininess %s" %
586                            (round(shininess,self.cp)) +
587                            "\n" )
588         self.writeIndented("transparency %s" %
589                            (round(transp,self.cp)) +
590                            "\n")
591         self.writeIndented("} # Material\n",-1)
592
593     #------------------------------------------------------------------------
594     def writeImageTexture(self, name):
595         if self.texNames.has_key(name):
596             self.writeIndented("texture USE %s\n" % self.cleanStr(name))
597             self.texNames[name] += 1
598             return
599         else:
600             self.writeIndented("texture DEF %s ImageTexture {\n" % self.cleanStr(name), 1)
601             self.writeIndented("url \"%s\"\n" % name)
602             self.writeIndented("} # ImageTexture \n",-1)
603             self.texNames[name] = 1
604         #endif
605
606     #------------------------------------------------------------------------
607     def writeSpotLight(self, object, lamp):
608         safeName = self.cleanStr(object.name)
609
610         # compute cutoff and beamwidth
611         intensity=min(lamp.energy/1.5,1.0) # TBD: figure out the right value
612
613         beamWidth=deg2rad(lamp.spotSize)*.5;
614         cutOffAngle=beamWidth*.99
615
616         (dx,dy,dz)=self.computeDirection(object)
617         # note -dx seems to equal om[3][0]
618         # note -dz seems to equal om[3][1]
619         # note  dy seems to equal om[3][2]
620         om = object.getMatrix()
621             
622         location=self.rotVertex(om, (0,0,0));
623         radius = lamp.dist*math.cos(beamWidth)
624         self.writeIndented("DEF %s SpotLight {\n" % safeName,1)
625         self.writeIndented("radius %s\n" % radius )
626         self.writeIndented("intensity %s\n" % intensity )
627         self.writeIndented("beamWidth %s # lamp.spotSize %s\n" % (beamWidth, lamp.spotSize) )
628         self.writeIndented("cutOffAngle %s # lamp.spotBlend %s\n" % (cutOffAngle, lamp.spotBlend))
629         self.writeIndented("direction %s %s %s # lamp.RotX=%s RotY=%s RotZ=%s\n" % \
630                            (round(dx,3),round(dy,3),round(dz,3),
631                             round(rad2deg(object.RotX),3),
632                             round(rad2deg(object.RotY),3),
633                             round(rad2deg(object.RotZ),3)))
634         self.writeIndented("location %s %s %s\n" % (round(location[0],3),
635                                                     round(location[1],3),
636                                                     round(location[2],3)))
637         self.writeIndented("} # SpotLight\n",-1)
638
639         # export a cone that matches the spotlight in verbose mode
640         if self.verbose > 1:
641             self.writeIndented("#generated visible spotlight cone\n")
642             self.writeIndented("Transform { # Spotlight Cone\n",1)
643             self.writeIndented("translation %s %s %s\n" % (round(location[0],3),
644                                                            round(location[1],3),
645                                                            round(location[2],3)))
646             rot = [object.RotX, object.RotY, object.RotZ]
647             nRot = self.rotatePointForVRML(rot)
648             
649             # convert to Quaternion and to Angle Axis
650             Q  = self.eulerToQuaternions(nRot[0], nRot[1], nRot[2])
651             Q1 = self.multiplyQuaternions(Q[0], Q[1])
652             Qf = self.multiplyQuaternions(Q1, Q[2])
653             angleAxis = self.quaternionToAngleAxis(Qf)
654             
655             # write orientation statement
656             self.writeIndented("rotation %3.2f %3.2f %3.2f %3.2f\n" %
657                                (angleAxis[0], angleAxis[1], -angleAxis[2], angleAxis[3]))
658             
659             self.writeIndented("children [\n",1)
660             
661             ch=radius
662             br=ch*math.sin(beamWidth)
663             self.writeIndented("Transform {\n",1)
664             self.writeIndented("translation 0 -%s 0\n" % (ch/2))
665             self.writeIndented("children ")
666             self.writeIndented("Collision {\n",1)
667             self.writeIndented("collide FALSE children Shape {\n",1)
668             self.writeIndented("geometry Cone { height %s bottomRadius %s }\n" % (ch, br))
669             self.writeIndented("appearance Appearance{\n",1)
670             self.writeIndented("material Material { diffuseColor 1 1 1 transparency .8 }\n")
671             self.writeIndented("} # Appearance\n",-1)
672             self.writeIndented("} # Shape\n",-1)
673             self.writeIndented("} # Collision\n",-1)
674             self.writeIndented("} # Transform visible cone \n",-1)
675             self.writeIndented("] # Spot children\n",-1)
676             self.writeIndented("} # SpotLight Cone Transform\n",-1)
677         #endif debug cone
678         self.writeIndented("\n")
679         
680     #------------------------------------------------------------------------
681     def writeDirectionalLight(self, object, lamp):
682         safeName = self.cleanStr(object.name)
683
684         intensity=min(lamp.energy/1.5, 1.0) # TBD: figure out the right value
685         (dx,dy,dz)=self.computeDirection(object)
686
687         self.writeIndented("DEF %s DirectionalLight {\n" % safeName,1)
688         self.writeIndented("ambientIntensity %s\n" % self.ambientIntensity )
689         self.writeIndented("intensity %s\n" % intensity )
690         self.writeIndented("direction %s %s %s\n" % (round(dx,4),round(dy,4),round(dz,4)))
691         self.writeIndented("} # DirectionalLight\n",-1)
692         self.writeIndented("\n")
693
694     #------------------------------------------------------------------------
695     def writePointLight(self, object, lamp):
696         safeName = self.cleanStr(object.name)
697
698         om = object.getMatrix()
699         location=self.rotVertex(om, (0,0,0));
700         intensity=min(lamp.energy/1.5,1.0) # TBD: figure out the right value
701
702         radius = lamp.dist
703         self.writeIndented("DEF %s PointLight {\n" % safeName,1)
704         self.writeIndented("ambientIntensity %s\n" % self.ambientIntensity )
705         self.writeIndented("intensity %s\n" % intensity )
706         self.writeIndented("location %s %s %s\n" % (round(location[0],3),
707                                                     round(location[1],3),
708                                                     round(location[2],3)))
709         self.writeIndented("radius %s\n" % radius )
710         self.writeIndented("} # PointLight\n",-1)
711         self.writeIndented("\n")
712
713     #------------------------------------------------------------------------
714     def writeNavigationInfo(self):
715         self.writeIndented("NavigationInfo {\n",1)
716         self.writeIndented("headlight FALSE\n")
717         self.writeIndented("avatarSize [0.25, 1.75, 0.75]\n")
718         self.writeIndented("} # NavigationInfo\n",-1)
719         self.writeIndented("\n")
720
721     #------------------------------------------------------------------------
722     #--- Utility methods
723     #------------------------------------------------------------------------
724
725     def cleanStr(self, name, prefix='rsvd_'):
726         """cleanStr(name,prefix) - try to create a valid VRML DEF name from object name"""
727
728         newName=name[:]
729         if len(newName) == 0:
730             self.nNodeID+=1
731             return "%s%d" % (prefix, self.nNodeID)
732         
733         if newName in self.VRMLReserved:
734             newName='%s%s' % (prefix,newName)
735         #endif
736         
737         if newName[0] in ['0','1','2','3','4','5','6','7','8','9']:
738             newName='%s%s' % ('_',newName)
739         #endif
740
741         for bad in [' ','"','#',"'",',','.','[','\\',']','{','}']:
742             newName=newName.replace(bad,'_')
743         return newName
744
745     def countIFSSetsNeeded(self, mesh, imageMap, sided, vColors):
746         """
747         countIFFSetsNeeded() - should look at a blender mesh to determine
748         how many VRML IndexFaceSets or IndexLineSets are needed.  A
749         new mesh created under the following conditions:
750         
751          o - split by UV Textures / one per mesh
752          o - split by face, one sided and two sided
753          o - split by smooth and flat faces
754          o - split when faces only have 2 vertices * needs to be an IndexLineSet
755         """
756         
757         imageNameMap={}
758         faceMap={}
759         nFaceIndx=0
760         
761         for face in mesh.faces:
762             sidename='';
763             if (face.mode & NMesh.FaceModes.TWOSIDE) == NMesh.FaceModes.TWOSIDE:
764                 sidename='two'
765             else:
766                 sidename='one'
767             #endif
768
769             if not vColors.has_key('multi'):
770                 for face in mesh.faces:
771                     if face.col:
772                         c=face.col[0]
773                         if c.r != 255 and c.g != 255 and c.b !=255:
774                             vColors['multi']=1
775                         #endif
776                     #endif
777                 #endfor
778             #endif
779
780             if sided.has_key(sidename):
781                 sided[sidename]+=1
782             else:
783                 sided[sidename]=1
784             #endif
785
786             if face.image:
787                 faceName="%s_%s" % (face.image.name, sidename);
788
789                 if imageMap.has_key(faceName):
790                     imageMap[faceName].append(face)
791                 else:
792                     imageMap[faceName]=[face.image.name,sidename,face]
793                 #endif
794             #endif
795         #endfor
796
797         if self.verbose > 0:
798             for faceName in imageMap.keys():
799                 ifs=imageMap[faceName]
800                 print "Debug: faceName=%s image=%s, solid=%s facecnt=%d" % \
801                       (faceName, ifs[0], ifs[1], len(ifs)-2)
802             #endif
803         #endif
804
805         return len(imageMap.keys())
806     
807     def faceToString(self,face):
808         print "Debug: face.flag=0x%x (bitflags)" % face.flag
809         if face.flag & NMesh.FaceFlags.SELECT == NMesh.FaceFlags.SELECT:
810             print "Debug: face.flag.SELECT=true"
811         #endif
812
813         print "Debug: face.mode=0x%x (bitflags)" % face.mode
814         if (face.mode & NMesh.FaceModes.TWOSIDE) == NMesh.FaceModes.TWOSIDE:
815             print "Debug: face.mode twosided"
816         #endif
817
818         print "Debug: face.transp=0x%x (enum)" % face.transp
819         if face.transp == NMesh.FaceTranspModes.SOLID:
820             print "Debug: face.transp.SOLID"
821         #
822
823         if face.image:
824             print "Debug: face.image=%s" % face.image.name
825         #endif
826         print "Debug: face.materialIndex=%d" % face.materialIndex
827
828     def getVertexColorByIndx(self, mesh, indx):
829         for face in mesh.faces:
830             j=0
831             for vertex in face.v:
832                 if vertex.index == indx:
833                     c=face.col[j]
834                 #endif
835                 j=j+1
836             #endfor
837         #endfor
838         return c
839
840     def meshToString(self,mesh):
841         print "Debug: mesh.hasVertexUV=%d" % mesh.hasVertexUV()
842         print "Debug: mesh.hasFaceUV=%d" % mesh.hasFaceUV()
843         print "Debug: mesh.hasVertexColours=%d" % mesh.hasVertexColours()
844         print "Debug: mesh.verts=%d" % len(mesh.verts)
845         print "Debug: mesh.faces=%d" % len(mesh.faces)
846         print "Debug: mesh.materials=%d" % len(mesh.materials)
847
848     def rgbToFS(self, c):
849         s="%s %s %s" % (
850             round(c.r/255.0,self.cp),
851             round(c.g/255.0,self.cp),
852             round(c.b/255.0,self.cp))
853         return s
854
855     def computeDirection(self, object):
856         x,y,z=(0,-1.0,0) # point down
857         ax,ay,az = (object.RotX,object.RotZ,object.RotY)
858
859         # rot X
860         x1=x
861         y1=y*math.cos(ax)-z*math.sin(ax)
862         z1=y*math.sin(ax)+z*math.cos(ax)
863
864         # rot Y
865         x2=x1*math.cos(ay)+z1*math.sin(ay)
866         y2=y1
867         z2=z1*math.cos(ay)-x1*math.sin(ay)
868
869         # rot Z
870         x3=x2*math.cos(az)-y2*math.sin(az)
871         y3=x2*math.sin(az)+y2*math.cos(az)
872         z3=z2
873
874         return [x3,y3,z3]
875         
876
877     # swap Y and Z to handle axis difference between Blender and VRML
878     #------------------------------------------------------------------------
879     def rotatePointForVRML(self, v):
880         x = v[0]
881         y = v[2]
882         z = -v[1]
883         
884         vrmlPoint=[x, y, z]
885         return vrmlPoint
886     
887     def rotVertex(self, mm, v):
888         lx,ly,lz=v[0],v[1],v[2]
889         gx=(mm[0][0]*lx + mm[1][0]*ly + mm[2][0]*lz) + mm[3][0]
890         gy=((mm[0][2]*lx + mm[1][2]*ly+ mm[2][2]*lz) + mm[3][2])
891         gz=-((mm[0][1]*lx + mm[1][1]*ly + mm[2][1]*lz) + mm[3][1])
892         rotatedv=[gx,gy,gz]
893         return rotatedv
894
895     def writeIndented(self, s, inc=0):
896         if inc < 1:
897             self.indentLevel = self.indentLevel + inc
898         #endif
899
900         spaces=""
901         for x in xrange(self.indentLevel):
902             spaces = spaces + "   "
903         #endfor
904         self.file.write(spaces + s)
905
906         if inc > 0:
907             self.indentLevel = self.indentLevel + inc
908         #endif
909
910     # Converts a Euler to three new Quaternions
911     # Angles of Euler are passed in as radians
912     #------------------------------------------------------------------------
913     def eulerToQuaternions(self, x, y, z):
914         Qx = [math.cos(x/2), math.sin(x/2), 0, 0]
915         Qy = [math.cos(y/2), 0, math.sin(y/2), 0]
916         Qz = [math.cos(z/2), 0, 0, math.sin(z/2)]
917         
918         quaternionVec=[Qx,Qy,Qz]
919         return quaternionVec
920     
921     # Multiply two Quaternions together to get a new Quaternion
922     #------------------------------------------------------------------------
923     def multiplyQuaternions(self, Q1, Q2):
924         result = [((Q1[0] * Q2[0]) - (Q1[1] * Q2[1]) - (Q1[2] * Q2[2]) - (Q1[3] * Q2[3])),
925                   ((Q1[0] * Q2[1]) + (Q1[1] * Q2[0]) + (Q1[2] * Q2[3]) - (Q1[3] * Q2[2])),
926                   ((Q1[0] * Q2[2]) + (Q1[2] * Q2[0]) + (Q1[3] * Q2[1]) - (Q1[1] * Q2[3])),
927                   ((Q1[0] * Q2[3]) + (Q1[3] * Q2[0]) + (Q1[1] * Q2[2]) - (Q1[2] * Q2[1]))]
928         
929         return result
930     
931     # Convert a Quaternion to an Angle Axis (ax, ay, az, angle)
932     # angle is in radians
933     #------------------------------------------------------------------------
934     def quaternionToAngleAxis(self, Qf):
935         scale = math.pow(Qf[1],2) + math.pow(Qf[2],2) + math.pow(Qf[3],2)
936         ax = Qf[1]
937         ay = Qf[2]
938         az = Qf[3]
939
940         if scale > .0001:
941             ax/=scale
942             ay/=scale
943             az/=scale
944         #endif
945         
946         angle = 2 * math.acos(Qf[0])
947         
948         result = [ax, ay, az, angle]
949         return result
950
951 def file_callback(filename):
952     if filename.find('.wrl', -4) < 0: filename += '.wrl'
953     wrlexport=VRML2Export(filename)
954     scene = Blender.Scene.getCurrent()
955     wrlexport.export(scene)
956 #enddef
957     
958 #------------------------------------------------------------------------
959 # main routine
960 #------------------------------------------------------------------------
961 try:
962     ARG = __script__['arg'] # user selected argument
963 except:
964     print "older version"
965
966 if Blender.Get('version') < 225:
967     print "Warning: VRML2 export failed, wrong blender version!"
968     print " You aren't running blender version 2.25 or greater"
969     print " download a newer version from http://blender.org/"
970 else:
971     if ARG == 'all' or ARG == 'selected':
972         Blender.Window.FileSelector(file_callback,"Export VRML 2.0")
973     else:
974         baseFileName=Blender.Get('filename')
975         if baseFileName.find('.') != -1:
976             dots=Blender.Get('filename').split('.')[0:-1]
977         else:
978             dots=[baseFileName]
979         #endif
980         dots+=["wrl"]
981         vrmlFile=".".join(dots)
982
983         file_callback(vrmlFile)
984     #endif
985 #endif