def test_layer(self): from shapely.geometry import Point # create two disjoint circles and apply layers a = g.trimesh.load_path(Point([0, 0]).buffer(1)) a.apply_layer('ACIRCLE') b = g.trimesh.load_path(Point([2, 0]).buffer(1)) b.apply_layer('BCIRCLE') # combine two circles c = a + b # should be combined assert g.np.isclose(c.area, a.area + b.area) # export C with just layer of A aX = g.trimesh.load(g.io_wrap( c.export(file_type='svg', layers=['ACIRCLE'])), file_type='svg') # export C with all layers cX = g.trimesh.load(g.io_wrap(c.export(file_type='svg', layers=None)), file_type='svg') assert len(cX.entities) == len(c.entities) assert len(aX.entities) == 1 # make aR = g.trimesh.load(g.io_wrap( c.export(file_type='dxf', layers=['ACIRCLE'])), file_type='dxf') assert g.np.isclose(aR.area, a.area)
def test_text(self): # load file with a single text entity original = g.get_mesh('2D/text.dxf') # export then reload roundtrip = g.trimesh.load( file_obj=g.io_wrap(original.export(file_type='dxf')), file_type='dxf') for d in [original, roundtrip]: # should contain a single Text entity assert len(d.entities) == 1 # shouldn't crash anything assert len(d.polygons_closed) == 0 assert len(d.polygons_full) == 0 assert len(d.discrete) == 0 assert len(d.paths) == 0 # make sure it preserved case and special chars assert d.entities[0].text == "HEY WHAT's poppin" # height should 1.0 assert g.np.isclose(d.entities[0].height, 1.0) # get the 2D rotation of the text angle = d.entities[0].angle(d.vertices) # angle should be 30 degrees assert g.np.isclose(angle, g.np.radians(30.0))
def test_text(self): # load file with a single text entity original = g.get_mesh('2D/text.dxf') # export then reload roundtrip = g.trimesh.load( file_obj=g.io_wrap(original.export(file_type='dxf')), file_type='dxf') for d in [original, roundtrip]: # should contain a single Text entity assert len(d.entities) == 1 # shouldn't crash anything assert len(d.polygons_closed) == 0 assert len(d.polygons_full) == 0 assert len(d.discrete) == 0 assert len(d.paths) == 0 # make sure it preserved case and special chars assert d.entities[0].text == "HEY WHAT's poppin" # height should 1.0 assert g.np.isclose(d.entities[0].height, 1.0) # get the 2D rotation of the text angle = d.entities[0].angle(d.vertices) # angle should be 30 degrees assert g.np.isclose(angle, g.np.radians(30.0))
def test_layer(self): from shapely.geometry import Point # create two disjoint circles and apply layers a = g.trimesh.load_path(Point([0, 0]).buffer(1)) a.apply_layer('ACIRCLE') b = g.trimesh.load_path(Point([2, 0]).buffer(1)) b.apply_layer('BCIRCLE') # combine two circles c = a + b # should be combined assert g.np.isclose(c.area, a.area + b.area) # export C with just layer of A aX = c.export(file_type='svg', layers=['ACIRCLE'], return_path=True) # export C with just layer of A cX = c.export(file_type='svg', layers=None, return_path=True) assert len(cX) > len(aX) # make aR = g.trimesh.load(g.io_wrap(c.export(file_type='dxf', layers=['ACIRCLE'])), file_type='dxf') assert g.np.isclose(aR.area, a.area)
def test_dxf(self): # get a path we can write temp_name = g.tempfile.NamedTemporaryFile(suffix='.dxf', delete=False).name # split drawings into single body parts splits = [] for d in g.get_2D(): s = d.split() # check area of split result vs source assert g.np.isclose(sum(i.area for i in s), d.area) splits.append(s) # export the drawing to the file d.export(file_obj=temp_name) # try using ezdxf as a simple validator # it raises exceptions aggressively if ezdxf is not None: with open(temp_name, 'r') as f: ezdxf.read(f) # export to a string text = d.export(file_type='dxf') # DXF files are always pairs of lines lines = str.splitlines(str(text)) assert (len(lines) % 2) == 0 assert all(len(L.strip()) > 0 for L in lines) # reload the file by name and by stream rc = [ g.trimesh.load(temp_name), g.trimesh.load(g.io_wrap(text), file_type='dxf') ] # compare reloaded with original for r in rc: assert g.np.isclose(r.area, d.area) assert g.np.isclose(r.length, d.length, rtol=1e-4) assert len(r.entities) == len(d.entities) single = g.np.hstack(splits) for p in single: p.vertices /= p.scale # make sure exporting by name works # use tempfile to avoid dumping file in # our working directory p.export(temp_name) r = g.trimesh.load(temp_name) ratio = abs(p.length - r.length) / p.length if ratio > .01: g.log.error('perimeter ratio on export %s wrong! %f %f %f', p.metadata['file_name'], p.length, r.length, ratio) raise ValueError('perimeter ratio too large ({}) on {}'.format( ratio, p.metadata['file_name']))
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_scene(self): s = g.get_mesh('cycloidal.3DXML') e = g.trimesh.load(g.io_wrap(s.export(file_type='obj')), file_type='obj', split_object=True, group_materials=False) assert g.np.isclose(e.area, s.area, rtol=.01)
def test_dxf(self): # get a path we can write temp_name = g.tempfile.NamedTemporaryFile( suffix='.dxf', delete=False).name # split drawings into single body parts splits = [] for d in g.get_2D(): s = d.split() # check area of split result vs source assert g.np.isclose(sum(i.area for i in s), d.area) splits.append(s) # export the drawing to the file d.export(file_obj=temp_name) # export to a string text = d.export(file_type='dxf') # DXF files are always pairs of lines assert (len(str.splitlines(str(text))) % 2) == 0 # reload the file by name and by stream rc = [g.trimesh.load(temp_name), g.trimesh.load(g.io_wrap(text), file_type='dxf')] # compare reloaded with original for r in rc: assert g.np.isclose(r.area, d.area) assert g.np.isclose(r.length, d.length) assert len(r.entities) == len(d.entities) single = g.np.hstack(splits) for p in single: p.vertices /= p.scale # make sure exporting by name works # use tempfile to avoid dumping file in # our working directory p.export(temp_name) r = g.trimesh.load(temp_name) ratio = abs(p.length - r.length) / p.length if ratio > .01: g.log.error('perimeter ratio on export %s wrong! %f %f %f', p.metadata['file_name'], p.length, r.length, ratio) raise ValueError('perimeter ratio too large ({}) on {}'.format( ratio, p.metadata['file_name']))
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_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_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_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_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)