-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from readyplayerme/feat/identify-border-vertices
refactor!: tests and fixtures
- Loading branch information
Showing
9 changed files
with
217 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""Custom types for meshops.""" | ||
from typing import Protocol, TypeAlias | ||
|
||
import numpy as np | ||
import numpy.typing as npt | ||
|
||
# trimesh uses int64 and float64 for its arrays. | ||
Indices: TypeAlias = npt.NDArray[np.int32] | npt.NDArray[np.int64] # Shape (i,) | ||
Vertices: TypeAlias = npt.NDArray[np.float32] | npt.NDArray[np.float64] # Shape (v, 3) | ||
Edges: TypeAlias = npt.NDArray[np.int32] | npt.NDArray[np.int64] # Shape (e, 2) | ||
Faces: TypeAlias = npt.NDArray[np.int32] | npt.NDArray[np.int64] # Shape (f, 3) | ||
|
||
|
||
class Mesh(Protocol): | ||
"""Structural type for a mesh class. | ||
Any class considered a mesh must structurally be compatible with this protocol. | ||
""" | ||
|
||
vertices: Vertices | ||
edges: Edges | ||
faces: Faces |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
"""Pytest fixtures for the whole repo.""" | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Module holding data-files for resource mocks.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
"""Pytest fixtures for meshops tests.""" | ||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def gltf_simple_file(): | ||
"""Return a path to a simple glTF file.""" | ||
from importlib.resources import files | ||
|
||
import tests.mocks | ||
|
||
return files(tests.mocks).joinpath("uv-seams.glb") |
15 changes: 15 additions & 0 deletions
15
tests/readyplayerme/meshops/integration/test_integration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from pathlib import Path | ||
|
||
import numpy as np | ||
|
||
import readyplayerme.meshops.mesh as mops | ||
|
||
|
||
def test_boundary_vertices_from_file(gltf_simple_file: str | Path): | ||
"""Test the integration of extracting mesh boundary vertices from a file.""" | ||
local_mesh = mops.read_mesh(gltf_simple_file) | ||
edges = local_mesh.edges | ||
boundary_vertices = mops.get_boundary_vertices(edges) | ||
assert np.array_equiv( | ||
np.sort(boundary_vertices), [0, 2, 4, 6, 7, 9, 10] | ||
), "The vertices returned by get_border_vertices do not match the expected vertices." |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
"""Pytest fixtures for meshops unit tests.""" | ||
from dataclasses import dataclass | ||
|
||
import numpy as np | ||
import numpy.typing as npt | ||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def mock_mesh(): | ||
"""Return a mocked instance of a mesh.""" | ||
|
||
@dataclass | ||
class MockMesh: | ||
vertices: npt.NDArray[np.float32] | ||
edges: npt.NDArray[np.int32] | ||
faces: npt.NDArray[np.int32] | ||
|
||
vertices = np.array( | ||
[ | ||
[-1.0, -1.0, 1.0], | ||
[-1.0, 1.0, 1.0], | ||
[-1.0, -1.0, -1.0], | ||
[-1.0, 1.0, -1.0], | ||
[1.0, -1.0, 1.0], | ||
[1.0, 1.0, 1.0], | ||
[1.0, -1.0, -1.0], | ||
[1.0, 1.0, -1.0], | ||
[1.0, 0.0, 1.0], | ||
[1.0, 0.0, -1.0], | ||
[1.0, 0.0, -1.0], | ||
] | ||
) | ||
edges = np.array( | ||
[ | ||
[9, 2], | ||
[2, 3], | ||
[3, 9], | ||
[3, 7], | ||
[7, 9], | ||
[9, 3], | ||
[7, 8], | ||
[8, 10], | ||
[10, 7], | ||
[8, 5], | ||
[5, 1], | ||
[1, 8], | ||
[3, 5], | ||
[5, 7], | ||
[7, 3], | ||
[1, 2], | ||
[2, 0], | ||
[0, 1], | ||
[3, 1], | ||
[1, 5], | ||
[5, 3], | ||
[9, 6], | ||
[6, 2], | ||
[2, 9], | ||
[1, 3], | ||
[3, 2], | ||
[2, 1], | ||
[7, 5], | ||
[5, 8], | ||
[8, 7], | ||
[8, 0], | ||
[0, 4], | ||
[4, 8], | ||
[10, 8], | ||
[8, 4], | ||
[4, 10], | ||
[8, 1], | ||
[1, 0], | ||
[0, 8], | ||
] | ||
) | ||
faces = np.array( | ||
[ | ||
[9, 2, 3], | ||
[3, 7, 9], | ||
[7, 8, 10], | ||
[8, 5, 1], | ||
[3, 5, 7], | ||
[1, 2, 0], | ||
[3, 1, 5], | ||
[9, 6, 2], | ||
[1, 3, 2], | ||
[7, 5, 8], | ||
[8, 0, 4], | ||
[10, 8, 4], | ||
[8, 1, 0], | ||
] | ||
) | ||
return MockMesh(vertices=vertices, edges=edges, faces=faces) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""Unit tests for the mesh module.""" | ||
import types | ||
from collections.abc import Callable | ||
from pathlib import Path | ||
from typing import Union, get_args, get_origin | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
from readyplayerme.meshops import mesh | ||
from readyplayerme.meshops.types import Mesh | ||
|
||
|
||
class TestReadMesh: | ||
"""Test suite for the mesh reader functions.""" | ||
|
||
@pytest.mark.parametrize( | ||
"filepath, expected", | ||
[("test.glb", mesh.read_gltf), (Path("test.glb"), mesh.read_gltf), ("test.gltf", mesh.read_gltf)], | ||
) | ||
def test_get_mesh_reader_glb(self, filepath: str | Path, expected: Callable[[str | Path], Mesh]): | ||
"""Test the get_mesh_reader function with a .glb file path.""" | ||
reader = mesh.get_mesh_reader(filepath) | ||
assert callable(reader), "The mesh reader should be a callable function." | ||
assert reader == expected, "The reader function for .glTF files should be read_gltf." | ||
|
||
@pytest.mark.parametrize("filepath", ["test", "test.obj", Path("test.stl"), "test.fbx", "test.abc", "test.ply"]) | ||
def test_get_mesh_reader_unsupported(self, filepath: str | Path): | ||
"""Test the get_mesh_reader function with an unsupported file format.""" | ||
with pytest.raises(NotImplementedError): | ||
mesh.get_mesh_reader(filepath) | ||
|
||
def test_read_mesh_gltf(self, gltf_simple_file: str | Path): | ||
"""Test the read_gltf function returns the expected type.""" | ||
result = mesh.read_gltf(gltf_simple_file) | ||
# Check the result has all the expected attributes and of the correct type. | ||
for attr in Mesh.__annotations__: | ||
assert hasattr(result, attr), f"The mesh class should have a '{attr}' attribute." | ||
# Find the type so we can use it as a second argument to isinstance. | ||
tp = get_origin(Mesh.__annotations__[attr]) | ||
if tp is Union or tp is types.UnionType: | ||
tp = get_args(Mesh.__annotations__[attr]) | ||
# Loop through the types in the union and check if the result is compatible with any of them. | ||
assert any( | ||
isinstance(getattr(result, attr), get_origin(t)) for t in tp | ||
), f"The '{attr}' attribute should be compatible with {tp}." | ||
else: | ||
assert isinstance( | ||
getattr(result, attr), tp | ||
), f"The '{attr}' attribute should be compatible with {Mesh.__annotations__[attr]}." | ||
|
||
|
||
def test_get_boundary_vertices(mock_mesh: Mesh): | ||
"""Test the get_boundary_vertices function returns the expected indices.""" | ||
boundary_vertices = mesh.get_boundary_vertices(mock_mesh.edges) | ||
|
||
assert np.array_equiv( | ||
np.sort(boundary_vertices), [0, 2, 4, 6, 7, 9, 10] | ||
), "The vertices returned by get_border_vertices do not match the expected vertices." |