Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of rewrote it into a
more generic solution by introducing a global variable $camfer and
camfer-enabled modules cube_(), cylinder_() and sphere_() that allow
to be scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r,
d=d, center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5], center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and the other
children are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1,
d=size2, center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1,
d=size2, center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)[1,1,1]],
[pos2, (1+2.0/size2)[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
hulljoin.png
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen speedebikes@gmail.com wrote:
Here's an example of how I sometimes chamfer joints using
hull(). I have found this useful but it would be more useful if
a similar method could be applied between parent and child
objects using BOSL2's positioning/alignment/attachment but I
don't yet see a good way and would love it if someone could help
me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of
the chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
])
{
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
--
This email has been checked for viruses by AVG antivirus software.
www.avg.com
The "trick" is definitely a must-have. However, from this point the
fillet() operator just takes a snap of the fingers.
You tell the Fillet() operator the # iterations and the limits ... and
you get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10, center=true);
translate([-15, 0, 0])translate(pos2) rotate(a=[20,20,0]) cube_([10,
5, 5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 12.12.2025 um 01:42 schrieb Jon Bondy:
Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of rewrote it into a
more generic solution by introducing a global variable $camfer and
camfer-enabled modules cube_(), cylinder_() and sphere_() that allow
to be scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r,
d=d, center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5], center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and the other
children are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1,
d=size2, center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1,
d=size2, center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)[1,1,1]],
[pos2, (1+2.0/size2)[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
hulljoin.png
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen speedebikes@gmail.com wrote:
Here's an example of how I sometimes chamfer joints using
hull(). I have found this useful but it would be more useful if
a similar method could be applied between parent and child
objects using BOSL2's positioning/alignment/attachment but I
don't yet see a good way and would love it if someone could help
me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of
the chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
])
{
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
Virus-free.www.avg.com
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
<#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
Some hints: As the approach is based on scale() and hull(), one should
be aware that
i) hull() enforces convexity and will not perform well (or at all) with
concave shapes. E.g. rings can be made to work, but need a primitive
definition that diametrically scales both diameters, and the second
shape mustn't cut it twice.
ii) The scaling center of a primitive is crucial for the mechanism to
achieve the desired result. The scale parameters x, y must be adopted to
the dimensional size of a primitive
But if used with care, the solution is really powerful.
Am 12.12.2025 um 12:11 schrieb Rudolf via Discuss:
The "trick" is definitely a must-have. However, from this point the
fillet() operator just takes a snap of the fingers.
You tell the Fillet() operator the # iterations and the limits ... and
you get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r,
d=d, center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10, center=true);
translate([-15, 0, 0])translate(pos2) rotate(a=[20,20,0])
cube_([10, 5, 5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Rudolf, big thanks for your interest in this technique and your offered
changes! I hope your enthusiasm is contagious and there will be more
contributions.
Your revision of the hull() and intersection() targets brings some clarity
and I think efficiency to the method. I like your idea to use a $ var to
enable scaled children which can eliminate my passing of scaling factors as
parameters to hulled_join() for a cleaner interface. I prefer scaling in a
single separate module instead of adding it to cube(), sphere(), etc. as
hulled_join() can be applied to children formed in any fashion including
polyhedron(), linear_extrude(), rotate_extrude(), etc. I think you lost
independent scaling factors for each child which gave control of the angle
of the join. I liked your addition of rounding by iterative proportional
adjustment of scaling but reworked it to retain independent scale factors
for each child. I also retained my “all” parameter enabling the
specification of forming joins between all children or just between the
first child and the other children. I adapted this new approach of scaling
children of hulled_join() to work with BOSL2 attachables, improving that
interface too.
[image: hjtest2.png]
include <BOSL2/std.scad>
$fn = 128;
// OpenSCAD hulled_join() examples
size1 = 20;
size2 = 10;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// round joints between sphere and other children
translate([0,1.5size1,0])
hulled_join(iterations=10) {
hj_scale(pos1, (1+2.0/size1)[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// chamfer all the joints
hulled_join(all=true) {
hj_scale(pos1, (1+2.0/size1)[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// BOSL2 hulled_join() example of:
//
https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#function-desc_dir
right(40) back(30)
prismoid(20,10,h=15)
attach(RIGHT,BOT) cuboid([4,4,15])
position(TOP) cyl(d=12,h=5,orient=desc_dir(),anchor=BACK);
// with hulled_join() joints
module body(ao=false) {prismoid(20,10,h=15, anchor=ao?ao:BOT) children();}
module arm(ao=false) {cuboid([4,4,20], anchor=ao?ao:CTR) children();}
module hand(ao=false) {cyl(d=12,h=5,orient=desc_dir(), anchor=ao?ao:BACK)
children();}
right(40)
body() let(body_desc=parent())
attach(RIGHT,BOT) arm() let(arm_desc=parent())
position(TOP) hand() let(hand_desc=parent())
restore() hulled_join(3) {
// arm first to allow default case of only computing chamfers between
first child and the other children
hja_scale(1.6*[1,1,1], arm_desc) arm(CTR);
hja_scale(1.2*[1,1,1], body_desc) body(CTR);
hja_scale(1.2*[1,1,1], hand_desc) hand(CTR);
};
//
// this code could be in a library
//
$hj_scale=0;
// to control scaling a child of hulled_join()
module hj_scale(center_point, scale_vector) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
// optimizable to a single translation?
translate(center_point) scale(sv) translate(-1*center_point)
children();
}
else children();
}
// to control scaling a BOSL2 attachable child of hulled_join()
module hja_scale(scale_vector, desc) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
restore(transform_desc(scale(sv, cp=desc_point(desc,anchor=CTR)),
desc)) children();
}
else restore(desc) children();
}
// hulled joining of children objects
// unions objects with a hull of those objects limited to intersections of
the objects with scaled partners
// adjust scale_vector of hj_scale() or hja_scale() in each child to change
the size and angle of joints
// all=true, joints between all children are chamfered
// all=false, only the joints between the 1st child and the other children
are chamfered
module hulled_join(iterations=1, all=false) {
union() {
children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0]) for(j=[i+1:1:$children-1])
hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 5:11 AM Rudolf via Discuss <
discuss@lists.openscad.org> wrote:
The "trick" is definitely a must-have. However, from this point the
fillet() operator just takes a snap of the fingers.
You tell the Fillet() operator the # iterations and the limits ... and you
get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10, center=true);
translate([-15, 0, 0])translate(pos2) rotate(a=[20,20,0]) cube_([10, 5,
5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder_(h=size1,
d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 12.12.2025 um 01:42 schrieb Jon Bondy:
Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of rewrote it into a more
generic solution by introducing a global variable $camfer and
camfer-enabled modules cube_(), cylinder_() and sphere_() that allow to be
scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5], center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the intersection
of the objects after scaling
// adjust object scale factors to change the size and angle of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and the other children
are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)[1,1,1]],
[pos2, (1+2.0/size2)[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
[image: hulljoin.png]
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen speedebikes@gmail.com wrote:
Here's an example of how I sometimes chamfer joints using hull(). I have
found this useful but it would be more useful if a similar method could be
applied between parent and child objects using BOSL2's
positioning/alignment/attachment but I don't yet see a good way and would
love it if someone could help me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of the chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]]
])
{
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
Virus-free.www.avg.com
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
<#m_-1384773676116772327_DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
In the previous code I posted for hulled_join() the BOSL2 example would
union an additional unneeded copy of children(). Here's a fix, though one
will then need to pass children=true for non BOSL2 attachables cases.
module hulled_join(iterations=1, all=false, children=false) {
union() {
if (children) children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0]) for(j=[i+1:1:$children-1])
hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 12:16 PM Todd Allen speedebikes@gmail.com wrote:
Rudolf, big thanks for your interest in this technique and your offered
changes! I hope your enthusiasm is contagious and there will be more
contributions.
Your revision of the hull() and intersection() targets brings some clarity
and I think efficiency to the method. I like your idea to use a $ var to
enable scaled children which can eliminate my passing of scaling factors as
parameters to hulled_join() for a cleaner interface. I prefer scaling in a
single separate module instead of adding it to cube(), sphere(), etc. as
hulled_join() can be applied to children formed in any fashion including
polyhedron(), linear_extrude(), rotate_extrude(), etc. I think you lost
independent scaling factors for each child which gave control of the angle
of the join. I liked your addition of rounding by iterative proportional
adjustment of scaling but reworked it to retain independent scale factors
for each child. I also retained my “all” parameter enabling the
specification of forming joins between all children or just between the
first child and the other children. I adapted this new approach of scaling
children of hulled_join() to work with BOSL2 attachables, improving that
interface too.
[image: hjtest2.png]
include <BOSL2/std.scad>
$fn = 128;
// OpenSCAD hulled_join() examples
size1 = 20;
size2 = 10;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// round joints between sphere and other children
translate([0,1.5size1,0])
hulled_join(iterations=10) {
hj_scale(pos1, (1+2.0/size1)[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// chamfer all the joints
hulled_join(all=true) {
hj_scale(pos1, (1+2.0/size1)[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// BOSL2 hulled_join() example of:
//
https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#function-desc_dir
right(40) back(30)
prismoid(20,10,h=15)
attach(RIGHT,BOT) cuboid([4,4,15])
position(TOP) cyl(d=12,h=5,orient=desc_dir(),anchor=BACK);
// with hulled_join() joints
module body(ao=false) {prismoid(20,10,h=15, anchor=ao?ao:BOT) children();}
module arm(ao=false) {cuboid([4,4,20], anchor=ao?ao:CTR) children();}
module hand(ao=false) {cyl(d=12,h=5,orient=desc_dir(), anchor=ao?ao:BACK)
children();}
right(40)
body() let(body_desc=parent())
attach(RIGHT,BOT) arm() let(arm_desc=parent())
position(TOP) hand() let(hand_desc=parent())
restore() hulled_join(3) {
// arm first to allow default case of only computing chamfers between
first child and the other children
hja_scale(1.6*[1,1,1], arm_desc) arm(CTR);
hja_scale(1.2*[1,1,1], body_desc) body(CTR);
hja_scale(1.2*[1,1,1], hand_desc) hand(CTR);
};
//
// this code could be in a library
//
$hj_scale=0;
// to control scaling a child of hulled_join()
module hj_scale(center_point, scale_vector) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
// optimizable to a single translation?
translate(center_point) scale(sv) translate(-1*center_point)
children();
}
else children();
}
// to control scaling a BOSL2 attachable child of hulled_join()
module hja_scale(scale_vector, desc) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
restore(transform_desc(scale(sv, cp=desc_point(desc,anchor=CTR)),
desc)) children();
}
else restore(desc) children();
}
// hulled joining of children objects
// unions objects with a hull of those objects limited to intersections of
the objects with scaled partners
// adjust scale_vector of hj_scale() or hja_scale() in each child to
change the size and angle of joints
// all=true, joints between all children are chamfered
// all=false, only the joints between the 1st child and the other children
are chamfered
module hulled_join(iterations=1, all=false) {
union() {
children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0]) for(j=[i+1:1:$children-1])
hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 5:11 AM Rudolf via Discuss <
discuss@lists.openscad.org> wrote:
The "trick" is definitely a must-have. However, from this point the
fillet() operator just takes a snap of the fingers.
You tell the Fillet() operator the # iterations and the limits ... and
you get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10, center=true);
translate([-15, 0, 0])translate(pos2) rotate(a=[20,20,0]) cube_([10,
5, 5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 12.12.2025 um 01:42 schrieb Jon Bondy:
Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of rewrote it into a more
generic solution by introducing a global variable $camfer and
camfer-enabled modules cube_(), cylinder_() and sphere_() that allow to be
scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5], center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and the other children
are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)[1,1,1]],
[pos2, (1+2.0/size2)[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
[image: hulljoin.png]
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen speedebikes@gmail.com wrote:
Here's an example of how I sometimes chamfer joints using hull(). I
have found this useful but it would be more useful if a similar method
could be applied between parent and child objects using BOSL2's
positioning/alignment/attachment but I don't yet see a good way and would
love it if someone could help me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of the
chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]]
])
{
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
Virus-free.www.avg.com
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
<#m_-3373310449440574214_m_-1384773676116772327_DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
Todd,
the revision I made fits well into my libraries in which I redefine the
OpenSCAD primitives (and a lot more primitives) anyway.
Fillet() offers only two limit parameters which it applies to all
operand pairings given. That's right. But if one wants to define limit
parameters in a pairwise (or subset wise) fashion, syntax is his friend:
He can use a nested call pattern and gain maximal control:
Fillet(x0, y0, iter0)
{
elem0();
Fillet(x1, y1, iter1)
{
elem1();
elem2();
}
}
So, the lack of control argument doesn't hold. I think the main
limitation of the approach is "concavity". However it definitely
provides a valuable solution for a wide range of situations, even
scaling a primitive and finding the right limit parameters can be
tricky. What I love is that the approach handles overlapping fillets
like a charm. I already used it twice for prints I wouldn't have added
fillets today.
Am 12.12.2025 um 22:43 schrieb Todd Allen via Discuss:
In the previous code I posted for hulled_join() the BOSL2 example
would union an additional unneeded copy of children(). Here's a fix,
though one will then need to pass children=true for non BOSL2
attachables cases.
module hulled_join(iterations=1, all=false, children=false) {
union() {
if (children) children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0]) for(j=[i+1:1:$children-1])
hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 12:16 PM Todd Allen speedebikes@gmail.com wrote:
Rudolf, big thanks for your interest in this technique and your
offered changes! I hope your enthusiasm is contagious and there
will be more contributions.
Your revision of the hull() and intersection() targets brings some
clarity and I think efficiency to the method. I like your idea to
use a $ var to enable scaled children which can eliminate my
passing of scaling factors as parameters to hulled_join() for a
cleaner interface. I prefer scaling in a single separate module
instead of adding it to cube(), sphere(), etc. as hulled_join()
can be applied to children formed in any fashion including
polyhedron(), linear_extrude(), rotate_extrude(), etc. I think
you lost independent scaling factors for each child which gave
control of the angle of the join. I liked your addition of
rounding by iterative proportional adjustment of scaling but
reworked it to retain independent scale factors for each child. I
also retained my “all” parameter enabling the specification of
forming joins between all children or just between the first child
and the other children. I adapted this new approach of scaling
children of hulled_join() to work with BOSL2 attachables,
improving that interface too.
hjtest2.png
include <BOSL2/std.scad>
$fn = 128;
// OpenSCAD hulled_join() examples
size1 = 20;
size2 = 10;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// round joints between sphere and other children
translate([0,1.5*size1,0])
hulled_join(iterations=10) {
hj_scale(pos1, (1+2.0/size1)*[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// chamfer all the joints
hulled_join(all=true) {
hj_scale(pos1, (1+2.0/size1)*[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// BOSL2 hulled_join() example of:
//
https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#function-desc_dir
right(40) back(30)
prismoid(20,10,h=15)
attach(RIGHT,BOT) cuboid([4,4,15])
position(TOP) cyl(d=12,h=5,orient=desc_dir(),anchor=BACK);
// with hulled_join() joints
module body(ao=false) {prismoid(20,10,h=15, anchor=ao?ao:BOT)
children();}
module arm(ao=false) {cuboid([4,4,20], anchor=ao?ao:CTR) children();}
module hand(ao=false) {cyl(d=12,h=5,orient=desc_dir(),
anchor=ao?ao:BACK) children();}
right(40)
body() let(body_desc=parent())
attach(RIGHT,BOT) arm() let(arm_desc=parent())
position(TOP) hand() let(hand_desc=parent())
restore() hulled_join(3) {
// arm first to allow default case of only computing chamfers
between first child and the other children
hja_scale(1.6*[1,1,1], arm_desc) arm(CTR);
hja_scale(1.2*[1,1,1], body_desc) body(CTR);
hja_scale(1.2*[1,1,1], hand_desc) hand(CTR);
};
//
// this code could be in a library
//
$hj_scale=0;
// to control scaling a child of hulled_join()
module hj_scale(center_point, scale_vector) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
// optimizable to a single translation?
translate(center_point) scale(sv)
translate(-1*center_point) children();
}
else children();
}
// to control scaling a BOSL2 attachable child of hulled_join()
module hja_scale(scale_vector, desc) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
restore(transform_desc(scale(sv,
cp=desc_point(desc,anchor=CTR)), desc)) children();
}
else restore(desc) children();
}
// hulled joining of children objects
// unions objects with a hull of those objects limited to
intersections of the objects with scaled partners
// adjust scale_vector of hj_scale() or hja_scale() in each child
to change the size and angle of joints
// all=true, joints between all children are chamfered
// all=false, only the joints between the 1st child and the other
children are chamfered
module hulled_join(iterations=1, all=false) {
union() {
children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1]) hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 5:11 AM Rudolf via Discuss
<discuss@lists.openscad.org> wrote:
The "trick" is definitely a must-have. However, from this
point the fillet() operator just takes a snap of the fingers.
You tell the Fillet() operator the # iterations and the limits
... and you get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h,
r=r, d=d, center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10, center=true);
translate([-15, 0, 0])translate(pos2) rotate(a=[20,20,0])
cube_([10, 5, 5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 12.12.2025 um 01:42 schrieb Jon Bondy:
Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of rewrote it
into a more generic solution by introducing a global
variable $camfer and camfer-enabled modules cube_(),
cylinder_() and sphere_() that allow to be scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center)
scale($camfer)cylinder(h=h, r=r, d=d, center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5],
center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to
the intersection of the objects after scaling
// adjust object scale factors to change the size and angle
of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and the
other children are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)*[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1,
d=size2, center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1,
d=size2, center=true);
}
hulljoin.png
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen
<speedebikes@gmail.com> wrote:
Here's an example of how I sometimes
chamfer joints using hull(). I have found this useful
but it would be more useful if a similar method could
be applied between parent and child objects using
BOSL2's positioning/alignment/attachment but I don't
yet see a good way and would love it if someone could
help me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited
to the intersection of the objects after scaling
// adjust object scale factors to change the size and
angle of the chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
])
{
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
<http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient>
Virus-free.www.avg.com
<http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient>
<#m_-3373310449440574214_m_-1384773676116772327_DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
if one wants to define limit parameters in a pairwise (or subset wise)
fashion, syntax is his friend: He can use a nested call pattern and gain
maximal control
Nesting calls is semantically different and increases computation.
So, the lack of control argument doesn't hold. … finding the right limit
parameters can be tricky.
When there was a scaling vector associated with each child, if say the
child was a cylinder one could make a join that would extend 1 mm from the
radius and 2 mm from the ends. Or if it was a cube of non uniform
dimensions one could make the join extend uniformly in width, depth and
height. You named your scalars X & Y despite them having nothing to do
with a coordinate system nor even being orthogonal controls and made it
tricky to find the right limits.
I think the main limitation of the approach is "concavity"
Which the name hulled_join() conveyed. It’s a bigger problem when you
name the operation fillet() which also confuses that the operation is
useful for chamfers.
On Fri, Dec 12, 2025 at 7:41 PM Rudolf via Discuss <
discuss@lists.openscad.org> wrote:
Todd,
the revision I made fits well into my libraries in which I redefine the
OpenSCAD primitives (and a lot more primitives) anyway.
Fillet() offers only two limit parameters which it applies to all operand
pairings given. That's right. But if one wants to define limit parameters
in a pairwise (or subset wise) fashion, syntax is his friend: He can use a
nested call pattern and gain maximal control:
Fillet(x0, y0, iter0)
{
elem0();
Fillet(x1, y1, iter1)
{
elem1();
elem2();
}
}
So, the lack of control argument doesn't hold. I think the main limitation
of the approach is "concavity". However it definitely provides a valuable
solution for a wide range of situations, even scaling a primitive and
finding the right limit parameters can be tricky. What I love is that the
approach handles overlapping fillets like a charm. I already used it twice
for prints I wouldn't have added fillets today.
Am 12.12.2025 um 22:43 schrieb Todd Allen via Discuss:
In the previous code I posted for hulled_join() the BOSL2 example would
union an additional unneeded copy of children(). Here's a fix, though one
will then need to pass children=true for non BOSL2 attachables cases.
module hulled_join(iterations=1, all=false, children=false) {
union() {
if (children) children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0]) for(j=[i+1:1:$children-1])
hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 12:16 PM Todd Allen speedebikes@gmail.com wrote:
Rudolf, big thanks for your interest in this technique and your offered
changes! I hope your enthusiasm is contagious and there will be more
contributions.
Your revision of the hull() and intersection() targets brings some
clarity and I think efficiency to the method. I like your idea to use a $
var to enable scaled children which can eliminate my passing of scaling
factors as parameters to hulled_join() for a cleaner interface. I prefer
scaling in a single separate module instead of adding it to cube(),
sphere(), etc. as hulled_join() can be applied to children formed in any
fashion including polyhedron(), linear_extrude(), rotate_extrude(), etc.
I think you lost independent scaling factors for each child which gave
control of the angle of the join. I liked your addition of rounding by
iterative proportional adjustment of scaling but reworked it to retain
independent scale factors for each child. I also retained my “all”
parameter enabling the specification of forming joins between all children
or just between the first child and the other children. I adapted this new
approach of scaling children of hulled_join() to work with BOSL2
attachables, improving that interface too.
[image: hjtest2.png]
include <BOSL2/std.scad>
$fn = 128;
// OpenSCAD hulled_join() examples
size1 = 20;
size2 = 10;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// round joints between sphere and other children
translate([0,1.5size1,0])
hulled_join(iterations=10) {
hj_scale(pos1, (1+2.0/size1)[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// chamfer all the joints
hulled_join(all=true) {
hj_scale(pos1, (1+2.0/size1)[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// BOSL2 hulled_join() example of:
//
https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#function-desc_dir
right(40) back(30)
prismoid(20,10,h=15)
attach(RIGHT,BOT) cuboid([4,4,15])
position(TOP) cyl(d=12,h=5,orient=desc_dir(),anchor=BACK);
// with hulled_join() joints
module body(ao=false) {prismoid(20,10,h=15, anchor=ao?ao:BOT) children();}
module arm(ao=false) {cuboid([4,4,20], anchor=ao?ao:CTR) children();}
module hand(ao=false) {cyl(d=12,h=5,orient=desc_dir(), anchor=ao?ao:BACK)
children();}
right(40)
body() let(body_desc=parent())
attach(RIGHT,BOT) arm() let(arm_desc=parent())
position(TOP) hand() let(hand_desc=parent())
restore() hulled_join(3) {
// arm first to allow default case of only computing chamfers between
first child and the other children
hja_scale(1.6*[1,1,1], arm_desc) arm(CTR);
hja_scale(1.2*[1,1,1], body_desc) body(CTR);
hja_scale(1.2*[1,1,1], hand_desc) hand(CTR);
};
//
// this code could be in a library
//
$hj_scale=0;
// to control scaling a child of hulled_join()
module hj_scale(center_point, scale_vector) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
// optimizable to a single translation?
translate(center_point) scale(sv) translate(-1*center_point)
children();
}
else children();
}
// to control scaling a BOSL2 attachable child of hulled_join()
module hja_scale(scale_vector, desc) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
restore(transform_desc(scale(sv, cp=desc_point(desc,anchor=CTR)),
desc)) children();
}
else restore(desc) children();
}
// hulled joining of children objects
// unions objects with a hull of those objects limited to intersections
of the objects with scaled partners
// adjust scale_vector of hj_scale() or hja_scale() in each child to
change the size and angle of joints
// all=true, joints between all children are chamfered
// all=false, only the joints between the 1st child and the other
children are chamfered
module hulled_join(iterations=1, all=false) {
union() {
children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0]) for(j=[i+1:1:$children-1])
hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i, $hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 5:11 AM Rudolf via Discuss <
discuss@lists.openscad.org> wrote:
The "trick" is definitely a must-have. However, from this point the
fillet() operator just takes a snap of the fingers.
You tell the Fillet() operator the # iterations and the limits ... and
you get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10, center=true);
translate([-15, 0, 0])translate(pos2) rotate(a=[20,20,0]) cube_([10,
5, 5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 12.12.2025 um 01:42 schrieb Jon Bondy:
Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of rewrote it into a more
generic solution by introducing a global variable $camfer and
camfer-enabled modules cube_(), cylinder_() and sphere_() that allow to be
scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($camfer)cylinder(h=h, r=r, d=d,
center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5], center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and the other
children are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)[1,1,1]],
[pos2, (1+2.0/size2)[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1]) cylinder(h=size1, d=size2,
center=true);
}
[image: hulljoin.png]
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen speedebikes@gmail.com wrote:
Here's an example of how I sometimes chamfer joints using hull(). I
have found this useful but it would be more useful if a similar method
could be applied between parent and child objects using BOSL2's
positioning/alignment/attachment but I don't yet see a good way and would
love it if someone could help me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the
intersection of the objects after scaling
// adjust object scale factors to change the size and angle of the
chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]]
])
{
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
Virus-free.www.avg.com
http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient
<#m_5948437202312854252_m_-3373310449440574214_m_-1384773676116772327_DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
Am 13.12.2025 um 17:01 schrieb Todd Allen:
Nesting calls is semantically different and increases computation.
I am not convinced of that.
So, the lack of control argument doesn't hold. … finding the right
limit parameters can be tricky.
When there was a scaling vector associated with each child, if say the
child was a cylinder one could make a join that would extend 1 mm from
the radius and 2 mm from the ends. Or if it was a cube of non uniform
dimensions one could make the join extend uniformly in width, depth
and height. You named your scalars X & Y despite them having nothing
to do with a coordinate system nor even being orthogonal controls and
made it tricky to find the right limits.
I admit, my naming is not the best, but, hey, I posted my unpolished
test code. In production code I usually write something like this:
module Fillet(scale=[1.5, 1.5], iterations=30)
{
scale = is_list(scale)?scale:[scale, scale];
assert(iterations > 0 && iterations<=30, "enforcing 0 < iterations
<=30");
...
for(k=[0:iterations]) // fillet iteration
I think the main limitation of the approach is "concavity"
Which the name hulled_join() conveyed. It’s a bigger problem when
you name the operation fillet() which also confuses that the operation
is useful for chamfers.
(Very) Well-behaved concave objects do work, as the following code shows
(right object). And: a chamfer is just a special case of a fillet.
iterations=1 produces a chamfer, iterations=10 or so a fillet (left
object).
$fn=100;
$chamfer=1;
module Fillet(scale=[1.5, 1.5], iterations=30)
{
scale = is_list(scale)?scale:[scale, scale];
assert(iterations > 0 && iterations<=30, "enforcing 0 < iterations
<=30");
for(i=[0:$children-1]) children(i); // all children
for(k=[0:iterations]) // fillet iteration
{
x_ = 1+k*(scale[0]-1)/(iterations+1);
y_ = scale[1]-k*(scale[1]-1)/(iterations+1);
for(i=[0:$children-1], j=[0:$children-1]) // chamfers
if(i<j) hull()
{
intersection() {children(i); children(j, $chamfer=x_); }
intersection() {children(i, $chamfer=y_); children(j); }
}
}
}
module cube_(size, center) scale($chamfer)cube(size, center);
module sphere_(r, d) scale($chamfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center) scale($chamfer)cylinder(h=h, r=r, d=d,
center=center);
translate([0, -14, 0])Fillet([1.5, 1.2], 1) // nested call
{
Fillet([1.5, 1.2], 10)
{
sphere_(d = 10);
translate([3, 0, 0])cylinder_(d = 3, h=10, true);
}
translate([3, 0, 0])rotate([90, 0, 0]) cylinder_(d = 3, h=10, true);
}
Fillet([2, 4], 2) // concave shape, very well-behaved case.
{
concave_shape_(); // concave shape must be first child
translate([0, 0, 2])rotate([0,90,0])cylinder_(r=1.5, h=10,
center=false);
translate([0, 0, -3])rotate([0,90,0])cylinder_(r=1.5, h=10,
center=false);
}
module concave_shape_()
scale($chamfer)
{
difference()
{
cube([6, 12, 12], center=true);
translate([10, 0, 0])sphere(9);
}
}
On Fri, Dec 12, 2025 at 7:41 PM Rudolf via Discuss
discuss@lists.openscad.org wrote:
Todd,
the revision I made fits well into my libraries in which I
redefine the OpenSCAD primitives (and a lot more primitives) anyway.
Fillet() offers only two limit parameters which it applies to all
operand pairings given. That's right. But if one wants to define
limit parameters in a pairwise (or subset wise) fashion, syntax is
his friend: He can use a nested call pattern and gain maximal
control:
Fillet(x0, y0, iter0)
{
elem0();
Fillet(x1, y1, iter1)
{
elem1();
elem2();
}
}
So, the lack of control argument doesn't hold. I think the main
limitation of the approach is "concavity". However it definitely
provides a valuable solution for a wide range of situations, even
scaling a primitive and finding the right limit parameters can be
tricky. What I love is that the approach handles overlapping
fillets like a charm. I already used it twice for prints I
wouldn't have added fillets today.
Am 12.12.2025 um 22:43 schrieb Todd Allen via Discuss:
In the previous code I posted for hulled_join() the BOSL2 example
would union an additional unneeded copy of children(). Here's a
fix, though one will then need to pass children=true for non
BOSL2 attachables cases.
module hulled_join(iterations=1, all=false, children=false) {
union() {
if (children) children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1]) hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i,
$hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 12:16 PM Todd Allen
<speedebikes@gmail.com> wrote:
Rudolf, big thanks for your interest in this technique and
your offered changes! I hope your enthusiasm is contagious
and there will be more contributions.
Your revision of the hull() and intersection() targets brings
some clarity and I think efficiency to the method. I like
your idea to use a $ var to enable scaled children which can
eliminate my passing of scaling factors as parameters to
hulled_join() for a cleaner interface. I prefer scaling in a
single separate module instead of adding it to cube(),
sphere(), etc. as hulled_join() can be applied to children
formed in any fashion including polyhedron(),
linear_extrude(), rotate_extrude(), etc. I think you lost
independent scaling factors for each child which gave control
of the angle of the join. I liked your addition of rounding
by iterative proportional adjustment of scaling but reworked
it to retain independent scale factors for each child. I
also retained my “all” parameter enabling the specification
of forming joins between all children or just between the
first child and the other children. I adapted this new
approach of scaling children of hulled_join() to work with
BOSL2 attachables, improving that interface too.
hjtest2.png
include <BOSL2/std.scad>
$fn = 128;
// OpenSCAD hulled_join() examples
size1 = 20;
size2 = 10;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// round joints between sphere and other children
translate([0,1.5*size1,0])
hulled_join(iterations=10) {
hj_scale(pos1, (1+2.0/size1)*[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// chamfer all the joints
hulled_join(all=true) {
hj_scale(pos1, (1+2.0/size1)*[1,1,1])
translate(pos1) sphere(d=size1);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
hj_scale(pos2, (1+1.0/size2)*[1,1,1])
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// BOSL2 hulled_join() example of:
//
https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#function-desc_dir
right(40) back(30)
prismoid(20,10,h=15)
attach(RIGHT,BOT) cuboid([4,4,15])
position(TOP) cyl(d=12,h=5,orient=desc_dir(),anchor=BACK);
// with hulled_join() joints
module body(ao=false) {prismoid(20,10,h=15, anchor=ao?ao:BOT)
children();}
module arm(ao=false) {cuboid([4,4,20], anchor=ao?ao:CTR)
children();}
module hand(ao=false) {cyl(d=12,h=5,orient=desc_dir(),
anchor=ao?ao:BACK) children();}
right(40)
body() let(body_desc=parent())
attach(RIGHT,BOT) arm() let(arm_desc=parent())
position(TOP) hand() let(hand_desc=parent())
restore() hulled_join(3) {
// arm first to allow default case of only computing
chamfers between first child and the other children
hja_scale(1.6*[1,1,1], arm_desc) arm(CTR);
hja_scale(1.2*[1,1,1], body_desc) body(CTR);
hja_scale(1.2*[1,1,1], hand_desc) hand(CTR);
};
//
// this code could be in a library
//
$hj_scale=0;
// to control scaling a child of hulled_join()
module hj_scale(center_point, scale_vector) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
// optimizable to a single translation?
translate(center_point) scale(sv)
translate(-1*center_point) children();
}
else children();
}
// to control scaling a BOSL2 attachable child of hulled_join()
module hja_scale(scale_vector, desc) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
restore(transform_desc(scale(sv,
cp=desc_point(desc,anchor=CTR)), desc)) children();
}
else restore(desc) children();
}
// hulled joining of children objects
// unions objects with a hull of those objects limited to
intersections of the objects with scaled partners
// adjust scale_vector of hj_scale() or hja_scale() in each
child to change the size and angle of joints
// all=true, joints between all children are chamfered
// all=false, only the joints between the 1st child and the
other children are chamfered
module hulled_join(iterations=1, all=false) {
union() {
children();
for(si=[0:1:iterations-1]) {
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1]) hull() {
intersection() {
children(i, $hj_scale=0);
children(j, $hj_scale=(si+1)/iterations);
}
intersection() {
children(i,
$hj_scale=(iterations-si)/iterations);
children(j, $hj_scale=0);
}
}
}
}
}
On Fri, Dec 12, 2025 at 5:11 AM Rudolf via Discuss
<discuss@lists.openscad.org> wrote:
The "trick" is definitely a must-have. However, from this
point the fillet() operator just takes a snap of the
fingers.
You tell the Fillet() operator the # iterations and the
limits ... and you get this: I am thrilled!
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
module Fillet(x=1.5, y=1.5, iterations=10)
{
for(i=[0:$children-1]) children(i);
for(k=[0:iterations-1]) // fillet iteration
{
x_ = 1+ k*(x-1)/iterations;
y_ = y - k*(y-1)/iterations;
for(i=[0:$children-1], j=[0:$children-1]) // Chamfer
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x_);
}
intersection()
{
children(i, $camfer=y_);
children(j);
}
}
}
}
module cube_(size, center) scale($camfer)cube(size, center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center)
scale($camfer)cylinder(h=h, r=r, d=d, center=center);
Fillet()
{
translate([-15, 0, 0])translate(pos1) cube_(10,
center=true);
translate([-15, 0, 0])translate(pos2)
rotate(a=[20,20,0]) cube_([10, 5, 5], center=true);
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder_(h=size1, d=size2, center=false);
translate(pos2) rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder_(h=size1, d=size2, center=true);
}
Am 12.12.2025 um 01:42 schrieb Jon Bondy:
Thank you! I tossed that into my bag of tricks!
On 12/11/2025 7:00 PM, Rudolf via Discuss wrote:
I like the idea behind your approach and kind of
rewrote it into a more generic solution by introducing
a global variable $camfer and camfer-enabled modules
cube_(), cylinder_() and sphere_() that allow to be
scaled by $camfer.
$fn=100;
$camfer=1;
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
module camfer(x=1.2)
{
for(i=[0:$children-1])
children(i);
for(i=[0:$children-1], j=[0:$children-1])
if(i<j)
hull()
{
intersection()
{
children(i);
children(j, $camfer=x);
}
intersection()
{
children(i, $camfer=x);
children(j);
}
}
}
module cube_(size, center) scale($camfer)cube(size,
center);
module sphere_(r, d) scale($camfer)sphere(r=r, d=d);
module cylinder_(h, r, d, center)
scale($camfer)cylinder(h=h, r=r, d=d, center=center);
camfer()
{
translate(pos1) cube_(10, center=true);
translate(pos2) rotate(a=[20,20,0]) cube_([6, 5, 5],
center=true);
}
translate([20, 0, 0])
camfer()
{
translate(pos1) sphere_(d=size1);
translate(pos2) rotate(a=[90,20,0])
scale([1,0.5,1]) cylinder_(h=size1, d=size2, center=true);
translate(pos2) rotate(a=[90,20,90])
scale([0.5,1,1]) cylinder_(h=size1, d=size2, center=true);
}
Am 08.12.2025 um 00:03 schrieb Todd Allen via Discuss:
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited
to the intersection of the objects after scaling
// adjust object scale factors to change the size and
angle of chamfers
// all=true then joints between all children are chamfered
// all=false only the joints between the 1st child and
the other children are chamfered
module hulled_join(centered_scales, all=false) {
union() {
children();
for(i=[0:1:all?$children-2:0])
for(j=[i+1:1:$children-1])
hull() intersection() {
centered_scale(centered_scales[i][0],
centered_scales[i][1]) children(i);
centered_scale(centered_scales[j][0],
centered_scales[j][1]) children(j);
union() {
children(i);
children(j);
}
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// EXAMPLE 1
translate([-size1,0,0])
hulled_join([
[pos1, (1+2.0/size1)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
// EXAMPLE 2
translate([size1,0,0])
hulled_join([
[pos1, (1+1.0/size1)*[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]],
[pos2, (1+2.0/size2)*[1,1,1]]
], all=true, $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1])
cylinder(h=size1, d=size2, center=true);
translate(pos2)
rotate(a=[90,20,90]) scale([0.5,1,1])
cylinder(h=size1, d=size2, center=true);
}
hulljoin.png
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen
<speedebikes@gmail.com> wrote:
Here's an example of how I sometimes
chamfer joints using hull(). I have found this
useful but it would be more useful if a similar
method could be applied between parent and child
objects using BOSL2's
positioning/alignment/attachment but I don't yet
see a good way and would love it if someone could
help me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects
limited to the intersection of the objects after
scaling
// adjust object scale factors to change the size
and angle of the chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size1*0.5+size2*0.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)*[1,1,1]],
[pos2, (1+1.0/size2)*[1,1,1]]
])
{
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
<http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient>
Virus-free.www.avg.com
<http://www.avg.com/email-signature?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient>
<#m_5948437202312854252_m_-3373310449440574214_m_-1384773676116772327_DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email to
discuss-leave@lists.openscad.org
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
_______________________________________________
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
Here is a revised hull_join.scad that works better with BOSL2 along with
some examples both without and with BOSL2 of how it can be used. It’s
better than I expected but will appreciate any thoughts for further
improvements.
corner.scad: At back right an even chamfer, in the middle a second join
with different scaling adds a shaped chamfer for an inner gusset, at the
front left iterating on the second join for a rounded gusset.
[image: corner.png]
stool.scad: Separate hull_join() operations for the leg to seat joint and
the leg to legbrace joint allow different scale parameters to be used for
different joint shapes. Although the legbrace is concave by selecting a
center point of scaling at the center of the intersection between the leg
and legbrace a scale vector is calculated to produce a precisely sized and
shaped joint.
[image: stool.png]
pod.scad: A BOSL2 example
[image: pod.png]
siblings.scad: A BOSL2 example using parent() and restore() to join
siblings in a BOSL2 attachment hierarchy. It demonstrates making uniform
joins of specified size across dimensions in which those objects are not
uniform.
[image: siblings.png]
// ****************************** hull_join.scad
/*
Hulled joining of children objects
Hulls child pairs limited to intersections of each child with scaled partner
Each child object needs hj_scale(scale_vector, center_point=[0,0,0])
or hjt_scale() as a parent to control its scaling for the hull_join()
operation.
Scale vector and optional center point of scaling passed to
hj_scale() or hjt_scale() of each child control the size and angle of
joints.
When used with BOSL2:
After an object producing module invocation to which we want to apply
hull_join()
use let(child_t=transform()) to capture transformations produced by
parameters such
as anchor, spin and orient. When reproducing this object as a child of
hull_join()
suppress transformation generating parameters as they will be applied in
hjt_scale()
by passing it the captured transformation matrix as shown in the examples.
pairs = specifies which pairs of children to join
"chain" : [0,1], [1,2], [2,3], ... [n-2, n-1]
"loop" : [0,1], [1,2], [2,3], ... [n-2, n-1], [n-1, 0]
"first" : [0,1], [0,2], [0,3], ... [0, n-1]
"all" : all pairs
*/
$hj_scale=0;
function get_pairs_chain(n) = [for(i=[0:n-2]) [i,i+1]];
function get_pairs_loop(n) = [for(i=[0:n-2]) [i,i+1], [n-1,0]];
function get_pairs_first(n) = [for(i=[1:n-1]) [0,i]];
function get_pairs_all(n) = [for(i=[0:n-2]) for(j=[i+1:n-1]) [i,j]];
module hull_join(iterations=1, pairs="chain", children=false) {
assert($children > 1, "hull_join() requires 2 or more children");
union() {
if(children)
children();
pairlist =
pairs=="chain" ? get_pairs_chain($children) :
pairs=="loop" ? get_pairs_loop($children) :
pairs=="first" ? get_pairs_first($children) :
pairs=="all" ? get_pairs_all($children) :
assert(false,"hulled_join(): invalid pairs parameter.");
for(si=[0:1:iterations-1]) for(pair = pairlist) hull() {
intersection() {
children(pair[0], $hj_scale=0);
children(pair[1], $hj_scale=(si+1)/iterations);
}
intersection() {
children(pair[0], $hj_scale=(iterations-si)/iterations);
children(pair[1], $hj_scale=0);
}
}
}
}
// returns a scale vector to resize something of a given size by an offset
// assumes scaling about a center point, thus 2.0offset to offset in each
direction
function hj_get_scale_for_offset(size, offset) = [
1.0 + 2.0offset.x/size.x,
1.0 + 2.0offset.y/size.y,
1.0 + 2.0offset.z/size.z
];
// controlled scaling about a center point of a child of hulled_join()
module hj_scale(scale_vector, center_point=[0,0,0]) {
if ($hj_scale) {
sv = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
if(center_point.x || center_point.y || center_point.z)
// optimizable to a single translation?
translate(center_point) scale(sv) translate(-1*center_point)
children();
else
scale(sv) children();
}
else children();
}
function transform() = $transform;
// controlled scaling about a center point of a BOSL2 child of hulled_join()
module hjt_scale(transform, scale_vector, center_point=[0,0,0]) {
if ($hj_scale) {
s = [
1+$hj_scale*(scale_vector.x-1),
1+$hj_scale*(scale_vector.y-1),
1+$hj_scale*(scale_vector.z-1)
];
scaled = (center_point.x || center_point.y || center_point.z) ?
transform * move(center_point) * scale(s) * move(-center_point)
:
transform * scale(s);
multmatrix(scaled) children();
}
else multmatrix(transform) children();
}
// ****************************** corner.scad
include <hull_join.scad>
radius = 2;
length = 20;
rod_cp = [0, 0, length*0.5];
a1 = 30;
a2 = 90;
module rod() {cylinder(h=length, r=radius, $fn=32);}
module yrot(angle) {rotate(a=angle, v=[0,1,0]) children();}
translate([8,8,0])
// join with an even chamfer
hull_join(children=true) {
yrot(a1) hj_scale([1.5, 1.5, 2.0], rod_cp) rod();
yrot(a2) hj_scale([1.5, 1.5, 2.0], rod_cp) rod();
}
union() {
// join with an even chamfer
hull_join(children=true) {
yrot(a1) hj_scale([1.5, 1.5, 2.0], rod_cp) rod();
yrot(a2) hj_scale([1.5, 1.5, 2.0], rod_cp) rod();
}
// add a shaped inner gusset
hull_join() {
yrot(a1) hj_scale([2.25, 1.5, 1.0], rod_cp) rod();
yrot(a2) hj_scale([4.0 ,0.6, 1.0], rod_cp) rod();
}
}
translate([-8,-8,0]) union() {
// join with an even chamfer
hull_join(children=true) {
yrot(a1) hj_scale([1.5, 1.5, 2.0], rod_cp) rod();
yrot(a2) hj_scale([1.5, 1.5, 2.0], rod_cp) rod();
}
// add a shaped inner gusset
hull_join(32) {
yrot(a1) hj_scale([2.25, 1.5, 1.0], rod_cp) rod();
yrot(a2) hj_scale([4.0 ,0.6, 1.0], rod_cp) rod();
}
}
// ****************************** stool.scad
include <hull_join.scad>
$fn = 128;
eps = 0.001;
seat_size = [20,20,2];
seat_cp = [0,0,seat_size.z*0.5]; // seat center point
seat_offset = [0,0,1.0]; // distance join will extend from seat
module seat() {
rotate_extrude() union() {
square([(seat_size.x-seat_size.z)*0.5+eps, seat_size.z]);
translate([(seat_size.x-seat_size.z)0.5,seat_size.z0.5])
circle(d=seat_size.z);
}
}
leg_size = [2,2,26];
// joins will only be formed around diameter of leg, not around the ends
// thus the z component of offsets is 0 and we don't need to set the center
point of scaling in z
leg_to_seat_offset = [1.5,1.5,0];
leg_to_brace_offset = [0.75,0.75,0];
module leg() {
cylinder(h=leg_size.z, d1=leg_size.x, d2=leg_size.x*0.7);
}
module place_leg() {
translate([seat_size.x0.3,0,seat_size.z0.5]) rotate(a=10, v=[0,1,0])
children();
}
legbrace_size = [18.5,18.5,0.75];
legbrace_join_size = [legbrace_size.z, leg_size.y, legbrace_size.z2.0]; //
size of intersection of legbrace and leg
legbrace_offset = [0.10,0.10,0.125];
legbrace_cp = [legbrace_size.x0.5,0,0]; // center of intersection of
legbrace and leg
module legbrace() {
rotate_extrude()
translate([legbrace_size.x0.5,0]) rotate(-10) scale([1,2.0])
circle(d=legbrace_size.z);
}
module place_legbrace() {
translate([0,0,seat_size.z0.5+leg_size.z*0.7]) children();
}
module placed_leg() {
place_leg() leg();
// join seat & leg
hull_join(iterations=2) {
hj_scale(hj_get_scale_for_offset(seat_size, seat_offset), seat_cp)
seat();
place_leg() hj_scale(hj_get_scale_for_offset(leg_size,
leg_to_seat_offset)) leg();
}
// join leg & legbrace
hull_join(iterations=10) {
place_leg() hj_scale(hj_get_scale_for_offset(leg_size,
leg_to_brace_offset)) leg();
place_legbrace()
hj_scale(hj_get_scale_for_offset(legbrace_join_size, legbrace_offset),
legbrace_cp) legbrace();
}
}
module legs() {
for(zrot=[0,120,240])
rotate(a=zrot, v=[0,0,1]) placed_leg();
}
union() {
seat();
place_legbrace() legbrace();
legs();
}
// ****************************** pod.scad ******************************
include <BOSL2/std.scad>
include <hull_join.scad>
eps = 0.001;
wall = 1.5;
base_size = [10,10,8.5];
base_offset = 8*[1,1,0];
// Make wrapper modules for objects used in a hierarchy with BOSL2 features
for positioning and orientation.
// After each object to be used in hull_join() capture its transformation
matrix.
// Then restore() the coordinate system and inside hull_join() reproduce
each object as
// a child of hjt_scale() which applies scaling before applying the
captured matrix.
// The (transform=false) parameter is added to wrapper modules when needed
to enable suppressing features such as anchor, spin and orientation which
modify the transformation matrix. We pass transform=true in the first call
of the wrapper before capturing the transformation matrix. Then in
hull_join() hjt_scale() can operate on an untransformed version of the
object before applying the captured matrix.
module base(transform=false) {zcyl(d1=base_size.x+5, d2=base_size.x-5,
h=base_size.z, chamfer=0.5, anchor=transform?BOT:CTR, $fn=3) children();}
module basex() {tag("podx") zcyl(d1=base_size.x+5-3wall,
d2=base_size.x-5-3wall, h=base_size.z+2*eps, chamfer=-0.5, $fn=3)
children();}
body_size = [10,10,10];
body_offset = 3*[1,1,0];
module body() {zcyl(d1=body_size.x+2, d2=body_size.x-2, h=body_size.z,
chamfer=0.5, $fn=6) children();}
module bodyx() {tag("podx") zcyl(d1=body_size.x+2-2wall,
d2=body_size.x-2-2wall, h=body_size.z+0.6, chamfer1=-0.5, $fn=6)
children();}
diff(remove="podx")
base(true) let(base_t=transform()) {
basex();
zrot_copies([0, 120, 240]) up(0.45) attach(RIGHT,LEFT,overlap=1) body()
let(body_t=transform()) {
bodyx();
restore() hull_join(iterations=32) {
hjt_scale(base_t, hj_get_scale_for_offset(base_size,
base_offset)) base();
hjt_scale(body_t, hj_get_scale_for_offset(body_size,
body_offset)) body();
}
}
}
// ****************************** siblings.scad
include <BOSL2/std.scad>
include <hull_join.scad>
$fs = 0.05;
join = 0.33;
base_size = [10,15,1];
base_offset = join*[1,1,1];
module base(transform=false) {cuboid(base_size, rounding=0.5,
anchor=transform?BACK+BOT:CTR) children();}
body_size = [1,10,20];
body_offset = join*[1,1,1];
module body(angle=0) {cuboid(body_size, rounding=0.5, spin=angle)
children();}
module place_body(angle) zrot(angle) children();
xrot(-35) base(true) let(base_desc=parent(), base_t=transform()) {
// if both bodies were direct children of base,
// then as siblings body1_t & body2_t would be in different scopes
unable to be together in hull_join()
// restore(base_desc) allows 2nd body to effectively have base as its
parent while being a child of 1st body
attach(FRONT+TOP+LEFT,RIGHT,overlap=5) body(-45)
let(body1_t=transform())
restore(base_desc)
attach(FRONT+TOP+RIGHT,LEFT,overlap=5) body(45) let(body2_t=transform())
restore()
hull_join(iterations=8, pairs="all") {
hjt_scale(base_t, hj_get_scale_for_offset(base_size, base_offset))
base();
hjt_scale(body1_t, hj_get_scale_for_offset(body_size, body_offset))
body();
hjt_scale(body2_t, hj_get_scale_for_offset(body_size, body_offset))
body();
}
}
On Sat, Dec 6, 2025 at 4:23 PM Todd Allen speedebikes@gmail.com wrote:
Here's an example of how I sometimes chamfer joints using hull(). I have
found this useful but it would be more useful if a similar method could be
applied between parent and child objects using BOSL2's
positioning/alignment/attachment but I don't yet see a good way and would
love it if someone could help me do it.
module centered_scale(center_point, scale_vector) {
translate(center_point)
scale(scale_vector)
translate(-1*center_point)
children(0);
}
// chamfered joining of objects
// unions objects with a hull of those objects limited to the intersection
of the objects after scaling
// adjust object scale factors to change the size and angle of the chamfer
module hulled_join(centered_scales) {
union() {
children();
hull() intersection() {
intersection_for(i=[0:1:$children-1])
centered_scale(centered_scales[i][0],
centered_scales[i][1])
children(i);
union()
children();
}
}
}
size1 = 10;
size2 = 5;
pos1 = [0,0,0];
pos2 = [size10.5+size20.3,0,0];
// EXAMPLE 1
hulled_join([
[pos1, (1+1.5/size1)[1,1,1]],
[pos2, (1+1.0/size2)[1,1,1]]
])
{
translate(pos1)
cube(10, center=true);
translate(pos2)
rotate(a=[20,20,0]) cube(5, center=true);
}
// EXAMPLE 2
translate([size12,0,0])
hulled_join([
[pos1, (1+1.0/size1)[1,1,1]],
[pos2, (1+1.3/size2)*[1,1,1]]
], $fn=128)
{
translate(pos1)
sphere(d=size1);
translate(pos2)
rotate(a=[90,20,0]) scale([1,0.5,1]) cylinder(h=size1, d=size2,
center=true);
}