From 5148b2a2172b8597d0c06c9aef9e59cfb72b3fab Mon Sep 17 00:00:00 2001 From: Emmett Lalish Date: Wed, 26 Jul 2023 09:14:46 -0700 Subject: [PATCH 1/3] adding test of properties --- .vscode/settings.json | 4 +++ README.md | 2 +- bindings/python/examples/sponge.py | 39 ++++++++++++++++++++++++++++++ bindings/python/manifold3d.cpp | 32 ++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 bindings/python/examples/sponge.py diff --git a/.vscode/settings.json b/.vscode/settings.json index bbbc28074..641a3ad5c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -140,4 +140,8 @@ "Verts" ], "javascript.format.semicolons": "insert", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.formatting.provider": "none", } \ No newline at end of file diff --git a/README.md b/README.md index ae1c4c1c6..3f3e03853 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ node test/manifold_test.js ### Python The CMake script will build the python binding `manifold3d` automatically. To -use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where +use the extension, please add `$BUILD_DIR/bindings/python` to your `PYTHONPATH`, where `$BUILD_DIR` is the build directory for CMake. Examples using the python binding can be found in `bindings/python/examples`. To see exported samples, run: ``` diff --git a/bindings/python/examples/sponge.py b/bindings/python/examples/sponge.py new file mode 100644 index 000000000..19f63b190 --- /dev/null +++ b/bindings/python/examples/sponge.py @@ -0,0 +1,39 @@ +from manifold3d import Manifold + + +def fractal(holes, hole, w, position, depth, maxDepth): + w /= 3 + holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0])) + if depth == maxDepth: + return + offsets = [ + [-w, -w], + [-w, 0.0], + [-w, w], + [0.0, w], + [w, w], + [w, 0.0], + [w, -w], + [0.0, -w], + ] + for offset in offsets: + fractal(holes, hole, w, position + offset, depth + 1, maxDepth) + + +def posColors(newProp, pos): + for i in [0, 1, 2]: + newProp[i] = (1 - pos[i]) / 2 + + +def run(n=1): + result = Manifold.cube([1, 1, 1], True) + holes = [] + fractal(holes, result, 1.0, [0.0, 0.0], 1, n) + + hole = Manifold.compose(holes) + + result -= hole + result -= hole.rotate([90, 0, 0]) + result -= hole.rotate([0, 90, 0]) + + return result.trim_by_plane([1, 1, 1], 0).setProperties(3, posColors).scale(100) diff --git a/bindings/python/manifold3d.cpp b/bindings/python/manifold3d.cpp index c53fe8644..254367c35 100644 --- a/bindings/python/manifold3d.cpp +++ b/bindings/python/manifold3d.cpp @@ -254,6 +254,31 @@ PYBIND11_MODULE(manifold3d, m) { "the user to choose their function with discretion." "\n\n" ":param warpFunc: A function that modifies a given vertex position.") + .def( + "set_properties", + [](Manifold &self, int newNumProp, + const std::function &, Float3, + const py::array_t &)> &f) { + const int oldNumProp = self.NumProp(); + return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f]( + float *newProps, + glm::vec3 v, + const float *oldProps) { + f(py::array(newNumProp, newProps), std::make_tuple(v.x, v.y, v.z), + py::array(oldNumProp, oldProps)); + }); + }, + py::arg("new_num_prop"), py::arg("f"), + "Create a new copy of this manifold with updated vertex properties " + "by supplying a function that takes the existing position and " + "properties as input. You may specify any number of output " + "properties, allowing creation and removal of channels. Note: " + "undefined behavior will result if you read past the number of input " + "properties or write past the number of output properties." + "\n\n" + ":param numProp: The new number of properties per vertex." + ":param propFunc: A function that modifies the properties of a given " + "vertex.") .def( "refine", [](Manifold &self, int n) { return self.Refine(n); }, py::arg("n"), @@ -428,6 +453,13 @@ PYBIND11_MODULE(manifold3d, m) { .def_static( "from_mesh", [](const MeshGL &mesh) { return Manifold(mesh); }, py::arg("mesh")) + .def_static( + "compose", + [](const std::vector &list) { + return Manifold::Compose(list); + }, + "combine several manifolds into one without checking for " + "intersections.") .def_static( "tetrahedron", []() { return Manifold::Tetrahedron(); }, "Constructs a tetrahedron centered at the origin with one vertex at " From acfd30eb87d125dde5344e0fd0b8390e3adcbdda Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 29 Jul 2023 21:24:14 +0800 Subject: [PATCH 2/3] python set_properties --- bindings/python/examples/run_all.py | 7 +++++-- bindings/python/examples/sponge.py | 10 +++++----- bindings/python/manifold3d.cpp | 13 +++++++++---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/bindings/python/examples/run_all.py b/bindings/python/examples/run_all.py index a53e483e0..d4bbb9502 100644 --- a/bindings/python/examples/run_all.py +++ b/bindings/python/examples/run_all.py @@ -36,10 +36,13 @@ mesh = model.to_mesh() if export_models: if mesh.vert_properties.shape[1] > 3: - vertices = np.hsplit(mesh.vert_properties, 3)[0] + vertices = mesh.vert_properties[:, :3] + colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8) else: vertices = mesh.vert_properties - meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts) + colors = None + meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts, + vertex_colors=colors) trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb') print(f'Exported model to {f}.glb') t1 = time() diff --git a/bindings/python/examples/sponge.py b/bindings/python/examples/sponge.py index 19f63b190..5c59f7758 100644 --- a/bindings/python/examples/sponge.py +++ b/bindings/python/examples/sponge.py @@ -3,7 +3,8 @@ def fractal(holes, hole, w, position, depth, maxDepth): w /= 3 - holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0])) + holes.append(hole.scale([w, w, 1.0]).translate( + [position[0], position[1], 0.0])) if depth == maxDepth: return offsets = [ @@ -20,9 +21,8 @@ def fractal(holes, hole, w, position, depth, maxDepth): fractal(holes, hole, w, position + offset, depth + 1, maxDepth) -def posColors(newProp, pos): - for i in [0, 1, 2]: - newProp[i] = (1 - pos[i]) / 2 +def posColors(pos, _): + return [1 - p / 2 for p in pos] + [1.0] def run(n=1): @@ -36,4 +36,4 @@ def run(n=1): result -= hole.rotate([90, 0, 0]) result -= hole.rotate([0, 90, 0]) - return result.trim_by_plane([1, 1, 1], 0).setProperties(3, posColors).scale(100) + return result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale(100) diff --git a/bindings/python/manifold3d.cpp b/bindings/python/manifold3d.cpp index 254367c35..e562d0549 100644 --- a/bindings/python/manifold3d.cpp +++ b/bindings/python/manifold3d.cpp @@ -257,15 +257,20 @@ PYBIND11_MODULE(manifold3d, m) { .def( "set_properties", [](Manifold &self, int newNumProp, - const std::function &, Float3, - const py::array_t &)> &f) { + const std::function( + Float3, const py::array_t &)> &f) { const int oldNumProp = self.NumProp(); return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f]( float *newProps, glm::vec3 v, const float *oldProps) { - f(py::array(newNumProp, newProps), std::make_tuple(v.x, v.y, v.z), - py::array(oldNumProp, oldProps)); + auto array = f(std::make_tuple(v.x, v.y, v.z), + py::array(oldNumProp, oldProps)); + if (array.ndim() != 1 || array.shape(0) != newNumProp) + throw std::runtime_error("Invalid vector shape, expected (" + + std::to_string(newNumProp) + ")"); + auto array_view = array.unchecked<1>(); + for (int i = 0; i < newNumProp; i++) newProps[i] = array_view(i); }); }, py::arg("new_num_prop"), py::arg("f"), From 389476179ed91049061824eb11d6534f7bba422f Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sat, 29 Jul 2023 21:39:07 +0800 Subject: [PATCH 3/3] fixed sponge example --- bindings/python/examples/sponge.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bindings/python/examples/sponge.py b/bindings/python/examples/sponge.py index 5c59f7758..86d58b66e 100644 --- a/bindings/python/examples/sponge.py +++ b/bindings/python/examples/sponge.py @@ -1,4 +1,5 @@ from manifold3d import Manifold +import numpy as np def fractal(holes, hole, w, position, depth, maxDepth): @@ -7,7 +8,7 @@ def fractal(holes, hole, w, position, depth, maxDepth): [position[0], position[1], 0.0])) if depth == maxDepth: return - offsets = [ + offsets = np.array([ [-w, -w], [-w, 0.0], [-w, w], @@ -16,7 +17,7 @@ def fractal(holes, hole, w, position, depth, maxDepth): [w, 0.0], [w, -w], [0.0, -w], - ] + ]) for offset in offsets: fractal(holes, hole, w, position + offset, depth + 1, maxDepth) @@ -28,7 +29,7 @@ def posColors(pos, _): def run(n=1): result = Manifold.cube([1, 1, 1], True) holes = [] - fractal(holes, result, 1.0, [0.0, 0.0], 1, n) + fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n) hole = Manifold.compose(holes)