UNPKG

ducki

Version:

Simple CLI for working with KiCad projects.

141 lines (115 loc) 3.74 kB
// Single-shot ChatGPT; absurd this works out of the box. AI will eat us all. // Prompt: // // I am making a script for kicad that uses the cli that will // render a fly around the board. I want to give an initial // rotation of the board, then from the board's perspective // rotate it about its y-axis. Should I use quaternions and // can you provide code that would create the series of x, y, // z rotation values I need? // // Note: I had to further specify JavaScript (it generated Python) // and a handful of types were added to make it TypeScript. // ============================== // Quaternion utilities // Stored as [w, x, y, z] // ============================== type Vector3 = [ number, number, number ]; type Vector4 = [ number, number, number, number ]; export type XYZ = { x: number, y: number, z: number }; function quatMul(a: Vector4, b: Vector4): Vector4 { const [aw, ax, ay, az] = a; const [bw, bx, by, bz] = b; return [ aw * bw - ax * bx - ay * by - az * bz, aw * bx + ax * bw + ay * bz - az * by, aw * by - ax * bz + ay * bw + az * bx, aw * bz + ax * by - ay * bx + az * bw, ]; } function quatNormalize(q: Vector4): Vector4 { const [w, x, y, z] = q; const n = Math.hypot(w, x, y, z); return [w / n, x / n, y / n, z / n]; } function quatFromAxisAngle(axis: Vector3, angleRad: number): Vector4 { const [ax, ay, az] = axis; const half = angleRad * 0.5; const s = Math.sin(half); return quatNormalize([ Math.cos(half), ax * s, ay * s, az * s, ]); } // Intrinsic XYZ (local X, then Y, then Z) function quatFromEulerXYZ(x: number, y: number, z: number) { const qx = quatFromAxisAngle([1, 0, 0], x); const qy = quatFromAxisAngle([0, 1, 0], y); const qz = quatFromAxisAngle([0, 0, 1], z); return quatNormalize(quatMul(quatMul(qx, qy), qz)); } function quatToEulerXYZ(q: Vector4): Vector3 { const [w, x, y, z] = q; // Rotation matrix elements const r00 = 1 - 2 * (y * y + z * z); const r01 = 2 * (x * y - w * z); const r02 = 2 * (x * z + w * y); const r12 = 2 * (y * z - w * x); const r22 = 1 - 2 * (x * x + y * y); // Clamp for numerical safety const clamped = Math.max(-1, Math.min(1, r02)); const yAngle = Math.asin(clamped); const xAngle = Math.atan2(-r12, r22); const zAngle = Math.atan2(-r01, r00); return [xAngle, yAngle, zAngle]; } // ============================== // Main generator // ============================== export function generateSpinFrames( rot: XYZ, // [x, y, z] degrees totalSpinDeg: number, // degrees frames: number, // number of frames ) { const deg2rad = (d: number) => d * Math.PI / 180; const rad2deg = (r: number) => r * 180 / Math.PI; const [x0, y0, z0] = [ rot.x, rot.y, rot.z ].map(deg2rad); const q0 = quatFromEulerXYZ(x0, y0, z0); const result: Array<XYZ> = []; for (let i = 0; i < frames; i++) { const t = frames > 1 ? i / (frames - 1) : 0; const spinRad = deg2rad(totalSpinDeg * t); // Local Y rotation (post-multiply) const qSpinY = quatFromAxisAngle([0, 1, 0], spinRad); const q = quatNormalize(quatMul(q0, qSpinY)); const [xr, yr, zr] = quatToEulerXYZ(q); result.push({ x: rad2deg(xr), y: rad2deg(yr), z: rad2deg(zr), }); } return result; } // ============================== // Example usage // ============================== /* const frames = generateLocalYSpinFrames({ initialEulerDeg: [20, 30, 0], // initial board orientation totalSpinDeg: 360, // full local-Y spin frames: 120, }); // Print first few frames for (let i = 0; i < 5; i++) { const [x, y, z] = frames[i]; console.log( i, x.toFixed(2), y.toFixed(2), z.toFixed(2) ); } */