Skip to content

Commit a045f82

Browse files
committed
Inspired by mickael.bosch@posteo.net, Let solid.utils.extrude_along_path() accept a list of Point2 for scale_factors, allowing independent x & y scaling for each point along the extrusion path. Added tests & expanded example
1 parent fa26086 commit a045f82

File tree

3 files changed

+83
-34
lines changed

3 files changed

+83
-34
lines changed
Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
#! /usr/bin/env python3
2+
from solid.solidpython import OpenSCADObject
23
import sys
3-
from math import cos, radians, sin
4+
from math import cos, radians, sin, pi, tau
5+
from pathlib import Path
46

5-
from euclid3 import Point3
7+
from euclid3 import Point2, Point3
68

7-
from solid import scad_render_to_file
8-
from solid.utils import extrude_along_path
9+
from solid import scad_render_to_file, text, translate
10+
from solid.utils import extrude_along_path, right
11+
12+
13+
from typing import Set, Sequence, List, Callable, Optional, Union, Iterable
914

1015
SEGMENTS = 48
1116

1217

1318
def sinusoidal_ring(rad=25, segments=SEGMENTS):
1419
outline = []
1520
for i in range(segments):
16-
angle = i * 360 / segments
17-
x = rad * cos(radians(angle))
18-
y = rad * sin(radians(angle))
19-
z = 2 * sin(radians(angle * 6))
21+
angle = radians(i * 360 / segments)
22+
scaled_rad = (1 + 0.18*cos(angle*5)) * rad
23+
x = scaled_rad * cos(angle)
24+
y = scaled_rad * sin(angle)
25+
z = 0
26+
# Or stir it up and add an oscillation in z as well
27+
# z = 3 * sin(angle * 6)
2028
outline.append(Point3(x, y, z))
2129
return outline
2230

@@ -29,28 +37,68 @@ def star(num_points=5, outer_rad=15, dip_factor=0.5):
2937
star_pts.append(Point3(rad * cos(angle), rad * sin(angle), 0))
3038
return star_pts
3139

40+
def circle_points(rad: float = 15, num_points: int = SEGMENTS) -> List[Point2]:
41+
angles = [tau/num_points * i for i in range(num_points)]
42+
points = list([Point2(rad*cos(a), rad*sin(a)) for a in angles])
43+
return points
3244

3345
def extrude_example():
3446
# Note the incorrect triangulation at the two ends of the path. This
3547
# is because star isn't convex, and the triangulation algorithm for
3648
# the two end caps only works for convex shapes.
49+
path_rad = 50
3750
shape = star(num_points=5)
38-
path = sinusoidal_ring(rad=50)
51+
path = sinusoidal_ring(rad=path_rad, segments=240)
52+
53+
# # If scale_factors aren't included, they'll default to
54+
# # no scaling at each step along path. Here, let's
55+
# # make the shape twice as big at beginning and end of the path
56+
# scales = [1] * len(path)
57+
# n = len(path)
58+
# scales = [1 + 0.5*sin(i*6*pi/n) for i in range(n)]
59+
# scales[0] = 2
60+
# scales[-1] = 2
61+
62+
extruded = extrude_along_path( shape_pts=shape, path_pts=path)
63+
# Label
64+
extruded += translate([-path_rad/2, 2*path_rad])(text('Basic Extrude'))
65+
return extruded
66+
67+
def extrude_example_xy_scaling() -> OpenSCADObject:
68+
num_points = SEGMENTS
69+
path_rad = 50
70+
circle = circle_points(15)
71+
path = circle_points(rad = path_rad)
72+
73+
# angle: from 0 to 6*Pi
74+
angles = list((i/(num_points - 1)*tau*3 for i in range(len(path))))
75+
3976

4077
# If scale_factors aren't included, they'll default to
41-
# no scaling at each step along path. Here, let's
42-
# make the shape twice as big at beginning and end of the path
43-
scales = [1] * len(path)
44-
scales[0] = 2
45-
scales[-1] = 2
78+
# no scaling at each step along path.
79+
no_scale_obj = translate([-path_rad / 2, 2 * path_rad])(text('No Scale'))
80+
no_scale_obj += extrude_along_path(circle, path)
4681

