def extrude_example_xy_scaling() -> OpenSCADObject: num_points = SEGMENTS path_rad = PATH_RAD circle = circle_points(15) path = circle_points(rad=path_rad) # If scales aren't included, they'll default to # no scaling at each step along path. no_scale_obj = make_label('No Scale') no_scale_obj += extrude_along_path(circle, path) # angles: from 0 to 6*Pi angles = list((frange(0, 3 * tau, num_steps=len(path)))) # With a 1-D scale factor, an extrusion grows and shrinks uniformly x_scales = [(1 + cos(a) / 2) for a in angles] x_obj = make_label('1D Scale') x_obj += extrude_along_path(circle, path, scales=x_scales) # With a 2D scale factor, a shape's X & Y dimensions can scale # independently, leading to more interesting shapes # X & Y scales vary between 0.5 & 1.5 xy_scales = [Point2(1 + cos(a) / 2, 1 + sin(a) / 2) for a in angles] xy_obj = make_label('2D Scale') xy_obj += extrude_along_path(circle, path, scales=xy_scales) obj = no_scale_obj + right(3 * path_rad)(x_obj) + right( 6 * path_rad)(xy_obj) return obj
def test_extrude_along_path_rotations(self): # confirm we can rotate for each point in path path = [[0, 0, 0], [20, 0, 0]] rotations = [-45, 45] actual = extrude_along_path(tri, path, rotations=rotations) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[6,0,1],[6,1,2],[6,2,0],[7,3,4],[7,4,5],[7,5,3]],points=[[0.0000000000,0.0000000000,0.0000000000],[0.0000000000,-7.0710678119,-7.0710678119],[0.0000000000,-7.0710678119,7.0710678119],[20.0000000000,0.0000000000,0.0000000000],[20.0000000000,-7.0710678119,7.0710678119],[20.0000000000,7.0710678119,7.0710678119],[0.0000000000,-4.7140452079,0.0000000000],[20.0000000000,-0.0000000000,4.7140452079]]);' self.assertEqualOpenScadObject(expected, actual) # confirm we can rotate with a single supplied value path = [[0, 0, 0], [20, 0, 0]] rotations = [45] actual = extrude_along_path(tri, path, rotations=rotations) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[6,0,1],[6,1,2],[6,2,0],[7,3,4],[7,4,5],[7,5,3]],points=[[0.0000000000,0.0000000000,0.0000000000],[0.0000000000,-10.0000000000,0.0000000000],[0.0000000000,0.0000000000,10.0000000000],[20.0000000000,0.0000000000,0.0000000000],[20.0000000000,-7.0710678119,7.0710678119],[20.0000000000,7.0710678119,7.0710678119],[0.0000000000,-3.3333333333,3.3333333333],[20.0000000000,-0.0000000000,4.7140452079]]);' self.assertEqualOpenScadObject(expected, actual)
def extrude_example_rotations() -> OpenSCADObject: path_rad = PATH_RAD shape = star(num_points=5) path = circle_points(path_rad, num_points=240) # For a simple example, make one complete revolution by the end of the extrusion simple_rot = make_label('Simple Rotation') simple_rot += extrude_along_path(shape, path, rotations=[360], connect_ends=True) # For a more complex set of rotations, add a rotation degree for each point in path complex_rotations = [] degs = 0 oscillation_max = 60 for i in frange(0, 1, num_steps=len(path)): # For the first third of the path, do one complete rotation if i <= 0.333: degs = i / 0.333 * 360 # For the second third of the path, oscillate between +/- oscillation_max degrees elif i <= 0.666: angle = lerp(i, 0.333, 0.666, 0, 2 * tau) degs = oscillation_max * sin(angle) # For the last third of the path, oscillate increasingly fast but with smaller magnitude else: # angle increases in a nonlinear curve, so # oscillations should get quicker and quicker x = lerp(i, 0.666, 1.0, 0, 2) angle = pow(x, 2.2) * tau # decrease the size of the oscillations by a factor of 10 # over the course of this stretch osc = lerp(i, 0.666, 1.0, oscillation_max, oscillation_max / 10) degs = osc * sin(angle) complex_rotations.append(degs) complex_rot = make_label('Complex Rotation') complex_rot += extrude_along_path(shape, path, rotations=complex_rotations) # Make some red markers to show the boundaries between the three sections of this path marker_w = SHAPE_RAD * 1.5 marker = translate([path_rad, 0, 0])(cube([marker_w, 1, marker_w], center=True)) markers = [color('red')(rotate([0, 0, 120 * i])(marker)) for i in range(3)] complex_rot += markers return simple_rot + right(3 * path_rad)(complex_rot)
def test_extrude_along_path_transforms(self): path = [[0, 0, 0], [20, 0, 0]] # scale points by a factor of 2 & then 1/2 # Make sure we can take a transform function for each point in path transforms = [ lambda p, path, loop: 2 * p, lambda p, path, loop: 0.5 * p ] actual = extrude_along_path(tri, path, transforms=transforms) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[6,0,1],[6,1,2],[6,2,0],[7,3,4],[7,4,5],[7,5,3]],points=[[0.0000000000,0.0000000000,0.0000000000],[0.0000000000,-20.0000000000,0.0000000000],[0.0000000000,0.0000000000,20.0000000000],[20.0000000000,0.0000000000,0.0000000000],[20.0000000000,-5.0000000000,0.0000000000],[20.0000000000,0.0000000000,5.0000000000],[0.0000000000,-6.6666666667,6.6666666667],[20.0000000000,-1.6666666667,1.6666666667]]);' self.assertEqualOpenScadObject(expected, actual) # Make sure we can take a single transform function for all points transforms = [lambda p, path, loop: 2 * p] actual = extrude_along_path(tri, path, transforms=transforms) expected = 'polyhedron(faces = [[0, 3, 1], [1, 3, 4], [1, 4, 2], [2, 4, 5], [2, 5, 0], [0, 5, 3], [6, 0, 1], [6, 1, 2], [6, 2, 0], [7, 3, 4], [7, 4, 5], [7, 5, 3]], points = [[0.0000000000, 0.0000000000, 0.0000000000], [0.0000000000, -20.0000000000, 0.0000000000], [0.0000000000, 0.0000000000, 20.0000000000], [20.0000000000, 0.0000000000, 0.0000000000], [20.0000000000, -20.0000000000, 0.0000000000], [20.0000000000, 0.0000000000, 20.0000000000], [0.0000000000, -6.6666666667, 6.6666666667], [20.0000000000, -6.6666666667, 6.6666666667]]);' self.assertEqualOpenScadObject(expected, actual)
def test_extrude_along_path_1d_scale(self): # verify that we can apply scalar scaling path = [[0, 0, 0], [0, 20, 0]] scales_1d = [1.5, 0.5] actual = extrude_along_path(tri, path, scales=scales_1d) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[6,0,1],[6,1,2],[6,2,0],[7,3,4],[7,4,5],[7,5,3]],points=[[0.0000000000,0.0000000000,0.0000000000],[15.0000000000,0.0000000000,0.0000000000],[0.0000000000,0.0000000000,15.0000000000],[0.0000000000,20.0000000000,0.0000000000],[5.0000000000,20.0000000000,0.0000000000],[0.0000000000,20.0000000000,5.0000000000],[5.0000000000,0.0000000000,5.0000000000],[1.6666666667,20.0000000000,1.6666666667]]);' self.assertEqualOpenScadObject(expected, actual)
def extrude_example_transforms() -> OpenSCADObject: path_rad = PATH_RAD height = 2 * SHAPE_RAD num_steps = 120 shape = circle_points(rad=path_rad, num_points=120) path = [Point3(0, 0, i) for i in frange(0, height, num_steps=num_steps)] max_rotation = radians(15) max_z_displacement = height / 10 up = Vector3(0, 0, 1) # The transforms argument is powerful. # Each point in the entire extrusion will call this function with unique arguments: # -- `path_norm` in [0, 1] specifying how far along in the extrusion a point's loop is # -- `loop_norm` in [0, 1] specifying where in its loop a point is. def point_trans(point: Point3, path_norm: float, loop_norm: float) -> Point3: # scale the point from 1x to 2x in the course of the # extrusion, scale = 1 + path_norm * path_norm / 2 p = scale * point # Rotate the points sinusoidally up to max_rotation p = p.rotate_around(up, max_rotation * sin(tau * path_norm)) # Oscillate z values sinusoidally, growing from # 0 magnitude to max_z_displacement max_z = lerp(path_norm, 0, 1, 0, max_z_displacement) angle = lerp(loop_norm, 0, 1, 0, 10 * tau) p.z += max_z * sin(angle) return p no_trans = make_label('No Transform') no_trans += down(height / 2)(extrude_along_path(shape, path, cap_ends=False)) # We can pass transforms a single function that will be called on all points, # or pass a list with a transform function for each point along path arb_trans = make_label('Arbitrary Transform') arb_trans += down(height / 2)(extrude_along_path(shape, path, transforms=[point_trans], cap_ends=False)) return no_trans + right(3 * path_rad)(arb_trans)
def basic_extrude_example(): path_rad = PATH_RAD shape = star(num_points=5) path = sinusoidal_ring(rad=path_rad, segments=240) # At its simplest, just sweep a shape along a path extruded = extrude_along_path(shape_pts=shape, path_pts=path) extruded += make_label('Basic Extrude') return extruded
def extrude_example_capped_ends() -> OpenSCADObject: num_points = SEGMENTS / 2 path_rad = 50 circle = star(6) path = circle_points(rad=path_rad)[:-4] # If `connect_ends` is False or unspecified, ends will be capped. # Endcaps will be correct for most convex or mildly concave (e.g. stars) cross sections capped_obj = make_label('Capped Ends') capped_obj += extrude_along_path(circle, path, connect_ends=False, cap_ends=True) # If `connect_ends` is specified, create a continuous manifold object connected_obj = make_label('Connected Ends') connected_obj += extrude_along_path(circle, path, connect_ends=True) return capped_obj + right(3 * path_rad)(connected_obj)
def test_extrude_along_path_2d_scale_list_input(self): # verify that we can apply differential x & y scaling path = [[0, 0, 0], [0, 20, 0], [0, 40, 0]] scales_2d = [ (1, 1), (0.5, 1.5), (1.5, 0.5), ] actual = extrude_along_path(tri, path, scales=scales_2d) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[3,6,4],[4,6,7],[4,7,5],[5,7,8],[5,8,3],[3,8,6],[9,0,1],[9,1,2],[9,2,0],[10,6,7],[10,7,8],[10,8,6]],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],[5.0000000000,20.0000000000,0.0000000000],[0.0000000000,20.0000000000,15.0000000000],[0.0000000000,40.0000000000,0.0000000000],[15.0000000000,40.0000000000,0.0000000000],[0.0000000000,40.0000000000,5.0000000000],[3.3333333333,0.0000000000,3.3333333333],[5.0000000000,40.0000000000,1.6666666667]]);' self.assertEqualOpenScadObject(expected, actual)
def extrude_bridge(): start1 = [0, 0, 0] face1 = face(start1, 2, 'xy') path1 = linearpath(start1, 10, 'z') cube1 = extrude_along_path(shape_pts=face1, path_pts=path1) start2 = [0, 0, 10] face2 = face(start2, 2, 'xz') print(np.array(face2)) path2 = linearpath(start2, 32, 'y') print(np.array(path2)) cube2 = extrude_along_path(shape_pts=face2, path_pts=path2) start3 = [0, 10, 0] face3 = face(start3, 2, 'xy') path3 = linearpath(start3, 10, 'z') cube3 = extrude_along_path(shape_pts=face3, path_pts=path3) return cube1 + cube2 + cube3
def test_extrude_along_path_numpy(self): try: import numpy as np except ImportError: return N = 3 thetas=np.linspace(0,np.pi,N) path=list(zip(3*np.sin(thetas),3*np.cos(thetas),thetas)) profile=list(zip(np.sin(thetas),np.cos(thetas), [0]*len(thetas))) scalepts=list(np.linspace(1,.1,N)) # in earlier code, this would have thrown an exception a = extrude_along_path(shape_pts=profile, path_pts=path, scale_factors=scalepts)
def extrude_example(): # Note the incorrect triangulation at the two ends of the path. This # is because star isn't convex, and the triangulation algorithm for # the two end caps only works for convex shapes. shape = star(num_points=5) path = sinusoidal_ring(rad=50) # If scale_factors aren't included, they'll default to # no scaling at each step along path. Here, let's # make the shape twice as big at beginning and end of the path scales = [1] * len(path) scales[0] = 2 scales[-1] = 2 extruded = extrude_along_path(shape_pts=shape, path_pts=path, scale_factors=scales) return extruded
def test_extrude_along_path(self): path = [[0, 0, 0], [0, 20, 0]] # basic test actual = extrude_along_path(tri, path) expected = 'polyhedron(faces = [[0, 3, 1], [1, 3, 4], [1, 4, 2], [2, 4, 5], [2, 5, 0], [0, 5, 3], [6, 0, 1], [6, 1, 2], [6, 2, 0], [7, 3, 4], [7, 4, 5], [7, 5, 3]], 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], [3.3333333333, 0.0000000000, 3.3333333333], [3.3333333333, 20.0000000000, 3.3333333333]]);' self.assertEqualOpenScadObject(expected, actual)
def extrude_cube(): shape = face(2, 2) path = linearpath(0, 20, 0) scales = [1] extruded = extrude_along_path(shape_pts=shape, path_pts=path) return extruded
def test_extrude_along_path_connect_ends(self): path = [[0, 0, 0], [20, 0, 0], [20, 20, 0], [0, 20, 0]] actual = extrude_along_path(tri, path, connect_ends=True) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[3,6,4],[4,6,7],[4,7,5],[5,7,8],[5,8,3],[3,8,6],[6,9,7],[7,9,10],[7,10,8],[8,10,11],[8,11,6],[6,11,9],[0,9,1],[1,9,10],[1,10,2],[2,10,11],[2,11,0],[0,11,9]],points=[[0.0000000000,0.0000000000,0.0000000000],[-7.0710678119,-7.0710678119,0.0000000000],[0.0000000000,0.0000000000,10.0000000000],[20.0000000000,0.0000000000,0.0000000000],[27.0710678119,-7.0710678119,0.0000000000],[20.0000000000,0.0000000000,10.0000000000],[20.0000000000,20.0000000000,0.0000000000],[27.0710678119,27.0710678119,0.0000000000],[20.0000000000,20.0000000000,10.0000000000],[0.0000000000,20.0000000000,0.0000000000],[-7.0710678119,27.0710678119,0.0000000000],[0.0000000000,20.0000000000,10.0000000000]]);' self.assertEqualOpenScadObject(expected, actual)
def test_extrude_along_path_end_caps(self): path = [[0, 0, 0], [0, 20, 0]] actual = scad_render(extrude_along_path(tri, path, connect_ends=False)) expected = 'polyhedron(faces = [[0, 3, 1], [1, 3, 4], [1, 4, 2], [2, 4, 5], [2, 5, 0], [0, 5, 3], [6, 0, 1], [6, 1, 2], [6, 2, 0], [7, 3, 4], [7, 4, 5], [7, 5, 3]], 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], [3.3333333333, 0.0000000000, 3.3333333333], [3.3333333333, 20.0000000000, 3.3333333333]]);' self.assertEqualNoWhitespace(expected, actual)
def test_extrude_along_path_vertical(self): # make sure we still look good extruding along z axis; gimbal lock can mess us up vert_path = [[0, 0, 0], [0, 0, 20]] actual = extrude_along_path(tri, vert_path) expected = 'polyhedron(faces=[[0,3,1],[1,3,4],[1,4,2],[2,4,5],[2,5,0],[0,5,3],[6,0,1],[6,1,2],[6,2,0],[7,3,4],[7,4,5],[7,5,3]],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],[-3.3333333333,3.3333333333,0.0000000000],[-3.3333333333,3.3333333333,20.0000000000]]);' self.assertEqualOpenScadObject(expected, actual)