Skip to content

Commit d9d9d6d

Browse files
committed
docs: add docstrings and fix ruff warnings
1 parent 5df1d9d commit d9d9d6d

6 files changed

Lines changed: 236 additions & 12 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ __pycache__/
33
*.egg-info/
44
dist/
55
build/
6+
.coverage
67

78
*.stl
89
*.obj

edge_mender/edge_mender.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def __init__(self, mesh: trimesh.Trimesh, *, debug: bool = False) -> None:
4444
def validate(self, *, spacing: tuple[float, float, float]) -> None:
4545
"""Validate that the mesh is a valid voxel boundary mesh before repair.
4646
47+
Parameters
48+
----------
49+
spacing : tuple[float, float, float]
50+
The spacing of the mesh in each dimension. This is used to check that the
51+
face areas are uniform and that the angles are correct.
52+
4753
Raises
4854
------
4955
ValueError
@@ -83,16 +89,42 @@ def validate(self, *, spacing: tuple[float, float, float]) -> None:
8389
msg = f"WARNING: Mesh has {unique_areas} unique non-uniform face areas."
8490
raise ValueError(msg)
8591

86-
def find_non_manifold_edges(self) -> tuple[NDArray, NDArray, NDArray]:
92+
def find_non_manifold_edges(
93+
self,
94+
) -> tuple[NDArray[np.int64], NDArray[np.int64], NDArray[np.int64]]:
95+
"""Find non-manifold edges within the mesh.
96+
97+
Non-manifold edges are defined as edges shared by 4 faces.
98+
99+
Returns
100+
-------
101+
non_manifold_faces : NDArray[np.int64]
102+
An (n, 4) array of the four face indices for each non-manifold edge.
103+
non_manifold_vertices : NDArray[np.int64]
104+
An (n, 2) array of the two vertex indices for each non-manifold edge.
105+
non_manifold_edges : NDArray[np.int64]
106+
An (n,) array of the edge indices for each non-manifold edge.
107+
108+
Raises
109+
------
110+
ValueError
111+
If there is a problem with the edge face lookup.
112+
"""
113+
# Find all unique edges and their face counts
87114
unique_edges, counts = np.unique(
88115
self.mesh.faces_unique_edges.flatten(),
89116
return_counts=True,
90117
)
118+
# Find the edges that are shared by 4 faces
91119
edges = unique_edges[counts == NON_MANIFOLD_EDGE_FACE_COUNT]
120+
121+
# Get the vertices for each edge
92122
vertices = self.mesh.edges_unique[edges]
93123

124+
# Get the faces for each edge
94125
distance_check, edge_index = self.mesh.edges_sorted_tree.query(
95-
vertices, k=NON_MANIFOLD_EDGE_FACE_COUNT
126+
vertices,
127+
k=NON_MANIFOLD_EDGE_FACE_COUNT,
96128
)
97129
if np.any(distance_check):
98130
msg = "Problem with edge face lookup"

edge_mender/tests/test_edge_mender.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
"""Test major functions in the EdgeMender class."""
2+
13
import pytest
24
import trimesh
5+
from numpy.typing import NDArray
36

47
from edge_mender.data_factory import DataFactory
58
from edge_mender.edge_mender import EdgeMender
69
from edge_mender.mesh_generator import MeshGenerator
710

811

9-
@pytest.mark.parametrize("spacing", [[1, 1, 1], [1.25, 0.5, 0.25]])
12+
@pytest.mark.parametrize("spacing", [(1, 1, 1), (1.25, 0.5, 0.25)])
1013
@pytest.mark.parametrize(
1114
"data",
1215
[
@@ -20,22 +23,25 @@
2023
DataFactory.checkerboard(),
2124
],
2225
)
23-
def test_validate(data, spacing):
26+
def test_validate(data: NDArray, spacing: tuple[float, float, float]) -> None:
27+
"""Test that the validate function works for valid meshes."""
2428
mesh = MeshGenerator.to_mesh_surface_nets(data)
2529
mesh.vertices *= spacing
2630
mender = EdgeMender(mesh)
2731
mender.validate(spacing=spacing)
2832

2933

30-
def test_validate_fail_normals():
34+
def test_validate_fail_normals() -> None:
35+
"""Test that the validate function fails for non-axis-aligned face normals."""
3136
# Pyramid with non-axis-aligned face normals
3237
mesh = trimesh.creation.cone(1, 1, sections=3)
3338
mender = EdgeMender(mesh)
3439
with pytest.raises(ValueError, match="non-axis-aligned face normals"):
3540
mender.validate(spacing=(1, 1, 1))
3641

3742

38-
def test_validate_fail_angles():
43+
def test_validate_fail_angles() -> None:
44+
"""Test that the validate function fails for non-standard degree angles."""
3945
mesh = trimesh.creation.box()
4046
# Stretch the box to create bad angles
4147
mesh.vertices *= [1, 1, 1.25]
@@ -44,7 +50,8 @@ def test_validate_fail_angles():
4450
mender.validate(spacing=(1, 1, 1))
4551

4652

47-
def test_validate_fail_areas():
53+
def test_validate_fail_areas() -> None:
54+
"""Test that the validate function fails for non-uniform face areas."""
4855
# Subdivide everything except one face to make the faces larger
4956
mesh = trimesh.creation.box().subdivide(list(range(10)))
5057
mender = EdgeMender(mesh)

edge_mender/tests/test_geometry_helper.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Test the GeometryHelper class."""
2+
13
import numpy as np
24
import pytest
35

