Beispiel #1
0
    def test_concat(self):

        a = g.get_mesh('ballA.off')
        b = g.get_mesh('ballB.off')

        hA = a.md5()
        hB = b.md5()

        # make sure we're not mutating original mesh
        for i in range(4):
            c = a + b
            assert g.np.isclose(c.volume,
                                a.volume + b.volume)
            assert a.md5() == hA
            assert b.md5() == hB

        count = 5
        meshes = []
        for i in range(count):
            m = a.copy()
            m.apply_translation([a.scale, 0, 0])
            meshes.append(m)

        # do a multimesh concatenate
        r = g.trimesh.util.concatenate(meshes)
        assert g.np.isclose(r.volume,
                            a.volume * count)
        assert a.md5() == hA
Beispiel #2
0
    def test_ply(self):
        m = g.get_mesh('machinist.XAML')

        assert m.visual.kind == 'face'
        assert m.visual.face_colors.ptp(axis=0).max() > 0

        export = m.export(file_type='ply')
        reconstructed = g.trimesh.load(g.trimesh.util.wrap_as_stream(export),
                                       file_type='ply')

        assert reconstructed.visual.kind == 'face'

        assert g.np.allclose(reconstructed.visual.face_colors,
                             m.visual.face_colors)

        m = g.get_mesh('reference.ply')

        assert m.visual.kind == 'vertex'
        assert m.visual.vertex_colors.ptp(axis=0).max() > 0

        export = m.export(file_type='ply')
        reconstructed = g.trimesh.load(g.trimesh.util.wrap_as_stream(export),
                                       file_type='ply')
        assert reconstructed.visual.kind == 'vertex'

        assert g.np.allclose(reconstructed.visual.vertex_colors,
                             m.visual.vertex_colors)
Beispiel #3
0
    def test_components(self):
        # a soup of random triangles, with no adjacent pairs
        soup = g.get_mesh('soup.stl')
        # a mesh with multiple watertight bodies
        mult = g.get_mesh('cycloidal.ply')
        # a mesh with a single watertight body
        sing = g.get_mesh('featuretype.STL')
        # mesh with a single tetrahedron
        tet = g.get_mesh('tet.ply')

        for engine in self.engines:
            # without requiring watertight the split should be into every face
            split = soup.split(only_watertight=False, engine=engine)
            assert len(split) == len(soup.faces)

            # with watertight there should be an empty list
            split = soup.split(only_watertight=True, engine=engine)
            assert len(split) == 0

            split = mult.split(only_watertight=False, engine=engine)
            assert len(split) >= 119

            split = mult.split(only_watertight=True, engine=engine)
            assert len(split) >= 117

            # random triangles should have no facets
            facets = g.trimesh.graph.facets(mesh=soup, engine=engine)
            assert len(facets) == 0

            facets = g.trimesh.graph.facets(mesh=mult, engine=engine)
            assert all(len(i) >= 2 for i in facets)
            assert len(facets) >= 8654

            split = sing.split(only_watertight=False, engine=engine)
            assert len(split) == 1
            assert split[0].is_watertight
            assert split[0].is_winding_consistent

            split = sing.split(only_watertight=True, engine=engine)
            assert len(split) == 1
            assert split[0].is_watertight
            assert split[0].is_winding_consistent

            # single tetrahedron
            assert tet.is_volume
            assert tet.body_count == 1
            # regardless of method or flag we should have one body result
            split = tet.split(only_watertight=True, engine=engine)
            assert len(split) == 1
            split = tet.split(only_watertight=False, engine=engine)
            assert len(split) == 1
Beispiel #4
0
    def test_3MF(self):
        # an assembly with instancing
        s = g.get_mesh('counterXP.3MF')
        # should be 2 unique meshes
        assert len(s.geometry) == 2
        # should be 6 instances around the scene
        assert len(s.graph.nodes_geometry) == 6

        # a single body 3MF assembly
        s = g.get_mesh('featuretype.3MF')
        # should be 2 unique meshes
        assert len(s.geometry) == 1
        # should be 6 instances around the scene
        assert len(s.graph.nodes_geometry) == 1
Beispiel #5
0
    def test_vertex_adjacency_graph(self):
        f = g.trimesh.graph.vertex_adjacency_graph

        # a mesh with a single watertight body
        sing = g.get_mesh('featuretype.STL')
        vert_adj_g = f(sing)
        assert len(sing.vertices) == len(vert_adj_g)
Beispiel #6
0
 def setUp(self):
     # inertia numbers pulled from solidworks
     self.truth = g.data['mass_properties']
     self.meshes = dict()
     for data in self.truth:
         filename = data['filename']
         self.meshes[filename] = g.get_mesh(filename)
Beispiel #7
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))
Beispiel #8
0
    def test_scene_id(self):
        """
        A scene has a nicely constructed transform tree, so
        make sure transforming meshes around it doesn't change
        the nuts of their identifier hash.
        """
        scenes = [g.get_mesh('cycloidal.3DXML')]

        for s in scenes:
            for geom_name, mesh in s.geometry.items():
                meshes = []
                for node in s.graph.nodes_geometry:
                    T, geo = s.graph[node]
                    if geom_name != geo:
                        continue

                    m = s.geometry[geo].copy()
                    m.apply_transform(T)
                    meshes.append(m)
                if not all(meshes[0].identifier_md5 == i.identifier_md5
                           for i in meshes):
                    raise ValueError(
                        '{} differs after transform!'.format(geom_name))

        assert (scenes[0].geometry['disc_cam_B'].identifier_md5 !=
                scenes[0].geometry['disc_cam_A'].identifier_md5)
