discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

offset_extrude()

TA
Todd Allen
Sat, Sep 19, 2026 6:59 PM

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 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+s*0.5 : rise-s*0.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(); } }
TA
Todd Allen
Sat, Sep 19, 2026 11:41 PM

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();
}
}

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+s*0.5 : rise-s*0.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+s*0.5 : rise-s*0.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(); > } > } > >
TA
Todd Allen
Sat, Sep 19, 2026 11:47 PM

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();
}
}

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+s*0.5 : rise-s*0.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(); } }