diff --git a/all/dotSCAD.scad b/all/dotSCAD.scad new file mode 100644 index 00000000..f26a1e46 --- /dev/null +++ b/all/dotSCAD.scad @@ -0,0 +1,79 @@ +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; +include ; \ No newline at end of file diff --git a/src/dotSCAD.scad b/src/dotSCAD.scad new file mode 100644 index 00000000..755e1e12 --- /dev/null +++ b/src/dotSCAD.scad @@ -0,0 +1,4417 @@ + + +/** + * The dotSCAD library + * @copyright Justin Lin, 2017 + * @license https://opensource.org/licenses/lgpl-3.0.html + * + * @see https://github.com/JustinSDK/dotSCAD +*/ + +/** +* circle_path.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-circle_path.html +* +**/ + + +function circle_path(radius, n) = + let( + _frags = __frags(radius), + step_a = 360 / _frags, + end_a = 360 - step_a * ((n == undef || n > _frags) ? 1 : _frags - n + 1) + ) + [ + for(a = [0 : step_a : end_a]) + [radius * cos(a), radius * sin(a)] + ]; + + +/** +* split_str.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-split_str.html +* +**/ + +function _split_t_by(idxs, t, ts = [], i = -1) = + i == -1 ? _split_t_by(idxs, t, [sub_str(t, 0, idxs[0])], i + 1) : ( + i == len(idxs) - 1 ? concat(ts, sub_str(t, idxs[i] + 1)) : + _split_t_by(idxs, t, concat(ts, sub_str(t, idxs[i] + 1, idxs[i + 1])), i + 1) + ); + +function split_str(t, delimiter) = + len(search(delimiter, t)) == 0 ? + [t] : _split_t_by(search(delimiter, t, 0)[0], t); + +/** +* rounded_square.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-rounded_square.html +* +**/ + + +module rounded_square(size, corner_r, center = false) { + is_flt = __is_float(size); + x = is_flt ? size : size[0]; + y = is_flt ? size : size[1]; + + position = center ? [0, 0] : [x / 2, y / 2]; + points = __trapezium( + length = x, + h = y, + round_r = corner_r + ); + + translate(position) + polygon(points); + + // hook for testing + test_rounded_square(position, points); +} + +// override it to test +module test_rounded_square(position, points) { +} + +/** +* m_mirror.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-m_mirror.html +* +**/ + +function m_mirror(v) = + let( + nv = v / norm(v), + txx = -2* nv[0] * nv[0], + txy = -2* nv[0] * nv[1], + txz = -2* nv[0] * nv[2], + tyy = -2* nv[1] * nv[1], + tyz = -2* nv[1] * nv[2], + tzz = -2* nv[2] * nv[2] + ) + [ + [1 + txx, txy, txz, 0], + [txy, 1 + tyy, tyz, 0], + [txz, tyz, 1 + tzz, 0], + [0, 0, 0, 1] + ]; + +/** +* bijection_offset.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-bijection_offset.html +* +**/ + + +function _bijection_inward_edge_normal(edge) = + let( + pt1 = edge[0], + pt2 = edge[1], + dx = pt2[0] - pt1[0], + dy = pt2[1] - pt1[1], + edge_leng = norm([dx, dy]) + ) + [-dy / edge_leng, dx / edge_leng]; + +function _bijection_outward_edge_normal(edge) = -1 * _bijection_inward_edge_normal(edge); + +function _bijection_offset_edge(edge, dx, dy) = + let( + pt1 = edge[0], + pt2 = edge[1], + dxy = [dx, dy] + ) + [pt1 + dxy, pt2 + dxy]; + +function _bijection__bijection_offset_edges(edges, d) = + [ + for(edge = edges) + let( + ow_normal = _bijection_outward_edge_normal(edge), + dx = ow_normal[0] * d, + dy = ow_normal[1] * d + ) + _bijection_offset_edge(edge, dx, dy) + ]; + +function bijection_offset(pts, d, epsilon = 0.0001) = + let( + es = __lines_from(pts, true), + offset_es = _bijection__bijection_offset_edges(es, d), + leng = len(offset_es), + last_p = __line_intersection(offset_es[leng - 1], offset_es[0], epsilon) + ) + concat( + [ + for(i = [0:leng - 2]) + let( + this_edge = offset_es[i], + next_edge = offset_es[i + 1], + p = __line_intersection(this_edge, next_edge, epsilon) + ) + // p == p to avoid [nan, nan], because [nan, nan] != [nan, nan] + if(p != [] && p == p) p + ], + last_p != [] && last_p == last_p ? [last_p] : [] + ); + + +/** +* m_scaling.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-m_scaling.html +* +**/ + + +function _to_3_elems_scaling_vect(s) = + let(leng = len(s)) + leng == 3 ? s : ( + leng == 2 ? [s[0], s[1], 1] : [s[0], 1, 1] + ); + +function _to_scaling_vect(s) = __is_float(s) ? [s, s, s] : _to_3_elems_scaling_vect(s); + +function m_scaling(s) = + let(v = _to_scaling_vect(s)) + [ + [v[0], 0, 0, 0], + [0, v[1], 0, 0], + [0, 0, v[2], 0], + [0, 0, 0, 1] + ]; + +/** +* m_shearing.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-m_shearing.html +* +**/ + + +function m_shearing(sx = [0, 0], sy = [0, 0], sz = [0, 0]) = __m_shearing(sx, sy, sz); + +/** +* shape_path_extend.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-path_extend.html +* +**/ + + +function _shape_path_extend_az(p1, p2) = + let( + x1 = p1[0], + y1 = p1[1], + x2 = p2[0], + y2 = p2[1] + ) -90 + atan2((y2 - y1), (x2 - x1)); + +function _shape_path_first_stroke(stroke_pts, path_pts) = + let( + p1 = path_pts[0], + p2 = path_pts[1], + a = _shape_path_extend_az(p1, p2) + ) + [ + for(p = stroke_pts) + rotate_p(p, a) + p1 + ]; + +function _shape_path_extend_stroke(stroke_pts, p1, p2, scale_step, i) = + let( + leng = norm(__to3d(p2) - __to3d(p1)), + a = _shape_path_extend_az(p1, p2) + ) + [ + for(p = stroke_pts) + rotate_p(p * (1 + scale_step * i) + [0, leng], a) + p1 + ]; + +function _shape_path_extend_inner(stroke_pts, path_pts, leng_path_pts, scale_step, index) = + index == leng_path_pts ? [] : + concat( + [ + _shape_path_extend_stroke( + stroke_pts, + path_pts[index - 1], + path_pts[index], + scale_step, + index + ) + ], + _shape_path_extend_inner( + stroke_pts, + path_pts, + leng_path_pts, + scale_step, + index + 1 + ) + ); + +function shape_path_extend(stroke_pts, path_pts, scale = 1.0, closed = false) = + let( + leng_path_pts = len(path_pts), + scale_step = (scale - 1) / (leng_path_pts - 1), + strokes = _shape_path_extend_inner(stroke_pts, path_pts, leng_path_pts, scale_step, 1) + ) + closed && path_pts[0] == path_pts[leng_path_pts - 1] ? + __polytransversals(concat(strokes, [strokes[0]])) : + __polytransversals( + concat([_shape_path_first_stroke(stroke_pts, path_pts)], strokes) + ); + + + +/** +* log.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-log.html +* +**/ + +/* + The accepted values are "OFF" (-1), "INFO" (20), + "WARNING" (30), "ERROR" (40) or positive integers. +*/ +$log_level = "INFO"; + +module log(level = "INFO", level_color) { + default_level_ints = [ + ["OFF", -1], + ["INFO", 20], + ["WARNING", 30], + ["ERROR", 40] + ]; + + default_level_colors = [ + ["INFO", "green"], + ["WARNING", "orange"], + ["ERROR", "red"] + ]; + + /* + The built-in lookup function require integer keys. + I overwrite it so that using string keys is ok. + */ + function lookup(key, mappings, i = 0) = + i == len(mappings) ? key : ( + key == mappings[i][0] ? + mappings[i][1] : + lookup(key, mappings, i + 1) + ); + + if($log_level != "OFF") { + argu_level = lookup(level, default_level_ints); + golbal_level = lookup( + $log_level == undef ? "INFO" : $log_level, + default_level_ints + ); + + if(argu_level >= golbal_level) { + c = level_color == undef ? lookup(level, default_level_colors) : level_color; + lv = str(len(level) == undef ? "LEVEL " : "", level); + + // level text + echo( + str( + "", + "", + lv, + "", + "" + ) + ); + + // echo + for(i = [0:$children - 1]) { + children(i); + } + } + } +} + +/** +* archimedean_spiral.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-archimedean_spiral.html +* +**/ + +function _radian_step(b, theta, l) = + let(r_square = pow(b * theta, 2)) + acos((2 * r_square - pow(l, 2)) / (2 * r_square)) / 180 * 3.14159; + +function _find_radians(b, point_distance, radians, n, count = 1) = + let(pre_radians = radians[count - 1]) + count == n ? radians : ( + _find_radians( + b, + point_distance, + concat( + radians, + [pre_radians + _radian_step(b, pre_radians, point_distance)] + ), + n, + count + 1) + ); + +function archimedean_spiral(arm_distance, init_angle, point_distance, num_of_points, rt_dir = "CT_CLK") = + let(b = arm_distance / 6.28318, init_radian = init_angle *3.14159 / 180) + [ + for(theta = _find_radians(b, point_distance, [init_radian], num_of_points)) + let(r = b * theta, a = (rt_dir == "CT_CLK" ? 1 : -1) * theta * 57.2958) + [[r * cos(a), r * sin(a)], a] + ]; + +function __reverse(vt) = + let(leng = len(vt)) + [ + for(i = [0:leng - 1]) + vt[leng - 1 - i] + ]; + +function __tr__corner_t_leng_lt_zero(frags, t_sector_angle, l1, l2, h, round_r) = + let(t_height = tan(t_sector_angle) * l1 - round_r / sin(90 - t_sector_angle) - h / 2) + [ + for(pt = __pie_for_rounding(round_r, 90 - t_sector_angle, 90, frags * t_sector_angle / 180)) + [pt[0], pt[1] + t_height] + ]; + +function __tr_corner_t_leng_gt_or_eq_zero(frags, t_sector_angle, t_leng, h, round_r) = + let(offset_y = h / 2 - round_r) + [ + for(pt = __pie_for_rounding(round_r, 90 - t_sector_angle, 90, frags * t_sector_angle / 360)) + [pt[0] + t_leng, pt[1] + offset_y] + ]; + +function __tr_corner(frags, b_ang, l1, l2, h, round_r) = + let(t_leng = l2 - round_r * tan(b_ang / 2)) + t_leng >= 0 ? + __tr_corner_t_leng_gt_or_eq_zero(frags, b_ang, t_leng, h, round_r) : + __tr__corner_t_leng_lt_zero(frags, b_ang, l1, l2, h, round_r); + +function __tr__corner_b_leng_lt_zero(frags, b_sector_angle, l1, l2, h, round_r) = + let( + reversed = __tr__corner_t_leng_lt_zero(frags, b_sector_angle, l2, l1, h, round_r), + leng = len(reversed) + ) + [ + for(i = [0:leng - 1]) + let(pt = reversed[leng - 1 - i]) + [pt[0], -pt[1]] + ]; + +function __br_corner_b_leng_gt_or_eq_zero(frags, b_sector_angle, l1, l2, b_leng, h, round_r) = + let(half_h = h / 2) + [ + for(pt = __pie_for_rounding(round_r, -90, -90 + b_sector_angle, frags * b_sector_angle / 360)) + [pt[0] + b_leng, pt[1] + round_r - half_h] + ]; + +function __br_corner(frags, b_ang, l1, l2, h, round_r) = + let(b_leng = l1 - round_r / tan(b_ang / 2)) + b_leng >= 0 ? + __br_corner_b_leng_gt_or_eq_zero(frags, 180 - b_ang, l1, l2, b_leng, h, round_r) : + __tr__corner_b_leng_lt_zero(frags, 180 - b_ang, l1, l2, h, round_r); + +function __half_trapezium(length, h, round_r) = + let( + is_flt = __is_float(length), + l1 = is_flt ? length : length[0], + l2 = is_flt ? length : length[1], + frags = __frags(round_r), + b_ang = atan2(h, l1 - l2), + br_corner = __br_corner(frags, b_ang, l1, l2, h, round_r), + tr_corner = __tr_corner(frags, b_ang, l1, l2, h, round_r) + ) + concat( + br_corner, + tr_corner + ); + +/** +* m_cumulate.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-m_cumulate.html +* +**/ + +function _m_cumulate(matrice, i) = + i == len(matrice) - 2 ? + matrice[i] * matrice[i + 1] : + matrice[i] * _m_cumulate(matrice, i + 1); + +function m_cumulate(matrice) = + len(matrice) == 1 ? matrice[0] : _m_cumulate(matrice, 0); + + + +/** +* shape_trapezium.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_trapezium.html +* +**/ + + +function shape_trapezium(length, h, corner_r = 0) = + __trapezium( + length = length, + h = h, + round_r = corner_r + ); + + +/** +* ellipse_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-ellipse_extrude.html +* +**/ + +module ellipse_extrude(semi_minor_axis, height, center = false, convexity = 10, twist = 0, slices = 20) { + h = height == undef ? semi_minor_axis : ( + // `semi_minor_axis` is always equal to or greater than `height`. + height > semi_minor_axis ? semi_minor_axis : height + ); + angle = asin(h / semi_minor_axis) / slices; + + function f_extrude(i = 1) = + i <= slices ? + concat( + [ + [ + cos(angle * i) / cos(angle * (i - 1)), + semi_minor_axis * sin(angle * i) + ] + ], + f_extrude(i + 1) + ) : []; + + fzs = f_extrude(); + len_fzs = len(fzs); + + function accm_fs(pre_f = 1, i = 0) = + i < len_fzs ? + concat( + [pre_f * fzs[i][0]], + accm_fs(pre_f * fzs[i][0], i + 1) + ) : []; + + child_fs = concat([1], accm_fs()); + pre_zs = concat( + [0], + [ + for(i = [0:len_fzs - 1]) + fzs[i][1] + ] + ); + + module extrude() { + for(i = [0:len_fzs - 1]) { + f = fzs[i][0]; + z = fzs[i][1]; + + translate([0, 0, pre_zs[i]]) + rotate(-twist / slices * i) + linear_extrude( + z - pre_zs[i], + convexity = convexity, + twist = twist / slices, + slices = 1, + scale = f + ) scale(child_fs[i]) children(); + } + } + + center_offset = [0, 0, center == true ? -h / 2 : 0]; + translate(center_offset) + extrude() + children(); + + // hook for testing + test_ellipse_extrude_fzc(child_fs, pre_zs, center_offset); +} + +// override for testing +module test_ellipse_extrude_fzc(child_fs, pre_zs, center_offset) { + +} + +/** +* line3d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-line3d.html +* +**/ + + +module line3d(p1, p2, thickness, p1Style = "CAP_CIRCLE", p2Style = "CAP_CIRCLE") { + r = thickness / 2; + + frags = __nearest_multiple_of_4(__frags(r)); + half_fa = 180 / frags; + + dx = p2[0] - p1[0]; + dy = p2[1] - p1[1]; + dz = p2[2] - p1[2]; + + length = sqrt(pow(dx, 2) + pow(dy, 2) + pow(dz, 2)); + ay = 90 - atan2(dz, sqrt(pow(dx, 2) + pow(dy, 2))); + az = atan2(dy, dx); + + angles = [0, ay, az]; + + module cap_with(p) { + translate(p) + rotate(angles) + children(); + } + + module cap_butt() { + cap_with(p1) + linear_extrude(length) + circle(r, $fn = frags); + + // hook for testing + test_line3d_butt(p1, r, frags, length, angles); + } + + module cap(p, style) { + if(style == "CAP_CIRCLE") { + cap_leng = r / 1.414; + cap_with(p) + linear_extrude(cap_leng * 2, center = true) + circle(r, $fn = frags); + + // hook for testing + test_line3d_cap(p, r, frags, cap_leng, angles); + } else if(style == "CAP_SPHERE") { + cap_leng = r / cos(half_fa); + cap_with(p) + sphere(cap_leng, $fn = frags); + + // hook for testing + test_line3d_cap(p, r, frags, cap_leng, angles); + } + } + + + cap_butt(); + cap(p1, p1Style); + cap(p2, p2Style); +} + +// Override them to test +module test_line3d_butt(p, r, frags, length, angles) { + +} + +module test_line3d_cap(p, r, frags, cap_leng, angles) { + +} + +/** +* arc_shape.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-arc_path.html +* +**/ + + +function arc_path(radius, angle) = + let( + frags = __frags(radius), + a_step = 360 / frags, + angles = __is_float(angle) ? [0, angle] : angle, + m = floor(angles[0] / a_step) + 1, + n = floor(angles[1] / a_step), + points = concat([__ra_to_xy(__edge_r_begin(radius, angles[0], a_step, m), angles[0])], + m > n ? [] : [ + for(i = [m:n]) + __ra_to_xy(radius, a_step * i) + ], + angles[1] == a_step * n ? [] : [__ra_to_xy(__edge_r_end(radius, angles[1], a_step, n), angles[1])]) + ) points; + +/** +* box_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-box_extrude.html +* +**/ + +module box_extrude(height, shell_thickness, offset_mode = "delta", chamfer = false) { + linear_extrude(shell_thickness) + children(); + + linear_extrude(height) + difference() { + children(); + if(offset_mode == "delta") { + offset(delta = -shell_thickness, chamfer = chamfer) + children(); + } else { + offset(r = -shell_thickness) + children(); + } + } +} + + + +/** +* m_translation.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-m_translation.html +* +**/ + + +function _to_3_elems_translation_vect(v) = + let(leng = len(v)) + leng == 3 ? v : ( + leng == 2 ? [v[0], v[1], 0] : [v[0], 0, 0] + ); + +function _to_translation_vect(v) = __is_float(v) ? [v, 0, 0] : _to_3_elems_translation_vect(v); + +function m_translation(v) = + let(vt = _to_translation_vect(v)) + [ + [1, 0, 0, vt[0]], + [0, 1, 0, vt[1]], + [0, 0, 1, vt[2]], + [0, 0, 0, 1] + ]; + +/** +* pie.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-pie.html +* +**/ + + +module pie(radius, angle) { + polygon(__shape_pie(radius, angle)); +} + +/** +* rounded_cylinder.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-rounded_cylinder.html +* +**/ + + +module rounded_cylinder(radius, h, round_r, convexity = 2, center = false) { + r_corners = __half_trapezium(radius, h, round_r); + + shape_pts = concat( + [[0, -h/2]], + r_corners, + [[0, h/2]] + ); + + center_pt = center ? [0, 0, 0] : [0, 0, h/2]; + + translate(center ? [0, 0, 0] : [0, 0, h/2]) + rotate(180) + rotate_extrude(convexity = convexity) + polygon(shape_pts); + + // hook for testing + test_center_half_trapezium(center_pt, shape_pts); +} + +// override it to test +module test_center_half_trapezium(center_pt, shape_pts) { + +} + + +function __frags(radius) = $fn > 0 ? + ($fn >= 3 ? $fn : 3) : + max(min(360 / $fa, radius * 6.28318 / $fs), 5); + +/** +* turtle2d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-turtle2d.html +* +**/ + +function _turtle2d_turtle(x, y, angle) = [[x, y], angle]; + +function _turtle2d_set_point(turtle, point) = [point, _turtle2d_get_angle(turtle)]; + +function _turtle2d_set_x(turtle, x) = [[x, _turtle2d_get_y(turtle)], _turtle2d_get_angle(turtle)]; +function _turtle2d_set_y(turtle, y) = [[_turtle2d_get_x(turtle), y], _turtle2d_get_angle(turtle)]; +function _turtle2d_set_angle(turtle, angle) = [_turtle2d_get_pt(turtle), angle]; + +function _turtle2d_forward(turtle, leng) = + _turtle2d_turtle( + _turtle2d_get_x(turtle) + leng * cos(_turtle2d_get_angle(turtle)), + _turtle2d_get_y(turtle) + leng * sin(_turtle2d_get_angle(turtle)), + _turtle2d_get_angle(turtle) + ); + +function _turtle2d_turn(turtle, angle) = [_turtle2d_get_pt(turtle), _turtle2d_get_angle(turtle) + angle]; + +function _turtle2d_get_x(turtle) = turtle[0][0]; +function _turtle2d_get_y(turtle) = turtle[0][1]; +function _turtle2d_get_pt(turtle) = turtle[0]; +function _turtle2d_get_angle(turtle) = turtle[1]; + +function _turtle2d_three_args_command(cmd, arg1, arg2, arg3) = + cmd == "create" ? _turtle2d_turtle(arg1, arg2, arg3) : _turtle2d_two_args_command(cmd, arg1, arg2); + +function _turtle2d_two_args_command(cmd, arg1, arg2) = + arg2 == undef ? _turtle2d_one_arg_command(cmd, arg1) : ( + cmd == "pt" ? _turtle2d_set_point(arg1, arg2) : ( + cmd == "x" ? _turtle2d_set_x(arg1, arg2) : ( + cmd == "y" ? _turtle2d_set_y(arg1, arg2) : ( + cmd == "angle" ? _turtle2d_set_angle(arg1, arg2) : ( + cmd == "forward" ? _turtle2d_forward(arg1, arg2) : ( + cmd == "turn" ? _turtle2d_turn(arg1, arg2) : undef + ) + ) + ) + ) + ) + ); + +function _turtle2d_one_arg_command(cmd, arg) = + cmd == "x" ? _turtle2d_get_x(arg) : ( + cmd == "y" ? _turtle2d_get_y(arg) : ( + cmd == "angle" ? _turtle2d_get_angle(arg) : ( + cmd == "pt" ? _turtle2d_get_pt(arg) : undef + ) + ) + ); + +function turtle2d(cmd, arg1, arg2, arg3) = + _turtle2d_three_args_command(cmd, arg1, arg2, arg3); + + +/** +* shape_ellipse.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_ellipse.html +* +**/ + + +function shape_ellipse(axes) = + let( + frags = __frags(axes[0]), + step_a = 360 / frags, + shape_pts = [ + for(a = [0:step_a:360 - step_a]) + [axes[0] * cos(a), axes[1] * sin(a)] + ] + ) shape_pts; + +/** +* shape_star.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_starburst.html +* +**/ + +function __outer_points_shape_starburst(r1, r2, n) = + let( + a = 360 / n + ) + [for(i = [0:n-1]) [r1 * cos(a * i), r1 * sin(a * i)]]; +function __inner_points_shape_starburst(r1, r2, n) = + let ( + a = 360 / n, + half_a = a / 2 + ) + [for(i = [0:n-1]) [r2 * cos(a * i + half_a), r2 * sin(a * i + half_a)]]; + +function __one_by_one_shape_starburst(outer_points, inner_points, i = 0) = + len(outer_points) == i ? [] : + concat([outer_points[i], inner_points[i]], __one_by_one_shape_starburst(outer_points, inner_points, i + 1)); + +function shape_starburst(r1, r2, n) = __one_by_one_shape_starburst( + __outer_points_shape_starburst(r1, r2, n), __inner_points_shape_starburst(r1, r2, n) +); + +function __in_line(line_pts, pt, epsilon = 0.0001) = + let( + pts = len(line_pts[0]) == 2 ? [for(p = line_pts) __to3d(p)] : line_pts, + pt3d = len(pt) == 2 ? __to3d(pt) : pt, + v1 = pts[0] - pt3d, + v2 = pts[1] - pt3d + ) + (norm(cross(v1, v2)) < epsilon) && ((v1 * v2) <= epsilon); + +/** +* polyline3d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-polyline3d.html +* +**/ + +module polyline3d(points, thickness, startingStyle = "CAP_CIRCLE", endingStyle = "CAP_CIRCLE") { + leng_pts = len(points); + + module line_segment(index) { + styles = index == 1 ? [startingStyle, "CAP_BUTT"] : ( + index == leng_pts - 1 ? ["CAP_SPHERE", endingStyle] : [ + "CAP_SPHERE", "CAP_BUTT" + ] + ); + + p1 = points[index - 1]; + p2 = points[index]; + p1Style = styles[0]; + p2Style = styles[1]; + + line3d(p1, p2, thickness, + p1Style = p1Style, p2Style = p2Style); + + // hook for testing + test_line3d_segment(index, p1, p2, thickness, p1Style, p2Style); + } + + module polyline3d_inner(index) { + if(index < leng_pts) { + line_segment(index); + polyline3d_inner(index + 1); + } + } + + polyline3d_inner(1); +} + +// override it to test +module test_line3d_segment(index, point1, point2, thickness, p1Style, p2Style) { + +} + +/** +* turtle3d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-turtle3d.html +* +**/ + +function _turtle3d_x(pt) = pt[0]; +function _turtle3d_y(pt) = pt[1]; +function _turtle3d_z(pt) = pt[2]; + +function _turtle3d_pt3D(x, y, z) = [x, y, z]; + +function _turtle3d_create(pt, unit_vts) = [pt, unit_vts]; +function _turtle3d_create_default() = _turtle3d_create( + _turtle3d_pt3D(0, 0, 0), + // unit vectors from the turtle's viewpoint + [_turtle3d_pt3D(1, 0, 0), _turtle3d_pt3D(0, 1, 0), _turtle3d_pt3D(0, 0, 1)] +); + +function _turtle3d_plus(pt, n) = + _turtle3d_pt3D(_turtle3d_x(pt) + n, _turtle3d_y(pt) + n, _turtle3d_z(pt) + n); +function _turtle3d_minus(pt, n) = + _turtle3d_pt3D(_turtle3d_x(pt) - n, _turtle3d_y(pt) - n, _turtle3d_z(pt) + n); +function _turtle3d_mlt(pt, n) = + _turtle3d_pt3D(_turtle3d_x(pt) * n, _turtle3d_y(pt) * n, _turtle3d_z(pt) * n); +function _turtle3d_div(pt, n) = + _turtle3d_pt3D(_turtle3d_x(pt) / n, _turtle3d_y(pt) / n, _turtle3d_z(pt) / n); +function _turtle3d_neg(pt, n) = + _turtle3d_mlt(pt, -1); + +function _turtle3d_ptPlus(pt1, pt2) = + _turtle3d_pt3D( + _turtle3d_x(pt1) + _turtle3d_x(pt2), + _turtle3d_y(pt1) + _turtle3d_y(pt2), + _turtle3d_z(pt1) + _turtle3d_z(pt2) + ); + + +function _turtle3d_pt(turtle) = turtle[0]; +function _turtle3d_unit_vts(turtle) = turtle[1]; + +// forward the turtle in the x' direction +function _turtle3d_xu_move(turtle, leng) = _turtle3d_create( + _turtle3d_ptPlus(_turtle3d_pt(turtle), _turtle3d_mlt(_turtle3d_unit_vts(turtle)[0], leng)), + _turtle3d_unit_vts(turtle) +); + +// forward the turtle in the y' direction +function _turtle3d_yu_move(turtle, leng) = _turtle3d_create( + _turtle3d_ptPlus(_turtle3d_pt(turtle), _turtle3d_mlt(_turtle3d_unit_vts(turtle)[1], leng)), + _turtle3d_unit_vts(turtle) +); + +// forward the turtle in the z' direction +function _turtle3d_zu_move(turtle, leng) = _turtle3d_create( + _turtle3d_ptPlus( + _turtle3d_pt(turtle), + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[2], leng) + ), + _turtle3d_unit_vts(turtle) +); + +// turn the turtle around the x'-axis +// return a new unit vector +function _turtle3d_xu_turn(turtle, a) = + let(cosa = cos(a), sina = sin(a)) + _turtle3d_create( + _turtle3d_pt(turtle), + [ + _turtle3d_unit_vts(turtle)[0], + _turtle3d_ptPlus( + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[1], cosa), + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[2], sina) + ), + _turtle3d_ptPlus( + _turtle3d_mlt(_turtle3d_neg(_turtle3d_unit_vts(turtle)[1]), sina), + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[2], cosa) + ) + ] + ); + +// turn the turtle around the y'-axis +// return a new unit vector +function _turtle3d_yu_turn(turtle, a) = + let(cosa = cos(a), sina = sin(a)) + _turtle3d_create( + _turtle3d_pt(turtle), + [ + _turtle3d_ptPlus( + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[0], cosa), + _turtle3d_mlt(_turtle3d_neg(_turtle3d_unit_vts(turtle)[2]), sina) + ), + _turtle3d_unit_vts(turtle)[1], + _turtle3d_ptPlus( + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[0], sina), + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[2], cosa) + ) + ] + ); + +// turn the turtle around the z'-axis +// return a new unit vector +function _turtle3d_zu_turn(turtle, a) = + let(cosa = cos(a), sina = sin(a)) + _turtle3d_create( + _turtle3d_pt(turtle), + [ + _turtle3d_ptPlus( + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[0], cosa), + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[1], sina) + ), + _turtle3d_ptPlus( + _turtle3d_mlt(_turtle3d_neg(_turtle3d_unit_vts(turtle)[0]), sina), + _turtle3d_mlt(_turtle3d_unit_vts(turtle)[1], cosa) + ), + _turtle3d_unit_vts(turtle)[2], + ] + ); + +function _turtle3d_create_cmd(arg1, arg2) = + (arg1 == undef && arg2 == undef) ? _turtle3d_create_default() : ( + (arg1 != undef && arg2 != undef) ? _turtle3d_create(arg1, arg2) : undef + ); + +function _turtle3d_chain_move(cmd, arg1, arg2) = + cmd == "xu_move" ? _turtle3d_xu_move(arg1, arg2) : ( + cmd == "yu_move" ? _turtle3d_yu_move(arg1, arg2) : ( + cmd == "zu_move" ? _turtle3d_zu_move(arg1, arg2) : _turtle3d_chain_turn(cmd, arg1, arg2) + ) + ); + +function _turtle3d_chain_turn(cmd, arg1, arg2) = + cmd == "xu_turn" ? _turtle3d_xu_turn(arg1, arg2) : ( + cmd == "yu_turn" ? _turtle3d_yu_turn(arg1, arg2) : ( + cmd == "zu_turn" ? _turtle3d_zu_turn(arg1, arg2) : _turtle3d_chain_one_arg(cmd, arg1) + ) + ); + +function _turtle3d_chain_one_arg(cmd, arg) = + cmd == "pt" ? _turtle3d_pt(arg) : ( + cmd == "unit_vts" ? _turtle3d_unit_vts(arg) : undef + ); + +function turtle3d(cmd, arg1, arg2) = + cmd == "create" ? + _turtle3d_create_cmd(arg1, arg2) : + _turtle3d_chain_move(cmd, arg1, arg2); + +/** +* sub_str.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-sub_str.html +* +**/ + +function sub_str(t, begin, end, result = "") = + end == undef ? sub_str(t, begin, len(t)) : ( + begin == end ? result : sub_str(t, begin + 1, end, str(result, t[begin])) + ); + + +/** +* cross_sections.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-cross_sections.html +* +**/ + + +function cross_sections(shape_pts, path_pts, angles, twist = 0, scale = 1.0) = + let( + len_path_pts_minus_one = len(path_pts) - 1, + sh_pts = len(shape_pts[0]) == 3 ? shape_pts : [for(p = shape_pts) __to3d(p)], + pth_pts = len(path_pts[0]) == 3 ? path_pts : [for(p = path_pts) __to3d(p)], + scale_step_vt = __is_float(scale) ? + [(scale - 1) / len_path_pts_minus_one, (scale - 1) / len_path_pts_minus_one] : + [(scale[0] - 1) / len_path_pts_minus_one, (scale[1] - 1) / len_path_pts_minus_one] + , + scale_step_x = scale_step_vt[0], + scale_step_y = scale_step_vt[1], + twist_step = twist / len_path_pts_minus_one + ) + [ + for(i = [0:len_path_pts_minus_one]) + [ + for(p = sh_pts) + let(scaled_p = [p[0] * (1 + scale_step_x * i), p[1] * (1 + scale_step_y * i), p[2]]) + rotate_p( + rotate_p(scaled_p, twist_step * i) + , angles[i] + ) + pth_pts[i] + ] + ]; + + +/** +* archimedean_spiral_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-archimedean_spiral_extrude.html +* +**/ + +module archimedean_spiral_extrude(shape_pts, arm_distance, init_angle, point_distance, num_of_points, + rt_dir = "CT_CLK", twist = 0, scale = 1.0, triangles = "SOLID") { + points_angles = archimedean_spiral( + arm_distance = arm_distance, + init_angle = init_angle, + point_distance = point_distance, + num_of_points = num_of_points, + rt_dir = rt_dir + ); + + clk_a = rt_dir == "CT_CLK" ? 0 : 180; + + points = [for(pa = points_angles) pa[0]]; + angles = [ + for(pa = points_angles) + [90, 0, pa[1] + clk_a] + ]; + + sections = cross_sections(shape_pts, points, angles, twist, scale); + + polysections( + sections, + triangles = triangles + ); + + // testing hook + test_archimedean_spiral_extrude(sections); +} + +// override it to test +module test_archimedean_spiral_extrude(sections) { + +} + +/** +* in_polyline.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-in_polyline.html +* +**/ + + +function _in_polyline_sub(pts, pt, epsilon, iend, i = 0) = + i == iend ? false : ( + __in_line([pts[i], pts[i + 1]], pt, epsilon) ? true : + _in_polyline_sub(pts, pt, epsilon, iend, i + 1) + ); + +function in_polyline(line_pts, pt, epsilon = 0.0001) = + _in_polyline_sub(line_pts, pt, epsilon, len(line_pts) - 1); + + +/** +* voronoi3d.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-voronoi3d.html +* +**/ + + +// slow but workable + +module voronoi3d(points, space_size = "auto", spacing = 1) { + xs = [for(p = points) p[0]]; + ys = [for(p = points) abs(p[1])]; + zs = [for(p = points) abs(p[2])]; + + space_size = max([max(xs) - min(xs), max(ys) - min(ys), max(zs) - min(zs)]); + half_space_size = 0.5 * space_size; + offset_leng = spacing * 0.5 + half_space_size; + + function normalize(v) = v / norm(v); + + module space(pt) { + intersection_for(p = points) { + if(pt != p) { + v = p - pt; + ryz = __angy_angz(p, pt); + + translate((pt + p) / 2 - normalize(v) * offset_leng) + rotate([0, -ryz[0], ryz[1]]) + cube([space_size, space_size * 2, space_size * 2], center = true); + } + } + } + + for(p = points) { + space(p); + } +} + +/** +* crystal_ball.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-crystal_ball.html +* +**/ + + +module crystal_ball(radius, theta = 360, phi = 180) { + phis = __is_float(phi) ? [0, phi] : phi; + + frags = __frags(radius); + + shape_pts = shape_pie( + radius, + [90 - phis[1], 90 - phis[0]], + $fn = __nearest_multiple_of_4(frags) + ); + + // _hole_r = 0.0005 for avoiding warnings when using 2015.03 + // I downloaded 2017.01.20 and found that the problem is solved. + _hole_r = version_num() >= 20170120 ? 0 : 0.0005; + ring_extrude( + shape_pts, + angle = theta, + radius = _hole_r, + $fn = frags + ); + + // hook for testing + test_crystal_ball_pie(shape_pts); +} + +// override it to test +module test_crystal_ball_pie(shape_pts) { + +} + +/** +* trim_shape.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-trim_shape.html +* +**/ + + +function _trim_shape_any_intersection_sub(lines, line, lines_leng, i, epsilon) = + let( + p = __line_intersection(lines[i], line, epsilon) + ) + (p != [] && __in_line(line, p, epsilon) && __in_line(lines[i], p, epsilon)) ? [i, p] : _trim_shape_any_intersection(lines, line, lines_leng, i + 1, epsilon); + +// return [idx, [x, y]] or [] +function _trim_shape_any_intersection(lines, line, lines_leng, i, epsilon) = + i == lines_leng ? [] : _trim_shape_any_intersection_sub(lines, line, lines_leng, i, epsilon); + +function _trim_sub(lines, leng, epsilon) = + let( + current_line = lines[0], + next_line = lines[1], + lines_from_next = [for(j = [1 : leng - 1]) lines[j]], + lines_from_next2 = [for(j = [2 : leng - 1]) lines[j]], + current_p = current_line[0], + leng_lines_from_next2 = len(lines_from_next2), + inter_p = _trim_shape_any_intersection(lines_from_next2, current_line, leng_lines_from_next2, 0, epsilon) + ) + // no intersecting pt, collect current_p and trim remain lines + inter_p == [] ? (concat([current_p], _trim_shape_trim_lines(lines_from_next, epsilon))) : ( + // collect current_p, intersecting pt and the last pt + (leng == 3 || (inter_p[0] == (leng_lines_from_next2 - 1))) ? [current_p, inter_p[1], lines[leng - 1]] : ( + // collect current_p, intersecting pt and trim remain lines + concat([current_p, inter_p[1]], + _trim_shape_trim_lines([for(i = [inter_p[0] + 1 : leng_lines_from_next2 - 1]) lines_from_next2[i]], epsilon) + ) + ) + ); + +function _trim_shape_trim_lines(lines, epsilon) = + let(leng = len(lines)) + leng > 2 ? _trim_sub(lines, leng, epsilon) : _trim_shape_collect_pts_from(lines, leng); + +function _trim_shape_collect_pts_from(lines, leng) = + concat([for(line = lines) line[0]], [lines[leng - 1][1]]); + +function trim_shape(shape_pts, from, to, epsilon = 0.0001) = + let( + pts = [for(i = [from:to]) shape_pts[i]], + trimmed = _trim_shape_trim_lines(__lines_from(pts), epsilon) + ) + len(shape_pts) == len(trimmed) ? trimmed : trim_shape(trimmed, 0, len(trimmed) - 1, epsilon); + + +/** +* stereographic_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-stereographic_extrude.html +* +**/ + +module stereographic_extrude(shadow_side_leng) { + half_side_length = shadow_side_leng / 2; + outer_sphere_r = half_side_length / 3; + a = atan(sqrt(2) * half_side_length / (2 * outer_sphere_r)); + inner_sphere_r = outer_sphere_r * sin(a); + + intersection() { + translate([0, 0, outer_sphere_r]) difference() { + sphere(outer_sphere_r); + sphere(outer_sphere_r / 2 + inner_sphere_r / 2); + + translate([0, 0, outer_sphere_r / 2]) + linear_extrude(outer_sphere_r) + circle(inner_sphere_r * cos(a)); + } + + linear_extrude(outer_sphere_r * 2, scale = 0.01) + children(); + } + + // hook for testing + test_stereographic_extrude_rs(outer_sphere_r, inner_sphere_r); +} + +// override for testing +module test_stereographic_extrude_rs(outer_sphere_r, inner_sphere_r) { + +} + +function __m_shearing(sx, sy, sz) = + let( + sx_along_y = sx[0], + sx_along_z = sx[1], + sy_along_x = sy[0], + sy_along_z = sy[1], + sz_along_x = sz[0], + sz_along_y = sz[1] + ) + [ + [1, sx_along_y, sx_along_z, 0], + [sy_along_x, 1, sy_along_z, 0], + [sz_along_x, sz_along_y, 1, 0], + [0, 0, 0, 1] + ]; + +/** +* polytransversals.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-polytransversals.html +* +**/ + + +module polytransversals(transversals) { + polygon( + __polytransversals(transversals) + ); +} + +/** +* hexagons.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-hexagons.html +* +**/ + +module hexagons(radius, spacing, levels) { + beginning_n = 2 * levels - 1; + offset_x = radius * cos(30); + offset_y = radius + radius * sin(30); + r_hexagon = radius - spacing / 2; + offset_step = 2 * offset_x; + center_offset = 2 * (offset_x - offset_x * levels); + + module hexagon() { + rotate(30) + circle(r_hexagon, $fn = 6); + } + + function hexagons_pts(hex_datum) = + let( + tx = hex_datum[0][0], + ty = hex_datum[0][1], + n = hex_datum[1], + offset_xs = [for(i = [0:n - 1]) i * offset_step + center_offset] + ) + [ + for(x = offset_xs) [x + tx, ty] + ]; + + module line_hexagons(hex_datum) { + tx = hex_datum[0][0]; + ty = hex_datum[0][1]; + n = hex_datum[1]; + + offset_xs = [for(i = [0:n - 1]) i * offset_step + center_offset]; + for(x = offset_xs) { + p = [x + tx, ty, 0]; + translate(p) + hexagon(); + } + } + + upper_hex_data = levels > 1 ? [ + for(i = [1:beginning_n - levels]) + let( + x = offset_x * i, + y = offset_y * i, + n = beginning_n - i + ) [[x, y], n] + ] : []; + + lower_hex_data = levels > 1 ? [ + for(hex_datum = upper_hex_data) + [[hex_datum[0][0], -hex_datum[0][1]], hex_datum[1]] + ] : []; + + total_hex_data = concat( + [ + [[0, 0], beginning_n] // first line + ], + upper_hex_data, + lower_hex_data + ); + + pts_all_lines = [ + for(hex_datum = total_hex_data) + hexagons_pts(hex_datum) + ]; + + for(pts_one_line = pts_all_lines) { + for(pt = pts_one_line) { + translate(pt) + hexagon(); + } + } + + test_each_hexagon(r_hexagon, pts_all_lines); +} + +// override it to test +module test_each_hexagon(hex_r, pts_all_lines) { + +} + +/** +* bend.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-bend.html +* +**/ + + +module bend(size, angle, frags = 24) { + x = size[0]; + y = size[1]; + z = size[2]; + frag_width = x / frags; + frag_angle = angle / frags; + half_frag_width = 0.5 * frag_width; + half_frag_angle = 0.5 * frag_angle; + r = half_frag_width / sin(half_frag_angle); + h = r * cos(half_frag_angle); + + tri_frag_pts = [ + [0, 0], + [half_frag_width, h], + [frag_width, 0], + [0, 0] + ]; + + module triangle_frag() { + translate([0, -z, 0]) + linear_extrude(y) + polygon(tri_frag_pts); + } + + module get_frag(i) { + translate([-frag_width * i - half_frag_width, -h + z, 0]) + intersection() { + translate([frag_width * i, 0, 0]) + triangle_frag(); + rotate([90, 0, 0]) + children(); + } + } + + rotate(90) for(i = [0 : frags - 1]) { + rotate(i * frag_angle + half_frag_angle) + get_frag(i) + children(); + } + + // hook for testing + test_bend_tri_frag(tri_frag_pts, frag_angle); +} + +// override it to test +module test_bend_tri_frag(points, angle) { + +} + +function __to3d(p) = [p[0], p[1], 0]; + +/** +* hull_polyline3d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/hull_polyline3d.html +* +**/ + +module hull_polyline3d(points, thickness) { + half_thickness = thickness / 2; + leng = len(points); + + module hull_line3d(index) { + point1 = points[index - 1]; + point2 = points[index]; + + hull() { + translate(point1) + sphere(half_thickness); + translate(point2) + sphere(half_thickness); + } + + // hook for testing + test_line_segment(index, point1, point2, half_thickness); + } + + module polyline3d_inner(index) { + if(index < leng) { + hull_line3d(index); + polyline3d_inner(index + 1); + } + } + + polyline3d_inner(1); +} + +// override it to test +module test_line_segment(index, point1, point2, radius) { + +} + +/** +* bezier_curve.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-bezier_curve.html +* +**/ + + +function _combi(n, k) = + let( + bi_coef = [ + [1], // n = 0: for padding + [1,1], // n = 1: for Linear curves, how about drawing a line directly? + [1,2,1], // n = 2: for Quadratic curves + [1,3,3,1] // n = 3: for Cubic Bézier curves + ] + ) + n < len(bi_coef) ? bi_coef[n][k] : ( + k == 0 ? 1 : (_combi(n, k - 1) * (n - k + 1) / k) + ); + +function bezier_curve_coordinate(t, pn, n, i = 0) = + i == n + 1 ? 0 : + (_combi(n, i) * pn[i] * pow(1 - t, n - i) * pow(t, i) + + bezier_curve_coordinate(t, pn, n, i + 1)); + +function _bezier_curve_point(t, points) = + let(n = len(points) - 1) + [ + bezier_curve_coordinate( + t, + [for(p = points) p[0]], + n + ), + bezier_curve_coordinate( + t, + [for(p = points) p[1]], + n + ), + bezier_curve_coordinate( + t, + [for(p = points) p[2]], + n + ) + ]; + +function bezier_curve(t_step, points) = + let( + pts = concat([ + for(t = [0: ceil(1 / t_step) - 1]) + _bezier_curve_point(t * t_step, points) + ], [_bezier_curve_point(1, points)]) + ) + len(points[0]) == 3 ? pts : [for(pt = pts) __to2d(pt)]; + + +/** +* multi_line_text.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-multi_line_text.html +* +**/ +module multi_line_text(lines, line_spacing = 15, size = 10, font = "Arial", halign = "left", valign = "baseline", direction = "ltr", language = "en", script = "latin"){ + to = len(lines) - 1; + inc = line_spacing; + offset_y = inc * to / 2; + union() { + for (i = [0 : to]) { + translate([0 , -i * inc + offset_y, 0]) + text(lines[i], size, font = font, valign = valign, halign = halign, direction = direction, language = language, script = script); + } + } +} + +/** +* helix_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-helix_extrude.html +* +**/ + + +module helix_extrude(shape_pts, radius, levels, level_dist, + vt_dir = "SPI_DOWN", rt_dir = "CT_CLK", + twist = 0, scale = 1.0, triangles = "SOLID") { + + function reverse(vt) = + let(leng = len(vt)) + [ + for(i = [0:leng - 1]) + vt[leng - 1 - i] + ]; + + is_flt = __is_float(radius); + r1 = is_flt ? radius : radius[0]; + r2 = is_flt ? radius : radius[1]; + + init_r = vt_dir == "SPI_DOWN" ? r2 : r1; + + frags = __frags(init_r); + + v_dir = vt_dir == "SPI_UP" ? 1 : -1; + r_dir = rt_dir == "CT_CLK" ? 1 : -1; + + angle_step = 360 / frags * r_dir; + initial_angle = atan2(level_dist / frags, 6.28318 * init_r / frags) * v_dir * r_dir; + + path_points = helix( + radius = radius, + levels = levels, + level_dist = level_dist, + vt_dir = vt_dir, + rt_dir = rt_dir + ); + + clk_a = r_dir == 1 ? 0 : 180; + angles = [for(i = [0:len(path_points) - 1]) [90 + initial_angle, 0, clk_a + angle_step * i]]; + + sections = cross_sections(shape_pts, path_points, angles, twist, scale); + + polysections( + sections, + triangles = triangles + ); + + // hook for testing + test_helix_extrude(sections); +} + +// override it to test +module test_helix_extrude(sections) { + +} + +function __is_float(value) = value + 0 != undef; + +/** +* hollow_out.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-hollow_out.html +* +**/ + +module hollow_out(shell_thickness) { + difference() { + children(); + offset(delta = -shell_thickness) children(); + } +} + +/** +* golden_spiral.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-golden_spiral.html +* +**/ + +function _fast_fibonacci_sub(nth) = + let( + _f = _fast_fibonacci_2_elems(floor(nth / 2)), + a = _f[0], + b = _f[1], + c = a * (b * 2 - a), + d = a * a + b * b + ) + nth % 2 == 0 ? [c, d] : [d, c + d]; + +function _fast_fibonacci_2_elems(nth) = + nth == 0 ? [0, 1] : _fast_fibonacci_sub(nth); + +function _fast_fibonacci(nth) = + _fast_fibonacci_2_elems(nth)[0]; + +function _remove_same_pts(pts1, pts2) = + pts1[len(pts1) - 1] == pts2[0] ? + concat(pts1, [for(i = [1:len(pts2) - 1]) pts2[i]]) : + concat(pts1, pts2); + +function _golden_spiral_from_ls_or_eql_to(from, to, point_distance, rt_dir) = + let( + f1 = _fast_fibonacci(from), + f2 = _fast_fibonacci(from + 1), + fn = floor(f1 * 6.28312 / point_distance), + $fn = fn + 4 - (fn % 4), + circle_pts = circle_path(radius = f1, n = $fn / 4 + 1), + len_pts = len(circle_pts), + a_step = 360 / $fn * rt_dir, + arc_points_angles = (rt_dir == 1 ? [ + for(i = [0:len_pts - 1]) + [circle_pts[i], a_step * i] + ] : [ + for(i = [0:len_pts - 1]) let(idx = len_pts - i - 1) + [circle_pts[idx], a_step * i] + ]), + offset = f2 - f1 + ) _remove_same_pts( + arc_points_angles, + [ + for(pt_a = _golden_spiral(from + 1, to, point_distance, rt_dir)) + [ + rotate_p(pt_a[0], [0, 0, 90 * rt_dir]) + + (rt_dir == 1 ? [0, -offset, 0] : [-offset, 0, 0]), + pt_a[1] + 90 * rt_dir + ] + ] + ); + +function _golden_spiral(from, to, point_distance, rt_dir) = + from <= to ? + _golden_spiral_from_ls_or_eql_to(from, to, point_distance, rt_dir) : []; + +function golden_spiral(from, to, point_distance, rt_dir = "CT_CLK") = + _golden_spiral(from, to, point_distance, (rt_dir == "CT_CLK" ? 1 : -1)); + +function __shape_arc(radius, angle, width, width_mode = "LINE_CROSS") = + let( + w_offset = width_mode == "LINE_CROSS" ? [width / 2, -width / 2] : ( + width_mode == "LINE_INWARD" ? [0, -width] : [width, 0] + ), + frags = __frags(radius), + a_step = 360 / frags, + half_a_step = a_step / 2, + angles = __is_float(angle) ? [0, angle] : angle, + m = floor(angles[0] / a_step) + 1, + n = floor(angles[1] / a_step), + r_outer = radius + w_offset[0], + r_inner = radius + w_offset[1], + points = concat( + // outer arc path + [__ra_to_xy(__edge_r_begin(r_outer, angles[0], a_step, m), angles[0])], + m > n ? [] : [ + for(i = [m:n]) + __ra_to_xy(r_outer, a_step * i) + ], + angles[1] == a_step * n ? [] : [__ra_to_xy(__edge_r_end(r_outer, angles[1], a_step, n), angles[1])], + // inner arc path + angles[1] == a_step * n ? [] : [__ra_to_xy(__edge_r_end(r_inner, angles[1], a_step, n), angles[1])], + m > n ? [] : [ + for(i = [m:n]) + let(idx = (n + (m - i))) + __ra_to_xy(r_inner, a_step * idx) + + ], + [__ra_to_xy(__edge_r_begin(r_inner, angles[0], a_step, m), angles[0])] + ) + ) points; + +/** +* sphere_spiral.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-sphere_spiral.html +* +**/ + +function sphere_spiral(radius, za_step, z_circles = 1, begin_angle = 0, end_angle = 0, vt_dir = "SPI_DOWN", rt_dir = "CT_CLK") = + [ + for(a = [begin_angle:za_step:90 * z_circles - end_angle]) + let( + ya = vt_dir == "SPI_DOWN" ? (-90 + 2 * a / z_circles) : (90 + 2 * a / z_circles), + za = (rt_dir == "CT_CLK" ? 1 : -1) * a, + ra = [0, ya, za] + ) + [rotate_p([radius, 0, 0], ra), ra] + ]; + +/** +* hull_polyline2d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/hull_polyline2d.html +* +**/ + +module hull_polyline2d(points, width) { + half_width = width / 2; + leng = len(points); + + module hull_line2d(index) { + point1 = points[index - 1]; + point2 = points[index]; + + hull() { + translate(point1) + circle(half_width); + translate(point2) + circle(half_width); + } + + // hook for testing + test_line_segment(index, point1, point2, half_width); + } + + module polyline2d_inner(index) { + if(index < leng) { + hull_line2d(index); + polyline2d_inner(index + 1); + } + } + + polyline2d_inner(1); +} + +// override it to test +module test_line_segment(index, point1, point2, radius) { + +} + +function __polytransversals(transversals) = + let( + leng_trs = len(transversals), + leng_tr = len(transversals[0]), + lefts = [ + for(i = [1:leng_trs - 2]) + let(tr = transversals[leng_trs - i]) + tr[0] + ], + rights = [ + for(i = [1:leng_trs - 2]) + let(tr = transversals[i]) + tr[leng_tr - 1] + ] + ) concat( + transversals[0], + rights, + __reverse(transversals[leng_trs - 1]), + lefts + ); + +/** +* bend_extrude.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-bend_extrude.html +* +**/ + +module bend_extrude(size, thickness, angle, frags = 24) { + x = size[0]; + y = size[1]; + frag_width = x / frags ; + frag_angle = angle / frags; + half_frag_width = 0.5 * frag_width; + half_frag_angle = 0.5 * frag_angle; + r = half_frag_width / sin(half_frag_angle); + s = (r - thickness) / r; + + module get_frag(i) { + offsetX = i * frag_width; + linear_extrude(thickness, scale = [s, 1]) + translate([-offsetX - half_frag_width, 0, 0]) + intersection() { + translate([x, 0, 0]) mirror([1, 0, 0]) children(); + translate([offsetX, 0, 0]) + square([frag_width, y]); + } + } + + offsetY = -r * cos(half_frag_angle) ; + rotate([180, 0, 180]) for(i = [0 : frags - 1]) { + rotate(i * frag_angle + half_frag_angle) + translate([0, offsetY, 0]) + rotate([-90, 0, 0]) + get_frag(i) + children(); + } + +} + + +/** +* helix.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-helix.html +* +**/ + + +function helix(radius, levels, level_dist, vt_dir = "SPI_DOWN", rt_dir = "CT_CLK") = + let( + is_flt = __is_float(radius), + r1 = is_flt ? radius : radius[0], + r2 = is_flt ? radius : radius[1], + init_r = vt_dir == "SPI_DOWN" ? r2 : r1, + _frags = __frags(init_r), + h = level_dist * levels, + vt_d = vt_dir == "SPI_DOWN" ? 1 : -1, + rt_d = rt_dir == "CT_CLK" ? 1 : -1, + r_diff = (r1 - r2) * vt_d, + h_step = level_dist / _frags * vt_d, + r_step = r_diff / (levels * _frags), + a_step = 360 / _frags * rt_d, + begin_r = vt_dir == "SPI_DOWN" ? r2 : r1, + begin_h = vt_dir == "SPI_DOWN" ? h : 0 + ) + [ + for(i = [0:_frags * levels]) + let(r = begin_r + r_step * i, a = a_step * i) + [r * cos(a), r * sin(a), begin_h - h_step * i] + ]; + +/** +* polyline2d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-polyline2d.html +* +**/ + +module polyline2d(points, width, startingStyle = "CAP_SQUARE", endingStyle = "CAP_SQUARE") { + leng_pts = len(points); + + module line_segment(index) { + styles = index == 1 ? [startingStyle, "CAP_ROUND"] : ( + index == leng_pts - 1 ? ["CAP_BUTT", endingStyle] : [ + "CAP_BUTT", "CAP_ROUND" + ] + ); + + p1 = points[index - 1]; + p2 = points[index]; + p1Style = styles[0]; + p2Style = styles[1]; + + line2d(points[index - 1], points[index], width, + p1Style = p1Style, p2Style = p2Style); + + // hook for testing + test_line_segment(index, p1, p2, width, p1Style, p2Style); + } + + module polyline2d_inner(index) { + if(index < leng_pts) { + line_segment(index); + polyline2d_inner(index + 1); + } + } + + polyline2d_inner(1); +} + +// override it to test +module test_line_segment(index, point1, point2, width, p1Style, p2Style) { + +} + +/** +* shape_pentagram.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_ellipse.html +* +**/ + +function shape_pentagram(r) = + [ + [0, 1], [-0.224514, 0.309017], + [-0.951057, 0.309017], [-0.363271, -0.118034], + [-0.587785, -0.809017], [0, -0.381966], + [0.587785, -0.809017], [0.363271, -0.118034], + [0.951057, 0.309017], [0.224514, 0.309017] + ] * r; + +/** +* along_with.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-along_with.html +* +**/ + + +// Becuase of improving the performance, this module requires m_rotation.scad which doesn't require in dotSCAD 1.0. +// For backward compatibility, I directly include m_rotation here. + +module along_with(points, angles, twist = 0, scale = 1.0, method = "AXIS_ANGLE") { + leng_points = len(points); + leng_points_minus_one = leng_points - 1; + twist_step_a = twist / leng_points; + + angles_defined = angles != undef; + + scale_step_vt = __is_float(scale) ? + scale_step() : + [ + (scale[0] - 1) / leng_points_minus_one, + (scale[1] - 1) / leng_points_minus_one, + scale[2] == undef ? 0 : (scale[2] - 1) / leng_points_minus_one + ]; + + + function scale_step() = + let(s = (scale - 1) / leng_points_minus_one) + [s, s, s]; + + + /* + Sadly, children(n) cannot be used with inner modules + so I have to do things in the first level. Ugly!! + */ + + // >>> begin: modules and functions for "AXIS-ANGLE" + + // get rotation matrice for sections + identity_matrix = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + function axis_angle_local_ang_vects(j) = + j == 0 ? [] : axis_angle_local_ang_vects_sub(j); + + function axis_angle_local_ang_vects_sub(j) = + let( + vt0 = points[j] - points[j - 1], + vt1 = points[j + 1] - points[j], + a = acos((vt0 * vt1) / (norm(vt0) * norm(vt1))), + v = cross(vt0, vt1) + ) + concat([[a, v]], axis_angle_local_ang_vects(j - 1)); + + function axis_angle_cumulated_rot_matrice(i, rot_matrice) = + let( + leng_rot_matrice = len(rot_matrice), + leng_rot_matrice_minus_one = leng_rot_matrice - 1, + leng_rot_matrice_minus_two = leng_rot_matrice - 2 + ) + leng_rot_matrice == 0 ? [identity_matrix] : ( + leng_rot_matrice == 1 ? [rot_matrice[0], identity_matrix] : ( + i == leng_rot_matrice_minus_two ? + [ + rot_matrice[leng_rot_matrice_minus_one], + rot_matrice[leng_rot_matrice_minus_two] * rot_matrice[leng_rot_matrice_minus_one] + ] + : axis_angle_cumulated_rot_matrice_sub(i, rot_matrice) + ) + ); + + function axis_angle_cumulated_rot_matrice_sub(i, rot_matrice) = + let( + matrice = axis_angle_cumulated_rot_matrice(i + 1, rot_matrice), + curr_matrix = rot_matrice[i], + prev_matrix = matrice[len(matrice) - 1] + ) + concat(matrice, [curr_matrix * prev_matrix]); + + // align modules + + module axis_angle_align_with_pts_angles(i) { + translate(points[i]) + rotate(angles[i]) + rotate(twist_step_a * i) + scale([1, 1, 1] + scale_step_vt * i) + children(0); + } + + module axis_angle_align_with_pts_init(a, s) { + angleyz = __angy_angz(__to3d(points[0]), __to3d(points[1])); + + rotate([0, -angleyz[0], angleyz[1]]) + rotate([90, 0, -90]) + rotate(a) + scale(s) + children(0); + } + + module axis_angle_align_with_pts_local_rotate(j, init_a, init_s, cumu_rot_matrice) { + if(j == 0) { // first child + axis_angle_align_with_pts_init(init_a, init_s) + children(0); + } + else { + multmatrix(cumu_rot_matrice[j - 1]) + axis_angle_align_with_pts_init(init_a, init_s) + children(0); + } + } + + // <<< end: modules and functions for "AXIS-ANGLE" + + + // >>> begin: modules and functions for "EULER-ANGLE" + + function _euler_angle_path_angles(pts, end_i, i = 0) = + i == end_i ? + [] : + concat( + [__angy_angz(pts[i], pts[i + 1])], + _euler_angle_path_angles(pts, end_i, i + 1) + ); + + function euler_angle_path_angles(children) = + let( + pts = len(points[0]) == 3 ? points : [for(pt = points) __to3d(pt)], + end_i = children == 1 ? leng_points_minus_one : children - 1, + angs = _euler_angle_path_angles(pts, end_i) + ) + concat( + [[0, -angs[0][0], angs[0][1]]], + [for(a = angs) [0, -a[0], a[1]]] + ); + + module euler_angle_align(i, angs) { + translate(points[i]) + rotate(angs[i]) + rotate(angles_defined ? [0, 0, 0] : [90, 0, -90]) + rotate(twist_step_a * i) + scale([1, 1, 1] + scale_step_vt * i) + children(0); + } + + // <<< end: modules and functions for "EULER-ANGLE" + + if(method == "AXIS_ANGLE") { + if(angles_defined) { + if($children == 1) { + for(i = [0:leng_points_minus_one]) { + axis_angle_align_with_pts_angles(i) children(0); + } + } else { + for(i = [0:min(leng_points, $children) - 1]) { + axis_angle_align_with_pts_angles(i) children(i); + } + } + } + else { + cumu_rot_matrice = axis_angle_cumulated_rot_matrice(0, [ + for(ang_vect = axis_angle_local_ang_vects(leng_points - 2)) + m_rotation(ang_vect[0], ang_vect[1]) + ]); + + translate(points[0]) + axis_angle_align_with_pts_local_rotate(0, 0, [1, 1, 1], cumu_rot_matrice) + children(0); + + if($children == 1) { + for(i = [0:leng_points - 2]) { + translate(points[i + 1]) + axis_angle_align_with_pts_local_rotate(i, i * twist_step_a, [1, 1, 1] + scale_step_vt * i, cumu_rot_matrice) + children(0); + } + } else { + for(i = [0:min(leng_points, $children) - 2]) { + translate(points[i + 1]) + axis_angle_align_with_pts_local_rotate(i, i * twist_step_a, [1, 1, 1] + scale_step_vt * i, cumu_rot_matrice) + children(i + 1); + } + } + } + } + else if(method == "EULER_ANGLE") { + angs = angles_defined ? angles : euler_angle_path_angles($children); + + if($children == 1) { + for(i = [0:leng_points_minus_one]) { + euler_angle_align(i, angs) children(0); + } + + } else { + for(i = [0:min(leng_points, $children) - 1]) { + euler_angle_align(i, angs) children(i); + } + } + } +} + +function __nearest_multiple_of_4(n) = + let( + remain = n % 4 + ) + (remain / 4) > 0.5 ? n - remain + 4 : n - remain; + + +/** +* path_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-path_extrude.html +* +**/ + + +// Becuase of improving the performance, this module requires m_rotation.scad which doesn't require in dotSCAD 1.0. +// For backward compatibility, I directly include m_rotation here. + +module path_extrude(shape_pts, path_pts, triangles = "SOLID", twist = 0, scale = 1.0, closed = false, method = "AXIS_ANGLE") { + sh_pts = len(shape_pts[0]) == 3 ? shape_pts : [for(p = shape_pts) __to3d(p)]; + pth_pts = len(path_pts[0]) == 3 ? path_pts : [for(p = path_pts) __to3d(p)]; + + len_path_pts = len(pth_pts); + len_path_pts_minus_one = len_path_pts - 1; + + + module axis_angle_path_extrude() { + twist_step_a = twist / len_path_pts; + + function scale_pts(pts, s) = [ + for(p = pts) [p[0] * s[0], p[1] * s[1], p[2] * s[2]] + ]; + + function translate_pts(pts, t) = [ + for(p = pts) [p[0] + t[0], p[1] + t[1], p[2] + t[2]] + ]; + + function rotate_pts(pts, a, v) = [for(p = pts) rotate_p(p, a, v)]; + + function scale_step() = + let(s = (scale - 1) / len_path_pts_minus_one) + [s, s, s]; + + scale_step_vt = __is_float(scale) ? + scale_step() : + [ + (scale[0] - 1) / len_path_pts_minus_one, + (scale[1] - 1) / len_path_pts_minus_one, + scale[2] == undef ? 0 : (scale[2] - 1) / len_path_pts_minus_one + ]; + + // get rotation matrice for sections + + function local_ang_vects(j) = + j == 0 ? [] : local_ang_vects_sub(j); + + function local_ang_vects_sub(j) = + let( + vt0 = pth_pts[j] - pth_pts[j - 1], + vt1 = pth_pts[j + 1] - pth_pts[j], + a = acos((vt0 * vt1) / (norm(vt0) * norm(vt1))), + v = cross(vt0, vt1) + ) + concat([[a, v]], local_ang_vects(j - 1)); + + rot_matrice = [ + for(ang_vect = local_ang_vects(len_path_pts - 2)) + m_rotation(ang_vect[0], ang_vect[1]) + ]; + + leng_rot_matrice = len(rot_matrice); + leng_rot_matrice_minus_one = leng_rot_matrice - 1; + leng_rot_matrice_minus_two= leng_rot_matrice - 2; + + identity_matrix = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + function cumulated_rot_matrice(i) = + leng_rot_matrice == 0 ? [identity_matrix] : ( + leng_rot_matrice == 1 ? [rot_matrice[0], identity_matrix] : + ( + i == leng_rot_matrice_minus_two ? + [ + rot_matrice[leng_rot_matrice_minus_one], + rot_matrice[leng_rot_matrice_minus_two] * rot_matrice[leng_rot_matrice_minus_one] + ] + : cumulated_rot_matrice_sub(i)) + ); + + function cumulated_rot_matrice_sub(i) = + let( + matrice = cumulated_rot_matrice(i + 1), + curr_matrix = rot_matrice[i], + prev_matrix = matrice[len(matrice) - 1] + ) + concat(matrice, [curr_matrix * prev_matrix]); + + cumu_rot_matrice = cumulated_rot_matrice(0); + + // get all sections + + function init_section(a, s) = + let(angleyz = __angy_angz(pth_pts[0], pth_pts[1])) + rotate_pts( + rotate_pts( + rotate_pts( + scale_pts(sh_pts, s), a + ), [90, 0, -90] + ), [0, -angleyz[0], angleyz[1]] + ); + + function local_rotate_section(j, init_a, init_s) = + j == 0 ? + init_section(init_a, init_s) : + local_rotate_section_sub(j, init_a, init_s); + + function local_rotate_section_sub(j, init_a, init_s) = + let( + vt0 = pth_pts[j] - pth_pts[j - 1], + vt1 = pth_pts[j + 1] - pth_pts[j], + ms = cumu_rot_matrice[j - 1] + ) + [ + for(p = init_section(init_a, init_s)) + [ + [ms[0][0], ms[0][1], ms[0][2]] * p, + [ms[1][0], ms[1][1], ms[1][2]] * p, + [ms[2][0], ms[2][1], ms[2][2]] * p + ] + ]; + + function sections() = + let( + fst_section = + translate_pts(local_rotate_section(0, 0, [1, 1, 1]), pth_pts[0]), + remain_sections = [ + for(i = [0:len_path_pts - 2]) + + translate_pts( + local_rotate_section(i, i * twist_step_a, [1, 1, 1] + scale_step_vt * i), + pth_pts[i + 1] + ) + + + ] + ) concat([fst_section], remain_sections); + + sects = sections(); + + function calculated_sections() = + closed && pth_pts[0] == pth_pts[len_path_pts_minus_one] ? + concat(sects, [sects[0]]) : // round-robin + sects; + + polysections( + calculated_sections(), + triangles = triangles + ); + + // hook for testing + test_path_extrude(sects); + } + + module euler_angle_path_extrude() { + scale_step_vt = __is_float(scale) ? + [(scale - 1) / len_path_pts_minus_one, (scale - 1) / len_path_pts_minus_one] : + [(scale[0] - 1) / len_path_pts_minus_one, (scale[1] - 1) / len_path_pts_minus_one]; + + scale_step_x = scale_step_vt[0]; + scale_step_y = scale_step_vt[1]; + twist_step = twist / len_path_pts_minus_one; + + function section(p1, p2, i) = + let( + length = norm(p1 - p2), + angy_angz = __angy_angz(p1, p2), + ay = -angy_angz[0], + az = angy_angz[1] + ) + [ + for(p = sh_pts) + let(scaled_p = [p[0] * (1 + scale_step_x * i), p[1] * (1 + scale_step_y * i), p[2]]) + rotate_p( + rotate_p( + rotate_p(scaled_p, twist_step * i), [90, 0, -90] + ) + [i == 0 ? 0 : length, 0, 0], + [0, ay, az] + ) + p1 + ]; + + function path_extrude_inner(index) = + index == len_path_pts ? [] : + concat( + [section(pth_pts[index - 1], pth_pts[index], index)], + path_extrude_inner(index + 1) + ); + + function calculated_sections() = + let(sections = path_extrude_inner(1)) + closed && pth_pts[0] == pth_pts[len_path_pts_minus_one] ? + concat(sections, [sections[0]]) : // round-robin + concat([section(pth_pts[0], pth_pts[1], 0)], sections); + + sections = calculated_sections(); + + polysections( + sections, + triangles = triangles + ); + + // hook for testing + test_path_extrude(sections); + } + + if(method == "AXIS_ANGLE") { + axis_angle_path_extrude(); + } + else if(method == "EULER_ANGLE") { + euler_angle_path_extrude(); + } +} + + +// override to test +module test_path_extrude(sections) { + +} + +function __to_3_elems_ang_vect(a) = + let(leng = len(a)) + leng == 3 ? a : ( + leng == 2 ? [a[0], a[1], 0] : [a[0], 0, 0] + ); + +function __to_ang_vect(a) = __is_float(a) ? [0, 0, a] : __to_3_elems_ang_vect(a); + +function __pie_for_rounding(r, begin_a, end_a, frags) = + let( + sector_angle = end_a - begin_a, + step_a = sector_angle / frags, + is_integer = frags % 1 == 0 + ) + r < 0.00005 ? [[0, 0]] : concat([ + for(ang = [begin_a:step_a:end_a]) + [ + r * cos(ang), + r * sin(ang) + ] + ], + is_integer ? [] : [[ + r * cos(end_a), + r * sin(end_a) + ]] + ); + +function __shape_pie(radius, angle) = + let( + frags = __frags(radius), + a_step = 360 / frags, + leng = radius * cos(a_step / 2), + angles = __is_float(angle) ? [0:angle] : angle, + m = floor(angles[0] / a_step) + 1, + n = floor(angles[1] / a_step), + edge_r_begin = leng / cos((m - 0.5) * a_step - angles[0]), + edge_r_end = leng / cos((n + 0.5) * a_step - angles[1]), + shape_pts = concat( + [[0, 0], __ra_to_xy(edge_r_begin, angles[0])], + m > n ? [] : [ + for(i = [m:n]) + let(a = a_step * i) + __ra_to_xy(radius, a) + ], + angles[1] == a_step * n ? [] : [__ra_to_xy(edge_r_end, angles[1])] + ) + ) shape_pts; + +function __to2d(p) = [p[0], p[1]]; + +/** +* parse_number.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-parse_number.html +* +**/ + +function _str_to_int(t, i = 0, mapper = [["0", 0], ["1", 1], ["2", 2], ["3", 3], ["4", 4], ["5", 5], ["6", 6], ["7", 7], ["8", 8], ["9", 9]]) = + i == len(mapper) ? -1 : ( + mapper[i][0] == t ? mapper[i][1] : _str_to_int(t, i + 1) + ); + +function _parse_positive_int(t, value = 0, i = 0) = + i == len(t) ? value : _parse_positive_int(t, value * pow(10, i) + _str_to_int(t[i]), i + 1); + +function _parse_positive_decimal(t, value = 0, i = 0) = + i == len(t) ? value : _parse_positive_decimal(t, value + _str_to_int(t[i]) * pow(10, -(i + 1)), i + 1); + +function _parse_positive_number(t) = + len(search(".", t)) == 0 ? _parse_positive_int(t) : + _parse_positive_int(split_str(t, ".")[0]) + _parse_positive_decimal(split_str(t, ".")[1]); + +function parse_number(t) = + t[0] == "-" ? -_parse_positive_number(sub_str(t, 1, len(t))) : _parse_positive_number(t); + + + +/** +* shape_superformula.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_superformula.html +* +**/ + + +function _superformula_r(angle, m1, m2, n1, n2 = 1, n3 = 1, a = 1, b = 1) = + pow( + pow(abs(cos(m1 * angle / 4) / a), n2) + + pow(abs(sin(m2 * angle / 4) / b), n3), + - 1 / n1 + ); + +function shape_superformula(phi_step, m1, m2, n1, n2 = 1, n3 = 1, a = 1, b = 1) = + [ + for(phi = [0:phi_step:6.28318]) + let( + angle = __to_degree(phi), + r = _superformula_r(angle, m1, m2, n1, n2, n3, a, b) + + ) + __ra_to_xy(r, angle) + ]; + +/** +* bezier_smooth.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-bezier_curve.html +* +**/ + + +function _corner_ctrl_pts(round_d, p1, p2, p3) = + let( + _ya_za_1 = __angy_angz(p1, p2), + _ya_za_2 = __angy_angz(p3, p2), + + dz1 = sin(_ya_za_1[0]) * round_d, + dxy1 = cos(_ya_za_1[0]) * round_d, + dy1 = sin(_ya_za_1[1]) * dxy1, + dx1 = cos(_ya_za_1[1]) * dxy1, + + dz2 = sin(_ya_za_2[0]) * round_d, + dxy2 = cos(_ya_za_2[0]) * round_d, + dy2 = sin(_ya_za_2[1]) * dxy2, + dx2 = cos(_ya_za_2[1]) * dxy2 + ) + [ + p2 - [dx1, dy1, dz1], + p2, + p2 - [dx2, dy2, dz2] + ]; + + +function _bezier_corner(round_d, t_step, p1, p2, p3) = + bezier_curve(t_step, _corner_ctrl_pts(round_d, p1, p2, p3)); + +function _recursive_bezier_smooth(pts, round_d, t_step, leng, i = 0) = + i <= leng - 3 ? + concat( + _bezier_corner(round_d, t_step, pts[i], pts[i + 1], pts[i + 2]), + _recursive_bezier_smooth(pts, round_d, t_step, leng, i + 1) + ) + : []; + +function bezier_smooth(path_pts, round_d, t_step = 0.1, closed = false) = + let( + pts = len(path_pts[0]) == 3 ? path_pts : [for(p = path_pts) __to3d(p)], + leng = len(pts), + middle_pts = _recursive_bezier_smooth(pts, round_d, t_step, leng), + pth_pts = closed ? + concat( + _recursive_bezier_smooth( + [pts[leng - 1], pts[0], pts[1]], + round_d, t_step, 3 + ), + middle_pts, + _recursive_bezier_smooth( + [pts[leng - 2], pts[leng - 1], pts[0]], + round_d, t_step, 3 + ) + ) : + concat( + [pts[0]], + middle_pts, + [pts[leng - 1]] + ) + ) + len(path_pts[0]) == 2 ? [for(p = pth_pts) __to2d(p)] : pth_pts; + +function __ra_to_xy(r, a) = [r * cos(a), r * sin(a)]; + +function __line_intersection(line_pts1, line_pts2, epsilon = 0.0001) = + let( + a1 = line_pts1[0], + a2 = line_pts1[1], + b1 = line_pts2[0], + b2 = line_pts2[1], + a = a2 - a1, + b = b2 - b1, + s = b1 - a1 + ) + abs(cross(a, b)) < epsilon ? [] : // they are parallel or conincident edges + a1 + a * cross(s, b) / cross(a, b); + +/** +* shape_pie.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_pie.html +* +**/ + + +function shape_pie(radius, angle) = + __shape_pie(radius, angle); + + +/** +* shape_glued2circles.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_glued2circles.html +* +**/ + +function _glued2circles_pie_curve(radius, centre_dist, tangent_angle) = + let( + begin_ang = 90 + tangent_angle, + shape_pts = shape_pie(radius, [-begin_ang, begin_ang]) + ) + [ + for(i = [1:len(shape_pts) - 1]) + shape_pts[i] + [centre_dist / 2, 0] + ]; + +function _glued2circles_bezier(radius, centre_dist, tangent_angle, t_step, ctrl_p1) = + let( + ctrl_p = rotate_p([radius * tan(tangent_angle), -radius], tangent_angle), + ctrl_p2 = [-ctrl_p[0], ctrl_p[1]] + [centre_dist / 2, 0], + ctrl_p3 = [-ctrl_p2[0], ctrl_p2[1]], + ctrl_p4 = [-ctrl_p1[0], ctrl_p1[1]] + ) + bezier_curve( + t_step, + [ + ctrl_p1, + ctrl_p2, + ctrl_p3, + ctrl_p4 + ] + ); + +function _glued2circles_lower_half_curve(curve_pts, leng, i = 0) = + i < leng ? ( + curve_pts[leng - 1 - i][0] >= 0 ? + concat( + [curve_pts[leng - 1 - i]], + _glued2circles_lower_half_curve(curve_pts, leng, i + 1) + ) : + _glued2circles_lower_half_curve(curve_pts, leng, i + 1) + ) : []; + +function _glued2circles_half_glued_circle(radius, centre_dist, tangent_angle, t_step) = + let( + pie_curve_pts = _glued2circles_pie_curve(radius, centre_dist, tangent_angle), + curve_pts = _glued2circles_bezier(radius, centre_dist, tangent_angle, t_step, pie_curve_pts[0]), + lower_curve_pts = _glued2circles_lower_half_curve(curve_pts, len(curve_pts)), + leng_half_curve_pts = len(lower_curve_pts), + upper_curve_pts = [ + for(i = [0:leng_half_curve_pts - 1]) + let(pt = lower_curve_pts[leng_half_curve_pts - 1 - i]) + [pt[0], -pt[1]] + ] + ) concat( + lower_curve_pts, + pie_curve_pts, + upper_curve_pts + ); + +function shape_glued2circles(radius, centre_dist, tangent_angle = 30, t_step = 0.1) = + let( + half_glued_circles = _glued2circles_half_glued_circle(radius, centre_dist, tangent_angle, t_step), + leng_half_glued_circles = len(half_glued_circles), + left_half_glued_circles = [ + for(i = [0:leng_half_glued_circles - 1]) + let(pt = half_glued_circles[leng_half_glued_circles - 1 - i]) + [-pt[0], pt[1]] + ] + ) concat(half_glued_circles, left_half_glued_circles); + +/** +* shape_cyclicpolygon.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_cyclicpolygon.html +* +**/ + + +function shape_cyclicpolygon(sides, circle_r, corner_r) = + let( + frag_a = 360 / sides, + corner_a = (180 - frag_a), + corner_circle_a = 180 - corner_a, + half_corner_circle_a = corner_circle_a / 2, + corner_circle_center = circle_r - corner_r / sin(corner_a / 2), + first_corner = [ + for( + pt = __pie_for_rounding( + corner_r, + -half_corner_circle_a, + half_corner_circle_a, + __frags(corner_r) * corner_circle_a / 360 + ) + ) + [pt[0] + corner_circle_center, pt[1]] + ] + + ) + concat( + first_corner, + [ + for(side = [1:sides - 1]) + for(pt = first_corner) + let( + a = frag_a * side, + x = pt[0], + y = pt[1], + sina = sin(a), + cosa = cos(a) + ) + [ + x * cosa - y * sina, + x * sina + y * cosa + ] + ] + ); + +/** +* ring_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-ring_extrude.html +* +**/ + + +module ring_extrude(shape_pts, radius, angle = 360, twist = 0, scale = 1.0, triangles = "SOLID") { + if(angle == 360 && twist == 0 && scale == 1.0) { + rotate_extrude() + translate([radius, 0, 0]) + polygon(shape_pts); + } else { + a_step = 360 / __frags(radius); + + angles = __is_float(angle) ? [0, angle] : angle; + + m = floor(angles[0] / a_step) + 1; + n = floor(angles[1] / a_step); + + leng = radius * cos(a_step / 2); + + begin_r = + leng / cos((m - 0.5) * a_step - angles[0]); + + end_r = + leng / cos((n + 0.5) * a_step - angles[1]); + + angs = concat( + [[90, 0, angles[0]]], + m > n ? [] : [for(i = [m:n]) [90, 0, a_step * i]] + ); + pts = concat( + [__ra_to_xy(begin_r, angles[0])], + m > n ? [] : [for(i = [m:n]) __ra_to_xy(radius, a_step * i)] + ); + + is_angle_frag_end = angs[len(angs) - 1][2] == angles[1]; + + all_angles = is_angle_frag_end ? + angs : + concat(angs, [[90, 0, angles[1]]]); + + all_points = is_angle_frag_end ? + pts : + concat(pts, [__ra_to_xy(end_r, angles[1])]); + + sections = cross_sections(shape_pts, all_points, all_angles, twist, scale); + + polysections( + sections, + triangles = triangles + ); + + // hook for testing + test_ring_extrude(sections); + } +} + +// Override it to test +module test_ring_extrude(sections) { + +} + +function __trapezium(length, h, round_r) = + let( + r_half_trapezium = __half_trapezium(length / 2, h, round_r), + to = len(r_half_trapezium) - 1, + l_half_trapezium = [ + for(i = [0:to]) + let(pt = r_half_trapezium[to - i]) + [-pt[0], pt[1]] + ] + ) + concat( + r_half_trapezium, + l_half_trapezium + ); + +function __lines_from(pts, closed = false) = + let(leng = len(pts)) + concat( + [for(i = [0:leng - 2]) [pts[i], pts[i + 1]]], + closed ? [[pts[len(pts) - 1], pts[0]]] : [] + ); + + +/** +* polysections.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-polysections.html +* +**/ + + +module polysections(sections, triangles = "SOLID") { + + function side_indexes(sects, begin_idx = 0) = + let( + leng_sects = len(sects), + leng_pts_sect = len(sects[0]) + ) + concat( + [ + for(j = [begin_idx:leng_pts_sect:begin_idx + (leng_sects - 2) * leng_pts_sect]) + for(i = [0:leng_pts_sect - 1]) + [ + j + i, + j + (i + 1) % leng_pts_sect, + j + (i + 1) % leng_pts_sect + leng_pts_sect + ] + ], + [ + for(j = [begin_idx:leng_pts_sect:begin_idx + (leng_sects - 2) * leng_pts_sect]) + for(i = [0:leng_pts_sect - 1]) + [ + j + i, + j + (i + 1) % leng_pts_sect + leng_pts_sect , + j + i + leng_pts_sect + ] + ] + ); + + function search_at(f_sect, p, leng_pts_sect, i = 0) = + i < leng_pts_sect ? + (p == f_sect[i] ? i : search_at(f_sect, p, leng_pts_sect, i + 1)) : -1; + + function the_same_after_twisting(f_sect, l_sect, leng_pts_sect) = + let( + found_at_i = search_at(f_sect, l_sect[0], leng_pts_sect) + ) + found_at_i <= 0 ? false : + l_sect == concat( + [for(i = [found_at_i:leng_pts_sect-1]) f_sect[i]], + [for(i = [0:found_at_i - 1]) f_sect[i]] + ); + + function to_v_pts(sects) = + [ + for(sect = sects) + for(pt = sect) + pt + ]; + + module solid_sections(sects) { + + leng_sects = len(sects); + leng_pts_sect = len(sects[0]); + first_sect = sects[0]; + last_sect = sects[leng_sects - 1]; + + v_pts = [ + for(sect = sects) + for(pt = sect) + pt + ]; + + function begin_end_the_same() = + first_sect == last_sect || + the_same_after_twisting(first_sect, last_sect, leng_pts_sect); + + if(begin_end_the_same()) { + f_idxes = side_indexes(sects); + + polyhedron( + v_pts, + f_idxes + ); + + // hook for testing + test_polysections_solid(v_pts, f_idxes); + } else { + first_idxes = [for(i = [0:leng_pts_sect - 1]) leng_pts_sect - 1 - i]; + last_idxes = [ + for(i = [0:leng_pts_sect - 1]) + i + leng_pts_sect * (leng_sects - 1) + ]; + + f_idxes = concat([first_idxes], side_indexes(sects), [last_idxes]); + + polyhedron( + v_pts, + f_idxes + ); + + // hook for testing + test_polysections_solid(v_pts, f_idxes); + } + } + + module hollow_sections(sects) { + leng_sects = len(sects); + leng_sect = len(sects[0]); + half_leng_sect = leng_sect / 2; + half_leng_v_pts = leng_sects * half_leng_sect; + + function strip_sects(begin_idx, end_idx) = + [ + for(i = [0:leng_sects - 1]) + [ + for(j = [begin_idx:end_idx]) + sects[i][j] + ] + ]; + + function first_idxes() = + [ + for(i = [0:half_leng_sect - 1]) + [ + i, + i + half_leng_v_pts, + (i + 1) % half_leng_sect + half_leng_v_pts, + (i + 1) % half_leng_sect + ] + ]; + + function last_idxes(begin_idx) = + [ + for(i = [0:half_leng_sect - 1]) + [ + begin_idx + i, + begin_idx + (i + 1) % half_leng_sect, + begin_idx + (i + 1) % half_leng_sect + half_leng_v_pts, + begin_idx + i + half_leng_v_pts + ] + ]; + + outer_sects = strip_sects(0, half_leng_sect - 1); + inner_sects = strip_sects(half_leng_sect, leng_sect - 1); + + outer_v_pts = to_v_pts(outer_sects); + inner_v_pts = to_v_pts(inner_sects); + + outer_idxes = side_indexes(outer_sects); + inner_idxes = [ + for(idxes = side_indexes(inner_sects, half_leng_v_pts)) + __reverse(idxes) + ]; + + first_outer_sect = outer_sects[0]; + last_outer_sect = outer_sects[leng_sects - 1]; + first_inner_sect = inner_sects[0]; + last_inner_sect = inner_sects[leng_sects - 1]; + + leng_pts_sect = len(first_outer_sect); + + function begin_end_the_same() = + (first_outer_sect == last_outer_sect && first_inner_sect == last_inner_sect) || + ( + the_same_after_twisting(first_outer_sect, last_outer_sect, leng_pts_sect) && + the_same_after_twisting(first_inner_sect, last_inner_sect, leng_pts_sect) + ); + + v_pts = concat(outer_v_pts, inner_v_pts); + + if(begin_end_the_same()) { + f_idxes = concat(outer_idxes, inner_idxes); + + polyhedron( + v_pts, + f_idxes + ); + + // hook for testing + test_polysections_solid(v_pts, f_idxes); + } else { + first_idxes = first_idxes(); + last_idxes = last_idxes(half_leng_v_pts - half_leng_sect); + + f_idxes = concat(first_idxes, outer_idxes, inner_idxes, last_idxes); + + polyhedron( + v_pts, + f_idxes + ); + + // hook for testing + test_polysections_solid(v_pts, f_idxes); + } + } + + module triangles_defined_sections() { + module tri_sections(tri1, tri2) { + hull() polyhedron( + points = concat(tri1, tri2), + faces = [ + [0, 1, 2], + [3, 5, 4], + [1, 3, 4], [2, 1, 4], [2, 3, 0], + [0, 3, 1], [2, 4, 5], [2, 5, 3] + ] + ); + } + + module two_sections(section1, section2) { + for(idx = triangles) { + tri_sections( + [ + section1[idx[0]], + section1[idx[1]], + section1[idx[2]] + ], + [ + section2[idx[0]], + section2[idx[1]], + section2[idx[2]] + ] + ); + } + } + + for(i = [0:len(sections) - 2]) { + two_sections( + sections[i], + sections[i + 1] + ); + } + } + + if(triangles == "SOLID") { + solid_sections(sections); + } else if(triangles == "HOLLOW") { + hollow_sections(sections); + } + else { + triangles_defined_sections(); + } +} + +// override it to test + +module test_polysections_solid(points, faces) { + +} + +/** +* starburst.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-starburst.html +* +**/ + +module starburst(r1, r2, n, height) { + a = 180 / n; + + p0 = [0, 0, 0]; + p1 = [r2 * cos(a), r2 * sin(a), 0]; + p2 = [r1, 0, 0]; + p3 = [0, 0, height]; + + module half_burst() { + polyhedron(points = [p0, p1, p2, p3], + faces = [ + [0, 2, 1], + [0, 1, 3], + [0, 3, 2], + [2, 1, 3] + ] + ); + } + + module burst() { + hull() { + half_burst(); + mirror([0, 1,0]) half_burst(); + } + } + + union() { + for(i = [0 : n - 1]) { + rotate(2 * a * i) burst(); + } + } +} + +/** +* paths2sections.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-paths2sections.html +* +**/ + +function paths2sections(paths) = + let( + leng_path = len(paths[0]), + leng_paths = len(paths) + ) + [ + for(i = [0:leng_path - 1]) + [ + for(j = [0:leng_paths - 1]) + paths[j][i] + ] + ]; + +/** +* voronoi2d.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-voronoi2d.html +* +**/ + +module voronoi2d(points, spacing = 1, r = 0, delta = 0, chamfer = false, region_type = "square") { + xs = [for(p = points) p[0]]; + ys = [for(p = points) abs(p[1])]; + + region_size = max([(max(xs) - min(xs) / 2), (max(ys) - min(ys)) / 2]); + half_region_size = 0.5 * region_size; + offset_leng = spacing * 0.5 + half_region_size; + + function normalize(v) = v / norm(v); + + module region(pt) { + intersection_for(p = points) { + if(pt != p) { + v = p - pt; + translate((pt + p) / 2 - normalize(v) * offset_leng) + rotate(atan2(v[1], v[0])) + if(region_type == "square") { + square(region_size, center = true); + } + else if(region_type == "circle") { + circle(region_size / 2); + } + + } + } + } + + for(p = points) { + if(r != 0) { + offset(r) region(p); + } + else { + offset(delta = delta, chamfer = chamfer) region(p); + } + } +} + +/** +* shear.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shear.html +* +**/ + + +module shear(sx = [0, 0], sy = [0, 0], sz = [0, 0]) { + multmatrix(__m_shearing(sx, sy, sz)) children(); +} + +/** +* rounded_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-rounded_extrude.html +* +**/ + + +module rounded_extrude(size, round_r, angle = 90, twist = 0, convexity = 10) { + + is_flt = __is_float(size); + x = is_flt ? size : size[0]; + y = is_flt ? size : size[1]; + + q_corner_frags = __frags(round_r) / 4; + + step_a = angle / q_corner_frags; + twist_step = twist / q_corner_frags; + + module layers(pre_x, pre_y, pre_h = 0, i = 1) { + module one_layer(current_a) { + wx = pre_x; + wy = pre_y; + + h = (round_r - pre_h) - round_r * cos(current_a); + + d_leng = + round_r * (sin(current_a) - sin(step_a * (i - 1))); + + sx = (d_leng * 2 + wx) / wx; + sy = (d_leng * 2 + wy) / wy; + + translate([0, 0, pre_h]) + rotate(-twist_step * (i - 1)) + linear_extrude( + h, + slices = 1, + scale = [sx, sy], + convexity = convexity, + twist = twist_step + ) scale([wx / x, wy / y]) + children(); + + test_rounded_extrude_data(i, wx, wy, pre_h, sx, sy); + + layers(wx * sx, wy * sy, h + pre_h, i + 1) + children(); + + } + + if(i <= q_corner_frags) { + one_layer(i * step_a) + children(); + } else if(i - q_corner_frags < 1) { + one_layer(q_corner_frags * step_a) + children(); + } + } + + layers(x, y) + children(); +} + +module test_rounded_extrude_data(i, wx, wy, pre_h, sx, sy) { + +} + +/** +* arc.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-arc.html +* +**/ + + +module arc(radius, angle, width, width_mode = "LINE_CROSS") { + polygon(__shape_arc(radius, angle, width, width_mode)); +} + +function __angy_angz(p1, p2) = + let( + dx = p2[0] - p1[0], + dy = p2[1] - p1[1], + dz = p2[2] - p1[2], + ya = atan2(dz, sqrt(pow(dx, 2) + pow(dy, 2))), + za = atan2(dy, dx) + ) [ya, za]; + +/** +* shape_arc.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_arc.html +* +**/ + + +function shape_arc(radius, angle, width, width_mode = "LINE_CROSS") = + __shape_arc(radius, angle, width, width_mode); + +function __edge_r_begin(orig_r, a, a_step, m) = + let(leng = orig_r * cos(a_step / 2)) + leng / cos((m - 0.5) * a_step - a); + +function __edge_r_end(orig_r, a, a_step, n) = + let(leng = orig_r * cos(a_step / 2)) + leng / cos((n + 0.5) * a_step - a); + +/** +* sphere_spiral_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-sphere_spiral_extrude.html +* +**/ + +module sphere_spiral_extrude(shape_pts, radius, za_step, + z_circles = 1, begin_angle = 0, end_angle = 0, vt_dir = "SPI_DOWN", rt_dir = "CT_CLK", + twist = 0, scale = 1.0, triangles = "SOLID") { + + points_angles = sphere_spiral( + radius = radius, + za_step = za_step, + z_circles = z_circles, + begin_angle = begin_angle, + end_angle = end_angle, + vt_dir = vt_dir, + rt_dir = rt_dir + ); + + v_clk = vt_dir == "SPI_DOWN" ? 90 : -90; + r_clk = rt_dir == "CT_CLK" ? 0 : 180; + + points = [for(pa = points_angles) pa[0]]; + angles = [for(pa = points_angles) [pa[1][0] + v_clk, pa[1][1], pa[1][2] + r_clk]]; + + sections = cross_sections( + shape_pts, points, angles, twist = twist, scale = scale + ); + + polysections( + sections, + triangles = triangles + ); + + // testing hook + test_sphere_spiral_extrude(sections); +} + +// override it to test +module test_sphere_spiral_extrude(sections) { + +} + +/** +* line2d.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-line2d.html +* +**/ + + +module line2d(p1, p2, width, p1Style = "CAP_SQUARE", p2Style = "CAP_SQUARE") { + half_width = 0.5 * width; + + atan_angle = atan2(p2[1] - p1[1], p2[0] - p1[0]); + leng = sqrt(pow(p2[0] - p1[0], 2) + pow(p2[1] - p1[1], 2)); + + frags = __nearest_multiple_of_4(__frags(half_width)); + + module square_end(point) { + translate(point) + rotate(atan_angle) + square(width, center = true); + + // hook for testing + test_line2d_cap(point, "CAP_SQUARE"); + } + + module round_end(point) { + translate(point) + rotate(atan_angle) + circle(half_width, $fn = frags); + + // hook for testing + test_line2d_cap(point, "CAP_ROUND"); + } + + if(p1Style == "CAP_SQUARE") { + square_end(p1); + } else if(p1Style == "CAP_ROUND") { + round_end(p1); + } + + translate(p1) + rotate(atan_angle) + translate([0, -width / 2]) + square([leng, width]); + + if(p2Style == "CAP_SQUARE") { + square_end(p2); + } else if(p2Style == "CAP_ROUND") { + round_end(p2); + } + + // hook for testing + test_line2d_line(atan_angle, leng, width, frags); +} + +// override them to test +module test_line2d_cap(point, style) { +} + +module test_line2d_line(angle, length, width, frags) { +} + + + +/** +* torus_knot.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-torus_knot.html +* +**/ + +function torus_knot(p, q, phi_step) = [ + for(phi = [0:phi_step:6.28318]) + let( + degree = phi * 180 / 3.14159, + r = cos(q * degree) + 2, + x = r * cos(p * degree), + y = r * sin(p * degree), + z = -sin(q * degree) + ) + [x, y, z] +]; + +/** +* midpt_smooth.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-midpt_smooth.html +* +**/ + +function _midpt_smooth_sub(points, iend, i, closed = false) = + i == iend ? ( + closed ? [(points[i] + points[0]) / 2] + : [] + ) : concat([(points[i] + points[i + 1]) / 2], _midpt_smooth_sub(points, iend, i + 1, closed)); + +function midpt_smooth(points, n, closed = false) = + let( + smoothed = _midpt_smooth_sub(points, len(points) - 1, 0, closed) + ) + n == 1 ? smoothed : midpt_smooth(smoothed, n - 1, closed); + +/** +* function_grapher.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-function_grapher.html +* +**/ + + +module function_grapher(points, thickness, style = "FACES", slicing = "SLASH") { + + rows = len(points); + columns = len(points[0]); + + // Increasing $fn will be slow when you use "LINES", "HULL_FACES" or "HULL_LINES". + + module faces() { + function xy_to_index(x, y, columns) = y * columns + x; + + top_pts = [ + for(row_pts = points) + for(pt = row_pts) + pt + ]; + + base_pts = [ + for(pt = top_pts) + [pt[0], pt[1], pt[2] - thickness] + ]; + + leng_pts = len(top_pts); + + top_tri_faces1 = slicing == "SLASH" ? [ + for(yi = [0:rows - 2]) + for(xi = [0:columns - 2]) + [ + xy_to_index(xi, yi, columns), + xy_to_index(xi + 1, yi + 1, columns), + xy_to_index(xi + 1, yi, columns) + ] + ] : [ + for(yi = [0:rows - 2]) + for(xi = [0:columns - 2]) + [ + xy_to_index(xi, yi, columns), + xy_to_index(xi, yi + 1, columns), + xy_to_index(xi + 1, yi, columns) + ] + ]; + + top_tri_faces2 = slicing == "SLASH" ? [ + for(yi = [0:rows - 2]) + for(xi = [0:columns - 2]) + [ + xy_to_index(xi, yi, columns), + xy_to_index(xi, yi + 1, columns), + xy_to_index(xi + 1, yi + 1, columns) + ] + ] : [ + for(yi = [0:rows - 2]) + for(xi = [0:columns - 2]) + [ + xy_to_index(xi, yi + 1, columns), + xy_to_index(xi + 1, yi + 1, columns), + xy_to_index(xi + 1, yi, columns) + ] + ]; + + base_tri_faces1 = [ + for(face = top_tri_faces1) + __reverse(face) + [leng_pts, leng_pts, leng_pts] + ]; + + base_tri_faces2 = [ + for(face = top_tri_faces2) + __reverse(face) + [leng_pts, leng_pts, leng_pts] + ]; + + side_faces1 = [ + for(xi = [0:columns - 2]) + let( + idx1 = xy_to_index(xi, 0, columns), + idx2 = xy_to_index(xi + 1, 0, columns) + ) + [ + idx1, + idx2, + idx2 + leng_pts, + idx1 + leng_pts + ] + ]; + + side_faces2 = [ + for(yi = [0:rows - 2]) + let( + xi = columns - 1, + idx1 = xy_to_index(xi, yi, columns), + idx2 = xy_to_index(xi, yi + 1, columns) + ) + [ + idx1, + idx2, + idx2 + leng_pts, + idx1 + leng_pts + ] + ]; + + side_faces3 = [ + for(xi = [0:columns - 2]) + let( + yi = rows - 1, + idx1 = xy_to_index(xi, yi, columns), + idx2 = xy_to_index(xi + 1, yi, columns) + ) + [ + idx2, + idx1, + idx1 + leng_pts, + idx2 + leng_pts + ] + ]; + + side_faces4 = [ + for(yi = [0:rows - 2]) + let( + idx1 = xy_to_index(0, yi, columns), + idx2 = xy_to_index(0, yi + 1, columns) + ) + [ + idx2, + idx1, + idx1 + leng_pts, + idx2 + leng_pts + ] + ]; + + pts = concat(top_pts, base_pts); + face_idxs = concat( + top_tri_faces1, top_tri_faces2, + base_tri_faces1, base_tri_faces2, + side_faces1, + side_faces2, + side_faces3, + side_faces4 + ); + + polyhedron( + points = pts, + faces = face_idxs + ); + + // hook for testing + test_function_grapher_faces(pts, face_idxs); + } + + module tri_to_lines(tri1, tri2) { + polyline3d(concat(tri1, [tri1[0]]), thickness); + polyline3d(concat(tri2, [tri2[0]]), thickness); + } + + module tri_to_hull_lines(tri1, tri2) { + hull_polyline3d(concat(tri1, [tri1[0]]), thickness); + hull_polyline3d(concat(tri2, [tri2[0]]), thickness); + } + + module hull_pts(tri) { + half_thickness = thickness / 2; + hull() { + translate(tri[0]) sphere(half_thickness); + translate(tri[1]) sphere(half_thickness); + translate(tri[2]) sphere(half_thickness); + } + } + + module tri_to_hull_faces(tri1, tri2) { + hull_pts(tri1); + hull_pts(tri2); + } + + module tri_to_graph(tri1, tri2) { + if(style == "LINES") { + tri_to_lines(tri1, tri2); + } else if(style == "HULL_FACES") { // Warning: May be very slow!! + tri_to_hull_faces(tri1, tri2); + } else if(style == "HULL_LINES") { // Warning: very very slow!! + tri_to_hull_lines(tri1, tri2); + } + } + + if(style == "FACES") { + faces(); + } else { + for(yi = [0:rows - 2]) { + for(xi = [0:columns - 2]) { + if(slicing == "SLASH") { + tri_to_graph([ + points[yi][xi], + points[yi][xi + 1], + points[yi + 1][xi + 1] + ], [ + points[yi][xi], + points[yi + 1][xi + 1], + points[yi + 1][xi] + ]); + } else { + tri_to_graph([ + points[yi][xi], + points[yi][xi + 1], + points[yi + 1][xi] + ], [ + points[yi + 1][xi], + points[yi][xi + 1], + points[yi + 1][xi + 1] + ]); + } + } + } + } +} + +// override it to test +module test_function_grapher_faces(points, faces) { + +} + +/** +* path_scaling_sections.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-path_scaling_sections.html +* +**/ + + +function path_scaling_sections(shape_pts, edge_path) = + let( + start_point = edge_path[0], + base_leng = norm(start_point), + scaling_matrice = [ + for(p = edge_path) + let(s = norm([p[0], p[1], 0]) / base_leng) + m_scaling([s, s, 1]) + ] + ) + __reverse([ + for(i = [0:len(edge_path) - 1]) + [ + for(p = shape_pts) + let(scaled_p = scaling_matrice[i] * [p[0], p[1], edge_path[i][2], 1]) + [scaled_p[0], scaled_p[1], scaled_p[2]] + ] + ]); + +function __to_degree(phi) = 180 / 3.14159 * phi; + +/** +* shape_square.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_square.html +* +**/ + + +function shape_square(size, corner_r = 0) = + let( + is_flt = __is_float(size), + x = is_flt ? size : size[0], + y = is_flt ? size : size[1] + ) + __trapezium( + length = x, + h = y, + round_r = corner_r + ); + + +/** +* rounded_cube.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-rounded_cube.html +* +**/ + + +module rounded_cube(size, corner_r, center = false) { + is_flt = __is_float(size); + x = is_flt ? size : size[0]; + y = is_flt ? size : size[1]; + z = is_flt ? size : size[2]; + + corner_frags = __nearest_multiple_of_4(__frags(corner_r)); + edge_d = corner_r * cos(180 / corner_frags); + + half_x = x / 2; + half_y = y / 2; + half_z = z / 2; + + half_l = half_x - edge_d; + half_w = half_y - edge_d; + half_h = half_z - edge_d; + + half_cube_leng = size / 2; + half_leng = half_cube_leng - edge_d; + + corners = [ + for(z = [1, -1]) + for(y = [1, -1]) + for(x = [1, -1]) + [half_l * x, half_w * y, half_h * z] + ]; + + module corner(i) { + translate(corners[i]) + sphere(corner_r, $fn = corner_frags); + } + + center_pts = center ? [0, 0, 0] : [half_x, half_y, half_z]; + + // Don't use `hull() for(...) {...}` because it's slow. + translate(center_pts) hull() { + corner(0); + corner(1); + corner(2); + corner(3); + corner(4); + corner(5); + corner(6); + corner(7); + } + + // hook for testing + test_rounded_edge_corner_center(corner_frags, corners, center_pts); +} + +// override it to test +module test_rounded_edge_corner_center(corner_frags, corners, center_pts) { + +} + +/** +* triangulate.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-triangulate.html +* +**/ + +function _triangulate_in_triangle(p0, p1, p2, p) = + let( + v0 = p0 - p, + v1 = p1 - p, + v2 = p2 - p, + c0 = cross(v0, v1), + c1 = cross(v1, v2), + c2 = cross(v2, v0) + ) + (c0 > 0 && c1 > 0 && c2 > 0) || (c0 < 0 && c1 < 0 && c2 < 0); + +function _triangulate_snipable(shape_pts, u, v, w, n, indices, epsilon = 0.0001) = + let( + a = shape_pts[indices[u]], + b = shape_pts[indices[v]], + c = shape_pts[indices[w]], + ax = a[0], + ay = a[1], + bx = b[0], + by = b[1], + cx = c[0], + cy = c[1] + ) + epsilon > (((bx - ax) * (cy - ay)) - ((by - ay) * (cx - ax))) ? + false : _triangulate_snipable_sub(shape_pts, n, u, v, w, a, b, c, indices); + +function _triangulate_snipable_sub(shape_pts, n, u, v, w, a, b, c, indices, p = 0) = + p == n ? true : ( + ((p == u) || (p == v) || (p == w)) ? _triangulate_snipable_sub(shape_pts, n, u, v, w, a, b, c, indices, p + 1) : ( + _triangulate_in_triangle(a, b, c, shape_pts[indices[p]]) ? + false : _triangulate_snipable_sub(shape_pts, n, u, v, w, a, b, c, indices, p + 1) + ) + ); + +// remove the elem at idx v from indices +function _triangulate_remove_v(indices, v, num_of_vertices) = + let( + nv_minuns_one = num_of_vertices - 1 + ) + v == 0 ? [for(i = [1:nv_minuns_one]) indices[i]] : ( + v == nv_minuns_one ? [for(i = [0:v - 1]) indices[i]] : concat( + [for(i = [0:v - 1]) indices[i]], + [for(i = [v + 1:nv_minuns_one]) indices[i]] + ) + ); + +function _triangulate_zero_or_value(num_of_vertices, value) = + num_of_vertices <= value ? 0 : value; + + +function _triangulate_real_triangulate_sub(shape_pts, collector, indices, v, num_of_vertices, count, epsilon) = + let( + // idxes of three consecutive vertices + u = _triangulate_zero_or_value(num_of_vertices, v), + vi = _triangulate_zero_or_value(num_of_vertices, u + 1), + w = _triangulate_zero_or_value(num_of_vertices, vi + 1) + ) + _triangulate_snipable(shape_pts, u, vi, w, num_of_vertices, indices, epsilon) ? + _triangulate_snip(shape_pts, collector, indices, u, vi, w, num_of_vertices, count, epsilon) : + _triangulate_real_triangulate(shape_pts, collector, indices, vi, num_of_vertices, count, epsilon); + +function _triangulate_snip(shape_pts, collector, indices, u, v, w, num_of_vertices, count, epsilon) = + let( + a = indices[u], + b = indices[v], + c = indices[w], + new_nv = num_of_vertices - 1 + ) + _triangulate_real_triangulate( + shape_pts, + concat(collector, [[a, b, c]]), + _triangulate_remove_v(indices, v, num_of_vertices), + v, + new_nv, + 2 * new_nv, + epsilon + ); + +function _triangulate_real_triangulate(shape_pts, collector, indices, v, num_of_vertices, count, epsilon) = + count <= 0 ? [] : ( + num_of_vertices == 2 ? + collector : _triangulate_real_triangulate_sub(shape_pts, collector, indices, v, num_of_vertices, count - 1, epsilon) + ); + +function triangulate(shape_pts, epsilon = 0.0001) = + let( + num_of_vertices = len(shape_pts), + v = num_of_vertices - 1, + indices = [for(vi = [0:v]) vi], + count = 2 * num_of_vertices + ) + num_of_vertices < 3 ? [] : _triangulate_real_triangulate(shape_pts, [], indices, v, num_of_vertices, count, epsilon); + + +/** +* shape_taiwan.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-shape_taiwan.html +* +**/ + +function shape_taiwan(h, distance = 0) = + let(tw = [[3.85724, 6.24078], [3.7902, 6.25779], [3.73596, 6.26288], [3.62807, 6.24587], [3.59581, 6.2602], [3.55429, 6.32386], [3.51698, 6.4571], [3.50768, 6.5361], [3.51188, 6.58284], [3.53917, 6.63708], [3.48981, 6.66934], [2.78286, 6.83905], [2.7218, 6.86112], [2.68441, 6.90272], [2.61063, 6.96209], [2.64288, 7.05549], [2.61062, 7.06568], [2.56396, 7.06058], [2.50281, 7.04109], [2.44849, 7.15647], [2.29319, 7.37123], [2.22438, 7.42555], [2.05644, 7.44248], [1.88674, 7.40509], [1.73901, 7.32879], [1.63795, 7.238], [1.58868, 7.14974], [1.54463, 7.0555], [1.5149, 7.0411], [1.48526, 7.01895], [1.53658, 6.9725], [1.58825, 6.92909], [1.63096, 6.86281], [1.61348, 6.80387], [1.57757, 6.83721], [1.53949, 6.89116], [1.48525, 6.87393], [1.43851, 6.87393], [1.40381, 6.86623], [1.37408, 6.85174], [1.28068, 6.77282], [1.20682, 6.7457], [0.723984, 6.65659], [0.586451, 6.60741], [0.5152, 6.57094], [0.458435, 6.52167], [0.393838, 6.47922], [0.108666, 6.38843], [-0.002506, 6.2925], [-0.110307, 6.16693], [-0.278327, 5.89785], [-0.312266, 5.80452], [-0.319942, 5.74076], [-0.35978, 5.72804], [-0.398773, 5.69411], [-0.431113, 5.64921], [-0.443073, 5.60508], [-0.45317, 5.54563], [-0.477762, 5.51429], [-0.504966, 5.48196], [-0.527025, 5.4379], [-0.511683, 5.32413], [-0.544022, 5.28008], [-0.630518, 5.10523], [-0.652506, 5.02118], [-0.721317, 4.96517], [-0.751047, 4.87851], [-0.807895, 4.81922], [-0.849502, 4.76313], [-0.835106, 4.67916], [-0.94535, 4.70123], [-1.03193, 4.67664], [-1.10318, 4.62476], [-1.16685, 4.55856], [-1.18724, 4.52125], [-1.20679, 4.47981], [-1.23643, 4.44755], [-1.33927, 4.41782], [-1.36462, 4.37621], [-1.38844, 4.28037], [-1.47747, 4.11993], [-1.51477, 4.03419], [-1.52674, 3.93321], [-1.55638, 3.87122], [-1.87389, 3.52162], [-1.92308, 3.43757], [-1.96721, 3.28479], [-2.11055, 3.03862], [-2.15224, 2.84946], [-2.18703, 2.75092], [-2.23874, 2.70864], [-2.2481, 2.65676], [-2.43313, 2.44284], [-2.47482, 2.40393], [-2.48998, 2.36148], [-2.49681, 2.20617], [-2.51727, 2.15951], [-2.59526, 2.09828], [-2.61312, 2.05583], [-2.62237, 2.00926], [-2.64705, 1.98728], [-2.68183, 1.97279], [-2.71915, 1.94559], [-2.76589, 1.90137], [-2.80741, 1.82768], [-2.89576, 1.58074], [-2.9601, 1.46267], [-3.00171, 1.34223], [-3.03396, 1.29978], [-3.14261, 1.19451], [-3.16223, 1.14945], [-3.18683, 1.04173], [-3.21142, 0.994987], [-3.30988, 0.906048], [-3.32504, 0.864443], [-3.42105, 0.738702], [-3.43797, 0.692212], [-3.47452, 0.558807], [-3.54409, 0.411083], [-3.62553, 0.283068], [-3.69434, 0.142925], [-3.72407, -0.047077], [-3.71634, -0.135425], [-3.67474, -0.317847], [-3.65948, -0.475761], [-3.62302, -0.539348], [-3.61029, -0.588533], [-3.62554, -0.628538], [-3.65703, -0.687068], [-3.61029, -0.73878], [-3.64002, -0.795629], [-3.57542, -0.918676], [-3.67902, -1.14784], [-3.62553, -1.18944], [-3.59589, -1.2046], [-3.60515, -1.24376], [-3.6476, -1.28781], [-3.68162, -1.37186], [-3.72138, -1.38205], [-3.74597, -1.40925], [-3.79785, -1.54426], [-3.80458, -1.58157], [-3.81731, -1.61121], [-3.84956, -1.65526], [-3.87677, -1.70958], [-3.8665, -1.75893], [-3.84714, -1.80812], [-3.83434, -1.86665], [-3.85397, -1.90666], [-3.89633, -1.91845], [-3.93532, -1.94052], [-3.94552, -2.0025], [-3.90315, -2.05169], [-3.83687, -2.10846], [-3.91452, -2.12525], [-3.92486, -2.16746], [-3.8021, -2.21718], [-3.78004, -2.24169], [-3.79537, -2.28599], [-3.84194, -2.30292], [-3.9014, -2.31052], [-3.94553, -2.3325], [-3.90902, -2.38984], [-3.81228, -2.42599], [-3.7487, -2.45311], [-3.7359, -2.49042], [-3.61032, -2.51249], [-3.51439, -2.52698], [-3.49737, -2.60598], [-3.53645, -2.69921], [-3.61031, -2.75101], [-3.55085, -2.813], [-3.52197, -2.86732], [-3.47455, -3.17194], [-3.46268, -3.20175], [-3.42108, -3.27805], [-3.36173, -3.36723], [-3.3667, -3.40353], [-3.39137, -3.45802], [-3.39137, -3.50468], [-3.37605, -3.57332], [-3.3048, -3.72879], [-3.22849, -3.84755], [-3.18689, -3.9332], [-3.16473, -4.02239], [-3.18175, -4.08606], [-3.21401, -4.15723], [-3.18942, -4.22848], [-3.1377, -4.29316], [-2.99756, -4.4237], [-2.91612, -4.51458], [-2.85675, -4.62575], [-2.84478, -4.76572], [-2.64282, -4.92119], [-2.55624, -4.90831], [-2.51219, -4.92877], [-2.47733, -4.95075], [-2.40355, -5.01441], [-2.33222, -5.06116], [-2.17691, -5.13485], [-2.10053, -5.19944], [-2.05126, -5.27328], [-2.00199, -5.30293], [-1.87641, -5.40147], [-1.84416, -5.44552], [-1.77804, -5.59577], [-1.69399, -5.70181], [-1.66687, -5.74611], [-1.35963, -6.53694], [-1.36899, -6.58351], [-1.4063, -6.638], [-1.41556, -6.68466], [-1.41556, -6.84915], [-1.42322, -6.93076], [-1.41556, -6.96992], [-1.33, -7.11073], [-1.34945, -7.21845], [-1.34692, -7.25315], [-1.28999, -7.29989], [-1.23921, -7.31522], [-1.20181, -7.27783], [-1.19675, -7.17685], [-1.07362, -7.20658], [-0.953101, -7.2609], [-0.849593, -7.33982], [-0.778259, -7.44248], [-0.775729, -7.35413], [-0.795186, -7.21416], [-0.797786, -7.08606], [-0.736641, -7.02913], [-0.685014, -6.99014], [-0.645009, -6.89851], [-0.620417, -6.78565], [-0.637343, -5.77062], [-0.612751, -5.52192], [-0.590692, -5.43534], [-0.443136, -5.1799], [-0.371802, -4.92362], [-0.341989, -4.73344], [-0.29036, -4.64283], [-0.16723, -4.48735], [-0.128151, -4.41105], [-0.046711, -4.20395], [-0.014539, -4.16235], [0.027066, -4.14297], [0.191803, -3.96039], [0.441265, -3.79565], [0.515127, -3.72608], [0.588904, -3.63285], [0.62032, -3.58105], [0.640788, -3.5317], [0.643228, -3.48757], [0.633124, -3.39173], [0.640794, -3.35189], [0.689979, -3.29336], [0.9039, -3.11338], [0.923353, -3.07093], [1.08363, -2.84522], [1.11605, -2.75342], [1.16262, -2.55407], [1.23396, -2.42344], [1.26116, -2.33518], [1.27809, -2.30031], [1.31035, -2.26637], [1.38421, -2.21213], [1.41638, -2.17987], [1.49024, -2.02701], [1.50464, -1.87935], [1.50212, -1.72405], [1.52671, -1.54937], [1.55897, -1.45589], [1.59897, -1.37942], [1.70669, -1.23616], [1.74147, -1.16044], [1.98243, 0.226167], [2.05637, 0.376416], [2.18195, 0.989965], [2.27788, 1.25316], [2.32959, 1.50186], [2.33469, 1.60107], [2.34741, 1.65447], [2.40173, 1.74366], [2.42118, 1.79798], [2.38391, 1.88363], [2.36176, 1.9185], [2.33473, 1.99994], [2.33213, 2.07136], [2.3542, 2.13747], [2.46798, 2.30473], [2.51203, 2.49221], [2.55625, 2.58553], [2.59356, 2.62284], [2.71147, 2.72138], [2.69968, 2.75364], [2.72175, 2.78497], [2.793, 2.8419], [2.89145, 2.97513], [2.98064, 3.06078], [2.97805, 3.26778], [2.98485, 3.3638], [2.99757, 3.41297], [3.01702, 3.44775], [3.03412, 3.48936], [3.02732, 3.59547], [3.03152, 3.64474], [3.14008, 3.76771], [3.23348, 3.82464], [3.27332, 3.8611], [3.26572, 3.90356], [3.23852, 3.94517], [3.22673, 3.98929], [3.23343, 4.02904], [3.32515, 4.08085], [3.33955, 4.16659], [3.31748, 4.26758], [3.2784, 4.34405], [3.3056, 4.3635], [3.37526, 4.38649], [3.39869, 4.42513], [3.32514, 4.42541], [3.22669, 4.4874], [3.17237, 4.58324], [3.16977, 4.70636], [3.19192, 4.7675], [3.19432, 4.8294], [3.14261, 5.06624], [3.14001, 5.17909], [3.14781, 5.29709], [3.1724, 5.41583], [3.20634, 5.51943], [3.25384, 5.59329], [3.54153, 5.94036], [3.62558, 6.01431], [3.94553, 6.17215], [3.91327, 6.21106]] / 15 * h) + distance == 0 ? tw : [for(i = [0 : len(tw) - 1]) if(norm(tw[i] - tw[i + 1]) > distance) tw[i]]; + + +/** +* m_rotation.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-m_rotation.html +* +**/ + + +function _q_rotation(a, v) = + let( + half_a = a / 2, + axis = v / norm(v), + s = sin(half_a), + x = s * axis[0], + y = s * axis[1], + z = s * axis[2], + w = cos(half_a), + + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + yx = y * x2, + yy = y * y2, + zx = z * x2, + zy = z * y2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2 + ) + [ + [1 - yy - zz, yx - wz, zx + wy, 0], + [yx + wz, 1 - xx - zz, zy - wx, 0], + [zx - wy, zy + wx, 1 - xx - yy, 0], + [0, 0, 0, 1] + ]; + +function _m_xRotation(a) = + let(c = cos(a), s = sin(a)) + [ + [1, 0, 0, 0], + [0, c, -s, 0], + [0, s, c, 0], + [0, 0, 0, 1] + ]; + +function _m_yRotation(a) = + let(c = cos(a), s = sin(a)) + [ + [c, 0, s, 0], + [0, 1, 0, 0], + [-s, 0, c, 0], + [0, 0, 0, 1] + ]; + +function _m_zRotation(a) = + let(c = cos(a), s = sin(a)) + [ + [c, -s, 0, 0], + [s, c, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + +function _xyz_rotation(a) = + let(ang = __to_ang_vect(a)) + _m_zRotation(ang[2]) * _m_yRotation(ang[1]) * _m_xRotation(ang[0]); + +function m_rotation(a, v) = + (a == 0 || a == [0, 0, 0] || a == [0] || a == [0, 0]) ? [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] : (v == undef ? _xyz_rotation(a) : _q_rotation(a, v)); + +/** +* golden_spiral_extrude.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-golden_spiral_extrude.html +* +**/ + +module golden_spiral_extrude(shape_pts, from, to, point_distance, + rt_dir = "CT_CLK", twist = 0, scale = 1.0, triangles = "SOLID") { + + pts_angles = golden_spiral( + from = from, + to = to, + point_distance = point_distance, + rt_dir = rt_dir + ); + + pts = [for(pt_angle = pts_angles) pt_angle[0]]; + angles = [ + for(pt_angle = pts_angles) + [90, 0, pt_angle[1] + (rt_dir == "CT_CLK" ? 0 : -90)] + ]; + + sections = cross_sections( + shape_pts, + pts, angles, + twist = twist, + scale = scale + ); + + polysections( + sections, + triangles = triangles + ); + + // testing hook + test_golden_spiral_extrude(sections); +} + +// override it to test +module test_golden_spiral_extrude(sections) { + +} + +/** +* bezier_surface.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-bezier_surface.html +* +**/ + +function bezier_surface(t_step, ctrl_pts) = + let(pts = [ + for(i = [0:len(ctrl_pts) - 1]) + bezier_curve(t_step, ctrl_pts[i]) + ]) + [for(x = [0:len(pts[0]) - 1]) + bezier_curve(t_step, + [for(y = [0:len(pts) - 1]) pts[y][x]] + ) + ]; + +/** +* rotate_p.scad +* +* @copyright Justin Lin, 2017 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-rotate_p.html +* +**/ + + +function _q_rotate_p_3d(p, a, v) = + let( + half_a = a / 2, + axis = v / norm(v), + s = sin(half_a), + x = s * axis[0], + y = s * axis[1], + z = s * axis[2], + w = cos(half_a), + + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + yx = y * x2, + yy = y * y2, + zx = z * x2, + zy = z * y2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2 + ) + [ + [1 - yy - zz, yx - wz, zx + wy] * p, + [yx + wz, 1 - xx - zz, zy - wx] * p, + [zx - wy, zy + wx, 1 - xx - yy] * p + ]; + +function _rotx(pt, a) = + let(cosa = cos(a), sina = sin(a)) + [ + pt[0], + pt[1] * cosa - pt[2] * sina, + pt[1] * sina + pt[2] * cosa + ]; + +function _roty(pt, a) = + let(cosa = cos(a), sina = sin(a)) + [ + pt[0] * cosa + pt[2] * sina, + pt[1], + -pt[0] * sina + pt[2] * cosa, + ]; + +function _rotz(pt, a) = + let(cosa = cos(a), sina = sin(a)) + [ + pt[0] * cosa - pt[1] * sina, + pt[0] * sina + pt[1] * cosa, + pt[2] + ]; + +function _rotate_p_3d(point, a) = + _rotz(_roty(_rotx(point, a[0]), a[1]), a[2]); + +function _rotate_p(p, a) = + let(angle = __to_ang_vect(a)) + len(p) == 3 ? + _rotate_p_3d(p, angle) : + __to2d( + _rotate_p_3d(__to3d(p), angle) + ); + + +function _q_rotate_p(p, a, v) = + len(p) == 3 ? + _q_rotate_p_3d(p, a, v) : + __to2d( + _q_rotate_p_3d(__to3d(p), a, v) + ); + +function rotate_p(point, a, v) = + v == undef ? _rotate_p(point, a) : _q_rotate_p(point, a, v); + + +/** +* in_shape.scad +* +* @copyright Justin Lin, 2019 +* @license https://opensource.org/licenses/lgpl-3.0.html +* +* @see https://openhome.cc/eGossip/OpenSCAD/lib-in_shape.html +* +**/ + + +function _in_shape_in_line_equation(edge, pt) = + let( + x1 = edge[0][0], + y1 = edge[0][1], + x2 = edge[1][0], + y2 = edge[1][1], + a = (y2 - y1) / (x2 - x1), + b = y1 - a * x1 + ) + (pt[1] == a * pt[0] + b); + +function _in_shape_in_any_edges_sub(edges, leng, pt, i, epsilon) = + leng == i ? false : ( + __in_line(edges[i], pt, epsilon) ? true : _in_shape_in_any_edges_sub(edges, leng, pt, i + 1, epsilon) + ); + +function _in_shape_in_any_edges(edges, pt, epsilon) = _in_shape_in_any_edges_sub(edges, len(edges), pt, 0, epsilon); + +function _in_shape_interpolate_x(y, p1, p2) = + p1[1] == p2[1] ? p1[0] : ( + p1[0] + (p2[0] - p1[0]) * (y - p1[1]) / (p2[1] - p1[1]) + ); + +function _in_shape_does_pt_cross(pts, i, j, pt) = + ((pts[i][1] > pt[1]) != (pts[j][1] > pt[1])) && + (pt[0] < _in_shape_interpolate_x(pt[1], pts[i], pts[j])); + + +function _in_shape_sub(shapt_pts, leng, pt, cond, i, j) = + j == leng ? cond : ( + _in_shape_does_pt_cross(shapt_pts, i, j, pt) ? + _in_shape_sub(shapt_pts, leng, pt, !cond, j, j + 1) : + _in_shape_sub(shapt_pts, leng, pt, cond, j, j + 1) + ); + +function in_shape(shapt_pts, pt, include_edge = false, epsilon = 0.0001) = + let( + leng = len(shapt_pts), + edges = __lines_from(points, true) + ) + _in_shape_in_any_edges(edges, pt, epsilon) ? include_edge : + _in_shape_sub(shapt_pts, leng, pt, false, leng - 1, 0); +