47-
extruded = extrude_along_path(shape_pts=shape, path_pts=path, scale_factors=scales)
82+
# With a 1-D scale factor, an extrusion grows and shrinks uniformly
83+
x_scales = [(1 + cos(a)/2) for a in angles]
84+
x_obj = translate([-path_rad / 2, 2 * path_rad])(text('1D Scale'))
85+
x_obj += extrude_along_path(circle, path, scale_factors=x_scales)
4886

49-
return extruded
87+
# With a 2D scale factor, a shape's X & Y dimensions can scale
88+
# independently, leading to more interesting shapes
89+
# X & Y scales vary between 0.5 & 1.5
90+
xy_scales = [Point2( 1 + cos(a)/2, 1 + sin(a)/2) for a in angles]
91+
xy_obj = translate([-path_rad / 2, 2 * path_rad])( text('2D Scale'))
92+
xy_obj += extrude_along_path(circle, path, scale_factors=xy_scales)
93+
94+
obj = no_scale_obj + right(3*path_rad)(x_obj) + right(6 * path_rad)(xy_obj)
95+
return obj
5096

97+
if __name__ == "__main__":
98+
out_dir = sys.argv[1] if len(sys.argv) > 1 else Path(__file__).parent
5199

52-
if __name__ == '__main__':
53-
out_dir = sys.argv[1] if len(sys.argv) > 1 else None
54-
a = extrude_example()
55-
file_out = scad_render_to_file(a, out_dir=out_dir, include_orig_code=True)
100+
basic_extrude = extrude_example()
101+
scaled_extrusions = extrude_example_xy_scaling()
102+
a = basic_extrude + translate([0,-250])(scaled_extrusions)
103+
file_out = scad_render_to_file(a, out_dir=out_dir, include_orig_code=True)
56104
print(f"{__file__}: SCAD file written to: \n{file_out}")

solid/test/test_utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
('arc_inverted', arc_inverted, [10, 0, 90, 24], '\n\ndifference() {\n\tintersection() {\n\t\trotate(a = 0) {\n\t\t\ttranslate(v = [-990, 0, 0]) {\n\t\t\t\tsquare(center = false, size = [1000, 1000]);\n\t\t\t}\n\t\t}\n\t\trotate(a = 90) {\n\t\t\ttranslate(v = [-990, -1000, 0]) {\n\t\t\t\tsquare(center = false, size = [1000, 1000]);\n\t\t\t}\n\t\t}\n\t}\n\tcircle($fn = 24, r = 10);\n}'),
3030
('transform_to_point_scad', transform_to_point, [cube(2), [2, 2, 2], [3, 3, 1]], '\n\nmultmatrix(m = [[0.7071067812, -0.1622214211, -0.6882472016, 2], [-0.7071067812, -0.1622214211, -0.6882472016, 2], [0.0000000000, 0.9733285268, -0.2294157339, 2], [0, 0, 0, 1.0000000000]]) {\n\tcube(size = 2);\n}'),
3131
('extrude_along_path', extrude_along_path, [tri, [[0, 0, 0], [0, 20, 0]]], '\n\npolyhedron(faces = [[0, 3, 1], [1, 3, 4], [1, 4, 2], [2, 4, 5], [0, 2, 5], [0, 5, 3], [0, 1, 2], [3, 5, 4]], points = [[0.0000000000, 0.0000000000, 0.0000000000], [10.0000000000, 0.0000000000, 0.0000000000], [0.0000000000, 0.0000000000, 10.0000000000], [0.0000000000, 20.0000000000, 0.0000000000], [10.0000000000, 20.0000000000, 0.0000000000], [0.0000000000, 20.0000000000, 10.0000000000]]);'),
32+
('extrude_along_path_xy_scale', extrude_along_path, [tri, [[0, 0, 0], [0, 20, 0]], [Point2(0.5, 1.5), Point2(1.5, 0.5)]], '\n\npolyhedron(faces = [[0, 3, 1], [1, 3, 4], [1, 4, 2], [2, 4, 5], [0, 2, 5], [0, 5, 3], [0, 1, 2], [3, 5, 4]], points = [[0.0000000000, 0.0000000000, 0.0000000000], [5.0000000000, 0.0000000000, 0.0000000000], [0.0000000000, 0.0000000000, 15.0000000000], [0.0000000000, 20.0000000000, 0.0000000000], [15.0000000000, 20.0000000000, 0.0000000000], [0.0000000000, 20.0000000000, 5.0000000000]]);'),
3233
('extrude_along_path_vertical', extrude_along_path, [tri, [[0, 0, 0], [0, 0, 20]]], '\n\npolyhedron(faces = [[0, 3, 1], [1, 3, 4], [1, 4, 2], [2, 4, 5], [0, 2, 5], [0, 5, 3], [0, 1, 2], [3, 5, 4]], points = [[0.0000000000, 0.0000000000, 0.0000000000], [-10.0000000000, 0.0000000000, 0.0000000000], [0.0000000000, 10.0000000000, 0.0000000000], [0.0000000000, 0.0000000000, 20.0000000000], [-10.0000000000, 0.0000000000, 20.0000000000], [0.0000000000, 10.0000000000, 20.0000000000]]);'),
3334

