Exemple #1
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 = 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)
Exemple #2
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))
Exemple #3
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))
Exemple #4
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)
Exemple #5
0
    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']))
Exemple #6
0
    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
Exemple #7
0
    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)
Exemple #8
0
    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']))
Exemple #9
0
    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)
Exemple #10
0
    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')
Exemple #11
0
    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')
Exemple #12
0
    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')
Exemple #13
0
    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)
Exemple #14
0
    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)