Add support for polyhedron from height maps

This commit is contained in:
Elod Csirmaz 2024-11-30 17:19:51 +00:00
parent 20a12e7047
commit ffbee53da3
3 changed files with 121 additions and 3 deletions

View file

@ -25,6 +25,8 @@ translate(v=[0, 0, 1]) { cube(size=[1, 1, 2], center=false); }
## Notable convenience functions
### Computational geometry
Usual computational geometry functions on the `Point` class that work in an arbitrary number of dimensions. Overloads for algebraic operators.
```
@ -32,13 +34,21 @@ distance = (Point((0, 0, 1)) - Point((1, 0, 1))).length()
angle_between = Point((0, 0, 1) * 2).angle(Point((1, 0, 1)))
```
### Cylinders
`Cylinder.from_ends()` constructs a cylinder between two given points in space
```
openscad_code = Cylinder.from_ends(radius=2, p1=(0, 0, 1), p2=(1, 0, 2)).render()
```
`Polyhedron.tube()` creates a tube-like or toroid polyhedron from a 2D array of points
### Tubes and toroids from a point grid
`Polyhedron.tube()` creates a tube-like polyhedron from a 2D array of points
`Polyhedron.torus()` creates a toroid polyhedron from a 2D array of points
### Tubes from a path
`PathTube` creates a tube-like or toroid polyhedron from an arbitrary path
@ -52,4 +62,23 @@ PathTube(
![PathTube example](https://raw.github.com/csirmaz/openscad-py/master/images/pathtube.png)
`Polyhedron.render_stl()` export a polyhedra into STL directly
### Polyhedron from a height map
```
Polyhedron.from_heightmap(
heights=[
[3, 3, 1, 1, 1],
[3, 3, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 2, 2],
[1, 1, 1, 2, 2],
],
base=-5
)
```
![Heightmap example](https://raw.github.com/csirmaz/openscad-py/master/images/heightmap.png)
### Direct STL export
`Polyhedron.render_stl()` exports any polyhedron into STL directly

BIN
images/heightmap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -297,6 +297,8 @@ class Cylinder(Object):
class Polyhedron(Object):
"""A 3D primitive, a polyhedron defined by a list of points and faces."""
# See https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language#polyhedron
# Faces are defined by lists of point indices. The points of a face must be listed clockwise when
# looking at the face from the outside inward.
# Nonplanar faces should be triangulated by opensCAD
def __init__(self, points: List[TUnion[list, Point]], faces: List[list], convexity: int = 10):
@ -304,10 +306,11 @@ class Polyhedron(Object):
self.faces = faces
self.convexity = convexity
@classmethod
def torus(cls, points: List[List[TUnion[list, Point]]], torus_connect_offset: int = 0, convexity: int = 10):
"""Construct a torus-like polyhedron from a 2D array of points.
Each row of points must be oriented clickwise when looking from the first row (loop) toward the next.
Each row of points must be oriented clockwise when looking from the first row (loop) toward the next.
The rows of points form loops.
points: A 2D array of points
@ -316,6 +319,7 @@ class Polyhedron(Object):
"""
return cls.tube(points=points, convexity=convexity, make_torus=True, torus_connect_offset=torus_connect_offset)
@classmethod
def tube(cls, points: List[List[TUnion[list, Point]]], make_torus: bool = False, torus_connect_offset: int = 0, convexity: int = 10):
"""Construct a tube-like polyhedron from a 2D array of points.
@ -373,6 +377,91 @@ class Polyhedron(Object):
])
return cls(points=point_list, faces=faces, convexity=convexity)
@classmethod
def from_heightmap(cls, heights: List[List[float]], base: float = 0., convexity: int = 10):
"""Construct a polyhedron from a 2D matrix of heights. If the height at [0,0] is Z, it maps
to the point (0, 0, Z).
heights: The 2D matrix of heights
base: The height at which the base will be - in the scale of heights (optional; default 0)
convexity: see OpenSCAD
"""
rows = len(heights)
row_len = len(heights[0])
point_list = []
point_map = {} # { (row_ix,col_ix) -> list_ix, ...
bottom_point_map = {}
for row_ix, row in enumerate(heights):
for col_ix, height in enumerate(row):
point = Point([row_ix, col_ix, height])
bottom_point = Point([row_ix, col_ix, base])
point_map[(row_ix, col_ix)] = len(point_list)
point_list.append(point)
bottom_point_map[(row_ix, col_ix)] = len(point_list)
point_list.append(bottom_point)
faces = []
# Surface (top) faces
# r 10 11
# c
# 10 1 2
# 11 4 3
for row_ix in range(1, rows):
for col_ix in range(1, row_len):
faces.append([
point_map[(row_ix-1, col_ix-1)],
point_map[(row_ix, col_ix-1)],
point_map[(row_ix, col_ix)],
point_map[(row_ix-1, col_ix)]
])
# Bottom faces
for row_ix in range(1, rows):
for col_ix in range(1, row_len):
faces.append([
bottom_point_map[(row_ix-1, col_ix-1)], # 1
bottom_point_map[(row_ix-1, col_ix)], # 4
bottom_point_map[(row_ix, col_ix)], # 3
bottom_point_map[(row_ix, col_ix-1)] # 2
])
# Side faces
for row_ix in range(1, rows):
m = row_len - 1
faces.append([
point_map[(row_ix-1, m)],
point_map[(row_ix, m)],
bottom_point_map[(row_ix, m)],
bottom_point_map[(row_ix-1, m)]
])
faces.append([
point_map[(row_ix, 0)],
point_map[(row_ix-1, 0)],
bottom_point_map[(row_ix-1, 0)],
bottom_point_map[(row_ix, 0)]
])
for col_ix in range(1, row_len):
m = rows - 1
faces.append([
point_map[(m, col_ix-1)],
point_map[(m, col_ix)],
bottom_point_map[(m, col_ix)],
bottom_point_map[(m, col_ix-1)]
])
faces.append([
point_map[(0, col_ix)],
point_map[(0, col_ix-1)],
bottom_point_map[(0, col_ix-1)],
bottom_point_map[(0, col_ix)]
])
return cls(points=point_list, faces=faces, convexity=convexity)
def render(self) -> str:
faces_list = [f"[{','.join([str(x) for x in face])}]" for face in self.faces]