3131# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3232# POSSIBILITY OF SUCH DAMAGE.
3333# ----------------------------------------------------------------------------
34+ from collections import namedtuple
3435import logging
3536import os
3637import time
@@ -51,7 +52,7 @@ class ObjParser(Parser):
5152 cache_writer_cls = CacheWriter
5253
5354 def __init__ (self , wavefront , file_name , strict = False , encoding = "utf-8" ,
54- create_materials = False , parse = True , cache = False ):
55+ create_materials = False , collect_faces = False , parse = True , cache = False ):
5556 """
5657 Create a new obj parser
5758 :param wavefront: The wavefront object
@@ -68,11 +69,11 @@ def __init__(self, wavefront, file_name, strict=False, encoding="utf-8",
6869 self .mesh = None
6970 self .material = None
7071 self .create_materials = create_materials
72+ self .collect_faces = collect_faces
7173 self .cache = cache
7274 self .cache_loaded = None
7375
74- # Stores ALL vertices, normals and texcoords for the entire file
75- self .vertices = []
76+ # Stores normals and texcoords for the entire file
7677 self .normals = []
7778 self .tex_coords = []
7879
@@ -109,7 +110,7 @@ def post_parse(self):
109110
110111 # methods for parsing types of wavefront lines
111112 def parse_v (self ):
112- self .vertices += list (self .consume_vertices ())
113+ self .wavefront . vertices += list (self .consume_vertices ())
113114
114115 def consume_vertices (self ):
115116 """
@@ -213,7 +214,9 @@ def parse_mtllib(self):
213214 materials = self .material_parser_cls (
214215 os .path .join (self .dir , mtllib ),
215216 encoding = self .encoding ,
216- strict = self .strict ).materials
217+ strict = self .strict ,
218+ collect_faces = self .collect_faces
219+ ).materials
217220 self .wavefront .mtllibs .append (mtllib )
218221 except IOError :
219222 if self .create_materials :
@@ -233,7 +236,7 @@ def parse_usemtl(self):
233236 raise PywavefrontException ('Unknown material: %s' % name )
234237
235238 # Create a new default material if configured to resolve missing ones
236- self .material = Material (name , is_default = True )
239+ self .material = Material (name , is_default = True , has_faces = self . collect_faces )
237240 self .wavefront .materials [name ] = self .material
238241
239242 if self .mesh is not None :
@@ -244,44 +247,82 @@ def parse_usemat(self):
244247
245248 @auto_consume
246249 def parse_o (self ):
247- self .mesh = Mesh (self .values [1 ])
250+ self .mesh = Mesh (self .values [1 ], has_faces = self . collect_faces )
248251 self .wavefront .add_mesh (self .mesh )
249252
250253 def parse_f (self ):
251254 # Add default material if not created
252255 if self .material is None :
253- self .material = Material ("default{}" .format (len (self .wavefront .materials )), is_default = True )
256+ self .material = Material (
257+ "default{}" .format (len (self .wavefront .materials )),
258+ is_default = True ,
259+ has_faces = self .collect_faces
260+ )
254261 self .wavefront .materials [self .material .name ] = self .material
255262
256263 # Support objects without `o` statement
257264 if self .mesh is None :
258- self .mesh = Mesh ()
265+ self .mesh = Mesh (has_faces = self . collect_faces )
259266 self .wavefront .add_mesh (self .mesh )
260267 self .mesh .add_material (self .material )
261268
262269 self .mesh .add_material (self .material )
263270
264- self .material .vertices += list (self .consume_faces ())
271+ collected_faces = []
272+ consumed_vertices = self .consume_faces (collected_faces if self .collect_faces else None )
273+ self .material .vertices += list (consumed_vertices )
274+
275+ if self .collect_faces :
276+ self .mesh .faces += list (collected_faces )
265277
266278 # Since list() also consumes StopIteration we need to sanity check the line
267279 # to make sure the parser advances
268280 if self .values and self .values [0 ] == "f" :
269281 self .next_line ()
270282
271- def consume_faces (self ):
283+ def consume_faces (self , collected_faces = None ):
272284 """
273285 Consume all consecutive faces
274286
275- If a 4th vertex is specified, we triangulate.
276- In a perfect world we could consume this straight forward and draw using GL_TRIANGLE_FAN.
277- This is however rarely the case..
287+ If more than three vertices are specified, we triangulate by the following procedure:
288+
289+ Let the face have n vertices in the order v_1 v_2 v_3 ... v_n, n >= 3.
290+ We emit the first face as usual: (v_1, v_2, v_3). For each remaining vertex v_j,
291+ j > 3, we emit (v_j, v_1, v_{j - 1}), e.g. (v_4, v_1, v_3), (v_5, v_1, v_4).
278292
279- * If the face is co-planar but concave, then you need to triangulate the face
293+ In a perfect world we could consume all vertices straight forward and draw using
294+ GL_TRIANGLE_FAN (which exactly matches the procedure above).
295+ This is however rarely the case.
296+
297+ * If the face is co-planar but concave, then you need to triangulate the face.
280298 * If the face is not-coplanar, you are screwed, because OBJ doesn't preserve enough information
281- to know what tessellation was intended
299+ to know what tessellation was intended.
300+
301+ We always triangulate to make it simple.
282302
283- We always triangulate to make it simple
303+ :param collected_faces: A list into which all (possibly triangulated) faces will be written in the form
304+ of triples of the corresponding absolute vertex IDs. These IDs index the list
305+ self.wavefront.vertices.
306+ Specify None to prevent consuming faces (and thus saving memory usage).
284307 """
308+
309+ # Helper tuple and function
310+ Vertex = namedtuple ('Vertex' , 'idx pos color uv normal' )
311+ def emit_vertex (vertex ):
312+ # Just yield all the values except for the index
313+ for v in vertex .uv :
314+ yield v
315+
316+ for v in vertex .color :
317+ yield v
318+
319+ for v in vertex .normal :
320+ yield v
321+
322+ for v in vertex .pos :
323+ yield v
324+
325+
285326 # Figure out the format of the first vertex
286327 # We raise an exception if any following vertex has a different format
287328 # NOTE: Order is always v/vt/vn where v is mandatory and vt and vn is optional
@@ -304,11 +345,11 @@ def consume_faces(self):
304345 # Are we referencing vertex with color info?
305346 vindex = int (parts [0 ])
306347 if vindex < 0 :
307- vindex += len (self .vertices )
348+ vindex += len (self .wavefront . vertices )
308349 else :
309350 vindex -= 1
310351
311- vertex = self .vertices [vindex ]
352+ vertex = self .wavefront . vertices [vindex ]
312353 has_colors = len (vertex ) == 6
313354
314355 # Prepare vertex format string
@@ -331,10 +372,8 @@ def consume_faces(self):
331372 # The first iteration processes the current/first f statement.
332373 # The loop continues until there are no more f-statements or StopIteration is raised by generator
333374 while True :
334- v1 , vlast = None , None
335-
336- # Do we need to triangulate? Each line may contain a varying amount of elements
337- triangulate = (len (self .values ) - 1 ) > 3
375+ # The very first vertex, the last encountered and the current one
376+ v1 , vlast , vcurrent = None , None , None
338377
339378 for i , v in enumerate (self .values [1 :]):
340379 parts = v .split ('/' )
@@ -344,50 +383,48 @@ def consume_faces(self):
344383
345384 # Resolve negative index lookups
346385 if v_index < 0 :
347- v_index += len (self .vertices ) + 1
386+ v_index += len (self .wavefront . vertices ) + 1
348387
349388 if has_vt and t_index < 0 :
350389 t_index += len (self .tex_coords ) + 1
351390
352391 if has_vn and n_index < 0 :
353392 n_index += len (self .normals ) + 1
354393
355- pos = self . vertices [ v_index ][ 0 : 3 ] if has_colors else self . vertices [ v_index ]
356- color = self . vertices [ v_index ][ 3 :] if has_colors else ()
357- uv = self . tex_coords [ t_index ] if has_vt else ()
358- normal = self .normals [ n_index ] if has_vn else ()
359-
360- # Just yield all the values
361- for v in uv :
362- yield v
394+ vlast = vcurrent
395+ vcurrent = Vertex (
396+ idx = v_index ,
397+ pos = self .wavefront . vertices [ v_index ][ 0 : 3 ] if has_colors else self . wavefront . vertices [ v_index ],
398+ color = self . wavefront . vertices [ v_index ][ 3 :] if has_colors else (),
399+ uv = self . tex_coords [ t_index ] if has_vt else (),
400+ normal = self . normals [ n_index ] if has_vn else ()
401+ )
363402
364- for v in color :
365- yield v
403+ yield from emit_vertex (vcurrent )
366404
367- for v in normal :
368- yield v
405+ # Triangulation when more than 3 elements are present
406+ if i >= 3 :
407+ # The current vertex has already been emitted.
408+ # Now just emit the first and the third vertices from the face
409+ yield from emit_vertex (v1 )
410+ yield from emit_vertex (vlast )
369411
370- for v in pos :
371- yield v
412+ if i == 0 :
413+ # Store the first vertex
414+ v1 = vcurrent
372415
373- # Triangulation when more than 3 elements is present
374- if triangulate :
416+ if (collected_faces is not None ) and (i >= 2 ):
417+ if i == 2 :
418+ # Append the first triangle face in usual order (i.e. as specified in the Wavefront file)
419+ collected_faces .append ([v1 .idx , vlast .idx , vcurrent .idx ])
375420 if i >= 3 :
376- # Emit vertex 1 and 3 triangulating when a 4th vertex is specified
377- for v in v1 :
378- yield v
379-
380- for v in vlast :
381- yield v
382-
383- if i == 0 :
384- # Store the first vertex
385- v1 = uv + color + normal + pos
386-
387- # Store the last vertex
388- vlast = uv + color + normal + pos
421+ # Triangulate the remaining part of the face by putting the current, the first
422+ # and the last parsed vertex in that order as a new face.
423+ # This order coincides deliberately with the order from vertex yielding above.
424+ collected_faces .append ([vcurrent .idx , v1 .idx , vlast .idx ])
389425
390426 # Break out of the loop when there are no more f statements
427+
391428 try :
392429 self .next_line ()
393430 except StopIteration :
0 commit comments