File: test_convex.py

package info (click to toggle)
trimesh 4.5.1-3
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 33,416 kB
  • sloc: python: 35,596; makefile: 96; javascript: 85; sh: 38
file content (107 lines) | stat: -rw-r--r-- 3,936 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
try:
    from . import generic as g
except BaseException:
    import generic as g


class ConvexTest(g.unittest.TestCase):
    def test_convex(self):
        # store (true is_convex, mesh) tuples
        meshes = [
            (False, g.get_mesh("featuretype.STL")),
            (False, g.get_mesh("quadknot.obj")),
            (True, g.get_mesh("unit_cube.STL")),
            (False, g.get_mesh("1002_tray_bottom.STL")),
            (True, g.trimesh.creation.icosphere()),
            (True, g.trimesh.creation.uv_sphere()),
            (True, g.trimesh.creation.box()),
            (True, g.trimesh.creation.cylinder(radius=1, height=10)),
            (True, g.trimesh.creation.capsule()),
            (
                False,
                (
                    g.trimesh.creation.box(extents=[1, 1, 1])
                    + g.trimesh.creation.box(bounds=[[10, 10, 10], [12, 12, 12]])
                ),
            ),
        ]

        for is_convex, mesh in meshes:
            assert mesh.is_watertight

            # mesh should match it's true convexity
            assert mesh.is_convex == is_convex

            hulls = []
            volume = []

            # transform the mesh around space and check volume
            # use repeatable transforms to avoid spurious failures
            for T in g.transforms[::2]:
                permutated = mesh.copy()
                permutated.apply_transform(T)
                hulls.append(permutated.convex_hull)
                volume.append(hulls[-1].volume)

            # which of the volumes are close to the median volume
            close = g.np.isclose(
                volume, g.np.median(volume), atol=mesh.bounding_box.volume / 1000
            )

            if g.platform.system() == "Linux":
                # on linux the convex hulls are pretty robust
                close_ok = close.all()
            else:
                # on windows sometimes there is flaky numerical weirdness
                # which causes spurious CI failures, so use softer metric
                # for success: here of 90% of values are close to the median
                # then declare everything hunky dory
                ratio = close.sum() / float(len(close))
                close_ok = ratio > 0.9

            if not close_ok:
                g.log.error(f"volume inconsistent: {volume}")
                raise ValueError(
                    "volume is inconsistent on {}".format(mesh.metadata["file_name"])
                )
            assert min(volume) > 0.0

            if not all(i.is_winding_consistent for i in hulls):
                raise ValueError(
                    "mesh %s reported bad winding on convex hull!",
                    mesh.metadata["file_name"],
                )

            if not all(i.is_convex for i in hulls):
                raise ValueError(
                    "mesh %s reported non-convex convex hull!", mesh.metadata["file_name"]
                )

    def test_primitives(self):
        for prim in [
            g.trimesh.primitives.Sphere(),
            g.trimesh.primitives.Cylinder(),
            g.trimesh.primitives.Box(),
        ]:
            assert prim.is_convex
            # convex things should have hulls of the same volume
            # convert to mesh to get tessellated volume rather than
            # analytic primitive volume
            tess = prim.to_mesh()
            assert g.np.isclose(tess.convex_hull.volume, tess.volume)

    def test_projections(self):
        # check the vertex projection onto adjacent face plane
        # this is used to calculate convexity
        for m in g.get_meshes(4):
            assert len(m.face_adjacency_projections) == (len(m.face_adjacency))

    def test_truth(self):
        # check a non-watertight mesh
        m = g.get_mesh("not_convex.obj")
        assert not m.is_convex


if __name__ == "__main__":
    g.trimesh.util.attach_to_log()
    g.unittest.main()