Skip to content

Commit 56ec49d

Browse files
authored
Merge branch 'master' into mat-parser
2 parents ea7d0e6 + bb0d5e6 commit 56ec49d

File tree

5 files changed

+72
-8
lines changed

5 files changed

+72
-8
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ A more complex example
3434

3535
* `strict` will raise an exception if unsupported features are found in the obj or mtl file. Default `True`.
3636
* `encoding` of the obj and mtl file(s). Default `utf-8`.
37-
* `parse` decides if parsing should start immediately. Default 'False'.
37+
* `create_materials` will create materials if mtl file is missing or obj file references non-exiting materials . Default `False`.
38+
* `parse` decides if parsing should start immediately. Default `False`.
3839

3940
```python
4041
import pywavefront

pywavefront/obj.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@ class ObjParser(Parser):
1010
"""This parser parses lines from .obj files."""
1111
material_parser_cls = MaterialParser
1212

13-
def __init__(self, wavefront, file_name, strict=False, encoding="utf-8", parse=True):
13+
def __init__(self, wavefront, file_name, strict=False, encoding="utf-8", create_materials=False, parse=True):
1414
"""
1515
Create a new obj parser
1616
:param wavefront: The wavefront object
1717
:param file_name: file name and path of obj file to read
1818
:param strict: Enable strict mode
1919
:param encoding: Encoding to read the text files
20+
:param create_materials: Create materials if they don't exist
2021
:param parse: Should parse be called immediately or manually called later?
2122
"""
2223
super(ObjParser, self).__init__(file_name, strict=strict, encoding=encoding)
2324
self.wavefront = wavefront
2425

2526
self.mesh = None
2627
self.material = None
28+
self.create_materials = create_materials
2729

2830
# Stores ALL vertices, normals and texcoords for the entire file
2931
self.vertices = []
@@ -123,17 +125,28 @@ def consume_texture_coordinates(self):
123125
@auto_consume
124126
def parse_mtllib(self):
125127
mtllib = os.path.join(self.dir, " ".join(self.values[1:]))
126-
materials = self.material_parser_cls(mtllib, encoding=self.encoding, strict=self.strict).materials
128+
try:
129+
materials = self.material_parser_cls(mtllib, encoding=self.encoding, strict=self.strict).materials
130+
except IOError:
131+
if self.create_materials:
132+
return
133+
raise
127134

128135
for name, material in materials.items():
129136
self.wavefront.materials[name] = material
130137

131138
@auto_consume
132139
def parse_usemtl(self):
133-
self.material = self.wavefront.materials.get(self.values[1], None)
140+
name = " ".join(self.values[1:])
141+
self.material = self.wavefront.materials.get(name, None)
134142

135143
if self.material is None:
136-
raise PywavefrontException('Unknown material: %s' % self.values[1])
144+
if not self.create_materials:
145+
raise PywavefrontException('Unknown material: %s' % name)
146+
147+
# Create a new default material if configured to resolve missing ones
148+
self.material = Material(name=name, is_default=True)
149+
self.wavefront.materials[name] = self.material
137150

138151
if self.mesh is not None:
139152
self.mesh.add_material(self.material)
@@ -214,7 +227,10 @@ def consume_faces(self):
214227

215228
# If the material already have vertex data, ensure the same format is used
216229
if self.material.vertex_format and self.material.vertex_format != vertex_format:
217-
raise ValueError("Trying to merge vertex data with different formats")
230+
raise ValueError((
231+
"Trying to merge vertex data with different format: {}. "
232+
"Material {} has vertex format {}"
233+
).format(vertex_format, self.material.name, self.material.vertex_format))
218234

219235
self.material.vertex_format = vertex_format
220236

pywavefront/wavefront.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ class Wavefront(object):
66
parser_cls = ObjParser
77

88
"""Import a wavefront .obj file."""
9-
def __init__(self, file_name, strict=False, encoding="utf-8", parse=True):
9+
def __init__(self, file_name, strict=False, encoding="utf-8", create_materials=False, parse=True):
1010
"""
1111
Create a Wavefront instance
1212
:param file_name: file name and path of obj file to read
1313
:param strict: Enable strict mode
14+
:param encoding: What text encoding the parser should use
15+
:param create_materials: Create materials if they don't exist
1416
:param parse: Should parse be called immediately or manually called later?
1517
"""
1618
self.file_name = file_name
@@ -19,7 +21,13 @@ def __init__(self, file_name, strict=False, encoding="utf-8", parse=True):
1921
self.meshes = {} # Name mapping
2022
self.mesh_list = [] # Also includes anonymous meshes
2123

22-
self.parser = self.parser_cls(self, self.file_name, strict=strict, encoding=encoding, parse=parse)
24+
self.parser = self.parser_cls(
25+
self,
26+
self.file_name,
27+
strict=strict,
28+
encoding=encoding,
29+
create_materials=create_materials,
30+
parse=parse)
2331

2432
def parse(self):
2533
"""Manually call the parser. This is used when parse=False"""

test/simple_missing_material.obj

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Comment
2+
mtllib simple_missing.mtl
3+
o Simple
4+
v 0.01 0.02 0.03
5+
v 0.04 0.05 0.06
6+
v 0.07 0.08 0.09
7+
v 0.11 0.12 0.13
8+
vt 10 11
9+
vt 12 13
10+
vt 14 15
11+
vt 16 17
12+
vn 20 21 22
13+
usemtl Material.simple
14+
f 2/3/1 1/2/1 3/1/1
15+
o SimpleB
16+
v 1.0 0.0 1.0
17+
v -1.0 0.0 1.0
18+
v 1.0 0.0 -1.0
19+
v -1.0 0.0 -1.0
20+
vt 0.0 1.0
21+
vt 0.0 0.0
22+
vt 1.0 0.0
23+
vt 1.0 1.0
24+
vn 0.0 1.0 -0.0
25+
usemtl Material2.simple
26+
f 6/7/2 5/6/2 7/5/2

test/test_parser.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ def setUp(self):
5454
self.mesh2 = meshes.mesh_list[1]
5555

5656

57+
class TestParserMissingMaterials(unittest.TestCase):
58+
"""Test `create_materials` functionality"""
59+
60+
def test_missing_material_error(self):
61+
"""Parser should crash if `create_materials` is not set"""
62+
with self.assertRaises(IOError):
63+
pywavefront.Wavefront(prepend_dir('simple_missing_material.obj'))
64+
65+
def test_missing_material_create(self):
66+
"""Parser should handle missing materials if `create_materials` is set"""
67+
pywavefront.Wavefront(prepend_dir('simple_missing_material.obj'), create_materials=True)
68+
69+
5770
class TestParserVertexVariants(unittest.TestCase):
5871

5972
def testObjNoNormals(self):

0 commit comments

Comments
 (0)