Manual merge of soc-2009-kazanbas branch:
[blender.git] / release / io / export_x3d.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: export_x3d.py 23222 2009-09-14 14:55:49Z kazanbas $
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 math
57 import os
58
59 import bpy
60 import Mathutils
61
62 from export_3ds import create_derived_objects, free_derived_objects
63
64 # import Blender
65 # from Blender import Object, Lamp, Draw, Image, Text, sys, Mesh
66 # from Blender.Scene import Render
67 # import BPyObject
68 # import BPyMesh
69
70
71 DEG2RAD=0.017453292519943295
72 MATWORLD= Mathutils.RotationMatrix(-90, 4, 'x')
73
74 ####################################
75 # Global Variables
76 ####################################
77
78 filename = ""
79 # filename = Blender.Get('filename')
80 _safeOverwrite = True
81
82 extension = ''
83
84 ##########################################################
85 # Functions for writing output file
86 ##########################################################
87
88 class x3d_class:
89
90         def __init__(self, filename):
91                 #--- public you can change these ---
92                 self.writingcolor = 0
93                 self.writingtexture = 0
94                 self.writingcoords = 0
95                 self.proto = 1
96                 self.matonly = 0
97                 self.share = 0
98                 self.billnode = 0
99                 self.halonode = 0
100                 self.collnode = 0
101                 self.tilenode = 0
102                 self.verbose=2   # level of verbosity in console 0-none, 1-some, 2-most
103                 self.cp=3                 # decimals for material color values   0.000 - 1.000
104                 self.vp=3                 # decimals for vertex coordinate values  0.000 - n.000
105                 self.tp=3                 # decimals for texture coordinate values 0.000 - 1.000
106                 self.it=3
107                 
108                 #--- class private don't touch ---
109                 self.texNames={}   # dictionary of textureNames
110                 self.matNames={}   # dictionary of materiaNames
111                 self.meshNames={}   # dictionary of meshNames
112                 self.indentLevel=0 # keeps track of current indenting
113                 self.filename=filename
114                 self.file = None
115                 if filename.lower().endswith('.x3dz'):
116                         try:
117                                 import gzip
118                                 self.file = gzip.open(filename, "w")                            
119                         except:
120                                 print("failed to import compression modules, exporting uncompressed")
121                                 self.filename = filename[:-1] # remove trailing z
122                 
123                 if self.file == None:
124                         self.file = open(self.filename, "w")
125
126                 self.bNav=0
127                 self.nodeID=0
128                 self.namesReserved=[ "Anchor","Appearance","Arc2D","ArcClose2D","AudioClip","Background","Billboard",
129                                                          "BooleanFilter","BooleanSequencer","BooleanToggle","BooleanTrigger","Box","Circle2D",
130                                                          "Collision","Color","ColorInterpolator","ColorRGBA","component","Cone","connect",
131                                                          "Contour2D","ContourPolyline2D","Coordinate","CoordinateDouble","CoordinateInterpolator",
132                                                          "CoordinateInterpolator2D","Cylinder","CylinderSensor","DirectionalLight","Disk2D",
133                                                          "ElevationGrid","EspduTransform","EXPORT","ExternProtoDeclare","Extrusion","field",
134                                                          "fieldValue","FillProperties","Fog","FontStyle","GeoCoordinate","GeoElevationGrid",
135                                                          "GeoLocationLocation","GeoLOD","GeoMetadata","GeoOrigin","GeoPositionInterpolator",
136                                                          "GeoTouchSensor","GeoViewpoint","Group","HAnimDisplacer","HAnimHumanoid","HAnimJoint",
137                                                          "HAnimSegment","HAnimSite","head","ImageTexture","IMPORT","IndexedFaceSet",
138                                                          "IndexedLineSet","IndexedTriangleFanSet","IndexedTriangleSet","IndexedTriangleStripSet",
139                                                          "Inline","IntegerSequencer","IntegerTrigger","IS","KeySensor","LineProperties","LineSet",
140                                                          "LoadSensor","LOD","Material","meta","MetadataDouble","MetadataFloat","MetadataInteger",
141                                                          "MetadataSet","MetadataString","MovieTexture","MultiTexture","MultiTextureCoordinate",
142                                                          "MultiTextureTransform","NavigationInfo","Normal","NormalInterpolator","NurbsCurve",
143                                                          "NurbsCurve2D","NurbsOrientationInterpolator","NurbsPatchSurface",
144                                                          "NurbsPositionInterpolator","NurbsSet","NurbsSurfaceInterpolator","NurbsSweptSurface",
145                                                          "NurbsSwungSurface","NurbsTextureCoordinate","NurbsTrimmedSurface","OrientationInterpolator",
146                                                          "PixelTexture","PlaneSensor","PointLight","PointSet","Polyline2D","Polypoint2D",
147                                                          "PositionInterpolator","PositionInterpolator2D","ProtoBody","ProtoDeclare","ProtoInstance",
148                                                          "ProtoInterface","ProximitySensor","ReceiverPdu","Rectangle2D","ROUTE","ScalarInterpolator",
149                                                          "Scene","Script","Shape","SignalPdu","Sound","Sphere","SphereSensor","SpotLight","StaticGroup",
150                                                          "StringSensor","Switch","Text","TextureBackground","TextureCoordinate","TextureCoordinateGenerator",
151                                                          "TextureTransform","TimeSensor","TimeTrigger","TouchSensor","Transform","TransmitterPdu",
152                                                          "TriangleFanSet","TriangleSet","TriangleSet2D","TriangleStripSet","Viewpoint","VisibilitySensor",
153                                                          "WorldInfo","X3D","XvlShell","VertexShader","FragmentShader","MultiShaderAppearance","ShaderAppearance" ]
154                 self.namesStandard=[ "Empty","Empty.000","Empty.001","Empty.002","Empty.003","Empty.004","Empty.005",
155                                                          "Empty.006","Empty.007","Empty.008","Empty.009","Empty.010","Empty.011","Empty.012",
156                                                          "Scene.001","Scene.002","Scene.003","Scene.004","Scene.005","Scene.06","Scene.013",
157                                                          "Scene.006","Scene.007","Scene.008","Scene.009","Scene.010","Scene.011","Scene.012",
158                                                          "World","World.000","World.001","World.002","World.003","World.004","World.005" ]
159                 self.namesFog=[ "","LINEAR","EXPONENTIAL","" ]
160
161 ##########################################################
162 # Writing nodes routines
163 ##########################################################
164
165         def writeHeader(self):
166                 #bfile = sys.expandpath( Blender.Get('filename') ).replace('<', '&lt').replace('>', '&gt')
167                 bfile = self.filename.replace('<', '&lt').replace('>', '&gt') # use outfile name
168                 self.file.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
169                 self.file.write("<!DOCTYPE X3D PUBLIC \"ISO//Web3D//DTD X3D 3.0//EN\" \"http://www.web3d.org/specifications/x3d-3.0.dtd\">\n")
170                 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")
171                 self.file.write("<head>\n")
172                 self.file.write("\t<meta name=\"filename\" content=\"%s\" />\n" % os.path.basename(bfile))
173                 # self.file.write("\t<meta name=\"filename\" content=\"%s\" />\n" % sys.basename(bfile))
174                 self.file.write("\t<meta name=\"generator\" content=\"Blender %s\" />\n" % '2.5')
175                 # self.file.write("\t<meta name=\"generator\" content=\"Blender %s\" />\n" % Blender.Get('version'))
176                 self.file.write("\t<meta name=\"translator\" content=\"X3D exporter v1.55 (2006/01/17)\" />\n")
177                 self.file.write("</head>\n")
178                 self.file.write("<Scene>\n")
179         
180         # This functionality is poorly defined, disabling for now - campbell
181         '''
182         def writeInline(self):
183                 inlines = Blender.Scene.Get()
184                 allinlines = len(inlines)
185                 if scene != inlines[0]:
186                         return
187                 else:
188                         for i in xrange(allinlines):
189                                 nameinline=inlines[i].name
190                                 if (nameinline not in self.namesStandard) and (i > 0):
191                                         self.file.write("<Inline DEF=\"%s\" " % (self.cleanStr(nameinline)))
192                                         nameinline = nameinline+".x3d"
193                                         self.file.write("url=\"%s\" />" % nameinline)
194                                         self.file.write("\n\n")
195
196         
197         def writeScript(self):
198                 textEditor = Blender.Text.Get() 
199                 alltext = len(textEditor)
200                 for i in xrange(alltext):
201                         nametext = textEditor[i].name
202                         nlines = textEditor[i].getNLines()
203                         if (self.proto == 1):
204                                 if (nametext == "proto" or nametext == "proto.js" or nametext == "proto.txt") and (nlines != None):
205                                         nalllines = len(textEditor[i].asLines())
206                                         alllines = textEditor[i].asLines()
207                                         for j in xrange(nalllines):
208                                                 self.writeIndented(alllines[j] + "\n")
209                         elif (self.proto == 0):
210                                 if (nametext == "route" or nametext == "route.js" or nametext == "route.txt") and (nlines != None):
211                                         nalllines = len(textEditor[i].asLines())
212                                         alllines = textEditor[i].asLines()
213                                         for j in xrange(nalllines):
214                                                 self.writeIndented(alllines[j] + "\n")
215                 self.writeIndented("\n")
216         '''
217         
218         def writeViewpoint(self, ob, mat, scene):
219                 context = scene.render_data
220                 # context = scene.render
221                 ratio = float(context.resolution_x)/float(context.resolution_y)
222                 # ratio = float(context.imageSizeY())/float(context.imageSizeX())
223                 lens = (360* (math.atan(ratio *16 / ob.data.lens) / math.pi))*(math.pi/180)
224                 # lens = (360* (math.atan(ratio *16 / ob.data.getLens()) / math.pi))*(math.pi/180)
225                 lens = min(lens, math.pi) 
226                 
227                 # get the camera location, subtract 90 degress from X to orient like X3D does
228                 # mat = ob.matrixWorld - mat is now passed!
229                 
230                 loc = self.rotatePointForVRML(mat.translationPart())
231                 rot = mat.toEuler()
232                 rot = (((rot[0]-90)), rot[1], rot[2])
233                 # rot = (((rot[0]-90)*DEG2RAD), rot[1]*DEG2RAD, rot[2]*DEG2RAD)
234                 nRot = self.rotatePointForVRML( rot )
235                 # convert to Quaternion and to Angle Axis
236                 Q  = self.eulerToQuaternions(nRot[0], nRot[1], nRot[2])
237                 Q1 = self.multiplyQuaternions(Q[0], Q[1])
238                 Qf = self.multiplyQuaternions(Q1, Q[2])
239                 angleAxis = self.quaternionToAngleAxis(Qf)
240                 self.file.write("<Viewpoint DEF=\"%s\" " % (self.cleanStr(ob.name)))
241                 self.file.write("description=\"%s\" " % (ob.name))
242                 self.file.write("centerOfRotation=\"0 0 0\" ")
243                 self.file.write("position=\"%3.2f %3.2f %3.2f\" " % (loc[0], loc[1], loc[2]))
244                 self.file.write("orientation=\"%3.2f %3.2f %3.2f %3.2f\" " % (angleAxis[0], angleAxis[1], -angleAxis[2], angleAxis[3]))
245                 self.file.write("fieldOfView=\"%.3f\" />\n\n" % (lens))
246
247         def writeFog(self, world):
248                 if world:
249                         mtype = world.mist.falloff
250                         # mtype = world.getMistype()
251                         mparam = world.mist
252                         # mparam = world.getMist()
253                         grd = world.horizon_color
254                         # grd = world.getHor()
255                         grd0, grd1, grd2 = grd[0], grd[1], grd[2]
256                 else:
257                         return
258                 if (mtype == 'LINEAR' or mtype == 'INVERSE_QUADRATIC'):
259                         mtype = 1 if mtype == 'LINEAR' else 2
260                 # if (mtype == 1 or mtype == 2):
261                         self.file.write("<Fog fogType=\"%s\" " % self.namesFog[mtype])                                          
262                         self.file.write("color=\"%s %s %s\" " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
263                         self.file.write("visibilityRange=\"%s\" />\n\n" % round(mparam[2],self.cp))
264                 else:
265                         return
266         
267         def writeNavigationInfo(self, scene):
268                 self.file.write('<NavigationInfo headlight="FALSE" visibilityLimit="0.0" type=\'"EXAMINE","ANY"\' avatarSize="0.25, 1.75, 0.75" />\n')
269         
270         def writeSpotLight(self, ob, mtx, lamp, world):
271                 safeName = self.cleanStr(ob.name)
272                 if world:
273                         ambi = world.ambient_color
274                         # ambi = world.amb
275                         ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
276                 else:
277                         ambi = 0
278                         ambientIntensity = 0
279
280                 # compute cutoff and beamwidth
281                 intensity=min(lamp.energy/1.75,1.0)
282                 beamWidth=((lamp.spot_size*math.pi)/180.0)*.37;
283                 # beamWidth=((lamp.spotSize*math.pi)/180.0)*.37;
284                 cutOffAngle=beamWidth*1.3
285
286                 dx,dy,dz=self.computeDirection(mtx)
287                 # note -dx seems to equal om[3][0]
288                 # note -dz seems to equal om[3][1]
289                 # note  dy seems to equal om[3][2]
290
291                 #location=(ob.matrixWorld*MATWORLD).translationPart() # now passed
292                 location=(mtx*MATWORLD).translationPart()
293                 
294                 radius = lamp.distance*math.cos(beamWidth)
295                 # radius = lamp.dist*math.cos(beamWidth)
296                 self.file.write("<SpotLight DEF=\"%s\" " % safeName)
297                 self.file.write("radius=\"%s\" " % (round(radius,self.cp)))
298                 self.file.write("ambientIntensity=\"%s\" " % (round(ambientIntensity,self.cp)))
299                 self.file.write("intensity=\"%s\" " % (round(intensity,self.cp)))
300                 self.file.write("color=\"%s %s %s\" " % (round(lamp.color[0],self.cp), round(lamp.color[1],self.cp), round(lamp.color[2],self.cp)))
301                 # 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)))
302                 self.file.write("beamWidth=\"%s\" " % (round(beamWidth,self.cp)))
303                 self.file.write("cutOffAngle=\"%s\" " % (round(cutOffAngle,self.cp)))
304                 self.file.write("direction=\"%s %s %s\" " % (round(dx,3),round(dy,3),round(dz,3)))
305                 self.file.write("location=\"%s %s %s\" />\n\n" % (round(location[0],3), round(location[1],3), round(location[2],3)))
306                 
307                 
308         def writeDirectionalLight(self, ob, mtx, lamp, world):
309                 safeName = self.cleanStr(ob.name)
310                 if world:
311                         ambi = world.ambient_color
312                         # ambi = world.amb
313                         ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
314                 else:
315                         ambi = 0
316                         ambientIntensity = 0
317
318                 intensity=min(lamp.energy/1.75,1.0) 
319                 (dx,dy,dz)=self.computeDirection(mtx)
320                 self.file.write("<DirectionalLight DEF=\"%s\" " % safeName)
321                 self.file.write("ambientIntensity=\"%s\" " % (round(ambientIntensity,self.cp)))
322                 self.file.write("color=\"%s %s %s\" " % (round(lamp.color[0],self.cp), round(lamp.color[1],self.cp), round(lamp.color[2],self.cp)))
323                 # 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)))
324                 self.file.write("intensity=\"%s\" " % (round(intensity,self.cp)))
325                 self.file.write("direction=\"%s %s %s\" />\n\n" % (round(dx,4),round(dy,4),round(dz,4)))
326
327         def writePointLight(self, ob, mtx, lamp, world):
328                 safeName = self.cleanStr(ob.name)
329                 if world:
330                         ambi = world.ambient_color
331                         # ambi = world.amb
332                         ambientIntensity = ((float(ambi[0] + ambi[1] + ambi[2]))/3)/2.5
333                 else:
334                         ambi = 0
335                         ambientIntensity = 0
336                 
337                 # location=(ob.matrixWorld*MATWORLD).translationPart() # now passed
338                 location= (mtx*MATWORLD).translationPart()
339                 
340                 self.file.write("<PointLight DEF=\"%s\" " % safeName)
341                 self.file.write("ambientIntensity=\"%s\" " % (round(ambientIntensity,self.cp)))
342                 self.file.write("color=\"%s %s %s\" " % (round(lamp.color[0],self.cp), round(lamp.color[1],self.cp), round(lamp.color[2],self.cp)))
343                 # 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)))
344                 self.file.write("intensity=\"%s\" " % (round( min(lamp.energy/1.75,1.0) ,self.cp)))
345                 self.file.write("radius=\"%s\" " % lamp.distance )
346                 # self.file.write("radius=\"%s\" " % lamp.dist )
347                 self.file.write("location=\"%s %s %s\" />\n\n" % (round(location[0],3), round(location[1],3), round(location[2],3)))
348         '''
349         def writeNode(self, ob, mtx):
350                 obname=str(ob.name)
351                 if obname in self.namesStandard:
352                         return
353                 else:
354                         dx,dy,dz = self.computeDirection(mtx)
355                         # location=(ob.matrixWorld*MATWORLD).translationPart()
356                         location=(mtx*MATWORLD).translationPart()
357                         self.writeIndented("<%s\n" % obname,1)
358                         self.writeIndented("direction=\"%s %s %s\"\n" % (round(dx,3),round(dy,3),round(dz,3)))
359                         self.writeIndented("location=\"%s %s %s\"\n" % (round(location[0],3), round(location[1],3), round(location[2],3)))
360                         self.writeIndented("/>\n",-1)
361                         self.writeIndented("\n")
362         '''
363         def secureName(self, name):
364                 name = name + str(self.nodeID)
365                 self.nodeID=self.nodeID+1
366                 if len(name) <= 3:
367                         newname = "_" + str(self.nodeID)
368                         return "%s" % (newname)
369                 else:
370                         for bad in ['"','#',"'",',','.','[','\\',']','{','}']:
371                                 name=name.replace(bad,'_')
372                         if name in self.namesReserved:
373                                 newname = name[0:3] + "_" + str(self.nodeID)
374                                 return "%s" % (newname)
375                         elif name[0].isdigit():
376                                 newname = "_" + name + str(self.nodeID)
377                                 return "%s" % (newname)
378                         else:
379                                 newname = name
380                                 return "%s" % (newname)
381
382         def writeIndexedFaceSet(self, ob, mesh, mtx, world, EXPORT_TRI = False):
383                 imageMap={}   # set of used images
384                 sided={}          # 'one':cnt , 'two':cnt
385                 vColors={}      # 'multi':1
386                 meshName = self.cleanStr(ob.name)
387                 
388                 meshME = self.cleanStr(ob.data.name) # We dont care if its the mesh name or not
389                 # meshME = self.cleanStr(ob.getData(mesh=1).name) # We dont care if its the mesh name or not
390                 if len(mesh.faces) == 0: return
391                 mode = []
392                 # mode = 0
393                 if mesh.active_uv_texture:
394                 # if mesh.faceUV:
395                         for face in mesh.active_uv_texture.data:
396                         # for face in mesh.faces:
397                                 if face.halo and 'HALO' not in mode:
398                                         mode += ['HALO']
399                                 if face.billboard and 'BILLBOARD' not in mode:
400                                         mode += ['BILLBOARD']
401                                 if face.object_color and 'OBJECT_COLOR' not in mode:
402                                         mode += ['OBJECT_COLOR']
403                                 if face.collision and 'COLLISION' not in mode:
404                                         mode += ['COLLISION']
405                                 # mode |= face.mode 
406                 
407                 if 'HALO' in mode and self.halonode == 0:
408                 # if mode & Mesh.FaceModes.HALO and self.halonode == 0:
409                         self.writeIndented("<Billboard axisOfRotation=\"0 0 0\">\n",1)
410                         self.halonode = 1
411                 elif 'BILLBOARD' in mode and self.billnode == 0:
412                 # elif mode & Mesh.FaceModes.BILLBOARD and self.billnode == 0:
413                         self.writeIndented("<Billboard axisOfRotation=\"0 1 0\">\n",1)
414                         self.billnode = 1
415                 elif 'OBJECT_COLOR' in mode and self.matonly == 0:
416                 # elif mode & Mesh.FaceModes.OBCOL and self.matonly == 0:
417                         self.matonly = 1
418                 # TF_TILES is marked as deprecated in DNA_meshdata_types.h
419                 # elif mode & Mesh.FaceModes.TILES and self.tilenode == 0:
420                 #       self.tilenode = 1
421                 elif 'COLLISION' not in mode and self.collnode == 0:
422                 # elif not mode & Mesh.FaceModes.DYNAMIC and self.collnode == 0:
423                         self.writeIndented("<Collision enabled=\"false\">\n",1)
424                         self.collnode = 1
425                 
426                 nIFSCnt=self.countIFSSetsNeeded(mesh, imageMap, sided, vColors)
427                 
428                 if nIFSCnt > 1:
429                         self.writeIndented("<Group DEF=\"%s%s\">\n" % ("G_", meshName),1)
430                 
431                 if 'two' in sided and sided['two'] > 0:
432                         bTwoSided=1
433                 else:
434                         bTwoSided=0
435
436                 # mtx = ob.matrixWorld * MATWORLD # mtx is now passed
437                 mtx = mtx * MATWORLD
438                 
439                 loc= mtx.translationPart()
440                 sca= mtx.scalePart()
441                 quat = mtx.toQuat()
442                 rot= quat.axis
443
444                 self.writeIndented('<Transform DEF="%s" translation="%.6f %.6f %.6f" scale="%.6f %.6f %.6f" rotation="%.6f %.6f %.6f %.6f">\n' % \
445                                                    (meshName, loc[0], loc[1], loc[2], sca[0], sca[1], sca[2], rot[0], rot[1], rot[2], quat.angle) )
446                 # self.writeIndented('<Transform DEF="%s" translation="%.6f %.6f %.6f" scale="%.6f %.6f %.6f" rotation="%.6f %.6f %.6f %.6f">\n' % \
447                 #   (meshName, loc[0], loc[1], loc[2], sca[0], sca[1], sca[2], rot[0], rot[1], rot[2], quat.angle*DEG2RAD) )
448
449                 self.writeIndented("<Shape>\n",1)
450                 maters=mesh.materials
451                 hasImageTexture=0
452                 issmooth=0
453
454                 if len(maters) > 0 or mesh.active_uv_texture:
455                 # if len(maters) > 0 or mesh.faceUV:
456                         self.writeIndented("<Appearance>\n", 1)
457                         # right now this script can only handle a single material per mesh.
458                         if len(maters) >= 1:
459                                 mat=maters[0]
460                                 # matFlags = mat.getMode()
461                                 if not mat.face_texture:
462                                 # if not matFlags & Blender.Material.Modes['TEXFACE']:
463                                         self.writeMaterial(mat, self.cleanStr(mat.name,''), world)
464                                         # self.writeMaterial(mat, self.cleanStr(maters[0].name,''), world)
465                                         if len(maters) > 1:
466                                                 print("Warning: mesh named %s has multiple materials" % meshName)
467                                                 print("Warning: only one material per object handled")
468                         
469                                 #-- textures
470                                 face = None
471                                 if mesh.active_uv_texture:
472                                 # if mesh.faceUV:
473                                         for face in mesh.active_uv_texture.data:
474                                         # for face in mesh.faces:
475                                                 if face.image:
476                                                 # if (hasImageTexture == 0) and (face.image):
477                                                         self.writeImageTexture(face.image)
478                                                         # hasImageTexture=1  # keep track of face texture
479                                                         break
480                                 if self.tilenode == 1 and face and face.image:
481                                 # if self.tilenode == 1:
482                                         self.writeIndented("<TextureTransform   scale=\"%s %s\" />\n" % (face.image.xrep, face.image.yrep))
483                                         self.tilenode = 0
484                                 self.writeIndented("</Appearance>\n", -1)
485
486                 #-- IndexedFaceSet or IndexedLineSet
487
488                 # user selected BOUNDS=1, SOLID=3, SHARED=4, or TEXTURE=5
489                 ifStyle="IndexedFaceSet"
490                 # look up mesh name, use it if available
491                 if meshME in self.meshNames:
492                         self.writeIndented("<%s USE=\"ME_%s\">" % (ifStyle, meshME), 1)
493                         self.meshNames[meshME]+=1
494                 else:
495                         if int(mesh.users) > 1:
496                                 self.writeIndented("<%s DEF=\"ME_%s\" " % (ifStyle, meshME), 1)
497                                 self.meshNames[meshME]=1
498                         else:
499                                 self.writeIndented("<%s " % ifStyle, 1)
500                         
501                         if bTwoSided == 1:
502                                 self.file.write("solid=\"false\" ")
503                         else:
504                                 self.file.write("solid=\"true\" ")
505
506                         for face in mesh.faces:
507                                 if face.smooth:
508                                          issmooth=1
509                                          break
510                         if issmooth==1:
511                                 creaseAngle=(mesh.autosmooth_angle)*(math.pi/180.0)
512                                 # creaseAngle=(mesh.degr)*(math.pi/180.0)
513                                 self.file.write("creaseAngle=\"%s\" " % (round(creaseAngle,self.cp)))
514
515                         #--- output textureCoordinates if UV texture used
516                         if mesh.active_uv_texture:
517                         # if mesh.faceUV:
518                                 if self.matonly == 1 and self.share == 1:
519                                         self.writeFaceColors(mesh)
520                                 elif hasImageTexture == 1:
521                                         self.writeTextureCoordinates(mesh)
522                         #--- output coordinates
523                         self.writeCoordinates(ob, mesh, meshName, EXPORT_TRI)
524
525                         self.writingcoords = 1
526                         self.writingtexture = 1
527                         self.writingcolor = 1
528                         self.writeCoordinates(ob, mesh, meshName, EXPORT_TRI)
529                         
530                         #--- output textureCoordinates if UV texture used
531                         if mesh.active_uv_texture:
532                         # if mesh.faceUV:
533                                 if hasImageTexture == 1:
534                                         self.writeTextureCoordinates(mesh)
535                                 elif self.matonly == 1 and self.share == 1:
536                                         self.writeFaceColors(mesh)
537                         #--- output vertexColors
538                 self.matonly = 0
539                 self.share = 0
540                 
541                 self.writingcoords = 0
542                 self.writingtexture = 0
543                 self.writingcolor = 0
544                 #--- output closing braces
545                 self.writeIndented("</%s>\n" % ifStyle, -1)
546                 self.writeIndented("</Shape>\n", -1)
547                 self.writeIndented("</Transform>\n", -1)
548
549                 if self.halonode == 1:
550                         self.writeIndented("</Billboard>\n", -1)
551                         self.halonode = 0
552
553                 if self.billnode == 1:
554                         self.writeIndented("</Billboard>\n", -1)
555                         self.billnode = 0
556
557                 if self.collnode == 1:
558                         self.writeIndented("</Collision>\n", -1)
559                         self.collnode = 0
560
561                 if nIFSCnt > 1:
562                         self.writeIndented("</Group>\n", -1)
563
564                 self.file.write("\n")
565
566         def writeCoordinates(self, ob, mesh, meshName, EXPORT_TRI = False):
567                 # create vertex list and pre rotate -90 degrees X for VRML
568                 
569                 if self.writingcoords == 0:
570                         self.file.write('coordIndex="')
571                         for face in mesh.faces:
572                                 fv = face.verts
573                                 # fv = face.v
574                                 
575                                 if len(fv)==3:
576                                 # if len(face)==3:
577                                         self.file.write("%i %i %i -1, " % (fv[0], fv[1], fv[2]))
578                                         # self.file.write("%i %i %i -1, " % (fv[0].index, fv[1].index, fv[2].index))
579                                 else:
580                                         if EXPORT_TRI:
581                                                 self.file.write("%i %i %i -1, " % (fv[0], fv[1], fv[2]))
582                                                 # self.file.write("%i %i %i -1, " % (fv[0].index, fv[1].index, fv[2].index))
583                                                 self.file.write("%i %i %i -1, " % (fv[0], fv[2], fv[3]))
584                                                 # self.file.write("%i %i %i -1, " % (fv[0].index, fv[2].index, fv[3].index))
585                                         else:
586                                                 self.file.write("%i %i %i %i -1, " % (fv[0], fv[1], fv[2], fv[3]))
587                                                 # self.file.write("%i %i %i %i -1, " % (fv[0].index, fv[1].index, fv[2].index, fv[3].index))
588                         
589                         self.file.write("\">\n")
590                 else:
591                         #-- vertices
592                         # mesh.transform(ob.matrixWorld)
593                         self.writeIndented("<Coordinate DEF=\"%s%s\" \n" % ("coord_",meshName), 1)
594                         self.file.write("\t\t\t\tpoint=\"")
595                         for v in mesh.verts:
596                                 self.file.write("%.6f %.6f %.6f, " % tuple(v.co))
597                         self.file.write("\" />")
598                         self.writeIndented("\n", -1)
599
600         def writeTextureCoordinates(self, mesh):
601                 texCoordList=[] 
602                 texIndexList=[]
603                 j=0
604
605                 for face in mesh.active_uv_texture.data:
606                 # for face in mesh.faces:
607                         uvs = [face.uv1, face.uv2, face.uv3, face.uv4] if face.verts[3] else [face.uv1, face.uv2, face.uv3]
608
609                         for uv in uvs:
610                         # for uv in face.uv:
611                                 texIndexList.append(j)
612                                 texCoordList.append(uv)
613                                 j=j+1
614                         texIndexList.append(-1)
615                 if self.writingtexture == 0:
616                         self.file.write("\n\t\t\ttexCoordIndex=\"")
617                         texIndxStr=""
618                         for i in range(len(texIndexList)):
619                                 texIndxStr = texIndxStr + "%d, " % texIndexList[i]
620                                 if texIndexList[i]==-1:
621                                         self.file.write(texIndxStr)
622                                         texIndxStr=""
623                         self.file.write("\"\n\t\t\t")
624                 else:
625                         self.writeIndented("<TextureCoordinate point=\"", 1)
626                         for i in range(len(texCoordList)):
627                                 self.file.write("%s %s, " % (round(texCoordList[i][0],self.tp), round(texCoordList[i][1],self.tp)))
628                         self.file.write("\" />")
629                         self.writeIndented("\n", -1)
630
631         def writeFaceColors(self, mesh):
632                 if self.writingcolor == 0:
633                         self.file.write("colorPerVertex=\"false\" ")
634                 elif mesh.active_vertex_color:
635                 # else:
636                         self.writeIndented("<Color color=\"", 1)
637                         for face in mesh.active_vertex_color.data:
638                                 c = face.color1
639                                 if self.verbose > 2:
640                                         print("Debug: face.col r=%d g=%d b=%d" % (c[0], c[1], c[2]))
641                                         # print("Debug: face.col r=%d g=%d b=%d" % (c.r, c.g, c.b))
642                                 aColor = self.rgbToFS(c)
643                                 self.file.write("%s, " % aColor)
644
645                         # for face in mesh.faces:
646                         #       if face.col:
647                         #               c=face.col[0]
648                         #               if self.verbose > 2:
649                         #                       print("Debug: face.col r=%d g=%d b=%d" % (c.r, c.g, c.b))
650                         #               aColor = self.rgbToFS(c)
651                         #               self.file.write("%s, " % aColor)
652                         self.file.write("\" />")
653                         self.writeIndented("\n",-1)
654         
655         def writeMaterial(self, mat, matName, world):
656                 # look up material name, use it if available
657                 if matName in self.matNames:
658                         self.writeIndented("<Material USE=\"MA_%s\" />\n" % matName)
659                         self.matNames[matName]+=1
660                         return;
661
662                 self.matNames[matName]=1
663
664                 ambient = mat.ambient/3
665                 # ambient = mat.amb/3
666                 diffuseR, diffuseG, diffuseB = tuple(mat.diffuse_color)
667                 # diffuseR, diffuseG, diffuseB = mat.rgbCol[0], mat.rgbCol[1],mat.rgbCol[2]
668                 if world:
669                         ambi = world.ambient_color
670                         # ambi = world.getAmb()
671                         ambi0, ambi1, ambi2 = (ambi[0]*mat.ambient)*2, (ambi[1]*mat.ambient)*2, (ambi[2]*mat.ambient)*2
672                         # ambi0, ambi1, ambi2 = (ambi[0]*mat.amb)*2, (ambi[1]*mat.amb)*2, (ambi[2]*mat.amb)*2
673                 else:
674                         ambi0, ambi1, ambi2 = 0, 0, 0
675                 emisR, emisG, emisB = (diffuseR*mat.emit+ambi0)/2, (diffuseG*mat.emit+ambi1)/2, (diffuseB*mat.emit+ambi2)/2
676
677                 shininess = mat.specular_hardness/512.0
678                 # shininess = mat.hard/512.0
679                 specR = (mat.specular_color[0]+0.001)/(1.25/(mat.specular_reflection+0.001))
680                 # specR = (mat.specCol[0]+0.001)/(1.25/(mat.spec+0.001))
681                 specG = (mat.specular_color[1]+0.001)/(1.25/(mat.specular_reflection+0.001))
682                 # specG = (mat.specCol[1]+0.001)/(1.25/(mat.spec+0.001))
683                 specB = (mat.specular_color[2]+0.001)/(1.25/(mat.specular_reflection+0.001))
684                 # specB = (mat.specCol[2]+0.001)/(1.25/(mat.spec+0.001))
685                 transp = 1-mat.alpha
686                 # matFlags = mat.getMode()
687                 if mat.shadeless:
688                 # if matFlags & Blender.Material.Modes['SHADELESS']:
689                   ambient = 1
690                   shine = 1
691                   specR = emitR = diffuseR
692                   specG = emitG = diffuseG
693                   specB = emitB = diffuseB
694                 self.writeIndented("<Material DEF=\"MA_%s\" " % matName, 1)
695                 self.file.write("diffuseColor=\"%s %s %s\" " % (round(diffuseR,self.cp), round(diffuseG,self.cp), round(diffuseB,self.cp)))
696                 self.file.write("specularColor=\"%s %s %s\" " % (round(specR,self.cp), round(specG,self.cp), round(specB,self.cp)))
697                 self.file.write("emissiveColor=\"%s %s %s\" \n" % (round(emisR,self.cp), round(emisG,self.cp), round(emisB,self.cp)))
698                 self.writeIndented("ambientIntensity=\"%s\" " % (round(ambient,self.cp)))
699                 self.file.write("shininess=\"%s\" " % (round(shininess,self.cp)))
700                 self.file.write("transparency=\"%s\" />" % (round(transp,self.cp)))
701                 self.writeIndented("\n",-1)
702
703         def writeImageTexture(self, image):
704                 name = image.name
705                 filename = image.filename.split('/')[-1].split('\\')[-1]
706                 if name in self.texNames:
707                         self.writeIndented("<ImageTexture USE=\"%s\" />\n" % self.cleanStr(name))
708                         self.texNames[name] += 1
709                         return
710                 else:
711                         self.writeIndented("<ImageTexture DEF=\"%s\" " % self.cleanStr(name), 1)
712                         self.file.write("url=\"%s\" />" % name)
713                         self.writeIndented("\n",-1)
714                         self.texNames[name] = 1
715
716         def writeBackground(self, world, alltextures):
717                 if world:       worldname = world.name
718                 else:           return
719                 blending = (world.blend_sky, world.paper_sky, world.real_sky)
720                 # blending = world.getSkytype() 
721                 grd = world.horizon_color
722                 # grd = world.getHor()
723                 grd0, grd1, grd2 = grd[0], grd[1], grd[2]
724                 sky = world.zenith_color
725                 # sky = world.getZen()
726                 sky0, sky1, sky2 = sky[0], sky[1], sky[2]
727                 mix0, mix1, mix2 = grd[0]+sky[0], grd[1]+sky[1], grd[2]+sky[2]
728                 mix0, mix1, mix2 = mix0/2, mix1/2, mix2/2
729                 self.file.write("<Background ")
730                 if worldname not in self.namesStandard:
731                         self.file.write("DEF=\"%s\" " % self.secureName(worldname))
732                 # No Skytype - just Hor color
733                 if blending == (0, 0, 0):
734                 # if blending == 0:
735                         self.file.write("groundColor=\"%s %s %s\" " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
736                         self.file.write("skyColor=\"%s %s %s\" " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
737                 # Blend Gradient
738                 elif blending == (1, 0, 0):
739                 # elif blending == 1:
740                         self.file.write("groundColor=\"%s %s %s, " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
741                         self.file.write("%s %s %s\" groundAngle=\"1.57, 1.57\" " %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
742                         self.file.write("skyColor=\"%s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
743                         self.file.write("%s %s %s\" skyAngle=\"1.57, 1.57\" " %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
744                 # Blend+Real Gradient Inverse
745                 elif blending == (1, 0, 1):
746                 # elif blending == 3:
747                         self.file.write("groundColor=\"%s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
748                         self.file.write("%s %s %s\" groundAngle=\"1.57, 1.57\" " %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
749                         self.file.write("skyColor=\"%s %s %s, " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
750                         self.file.write("%s %s %s\" skyAngle=\"1.57, 1.57\" " %(round(mix0,self.cp), round(mix1,self.cp), round(mix2,self.cp)))
751                 # Paper - just Zen Color
752                 elif blending == (0, 0, 1):
753                 # elif blending == 4:
754                         self.file.write("groundColor=\"%s %s %s\" " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
755                         self.file.write("skyColor=\"%s %s %s\" " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
756                 # Blend+Real+Paper - komplex gradient
757                 elif blending == (1, 1, 1):
758                 # elif blending == 7:
759                         self.writeIndented("groundColor=\"%s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
760                         self.writeIndented("%s %s %s\" groundAngle=\"1.57, 1.57\" " %(round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
761                         self.writeIndented("skyColor=\"%s %s %s, " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
762                         self.writeIndented("%s %s %s\" skyAngle=\"1.57, 1.57\" " %(round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
763                 # Any Other two colors
764                 else:
765                         self.file.write("groundColor=\"%s %s %s\" " % (round(grd0,self.cp), round(grd1,self.cp), round(grd2,self.cp)))
766                         self.file.write("skyColor=\"%s %s %s\" " % (round(sky0,self.cp), round(sky1,self.cp), round(sky2,self.cp)))
767
768                 alltexture = len(alltextures)
769
770                 for i in range(alltexture):
771                         tex = alltextures[i]
772
773                         if tex.type != 'IMAGE' or tex.image == None:
774                                 continue
775
776                         namemat = tex.name
777                         # namemat = alltextures[i].name
778
779                         pic = tex.image
780
781                         # using .expandpath just in case, os.path may not expect //
782                         basename = os.path.basename(bpy.sys.expandpath(pic.filename))
783
784                         pic = alltextures[i].image
785                         # pic = alltextures[i].getImage()
786                         if (namemat == "back") and (pic != None):
787                                 self.file.write("\n\tbackUrl=\"%s\" " % basename)
788                                 # self.file.write("\n\tbackUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1])
789                         elif (namemat == "bottom") and (pic != None):
790                                 self.writeIndented("bottomUrl=\"%s\" " % basename)
791                                 # self.writeIndented("bottomUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1])
792                         elif (namemat == "front") and (pic != None):
793                                 self.writeIndented("frontUrl=\"%s\" " % basename)
794                                 # self.writeIndented("frontUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1])
795                         elif (namemat == "left") and (pic != None):
796                                 self.writeIndented("leftUrl=\"%s\" " % basename)
797                                 # self.writeIndented("leftUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1])
798                         elif (namemat == "right") and (pic != None):
799                                 self.writeIndented("rightUrl=\"%s\" " % basename)
800                                 # self.writeIndented("rightUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1])
801                         elif (namemat == "top") and (pic != None):
802                                 self.writeIndented("topUrl=\"%s\" " % basename)
803                                 # self.writeIndented("topUrl=\"%s\" " % pic.filename.split('/')[-1].split('\\')[-1])
804                 self.writeIndented("/>\n\n")
805
806 ##########################################################
807 # export routine
808 ##########################################################
809
810         def export(self, scene, world, alltextures,\
811                         EXPORT_APPLY_MODIFIERS = False,\
812                         EXPORT_TRI=                             False,\
813                 ):
814                 
815                 print("Info: starting X3D export to " + self.filename + "...")
816                 self.writeHeader()
817                 # self.writeScript()
818                 self.writeNavigationInfo(scene)
819                 self.writeBackground(world, alltextures)
820                 self.writeFog(world)
821                 self.proto = 0
822                 
823                 
824                 # # COPIED FROM OBJ EXPORTER
825                 # if EXPORT_APPLY_MODIFIERS:
826                 #       temp_mesh_name = '~tmp-mesh'
827                 
828                 #       # Get the container mesh. - used for applying modifiers and non mesh objects.
829                 #       containerMesh = meshName = tempMesh = None
830                 #       for meshName in Blender.NMesh.GetNames():
831                 #               if meshName.startswith(temp_mesh_name):
832                 #                       tempMesh = Mesh.Get(meshName)
833                 #                       if not tempMesh.users:
834                 #                               containerMesh = tempMesh
835                 #       if not containerMesh:
836                 #               containerMesh = Mesh.New(temp_mesh_name)
837                 # -------------------------- 
838                 
839                 
840                 for ob_main in [o for o in scene.objects if o.is_visible()]:
841                 # for ob_main in scene.objects.context:
842
843                         free, derived = create_derived_objects(ob_main)
844
845                         if derived == None: continue
846
847                         for ob, ob_mat in derived:
848                         # for ob, ob_mat in BPyObject.getDerivedObjects(ob_main):
849                                 objType=ob.type
850                                 objName=ob.name
851                                 self.matonly = 0
852                                 if objType == "CAMERA":
853                                 # if objType == "Camera":
854                                         self.writeViewpoint(ob, ob_mat, scene)
855                                 elif objType in ("MESH", "CURVE", "SURF", "TEXT") :
856                                 # elif objType in ("Mesh", "Curve", "Surf", "Text") :                           
857                                         if EXPORT_APPLY_MODIFIERS or objType != 'MESH':
858                                         # if  EXPORT_APPLY_MODIFIERS or objType != 'Mesh':
859                                                 me = ob.create_mesh(EXPORT_APPLY_MODIFIERS, 'PREVIEW')
860                                                 # me= BPyMesh.getMeshFromObject(ob, containerMesh, EXPORT_APPLY_MODIFIERS, False, scene)
861                                         else:
862                                                 me = ob.data
863                                                 # me = ob.getData(mesh=1)
864                                         
865                                         self.writeIndexedFaceSet(ob, me, ob_mat, world, EXPORT_TRI = EXPORT_TRI)
866
867                                         # free mesh created with create_mesh()
868                                         if me != ob.data:
869                                                 bpy.data.remove_mesh(me)
870
871                                 elif objType == "LAMP":
872                                 # elif objType == "Lamp":
873                                         data= ob.data
874                                         datatype=data.type
875                                         if datatype == 'POINT':
876                                         # if datatype == Lamp.Types.Lamp:
877                                                 self.writePointLight(ob, ob_mat, data, world)
878                                         elif datatype == 'SPOT':
879                                         # elif datatype == Lamp.Types.Spot:
880                                                 self.writeSpotLight(ob, ob_mat, data, world)
881                                         elif datatype == 'SUN':
882                                         # elif datatype == Lamp.Types.Sun:
883                                                 self.writeDirectionalLight(ob, ob_mat, data, world)
884                                         else:
885                                                 self.writeDirectionalLight(ob, ob_mat, data, world)
886                                 # do you think x3d could document what to do with dummy objects?
887                                 #elif objType == "Empty" and objName != "Empty":
888                                 #       self.writeNode(ob, ob_mat)
889                                 else:
890                                         #print "Info: Ignoring [%s], object type [%s] not handle yet" % (object.name,object.getType)
891                                         pass
892                                 
893                         if free:
894                                 free_derived_objects(ob_main)
895                         
896                 self.file.write("\n</Scene>\n</X3D>")
897                 
898                 # if EXPORT_APPLY_MODIFIERS:
899                 #       if containerMesh:
900                 #               containerMesh.verts = None
901                 
902                 self.cleanup()
903                 
904 ##########################################################
905 # Utility methods
906 ##########################################################
907
908         def cleanup(self):
909                 self.file.close()
910                 self.texNames={}
911                 self.matNames={}
912                 self.indentLevel=0
913                 print("Info: finished X3D export to %s\n" % self.filename)
914
915         def cleanStr(self, name, prefix='rsvd_'):
916                 """cleanStr(name,prefix) - try to create a valid VRML DEF name from object name"""
917
918                 newName=name[:]
919                 if len(newName) == 0:
920                         self.nNodeID+=1
921                         return "%s%d" % (prefix, self.nNodeID)
922                 
923                 if newName in self.namesReserved:
924                         newName='%s%s' % (prefix,newName)
925                 
926                 if newName[0].isdigit():
927                         newName='%s%s' % ('_',newName)
928
929                 for bad in [' ','"','#',"'",',','.','[','\\',']','{','}']:
930                         newName=newName.replace(bad,'_')
931                 return newName
932
933         def countIFSSetsNeeded(self, mesh, imageMap, sided, vColors):
934                 """
935                 countIFFSetsNeeded() - should look at a blender mesh to determine
936                 how many VRML IndexFaceSets or IndexLineSets are needed.  A
937                 new mesh created under the following conditions:
938                 
939                  o - split by UV Textures / one per mesh
940                  o - split by face, one sided and two sided
941                  o - split by smooth and flat faces
942                  o - split when faces only have 2 vertices * needs to be an IndexLineSet
943                 """
944                 
945                 imageNameMap={}
946                 faceMap={}
947                 nFaceIndx=0
948                 
949                 if mesh.active_uv_texture:
950                 # if mesh.faceUV:
951                         for face in mesh.active_uv_texture.data:
952                         # for face in mesh.faces:
953                                 sidename='';
954                                 if face.twoside:
955                                 # if  face.mode & Mesh.FaceModes.TWOSIDE:
956                                         sidename='two'
957                                 else:
958                                         sidename='one'
959                                 
960                                 if sidename in sided:
961                                         sided[sidename]+=1
962                                 else:
963                                         sided[sidename]=1
964                                 
965                                 image = face.image
966                                 if image:
967                                         faceName="%s_%s" % (face.image.name, sidename);
968                                         try:
969                                                 imageMap[faceName].append(face)
970                                         except:
971                                                 imageMap[faceName]=[face.image.name,sidename,face]
972
973                         if self.verbose > 2:
974                                 for faceName in imageMap.keys():
975                                         ifs=imageMap[faceName]
976                                         print("Debug: faceName=%s image=%s, solid=%s facecnt=%d" % \
977                                                   (faceName, ifs[0], ifs[1], len(ifs)-2))
978
979                 return len(imageMap)
980         
981         def faceToString(self,face):
982
983                 print("Debug: face.flag=0x%x (bitflags)" % face.flag)
984                 if face.sel:
985                         print("Debug: face.sel=true")
986
987                 print("Debug: face.mode=0x%x (bitflags)" % face.mode)
988                 if face.mode & Mesh.FaceModes.TWOSIDE:
989                         print("Debug: face.mode twosided")
990
991                 print("Debug: face.transp=0x%x (enum)" % face.transp)
992                 if face.transp == Mesh.FaceTranspModes.SOLID:
993                         print("Debug: face.transp.SOLID")
994
995                 if face.image:
996                         print("Debug: face.image=%s" % face.image.name)
997                 print("Debug: face.materialIndex=%d" % face.materialIndex) 
998
999         # XXX not used
1000         # def getVertexColorByIndx(self, mesh, indx):
1001         #       c = None
1002         #       for face in mesh.faces:
1003         #               j=0
1004         #               for vertex in face.v:
1005         #                       if vertex.index == indx:
1006         #                               c=face.col[j]
1007         #                               break
1008         #                       j=j+1
1009         #               if c: break
1010         #       return c
1011
1012         def meshToString(self,mesh):
1013                 # print("Debug: mesh.hasVertexUV=%d" % mesh.vertexColors)
1014                 print("Debug: mesh.faceUV=%d" % (len(mesh.uv_textures) > 0))
1015                 # print("Debug: mesh.faceUV=%d" % mesh.faceUV)
1016                 print("Debug: mesh.hasVertexColours=%d" % (len(mesh.vertex_colors) > 0))
1017                 # print("Debug: mesh.hasVertexColours=%d" % mesh.hasVertexColours())
1018                 print("Debug: mesh.verts=%d" % len(mesh.verts))
1019                 print("Debug: mesh.faces=%d" % len(mesh.faces))
1020                 print("Debug: mesh.materials=%d" % len(mesh.materials))
1021
1022         def rgbToFS(self, c):
1023                 s="%s %s %s" % (round(c[0]/255.0,self.cp),
1024                                                 round(c[1]/255.0,self.cp),
1025                                                 round(c[2]/255.0,self.cp))
1026
1027                 # s="%s %s %s" % (
1028                 #       round(c.r/255.0,self.cp),
1029                 #       round(c.g/255.0,self.cp),
1030                 #       round(c.b/255.0,self.cp))
1031                 return s
1032
1033         def computeDirection(self, mtx):
1034                 x,y,z=(0,-1.0,0) # point down
1035                 
1036                 ax,ay,az = (mtx*MATWORLD).toEuler()
1037                 
1038                 # ax *= DEG2RAD
1039                 # ay *= DEG2RAD
1040                 # az *= DEG2RAD
1041
1042                 # rot X
1043                 x1=x
1044                 y1=y*math.cos(ax)-z*math.sin(ax)
1045                 z1=y*math.sin(ax)+z*math.cos(ax)
1046
1047                 # rot Y
1048                 x2=x1*math.cos(ay)+z1*math.sin(ay)
1049                 y2=y1
1050                 z2=z1*math.cos(ay)-x1*math.sin(ay)
1051
1052                 # rot Z
1053                 x3=x2*math.cos(az)-y2*math.sin(az)
1054                 y3=x2*math.sin(az)+y2*math.cos(az)
1055                 z3=z2
1056
1057                 return [x3,y3,z3]
1058                 
1059
1060         # swap Y and Z to handle axis difference between Blender and VRML
1061         #------------------------------------------------------------------------
1062         def rotatePointForVRML(self, v):
1063                 x = v[0]
1064                 y = v[2]
1065                 z = -v[1]
1066                 
1067                 vrmlPoint=[x, y, z]
1068                 return vrmlPoint
1069
1070         # For writing well formed VRML code
1071         #------------------------------------------------------------------------
1072         def writeIndented(self, s, inc=0):
1073                 if inc < 1:
1074                         self.indentLevel = self.indentLevel + inc
1075
1076                 spaces=""
1077                 for x in range(self.indentLevel):
1078                         spaces = spaces + "\t"
1079                 self.file.write(spaces + s)
1080
1081                 if inc > 0:
1082                         self.indentLevel = self.indentLevel + inc
1083
1084         # Converts a Euler to three new Quaternions
1085         # Angles of Euler are passed in as radians
1086         #------------------------------------------------------------------------
1087         def eulerToQuaternions(self, x, y, z):
1088                 Qx = [math.cos(x/2), math.sin(x/2), 0, 0]
1089                 Qy = [math.cos(y/2), 0, math.sin(y/2), 0]
1090                 Qz = [math.cos(z/2), 0, 0, math.sin(z/2)]
1091                 
1092                 quaternionVec=[Qx,Qy,Qz]
1093                 return quaternionVec
1094         
1095         # Multiply two Quaternions together to get a new Quaternion
1096         #------------------------------------------------------------------------
1097         def multiplyQuaternions(self, Q1, Q2):
1098                 result = [((Q1[0] * Q2[0]) - (Q1[1] * Q2[1]) - (Q1[2] * Q2[2]) - (Q1[3] * Q2[3])),
1099                                   ((Q1[0] * Q2[1]) + (Q1[1] * Q2[0]) + (Q1[2] * Q2[3]) - (Q1[3] * Q2[2])),
1100                                   ((Q1[0] * Q2[2]) + (Q1[2] * Q2[0]) + (Q1[3] * Q2[1]) - (Q1[1] * Q2[3])),
1101                                   ((Q1[0] * Q2[3]) + (Q1[3] * Q2[0]) + (Q1[1] * Q2[2]) - (Q1[2] * Q2[1]))]
1102                 
1103                 return result
1104         
1105         # Convert a Quaternion to an Angle Axis (ax, ay, az, angle)
1106         # angle is in radians
1107         #------------------------------------------------------------------------
1108         def quaternionToAngleAxis(self, Qf):
1109                 scale = math.pow(Qf[1],2) + math.pow(Qf[2],2) + math.pow(Qf[3],2)
1110                 ax = Qf[1]
1111                 ay = Qf[2]
1112                 az = Qf[3]
1113
1114                 if scale > .0001:
1115                         ax/=scale
1116                         ay/=scale
1117                         az/=scale
1118                 
1119                 angle = 2 * math.acos(Qf[0])
1120                 
1121                 result = [ax, ay, az, angle]
1122                 return result
1123
1124 ##########################################################
1125 # Callbacks, needed before Main
1126 ##########################################################
1127
1128 def x3d_export(filename,
1129                            context,
1130                            EXPORT_APPLY_MODIFIERS=False,
1131                            EXPORT_TRI=False,
1132                            EXPORT_GZIP=False):
1133         
1134         if EXPORT_GZIP:
1135                 if not filename.lower().endswith('.x3dz'):
1136                         filename = '.'.join(filename.split('.')[:-1]) + '.x3dz'
1137         else:
1138                 if not filename.lower().endswith('.x3d'):
1139                         filename = '.'.join(filename.split('.')[:-1]) + '.x3d'
1140         
1141         
1142         scene = context.scene
1143         # scene = Blender.Scene.GetCurrent()
1144         world = scene.world
1145
1146         # XXX these are global textures while .Get() returned only scene's?
1147         alltextures = bpy.data.textures
1148         # alltextures = Blender.Texture.Get()
1149
1150         wrlexport=x3d_class(filename)
1151         wrlexport.export(\
1152                 scene,\
1153                 world,\
1154                 alltextures,\
1155                 \
1156                 EXPORT_APPLY_MODIFIERS = EXPORT_APPLY_MODIFIERS,\
1157                 EXPORT_TRI = EXPORT_TRI,\
1158                 )
1159
1160
1161 def x3d_export_ui(filename):
1162         if not filename.endswith(extension):
1163                 filename += extension
1164         #if _safeOverwrite and sys.exists(filename):
1165         #       result = Draw.PupMenu("File Already Exists, Overwrite?%t|Yes%x1|No%x0")
1166         #if(result != 1):
1167         #       return
1168         
1169         # Get user options
1170         EXPORT_APPLY_MODIFIERS = Draw.Create(1)
1171         EXPORT_TRI = Draw.Create(0)
1172         EXPORT_GZIP = Draw.Create( filename.lower().endswith('.x3dz') )
1173         
1174         # Get USER Options
1175         pup_block = [\
1176         ('Apply Modifiers', EXPORT_APPLY_MODIFIERS, 'Use transformed mesh data from each object.'),\
1177         ('Triangulate', EXPORT_TRI, 'Triangulate quads.'),\
1178         ('Compress', EXPORT_GZIP, 'GZip the resulting file, requires a full python install'),\
1179         ]
1180
1181         if not Draw.PupBlock('Export...', pup_block):
1182                 return
1183
1184         Blender.Window.EditMode(0)
1185         Blender.Window.WaitCursor(1)
1186         
1187         x3d_export(filename,\
1188                 EXPORT_APPLY_MODIFIERS = EXPORT_APPLY_MODIFIERS.val,\
1189                 EXPORT_TRI = EXPORT_TRI.val,\
1190                 EXPORT_GZIP = EXPORT_GZIP.val\
1191         )
1192         
1193         Blender.Window.WaitCursor(0)
1194
1195
1196
1197 #########################################################
1198 # main routine
1199 #########################################################
1200
1201
1202 # if __name__ == '__main__':
1203 #       Blender.Window.FileSelector(x3d_export_ui,"Export X3D", Blender.Get('filename').replace('.blend', '.x3d'))
1204
1205 class EXPORT_OT_x3d(bpy.types.Operator):
1206         '''
1207         X3D Exporter
1208         '''
1209         __idname__ = "export.x3d"
1210         __label__ = 'Export X3D'
1211         
1212         # List of operator properties, the attributes will be assigned
1213         # to the class instance from the operator settings before calling.
1214
1215         __props__ = [
1216                 bpy.props.StringProperty(attr="filename", name="File Name", description="File name used for exporting the X3D file", maxlen=1024, default=""),
1217
1218                 bpy.props.BoolProperty(attr="apply_modifiers", name="Apply Modifiers", description="Use transformed mesh data from each object.", default=True),
1219                 bpy.props.BoolProperty(attr="triangulate", name="Triangulate", description="Triangulate quads.", default=False),
1220                 bpy.props.BoolProperty(attr="compress", name="Compress", description="GZip the resulting file, requires a full python install.", default=False),
1221         ]
1222         
1223         def execute(self, context):
1224                 x3d_export(self.filename, context, self.apply_modifiers, self.triangulate, self.compress)
1225                 return ('FINISHED',)
1226         
1227         def invoke(self, context, event):
1228                 wm = context.manager
1229                 wm.add_fileselect(self.__operator__)
1230                 return ('RUNNING_MODAL',)
1231         
1232         def poll(self, context): # Poll isnt working yet
1233                 print("Poll")
1234                 return context.active_object != None
1235
1236 bpy.ops.add(EXPORT_OT_x3d)
1237
1238 # NOTES
1239 # - blender version is hardcoded