Beispiel #9
0
    def test_units(self):

        fake_units = 'blorbs'
        self.assertFalse(g.trimesh.units.validate(fake_units))

        m = g.get_mesh('featuretype.STL')

        self.assertTrue(m.units is None)
        
        with self.assertRaises(ValueError):
            m.units = fake_units

        self.assertTrue(m.units is None)
        
        m.units = 'in'
        self.assertTrue(m.units == 'in')

        extents_pre = m.extents
        with self.assertRaises(ValueError):
            m.convert_units(fake_units)
        self.assertTrue(m.units == 'in')

        m.convert_units('mm')
        scale = g.np.divide(m.extents, extents_pre)
        self.assertTrue(g.np.allclose(scale, 25.4))
        self.assertTrue(m.units == 'mm')
Beispiel #10
0
    def test_split(self):

        for fn in ['2D/ChuteHolderPrint.DXF',
                   '2D/tray-easy1.dxf',
                   '2D/sliding-base.dxf',
                   '2D/wrench.dxf',
                   '2D/spline_1.dxf']:
            p = g.get_mesh(fn)

            # make sure something was loaded
            assert len(p.root) > 0

            # split by connected
            split = p.split()

            # make sure split parts have same area as source
            assert g.np.isclose(p.area, sum(i.area for i in split))
            # make sure concatenation doesn't break that
            assert g.np.isclose(p.area, g.np.sum(split).area)

            # check that cache didn't screw things up
            for s in split:
                assert len(s.root) == 1
                assert len(s.path_valid) == len(s.paths)
                assert len(s.paths) == len(s.discrete)
                assert s.path_valid.sum() == len(s.polygons_closed)
                g.check_path2D(s)
Beispiel #11
0
    def test_vhacd(self):

        # exit if no VHACD
        if not g.trimesh.interfaces.vhacd.exists and not g.all_dep:
            g.log.warning(
                'not testing convex decomposition (no vhacd)!')
            return

        g.log.info('testing convex decomposition using vhacd')

        mesh = g.get_mesh('bunny.ply')

        # run a convex decomposition using vhacd
        decomposed = mesh.convex_decomposition(maxhulls=10)

        # it should return the correct number of meshes
        assert len(decomposed) == 10

        # make sure everything is convex
        # also this will fail if the type is returned incorrectly
        assert all(i.is_convex for i in decomposed)

        # make sure every result is actually a volume
        # ie watertight, consistent winding, positive nonzero volume
        assert all(i.is_volume for i in decomposed)
Beispiel #12
0
    def test_section(self):
        mesh = g.get_mesh('tube.obj')

        # check the CCW correctness with a normal in both directions
        for sign in [1.0, -1.0]:
            # get a cross section of the tube
            section = mesh.section(plane_origin=mesh.center_mass,
                                   plane_normal=[0.0, sign, 0.0])

            # Path3D -> Path2D
            planar, T = section.to_planar()

            # tube should have one closed polygon
            assert len(planar.polygons_full) == 1
            polygon = planar.polygons_full[0]
            # closed polygon should have one interior
            assert len(polygon.interiors) == 1

            # the exterior SHOULD be counterclockwise
            assert g.trimesh.path.util.is_ccw(
                polygon.exterior.coords)
            # the interior should NOT be counterclockwise
            assert not g.trimesh.path.util.is_ccw(
                polygon.interiors[0].coords)

            # should be a valid Path2D
            g.check_path2D(planar)
Beispiel #13
0
    def test_cylinder(self):
        """
        Check bounding cylinders on basically a cuboid
        """
        # not rotationally symmetric
        mesh = g.get_mesh('featuretype.STL')

        height = 10.0
        radius = 1.0

        # spherical coordinates to loop through
        sphere = g.trimesh.util.grid_linspace(
            [[0, 0], [g.np.pi * 2, g.np.pi * 2]], 5)

        for s in sphere:
            T = g.trimesh.transformations.spherical_matrix(*s)
            p = g.trimesh.creation.cylinder(radius=radius,
                                            height=height,
                                            transform=T)
            assert g.np.isclose(radius,
                                p.bounding_cylinder.primitive.radius,
                                rtol=.01)
            assert g.np.isclose(height,
                                p.bounding_cylinder.primitive.height,
                                rtol=.01)

            # regular mesh should have the same bounding cylinder
            # regardless of transform
            copied = mesh.copy()
            copied.apply_transform(T)
            assert g.np.isclose(mesh.bounding_cylinder.volume,
                                copied.bounding_cylinder.volume,
                                rtol=.05)
Beispiel #14
0
    def test_multiple(self):
        for mesh in [g.trimesh.creation.icosahedron(),
                     g.get_mesh('unit_cube.STL')]:

            vectors = g.trimesh.util.grid_linspace([[0.0, 0], [1, 1.0]], 5)[1:]
            vectors = g.trimesh.unitize(g.np.column_stack(
                (vectors, g.np.ones(len(vectors)))))
            for vector, angle in zip(
                    vectors, g.np.linspace(0.0, g.np.pi, len(vectors))):
                matrix = g.trimesh.transformations.rotation_matrix(
                    angle, vector)

                copied = mesh.copy()
                copied.apply_transform(matrix)

                # Compute the stable poses of the icosahedron
                trans, probs = copied.compute_stable_poses()

                # we are only testing primitives with point symmetry
                # AKA 3 principal components of inertia are the same
                facet_count = len(mesh.facets)
                if facet_count == 0:
                    facet_count = len(mesh.faces)
                probability = 1.0 / float(facet_count)

                assert g.np.allclose(g.np.array(probs) - probability, 0.0)
Beispiel #15
0
def get_readonly(model_name):
    """
    Get a mesh and make vertices and faces read only.

    Parameters
    ------------
    model_name : str
     Model name in models directory

    Returns
    -----------
    mesh : trimesh.Trimesh
      Geometry with read-only data
    verts : (n, 3) float
      Read- only vertices
    faces : (m, 3) int
      Read- only faces
    """
    original = g.get_mesh(model_name)
    # get the original data from the mesh
    verts = original.vertices
    faces = original.faces
    # use the buffer interface to generate read-only arrays
    verts = g.np.ndarray(verts.shape, verts.dtype, bytes(verts.tostring()))
    faces = g.np.ndarray(faces.shape, faces.dtype, bytes(faces.tostring()))
    # everything should be read only now
    assert not verts.flags['WRITEABLE']
    assert not faces.flags['WRITEABLE']

    mesh = g.trimesh.Trimesh(verts, faces, process=False, validate=False)
    assert not mesh.vertices.flags['WRITEABLE']
    assert not mesh.faces.flags['WRITEABLE']

    # return the mesh, and read-only vertices and faces
    return mesh, verts, faces
