def test_permutate(self): def close(a, b): if len(a) == len(b) == 0: return False if a.shape != b.shape: return False return g.np.allclose(a, b) def make_assertions(mesh, test, rigid=False): if (close(test.face_adjacency, mesh.face_adjacency) and len(mesh.faces) > MIN_FACES): raise ValueError( 'face adjacency of %s the same after permutation!', mesh.metadata['file_name']) if (close(test.face_adjacency_edges, mesh.face_adjacency_edges) and len(mesh.faces) > MIN_FACES): raise ValueError( 'face adjacency edges of %s the same after permutation!', mesh.metadata['file_name']) self.assertFalse(close(test.faces, mesh.faces)) self.assertFalse(close(test.vertices, mesh.vertices)) self.assertFalse(test.md5() == mesh.md5()) # rigid transforms don't change area or volume if rigid: assert g.np.allclose(mesh.area, test.area) # volume is very dependent on meshes being watertight and sane if (mesh.is_watertight and test.is_watertight and mesh.is_winding_consistent and test.is_winding_consistent): assert g.np.allclose(mesh.volume, test.volume, rtol=.05) for mesh in g.get_meshes(): if len(mesh.faces) < MIN_FACES: continue # warp the mesh to be a unit cube mesh.vertices /= mesh.extents original = mesh.copy() for i in range(5): mesh = original.copy() noise = g.trimesh.permutate.noise(mesh, magnitude=mesh.scale / 50.0) # make sure that if we permutate vertices with no magnitude # area and volume remain the same no_noise = g.trimesh.permutate.noise(mesh, magnitude=0.0) transform = g.trimesh.permutate.transform(mesh) tesselate = g.trimesh.permutate.tesselation(mesh) make_assertions(mesh, noise, rigid=False) make_assertions(mesh, no_noise, rigid=True) make_assertions(mesh, transform, rigid=True) make_assertions(mesh, tesselate, rigid=True) # make sure permutate didn't alter the original mesh self.assertTrue(original.md5() == mesh.md5())
def test_minball(self): # how close do we need to be tol_fit = 1e-2 # get some assorted mesh geometries to test performance # and a perfect sphere mesh to test the degenerate case for m in g.np.append(list(g.get_meshes(5)), g.trimesh.primitives.Sphere()): s = m.bounding_sphere R_check = ((m.vertices - s.primitive.center)**2).sum(axis=1).max()**.5 assert len(s.primitive.center) == 3 assert s.primitive.radius > 0.0 assert abs(s.primitive.radius - R_check) < tol_fit assert s.volume > (m.volume - tol_fit) # check minimum n-sphere for points in 2, 3, 4 dimensions for d in [2, 3, 4]: for i in range(5): points = g.np.random.random((100, d)) C, R = g.trimesh.nsphere.minimum_nsphere(points) R_check = ((points - C)**2).sum(axis=1).max()**.5 assert len(C) == d assert R > 0.0 assert abs(R - R_check) < g.tol.merge
def test_fill_holes(self): for mesh in g.get_meshes(5): if not mesh.is_watertight: continue mesh.faces = mesh.faces[1:-1] self.assertFalse(mesh.is_watertight) mesh.fill_holes() self.assertTrue(mesh.is_watertight)
def test_convex(self): for mesh in g.get_meshes(10): if not mesh.is_watertight: continue hulls = [] for i in range(50): permutated = mesh.permutate.transform() if i % 10 == 0: permutated = permutated.permutate.tesselation() hulls.append(permutated.convex_hull) volume = g.np.array([i.volume for i in hulls]) if volume.ptp() > (mesh.scale / 1000): print(volume) raise ValueError('volume is inconsistent on {}'.format( mesh.metadata['file_name'])) self.assertTrue(volume.min() > 0.0) if not all(i.is_winding_consistent for i in hulls): raise ValueError( 'mesh %s reported bad winding on convex hull!', mesh.metadata['file_name']) '''
def test_meshes(self): # make sure we can load everything we think we can # while getting a list of meshes to run tests on meshes = g.get_meshes(raise_error=True) g.log.info('Running tests on %d meshes', len(meshes)) for mesh in meshes: g.log.info('Testing %s', mesh.metadata['file_name']) self.assertTrue(len(mesh.faces) > 0) self.assertTrue(len(mesh.vertices) > 0) self.assertTrue(len(mesh.edges) > 0) self.assertTrue(len(mesh.edges_unique) > 0) self.assertTrue(len(mesh.edges_sorted) > 0) self.assertTrue(len(mesh.edges_face) > 0) self.assertTrue(isinstance(mesh.euler_number, int)) mesh.process() if not mesh.is_watertight: continue assert len(mesh.facets) == len(mesh.facets_area) assert len(mesh.facets) == len(mesh.facets_normal) assert len(mesh.facets) == len(mesh.facets_boundary) if len(mesh.facets) != 0: faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) smoothed = mesh.smoothed() assert mesh.volume > 0.0 section = mesh.section(plane_normal=[0, 0, 1], plane_origin=mesh.centroid) sample = mesh.sample(1000) even_sample = g.trimesh.sample.sample_surface_even(mesh, 100) assert sample.shape == (1000, 3) g.log.info('finished testing meshes') # make sure vertex kdtree and triangles rtree exist t = mesh.kdtree() self.assertTrue(hasattr(t, 'query')) g.log.info('Creating triangles tree') r = mesh.triangles_tree() self.assertTrue(hasattr(r, 'intersection')) g.log.info('Triangles tree ok') # some memory issues only show up when you copy the mesh a bunch # specifically, if you cache c- objects then deepcopy the mesh this # generally segfaults randomly copy_count = 200 g.log.info('Attempting to copy mesh %d times', copy_count) for i in range(copy_count): copied = mesh.copy() g.log.info('Multiple copies done') self.assertTrue(g.np.allclose(copied.identifier, mesh.identifier))
def test_export(self): file_types = list(g.trimesh.io.export._mesh_exporters.keys()) for mesh in g.get_meshes(5): for file_type in file_types: export = mesh.export(file_type=file_type) if export is None: raise ValueError('Exporting mesh %s to %s resulted in None!', mesh.metadata['file_name'], file_type) self.assertTrue(len(export) > 0) if file_type in ['dae', # collada, no native importers 'collada', # collada, no native importers 'msgpack', # kind of flaky, but usually works 'drc']: # DRC is not a lossless format g.log.warning( 'Still no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) loaded = g.trimesh.load(file_obj=g.io_wrap(export), file_type=file_type) if (not g.trimesh.util.is_shape(loaded._data['faces'], (-1, 3)) or not g.trimesh.util.is_shape(loaded._data['vertices'], (-1, 3)) or loaded.faces.shape != mesh.faces.shape): g.log.error('Export -> import for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) if loaded.vertices is None: g.log.error('Export -> import for %s on %s gave None for vertices!', file_type, mesh.metadata['file_name']) if loaded.faces.shape != mesh.faces.shape: raise ValueError('Export -> import for {} on {} gave vertices {}->{}!'.format( file_type, mesh.metadata['file_name'], str(mesh.faces.shape), str(loaded.faces.shape))) self.assertTrue(loaded.vertices.shape == mesh.vertices.shape) # try exporting/importing certain file types by name if file_type in ['obj', 'stl', 'ply', 'off']: temp = g.tempfile.NamedTemporaryFile(suffix='.' + file_type, delete=False) # windows throws permissions errors if you keep it open temp.close() mesh.export(temp.name) load = g.trimesh.load(temp.name) # manual cleanup g.os.remove(temp.name) assert mesh.faces.shape == load.faces.shape assert mesh.vertices.shape == load.vertices.shape
def test_helper(self): # just make sure the plumbing returns something for mesh in g.get_meshes(2): points = (g.np.random.random((100, 3)) - .5) * 100 a = mesh.nearest.on_surface(points) assert a is not None b = mesh.nearest.vertex(points) assert b is not None
def test_tesselation(self): for mesh in g.get_meshes(5): tess = g.trimesh.permutate.tesselation(mesh) #print(tess.area-mesh.area) self.assertTrue(tess.area - mesh.area < g.tol.merge) self.assertTrue(tess.volume - mesh.volume < g.tol.merge) self.assertTrue(len(mesh.faces) < len(tess.faces)) if mesh.is_winding_consistent: self.assertTrue(tess.is_winding_consistent) if mesh.is_watertight: self.assertTrue(tess.is_watertight)
def test_tesselation(self): for mesh in g.get_meshes(5): tess = g.trimesh.permutate.tesselation(mesh) # print(tess.area-mesh.area) self.assertTrue(abs(tess.area - mesh.area) < g.tol.merge) volume_check = abs(tess.volume - mesh.volume) / mesh.scale self.assertTrue(volume_check < g.tol.merge) self.assertTrue(len(mesh.faces) < len(tess.faces)) if mesh.is_winding_consistent: self.assertTrue(tess.is_winding_consistent) if mesh.is_watertight: self.assertTrue(tess.is_watertight)
def test_tesselation(self): for mesh in g.get_meshes(5): tess = g.trimesh.permutate.tessellation(mesh) # print(tess.area-mesh.area) assert abs(tess.area - mesh.area) < g.tol.merge volume_check = abs(tess.volume - mesh.volume) / mesh.scale assert volume_check < g.tol.merge assert len(mesh.faces) < len(tess.faces) if mesh.is_winding_consistent: assert tess.is_winding_consistent if mesh.is_watertight: assert tess.is_watertight
def test_helper(self): # just make sure the plumbing returns something for mesh in g.get_meshes(2): points = (g.np.random.random((100, 3)) - .5) * 100 a = mesh.nearest.on_surface(points) self.assertTrue(a is not None) b = mesh.nearest.vertex(points) self.assertTrue(b is not None) c = mesh.nearest.contains([mesh.center_mass]) assert len(c) == 1
def test_engine_time(self): for mesh in g.get_meshes(): tic = [g.time.time()] for engine in self.engines: split = mesh.split(engine=engine, only_watertight=False) facets = g.trimesh.graph.facets(mesh=mesh, engine=engine) tic.append(g.time.time()) tic_diff = g.np.diff(tic) tic_min = tic_diff.min() tic_diff /= tic_min g.log.info('graph engine on %s (scale %f sec):\n%s', mesh.metadata['file_name'], tic_min, str(g.np.column_stack((self.engines, tic_diff))))
def test_barycentric(self): for m in g.get_meshes(4): # a simple test which gets the barycentric coordinate at each of the three # vertices, checks to make sure the barycentric is [1,0,0] for the vertex # and then converts back to cartesian and makes sure the original points # are the same as the conversion and back for method in ['cross', 'cramer']: for i in range(3): barycentric = g.trimesh.triangles.points_to_barycentric( m.triangles, m.triangles[:, i], method=method) assert (g.np.abs(barycentric - g.np.roll([1.0, 0, 0], i)) < 1e-8).all() points = g.trimesh.triangles.barycentric_to_points( m.triangles, barycentric) assert (g.np.abs(points - m.triangles[:, i]) < 1e-8).all()
def test_engine_time(self): for mesh in g.get_meshes(): tic = [g.time.time()] for engine in self.engines: mesh.split(engine=engine, only_watertight=False) g.trimesh.graph.facets(mesh=mesh, engine=engine) tic.append(g.time.time()) tic_diff = g.np.diff(tic) tic_min = tic_diff.min() tic_diff /= tic_min g.log.info('graph engine on %s (scale %f sec):\n%s', mesh.metadata['file_name'], tic_min, str(g.np.column_stack((self.engines, tic_diff))))
def test_export(self): file_types = list(g.trimesh.io.export._mesh_exporters.keys()) for mesh in g.get_meshes(5): for file_type in file_types: export = mesh.export(file_type=file_type) if export is None: raise ValueError( 'Exporting mesh %s to %s resulted in None!', mesh.metadata['file_name'], file_type) self.assertTrue(len(export) > 0) if file_type in [ 'dae', # collada, no native importers 'collada', # collada, no native importers 'msgpack', # kind of flaky, but usually works 'drc' ]: # DRC is not a lossless format g.log.warning( 'Still no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) loaded = g.trimesh.load(file_obj=g.io_wrap(export), file_type=file_type) if (not g.trimesh.util.is_shape(loaded._data['faces'], (-1, 3)) or not g.trimesh.util.is_shape(loaded._data['vertices'], (-1, 3)) or loaded.faces.shape != mesh.faces.shape): g.log.error('Export -> inport for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) if loaded.vertices is None: g.log.error( 'Export -> import for %s on %s gave None for vertices!', file_type, mesh.metadata['file_name']) if loaded.faces.shape != mesh.faces.shape: raise ValueError( 'Export -> import for {} on {} gave vertices {}->{}!'. format(file_type, mesh.metadata['file_name'], str(mesh.faces.shape), str(loaded.faces.shape))) self.assertTrue(loaded.vertices.shape == mesh.vertices.shape)
def test_permutate(self): def close(a, b): if len(a) == len(b) == 0: return False if a.shape != b.shape: return False return g.np.allclose(a, b) def make_assertions(mesh, test): if (close(test.face_adjacency, mesh.face_adjacency) and len(mesh.faces) > MIN_FACES): raise ValueError( 'face adjacency of %s the same after permutation!', mesh.metadata['file_name']) if (close(test.face_adjacency_edges, mesh.face_adjacency_edges) and len(mesh.faces) > MIN_FACES): raise ValueError( 'face adjacency edges of %s the same after permutation!', mesh.metadata['file_name']) self.assertFalse(close(test.faces, mesh.faces)) self.assertFalse(close(test.vertices, mesh.vertices)) self.assertFalse(test.md5() == mesh.md5()) for mesh in g.get_meshes(): if len(mesh.faces) < MIN_FACES: continue # warp the mesh to be a unit cube mesh.vertices /= mesh.extents original = mesh.copy() for i in range(5): mesh = original.copy() noise = g.trimesh.permutate.noise(mesh, magnitude=mesh.scale / 50.0) transform = g.trimesh.permutate.transform(mesh) tesselate = g.trimesh.permutate.tesselation(mesh) make_assertions(mesh, noise) make_assertions(mesh, transform) make_assertions(mesh, tesselate) # make sure permutate didn't alter the original mesh self.assertTrue(original.md5() == mesh.md5())
def test_identifier(self): count = 100 for mesh in g.get_meshes(5): if not mesh.is_watertight: g.log.warning('Mesh %s is not watertight!', mesh.metadata['file_name']) continue self.assertTrue(mesh.is_watertight) g.log.info('Trying hash at %d random transforms', count) result = g.deque() for i in range(count): permutated = mesh.permutate.transform() result.append(permutated.identifier) ok = (g.np.abs(g.np.diff(result, axis=0)) < 1e-3).all() if not ok: g.log.error('Hashes on %s differ after transform! diffs:\n %s\n', mesh.metadata['file_name'], str(g.np.diff(result, axis=0))) self.assertTrue(ok)
def test_identifier(self): count = 100 for mesh in g.get_meshes(5): if not mesh.is_watertight: g.log.warning('Mesh %s is not watertight!', mesh.metadata['file_name']) continue self.assertTrue(mesh.is_watertight) g.log.info('Trying hash at %d random transforms', count) result = g.deque() for i in range(count): permutated = mesh.permutate.transform() result.append(permutated.identifier) ok = (g.np.abs(g.np.diff(result, axis=0)) < 1e-3).all() if not ok: g.log.error( 'Hashes on %s differ after transform! diffs:\n %s\n', mesh.metadata['file_name'], str(g.np.diff(result, axis=0))) self.assertTrue(ok)
def test_identifier(self): count = 25 meshes = g.np.append(list(g.get_meshes(10)), g.get_mesh('fixed_top.ply')) for mesh in meshes: if not mesh.is_volume: g.log.warning('Mesh %s is not watertight!', mesh.metadata['file_name']) continue g.log.info('Trying hash at %d random transforms', count) md5 = g.deque() idf = g.deque() for i in range(count): permutated = mesh.permutate.transform() permutated = permutated.permutate.tessellation() md5.append(permutated.identifier_md5) idf.append(permutated.identifier) result = g.np.array(md5) ok = (result[0] == result[1:]).all() if not ok: debug = [] for a in idf: as_int, exp = g.trimesh.util.sigfig_int( a, g.trimesh.comparison.id_sigfig) debug.append(as_int * (10**exp)) g.log.error( 'Hashes on %s differ after transform! diffs:\n %s\n', mesh.metadata['file_name'], str(g.np.array(debug, dtype=g.np.int))) raise ValueError('values differ after transform!') if md5[-1] == permutated.permutate.noise(mesh.scale / 100.0).identifier_md5: raise ValueError('Hashes on %s didn\'t change after noise!', mesh.metadata['file_name'])
def test_copy(self): for mesh in g.get_meshes(raise_error=True): if not isinstance(mesh, g.trimesh.Trimesh): continue start = {mesh.md5(), mesh.crc()} # make sure some stuff is populated mesh.kdtree mesh.triangles_tree mesh.face_adjacency_angles mesh.facets assert 'triangles_tree' in mesh._cache assert len(mesh._cache) > 0 # if you cache c-objects then deepcopy the mesh # it randomly segfaults copy_count = 200 for i in range(copy_count): copied = mesh.copy(include_cache=False) assert len(copied._cache) == 0 assert len(mesh._cache) > 0 # deepcopy should clear the cache copied = g.copy.deepcopy(mesh) assert len(copied._cache) == 0 assert len(mesh._cache) > 0 # regular copy should try to preserve the cache copied = g.copy.copy(mesh) assert len(copied._cache) == len(mesh._cache) # the triangles_tree should be the SAME OBJECT assert id(copied.triangles_tree) == id(mesh.triangles_tree) # cache should be same data in different object assert id(copied._cache.cache) != id(mesh._cache.cache) assert id(copied._cache) != id(mesh._cache) # identifier shouldn't change assert g.np.allclose(copied.identifier, mesh.identifier) # ...still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()}
def test_export(self): file_types = list(g.trimesh.io.export._mesh_exporters.keys()) for mesh in g.get_meshes(5): for file_type in file_types: export = mesh.export(file_type=file_type) if export is None: raise ValueError( 'Exporting mesh %s to %s resulted in None!', mesh.metadata['file_name'], file_type) self.assertTrue(len(export) > 0) # we don't have native loaders implemented for collada yet if file_type in ['dae', 'collada']: g.log.warning( 'Still no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) loaded = g.trimesh.load(file_obj=g.io_wrap(export), file_type=file_type) if (not g.trimesh.util.is_shape(loaded._data['faces'], (-1, 3)) or not g.trimesh.util.is_shape(loaded._data['vertices'], (-1, 3)) or loaded.faces.shape != mesh.faces.shape): g.log.error('Export -> inport for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) if loaded.vertices is None: log.error( 'Export -> import for %s on %s gave None for vertices!', file_type, mesh.metadata['file_name']) self.assertTrue(loaded.faces.shape == mesh.faces.shape) self.assertTrue(loaded.vertices.shape == mesh.vertices.shape) g.log.info( 'Mesh vertices/faces consistent after export->import')
def test_identifier(self): count = 25 meshes = g.np.append(list(g.get_meshes(10)), g.get_mesh('fixed_top.ply')) for mesh in meshes: if not mesh.is_volume: g.log.warning('Mesh %s is not watertight!', mesh.metadata['file_name']) continue g.log.info('Trying hash at %d random transforms', count) md5 = g.deque() idf = g.deque() for i in range(count): permutated = mesh.permutate.transform() permutated = permutated.permutate.tessellation() md5.append(permutated.identifier_md5) idf.append(permutated.identifier) result = g.np.array(md5) ok = (result[0] == result[1:]).all() if not ok: debug = [] for a in idf: as_int, exp = g.trimesh.util.sigfig_int( a, g.trimesh.comparison.id_sigfig) debug.append(as_int * (10**exp)) g.log.error('Hashes on %s differ after transform! diffs:\n %s\n', mesh.metadata['file_name'], str(g.np.array(debug, dtype=g.np.int))) raise ValueError('values differ after transform!') if md5[-1] == permutated.permutate.noise( mesh.scale / 100.0).identifier_md5: raise ValueError('Hashes on %s didn\'t change after noise!', mesh.metadata['file_name'])
def test_identifier(self): count = 25 for mesh in g.get_meshes(10): if not (mesh.is_watertight and mesh.is_winding_consistent): g.log.warning('Mesh %s is not watertight!', mesh.metadata['file_name']) continue self.assertTrue(mesh.is_watertight) g.log.info('Trying hash at %d random transforms', count) md5 = g.deque() idf = g.deque() for i in range(count): permutated = mesh.permutate.transform() permutated = permutated.permutate.tesselation() md5.append(permutated.identifier_md5) idf.append(permutated.identifier) result = g.np.array(md5) ok = (result[0] == result[1:]).all() if not ok: debug = [] for a in idf: as_int, exp = g.trimesh.util.sigfig_int( a, g.trimesh.comparison.identifier_sigfig) debug.append(as_int * (10**exp)) g.log.error( 'Hashes on %s differ after transform! diffs:\n %s\n', mesh.metadata['file_name'], str(g.np.array(debug, dtype=g.np.int))) self.assertTrue(False) if md5[-1] == permutated.permutate.noise(mesh.scale / 100.0).identifier_md5: g.log.error('Hashes on %s didn\'t change after noise!', mesh.metadata['file_name']) self.assertTrue(False)
def typical_application(): # make sure we can load everything we think we can # while getting a list of meshes to run tests on meshes = g.get_meshes(raise_error=True) g.log.info('Running tests on %d meshes', len(meshes)) for mesh in meshes: g.log.info('Testing %s', mesh.metadata['file_name']) assert len(mesh.faces) > 0 assert len(mesh.vertices) > 0 assert len(mesh.edges) > 0 assert len(mesh.edges_unique) > 0 assert len(mesh.edges_sorted) > 0 assert len(mesh.edges_face) > 0 assert isinstance(mesh.euler_number, int) if not mesh.is_volume: continue assert len(mesh.facets) == len(mesh.facets_area) if len(mesh.facets) == 0: continue faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) smoothed = mesh.smoothed() assert mesh.volume > 0.0 section = mesh.section(plane_normal=[0, 0, 1], plane_origin=mesh.centroid) sample = mesh.sample(1000) assert sample.shape == (1000, 3) ident = mesh.identifier_md5 assert len(ident) > 0
def typical_application(): # make sure we can load everything we think we can # while getting a list of meshes to run tests on meshes = g.get_meshes(raise_error=True) for mesh in meshes: g.log.info('Testing %s', mesh.metadata['file_name']) assert len(mesh.faces) > 0 assert len(mesh.vertices) > 0 assert len(mesh.edges) > 0 assert len(mesh.edges_unique) > 0 assert len(mesh.edges_sorted) > 0 assert len(mesh.edges_face) > 0 assert isinstance(mesh.euler_number, int) if not mesh.is_volume: continue assert len(mesh.facets) == len(mesh.facets_area) if len(mesh.facets) == 0: continue faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) # NOQA smoothed = mesh.smoothed() # NOQA assert mesh.volume > 0.0 section = mesh.section(plane_normal=[0, 0, 1], # NOQA plane_origin=mesh.centroid) sample = mesh.sample(1000) assert sample.shape == (1000, 3) ident = mesh.identifier_md5 assert len(ident) > 0
def test_minball(self): # get some assorted mesh geometries to test general performance # and a perfect sphere mesh to test the degenerate case for m in g.np.append(g.get_meshes(5), g.trimesh.primitives.Sphere()): s = m.bounding_sphere R_check = ((m.vertices - s.primitive.center)**2).sum(axis=1).max()**.5 self.assertTrue(len(s.primitive.center) == 3) self.assertTrue(s.primitive.radius > 0.0) self.assertTrue(abs(s.primitive.radius - R_check) < g.tol.fit) self.assertTrue(s.volume > (m.volume - g.tol.fit)) # check minimum n-sphere for sets of points in 2,3, and 4 dimensions for d in [2, 3, 4]: for i in range(5): points = g.np.random.random((100, d)) C, R = g.trimesh.nsphere.minimum_nsphere(points) R_check = ((points - C)**2).sum(axis=1).max()**.5 self.assertTrue(len(C) == d) self.assertTrue(R > 0.0) self.assertTrue(abs(R - R_check) < g.tol.merge)
def test_export(self): file_types = list(g.trimesh.io.export._mesh_exporters.keys()) for mesh in g.get_meshes(3): for file_type in file_types: export = mesh.export(file_type = file_type) self.assertTrue(len(export) > 0) # we don't have native loaders implemented for collada yet if file_type in ['dae', 'collada']: g.log.warning('Still no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) loaded = g.trimesh.load(file_obj = g.io_wrap(export), file_type = file_type) if loaded.faces.shape != mesh.faces.shape: g.log.error('Export -> inport for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) self.assertTrue(loaded.faces.shape == mesh.faces.shape) self.assertTrue(loaded.vertices.shape == mesh.vertices.shape) g.log.info('Mesh vertices/faces consistent after export->import')
def test_permutate(self): def make_assertions(mesh, test): self.assertFalse(g.np.allclose(test.faces, mesh.faces)) self.assertFalse(g.np.allclose(test.face_adjacency, mesh.face_adjacency)) self.assertFalse(g.np.allclose(test.face_adjacency_edges, mesh.face_adjacency_edges)) self.assertFalse(g.np.allclose(test.extents, mesh.extents)) self.assertFalse(test.md5() == mesh.md5()) for mesh in g.get_meshes(5): original = mesh.copy() noise = g.trimesh.permutate.noise(mesh) noise_1 = g.trimesh.permutate.noise(mesh, magnitude=mesh.scale/10.0) transform = g.trimesh.permutate.transform(mesh) make_assertions(mesh, noise) make_assertions(mesh, noise_1) make_assertions(mesh, transform) # make sure permutate didn't alter the original mesh self.assertTrue(original.md5() == mesh.md5())
def test_permutate(self): def make_assertions(mesh, test): self.assertFalse(g.np.allclose(test.faces, mesh.faces)) self.assertFalse( g.np.allclose(test.face_adjacency, mesh.face_adjacency)) self.assertFalse( g.np.allclose(test.face_adjacency_edges, mesh.face_adjacency_edges)) self.assertFalse(g.np.allclose(test.extents, mesh.extents)) self.assertFalse(test.md5() == mesh.md5()) for mesh in g.get_meshes(5): original = mesh.copy() noise = g.trimesh.permutate.noise(mesh) noise_1 = g.trimesh.permutate.noise(mesh, magnitude=mesh.scale / 50.0) transform = g.trimesh.permutate.transform(mesh) make_assertions(mesh, noise) make_assertions(mesh, noise_1) make_assertions(mesh, transform) # make sure permutate didn't alter the original mesh self.assertTrue(original.md5() == mesh.md5())
def test_export(self): file_types = list(g.trimesh.io.export._mesh_exporters.keys()) for mesh in g.get_meshes(5): for file_type in file_types: export = mesh.export(file_type = file_type) self.assertTrue(len(export) > 0) # we don't have native loaders implemented for collada yet if file_type in ['dae', 'collada']: g.log.warning('Still no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) loaded = g.trimesh.load(file_obj = g.io_wrap(export), file_type = file_type) if loaded.faces.shape != mesh.faces.shape: g.log.error('Export -> inport for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) self.assertTrue(loaded.faces.shape == mesh.faces.shape) self.assertTrue(loaded.vertices.shape == mesh.vertices.shape) g.log.info('Mesh vertices/faces consistent after export->import')
def test_meshes(self): self.meshes = g.get_meshes() has_gt = g.trimesh.graph._has_gt g.trimesh.graph._has_gt = False if not has_gt: g.log.warning('No graph-tool to test!') g.log.info('Running tests on %d meshes', len(self.meshes)) for mesh in self.meshes: g.log.info('Testing %s', mesh.metadata['file_name']) self.assertTrue(len(mesh.faces) > 0) self.assertTrue(len(mesh.vertices) > 0) self.assertTrue(len(mesh.edges) > 0) self.assertTrue(len(mesh.edges_unique) > 0) self.assertTrue(len(mesh.edges_sorted) > 0) self.assertTrue(len(mesh.edges_face) > 0) self.assertFalse(mesh.euler_number is None) mesh.process() tic = [g.time.time()] if has_gt: g.trimesh.graph._has_gt = True split = g.trimesh.graph.split(mesh) tic.append(g.time.time()) facets = g.trimesh.graph.facets(mesh) tic.append(g.time.time()) g.trimesh.graph._has_gt = False split = g.trimesh.graph.split(mesh) tic.append(g.time.time()) facets = g.trimesh.graph.facets(mesh) tic.append(g.time.time()) facets, area = mesh.facets(return_area=True) self.assertTrue(len(facets) == len(area)) if len(facets) == 0: continue faces = facets[g.np.argmax(area)] outline = mesh.outline(faces) smoothed = mesh.smoothed() if has_gt: times = g.np.diff(tic) g.log.info('Graph-tool sped up split by %f and facets by %f', (times[2] / times[0]), (times[3] / times[1])) self.assertTrue(mesh.volume > 0.0) section = mesh.section(plane_normal=[0,0,1], plane_origin=mesh.centroid) hull = mesh.convex_hull volume_ok = hull.volume > 0.0 if not volume_ok: g.log.error('zero hull volume for %s', mesh.metadata['file_name']) self.assertTrue(volume_ok) sample = mesh.sample(1000) even_sample = g.trimesh.sample.sample_surface_even(mesh, 100) self.assertTrue(sample.shape == (1000,3)) g.log.info('finished testing meshes')
def test_fix_normals(self): for mesh in g.get_meshes(5): mesh.fix_normals()
def test_meshes(self): # make sure we can load everything we think we can formats = g.trimesh.available_formats() assert all(isinstance(i, str) for i in formats) assert all(len(i) > 0 for i in formats) assert all(i in formats for i in ['stl', 'ply', 'off', 'obj']) for mesh in g.get_meshes(raise_error=True): # log file name for debugging file_name = mesh.metadata['file_name'] g.log.info('Testing %s', file_name) start = {mesh.md5(), mesh.crc()} assert len(mesh.faces) > 0 assert len(mesh.vertices) > 0 assert len(mesh.edges) > 0 assert len(mesh.edges_unique) > 0 assert len(mesh.edges_sorted) > 0 assert len(mesh.edges_face) > 0 assert isinstance(mesh.euler_number, int) # check bounding primitives assert mesh.bounding_box.volume > 0.0 assert mesh.bounding_primitive.volume > 0.0 # none of these should have mutated anything assert start == {mesh.md5(), mesh.crc()} # run processing, again mesh.process() # still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()} if not (mesh.is_watertight and mesh.is_winding_consistent): continue assert len(mesh.facets) == len(mesh.facets_area) assert len(mesh.facets) == len(mesh.facets_normal) assert len(mesh.facets) == len(mesh.facets_boundary) if len(mesh.facets) != 0: faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) # check to make sure we can generate closed paths # on a Path3D object test = outline.paths smoothed = mesh.smoothed() assert mesh.volume > 0.0 section = mesh.section(plane_normal=[0, 0, 1], plane_origin=mesh.centroid) sample = mesh.sample(1000) even_sample = g.trimesh.sample.sample_surface_even(mesh, 100) assert sample.shape == (1000, 3) g.log.info('finished testing meshes') # make sure vertex kdtree and triangles rtree exist t = mesh.kdtree assert hasattr(t, 'query') g.log.info('Creating triangles tree') r = mesh.triangles_tree assert hasattr(r, 'intersection') g.log.info('Triangles tree ok') # face angles should have same assert mesh.face_angles.shape == mesh.faces.shape assert len(mesh.vertices) == len(mesh.vertex_defects) assert len(mesh.principal_inertia_components) == 3 # we should have built up a bunch of stuff into # our cache, so make sure all numpy arrays cached are # finite for name, cached in mesh._cache.cache.items(): # only check numpy arrays if not isinstance(cached, g.np.ndarray): continue # only check int, float, and bool if cached.dtype.kind not in 'ibf': continue # there should never be NaN values if g.np.isnan(cached).any(): raise ValueError('NaN values in %s/%s', file_name, name) # fields allowed to have infinite values if name in ['face_adjacency_radius']: continue # make sure everything is finite if not g.np.isfinite(cached).all(): raise ValueError('inf values in %s/%s', file_name, name) # some memory issues only show up when you copy the mesh a bunch # specifically, if you cache c- objects then deepcopy the mesh this # generally segfaults randomly copy_count = 200 g.log.info('Attempting to copy mesh %d times', copy_count) for i in range(copy_count): copied = mesh.copy() assert copied.is_empty == mesh.is_empty #t = copied.triangles_tree c = copied.kdtree copied.apply_transform( g.trimesh.transformations.rotation_matrix( g.np.degrees(i), [0, 1, 1])) g.log.info('Multiple copies done') if not g.np.allclose(copied.identifier, mesh.identifier): raise ValueError('copied identifier changed!') # ...still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()}
def test_export(self): export_types = list( g.trimesh.exchange.export._mesh_exporters.keys()) meshes = list(g.get_meshes(8)) # make sure we've got something with texture meshes.append(g.get_mesh('fuze.obj')) for mesh in meshes: # disregard texture mesh.merge_vertices(textured=False) for file_type in export_types: export = mesh.export(file_type=file_type) if export is None: raise ValueError('Exporting mesh %s to %s resulted in None!', mesh.metadata['file_name'], file_type) assert len(export) > 0 if file_type in [ 'dae', # collada, no native importers 'collada', # collada, no native importers 'msgpack', # kind of flaky, but usually works 'drc']: # DRC is not a lossless format g.log.warning( 'no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) # if export is string or bytes wrap as pseudo file object if isinstance(export, str) or isinstance(export, bytes): file_obj = g.io_wrap(export) else: file_obj = export loaded = g.trimesh.load(file_obj=file_obj, file_type=file_type) # if we exported as GLTF/dae it will come back as a Scene if isinstance(loaded, g.trimesh.Scene) and isinstance( mesh, g.trimesh.Trimesh): assert len(loaded.geometry) == 1 loaded = next(iter(loaded.geometry.values())) if (not g.trimesh.util.is_shape(loaded._data['faces'], (-1, 3)) or not g.trimesh.util.is_shape(loaded._data['vertices'], (-1, 3)) or loaded.faces.shape != mesh.faces.shape): g.log.error('Export -> import for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) if loaded.vertices is None: g.log.error('Export -> import for %s on %s gave None for vertices!', file_type, mesh.metadata['file_name']) if loaded.faces.shape != mesh.faces.shape: raise ValueError('export cycle {} on {} gave faces {}->{}!'.format( file_type, mesh.metadata['file_name'], str(mesh.faces.shape), str(loaded.faces.shape))) if loaded.vertices.shape != mesh.vertices.shape: raise ValueError('export cycle {} on {} gave vertices {}->{}!'.format( file_type, mesh.metadata['file_name'], mesh.vertices.shape, loaded.vertices.shape)) # try exporting/importing certain file types by name if file_type in ['obj', 'stl', 'ply', 'off']: temp = g.tempfile.NamedTemporaryFile(suffix='.' + file_type, delete=False) # windows throws permissions errors if you keep it open temp.close() mesh.export(temp.name) load = g.trimesh.load(temp.name) # manual cleanup g.os.remove(temp.name) assert mesh.faces.shape == load.faces.shape assert mesh.vertices.shape == load.vertices.shape # if we're not on linux don't run meshlab tests if not g.is_linux: continue # formats exportable by trimesh and importable by meshlab # make sure things we export can be loaded by meshlab both = set(g.meshlab_formats).intersection( set(export_types)) # additional options to pass to exporters to try to ferret # out combinations which lead to invalid output kwargs = {'ply': [{'vertex_normal': True, 'encoding': 'ascii'}, {'vertex_normal': True, 'encoding': 'binary'}, {'vertex_normal': False, 'encoding': 'ascii'}, {'vertex_normal': False, 'encoding': 'binary'}], 'stl': [{'file_type': 'stl'}, {'file_type': 'stl_ascii'}]} # make sure input mesh has garbage removed mesh._validate = True # since we're going to be looking for exact export # counts remove anything small/degenerate again mesh.process() # run through file types supported by both meshlab and trimesh for file_type in both: # pull different exporter options for the format if file_type in kwargs: options = kwargs[file_type] else: options = [{}] # try each combination of options for option in options: temp = g.tempfile.NamedTemporaryFile( suffix='.' + file_type, delete=False) temp_off = g.tempfile.NamedTemporaryFile( suffix='.off', delete=False) # windows throws permissions errors if you keep it open temp.close() temp_off.close() # write over the tempfile option['file_obj'] = temp.name mesh.export(**option) # will raise CalledProcessError if meshlab # can't successfully import the file try: # have meshlab take the export and convert it into # an OFF file, which is basically the simplest format # that uses by- reference vertices # meshlabserver requires X so wrap it with XVFB cmd = 'xvfb-run -a -s "-screen 0 800x600x24" meshlabserver ' cmd += '-i {} -o {}'.format(temp.name, temp_off.name) g.subprocess.check_call(cmd, shell=True) except g.subprocess.CalledProcessError as E: # log the options that produced the failure g.log.error('failed to export {}'.format( option)) # raise the error again raise E # load meshlabs export back into trimesh r = g.trimesh.load(temp_off.name) # we should have the same number of vertices and faces assert len(r.vertices) == len(mesh.vertices) assert len(r.faces) == len(mesh.faces) # manual cleanup g.os.remove(temp.name) g.os.remove(temp_off.name)
def test_export(self): export_types = list(g.trimesh.exchange.export._mesh_exporters.keys()) meshes = list(g.get_meshes(8)) # make sure we've got something with texture meshes.append(g.get_mesh('fuze.obj')) for mesh in meshes: # disregard texture mesh.merge_vertices(textured=False) for file_type in export_types: export = mesh.export(file_type=file_type) if export is None: raise ValueError( 'Exporting mesh %s to %s resulted in None!', mesh.metadata['file_name'], file_type) assert len(export) > 0 if file_type in [ 'dae', # collada, no native importers 'collada', # collada, no native importers 'msgpack', # kind of flaky, but usually works 'drc' ]: # DRC is not a lossless format g.log.warning('no native loaders implemented for collada!') continue g.log.info('Export/import testing on %s', mesh.metadata['file_name']) # if export is string or bytes wrap as pseudo file object if isinstance(export, str) or isinstance(export, bytes): file_obj = g.io_wrap(export) else: file_obj = export loaded = g.trimesh.load(file_obj=file_obj, file_type=file_type) # if we exported as GLTF/dae it will come back as a Scene if isinstance(loaded, g.trimesh.Scene) and isinstance( mesh, g.trimesh.Trimesh): assert len(loaded.geometry) == 1 loaded = next(iter(loaded.geometry.values())) if (not g.trimesh.util.is_shape(loaded._data['faces'], (-1, 3)) or not g.trimesh.util.is_shape(loaded._data['vertices'], (-1, 3)) or loaded.faces.shape != mesh.faces.shape): g.log.error('Export -> import for %s on %s wrong shape!', file_type, mesh.metadata['file_name']) if loaded.vertices is None: g.log.error( 'Export -> import for %s on %s gave None for vertices!', file_type, mesh.metadata['file_name']) if loaded.faces.shape != mesh.faces.shape: raise ValueError( 'export cycle {} on {} gave faces {}->{}!'.format( file_type, mesh.metadata['file_name'], str(mesh.faces.shape), str(loaded.faces.shape))) if loaded.vertices.shape != mesh.vertices.shape: raise ValueError( 'export cycle {} on {} gave vertices {}->{}!'.format( file_type, mesh.metadata['file_name'], mesh.vertices.shape, loaded.vertices.shape)) # try exporting/importing certain file types by name if file_type in ['obj', 'stl', 'ply', 'off']: temp = g.tempfile.NamedTemporaryFile(suffix='.' + file_type, delete=False) # windows throws permissions errors if you keep it open temp.close() mesh.export(temp.name) load = g.trimesh.load(temp.name) # manual cleanup g.os.remove(temp.name) assert mesh.faces.shape == load.faces.shape assert mesh.vertices.shape == load.vertices.shape # if we're not on linux don't run meshlab tests if not g.is_linux: continue # formats exportable by trimesh and importable by meshlab # make sure things we export can be loaded by meshlab both = set(g.meshlab_formats).intersection(set(export_types)) # additional options to pass to exporters to try to ferret # out combinations which lead to invalid output kwargs = { 'ply': [{ 'vertex_normal': True, 'encoding': 'ascii' }, { 'vertex_normal': True, 'encoding': 'binary' }, { 'vertex_normal': False, 'encoding': 'ascii' }, { 'vertex_normal': False, 'encoding': 'binary' }], 'stl': [{ 'file_type': 'stl' }, { 'file_type': 'stl_ascii' }] } # make sure input mesh has garbage removed mesh._validate = True # since we're going to be looking for exact export # counts remove anything small/degenerate again mesh.process() # run through file types supported by both meshlab and trimesh for file_type in both: # pull different exporter options for the format if file_type in kwargs: options = kwargs[file_type] else: options = [{}] # try each combination of options for option in options: temp = g.tempfile.NamedTemporaryFile(suffix='.' + file_type, delete=False) temp_off = g.tempfile.NamedTemporaryFile(suffix='.off', delete=False) # windows throws permissions errors if you keep it open temp.close() temp_off.close() # write over the tempfile option['file_obj'] = temp.name mesh.export(**option) # will raise CalledProcessError if meshlab # can't successfully import the file try: # have meshlab take the export and convert it into # an OFF file, which is basically the simplest format # that uses by- reference vertices # meshlabserver requires X so wrap it with XVFB cmd = 'xvfb-run -a -s "-screen 0 800x600x24" meshlabserver ' cmd += '-i {} -o {}'.format(temp.name, temp_off.name) g.subprocess.check_call(cmd, shell=True) except g.subprocess.CalledProcessError as E: # log the options that produced the failure g.log.error('failed to export {}'.format(option)) # raise the error again raise E # load meshlabs export back into trimesh r = g.trimesh.load(temp_off.name) # we should have the same number of vertices and faces assert len(r.vertices) == len(mesh.vertices) assert len(r.faces) == len(mesh.faces) # manual cleanup g.os.remove(temp.name) g.os.remove(temp_off.name)
def test_meshes(self): # make sure we can load everything we think we can formats = g.trimesh.available_formats() assert all(isinstance(i, str) for i in formats) assert all(len(i) > 0 for i in formats) assert all(i in formats for i in ['stl', 'ply', 'off', 'obj']) for mesh in g.get_meshes(raise_error=True): # log file name for debugging file_name = mesh.metadata['file_name'] # ply files can return PointCloud objects if file_name.startswith('points_'): continue g.log.info('Testing %s', file_name) start = {mesh.md5(), mesh.crc()} assert len(mesh.faces) > 0 assert len(mesh.vertices) > 0 # make sure vertex normals match vertices and are valid assert mesh.vertex_normals.shape == mesh.vertices.shape assert g.np.isfinite(mesh.vertex_normals).all() # should be one per vertex assert len(mesh.vertex_faces) == len(mesh.vertices) # check some edge properties assert len(mesh.edges) > 0 assert len(mesh.edges_unique) > 0 assert len(mesh.edges_sorted) == len(mesh.edges) assert len(mesh.edges_face) == len(mesh.edges) # check edges_unique assert len(mesh.edges) == len(mesh.edges_unique_inverse) assert g.np.allclose(mesh.edges_sorted, mesh.edges_unique[mesh.edges_unique_inverse]) assert len(mesh.edges_unique) == len(mesh.edges_unique_length) # euler number should be an integer assert isinstance(mesh.euler_number, int) # check bounding primitives assert mesh.bounding_box.volume > 0.0 assert mesh.bounding_primitive.volume > 0.0 # none of these should have mutated anything assert start == {mesh.md5(), mesh.crc()} # run processing, again mesh.process() # still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()} if not (mesh.is_watertight and mesh.is_winding_consistent): continue assert len(mesh.facets) == len(mesh.facets_area) assert len(mesh.facets) == len(mesh.facets_normal) assert len(mesh.facets) == len(mesh.facets_boundary) if len(mesh.facets) != 0: faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) # check to make sure we can generate closed paths # on a Path3D object test = outline.paths # NOQA smoothed = mesh.smoothed() # NOQA assert mesh.volume > 0.0 section = mesh.section( plane_normal=[0, 0, 1], # NOQA plane_origin=mesh.centroid) sample = mesh.sample(1000) even_sample = g.trimesh.sample.sample_surface_even(mesh, 100) # NOQA assert sample.shape == (1000, 3) g.log.info('finished testing meshes') # make sure vertex kdtree and triangles rtree exist t = mesh.kdtree assert hasattr(t, 'query') g.log.info('Creating triangles tree') r = mesh.triangles_tree assert hasattr(r, 'intersection') g.log.info('Triangles tree ok') # face angles should have same assert mesh.face_angles.shape == mesh.faces.shape assert len(mesh.vertices) == len(mesh.vertex_defects) assert len(mesh.principal_inertia_components) == 3 # collect list of cached properties that are writeable writeable = [] # we should have built up a bunch of stuff into # our cache, so make sure all numpy arrays cached # are read-only and not crazy for name, cached in mesh._cache.cache.items(): # only check numpy arrays if not isinstance(cached, g.np.ndarray): continue # nothing in the cache should be writeable if cached.flags['WRITEABLE']: raise ValueError('{} is writeable!'.format(name)) # only check int, float, and bool if cached.dtype.kind not in 'ibf': continue # there should never be NaN values if g.np.isnan(cached).any(): raise ValueError('NaN values in %s/%s', file_name, name) # fields allowed to have infinite values if name in ['face_adjacency_radius']: continue # make sure everything is finite if not g.np.isfinite(cached).all(): raise ValueError('inf values in %s/%s', file_name, name) # ...still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()} # log the names of properties we need to make read-only if len(writeable) > 0: # TODO : all cached values should be read-only g.log.error('cached properties writeable: {}'.format( ', '.join(writeable)))
def test_meshes(self): # make sure we can load everything we think we can formats = g.trimesh.available_formats() assert all(isinstance(i, str) for i in formats) assert all(len(i) > 0 for i in formats) assert all(i in formats for i in ['stl', 'ply', 'off', 'obj']) for mesh in g.get_meshes(raise_error=True): g.log.info('Testing %s', mesh.metadata['file_name']) start = {mesh.md5(), mesh.crc()} assert len(mesh.faces) > 0 assert len(mesh.vertices) > 0 assert len(mesh.edges) > 0 assert len(mesh.edges_unique) > 0 assert len(mesh.edges_sorted) > 0 assert len(mesh.edges_face) > 0 assert isinstance(mesh.euler_number, int) # check bounding primitives assert mesh.bounding_box.volume > 0.0 assert mesh.bounding_primitive.volume > 0.0 # none of these should have mutated anything assert start == {mesh.md5(), mesh.crc()} # run processing, again mesh.process() # still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()} if not (mesh.is_watertight and mesh.is_winding_consistent): continue assert len(mesh.facets) == len(mesh.facets_area) assert len(mesh.facets) == len(mesh.facets_normal) assert len(mesh.facets) == len(mesh.facets_boundary) if len(mesh.facets) != 0: faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) # check to make sure we can generate closed paths # on a Path3D object test = outline.paths smoothed = mesh.smoothed() assert mesh.volume > 0.0 section = mesh.section(plane_normal=[0, 0, 1], plane_origin=mesh.centroid) sample = mesh.sample(1000) even_sample = g.trimesh.sample.sample_surface_even(mesh, 100) assert sample.shape == (1000, 3) g.log.info('finished testing meshes') # make sure vertex kdtree and triangles rtree exist t = mesh.kdtree assert hasattr(t, 'query') g.log.info('Creating triangles tree') r = mesh.triangles_tree assert hasattr(r, 'intersection') g.log.info('Triangles tree ok') # some memory issues only show up when you copy the mesh a bunch # specifically, if you cache c- objects then deepcopy the mesh this # generally segfaults randomly copy_count = 200 g.log.info('Attempting to copy mesh %d times', copy_count) for i in range(copy_count): copied = mesh.copy() assert copied.is_empty == mesh.is_empty #t = copied.triangles_tree c = copied.kdtree copied.apply_transform( g.trimesh.transformations.rotation_matrix( g.np.degrees(i), [0, 1, 1])) g.log.info('Multiple copies done') if not g.np.allclose(copied.identifier, mesh.identifier): raise ValueError('copied identifier changed!') # ...still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()}
def setUp(self): meshes = [g.get_mesh(i) for i in ['large_block.STL', 'featuretype.STL']] self.meshes = g.np.append(meshes, list(g.get_meshes(5)))
def test_projections(self): for m in g.get_meshes(4): assert (len(m.face_adjacency_projections) == (len( m.face_adjacency)))
def setUp(self): meshes = [g.get_mesh(i) for i in ['large_block.STL', 'featuretype.STL']] self.meshes = g.np.append(meshes, g.get_meshes(5))
def test_projections(self): # check the vertex projection onto adjacent face plane # this is used to calculate convexity for m in g.get_meshes(4): assert (len(m.face_adjacency_projections) == (len(m.face_adjacency)))
def test_meshes(self): # make sure we can load everything we think we can formats = g.trimesh.available_formats() assert all(isinstance(i, str) for i in formats) assert all(len(i) > 0 for i in formats) assert all(i in formats for i in ['stl', 'ply', 'off', 'obj']) for mesh in g.get_meshes(raise_error=True): # log file name for debugging file_name = mesh.metadata['file_name'] g.log.info('Testing %s', file_name) start = {mesh.md5(), mesh.crc()} assert len(mesh.faces) > 0 assert len(mesh.vertices) > 0 assert len(mesh.edges) > 0 assert len(mesh.edges_unique) > 0 assert len(mesh.edges_sorted) > 0 assert len(mesh.edges_face) > 0 assert isinstance(mesh.euler_number, int) # check bounding primitives assert mesh.bounding_box.volume > 0.0 assert mesh.bounding_primitive.volume > 0.0 # none of these should have mutated anything assert start == {mesh.md5(), mesh.crc()} # run processing, again mesh.process() # still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()} if not (mesh.is_watertight and mesh.is_winding_consistent): continue assert len(mesh.facets) == len(mesh.facets_area) assert len(mesh.facets) == len(mesh.facets_normal) assert len(mesh.facets) == len(mesh.facets_boundary) if len(mesh.facets) != 0: faces = mesh.facets[mesh.facets_area.argmax()] outline = mesh.outline(faces) # check to make sure we can generate closed paths # on a Path3D object test = outline.paths # NOQA smoothed = mesh.smoothed() # NOQA assert mesh.volume > 0.0 section = mesh.section(plane_normal=[0, 0, 1], # NOQA plane_origin=mesh.centroid) sample = mesh.sample(1000) even_sample = g.trimesh.sample.sample_surface_even(mesh, 100) # NOQA assert sample.shape == (1000, 3) g.log.info('finished testing meshes') # make sure vertex kdtree and triangles rtree exist t = mesh.kdtree assert hasattr(t, 'query') g.log.info('Creating triangles tree') r = mesh.triangles_tree assert hasattr(r, 'intersection') g.log.info('Triangles tree ok') # face angles should have same assert mesh.face_angles.shape == mesh.faces.shape assert len(mesh.vertices) == len(mesh.vertex_defects) assert len(mesh.principal_inertia_components) == 3 # we should have built up a bunch of stuff into # our cache, so make sure all numpy arrays cached are # finite for name, cached in mesh._cache.cache.items(): # only check numpy arrays if not isinstance(cached, g.np.ndarray): continue # only check int, float, and bool if cached.dtype.kind not in 'ibf': continue # there should never be NaN values if g.np.isnan(cached).any(): raise ValueError('NaN values in %s/%s', file_name, name) # fields allowed to have infinite values if name in ['face_adjacency_radius']: continue # make sure everything is finite if not g.np.isfinite(cached).all(): raise ValueError('inf values in %s/%s', file_name, name) # some memory issues only show up when you copy the mesh a bunch # specifically, if you cache c- objects then deepcopy the mesh this # generally segfaults randomly copy_count = 200 g.log.info('Attempting to copy mesh %d times', copy_count) for i in range(copy_count): copied = mesh.copy() assert copied.is_empty == mesh.is_empty # t = copied.triangles_tree c = copied.kdtree # NOQA copied.apply_transform( g.trimesh.transformations.rotation_matrix( g.np.degrees(i), [0, 1, 1])) g.log.info('Multiple copies done') if not g.np.allclose(copied.identifier, mesh.identifier): raise ValueError('copied identifier changed!') # ...still shouldn't have changed anything assert start == {mesh.md5(), mesh.crc()}
def test_permutate(self): def close(a, b): if len(a) == len(b) == 0: return False if a.shape != b.shape: return False return g.np.allclose(a, b) def make_assertions(mesh, test, rigid=False): if (close(test.face_adjacency, mesh.face_adjacency) and len(mesh.faces) > MIN_FACES): g.log.error( 'face_adjacency unchanged: {}'.format( str(test.face_adjacency))) raise ValueError( 'face adjacency of %s the same after permutation!', mesh.metadata['file_name']) if (close(test.face_adjacency_edges, mesh.face_adjacency_edges) and len(mesh.faces) > MIN_FACES): g.log.error( 'face_adjacency_edges unchanged: {}'.format( str(test.face_adjacency_edges))) raise ValueError( 'face adjacency edges of %s the same after permutation!', mesh.metadata['file_name']) assert not close(test.faces, mesh.faces) assert not close(test.vertices, mesh.vertices) assert not test.md5() == mesh.md5() # rigid transforms don't change area or volume if rigid: assert g.np.allclose(mesh.area, test.area) # volume is very dependent on meshes being watertight and sane if (mesh.is_watertight and test.is_watertight and mesh.is_winding_consistent and test.is_winding_consistent): assert g.np.allclose(mesh.volume, test.volume, rtol=.05) for mesh in g.get_meshes(5): if len(mesh.faces) < MIN_FACES: continue # warp the mesh to be a unit cube mesh.vertices /= mesh.extents original = mesh.copy() for i in range(5): mesh = original.copy() noise = g.trimesh.permutate.noise(mesh, magnitude=mesh.scale / 50.0) # make sure that if we permutate vertices with no magnitude # area and volume remain the same no_noise = g.trimesh.permutate.noise(mesh, magnitude=0.0) transform = g.trimesh.permutate.transform(mesh) tessellate = g.trimesh.permutate.tessellation(mesh) make_assertions(mesh, noise, rigid=False) make_assertions(mesh, no_noise, rigid=True) make_assertions(mesh, transform, rigid=True) make_assertions(mesh, tessellate, rigid=True) # make sure permutate didn't alter the original mesh assert original.md5() == mesh.md5()
def test_base(self): for mesh in g.get_meshes(1): tess = mesh.permutate.tessellation() # NOQA noise = mesh.permutate.noise() noise = mesh.permutate.noise(magnitude=mesh.scale / 10) # NOQA transform = mesh.permutate.transform() # NOQA
def test_projections(self): # check the vertex projection onto adjacent face plane # this is used to calculate convexity for m in g.get_meshes(4): assert (len(m.face_adjacency_projections) == (len( m.face_adjacency)))