// path = list of points defining the path to be rounded. Can be 2D or 3D, and may have an extra coordinate giving rounding parameters. If you specify rounding parameters you must do so on every point.
// curve = rounding method to use. Set to "chamfer" for chamfers, "circle" for circular rounding and "smooth" for continuous curvature 4th order bezier rounding
// measure = how to measure the amount of rounding. Set to "cut" to specify the cut back with either "chamfer", "smooth", or "circle" rounding curves. Set to "radius" with `curve="circle"` to set circular radius rounding. Set to "joint" with `curve="smooth"` for joint type rounding. (See above for details on these rounding options.)
// size = curvature parameter(s). Set this to a single curvature parameter or parameter pair to apply uniform roundovers to every corner. Alternatively set this to a list of curvature parameters with the same length as `path` to specify the curvature at every corner. If you set this then all values given in `path` are treated as geometric coordinates. If you don't set this then the last value of each entry in `path` is treated as a rounding parameter.
// closed = if true treat the path as a closed polygon, otherwise treat it as open. Default: true.
// k = continuous curvature smoothness parameter default value. This value will apply with `curve=="smooth"` if you don't otherwise specify a smoothness parameter for a corner. Default: 0.5.
//
// Example(Med2D): Standard circular roundover with radius the same at every point. Compare results at the different corners.
// Example(Med2D): Continous curvature roundover using "cut", still the same at every corner. The default smoothness parameter of 0.5 was too gradual for these roundovers to fit, but 0.7 works.
// Example(Med3D): 3D printing test pieces to display different curvature shapes. You can see the discontinuity in the curvature on the "C" piece in the rendered image.
// 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.
// You can specify a sequence of offsets values, or you can use several built-in offset profiles that are designed to provide end treatments such as roundovers.
// The path is shifted by `offset()` multiple times in sequence
// to produce the final shape (not multiple shifts from one parent), so coarse definition of the input path will degrade
// Define the offset profile with a list of points. The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward. If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid. Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them. Note that the starting point of the profile is the center of the extrusion. If you use a profile as the top it will rise upwards. If you use it as the bottom it will be inverted, and will go downward.
// - circle: os_circle(r|cut). Define circular rounding either by specifying the radius or cut distance.
// - smooth: os_smooth(cut|joint). Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
// - teardrop: os_teardrop(r|cut). Rounding using a 1/8 circle that then changes to a 45 degree chamfer. The chamfer is at the end, and enables the object to be 3d printed without support. The radius gives the radius of the circular part.
// - chamfer: os_chamfer([height], [width], [cut], [angle]). Chamfer the edge at desired angle or with desired height and width. You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape. Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
// - extra: Add an extra vertical step of the specified height, to be used for intersections or differences. This extra step will extend the resulting object beyond the height you specify. Default: 0
// - check_valid: passed to offset(). Default: true
// - quality: passed to offset(). Default: 1
// - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16
// - offset_maxstep: The maxstep distance for offset() calls; controls the horizontal step density. Set smaller if you don't get the expected rounding. Default: 1
// - offset: Select "round" (r=) or "delta" (delta=) offset types for offset. You can also choose "chamfer" but this leads to exponential growth in the number of vertices with the steps parameter. Default: "round"
// You will generally want to use the above helper functions to generate the profiles.
// The profile specification is a list of pairs of keywords and values, e.g. ["r",12, type, "circle"]. The keywords are
// - "type" - type of rounding to apply, one of "circle", "teardrop", "chamfer", "smooth", or "profile" (Default: "circle")
// - "r" - the radius of the roundover, which may be zero for no roundover, or negative to round or flare outward. Default: 0
// - "cut" - the cut distance for the roundover or chamfer, which may be negative for flares
// - "chamfer_width" - the width of a chamfer
// - "chamfer_height" - the height of a chamfer
// - "angle" - the chamfer angle, measured from the vertical (so zero is vertical, 90 is horizontal). Default: 45
// - "joint" - the joint distance for a "smooth" roundover
// - "k" - the curvature smoothness parameter for "smooth" roundovers, a value in [0,1]. Default: 0.75
// - "points" - point list for use with the "profile" type
// - "extra" - extra height added for unions/differences. This makes the shape taller than the requested height. (Default: 0)
// - "check_valid" - passed to offset. Default: true.
// - "quality" - passed to offset. Default: 1.
// - "steps" - number of vertical steps to use for the roundover. Default: 16.
// - "offset_maxstep" - maxstep distance for offset() calls; controls the horizontal step density. Set smaller if you don't get expected rounding. Default: 1
// height / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
// Example: Here is the star chamfered at the top with a teardrop rounding at the bottom. Check out the rounded corners on the chamfer. Note that a very small value of `offset_maxstep` is needed to keep these round. Observe how the rounded star points vanish at the bottom in the teardrop: the number of vertices does not remain constant from layer to layer.
// Example: We round a cube using the continous curvature rounding profile. But note that the corners are not smooth because the curved square collapses into a square with corners. When a collapse like this occurs, we cannot turn `check_valid` off.
// Example: This box is much thicker, and cut in half to show the profiles. Note also that we can turn `check_valid` off for the outside and for the top inside, but not for the bottom inside. This example shows use of the direct keyword syntax without the helper functions.
// Example: This example uses a sine wave offset profile. Note that because the offsets occur sequentially and the path grows incrementally the offset needs a very fine resolution to produce the proper result. Note that we give no specification for the bottom, so it is straight.
echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested many layers. This can be slow or run out of recursion depth.");
// Extrudes 2d children with layers formed from the convex hull of the offset of each child according to a sequence of offset values.
// Like `offset_sweep` this module can use built-in offset profiles to provide treatments such as roundovers or chamfers but unlike `offset_sweep()` it
// operates on 2d children rather than a point list. Each offset is computed using
// the native `offset()` module from the input geometry. If your geometry has internal holes or is too small for the specified offset then you may get
// unexpected results.
//
// The build-in profiles are: circular rounding, teardrop rounding, chamfer, continuous curvature rounding, and chamfer.
// Also note that when a rounding radius is negative the rounding will flare outwards. The easieast way to specify
// the profile is by using the profile helper functions. These functions take profile parameters, as well as some
// general settings and translate them into a profile specification, with error checking on your input. The description below
// describes the helper functions and the parameters specific to each function. Below that is a description of the generic
// settings that you can optionally use with all of the helper functions.
//
// The final shape is created by combining convex hulls of small extrusions. The thickness of these small extrusions may result
// your model being slightly too long (if the curvature at the end is flaring outward), so if the exact length is very important
// you may need to intersect with a bounding cube. (Note that extra length can also be intentionally added with the `extra` argument.)
//
// - profile: os_profile(points)
// Define the offset profile with a list of points. The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward. If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid. Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them. Note that the starting point of the profile is the center of the extrusion. If you use a profile as the top it will rise upwards. If you use it as the bottom it will be inverted, and will go downward.
// - circle: os_circle(r|cut). Define circular rounding either by specifying the radius or cut distance.
// - smooth: os_smooth(cut|joint). Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
// - teardrop: os_teardrop(r|cut). Rounding using a 1/8 circle that then changes to a 45 degree chamfer. The chamfer is at the end, and enables the object to be 3d printed without support. The radius gives the radius of the circular part.
// - chamfer: os_chamfer([height], [width], [cut], [angle]). Chamfer the edge at desired angle or with desired height and width. You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape. Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
//
// The general settings that you can use with all of the helper functions are mostly used to control how offset_sweep() calls the offset() function.
// - extra: Add an extra vertical step of the specified height, to be used for intersections or differences. This extra step will extend the resulting object beyond the height you specify. Default: 0
// - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16
// - offset: Select "round" (r=), "delta" (delta=), or "chamfer" offset types for offset. Default: "round"
//
// Many of the arguments are described as setting "default" values because they establish settings which may be overridden by
// the top and bottom profile specifications.
//
// You will generally want to use the above helper functions to generate the profiles.
// The profile specification is a list of pairs of keywords and values, e.g. ["r",12, type, "circle"]. The keywords are
// - "type" - type of rounding to apply, one of "circle", "teardrop", "chamfer", "smooth", or "profile" (Default: "circle")
// - "r" - the radius of the roundover, which may be zero for no roundover, or negative to round or flare outward. Default: 0
// - "cut" - the cut distance for the roundover or chamfer, which may be negative for flares
// - "chamfer_width" - the width of a chamfer
// - "chamfer_height" - the height of a chamfer
// - "angle" - the chamfer angle, measured from the vertical (so zero is vertical, 90 is horizontal). Default: 45
// - "joint" - the joint distance for a "smooth" roundover
// - "k" - the curvature smoothness parameter for "smooth" roundovers, a value in [0,1]. Default: 0.75
// - "points" - point list for use with the "profile" type
// - "extra" - extra height added for unions/differences. This makes the shape taller than the requested height. (Default: 0)
// - "steps" - number of vertical steps to use for the roundover. Default: 16.
// - "offset" - select "round" (r=) or "delta" (delta=) offset type for offset. Default: "round"
// Note that unlike `offset_sweep`, because the offset operation is always performed from the base shape, using chamfered offsets does not increase the
// number of vertices or lead to any special complications.
// height / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
// 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
// rounded, flat or pointed with the `start` and `end` parameters.
//
// The `check_valid`, `quality` and `maxstep` parameters are passed through to `offset()`
//
// If `width` is a scalar then the output will be a centered stroke of the specified width. If width
// is a list of two values then those two values will define the stroke side positions relative to the center line, where
// as with offset(), the shift is to the left for open paths and outward for closed paths. For example,
// setting `width` to `[0,1]` will create a stroke of width 1 that extends entirely to the left of the input, and and [-4,-6]
// will create a stroke of width 2 offset 4 units to the right of the input path.
//
// If closed==false then the function form will return a path. If closed==true then it will return a region. The `start` and
// `end` parameters are forbidden for closed paths.
//
// Three simple end treatments are supported, "flat" (the default), "round" and "pointed". The "flat" treatment
// cuts off the ends perpendicular to the path and the "round" treatment applies a semicircle to the end. The
// "pointed" end treatment caps the stroke with a centered triangle that has 45 degree angles on each side.
//
// More complex end treatments are available through parameter lists with helper functions to ease parameter passing. The parameter list
// keywords are
// - "type": the type of end treatment, one of "shifted_point", "roundover", or "flat"
// - "angle": relative angle (relative to the path)
// - "abs_angle": absolute angle (angle relative to x-axis)
// - "cut": cut distance for roundovers, a single value to round both corners identically or a list of two values for the two corners. Negative values round outward.
// - "k": curvature smoothness parameter for roundovers, default 0.75
//
// Function helpers for defining ends, prefixed by "os" for offset_stroke.
//
// os_flat(angle|absangle): specify a flat end either relative to the path or relative to the x-axis
// os_pointed(loc,dist): specify a pointed tip where the point is distance `loc` from the centerline (positive is the left direction as for offset), and `dist` is the distance from the path end to the point tip. The default value for `loc` is zero (the center). You must specify `dist` when using this option.
// os_round(cut,angle|absangle,k). Rounded ends with the specified cut distance, based on the specified angle or absolute angle. The `k` parameter is the smoothness parameter for continuous curvature rounding.
//
// Note that `offset_stroke()` will attempt to apply roundovers and angles at the ends even when it means deleting segments of the stroke, unlike round_corners which only works on a segment adjacent to a corner. If you specify an overly extreme angle it will fail to find an intersection with the stroke and display an error. When you specify an angle the end segment is rotated around the center of the stroke and the last segment of the stroke one one side is extended to the corner.
//
// The $fn and $fs variables are used to determine the number of segments for rounding, while maxstep is used to determine the segments of `offset`. If you
// get the expected rounding along the path, decrease `maxstep` and if the curves created by `os_round()` are too coarse, adjust $fn or $fs.
//
// Arguments:
// path = path that defines the stroke
// width = width of the stroke, a scalar or a vector of 2 values giving the offset from the path. Default: 1
// rounded = set to true to use rounded offsets, false to use sharp (delta) offsets. Default: true
// chamfer = set to true to use chamfers when `rounded=false`. Default: false
// start = end streatment for the start of the stroke. See above for details. Default: "flat"
// end = end streatment for the end of the stroke. See above for details. Default: "flat"
// check_valid = passed to offset(). Default: true
// quality = passed to offset(). Default: 1
// maxstep = passed to offset() to define number of points in the offset. Default: 0.1
// closed = true if the curve is closed, false otherwise. Default: false
// Example(2D): The effect of the `rounded` and `chamfer` options is most evident at sharp corners. This only affects the middle of the path, not the ends.
// Example(2D): The left stroke uses flat ends with a relative angle of zero. The right hand one uses flat ends with an absolute angle of zero, so the ends are parallel to the x-axis.
// Example(2D): With continuous sampling the end treatment can remove segments or extend the last segment linearly, as shown here. Again the left side uses relative angle flat ends and the right hand example uses absolute angle.
// Example(2D): The os_round() end treatment adds roundovers to the end corners by specifying the `cut` parameter. In the first example, the cut parameter is the same at each corner. The bezier smoothness parameter `k` is given to allow a larger cut. In the second example, each corner is given a different roundover, including zero for no rounding at all. The red shows the same strokes without the roundover.
// Example(2D): Negative cut values produce a flaring end. Note how the absolute angle aligns the ends of the first example withi the axes. In the second example positive and negative cut values are combined. Note also that very different cuts are needed at the start end to produce a similar looking flare.
// Example(2D): In this example a spurious triangle appears. This results from overly enthusiastic validity checking. Turning validity checking off fixes it in this case.