3435
]

solid/utils.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ def path_2d_polygon(points:Sequence[Point23], width:float=1, closed:bool=False)
11201120
# ==========================
11211121
def extrude_along_path( shape_pts:Points,
11221122
path_pts:Points,
1123-
scale_factors:Sequence[float]=None) -> OpenSCADObject:
1123+
scale_factors:Sequence[Union[Vector2, float]]=None) -> OpenSCADObject:
11241124
# Extrude the convex curve defined by shape_pts along path_pts.
11251125
# -- For predictable results, shape_pts must be planar, convex, and lie
11261126
# in the XY plane centered around the origin.
@@ -1132,9 +1132,6 @@ def extrude_along_path( shape_pts:Points,
11321132
polyhedron_pts:Points= []
11331133
facet_indices:List[Tuple[int, int, int]] = []
11341134

1135-
if not scale_factors:
1136-
scale_factors = [1.0] * len(path_pts)
1137-
11381135
# Make sure we've got Euclid Point3's for all elements
11391136
shape_pts = euclidify(shape_pts, Point3)
11401137
path_pts = euclidify(path_pts, Point3)
@@ -1143,7 +1140,6 @@ def extrude_along_path( shape_pts:Points,
11431140

11441141
for which_loop in range(len(path_pts)):
11451142
path_pt = path_pts[which_loop]
1146-
scale = float(scale_factors[which_loop])
11471143

11481144
# calculate the tangent to the curve at this point
11491145
if which_loop > 0 and which_loop < len(path_pts) - 1:
@@ -1159,17 +1155,21 @@ def extrude_along_path( shape_pts:Points,
11591155
tangent = path_pt - path_pts[which_loop - 1]
11601156

11611157
# Scale points
1162-
this_loop:Point3 = []
1163-
if scale != 1.0:
1164-
this_loop = [(scale * sh) for sh in shape_pts]
1165-
# Convert this_loop back to points; scaling changes them to Vectors
1166-
this_loop = [Point3(v.x, v.y, v.z) for v in this_loop]
1167-
else:
1168-
this_loop = shape_pts[:] # type: ignore
1158+
this_loop = shape_pts[:] # type: ignore
1159+
scale_x, scale_y = [1, 1]
1160+
if scale_factors:
1161+
scale = scale_factors[which_loop]
1162+
if isinstance(scale, (int, float)):
1163+
scale_x, scale_y = scale, scale
1164+
elif isinstance(scale, Vector2):
1165+
scale_x, scale_y = scale.x, scale.y
1166+
else:
1167+
raise ValueError(f'Unable to scale shape_pts with scale value: {scale}')
1168+
this_loop = [Point3(v.x * scale_x, v.y * scale_y, v.z) for v in this_loop]
11691169

11701170
# Rotate & translate
1171-
this_loop = transform_to_point(this_loop, dest_point=path_pt,
1172-
dest_normal=tangent, src_up=src_up)
1171+
this_loop = transform_to_point(this_loop, dest_point=path_pt,
1172+
dest_normal=tangent, src_up=src_up)
11731173

11741174
# Add the transformed points to our final list
11751175
polyhedron_pts += this_loop

0 commit comments

Comments
 (0)