refactor(3d): architecture modulaire — 10 pièces séparées (Frame, Internals, 6x Carriers, 6x Covers)

This commit is contained in:
Julien Holtzer 2026-02-08 13:07:33 +01:00
parent c976b7182e
commit 7d0c22bed9
3 changed files with 789 additions and 629 deletions

View file

@ -1,72 +1,112 @@
# Modèle 3D — Companion Cube
## Architecture : Squelette + 6 Faces Modulaires
## Architecture : Pièces Modulaires Séparées
```
┌───────────────┐
/│ /│
/ │ Face 1 / │ Face 1 = Dessus (NFC)
/ │ (NFC) / │ Face 2 = Avant
┌───────────────┐ │ Face 3 = Droite
│ │ │ │ Face 4 = Arrière
│ │ Face 5 │ │ Face 3 Face 5 = Gauche
│ │ (Gauche) │ │(Droite) Face 6 = Dessous (Qi)
│ └───────────│───┘
│ / Face 4 │ /
│ / (Arrière) │ /
│/ │/
└───────────────┘
Face 2 (Avant)
Face 6 (Qi, dessous)
ASSEMBLAGE
==========
1. Frame (squelette)
↓ vissé dessus
2. Battery Cradle + PCB Central Mount
↓ électronique montée
3. PCB Carriers (x6) vissés sur plots du Frame
↓ PCBs montés + câblés
4. Face Covers (x6) vissées sur Carriers
└─ Joint O-ring entre Carrier et Cover
```
## Fichiers
| Fichier | Description |
|---------|-------------|
| `cube-config.scad` | Paramètres dimensionnels (modifier ici pour ajuster) |
| `cube-assembly.scad` | Modèle principal — ouvrir dans OpenSCAD |
| `cube-config.scad` | **Paramètres** — modifier ici pour ajuster dimensions |
| `cube-parts.scad` | **Pièces** — définition des 10 pièces à imprimer |
| `cube-assembly.scad` | **Assemblage** — vue complète, ouvrir dans OpenSCAD |
## 10 Pièces à Imprimer
| # | Pièce | Qté | Matériau | Rôle |
|---|-------|-----|----------|------|
| 1 | `frame` | 1 | PLA/PETG opaque gris | Squelette principal |
| 2 | `battery_cradle` | 1 | PLA/PETG | Support batterie LiPo |
| 3 | `pcb_central_mount` | 1 | PLA/PETG | Support PCB ESP32-S3 |
| 4 | `pcb_carrier_standard` | 4 | PLA/PETG | Supports PCB faces latérales |
| 5 | `pcb_carrier_nfc` | 1 | PLA/PETG | Support PCB face dessus (NFC) |
| 6 | `pcb_carrier_qi` | 1 | PLA/PETG | Support PCB face dessous (Qi) |
| 7 | `face_cover_standard` | 4 | PETG translucide | Coques extérieures latérales |
| 8 | `face_cover_nfc` | 1 | PETG translucide | Coque extérieure dessus |
| 9 | `face_cover_qi` | 1 | PETG translucide | Coque extérieure dessous |
**Total : 15 pièces** (1+1+1 + 4+1+1 + 4+1+1)
## Utilisation
1. **Installer OpenSCAD** : https://openscad.org/
2. **Ouvrir** `cube-assembly.scad`
3. **Prévisualiser** : F5 (aperçu rapide) ou F6 (rendu complet)
3. **Prévisualiser** : F5 (rapide) ou F6 (complet)
### Contrôles de visualisation
Dans `cube-assembly.scad`, modifier les variables en haut du fichier :
Dans `cube-assembly.scad`, modifier les variables :
```openscad
SHOW_FRAME = true; // Squelette
SHOW_FACE_TOP = true; // Face dessus (NFC)
SHOW_ELECTRONICS = true; // Batterie + PCB central
SHOW_SECTION = false; // Vue en coupe
SHOW_FRAME = true; // Squelette
SHOW_INTERNALS = true; // Battery Cradle + PCB Mount
SHOW_PCB_CARRIERS = true; // 6 supports PCB
SHOW_FACE_COVERS = true; // 6 coques extérieures
SHOW_ELECTRONICS = true; // Batterie + PCB (visuel)
SHOW_SECTION = false; // Vue en coupe
EXPLODE = 0; // 0 = assemblé, 1 = vue éclatée
EXPLODE = 0; // 0 à 1 (vue éclatée)
```
### Export STL pour impression
1. Décommenter le module souhaité en bas de `cube-assembly.scad`
2. Commenter `assembly()`
3. F6 → File → Export as STL
1. Éditer `cube-assembly.scad`
2. **Décommenter** le module souhaité (fin du fichier)
3. **Commenter** `assembly()`
4. F6 → File → Export as STL
### Pièces à imprimer
Exemple :
```openscad
// assembly(); // ← commenter
frame(); // ← décommenter
```
| Pièce | Quantité | Matériau | Notes |
|-------|----------|----------|-------|
| `frame` (squelette) | 1 | PLA/PETG opaque gris | Structurel |
| `face-standard` | 4 | PETG translucide | Diffusion LED |
| `face-nfc` | 1 | PETG translucide | Dessus, membrane fine NFC |
| `face-qi` | 1 | PETG translucide | Dessous, aminci pour Qi |
## Séquence d'Assemblage
## Paramètres clés
### Étape 1 : Frame + Internals
1. Imprimer `frame`, `battery_cradle`, `pcb_central_mount`
2. Visser les supports au frame (vis M2.5 ou M3)
3. Monter batterie + PCB central + câblage
### Étape 2 : PCB Carriers
1. Imprimer les 6 carriers (4 standard + 1 NFC + 1 Qi)
2. Monter les PCBs Anneau + Bouchon sur chaque carrier
3. Visser chaque carrier sur les 4 plots du frame (M3)
4. Câbler JST depuis le PCB central vers chaque carrier
### Étape 3 : Face Covers
1. Imprimer les 6 covers (PETG translucide)
2. Insérer joint O-ring (Ø cordon 2mm) dans la gorge
3. Visser chaque cover sur son carrier (M3)
4. Serrer uniformément les 4 vis
## Paramètres Clés
| Paramètre | Valeur | Impact |
|-----------|--------|--------|
| `CUBE_SIZE` | 120 mm | Taille globale |
| `WALL_THICKNESS` | 2.5 mm | Solidité vs poids |
| `FACE_MEMBRANE_T` | 1.0 mm | Sensibilité tactile |
| `RING_SHROUD_HEIGHT` | 6 mm | Qualité de diffusion LED |
| `CUBE_SIZE` | 120 mm | Taille globale du cube |
| `CORNER_RADIUS` | 6 mm | Arrondi des coins (Portal style) |
| `FACE_MEMBRANE_T` | 1.0 mm | Membrane tactile (sensibilité) |
| `SEAL_CORD_DIAM` | 2.0 mm | Diamètre cordon O-ring |
| `PRINT_TOLERANCE` | 0.2 mm | Ajuster selon votre imprimante |
## Matériel nécessaire
- **Vis M3 x 8mm** : 24 (fixation carriers sur frame)
- **Vis M3 x 6mm** : 24 (fixation covers sur carriers)
- **Vis M2.5 x 6mm** : 8 (battery cradle + pcb mount)
- **Joint O-ring Ø2mm** : 6m (1m par face, gorge continue)
- **Inserts filetés M3** (optionnel) : 48

