Skip to content

Commit 71213d3

Browse files
committed
feat: Add project documentation, type stubs, Pyright configuration, and parsing examples.
1 parent a5b1055 commit 71213d3

13 files changed

Lines changed: 315 additions & 14 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.out
22
build/
3+
dist/
34
benchmark
45
*.ipc

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,10 @@ target_include_directories(rapidobj PRIVATE ${CMAKE_SOURCE_DIR}/src)
2323

2424
# Install the extension module
2525
install(TARGETS rapidobj LIBRARY DESTINATION .)
26+
27+
# Install typing metadata for IDE autocomplete and static checkers.
28+
install(FILES
29+
${CMAKE_SOURCE_DIR}/rapidobj.pyi
30+
${CMAKE_SOURCE_DIR}/py.typed
31+
DESTINATION .
32+
)

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 rapidobj contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## rapidobj
2+
3+
`rapidobj` is a fast Wavefront OBJ parser exposed as a Python extension module
4+
using nanobind. It returns NumPy views for mesh data so parsing and data access
5+
stay efficient.
6+
7+
## Requirements
8+
9+
- Python 3.12+
10+
- CMake 3.18+
11+
- A C++17 compiler
12+
13+
## Install
14+
15+
From source:
16+
17+
```bash
18+
uv sync
19+
uv build
20+
python -m pip install dist/rapidobj-0.1.0-cp312-cp312-*.whl
21+
```
22+
23+
## Minimal Usage
24+
25+
```python
26+
from rapidobj import parse_obj
27+
28+
result = parse_obj("mesh.obj")
29+
if not result.ok:
30+
raise RuntimeError(result.error_message)
31+
32+
print(result.vertices.shape) # (V, 3)
33+
print(result.faces.shape) # (F, 3)
34+
print(result.texcoords.shape) # (T, 2)
35+
```
36+
37+
See `examples/` for runnable scripts.
38+
39+
## API
40+
41+
- `parse_obj(filename: str) -> ObjParseResult`
42+
- `ObjParseResult.ok: bool`
43+
- `ObjParseResult.error_message: str`
44+
- `ObjParseResult.vertex_count: int`
45+
- `ObjParseResult.normal_count: int`
46+
- `ObjParseResult.uv_count: int`
47+
- `ObjParseResult.shape_count: int`
48+
- `ObjParseResult.material_count: int`
49+
- `ObjParseResult.texture_paths: list[str]`
50+
- `ObjParseResult.vertices: np.ndarray`
51+
- `ObjParseResult.faces: np.ndarray`
52+
- `ObjParseResult.texcoords: np.ndarray`
53+
- `ObjParseResult.wedge_texcoord_indices: np.ndarray`
54+
- `ObjParseResult.wedge_material_ids: np.ndarray`
55+
56+
## Release Notes
57+
58+
Release workflow and checklist are documented in `RELEASE.md`.

RELEASE.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Release Checklist
2+
3+
## Versioning
4+
5+
Use semantic version tags: `vX.Y.Z`.
6+
7+
## Build and Validate
8+
9+
1. Clean old artifacts.
10+
- `rm -rf dist/`
11+
2. Build distributions.
12+
- `uv build`
13+
3. Validate metadata and artifacts.
14+
- `uvx --from twine twine check dist/*`
15+
4. Smoke test wheel in a clean environment.
16+
- Install wheel from `dist/`
17+
- Import `rapidobj`
18+
- Parse a small `.obj` file and assert `result.ok`
19+
20+
## GitHub Source Release
21+
22+
1. Push commit to `main`.
23+
2. Create and push tag.
24+
- `git tag vX.Y.Z`
25+
- `git push origin vX.Y.Z`
26+
3. Create GitHub release from the tag and include changelog notes.
27+
28+
## PyPI Publish
29+
30+
1. Upload validated artifacts:
31+
- `uvx --from twine twine upload dist/*`
32+
2. Verify package page metadata and install command.

benchmark_overhead.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
print("-" * 50)
2525

2626
times = []
27+
ret = None
2728
for i in range(iterations):
2829
start = time.perf_counter()
2930
ret = parse_obj(obj_path)
@@ -41,5 +42,7 @@
4142
print(f"Min: {min(times):.2f} ms")
4243
print(f"Max: {max(times):.2f} ms")
4344
print()
45+
if ret is None:
46+
raise RuntimeError("No iterations were run")
4447
print(f"Vertices: {ret.vertices.shape}")
4548
print(f"Faces: {ret.faces.shape}")

