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