@tscircuit/cli 0.1.126 → 0.1.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/main.js +652 -5
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -54,7 +54,8 @@ Commands:
54
54
  add <component> Add a tscircuit component package to your project
55
55
  remove <component> Remove a tscircuit component package from your
56
56
  project
57
- snapshot [options] Generate schematic and PCB snapshots
57
+ snapshot [options] Generate schematic and PCB snapshots (add --3d for
58
+ 3d preview)
58
59
  setup Setup utilities like GitHub Actions
59
60
  upgrade Upgrade CLI to the latest version
60
61
  search <query> Search for packages in the tscircuit registry
package/dist/main.js CHANGED
@@ -441906,7 +441906,7 @@ var getGlobalDepsInstallCommand = (packageManager, deps) => {
441906
441906
  import { execSync as execSync2 } from "node:child_process";
441907
441907
  var import_semver2 = __toESM2(require_semver2(), 1);
441908
441908
  // package.json
441909
- var version = "0.1.125";
441909
+ var version = "0.1.126";
441910
441910
  var package_default = {
441911
441911
  name: "@tscircuit/cli",
441912
441912
  version,
@@ -441951,7 +441951,9 @@ var package_default = {
441951
441951
  tempy: "^3.1.0",
441952
441952
  tscircuit: "^0.0.491",
441953
441953
  "@tscircuit/circuit-json-util": "^0.0.47",
441954
- "typed-ky": "^0.0.4"
441954
+ "typed-ky": "^0.0.4",
441955
+ "circuit-json-to-simple-3d": "^0.0.3",
441956
+ "@tscircuit/simple-3d-svg": "^0.0.10"
441955
441957
  },
441956
441958
  peerDependencies: {
441957
441959
  typescript: "^5.0.0"
@@ -495782,9 +495784,648 @@ var registerBuild = (program3) => {
495782
495784
  import fs24 from "node:fs";
495783
495785
  import path24 from "node:path";
495784
495786
  init_dist6();
495787
+
495788
+ // node_modules/circuit-json-to-simple-3d/dist/index.js
495789
+ init_dist4();
495790
+
495791
+ // node_modules/@tscircuit/simple-3d-svg/dist/index.js
495792
+ var stlCache = /* @__PURE__ */ new Map;
495793
+ async function loadSTL(url) {
495794
+ if (stlCache.has(url)) {
495795
+ return stlCache.get(url);
495796
+ }
495797
+ const response = await fetch(url);
495798
+ const buffer = await response.arrayBuffer();
495799
+ const mesh = parseSTL(buffer);
495800
+ stlCache.set(url, mesh);
495801
+ return mesh;
495802
+ }
495803
+ function parseSTL(buffer) {
495804
+ const view = new DataView(buffer);
495805
+ const header = new TextDecoder().decode(buffer.slice(0, 5));
495806
+ if (header.toLowerCase() === "solid") {
495807
+ return parseASCIISTL(buffer);
495808
+ } else {
495809
+ return parseBinarySTL(view);
495810
+ }
495811
+ }
495812
+ function parseASCIISTL(buffer) {
495813
+ const text = new TextDecoder().decode(buffer);
495814
+ const lines = text.split(`
495815
+ `).map((line) => line.trim());
495816
+ const triangles = [];
495817
+ let i = 0;
495818
+ while (i < lines.length) {
495819
+ const line = lines[i];
495820
+ if (line && line.startsWith("facet normal")) {
495821
+ const normalMatch = line.match(/facet normal\s+([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)\s+([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)\s+([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/);
495822
+ const normal = normalMatch ? {
495823
+ x: parseFloat(normalMatch[1]),
495824
+ y: parseFloat(normalMatch[2]),
495825
+ z: parseFloat(normalMatch[3])
495826
+ } : { x: 0, y: 0, z: 1 };
495827
+ i++;
495828
+ const vertices = [];
495829
+ while (i < lines.length && lines[i] && !lines[i].startsWith("endfacet")) {
495830
+ const vertexLine = lines[i];
495831
+ if (vertexLine.startsWith("vertex")) {
495832
+ const vertexMatch = vertexLine.match(/vertex\s+([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)\s+([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)\s+([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/);
495833
+ if (vertexMatch) {
495834
+ vertices.push({
495835
+ x: parseFloat(vertexMatch[1]),
495836
+ y: parseFloat(vertexMatch[2]),
495837
+ z: parseFloat(vertexMatch[3])
495838
+ });
495839
+ }
495840
+ }
495841
+ i++;
495842
+ }
495843
+ if (vertices.length === 3) {
495844
+ triangles.push({
495845
+ vertices: [vertices[0], vertices[1], vertices[2]],
495846
+ normal
495847
+ });
495848
+ }
495849
+ }
495850
+ i++;
495851
+ }
495852
+ return {
495853
+ triangles,
495854
+ boundingBox: calculateBoundingBox(triangles)
495855
+ };
495856
+ }
495857
+ function parseBinarySTL(view) {
495858
+ let offset = 80;
495859
+ const numTriangles = view.getUint32(offset, true);
495860
+ offset += 4;
495861
+ const triangles = [];
495862
+ for (let i = 0;i < numTriangles; i++) {
495863
+ const normal = {
495864
+ x: view.getFloat32(offset, true),
495865
+ y: view.getFloat32(offset + 4, true),
495866
+ z: view.getFloat32(offset + 8, true)
495867
+ };
495868
+ offset += 12;
495869
+ const vertices = [
495870
+ {
495871
+ x: view.getFloat32(offset, true),
495872
+ y: view.getFloat32(offset + 4, true),
495873
+ z: view.getFloat32(offset + 8, true)
495874
+ },
495875
+ {
495876
+ x: view.getFloat32(offset + 12, true),
495877
+ y: view.getFloat32(offset + 16, true),
495878
+ z: view.getFloat32(offset + 20, true)
495879
+ },
495880
+ {
495881
+ x: view.getFloat32(offset + 24, true),
495882
+ y: view.getFloat32(offset + 28, true),
495883
+ z: view.getFloat32(offset + 32, true)
495884
+ }
495885
+ ];
495886
+ offset += 36;
495887
+ offset += 2;
495888
+ triangles.push({ vertices, normal });
495889
+ }
495890
+ return {
495891
+ triangles,
495892
+ boundingBox: calculateBoundingBox(triangles)
495893
+ };
495894
+ }
495895
+ function calculateBoundingBox(triangles) {
495896
+ if (triangles.length === 0) {
495897
+ return {
495898
+ min: { x: 0, y: 0, z: 0 },
495899
+ max: { x: 0, y: 0, z: 0 }
495900
+ };
495901
+ }
495902
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
495903
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
495904
+ for (const triangle of triangles) {
495905
+ for (const vertex of triangle.vertices) {
495906
+ minX = Math.min(minX, vertex.x);
495907
+ minY = Math.min(minY, vertex.y);
495908
+ minZ = Math.min(minZ, vertex.z);
495909
+ maxX = Math.max(maxX, vertex.x);
495910
+ maxY = Math.max(maxY, vertex.y);
495911
+ maxZ = Math.max(maxZ, vertex.z);
495912
+ }
495913
+ }
495914
+ return {
495915
+ min: { x: minX, y: minY, z: minZ },
495916
+ max: { x: maxX, y: maxY, z: maxZ }
495917
+ };
495918
+ }
495919
+ function colorToCss(c) {
495920
+ return typeof c === "string" ? c : `rgba(${c[0]},${c[1]},${c[2]},${c[3]})`;
495921
+ }
495922
+ function add(a, b) {
495923
+ return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
495924
+ }
495925
+ function sub(a, b) {
495926
+ return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
495927
+ }
495928
+ function dot(a, b) {
495929
+ return a.x * b.x + a.y * b.y + a.z * b.z;
495930
+ }
495931
+ function cross3(a, b) {
495932
+ return {
495933
+ x: a.y * b.z - a.z * b.y,
495934
+ y: a.z * b.x - a.x * b.z,
495935
+ z: a.x * b.y - a.y * b.x
495936
+ };
495937
+ }
495938
+ function scale9(v, k) {
495939
+ return { x: v.x * k, y: v.y * k, z: v.z * k };
495940
+ }
495941
+ function len(v) {
495942
+ return Math.sqrt(dot(v, v));
495943
+ }
495944
+ function norm(v) {
495945
+ const l = len(v) || 1;
495946
+ return scale9(v, 1 / l);
495947
+ }
495948
+ function rotLocal(p, r = { x: 0, y: 0, z: 0 }) {
495949
+ let { x, y, z: z3 } = p;
495950
+ if (r.x) {
495951
+ const c = Math.cos(r.x);
495952
+ const s = Math.sin(r.x);
495953
+ const y2 = y * c - z3 * s;
495954
+ z3 = y * s + z3 * c;
495955
+ y = y2;
495956
+ }
495957
+ if (r.y) {
495958
+ const c = Math.cos(r.y);
495959
+ const s = Math.sin(r.y);
495960
+ const x2 = x * c + z3 * s;
495961
+ z3 = -x * s + z3 * c;
495962
+ x = x2;
495963
+ }
495964
+ if (r.z) {
495965
+ const c = Math.cos(r.z);
495966
+ const s = Math.sin(r.z);
495967
+ const x2 = x * c - y * s;
495968
+ y = x * s + y * c;
495969
+ x = x2;
495970
+ }
495971
+ return { x, y, z: z3 };
495972
+ }
495973
+ var W_DEF = 400;
495974
+ var H_DEF = 400;
495975
+ var FOCAL = 2;
495976
+ function axes(cam) {
495977
+ const f = norm(sub(cam.lookAt, cam.position));
495978
+ const wUp = { x: 0, y: 1, z: 0 };
495979
+ let r = norm(cross3(f, wUp));
495980
+ if (!len(r))
495981
+ r = { x: 1, y: 0, z: 0 };
495982
+ const u = cross3(r, f);
495983
+ return { r, u, f };
495984
+ }
495985
+ function toCam(p, cam) {
495986
+ const { r, u, f } = axes(cam);
495987
+ const d = sub(p, cam.position);
495988
+ return { x: dot(d, r), y: dot(d, u), z: dot(d, f) };
495989
+ }
495990
+ function proj(p, w, h, focal) {
495991
+ if (p.z <= 0)
495992
+ return null;
495993
+ const s = focal / p.z;
495994
+ return { x: p.x * s * w / 2, y: -p.y * s * h / 2, z: p.z };
495995
+ }
495996
+ var FACES = [
495997
+ [0, 1, 2, 3],
495998
+ [4, 5, 6, 7],
495999
+ [0, 1, 5, 4],
496000
+ [3, 2, 6, 7],
496001
+ [1, 2, 6, 5],
496002
+ [0, 3, 7, 4]
496003
+ ];
496004
+ var TOP = [3, 2, 6, 7];
496005
+ function verts(b) {
496006
+ const {
496007
+ size: { x: sx, y: sy, z: sz },
496008
+ center,
496009
+ rotation: rotation4
496010
+ } = b;
496011
+ const offs = [
496012
+ { x: -sx / 2, y: -sy / 2, z: -sz / 2 },
496013
+ { x: sx / 2, y: -sy / 2, z: -sz / 2 },
496014
+ { x: sx / 2, y: sy / 2, z: -sz / 2 },
496015
+ { x: -sx / 2, y: sy / 2, z: -sz / 2 },
496016
+ { x: -sx / 2, y: -sy / 2, z: sz / 2 },
496017
+ { x: sx / 2, y: -sy / 2, z: sz / 2 },
496018
+ { x: sx / 2, y: sy / 2, z: sz / 2 },
496019
+ { x: -sx / 2, y: sy / 2, z: sz / 2 }
496020
+ ];
496021
+ return offs.map((o) => add(center, rotLocal(o, rotation4)));
496022
+ }
496023
+ function inv3(m2) {
496024
+ const a = m2[0][0], d = m2[0][1], g = m2[0][2];
496025
+ const b = m2[1][0], e = m2[1][1], h = m2[1][2];
496026
+ const c = m2[2][0], f = m2[2][1], i = m2[2][2];
496027
+ const A = e * i - f * h;
496028
+ const B = -(d * i - f * g);
496029
+ const C = d * h - e * g;
496030
+ const D = -(b * i - c * h);
496031
+ const E = a * i - c * g;
496032
+ const F = -(a * h - b * g);
496033
+ const G = b * f - c * e;
496034
+ const H = -(a * f - c * d);
496035
+ const I = a * e - b * d;
496036
+ const det = a * A + d * D + g * G;
496037
+ const invDet = det ? 1 / det : 0;
496038
+ return [
496039
+ [A * invDet, B * invDet, C * invDet],
496040
+ [D * invDet, E * invDet, F * invDet],
496041
+ [G * invDet, H * invDet, I * invDet]
496042
+ ];
496043
+ }
496044
+ function mul3(a, b) {
496045
+ const r = [
496046
+ [0, 0, 0],
496047
+ [0, 0, 0],
496048
+ [0, 0, 0]
496049
+ ];
496050
+ for (let i = 0;i < 3; i++) {
496051
+ for (let j = 0;j < 3; j++) {
496052
+ r[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
496053
+ }
496054
+ }
496055
+ return r;
496056
+ }
496057
+ function affineMatrix(src, dst) {
496058
+ const S = [
496059
+ [src[0].x, src[1].x, src[2].x],
496060
+ [src[0].y, src[1].y, src[2].y],
496061
+ [1, 1, 1]
496062
+ ];
496063
+ const D = [
496064
+ [dst[0].x, dst[1].x, dst[2].x],
496065
+ [dst[0].y, dst[1].y, dst[2].y],
496066
+ [1, 1, 1]
496067
+ ];
496068
+ const M = mul3(D, inv3(S));
496069
+ return `matrix(${M[0][0]} ${M[1][0]} ${M[0][1]} ${M[1][1]} ${M[0][2]} ${M[1][2]})`;
496070
+ }
496071
+ function scaleAndPositionMesh(mesh, box) {
496072
+ const { boundingBox } = mesh;
496073
+ const meshSize = sub(boundingBox.max, boundingBox.min);
496074
+ const boxSize = box.size;
496075
+ const scaleX = boxSize.x / meshSize.x;
496076
+ const scaleY = boxSize.y / meshSize.y;
496077
+ const scaleZ = boxSize.z / meshSize.z;
496078
+ const uniformScale = Math.min(scaleX, scaleY, scaleZ);
496079
+ const meshCenter = scale9(add(boundingBox.min, boundingBox.max), 0.5);
496080
+ const transformedVertices = [];
496081
+ for (const triangle of mesh.triangles) {
496082
+ for (const vertex of triangle.vertices) {
496083
+ let transformed = sub(vertex, meshCenter);
496084
+ transformed = scale9(transformed, uniformScale);
496085
+ if (box.stlRotation) {
496086
+ transformed = rotLocal(transformed, box.stlRotation);
496087
+ }
496088
+ if (box.stlPosition) {
496089
+ transformed = add(transformed, box.stlPosition);
496090
+ }
496091
+ if (box.rotation) {
496092
+ transformed = rotLocal(transformed, box.rotation);
496093
+ }
496094
+ transformed = add(transformed, box.center);
496095
+ transformedVertices.push(transformed);
496096
+ }
496097
+ }
496098
+ return transformedVertices;
496099
+ }
496100
+ async function renderScene(scene, opt = {}) {
496101
+ const W = opt.width ?? W_DEF;
496102
+ const H = opt.height ?? H_DEF;
496103
+ const focal = scene.camera.focalLength ?? FOCAL;
496104
+ const faces = [];
496105
+ const images = [];
496106
+ const labels = [];
496107
+ let clipSeq = 0;
496108
+ const texId = /* @__PURE__ */ new Map;
496109
+ const stlMeshes = /* @__PURE__ */ new Map;
496110
+ for (const box of scene.boxes) {
496111
+ if (box.stlUrl && !stlMeshes.has(box.stlUrl)) {
496112
+ try {
496113
+ const mesh = await loadSTL(box.stlUrl);
496114
+ stlMeshes.set(box.stlUrl, mesh);
496115
+ } catch (error) {
496116
+ console.warn(`Failed to load STL from ${box.stlUrl}:`, error);
496117
+ }
496118
+ }
496119
+ }
496120
+ for (const box of scene.boxes) {
496121
+ if (box.stlUrl && stlMeshes.has(box.stlUrl)) {
496122
+ const mesh = stlMeshes.get(box.stlUrl);
496123
+ const transformedVertices = scaleAndPositionMesh(mesh, box);
496124
+ for (let i = 0;i < mesh.triangles.length; i++) {
496125
+ const triangle = mesh.triangles[i];
496126
+ const vertexStart = i * 3;
496127
+ const v0w = transformedVertices[vertexStart];
496128
+ const v1w = transformedVertices[vertexStart + 1];
496129
+ const v2w = transformedVertices[vertexStart + 2];
496130
+ const v0c = toCam(v0w, scene.camera);
496131
+ const v1c = toCam(v1w, scene.camera);
496132
+ const v2c = toCam(v2w, scene.camera);
496133
+ const v0p = proj(v0c, W, H, focal);
496134
+ const v1p = proj(v1c, W, H, focal);
496135
+ const v2p = proj(v2c, W, H, focal);
496136
+ if (v0p && v1p && v2p) {
496137
+ const edge1 = sub(v1c, v0c);
496138
+ const edge2 = sub(v2c, v0c);
496139
+ const faceNormal = cross3(edge1, edge2);
496140
+ if (faceNormal.z < 0) {
496141
+ const depth = Math.max(v0c.z, v1c.z, v2c.z);
496142
+ faces.push({
496143
+ pts: [v0p, v1p, v2p],
496144
+ depth,
496145
+ fill: colorToCss(box.color)
496146
+ });
496147
+ }
496148
+ }
496149
+ }
496150
+ } else {
496151
+ const vw = verts(box);
496152
+ const vc = vw.map((v) => toCam(v, scene.camera));
496153
+ const vp = vc.map((v) => proj(v, W, H, focal));
496154
+ for (const idx of FACES) {
496155
+ const p4 = [];
496156
+ let zMax = -Infinity;
496157
+ let behind = false;
496158
+ for (const i of idx) {
496159
+ const p = vp[i];
496160
+ if (!p) {
496161
+ behind = true;
496162
+ break;
496163
+ }
496164
+ p4.push(p);
496165
+ zMax = Math.max(zMax, vc[i].z);
496166
+ }
496167
+ if (behind)
496168
+ continue;
496169
+ const [a, b, c] = idx;
496170
+ const n2 = cross3(sub(vc[b], vc[a]), sub(vc[c], vc[a]));
496171
+ if (n2.z >= 0)
496172
+ continue;
496173
+ faces.push({
496174
+ pts: p4,
496175
+ depth: zMax,
496176
+ fill: colorToCss(box.color)
496177
+ });
496178
+ }
496179
+ if (box.faceImages?.top) {
496180
+ const pts = TOP.map((i) => vp[i]);
496181
+ if (pts.every(Boolean)) {
496182
+ const dst = pts;
496183
+ const cz = Math.max(...TOP.map((i) => vc[i].z));
496184
+ const href = box.faceImages.top;
496185
+ if (!texId.has(href)) {
496186
+ texId.set(href, `tex${texId.size}`);
496187
+ }
496188
+ const sym = texId.get(href);
496189
+ const subdivisions = box.projectionSubdivision ?? 2;
496190
+ const quadsPerSide = subdivisions;
496191
+ for (let row = 0;row < quadsPerSide; row++) {
496192
+ for (let col = 0;col < quadsPerSide; col++) {
496193
+ const u0 = col / quadsPerSide;
496194
+ const u1 = (col + 1) / quadsPerSide;
496195
+ const v0 = row / quadsPerSide;
496196
+ const v1 = (row + 1) / quadsPerSide;
496197
+ const lerp = (a, b, t2) => ({
496198
+ x: a.x * (1 - t2) + b.x * t2,
496199
+ y: a.y * (1 - t2) + b.y * t2,
496200
+ z: a.z * (1 - t2) + b.z * t2
496201
+ });
496202
+ const p00 = lerp(lerp(dst[0], dst[1], u0), lerp(dst[3], dst[2], u0), v0);
496203
+ const p10 = lerp(lerp(dst[0], dst[1], u1), lerp(dst[3], dst[2], u1), v0);
496204
+ const p01 = lerp(lerp(dst[0], dst[1], u0), lerp(dst[3], dst[2], u0), v1);
496205
+ const p11 = lerp(lerp(dst[0], dst[1], u1), lerp(dst[3], dst[2], u1), v1);
496206
+ const tri0Mat = affineMatrix([
496207
+ { x: u0, y: v0 },
496208
+ { x: u1, y: v0 },
496209
+ { x: u1, y: v1 }
496210
+ ], [p00, p10, p11]);
496211
+ const id0 = `clip${clipSeq++}`;
496212
+ images.push({
496213
+ matrix: tri0Mat,
496214
+ depth: cz,
496215
+ href,
496216
+ clip: id0,
496217
+ points: `${u0},${v0} ${u1},${v0} ${u1},${v1}`,
496218
+ sym
496219
+ });
496220
+ const tri1Mat = affineMatrix([
496221
+ { x: u0, y: v0 },
496222
+ { x: u1, y: v1 },
496223
+ { x: u0, y: v1 }
496224
+ ], [p00, p11, p01]);
496225
+ const id1 = `clip${clipSeq++}`;
496226
+ images.push({
496227
+ matrix: tri1Mat,
496228
+ depth: cz,
496229
+ href,
496230
+ clip: id1,
496231
+ points: `${u0},${v0} ${u1},${v1} ${u0},${v1}`,
496232
+ sym
496233
+ });
496234
+ }
496235
+ }
496236
+ }
496237
+ }
496238
+ if (box.topLabel) {
496239
+ const pts = TOP.map((i) => vp[i]);
496240
+ if (pts.every(Boolean)) {
496241
+ const p0 = pts[0];
496242
+ const p1 = pts[1];
496243
+ const p3 = pts[3];
496244
+ const u = sub(p1, p0);
496245
+ const v = sub(p3, p0);
496246
+ const lu = len(u);
496247
+ const lv = len(v);
496248
+ if (lu && lv) {
496249
+ const uN = scale9(u, 1 / lu);
496250
+ const vN = scale9(v, 1 / lv);
496251
+ const cx = pts.reduce((s, p) => s + p.x, 0) / 4;
496252
+ const cy = pts.reduce((s, p) => s + p.y, 0) / 4;
496253
+ const cz = Math.max(...TOP.map((i) => vc[i].z));
496254
+ const m2 = `matrix(${uN.x} ${uN.y} ${vN.x} ${vN.y} ${cx} ${cy})`;
496255
+ const fillCol = box.topLabelColor ?? [0, 0, 0, 1];
496256
+ labels.push({
496257
+ matrix: m2,
496258
+ depth: cz,
496259
+ text: box.topLabel,
496260
+ fill: colorToCss(fillCol)
496261
+ });
496262
+ }
496263
+ }
496264
+ }
496265
+ }
496266
+ }
496267
+ const allElements = [
496268
+ ...faces.map((f) => ({ type: "face", data: f })),
496269
+ ...images.map((i) => ({ type: "image", data: i })),
496270
+ ...labels.map((l) => ({ type: "label", data: l }))
496271
+ ];
496272
+ allElements.sort((a, b) => b.data.depth - a.data.depth);
496273
+ const out = [];
496274
+ out.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="${-W / 2} ${-H / 2} ${W} ${H}">`);
496275
+ if (opt.backgroundColor) {
496276
+ out.push(` <rect x="${-W / 2}" y="${-H / 2}" width="${W}" height="${H}" fill="${colorToCss(opt.backgroundColor)}" />
496277
+ `);
496278
+ }
496279
+ if (images.length) {
496280
+ out.push(` <defs>
496281
+ `);
496282
+ for (const [href, id] of texId) {
496283
+ out.push(` <image id="${id}" href="${href}" width="1" height="1" preserveAspectRatio="none" style="image-rendering:pixelated"/>
496284
+ `);
496285
+ }
496286
+ for (const img of images) {
496287
+ out.push(` <clipPath id="${img.clip}" clipPathUnits="objectBoundingBox"><polygon points="${img.points}" /></clipPath>
496288
+ `);
496289
+ }
496290
+ out.push(` </defs>
496291
+ `);
496292
+ }
496293
+ let inStrokeGroup = false;
496294
+ for (const element of allElements) {
496295
+ if (element.type === "face" || element.type === "image") {
496296
+ if (!inStrokeGroup) {
496297
+ out.push(` <g stroke="#000" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
496298
+ `);
496299
+ inStrokeGroup = true;
496300
+ }
496301
+ if (element.type === "face") {
496302
+ const f = element.data;
496303
+ out.push(` <polygon fill="${f.fill}" points="${f.pts.map((p) => `${p.x},${p.y}`).join(" ")}" />
496304
+ `);
496305
+ } else {
496306
+ const img = element.data;
496307
+ out.push(` <g transform="${img.matrix}" clip-path="url(#${img.clip})"><use href="#${img.sym}"/></g>
496308
+ `);
496309
+ }
496310
+ } else if (element.type === "label") {
496311
+ if (inStrokeGroup) {
496312
+ out.push(` </g>
496313
+ `);
496314
+ inStrokeGroup = false;
496315
+ }
496316
+ const l = element.data;
496317
+ out.push(` <g font-family="sans-serif" font-size="14" text-anchor="middle" dominant-baseline="central" transform="${l.matrix}"><text x="0" y="0" fill="${l.fill}">${l.text}</text></g>
496318
+ `);
496319
+ }
496320
+ }
496321
+ if (inStrokeGroup) {
496322
+ out.push(` </g>
496323
+ `);
496324
+ }
496325
+ out.push("</svg>");
496326
+ return out.join("");
496327
+ }
496328
+
496329
+ // node_modules/circuit-json-to-simple-3d/dist/index.js
496330
+ init_dist6();
496331
+ function getDefaultCameraForPcbBoard(pcbBoard, anglePreset = "angle1") {
496332
+ const w = pcbBoard.width;
496333
+ const h = pcbBoard.height;
496334
+ const cx = pcbBoard.center?.x;
496335
+ const cz = pcbBoard.center?.y;
496336
+ const dist = Math.max(w, h) * 1.5;
496337
+ let position4;
496338
+ if (anglePreset === "angle1") {
496339
+ position4 = { x: cx - dist, y: dist, z: cz - dist };
496340
+ } else if (anglePreset === "angle2") {
496341
+ position4 = { x: cx + dist, y: dist, z: cz - dist };
496342
+ } else if (anglePreset === "left") {
496343
+ position4 = { x: cx - dist, y: 0, z: 0 };
496344
+ } else if (anglePreset === "right") {
496345
+ position4 = { x: cx + dist, y: 0, z: 0 };
496346
+ } else if (anglePreset === "left-raised") {
496347
+ position4 = { x: cx - dist, y: dist, z: 0 };
496348
+ } else if (anglePreset === "right-raised") {
496349
+ position4 = { x: cx + dist, y: dist, z: 0 };
496350
+ } else {
496351
+ throw new Error(`Unknown angle preset: ${anglePreset}`);
496352
+ }
496353
+ return {
496354
+ position: position4,
496355
+ lookAt: { x: cx, y: 0, z: cz },
496356
+ focalLength: 2
496357
+ };
496358
+ }
496359
+ async function convertCircuitJsonToSimple3dSvg(circuitJson, opts = {}) {
496360
+ const db = cju_default(circuitJson);
496361
+ const boxes = [];
496362
+ const pcbTopSvg = convertCircuitJsonToPcbSvg(circuitJson, {
496363
+ layer: "top",
496364
+ matchBoardAspectRatio: true,
496365
+ backgroundColor: "transparent",
496366
+ drawPaddingOutsideBoard: false,
496367
+ colorOverrides: {
496368
+ copper: {
496369
+ top: "#ffe066",
496370
+ bottom: "#ffe066"
496371
+ },
496372
+ drill: "rgba(0,0,0,0.5)"
496373
+ }
496374
+ }).replace("<svg", "<svg transform='scale(1, -1)'");
496375
+ const pcbBoard = db.pcb_board.list()[0];
496376
+ if (!pcbBoard)
496377
+ throw new Error("No pcb_board, can't render to 3d");
496378
+ const camera = opts.camera ?? getDefaultCameraForPcbBoard(pcbBoard, opts.anglePreset ?? "angle1");
496379
+ if (!camera.focalLength) {
496380
+ camera.focalLength = 1;
496381
+ }
496382
+ boxes.push({
496383
+ center: {
496384
+ x: pcbBoard.center.x,
496385
+ y: 0,
496386
+ z: pcbBoard.center.y
496387
+ },
496388
+ size: {
496389
+ x: pcbBoard.width,
496390
+ y: pcbBoard.thickness,
496391
+ z: pcbBoard.height
496392
+ },
496393
+ faceImages: {
496394
+ top: `data:image/svg+xml;base64,${btoa(pcbTopSvg)}`
496395
+ },
496396
+ projectionSubdivision: 10,
496397
+ color: "rgba(0,140,0,0.8)"
496398
+ });
496399
+ const DEFAULT_COMP_HEIGHT = 2;
496400
+ for (const comp of db.pcb_component.list()) {
496401
+ const sourceComponent = db.source_component.get(comp.source_component_id);
496402
+ const compHeight = Math.min(Math.min(comp.width, comp.height), DEFAULT_COMP_HEIGHT);
496403
+ boxes.push({
496404
+ center: {
496405
+ x: comp.center.x,
496406
+ y: pcbBoard.thickness / 2 + compHeight / 2,
496407
+ z: comp.center.y
496408
+ },
496409
+ size: {
496410
+ x: comp.width,
496411
+ y: compHeight,
496412
+ z: comp.height
496413
+ },
496414
+ color: "rgba(128,128,128,0.5)",
496415
+ topLabel: sourceComponent?.name ?? "?",
496416
+ topLabelColor: "white"
496417
+ });
496418
+ }
496419
+ return await renderScene({ boxes, camera }, {
496420
+ backgroundColor: "lightgray"
496421
+ });
496422
+ }
496423
+
496424
+ // lib/shared/snapshot-project.ts
495785
496425
  var snapshotProject = async ({
495786
496426
  update = false,
495787
496427
  ignored = [],
496428
+ threeD = false,
495788
496429
  onExit = (code) => process.exit(code),
495789
496430
  onError = (msg) => console.error(msg),
495790
496431
  onSuccess = (msg) => console.log(msg)
@@ -495811,13 +496452,18 @@ var snapshotProject = async ({
495811
496452
  const { circuitJson } = await generateCircuitJson({ filePath: file });
495812
496453
  const pcbSvg = convertCircuitJsonToPcbSvg(circuitJson);
495813
496454
  const schSvg = convertCircuitJsonToSchematicSvg(circuitJson);
496455
+ const svg3d = threeD ? await convertCircuitJsonToSimple3dSvg(circuitJson) : null;
495814
496456
  const snapDir = path24.join(path24.dirname(file), "__snapshots__");
495815
496457
  fs24.mkdirSync(snapDir, { recursive: true });
495816
496458
  const base = path24.basename(file).replace(/\.tsx$/, "");
495817
- for (const [type, svg] of [
496459
+ const snapshotPairs = [
495818
496460
  ["pcb", pcbSvg],
495819
496461
  ["schematic", schSvg]
495820
- ]) {
496462
+ ];
496463
+ if (threeD && svg3d) {
496464
+ snapshotPairs.push(["3d", svg3d]);
496465
+ }
496466
+ for (const [type, svg] of snapshotPairs) {
495821
496467
  const snapPath = path24.join(snapDir, `${base}-${type}.snap.svg`);
495822
496468
  if (update || !fs24.existsSync(snapPath)) {
495823
496469
  fs24.writeFileSync(snapPath, svg);
@@ -495844,9 +496490,10 @@ ${mismatches.join(`
495844
496490
 
495845
496491
  // cli/snapshot/register.ts
495846
496492
  var registerSnapshot = (program3) => {
495847
- program3.command("snapshot").description("Generate schematic and PCB snapshots").option("-u, --update", "Update snapshots on disk").action(async (options) => {
496493
+ program3.command("snapshot").description("Generate schematic and PCB snapshots (add --3d for 3d preview)").option("-u, --update", "Update snapshots on disk").option("--3d", "Generate 3d preview snapshots").action(async (options) => {
495848
496494
  await snapshotProject({
495849
496495
  update: options.update ?? false,
496496
+ threeD: options["3d"] ?? false,
495850
496497
  onExit: (code) => process.exit(code),
495851
496498
  onError: (msg) => console.error(msg),
495852
496499
  onSuccess: (msg) => console.log(msg)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.1.126",
3
+ "version": "0.1.127",
4
4
  "main": "dist/main.js",
5
5
  "devDependencies": {
6
6
  "@babel/standalone": "^7.26.9",
@@ -42,7 +42,9 @@
42
42
  "tempy": "^3.1.0",
43
43
  "tscircuit": "^0.0.491",
44
44
  "@tscircuit/circuit-json-util": "^0.0.47",
45
- "typed-ky": "^0.0.4"
45
+ "typed-ky": "^0.0.4",
46
+ "circuit-json-to-simple-3d": "^0.0.3",
47
+ "@tscircuit/simple-3d-svg": "^0.0.10"
46
48
  },
47
49
  "peerDependencies": {
48
50
  "typescript": "^5.0.0"