Beispiel #16
0
    def test_gltf(self):
        # split a multibody mesh into a scene
        scene = g.trimesh.scene.split_scene(
            g.get_mesh('cycloidal.ply'))
        # should be 117 geometries
        assert len(scene.geometry) >= 117

        # a dict with {file name: str}
        export = scene.export('gltf')
        # load from just resolver
        r = g.trimesh.load(file_obj=None,
                           file_type='gltf',
                           resolver=export)
        # will assert round trip is roughly equal
        g.scene_equal(r, scene)

        # try loading from a ZIP archive
        zipped = g.trimesh.util.compress(export)
        r = g.trimesh.load(
            file_obj=g.trimesh.util.wrap_as_stream(zipped),
            file_type='zip')

        # try loading from a file name
        # will require a file path resolver
        with g.TemporaryDirectory() as d:
            for file_name, data in export.items():
                with open(g.os.path.join(d, file_name), 'wb') as f:
                    f.write(data)
            # load from file path of header GLTF
            rd = g.trimesh.load(
                g.os.path.join(d, 'model.gltf'))
            # will assert round trip is roughly equal
            g.scene_equal(rd, scene)
Beispiel #17
0
    def test_edges(self):
        """
        Test edges_to_polygon
        """
        m = g.get_mesh('featuretype.STL')

        # get a polygon for the second largest facet
        index = m.facets_area.argsort()[-2]
        normal = m.facets_normal[index]
        origin = m._cache['facets_origin'][index]
        T = g.trimesh.geometry.plane_transform(origin, normal)
        vertices = g.trimesh.transform_points(m.vertices, T)[:, :2]

        # find boundary edges for the facet
        edges = m.edges_sorted.reshape(
            (-1, 6))[m.facets[index]].reshape((-1, 2))
        group = g.trimesh.grouping.group_rows(edges, require_count=1)

        # run the polygon conversion
        polygon = g.trimesh.path.polygons.edges_to_polygons(
            edges=edges[group],
            vertices=vertices)

        assert len(polygon) == 1
        assert g.np.isclose(polygon[0].area,
                            m.facets_area[index])

        # try transforming the polygon around
        M = g.np.eye(3)
        M[0][2] = 10.0
        P2 = g.trimesh.path.polygons.transform_polygon(polygon[0], M)
        distance = g.np.array(P2.centroid) - g.np.array(polygon[0].centroid)
        assert g.np.allclose(distance, [10.0, 0])
Beispiel #18
0
    def test_obj_quad(self):
        mesh = g.get_mesh('quadknot.obj')
        # make sure some data got loaded
        assert g.trimesh.util.is_shape(mesh.faces, (-1, 3))
        assert g.trimesh.util.is_shape(mesh.vertices, (-1, 3))

        assert mesh.is_watertight
        assert mesh.is_winding_consistent
Beispiel #19
0
    def test_dict(self):
        mesh = g.get_mesh('machinist.XAML')
        assert mesh.visual.kind == 'face'
        mesh.visual.vertex_colors = mesh.visual.vertex_colors
        assert mesh.visual.kind == 'vertex'

        as_dict = mesh.to_dict()
        back = g.trimesh.Trimesh(**as_dict)  # NOQA
Beispiel #20
0
    def test_shoulder(self):
        if collada is None:
            g.log.error('no pycollada to test!')
            return

        scene = g.get_mesh('shoulder.zae')
        assert len(scene.geometry) == 3
        assert len(scene.graph.nodes_geometry) == 3
Beispiel #21
0
 def test_triangulate_plumbing(self):
     """
     Check the plumbing of path triangulation
     """
     if len(self.engines) == 0:
         return
     p = g.get_mesh('2D/ChuteHolderPrint.DXF')
     v, f = p.triangulate()
     check_triangulation(v, f, p.area)
Beispiel #22
0
    def test_on_edge(self):
        for use_embree in [True, False]:
            m = g.get_mesh('7_8ths_cube.stl')

            points = [[4.5, 0, -23], [4.5, 0, -2], [0, 0, -1e-6], [0, 0, -1]]
            truth = [False, True, True, True]
            result = g.trimesh.ray.ray_util.contains_points(m.ray, points)

            assert (result == truth).all()
Beispiel #23
0
 def test_obj(self):
     m = g.get_mesh('textured_tetrahedron.obj', process=False)
     export = m.export(file_type='obj')
     reconstructed = g.trimesh.load(g.trimesh.util.wrap_as_stream(export),
                                    file_type='obj', process=False)
     # test that we get at least the same number of normals and texcoords out;
     # the loader may reorder vertices, so we shouldn't check direct
     # equality
     assert m.vertex_normals.shape == reconstructed.vertex_normals.shape
Beispiel #24
0
    def test_scene(self):
        try:
            import fcl  # NOQA
        except ImportError:
            return
        scene = g.get_mesh('cycloidal.3DXML')

        manager, objects = g.trimesh.collision.scene_to_collision(scene)

        assert manager.in_collision_internal()
Beispiel #25
0
 def test_obj_split_attributes(self):
     # test a wavefront file where pos/uv/norm have different indices
     # and where multiple objects share vertices
     # Note 'process=False' to avoid merging vertices
     meshes = g.get_mesh('joined_tetrahedra.obj', process=False)
     self.assertTrue(len(meshes) == 2)
     assert g.trimesh.util.is_shape(meshes[0].faces, (4, 3))
     assert g.trimesh.util.is_shape(meshes[0].vertices, (9, 3))
     assert g.trimesh.util.is_shape(meshes[1].faces, (4, 3))
     assert g.trimesh.util.is_shape(meshes[1].vertices, (9, 3))
