Using the OpenSCAD code for this design included at the bottom of this post you can make your own bubble model with any three bubbles you like. The code will automatically position the bubbles correctly and create the correct surface components that intersect in the center of the model. Or, to print exactly the model above, use the Thingiverse link.
Thingiverse link: http://www.thingiverse.com/thing:438777
Settings: This is very large model that took up most of the build plate of a MakerBot Replicator 2 and printed at .2mm layer height with raft but no supports. It took many hours but we forgot to make a note of exactly how many! So, just, a lot.
Technical notes, math flavor: This model was requested by MoMath to use as a cake centerpiece for mathematician Jean Taylor's 70th birthday party. Jean Taylor famously proved that Plateau's Laws hold for minimal surfaces in her paper The structure of singularities in soap-bubble-like and soap-film-like minimal surfaces. According to Plateau's Laws, three bubbles will meet at 120-degree angles - just as in the center of the model shown above. However, the three surface components that meet at the 120-degree angle are not flat; they are curved. Each of the three components is a part of a larger sphere whose center lies outside of the bubbles. The three centers are shown by the points F, G, and H in the diagram below, taken from S.M. Blinder's Wolfram Demonstration Three Coalescing Soap Bubbles.
We used S.M. Blinder's Demonstration code to get the mathematical descriptions of the points F, G, and H given the radii of the three intersecting bubbles.
Technical notes, failure flavor: This model left a particularly large trail of fails; below is one that was designed with a much coarser mesh. The coarseness of the mesh left some gaps at the joins between the surfaces, which cased the model to separate. In addition we tried to print the entire top without supports, which caused the problem on the smallest sphere that led us to cancel the print. Most models can be made rather coarsely and still look fine, but this particular model needed a mesh with a very high degree of accuracy to even print correctly.
Technical notes, resolution flavor: To get a fine enough resolution for successful printing, we had to use a lot of triangles in our mesh. The hard part was getting the interior surface segments to contain enough triangles, since they came from much larger spheres. The key to doing this was using very small numbers for the fragment angle and fragment size of the spheres:
fragA = 2; // fragment angle
fragS = 2; // fragment size
Here's what the mesh looks like in OpenSCAD:
Because of the high resolution needed to make components of this model intersect successfully, I am not putting its OpenSCAD code on the Thingiverse Customizer. I think it would hang or crash to try to run this online. If you're interested in making different bubble configurations then download OpenSCAD and run the code at the bottom of this post, changing the bubble radii as desired.
Technical notes, OpenSCAD flavor: This was one of the most difficult things that I've made in OpenSCAD. When MoMath asked me if I could design this triple-bubble model, my first reaction was that I had absolutely no idea how to do that, so probably not. But a bit of poking around on the web led me to the excellent triple-bubble Wolfram Demonstration, and they had already done all of the math! I tried to export the Wolfram object using Mathematica, but it was not the right kind of graphics object to allow an STL export. This meant that it had to be reconstructed from the ground up in OpenSCAD. Alas, this in turn meant that I really needed to understand all the mathematics in the code, and also translate the code from Mathematica syntax to OpenSCAD syntax. All this together with the need for high-resolution accuracy meant that this project took many days to put together and get working correctly.
The OpenSCAD code starts with any three radii for the bubbles and then positions the spheres correctly, computes the center coordinates of the larger spheres that determine the intersecting surface components, and then uses unions, differences, and intersections to output the correct sections of the three bubbles and thee surface components. Because it would be difficult to see the inside intersections if the bubbles were printed as full spheres, the final step is to cut off the top and bottom of the model. This also makes the model very easy to print.
// mathgrrl triple bubble
////////////////////////////////////////////////////////////////
// bubble radii
a = 60;
b = 56;
c = 44;
////////////////////////////////////////////////////////////////
// resolution parameters
th = .9; // thickness
fragA = 2; // fragment angle
fragS = 2; // fragment size
////////////////////////////////////////////////////////////////
// scale and render
intersection(){
build_bubbles();
cube([250,200,70],center=true);
}
////////////////////////////////////////////////////////////////
// translation coordinates
// based on code from Wolfram Mathematica Demonstrations Project
// http://demonstrations.wolfram.com/ThreeCoalescingSoapBubbles/
ab = sqrt(a*a+b*b-a*b);
ac = sqrt(a*a+c*c-a*c);
bc = sqrt(b*b+c*c-b*c);
f = 1/(1/b-1/a);
g = 1/(1/c-1/a);
h = 1/(1/c-1/b);
// translation coords for sphere b
xb = ab;
// translation coords for sphere c
xc = (pow(ab,2)+pow(ac,2)-pow(bc,2))/(2*ab);
yc = (-1/(2*ab))*
sqrt( -pow(ab,4)
-(pow(ac,2)-pow(bc,2))*(pow(ac,2)-pow(bc,2))
+2*pow(ab,2)*(pow(ac,2)+pow(bc,2)));
// translation coords for interface sphere f
bf = sqrt(pow(b,2)+pow(f,2)-b*f);
xf = ab+bf;
yf = 0;
// translation coords for interface sphere g
cg = sqrt(pow(c,2)+pow(g,2)-c*g);
xg = (ac*xc+cg*xc)/ac;
yg = (ac*yc+cg*yc)/ac;
// translation coords for interface sphere h
ch = sqrt(pow(c,2)+pow(h,2)-c*h);
xh = (-ch*xb+bc*xc+ch*xc)/bc;
yh = (bc*yc+ch*yc)/bc;
////////////////////////////////////////////////////////////////
// module for sphere parts and dividers
module build_bubbles(){
//first bubble shell fragment
difference(){
sphere_a(open=1,fudge=0); // start with first bubble shell
intersection(){ // take away the part of the shell
sphere_a(open=1,fudge=0); // that intersects the
union(){ // second and third bubbles
sphere_b(open=0,fudge=-th);
sphere_c(open=0,fudge=-th);
}
}
}
//second bubble shell fragment
difference(){
sphere_b(open=1,fudge=0); // start with second bubble shell
intersection(){ // take away the part of the shell
sphere_b(open=1,fudge=0); // that intersects the
union(){ // first and third bubbles
sphere_a(open=0,fudge=-th);
sphere_c(open=0,fudge=-th);
}
}
}
//third bubble shell fragment
difference(){
sphere_c(open=1,fudge=0); // start with third bubble shell
intersection(){ // take away the part of the shell
sphere_c(open=1,fudge=0); // that intersects the
union(){ // first and second bubbles
sphere_a(open=0,fudge=-th);
sphere_b(open=0,fudge=-th);
}
}
}
//interface between first and second
intersection(){
sphere_ab(open=1,fudge=0); // take only the arc of the circle
difference(){
sphere_a(open=0,fudge=0.1); // that intersects the first bubble
sphere_ac(open=0,fudge=-th); // but not the other interface
}
}
//interface between first and third
intersection(){
sphere_ac(open=1,fudge=0); // take only the arc of the circle
difference(){
sphere_a(open=0,fudge=0.02); // that intersects the first bubble
sphere_ab(open=0,fudge=-0.02); // but not the other interface
}
}
//interface between second and third
intersection(){
sphere_bc(open=1,fudge=0); // take only the arc of the circle
difference(){
sphere_b(open=0,fudge=0.02); // that intersects the second bubble
difference(){ // but not the other side of
sphere(100);
sphere_ac(open=0,fudge=0.02); // the other interface
}
}
}
}
////////////////////////////////////////////////////////////////
// modules for the spheres
// use open=1 for shell and open=0 for full sphere
module sphere_a(open,fudge){
translate([0,0,0])
difference(){
sphere(a+th/2+fudge,$fa=fragA,$fs=fragS);
sphere(open*(a-th/2+fudge),$fa=fragA,$fs=fragS);
}
}
module sphere_b(open,fudge){
translate([xb,0,0])
difference(){
sphere(b+th/2,$fa=fragA,$fs=fragS);
sphere(open*(b-th/2+fudge),$fa=fragA,$fs=fragS);
}
}
module sphere_c(open,fudge){
translate([xc,yc,0])
difference(){
sphere(c+th/2+fudge,$fa=fragA,$fs=fragS);
sphere(open*(c+-th/2+fudge),$fa=fragA,$fs=fragS);
}
}
////////////////////////////////////////////////////////////////
// modules for the interface spheres
// use open=1 for shell and open=0 for full sphere
module sphere_ab(open,fudge){
translate([xf,yf,0])
difference(){
sphere(f+th/2+fudge,$fa=fragA/2,$fs=fragS/2);
sphere(open*(f-th/2+fudge),$fa=fragA/2,$fs=fragS/2);
}
}
module sphere_ac(open,fudge){
translate([xg,yg,0])
difference(){
sphere(g+th/2+fudge,$fa=fragA/2,$fs=fragS/2);
sphere(open*(g-th/2+fudge),$fa=fragA/2,$fs=fragS/2);
}
}
module sphere_bc(open,fudge){
translate([xh,yh,0])
difference(){
sphere(h+th/2+fudge,$fa=fragA/2,$fs=fragS/2);
sphere(open*(h-th/2+fudge),$fa=fragA/2,$fs=fragS/2);
}
}