Support for variable radii and torus-like paths

This commit is contained in:
Elod Csirmaz 2024-08-16 00:51:43 +01:00
parent b2507137fd
commit 8b5d63efab

View file

@ -291,10 +291,20 @@ class Polyhedron(Object):
self.convexity = convexity self.convexity = convexity
@classmethod @classmethod
def tube(cls, points: List[List[TUnion[list, Point]]], convexity: int = 10): def torus(cls, points: List[List[TUnion[list, Point]]], 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.
The rows of points form loops.
"""
return cls.tube(points=points, convexity=convexity, make_torus=True)
@classmethod
def tube(cls, points: List[List[TUnion[list, Point]]], convexity: int = 10, make_torus: bool = False):
"""Construct a tube-like polyhedron from a 2D array of points. """Construct a tube-like polyhedron from a 2D array of points.
Each row of points must be oriented clockwise when looking at the pipe at the start inwards. Each row of points must be oriented clockwise when looking at the pipe at the start inwards.
The rows of points form loops. The rows of points form loops.
If `make_torus`, create a torus-like shape instead of a pipe with ends.
""" """
rows = len(points) rows = len(points)
row_len = len(points[0]) row_len = len(points[0])
@ -323,12 +333,24 @@ class Polyhedron(Object):
point_map[(row_ix-1, row_len-1)] point_map[(row_ix-1, row_len-1)]
]) ])
if not make_torus:
# Starting cap # Starting cap
faces.append([point_map[(0,x)] for x in range(row_len)]) faces.append([point_map[(0,x)] for x in range(row_len)])
# Ending cap # Ending cap
faces.append([point_map[(rows-1,row_len-1-x)] for x in range(row_len)]) faces.append([point_map[(rows-1,row_len-1-x)] for x in range(row_len)])
else:
# Connect the end to the start
for col_ix in range(1, row_len):
faces.append([
point_map[(0, col_ix-1)],
point_map[(0, col_ix)],
point_map[(rows-1, col_ix)],
point_map[(rows-1, col_ix-1)]
])
return cls(points=point_list, faces=faces, convexity=convexity) return cls(points=point_list, faces=faces, convexity=convexity)
def render(self) -> str: def render(self) -> str:
@ -339,10 +361,18 @@ class Polyhedron(Object):
class PathTube(Object): class PathTube(Object):
"""Creates a tube-like or toroid polyhedron from a path (list of points).""" """Creates a tube-like or toroid polyhedron from a path (list of points)."""
def __init__(self, points: List[TUnion[list, Point]], radius: float, fn: int, convexity: int = 10): def __init__(self, points: List[TUnion[list, Point]], radius: TUnion[float, list], fn: int, make_torus: bool = False, convexity: int = 10):
"""
points: The list of points
radius: A float or a list of floats for each point
fn: int, The number of sides
make_torus: bool, Whether to make a torus instead of a pipe with ends. Warning: the last segment may be twisted.
convexity: see openscad
"""
self.points = [Point.c(p) for p in points] self.points = [Point.c(p) for p in points]
self.radius = radius self.radii = radius if isinstance(radius, list) else [radius for p in points]
self.fn = fn # number of sides self.fn = fn
self.make_torus = make_torus
self.convexity = convexity self.convexity = convexity
def process(self, debug: bool = False) -> Polyhedron: def process(self, debug: bool = False) -> Polyhedron:
@ -351,7 +381,7 @@ class PathTube(Object):
for ix, point in enumerate(self.points): for ix, point in enumerate(self.points):
if debug: print(f"//LOOP {ix}: {point.render()}") if debug: print(f"//LOOP {ix}: {point.render()}")
if ix == 0: if (not self.make_torus) and ix == 0:
# Start of the path # Start of the path
v = self.points[1].sub(point) # vector toward the first point v = self.points[1].sub(point) # vector toward the first point
z_point = Point([0,0,1]) z_point = Point([0,0,1])
@ -364,11 +394,11 @@ class PathTube(Object):
points = [] points = []
for i in range(self.fn): for i in range(self.fn):
a = math.pi*2*i/self.fn a = math.pi*2*i/self.fn
points.append((seam*math.cos(a) + seam2*math.sin(a))*self.radius + point) points.append((seam*math.cos(a) + seam2*math.sin(a))*self.radii[ix] + point)
points_rows.append(points) points_rows.append(points)
if debug: print(f"// Row: {', '.join([p.render() for p in points])}") if debug: print(f"// Row: {', '.join([p.render() for p in points])}")
elif ix == len(self.points) - 1: elif (not self.make_torus) and ix == len(self.points) - 1:
# End of the path # End of the path
v = point.sub(self.points[-2]) v = point.sub(self.points[-2])
seam2 = v.cross(seam).norm() seam2 = v.cross(seam).norm()
@ -376,15 +406,17 @@ class PathTube(Object):
points = [] points = []
for i in range(self.fn): for i in range(self.fn):
a = math.pi*2*i/self.fn a = math.pi*2*i/self.fn
points.append((seam*math.cos(a) + seam2*math.sin(a))*self.radius + point) points.append((seam*math.cos(a) + seam2*math.sin(a))*self.radii[ix] + point)
points_rows.append(points) points_rows.append(points)
if debug: print(f"// Row: {', '.join([p.render() for p in points])}") if debug: print(f"// Row: {', '.join([p.render() for p in points])}")
else: else:
# Middle of the path # Middle of the path
iprev = ix - 1 if ix > 0 else len(self.points) - 1
inext = ix + 1 if ix < len(self.points) - 1 else 0
# (p[-1]) -va-> (p[0]) -vb-> (p[1]) # (p[-1]) -va-> (p[0]) -vb-> (p[1])
va = point.sub(self.points[ix-1]).norm() # vector incoming to this elbow va = point.sub(self.points[iprev]).norm() # vector incoming to this elbow
vb = self.points[ix+1].sub(point).norm() # vector going out from this elbow vb = self.points[inext].sub(point).norm() # vector going out from this elbow
if debug: print(f"//Middle. va={va.render()} vb={vb.render()}") if debug: print(f"//Middle. va={va.render()} vb={vb.render()}")
# Get the vector perpendicular to va that points to the inside of the cylinder around va according # Get the vector perpendicular to va that points to the inside of the cylinder around va according
# to the elbow at p[0]. This is the component of vb in a basis defined by va. # to the elbow at p[0]. This is the component of vb in a basis defined by va.
@ -400,6 +432,10 @@ class PathTube(Object):
vb_inner = va_perp.scale(-1).norm() # Here we want to project -va onto vb vb_inner = va_perp.scale(-1).norm() # Here we want to project -va onto vb
if debug: print(f"// va_inner={va_inner.render()} vb_inner={vb_inner.render()}") if debug: print(f"// va_inner={va_inner.render()} vb_inner={vb_inner.render()}")
if ix == 0:
# We just choose a seam when making a torus
seam_angle = 0
else:
# The new seam on vb (seam_b) has the same angle to vb_inner as it had on va to va_inner # The new seam on vb (seam_b) has the same angle to vb_inner as it had on va to va_inner
seam_angle = seam.angle(va_inner, mode="rad") seam_angle = seam.angle(va_inner, mode="rad")
# need to figure out the sign of the angle # need to figure out the sign of the angle
@ -408,7 +444,11 @@ class PathTube(Object):
seam_angle = -seam_angle seam_angle = -seam_angle
vb_inner2 = vb.cross(vb_inner).norm() vb_inner2 = vb.cross(vb_inner).norm()
seam_b = vb_inner*math.cos(seam_angle) + vb_inner2*math.sin(seam_angle) seam_b = vb_inner*math.cos(seam_angle) + vb_inner2*math.sin(seam_angle)
if debug: print(f"// seam={seam.render()} seam_b={seam_b.render()}") if debug:
if ix == 0:
print(f"// seam=N/A seam_b={seam_b.render()}")
else:
print(f"// seam={seam.render()} seam_b={seam_b.render()}")
vangle = va.scale(-1).angle(vb, mode="rad") vangle = va.scale(-1).angle(vb, mode="rad")
long_inner = (vb-va).norm().scale(1/math.sin(vangle/2)) long_inner = (vb-va).norm().scale(1/math.sin(vangle/2))
@ -419,13 +459,13 @@ class PathTube(Object):
for i in range(self.fn): for i in range(self.fn):
# We draw the ellipse according to long_inner and short, but use seam_angle to get the right points # We draw the ellipse according to long_inner and short, but use seam_angle to get the right points
a = math.pi*2*i/self.fn + seam_angle a = math.pi*2*i/self.fn + seam_angle
points.append((long_inner*math.cos(a) + short*math.sin(a))*self.radius + point) points.append((long_inner*math.cos(a) + short*math.sin(a))*self.radii[ix] + point)
points_rows.append(points) points_rows.append(points)
if debug: print(f"// Row: {', '.join([p.render() for p in points])}") if debug: print(f"// Row: {', '.join([p.render() for p in points])}")
seam = seam_b seam = seam_b
return Polyhedron.tube(points=points_rows, convexity=self.convexity) return Polyhedron.tube(points=points_rows, convexity=self.convexity, make_torus=self.make_torus)
def render(self) -> str: def render(self) -> str:
return self.process().render() return self.process().render()