Beispiel #26
0
    def test_contains(self):
        mesh = g.get_mesh('unit_cube.STL')
        scale = 1+(g.trimesh.constants.tol.merge*2)

        test_on  = mesh.contains(mesh.vertices)
        test_in  = mesh.contains(mesh.vertices * (1.0/scale))
        test_out = mesh.contains(mesh.vertices * scale)
        
        #assert test_on.all()
        self.assertTrue(test_in.all())
        self.assertFalse(test_out.any())
Beispiel #27
0
    def test_tex_export(self):
        # load textured PLY
        mesh = g.get_mesh('fuze.ply')
        assert hasattr(mesh.visual, 'uv')

        # make sure export as GLB doesn't crash on scenes
        export = mesh.scene().export(file_type='glb')
        assert len(export) > 0
        # make sure it works on meshes
        export = mesh.export(file_type='glb')
        assert len(export) > 0
Beispiel #28
0
    def test_facet(self):
        m = g.get_mesh('featuretype.STL')

        assert len(m.facets) > 0
        assert len(m.facets) == len(m.facets_boundary)
        assert len(m.facets) == len(m.facets_normal)
        assert len(m.facets) == len(m.facets_area)
        assert len(m.facets) == len(m.facets_on_hull)

        # this mesh should have 8 facets on the convex hull
        assert m.facets_on_hull.astype(int).sum() == 8
Beispiel #29
0
    def test_box(self):
        """
        Run box- ray intersection along Z and make sure XY match
        ray origin XY.
        """

        for kwargs in [{'use_embree': True},
                       {'use_embree': False}]:

            mesh = g.get_mesh('unit_cube.STL', **kwargs)
            # grid is across meshes XY profile
            origins = g.trimesh.util.grid_linspace(mesh.bounds[:, :2] +
                                                   g.np.reshape(
                                                       [-.02, .02], (-1, 1)),
                                                   100)
            origins = g.np.column_stack((
                origins,
                g.np.ones(len(origins)) * -100))
            # all vectors are along Z axis
            vectors = g.np.ones((len(origins), 3)) * [0, 0, 1.0]

            # (n,3) float intersection position in space
            # (n,) int, index of original ray
            # (m,) int, index of mesh.faces
            pos, ray, tri = mesh.ray.intersects_location(
                ray_origins=origins,
                ray_directions=vectors)

            for p, r in zip(pos, ray):
                # intersect location XY should match ray origin XY
                assert g.np.allclose(p[:2], origins[r][:2])
                # the Z of the hit should be on the cube's
                # top or bottom face
                assert g.np.isclose(p[2], mesh.bounds[:, 2]).any()

        def test_broken(self):
            """
            Test a mesh with badly defined face normals
            """

            ray_origins = g.np.array([[0.12801793, 24.5030052, -5.],
                                      [0.12801793, 24.5030052, -5.]])
            ray_directions = g.np.array([[-0.13590759, -0.98042506, 0.],
                                         [0.13590759, 0.98042506, -0.]])

            for kwargs in [{'use_embree': True},
                           {'use_embree': False}]:
                mesh = g.get_mesh('broken.STL', **kwargs)

                locations, index_ray, index_tri = mesh.ray.intersects_location(
                    ray_origins=ray_origins, ray_directions=ray_directions)

                # should be same number of location hits
                assert len(locations) == len(ray_origins)
Beispiel #30
0
 def test_path(self):
     p = g.get_mesh('2D/tray-easy1.dxf')
     # should be inches
     assert 'in' in p.units
     extents_pre = p.extents
     p.convert_units('mm')
     # should have converted in -> mm 25.4
     # extents should scale exactly with unit conversion
     assert g.np.allclose(p.extents / extents_pre,
                          25.4,
                          atol=.01)
Beispiel #31
0
    def test_winding(self):
        """
        Reverse some faces and make sure fix_face_winding flips
        them back.
        """

        meshes = [g.get_mesh(i) for i in
                  ['unit_cube.STL',
                   'machinist.XAML',
                   'round.stl',
                   'quadknot.obj',
                   'soup.stl']]

        for i, mesh in enumerate(meshes):
            # turn scenes into multibody meshes
            if g.trimesh.util.is_instance_named(mesh, 'Scene'):
                meta = mesh.metadata
                meshes[i] = mesh.dump().sum()
                meshes[i].metadata = meta

        timing = {}
        for mesh in meshes:
            # save the initial state
            is_volume = mesh.is_volume
            winding = mesh.is_winding_consistent

            tic = g.time.time()
            # flip faces to break winding
            mesh.faces[:4] = g.np.fliplr(mesh.faces[:4])

            # run the operation
            mesh.fix_normals()

            # make sure mesh is repaired to former glory
            assert mesh.is_volume == is_volume
            assert mesh.is_winding_consistent == winding

            # save timings
            timing[mesh.metadata['file_name']] = g.time.time() - tic
        # print timings as a warning
        g.log.warning(g.json.dumps(timing, indent=4))
Beispiel #32
0
    def test_header(self):
        m = g.get_mesh('featuretype.STL')
        # make sure we have the right mesh
        assert g.np.isclose(m.volume, 11.627733431196749, atol=1e-6)

        # should have saved the header from the STL file
        assert len(m.metadata['header']) > 0

        # should have saved the STL face attributes
        assert len(m.face_attributes['stl']) == len(m.faces)
        assert len(m.faces) > 1000
        # add a non-correlated face attribute, which should be ignored
        m.face_attributes['nah'] = 10

        # remove all faces except three random ones
        m.update_faces([1, 3, 4])
        # faces and face attributes should be untouched
        assert len(m.faces) == 3
        assert len(m.face_attributes['stl']) == 3
        # attribute that wasn't len(m.faces) shouldn't have been touched
        assert m.face_attributes['nah'] == 10
