mirror of
https://github.com/csirmaz/openscad-py.git
synced 2025-06-21 02:15:40 +02:00
Add support for polyhedron from height maps
This commit is contained in:
parent
20a12e7047
commit
ffbee53da3
3 changed files with 121 additions and 3 deletions
33
README.md
33
README.md
|
@ -25,6 +25,8 @@ translate(v=[0, 0, 1]) { cube(size=[1, 1, 2], center=false); }
|
||||||
|
|
||||||
## Notable convenience functions
|
## 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.
|
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)))
|
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
|
`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()
|
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
|
`PathTube` creates a tube-like or toroid polyhedron from an arbitrary path
|
||||||
|
|
||||||
|
@ -52,4 +62,23 @@ PathTube(
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`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
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Direct STL export
|
||||||
|
|
||||||
|
`Polyhedron.render_stl()` exports any polyhedron into STL directly
|
||||||
|
|
BIN
images/heightmap.png
Normal file
BIN
images/heightmap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9 KiB |
|
@ -297,6 +297,8 @@ class Cylinder(Object):
|
||||||
class Polyhedron(Object):
|
class Polyhedron(Object):
|
||||||
"""A 3D primitive, a polyhedron defined by a list of points and faces."""
|
"""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
|
# 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
|
# Nonplanar faces should be triangulated by opensCAD
|
||||||
|
|
||||||
def __init__(self, points: List[TUnion[list, Point]], faces: List[list], convexity: int = 10):
|
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.faces = faces
|
||||||
self.convexity = convexity
|
self.convexity = convexity
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def torus(cls, points: List[List[TUnion[list, Point]]], torus_connect_offset: int = 0, convexity: int = 10):
|
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.
|
"""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.
|
The rows of points form loops.
|
||||||
|
|
||||||
points: A 2D array of points
|
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)
|
return cls.tube(points=points, convexity=convexity, make_torus=True, torus_connect_offset=torus_connect_offset)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tube(cls, points: List[List[TUnion[list, Point]]], make_torus: bool = False, torus_connect_offset: int = 0, convexity: int = 10):
|
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.
|
"""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)
|
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:
|
def render(self) -> str:
|
||||||
faces_list = [f"[{','.join([str(x) for x in face])}]" for face in self.faces]
|
faces_list = [f"[{','.join([str(x) for x in face])}]" for face in self.faces]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue