@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.
- package/README.md +2 -1
- package/dist/main.js +652 -5
- 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.
|
|
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
|
-
|
|
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.
|
|
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"
|