Beispiel #33
0
    def test_contains(self):
        scale = 1.5
        for use_embree in [True, False]:
            mesh = g.get_mesh('unit_cube.STL', use_embree=use_embree)
            g.log.info('Contains test ray engine: ' + str(mesh.ray.__class__))

            test_on = mesh.ray.contains_points(mesh.vertices)
            test_in = mesh.ray.contains_points(mesh.vertices * (1.0 / scale))
            assert test_in.all()

            test_out = mesh.ray.contains_points(mesh.vertices * scale)
            assert not test_out.any()

            points_way_out = (
                g.np.random.random(
                    (30, 3)) * 100) + 1.0 + mesh.bounds[1]
            test_way_out = mesh.ray.contains_points(points_way_out)
            assert not test_way_out.any()

            test_centroid = mesh.ray.contains_points([mesh.center_mass])
            assert test_centroid.all()
Beispiel #34
0
    def test_hash(self):
        setup = 'import numpy, trimesh;'
        setup += 'd = numpy.random.random((10000,3));'
        setup += 't = trimesh.caching.tracked_array(d)'

        count = 10000

        g.timeit.timeit(setup=setup,
                        stmt='t._modified_m=True;t.md5()',
                        number=count)
        g.timeit.timeit(setup=setup,
                        stmt='t._modified_c=True;t.crc()',
                        number=count)
        g.timeit.timeit(setup=setup,
                        stmt='t._modified_x=True;t.fast_hash()',
                        number=count)

        m = g.get_mesh('featuretype.STL')
        # log result values
        g.log.info('\nResult\nMD5:\n{}\nCRC:\n{}\nXX:\n{}'.format(
            m.vertices.md5(), m.vertices.crc(), m.vertices.fast_hash()))
Beispiel #35
0
    def test_successors(self):
        s = g.get_mesh('CesiumMilkTruck.glb')
        assert len(s.graph.nodes_geometry) == 5

        # world should be root frame
        assert (s.graph.transforms.successors(
            s.graph.base_frame) == s.graph.nodes)

        for n in s.graph.nodes:
            # successors should always return subset of nodes
            succ = s.graph.transforms.successors(n)
            assert succ.issubset(s.graph.nodes)
            # we self-include node in successors
            assert n in succ

        # test getting a subscene from successors
        ss = s.subscene('3')
        assert len(ss.geometry) == 1
        assert len(ss.graph.nodes_geometry) == 1

        assert isinstance(s.graph.to_networkx(), g.nx.DiGraph)
Beispiel #36
0
    def test_split(self):
        for fn in [
                '2D/ChuteHolderPrint.DXF', '2D/tray-easy1.dxf',
                '2D/sliding-base.dxf', '2D/wrench.dxf', '2D/spline_1.dxf'
        ]:
            p = g.get_mesh(fn)

            # split by connected
            split = p.split()

            # make sure split parts have same area as source
            assert g.np.isclose(p.area, sum(i.area for i in split))
            # make sure concatenation doesn't break that
            assert g.np.isclose(p.area, g.np.sum(split).area)

            # check that cache didn't screw things up
            for s in split:
                assert len(s.root) == 1
                assert len(s.path_valid) == len(s.paths)
                assert len(s.paths) == len(s.discrete)
                assert s.path_valid.sum() == len(s.polygons_closed)
Beispiel #37
0
    def test_vertex_attributes(self):
        """
        Test writing vertex attributes to a ply, by reading them back and asserting the
        written attributes array matches
        """

        m = g.get_mesh('box.STL')
        test_1d_attribute = g.np.copy(m.vertices[:, 0])
        test_nd_attribute = g.np.copy(m.vertices)
        m.vertex_attributes['test_1d_attribute'] = test_1d_attribute
        m.vertex_attributes['test_nd_attribute'] = test_nd_attribute

        export = m.export(file_type='ply')
        reconstructed = g.wrapload(export, file_type='ply')

        vertex_attributes = reconstructed.metadata['ply_raw']['vertex']['data']
        result_1d = vertex_attributes['test_1d_attribute']
        result_nd = vertex_attributes['test_nd_attribute']['f1']

        g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
        g.np.testing.assert_almost_equal(result_nd, test_nd_attribute)
Beispiel #38
0
    def test_poly(self):
        p = g.get_mesh('2D/LM2.dxf')
        assert p.is_closed
        assert any(
            len(i.points) > 2 for i in p.entities
            if g.trimesh.util.is_instance_named(i, 'Line'))

        assert len(p.layers) == len(p.entities)
        assert len(g.np.unique(p.layers)) > 1

        p.explode()
        assert all(
            len(i.points) == 2 for i in p.entities
            if g.trimesh.util.is_instance_named(i, 'Line'))
        assert p.is_closed
        p.entities = p.entities[:-1]
        assert not p.is_closed

        # fill gaps of any distance
        p.fill_gaps(g.np.inf)
        assert p.is_closed
Beispiel #39
0
    def test_conversion(self):
        # test conversions on a multibody STL in a scene

        # a multibody STL with a unit hint in filename
        m = g.get_mesh('counter.unitsmm.STL')

        # nothing should be set
        assert m.units is None

        # split into watertight bodies
        s = g.trimesh.scene.split_scene(m)

        # save the extents
        extents_pre = s.extents

        # should extract units from file name without
        # raising a ValueError
        c = s.convert_units('in', guess=False)
        # should have converted mm -> in, 1/25.4
        # extents should scale exactly with unit conversion
        assert g.np.allclose(extents_pre / c.extents, 25.4, atol=.01)
