diff --git a/3d-model/README.md b/3d-model/README.md index 4eed754..76d5303 100644 --- a/3d-model/README.md +++ b/3d-model/README.md @@ -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 diff --git a/3d-model/cube-assembly.scad b/3d-model/cube-assembly.scad index cff23e9..198d726 100644 --- a/3d-model/cube-assembly.scad +++ b/3d-model/cube-assembly.scad @@ -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 +include // --- 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) diff --git a/3d-model/cube-parts.scad b/3d-model/cube-parts.scad new file mode 100644 index 0000000..cef761f --- /dev/null +++ b/3d-model/cube-parts.scad @@ -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 + +// ============================================================================ +// 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); + } +}