View file

@ -1,557 +1,55 @@
// ============================================================================
// Companion Cube Modèle 3D Complet
// Version : 0.2.0
// Architecture : Squelette interne + 6 faces modulaires vissées
// Version : 0.3.0
// Architecture : Pièces modulaires séparées
// ============================================================================
//
// STRUCTURE :
// 1. Squelette (frame) Structure porteuse interne en forme de cube
// 2. Face Standard (x4) Module face : coque + shroud + emplacements PCB
// 3. Face NFC (dessus) Comme standard + logement PN532
// 4. Face Qi (dessous) Comme standard + logement bobine Qi
// 5. Électronique PCB central, batterie (visualisation)
// STRUCTURE (10 pièces à imprimer) :
// 1. Frame (squelette) Structure principale avec plots de fixation
// 2. Battery Cradle Support batterie (vissé au frame)
// 3. PCB Central Mount Support PCB central (vissé au frame)
// 4-9. PCB Carriers (x6) Support PCB pour chaque face (vissé sur plots)
// - Standard (x4)
// - NFC (x1, dessus)
// - Qi (x1, dessous)
// 10-15. Face Covers (x6) Coques extérieures (étanchéité + look Portal)
// - Standard (x4)
// - NFC (x1)
// - Qi (x1)
//
// ASSEMBLAGE :
// 1. Monter le PCB central + batterie dans le squelette
// 2. Câbler les connecteurs JST vers chaque face
// 3. Assembler chaque module face (PCB Anneau + PCB Bouchon dans la coque)
// 4. Visser chaque module face sur le squelette (4x M3 par face)
// 1. Visser Battery Cradle + PCB Central Mount au Frame
// 2. Monter l'électronique (batterie, PCB central, câbles)
// 3. Visser chaque PCB Carrier sur les plots du Frame
// 4. Monter les PCBs (Anneau + Bouchon) sur chaque Carrier
// 5. Câbler JST depuis le PCB central vers chaque Carrier
// 6. Visser les Face Covers sur les Carriers (joint O-ring entre)
//
// UTILISATION :
// - Ouvrir dans OpenSCAD
// - Variable `SHOW_*` pour afficher/masquer les pièces
// - Variable `EXPLODE` pour la vue éclatée
// - Modules individuels exportables en STL pour l'impression
// - Variables `SHOW_*` pour afficher/masquer les pièces
// - Variable `EXPLODE` pour la vue éclatée (0 à 1)
// - Modules individuels exportables en STL
//
// ============================================================================
include <cube-config.scad>
include <cube-parts-new.scad>
// --- Contrôle de l'affichage ------------------------------------------------
SHOW_FRAME = true; // Squelette interne
SHOW_FACE_TOP = true; // Face dessus (NFC)
SHOW_FACE_BOTTOM = true; // Face dessous (Qi)
SHOW_FACE_FRONT = true; // Face avant
SHOW_FACE_BACK = true; // Face arrière
SHOW_FACE_LEFT = true; // Face gauche
SHOW_FACE_RIGHT = true; // Face droite
SHOW_ELECTRONICS = true; // PCB central + batterie (visualisation)
SHOW_PCBS = true; // PCBs anneau + bouchon dans les faces
SHOW_SECTION = false; // Coupe en section pour voir l'intérieur
SHOW_FRAME = true; // Squelette principal
SHOW_INTERNALS = true; // Battery Cradle + PCB Central Mount
SHOW_PCB_CARRIERS = true; // 6 supports PCB
SHOW_FACE_COVERS = true; // 6 coques extérieures
SHOW_ELECTRONICS = true; // Batterie + PCB (visualisation)
SHOW_SECTION = false; // Coupe en section
EXPLODE = 1; // 0 = assemblé, 1 = vue éclatée (0 à 1 progressif)
EXPLODE = 0; // 0 = assemblé, 1 = vue éclatée (progressif)
// Distance d'éclatement
EXPLODE_DIST = 40 * EXPLODE;
EXPLODE_DIST = 40 * EXPLODE;
// ============================================================================
// MODULE : Squelette Interne (Frame)
// ============================================================================
// Le squelette est constitué des 12 arêtes d'un cube, avec des plots de
// fixation M3 sur chaque face intérieure (4 par face = 24 total).
// Il accueille le PCB central et la batterie en son centre.
// ============================================================================
module frame() {
half = CUBE_SIZE / 2;
edge_w = FRAME_EDGE_WIDTH;
edge_d = FRAME_EDGE_DEPTH;
inner = CUBE_SIZE - 2 * edge_d;
difference() {
union() {
// --- Squelette arrondi (12 arêtes + 8 coins sphériques) ---
_rounded_frame_skeleton();
// --- Corniches de contact (surface plane pour joint O-ring) ---
_frame_contact_ledges();
// --- Plots de fixation (4 par face, 6 faces) ---
// Face +Z (dessus) et -Z (dessous)
for (z = [-1, 1]) {
for (xy = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([xy[0], xy[1], z * (half - edge_d)])
_mount_post(z);
}
}
// Face +Y (arrière) et -Y (avant)
for (y_dir = [-1, 1]) {
for (xz = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([xz[0], y_dir * (half - edge_d), xz[1]])
rotate([y_dir * 90, 0, 0])
_mount_post(1);
}
}
// Face +X (droite) et -X (gauche)
for (x_dir = [-1, 1]) {
for (yz = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([x_dir * (half - edge_d), yz[0], yz[1]])
rotate([0, x_dir * -90, 0])
_mount_post(1);
}
}
// --- Support batterie (berceau central) ---
_battery_cradle();
// --- Support PCB central ---
_central_pcb_mount();
}
// --- Perçages des plots M3 ---
_all_mount_holes();
// --- Passages de câbles (JST vers chaque face) ---
_cable_channels();
}
}
// Plot de fixation cylindrique (s'élève vers la face)
module _mount_post(direction) {
post_h = 8;
post_d = 7;
translate([0, 0, direction > 0 ? 0 : -post_h])
cylinder(d=post_d, h=post_h);
}
// Berceau pour la batterie LiPo
module _battery_cradle() {
bw = BATT_WIDTH + 2 * BATT_TOLERANCE + 4; // +4 pour les parois
bl = BATT_LENGTH + 2 * BATT_TOLERANCE + 4;
bh = BATT_HEIGHT + BATT_TOLERANCE + 2;
wall = 2;
// Position : centré, décalé vers le bas pour laisser de la place au PCB central au-dessus
translate([0, 0, -10])
difference() {
cube([bl, bw, bh], center=true);
translate([0, 0, wall])
cube([bl - 2*wall, bw - 2*wall, bh], center=true);
// Ouverture pour les fils
translate([0, bw/2, 0])
cube([20, wall*3, bh - wall], center=true);
}
}
// Supports pour le PCB central (4 entretoises)
module _central_pcb_mount() {
standoff_h = 5;
standoff_d = 6;
hole_d = 2.5; // Pour vis M2.5 ou insert
pcb_mount_offset = 8; // Au-dessus du centre (batterie en dessous)
for (x = [-1, 1], y = [-1, 1]) {
translate([
x * (CENTRAL_PCB_LENGTH/2 - 5),
y * (CENTRAL_PCB_WIDTH/2 - 5),
pcb_mount_offset
])
difference() {
cylinder(d=standoff_d, h=standoff_h);
cylinder(d=hole_d, h=standoff_h + 1);
}
}
}
// Perçage de tous les trous M3 (traversant les plots)
module _all_mount_holes() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
// Face +Z et -Z
for (z = [-1, 1]) {
for (xy = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([xy[0], xy[1], z * half])
cylinder(d=FRAME_MOUNT_DIAM, h=20, center=true);
}
}
// Face +Y et -Y
for (y_dir = [-1, 1]) {
for (xz = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([xz[0], y_dir * half, xz[1]])
rotate([90, 0, 0])
cylinder(d=FRAME_MOUNT_DIAM, h=20, center=true);
}
}
// Face +X et -X
for (x_dir = [-1, 1]) {
for (yz = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([x_dir * half, yz[0], yz[1]])
rotate([0, 90, 0])
cylinder(d=FRAME_MOUNT_DIAM, h=20, center=true);
}
}
}
// Cube arrondi (hull de 8 sphères aux coins)
module _rounded_cube(size, r) {
hull() {
c = size/2 - r;
for (x = [-1, 1], y = [-1, 1], z = [-1, 1])
translate([x*c, y*c, z*c])
sphere(r=r);
}
}
// Squelette arrondi : coque arrondie évidée + ouvertures pour les 6 faces
module _rounded_frame_skeleton() {
half = CUBE_SIZE / 2;
r = CORNER_RADIUS;
edge_d = FRAME_EDGE_DEPTH;
opening = CUBE_SIZE - 2 * edge_d;
difference() {
// Coque extérieure arrondie
_rounded_cube(CUBE_SIZE, r);
// Évidement intérieur
cube([opening, opening, opening], center=true);
// Ouvertures pour les 6 faces (traversées)
cube([opening, opening, CUBE_SIZE + 2], center=true); // ±Z
cube([CUBE_SIZE + 2, opening, opening], center=true); // ±X
cube([opening, CUBE_SIZE + 2, opening], center=true); // ±Y
}
}
// Corniches plates sur chaque face du squelette
// Surface de contact plane pour l'assise du joint O-ring et la fixation
module _frame_contact_ledges() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
lw = FRAME_LEDGE_WIDTH;
lh = FRAME_LEDGE_HEIGHT;
opening = CUBE_SIZE - 2 * edge_d;
outer = opening + 2 * lw;
// Corniche sur chaque face : anneau plat (outer - opening)
// +Z et -Z
for (z = [-1, 1]) {
translate([0, 0, z * (half - edge_d + (z > 0 ? 0 : -lh))])
difference() {
linear_extrude(lh)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([outer, outer], center=true);
translate([0, 0, -0.5])
linear_extrude(lh + 1)
square([opening, opening], center=true);
}
}
// +Y et -Y
for (y_dir = [-1, 1]) {
translate([0, y_dir * (half - edge_d + (y_dir > 0 ? 0 : -lh)), 0])
rotate([90, 0, 0])
difference() {
linear_extrude(lh)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([outer, outer], center=true);
translate([0, 0, -0.5])
linear_extrude(lh + 1)
square([opening, opening], center=true);
}
}
// +X et -X
for (x_dir = [-1, 1]) {
translate([x_dir * (half - edge_d + (x_dir > 0 ? 0 : -lh)), 0, 0])
rotate([0, 90, 0])
difference() {
linear_extrude(lh)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([outer, outer], center=true);
translate([0, 0, -0.5])
linear_extrude(lh + 1)
square([opening, opening], center=true);
}
}
}
// Canaux pour le passage des nappes/câbles JST vers chaque face
module _cable_channels() {
ch_w = 10; // Largeur du canal
ch_h = 6; // Hauteur du canal
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
// Un canal par face, radial depuis le centre
// +Z / -Z
for (z = [-1, 1]) {
translate([0, 0, z * (half - edge_d/2)])
cube([ch_w, ch_h, edge_d + 2], center=true);
}
// +Y / -Y
for (y = [-1, 1]) {
translate([0, y * (half - edge_d/2), 0])
cube([ch_w, edge_d + 2, ch_h], center=true);
}
// +X / -X
for (x = [-1, 1]) {
translate([x * (half - edge_d/2), 0, 0])
cube([edge_d + 2, ch_w, ch_h], center=true);
}
}
// ============================================================================
// MODULE : Face Standard
// ============================================================================
// Module face complet comprenant :
// - Coque extérieure avec membrane tactile amincie au centre
// - Rainure pour joint torique (étanchéité visuelle)
// - Ring Shroud (chambre de diffusion LEDs)
// - Emplacements PCB Anneau (étage B) et PCB Bouchon (étage A)
// - Trous de fixation M3 (alignés avec les plots du squelette)
// ============================================================================
module face_standard() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
face_size = CUBE_SIZE - 2 * PRINT_TOLERANCE;
difference() {
union() {
// --- Coque extérieure (coins arrondis) ---
difference() {
// Parois extérieures avec coins arrondis
linear_extrude(FACE_TOTAL_DEPTH)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([face_size - 2*edge_d + 6, face_size - 2*edge_d + 6], center=true);
// Évidement intérieur
translate([0, 0, WALL_THICKNESS])
linear_extrude(FACE_TOTAL_DEPTH)
offset(r=max(0.5, CORNER_RADIUS - WALL_THICKNESS))
offset(delta=-max(0.5, CORNER_RADIUS - WALL_THICKNESS))
square([face_size - 2*edge_d + 6 - 2*WALL_THICKNESS,
face_size - 2*edge_d + 6 - 2*WALL_THICKNESS], center=true);
}
// --- Membrane tactile (centre, amincie) ---
cylinder(d=FACE_CENTER_DIAM + 10, h=FACE_MEMBRANE_T);
// --- Paroi extérieure pleine (face visible) ---
translate([0, 0, 0])
_face_outer_plate(face_size - 2*edge_d + 6);
// --- Ring Shroud (chambre de diffusion) ---
translate([0, 0, WALL_THICKNESS])
_ring_shroud();
// --- Oreilles de fixation M3 ---
_face_mount_ears();
}
// --- Zone tactile amincie (creuser pour ne garder que 1mm) ---
translate([0, 0, FACE_MEMBRANE_T])
cylinder(d=FACE_CENTER_DIAM, h=FACE_TOTAL_DEPTH);
// --- Rainure joint torique ---
translate([0, 0, FACE_TOTAL_DEPTH - 1])
_seal_groove(face_size - 2*edge_d + 2);
// --- Trous de fixation M3 ---
_face_mount_holes();
// --- Ouverture d'air centrale (côté intérieur) ---
translate([0, 0, WALL_THICKNESS])
cylinder(d=AIR_HOLE_DIAM, h=FACE_TOTAL_DEPTH);
}
}
// Plaque extérieure de la face (surface visible, coins arrondis)
module _face_outer_plate(size) {
linear_extrude(WALL_THICKNESS)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([size, size], center=true);
}
// Chambre de diffusion annulaire pour les LEDs
module _ring_shroud() {
ext = LED_RING_DIAM_EXT + 2 * RING_SHROUD_WALL;
int_ = LED_RING_DIAM_INT - 2 * RING_SHROUD_WALL;
difference() {
cylinder(d=ext, h=RING_SHROUD_HEIGHT);
translate([0, 0, -0.5])
cylinder(d=int_, h=RING_SHROUD_HEIGHT + 1);
}
}
// Oreilles de fixation (dépassent pour atteindre les plots du squelette)
module _face_mount_ears() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
ear_size = 10;
for (xy = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([xy[0], xy[1], FACE_TOTAL_DEPTH/2])
cube([ear_size, ear_size, FACE_TOTAL_DEPTH], center=true);
}
}
// Trous de fixation dans les oreilles
module _face_mount_holes() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
for (xy = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
]) {
translate([xy[0], xy[1], -1])
cylinder(d=FRAME_MOUNT_DIAM, h=FACE_TOTAL_DEPTH + 2);
// Lamage tête de vis (côté extérieur)
translate([xy[0], xy[1], -1])
cylinder(d=FRAME_MOUNT_HEAD, h=FRAME_MOUNT_DEPTH + 1);
}
}
// Rainure pour joint torique O-ring
// Gorge rectangulaire continue suivant le contour arrondi de la face
// Dimensions selon norme : largeur ~1.4ר cordon, profondeur ~0.7ר cordon
module _seal_groove(size) {
w = FACE_SEAL_GROOVE_W;
d = FACE_SEAL_GROOVE_D;
inset = 3; // Retrait depuis le bord extérieur
linear_extrude(d + 0.1)
difference() {
// Contour extérieur de la gorge (suit l'arrondi des coins)
offset(r=CORNER_RADIUS - inset)
offset(delta=-(CORNER_RADIUS - inset))
square([size - 2*inset, size - 2*inset], center=true);
// Contour intérieur de la gorge
offset(r=max(0.5, CORNER_RADIUS - inset - w))
offset(delta=-max(0.5, CORNER_RADIUS - inset - w))
square([size - 2*inset - 2*w, size - 2*inset - 2*w], center=true);
}
}
// ============================================================================
// MODULE : Face NFC (Dessus)
// ============================================================================
// Identique à la face standard mais avec un logement pour le module PN532
// à la place du simple PCB Bouchon.
// ============================================================================
module face_nfc() {
difference() {
union() {
face_standard();
// Logement renforcé pour le PN532 (plus grand que le bouchon standard)
translate([0, 0, WALL_THICKNESS + RING_SHROUD_HEIGHT])
_nfc_mount();
}
// Ouverture pour l'antenne NFC (dans la membrane)
// L'antenne doit être au plus près de la surface pour une bonne lecture
translate([0, 0, -1])
cylinder(d=NFC_ANTENNA_DIAM, h=FACE_MEMBRANE_T + 2);
}
}
module _nfc_mount() {
// Support carré pour le module PN532
wall = 1.5;
size = NFC_PCB_SIZE + 2 * wall + 2 * PRINT_TOLERANCE;
inner = NFC_PCB_SIZE + 2 * PRINT_TOLERANCE;
difference() {
cube([size, size, NFC_PCB_HEIGHT + 2], center=true);
cube([inner, inner, NFC_PCB_HEIGHT + 3], center=true);
}
}
// ============================================================================
// MODULE : Face Qi (Dessous)
// ============================================================================
// Identique à la face standard mais avec un logement pour la bobine Qi
// et son PCB récepteur.
// ============================================================================
module face_qi() {
difference() {
union() {
face_standard();
// Logement bobine Qi
translate([0, 0, WALL_THICKNESS])
_qi_mount();
}
// La bobine Qi doit être au plus près de la surface extérieure
// Amincissement supplémentaire au centre pour la bobine
translate([0, 0, -0.5])
cylinder(d=QI_COIL_DIAM + 4, h=WALL_THICKNESS - 0.8 + 0.5);
}
}
module _qi_mount() {
wall = 1.5;
// Anneau de maintien pour la bobine
difference() {
cylinder(d=QI_COIL_DIAM + 2*wall + 2*PRINT_TOLERANCE, h=QI_COIL_HEIGHT + 1);
translate([0, 0, -0.5])
cylinder(d=QI_COIL_DIAM + 2*PRINT_TOLERANCE, h=QI_COIL_HEIGHT + 2);
}
// Support PCB récepteur Qi (décalé)
translate([0, QI_COIL_DIAM/2 + 5, 0])
difference() {
cube([QI_PCB_LENGTH + 2*wall, QI_PCB_WIDTH + 2*wall, QI_PCB_HEIGHT + 1], center=true);
cube([QI_PCB_LENGTH + PRINT_TOLERANCE, QI_PCB_WIDTH + PRINT_TOLERANCE, QI_PCB_HEIGHT + 2], center=true);
}
}
// ============================================================================
// MODULE : Électronique (Visualisation uniquement)
// ============================================================================
// Représentation simplifiée des composants pour vérifier l'encombrement.
// Ne pas exporter en STL.
// VISUALISATION ÉLECTRONIQUE (Ne pas imprimer)
// ============================================================================
module electronics() {
@ -562,21 +60,21 @@ module electronics() {
// --- PCB Central ---
color(COLOR_PCB)
translate([0, 0, 8 + 5]) // Au-dessus de la batterie, sur les entretoises
translate([0, 0, 8 + 5])
cube([CENTRAL_PCB_LENGTH, CENTRAL_PCB_WIDTH, CENTRAL_PCB_HEIGHT], center=true);
// --- ESP32-S3 module (sur le PCB central) ---
// --- ESP32-S3 module ---
color([0.15, 0.15, 0.15])
translate([0, 0, 8 + 5 + CENTRAL_PCB_HEIGHT/2 + 1.5])
cube([18, 18, 3], center=true);
// --- TP4056 (sur le PCB central) ---
// --- TP4056 ---
color(COLOR_PCB)
translate([20, 0, 8 + 5 + CENTRAL_PCB_HEIGHT/2 + 1])
cube([10, 8, 2], center=true);
}
// PCB Anneau (visualisation, placé dans une face)
// PCB Anneau (visualisation)
module pcb_ring() {
color(COLOR_PCB)
difference() {
@ -591,16 +89,16 @@ module pcb_ring() {
angle = i * 360 / LED_COUNT;
r = (LED_RING_DIAM_EXT + LED_RING_DIAM_INT) / 4;
translate([r * cos(angle), r * sin(angle), LED_RING_HEIGHT])
cube([5, 5, 1.5], center=true); // WS2812B = 5x5mm
cube([5, 5, 1.5], center=true);
}
}
// PCB Bouchon (visualisation, placé dans une face)
// PCB Bouchon (visualisation)
module pcb_cap() {
color(COLOR_PCB)
cylinder(d=CAP_PCB_DIAM, h=CAP_PCB_HEIGHT);
// Pad capacitif (cuivre)
// Pad capacitif
color([0.8, 0.6, 0.2])
translate([0, 0, CAP_PCB_HEIGHT])
cylinder(d=CAP_PCB_DIAM - 5, h=0.1);
@ -610,7 +108,6 @@ module pcb_cap() {
module nfc_module() {
color(COLOR_NFC) {
cube([NFC_PCB_SIZE, NFC_PCB_SIZE, NFC_PCB_HEIGHT], center=true);
// Antenne
translate([0, 0, NFC_PCB_HEIGHT/2])
cylinder(d=NFC_ANTENNA_DIAM, h=0.5);
}
@ -620,77 +117,118 @@ module nfc_module() {
module qi_coil() {
color(COLOR_QI) {
cylinder(d=QI_COIL_DIAM, h=QI_COIL_HEIGHT);
// PCB récepteur
translate([0, QI_COIL_DIAM/2 + 5, 0])
cube([QI_PCB_LENGTH, QI_PCB_WIDTH, QI_PCB_HEIGHT], center=true);
}
}
// ============================================================================
// ASSEMBLAGE PRINCIPAL
// ============================================================================
module assembly() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
// --- Squelette ---
// --- 1. FRAME (Squelette) ---
if (SHOW_FRAME)
color(COLOR_FRAME) frame();
// --- Électronique interne ---
// --- 2. INTERNALS (Supports internes) ---
if (SHOW_INTERNALS) {
// Battery Cradle (centré, décalé vers le bas)
color([0.4, 0.4, 0.4])
translate([0, 0, -10 - EXPLODE_DIST * 0.3])
battery_cradle();
// PCB Central Mount (centré, au-dessus de la batterie)
color([0.4, 0.4, 0.4])
translate([0, 0, 8 + EXPLODE_DIST * 0.3])
pcb_central_mount();
}
// --- 3. ÉLECTRONIQUE (Visualisation) ---
if (SHOW_ELECTRONICS)
electronics();
// --- Face Dessus (+Z) NFC ---
if (SHOW_FACE_TOP)
translate([0, 0, half - FACE_TOTAL_DEPTH + EXPLODE_DIST])
color(COLOR_FACE) face_nfc();
// --- 4-9. PCB CARRIERS (6 supports PCB) ---
if (SHOW_PCB_CARRIERS) {
carrier_offset = 10; // Distance depuis la corniche du frame
// --- Face Dessous (-Z) Qi ---
if (SHOW_FACE_BOTTOM)
translate([0, 0, -(half - FACE_TOTAL_DEPTH) - EXPLODE_DIST])
// Carrier Dessus (+Z) NFC
color([0.5, 0.5, 0.5])
translate([0, 0, half - edge_d + carrier_offset + EXPLODE_DIST * 0.7])
pcb_carrier_nfc();
// Carrier Dessous (-Z) Qi
color([0.5, 0.5, 0.5])
translate([0, 0, -(half - edge_d + carrier_offset) - EXPLODE_DIST * 0.7])
rotate([180, 0, 0])
color(COLOR_FACE) face_qi();
pcb_carrier_qi();
// --- Face Avant (-Y) ---
if (SHOW_FACE_FRONT)
translate([0, -(half - FACE_TOTAL_DEPTH) - EXPLODE_DIST, 0])
// Carrier Avant (-Y)
color([0.5, 0.5, 0.5])
translate([0, -(half - edge_d + carrier_offset) - EXPLODE_DIST * 0.7, 0])
rotate([90, 0, 0])
color(COLOR_FACE) face_standard();
pcb_carrier_standard();
// --- Face Arrière (+Y) ---
if (SHOW_FACE_BACK)
translate([0, (half - FACE_TOTAL_DEPTH) + EXPLODE_DIST, 0])
// Carrier Arrière (+Y)
color([0.5, 0.5, 0.5])
translate([0, (half - edge_d + carrier_offset) + EXPLODE_DIST * 0.7, 0])
rotate([-90, 0, 0])
color(COLOR_FACE) face_standard();
pcb_carrier_standard();
// --- Face Gauche (-X) ---
if (SHOW_FACE_LEFT)
translate([-(half - FACE_TOTAL_DEPTH) - EXPLODE_DIST, 0, 0])
// Carrier Gauche (-X)
color([0.5, 0.5, 0.5])
translate([-(half - edge_d + carrier_offset) - EXPLODE_DIST * 0.7, 0, 0])
rotate([0, -90, 0])
color(COLOR_FACE) face_standard();
pcb_carrier_standard();
// --- Face Droite (+X) ---
if (SHOW_FACE_RIGHT)
translate([(half - FACE_TOTAL_DEPTH) + EXPLODE_DIST, 0, 0])
// Carrier Droite (+X)
color([0.5, 0.5, 0.5])
translate([(half - edge_d + carrier_offset) + EXPLODE_DIST * 0.7, 0, 0])
rotate([0, 90, 0])
color(COLOR_FACE) face_standard();
pcb_carrier_standard();
}
// --- PCBs dans les faces (visualisation) ---
if (SHOW_PCBS) {
// Anneau + bouchon dans chaque face (exemple face dessus)
translate([0, 0, half - FACE_TOTAL_DEPTH + WALL_THICKNESS + RING_SHROUD_HEIGHT + EXPLODE_DIST]) {
pcb_ring();
translate([0, 0, LED_RING_HEIGHT + CAP_CONNECTOR_H])
pcb_cap();
}
// NFC module (dessus)
translate([0, 0, half - 8 + EXPLODE_DIST])
nfc_module();
// Qi coil (dessous)
translate([0, 0, -(half - 4) - EXPLODE_DIST])
qi_coil();
// --- 10-15. FACE COVERS (6 coques extérieures) ---
if (SHOW_FACE_COVERS) {
cover_offset = 16; // Distance depuis le frame (par-dessus les carriers)
// Cover Dessus (+Z) NFC
color(COLOR_FACE)
translate([0, 0, half - edge_d + cover_offset + EXPLODE_DIST])
face_cover_nfc();
// Cover Dessous (-Z) Qi
color(COLOR_FACE)
translate([0, 0, -(half - edge_d + cover_offset) - EXPLODE_DIST])
rotate([180, 0, 0])
face_cover_qi();
// Cover Avant (-Y)
color(COLOR_FACE)
translate([0, -(half - edge_d + cover_offset) - EXPLODE_DIST, 0])
rotate([90, 0, 0])
face_cover_standard();
// Cover Arrière (+Y)
color(COLOR_FACE)
translate([0, (half - edge_d + cover_offset) + EXPLODE_DIST, 0])
rotate([-90, 0, 0])
face_cover_standard();
// Cover Gauche (-X)
color(COLOR_FACE)
translate([-(half - edge_d + cover_offset) - EXPLODE_DIST, 0, 0])
rotate([0, -90, 0])
face_cover_standard();
// Cover Droite (+X)
color(COLOR_FACE)
translate([(half - edge_d + cover_offset) + EXPLODE_DIST, 0, 0])
rotate([0, 90, 0])
face_cover_standard();
}
}
@ -717,7 +255,17 @@ if (SHOW_SECTION) {
// Pour exporter une pièce : décommenter la ligne, commenter assembly(),
// puis Exporter en STL (F6 File Export STL)
// frame(); // frame.stl
// face_standard(); // face-standard.stl (x4)
// face_nfc(); // face-nfc.stl (x1, dessus)
// face_qi(); // face-qi.stl (x1, dessous)
// --- Pièces Structurelles ---
// frame(); // 01-frame.stl
// battery_cradle(); // 02-battery-cradle.stl
// pcb_central_mount(); // 03-pcb-central-mount.stl
// --- PCB Carriers (6 pièces) ---
// pcb_carrier_standard(); // 04-pcb-carrier-standard.stl (à imprimer x4)
// pcb_carrier_nfc(); // 05-pcb-carrier-nfc.stl (dessus)
// pcb_carrier_qi(); // 06-pcb-carrier-qi.stl (dessous)
// --- Face Covers (6 pièces) ---
// face_cover_standard(); // 07-face-cover-standard.stl (à imprimer x4)
// face_cover_nfc(); // 08-face-cover-nfc.stl (dessus)
// face_cover_qi(); // 09-face-cover-qi.stl (dessous)

572
3d-model/cube-parts.scad Normal file
View file

@ -0,0 +1,572 @@
// ============================================================================
// Companion Cube Pièces Modulaires Séparées
// Version : 0.3.0
// ============================================================================
// Toutes les pièces à imprimer, définies comme modules indépendants.
// Ce fichier sera inclus par cube-assembly.scad
// ============================================================================
include <cube-config.scad>
// ============================================================================
// UTILITAIRES GÉOMÉTRIQUES
// ============================================================================
// Cube arrondi (hull de 8 sphères aux coins)
module rounded_cube(size, r) {
hull() {
c = size/2 - r;
for (x = [-1, 1], y = [-1, 1], z = [-1, 1])
translate([x*c, y*c, z*c])
sphere(r=r);
}
}
// ============================================================================
// PIÈCE 1 : FRAME (Squelette Principal)
// ============================================================================
// Structure porteuse avec plots de fixation pour les PCB Carriers.
// 4 plots M3 par face (24 total).
// ============================================================================
module frame() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
opening = CUBE_SIZE - 2 * edge_d;
difference() {
union() {
// --- Coque arrondie évidée ---
difference() {
rounded_cube(CUBE_SIZE, CORNER_RADIUS);
cube([opening, opening, opening], center=true);
// Ouvertures pour les 6 faces
cube([opening, opening, CUBE_SIZE + 2], center=true); // ±Z
cube([CUBE_SIZE + 2, opening, opening], center=true); // ±X
cube([opening, CUBE_SIZE + 2, opening], center=true); // ±Y
}
// --- Corniches plates (assise pour PCB Carriers) ---
_frame_ledges();
// --- Plots de fixation M3 (4 par face) ---
_frame_mount_posts();
}
// --- Trous traversants M3 ---
_frame_mount_holes();
// --- Canaux de câblage ---
_cable_channels();
}
}
// Corniches planes pour l'assise des PCB Carriers
module _frame_ledges() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
lw = FRAME_LEDGE_WIDTH;
lh = FRAME_LEDGE_HEIGHT;
opening = CUBE_SIZE - 2 * edge_d;
outer = opening + 2 * lw;
// +Z et -Z
for (z = [-1, 1]) {
translate([0, 0, z * (half - edge_d + (z > 0 ? 0 : -lh))])
difference() {
linear_extrude(lh)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([outer, outer], center=true);
translate([0, 0, -0.5])
linear_extrude(lh + 1)
square([opening, opening], center=true);
}
}
// +Y et -Y
for (y_dir = [-1, 1]) {
translate([0, y_dir * (half - edge_d + (y_dir > 0 ? 0 : -lh)), 0])
rotate([90, 0, 0])
difference() {
linear_extrude(lh)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([outer, outer], center=true);
translate([0, 0, -0.5])
linear_extrude(lh + 1)
square([opening, opening], center=true);
}
}
// +X et -X
for (x_dir = [-1, 1]) {
translate([x_dir * (half - edge_d + (x_dir > 0 ? 0 : -lh)), 0, 0])
rotate([0, 90, 0])
difference() {
linear_extrude(lh)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([outer, outer], center=true);
translate([0, 0, -0.5])
linear_extrude(lh + 1)
square([opening, opening], center=true);
}
}
}
// Plots de fixation (poteau cylindrique + base renforcée)
module _frame_mount_posts() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
post_h = 8;
post_d = 7;
mount_positions = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
];
// Face +Z et -Z
for (z = [-1, 1]) {
for (xy = mount_positions) {
translate([xy[0], xy[1], z * (half - edge_d + (z > 0 ? 0 : -post_h))])
cylinder(d=post_d, h=post_h);
}
}
// Face +Y et -Y
for (y_dir = [-1, 1]) {
for (xz = mount_positions) {
translate([xz[0], y_dir * (half - edge_d + (y_dir > 0 ? 0 : -post_h)), xz[1]])
rotate([90, 0, 0])
cylinder(d=post_d, h=post_h);
}
}
// Face +X et -X
for (x_dir = [-1, 1]) {
for (yz = mount_positions) {
translate([x_dir * (half - edge_d + (x_dir > 0 ? 0 : -post_h)), yz[0], yz[1]])
rotate([0, 90, 0])
cylinder(d=post_d, h=post_h);
}
}
}
// Trous traversants M3 dans les plots
module _frame_mount_holes() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
mount_positions = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
];
for (z = [-1, 1]) {
for (xy = mount_positions) {
translate([xy[0], xy[1], z * half])
cylinder(d=FRAME_MOUNT_DIAM, h=20, center=true);
}
}
for (y_dir = [-1, 1]) {
for (xz = mount_positions) {
translate([xz[0], y_dir * half, xz[1]])
rotate([90, 0, 0])
cylinder(d=FRAME_MOUNT_DIAM, h=20, center=true);
}
}
for (x_dir = [-1, 1]) {
for (yz = mount_positions) {
translate([x_dir * half, yz[0], yz[1]])
rotate([0, 90, 0])
cylinder(d=FRAME_MOUNT_DIAM, h=20, center=true);
}
}
}
// Canaux pour câbles JST
module _cable_channels() {
ch_w = 10;
ch_h = 6;
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
for (z = [-1, 1]) {
translate([0, 0, z * (half - edge_d/2)])
cube([ch_w, ch_h, edge_d + 2], center=true);
}
for (y = [-1, 1]) {
translate([0, y * (half - edge_d/2), 0])
cube([ch_w, edge_d + 2, ch_h], center=true);
}
for (x = [-1, 1]) {
translate([x * (half - edge_d/2), 0, 0])
cube([edge_d + 2, ch_w, ch_h], center=true);
}
}
// ============================================================================
// PIÈCE 2 : BATTERY CRADLE (Support Batterie)
// ============================================================================
// Berceau pour batterie LiPo, se visse au frame via 4 vis M2.5.
// ============================================================================
module battery_cradle() {
bw = BATT_WIDTH + 2 * BATT_TOLERANCE;
bl = BATT_LENGTH + 2 * BATT_TOLERANCE;
bh = BATT_HEIGHT + BATT_TOLERANCE;
wall = 2;
difference() {
// Berceau avec parois
union() {
cube([bl + 2*wall, bw + 2*wall, bh + wall], center=true);
// Pieds de fixation (4 coins)
for (x = [-1, 1], y = [-1, 1]) {
translate([x * (bl/2), y * (bw/2), -(bh + wall)/2])
cylinder(d=6, h=2);
}
}
// Évidement
translate([0, 0, wall])
cube([bl, bw, bh + wall], center=true);
// Ouverture pour les fils
translate([0, bw/2 + wall, 0])
cube([20, wall*3, bh - wall], center=true);
// Trous de fixation M2.5
for (x = [-1, 1], y = [-1, 1]) {
translate([x * (bl/2), y * (bw/2), 0])
cylinder(d=2.7, h=20, center=true);
}
}
}
// ============================================================================
// PIÈCE 3 : PCB CENTRAL MOUNT (Support PCB Central)
// ============================================================================
// Plateforme avec 4 entretoises pour le PCB central (ESP32-S3).
// ============================================================================
module pcb_central_mount() {
pcb_l = CENTRAL_PCB_LENGTH;
pcb_w = CENTRAL_PCB_WIDTH;
standoff_h = 5;
standoff_d = 6;
hole_d = 2.5;
base_h = 2;
difference() {
union() {
// Base
cube([pcb_l + 10, pcb_w + 10, base_h], center=true);
// 4 entretoises
for (x = [-1, 1], y = [-1, 1]) {
translate([x * (pcb_l/2 - 5), y * (pcb_w/2 - 5), base_h/2])
cylinder(d=standoff_d, h=standoff_h);
}
}
// Trous dans les entretoises (M2.5)
for (x = [-1, 1], y = [-1, 1]) {
translate([x * (pcb_l/2 - 5), y * (pcb_w/2 - 5), 0])
cylinder(d=hole_d, h=20, center=true);
}
// Trous de fixation au frame (4 vis M2.5)
for (x = [-1, 1], y = [-1, 1]) {
translate([x * (pcb_l/2 + 3), y * (pcb_w/2 + 3), 0])
cylinder(d=2.7, h=20, center=true);
}
}
}
// ============================================================================
// PIÈCE 4-9 : PCB CARRIERS (Supports PCB pour chaque face)
// ============================================================================
// Support qui porte le PCB Anneau + PCB Bouchon, se visse sur les 4 plots du frame.
// ============================================================================
module pcb_carrier_standard() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
carrier_depth = 10; // Profondeur du support
opening = CUBE_SIZE - 2 * edge_d;
difference() {
union() {
// Plaque de base
linear_extrude(carrier_depth)
offset(r=CORNER_RADIUS - 2)
offset(delta=-(CORNER_RADIUS - 2))
square([opening - 4, opening - 4], center=true);
// Rebord pour PCB Anneau
translate([0, 0, carrier_depth - LED_RING_HEIGHT - 0.5])
_ring_holder();
// Support PCB Bouchon (central)
translate([0, 0, carrier_depth - LED_RING_HEIGHT - CAP_CONNECTOR_H - CAP_PCB_HEIGHT - 0.5])
cylinder(d=CAP_PCB_DIAM + 2, h=CAP_CONNECTOR_H + CAP_PCB_HEIGHT + 0.5);
// Oreilles de fixation (4 coins)
_carrier_mount_ears();
}
// Ouverture centrale (air + câblage)
translate([0, 0, -0.5])
cylinder(d=AIR_HOLE_DIAM, h=carrier_depth + 1);
// Évidement pour le PCB Anneau
translate([0, 0, carrier_depth - LED_RING_HEIGHT])
difference() {
cylinder(d=LED_RING_DIAM_EXT - 2, h=LED_RING_HEIGHT + 1);
cylinder(d=LED_RING_DIAM_INT + 2, h=LED_RING_HEIGHT + 2, center=true);
}
// Évidement pour le PCB Bouchon
translate([0, 0, carrier_depth - LED_RING_HEIGHT - CAP_CONNECTOR_H - CAP_PCB_HEIGHT])
cylinder(d=CAP_PCB_DIAM, h=CAP_CONNECTOR_H + CAP_PCB_HEIGHT + 1);
// Trous de fixation M3
_carrier_mount_holes();
}
}
// Rebord de maintien pour le PCB Anneau
module _ring_holder() {
ext = LED_RING_DIAM_EXT + 1;
int_ = LED_RING_DIAM_INT - 1;
h = 2;
difference() {
cylinder(d=ext, h=h);
translate([0, 0, -0.5])
cylinder(d=int_, h=h + 1);
// Encoches pour les connecteurs
for (a = [0, 90, 180, 270]) {
rotate([0, 0, a])
translate([ext/2-1, 0, 0])
cube([4, 6, h + 1], center=true);
}
}
}
// Oreilles de fixation pour les PCB Carriers
module _carrier_mount_ears() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
ear_size = 12;
ear_h = 10;
mount_positions = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
];
for (xy = mount_positions) {
translate([xy[0], xy[1], ear_h/2])
cube([ear_size, ear_size, ear_h], center=true);
}
}
// Trous de fixation dans les oreilles
module _carrier_mount_holes() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
mount_positions = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
];
for (xy = mount_positions) {
translate([xy[0], xy[1], -1])
cylinder(d=FRAME_MOUNT_DIAM, h=20);
}
}
// Variante NFC (dessus)
module pcb_carrier_nfc() {
difference() {
union() {
pcb_carrier_standard();
// Ajout d'un support renforcé pour le PN532
translate([0, 0, 10 - NFC_PCB_HEIGHT - 0.5])
_nfc_holder();
}
// Évidement pour le module NFC
translate([0, 0, 10 - NFC_PCB_HEIGHT - 0.5])
cube([NFC_PCB_SIZE, NFC_PCB_SIZE, NFC_PCB_HEIGHT + 1], center=true);
}
}
module _nfc_holder() {
wall = 1.5;
size = NFC_PCB_SIZE + 2 * wall;
h = NFC_PCB_HEIGHT + 2;
difference() {
cube([size, size, h], center=true);
translate([0, 0, wall])
cube([NFC_PCB_SIZE, NFC_PCB_SIZE, h], center=true);
}
}
// Variante Qi (dessous)
module pcb_carrier_qi() {
difference() {
union() {
pcb_carrier_standard();
// Support pour bobine Qi
translate([0, 0, 2])
_qi_holder();
}
// Évidement pour la bobine Qi
translate([0, 0, 2])
cylinder(d=QI_COIL_DIAM, h=QI_COIL_HEIGHT + 1);
}
}
module _qi_holder() {
wall = 1.5;
h = QI_COIL_HEIGHT + 1;
difference() {
cylinder(d=QI_COIL_DIAM + 2*wall, h=h);
translate([0, 0, -0.5])
cylinder(d=QI_COIL_DIAM, h=h + 1);
}
// Support PCB récepteur
translate([0, QI_COIL_DIAM/2 + 5, 0])
difference() {
cube([QI_PCB_LENGTH + 2*wall, QI_PCB_WIDTH + 2*wall, QI_PCB_HEIGHT + 1], center=true);
cube([QI_PCB_LENGTH, QI_PCB_WIDTH, QI_PCB_HEIGHT + 2], center=true);
}
}
// ============================================================================
// PIÈCE 10-15 : FACE COVERS (Coques extérieures)
// ============================================================================
// Coque visible avec membrane tactile centrale + gorge joint O-ring.
// Se visse sur le PCB Carrier (4 vis M3).
// ============================================================================
module face_cover_standard() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
face_size = CUBE_SIZE - 2 * PRINT_TOLERANCE;
cover_depth = 6; // Profondeur de la coque (hors membrane)
difference() {
union() {
// Coque extérieure arrondie
difference() {
linear_extrude(cover_depth)
offset(r=CORNER_RADIUS)
offset(delta=-CORNER_RADIUS)
square([face_size - 2*edge_d + 6, face_size - 2*edge_d + 6], center=true);
translate([0, 0, WALL_THICKNESS])
linear_extrude(cover_depth)
offset(r=max(0.5, CORNER_RADIUS - WALL_THICKNESS))
offset(delta=-max(0.5, CORNER_RADIUS - WALL_THICKNESS))
square([face_size - 2*edge_d + 6 - 2*WALL_THICKNESS,
face_size - 2*edge_d + 6 - 2*WALL_THICKNESS], center=true);
}
// Membrane tactile centrale
cylinder(d=FACE_CENTER_DIAM + 10, h=FACE_MEMBRANE_T);
// Oreilles de fixation
_cover_mount_ears(cover_depth);
}
// Zone tactile amincie
translate([0, 0, FACE_MEMBRANE_T])
cylinder(d=FACE_CENTER_DIAM, h=cover_depth);
// Gorge joint O-ring (intérieure)
translate([0, 0, cover_depth - FACE_SEAL_GROOVE_D])
_seal_groove(face_size - 2*edge_d + 2);
// Trous de fixation M3
_cover_mount_holes();
}
}
module _cover_mount_ears(depth) {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
ear_size = 12;
mount_positions = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
];
for (xy = mount_positions) {
translate([xy[0], xy[1], depth/2])
cube([ear_size, ear_size, depth], center=true);
}
}
module _cover_mount_holes() {
half = CUBE_SIZE / 2;
edge_d = FRAME_EDGE_DEPTH;
mount_positions = [
[ (half - edge_d - 10), (half - edge_d - 10)],
[-(half - edge_d - 10), (half - edge_d - 10)],
[ (half - edge_d - 10), -(half - edge_d - 10)],
[-(half - edge_d - 10), -(half - edge_d - 10)]
];
for (xy = mount_positions) {
translate([xy[0], xy[1], -1])
cylinder(d=FRAME_MOUNT_DIAM, h=20);
// Lamage tête de vis
translate([xy[0], xy[1], -1])
cylinder(d=FRAME_MOUNT_HEAD, h=FRAME_MOUNT_DEPTH + 1);
}
}
// Gorge joint O-ring
module _seal_groove(size) {
w = FACE_SEAL_GROOVE_W;
d = FACE_SEAL_GROOVE_D;
inset = 3;
linear_extrude(d + 0.1)
difference() {
offset(r=CORNER_RADIUS - inset)
offset(delta=-(CORNER_RADIUS - inset))
square([size - 2*inset, size - 2*inset], center=true);
offset(r=max(0.5, CORNER_RADIUS - inset - w))
offset(delta=-max(0.5, CORNER_RADIUS - inset - w))
square([size - 2*inset - 2*w, size - 2*inset - 2*w], center=true);
}
}
// Variantes NFC et Qi (identiques à standard pour l'extérieur)
module face_cover_nfc() {
difference() {
face_cover_standard();
// Amincissement supplémentaire pour l'antenne NFC (optionnel)
translate([0, 0, -0.5])
cylinder(d=NFC_ANTENNA_DIAM, h=FACE_MEMBRANE_T + 0.5);
}
}
module face_cover_qi() {
difference() {
face_cover_standard();
// Amincissement supplémentaire pour la bobine Qi
translate([0, 0, -0.5])
cylinder(d=QI_COIL_DIAM + 4, h=WALL_THICKNESS);
}
}