// ============================================================================ // 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); } }