Cascade Overflow Funnel
Fleeting- Objectif
- Le bouchon
- L’entonnoir
- Mise en place d’un pas de vis à course limitée entre entonnoir et bouchon
- Ajout d’un tuyau de trop-plein et d’un coude
- Abandon
- Annexe
- Notes pointant ici
- Permalink
Objectif
Remplir tous nos bidons d’eau de pluie (récupérateur → bidons 5L) en une seule fois, sans avoir à déplacer chaque bidon quand il est plein. Les bidons sont alignés et branchés en série : le premier reçoit l’eau du récupérateur, et quand il déborde l’excédent cascade dans le suivant via un tuyau de trop-plein, et ainsi de suite.
Chaque bidon reçoit un bouchon fileté imprimé 3D qui remplace le bouchon d’origine et qui reste vissé en permanence. Ce bouchon accueille par un pas de vis à course limitée deux pièces interchangeables à la main :
- un entonnoir (vissé pendant le remplissage) qui reçoit l’eau par le haut et porte un tuyau de trop-plein pour cascader l’excédent vers le bidon suivant ;
- une pièce de fermeture (vissée après remplissage) qui scelle le bidon pour le stockage.
Principe : on ne dévisse plus jamais le bouchon du bidon — on bascule entonnoir ↔ pièce de fermeture à la main.
Le bouchon
Trouvons le bon filetage
Pour que le bouchon se visse proprement sur le col du bidon, il faut identifier le standard de filetage exact (Ø, pas, profil). Méthode : mesurer grossièrement, croiser avec un catalogue de standards pour lister les candidats, puis imprimer des anneaux de test de chaque candidat pour confirmer par essai au vissage.
Sources et mesures
Deux bidons candidats disponibles commercialement (eau déminéralisée, vides) :
- MIEUXA (Amazon) — col mesuré ~35mm int / ~40mm ext, pas ~3mm
- Netto/Ardea — col mesuré ~33mm int / ~38mm ext, pas ~4mm
Ces mesures sont approximatives (pied à coulisse rudimentaire). Il faut les croiser avec un catalogue de standards de filetage plastique pour trouver le bon candidat avant d’imprimer le bouchon complet.
Références : SCAT Europe (catalogue de standards), Thingiverse thing #5397832 (modèles paramétriques existants).
Les robinets vendus pour bidons 5L sont souvent étiquetés “DIN 40” ou “DIN 45”. Méthode de travail : imprimer des anneaux de test (juste le filetage, rapide) pour confirmer le standard avant d’imprimer un bouchon complet.
Standards identifiés (source : SCAT Europe, gewinde-normen.de)
| Standard | Norme | Ø ext max | Ø ext min | Ø int noyau max | Pas (mm) |
|---|---|---|---|---|---|
| S 40/41 (plastique) | DIN 6063-1 | 41.00 | 39.50 | 37.00 | 3.50 |
| GL 38 (verre) | DIN 168-1 | 37.49 | 36.88 | 35.10 | 4.23 |
| GL 40 (verre) | DIN 168-1 | 40.00 | 39.30 | 37.30 | 4.00 |
| DIN 45 | DIN 45 | 44.30 | 39.70 | 40.80 | 4.00 |
| S 45 (plastique) | DIN 6063-1/2 | 45.00 | 44.30 | 41.00 | 4.00 |
| GL 45 (verre) | DIN 168-1 | 45.00 | 44.30 | 42.30 | 4.00 |
| KS 36 (plastique) | DIN 6063-1 | 36.00 | - | 33.00 | 3.00 |
| KS 40 (plastique) | DIN 6063-1 | 40.00 | - | 37.00 | 3.00 |
Filetage retenu par bidon
Les mesures sont approximatives. Candidats par ordre de vraisemblance :
- MIEUXA (~35-40mm, pas ~3mm) :
KS 40 (DIN 6063-1) : Ø 40mm, pas 3.0mm — éliminé (test 1)DIN 45 : Ø ext 39.7-44.3mm, pas 4.0mm — inutile, S 40/41 fait l’affaire (test 2)- S 40/41 (DIN 6063-1) : Ø 41mm, pas 3.5mm — confirmé d=42, pas=3.5 (test 2)
- Netto/Ardea (~33-38mm, pas ~4mm) : confirmé d=40, pas=4.0 (test 5)
GL 40 (DIN 168-1) : Ø 40mm, pas 4.0mm — éliminé, d=41 trop gros (test 4)- ~
GL 38 (DIN 168-1) : Ø37.5mm, pas 4.23mm — éliminé, d=38.5 trop fin (test 3) - Filetage custom entre GL 38 et GL 40
Paramètres figés à partir d’ici — dimensions du col et spec du filetage — qui seront réutilisés par toutes les variantes ultérieures de bouchon (nu, avec entonnoir, pièce de fermeture) :
wallThickness=2;
innerDiameter=42;
innerDepth=15;
threadPitch=3.50;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
wallThickness=2;
innerDiameter=40;
innerDepth=15;
threadPitch=4.00;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=3;
threads=1;
threadStart=1.3;
Anneaux de test pour tester les filetages
Avant d’investir dans un bouchon entier, on confirme le bon standard de filetage en imprimant juste un anneau fileté et en essayant de le visser sur le col du bidon. C’est rapide (quelques minutes en precision=1), et c’est la seule façon fiable de savoir si les mesures au pied à coulisse correspondent à un vrai standard.
L’anneau est un cylindre qui porte juste le filetage paramétré (rayon, pas), sans dessus ni stries — minimum vital pour tester le vissage.
Anneau de test — paramétrique
// --- PARAMÈTRES À CHANGER POUR CHAQUE TEST ---
// Candidats MIEUXA : KS40 (d=41, p=3.0) / S40/41 (d=42, p=3.5)
// Candidats Netto : GL38 (d=38.5, p=4.23) / GL40 (d=41, p=4.0)
innerDiameter=42; // Ø ext du col + 1mm jeu
threadPitch=3.5; // pas du filetage
// --- FIN PARAMÈTRES ---
precision=1; // rough pour impression rapide
wallThickness=2;
innerDepth=8; // court, juste assez pour tester le vissage
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
difference()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
}
Valeurs à tester :
| Test | innerDiameter | threadPitch | Standard visé | |
|---|---|---|---|---|
| 1 | 41 | 3.00 | KS 40 (MIEUXA) | |
| 2 | 42 | 3.50 | S 40/41 (MIEUXA) | semble faire l’affaire |
| 3 | 38.5 | 4.23 | GL 38 (Netto) | trop fin, le filetage bloque le passage de l’anneau |
| 4 | 41 | 4.00 | GL 40 (Netto) | trop gros, le cou flotte dedans, mais toujours trop fin pour le MIEUXA |
| 5 | 40 | 4.00 | custom (Netto) | semble faire l’affaire |
| 6 | 43 | 4.00 | DIN 45 (MIEUXA) | inutile, S 40/41 (test 2) fait déjà l’affaire |
Mise en place d’une jupe d’étanchéité
Avec le bon filetage, le bouchon se visse sur le col, mais rien n’empêche l’eau de fuir entre le col et le bouchon. La solution vient directement de l’observation des bouchons d’origine des bidons du commerce : ils portent tous, sous leur disque, une petite jupe annulaire intérieure qui vient s’emboîter dans le col et sceller contre sa paroi interne pendant le vissage. On reproduit cette jupe dans nos bouchons imprimés. On l’itère d’abord seule (debug) pour trouver les bonnes cotes (Ø, hauteur, épaisseur, jeu radial), puis on assemble le premier bouchon complet qui visse et scelle.
Paramètres retenus de la jupe pour chaque bidon (sealHeight = hauteur
axiale, sealThickness = épaisseur radiale, sealGap = jeu radial par
rapport au Ø intérieur du col) + ridges = nombre de stries
extérieures de préhension au vissage :
ridges=25;
sealHeight=7;
sealThickness=1.2;
sealGap=3.6;
ridges=25;
sealHeight=8;
sealThickness=1.0;
sealGap=3.05;
Jupe seule (debug)
Pour valider les dimensions de la bague d’étanchéité intérieure (celle qui vient sceller contre la lèvre du col) sans imprimer tout un bouchon, on l’imprime seule sur un petit plancher via la jupe de test. Ça se monte sur le col comme un bouchon-tampon — on juge à la main si l’étanchéité est correcte.
Jupe MIEUXA (S 40/41)
wallThickness=2;
innerDiameter=42;
innerDepth=15;
threadPitch=3.50;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=7;
sealThickness=1.2;
sealGap=3.6;
$fn=50;
union()
{
// cylinder(h=wallThickness,r=innerDiameter/2-sealGap);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}
Jupe Netto/Ardea (custom d=40)
wallThickness=2;
innerDiameter=40;
innerDepth=15;
threadPitch=4.00;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=3;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=8;
sealThickness=1.0;
sealGap=3.05;
// pour debug
sealHeight=3;
wallThickness=1;
$fn=50;
union()
{
// cylinder(h=wallThickness,r=innerDiameter/2-sealGap);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}
Bouchon complet (filetage + jupe)
Avec les bonnes cotes de filetage et de jupe, on assemble le premier bouchon complet fermé : filetage + corps cylindrique + disque de fermeture + bague d’étanchéité. Ces bouchons nus servent à boucher un bidon qu’on ne remplit pas (et aussi à vérifier que le standard choisi tient vraiment à la pression, à la manipulation).
Deux jeux de paramètres (params-mieuxa et params-netto) sont les
sources de vérité réutilisées par toutes les variantes ultérieures
(bouchons avec entonnoir, entonnoirs séparés).
Bouchon MIEUXA (S 40/41)
wallThickness=2;
innerDiameter=42;
innerDepth=15;
threadPitch=3.50;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=7;
sealThickness=1.2;
sealGap=3.6;
precision=3;
wallThickness=2;
innerDiameter=42;
innerDepth=15;
threadPitch=3.50;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=7;
sealThickness=1.2;
sealGap=3.6;
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
translate([0,0,wallThickness/2])cylinder(h=wallThickness,r=innerDiameter/2,center=true);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}
Bouchon Netto/Ardea (custom d=40, pas=4.0)
wallThickness=2;
innerDiameter=40;
innerDepth=15;
threadPitch=4.00;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=3;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=8;
sealThickness=1.0;
sealGap=3.05;
precision=3;
wallThickness=2;
innerDiameter=40;
innerDepth=15;
threadPitch=4.00;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=3;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=8;
sealThickness=1.0;
sealGap=3.05;
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
translate([0,0,wallThickness/2])cylinder(h=wallThickness,r=innerDiameter/2,center=true);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}
L’entonnoir
Première variante enrichie : on part du corps commun du bouchon (filetage + paroi + stries + chanfrein), on perce le dessus, et on y prolonge un cône — l’entonnoir. L’eau du récupérateur tombe dans l’entonnoir, est guidée par le cône vers le trou central, et descend dans le bidon. Pas encore de trop-plein ni de séparation bouchon/entonnoir à ce stade : c’est le monobloc le plus simple.
Impression avec l’ouverture large de l’entonnoir posée sur le plateau → ni support ni overhang problématique.
Orientation : bas = partie fine (sortie, côté trou), haut = partie large (entrée, où tombe l’eau). Le code SCAD est écrit avec Z inversé par rapport à cette orientation.
La grandeur active de l’entonnoir est sa génératrice — la distance
mesurée sur le flanc du cône de la lèvre extérieure jusqu’au trou de
sortie. funnelSlope fixe la pente (rapport Δr/h) et
funnelGenLength la longueur de cette génératrice ; hauteur et
diamètre en découlent (dérivation). À funnelSlope=0.68 et
funnelGenLength=70mm, la génératrice de 70mm donne assez de course
pour ramener vers le trou l’eau tombant plusieurs centimètres hors
axe, absorbant les imprécisions de pose et évitant les projections
hors du cône.
Le diamètre du trou à la jonction avec le collet doit avoir à peu près la même dimension, pour éviter d’avoir des parties en l’air lors de l’impression.
funnelSlope=0.68;
funnelGenLength=70;
funnelHoleDiameter=39;
funnelHeight=funnelGenLength/sqrt(funnelSlope*funnelSlope+1);
funnelTopDiameter=2*(funnelSlope*funnelHeight+funnelHoleDiameter/2);
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
difference()
{
translate([0,0,wallThickness/2])
cylinder(h=wallThickness, r=innerDiameter/2, center=true);
cylinder(h=wallThickness*3, r=funnelHoleDiameter/2, center=true);
}
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
union()
{
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
difference()
{
translate([0,0,wallThickness/2])
cylinder(h=wallThickness, r=innerDiameter/2, center=true);
cylinder(h=wallThickness*3, r=funnelHoleDiameter/2, center=true);
}
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
}
union()
{
$fn=50;
union()
{
// cylinder(h=wallThickness,r=innerDiameter/2-sealGap);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}
difference()
{
translate([0,0,wallThickness/2])
cylinder(h=wallThickness, r=innerDiameter/2, center=true);
cylinder(h=wallThickness*3, r=funnelHoleDiameter/2, center=true);
}
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
}
Bouchon entonnoir MIEUXA (S 40/41)
precision=3;
wallThickness=2;
innerDiameter=42;
innerDepth=15;
threadPitch=3.50;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=7;
sealThickness=1.2;
sealGap=3.6;
funnelSlope=0.68;
funnelGenLength=70;
funnelHoleDiameter=39;
funnelHeight=funnelGenLength/sqrt(funnelSlope*funnelSlope+1);
funnelTopDiameter=2*(funnelSlope*funnelHeight+funnelHoleDiameter/2);
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
difference()
{
translate([0,0,wallThickness/2])
cylinder(h=wallThickness, r=innerDiameter/2, center=true);
cylinder(h=wallThickness*3, r=funnelHoleDiameter/2, center=true);
}
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
}
Bouchon entonnoir Netto/Ardea (custom d=40, pas=4.0)
precision=3;
wallThickness=2;
innerDiameter=40;
innerDepth=15;
threadPitch=4.00;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=3;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=8;
sealThickness=1.0;
sealGap=3.05;
funnelSlope=0.68;
funnelGenLength=70;
funnelHoleDiameter=39;
funnelHeight=funnelGenLength/sqrt(funnelSlope*funnelSlope+1);
funnelTopDiameter=2*(funnelSlope*funnelHeight+funnelHoleDiameter/2);
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
difference()
{
translate([0,0,wallThickness/2])
cylinder(h=wallThickness, r=innerDiameter/2, center=true);
cylinder(h=wallThickness*3, r=funnelHoleDiameter/2, center=true);
}
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
}
Mise en place d’un pas de vis à course limitée entre entonnoir et bouchon
Pourquoi imprimer le bouchon et l’entonnoir en deux pièces séparées : on veut orienter le tuyau de trop-plein librement une fois le bouchon vissé. Avec un monobloc, l’orientation du tuyau dépend de combien de tours on a serré le filetage sur le col — incontrôlable. En séparant bouchon (filetage sur le col) et entonnoir (qui porte le tuyau), puis en les assemblant par un pas de vis multi-tours qui se termine dans une gorge annulaire, on visse d’abord le bouchon à fond sur le bidon, puis on engage l’entonnoir et on le serre jusqu’à ce que le filet femelle de la jupe quitte le filetage mâle du collet pour tomber dans la gorge — la rotation devient alors libre à 360°, ce qui permet de pointer le tuyau vers le bidon suivant sans défaire le serrage.
L’assemblage combine trois fonctions :
- étanchéité axiale par un joint fibre plat posé sur le plancher du collet, écrasé par la pointe annulaire du manchon descendant du bouchon ;
- tenue mécanique radiale par une jupe extérieure du bouchon qui enveloppe le collet, avec son filetage femelle piégé dans la gorge annulaire ceinturant le collet — translation axiale bloquée par le pied du filetage mâle au-dessus, qui referme la gorge ;
- rotation libre après que le filet femelle a quitté l’engagement avec le filet mâle pour rejoindre la gorge, celle-ci étant continue sur 360°.
Le filetage compte plusieurs tours d’engagement : ça donne une course de descente longue avec une rotation totale importante, ce qui permet d’arrêter le serrage à n’importe quel angle dans la zone d’engagement avant la chute dans la gorge — utile pour ajuster l’orientation du tuyau de trop-plein si la chute dans la gorge ne tombe pas pile sur l’angle voulu.
Manchon du bouchon (plug tube)
Le manchon est un tube creux qui descend du bouchon dans le collet pour écraser le joint. Son Ø extérieur est juste sous le Ø intérieur du collet (0.3mm de jeu) pour glisser sans frotter ; la paroi (2.5mm) est assez raide pour transmettre la compression du joint sans fléchir.
jointWall=2.5;
jointPlugR=funnelHoleDiameter/2-0.3;
jointPlugInnerR=jointPlugR-jointWall;
Joint fibre plat
On choisit un joint fibre plat du commerce (plomberie, jardinerie,
30x38x2mm ici) plutôt qu’un joint imprimé ou un o-ring. Arguments : pas
cher, trouvable partout, fiable en statique, insensible à la pression
basse qu’on a ici, et compense les imperfections de surface de
l’impression 3D (le PLA n’est pas miroir-lisse). On le pose à plat sur
le plancher du collet ; la pointe du manchon vient l’écraser de 0.3mm
(compression contrôlée pour ne pas déchirer la fibre).
Le plancher du collet a un petit bec de rétention qui dépasse radialement dans le trou central (=gasketRetentionLip~) : ça empêche le joint de tomber par gravité dans la cavité du cône quand on manipule l’entonnoir avant clipsage.
gasketID=30;
gasketOD=38;
gasketThickness=2.0;
gasketCompression=0.3;
gasketSeatClearance=0.2;
gasketRetentionLip=0.5;
Filetage + gorge annulaire
Le principe : la paroi externe du collet porte un filetage mâle hélicoïdal multi-tours qui descend depuis le sommet du collet jusqu’à une gorge annulaire continue sur 360° en pied du filet. La face interne de la jupe du bouchon porte le filetage femelle correspondant — empreinte du mâle, même pas, même profondeur, même sens. Pour visser : on présente le bouchon au-dessus du collet, on engage les premiers filets en haut, puis on tourne dans le sens serrage — le filet femelle descend le long du filet mâle au rythme du pas. Arrivé en bas, le filet femelle quitte le filet mâle pour glisser dans la gorge ; la translation axiale est piégée vers le haut par le pied du filet mâle qui referme le plafond de la gorge, la rotation reste libre puisque la gorge est continue sur 360°.
Calibration de la descente. Le pied du filet mâle (= plafond de
la gorge, collarGrooveTopZ) est calé pour que, quand le sommet
du filet femelle l’atteint en descendant, la pointe du manchon ait
écrasé le joint fibre exactement de gasketCompression. Ça se
gère via la hauteur du manchon (jointHeight) et l’ancrage du
pied du filet femelle au bord ouvert de la jupe. La descente
pendant le serrage est progressive (hélicoïdale continue) : on
sent la résistance monter doucement, on peut arrêter avant la
chute dans la gorge si le tuyau de trop-plein se trouve déjà bien
orienté à un pas près, ou continuer jusqu’à la chute pour
bénéficier de la rotation libre.
Single-start avec plusieurs tours. Un seul filet hélicoïdal qui
tourne sur collarThreadTurns tours pour atteindre la gorge.
Avantage : le ratio rotation/descente est élevé, donc l’angle
final dans la zone d’engagement (avant la chute dans la gorge) se
règle finement. Avec collarThreadTurns × 360° de rotation totale
pour collarThreadPitch × collarThreadTurns mm de descente, on
dispose de plusieurs positions sealed-and-correctly-oriented dans
la course, espacées chacune d’un pitch (= 360° de rotation).
collarThreadPitch=2.0; // mm/turn — pas du filetage
collarThreadTurns=2; // tours d'engagement
collarThreadStarts=1; // single-start
collarThreadDepth=1.0; // profondeur radiale du filet (mm)
collarThreadWidth=1.2; // largeur axiale du profil de filet (mm)
collarSkirtClearance=0.3; // jeu radial entre crête mâle et flanc lisse de la jupe (mm)
collarGrooveDepthExtra=0.5; // surcreusement de la gorge sous le mur du collet (mm)
collarGrooveAxialExtra=0.5; // jeu axial entre filet femelle et plancher de la gorge (mm)
collarThreadAboveGasket=3.7; // hauteur du pied du filet mâle au-dessus du joint (mm)
collarWall=2.5; // épaisseur paroi du collet (mm)
collarDiskGap=1.0; // jeu axial disque bouchon / haut du collet (mm)
Géométrie dérivée
Les trois blocs précédents (manchon, joint, filetage) fournissent tous les paramètres utilisateur. Tout le reste — les rayons imbriqués du collet et de la jupe, la position axiale de la gorge, la hauteur du collet et du manchon, la position du filet femelle sur la jupe — se déduit.
Deux quantités méritent un mot. jointHeight (hauteur du manchon)
est calibrée pour que, une fois le bouchon vissé à fond sur le
collet, la pointe du manchon appuie sur le joint fibre exactement
avec la compression voulue (gasketCompression).
collarGrooveTopZ — le pied du filet mâle, qui est aussi le
plafond de la gorge — est placé à la hauteur axiale telle que le
filet femelle l’atteint précisément quand le manchon a fini son
écrasement du joint.
La hauteur du collet collarHeight = collarGrooveTopZ + collarThreadAxialExtent où collarThreadAxialExtent = collarThreadPitch × collarThreadTurns est la course axiale
couverte par le filetage. Le filet mâle commence pile à
collarGrooveTopZ et monte jusqu’au sommet du collet.
La gorge a une hauteur axiale collarThreadAxialExtent + collarGrooveAxialExtra : assez pour absorber la crête du filet
femelle complète quand celle-ci quitte le filet mâle, plus un jeu
sous la pointe (collarGrooveAxialExtra) pour que la chute soit
franche.
Le filet femelle de la jupe, lui, débouche pile sur le bord
ouvert du bas de la jupe — sinon il n’y a pas de point d’entrée
pour engager le filet mâle au démarrage du vissage. Comme le filet
femelle est ancré au bord ouvert (z=-jointHeight) et qu’il
monte sur collarThreadAxialExtent, son sommet est à
z=-jointHeight + collarThreadAxialExtent dans le repère du
bouchon. Pour que ce sommet coïncide avec le plafond de la gorge à
l’arrêt (manchon en compression), on fixe collarThreadAboveGasket = collarThreadAxialExtent - gasketCompression — la valeur exacte
qui aligne simultanément (1) le pied du filet femelle au bord
ouvert et (2) le sommet du filet femelle contre le plafond de la
gorge quand le joint est compressé.
// Rayons
jointBoreR=gasketOD/2+gasketSeatClearance;
collarFloorHoleR=gasketID/2-gasketRetentionLip;
collarOuterR=jointBoreR+collarWall;
// Crête mâle au-dessus du mur du collet ; flanc lisse de la jupe au-delà,
// décalé d'un jeu collarSkirtClearance pour que les deux crêtes
// s'engagent sans frotter sur la paroi opposée.
threadCrestR=collarOuterR+collarThreadDepth;
skirtInnerR=threadCrestR+collarSkirtClearance;
skirtOuterR=skirtInnerR+jointWall;
femaleCrestR=skirtInnerR-collarThreadDepth;
// Gorge surcreusée par rapport au mur du collet pour absorber la
// crête du filet femelle quand celle-ci y descend en fin de course.
collarGrooveInnerR=collarOuterR-collarGrooveDepthExtra;
// Cotes axiales
collarThreadAxialExtent=collarThreadPitch*collarThreadTurns;
// Plafond de la gorge = pied du filet mâle. Au moment où le filet
// femelle atteint cette cote en descendant, le manchon a écrasé
// le joint de gasketCompression.
collarGrooveTopZ=wallThickness+gasketThickness+collarThreadAboveGasket;
collarThreadBottomZ=collarGrooveTopZ;
collarThreadTopZ=collarThreadBottomZ+collarThreadAxialExtent;
collarHeight=collarThreadTopZ;
collarGrooveBottomZ=collarGrooveTopZ-collarThreadAxialExtent-collarGrooveAxialExtra;
// Hauteur du manchon — cote du bottom de la jupe sous le disque du
// bouchon, calée sur la compression du joint à l'arrêt.
jointHeight=collarHeight+collarDiskGap-(wallThickness+gasketThickness-gasketCompression);
jointWall=2.5;
jointPlugR=funnelHoleDiameter/2-0.3;
jointPlugInnerR=jointPlugR-jointWall;
gasketID=30;
gasketOD=38;
gasketThickness=2.0;
gasketCompression=0.3;
gasketSeatClearance=0.2;
gasketRetentionLip=0.5;
collarThreadPitch=2.0; // mm/turn — pas du filetage
collarThreadTurns=2; // tours d'engagement
collarThreadStarts=1; // single-start
collarThreadDepth=1.0; // profondeur radiale du filet (mm)
collarThreadWidth=1.2; // largeur axiale du profil de filet (mm)
collarSkirtClearance=0.3; // jeu radial entre crête mâle et flanc lisse de la jupe (mm)
collarGrooveDepthExtra=0.5; // surcreusement de la gorge sous le mur du collet (mm)
collarGrooveAxialExtra=0.5; // jeu axial entre filet femelle et plancher de la gorge (mm)
collarThreadAboveGasket=3.7; // hauteur du pied du filet mâle au-dessus du joint (mm)
collarWall=2.5; // épaisseur paroi du collet (mm)
collarDiskGap=1.0; // jeu axial disque bouchon / haut du collet (mm)
// Rayons
jointBoreR=gasketOD/2+gasketSeatClearance;
collarFloorHoleR=gasketID/2-gasketRetentionLip;
collarOuterR=jointBoreR+collarWall;
// Crête mâle au-dessus du mur du collet ; flanc lisse de la jupe au-delà,
// décalé d'un jeu collarSkirtClearance pour que les deux crêtes
// s'engagent sans frotter sur la paroi opposée.
threadCrestR=collarOuterR+collarThreadDepth;
skirtInnerR=threadCrestR+collarSkirtClearance;
skirtOuterR=skirtInnerR+jointWall;
femaleCrestR=skirtInnerR-collarThreadDepth;
// Gorge surcreusée par rapport au mur du collet pour absorber la
// crête du filet femelle quand celle-ci y descend en fin de course.
collarGrooveInnerR=collarOuterR-collarGrooveDepthExtra;
// Cotes axiales
collarThreadAxialExtent=collarThreadPitch*collarThreadTurns;
// Plafond de la gorge = pied du filet mâle. Au moment où le filet
// femelle atteint cette cote en descendant, le manchon a écrasé
// le joint de gasketCompression.
collarGrooveTopZ=wallThickness+gasketThickness+collarThreadAboveGasket;
collarThreadBottomZ=collarGrooveTopZ;
collarThreadTopZ=collarThreadBottomZ+collarThreadAxialExtent;
collarHeight=collarThreadTopZ;
collarGrooveBottomZ=collarGrooveTopZ-collarThreadAxialExtent-collarGrooveAxialExtra;
// Hauteur du manchon — cote du bottom de la jupe sous le disque du
// bouchon, calée sur la compression du joint à l'arrêt.
jointHeight=collarHeight+collarDiskGap-(wallThickness+gasketThickness-gasketCompression);
Collet de l’entonnoir
Le collet est la partie qui reçoit le bouchon : un cylindre creux posé au sommet (bas-réel) de l’entonnoir, avec le filetage mâle ajouté sur sa paroi externe et la gorge annulaire soustraite en pied du filet, et le plancher support du joint à l’intérieur.
Coque du collet. Anneau cylindrique plein 360°, obtenu par
rotate_extrude d’un rectangle (r,z) entre jointBoreR et
collarOuterR, de z=0 à z=collarHeight.
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
Filetage mâle (additif). Hélice continue plaquée sur la paroi
externe du collet à r=collarOuterR. Pied à
collarThreadBottomZ, sommet à collarThreadTopZ — qui est aussi
le sommet du collet, donc le filet débouche pile en haut, point
d’engagement naturel pour le filet femelle de la jupe. Le profil
elliptique (profondeur collarThreadDepth, largeur axiale
collarThreadWidth) est généré par le module générique
screwThread.
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
Gorge annulaire (soustractive). Recess 360° en pied du filet
mâle, axialement de collarGrooveBottomZ à collarGrooveTopZ,
radialement de collarGrooveInnerR jusqu’au-delà de
collarOuterR (le débordement à +1mm garantit qu’on retire bien
tout le mur dans cette zone). Le surcreusement
collarGrooveDepthExtra par rapport au mur extérieur du collet
dégage assez de jeu radial pour que les crêtes du filet femelle
de la jupe y descendent sans accrocher.
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
Plancher support du joint. Disque plein percé en son centre pour
laisser passer l’eau qui descend dans la cavité du cône. Le bord
du trou porte le bec de rétention du joint (cf.
gasketRetentionLip).
difference()
{
cylinder(h=wallThickness,r=jointBoreR);
cylinder(h=wallThickness*3,r=collarFloorHoleR,center=true);
}
Chanfrein sous le plancher. Sans ce chanfrein, l’anneau intérieur du
plancher (de collarFloorHoleR à funnelHoleDiameter/2) est en
porte-à-faux au-dessus de la cavité du cône — impression impossible
sans support. Le chanfrein descend depuis le bord du trou du plancher
et rejoint la paroi intérieure du cône qui s’élargit en descendant.
Triangle (r,z) qui se ferme là où les deux pentes se croisent.
collarChamferAngle = angle du chanfrein par rapport à l’horizontale.
Plus il est grand, plus le chanfrein est vertical (meilleur pour
l’impression) mais plus il plonge profondément dans la cavité du
cône avant de rejoindre la paroi (il doit rattraper la pente fixe du
cône). 45° est la limite PLA avec buse fine ; en buse 0.8 on force à
50° pour éviter les ratés d’impression. Contrainte : doit rester
inférieur à l’angle du cône lui-même (sinon le chanfrein parallélise
la paroi et ne la rejoint jamais).
collarChamferAngle=50;
funnelInnerSlope=(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight);
chamferDrPerDz=1/tan(collarChamferAngle);
chamferDepth=(funnelHoleDiameter/2-collarFloorHoleR)/(chamferDrPerDz-funnelInnerSlope);
rotate_extrude(convexity=10)
polygon(points=[
[collarFloorHoleR,0],
[funnelHoleDiameter/2,0],
[funnelHoleDiameter/2+funnelInnerSlope*chamferDepth,-chamferDepth]
]);
Assemblage final. La coque + le filetage mâle + le plancher + le
chanfrein sous le plancher, moins la gorge annulaire. Une seule
opération difference().
difference()
{
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
difference()
{
cylinder(h=wallThickness,r=jointBoreR);
cylinder(h=wallThickness*3,r=collarFloorHoleR,center=true);
}
collarChamferAngle=50;
funnelInnerSlope=(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight);
chamferDrPerDz=1/tan(collarChamferAngle);
chamferDepth=(funnelHoleDiameter/2-collarFloorHoleR)/(chamferDrPerDz-funnelInnerSlope);
rotate_extrude(convexity=10)
polygon(points=[
[collarFloorHoleR,0],
[funnelHoleDiameter/2,0],
[funnelHoleDiameter/2+funnelInnerSlope*chamferDepth,-chamferDepth]
]);
}
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
}
union()
{
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
difference()
{
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
difference()
{
cylinder(h=wallThickness,r=jointBoreR);
cylinder(h=wallThickness*3,r=collarFloorHoleR,center=true);
}
collarChamferAngle=50;
funnelInnerSlope=(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight);
chamferDrPerDz=1/tan(collarChamferAngle);
chamferDepth=(funnelHoleDiameter/2-collarFloorHoleR)/(chamferDrPerDz-funnelInnerSlope);
rotate_extrude(convexity=10)
polygon(points=[
[collarFloorHoleR,0],
[funnelHoleDiameter/2,0],
[funnelHoleDiameter/2+funnelInnerSlope*chamferDepth,-chamferDepth]
]);
}
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
}
}
Corps du bouchon
Le bouchon s’imprime plug-down : le manchon et la jupe descendent sous le disque de fermeture, le tout reposant sur le plateau (même cote z=-jointHeight). Le dessous du disque ponte les deux (~3mm à traverser en bridge sans support), orientation naturelle pour ne pas avoir de porte-à-faux sur la géométrie délicate. L’assemblage se fait en cinq composants unifiés.
Filetage. Généré par le wrapper capThread() (qui appelle le
module générique screwThread avec les paramètres du bouchon).
Spirale paramétrée par params-mieuxa ou params-netto selon le col
visé.
capThread();
Paroi filetée. Anneau cylindrique sur lequel s’enroule le filetage.
Elle descend jusqu’à z=0 (et coexiste avec le disque sur
[0, wallThickness]) au lieu de démarrer en pointe à z=wallThickness —
sinon on obtient un knife edge sur le pourtour du disque qui fragilise
l’impression. Supportée en bas par le disque et la jupe, donc pas besoin
de chanfrein.
rotate_extrude(convexity=10)
polygon(points=[
[innerDiameter/2, 0],
[innerDiameter/2+wallThickness, 0],
[innerDiameter/2+wallThickness, innerDepth+wallThickness],
[innerDiameter/2, innerDepth+wallThickness]
]);
Disque de fermeture. Ferme le dessus du bouchon (sinon l’eau coulerait
dans le filetage). Trapèze (r,z) : à z=0 il va de jointPlugInnerR
jusqu’à skirtInnerR (pour rejoindre la jupe) ; à z=wallThickness il
revient à innerDiameter/2 (pour rejoindre la paroi filetée au-dessus).
Cette bascule de rayon crée l’étagement “jupe en bas, paroi au-dessus”.
rotate_extrude(convexity=10)
polygon(points=[
[jointPlugInnerR, 0],
[skirtInnerR, 0],
[innerDiameter/2, wallThickness],
[jointPlugInnerR, wallThickness]
]);
Manchon (tube plug). Tube creux qui descend sous le disque vers le
plateau. Sa pointe annulaire (rayon jointPlugR extérieur,
jointPlugInnerR intérieur) est ce qui écrase le joint fibre. Les
cylindres extérieur et intérieur du difference() ont exactement la
même hauteur : une dissymétrie (même 0.01mm) créerait à l’interface avec
le disque (au même rayon jointPlugInnerR) des faces coplanaires
dupliquées qui font tousser CGAL et produisent un STL non-manifold.
translate([0,0,-jointHeight])
difference()
{
cylinder(h=jointHeight, r=jointPlugR);
cylinder(h=jointHeight, r=jointPlugInnerR);
}
Jupe extérieure. Descend du disque jusqu’à z=-jointHeight (même
cote que le bas du manchon). Cylindre creux 360°
(plug-skirt-cylinder) auquel on unit le filetage femelle
(plug-skirt-female-thread) — une crête hélicoïdale qui dépasse
vers l’axe depuis la face interne de la jupe.
rotate_extrude(convexity=10)
polygon(points=[
[skirtOuterR, -jointHeight],
[skirtOuterR, 0],
[skirtInnerR, 0],
[skirtInnerR, -jointHeight]
]);
Filetage femelle. Crête hélicoïdale ajoutée à la face interne de
la jupe, miroir du filet mâle : même pas, même profondeur, même
largeur, même nombre de tours, même sens. Le screwThread est
placé à r=skirtInnerR (le flanc lisse de la jupe), et son profil
ellipsoïdal s’étend ±collarThreadDepth radialement — la moitié
interne (r < skirtInnerR) protrude dans la cavité comme la crête
visible du filet, la moitié externe (r > skirtInnerR) se fond
dans la matière de la jupe sans effet visible.
Le pied du filet femelle est placé à z = -jointHeight — le bord
ouvert du bas de la jupe — pour donner un point d’entrée hélicoïdal
au démarrage du vissage. La sphère de bout du screwThread déborde
axialement sous la jupe par collarThreadWidth/2 ; on la clippe
par une intersection avec un cylindre borné à la hauteur de la
jupe, pour que la pièce reste imprimable plug-down (pas de
porte-à-faux sous le plateau).
intersection()
{
translate([0, 0, -jointHeight])
cylinder(h=jointHeight, r=skirtInnerR + collarThreadDepth + 1);
translate([0, 0, -jointHeight])
screwThread(skirtInnerR,
0,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
}
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[skirtOuterR, -jointHeight],
[skirtOuterR, 0],
[skirtInnerR, 0],
[skirtInnerR, -jointHeight]
]);
intersection()
{
translate([0, 0, -jointHeight])
cylinder(h=jointHeight, r=skirtInnerR + collarThreadDepth + 1);
translate([0, 0, -jointHeight])
screwThread(skirtInnerR,
0,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
}
}
Bague d’étanchéité sur le col du bidon. Un petit anneau intérieur qui vient sceller contre la lèvre du col du bidon pendant le vissage (avant que le joint fibre plat ne prenne le relais).
Stries extérieures. Cannelures verticales espacées régulièrement sur la paroi extérieure, pour la préhension pendant le vissage.
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
union()
{
capThread();
rotate_extrude(convexity=10)
polygon(points=[
[innerDiameter/2, 0],
[innerDiameter/2+wallThickness, 0],
[innerDiameter/2+wallThickness, innerDepth+wallThickness],
[innerDiameter/2, innerDepth+wallThickness]
]);
rotate_extrude(convexity=10)
polygon(points=[
[jointPlugInnerR, 0],
[skirtInnerR, 0],
[innerDiameter/2, wallThickness],
[jointPlugInnerR, wallThickness]
]);
translate([0,0,-jointHeight])
difference()
{
cylinder(h=jointHeight, r=jointPlugR);
cylinder(h=jointHeight, r=jointPlugInnerR);
}
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[skirtOuterR, -jointHeight],
[skirtOuterR, 0],
[skirtInnerR, 0],
[skirtInnerR, -jointHeight]
]);
intersection()
{
translate([0, 0, -jointHeight])
cylinder(h=jointHeight, r=skirtInnerR + collarThreadDepth + 1);
translate([0, 0, -jointHeight])
screwThread(skirtInnerR,
0,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
}
}
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
}
Bouchon avec manchon MIEUXA (S 40/41)
precision=3;
wallThickness=2;
innerDiameter=42;
innerDepth=15;
threadPitch=3.50;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=1.5;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=7;
sealThickness=1.2;
sealGap=3.6;
funnelSlope=0.68;
funnelGenLength=70;
funnelHoleDiameter=39;
funnelHeight=funnelGenLength/sqrt(funnelSlope*funnelSlope+1);
funnelTopDiameter=2*(funnelSlope*funnelHeight+funnelHoleDiameter/2);
jointWall=2.5;
jointPlugR=funnelHoleDiameter/2-0.3;
jointPlugInnerR=jointPlugR-jointWall;
gasketID=30;
gasketOD=38;
gasketThickness=2.0;
gasketCompression=0.3;
gasketSeatClearance=0.2;
gasketRetentionLip=0.5;
collarThreadPitch=2.0; // mm/turn — pas du filetage
collarThreadTurns=2; // tours d'engagement
collarThreadStarts=1; // single-start
collarThreadDepth=1.0; // profondeur radiale du filet (mm)
collarThreadWidth=1.2; // largeur axiale du profil de filet (mm)
collarSkirtClearance=0.3; // jeu radial entre crête mâle et flanc lisse de la jupe (mm)
collarGrooveDepthExtra=0.5; // surcreusement de la gorge sous le mur du collet (mm)
collarGrooveAxialExtra=0.5; // jeu axial entre filet femelle et plancher de la gorge (mm)
collarThreadAboveGasket=3.7; // hauteur du pied du filet mâle au-dessus du joint (mm)
collarWall=2.5; // épaisseur paroi du collet (mm)
collarDiskGap=1.0; // jeu axial disque bouchon / haut du collet (mm)
// Rayons
jointBoreR=gasketOD/2+gasketSeatClearance;
collarFloorHoleR=gasketID/2-gasketRetentionLip;
collarOuterR=jointBoreR+collarWall;
// Crête mâle au-dessus du mur du collet ; flanc lisse de la jupe au-delà,
// décalé d'un jeu collarSkirtClearance pour que les deux crêtes
// s'engagent sans frotter sur la paroi opposée.
threadCrestR=collarOuterR+collarThreadDepth;
skirtInnerR=threadCrestR+collarSkirtClearance;
skirtOuterR=skirtInnerR+jointWall;
femaleCrestR=skirtInnerR-collarThreadDepth;
// Gorge surcreusée par rapport au mur du collet pour absorber la
// crête du filet femelle quand celle-ci y descend en fin de course.
collarGrooveInnerR=collarOuterR-collarGrooveDepthExtra;
// Cotes axiales
collarThreadAxialExtent=collarThreadPitch*collarThreadTurns;
// Plafond de la gorge = pied du filet mâle. Au moment où le filet
// femelle atteint cette cote en descendant, le manchon a écrasé
// le joint de gasketCompression.
collarGrooveTopZ=wallThickness+gasketThickness+collarThreadAboveGasket;
collarThreadBottomZ=collarGrooveTopZ;
collarThreadTopZ=collarThreadBottomZ+collarThreadAxialExtent;
collarHeight=collarThreadTopZ;
collarGrooveBottomZ=collarGrooveTopZ-collarThreadAxialExtent-collarGrooveAxialExtra;
// Hauteur du manchon — cote du bottom de la jupe sous le disque du
// bouchon, calée sur la compression du joint à l'arrêt.
jointHeight=collarHeight+collarDiskGap-(wallThickness+gasketThickness-gasketCompression);
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
rotate_extrude(convexity=10)
polygon(points=[
[innerDiameter/2, 0],
[innerDiameter/2+wallThickness, 0],
[innerDiameter/2+wallThickness, innerDepth+wallThickness],
[innerDiameter/2, innerDepth+wallThickness]
]);
rotate_extrude(convexity=10)
polygon(points=[
[jointPlugInnerR, 0],
[skirtInnerR, 0],
[innerDiameter/2, wallThickness],
[jointPlugInnerR, wallThickness]
]);
translate([0,0,-jointHeight])
difference()
{
cylinder(h=jointHeight, r=jointPlugR);
cylinder(h=jointHeight, r=jointPlugInnerR);
}
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[skirtOuterR, -jointHeight],
[skirtOuterR, 0],
[skirtInnerR, 0],
[skirtInnerR, -jointHeight]
]);
intersection()
{
translate([0, 0, -jointHeight])
cylinder(h=jointHeight, r=skirtInnerR + collarThreadDepth + 1);
translate([0, 0, -jointHeight])
screwThread(skirtInnerR,
0,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
}
}
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
}
Bouchon avec manchon Netto/Ardea (custom d=40, pas=4.0)
precision=3;
wallThickness=2;
innerDiameter=40;
innerDepth=15;
threadPitch=4.00;
threadDepth=1.25;
threadWidth=1.3;
threadTurns=3;
threads=1;
threadStart=1.3;
ridges=25;
sealHeight=8;
sealThickness=1.0;
sealGap=3.05;
funnelSlope=0.68;
funnelGenLength=70;
funnelHoleDiameter=39;
funnelHeight=funnelGenLength/sqrt(funnelSlope*funnelSlope+1);
funnelTopDiameter=2*(funnelSlope*funnelHeight+funnelHoleDiameter/2);
jointWall=2.5;
jointPlugR=funnelHoleDiameter/2-0.3;
jointPlugInnerR=jointPlugR-jointWall;
gasketID=30;
gasketOD=38;
gasketThickness=2.0;
gasketCompression=0.3;
gasketSeatClearance=0.2;
gasketRetentionLip=0.5;
collarThreadPitch=2.0; // mm/turn — pas du filetage
collarThreadTurns=2; // tours d'engagement
collarThreadStarts=1; // single-start
collarThreadDepth=1.0; // profondeur radiale du filet (mm)
collarThreadWidth=1.2; // largeur axiale du profil de filet (mm)
collarSkirtClearance=0.3; // jeu radial entre crête mâle et flanc lisse de la jupe (mm)
collarGrooveDepthExtra=0.5; // surcreusement de la gorge sous le mur du collet (mm)
collarGrooveAxialExtra=0.5; // jeu axial entre filet femelle et plancher de la gorge (mm)
collarThreadAboveGasket=3.7; // hauteur du pied du filet mâle au-dessus du joint (mm)
collarWall=2.5; // épaisseur paroi du collet (mm)
collarDiskGap=1.0; // jeu axial disque bouchon / haut du collet (mm)
// Rayons
jointBoreR=gasketOD/2+gasketSeatClearance;
collarFloorHoleR=gasketID/2-gasketRetentionLip;
collarOuterR=jointBoreR+collarWall;
// Crête mâle au-dessus du mur du collet ; flanc lisse de la jupe au-delà,
// décalé d'un jeu collarSkirtClearance pour que les deux crêtes
// s'engagent sans frotter sur la paroi opposée.
threadCrestR=collarOuterR+collarThreadDepth;
skirtInnerR=threadCrestR+collarSkirtClearance;
skirtOuterR=skirtInnerR+jointWall;
femaleCrestR=skirtInnerR-collarThreadDepth;
// Gorge surcreusée par rapport au mur du collet pour absorber la
// crête du filet femelle quand celle-ci y descend en fin de course.
collarGrooveInnerR=collarOuterR-collarGrooveDepthExtra;
// Cotes axiales
collarThreadAxialExtent=collarThreadPitch*collarThreadTurns;
// Plafond de la gorge = pied du filet mâle. Au moment où le filet
// femelle atteint cette cote en descendant, le manchon a écrasé
// le joint de gasketCompression.
collarGrooveTopZ=wallThickness+gasketThickness+collarThreadAboveGasket;
collarThreadBottomZ=collarGrooveTopZ;
collarThreadTopZ=collarThreadBottomZ+collarThreadAxialExtent;
collarHeight=collarThreadTopZ;
collarGrooveBottomZ=collarGrooveTopZ-collarThreadAxialExtent-collarGrooveAxialExtra;
// Hauteur du manchon — cote du bottom de la jupe sous le disque du
// bouchon, calée sur la compression du joint à l'arrêt.
jointHeight=collarHeight+collarDiskGap-(wallThickness+gasketThickness-gasketCompression);
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
union()
{
capThread();
rotate_extrude(convexity=10)
polygon(points=[
[innerDiameter/2, 0],
[innerDiameter/2+wallThickness, 0],
[innerDiameter/2+wallThickness, innerDepth+wallThickness],
[innerDiameter/2, innerDepth+wallThickness]
]);
rotate_extrude(convexity=10)
polygon(points=[
[jointPlugInnerR, 0],
[skirtInnerR, 0],
[innerDiameter/2, wallThickness],
[jointPlugInnerR, wallThickness]
]);
translate([0,0,-jointHeight])
difference()
{
cylinder(h=jointHeight, r=jointPlugR);
cylinder(h=jointHeight, r=jointPlugInnerR);
}
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[skirtOuterR, -jointHeight],
[skirtOuterR, 0],
[skirtInnerR, 0],
[skirtInnerR, -jointHeight]
]);
intersection()
{
translate([0, 0, -jointHeight])
cylinder(h=jointHeight, r=skirtInnerR + collarThreadDepth + 1);
translate([0, 0, -jointHeight])
screwThread(skirtInnerR,
0,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
}
}
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
}
phone-stl "${stl}"
Pièce de fermeture
Pièce qui prend la place de l’entonnoir sur un bidon plein. Une fois le bidon rempli, on déclipse l’entonnoir et on clipse cette pièce à sa place : le bidon est scellé et peut être stocké sans avoir à toucher au bouchon fileté qui reste vissé en permanence.
Réutilisation intégrale du collet de l’entonnoir. Même coque
(collar-shell), même filetage mâle (collar-male-thread), même
gorge annulaire (collar-groove). Seul le plancher change : plein
au lieu d’être percé, puisque la raison d’être est précisément de
couper le passage de l’eau.
Ce qu’on enlève par rapport à funnel-collar :
- le trou central de
collar-floor(plus besoin de laisser passer l’eau) ; collar-floor-chamfer(existait pour ponter le trou vers la paroi intérieure du cône — sans cône ni trou, plus de porte-à-faux à supporter à l’impression).
Ce qu’on garde inchangé : le joint fibre plat 30×38×2 vient toujours se poser sur le plancher du collet et est écrasé par le manchon du bouchon comme côté entonnoir. Le plancher plein ajoute juste une seconde barrière : même si le joint fuit légèrement (bidon couché, secoué au stockage), l’eau ne passe pas.
Pièce unique MIEUXA/Netto : comme l’entonnoir, elle ne porte pas
de filetage bouteille — son alésage est commun aux deux standards
de col, et le filetage mâle qu’elle porte (côté bouchon-entonnoir)
est défini dans thread-params indépendamment du standard.
cylinder(h=wallThickness, r=jointBoreR);
difference()
{
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
cylinder(h=wallThickness, r=jointBoreR);
}
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
}
Chaîne noweb : funnel-globals pour satisfaire wallThickness
(utilisé par closing-floor et par collarGrooveTopZ dans
joint-geometry) et threadTurns (utilisé par cap-computed
pour calculer segments) ; joint-params pour la géométrie du
collet et du filetage ; cap-computed pour les presets de
maillage ; screw-thread pour le module générique appelé par
collar-male-thread. Pas besoin de funnel-params (ni
funnel-cone ni collar-floor-chamfer ne sont appelés).
precision=3;
wallThickness=2;
threadTurns=1;
jointWall=2.5;
jointPlugR=funnelHoleDiameter/2-0.3;
jointPlugInnerR=jointPlugR-jointWall;
gasketID=30;
gasketOD=38;
gasketThickness=2.0;
gasketCompression=0.3;
gasketSeatClearance=0.2;
gasketRetentionLip=0.5;
collarThreadPitch=2.0; // mm/turn — pas du filetage
collarThreadTurns=2; // tours d'engagement
collarThreadStarts=1; // single-start
collarThreadDepth=1.0; // profondeur radiale du filet (mm)
collarThreadWidth=1.2; // largeur axiale du profil de filet (mm)
collarSkirtClearance=0.3; // jeu radial entre crête mâle et flanc lisse de la jupe (mm)
collarGrooveDepthExtra=0.5; // surcreusement de la gorge sous le mur du collet (mm)
collarGrooveAxialExtra=0.5; // jeu axial entre filet femelle et plancher de la gorge (mm)
collarThreadAboveGasket=3.7; // hauteur du pied du filet mâle au-dessus du joint (mm)
collarWall=2.5; // épaisseur paroi du collet (mm)
collarDiskGap=1.0; // jeu axial disque bouchon / haut du collet (mm)
// Rayons
jointBoreR=gasketOD/2+gasketSeatClearance;
collarFloorHoleR=gasketID/2-gasketRetentionLip;
collarOuterR=jointBoreR+collarWall;
// Crête mâle au-dessus du mur du collet ; flanc lisse de la jupe au-delà,
// décalé d'un jeu collarSkirtClearance pour que les deux crêtes
// s'engagent sans frotter sur la paroi opposée.
threadCrestR=collarOuterR+collarThreadDepth;
skirtInnerR=threadCrestR+collarSkirtClearance;
skirtOuterR=skirtInnerR+jointWall;
femaleCrestR=skirtInnerR-collarThreadDepth;
// Gorge surcreusée par rapport au mur du collet pour absorber la
// crête du filet femelle quand celle-ci y descend en fin de course.
collarGrooveInnerR=collarOuterR-collarGrooveDepthExtra;
// Cotes axiales
collarThreadAxialExtent=collarThreadPitch*collarThreadTurns;
// Plafond de la gorge = pied du filet mâle. Au moment où le filet
// femelle atteint cette cote en descendant, le manchon a écrasé
// le joint de gasketCompression.
collarGrooveTopZ=wallThickness+gasketThickness+collarThreadAboveGasket;
collarThreadBottomZ=collarGrooveTopZ;
collarThreadTopZ=collarThreadBottomZ+collarThreadAxialExtent;
collarHeight=collarThreadTopZ;
collarGrooveBottomZ=collarGrooveTopZ-collarThreadAxialExtent-collarGrooveAxialExtra;
// Hauteur du manchon — cote du bottom de la jupe sous le disque du
// bouchon, calée sur la compression du joint à l'arrêt.
jointHeight=collarHeight+collarDiskGap-(wallThickness+gasketThickness-gasketCompression);
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
difference()
{
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
cylinder(h=wallThickness, r=jointBoreR);
}
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
}
Ajout d’un tuyau de trop-plein et d’un coude
Ajoute à l’entonnoir un petit tuyau de trop-plein incliné qui sort par la paroi du cône à 45° vers le bas-réel. Quand le bidon est plein, l’excédent d’eau monte dans l’entonnoir jusqu’à ce qu’il rencontre l’embouchure de ce tuyau, y tombe par gravité, et est évacué par la sortie extérieure. Une rallonge cylindrique imprimée séparément s’emboîte à friction sur le bout du tuyau pour prolonger l’évacuation jusqu’au bidon suivant. Une troisième pièce, le coude, s’emboîte à son tour sur la rallonge et redresse le flux à la verticale pour qu’il tombe d’aplomb dans l’ouverture du bidon suivant.
Le tuyau traverse la paroi du cône et est entièrement orienté à 45° : impression sans support, drainage par gravité assuré.
Stratégie géométrique
Le tuyau intégré est modélisé comme deux cylindres coaxiaux inclinés :
- le cylindre extérieur (paroi du tuyau) est la matière qu’on ajoute ;
- le cylindre intérieur (canal creux) est la matière qu’on retire.
Le cylindre intérieur joue d’ailleurs deux rôles simultanément dans
l’opération booléenne : il creuse le canal dans la paroi et perce le
trou dans la paroi du cône là où le tuyau la traverse — une seule
opération difference() pour les deux.
Le cylindre extérieur est lui aussi recoupé par la cavité intérieure du cône, pour ne garder que la portion utile (paroi du cône et au-delà) sans intrusion dans l’intérieur du cône.
Paramètres du tuyau
Diamètre de canal 14mm, paroi 1mm (↔ Ø extérieur 16mm), longueur 40mm
depuis l’axe. Le centre du pivot est à pipeCenterZ=-34 (en
coordonnées code ; plus négatif = plus haut en réel). L’angle de pente
est donné par pipeSlope=1 (rise/run = tan de l’angle ; 1 → 45°).
pipeHoleDiameter=14;
pipeWallThickness=1;
pipeLength=50;
pipeCenterZ=-34;
pipeSlope=1;
Module pipe() partagé
Un seul module factorise les deux cylindres (paroi et canal). Il prend
un rayon en argument, et optionnellement un extra (rallongement aux
deux extrémités) pour garantir un overlap propre en opération booléenne
— on l’utilise sur le canal pour éviter les faces coplanaires qui font
tousser CGAL.
La chaîne de transformations : translate au pivot, rotate autour de Y pour l’inclinaison, translate local en X pour l’overlap, rotate 90° Y pour coucher le cylindre le long de X.
module pipe(r, extra=0)
{
translate([0,0,pipeCenterZ])
rotate([0,-atan(pipeSlope),0])
translate([-extra,0,0])
rotate([0,90,0])
cylinder(h=pipeLength+2*extra, r=r);
}
Clippage de la paroi par la cavité du cône
La paroi du tuyau commence à X=0 (sur l’axe du cône) : elle traverse donc la moitié intérieure du cône avant de sortir par la paroi. On ne veut garder que la portion qui est dans la paroi et à l’extérieur, pas celle qui pénètre l’intérieur. On soustrait donc la cavité intérieure du cône de la paroi du tuyau.
Deux subtilités :
- le coin interne de la paroi (côté axe, côté haut-réel) plonge au-dessus du haut du cône matière — au-delà de où la cavité s’arrête normalement. On étend le cône de soustraction de 15mm au-delà du haut, en gardant la même pente, pour clipper ce débordement ;
- au-dessus du bas-réel du cône (i.e. côté sortie étroite), le sommet
incliné de la paroi pourrait dépasser dans l’ouverture. Un cylindre
droit de rayon
funnelHoleDiameter/2prolonge la soustraction au-dessus pour le couvrir.
union()
{
translate([0,0,-funnelHeight-15])
cylinder(h=funnelHeight+15.1,
r1=funnelTopDiameter/2 + 15*(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight),
r2=funnelHoleDiameter/2);
translate([0,0,-0.1])
cylinder(h=20,r=funnelHoleDiameter/2);
}
Entonnoir avec tuyau intégré
Pièce finale côté entonnoir : le cône de l’entonnoir + le collet
fileté + le petit tuyau de trop-plein. Une seule pièce commune
aux deux bidons : les cotes du cône sont fixées en constantes dans
funnel-params (indépendantes de innerDiameter), et tout le
collet/filetage dérive déjà de constantes. Le manchon du bouchon
(qui diffère entre MIEUXA et Netto) s’adapte à l’alésage commun du
collet. Joint fibre plat 30x38x2mm déposé sur le plancher du collet
avant vissage de l’entonnoir sur le filet mâle.
Assemblage final en un difference() : cône + collet + (paroi du
tuyau recoupée par la cavité), le tout moins le canal (qui perce à la
fois la paroi du cône au passage et l’intérieur de la paroi du
tuyau).
pipeHoleDiameter=14;
pipeWallThickness=1;
pipeLength=50;
pipeCenterZ=-34;
pipeSlope=1;
module pipe(r, extra=0)
{
translate([0,0,pipeCenterZ])
rotate([0,-atan(pipeSlope),0])
translate([-extra,0,0])
rotate([0,90,0])
cylinder(h=pipeLength+2*extra, r=r);
}
difference()
{
union()
{
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
difference()
{
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
difference()
{
cylinder(h=wallThickness,r=jointBoreR);
cylinder(h=wallThickness*3,r=collarFloorHoleR,center=true);
}
collarChamferAngle=50;
funnelInnerSlope=(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight);
chamferDrPerDz=1/tan(collarChamferAngle);
chamferDepth=(funnelHoleDiameter/2-collarFloorHoleR)/(chamferDrPerDz-funnelInnerSlope);
rotate_extrude(convexity=10)
polygon(points=[
[collarFloorHoleR,0],
[funnelHoleDiameter/2,0],
[funnelHoleDiameter/2+funnelInnerSlope*chamferDepth,-chamferDepth]
]);
}
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
}
difference()
{
pipe(pipeHoleDiameter/2+pipeWallThickness);
union()
{
translate([0,0,-funnelHeight-15])
cylinder(h=funnelHeight+15.1,
r1=funnelTopDiameter/2 + 15*(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight),
r2=funnelHoleDiameter/2);
translate([0,0,-0.1])
cylinder(h=20,r=funnelHoleDiameter/2);
}
}
}
pipe(pipeHoleDiameter/2, extra=0.1);
}
wallThickness et threadTurns sont définis ici pour satisfaire les
blocs noweb-inclus (joint-params, cap-computed, screw-thread)
qui les attendent, mais leurs valeurs n’influencent pas le rendu du
funnel — le filetage n’est pas appelé, et wallThickness (identique
entre MIEUXA et Netto de toute façon) sert uniquement à l’épaisseur
du plancher et à la position axiale de la gorge du collet.
wallThickness=2;
threadTurns=1;
precision=3;
wallThickness=2;
threadTurns=1;
funnelSlope=0.68;
funnelGenLength=70;
funnelHoleDiameter=39;
funnelHeight=funnelGenLength/sqrt(funnelSlope*funnelSlope+1);
funnelTopDiameter=2*(funnelSlope*funnelHeight+funnelHoleDiameter/2);
jointWall=2.5;
jointPlugR=funnelHoleDiameter/2-0.3;
jointPlugInnerR=jointPlugR-jointWall;
gasketID=30;
gasketOD=38;
gasketThickness=2.0;
gasketCompression=0.3;
gasketSeatClearance=0.2;
gasketRetentionLip=0.5;
collarThreadPitch=2.0; // mm/turn — pas du filetage
collarThreadTurns=2; // tours d'engagement
collarThreadStarts=1; // single-start
collarThreadDepth=1.0; // profondeur radiale du filet (mm)
collarThreadWidth=1.2; // largeur axiale du profil de filet (mm)
collarSkirtClearance=0.3; // jeu radial entre crête mâle et flanc lisse de la jupe (mm)
collarGrooveDepthExtra=0.5; // surcreusement de la gorge sous le mur du collet (mm)
collarGrooveAxialExtra=0.5; // jeu axial entre filet femelle et plancher de la gorge (mm)
collarThreadAboveGasket=3.7; // hauteur du pied du filet mâle au-dessus du joint (mm)
collarWall=2.5; // épaisseur paroi du collet (mm)
collarDiskGap=1.0; // jeu axial disque bouchon / haut du collet (mm)
// Rayons
jointBoreR=gasketOD/2+gasketSeatClearance;
collarFloorHoleR=gasketID/2-gasketRetentionLip;
collarOuterR=jointBoreR+collarWall;
// Crête mâle au-dessus du mur du collet ; flanc lisse de la jupe au-delà,
// décalé d'un jeu collarSkirtClearance pour que les deux crêtes
// s'engagent sans frotter sur la paroi opposée.
threadCrestR=collarOuterR+collarThreadDepth;
skirtInnerR=threadCrestR+collarSkirtClearance;
skirtOuterR=skirtInnerR+jointWall;
femaleCrestR=skirtInnerR-collarThreadDepth;
// Gorge surcreusée par rapport au mur du collet pour absorber la
// crête du filet femelle quand celle-ci y descend en fin de course.
collarGrooveInnerR=collarOuterR-collarGrooveDepthExtra;
// Cotes axiales
collarThreadAxialExtent=collarThreadPitch*collarThreadTurns;
// Plafond de la gorge = pied du filet mâle. Au moment où le filet
// femelle atteint cette cote en descendant, le manchon a écrasé
// le joint de gasketCompression.
collarGrooveTopZ=wallThickness+gasketThickness+collarThreadAboveGasket;
collarThreadBottomZ=collarGrooveTopZ;
collarThreadTopZ=collarThreadBottomZ+collarThreadAxialExtent;
collarHeight=collarThreadTopZ;
collarGrooveBottomZ=collarGrooveTopZ-collarThreadAxialExtent-collarGrooveAxialExtra;
// Hauteur du manchon — cote du bottom de la jupe sous le disque du
// bouchon, calée sur la compression du joint à l'arrêt.
jointHeight=collarHeight+collarDiskGap-(wallThickness+gasketThickness-gasketCompression);
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
pipeHoleDiameter=14;
pipeWallThickness=1;
pipeLength=50;
pipeCenterZ=-34;
pipeSlope=1;
module pipe(r, extra=0)
{
translate([0,0,pipeCenterZ])
rotate([0,-atan(pipeSlope),0])
translate([-extra,0,0])
rotate([0,90,0])
cylinder(h=pipeLength+2*extra, r=r);
}
difference()
{
union()
{
translate([0,0,-funnelHeight])
difference()
{
cylinder(h=funnelHeight, r1=funnelTopDiameter/2+wallThickness, r2=funnelHoleDiameter/2+wallThickness);
translate([0,0,-0.1])
cylinder(h=funnelHeight+0.2, r1=funnelTopDiameter/2, r2=funnelHoleDiameter/2);
}
difference()
{
union()
{
rotate_extrude(convexity=10)
polygon(points=[
[jointBoreR, 0],
[collarOuterR, 0],
[collarOuterR, collarHeight],
[jointBoreR, collarHeight]
]);
screwThread(collarOuterR,
collarThreadBottomZ,
collarThreadPitch,
collarThreadDepth,
collarThreadWidth,
collarThreadTurns,
collarThreadStarts,
smoothness,
facets);
difference()
{
cylinder(h=wallThickness,r=jointBoreR);
cylinder(h=wallThickness*3,r=collarFloorHoleR,center=true);
}
collarChamferAngle=50;
funnelInnerSlope=(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight);
chamferDrPerDz=1/tan(collarChamferAngle);
chamferDepth=(funnelHoleDiameter/2-collarFloorHoleR)/(chamferDrPerDz-funnelInnerSlope);
rotate_extrude(convexity=10)
polygon(points=[
[collarFloorHoleR,0],
[funnelHoleDiameter/2,0],
[funnelHoleDiameter/2+funnelInnerSlope*chamferDepth,-chamferDepth]
]);
}
rotate_extrude(convexity=10)
polygon(points=[
[collarGrooveInnerR, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveBottomZ],
[collarOuterR + 1, collarGrooveTopZ],
[collarGrooveInnerR, collarGrooveTopZ]
]);
}
difference()
{
pipe(pipeHoleDiameter/2+pipeWallThickness);
union()
{
translate([0,0,-funnelHeight-15])
cylinder(h=funnelHeight+15.1,
r1=funnelTopDiameter/2 + 15*(funnelTopDiameter-funnelHoleDiameter)/(2*funnelHeight),
r2=funnelHoleDiameter/2);
translate([0,0,-0.1])
cylinder(h=20,r=funnelHoleDiameter/2);
}
}
}
pipe(pipeHoleDiameter/2, extra=0.1);
}
Réglages slicer (Bambu Studio, A1 mini)
À l’impression, l’intérieur du cône se retrouve envahi de fils qui ressemblent à du stringing classique, mais le vrai diagnostic est double. La géométrie SCAD est saine — tout se passe côté slicer.
Cause 1 : position de la couture sur le bord du trou du tuyau
Sur les couches qui traversent le trou du tuyau, le pourtour du
cône est coupé en deux — un mur côté “gauche” du trou et un mur
côté “droit”. Bambu Studio place la couture (seam) par défaut
à la position la plus proche du point de départ précédent — ce
qui tombe souvent pile au bord du trou, là où le mur est
extrêmement fin voire absent.
Conséquence : au démarrage du périmètre, le filament est déposé sur rien (pas de mur en-dessous pour accrocher la première goutte). La buse tourne en continuant à extruder un “collier” censé reposer sur la couche du dessous — mais comme le départ n’a pas pris, tout le début du périmètre se fait tirer vers l’intérieur de la cavité. Ça ne se rattrape que quand la buse rejoint une zone de mur franc plus loin.
Fix principal :
- Quality → Seam → Seam position : passer de Aligned (défaut) à Rear. Force la couture à l’arrière du modèle — à positionner dans la plate opposé à la sortie du tuyau. Règle le problème en un réglage.
Alternatives si *Rear ne colle pas avec ton orientation sur le plateau* :
- Painted Seam : outil “Seam painting” dans Bambu Studio → on peint manuellement la couture sur le dos du cône, loin du trou.
- Scarf joint seam (option récente Bambu Studio) : fade progressivement l’extrusion au début du périmètre. Masque la couture et améliore l’accroche. Plus un pansement qu’un fix — la cause racine (position) reste.
Cause 2 : stringing pendant les travel moves
Indépendamment du seam, le trou du tuyau coupe le pourtour en deux segments à chaque couche concernée. Le printhead doit donc traverser la cavité pour passer d’un segment à l’autre, et le PLA coule pendant le travel. Sur A1 mini direct drive, le stringing est normalement rare mais la géométrie le favorise ici.
Ordre de priorité des settings à activer :
- Quality → Travel → “Avoid crossing walls” : coche. Force le printhead à contourner le pourtour au lieu de couper à travers la cavité. Rallonge le G-code mais c’est ce qu’on veut ici. Suffit seul dans la plupart des cas.
- Filament → Setting Overrides → Retraction length : défaut 0.8mm sur A1 mini → 1.0-1.2mm si le #1 ne suffit pas.
- Quality → Travel → “Z hop when retracting” : Normal lift, 0.4mm. Évite que la buse accroche les fils déjà déposés.
- Quality → Travel → “Wipe while retracting” : coche.
- Filament → Temperature → Nozzle temp : 220 → 210°C (PLA Bambu Basic). Dernier recours.
Protocole de test
- Appliquer Seam position = Rear en premier (cause 1 = diagnostic principal). Orienter le funnel dans la plate avec le tuyau face à soi pour que “rear” tombe à l’opposé.
- Si fils persistent, ajouter Avoid crossing walls.
- Si encore des fils, remonter la liste cause 2 un setting à la fois.
- En cas de doute, Bambu Studio → Preview → Layer slider pour inspecter les paths au niveau du trou du tuyau (print_Z ~10-22mm) : si les lignes d’extrusion passent par le vide, c’est la cause 1 ; si ce sont les travels, c’est la cause 2.
Rallonge
Cylindre creux qui coulisse à friction sur le bout du petit tuyau
sortant de l’entonnoir, pour rallonger l’évacuation jusqu’au bidon
suivant. Son Ø intérieur = Ø extérieur du petit tuyau +
pipeTolerance (jeu radial d’emboîtement). Sa paroi reprend la même
épaisseur pipeWallThickness que celle du petit tuyau. Identique
pour MIEUXA et Netto (le petit tuyau a les mêmes dimensions dans les
deux entonnoirs).
Pour améliorer l’accroche, on ajoute un bump radial sur la face
intérieure de la rallonge, près de son bord : une petite crête
annulaire qui pince le petit tuyau en friction quand la rallonge est
glissée dessus. Profondeur légèrement supérieure à pipeTolerance
pour créer une interférence contrôlée (compression élastique du
plastique pendant l’insertion, puis retenue).
pipeTolerance=0.3;
extensionInnerR=pipeHoleDiameter/2+pipeWallThickness+pipeTolerance;
extensionOuterR=extensionInnerR+pipeWallThickness;
extensionLength=90;
extensionBumpDepth=0.5; // pincement radial du bump (≈ pipeTolerance + 0.2 d'interférence)
extensionBumpWidth=1.5; // largeur axiale du bump
extensionBumpZ=extensionLength-2; // position axiale (près du bord haut)
difference()
{
cylinder(h=extensionLength,r=extensionOuterR);
rotate_extrude()
polygon(points=[
[0, -0.1],
[extensionInnerR, -0.1],
[extensionInnerR, extensionBumpZ-extensionBumpWidth/2],
[extensionInnerR-extensionBumpDepth, extensionBumpZ],
[extensionInnerR, extensionBumpZ+extensionBumpWidth/2],
[extensionInnerR, extensionLength+0.1],
[0, extensionLength+0.1]
]);
}
precision=3;
pipeHoleDiameter=14;
pipeWallThickness=1;
pipeLength=50;
pipeCenterZ=-34;
pipeSlope=1;
pipeTolerance=0.3;
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
extensionInnerR=pipeHoleDiameter/2+pipeWallThickness+pipeTolerance;
extensionOuterR=extensionInnerR+pipeWallThickness;
extensionLength=90;
extensionBumpDepth=0.5; // pincement radial du bump (≈ pipeTolerance + 0.2 d'interférence)
extensionBumpWidth=1.5; // largeur axiale du bump
extensionBumpZ=extensionLength-2; // position axiale (près du bord haut)
difference()
{
cylinder(h=extensionLength,r=extensionOuterR);
rotate_extrude()
polygon(points=[
[0, -0.1],
[extensionInnerR, -0.1],
[extensionInnerR, extensionBumpZ-extensionBumpWidth/2],
[extensionInnerR-extensionBumpDepth, extensionBumpZ],
[extensionInnerR, extensionBumpZ+extensionBumpWidth/2],
[extensionInnerR, extensionLength+0.1],
[0, extensionLength+0.1]
]);
}
Coude
Pièce finale de la chaîne d’évacuation : un tube creux coudé qui se glisse sur le bout de la rallonge — exactement comme la rallonge se glisse sur le bout du tuyau sortant de l’entonnoir — et qui redresse à la verticale le flux sortant à 45°. L’eau tombe alors d’aplomb dans l’ouverture du bidon suivant.
Raison d’être une pièce séparée :
- l’entonnoir doit rester imprimable sans support, donc le tuyau qui en sort reste droit à 45° ;
- le coude, imprimé seul, se positionne au slicer dans la meilleure orientation (segment de sortie vertical sur le plateau, arc et segment d’entrée en l’air, tous les angles ≤ 45°) — pas de support nécessaire.
Emboîtement : le Ø intérieur du segment d’entrée est calé sur le Ø
extérieur de la rallonge + pipeTolerance, avec le même bump radial
de retenue que celui de la rallonge (recopié du pattern
extension-body). Une fois emboîté, le coude tourne librement autour
de l’axe de la rallonge — ce qui permet d’ajuster à la main la
direction dans laquelle tombe la sortie.
Géométrie. Trois segments unifiés puis creusés par difference() :
- segment d’entrée cylindrique, orienté selon l’axe du tuyau (45°) ;
- arc de tore sur 45° (
rotate_extrude(angle=45)d’un disque à distanceelbowBendRadiusde l’axe), qui fait pivoter le flux ; - segment de sortie cylindrique vertical, court (quelques cm, juste ce qu’il faut pour laisser l’eau se détacher proprement).
Le canal intérieur est tracé par une seconde union au rayon intérieur,
rallongée de 0.1mm à chaque extrémité (pattern extra=0.1 déjà
utilisé pour le module pipe()) pour éviter les faces coplanaires
qui font tousser CGAL. Sur le segment d’entrée, le canal intérieur est
tracé par un rotate_extrude d’un polygone qui porte le bump radial
de retenue.
elbowBendRadius=20;
elbowInletLength=30;
elbowOutletLength=40;
elbowBumpDepth=0.5;
elbowBumpWidth=1.5;
elbowAngle=atan(pipeSlope);
elbowInletInnerR=extensionOuterR+pipeTolerance;
elbowInletOuterR=elbowInletInnerR+pipeWallThickness;
elbowBumpZ=elbowInletLength-2;
difference()
{
union()
{
translate([elbowBendRadius,0,-elbowOutletLength])
cylinder(h=elbowOutletLength+0.1,r=elbowInletOuterR);
rotate([90,0,0])
rotate_extrude(angle=elbowAngle,convexity=10)
translate([elbowBendRadius,0])
circle(r=elbowInletOuterR);
translate([elbowBendRadius*cos(elbowAngle),0,elbowBendRadius*sin(elbowAngle)])
rotate([0,-elbowAngle,0])
translate([0,0,-0.1])
cylinder(h=elbowInletLength+0.1,r=elbowInletOuterR);
}
union()
{
translate([elbowBendRadius,0,-elbowOutletLength-0.1])
cylinder(h=elbowOutletLength+0.2,r=elbowInletInnerR);
rotate([90,0,0])
rotate_extrude(angle=elbowAngle,convexity=10)
translate([elbowBendRadius,0])
circle(r=elbowInletInnerR);
translate([elbowBendRadius*cos(elbowAngle),0,elbowBendRadius*sin(elbowAngle)])
rotate([0,-elbowAngle,0])
rotate_extrude(convexity=10)
polygon(points=[
[0,-0.1],
[elbowInletInnerR,-0.1],
[elbowInletInnerR,elbowBumpZ-elbowBumpWidth/2],
[elbowInletInnerR-elbowBumpDepth,elbowBumpZ],
[elbowInletInnerR,elbowBumpZ+elbowBumpWidth/2],
[elbowInletInnerR,elbowInletLength+0.1],
[0,elbowInletLength+0.1]
]);
}
}
precision=3;
pipeHoleDiameter=14;
pipeWallThickness=1;
pipeLength=50;
pipeCenterZ=-34;
pipeSlope=1;
pipeTolerance=0.3;
elbowBendRadius=20;
elbowInletLength=30;
elbowOutletLength=40;
elbowBumpDepth=0.5;
elbowBumpWidth=1.5;
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
extensionInnerR=pipeHoleDiameter/2+pipeWallThickness+pipeTolerance;
extensionOuterR=extensionInnerR+pipeWallThickness;
elbowAngle=atan(pipeSlope);
elbowInletInnerR=extensionOuterR+pipeTolerance;
elbowInletOuterR=elbowInletInnerR+pipeWallThickness;
elbowBumpZ=elbowInletLength-2;
difference()
{
union()
{
translate([elbowBendRadius,0,-elbowOutletLength])
cylinder(h=elbowOutletLength+0.1,r=elbowInletOuterR);
rotate([90,0,0])
rotate_extrude(angle=elbowAngle,convexity=10)
translate([elbowBendRadius,0])
circle(r=elbowInletOuterR);
translate([elbowBendRadius*cos(elbowAngle),0,elbowBendRadius*sin(elbowAngle)])
rotate([0,-elbowAngle,0])
translate([0,0,-0.1])
cylinder(h=elbowInletLength+0.1,r=elbowInletOuterR);
}
union()
{
translate([elbowBendRadius,0,-elbowOutletLength-0.1])
cylinder(h=elbowOutletLength+0.2,r=elbowInletInnerR);
rotate([90,0,0])
rotate_extrude(angle=elbowAngle,convexity=10)
translate([elbowBendRadius,0])
circle(r=elbowInletInnerR);
translate([elbowBendRadius*cos(elbowAngle),0,elbowBendRadius*sin(elbowAngle)])
rotate([0,-elbowAngle,0])
rotate_extrude(convexity=10)
polygon(points=[
[0,-0.1],
[elbowInletInnerR,-0.1],
[elbowInletInnerR,elbowBumpZ-elbowBumpWidth/2],
[elbowInletInnerR-elbowBumpDepth,elbowBumpZ],
[elbowInletInnerR,elbowBumpZ+elbowBumpWidth/2],
[elbowInletInnerR,elbowInletLength+0.1],
[0,elbowInletLength+0.1]
]);
}
}
Abandon
Au final, le projet ne vaut pas le coup. La pile de problèmes techniques empilés les uns sur les autres dépasse largement le bénéfice attendu (remplir des bidons sans les déplacer) :
- STL non-manifolds. L’assemblage du bouchon plugged accumule des faces coplanaires entre le manchon, le disque et la bague d’étanchéité. CGAL produit des arêtes non-manifolds que le slicer doit réparer à l’aveugle, et chaque tentative de fix géométrique en déplace de nouvelles ailleurs.
- Difficultés d’impression. Le bord intérieur de la bague est en deçà du trou central du disque, donc il flotte au premier layer en plug-down. Toutes les corrections (chanfrein, embed par epsilon, extension du disque) introduisent soit un nouveau porte-à-faux, soit une nouvelle face coplanaire — on déplace le problème sans le résoudre.
- Étanchéité incertaine. Même si la pièce s’imprimait proprement, l’étanchéité combine un joint fibre, une bague imprimée pressant la lèvre du col, et un filetage à course limitée qui doit s’arrêter pile au bon moment. Trop de surfaces à calibrer, chacune sensible à ±0.1 mm d’impression. Le risque de fuite sur un bidon plein laissé plusieurs jours est réel.
Le coût (temps de design, itérations d’impression, calibration des cotes pour chaque standard de col) excède le gain pratique d’un remplissage en cascade. On revient au remplissage manuel séquentiel avec les bouchons d’origine.
Annexe
Dérivation des paramètres du cône
Les paramètres funnelSlope et funnelGenLength pilotent la géométrie
du cône. La génératrice d’un tronc de cône de hauteur h et de
différence de rayon Δr = r₁ − r₂ suit le théorème de Pythagore
appliqué au triangle rectangle (Δr, h) :
L = √( Δr² + h² ) avec Δr = funnelTopDiameter/2 − funnelHoleDiameter/2
Avec la contrainte de pente constante Δr / h = funnelSlope, on
substitue Δr = funnelSlope × h. En isolant h puis D, on obtient
les expressions directes des deux paramètres SCAD dérivés :
h = funnelGenLength / √(funnelSlope² + 1)
D = 2 × (funnelSlope × h + funnelHoleDiameter/2)
Modules SCAD partagés
Toutes les pièces finales (anneaux de test, bouchons filetés nus, bouchons avec entonnoir, entonnoirs séparés, pièces de fermeture) sont construites à partir d’un petit vocabulaire de primitives partagées définies ici une seule fois. L’objectif — remplir plusieurs bidons sans bouger — nécessite à terme :
- un filetage qui s’adapte au col du bidon cible (MIEUXA ou Netto) ;
- un corps de bouchon qui porte ce filetage et referme le col ;
- une bague d’étanchéité intérieure qui scelle contre la lèvre du col.
Chaque élément a son bloc paramétré ci-dessous, instancié par les pièces finales selon leurs besoins via noweb.
Résolution d’impression
precision sélectionne un preset de qualité de maillage ($fn,
nombre de facettes des extrusions, nombre de sphères par spire du
filetage). On tourne à precision=1 pour les anneaux de test (rapide,
un peu facetté mais suffisant pour confirmer un standard de filetage),
precision=3 pour les pièces définitives.
smoothness=(precision==1)?4:((precision==2)?6:20);
facets=(precision==1)?6:((precision==2)?20:50);
segments=facets*threadTurns;
$fn=max(20,facets);
Filetage (screwThread)
Module générique de filetage hélicoïdal — n’importe quel rayon,
n’importe quel pas, n’importe quel nombre de tours. Implémentation :
on discrétise la spire en petits segments de rotate_extrude d’un
profil elliptique (sphère écrasée en scale), placés tout au long
d’une hélice. Deux sphères aux extrémités pour arrondir le départ et
l’arrêt du filet (évite les angles vifs qui accrochent pendant le
vissage).
C’est le bloc critique du projet : c’est ce qui permet au bouchon
imprimé de se visser proprement sur le col du bidon du commerce. Les
paramètres (rayon, pas, profondeur, largeur, nombre de tours) sont
réglés par bidon cible dans params-mieuxa / params-netto.
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
Wrapper capThread()
Sucre syntaxique : capThread() instancie le module générique
screwThread avec tous les paramètres de filetage du bouchon
(innerDiameter, threadPitch, threadDepth, etc.) déjà câblés.
Évite de répéter la longue liste d’arguments chaque fois qu’un
bouchon en a besoin.
module screwThread(stR,stZ,stPitch,stDepth,stWidth,stTurns,stN,stSmooth,stFacets)
{
stSegs=stFacets*stTurns;
union()
{
for(t=[0:stN-1])
{
rotate([0,0,360*t/stN])
translate([stR,0,stZ])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
rotate([0,0,360*t/stN+stTurns*360])
translate([stR,0,stZ+stPitch*stN*stTurns])
scale([stDepth,stDepth,stWidth/2])sphere(r=1,$fn=stSmooth);
}
for(tw=[0:stSegs-1])
{
translate([0,0,stZ])
union()
{
for(t=[0:stN-1])
{
translate([0,0,stPitch*stN*tw/stSegs*stTurns])
rotate([0,0,360*(t/stN)+tw*360*stTurns/stSegs])
rotate([atan(stPitch*stN/(stR*2*3.1416)),0,0])
rotate_extrude(convexity=10,angle=360/stFacets,$fn=50)
translate([stR,0,0])
scale([stDepth,stWidth/2,0])circle(r=1,$fn=stSmooth);
}
}
}
}
}
module capThread()
{
screwThread(innerDiameter/2,wallThickness+innerDepth-threadStart-threadTurns*threadPitch*threads-threadWidth/2,threadPitch,threadDepth,threadWidth,threadTurns,threads,smoothness,facets);
}
Corps commun du bouchon (cap-body-common)
Éléments partagés par tous les bouchons fermés (bouchons nus, bouchons avec entonnoir) : le filetage, une paroi cylindrique externe qui porte le filetage, un chanfrein de raccord entre la paroi et le dessus, et des stries verticales extérieures pour la préhension au vissage. N’inclut pas le disque de fermeture du dessus (variable selon la variante — plein, percé pour recevoir un entonnoir, etc.) ni la bague d’étanchéité.
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
Bouchon nu (cap-body)
Bouchon complet fermé : cap-body-common + un disque plein de
fermeture + la bague d’étanchéité. C’est le bouchon “basique” qu’on
imprime pour boucher un bidon qu’on ne veut pas remplir. Utilisé dans
les bouchons de référence MIEUXA/Netto pour vérifier qu’un standard
de filetage donné s’adapte bien au col avant d’investir dans la
variante avec entonnoir.
union()
{
capThread();
difference()
{
union()
{
translate([0,0,innerDepth/2+wallThickness])
cylinder(h=innerDepth,r=(innerDiameter)/2+wallThickness,center=true);
translate([0,0,wallThickness])
rotate_extrude(convexity = 10)
translate([innerDiameter/2, 0, 0])
circle(r = wallThickness, $fn = smoothness);
}
cylinder(h=innerDepth*4,r=innerDiameter/2,center=true);
}
translate([0,0,wallThickness*1.5])
rotate([180,0,0])
rotate_extrude(convexity = 10)
translate([innerDiameter/2-wallThickness/2, 0, 0])
difference()
{
square(wallThickness,center=false);
circle(r = wallThickness/2, $fn = smoothness);
}
for(ridge=[0:ridges-1])
{
hull()
{
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,wallThickness*5/4])
sphere(r=wallThickness/4,$fn=smoothness);
rotate([0,0,360/ridges*ridge])
translate([innerDiameter/2+wallThickness,0,innerDepth+wallThickness*3/4])
sphere(r=wallThickness/4,$fn=smoothness);
}
}
translate([0,0,wallThickness/2])cylinder(h=wallThickness,r=innerDiameter/2,center=true);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}
Bague d’étanchéité (cap-joint)
Anneau intérieur qui descend du dessous du bouchon et qui vient
sceller contre la lèvre du col du bidon pendant le vissage du
bouchon sur le bidon. C’est l’étanchéité bouchon/bidon, distincte
du joint fibre plat qui, lui, assure l’étanchéité
bouchon/entonnoir à l’interface filetée. Dimensions réglées par
sealHeight, sealThickness et sealGap dans les params du
bidon.
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
Jupe de test pour la bague d’étanchéité
Petite pièce de test isolée : juste la bague d’étanchéité seule, avec
un plancher de base. Utile pour valider rapidement que les paramètres
sealHeight=/=sealThickness=/=sealGap donnent bien un ajustement
étanche sur le col, sans imprimer un bouchon entier.
$fn=50;
union()
{
// cylinder(h=wallThickness,r=innerDiameter/2-sealGap);
difference()
{
cylinder(h=sealHeight,r=innerDiameter/2-sealGap);
cylinder(h=sealHeight+1,r=innerDiameter/2-sealGap-sealThickness);
}
}