Beispiel #40
0
    def test_empty(self):
        """
        Test queries with no hits
        """
        for use_embree in [True, False]:
            dimension = (100, 3)
            sphere = g.get_mesh('unit_sphere.STL', use_embree=use_embree)
            # should never hit the sphere
            ray_origins = g.np.random.random(dimension)
            ray_directions = g.np.tile([0, 1, 0], (dimension[0], 1))
            ray_origins[:, 2] = -5

            # make sure ray functions always return numpy arrays
            # these functions return multiple results all of which
            # should always be a numpy array
            assert all(
                len(i.shape) >= 0
                for i in sphere.ray.intersects_id(ray_origins, ray_directions))
            assert all(
                len(i.shape) >= 0 for i in sphere.ray.intersects_location(
                    ray_origins, ray_directions))
Beispiel #41
0
 def test_mtl(self):
     # get a mesh with texture
     m = g.get_mesh('fuze.obj')
     # export the mesh including data
     obj, data = g.trimesh.exchange.export.export_obj(m,
                                                      include_texture=True)
     with g.trimesh.util.TemporaryDirectory() as path:
         # where is the OBJ file going to be saved
         obj_path = g.os.path.join(path, 'test.obj')
         with open(obj_path, 'w') as f:
             f.write(obj)
         # save the MTL and images
         for k, v in data.items():
             with open(g.os.path.join(path, k), 'wb') as f:
                 f.write(v)
         # reload the mesh from the export
         rec = g.trimesh.load(obj_path)
     # make sure loaded image is the same size as the original
     assert (rec.visual.material.image.size == m.visual.material.image.size)
     # make sure the faces are the same size
     assert rec.faces.shape == m.faces.shape
Beispiel #42
0
    def test_dupe(self):
        m = g.get_mesh('tube.obj')

        assert m.body_count == 1

        s = g.trimesh.scene.split_scene(m)
        assert len(s.graph.nodes) == 2
        assert len(s.graph.nodes_geometry) == 1
        assert len(s.duplicate_nodes) == 1
        assert len(s.duplicate_nodes[0]) == 1

        c = s.copy()
        assert len(c.graph.nodes) == 2
        assert len(c.graph.nodes_geometry) == 1
        assert len(c.duplicate_nodes) == 1
        assert len(c.duplicate_nodes[0]) == 1

        u = s.convert_units('in', guess=True)
        assert len(u.graph.nodes_geometry) == 1
        assert len(u.duplicate_nodes) == 1
        assert len(u.duplicate_nodes[0]) == 1
Beispiel #43
0
    def test_rps(self):
        for use_embree in [True, False]:
            dimension = (10000, 3)
            sphere = g.get_mesh('unit_sphere.STL', use_embree=use_embree)

            ray_origins = g.np.random.random(dimension)
            ray_directions = g.np.tile([0, 0, 1], (dimension[0], 1))
            ray_origins[:, 2] = -5

            # force ray object to allocate tree before timing it
            #tree = sphere.ray.tree
            tic = [g.time.time()]
            sphere.ray.intersects_id(ray_origins, ray_directions)
            tic.append(g.time.time())
            sphere.ray.intersects_location(ray_origins, ray_directions)
            tic.append(g.time.time())

            rps = dimension[0] / g.np.diff(tic)

            g.log.info('Measured %s rays/second with embree %d', str(rps),
                       use_embree)
