258 lines
12 KiB
OpenSCAD
258 lines
12 KiB
OpenSCAD
// ============================================================
|
||
// ZK-Glasses — Snap-On IR Clip Module
|
||
// Clips onto any existing glasses temple arm
|
||
// ============================================================
|
||
//
|
||
// ┌──────────────────────────────────────────────────────┐
|
||
// │ RENDER INSTRUCTIONS │
|
||
// │ F5 = Quick preview (colors, transparency) │
|
||
// │ F6 = Full render (slow, for STL export) │
|
||
// │ F7 = Export STL │
|
||
// │ │
|
||
// │ Toggle variables below for different views: │
|
||
// │ • show_beams = IR beam visualization │
|
||
// │ • show_temple = phantom glasses arm │
|
||
// │ • exploded = exploded assembly view │
|
||
// │ • cross_section = cutaway internal view │
|
||
// │ • show_electronics = ATtiny + MOSFETs + wiring │
|
||
// └──────────────────────────────────────────────────────┘
|
||
|
||
include <common.scad>
|
||
|
||
// ── View Toggles ─────────────────────────────────────────────
|
||
show_beams = true; // Show purple IR beam cones
|
||
show_temple = true; // Show transparent phantom glasses arm
|
||
exploded = false; // Exploded assembly view
|
||
cross_section = false; // Cutaway to show internals
|
||
show_electronics = true; // Show ATtiny, MOSFETs, wires
|
||
|
||
// ── Glasses Temple Parameters (measure yours!) ───────────────
|
||
temple_w = 5.0; // Width of your glasses temple arm
|
||
temple_h = 2.8; // Thickness of temple arm
|
||
temple_len = 140; // Length (for phantom display)
|
||
|
||
// ── Clip Parameters ──────────────────────────────────────────
|
||
clip_len = 52; // Length of main clip body
|
||
clip_wall = 2.0; // Shell thickness
|
||
clip_tol = 0.4; // Tolerance around temple
|
||
grip_lip = 0.6; // Inward lip for snap retention
|
||
|
||
// ── LED Array ────────────────────────────────────────────────
|
||
led_count = 5; // LEDs along top of each clip
|
||
led_pitch = 9.0; // Center-to-center spacing
|
||
led_dia = 3.0; // 3mm LEDs
|
||
led_fwd_tilt = 12; // Forward tilt angle (toward cameras)
|
||
led_out_tilt = 5; // Slight outward splay
|
||
|
||
// ── Battery Pod ──────────────────────────────────────────────
|
||
batt_l = 38; // Battery compartment length
|
||
batt_w = 22; // Battery compartment width
|
||
batt_h = 8; // Battery compartment height
|
||
|
||
// ── Bridge Connector ─────────────────────────────────────────
|
||
bridge_w = 3.0; // Width of brow connector strip
|
||
bridge_h = 2.5; // Height of connector
|
||
|
||
// ── Derived ──────────────────────────────────────────────────
|
||
clip_inner_w = temple_w + clip_tol * 2;
|
||
clip_inner_h = temple_h + clip_tol * 2;
|
||
clip_outer_w = clip_inner_w + clip_wall * 2;
|
||
clip_outer_h = clip_inner_h + clip_wall + 5; // extra height above for LEDs
|
||
ex = exploded ? 25 : 0; // exploded offset
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// CLIP BODY — main structural piece
|
||
// ══════════════════════════════════════════════════════════════
|
||
module clip_body() {
|
||
difference() {
|
||
// Outer shell — rounded block
|
||
minkowski() {
|
||
cube([clip_len - 2, clip_outer_w - 2, clip_outer_h - 1],
|
||
center = true);
|
||
sphere(r = 1.0, $fn = 16);
|
||
}
|
||
|
||
// Temple channel (open at bottom for snap-on)
|
||
translate([0, 0, -1])
|
||
cube([clip_len + 2, clip_inner_w, clip_inner_h + 2],
|
||
center = true);
|
||
|
||
// Bottom slot opening (narrower than channel for snap fit)
|
||
translate([0, 0, -(clip_outer_h/2)])
|
||
cube([clip_len + 2, clip_inner_w - grip_lip * 2, clip_wall + 2],
|
||
center = true);
|
||
|
||
// LED sockets (angled holes through top)
|
||
for (i = [0 : led_count - 1]) {
|
||
x = -(led_pitch * (led_count - 1) / 2) + i * led_pitch;
|
||
translate([x, 0, clip_outer_h / 2 - 1])
|
||
rotate([-led_fwd_tilt, 0, 0])
|
||
cylinder(d = led_dia + 0.4, h = 12, $fn = 24);
|
||
}
|
||
|
||
// Wire channel (horizontal bore through body)
|
||
translate([0, 0, clip_outer_h / 2 - 3.5])
|
||
rotate([0, 90, 0])
|
||
cylinder(d = 2.0, h = clip_len + 4, center = true, $fn = 16);
|
||
|
||
// Wire exit holes at each end
|
||
for (sx = [-1, 1])
|
||
translate([sx * (clip_len/2 + 0.5), 0, clip_outer_h / 2 - 3.5])
|
||
sphere(d = 3.0, $fn = 16);
|
||
}
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// BATTERY POD — sits behind the ear
|
||
// ══════════════════════════════════════════════════════════════
|
||
module battery_pod() {
|
||
difference() {
|
||
// Outer shell
|
||
minkowski() {
|
||
cube([batt_l - 3, batt_w - 3, batt_h - 1], center = true);
|
||
sphere(r = 1.5, $fn = 16);
|
||
}
|
||
// Battery cavity
|
||
translate([0, 0, 1])
|
||
cube([batt_l - 4, batt_w - 4, batt_h], center = true);
|
||
|
||
// Charge port cutout (USB-C, one end)
|
||
translate([-(batt_l/2), 0, 0])
|
||
rotate([0, 90, 0])
|
||
rounded_rect_extrude(9.5, 3.5, 1.0, 6);
|
||
|
||
// Wire exit (connects to clip)
|
||
translate([batt_l/2, 0, batt_h/2 - 2])
|
||
rotate([0, 90, 0])
|
||
cylinder(d = 2.5, h = 4, center = true, $fn = 16);
|
||
|
||
// Slide switch cutout (top)
|
||
translate([-8, 0, batt_h/2])
|
||
cube([10, 4.5, 4], center = true);
|
||
}
|
||
}
|
||
|
||
// Helper for charge port cutout
|
||
module rounded_rect_extrude(w, h, r, depth) {
|
||
linear_extrude(depth)
|
||
offset(r = r) offset(delta = -r)
|
||
square([w - 2*r, h - 2*r], center = true);
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// BROW BRIDGE — connects left and right clips
|
||
// ══════════════════════════════════════════════════════════════
|
||
module brow_bridge(span = 20) {
|
||
// Flexible strip that goes across the nose bridge area
|
||
color(CLR_FRAME)
|
||
difference() {
|
||
minkowski() {
|
||
cube([span, bridge_w - 1, bridge_h - 0.5], center = true);
|
||
sphere(r = 0.5, $fn = 12);
|
||
}
|
||
// Wire channel
|
||
rotate([0, 90, 0])
|
||
cylinder(d = 1.5, h = span + 4, center = true, $fn = 12);
|
||
}
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// FULL ASSEMBLY — single side (mirrored for both)
|
||
// ══════════════════════════════════════════════════════════════
|
||
module clip_assembly_side(side = "left") {
|
||
mirror_x = (side == "right") ? 1 : 0;
|
||
|
||
mirror([mirror_x, 0, 0]) {
|
||
// ── Clip body ──
|
||
translate([0, 0, ex])
|
||
color(CLR_FRAME)
|
||
clip_body();
|
||
|
||
// ── LEDs in sockets ──
|
||
for (i = [0 : led_count - 1]) {
|
||
x = -(led_pitch * (led_count - 1) / 2) + i * led_pitch;
|
||
translate([x, 0, clip_outer_h / 2 - 1 + ex * 1.5])
|
||
rotate([-led_fwd_tilt, 0, 0])
|
||
ir_led_3mm(show_beam = show_beams);
|
||
}
|
||
|
||
// ── Battery pod (behind clip) ──
|
||
translate([clip_len/2 + batt_l/2 + 3, 0, -ex * 0.3])
|
||
color(CLR_FRAME)
|
||
battery_pod();
|
||
|
||
// ── Battery inside pod ──
|
||
translate([clip_len/2 + batt_l/2 + 3, 0, 1 - ex * 0.1])
|
||
lipo_battery(l = batt_l - 6, w = batt_w - 6, h = batt_h - 4);
|
||
|
||
// ── Slide switch ──
|
||
translate([clip_len/2 + batt_l/2 + 3 - 8, 0, batt_h/2 + 0.5 - ex * 0.2])
|
||
slide_switch();
|
||
|
||
// ── Phantom temple arm ──
|
||
if (show_temple)
|
||
translate([20, 0, 0])
|
||
phantom_temple(length = temple_len, width = temple_w, height = temple_h);
|
||
|
||
// ── Electronics (inside clip body) ──
|
||
if (show_electronics) {
|
||
// ATtiny85
|
||
translate([10, 0, clip_outer_h / 2 - 5 + ex * 0.8])
|
||
rotate([0, 0, 90])
|
||
attiny85_dip8();
|
||
|
||
// MOSFETs
|
||
translate([-5, 2, clip_outer_h / 2 - 5 + ex * 0.6])
|
||
mosfet_sot23();
|
||
translate([-5, -2, clip_outer_h / 2 - 5 + ex * 0.6])
|
||
mosfet_sot23();
|
||
|
||
// Button (on outer face of clip)
|
||
translate([-15, clip_outer_w/2 + 1, 0])
|
||
rotate([90, 0, 0])
|
||
tact_button();
|
||
}
|
||
}
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// MAIN ASSEMBLY
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
// Left clip
|
||
translate([-40, 0, 0])
|
||
clip_assembly_side("left");
|
||
|
||
// Right clip
|
||
translate([40, 0, 0])
|
||
clip_assembly_side("right");
|
||
|
||
// Brow bridge connector
|
||
translate([0, 0, clip_outer_h / 2 + ex * 0.8])
|
||
brow_bridge(span = 60);
|
||
|
||
// ── Optional cross section ──
|
||
if (cross_section) {
|
||
// Cut away front half to show internals
|
||
translate([0, 50, 0])
|
||
cube([200, 100, 100], center = true);
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// PRINTABLE PARTS (uncomment one at a time for STL export)
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
// For 3D printing, render each part flat on the build plate:
|
||
|
||
// !clip_body(); // Print 2×
|
||
// !battery_pod(); // Print 2×
|
||
// !brow_bridge(span = 60); // Print 1×
|
||
|
||
// Print settings:
|
||
// Material: PETG (flexibility + heat resistance)
|
||
// Layer height: 0.16mm
|
||
// Infill: 30% gyroid
|
||
// Supports: Yes (for LED socket overhangs)
|
||
// Wall count: 3
|
||
// Brim: Yes (small footprint parts)
|