I use BOSL2's offset_sweep() and path_sweep() often when I can work with
paths from functions. When working with modules the closest tool is
convex_offset_extrude() but text() and projection() often aren't convex.
So I slightly tweaked it to make offset_extrude() using roof() instead of
hull() on pairs of linear_extrude(). I think this could be worthy of
submission to BOSL2's github but thought it could benefit from additional
eyes and some use.
Here's an example:
include <BOSL2/std.scad>
offset_extrude(height=2, top=os_teardrop(r=0.5),
bottom=os_teardrop(r=-0.5), steps=8, offset="delta", $fn=128)
text("S", font="Liberation Sans:style=Bold Italic");
[image: offset_extrude_test.png]
Here's the code:
module conditional_zflip_z(condition, z=0) {
if (condition)
scale([1,1,-1]) translate([0,0,-z]) children();
else children();
}
module conditional_offset(condition, delta) {
if (condition)
offset(delta=delta) children();
else children();
}
// generates an extrusion from z=0 cropped at z=rise.
// extrusion tapers inward with positive run and outward with negative run
module roofrr(rise=1, run=1, s=100, convexity=10) {
assert(run != 0, "run can not be zero.")
assert(rise != 0, "rise can not be zero.")
assert(s != 0, "s can not be zero.")
conditional_zflip_z(run<0, z=rise)
difference() {
scale([1,1,rise/abs(run)]) roof(convexity=convexity)
conditional_offset(run<0, delta=-run) children();
translate([0,0,rise>0 ? rise+s0.5 : rise-s0.5]) cube([s,s,s],
center=true);
}
}
module offset_extrude(
height,
bottom=[],
top=[],
h, l, length,
offset="round", r=0, steps=16,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10, thickness = 1/1024, s=100
) {
req_children($children);
argspec = [
["for", ""],
["r",r],
["extra",extra],
["type","circle"],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
];
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
offsets_bot = _rounding_offsets(bottom, -1);
offsets_top = _rounding_offsets(top, 1);
// "Extra" height enlarges the result beyond the requested height,
so subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1])
struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) -
struct_val(top,"extra");
height = one_defined([l,h,height,length], "l,h,height,length",
dflt=u_add(bottom_height,top_height));
middle = height-bottom_height-top_height;
check =
assert(height>=0, "Height must be nonnegative")
assert(middle>=0, str(
"Specified end treatments (bottom height =
",bottom_height,
" top_height = ",top_height,") are too
large for extrusion height (",height,")"
)
);
// The entry r[i] is [radius,z] for a given layer
r = move([0,bottom_height],p=concat(
reverse(offsets_bot), [[0,0], [0,middle]],
move([0,middle], p=offsets_top)));
delta = [for(val=deltas(column(r,0))) sign(val)];
below=[-thickness,0];
above=[0,thickness];
// layers is a list of pairs of the relative positions for each
layer, e.g. [0,thickness]
// puts the layer above the polygon, and [-thickness,0] puts it
below.
layers = [for (i=[0:len(r)-1])
i==0 ? (delta[0]<0 ? below : above) :
i==len(r)-1 ? (delta[len(delta)-1] < 0 ? below : above) :
delta[i]==0 ? above :
delta[i+1]==0 ? below :
delta[i]==delta[i-1] ? [-thickness/2, thickness/2] :
delta[i] == 1 ? above :
/* delta[i] == -1 ? */ below];
dochamfer = offset=="chamfer";
attachable(){
for(i=[0:len(r)-2])
for(j=[0:$children-1]) {
run = r[i][0] - r[i+1][0];
rise = r[i+1][1] - r[i][1];
if (run) {
up(r[i][1]+layers[i][0])
roofrr(rise=rise, run=run, s=s, convexity=convexity)
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
}
else {
up(r[i][1]+layers[i][0])
linear_extrude(convexity=convexity,height=rise)
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
}
}
union();
}
}
I eliminated some redundant code and the layers[][] stuff which I don't
think is helpful with slices produced by roof().
Here's my latest offset_extrude():
module conditional_zflip_z(condition, z=0) {
if (condition)
scale([1,1,-1]) translate([0,0,-z]) children();
else children();
}
module conditional_offset(condition, delta) {
if (condition)
offset(delta=delta) children();
else children();
}
// generates an extrusion from z=0 cropped at z=rise.
// extrusion tapers inward with positive run and outward with negative run
module roofrr(rise=1, run=1, s=100, convexity=10) {
assert(rise != 0, "rise can not be zero.")
assert(s != 0, "s can not be zero.")
if (run) {
conditional_zflip_z(run<0, z=rise)
difference() {
scale([1,1,rise/abs(run)]) roof(convexity=convexity)
conditional_offset(run<0, delta=-run) children();
translate([0,0,rise>0 ? rise+s0.5 : rise-s0.5]) cube([s,s,s],
center=true);
}
}
else {
conditional_zflip_z(rise<0) linear_extrude(height=abs(rise),
convexity=convexity) children();
}
}
module offset_extrude(
height,
bottom=[],
top=[],
h, l, length,
offset="round", r=0, steps=16,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10, thickness = 1/1024, s=100
) {
req_children($children);
argspec = [
["for", ""],
["r",r],
["extra",extra],
["type","circle"],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
];
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
offsets_bot = _rounding_offsets(bottom, -1);
offsets_top = _rounding_offsets(top, 1);
// "Extra" height enlarges the result beyond the requested height, so
subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1]) -
struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) -
struct_val(top,"extra");
height = one_defined([l,h,height,length], "l,h,height,length",
dflt=u_add(bottom_height,top_height));
middle = height-bottom_height-top_height;
check =
assert(height>=0, "Height must be nonnegative")
assert(middle>=0, str(
"Specified end treatments (bottom height =
",bottom_height,
" top_height = ",top_height,") are too large
for extrusion height (",height,")"
)
);
// The entry r[i] is [radius,z] for a given layer
r = move([0,bottom_height],p=concat(
reverse(offsets_bot), [[0,0], [0,middle]],
move([0,middle], p=offsets_top)));
dochamfer = offset=="chamfer";
attachable(){
last = len(r)-2;
for(i=[0:last]) {
overlap = /* i<last ? thickness : */ 0;
for(j=[0:$children-1]) {
run = r[i][0] - r[i+1][0];
rise = r[i+1][1] - r[i][1];
up(r[i][1]) // +layers[i][0]
roofrr(rise=rise+overlap, run=run, s=s, convexity=convexity)
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
}
}
union();
}
}
On Sat, Sep 19, 2026 at 1:59 PM Todd Allen speedebikes@gmail.com wrote:
I use BOSL2's offset_sweep() and path_sweep() often when I can work with
paths from functions. When working with modules the closest tool is
convex_offset_extrude() but text() and projection() often aren't convex.
So I slightly tweaked it to make offset_extrude() using roof() instead of
hull() on pairs of linear_extrude(). I think this could be worthy of
submission to BOSL2's github but thought it could benefit from additional
eyes and some use.
Here's an example:
include <BOSL2/std.scad>
offset_extrude(height=2, top=os_teardrop(r=0.5),
bottom=os_teardrop(r=-0.5), steps=8, offset="delta", $fn=128)
text("S", font="Liberation Sans:style=Bold Italic");
[image: offset_extrude_test.png]
Here's the code:
module conditional_zflip_z(condition, z=0) {
if (condition)
scale([1,1,-1]) translate([0,0,-z]) children();
else children();
}
module conditional_offset(condition, delta) {
if (condition)
offset(delta=delta) children();
else children();
}
// generates an extrusion from z=0 cropped at z=rise.
// extrusion tapers inward with positive run and outward with negative run
module roofrr(rise=1, run=1, s=100, convexity=10) {
assert(run != 0, "run can not be zero.")
assert(rise != 0, "rise can not be zero.")
assert(s != 0, "s can not be zero.")
conditional_zflip_z(run<0, z=rise)
difference() {
scale([1,1,rise/abs(run)]) roof(convexity=convexity)
conditional_offset(run<0, delta=-run) children();
translate([0,0,rise>0 ? rise+s0.5 : rise-s0.5]) cube([s,s,s],
center=true);
}
}
module offset_extrude(
height,
bottom=[],
top=[],
h, l, length,
offset="round", r=0, steps=16,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10, thickness = 1/1024, s=100
) {
req_children($children);
argspec = [
["for", ""],
["r",r],
["extra",extra],
["type","circle"],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
];
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
offsets_bot = _rounding_offsets(bottom, -1);
offsets_top = _rounding_offsets(top, 1);
// "Extra" height enlarges the result beyond the requested height,
so subtract it
bottom_height = len(offsets_bot)==0 ? 0 :
abs(last(offsets_bot)[1]) - struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) -
struct_val(top,"extra");
height = one_defined([l,h,height,length], "l,h,height,length",
dflt=u_add(bottom_height,top_height));
middle = height-bottom_height-top_height;
check =
assert(height>=0, "Height must be nonnegative")
assert(middle>=0, str(
"Specified end treatments (bottom height =
",bottom_height,
" top_height = ",top_height,") are too
large for extrusion height (",height,")"
)
);
// The entry r[i] is [radius,z] for a given layer
r = move([0,bottom_height],p=concat(
reverse(offsets_bot), [[0,0], [0,middle]],
move([0,middle], p=offsets_top)));
delta = [for(val=deltas(column(r,0))) sign(val)];
below=[-thickness,0];
above=[0,thickness];
// layers is a list of pairs of the relative positions for each
layer, e.g. [0,thickness]
// puts the layer above the polygon, and [-thickness,0] puts it
below.
layers = [for (i=[0:len(r)-1])
i==0 ? (delta[0]<0 ? below : above) :
i==len(r)-1 ? (delta[len(delta)-1] < 0 ? below : above) :
delta[i]==0 ? above :
delta[i+1]==0 ? below :
delta[i]==delta[i-1] ? [-thickness/2, thickness/2] :
delta[i] == 1 ? above :
/* delta[i] == -1 ? */ below];
dochamfer = offset=="chamfer";
attachable(){
for(i=[0:len(r)-2])
for(j=[0:$children-1]) {
run = r[i][0] - r[i+1][0];
rise = r[i+1][1] - r[i][1];
if (run) {
up(r[i][1]+layers[i][0])
roofrr(rise=rise, run=run, s=s, convexity=convexity)
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
}
else {
up(r[i][1]+layers[i][0])
linear_extrude(convexity=convexity,height=rise)
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
}
}
union();
}
}
It looks like in my previous reply to this thread something weird happened
with the concatenation of the previous post so I'm am reposting the latest
offset_exrude() without the message history.
module conditional_zflip_z(condition, z=0) {
if (condition)
scale([1,1,-1]) translate([0,0,-z]) children();
else children();
}
module conditional_offset(condition, delta) {
if (condition)
offset(delta=delta) children();
else children();
}
// generates an extrusion from z=0 cropped at z=rise.
// extrusion tapers inward with positive run and outward with negative run
module roofrr(rise=1, run=1, s=100, convexity=10) {
assert(rise != 0, "rise can not be zero.")
assert(s != 0, "s can not be zero.")
if (run) {
conditional_zflip_z(run<0, z=rise)
difference() {
scale([1,1,rise/abs(run)]) roof(convexity=convexity)
conditional_offset(run<0, delta=-run) children();
translate([0,0,rise>0 ? rise+s0.5 : rise-s0.5]) cube([s,s,s],
center=true);
}
}
else {
conditional_zflip_z(rise<0) linear_extrude(height=abs(rise),
convexity=convexity) children();
}
}
module offset_extrude(
height,
bottom=[],
top=[],
h, l, length,
offset="round", r=0, steps=16,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10, thickness = 1/1024, s=100
) {
req_children($children);
argspec = [
["for", ""],
["r",r],
["extra",extra],
["type","circle"],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
];
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
offsets_bot = _rounding_offsets(bottom, -1);
offsets_top = _rounding_offsets(top, 1);
// "Extra" height enlarges the result beyond the requested height, so
subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1]) -
struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) -
struct_val(top,"extra");
height = one_defined([l,h,height,length], "l,h,height,length",
dflt=u_add(bottom_height,top_height));
middle = height-bottom_height-top_height;
check =
assert(height>=0, "Height must be nonnegative")
assert(middle>=0, str(
"Specified end treatments (bottom height =
",bottom_height,
" top_height = ",top_height,") are too large
for extrusion height (",height,")"
)
);
// The entry r[i] is [radius,z] for a given layer
r = move([0,bottom_height],p=concat(
reverse(offsets_bot), [[0,0], [0,middle]],
move([0,middle], p=offsets_top)));
dochamfer = offset=="chamfer";
attachable(){
last = len(r)-2;
for(i=[0:last]) {
overlap = /* i<last ? thickness : */ 0;
for(j=[0:$children-1]) {
run = r[i][0] - r[i+1][0];
rise = r[i+1][1] - r[i][1];
up(r[i][1]) // +layers[i][0]
roofrr(rise=rise+overlap, run=run, s=s, convexity=convexity)
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
}
}
union();
}
}