Skip to content

Commit bb0d5e6

Browse files
author
Kurt Yoder
authored
Merge pull request #52 from einarf/create-mat
Parser can create missing materials
2 parents 2746c5f + 681c9d4 commit bb0d5e6

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
@@ -9,20 +9,22 @@
99
class ObjParser(Parser):
1010
"""This parser parses lines from .obj files."""
1111

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

2425
self.mesh = None
2526
self.material = None
27+
self.create_materials = create_materials
2628

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

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

130137
@auto_consume
131138
def parse_usemtl(self):
132-
self.material = self.wavefront.materials.get(self.values[1], None)
139+
name = " ".join(self.values[1:])
140+
self.material = self.wavefront.materials.get(name, None)
133141

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

137150
if self.mesh is not None:
138151
self.mesh.add_material(self.material)
@@ -213,7 +226,10 @@ def consume_faces(self):
213226

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

218234
self.material.vertex_format = vertex_format
219235

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)