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