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