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

Offset: Eliminate Dimples #669

Closed
wants to merge 16 commits into from
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/check_format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
source: '.'
exclude: '*/third_party'
extensions: 'h,cpp,js,ts,html'
clangFormatVersion: 12
clangFormatVersion: 11
- uses: psf/black@stable
with:
options: "--check --verbose"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ For more detailed documentation, please refer to the C++ API.

Contributions are welcome! A lower barrier contribution is to simply make a PR that adds a test, especially if it repros an issue you've found. Simply name it prepended with DISABLED_, so that it passes the CI. That will be a very strong signal to me to fix your issue. However, if you know how to fix it yourself, then including the fix in your PR would be much appreciated!

### Formatting

There is a formatting script `format.sh` that automatically formats everything.
It requires clang-format 11 and black formatter for python.

If you have clang-format installed but without clang-11, you can specify the
clang-format executable by setting the `CLANG_FORMAT` environment variable.

### Profiling

There is now basic support for the [Tracy profiler](https://github.com/wolfpld/tracy) for our tests.
Expand Down
11 changes: 11 additions & 0 deletions bindings/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifo
target_compile_features(manifold3d PUBLIC cxx_std_17)
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")

message(Python_EXECUTABLE = ${Python_EXECUTABLE})
add_custom_target(
autogen_docstrings
${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
BYPRODUCTS autogen_docstrings.inl
)
target_include_directories(manifold3d PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(autogen_docstrings manifold sdf polygon)
add_dependencies(manifold3d autogen_docstrings)

if(SKBUILD)
install(
TARGETS manifold3d
Expand Down
40 changes: 40 additions & 0 deletions bindings/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Python Bindings

## Autogenerated Doc-Strings

Doc-strings for the python API wrapper are generated by gen_docs.py
The script is run automatically during the build to keep python
doc strings fully up-to-date with c++ sources.

It scrapes documentation comments and c++ function signatures
from the manifold c++ sources, in order to generate a c++ header file
exposing the comments as string variables named by function names.
This allows python bindings to re-use the c++ comments directly.

Some snake-casing of params is applied for python use case.

---

When modifying the Manifold C++ sources, you may need to update
gen_docs.py. For example, top-level free functions are white-listed,
so if you add a new one, you will need to add it in gen_docs.py.

Similarly, the list of source files to parse is also white listed,
so if you define functions in new files that need python wrappers,
you will also need to up gen_docs.py.

To verify that python docs are correct after changes, you can
run the following commends from the manifold repo root:
```
pip install .
python -c 'import manifold3d; help(manifold3d)'
```

Alternateively you could generate stubs with roughly the same info
```
pip install nanobind-stubgen
pip install .
nanobind-stubgen manifold3d
```
It will emit some warnings and write a file `manifold3d.pyi`
which will show all the function signatures and docstrings.
101 changes: 101 additions & 0 deletions bindings/python/examples/all_apis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from manifold3d import *
import numpy as np


def all_root_level():
set_min_circular_angle(10)
set_min_circular_edge_length(1)
set_circular_segments(22)
n = get_circular_segments(1)
assert n == 22
poly = [[0, 0], [1, 0], [1, 1]]
tris = triangulate([poly])
tris = triangulate([np.array(poly)])


def all_cross_section():
poly = [[0, 0], [1, 0], [1, 1]]
c = CrossSection([np.array(poly)])
c = CrossSection([poly])
c = CrossSection() + c
a = c.area()
c = CrossSection.batch_hull([c, c.translate((1, 0))])
b = c.bounds()
c = CrossSection.circle(1)
cs = c.decompose()
m = c.extrude(1)
c = c.hull()
c = CrossSection.hull_points(poly)
c = CrossSection.hull_points(np.array(poly))
e = c.is_empty()
c = c.mirror((0, 1))
n = c.num_contour()
n = c.num_vert()
c = c.offset(1, JoinType.Round)
m = c.revolve()
c = c.rotate(90)
c = c.scale((2, 2))
c = c.simplify()
c = CrossSection.square((1, 1))
p = c.to_polygons()
c = c.transform([[1, 0, 0], [0, 1, 0]])
c = c.translate((1, 1))
c = c.warp(lambda p: (p[0] + 1, p[1] / 2))
c = c.warp_batch(lambda ps: ps * [1, 0.5] + [1, 0])


def all_manifold():
mesh = Manifold.sphere(1).to_mesh()
m = Manifold(mesh)
m = Manifold() + m
m = m.as_original()
m = Manifold.batch_hull([m, m.translate((0, 0, 1))])
b = m.bounding_box()
m = m.calculate_curvature(4, 5)
m = Manifold.compose([m, m.translate((5, 0, 0))])
m = Manifold.cube((1, 1, 1))
m = Manifold.cylinder(1, 1)
ms = m.decompose()
g = m.genus()
a = m.surface_area()
v = m.volume()
m = m.hull()
m = m.hull_points(mesh.vert_properties)
e = m.is_empty()
m = m.mirror((0, 0, 1))
n = m.num_edge()
n = m.num_prop()
n = m.num_prop_vert()
n = m.num_tri()
n = m.num_vert()
i = m.original_id()
p = m.precision()
c = m.project()
m = m.refine(2)
i = Manifold.reserve_ids(1)
m = m.scale((1, 2, 3))
m = m.set_properties(3, lambda pos, prop: pos)
c = m.slice(0.5)
m = Manifold.smooth(mesh, [0], [0.5])
m = Manifold.sphere(1)
m, n = m.split(m.translate((1, 0, 0)))
m, n = m.split_by_plane((0, 0, 1), 0)
e = m.status()
m = Manifold.tetrahedron()
mesh = m.to_mesh()
m = m.transform([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
m = m.translate((0, 0, 0))
m = m.trim_by_plane((0, 0, 1), 0)
m = m.warp(lambda p: (p[0] + 1, p[1] / 2, p[2] * 2))
m = m.warp_batch(lambda ps: ps * [1, 0.5, 2] + [1, 0, 0])


def run():
all_root_level()
all_cross_section()
all_manifold()
return Manifold()


if __name__ == "__main__":
run()
42 changes: 24 additions & 18 deletions bindings/python/examples/bricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,44 +28,50 @@


def brick():
return Manifold.cube(brick_length, brick_depth, brick_height)
return Manifold.cube([brick_length, brick_depth, brick_height])


def halfbrick():
return Manifold.cube((brick_length - mortar_gap) / 2, brick_depth, brick_height)
return Manifold.cube([(brick_length - mortar_gap) / 2, brick_depth, brick_height])


def row(length):
bricks = [
brick().translate((brick_length + mortar_gap) * x, 0, 0) for x in range(length)
brick().translate([(brick_length + mortar_gap) * x, 0, 0])
for x in range(length)
]
return Manifold(bricks)
return sum(bricks, Manifold())


def wall(length, height, alternate=0):
bricks = [
row(length).translate(
((z + alternate) % 2) * (brick_depth + mortar_gap),
0,
(brick_height + mortar_gap) * z,
[
((z + alternate) % 2) * (brick_depth + mortar_gap),
0,
(brick_height + mortar_gap) * z,
]
)
for z in range(height)
]
return Manifold(bricks)
return sum(bricks, Manifold())


def walls(length, width, height):
return Manifold(
return sum(
[
wall(length, height),
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
wall(width, height, 1).rotate([0, 0, 90]).translate([brick_depth, 0, 0]),
wall(length, height, 1).translate(
0, (width) * (brick_length + mortar_gap), 0
[0, (width) * (brick_length + mortar_gap), 0]
),
wall(width, height)
.rotate(0, 0, 90)
.translate((length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0),
]
.rotate([0, 0, 90])
.translate(
[(length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0]
),
],
Manifold(),
)


Expand All @@ -74,21 +80,21 @@ def floor(length, width):
if length > 1 and width > 1:
results.append(
floor(length - 1, width - 1).translate(
brick_depth + mortar_gap, brick_depth + mortar_gap, 0
[brick_depth + mortar_gap, brick_depth + mortar_gap, 0]
)
)
if length == 1 and width > 1:
results.append(row(width - 1).rotate(0, 0, 90))
if width == 1 and length > 1:
results.append(
row(length - 1).translate(
2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0
[2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0]
)
)
results.append(
halfbrick().translate(brick_depth + mortar_gap, brick_depth + mortar_gap, 0)
halfbrick().translate([brick_depth + mortar_gap, brick_depth + mortar_gap, 0])
)
return Manifold(results)
return sum(results, Manifold())


def run(width=10, length=10, height=10):
Expand Down
8 changes: 4 additions & 4 deletions bindings/python/examples/cube_with_dents.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@


def run(n=5, overlap=True):
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
a = Manifold.cube([n, n, 0.5]).translate([-0.5, -0.5, -0.5])

spheres = [
Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
Manifold.sphere(0.45 if overlap else 0.55, 50).translate([i, j, 0])
for i in range(n)
for j in range(n)
]
spheres = reduce(lambda a, b: a + b, spheres)
# spheres = reduce(lambda a, b: a + b, spheres)

return a - spheres
return a - sum(spheres, Manifold())
6 changes: 3 additions & 3 deletions bindings/python/examples/extrude.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ def run():
# extrude a polygon to create a manifold
extruded_polygon = cross_section.extrude(10.0)
eps = 0.001
observed_volume = extruded_polygon.get_volume()
observed_volume = extruded_polygon.volume()
expected_volume = 10.0
if abs(observed_volume - expected_volume) > eps:
raise Exception(
f"observed_volume={observed_volume} differs from expected_volume={expected_volume}"
)
observed_surface_area = extruded_polygon.get_surface_area()
observed_surface_area = extruded_polygon.surface_area()
expected_surface_area = 42.0
if abs(observed_surface_area - expected_surface_area) > eps:
raise Exception(
f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}"
)

# get bounding box from manifold
observed_bbox = extruded_polygon.bounding_box
observed_bbox = extruded_polygon.bounding_box()
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
if observed_bbox != expected_bbox:
raise Exception(
Expand Down
10 changes: 5 additions & 5 deletions bindings/python/examples/gyroid_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,28 @@ def gyroid(x, y, z):


def gyroid_levelset(level, period, size, n):
return Manifold.from_mesh(
return Manifold(
Mesh.level_set(
gyroid,
[-period, -period, -period, period, period, period],
period / n,
level,
)
).scale(size / period)
).scale([size / period] * 3)


def rhombic_dodecahedron(size):
box = Manifold.cube(size * math.sqrt(2.0) * np.array([1, 1, 2]), True)
result = box.rotate(90, 45) ^ box.rotate(90, 45, 90)
return result ^ box.rotate(0, 0, 45)
result = box.rotate([90, 45, 0]) ^ box.rotate([90, 45, 90])
return result ^ box.rotate([0, 0, 45])


def gyroid_module(size=20, n=15):
period = math.pi * 2.0
result = (
gyroid_levelset(-0.4, period, size, n) ^ rhombic_dodecahedron(size)
) - gyroid_levelset(0.4, period, size, n)
return result.rotate(-45, 0, 90).translate(0, 0, size / math.sqrt(2.0))
return result.rotate([-45, 0, 90]).translate([0, 0, size / math.sqrt(2.0)])


def run(size=20, n=15):
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/examples/maze.py
Original file line number Diff line number Diff line change
Expand Up @@ -4039,4 +4039,4 @@ def ball(*args):

ball([10, 10, 10], 0.4)

return Manifold.cube((n + 1) * np.array([1, 1, 1])) - Manifold(cavity)
return Manifold.cube([n + 1] * 3) - sum(cavity, Manifold())
3 changes: 2 additions & 1 deletion bindings/python/examples/scallop.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ def colorCurvature(_pos, oldProp):
blue = [0, 0, 1]
return [(1 - b) * blue[i] + b * red[i] for i in range(3)]

edges, smoothing = zip(*sharpenedEdges)
return (
Manifold.smooth(scallop, sharpenedEdges)
Manifold.smooth(scallop, edges, smoothing)
.refine(n)
.calculate_curvature(-1, 0)
.set_properties(3, colorCurvature)
Expand Down
4 changes: 2 additions & 2 deletions bindings/python/examples/split_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@


def run():
a = Manifold.cube(1.0, 1.0, 1.0)
b = Manifold.cube(1.0, 1.0, 1.0).rotate(45.0, 45.0, 45.0)
a = Manifold.cube([1.0, 1.0, 1.0])
b = Manifold.cube([1.0, 1.0, 1.0]).rotate([45.0, 45.0, 45.0])
return a.split(b)[0]
4 changes: 3 additions & 1 deletion bindings/python/examples/sponge.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ 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).set_properties(4, posColors).scale(100)
return (
result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale([100] * 3)
)
Loading