diff --git a/debug.scad b/debug.scad index e4cd754..299667c 100644 --- a/debug.scad +++ b/debug.scad @@ -72,7 +72,7 @@ module trace_path(path, closed=false, showpts=false, N=1, size=1, color="yellow" // ); module debug_polygon(points, paths=undef, convexity=2, size=1) { - pths = is_undef(paths)? [for (i=[0:1:len(points)-1]) i] : is_num(paths[0])? [paths] : paths; + paths = is_undef(paths)? [[for (i=[0:1:len(points)-1]) i]] : is_num(paths[0])? [paths] : paths; echo(points=points); echo(paths=paths); linear_extrude(height=0.01, convexity=convexity, center=true) { diff --git a/rounding.scad b/rounding.scad index 958db31..e48700b 100644 --- a/rounding.scad +++ b/rounding.scad @@ -14,7 +14,7 @@ include // Function: round_corners() // // Usage: -// rounded_path = round_corners(path, , *, , , , *); +// rounded_path = round_corners(path, , , , , , ); // // Description: // Takes a 2D or 3D path as input and rounds each corner @@ -24,25 +24,29 @@ include // tactile "bump" where the curvature changes from flat to circular. // See https://hackernoon.com/apples-icons-have-that-shape-for-a-very-good-reason-720d4e7c8a14 // . -// You select the type of rounding using the `method` option, which should be `"smooth"` to +// You select the type of rounding using the `method` parameter, which should be `"smooth"` to // get continuous curvature rounding, `"circle"` to get circular rounding, or `"chamfer"` to get chamfers. The default is circle -// rounding. Each method has two options you can use to specify the amount of rounding. -// All of the rounding methods accept the cut option. This mode specifies the distance from the unrounded corner to the rounded tip, so how +// rounding. Each method accepts multiple options to specify the amount of rounding. +// . +// The `cut` parameter specifies the distance from the unrounded corner to the rounded tip, so how // much of the corner to "cut" off. This can be easier to understand than setting a circular radius, which can be // unexpectedly extreme when the corner is very sharp. It also allows a systematic specification of // corner treatments that are the same size for all three methods. // . -// For circular rounding you can also use the `radius` parameter, which sets a circular rounding -// radius. For chamfers and smooth rounding you can specify the `joint` parameter, which specifies the distance +// The `joint` parameter specifies the distance // away from the corner along the path where the roundover or chamfer should start. The figure below shows -// the cut and joint distances for a given roundover. +// the cut and joint distances for a given roundover. This parameter is good for ensuring that your roundover will +// fit on the polygon, since you can easily tell whether adjacent corner treatments will interfere. +// . +// For circular rounding you can also use the `radius` parameter, which sets a circular rounding +// radius. // . // The `"smooth"` method rounding also has a parameter that specifies how smooth the curvature match // is. This parameter, `k`, ranges from 0 to 1, with a default of 0.5. Larger values give a more // abrupt transition and smaller ones a more gradual transition. If you set the value much higher // than 0.8 the curvature changes abruptly enough that though it is theoretically continuous, it may // not be continuous in practice. If you set it very small then the transition is so gradual that -// the length of the roundover may be extremely long. +// the length of the roundover may be extremely long. // . // If you select curves that are too large to fit the function will fail with an error. You can set `verbose=true` to // get a message showing a list of scale factors you can apply to your rounding parameters so that the @@ -54,6 +58,9 @@ include // of the curve are not rounded. In this case you can specify a full list of points anyway, and the endpoint values are ignored, // or you can specify a list that has length len(path)-2, omitting the two dummy values. // . +// If your input path includes collinear points you must use a cut or radius value of zero for those "corners". You can +// choose a nonzero joint parameter, which will cause extra points to be inserted. +// . // Examples: // * `method="circle", radius=2`: // Rounds every point with circular, radius 2 roundover @@ -191,6 +198,26 @@ include // // Try changing the value to see the effect. // rpath = round_corners(path3d, joint=rounding, k=1, method="smooth", closed=false); // path_sweep( regular_ngon(n=36, or=.1), rpath); +// Example(2D): The rounding invocation that is commented out gives an error because the rounding parameters interfere with each other. The error message gives a list of factors that can help you fix this: [0.852094, 0.852094, 1.85457, 10.1529] +// $fn=64; +// path = [[0, 0],[10, 0],[20, 20],[30, -10]]; +// debug_polygon(path); +// //polygon(round_corners(path,cut = [1,3,1,1], method="circle")); +// Example(2D): The list of factors shows that the problem is in the first two rounding values, because the factors are smaller than one. If we multiply the first two parameters by 0.85 then the roundings fit. The verbose option gives us the same fit factors. +// $fn=64; +// path = [[0, 0],[10, 0],[20, 20],[30, -10]]; +// polygon(round_corners(path,cut = [0.85,3*0.85,1,1], method="circle", verbose=true)); +// Example(2D): From the fit factors we can see that rounding at vertices 2 and 3 could be increased a lot. Applying those factors we get this more rounded shape. The new fit factors show that we can still further increase the rounding parameters if we wish. +// $fn=64; +// path = [[0, 0],[10, 0],[20, 20],[30, -10]]; +// polygon(round_corners(path,cut = [0.85,3*0.85,2.13, 10.15], method="circle",verbose=true)); +// Example(2D): Using the `joint` parameter it's easier to understand whether your roundvers will fit. We can guarantee a fairly large roundover on any path by picking each one to use up half the segment distance along the shorter of its two segments: +// $fn=64; +// path = [[0, 0],[10, 0],[20, 20],[30, -10]]; +// path_len = path_segment_lengths(path,closed=true); +// halflen = [for(i=idx(path)) min(select(path_len,i-1,i))/2]; +// polygon(round_corners(path,joint = halflen, method="circle",verbose=true)); + module round_corners(path, method="circle", radius, cut, joint, k, closed=true, verbose=false) {no_module();} function round_corners(path, method="circle", radius, cut, joint, k, closed=true, verbose=false) = assert(in_list(method,["circle", "smooth", "chamfer"]), "method must be one of \"circle\", \"smooth\" or \"chamfer\"") @@ -211,7 +238,6 @@ function round_corners(path, method="circle", radius, cut, joint, k, closed=true assert(k_ok,method=="smooth" ? str("Input k must be a number or list with length ",len(path), closed?"":str(" or ",len(path)-2)) : "Input k is only allowed with method=\"smooth\"") assert(method=="circle" || measure!="radius", "radius parameter allowed only with method=\"circle\"") - assert(method!="circle" || measure!="joint", "joint parameter not allowed with method=\"circle\"") let( parm = is_num(size) ? repeat(size, len(path)) : len(size)=1,"Roundovers are too big for the path") + assert(min(scalefactors)>=1,str("Roundovers are too big for the path. If you multitply them by this vector they should fit: ",scalefactors)) [ for(i=[0:1:len(path)-1]) each (dk[i][0] == 0)? [path[i]] : @@ -320,14 +349,17 @@ function _chamfcorner(points, parm) = function _circlecorner(points, parm) = let( - angle = vector_angle(points)/2, - d = parm[0], - r = parm[1], - prev = unit(points[0]-points[1]), - next = unit(points[2]-points[1]), - center = r/sin(angle) * unit(prev+next)+points[1], - start = points[1]+prev*d, - end = points[1]+next*d + angle = vector_angle(points)/2, + d = parm[0], + r = parm[1], + prev = unit(points[0]-points[1]), + next = unit(points[2]-points[1]) + ) + approx(angle,90) ? [points[1]+prev*d, points[1]+next*d] : + let( + center = r/sin(angle) * unit(prev+next)+points[1], + start = points[1]+prev*d, + end = points[1]+next*d ) // 90-angle is half the angle of the circular arc arc(max(3,ceil((90-angle)/180*segs(r))), cp=center, points=[start,end]); @@ -392,7 +424,7 @@ function _rounding_offsets(edgespec,z_dir=1) = // Function: smooth_path() // Usage: -// smoothed = smooth_path(path, , *, , , *) +// smoothed = smooth_path(path, , , , , ); // Description: // Smooths the input path using a cubic spline. Every segment of the path will be replaced by a cubic curve // with `splinesteps` points. The cubic interpolation will pass through every input point on the path @@ -475,7 +507,7 @@ function _scalar_to_vector(value,length,varname) = // Function: path_join() // Usage: -// joined_path = path_join(paths, , *, , *) +// joined_path = path_join(paths, , , , ); // Description: // Connect a sequence of paths together into a single path with optional rounding // applied at the joints. By default the first path is taken as specified and subsequent paths are @@ -637,9 +669,9 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false) // Function&Module: offset_sweep() // Usage: most common module arguments. See Arguments list below for more. -// offset_sweep(path, , , , *, *) +// offset_sweep(path, , , , , ,...) // Usage: most common function arguments. See Arguments list below for more. -// vnf = offset_sweep(path, , , , **) +// vnf = offset_sweep(path, , , , , ...) // Description: // Takes a 2d path as input and extrudes it upwards and/or downward. Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer. When invoked as a function returns a VNF; when invoked as a module produces geometry. // Using the `top` and/or `bottom` arguments you can specify a sequence of offsets values, or you can use several built-in offset profiles that @@ -1267,10 +1299,10 @@ function _remove_undefined_vals(list) = // Function&Module: offset_stroke() // Usage: as module -// offset_stroke(path, , *, , , , , , , *) +// offset_stroke(path, , , , , , , , , ); // Usage: as function -// path = offset_stroke(path, , *closed=false, , , , , , , *) -// region = offset_stroke(path, , *closed=true, , , , , , , *) +// path = offset_stroke(path, , closed=false, , , , , , , ); +// region = offset_stroke(path, , closed=true, , , , , , , ); // Description: // Uses `offset()` to compute a stroke for the input path. Unlike `stroke`, the result does not need to be // centered on the input path. The corners can be rounded, pointed, or chamfered, and you can make the ends @@ -1511,7 +1543,7 @@ function _stroke_end(width,left, right, spec) = normal_dir = unit(normal_seg[1]-normal_seg[0]), width_dir = sign(width[0]-width[1]) ) - type == "round"? [arc(points=[right[0],normal_pt,left[0]],N=50),1,1] : + type == "round"? [arc(points=[right[0],normal_pt,left[0]],N=ceil(segs(width/2)/2)),1,1] : type == "pointed"? [[normal_pt],0,0] : type == "shifted_point"? ( let(shiftedcenter = center + width_dir * parallel_dir * struct_val(spec, "loc")) @@ -1651,9 +1683,9 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) = // Function&Module: rounded_prism() // Usage: as a module -// rounded_prism(bottom, , *, , , , , , , , , , *); +// rounded_prism(bottom, , , , , , , , , , , , ,...) ; // Usage: as a function -// vnf = rounded_prism(bottom, , *, , , , , , , , , *); +// vnf = rounded_prism(bottom, , , , , , , , , , , ); // Description: // Construct a generalized prism with continuous curvature rounding. You supply the polygons for the top and bottom of the prism. The only // limitation is that joining the edges must produce a valid polyhedron with coplanar side faces. You specify the rounding by giving @@ -2033,7 +2065,7 @@ function _circle_mask(r) = // Module: bent_cutout_mask() // Usage: -// bent_cutout_mask(r|radius, thickness, path) +// bent_cutout_mask(r|radius, thickness, path); // Description: // Creates a mask for cutting a round-edged hole out of a vertical cylindrical shell. The specified radius // is the center radius of the cylindrical shell. The path needs to be sampled finely enough diff --git a/skin.scad b/skin.scad index 2f6128c..2d95458 100644 --- a/skin.scad +++ b/skin.scad @@ -13,16 +13,15 @@ // Function&Module: skin() // Usage: As module: -// skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z], [convexity], -// [anchor],[cp],[spin],[orient],[extent]); +// skin(profiles, slices, , , , , , , , ,,,,) ; // Usage: As function: -// vnf = skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]); +// vnf = skin(profiles, slices, , , , , , ); // Description: // Given a list of two or more path `profiles` in 3d space, produces faces to skin a surface between // the profiles. Optionally the first and last profiles can have endcaps, or the first and last profiles // can be connected together. Each profile should be roughly planar, but some variation is allowed. // Each profile must rotate in the same clockwise direction. If called as a function, returns a -// [VNF structure](vnf.scad) like `[VERTICES, FACES]`. If called as a module, creates a polyhedron +// [VNF structure](vnf.scad) `[VERTICES, FACES]`. If called as a module, creates a polyhedron // of the skinned profiles. // . // The profiles can be specified either as a list of 3d curves or they can be specified as @@ -80,11 +79,11 @@ // in the polyhedron---in will produce the least twisted possible result. This algorithm has quadratic // run time so it can be slow with very large profiles. // . -// The "distance" and "tangent" methods are work by duplicating vertices to create +// The "distance" and "tangent" methods work by duplicating vertices to create // triangular faces. The "distance" method finds the global minimum distance method for connecting two // profiles. This algorithm generally produces a good result when both profiles are discrete ones with // a small number of vertices. It is computationally intensive (O(N^3)) and may be -// slow on large inputs. The resulting surfaces generally have curves faces, so be +// slow on large inputs. The resulting surfaces generally have curved faces, so be // sure to select a sufficiently large value for `slices` and `refine`. // The `"tangent"` method generally produces good results when // connecting a discrete polygon to a convex, finely sampled curve. It works by finding @@ -109,6 +108,7 @@ // Arguments: // profiles = list of 2d or 3d profiles to be skinned. (If 2d must also give `z`.) // slices = scalar or vector number of slices to insert between each pair of profiles. Set to zero to use only the profiles you provided. Recommend starting with a value around 10. +// --- // refine = resample profiles to this number of points per edge. Can be a list to give a refinement for each profile. Recommend using a value above 10 when using the "distance" method. Default: 1. // sampling = sampling method to use with "direct" and "reindex" methods. Can be "length" or "segment". Ignored if any profile pair uses either the "distance" or "tangent" methods. Default: "length". // closed = set to true to connect first and last profile (to make a torus). Default: false @@ -511,7 +511,7 @@ function _skin_core(profiles, caps) = // Function: subdivide_and_slice() // Usage: -// subdivide_and_slice(profiles, slices, [numpoints], [method], [closed]) +// newprof = subdivide_and_slice(profiles, slices, , , ); // Description: // Subdivides the input profiles to have length `numpoints` where `numpoints` must be at least as // big as the largest input profile. By default `numpoints` is set equal to the length of the @@ -539,7 +539,7 @@ function subdivide_and_slice(profiles, slices, numpoints, method="length", close // Function: slice_profiles() // Usage: -// profs = slice_profiles(profiles,slices,); +// profs = slice_profiles(profiles, slices, ); // Description: // Given an input list of profiles, linearly interpolate between each pair to produce a // more finely sampled list. The parameters `slices` specifies the number of slices to @@ -757,7 +757,7 @@ function _find_one_tangent(curve, edge, curve_offset=[0,0,0], closed=true) = // Function: associate_vertices() // Usage: -// associate_vertices(polygons, split) +// newpoly = associate_vertices(polygons, split); // Description: // Takes as input a list of polygons and duplicates specified vertices in each polygon in the list through the series so // that the input can be passed to `skin()`. This allows you to decide how the vertices are linked up rather than accepting @@ -823,7 +823,7 @@ function associate_vertices(polygons, split, curpoly=0) = // Function&Module: sweep() // Usage: As Module -// sweep(shape, transforms, , ) +// sweep(shape, transforms, , , , , , , ) ; // Usage: As Function // vnf = sweep(shape, transforms, , ); // Description: @@ -845,6 +845,7 @@ function associate_vertices(polygons, split, curpoly=0) = // transforms = list of 4x4 matrices to apply // closed = set to true to form a closed (torus) model. Default: false // caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false. +// --- // convexity = convexity setting for use with polyhedron. (module only) Default: 10 // anchor = Translate so anchor point is at the origin. (module only) Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0 @@ -914,8 +915,9 @@ module sweep(shape, transforms, closed=false, caps, convexity=10, // Function&Module: path_sweep() -// Usage: -// path_sweep(shape, path, [method], [normal], [closed], [twist], [twist_by_length], [symmetry], [last_normal], [tangent], [relaxed], [caps], [convexity], [transforms]) +// Usage: As module +// path_sweep(shape, path, , , , , , , , , , , , , , , , , ) ; +// vnf = path_sweep(shape, path, , , , , , , , , , , , ); // Description: // Takes as input a 2D polygon path or region, and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path. // When run as a module returns the polyhedron geometry. When run as a function returns a VNF by default or if you set `transforms=true` @@ -967,6 +969,7 @@ module sweep(shape, transforms, closed=false, caps, convexity=10, // shape = A 2D polygon path or region describing the shape to be swept. // path = 2D or 3D path giving the path to sweep over // method = one of "incremental", "natural" or "manual". Default: "incremental" +// --- // normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise. // closed = path is a closed loop. Default: false // twist = amount of twist to add in degrees. For closed sweeps must be a multiple of 360/symmetry. Default: 0 @@ -1302,8 +1305,10 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi // Function&Module: path_sweep2d() -// Usage: -// path_sweep2d(shape, path, , ) +// Usage: as module +// path_sweep2d(shape, path, , , , , , , , , ) ; +// Usage: as function +// vnf = path_sweep2d(shape, path, , , ); // Description: // Takes an input 2D polygon (the shape) and a 2d path and constructs a polyhedron by sweeping the shape along the path. // When run as a module returns the polyhedron geometry. When run as a function returns a VNF. @@ -1320,6 +1325,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi // closed = path is a closed loop. Default: false // caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false. // quality = quality of offset used in calculation. Default: 1 +// --- // convexity = convexity parameter for polyhedron (module only) Default: 10 // anchor = Translate so anchor point is at the origin. (module only) Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0