Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ASCII check and add types #17

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ repos:
- id: debug-statements
- id: trailing-whitespace

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
hooks:
- id: mypy
additional_dependencies: [numpy>=2.0, npt-promote==0.1]


- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.14.0
hooks:
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ classifiers = [
"Programming Language :: Python :: 3.12"
]
dependencies = [
"pyvista",
"numpy"
]
description = "Read in STL files"
Expand All @@ -43,6 +42,11 @@ MACOSX_DEPLOYMENT_TARGET = "10.14" # Needed for full C++17 support on MacOS
quiet-level = 3
skip = '*.cxx,*.h,*.gif,*.png,*.jpg,*.js,*.html,*.doctree,*.ttf,*.woff,*.woff2,*.eot,*.mp4,*.inv,*.pickle,*.ipynb,flycheck*,./.git/*,./.hypothesis/*,*.yml,./doc/build/*,./doc/images/*,./dist/*,*~,.hypothesis*,*.cpp,*.c'

[tool.mypy]
plugins = ["numpy.typing.mypy_plugin", 'npt_promote']
# disable_error_code = ['assignment', 'index', 'misc']
strict = true

[tool.pytest.ini_options]
filterwarnings = [
# bogus numpy ABI warning (see numpy/#432)
Expand Down
1 change: 1 addition & 0 deletions src/stl_reader/py.typed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
partial
34 changes: 28 additions & 6 deletions src/stl_reader/reader.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
"""Read a STL file using a wrapper of https://github.com/aki5/libstl."""

from typing import TYPE_CHECKING, Tuple

import numpy as np
import numpy.typing as npt

from stl_reader import stl_reader as _stlfile_wrapper

if TYPE_CHECKING:
from pyvista.core.pointset import PolyData


def _check_stl_ascii(filename: str) -> bool:
"""Check if a STL is ASCII."""
with open(filename, "rb") as fid:
return fid.read(5) == b"solid"

def _polydata_from_faces(points, faces):

def _polydata_from_faces(points: npt.NDArray[float], faces: npt.NDArray[int]) -> "PolyData":
"""Generate a polydata from a faces array containing no padding and all triangles.

This is a more efficient way of instantiating PolyData from point and face
Expand All @@ -20,7 +32,7 @@ def _polydata_from_faces(points, faces):

"""
try:
import pyvista as pv
from pyvista.core.pointset import PolyData
except ModuleNotFoundError:
raise ModuleNotFoundError(
"To use this functionality, install PyVista with:\n\npip install pyvista"
Expand All @@ -33,14 +45,14 @@ def _polydata_from_faces(points, faces):

# zero copy polydata creation
offset = np.arange(0, faces.size + 1, faces.shape[1], dtype=ID_TYPE)
pdata = pv.PolyData()
pdata = PolyData()
pdata.points = points
pdata.faces = CellArray.from_arrays(offset, faces)

return pdata


def read(filename):
def read(filename: str) -> Tuple[npt.NDArray[np.float32], npt.NDArray[np.uint32]]:
"""
Read a binary STL file and returns the vertices and points.

Expand Down Expand Up @@ -87,10 +99,13 @@ def read(filename):
[9005998, 9005999, 9005995]], dtype=uint32)

"""
if _check_stl_ascii(filename):
raise RuntimeError("stl-reader only supports binary STL files")

return _stlfile_wrapper.get_stl_data(filename)


def read_as_mesh(filename):
def read_as_mesh(filename: str) -> "PolyData":
"""
Read a binary STL file and return it as a mesh.

Expand Down Expand Up @@ -133,5 +148,12 @@ def read_as_mesh(filename):
Requires the ``pyvista`` library.

"""
try:
from pyvista import ID_TYPE
except ModuleNotFoundError:
raise ModuleNotFoundError(
"To use this functionality, install PyVista with:\n\npip install pyvista"
)
vertices, indices = read(filename)
return _polydata_from_faces(vertices, indices)
indices_int = indices.astype(ID_TYPE, copy=False)
return _polydata_from_faces(vertices, indices_int)
6 changes: 6 additions & 0 deletions src/stl_reader/stl_reader.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import Tuple

import numpy as np
import numpy.typing as npt

def get_stl_data(filename: str) -> Tuple[npt.NDArray[np.float32], npt.NDArray[np.uint32]]: ...
142 changes: 142 additions & 0 deletions tests/sphere_ascii.stl
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
solid Visualization Toolkit generated SLA File
facet normal 0 0.93417234926898851 -0.35682211515159623
outer loop
vertex 0 0.52573114633560181 -0.85065090656280518
vertex -0.52573114633560181 0.85065090656280518 0
vertex 0.52573114633560181 0.85065090656280518 0
endloop
endfacet
facet normal 0 0.93417234926898851 0.35682211515159623
outer loop
vertex 0 0.52573114633560181 0.85065090656280518
vertex 0.52573114633560181 0.85065090656280518 0
vertex -0.52573114633560181 0.85065090656280518 0
endloop
endfacet
facet normal -0.35682211515159623 0 0.93417234926898851
outer loop
vertex 0 0.52573114633560181 0.85065090656280518
vertex -0.85065090656280518 0 0.52573114633560181
vertex 0 -0.52573114633560181 0.85065090656280518
endloop
endfacet
facet normal 0.35682211515159623 -0 0.93417234926898851
outer loop
vertex 0 0.52573114633560181 0.85065090656280518
vertex 0 -0.52573114633560181 0.85065090656280518
vertex 0.85065090656280518 0 0.52573114633560181
endloop
endfacet
facet normal 0.35682211515159623 0 -0.93417234926898851
outer loop
vertex 0 0.52573114633560181 -0.85065090656280518
vertex 0.85065090656280518 0 -0.52573114633560181
vertex 0 -0.52573114633560181 -0.85065090656280518
endloop
endfacet
facet normal -0.35682211515159623 0 -0.93417234926898851
outer loop
vertex 0 0.52573114633560181 -0.85065090656280518
vertex 0 -0.52573114633560181 -0.85065090656280518
vertex -0.85065090656280518 0 -0.52573114633560181
endloop
endfacet
facet normal 0 -0.93417234926898851 0.35682211515159623
outer loop
vertex 0 -0.52573114633560181 0.85065090656280518
vertex -0.52573114633560181 -0.85065090656280518 0
vertex 0.52573114633560181 -0.85065090656280518 0
endloop
endfacet
facet normal -0 -0.93417234926898851 -0.35682211515159623
outer loop
vertex 0 -0.52573114633560181 -0.85065090656280518
vertex 0.52573114633560181 -0.85065090656280518 0
vertex -0.52573114633560181 -0.85065090656280518 0
endloop
endfacet
facet normal -0.93417234926898851 0.35682211515159623 0
outer loop
vertex -0.52573114633560181 0.85065090656280518 0
vertex -0.85065090656280518 0 -0.52573114633560181
vertex -0.85065090656280518 0 0.52573114633560181
endloop
endfacet
facet normal -0.93417234926898851 -0.35682211515159623 -0
outer loop
vertex -0.52573114633560181 -0.85065090656280518 0
vertex -0.85065090656280518 0 0.52573114633560181
vertex -0.85065090656280518 0 -0.52573114633560181
endloop
endfacet
facet normal 0.93417234926898851 0.35682211515159623 0
outer loop
vertex 0.52573114633560181 0.85065090656280518 0
vertex 0.85065090656280518 0 0.52573114633560181
vertex 0.85065090656280518 0 -0.52573114633560181
endloop
endfacet
facet normal 0.93417234926898851 -0.35682211515159623 0
outer loop
vertex 0.52573114633560181 -0.85065090656280518 0
vertex 0.85065090656280518 0 -0.52573114633560181
vertex 0.85065090656280518 0 0.52573114633560181
endloop
endfacet
facet normal -0.57735026918962573 0.57735026918962573 0.57735026918962573
outer loop
vertex 0 0.52573114633560181 0.85065090656280518
vertex -0.52573114633560181 0.85065090656280518 0
vertex -0.85065090656280518 0 0.52573114633560181
endloop
endfacet
facet normal 0.57735026918962573 0.57735026918962573 0.57735026918962573
outer loop
vertex 0 0.52573114633560181 0.85065090656280518
vertex 0.85065090656280518 0 0.52573114633560181
vertex 0.52573114633560181 0.85065090656280518 0
endloop
endfacet
facet normal -0.57735026918962573 0.57735026918962573 -0.57735026918962573
outer loop
vertex 0 0.52573114633560181 -0.85065090656280518
vertex -0.85065090656280518 0 -0.52573114633560181
vertex -0.52573114633560181 0.85065090656280518 0
endloop
endfacet
facet normal 0.57735026918962573 0.57735026918962573 -0.57735026918962573
outer loop
vertex 0 0.52573114633560181 -0.85065090656280518
vertex 0.52573114633560181 0.85065090656280518 0
vertex 0.85065090656280518 0 -0.52573114633560181
endloop
endfacet
facet normal -0.57735026918962573 -0.57735026918962573 -0.57735026918962573
outer loop
vertex 0 -0.52573114633560181 -0.85065090656280518
vertex -0.52573114633560181 -0.85065090656280518 0
vertex -0.85065090656280518 0 -0.52573114633560181
endloop
endfacet
facet normal 0.57735026918962573 -0.57735026918962573 -0.57735026918962573
outer loop
vertex 0 -0.52573114633560181 -0.85065090656280518
vertex 0.85065090656280518 0 -0.52573114633560181
vertex 0.52573114633560181 -0.85065090656280518 0
endloop
endfacet
facet normal -0.57735026918962573 -0.57735026918962573 0.57735026918962573
outer loop
vertex 0 -0.52573114633560181 0.85065090656280518
vertex -0.85065090656280518 0 0.52573114633560181
vertex -0.52573114633560181 -0.85065090656280518 0
endloop
endfacet
facet normal 0.57735026918962573 -0.57735026918962573 0.57735026918962573
outer loop
vertex 0 -0.52573114633560181 0.85065090656280518
vertex 0.52573114633560181 -0.85065090656280518 0
vertex 0.85065090656280518 0 0.52573114633560181
endloop
endfacet
endsolid
Binary file added tests/sphere_binary.stl
Binary file not shown.
85 changes: 64 additions & 21 deletions tests/test_reader.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,83 @@
"""Test stl_reader."""

import os
import numpy as np
import pytest
import pyvista as pv

import stl_reader


@pytest.fixture
def stlfile(tmpdir):
filename = tmpdir.join("tmp.stl")
pv.Sphere().save(filename)
return str(filename)
try:
import pyvista as pv

PYVISTA_INSTALLED = True
except ImportError:
PYVISTA_INSTALLED = False

THIS_PATH = os.path.dirname(os.path.abspath(__file__))
TEST_FILE_ASCII = os.path.join(THIS_PATH, "sphere_ascii.stl")
TEST_FILE_BINARY = os.path.join(THIS_PATH, "sphere_binary.stl")

EXPECTED_POINTS = np.array(
[
[0.0, 0.52573115, -0.8506509],
[-0.52573115, 0.8506509, 0.0],
[0.52573115, 0.8506509, 0.0],
[0.0, 0.52573115, 0.8506509],
[-0.8506509, 0.0, 0.52573115],
[0.0, -0.52573115, 0.8506509],
[0.8506509, 0.0, 0.52573115],
[0.8506509, 0.0, -0.52573115],
[0.0, -0.52573115, -0.8506509],
[-0.8506509, 0.0, -0.52573115],
[-0.52573115, -0.8506509, 0.0],
[0.52573115, -0.8506509, 0.0],
],
dtype=np.float32,
)

@pytest.fixture
def stlfile_ascii(tmpdir):
filename = tmpdir.join("tmp.stl")
pv.Sphere().save(filename, binary=False)
return str(filename)

EXPECTED_FACES = np.array(
[
[0, 1, 2],
[3, 2, 1],
[3, 4, 5],
[3, 5, 6],
[0, 7, 8],
[0, 8, 9],
[5, 10, 11],
[8, 11, 10],
[1, 9, 4],
[10, 4, 9],
[2, 6, 7],
[11, 7, 6],
[3, 1, 4],
[3, 6, 2],
[0, 9, 1],
[0, 2, 7],
[8, 10, 9],
[8, 7, 11],
[5, 4, 10],
[5, 11, 6],
],
dtype=np.uint32,
)

def test_read_binary(stlfile):
pv_mesh = pv.read(stlfile)

points, ind = stl_reader.read(stlfile)
assert np.allclose(pv_mesh.points, points)
assert np.allclose(pv_mesh._connectivity_array, ind.ravel())
def test_read_binary() -> None:
points, ind = stl_reader.read(TEST_FILE_BINARY)
assert np.allclose(EXPECTED_POINTS, points)
assert np.allclose(EXPECTED_FACES, ind)


def test_read_ascii(stlfile_ascii):
def test_read_ascii() -> None:
with pytest.raises(RuntimeError):
stl_reader.read(stlfile_ascii)
stl_reader.read(TEST_FILE_ASCII)


def test_read_as_mesh(stlfile):
pv_mesh = pv.read(stlfile)
@pytest.mark.skipif(not PYVISTA_INSTALLED, reason="Requires PyVista") # type: ignore
def test_read_as_mesh() -> None:
pv_mesh = pv.read(TEST_FILE_BINARY)

stl_mesh = stl_reader.read_as_mesh(stlfile)
stl_mesh = stl_reader.read_as_mesh(TEST_FILE_BINARY)
assert pv_mesh == stl_mesh
Loading