Beispiel #44
0
    def test_identifier(self):
        count = 25
        meshes = g.np.append(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.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.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'])
Beispiel #45
0
    def test_poly(self):
        p = g.get_mesh('2D/LM2.dxf')
        assert p.is_closed

        # one of the lines should be a polyline
        assert any(len(e.points) > 2 for e in p.entities if
                   isinstance(e, g.trimesh.path.entities.Line))

        # layers should match entity count
        assert len(p.layers) == len(p.entities)
        assert len(set(p.layers)) > 1

        count = len(p.entities)

        p.explode()
        # explode should have created new entities
        assert len(p.entities) > count
        # explode should have added some new layers
        assert len(p.entities) == len(p.layers)
        # all line segments should have two points now
        assert all(len(i.points) == 2 for i in p.entities if
                   isinstance(i, g.trimesh.path.entities.Line))
        # should still be closed
        assert p.is_closed
        # chop off the last entity
        p.entities = p.entities[:-1]
        # should no longer be closed
        assert not p.is_closed

        # fill gaps of any distance
        p.fill_gaps(g.np.inf)
        # should have fixed this puppy
        assert p.is_closed

        # remove 2 short edges using remove_entity()
        count = len(p.entities)
        p.remove_entities([count - 1, count - 6])
        assert not p.is_closed
        p.fill_gaps(2)
        assert p.is_closed
Beispiel #46
0
    def test_scene(self):
        for mesh in g.get_mesh('cycloidal.ply',
                               'kinematic.tar.gz',
                               'sphere.ply'):
            scene_split = g.trimesh.scene.split_scene(mesh)

            scene_base = g.trimesh.Scene(mesh)

            for s in [scene_split, scene_base]:
                self.assertTrue(len(s.geometry) > 0)

                flattened = s.graph.to_flattened()
                g.json.dumps(flattened)
                edgelist = s.graph.to_edgelist()
                g.json.dumps(edgelist)

                assert s.bounds.shape == (2, 3)
                assert s.centroid.shape == (3,)
                assert s.extents.shape == (3,)
                assert isinstance(s.scale, float)
                assert g.trimesh.util.is_shape(s.triangles, (-1, 3, 3))
                assert len(s.triangles) == len(s.triangles_node)

                assert s.md5() is not None

                assert len(s.duplicate_nodes) > 0

                r = s.dump()

                for export_format in ['dict', 'dict64']:
                    # try exporting the scene as a dict
                    # then make sure json can serialize it
                    e = g.json.dumps(s.export(export_format))

                    # reconstitute the dict into a scene
                    r = g.trimesh.load(g.json.loads(e))

                    # make sure the extents are similar before and after
                    assert g.np.allclose(g.np.product(s.extents),
                                         g.np.product(r.extents))
Beispiel #47
0
    def test_rasterize(self):
        p = g.get_mesh('2D/wrench.dxf')

        origin = p.bounds[0]
        pitch = p.extents.max() / 600
        resolution = g.np.ceil(p.extents / pitch).astype(int)

        # rasterize with filled
        filled = p.rasterize(origin=origin,
                             pitch=pitch,
                             resolution=resolution,
                             fill=True,
                             width=None)

        # rasterize just the outline
        outline = p.rasterize(origin=origin,
                              pitch=pitch,
                              resolution=resolution,
                              fill=False,
                              width=2.0)

        # rasterize both
        both = p.rasterize(origin=origin,
                           pitch=pitch,
                           resolution=resolution,
                           fill=True,
                           width=2.0)

        # count the number of filled pixels
        fill_cnt = g.np.array(filled).sum()
        both_cnt = g.np.array(both).sum()
        outl_cnt = g.np.array(outline).sum()

        # filled should have more than an outline
        assert fill_cnt > outl_cnt
        # filled+outline should have more than outline
        assert both_cnt > outl_cnt
        # filled+outline should have more than filled
        assert both_cnt > fill_cnt
Beispiel #48
0
    def test_image(self):
        try:
            import xatlas  # noqa
        except BaseException:
            g.log.info('not testing unwrap as no `xatlas`')
            return
        a = g.get_mesh('bunny.ply', force="mesh")

        u = a.unwrap()
        assert u.visual.uv.shape == (len(u.vertices), 2)

        checkerboard = g.np.kron([[1, 0] * 4, [0, 1] * 4] * 4,
                                 g.np.ones((10, 10)))
        try:
            from PIL import Image
        except BaseException:
            return

        image = Image.fromarray((checkerboard * 255).astype(g.np.uint8))
        u = a.unwrap(image=image)
        # make sure image was attached correctly
        assert u.visual.material.image.size == image.size
Beispiel #49
0
    def test_text(self):
        """
        Do some checks on Text entities
        """
        p = g.get_mesh('2D/LM2.dxf')
        p.explode()
        # get some text entities
        text = [
            e for e in p.entities
            if isinstance(e, g.trimesh.path.entities.Text)
        ]
        assert len(text) > 1

        # loop through each of them
        for t in text:
            # a spurious error we were seeing in CI
            if g.trimesh.util.is_instance_named(t, 'Line'):
                raise ValueError(
                    'type bases:',
                    [i.__name__ for i in g.trimesh.util.type_bases(t)])
        # make sure this doesn't crash with text entities
        g.trimesh.rendering.convert_to_vertexlist(p)
    def test_integrate(self):
        from trimesh.integrate import symbolic_barycentric
        import sympy as sp
        m = g.get_mesh('featuretype.STL')

        integrator, expr = symbolic_barycentric('1')
        self.assertTrue(g.np.allclose(integrator(m).sum(), m.area))

        x, y, z = sp.symbols('x y z')
        functions = [x**2 + y**2, x + y + z]

        for f in functions:
            integrator, expr = symbolic_barycentric(f)
            integrator_p, expr_p = symbolic_barycentric(str(f))

            g.log.debug('expression %s was integrated to %s', str(f),
                        str(expr))

            summed = integrator(m).sum()
            summed_p = integrator_p(m).sum()
            self.assertTrue(g.np.allclose(summed, summed_p))
            self.assertFalse(g.np.allclose(summed, 0.0))
Beispiel #51
0
    def test_face_attributes(self):
        # Test writing face attributes to a ply, by reading
        # them back and asserting the written attributes array matches

        m = g.get_mesh('box.STL')
        test_1d_attribute = g.np.copy(m.face_angles[:, 0])
        test_nd_attribute = g.np.copy(m.face_angles)
        m.face_attributes['test_1d_attribute'] = test_1d_attribute
        m.face_attributes['test_nd_attribute'] = test_nd_attribute

        export = m.export(file_type='ply')
        reconstructed = g.wrapload(export, file_type='ply')

        face_attributes = reconstructed.metadata['ply_raw']['face']['data']
        result_1d = face_attributes['test_1d_attribute']
        result_nd = face_attributes['test_nd_attribute']['f1']

        g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
        g.np.testing.assert_almost_equal(result_nd, test_nd_attribute)

        no_attr = m.export(file_type='ply', include_attributes=False)
        assert len(no_attr) < len(export)
Beispiel #52
0
    def test_poly(self):
        p = g.get_mesh('2D/LM2.dxf')
        self.assertTrue(p.is_closed)
        self.assertTrue(
            any(
                len(i.points) > 2 for i in p.entities
                if g.trimesh.util.is_instance_named(i, 'Line')))

        assert len(p.layers) == len(p.entities)
        assert len(g.np.unique(p.layers)) > 1

        p.explode()
        self.assertTrue(
            all(
                len(i.points) == 2 for i in p.entities
                if g.trimesh.util.is_instance_named(i, 'Line')))
        self.assertTrue(p.is_closed)
        p.entities = p.entities[:-1]
        self.assertFalse(p.is_closed)

        p.fill_gaps()
        self.assertTrue(p.is_closed)
Beispiel #53
0
    def test_section(self):
        mesh = g.get_mesh('tube.obj')

        # check the CCW correctness with a normal in both directions
        for sign in [1.0, -1.0]:
            # get a cross section of the tube
            section = mesh.section(plane_origin=mesh.center_mass,
                                   plane_normal=[0.0, sign, 0.0])

            # Path3D -> Path2D
            planar, T = section.to_planar()

            # tube should have one closed polygon
            assert len(planar.polygons_full) == 1
            polygon = planar.polygons_full[0]
            # closed polygon should have one interior
            assert len(polygon.interiors) == 1

            # the exterior SHOULD be counterclockwise
            assert g.trimesh.path.util.is_ccw(polygon.exterior.coords)
            # the interior should NOT be counterclockwise
            assert not g.trimesh.path.util.is_ccw(polygon.interiors[0].coords)
Beispiel #54
0
    def test_simplify(self):

        for file_name in ['2D/cycloidal.dxf',
                          '2D/125_cycloidal.DXF',
                          '2D/spline_1.dxf']:

            original = g.get_mesh(file_name)

            split = original.split()

            assert g.np.allclose(original.area,
                                 sum(i.area for i in split))

            for drawing in split:
                # we split so there should be only one polygon per drawing now
                assert len(drawing.polygons_full) == 1
                polygon = drawing.polygons_full[0]
                arc_count = sum(int(type(i).__name__ == 'Arc')
                                for i in drawing.entities)

                self.polygon_simplify(polygon=polygon,
                                      arc_count=arc_count)
Beispiel #55
0
    def test_vhacd(self):
        if not g.trimesh.interfaces.vhacd.exists:
            g.log.warning('not testing convex decomposition (no vhacd)!')
            return

        g.log.info('testing convex decomposition using vhacd')

        mesh = g.get_mesh('bunny.ply')

        # run a convex decomposition using vhacd
        decomposed = mesh.convex_decomposition(maxhulls=10)

        # it should return the correct number of meshes
        assert len(decomposed) == 10

        # make sure everything is convex
        # also this will fail if the type is returned incorrectly
        assert all(i.is_convex for i in decomposed)

        # make sure every result is actually a volume
        # ie watertight, consistent winding, positive nonzero volume
        assert all(i.is_volume for i in decomposed)
Beispiel #56
0
    def test_duck(self):
        scene = g.get_mesh('Duck.glb')

        # should have one mesh
        assert len(scene.geometry) == 1

        # get the mesh
        geom = next(iter(scene.geometry.values()))
        # should not be watertight
        assert not geom.is_volume
        # make sure export doesn't crash
        export = scene.export(file_type='glb')
        assert len(export) > 0
        # check a roundtrip
        reloaded = g.trimesh.load(g.trimesh.util.wrap_as_stream(export),
                                  file_type='glb')
        # make basic assertions
        g.scene_equal(scene, reloaded)

        # if we merge ugly it should now be watertight
        geom.merge_vertices(textured=False)
        assert geom.is_volume
Beispiel #57
0
    def test_fill_holes(self):
        for mesh_name in ['unit_cube.STL',
                          'machinist.XAML',
                          'round.stl',
                          'quadknot.obj']:
            mesh = g.get_mesh(mesh_name)
            if not mesh.is_watertight:
                continue
            mesh.faces = mesh.faces[1:-1]
            assert not mesh.is_watertight
            assert not mesh.is_volume

            # color some faces
            g.trimesh.repair.broken_faces(mesh,
                                          color=[255, 0, 0, 255])

            # run the fill holes operation
            mesh.fill_holes()
            # should be a superset of the last two
            assert mesh.is_volume
            assert mesh.is_watertight
            assert mesh.is_winding_consistent
Beispiel #58
0
    def test_face_attributes(self):
        """
        Test writing face attributes to a ply, by reading them back and asserting the
        written attributes array matches
        """

        m = g.get_mesh('box.STL')
        test_1d_attribute = g.np.copy(m.face_angles[:, 0])
        test_nd_attribute = g.np.copy(m.face_angles)
        m.face_attributes['test_1d_attribute'] = test_1d_attribute
        m.face_attributes['test_nd_attribute'] = test_nd_attribute

        export = m.export(file_type='ply')
        reconstructed = g.trimesh.load(g.trimesh.util.wrap_as_stream(export),
                                       file_type='ply')

        face_attributes = reconstructed.metadata['ply_raw']['face']['data']
        result_1d = face_attributes['test_1d_attribute']
        result_nd = face_attributes['test_nd_attribute']['f1']

        g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
        g.np.testing.assert_almost_equal(result_nd, test_nd_attribute)
Beispiel #59
0
    def test_hash(self):
        setup = 'import numpy, trimesh;'
        setup += 'd = numpy.random.random((10000,3));'
        setup += 't = trimesh.caching.tracked_array(d)'

        count = 10000

        mt = g.timeit.timeit(setup=setup,
                             stmt='t._modified_m=True;t.md5()',
                             number=count)
        ct = g.timeit.timeit(setup=setup,
                             stmt='t._modified_c=True;t.crc()',
                             number=count)
        xt = g.timeit.timeit(setup=setup,
                             stmt='t._modified_x=True;t.fast_hash()',
                             number=count)

        m = g.get_mesh('featuretype.STL')
        # log result values
        g.log.info('\nResult\nMD5:\n{}\nCRC:\n{}\nXX:\n{}'.format(
            m.vertices.md5(),
            m.vertices.crc(),
            m.vertices.fast_hash()))

        # crc should always be faster than MD5
        g.log.info('\nTime\nMD5:\n{}\nCRC:\n{}\nXX:\n{}'.format(
            mt, ct, xt))

        # CRC should be faster than MD5
        # this is NOT true if you blindly call adler32
        # but our speed check on import should assure this
        assert ct < mt

        # xxhash should be faster than CRC and MD5
        # it is sometimes slower on Windows/Appveyor TODO: figure out why
        if g.trimesh.caching.xxhash is not None and g.platform.system() == 'Linux':
            assert xt < mt
            assert xt < ct
Beispiel #60
0
    def test_cap_coplanar(self):
        # check to see if we handle capping with
        # existing coplanar faces correctly

        try:
            from triangle import triangulate  # NOQA
        except BaseException as E:
            if g.all_dep:
                raise E
            else:
                return

        s = g.get_mesh('cap.zip')
        mesh = next(iter(s.geometry.values()))

        plane_origin = [0, 0, 5000]
        plane_normal = [0, 0, -1]

        assert mesh.is_watertight
        newmesh = mesh.slice_plane(plane_origin=plane_origin,
                                   plane_normal=plane_normal,
                                   cap=True)
        assert newmesh.is_watertight