[python] fix warp callback (#674)

* [python] fix warp callback

I recently broke this making it a no-op due to
the vec reference becoming a by-value copy in python
callback. This fix provides a simple adapter like
we had before allowing python to return modified
values from the callback.

* update torus_knot.py demo to expose both warp modes

* add torus_knot.py to pytest

* use pytest in github CI

* add pytest to flake.nix
This commit is contained in:
Kyle Finn
2023-12-28 11:54:54 +13:00
committed by GitHub
parent 54fa822408
commit 8c77a6aa05
5 changed files with 45 additions and 16 deletions
+2 -1
View File
@@ -23,7 +23,7 @@ jobs:
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y libglm-dev libgtest-dev libassimp-dev git libtbb-dev libthrust-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
pip install trimesh
pip install trimesh pytest
- uses: actions/checkout@v3
with:
submodules: recursive
@@ -42,6 +42,7 @@ jobs:
./manifold_test
cd ../../
python3 bindings/python/examples/run_all.py -e
python3 -m pytest
- name: Coverage Report
# only do code coverage for default sequential backend, it seems that TBB
# backend will cause failure
@@ -1,6 +1,6 @@
# %%
import numpy as np
from manifold3d import *
import numpy as np
import pytest
# Creates a classic torus knot, defined as a string wrapping periodically
# around the surface of an imaginary donut. If p and q have a common
@@ -9,7 +9,7 @@ from manifold3d import *
# handling of triangles.
def run():
def run(warp_single=False):
# The number of times the thread passes through the donut hole.
p = 1
# The number of times the thread circles the donut.
@@ -72,16 +72,26 @@ def run():
m2[:, 3, 0] = majorRadius
m3 = ax_rotate(2, psi)
v = v[:, None, :] @ (m1 @ m2 @ m3)
return v[:, 0, :3]
v = v[:, None, :] @ m1 @ m2 @ m3
pts[:] = v[:, 0, :3]
def func_single(v):
pts = np.array(v)[None, :]
pts = func(pts)
return tuple(pts[0])
pts = np.array([v])
func(pts)
return pts[0]
return circle.revolve(int(m)).warp_batch(func)
# return circle.revolve(int(m)).warp(func_single)
if warp_single:
return circle.revolve(int(m)).warp(func_single)
else:
return circle.revolve(int(m)).warp_batch(func)
@pytest.mark.parametrize("warp_single", [True, False])
def test_warp(warp_single):
m = run(warp_single=warp_single)
assert m.volume() == pytest.approx(20785.76)
assert m.surface_area() == pytest.approx(11176.8)
assert m.genus() == 1
if __name__ == "__main__":
+16 -4
View File
@@ -271,8 +271,14 @@ NB_MODULE(manifold3d, m) {
return self.Rotate(v.x, v.y, v.z);
},
nb::arg("v"), manifold__rotate__v.c_str())
.def("warp", &Manifold::Warp, nb::arg("warp_func"),
manifold__warp__warp_func)
.def(
"warp",
[](const Manifold &self,
std::function<glm::vec3(glm::vec3)> warp_func) {
// need a wrapper because python cant modify a reference in-place
return self.Warp([&warp_func](glm::vec3 &v) { v = warp_func(v); });
},
nb::arg("warp_func"), manifold__warp__warp_func)
.def("warp_batch", &Manifold::WarpBatch, nb::arg("warp_func"),
manifold__warp_batch__warp_func)
.def(
@@ -610,8 +616,14 @@ NB_MODULE(manifold3d, m) {
cross_section__mirror__ax)
.def("transform", &CrossSection::Transform, nb::arg("m"),
cross_section__transform__m)
.def("warp", &CrossSection::Warp, nb::arg("warp_func"),
cross_section__warp__warp_func)
.def(
"warp",
[](const CrossSection &self,
std::function<glm::vec2(glm::vec2)> warp_func) {
// need a wrapper because python cant modify a reference in-place
return self.Warp([&warp_func](glm::vec2 &v) { v = warp_func(v); });
},
nb::arg("warp_func"), cross_section__warp__warp_func)
.def("warp_batch", &CrossSection::WarpBatch, nb::arg("warp_func"),
cross_section__warp_batch__warp_func)
.def("simplify", &CrossSection::Simplify, nb::arg("epsilon") = 1e-6,
+4 -1
View File
@@ -60,7 +60,7 @@
glm
ninja
(python39.withPackages
(ps: with ps; [ trimesh ]))
(ps: with ps; [ trimesh pytest ]))
gtest
]) ++ build-tools;
cmakeFlags = [
@@ -74,6 +74,7 @@
./manifold_test
cd ../../
PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python python3 bindings/python/examples/run_all.py
PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python python3 -m pytest
cd build
'';
};
@@ -155,12 +156,14 @@
];
checkInputs = [
trimesh
pytest
];
format = "pyproject";
dontUseCmakeConfigure = true;
doCheck = true;
checkPhase = ''
python3 bindings/python/examples/run_all.py
python3 -m pytest
'';
};
};
+3
View File
@@ -0,0 +1,3 @@
[pytest]
testpaths =
bindings/python/examples