examples/parse_basic.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python3
2+
"""Minimal rapidobj parsing example."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
8+
from rapidobj import parse_obj
9+
10+
11+
def main() -> int:
12+
if len(sys.argv) != 2:
13+
print(f"Usage: {sys.argv[0]} <mesh.obj>")
14+
return 1
15+
16+
result = parse_obj(sys.argv[1])
17+
if not result.ok:
18+
print(f"Parse error: {result.error_message}")
19+
return 2
20+
21+
print(f"vertices: {result.vertices.shape}")
22+
print(f"faces: {result.faces.shape}")
23+
print(f"texcoords: {result.texcoords.shape}")
24+
print(f"materials: {result.material_count}")
25+
return 0
26+
27+
28+
if __name__ == "__main__":
29+
raise SystemExit(main())

examples/parse_error.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python3
2+
"""Minimal error handling example."""
3+
4+
from __future__ import annotations
5+
6+
from rapidobj import parse_obj
7+
8+
9+
def main() -> int:
10+
result = parse_obj("does-not-exist.obj")
11+
if result.ok:
12+
print("Unexpected success")
13+
return 1
14+
15+
print("Expected parse failure")
16+
print(result.error_message)
17+
return 0
18+
19+
20+
if __name__ == "__main__":
21+
raise SystemExit(main())

py.typed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

pyproject.toml

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@ name = "rapidobj"
33
version = "0.1.0"
44
description = "Fast OBJ parser with Python bindings"
55
readme = "README.md"
6+
license = { file = "LICENSE" }
67
requires-python = ">=3.12"
78
dependencies = [
89
"numpy",
910
]
11+
classifiers = [
12+
"Development Status :: 3 - Alpha",
13+
"Intended Audience :: Developers",
14+
"License :: OSI Approved :: MIT License",
15+
"Programming Language :: Python :: 3",
16+
"Programming Language :: Python :: 3.12",
17+
"Programming Language :: C++",
18+
"Topic :: Multimedia :: Graphics :: 3D Modeling",
19+
"Typing :: Typed",
20+
]
21+
22+
[project.urls]
23+
Homepage = "https://github.com/dhh/rapidobj-play"
24+
Repository = "https://github.com/dhh/rapidobj-play"
25+
Issues = "https://github.com/dhh/rapidobj-play/issues"
1026

1127
[dependency-groups]
1228
dev = [
@@ -23,11 +39,46 @@ requires = ["scikit-build-core>=0.10", "nanobind>=2.4.0"]
2339
build-backend = "scikit_build_core.build"
2440

2541
[tool.scikit-build]
26-
minimum-version = "build-system.requires"
42+
minimum-version = "0.12"
2743
build-dir = "build/{wheel_tag}"
44+
sdist.inclusion-mode = "manual"
45+
sdist.include = [
46+
"CMakeLists.txt",
47+
"LICENSE",
48+
"README.md",
49+
"RELEASE.md",
50+
"examples/parse_basic.py",
51+
"examples/parse_error.py",
52+
"py.typed",
53+
"pyproject.toml",
54+
"rapidobj.pyi",
55+
"src/rapidobj.hpp",
56+
"src/rapidobj_ext.cpp",
57+
]
58+
sdist.exclude = [
59+
".git/*",
60+
".envrc",
61+
".python-version",
62+
".venv/*",
63+
".venv/**",
64+
"benchmark",
65+
"benchmark.cpp",
66+
"benchmark.py",
67+
"benchmark_overhead.py",
68+
"build/*",
69+
"build/**",
70+
"dist",
71+
"dist/*",
72+
"dist/**",
73+
"feature_parity.py",
74+
"main.cpp",
75+
"main.py",
76+
"out.ipc",
77+
"sampling_points.py",
78+
"uv.lock",
79+
]
2880

2981
[tool.uv.sources]
30-
pymeshlab = { path = "../building-simp-mono/scripts/wheels/pymeshlab-2025.7-cp312-cp312-manylinux_2_35_x86_64.whl" }
3182
torch = [{ index = "pytorch-cu129", marker = "sys_platform == 'linux'" }]
3283
torchvision = [{ index = "pytorch-cu129", marker = "sys_platform == 'linux'" }]
3384

0 commit comments

Comments
 (0)