Skip to content

Commit ed29465

Browse files
committed
reformat test_repair to pytest style
1 parent 5749c32 commit ed29465

File tree

3 files changed

+259
-232
lines changed

3 files changed

+259
-232
lines changed

tests/test_repair.py

Lines changed: 192 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -4,198 +4,203 @@
44
import generic as g
55

66

7-
class RepairTests(g.unittest.TestCase):
8-
def test_fill_holes(self):
9-
for mesh_name in [
7+
def test_fill_holes():
8+
for mesh_name in [
9+
"unit_cube.STL",
10+
"machinist.XAML",
11+
"round.stl",
12+
"sphere.ply",
13+
"teapot.stl",
14+
"soup.stl",
15+
"featuretype.STL",
16+
"angle_block.STL",
17+
"quadknot.obj",
18+
]:
19+
mesh = g.get_mesh(mesh_name)
20+
if not mesh.is_watertight:
21+
# output of fill_holes should match watertight status
22+
returned = mesh.fill_holes()
23+
assert returned == mesh.is_watertight
24+
continue
25+
26+
hashes = [{mesh._data.__hash__(), hash(mesh)}]
27+
28+
mesh.faces = mesh.faces[1:-1]
29+
assert not mesh.is_watertight
30+
assert not mesh.is_volume
31+
32+
# color some faces
33+
g.trimesh.repair.broken_faces(mesh, color=[255, 0, 0, 255])
34+
35+
hashes.append({mesh._data.__hash__(), hash(mesh)})
36+
37+
assert hashes[0] != hashes[1]
38+
39+
# run the fill holes operation should succeed
40+
assert mesh.fill_holes()
41+
# should be a superset of the last two
42+
assert mesh.is_volume
43+
assert mesh.is_watertight
44+
assert mesh.is_winding_consistent
45+
46+
hashes.append({mesh._data.__hash__(), hash(mesh)})
47+
assert hashes[1] != hashes[2]
48+
49+
# try broken faces on a watertight mesh
50+
g.trimesh.repair.broken_faces(mesh, color=[255, 255, 0, 255])
51+
52+
53+
def test_fix_normals():
54+
for mesh in g.get_meshes(5):
55+
mesh.fix_normals()
56+
57+
58+
def test_winding():
59+
"""
60+
Reverse some faces and make sure fix_face_winding flips
61+
them back.
62+
"""
63+
64+
meshes = [
65+
g.get_mesh(i)
66+
for i in [
1067
"unit_cube.STL",
1168
"machinist.XAML",
1269
"round.stl",
13-
"sphere.ply",
14-
"teapot.stl",
15-
"soup.stl",
16-
"featuretype.STL",
17-
"angle_block.STL",
1870
"quadknot.obj",
19-
]:
20-
mesh = g.get_mesh(mesh_name)
21-
if not mesh.is_watertight:
22-
# output of fill_holes should match watertight status
23-
returned = mesh.fill_holes()
24-
assert returned == mesh.is_watertight
25-
continue
26-
27-
hashes = [{mesh._data.__hash__(), hash(mesh)}]
28-
29-
mesh.faces = mesh.faces[1:-1]
30-
assert not mesh.is_watertight
31-
assert not mesh.is_volume
32-
33-
# color some faces
34-
g.trimesh.repair.broken_faces(mesh, color=[255, 0, 0, 255])
35-
36-
hashes.append({mesh._data.__hash__(), hash(mesh)})
37-
38-
assert hashes[0] != hashes[1]
39-
40-
# run the fill holes operation should succeed
41-
assert mesh.fill_holes()
42-
# should be a superset of the last two
43-
assert mesh.is_volume
44-
assert mesh.is_watertight
45-
assert mesh.is_winding_consistent
46-
47-
hashes.append({mesh._data.__hash__(), hash(mesh)})
48-
assert hashes[1] != hashes[2]
49-
50-
# try broken faces on a watertight mesh
51-
g.trimesh.repair.broken_faces(mesh, color=[255, 255, 0, 255])
52-
53-
def test_fix_normals(self):
54-
for mesh in g.get_meshes(5):
55-
mesh.fix_normals()
56-
57-
def test_winding(self):
58-
"""
59-
Reverse some faces and make sure fix_face_winding flips
60-
them back.
61-
"""
62-
63-
meshes = [
64-
g.get_mesh(i)
65-
for i in [
66-
"unit_cube.STL",
67-
"machinist.XAML",
68-
"round.stl",
69-
"quadknot.obj",
70-
"soup.stl",
71-
]
71+
"soup.stl",
7272
]
73-
74-
for i, mesh in enumerate(meshes):
75-
# turn scenes into multibody meshes
76-
if g.trimesh.util.is_instance_named(mesh, "Scene"):
77-
meta = mesh.metadata
78-
meshes[i] = mesh.dump().sum()
79-
meshes[i].metadata = meta
80-
81-
timing = {}
82-
for mesh in meshes:
83-
# save the initial state
84-
is_volume = mesh.is_volume
85-
winding = mesh.is_winding_consistent
86-
87-
tic = g.time.time()
88-
# flip faces to break winding
89-
mesh.faces[:4] = g.np.fliplr(mesh.faces[:4])
90-
91-
# run the operation
92-
mesh.fix_normals()
93-
94-
# make sure mesh is repaired to former glory
95-
assert mesh.is_volume == is_volume
96-
assert mesh.is_winding_consistent == winding
97-
98-
# save timings
99-
timing[mesh.source.file_name] = g.time.time() - tic
100-
# print timings as a warning
101-
g.log.warning(g.json.dumps(timing, indent=4))
102-
103-
def test_inversion(self):
104-
"""Make sure fix_inversion switches all reversed faces back"""
105-
orig_mesh = g.get_mesh("unit_cube.STL")
106-
orig_verts = orig_mesh.vertices.copy()
107-
orig_faces = orig_mesh.faces.copy()
108-
109-
mesh = g.Trimesh(orig_verts, orig_faces[:, ::-1])
110-
inv_faces = mesh.faces.copy()
111-
# check not fixed on the way in
112-
assert not g.np.allclose(inv_faces, orig_faces)
113-
114-
g.trimesh.repair.fix_inversion(mesh)
115-
assert not g.np.allclose(mesh.faces, inv_faces)
116-
assert g.np.allclose(mesh.faces, orig_faces)
117-
118-
def test_multi(self):
119-
"""
120-
Try repairing a multibody geometry
121-
"""
122-
# create a multibody mesh with two cubes
123-
a = g.get_mesh("unit_cube.STL")
124-
b = a.copy()
125-
b.apply_translation([2, 0, 0])
126-
m = a + b
127-
# should be a volume: watertight, correct winding
128-
assert m.is_volume
129-
130-
# flip one face of A
131-
a.faces[:1] = g.np.fliplr(a.faces[:1])
132-
# flip every face of A
133-
a.invert()
134-
# flip one face of B
135-
b.faces[:1] = g.np.fliplr(b.faces[:1])
136-
m = a + b
137-
138-
# not a volume
139-
assert not m.is_volume
140-
141-
m.fix_normals(multibody=False)
142-
143-
# shouldn't fix inversion of one cube
144-
assert not m.is_volume
145-
146-
# run fix normal with multibody mode
147-
m.fix_normals()
148-
149-
# should be volume again
150-
assert m.is_volume
151-
152-
# mesh should be volume of two boxes, and positive
153-
assert g.np.isclose(m.volume, 2.0)
154-
155-
def test_flip(self):
156-
# create two spheres
157-
a = g.trimesh.creation.icosphere()
158-
b = g.trimesh.creation.icosphere().apply_translation([2, 3, 0])
159-
# invert the second sphere
160-
b.faces = g.np.fliplr(b.faces)
161-
m = a + b
162-
# make sure normals are in cache
163-
assert m.face_normals.shape == m.faces.shape
164-
m.fix_normals(multibody=True)
165-
assert g.np.isclose(m.volume, a.volume * 2.0)
166-
167-
def test_fan(self):
168-
# start by creating an icosphere and removing
169-
# all faces that include a single vertex to make
170-
# a nice hole in the mesh
171-
m = g.trimesh.creation.icosphere()
172-
clip = m.vertex_faces[0]
173-
clip = clip[clip >= 0]
174-
assert len(clip) > 4
175-
mask = g.np.ones(len(m.faces), dtype=bool)
176-
mask[clip] = False
177-
178-
# should have been watertight
179-
assert m.is_watertight
180-
assert m.is_winding_consistent
181-
m.update_faces(mask)
182-
# now should not be watertight
183-
assert not m.is_watertight
184-
assert m.is_winding_consistent
185-
186-
# create a triangle fan to cover the hole
187-
stitch = g.trimesh.repair.stitch(m)
188-
# should be an (n, 3) int
189-
assert len(stitch.shape) == 2
190-
assert stitch.shape[1] == 3
191-
assert stitch.dtype.kind == "i"
192-
193-
# now check our stitch to see if it handled the hole
194-
repair = g.trimesh.Trimesh(
195-
vertices=m.vertices.copy(), faces=g.np.vstack((m.faces, stitch))
196-
)
197-
assert repair.is_watertight
198-
assert repair.is_winding_consistent
73+
]
74+
75+
for i, mesh in enumerate(meshes):
76+
# turn scenes into multibody meshes
77+
if g.trimesh.util.is_instance_named(mesh, "Scene"):
78+
meta = mesh.metadata
79+
meshes[i] = mesh.dump().sum()
80+
meshes[i].metadata = meta
81+
82+
timing = {}
83+
for mesh in meshes:
84+
# save the initial state
85+
is_volume = mesh.is_volume
86+
winding = mesh.is_winding_consistent
87+
88+
tic = g.time.time()
89+
# flip faces to break winding
90+
mesh.faces[:4] = g.np.fliplr(mesh.faces[:4])
91+
92+
# run the operation
93+
mesh.fix_normals()
94+
95+
# make sure mesh is repaired to former glory
96+
assert mesh.is_volume == is_volume
97+
assert mesh.is_winding_consistent == winding
98+
99+
# save timings
100+
timing[mesh.source.file_name] = g.time.time() - tic
101+
# print timings as a warning
102+
g.log.warning(g.json.dumps(timing, indent=4))
103+
104+
105+
def test_inversion():
106+
"""Make sure fix_inversion switches all reversed faces back"""
107+
orig_mesh = g.get_mesh("unit_cube.STL")
108+
orig_verts = orig_mesh.vertices.copy()
109+
orig_faces = orig_mesh.faces.copy()
110+
111+
mesh = g.Trimesh(orig_verts, orig_faces[:, ::-1])
112+
inv_faces = mesh.faces.copy()
113+
# check not fixed on the way in
114+
assert not g.np.allclose(inv_faces, orig_faces)
115+
116+
g.trimesh.repair.fix_inversion(mesh)
117+
assert not g.np.allclose(mesh.faces, inv_faces)
118+
assert g.np.allclose(mesh.faces, orig_faces)
119+
120+
121+
def test_multi():
122+
"""
123+
Try repairing a multibody geometry
124+
"""
125+
# create a multibody mesh with two cubes
126+
a = g.get_mesh("unit_cube.STL")
127+
b = a.copy()
128+
b.apply_translation([2, 0, 0])
129+
m = a + b
130+
# should be a volume: watertight, correct winding
131+
assert m.is_volume
132+
133+
# flip one face of A
134+
a.faces[:1] = g.np.fliplr(a.faces[:1])
135+
# flip every face of A
136+
a.invert()
137+
# flip one face of B
138+
b.faces[:1] = g.np.fliplr(b.faces[:1])
139+
m = a + b
140+
141+
# not a volume
142+
assert not m.is_volume
143+
144+
m.fix_normals(multibody=False)
145+
146+
# shouldn't fix inversion of one cube
147+
assert not m.is_volume
148+
149+
# run fix normal with multibody mode
150+
m.fix_normals()
151+
152+
# should be volume again
153+
assert m.is_volume
154+
155+
# mesh should be volume of two boxes, and positive
156+
assert g.np.isclose(m.volume, 2.0)
157+
158+
159+
def test_flip():
160+
# create two spheres
161+
a = g.trimesh.creation.icosphere()
162+
b = g.trimesh.creation.icosphere().apply_translation([2, 3, 0])
163+
# invert the second sphere
164+
b.faces = g.np.fliplr(b.faces)
165+
m = a + b
166+
# make sure normals are in cache
167+
assert m.face_normals.shape == m.faces.shape
168+
m.fix_normals(multibody=True)
169+
assert g.np.isclose(m.volume, a.volume * 2.0)
170+
171+
172+
def test_fan():
173+
# start by creating an icosphere and removing
174+
# all faces that include a single vertex to make
175+
# a nice hole in the mesh
176+
m = g.trimesh.creation.icosphere()
177+
clip = m.vertex_faces[0]
178+
clip = clip[clip >= 0]
179+
assert len(clip) > 4
180+
mask = g.np.ones(len(m.faces), dtype=bool)
181+
mask[clip] = False
182+
183+
# should have been watertight
184+
assert m.is_watertight
185+
assert m.is_winding_consistent
186+
m.update_faces(mask)
187+
# now should not be watertight
188+
assert not m.is_watertight
189+
assert m.is_winding_consistent
190+
191+
# create a triangle fan to cover the hole
192+
stitch = g.trimesh.repair.stitch(m)
193+
# should be an (n, 3) int
194+
assert len(stitch.shape) == 2
195+
assert stitch.shape[1] == 3
196+
assert stitch.dtype.kind == "i"
197+
198+
# now check our stitch to see if it handled the hole
199+
repair = g.trimesh.Trimesh(
200+
vertices=m.vertices.copy(), faces=g.np.vstack((m.faces, stitch))
201+
)
202+
assert repair.is_watertight
203+
assert repair.is_winding_consistent
199204

200205

201206
if __name__ == "__main__":

0 commit comments

Comments
 (0)