@@ -13,10 +15,17 @@
1315
([0, 0], [0, 1], [-1, 0], True),
1416
([0, 0], [1, 0], [1, 1], True),
1517
([0, 0], [1, 0], [1, -1], False),
16-
([0, 0], [1, 0], [2, 0], ValueError),
18+
([0, 0], [1, 0], [2, 0], "Point is on the line"),
1719
],
1820
)
19-
def test_is_left(line_point, line_direction, test_point, expected):
21+
def test_is_left(
22+
line_point: list[int],
23+
line_direction: list[int],
24+
test_point: list[int],
25+
*,
26+
expected: bool | str,
27+
) -> None:
28+
"""Test GeometryHelper.is_left."""
2029
if isinstance(expected, bool):
2130
assert (
2231
GeometryHelper.is_left(
@@ -27,7 +36,7 @@ def test_is_left(line_point, line_direction, test_point, expected):
2736
== expected
2837
)
2938
else:
30-
with pytest.raises(expected):
39+
with pytest.raises(ValueError, match=expected):
3140
GeometryHelper.is_left(
3241
line_point=np.array(line_point),
3342
line_direction=np.array(line_direction),
@@ -50,7 +59,13 @@ def test_is_left(line_point, line_direction, test_point, expected):
5059
([0, 1, 1], [1, 1, 1], [1, 0, 0], 180),
5160
],
5261
)
53-
def test_angle_between_point_and_ray(point, ray_point, ray_dir, expected_angle):
62+
def test_angle_between_point_and_ray(
63+
point: list[int],
64+
ray_point: list[int],
65+
ray_dir: list[int],
66+
expected_angle: int,
67+
) -> None:
68+
"""Test GeometryHelper.angle_between_point_and_ray."""
5469
angle = GeometryHelper.angle_between_point_and_ray(
5570
point=np.array(point),
5671
ray_point=np.array(ray_point),
@@ -69,7 +84,15 @@ def test_angle_between_point_and_ray(point, ray_point, ray_dir, expected_angle):
6984
([0, 0], [1, 0], [1, 0], [-1, 0], "Colinear"),
7085
],
7186
)
72-
def test_rays_intersect(point_1, normal_1, point_2, normal_2, expected):
87+
def test_rays_intersect(
88+
point_1: list[int],
89+
normal_1: list[int],
90+
point_2: list[int],
91+
normal_2: list[int],
92+
*,
93+
expected: bool | str,
94+
) -> None:
95+
"""Test GeometryHelper.rays_intersect."""
7396
if isinstance(expected, bool):
7497
assert (
7598
GeometryHelper.rays_intersect(

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ test = [
2525
]
2626
dev = [
2727
"pytest>=8.4.2",
28+
"pytest-cov>=7.0.0",
2829
"ruff>=0.14.1",
2930
"uv>=0.9.5",
3031
"cython>=3.1.5",
@@ -55,3 +56,6 @@ unfixable = []
5556

5657
[tool.ruff.lint.pydocstyle]
5758
convention = "numpy"
59+
60+
[tool.ruff.lint.per-file-ignores]
61+
"*/tests/*" = ["S101"]

0 commit comments

Comments
 (0)