Devesh36/usb-c-phone-stand-light
A 3D-printed tilted phone stand integrating USB‑C PD power, a Qi wireless charging coil with magnetic alignment ring, indicator LEDs/light pipes, control and protection PCBs, wiring, fasteners, and rubber feet.
- Version
- 0.0.6
- License
- unset
- Stars
- 0
charger-models.ts
import type { JscadOperation } from "jscad-planner";
import { chargerFrame, dock } from "./stand-mechanics";
type V3 = [number, number, number];
const box = (size: V3, center: V3): JscadOperation => ({
type: "translate",
vector: center,
shape: { type: "cuboid", size },
});
const cylinder = (
radius: number,
height: number,
center: V3,
): JscadOperation => ({
type: "cylinder",
radius,
height,
center,
resolution: 64,
});
const union = (...shapes: JscadOperation[]): JscadOperation => ({
type: "union",
shapes,
});
const subtract = (...shapes: JscadOperation[]): JscadOperation => ({
type: "subtract",
shapes,
});
const color = (rgb: V3, shape: JscadOperation): JscadOperation => ({
type: "colorize",
color: rgb,
shape,
});
function usbCapsule(depth:number,width:number,height:number,center:V3):JscadOperation{
return {type:"hull",shapes:[-1,1].map(side=>({type:"translate",vector:[center[0],center[1]+side*(width-height)/2,center[2]],shape:{type:"rotate",angles:[0,Math.PI/2,0],shape:cylinder(height/2,depth,[0,0,0])}}))};
}
const pdModuleLocal = union(
color([0.1, 0.4, 0.68], box([20.8, 20.2, 1.6], [-26.9, 12, 6.2])),
color([0.08, 0.09, 0.12], box([5, 5, 1.5], [-24, 12, 7.75])),
color(
[0.68, 0.71, 0.75],
subtract(
usbCapsule(7.3,8.9,3.2,[-37.35,12,8.6]),
usbCapsule(7.6,8.25,2.5,[-38.4,12,8.6]),
),
),
color([0.06,0.07,0.09],box([3.5,5.8,0.65],[-38.2,12,8.6])),
color([0.06, 0.07, 0.09], box([5.8, 5.1, 1.8], [-17.5, 12, 7.9])),
);
export const pdModule:JscadOperation={type:"translate",vector:[-5,0,-2],shape:pdModuleLocal};
const qiControllerLocal = union(
color([0.09, 0.39, 0.64], box([68.58, 54.61, 1.6], [0, 0, -9.2])),
color([0.04, 0.05, 0.07], box([9, 9, 3], [0, 3, -6.9])),
color([0.12, 0.13, 0.16], box([9, 9, 4.4], [-16, 0, -6.2])),
color(
[0.65, 0.68, 0.72],
subtract(
box([7, 5, 2.4], [-30, -1.27, -7.2]),
box([5.6, 5.1, 1.4], [-31.2, -1.27, -7.2]),
),
),
);
export const qiController=union(
{type:"rotate",angles:[0,0,Math.PI/2],shape:qiControllerLocal},
color([.18,.19,.21],box([5,5,2.6],[1.27,-37,-7.2])),
color([.65,.68,.72],box([5,4,2.2],[1.27,-32.5,-7.2])),
);
export const qiPlate = union(
color([0.1, 0.11, 0.13], box([53, 53, 1], [0, 0, 0])),
color(
[0.69, 0.37, 0.12],
subtract(cylinder(22.5, 3, [0, 0, 2]), cylinder(9, 4, [0, 0, 2])),
),
color([0.76, 0.66, 0.43], cylinder(8, 3, [0, 0, 2])),
);
export const magneticRing = color(
[0.24, 0.27, 0.3],
subtract(cylinder(29, 1.4, [0, 0, 6.6]), cylinder(24, 3, [0, 0, 6.6])),
);
export const rubberFeet = color(
[0.06, 0.065, 0.07],
union(
...[-34, 34].flatMap((x) =>
[-41, 41].map((y) => cylinder(4, 2, [x, y, -1])),
),
),
);
export const standScrews = color(
[0.17, 0.19, 0.22],
union(
...[-24, 24].flatMap((x) =>
[-4, 4].map((y) =>
union(
cylinder(1.5, 12, [x, y - 8, 13]),
cylinder(2.8, 1.8, [x, y - 8, 19.9]),
),
),
),
),
);
export const supportScrews=color([.17,.19,.22],union(...[-17,17].map(x=>union(cylinder(1.5,12,[x,39,11]),cylinder(2.8,1.8,[x,39,17.9])))));
export const lidScrews = color(
[0.17, 0.19, 0.22],
union(
...[-34, 34].flatMap((x) =>
[-42, 42].map((y) =>
union(cylinder(1, 6, [x, y, 11]), cylinder(2, 1.2, [x, y, 14.6])),
),
),
),
);
export const coverScrews = color(
[0.3, 0.32, 0.34],
union(
...[-38, 38].map((x) =>
union(
cylinder(1.5, 12, [x, 0, -1.275]),
cylinder(2.8, 1.65, [x, 0, 5.55]),
),
),
),
);
export function toChargerWorld([x, y, z]: V3): V3 {
const c = Math.cos(dock.tilt),
s = Math.sin(dock.tilt);
return [x, chargerFrame.y + y * c - z * s, chargerFrame.z + y * s + z * c];
}
function wire(points: V3[], rgb: V3): JscadOperation {
return color(
rgb,
union(
...points.slice(1).map(
(p, i) =>
({
type: "hull",
shapes: [
{
type: "sphere",
radius: 0.7,
center: points[i],
resolution: 12,
},
{ type: "sphere", radius: 0.7, center: p, resolution: 12 },
],
}) as JscadOperation,
),
),
);
}
// All harness vertices are in assembly millimeters. Positive and return
// conductors terminate at separate connector pads; they are not decorative loops.
const chargePath: V3[] = [
[-10,-26.46,5],[-10,-12,8],[20,4,8],[20,4,15],
toChargerWorld([20,-78,-9]),toChargerWorld([1.27,-56,-9.5]),
toChargerWorld([1.27,-38,-7.2]),
];
export const chargingHarness = union(
wire(chargePath,[.77,.09,.08]),
wire([[-10,-31.54,5],...chargePath.slice(1).map(([x,y,z]):V3=>[x+1.8,y,z])],[.07,.08,.1]),
);
export const supplyHarness = union(
wire([[-22.5,12,7],[-33,4,8],[-30,-26.46,5]],[.77,.09,.08]),
wire([[-20.9,12,7],[-31.4,4,8],[-30,-31.54,5]],[.07,.08,.1]),
);
const statusPath:V3[]=[toChargerWorld([-14.6,-13.3,-7.6]),toChargerWorld([20,-30,-7.6]),toChargerWorld([20,-78,-9]),[20,4,15],[20,4,8],[7,-20.46,5]];
export const statusHarness=union(wire(statusPath,[.13,.63,.24]),wire([...statusPath.slice(0,-1).map(([x,y,z]):V3=>[x+1.7,y,z]),[7,-25.54,5]],[.08,.1,.12]));
export const panelHarness=union(...[[.8,.07,.06],[.07,.08,.1],[.1,.65,.24],[.12,.13,.15]].map((rgb,i)=>wire([[23,-25.19-i*2.54,5],[18,-34-i*1.2,7],[5,-35.19-i*2.54,9],[0,-35.19-i*2.54,11.2]],rgb as V3)));
export const coilHarness=union(
wire([[27,12,-7],[27.4,20,-5],[27.4,20,4.5],[16,15,3.2]],[.7,.34,.1]),
wire([[27,14,-7],[27.4,21,-5],[27.4,21,4.5],[14,17,3.2]],[.66,.3,.08]),
);
// Nominal 3.45 x 3.45 x 1.9 mm packages; both footprints rotate 45 degrees.
// Neutral lens colors describe the unpowered parts, not visible red/green light.
export const indicatorModule=union(
color([.05,.32,.18],box([34,14,.8],[0,-39,11.1])),
...[-9,9].flatMap(x=>[
color([.92,.92,.9],{type:"translate",vector:[x,-39,11.75],shape:{type:"rotate",angles:[0,0,Math.PI/4],shape:box([3.45,3.45,.5],[0,0,0])}}),
color([.8,.82,.85],cylinder(1.5,1.4,[x,-39,12.7])),
]),
);