968b081c10aa4a456b96276e5833ea105a20704b
[blender.git] / release / scripts / vrml97_export.py
1 #!BPY
2 """ Registration info for Blender menus:
3 Name: 'VRML97 (.wrl)...'
4 Blender: 235
5 Group: 'Export'
6 Submenu: 'All Objects...' all
7 Submenu: 'All Objects compressed...' comp
8 Submenu: 'Selected Objects...' selected
9 Tooltip: 'Export to VRML97 file (.wrl)'
10 """
11
12 __author__ = ("Rick Kimball", "Ken Miller", "Steve Matthews", "Bart")
13 __url__ = ["blender", "elysiun",
14 "Author's (Rick) homepage, http://kimballsoftware.com/blender",
15 "Author's (Bart) homepage, http://www.neeneenee.de/vrml"]
16 __email__ = ["Bart, bart:neeneenee*de"]
17 __version__ = "2006/01/17"
18 __bpydoc__ = """\
19 This script exports to VRML97 format.
20
21 Usage:
22
23 Run this script from "File->Export" menu.  A pop-up will ask whether you
24 want to export only selected or all relevant objects.
25
26 Known issues:<br>
27     Doesn't handle multiple materials (don't use material indices);<br>
28     Doesn't handle multiple UV textures on a single mesh (create a mesh
29 for each texture);<br>
30     Can't get the texture array associated with material * not the UV ones;
31 """
32
33
34 # $Id$
35 #
36 #------------------------------------------------------------------------
37 # VRML97 exporter for blender 2.36 or above
38 #
39 # ***** BEGIN GPL LICENSE BLOCK *****
40 #
41 # This program is free software; you can redistribute it and/or
42 # modify it under the terms of the GNU General Public License
43 # as published by the Free Software Foundation; either version 2
44 # of the License, or (at your option) any later version.
45 #
46 # This program is distributed in the hope that it will be useful,
47 # but WITHOUT ANY WARRANTY; without even the implied warranty of
48 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
49 # GNU General Public License for more details.
50 #
51 # You should have received a copy of the GNU General Public License
52 # along with this program; if not, write to the Free Software Foundation,
53 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
54 #
55 # ***** END GPL LICENCE BLOCK *****
56 #
57
58 ####################################
59 # Library dependancies
60 ####################################
61
62 import Blender
63 from Blender import Object, NMesh, Lamp, Draw, BGL, Image, Text, sys, Mathutils
64 from Blender.Scene import Render
65 try:
66     from os.path import exists, join
67     pytinst = 1
68 except:
69     print "No Python installed, for full features install Python (http://www.python.org/)."
70     pytinst = 0
71 import math
72
73 ####################################
74 # Global Variables
75 ####################################
76
77 scene = Blender.Scene.getCurrent()
78 world = Blender.World.Get() 
79 worldmat = Blender.Texture.Get()
80 filename = Blender.Get('filename')
81 _safeOverwrite = True
82 extension = ''
83 ARG=''
84
85 class DrawTypes:
86     """Object DrawTypes enum values
87     BOUNDS - draw only the bounding box of the object
88     WIRE - draw object as a wire frame
89     SOLID - draw object with flat shading
90     SHADED - draw object with OpenGL shading
91 """
92     BOUNDBOX  = 1
93     WIRE      = 2
94     SOLID     = 3
95     SHADED    = 4
96     TEXTURE   = 5
97
98 if not hasattr(Blender.Object,'DrawTypes'):
99     Blender.Object.DrawTypes = DrawTypes()
100
101 ##########################################################
102 # Functions for writing output file
103 ##########################################################
104
105 class VRML2Export:
106
107     def __init__(self, filename):
108         #--- public you can change these ---
109         self.wire = 0
110         self.proto = 1
111         self.matonly = 0
112         self.share = 0
113         self.billnode = 0
114         self.halonode = 0
115         self.collnode = 0
116         self.tilenode = 0
117         self.verbose=2     # level of verbosity in console 0-none, 1-some, 2-most
118         self.cp=3          # decimals for material color values     0.000 - 1.000
119         self.vp=3          # decimals for vertex coordinate values  0.000 - n.000
120         self.tp=3          # decimals for texture coordinate values 0.000 - 1.000
121         self.it=3
122         
123         #--- class private don't touch ---
124         self.texNames={}   # dictionary of textureNames
125         self.matNames={}   # dictionary of materialNames
126         self.meshNames={}   # dictionary of meshNames
127         self.indentLevel=0 # keeps track of current indenting
128         self.filename=filename
129         self.file = open(filename, "w")
130         self.bNav=0
131         self.nodeID=0
132         self.namesReserved=[ "Anchor", "Appearance", "AudioClip",
133                              "Background","Billboard", "Box", 
134                              "Collision", "Color", "ColorInterpolator", "Cone", "Coordinate", "CoordinateInterpolator", "Cylinder", "CylinderSensor",
135                              "DirectionalLight", 
136                              "ElevationGrid", "Extrustion", 
137                              "Fog", "FontStyle", "Group", 
138                              "ImageTexture", "IndexedFaceSet", "IndexedLineSet", "Inline", 
139                              "LOD", "Material", "MovieTexture", 
140                              "NavigationInfo", "Normal", "NormalInterpolator","OrientationInterpolator", 
141                              "PixelTexture", "PlaneSensor", "PointLight", "PointSet", "PositionInterpolator", "ProxmimitySensor", 
142                              "ScalarInterpolator", "Script", "Shape", "Sound", "Sphere", "SphereSensor", "SpotLight", "Switch",
143                              "Text", "TextureCoordinate", "TextureTransform", "TimeSensor", "TouchSensor", "Transform", 
144                              "Viewpoint", "VisibilitySensor", "WorldInfo" ]
145         self.namesStandard=[ "Empty","Empty.000","Empty.001","Empty.002","Empty.003","Empty.004","Empty.005",
146                              "Empty.006","Empty.007","Empty.008","Empty.009","Empty.010","Empty.011","Empty.012",
147                              "Scene.001","Scene.002","Scene.003","Scene.004","Scene.005","Scene.06","Scene.013",
148                              "Scene.006","Scene.007","Scene.008","Scene.009","Scene.010","Scene.011","Scene.012",
149                              "World","World.000","World.001","World.002","World.003","World.004","World.005" ]
150         self.namesFog=[ "","LINEAR","EXPONENTIAL","" ]
151
152 ##########################################################
153 # Writing nodes routines
154 ##########################################################
155
156     def writeHeader(self):
157         bfile = sys.expandpath(Blender.Get('filename'))
158         self.file.write("#VRML V2.0 utf8\n\n")
159         self.file.write("# This file was authored with Blender (http://www.blender.org/)\n")
160         self.file.write("# Blender version %s\n" % Blender.Get('version'))
161         self.file.write("# Blender file %s\n" % sys.basename(bfile))
162         self.file.write("# Exported using VRML97 exporter v1.55 (2006/01/17)\n\n")
163
164     def writeInline(self):
165         inlines = Blender.Scene.Get()
166         allinlines = len(inlines)
167         if scene != inlines[0]:
168             return
169         else:
170             for i in range(allinlines):
171                 nameinline=inlines[i].getName()
172                 if (nameinline not in self.namesStandard) and (i > 0):
173                     self.writeIndented("DEF %s Inline {\n" % (self.cleanStr(nameinline)), 1)
174                     nameinline = nameinline+".wrl"
175                     self.writeIndented("url \"%s\" \n" % nameinline)
176                     self.writeIndented("}\n", -1)
177                     self.writeIndented("\n")
178
179     def writeScript(self):
180         textEditor = Blender.Text.Get() 
181         alltext = len(textEditor)
182         for i in range(alltext):
183             nametext = textEditor[i].getName()
184             nlines = textEditor[i].getNLines()
185             if (self.proto == 1):
186                 if (nametext == "proto" or nametext == "proto.js" or nametext == "proto.txt") and (nlines != None):
187                     nalllines = len(textEditor[i].asLines())
188                     alllines = textEditor[i].asLines()
189                     for j in range(nalllines):
190                         self.writeIndented(alllines[j] + "\n")
191             elif (self.proto == 0):
192                 if (nametext == "route" or nametext == "route.js" or nametext == "route.txt") and (nlines != None):
193                     nalllines = len(textEditor[i].asLines())
194                     alllines = textEditor[i].asLines()
195                     for j in range(nalllines):
196                         self.writeIndented(alllines[j] + "\n")
197         self.writeIndented("\n")
198
199     def writeViewpoint(self, thisObj):
200         context = scene.getRenderingContext()
201         ratio = float(context.imageSizeY())/float(context.imageSizeX())
202         lens = (360* (math.atan(ratio *16 / thisObj.data.getLens()) / math.pi))*(math.pi/180)
203         lens = min(lens, math.pi) 
204         # get the camera location, subtract 90 degress from X to orient like VRML does
205         loc = self.rotatePointForVRML(thisObj.loc)
206         rot = [thisObj.RotX - 1.57, thisObj.RotY, thisObj.RotZ]
207         nRot = self.rotatePointForVRML(rot)
208         # convert to Quaternion and to Angle Axis
209         Q  = self.eulerToQuaternions(nRot[0], nRot[1], nRot[2])
210         Q1 = self.multiplyQuaternions(Q[0], Q[1])
211         Qf = self.multiplyQuaternions(Q1, Q[2])
212         angleAxis = self.quaternionToAngleAxis(Qf)
213         self.writeIndented("DEF %s Viewpoint {\n" % (self.cleanStr(thisObj.name)), 1)
214         self.writeIndented("description \"%s\" \n" % (thisObj.name))
215         self.writeIndented("position %3.2f %3.2f %3.2f\n" % (loc[0], loc[1], loc[2]))
216         self.writeIndented("orientation %3.2f %3.2f %3.2f %3.2f\n" % (angleAxis[0], angleAxis[1], -angleAxis[2], angleAxis[3]))
217         self.writeIndented("fieldOfView %.3f\n" % (lens))
218         self.writeIndented("}\n", -1)
219         self.writeIndented("\n")
220
221     def writeFog(self):
222         if len(world) > 0:
223             mtype = world[0].getMistype()
224             mparam = world[0].getMist()
225             grd = world[0].getHor()
226             grd0, grd1, grd2 = grd[0], grd[1], grd[2]
227         else:
228             return
229         if (mtype == 1 or mtype == 2):
230             self.writeIndented("Fog {\n",1)
231             self.writeIndented("fogType \"%s\"\n" % self.namesFog[mtype])                                               
232             self.writeIndented("color %s %s %s" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)) + "\n")
233             self.writeIndented("visibilityRange %s\n" % round(mparam[2],self.cp))  
234             self.writeIndented("}\n",-1)
235             self.writeIndented("\n")   
236         else:
237             return
238
239     def writeNavigationInfo(self, scene):
240         allObj = []
241         allObj = scene.getChildren()
242         headlight = "TRUE"
243         vislimit = 0.0
244         for thisObj in allObj:
245             objType=thisObj.getType()
246             if objType == "Camera":
247                 vislimit = thisObj.data.getClipEnd()
248             elif objType == "Lamp":
249                 headlight = "FALSE"
250         self.writeIndented("NavigationInfo {\n",1)
251         self.writeIndented("headlight %s" % headlight + "\n")
252         self.writeIndented("visibilityLimit %s\n" % (round(vislimit,self.cp)))
253         self.writeIndented("type [\"EXAMINE\", \"ANY\"]\n")            
254         self.writeIndented("avatarSize [0.25, 1.75, 0.75]\n")
255         self.writeIndented("} \n",-1)
256         self.writeIndented(" \n")
257
258     def writeSpotLight(self, object, lamp):
259         if len(world) > 0:
260             ambi = world[0].getAmb()
261             ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
262         else:
263             ambi = 0
264             ambientIntensity = 0
265
266         # compute cutoff and beamwidth
267         intensity=min(lamp.energy/1.75,1.0)
268         beamWidth=((lamp.spotSize*math.pi)/180.0)*.37;
269         cutOffAngle=beamWidth*1.3
270
271         (dx,dy,dz)=self.computeDirection(object)
272         # note -dx seems to equal om[3][0]
273         # note -dz seems to equal om[3][1]
274         # note  dy seems to equal om[3][2]
275         om = object.getMatrix()
276             
277         location=self.rotVertex(om, (0,0,0));
278         radius = lamp.dist*math.cos(beamWidth)
279         self.writeIndented("DEF %s SpotLight {\n" % self.cleanStr(object.name),1)
280         self.writeIndented("radius %s\n" % (round(radius,self.cp)))
281         self.writeIndented("ambientIntensity %s\n" % (round(ambientIntensity,self.cp)))
282         self.writeIndented("intensity %s\n" % (round(intensity,self.cp)))
283         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)))
284         self.writeIndented("beamWidth %s\n" % (round(beamWidth,self.cp)))
285         self.writeIndented("cutOffAngle %s\n" % (round(cutOffAngle,self.cp)))
286         self.writeIndented("direction %s %s %s\n" % (round(dx,3),round(dy,3),round(dz,3)))
287         self.writeIndented("location %s %s %s\n" % (round(location[0],3), round(location[1],3), round(location[2],3)))
288         self.writeIndented("}\n",-1)
289         self.writeIndented("\n")
290         
291     def writeDirectionalLight(self, object, lamp):
292         if len(world) > 0:
293             ambi = world[0].getAmb()
294             ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
295         else:
296             ambi = 0
297             ambientIntensity = 0
298
299         intensity=min(lamp.energy/1.75,1.0) 
300         (dx,dy,dz)=self.computeDirection(object)
301         self.writeIndented("DEF %s DirectionalLight {\n" % self.cleanStr(object.name),1)
302         self.writeIndented("ambientIntensity %s\n" % (round(ambientIntensity,self.cp)))
303         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)))
304         self.writeIndented("intensity %s\n" % (round(intensity,self.cp)))
305         self.writeIndented("direction %s %s %s\n" % (round(dx,4),round(dy,4),round(dz,4)))
306         self.writeIndented("}\n",-1)
307         self.writeIndented("\n")
308
309     def writePointLight(self, object, lamp):
310         if len(world) > 0:
311             ambi = world[0].getAmb()
312             ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
313         else:
314             ambi = 0
315             ambientIntensity = 0
316         om = object.getMatrix()
317         location=self.rotVertex(om, (0,0,0));
318         intensity=min(lamp.energy/1.75,1.0) 
319         radius = lamp.dist
320         self.writeIndented("DEF %s PointLight {\n" % self.cleanStr(object.name),1)
321         self.writeIndented("ambientIntensity %s\n" % (round(ambientIntensity,self.cp)))
322         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)))
323         self.writeIndented("intensity %s\n" % (round(intensity,self.cp)))
324         self.writeIndented("location %s %s %s\n" % (round(location[0],3), round(location[1],3), round(location[2],3)))
325         self.writeIndented("radius %s\n" % radius )
326         self.writeIndented("}\n",-1)
327         self.writeIndented("\n")
328
329     def writeNode(self, thisObj):
330         objectname=str(thisObj.getName())
331         if objectname in self.namesStandard:
332             return
333         else:
334             (dx,dy,dz)=self.computeDirection(thisObj)
335             om = thisObj.getMatrix()
336             location=self.rotVertex(om, (0,0,0));
337             self.writeIndented("%s {\n" % objectname,1)
338             self.writeIndented("# direction %s %s %s\n" % (round(dx,3),round(dy,3),round(dz,3)))
339             self.writeIndented("# location %s %s %s\n" % (round(location[0],3), round(location[1],3), round(location[2],3)))
340             self.writeIndented("}\n",-1)
341             self.writeIndented("\n")
342
343     def secureName(self, name):
344         name = name + str(self.nodeID)
345         self.nodeID += 1
346         if len(name) <= 3:
347             newname = "_" + str(self.nodeID)
348             return "%s" % (newname)
349         else:
350             for bad in ['"','#',"'",',','.','[','\\',']','{','}']:
351                 name=name.replace(bad,'_')
352             if name in self.namesReserved:
353                 newname = name[0:3] + "_" + str(self.nodeID)
354                 return "%s" % (newname)
355             elif name[0].isdigit():
356                 newname = "_" + name + str(self.nodeID)
357                 return "%s" % (newname)
358             else:
359                 newname = name
360                 return "%s" % (newname)
361
362     def writeIndexedFaceSet(self, object, normals = 0):
363         imageMap={}   # set of used images
364         sided={}      # 'one':cnt , 'two':cnt
365         vColors={}    # 'multi':1
366         meshName = self.cleanStr(object.name)
367         mesh=object.getData()
368         meshME = self.cleanStr(mesh.name)
369         if len(mesh.faces) == 0:
370                                         return
371         for face in mesh.faces:
372             if face.mode & Blender.NMesh.FaceModes['HALO'] and self.halonode == 0:
373                 self.writeIndented("Billboard {\n",1)
374                 self.writeIndented("axisOfRotation 0 0 0\n")
375                 self.writeIndented("children [\n")
376                 self.halonode = 1
377             elif face.mode & Blender.NMesh.FaceModes['BILLBOARD'] and self.billnode == 0:
378                 self.writeIndented("Billboard {\n",1)
379                 self.writeIndented("axisOfRotation 0 1 0\n")
380                 self.writeIndented("children [\n")
381                 self.billnode = 1
382             elif face.mode & Blender.NMesh.FaceModes['OBCOL'] and self.matonly == 0:
383                 self.matonly = 1
384             elif face.mode & Blender.NMesh.FaceModes['SHAREDCOL'] and self.share == 0:
385                 self.share = 1
386             elif face.mode & Blender.NMesh.FaceModes['TILES'] and self.tilenode == 0:
387                 self.tilenode = 1
388             elif not face.mode & Blender.NMesh.FaceModes['DYNAMIC'] and self.collnode == 0:
389                 self.writeIndented("Collision {\n",1)
390                 self.writeIndented("collide FALSE\n")
391                 self.writeIndented("children [\n")
392                 self.collnode = 1
393
394         nIFSCnt=self.countIFSSetsNeeded(mesh, imageMap, sided, vColors)
395         
396         if nIFSCnt > 1:
397             self.writeIndented("DEF %s%s Group {\n" % ("G_", meshName),1)
398             self.writeIndented("children [\n",1)
399         
400         if sided.has_key('two') and sided['two'] > 0:
401             bTwoSided=1
402         else:
403             bTwoSided=0
404         om = object.getMatrix();
405         location=self.rotVertex(om, (0,0,0));
406         self.writeIndented("DEF %s Transform {\n" % meshName,1)
407         self.writeIndented("translation %s %s %s\n" % (round(location[0],3), round(location[1],3), round(location[2],3)),1)
408         self.writeIndented("children [\n")
409         self.writeIndented("Shape {\n",1)
410             
411         maters=mesh.materials
412         hasImageTexture=0
413         issmooth=0
414
415         if len(maters) > 0 or mesh.hasFaceUV():
416           self.writeIndented("appearance Appearance {\n", 1)
417           # right now this script can only handle a single material per mesh.
418           if len(maters) >= 1:
419             mat=Blender.Material.Get(maters[0].name)
420             matFlags = mat.getMode()
421             if not matFlags & Blender.Material.Modes['TEXFACE']:
422               self.writeMaterial(mat, self.cleanStr(maters[0].name,''))
423               if len(maters) > 1:
424                 print "Warning: mesh named %s has multiple materials" % meshName
425                 print "Warning: only one material per object handled"
426         
427             #-- textures
428             if mesh.hasFaceUV():
429                 for face in mesh.faces:
430                     if (hasImageTexture == 0) and (face.image):
431                         self.writeImageTexture(face.image.name, face.image.filename)
432                         hasImageTexture=1  # keep track of face texture
433             if self.tilenode == 1:
434                 self.writeIndented("textureTransform TextureTransform   { scale %s %s }\n" % (face.image.xrep, face.image.yrep))
435                 self.tilenode = 0
436             self.writeIndented("}\n", -1)
437
438         #-- IndexedFaceSet or IndexedLineSet
439
440         # check if object is wireframe only
441         if object.drawType == Blender.Object.DrawTypes.WIRE:
442             # user selected WIRE=2 on the Drawtype=Wire on (F9) Edit page
443             ifStyle="IndexedLineSet"
444             self.wire = 1
445         else:
446             # user selected BOUNDS=1, SOLID=3, SHARED=4, or TEXTURE=5
447             ifStyle="IndexedFaceSet"
448         # look up mesh name, use it if available
449         if self.meshNames.has_key(meshME):
450             self.writeIndented("geometry USE ME_%s\n" % meshME)
451             self.meshNames[meshME]+=1
452         else:
453             if int(mesh.users) > 1:
454                 self.writeIndented("geometry DEF ME_%s %s {\n" % (meshME, ifStyle), 1)
455                 self.meshNames[meshME]=1
456             else:
457                 self.writeIndented("geometry %s {\n" % ifStyle, 1)
458             if object.drawType != Blender.Object.DrawTypes.WIRE:
459                 if bTwoSided == 1:
460                     self.writeIndented("solid FALSE\n")
461                 else:
462                     self.writeIndented("solid TRUE\n")
463
464             #--- output coordinates
465             self.writeCoordinates(object, mesh, meshName)
466         
467             if object.drawType != Blender.Object.DrawTypes.WIRE:
468                 #--- output textureCoordinates if UV texture used
469                 if mesh.hasFaceUV():
470                     if hasImageTexture == 1:
471                         self.writeTextureCoordinates(mesh)
472                     elif self.matonly == 1 and self.share == 1:
473                         self.writeFaceColors(mesh)
474
475             for face in mesh.faces:
476                 if face.smooth:
477                      issmooth=1
478             if issmooth==1 and self.wire == 0:
479                 creaseAngle=(mesh.getMaxSmoothAngle())*(math.pi/180.0)
480                 self.writeIndented("creaseAngle %s\n" % (round(creaseAngle,self.cp)))
481
482             #--- output vertexColors
483             if self.share == 1 and self.matonly == 0:
484                 self.writeVertexColors(mesh)
485             #--- output closing braces
486             self.writeIndented("}\n", -1)
487         self.writeIndented("}\n", -1)
488         self.writeIndented("]\n", -1)
489         self.matonly = 0
490         self.share = 0
491         self.wire = 0
492         self.writeIndented("}\n", -1)
493
494         if self.halonode == 1:
495             self.writeIndented("]\n", -1)
496             self.writeIndented("}\n", -1)
497             self.halonode = 0
498
499         if self.billnode == 1:
500             self.writeIndented("]\n", -1)
501             self.writeIndented("}\n", -1)
502             self.billnode = 0
503
504         if self.collnode == 1:
505             self.writeIndented("]\n", -1)
506             self.writeIndented("}\n", -1)
507             self.collnode = 0
508
509         if nIFSCnt > 1:
510             self.writeIndented("]\n", -1)
511             self.writeIndented("}\n", -1)
512
513         self.writeIndented("\n")
514
515     def writeCoordinates(self, object, mesh, meshName):
516         #-- vertices
517         self.writeIndented("coord DEF %s%s Coordinate {\n" % ("coord_",meshName), 1)
518         self.writeIndented("point [\n\t\t\t\t\t\t", 1)
519         meshVertexList = mesh.verts
520
521         # create vertex list and pre rotate -90 degrees X for VRML
522         mm=object.getMatrix()
523         location=self.rotVertex(mm, (0,0,0));
524         for vertex in meshVertexList:
525             v=self.rotVertex(mm, vertex);
526             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) ))
527         self.writeIndented("\n", 0)
528         self.writeIndented("]\n", -1)
529         self.writeIndented("}\n", -1)
530
531         self.writeIndented("coordIndex [\n\t\t\t\t\t", 1)
532         coordIndexList=[]  
533         for face in mesh.faces:
534             cordStr=""
535             for i in range(len(face)):
536                 indx=meshVertexList.index(face[i])
537                 cordStr = cordStr + "%s " % indx
538             self.file.write(cordStr + "-1, ")
539         self.writeIndented("\n", 0)
540         self.writeIndented("]\n", -1)
541
542     def writeTextureCoordinates(self, mesh):
543         texCoordList=[] 
544         texIndexList=[]
545         j=0
546
547         for face in mesh.faces:
548             for i in range(len(face)):
549                 texIndexList.append(j)
550                 texCoordList.append(face.uv[i])
551                 j=j+1
552             texIndexList.append(-1)
553
554         self.writeIndented("texCoord TextureCoordinate {\n", 1)
555         self.writeIndented("point [\n\t\t\t\t\t\t", 1)
556         for i in range(len(texCoordList)):
557             self.file.write("%s %s, " % (round(texCoordList[i][0],self.tp), round(texCoordList[i][1],self.tp)))
558         self.writeIndented("\n", 0)
559         self.writeIndented("]\n", -1)
560         self.writeIndented("}\n", -1)
561
562         self.writeIndented("texCoordIndex [\n\t\t\t\t\t\t", 1)
563         texIndxStr=""
564         for i in range(len(texIndexList)):
565             texIndxStr = texIndxStr + "%d, " % texIndexList[i]
566             if texIndexList[i]==-1:
567                 self.file.write(texIndxStr)
568                 texIndxStr=""
569         self.writeIndented("\n", 0)
570         self.writeIndented("]\n", -1)
571
572     def writeFaceColors(self, mesh):
573         self.writeIndented("colorPerVertex FALSE\n")
574         self.writeIndented("color Color {\n",1)
575         self.writeIndented("color [\n\t\t\t\t\t\t", 1)
576
577         for face in mesh.faces:
578             if face.col:
579                 c=face.col[0]
580                 if self.verbose > 2:
581                     print "Debug: face.col r=%d g=%d b=%d" % (c.r, c.g, c.b)
582
583                 aColor = self.rgbToFS(c)
584                 self.file.write("%s, " % aColor)
585         self.writeIndented("\n", 0)
586         self.writeIndented("]\n",-1)
587         self.writeIndented("}\n",-1)
588
589     def writeVertexColors(self, mesh):
590         self.writeIndented("colorPerVertex TRUE\n")
591         self.writeIndented("color Color {\n",1)
592         self.writeIndented("color [\n\t\t\t\t\t\t", 1)
593
594         for i in range(len(mesh.verts)):
595             c=self.getVertexColorByIndx(mesh,i)
596             if self.verbose > 2:
597                 print "Debug: vertex[%d].col r=%d g=%d b=%d" % (i, c.r, c.g, c.b)
598
599             aColor = self.rgbToFS(c)
600             self.file.write("%s, " % aColor)
601         self.writeIndented("\n", 0)
602         self.writeIndented("]\n",-1)
603         self.writeIndented("}\n",-1)
604
605     def writeMaterial(self, mat, matName):
606         # look up material name, use it if available
607         if self.matNames.has_key(matName):
608             self.writeIndented("material USE MA_%s\n" % matName)
609             self.matNames[matName]+=1
610             return;
611         
612         self.matNames[matName]=1
613
614         ambient = mat.amb/3
615         diffuseR, diffuseG, diffuseB = mat.rgbCol[0], mat.rgbCol[1],mat.rgbCol[2]
616         if len(world) > 0:
617             ambi = world[0].getAmb()
618             ambi0, ambi1, ambi2 = (ambi[0]*mat.amb)*2, (ambi[1]*mat.amb)*2, (ambi[2]*mat.amb)*2
619         else:
620             ambi0, ambi1, ambi2 = 0, 0, 0
621         emisR, emisG, emisB = (diffuseR*mat.emit+ambi0)/2, (diffuseG*mat.emit+ambi1)/2, (diffuseB*mat.emit+ambi2)/2
622
623         shininess = mat.hard/512.0
624         specR = (mat.specCol[0]+0.001)/(1.25/(mat.getSpec()+0.001))
625         specG = (mat.specCol[1]+0.001)/(1.25/(mat.getSpec()+0.001))
626         specB = (mat.specCol[2]+0.001)/(1.25/(mat.getSpec()+0.001))
627         transp = 1-mat.alpha
628         matFlags = mat.getMode()
629         if matFlags & Blender.Material.Modes['SHADELESS']:
630           ambient = 1
631           shine = 1
632           specR = emitR = diffuseR
633           specG = emitG = diffuseG
634           specB = emitB = diffuseB
635         self.writeIndented("material DEF MA_%s Material {\n" % matName, 1)
636         self.writeIndented("diffuseColor %s %s %s\n" % (round(diffuseR,self.cp), round(diffuseG,self.cp), round(diffuseB,self.cp)))
637         self.writeIndented("ambientIntensity %s\n" % (round(ambient,self.cp)))
638         self.writeIndented("specularColor %s %s %s\n" % (round(specR,self.cp), round(specG,self.cp), round(specB,self.cp)))
639         self.writeIndented("emissiveColor  %s %s %s\n" % (round(emisR,self.cp), round(emisG,self.cp), round(emisB,self.cp)))
640         self.writeIndented("shininess %s\n" % (round(shininess,self.cp)))
641         self.writeIndented("transparency %s\n" % (round(transp,self.cp)))
642         self.writeIndented("}\n",-1)
643
644     def writeImageTexture(self, name, filename):
645         if self.texNames.has_key(name):
646             self.writeIndented("texture USE %s\n" % self.cleanStr(name))
647             self.texNames[name] += 1
648             return
649         else:
650             self.writeIndented("texture DEF %s ImageTexture {\n" % self.cleanStr(name), 1)
651             self.writeIndented("url \"%s\"\n" % filename.split("\\")[-1].split("/")[-1])
652             self.writeIndented("}\n",-1)
653             self.texNames[name] = 1
654
655     def writeBackground(self):
656         if len(world) > 0:
657             worldname = world[0].getName()
658         else:
659             return
660         blending = world[0].getSkytype()        
661         grd = world[0].getHor()
662         grd0, grd1, grd2 = grd[0], grd[1], grd[2]
663         sky = world[0].getZen()
664         sky0, sky1, sky2 = sky[0], sky[1], sky[2]
665         mix0, mix1, mix2 = grd[0]+sky[0], grd[1]+sky[1], grd[2]+sky[2]
666         mix0, mix1, mix2 = mix0/2, mix1/2, mix2/2
667         if worldname in self.namesStandard:
668             self.writeIndented("Background {\n",1)
669         else:
670             self.writeIndented("DEF %s Background {\n" % self.secureName(worldname),1)
671         # No Skytype - just Hor color
672         if blending == 0:
673             self.writeIndented("groundColor %s %s %s\n" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
674             self.writeIndented("skyColor %s %s %s\n" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
675         # Blend Gradient
676         elif blending == 1:
677             self.writeIndented("groundColor [ %s %s %s, " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
678             self.writeIndented("%s %s %s ]\n" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
679             self.writeIndented("groundAngle [ 1.57, 1.57 ]\n")
680             self.writeIndented("skyColor [ %s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
681             self.writeIndented("%s %s %s ]\n" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
682             self.writeIndented("skyAngle [ 1.57, 1.57 ]\n")
683         # Blend+Real Gradient Inverse
684         elif blending == 3:
685             self.writeIndented("groundColor [ %s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
686             self.writeIndented("%s %s %s ]\n" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
687             self.writeIndented("groundAngle [ 1.57, 1.57 ]\n")
688             self.writeIndented("skyColor [ %s %s %s, " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
689             self.writeIndented("%s %s %s ]\n" %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
690             self.writeIndented("skyAngle [ 1.57, 1.57 ]\n")
691         # Paper - just Zen Color
692         elif blending == 4:
693             self.writeIndented("groundColor %s %s %s\n" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
694             self.writeIndented("skyColor %s %s %s\n" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
695         # Blend+Real+Paper - komplex gradient
696         elif blending == 7:
697             self.writeIndented("groundColor [ %s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
698             self.writeIndented("%s %s %s ]\n" %(round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
699             self.writeIndented("groundAngle [ 1.57, 1.57 ]\n")
700             self.writeIndented("skyColor [ %s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
701             self.writeIndented("%s %s %s ]\n" %(round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
702             self.writeIndented("skyAngle [ 1.57, 1.57 ]\n")
703         # Any Other two colors
704         else:
705             self.writeIndented("groundColor %s %s %s\n" % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
706             self.writeIndented("skyColor %s %s %s\n" % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
707         alltexture = len(worldmat)
708         for i in range(alltexture):
709             namemat = worldmat[i].getName()
710             pic = worldmat[i].getImage()        
711             if (namemat == "back") and (pic != None):
712                 self.writeIndented("backUrl \"%s\"\n" % str(pic.getName()))
713             elif (namemat == "bottom") and (pic != None):
714                 self.writeIndented("bottomUrl \"%s\"\n" % str(pic.getName()))
715             elif (namemat == "front") and (pic != None):
716                 self.writeIndented("frontUrl \"%s\"\n" % str(pic.getName()))
717             elif (namemat == "left") and (pic != None):
718                 self.writeIndented("leftUrl \"%s\"\n" % str(pic.getName()))
719             elif (namemat == "right") and (pic != None):
720                 self.writeIndented("rightUrl \"%s\"\n" % str(pic.getName()))
721             elif (namemat == "top") and (pic != None):
722                 self.writeIndented("topUrl \"%s\"\n" % str(pic.getName()))
723         self.writeIndented("}",-1)
724         self.writeIndented("\n\n")
725
726 ##########################################################
727 # export routine
728 ##########################################################
729
730     def export(self, scene, world, worldmat):
731         print "Info: starting VRML97 export to " + self.filename + "..."
732         self.writeHeader()
733         self.writeScript()
734         self.writeNavigationInfo(scene)
735         self.writeBackground()
736         self.writeFog()
737         self.proto = 0
738         allObj = []
739         if ARG == 'selected':
740             allObj = Blender.Object.GetSelected()
741         else:
742             allObj = scene.getChildren()
743             self.writeInline()
744         for thisObj in allObj:
745             try:
746                 objType=thisObj.getType()
747                 objName=thisObj.getName()
748                 self.matonly = 0
749                 if objType == "Camera":
750                     self.writeViewpoint(thisObj)
751                 elif objType == "Mesh":
752                     self.writeIndexedFaceSet(thisObj, normals = 0)
753                 elif objType == "Lamp":
754                     lmpName=Lamp.Get(thisObj.data.getName())
755                     lmpType=lmpName.getType()
756                     if lmpType == Lamp.Types.Lamp:
757                         self.writePointLight(thisObj, lmpName)
758                     elif lmpType == Lamp.Types.Spot:
759                         self.writeSpotLight(thisObj, lmpName)
760                     elif lmpType == Lamp.Types.Sun:
761                         self.writeDirectionalLight(thisObj, lmpName)
762                     else:
763                         self.writeDirectionalLight(thisObj, lmpName)
764                 elif objType == "Empty" and objName != "Empty":
765                     self.writeNode(thisObj)
766                 else:
767                     #print "Info: Ignoring [%s], object type [%s] not handle yet" % (object.name,object.getType())
768                     print ""
769             except AttributeError:
770                 print "Error: Unable to get type info for %s" % thisObj.getName()
771         if ARG != 'selected':
772             self.writeScript()
773         self.cleanup()
774         
775 ##########################################################
776 # Utility methods
777 ##########################################################
778
779     def cleanup(self):
780         self.file.close()
781         self.texNames={}
782         self.matNames={}
783         self.indentLevel=0
784         print "Info: finished VRML97 export to %s\n" % self.filename
785
786     def cleanStr(self, name, prefix='rsvd_'):
787         """cleanStr(name,prefix) - try to create a valid VRML DEF name from object name"""
788
789         newName=name[:]
790         if len(newName) == 0:
791             self.nNodeID+=1
792             return "%s%d" % (prefix, self.nNodeID)
793         
794         if newName in self.namesReserved:
795             newName='%s%s' % (prefix,newName)
796         
797         if newName[0].isdigit():
798             newName='%s%s' % ('_',newName)
799
800         for bad in [' ','"','#',"'",',','.','[','\\',']','{','}']:
801             newName=newName.replace(bad,'_')
802         return newName
803
804     def countIFSSetsNeeded(self, mesh, imageMap, sided, vColors):
805         """
806         countIFFSetsNeeded() - should look at a blender mesh to determine
807         how many VRML IndexFaceSets or IndexLineSets are needed.  A
808         new mesh created under the following conditions:
809         
810          o - split by UV Textures / one per mesh
811          o - split by face, one sided and two sided
812          o - split by smooth and flat faces
813          o - split when faces only have 2 vertices * needs to be an IndexLineSet
814         """
815         
816         imageNameMap={}
817         faceMap={}
818         nFaceIndx=0
819         
820         for face in mesh.faces:
821             sidename='';
822             if (face.mode & NMesh.FaceModes.TWOSIDE) == NMesh.FaceModes.TWOSIDE:
823                 sidename='two'
824             else:
825                 sidename='one'
826
827             if not vColors.has_key('multi'):
828                 for face in mesh.faces:
829                     if face.col:
830                         c=face.col[0]
831                         if c.r != 255 and c.g != 255 and c.b !=255:
832                             vColors['multi']=1
833
834             if sided.has_key(sidename):
835                 sided[sidename]+=1
836             else:
837                 sided[sidename]=1
838
839             if face.image:
840                 faceName="%s_%s" % (face.image.name, sidename);
841
842                 if imageMap.has_key(faceName):
843                     imageMap[faceName].append(face)
844                 else:
845                     imageMap[faceName]=[face.image.name,sidename,face]
846
847         if self.verbose > 2:
848             for faceName in imageMap.keys():
849                 ifs=imageMap[faceName]
850                 print "Debug: faceName=%s image=%s, solid=%s facecnt=%d" % \
851                       (faceName, ifs[0], ifs[1], len(ifs)-2)
852
853         return len(imageMap.keys())
854     
855     def faceToString(self,face):
856
857         print "Debug: face.flag=0x%x (bitflags)" % face.flag
858         if face.flag & NMesh.FaceFlags.SELECT == NMesh.FaceFlags.SELECT:
859             print "Debug: face.flag.SELECT=true"
860
861         print "Debug: face.mode=0x%x (bitflags)" % face.mode
862         if (face.mode & NMesh.FaceModes.TWOSIDE) == NMesh.FaceModes.TWOSIDE:
863             print "Debug: face.mode twosided"
864
865         print "Debug: face.transp=0x%x (enum)" % face.transp
866         if face.transp == NMesh.FaceTranspModes.SOLID:
867             print "Debug: face.transp.SOLID"
868
869         if face.image:
870             print "Debug: face.image=%s" % face.image.name
871         print "Debug: face.materialIndex=%d" % face.materialIndex 
872
873     def getVertexColorByIndx(self, mesh, indx):
874         for face in mesh.faces:
875             j=0
876             for vertex in face.v:
877                 if vertex.index == indx:
878                     c=face.col[j]
879                 j=j+1
880         return c
881
882     def meshToString(self,mesh):
883         print "Debug: mesh.hasVertexUV=%d" % mesh.hasVertexUV()
884         print "Debug: mesh.hasFaceUV=%d" % mesh.hasFaceUV()
885         print "Debug: mesh.hasVertexColours=%d" % mesh.hasVertexColours()
886         print "Debug: mesh.verts=%d" % len(mesh.verts)
887         print "Debug: mesh.faces=%d" % len(mesh.faces)
888         print "Debug: mesh.materials=%d" % len(mesh.materials)
889
890     def rgbToFS(self, c):
891         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))
892         return s
893
894     def computeDirection(self, object):
895         x,y,z=(0,-1.0,0) # point down
896         ax,ay,az = (object.RotX,object.RotZ,object.RotY)
897
898         # rot X
899         x1=x
900         y1=y*math.cos(ax)-z*math.sin(ax)
901         z1=y*math.sin(ax)+z*math.cos(ax)
902
903         # rot Y
904         x2=x1*math.cos(ay)+z1*math.sin(ay)
905         y2=y1
906         z2=z1*math.cos(ay)-x1*math.sin(ay)
907
908         # rot Z
909         x3=x2*math.cos(az)-y2*math.sin(az)
910         y3=x2*math.sin(az)+y2*math.cos(az)
911         z3=z2
912
913         return [x3,y3,z3]
914         
915
916     # swap Y and Z to handle axis difference between Blender and VRML
917     #------------------------------------------------------------------------
918     def rotatePointForVRML(self, v):
919         x = v[0]
920         y = v[2]
921         z = -v[1]
922         
923         vrmlPoint=[x, y, z]
924         return vrmlPoint
925     
926     def rotVertex(self, mm, v):
927         lx,ly,lz=v[0],v[1],v[2]
928         gx=(mm[0][0]*lx + mm[1][0]*ly + mm[2][0]*lz) + mm[3][0]
929         gy=((mm[0][2]*lx + mm[1][2]*ly+ mm[2][2]*lz) + mm[3][2])
930         gz=-((mm[0][1]*lx + mm[1][1]*ly + mm[2][1]*lz) + mm[3][1])
931         rotatedv=[gx,gy,gz]
932         return rotatedv
933
934     # For writing well formed VRML code
935     #------------------------------------------------------------------------
936     def writeIndented(self, s, inc=0):
937         if inc < 1:
938             self.indentLevel = self.indentLevel + inc
939         
940         self.file.write( self.indentLevel*"\t" + s)
941
942         if inc > 0:
943             self.indentLevel = self.indentLevel + inc
944
945     # Converts a Euler to three new Quaternions
946     # Angles of Euler are passed in as radians
947     #------------------------------------------------------------------------
948     def eulerToQuaternions(self, x, y, z):
949         Qx = [math.cos(x/2), math.sin(x/2), 0, 0]
950         Qy = [math.cos(y/2), 0, math.sin(y/2), 0]
951         Qz = [math.cos(z/2), 0, 0, math.sin(z/2)]
952         
953         quaternionVec=[Qx,Qy,Qz]
954         return quaternionVec
955     
956     # Multiply two Quaternions together to get a new Quaternion
957     #------------------------------------------------------------------------
958     def multiplyQuaternions(self, Q1, Q2):
959         result = [((Q1[0] * Q2[0]) - (Q1[1] * Q2[1]) - (Q1[2] * Q2[2]) - (Q1[3] * Q2[3])),
960                   ((Q1[0] * Q2[1]) + (Q1[1] * Q2[0]) + (Q1[2] * Q2[3]) - (Q1[3] * Q2[2])),
961                   ((Q1[0] * Q2[2]) + (Q1[2] * Q2[0]) + (Q1[3] * Q2[1]) - (Q1[1] * Q2[3])),
962                   ((Q1[0] * Q2[3]) + (Q1[3] * Q2[0]) + (Q1[1] * Q2[2]) - (Q1[2] * Q2[1]))]
963         
964         return result
965     
966     # Convert a Quaternion to an Angle Axis (ax, ay, az, angle)
967     # angle is in radians
968     #------------------------------------------------------------------------
969     def quaternionToAngleAxis(self, Qf):
970         scale = math.pow(Qf[1],2) + math.pow(Qf[2],2) + math.pow(Qf[3],2)
971         ax = Qf[1]
972         ay = Qf[2]
973         az = Qf[3]
974
975         if scale > .0001:
976             ax/=scale
977             ay/=scale
978             az/=scale
979         
980         angle = 2 * math.acos(Qf[0])
981         
982         result = [ax, ay, az, angle]
983         return result
984
985 ##########################################################
986 # Callbacks, needed before Main
987 ##########################################################
988
989 def select_file(filename):
990   if pytinst == 1:
991     if exists(filename) and _safeOverwrite:
992       result = Draw.PupMenu("File Already Exists, Overwrite?%t|Yes%x1|No%x0")
993       if(result != 1):
994         return
995
996   if not filename.endswith(extension):
997     filename += extension
998
999   wrlexport=VRML2Export(filename)
1000   wrlexport.export(scene, world, worldmat)
1001
1002 def createWRLPath():
1003   filename = Blender.Get('filename')
1004   print filename
1005   
1006   if filename.find('.') != -1:
1007     filename = filename.split('.')[0]
1008     filename += extension
1009     print filename
1010
1011   return filename
1012
1013 #########################################################
1014 # main routine
1015 #########################################################
1016
1017 try:
1018     ARG = __script__['arg'] # user selected argument
1019 except:
1020     print "older version"
1021
1022 if Blender.Get('version') < 235:
1023   print "Warning: VRML97 export failed, wrong blender version!"
1024   print " You aren't running blender version 2.35 or greater"
1025   print " download a newer version from http://blender3d.org/"
1026 else:
1027   if ARG == 'comp':
1028     extension=".wrz"
1029     from gzip import *
1030   else:
1031     extension=".wrl"
1032   Blender.Window.FileSelector(select_file,"Export VRML97",createWRLPath())
1033