zk-glasses/hardware/integrated_frame.scad

575 lines
26 KiB
OpenSCAD
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// ZK-Glasses — Stealth Integrated IR Frame
// Flush-mounted SMD LEDs behind IR-pass filter strips
// Looks like normal glasses — invisible except to cameras
// ============================================================
//
// Design philosophy:
// Like Meta Ray-Ban cameras — tiny components hidden in
// the frame's natural geometry. A thin IR-pass filter strip
// runs along the brow bar, looking like a decorative dark
// accent line. Behind it: SMD IR LEDs on a flex PCB.
//
// ┌──────────────────────────────────────────────────────┐
// │ F5 = Preview (fast, with colors + beams) │
// │ F6 = Render (slow, for STL export) │
// │ Customizer panel for real-time parameter tweaks │
// └──────────────────────────────────────────────────────┘
include <common.scad>
// ╔═══════════════════════════════════════════════════════════╗
// ║ VIEW CONTROLS ║
// ╚═══════════════════════════════════════════════════════════╝
/* [View] */
show_beams = true; // IR beam visualization (camera view)
show_filter = true; // IR-pass filter strip overlay
show_flex_pcb = true; // Internal flex PCB + SMD LEDs
show_internals = true; // Battery, MCU, etc.
cross_section = false; // Cutaway showing internal channels
exploded = false; // Exploded assembly view
print_layout = false; // Flat print-ready parts
camera_view = false; // Simulate how a camera sees it (bright beams)
// ╔═══════════════════════════════════════════════════════════╗
// ║ FACE MEASUREMENTS ║
// ╚═══════════════════════════════════════════════════════════╝
/* [Face Dimensions] */
face_width = 142; // Total frame width
lens_width = 50; // Lens opening width
lens_height = 36; // Lens opening height
lens_corner_r = 9; // Corner radius (↑ = rounder)
bridge_gap = 18; // Nose bridge gap
nose_bridge_drop = 5; // Bridge drop below lens center
/* [Temple Arms] */
temple_length = 140; // Temple arm length
temple_width = 5.5; // Temple width
temple_thickness = 4.0; // Temple thickness
temple_angle = 8; // Outward splay
ear_bend_angle = 22; // Ear hook curl
ear_bend_start = 108; // Where bend begins
// ╔═══════════════════════════════════════════════════════════╗
// ║ FRAME STRUCTURE ║
// ╚═══════════════════════════════════════════════════════════╝
/* [Frame] */
brow_thickness = 8.0; // Brow bar height (houses LED channel)
rim_thickness = 3.5; // Lower/side rim
frame_depth = 7.0; // Front-to-back depth
frame_fillet = 1.2; // Edge rounding
// ╔═══════════════════════════════════════════════════════════╗
// ║ STEALTH LED CONFIGURATION ║
// ╚═══════════════════════════════════════════════════════════╝
/* [IR LEDs (Stealth)] */
led_count_brow = 4; // SMD LEDs per side along brow
led_count_bridge = 1; // Bridge LED (0 or 1)
led_count_temple = 2; // LEDs per temple arm
led_pitch = 10.0; // Brow LED center-to-center
temple_led_pitch = 8.0; // Temple LED spacing
// Filter strip dimensions
filter_width = 3.5; // Height of the IR filter window
filter_depth = 0.6; // Filter material thickness
filter_recess = 0.3; // How deep filter sits into frame
// Internal LED channel
channel_height = 4.5; // Internal cavity height
channel_depth = 5.0; // Internal cavity depth
// ╔═══════════════════════════════════════════════════════════╗
// ║ BATTERY & ELECTRONICS ║
// ╚═══════════════════════════════════════════════════════════╝
/* [Battery] */
batt_length = 30;
batt_width = 10;
batt_thickness = 4.0;
// ── Derived ──────────────────────────────────────────────────
total_leds = (led_count_brow + led_count_temple) * 2 + led_count_bridge;
half_bridge = bridge_gap / 2;
lens_cx = half_bridge + lens_width / 2;
brow_y = lens_height / 2 + brow_thickness / 2;
filter_y = lens_height / 2 + brow_thickness * 0.35; // centered in brow
ex = exploded ? 30 : 0;
// Total brow LED span (for filter strip length)
brow_led_span = led_pitch * (led_count_brow - 1);
filter_length_side = brow_led_span + led_pitch; // per side
filter_length_total = face_width - 12; // nearly full width
// ══════════════════════════════════════════════════════════════
// FRAME FRONT — 2D profile
// ══════════════════════════════════════════════════════════════
module frame_front_2d() {
difference() {
union() {
// Left lens surround
translate([-lens_cx, 0])
offset(r = rim_thickness)
rounded_rect_2d(lens_width, lens_height, lens_corner_r);
// Right lens surround
translate([lens_cx, 0])
offset(r = rim_thickness)
rounded_rect_2d(lens_width, lens_height, lens_corner_r);
// Bridge
translate([0, lens_height/2 - nose_bridge_drop])
square([bridge_gap + rim_thickness * 1.5, rim_thickness * 2],
center = true);
// Thickened brow bar — the key structural element
translate([0, lens_height/2 + brow_thickness/2 - 0.5])
square([face_width + 2, brow_thickness], center = true);
}
// Lens openings
translate([-lens_cx, 0])
rounded_rect_2d(lens_width, lens_height, lens_corner_r);
translate([lens_cx, 0])
rounded_rect_2d(lens_width, lens_height, lens_corner_r);
}
}
// ══════════════════════════════════════════════════════════════
// FRAME FRONT — 3D with LED channels and filter slots
// ══════════════════════════════════════════════════════════════
module frame_front_3d() {
color(CLR_FRAME)
difference() {
// Base frame shape
minkowski() {
linear_extrude(height = frame_depth - frame_fillet * 2,
center = true)
offset(delta = -frame_fillet)
frame_front_2d();
sphere(r = frame_fillet, $fn = 16);
}
// ── Filter slot (front face) ──
// A narrow horizontal groove across the brow bar front
// The IR filter strip press-fits into this slot
translate([0, filter_y, frame_depth/2 - filter_recess])
cube([filter_length_total, filter_width, filter_depth * 2],
center = true);
// ── Internal LED channel ──
// Runs the full width of brow bar, behind the filter slot
// Houses the flex PCB strip with SMD LEDs
translate([0, filter_y, 0])
cube([filter_length_total - 4, channel_height, channel_depth],
center = true);
// ── Wire channels from brow to temples ──
for (sx = [-1, 1])
translate([sx * (face_width/2 - 2), filter_y, 0])
rotate([0, 90, 0])
cylinder(d = 2.2, h = 15, center = true, $fn = 16);
// ── Temple LED slots (near hinges) ──
for (sx = [-1, 1])
for (i = [0 : led_count_temple - 1]) {
tx = sx * (face_width/2 + 2 + i * temple_led_pitch);
translate([tx, 0, frame_depth/2 - filter_recess])
cube([3.8, 3.2, filter_depth * 2], center = true);
// Cavity behind
translate([tx, 0, 0])
cube([4.5, 4.0, channel_depth], center = true);
}
// ── Bridge LED slot ──
if (led_count_bridge > 0)
translate([0, lens_height/2 - nose_bridge_drop,
frame_depth/2 - filter_recess]) {
cube([3.8, 3.2, filter_depth * 2], center = true);
translate([0, 0, -channel_depth/2])
cube([4.5, 4.0, channel_depth], center = true);
}
// ── Flex PCB insertion slot (side of brow bar) ──
// The strip slides in from the temple end
for (sx = [-1, 1])
translate([sx * (filter_length_total/2 + 1), filter_y, 0])
cube([4, 4.5, 1.0], center = true);
}
}
// ══════════════════════════════════════════════════════════════
// IR-PASS FILTER STRIPS — the stealth magic
// Looks like a subtle dark accent line on the frame
// Actually transmits 940nm IR while blocking visible light
// ══════════════════════════════════════════════════════════════
module filter_strips() {
// Main brow bar filter — continuous strip
translate([0, filter_y, frame_depth/2 - filter_recess/2])
ir_filter_strip(
length = filter_length_total - 1,
width = filter_width - 0.2,
thickness = filter_depth
);
// Temple LED filters (small individual pieces)
for (sx = [-1, 1])
for (i = [0 : led_count_temple - 1]) {
tx = sx * (face_width/2 + 2 + i * temple_led_pitch);
translate([tx, 0, frame_depth/2 - filter_recess/2])
ir_filter_strip(length = 3.4, width = 2.8,
thickness = filter_depth);
}
// Bridge filter
if (led_count_bridge > 0)
translate([0, lens_height/2 - nose_bridge_drop,
frame_depth/2 - filter_recess/2])
ir_filter_strip(length = 3.4, width = 2.8,
thickness = filter_depth);
}
// ══════════════════════════════════════════════════════════════
// FLEX PCB + SMD LEDs — hidden inside the brow bar
// ══════════════════════════════════════════════════════════════
module internal_led_assembly() {
// Left brow flex strip
translate([-(half_bridge + filter_length_side/2 + 4),
filter_y, 0])
rotate([0, 0, 0])
flex_pcb_strip(
length = filter_length_side + 4,
led_count = led_count_brow,
led_pitch = led_pitch
);
// Right brow flex strip
translate([(half_bridge + filter_length_side/2 + 4),
filter_y, 0])
rotate([0, 0, 0])
flex_pcb_strip(
length = filter_length_side + 4,
led_count = led_count_brow,
led_pitch = led_pitch
);
// Bridge LED (single SMD)
if (led_count_bridge > 0)
translate([0, lens_height/2 - nose_bridge_drop, 0])
ir_led_smd(show_beam = false);
// Temple LEDs
for (sx = [-1, 1])
for (i = [0 : led_count_temple - 1]) {
tx = sx * (face_width/2 + 2 + i * temple_led_pitch);
translate([tx, 0, 0])
ir_led_smd(show_beam = false);
}
}
// ══════════════════════════════════════════════════════════════
// IR BEAM VISUALIZATION — what cameras see
// ══════════════════════════════════════════════════════════════
module ir_beams() {
beam_alpha = camera_view ? 0.12 : 0.04;
beam_color = camera_view
? [1.0, 1.0, 1.0, 0.15] // white bloom (camera saturated)
: [0.55, 0.0, 1.0, beam_alpha]; // faint purple
// Brow bar beams — wide wash from the filter strip
for (sx = [-1, 1])
for (i = [0 : led_count_brow - 1]) {
x = sx * (half_bridge + 8 + i * led_pitch);
translate([x, filter_y, frame_depth/2])
color(beam_color)
// Wide angle SMD beam (±60°)
cylinder(d1 = filter_width, d2 = 90, h = 70, $fn = 24);
}
// Bridge beam
if (led_count_bridge > 0)
translate([0, lens_height/2 - nose_bridge_drop, frame_depth/2])
color(beam_color)
cylinder(d1 = 3, d2 = 60, h = 50, $fn = 24);
// Temple beams
for (sx = [-1, 1])
for (i = [0 : led_count_temple - 1]) {
tx = sx * (face_width/2 + 2 + i * temple_led_pitch);
translate([tx, 0, frame_depth/2])
color(beam_color)
cylinder(d1 = 3, d2 = 50, h = 45, $fn = 24);
}
}
// ══════════════════════════════════════════════════════════════
// NOSE BRIDGE
// ══════════════════════════════════════════════════════════════
module nose_assembly() {
for (sx = [-1, 1]) {
color(CLR_SILVER)
translate([sx * 5, -lens_height/2 + nose_bridge_drop, 0])
rotate([20, 0, sx * 15])
cylinder(d = 1.2, h = 14, $fn = 12);
translate([sx * 7, -lens_height/2 + nose_bridge_drop - 10,
frame_depth/2 + 2])
rotate([30, sx * 10, 0])
nose_pad();
}
}
// ══════════════════════════════════════════════════════════════
// TEMPLE ARM — clean with hidden wire channel
// ══════════════════════════════════════════════════════════════
module temple_arm() {
color(CLR_FRAME)
difference() {
union() {
// Main section
minkowski() {
cube([ear_bend_start - 2,
temple_width - 1, temple_thickness - 0.5],
center = true);
sphere(r = 0.5, $fn = 12);
}
// Ear bend
translate([ear_bend_start/2, 0, 0])
rotate([0, ear_bend_angle, 0])
translate([(temple_length - ear_bend_start)/2, 0, 0])
minkowski() {
cube([temple_length - ear_bend_start - 2,
temple_width - 1,
temple_thickness - 0.5],
center = true);
sphere(r = 0.5, $fn = 12);
}
// Battery bulge (subtle thickening at tip)
translate([ear_bend_start/2, 0, 0])
rotate([0, ear_bend_angle, 0])
translate([(temple_length - ear_bend_start)/2 + 5,
0, 0])
minkowski() {
cube([batt_length + 2, batt_width + 1,
batt_thickness], center = true);
sphere(r = 1.0, $fn = 12);
}
}
// Wire channel (full length)
rotate([0, 90, 0])
cylinder(d = 1.8, h = ear_bend_start + 10,
center = true, $fn = 12);
// Battery cavity
translate([ear_bend_start/2, 0, 0])
rotate([0, ear_bend_angle, 0])
translate([(temple_length - ear_bend_start)/2 + 5,
0, 0.5])
cube([batt_length - 2, batt_width - 2,
batt_thickness - 0.5], center = true);
// Charge port (bottom of battery area, very discreet)
translate([ear_bend_start/2, 0, 0])
rotate([0, ear_bend_angle, 0])
translate([(temple_length - ear_bend_start)/2 + 5,
0, -(batt_thickness/2 + 1)])
cube([9, 3.2, 4], center = true);
// Button recess (tiny, inner face of temple)
translate([5, -(temple_width/2 + 0.5), 0])
cube([4, 2, 2.5], center = true);
}
}
// ══════════════════════════════════════════════════════════════
// HINGE
// ══════════════════════════════════════════════════════════════
module hinge_block() {
color(CLR_FRAME)
difference() {
minkowski() {
cube([8, 4, frame_depth - 1], center = true);
sphere(r = 0.5, $fn = 12);
}
cylinder(d = 1.5, h = frame_depth + 2,
center = true, $fn = 16);
}
color(CLR_SILVER)
cylinder(d = 1.2, h = frame_depth + 1,
center = true, $fn = 12);
}
// ══════════════════════════════════════════════════════════════
// TEMPLE INTERNALS
// ══════════════════════════════════════════════════════════════
module temple_electronics() {
// Battery
tip_x = ear_bend_start/2;
translate([tip_x, 0, 0])
rotate([0, ear_bend_angle, 0])
translate([(temple_length - ear_bend_start)/2 + 5, 0, 1])
rotate([0, 0, 90])
lipo_battery(l = batt_length - 4,
w = batt_width - 4,
h = batt_thickness - 1.5);
// Tiny MCU (one temple only)
translate([15, 0, 0.5])
scale(0.5)
attiny85_dip8();
}
// ══════════════════════════════════════════════════════════════
// FULL ASSEMBLY
// ══════════════════════════════════════════════════════════════
module full_assembly() {
// ── Frame front ──
translate([0, 0, ex * 0.3])
frame_front_3d();
// ── IR-pass filter strips ──
if (show_filter)
translate([0, 0, ex * 0.5])
filter_strips();
// ── Internal flex PCB + SMD LEDs ──
if (show_flex_pcb)
translate([0, 0, ex * 0.15])
internal_led_assembly();
// ── IR beams ──
if (show_beams)
translate([0, 0, ex * 0.5])
ir_beams();
// ── Nose pads ──
nose_assembly();
// ── Hinges + Temples ──
for (side = [-1, 1]) {
hinge_x = side * (face_width / 2 + 2);
translate([hinge_x, 0, 0])
hinge_block();
translate([hinge_x + side * ear_bend_start / 2,
0, -ex * 0.2])
rotate([0, 0, side * temple_angle])
translate([side * 5, 0, 0]) {
temple_arm();
if (show_internals)
temple_electronics();
}
}
}
// ══════════════════════════════════════════════════════════════
// PRINT LAYOUT
// ══════════════════════════════════════════════════════════════
module print_parts() {
// Frame front (flat on back)
translate([0, 0, frame_depth/2])
rotate([90, 0, 0])
frame_front_3d();
// Left temple
translate([0, -55, temple_thickness/2])
temple_arm();
// Right temple
translate([0, -75, temple_thickness/2])
temple_arm();
}
// ══════════════════════════════════════════════════════════════
// RENDER
// ══════════════════════════════════════════════════════════════
if (print_layout) {
print_parts();
} else if (cross_section) {
difference() {
full_assembly();
// Cut front half to expose LED channel
translate([0, 0, 50 + frame_depth/2 - 1])
cube([300, 300, 100], center = true);
}
} else {
full_assembly();
}
// ── Info overlay ──
color("White")
translate([0, -lens_height - 15, 0])
linear_extrude(0.1)
text(str("ZK-Glasses Stealth — ", total_leds,
" SMD IR LEDs @ 940nm"),
size = 3.5, halign = "center",
font = "Liberation Mono");
color("Gray")
translate([0, -lens_height - 22, 0])
linear_extrude(0.1)
text(str("Frame: ", face_width,
"mm | IR filter: Wratten 87C equiv"),
size = 2.8, halign = "center",
font = "Liberation Mono");
color([0.55, 0, 1], 0.4)
translate([0, -lens_height - 28, 0])
linear_extrude(0.1)
text("Invisible to eyes — visible only to cameras",
size = 2.5, halign = "center",
font = "Liberation Mono:style=Italic");
// ══════════════════════════════════════════════════════════════
// NOTES
// ══════════════════════════════════════════════════════════════
//
// STEALTH DESIGN:
// The brow bar has a thin horizontal slot cut into its
// front face. An IR-pass filter strip (Kodak Wratten 87C
// or Hoya IR-85) press-fits into this slot. It looks like
// a subtle dark decorative accent line — completely normal.
//
// Behind the filter: a flex PCB strip with SMD IR LEDs
// (OSRAM SFH 4726AS or similar, 940nm, PLCC-2 package).
// Each LED is 3.5 × 2.8 × 1.9mm — invisible from outside.
//
// The 940nm wavelength is completely invisible to human
// eyes (no red glow), but cameras see bright white spots.
//
// IR-PASS FILTER OPTIONS:
// - Kodak Wratten 87C gel: cheapest, ~$5, can be cut to size
// - Hoya IR-85 glass: durable, optical quality, ~$20
// - Lee 87C polyester: thin, flexible, press-fits easily
// - 3D print in dark PLA, thin enough to pass some IR
//
// ASSEMBLY:
// 1. 3D print frame (PETG, matte black)
// 2. Solder SMD LEDs to flex PCB strip
// 3. Slide flex strip into brow bar channel
// 4. Route wires through temple channels
// 5. Press-fit IR filter strips into front slots
// 6. Insert batteries into temple tips
// 7. Snap in charge port, button, MCU
//
// PRINT SETTINGS:
// Material: PETG matte black (or Nylon PA12)
// Layer: 0.12mm (fine detail for filter slot)
// Infill: 40% gyroid
// Walls: 4 perimeters (structural around channels)
// Supports: Tree, touching buildplate only
// Post: Light sanding, optional soft-touch paint
//
// ══════════════════════════════════════════════════════════════