Initial revision
[blender.git] / intern / python / modules / Converter / importer / VRMLimporter.py
1 # VRML import prototype
2 #
3 # strubi@blender.nl
4 #
5
6 """VRML import module
7
8   This is a prototype for VRML97 file import
9
10   Supported:
11
12   - Object hierarchies, transform collapsing (optional)
13   
14   - Meshes (IndexedFaceSet, no Basic primitives yet)
15
16   - Materials
17
18   - Textures (jpg, tga), conversion option from alien formats
19
20 """
21
22 import Blender.sys as os                # Blender os emulation
23 from beta import Scenegraph 
24
25 Transform = Scenegraph.Transform
26
27 import beta.Objects
28
29 _b = beta.Objects
30
31 #from Blender import Mesh
32 Color = _b.Color
33 DEFAULTFLAGS = _b.DEFAULTFLAGS
34 FACEFLAGS = _b.FACEFLAGS
35 shadowNMesh = _b.shadowNMesh
36
37 quat = Scenegraph.quat                # quaternion math
38 vect = quat.vect                      # vector math module
39 from vrml import loader
40
41 #### GLOBALS 
42
43 OB = Scenegraph.Object.Types  # CONST values
44 LA = Scenegraph.Lamp.Types
45
46 g_level = 1
47 g_supported_fileformats = ["jpg", "jpeg", "tga"]
48
49 #### OPTIONS
50
51 OPTIONS = {'cylres' : 16,        # resolution of cylinder
52            'flipnormals' : 0,    # flip normals (force)
53                    'mat_as_vcol' : 0,    # material as vertex color - warning, this increases mem usage drastically on big files
54                    'notextures' : 0,     # no textures - saves some memory
55                    'collapseDEFs' : 0,   # collapse DEF nodes
56                    'collapseTF' : 0,     # collapse Transforms (as far as possible,
57                                          # i.e. currently to Object transform level)
58                   }
59
60 #### CONSTANTS
61
62 LAYER_EMPTY = (1 << 2)
63 LAYER_LAMP = (1 << 4)
64 LAYER_CAMERA = 1 + (1 << 4)
65
66 CREASE_ANGLE_THRESHOLD = 0.45 # radians
67
68 PARSE_TIME = (loader.parser.IMPORT_PARSE_TIME )
69 PROCESS_TIME = (1.0 - PARSE_TIME )
70 PROGRESS_DEPTH = loader.parser.PROGRESS_DEPTH
71 VERBOSE_DEPTH = PROGRESS_DEPTH
72
73 #### DEBUG
74
75 def warn(text):
76         print "###", text
77
78 def debug2(text):
79         print (g_level - 1) * 4 * " " + text
80
81 def verbose(text):
82         print text
83
84 def quiet(text):
85         pass
86
87 debug = quiet
88
89 #### ERROR message filtering:
90
91 g_error = {} # dictionary for non-fatal errors to mark whether an error
92              # was already reported
93
94 def clrError():
95         global g_error
96         g_error['toomanyfaces'] = 0
97
98 def isError(name):
99         return g_error[name]
100
101 def setError(name):
102         global g_error
103         g_error[name] = 1
104
105 #### ERROR handling
106
107 class baseError:
108         def __init__(self, value):
109                 self.value = value
110         def __str__(self):
111                 return `self.value`
112
113 class MeshError(baseError):
114         pass
115
116 UnfinishedError = loader.parser.UnfinishedError
117
118 ##########################################################
119 # HELPER ROUTINES
120
121 def assignImage(f, img):
122         f.image = img
123
124 def assignUV(f, uv):
125         if len(uv) != len(f.v):
126                 uv = uv[:len(f.v)]
127                 #raise MeshError, "Number of UV coordinates does not match number of vertices in face"
128         f.uv = []
129         for u in uv:
130                 f.uv.append((u[0], u[1])) # make sure it's a tuple
131
132
133 #### VRML STUFF
134
135 # this is used for transform collapsing
136 class TransformStack:
137         def __init__(self):
138                 self.stack = [Transform()]
139         def push(self, t):
140                 self.stack.append(t)
141         def pop(self):
142                 return self.stack.pop()
143         def last(self):
144                 return self.stack[-1]
145
146 def fromVRMLTransform(tfnode):
147         t = Transform()
148         s = tfnode.scale
149         t.scale = (s[0], s[1], s[2])
150         r = tfnode.rotation
151         if r[0] == 0.0 and r[1] == 0.0 and r[2] == 0.0:
152                 rotaxis = (0.0, 0.0, 1.0)
153                 ang = 0.0
154         else:
155                 rotaxis = vect.norm3(r[:3])
156                 ang = r[3]
157
158         #t.rotation = (rotaxis, ang)
159         t.calcRotfromAxis((rotaxis, ang))
160         tr = tfnode.translation
161         t.translation = (tr[0], tr[1], tr[2])
162         # XXX more to come..
163         return t
164
165
166 ### TODO: enable material later on
167 #class dummyMaterial:
168         #def setMode(self, *args):
169                 #pass
170         
171 def fromVRMLMaterial(mat):
172         name = mat.DEF
173         from Blender import Material
174         m = Material.New(name)
175
176         m.rgbCol = mat.diffuseColor
177         m.alpha = 1.0 - mat.transparency
178         m.emit = vect.len3(mat.emissiveColor)
179         if m.Emit > 0.01:
180                 if vect.cross(mat.diffuseColor, mat.emissiveColor) > 0.01 * m.Emit:
181                         m.rgbCol = mat.emissiveColor
182
183         m.ref = 1.0
184         m.spec = mat.shininess
185         m.specCol = mat.specularColor
186         m.amb = mat.ambientIntensity
187         return m
188
189 # override:
190 #def fromVRMLMaterial(mat):
191 #       return dummyMaterial()
192
193 def buildVRMLTextureMatrix(tr):
194         from math import sin, cos
195         newMat = vect.Matrix
196         newVec = vect.Vector
197         # rotmatrix
198         s = tr.scale
199         t = tr.translation
200         c = tr.center
201
202         phi = tr.rotation
203
204         SR = newMat()
205         C = newMat()
206         C[2] = newVec(c[0], c[1], 1.0)
207
208         if abs(phi) > 0.00001:
209                 SR[0] = newVec(s[0] * cos(phi), s[1] * sin(phi), 0.0)
210                 SR[1] = newVec(-s[0] * sin(phi), s[1] * cos(phi), 0.0)
211         else:
212                 SR[0] = newVec(s[0], 0.0, 0.0)
213                 SR[1] = newVec(0.0, s[1], 0.0)
214
215         SR = C * SR * C.inverse()  # rotate & scale about rotation center
216
217         T = newMat()
218         T[2] = newVec(t[0], t[1], 1.0)
219         return SR * T # texture transform matrix
220
221 def imageConvert(fromfile, tofile):
222         """This should convert from a image file to another file, type is determined
223 automatically (on extension). It's currently just a stub - users can override
224 this function to implement their own converters"""
225         return 0 # we just fail in general
226
227 def addImage(path, filename):
228         "returns a possibly existing image which is imported by Blender"
229         from Blender import Image
230         img = None
231         try:
232                 r = filename.rindex('.')
233         except:
234                 return None
235
236         naked = filename[:r]
237         ext = filename[r+1:].lower()
238
239         if path:
240                 name = os.sep.join([path, filename])
241                 file = os.sep.join([path, naked])
242         else:
243                 name = filename
244                 file = naked
245
246         if not ext in g_supported_fileformats:
247                 tgafile = file + '.tga'
248                 jpgfile = file + '.jpg'
249                 for f in tgafile, jpgfile: # look for jpg, tga
250                         try:
251                                 img = Image.Load(f)
252                                 if img:
253                                         verbose("couldn't load %s (unsupported).\nFound %s instead" % (name, f))
254                                         return img
255                         except IOError, msg:
256                                 pass
257                 try:
258                         imgfile = open(name, "rb")
259                         imgfile.close()
260                 except IOError, msg:
261                         warn("Image %s not found" % name)
262                         return None
263
264                 verbose("Format unsupported, trying to convert to %s" % tgafile)
265                 if not imageConvert(name, tgafile):
266                         warn("image conversion failed")
267                         return None
268                 else:
269                         return Image.Load(tgafile)
270                 return None # failed
271         try:
272                 img = Image.Load(name)
273         except IOError, msg:
274                 warn("Image %s not found" % name)
275         return img
276         # ok, is supported
277
278 def callMethod(_class, method, vnode, newnode, warn = 1):
279         meth = None
280         try:
281                 meth = getattr(_class, method)
282         except AttributeError:
283                 if warn:
284                         unknownType(method)
285                 return None, None
286         if meth:
287                 return meth(vnode, parent = newnode)
288
289 def unknownType(type):
290         warn("unsupported:" + repr(type))
291
292 def getChildren(vnode):         
293         try:
294                 children = vnode.children
295         except:
296                 children = None
297         return children
298
299 def getNodeType(vnode):
300         return vnode.__gi__
301
302 GroupingNodeTypes = ["Group", "Collision", "Anchor", "Billboard", "Inline",
303                      "LOD", "Switch", "Transform"]
304
305 ################################################################################
306 #
307 #### PROCESSING CLASSES
308
309
310 class NullProcessor:
311         def __init__(self, tstack = TransformStack()):
312                 self.stack = tstack
313                 self.walker = None
314                 self.mesh = None
315                 self.ObjectNode = Scenegraph.NodefromData # may be altered...
316                 self.MaterialCache = {}
317                 self.ImageCache = {}
318
319 # This is currently not used XXX
320 class DEFcollapser(NullProcessor):
321         """This is for collapsing DEF Transform nodes into a single object"""
322         def __init__(self):
323                 self.collapsedNodes = []
324
325         def Transform(self, curnode, parent, **kw):
326                 name = curnode.DEF
327                 if not name: # node is a DEF node
328                         return None, None
329
330                 return children, None
331                 
332                 
333 class Processor(NullProcessor):
334         """The processor class defines the handler for a VRML Scenegraph node.
335 Definition of a handler method simply happens by use of the VRML Scenegraph
336 entity name.
337
338 A handler usually creates a new Scenegraph node in the target scenegraph, 
339 converting the data from the given VRML node.
340
341 A handler takes the arguments:
342
343         curnode: the currently visited VRML node
344         parent:  the previously generated target scenegraph parent node
345         **kw: additional keywords
346         
347 It MUST return: (children, newBnode) where:
348         children: the children of the current VRML node. These will be further
349                   processed by the processor. If this is not wanted (because they
350                           might have been processed by the handler), None must be returned.
351         newBnode: the newly created target node or None.
352         """
353
354         def _handleProto(self, curnode, parent, **kw):
355                 p = curnode.PROTO
356                 if not p.sceneGraph:
357                         print curnode.__gi__, "unsupported"
358                         return None, None
359
360         def _dummy(self, curnode, parent, **kw):
361                 print curnode.sceneGraph
362                 return None, None
363
364         #def __getattr__(self, name):
365                 #"""If method is not statically defined, look up prototypes"""
366                 #return self._handleProto
367
368         def _currentTransform(self):
369                 return self.stack.last()
370                 
371         def _parent(self, curnode, parent, trans):
372                 name = curnode.DEF
373                 children = getChildren(curnode)
374                 debug("children: %s" % children)
375                 objects = []
376                 transforms = []
377                 groups = []
378                 isempty = 0
379                 for c in children:
380                         type = getNodeType(c)
381                         if type == 'Transform':
382                                 transforms.append(c)
383                         elif type in GroupingNodeTypes:
384                                 groups.append(c)
385                         #else:
386                         elif hasattr(self, type):
387                                 objects.append(c)
388                 if transforms or groups or len(objects) != 1:
389                         # it's an empty
390                         if not name:
391                                 name = 'EMPTY'
392                         Bnode = self.ObjectNode(None, OB.EMPTY, name) # empty Blender Object node
393                         if options['layers']:
394                                 Bnode.object.Layer = LAYER_EMPTY
395                         Bnode.transform = trans
396                         Bnode.update()
397                         isempty = 1
398                         parent.insert(Bnode)
399                 else: # don't insert extra empty if only one object has children
400                         Bnode = parent
401
402                 for node in objects:
403                         c, new = self.walker.walk(node, Bnode)
404                         if not isempty: # only apply transform if no extra transform empty in hierarchy
405                                 new.transform = trans
406                         Bnode.insert(new)
407                 for node in transforms:
408                         self.walker.walk(node, Bnode)
409                 for node in groups:     
410                         self.walker.walk(node, Bnode)
411
412                 return None, None
413
414         def sceneGraph(self, curnode, parent, **kw):
415                 parent.type = 'ROOT'
416                 return curnode.children, None
417
418         def Transform(self, curnode, parent, **kw):
419                 # we support 'center' and 'scaleOrientation' by inserting
420                 # another Empty in between the Transforms
421
422                 t = fromVRMLTransform(curnode)
423                 cur = self._currentTransform()
424
425                 chainable = 0
426
427                 if OPTIONS['collapseTF']:
428                         try:
429                                 cur = cur * t # chain transforms
430                         except:
431                                 cur = self._currentTransform()
432                                 chainable = 1
433
434                 self.stack.push(cur)
435
436                 # here comes the tricky hacky transformation conversion
437
438                 # TODO: SR not supported yet
439
440                 if chainable == 1: # collapse, but not chainable
441                         # insert extra transform:
442                         Bnode = self.ObjectNode(None, OB.EMPTY, 'Transform') # Empty
443                         Bnode.transform = cur
444                         parent.insert(Bnode)
445                         parent = Bnode
446
447                 c = curnode.center
448                 if c != [0.0, 0.0, 0.0]:
449                         chainable = 1
450                         trans = Transform()
451                         trans.translation = (-c[0], -c[1], -c[2])
452                         tr = t.translation
453                         t.translation = (tr[0] + c[0], tr[1] + c[1], tr[2] + c[2])
454
455                         Bnode = self.ObjectNode(None, OB.EMPTY, 'C') # Empty
456                         Bnode.transform = t
457                         parent.insert(Bnode)
458                         parent = Bnode
459                 else:
460                         trans = t
461
462                 if chainable == 2: # collapse and is chainable
463                         # don't parent, insert into root node:
464                         for c in getChildren(curnode):
465                                 dummy, node = self.walker.walk(c, parent) # skip transform node, insert into parent
466                                 if node: # a valid Blender node
467                                         node.transform = cur
468                 else:
469                         self._parent(curnode, parent, trans)
470
471
472                 self.stack.pop()
473                 return None, None
474
475         def Switch(self, curnode, parent, **kw):
476                 return None, None
477
478         def Group(self, curnode, parent, **kw):
479                 if OPTIONS['collapseTF']: 
480                         cur = self._currentTransform()
481                         # don't parent, insert into root node:
482                         children = getChildren(curnode)
483                         for c in children:
484                                 dummy, node = self.walker.walk(c, parent) # skip transform node, insert into parent
485                                 if node: # a valid Blender node
486                                         node.transform = cur
487                 else:   
488                         t = Transform()
489                         self._parent(curnode, parent, t)
490                 return None, None
491
492         def Collision(self, curnode, parent, **kw):
493                 return self.Group(curnode, parent)
494
495 #       def LOD(self, curnode, parent, **kw):
496 #               c, node = self.walker.walk(curnode.level[0], parent)
497 #               parent.insert(node)
498 #               return None, None
499
500         def Appearance(self, curnode, parent, **kw):
501                 # material colors:
502                 mat = curnode.material
503                 self.curColor = mat.diffuseColor
504                         
505                 name = mat.DEF
506                 if name:  
507                         if self.MaterialCache.has_key(name):
508                                 self.curmaterial = self.MaterialCache[name]
509                         else:   
510                                 m = fromVRMLMaterial(mat)
511                                 self.MaterialCache[name] = m
512                                 self.curmaterial = m
513                 else:
514                         if curnode.DEF:
515                                 name = curnode.DEF
516                                 if self.MaterialCache.has_key(name):
517                                         self.curmaterial = self.MaterialCache[name]
518                                 else:   
519                                         m = fromVRMLMaterial(mat)
520                                         self.MaterialCache[name] = m
521                                         self.curmaterial = m
522                         else:
523                                 self.curmaterial = fromVRMLMaterial(mat)
524
525                 try:    
526                         name = curnode.texture.url[0]
527                 except:
528                         name = None
529                 if name:        
530                         if self.ImageCache.has_key(name):
531                                 self.curImage = self.ImageCache[name]
532                         else:   
533                                 self.ImageCache[name] = self.curImage = addImage(self.curpath, name)
534                 else:
535                         self.curImage = None
536
537                 tr = curnode.textureTransform
538                 if tr:
539                         self.curtexmatrix = buildVRMLTextureMatrix(tr)
540                 else:
541                         self.curtexmatrix = None
542                 return None, None
543
544         def Shape(self, curnode, parent, **kw):
545                 name = curnode.DEF
546                 debug(name)
547                 #self.mesh = Mesh.rawMesh()
548                 self.mesh = shadowNMesh()
549                 self.mesh.name = name
550                 
551                 # don't mess with the order of these..
552                 if curnode.appearance:
553                         self.walker.preprocess(curnode.appearance, self.walker.preprocessor)
554                 else:
555                         # no appearance, get colors from shape (vertex colors)
556                         self.curColor = None
557                         self.curImage = None
558                 self.walker.preprocess(curnode.geometry, self.walker.preprocessor)
559
560                 if hasattr(self, 'curmaterial'):
561                         self.mesh.assignMaterial(self.curmaterial)
562
563                 meshobj = self.mesh.write()  # write mesh
564                 del self.mesh
565                 bnode = Scenegraph.ObjectNode(meshobj, OB.MESH, name) 
566                 if name:
567                         curnode.setTargetnode(bnode) # mark as already processed
568                 return None, bnode
569
570         def Box(self, curnode, parent, **kw):
571                 col = apply(Color, self.curColor)
572         
573                 faces = []
574                 x, y, z = curnode.size
575                 x *= 0.5; y *= 0.5; z *= 0.5
576                 name = curnode.DEF
577                 m = self.mesh
578                 v0 = m.addVert((-x, -y, -z))
579                 v1 = m.addVert(( x, -y, -z))
580                 v2 = m.addVert(( x,  y, -z))
581                 v3 = m.addVert((-x,  y, -z))
582                 v4 = m.addVert((-x, -y,  z))
583                 v5 = m.addVert(( x, -y,  z))
584                 v6 = m.addVert(( x,  y,  z))
585                 v7 = m.addVert((-x,  y,  z))
586
587                 flags = DEFAULTFLAGS
588                 if not self.curImage:
589                         uvflag = 1
590                 else:
591                         uvflag = 0
592
593                 m.addFace([v3, v2, v1, v0], flags, uvflag)
594                 m.addFace([v0, v1, v5, v4], flags, uvflag)
595                 m.addFace([v1, v2, v6, v5], flags, uvflag)
596                 m.addFace([v2, v3, v7, v6], flags, uvflag)
597                 m.addFace([v3, v0, v4, v7], flags, uvflag)
598                 m.addFace([v4, v5, v6, v7], flags, uvflag)
599
600                 for f in m.faces:
601                         f.col = [col, col, col, col]
602                 return None, None
603         
604         def Viewpoint(self, curnode, parent, **kw):
605                 t = Transform()
606                 r = curnode.orientation
607                 name = 'View_' + curnode.description
608                 t.calcRotfromAxis((r[:3], r[3]))
609                 t.translation = curnode.position
610                 Bnode = self.ObjectNode(None, OB.CAMERA, name) # Empty
611                 Bnode.object.Layer = LAYER_CAMERA
612                 Bnode.transform = t
613                 return None, Bnode
614
615         def DirectionalLight(self, curnode, parent, **kw):
616                 loc = (0.0, 10.0, 0.0)
617                 l = self._lamp(curnode, loc)
618                 l.object.data.type = LA.SUN
619                 return None, l
620
621         def PointLight(self, curnode, parent, **kw):
622                 l = self._lamp(curnode, curnode.location)
623                 l.object.data.type = LA.LOCAL
624                 return None, l
625
626         def _lamp(self, curnode, location):
627                 t = Transform()
628                 name = curnode.DEF
629                 energy = curnode.intensity
630                 t.translation = location
631                 Bnode = self.ObjectNode(None, OB.LAMP, "Lamp")
632                 Bnode.object.data.energy = energy * 5.0
633                 if options['layers']:
634                         Bnode.object.Layer = LAYER_LAMP
635                 Bnode.transform = t
636                 return Bnode
637
638         def IndexedFaceSet(self, curnode, **kw):
639                 matxvec = vect.matxvec
640                 mesh = self.mesh
641                 debug("IFS, read mesh")
642
643                 texcoo = curnode.texCoord
644                 uvflag = 0
645
646                 if curnode.color:
647                         colors = curnode.color.color
648                         if curnode.colorIndex: # we have color indices
649                                 colindex = curnode.colorIndex
650                         else:
651                                 colindex = curnode.coordIndex
652                         if not texcoo:  
653                                 uvflag = 1
654                 else:
655                         colors = None
656
657                 faceflags = DEFAULTFLAGS
658
659                 if not texcoo and OPTIONS['mat_as_vcol'] and self.curColor:
660                         uvflag = 1
661                         col = apply(Color, self.curColor)
662                 elif self.curImage:
663                         faceflags += FACEFLAGS.TEX
664
665 # MAKE VERTICES
666
667                 coo = curnode.coord
668                 ncoo = len(coo.point)
669
670                 if curnode.normal: # normals defined
671                         normals = curnode.normal.vector
672                         if curnode.normalPerVertex and len(coo.point) == len(normals):
673                                 self.mesh.recalc_normals = 0
674                                 normindex = curnode.normalIndex
675                                 i = 0
676                                 for v in coo.point:
677                                         newv = mesh.addVert(v)
678                                         n = newv.no
679                                         n[0], n[1], n[2] = normals[normindex[i]]
680                                         i += 1
681                         else:
682                                 for v in coo.point:
683                                         mesh.addVert(v)
684                 else:           
685                         for v in coo.point:
686                                 mesh.addVert(v)
687                         if curnode.creaseAngle < CREASE_ANGLE_THRESHOLD:
688                                 self.mesh.smooth = 1
689
690                 nvertices = len(mesh.vertices)
691                 if nvertices != ncoo:
692                         print "todo: %d, done: %d" % (ncoo, nvertices)
693                         raise RuntimeError, "FATAL: could not create all vertices"
694
695 # MAKE FACES            
696
697                 index = curnode.coordIndex
698                 vlist = []
699
700                 flip = OPTIONS['flipnormals']
701                 facecount = 0
702                 vertcount = 0
703
704                 cols = []
705                 if curnode.colorPerVertex:    # per vertex colors
706                         for i in index:
707                                 if i == -1:
708                                         if flip or (curnode.ccw == 0 and not flip): # counterclockwise face def
709                                                 vlist.reverse()
710                                         f = mesh.addFace(vlist, faceflags, uvflag)
711                                         if uvflag or colors:
712                                                 f.col = cols
713                                                 cols = []
714                                         vlist = []
715                                 else:
716                                         if colors:
717                                                 col = apply(Color, colors[colindex[vertcount]])
718                                                 cols.append(col)
719                                                 vertcount += 1
720                                         v = mesh.vertices[i]
721                                         vlist.append(v) 
722                 else:                         # per face colors
723                         for i in index:
724                                 if i == -1:
725                                         if flip or (curnode.ccw == 0 and not flip): # counterclockwise face def
726                                                 vlist.reverse()
727                                         f = mesh.addFace(vlist, faceflags, uvflag)
728                                         facecount += 1
729
730                                         if colors:
731                                                 col = apply(Color, colors[colindex[facecount]])
732                                                 cols = len(f.v) * [col]
733
734                                         if uvflag or colors:
735                                                 f.col = cols
736                                         vlist = []
737                                 else:
738                                         v = mesh.vertices[i]
739                                         vlist.append(v) 
740
741 # TEXTURE COORDINATES
742
743                 if not texcoo:
744                         return None, None
745
746                 self.curmaterial.setMode("traceable", "shadow", "texFace")
747                 m = self.curtexmatrix
748                 if m: # texture transform exists:
749                         for uv in texcoo.point:
750                                 v = (uv[0], uv[1], 1.0)
751                                 v1 = matxvec(m, v)
752                                 uv[0], uv[1] = v1[0], v1[1]
753                                 
754                 UVindex = curnode.texCoordIndex
755                 if not UVindex: 
756                         UVindex = curnode.coordIndex
757                 # go assign UVs
758                 self.mesh.hasFaceUV(1)
759                 j = 0
760                 uv = []
761                 for i in UVindex:
762                         if i == -1: # flush
763                                 if not curnode.ccw:
764                                         uv.reverse()
765                                 assignUV(f, uv)
766                                 assignImage(f, self.curImage)
767                                 uv = []
768                                 j +=1
769                         else:
770                                 f = mesh.faces[j]
771                                 uv.append(texcoo.point[i])
772                 return None, None
773
774 class PostProcessor(NullProcessor):
775         def Shape(self, curnode, **kw):
776                 pass
777                 return None, None
778         def Transform(self, curnode, **kw):
779                 return None, None
780
781 class Walker:
782         """The node visitor (walker) class for VRML nodes"""
783         def __init__(self, pre, post = NullProcessor(), progress = None):
784                 self.scene = Scenegraph.BScene()
785                 self.preprocessor = pre
786                 self.postprocessor = post
787                 pre.walker = self # processor knows about walker
788                 post.walker = self 
789                 self.nodes = 1
790                 self.depth = 0
791                 self.progress = progress
792                 self.processednodes = 0
793
794         def walk(self, vnode, parent):
795                 """Essential walker routine. It walks along the scenegraph nodes and
796 processes them according to its pre/post processor methods.
797
798 The preprocessor methods return the children of the node remaining
799 to be processed or None. Also, a new created target node is returned.
800 If the target node is == None, the current node will be skipped in the
801 target scenegraph generation. If it is a valid node, the walker routine
802 inserts it into the 'parent' node of the target scenegraph, which
803 must be a valid root node on first call, leading us to the example usage:
804
805         p = Processor()
806         w = Walker(p, PostProcessor())
807         root = Scenegraph.RootNode()
808         w.walk(SG, root) # SG is a VRML scenegraph
809         """
810                 global g_level  #XXX
811                 self.depth += 1  
812                 g_level = self.depth
813                 if self.depth < PROGRESS_DEPTH:
814                         self.processednodes += 1
815                         if self.progress:
816                                 ret = self.progress(PARSE_TIME + PROCESS_TIME * float(self.processednodes) / self.nodes)
817                                 if not ret:
818                                         progress(1.0)
819                                         raise UnfinishedError, "User cancelled conversion"
820
821                 # if vnode has already been processed, call Linker method, Processor method otherwise
822                 id = vnode.DEF # get name
823                 if not id:
824                         id = 'Object'
825
826                 processed = vnode.getTargetnode()
827                 if processed: # has been processed ?
828                         debug("linked obj: %s" % id)
829                         children, bnode = self.link(processed, parent)          
830                 else:
831                         children, bnode = self.preprocess(vnode, parent)
832                         
833                 if not bnode:
834                         bnode = parent # pass on
835                 else:
836                         parent.insert(bnode) # insert into SG
837
838                 if children:
839                         for c in children:
840                                 self.walk(c, bnode)
841                 if not processed:
842                         self.postprocess(vnode, bnode)
843
844                 self.depth -= 1 
845
846                 return children, bnode
847
848         def link(self, bnode, parent):
849                 """Link already processed data"""
850                 # link data:
851                 new = bnode.clone()
852                 if not new:
853                         raise RuntimeError, "couldn't clone object"
854                 return None, new 
855
856         def preprocess(self, vnode, newnode = None):
857                 """Processes a VRML node 'vnode' and returns a custom node. The processor must
858 be specified in 'p'.            
859 Optionally, a custom parent node (previously created) is passed as 'newnode'."""
860
861                 pre = "pre"
862
863                 nodetype = vnode.__gi__
864
865                 debug(pre + "process:" + repr(nodetype) + " " + vnode.DEF)
866                 return callMethod(self.preprocessor, nodetype, vnode, newnode)
867
868         def postprocess(self, vnode, newnode = None):
869                 """Postprocessing of a VRML node, see Walker.preprocess()"""
870
871                 nodetype = vnode.__gi__
872                 pre = "post"
873
874                 debug(pre + "process:" + repr(nodetype) + " " + vnode.DEF)
875                 return callMethod(self.postprocessor, nodetype, vnode, newnode, 0)
876
877 testfile2 = '/home/strubi/exotic/wrl/BrownTrout1.wrl'
878 testfile = '/home/strubi/exotic/wrl/examples/VRML_Model_HSL.wrl'
879
880 def fix_VRMLaxes(root, scale):
881         from Blender import Object, Scene
882         q = quat.fromRotAxis((1.0, 0.0, 0.0), 1.57079)
883         empty = Object.New(OB.EMPTY)
884         empty.layer = LAYER_EMPTY
885         Scene.getCurrent().link(empty)
886         node = Scenegraph.ObjectNode(empty, None, "VRMLscene")
887         node.transform.rotation = q
888         if scale:
889                 node.transform.scale = (0.01, 0.01, 0.01)
890         for c in root.children:
891                 node.insert(c)
892         node.update()
893         root.children = [node]
894
895 #################################################################
896 # these are the routines that must be provided for the importer
897 # interface in blender
898
899 def checkmagic(name):
900         "check for file magic"
901         f = open(name, "r")
902         magic = loader.getFileType(f)
903         f.close()
904         if magic == 'vrml':
905                 return 1
906         elif magic == 'gzip':
907                 verbose("gzipped file detected")
908                 try:
909                         import gzip
910                 except ImportError, value:
911                         warn("Importing gzip module: %s" % value)
912                         return 0
913
914                 f = gzip.open(name, 'rb')
915                 header = f.readline()
916                 f.close()
917                 if header[:10] == "#VRML V2.0":
918                         return 1
919                 else:
920                         return 0
921         print "unknown file"
922         return 0
923
924 g_infotxt = ""
925
926 def progress(done):
927         from Blender import Window
928         ret = Window.draw_progressbar(done, g_infotxt)
929         return ret
930
931 class Counter:
932         def __init__(self):
933                 self._count = 0
934                 self.depth = 0
935         def count(self, node):
936                 if self.depth >= PROGRESS_DEPTH:
937                         return 0
938
939                 self.depth += 1
940                 self._count += 1
941                 if not getChildren(node):
942                         self.depth -= 1
943                         return 0
944                 else:
945                         for c in node.children:
946                                 self.count(c)
947                 self.depth -= 1
948                 return self._count
949
950 ################################################################################
951 # MAIN ROUTINE
952
953 def importfile(name):
954
955         global g_infotxt
956         global options
957         global DEFAULTFLAGS
958
959         from Blender import Get # XXX 
960         options = Get('vrmloptions')
961         DEFAULTFLAGS = FACEFLAGS.LIGHT + FACEFLAGS.DYNAMIC
962         if options['twoside']:
963                 print "TWOSIDE"
964                 DEFAULTFLAGS |= FACEFLAGS.TWOSIDE
965         clrError()
966         g_infotxt = "load & parse file..."
967         progress(0.0)
968         root = Scenegraph.RootNode()
969         try:
970                 l = loader.Loader(name, progress)
971                 SG = l.load()
972                 p = Processor()
973                 w = Walker(p, PostProcessor(), progress)
974                 g_infotxt = "convert data..."
975                 p.curpath = os.path.dirname(name)
976                 print "counting nodes...",
977                 c = Counter()
978                 nodes = c.count(SG)
979                 print "done."
980                 w.nodes = nodes # let walker know about number of nodes parsed # XXX
981                 w.walk(SG, root)
982         except UnfinishedError, msg:
983                 print msg
984
985         progress(1.0)
986         fix_VRMLaxes(root, options['autoscale']) # rotate coordinate system: in VRML, y is up!
987         root.update() # update baselist for proper display
988         return root