circuit-json-to-tscircuit 0.0.95 → 0.0.96
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/dist/{chunk-KYTTH2AG.js → chunk-ULPDKPX5.js} +2 -41
- package/dist/cli/main.js +4 -4
- package/dist/lib/index.js +1 -1
- package/lib/board-converter/get-pcb-component-render-placement.ts +5 -80
- package/lib/board-converter/localize-pcb-component-elements.ts +1 -0
- package/lib/board-converter/stages/convert-pcb-components.ts +0 -1
- package/package.json +3 -3
- package/tests/__snapshots__/beagleplay-usb-cad-origin-generated-3d.snap.png +0 -0
- package/tests/__snapshots__/beagleplay-usb-cad-origin-source-3d.snap.png +0 -0
- package/tests/__snapshots__/dp83825-usb-cad-origin-generated-bottom-3d.snap.png +0 -0
- package/tests/__snapshots__/dp83825-usb-cad-origin-generated-top-3d.snap.png +0 -0
- package/tests/__snapshots__/dp83825-usb-cad-origin-source-bottom-3d.snap.png +0 -0
- package/tests/__snapshots__/dp83825-usb-cad-origin-source-top-3d.snap.png +0 -0
- package/tests/beagleplay-usb-cad-origin.test.ts +57 -0
- package/tests/dp83825-usb-cad-origin.test.ts +82 -0
- package/tests/fixtures/beagleplay-j13-origin.circuit.json +173 -0
- package/tests/fixtures/expect-jscad-cad-model-3d-snapshots.ts +66 -0
- package/tests/fixtures/expect-png-snapshot.ts +59 -0
- package/tests/fixtures/expect-ti-evm-3d-snapshot.ts +2 -57
- package/tests/fixtures/ti-evms/README.md +8 -0
- package/tests/fixtures/ti-evms/dp83825-usb-cad-origin.circuit.json.gz +0 -0
|
@@ -4019,30 +4019,8 @@ var generateFootprintTsx = (circuitJson, context) => {
|
|
|
4019
4019
|
};
|
|
4020
4020
|
|
|
4021
4021
|
// lib/board-converter/get-pcb-component-render-placement.ts
|
|
4022
|
-
import { getBoundsOfPcbElements } from "@tscircuit/circuit-json-util";
|
|
4023
4022
|
import { applyToPoint as applyToPoint6, inverse, translate as translate5 } from "transformation-matrix";
|
|
4024
|
-
var physicalPcbElementTypes = /* @__PURE__ */ new Set([
|
|
4025
|
-
"pcb_copper_text",
|
|
4026
|
-
"pcb_cutout",
|
|
4027
|
-
"pcb_hole",
|
|
4028
|
-
"pcb_keepout",
|
|
4029
|
-
"pcb_plated_hole",
|
|
4030
|
-
"pcb_smtpad",
|
|
4031
|
-
"pcb_trace",
|
|
4032
|
-
"pcb_via"
|
|
4033
|
-
]);
|
|
4034
|
-
var isPhysicalPcbElement = (element) => physicalPcbElementTypes.has(element.type);
|
|
4035
|
-
var getElementsAsRenderedByGeneratedTsx = (elements) => elements.map((element) => {
|
|
4036
|
-
if (element.type !== "pcb_plated_hole" || element.shape !== "rotated_pill_hole_with_rect_pad") {
|
|
4037
|
-
return element;
|
|
4038
|
-
}
|
|
4039
|
-
return {
|
|
4040
|
-
...element,
|
|
4041
|
-
rect_ccw_rotation: element.hole_ccw_rotation
|
|
4042
|
-
};
|
|
4043
|
-
});
|
|
4044
4023
|
var getPcbComponentRenderPlacement = ({
|
|
4045
|
-
circuitJson,
|
|
4046
4024
|
pcbBoard,
|
|
4047
4025
|
pcbComponent
|
|
4048
4026
|
}) => {
|
|
@@ -4050,25 +4028,8 @@ var getPcbComponentRenderPlacement = ({
|
|
|
4050
4028
|
inverse(translate5(pcbBoard.center.x, pcbBoard.center.y)),
|
|
4051
4029
|
pcbComponent.center
|
|
4052
4030
|
);
|
|
4053
|
-
const physicalElements = circuitJson.filter(
|
|
4054
|
-
(element) => isPhysicalPcbElement(element) && "pcb_component_id" in element && element.pcb_component_id === pcbComponent.pcb_component_id
|
|
4055
|
-
);
|
|
4056
|
-
const physicalBounds = getBoundsOfPcbElements(
|
|
4057
|
-
getElementsAsRenderedByGeneratedTsx(physicalElements)
|
|
4058
|
-
);
|
|
4059
|
-
const physicalWidth = physicalBounds.maxX - physicalBounds.minX;
|
|
4060
|
-
const physicalHeight = physicalBounds.maxY - physicalBounds.minY;
|
|
4061
|
-
if (Number.isFinite(physicalWidth) && Number.isFinite(physicalHeight) && physicalWidth > 0 && physicalHeight > 0) {
|
|
4062
|
-
return {
|
|
4063
|
-
cadModelParentCenter: {
|
|
4064
|
-
x: (physicalBounds.minX + physicalBounds.maxX) / 2,
|
|
4065
|
-
y: (physicalBounds.minY + physicalBounds.maxY) / 2
|
|
4066
|
-
},
|
|
4067
|
-
componentPosition
|
|
4068
|
-
};
|
|
4069
|
-
}
|
|
4070
4031
|
return {
|
|
4071
|
-
cadModelParentCenter:
|
|
4032
|
+
cadModelParentCenter: pcbComponent.center,
|
|
4072
4033
|
componentPosition
|
|
4073
4034
|
};
|
|
4074
4035
|
};
|
|
@@ -4265,6 +4226,7 @@ var getOppositeOuterLayer = (layer) => {
|
|
|
4265
4226
|
};
|
|
4266
4227
|
var localizeBottomComponentLayers = (localComponentElements) => {
|
|
4267
4228
|
for (const localElement of localComponentElements) {
|
|
4229
|
+
if (localElement.type === "pcb_stackup") continue;
|
|
4268
4230
|
if ("layer" in localElement && (localElement.layer === "top" || localElement.layer === "bottom")) {
|
|
4269
4231
|
localElement.layer = getOppositeOuterLayer(localElement.layer);
|
|
4270
4232
|
}
|
|
@@ -4406,7 +4368,6 @@ var convertPcbComponents = ({
|
|
|
4406
4368
|
(element) => element.type === "source_component" && (element.source_component_id === pcbComponent.source_component_id || element.source_component_id !== void 0 && schematicSourceComponentIds.has(element.source_component_id))
|
|
4407
4369
|
);
|
|
4408
4370
|
const { cadModelParentCenter, componentPosition } = getPcbComponentRenderPlacement({
|
|
4409
|
-
circuitJson,
|
|
4410
4371
|
pcbBoard,
|
|
4411
4372
|
pcbComponent
|
|
4412
4373
|
});
|
package/dist/cli/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
convertCircuitJsonToTscircuit
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-ULPDKPX5.js";
|
|
5
5
|
|
|
6
6
|
// cli/main.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -16,7 +16,7 @@ var package_default = {
|
|
|
16
16
|
"./cli": "./dist/cli/main.js"
|
|
17
17
|
},
|
|
18
18
|
type: "module",
|
|
19
|
-
version: "0.0.
|
|
19
|
+
version: "0.0.95",
|
|
20
20
|
repository: {
|
|
21
21
|
type: "git",
|
|
22
22
|
url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
|
|
@@ -38,7 +38,7 @@ var package_default = {
|
|
|
38
38
|
"@types/react": "18",
|
|
39
39
|
"@types/react-dom": "18",
|
|
40
40
|
"bun-match-svg": "^0.0.15",
|
|
41
|
-
"circuit-json": "^0.0.
|
|
41
|
+
"circuit-json": "^0.0.539",
|
|
42
42
|
"circuit-json-to-gltf": "0.0.151",
|
|
43
43
|
"circuit-to-svg": "https://pkg.pr.new/tscircuit/circuit-to-svg@738ef1d",
|
|
44
44
|
color: "^5.0.3",
|
|
@@ -52,7 +52,7 @@ var package_default = {
|
|
|
52
52
|
shiki: "^4.3.1",
|
|
53
53
|
"stack-svgs": "^0.0.1",
|
|
54
54
|
"transformation-matrix": "3.1.0",
|
|
55
|
-
tscircuit: "0.0.
|
|
55
|
+
tscircuit: "0.0.3043-libonly",
|
|
56
56
|
tsup: "^8.3.5",
|
|
57
57
|
zod: "^3.25.76"
|
|
58
58
|
},
|
package/dist/lib/index.js
CHANGED
|
@@ -1,64 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { AnyCircuitElement, PcbBoard, PcbComponent } from "circuit-json"
|
|
1
|
+
import type { PcbBoard, PcbComponent } from "circuit-json"
|
|
3
2
|
import { applyToPoint, inverse, translate } from "transformation-matrix"
|
|
4
3
|
|
|
5
|
-
type PhysicalPcbElementType =
|
|
6
|
-
| "pcb_copper_text"
|
|
7
|
-
| "pcb_cutout"
|
|
8
|
-
| "pcb_hole"
|
|
9
|
-
| "pcb_keepout"
|
|
10
|
-
| "pcb_plated_hole"
|
|
11
|
-
| "pcb_smtpad"
|
|
12
|
-
| "pcb_trace"
|
|
13
|
-
| "pcb_via"
|
|
14
|
-
|
|
15
|
-
const physicalPcbElementTypes = new Set<PhysicalPcbElementType>([
|
|
16
|
-
"pcb_copper_text",
|
|
17
|
-
"pcb_cutout",
|
|
18
|
-
"pcb_hole",
|
|
19
|
-
"pcb_keepout",
|
|
20
|
-
"pcb_plated_hole",
|
|
21
|
-
"pcb_smtpad",
|
|
22
|
-
"pcb_trace",
|
|
23
|
-
"pcb_via",
|
|
24
|
-
])
|
|
25
|
-
|
|
26
|
-
const isPhysicalPcbElement = (
|
|
27
|
-
element: AnyCircuitElement,
|
|
28
|
-
): element is AnyCircuitElement & { type: PhysicalPcbElementType } =>
|
|
29
|
-
physicalPcbElementTypes.has(element.type as PhysicalPcbElementType)
|
|
30
|
-
|
|
31
|
-
const getElementsAsRenderedByGeneratedTsx = (
|
|
32
|
-
elements: AnyCircuitElement[],
|
|
33
|
-
): AnyCircuitElement[] =>
|
|
34
|
-
elements.map((element) => {
|
|
35
|
-
if (
|
|
36
|
-
element.type !== "pcb_plated_hole" ||
|
|
37
|
-
element.shape !== "rotated_pill_hole_with_rect_pad"
|
|
38
|
-
) {
|
|
39
|
-
return element
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// The plated-hole component has one rotation for both the hole and its
|
|
43
|
-
// rectangular pad. The TSX converter uses the imported hole rotation, so
|
|
44
|
-
// calculate the parent bounds from that same rendered geometry.
|
|
45
|
-
return {
|
|
46
|
-
...element,
|
|
47
|
-
rect_ccw_rotation: element.hole_ccw_rotation,
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
4
|
export interface PcbComponentRenderPlacement {
|
|
52
5
|
cadModelParentCenter: { x: number; y: number }
|
|
53
6
|
componentPosition: { x: number; y: number }
|
|
54
7
|
}
|
|
55
8
|
|
|
56
9
|
export const getPcbComponentRenderPlacement = ({
|
|
57
|
-
circuitJson,
|
|
58
10
|
pcbBoard,
|
|
59
11
|
pcbComponent,
|
|
60
12
|
}: {
|
|
61
|
-
circuitJson: AnyCircuitElement[]
|
|
62
13
|
pcbBoard: PcbBoard
|
|
63
14
|
pcbComponent: PcbComponent
|
|
64
15
|
}): PcbComponentRenderPlacement => {
|
|
@@ -66,37 +17,11 @@ export const getPcbComponentRenderPlacement = ({
|
|
|
66
17
|
inverse(translate(pcbBoard.center.x, pcbBoard.center.y)),
|
|
67
18
|
pcbComponent.center,
|
|
68
19
|
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"pcb_component_id" in element &&
|
|
73
|
-
element.pcb_component_id === pcbComponent.pcb_component_id,
|
|
74
|
-
)
|
|
75
|
-
const physicalBounds = getBoundsOfPcbElements(
|
|
76
|
-
getElementsAsRenderedByGeneratedTsx(physicalElements),
|
|
77
|
-
)
|
|
78
|
-
const physicalWidth = physicalBounds.maxX - physicalBounds.minX
|
|
79
|
-
const physicalHeight = physicalBounds.maxY - physicalBounds.minY
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
Number.isFinite(physicalWidth) &&
|
|
83
|
-
Number.isFinite(physicalHeight) &&
|
|
84
|
-
physicalWidth > 0 &&
|
|
85
|
-
physicalHeight > 0
|
|
86
|
-
) {
|
|
87
|
-
return {
|
|
88
|
-
cadModelParentCenter: {
|
|
89
|
-
x: (physicalBounds.minX + physicalBounds.maxX) / 2,
|
|
90
|
-
y: (physicalBounds.minY + physicalBounds.maxY) / 2,
|
|
91
|
-
},
|
|
92
|
-
componentPosition,
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Core retains the board-local chip position when there are no physical
|
|
97
|
-
// footprint primitives, so use that position as the CAD model's parent.
|
|
20
|
+
// Core anchors cadModel.positionOffset at the chip's pre-layout footprint
|
|
21
|
+
// origin, not at its pad bounds. CAD offsets use absolute board XY axes;
|
|
22
|
+
// the chip's board-local position becomes pcbComponent.center after rendering.
|
|
98
23
|
return {
|
|
99
|
-
cadModelParentCenter:
|
|
24
|
+
cadModelParentCenter: pcbComponent.center,
|
|
100
25
|
componentPosition,
|
|
101
26
|
}
|
|
102
27
|
}
|
|
@@ -155,6 +155,7 @@ const localizeBottomComponentLayers = (
|
|
|
155
155
|
localComponentElements: AnyCircuitElement[],
|
|
156
156
|
): void => {
|
|
157
157
|
for (const localElement of localComponentElements) {
|
|
158
|
+
if (localElement.type === "pcb_stackup") continue
|
|
158
159
|
if (
|
|
159
160
|
"layer" in localElement &&
|
|
160
161
|
(localElement.layer === "top" || localElement.layer === "bottom")
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"./cli": "./dist/cli/main.js"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.96",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/react": "18",
|
|
29
29
|
"@types/react-dom": "18",
|
|
30
30
|
"bun-match-svg": "^0.0.15",
|
|
31
|
-
"circuit-json": "^0.0.
|
|
31
|
+
"circuit-json": "^0.0.539",
|
|
32
32
|
"circuit-json-to-gltf": "0.0.151",
|
|
33
33
|
"circuit-to-svg": "https://pkg.pr.new/tscircuit/circuit-to-svg@738ef1d",
|
|
34
34
|
"color": "^5.0.3",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"shiki": "^4.3.1",
|
|
43
43
|
"stack-svgs": "^0.0.1",
|
|
44
44
|
"transformation-matrix": "3.1.0",
|
|
45
|
-
"tscircuit": "0.0.
|
|
45
|
+
"tscircuit": "0.0.3043-libonly",
|
|
46
46
|
"tsup": "^8.3.5",
|
|
47
47
|
"zod": "^3.25.76"
|
|
48
48
|
},
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import type { CadComponent, CircuitJson } from "circuit-json"
|
|
3
|
+
import { convertCircuitJsonToTscircuit } from "lib"
|
|
4
|
+
import { runTscircuitCode } from "tscircuit"
|
|
5
|
+
import { expectJscadCadModel3dSnapshots } from "./fixtures/expect-jscad-cad-model-3d-snapshots"
|
|
6
|
+
|
|
7
|
+
test("preserves BeaglePlay J13 CAD placement when pad bounds differ from its footprint origin", async () => {
|
|
8
|
+
// J13, its native pads and its ComponentBody come from BEAGLEPLAYV020_220915.
|
|
9
|
+
const sourceCircuitJson: CircuitJson = await Bun.file(
|
|
10
|
+
new URL("./fixtures/beagleplay-j13-origin.circuit.json", import.meta.url),
|
|
11
|
+
).json()
|
|
12
|
+
const sourceCadComponent = sourceCircuitJson.find(
|
|
13
|
+
(element): element is CadComponent => element.type === "cad_component",
|
|
14
|
+
)!
|
|
15
|
+
const generatedTsx = convertCircuitJsonToTscircuit(sourceCircuitJson, {
|
|
16
|
+
componentName: "BeaglePlayUsb",
|
|
17
|
+
})
|
|
18
|
+
expect(generatedTsx).toMatchInlineSnapshot(`
|
|
19
|
+
"export const BeaglePlayUsb = () => (
|
|
20
|
+
<board pcbX={-308.7624} pcbY={238.2139} width="24mm" height="20mm" thickness="1.6mm" layers={8} material="fr4">
|
|
21
|
+
<chip name="J13" pcbX={0} pcbY={0} pcbRotation="90deg" layer="top" noSchematicRepresentation cadModel={{ jscad: {"type":"colorize","color":[0.5019607843137255,0.5019607843137255,0.5019607843137255],"shape":{"type":"extrudeLinear","options":{"height":3.81},"shape":{"type":"fromPointsGeom2","points":[[-2.7939999999999827,-6.921500000000009],[2.7940000000000396,-6.921500000000009],[2.7940000000000396,6.921500000000009],[-2.7939999999999827,6.921500000000009]]}}}, positionOffset: { x: 0, y: 0, z: 0 }, rotationOffset: { x: 0, y: 0, z: -90 }, modelOriginPosition: { x: 0, y: 0, z: 0 }, modelUnitToMmScale: 1, modelBoardNormalDirection: "z+" }} obstructsWithinBounds={false} footprint={<footprint>
|
|
22
|
+
<platedhole portHints={["6"]} pcbX="4.032504mm" pcbY="3.080004mm" outerDiameter="2.199894mm" holeDiameter="1.400048mm" shape="circle" />
|
|
23
|
+
<platedhole portHints={["4"]} pcbX="6.0325mm" pcbY="2.100072mm" outerDiameter="1.199896mm" holeDiameter="0.649986mm" shape="circle" />
|
|
24
|
+
<platedhole portHints={["3"]} pcbX="6.0325mm" pcbY="0.700024mm" outerDiameter="1.199896mm" holeDiameter="0.649986mm" shape="circle" />
|
|
25
|
+
<platedhole portHints={["7"]} pcbX="-1.567434mm" pcbY="0mm" outerHeight="1.500124mm" outerWidth="2.9718mm" holeHeight="1.500124mm" holeWidth="2.9718mm" height="1.500124mm" shape="pill" pcbRotation="270deg" />
|
|
26
|
+
<platedhole portHints={["2"]} pcbX="6.0325mm" pcbY="-0.700024mm" outerDiameter="1.199896mm" holeDiameter="0.649986mm" shape="circle" />
|
|
27
|
+
<platedhole portHints={["1"]} pcbX="6.0325mm" pcbY="-2.100072mm" outerDiameter="1.199896mm" holeDiameter="0.649986mm" shape="circle" />
|
|
28
|
+
<platedhole portHints={["5"]} pcbX="4.032504mm" pcbY="-3.080004mm" outerDiameter="2.199894mm" holeDiameter="1.400048mm" shape="circle" />
|
|
29
|
+
</footprint>} />
|
|
30
|
+
</board>
|
|
31
|
+
)
|
|
32
|
+
export default BeaglePlayUsb"
|
|
33
|
+
`)
|
|
34
|
+
const renderedCircuitJson = (await runTscircuitCode(
|
|
35
|
+
generatedTsx,
|
|
36
|
+
)) as CircuitJson
|
|
37
|
+
const renderedCadComponent = renderedCircuitJson.find(
|
|
38
|
+
(element): element is CadComponent => element.type === "cad_component",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
await expectJscadCadModel3dSnapshots({
|
|
42
|
+
sourceCircuitJson,
|
|
43
|
+
generatedCircuitJson: renderedCircuitJson,
|
|
44
|
+
testPath: import.meta.path,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
for (const axis of ["x", "y", "z"] as const) {
|
|
48
|
+
expect(renderedCadComponent?.position[axis]).toBeCloseTo(
|
|
49
|
+
sourceCadComponent.position[axis],
|
|
50
|
+
8,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
expect(renderedCadComponent?.rotation).toEqual(sourceCadComponent.rotation)
|
|
54
|
+
expect(renderedCadComponent?.model_jscad).toEqual(
|
|
55
|
+
sourceCadComponent.model_jscad,
|
|
56
|
+
)
|
|
57
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { gunzipSync } from "node:zlib"
|
|
3
|
+
import type { CadComponent, CircuitJson } from "circuit-json"
|
|
4
|
+
import { convertCircuitJsonToTscircuit } from "lib"
|
|
5
|
+
import { runTscircuitCode } from "tscircuit"
|
|
6
|
+
import { expectTiEvm3dSnapshot } from "./fixtures/expect-ti-evm-3d-snapshot"
|
|
7
|
+
|
|
8
|
+
test("preserves the DP83825EVM Molex USB connector on its mounting pads", async () => {
|
|
9
|
+
const sourceCircuitJson: CircuitJson = JSON.parse(
|
|
10
|
+
gunzipSync(
|
|
11
|
+
await Bun.file(
|
|
12
|
+
new URL(
|
|
13
|
+
"./fixtures/ti-evms/dp83825-usb-cad-origin.circuit.json.gz",
|
|
14
|
+
import.meta.url,
|
|
15
|
+
),
|
|
16
|
+
).arrayBuffer(),
|
|
17
|
+
).toString("utf8"),
|
|
18
|
+
)
|
|
19
|
+
const sourceCadComponent = sourceCircuitJson.find(
|
|
20
|
+
(element): element is CadComponent => element.type === "cad_component",
|
|
21
|
+
)!
|
|
22
|
+
const generatedTsx = convertCircuitJsonToTscircuit(sourceCircuitJson, {
|
|
23
|
+
componentName: "Dp83825Usb",
|
|
24
|
+
})
|
|
25
|
+
expect(generatedTsx).toMatchInlineSnapshot(`
|
|
26
|
+
"export const Dp83825Usb = () => (
|
|
27
|
+
<board pcbX={20.605798895} pcbY={73.2536} width="18mm" height="16mm" thickness="1.6mm" layers={4} material="fr4">
|
|
28
|
+
<chip name="J1" pcbX={-2.7749988949999995} pcbY={-1.4210854715202004e-14} pcbRotation="270deg" layer="top" noSchematicRepresentation cadModel={<cadmodel modelUrl={"/cad-models/dp83825evm/8.step"} positionOffset={{ x: 12.800022659999996, y: -0.00008128000000340307, z: 2.25000058 }} rotationOffset={{ x: 270, y: 90, z: -270 }} modelUnitToMmScale={1} modelBoardNormalDirection={"z+"} modelOriginPosition={{ x: 0, y: 0, z: 0 }} showAsTranslucentModel={false} />} obstructsWithinBounds={false} footprint={<footprint>
|
|
29
|
+
<platedhole portHints={["7"]} pcbX="4.17500054mm" pcbY="1.44999964mm" holeShape="pill" padShape="rect" holeWidth="1.29999994mm" holeHeight="0.5999987999999999mm" rectPadWidth="0.8000009399999999mm" rectPadHeight="2.00000108mm" rectBorderRadius="0.40000046999999994mm" holeOffsetX="0mm" holeOffsetY="0mm" pcbRotation="270deg" shape="pill_hole_with_rect_pad" />
|
|
30
|
+
<platedhole portHints={["10"]} pcbX="-4.17500054mm" pcbY="1.44999964mm" holeShape="pill" padShape="rect" holeWidth="1.29999994mm" holeHeight="0.5999987999999999mm" rectPadWidth="0.8000009399999999mm" rectPadHeight="2.00000108mm" rectBorderRadius="0.40000046999999994mm" holeOffsetX="0mm" holeOffsetY="0mm" pcbRotation="270deg" shape="pill_hole_with_rect_pad" />
|
|
31
|
+
<platedhole portHints={["6"]} pcbX="2.22499936mm" pcbY="4.44999872mm" holeShape="pill" padShape="rect" holeWidth="0.8500008399999999mm" holeHeight="0.70000114mm" rectPadWidth="1.05000044mm" rectPadHeight="1.29999994mm" rectBorderRadius="0.52500022mm" holeOffsetX="0mm" holeOffsetY="0mm" pcbRotation="270deg" shape="pill_hole_with_rect_pad" />
|
|
32
|
+
<platedhole portHints={["11"]} pcbX="-2.22499936mm" pcbY="4.44999872mm" holeShape="pill" padShape="rect" holeWidth="0.8500008399999999mm" holeHeight="0.70000114mm" rectPadWidth="1.05000044mm" rectPadHeight="1.29999994mm" rectBorderRadius="0.52500022mm" holeOffsetX="0mm" holeOffsetY="0mm" pcbRotation="270deg" shape="pill_hole_with_rect_pad" />
|
|
33
|
+
<smtpad portHints={["3"]} pcbX="0mm" pcbY="4.12500064mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.04999989999999999mm" width="0.39999919999999994mm" height="1.34999984mm" ccwRotation={0} shape="rotated_rect" />
|
|
34
|
+
<smtpad portHints={["2"]} pcbX="-0.65000124mm" pcbY="4.12500064mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.04999989999999999mm" width="0.39999919999999994mm" height="1.34999984mm" ccwRotation={0} shape="rotated_rect" />
|
|
35
|
+
<smtpad portHints={["8"]} pcbX="1.15000024mm" pcbY="1.44999964mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.05399996819999999mm" width="1.79999894mm" height="2.00000108mm" ccwRotation={180} shape="rotated_rect" />
|
|
36
|
+
<smtpad portHints={["9"]} pcbX="-1.15000024mm" pcbY="1.44999964mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.05399996819999999mm" width="1.79999894mm" height="2.00000108mm" ccwRotation={180} shape="rotated_rect" />
|
|
37
|
+
<smtpad portHints={["5"]} pcbX="1.29999994mm" pcbY="4.12500064mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.04999989999999999mm" width="0.39999919999999994mm" height="1.34999984mm" ccwRotation={0} shape="rotated_rect" />
|
|
38
|
+
<smtpad portHints={["4"]} pcbX="0.65000124mm" pcbY="4.12500064mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.04999989999999999mm" width="0.39999919999999994mm" height="1.34999984mm" ccwRotation={0} shape="rotated_rect" />
|
|
39
|
+
<smtpad portHints={["1"]} pcbX="-1.29999994mm" pcbY="4.12500064mm" layer="top" solderMaskMargin="0.04999989999999999mm" cornerRadius="0.04999989999999999mm" width="0.39999919999999994mm" height="1.34999984mm" ccwRotation={0} shape="rotated_rect" />
|
|
40
|
+
<silkscreenline x1={3.750015359999992} y1={2.84699964} x2={3.750015359999992} y2={4.29999394} strokeWidth={0.127} />
|
|
41
|
+
<silkscreenline x1={3.750015359999992} y1={4.29999394} x2={3.200003759999987} y2={4.29999394} strokeWidth={0.127} />
|
|
42
|
+
<silkscreenline x1={-3.7499950400000017} y1={2.84699964} x2={-3.7499950400000017} y2={4.29999394} strokeWidth={0.127} />
|
|
43
|
+
<silkscreenline x1={-3.199983440000011} y1={4.29999394} x2={-3.7499950400000017} y2={4.29999394} strokeWidth={0.127} />
|
|
44
|
+
<fabricationnotetext pcbX={-2.2644176200000032} pcbY={1.4476831600000004} anchorAlignment="center" text="J1" font="tscircuit2024" fontSize={1.016} color="#ec4899" pcbRotation="0deg" />
|
|
45
|
+
<silkscreentext pcbX={-5.334000000000003} pcbY={1.0159999999999982} anchorAlignment="bottom_left" fontSize={0.762} font="tscircuit2024" pcbRotation="360deg" mirrored={false} layer="top" text="J1" />
|
|
46
|
+
</footprint>} />
|
|
47
|
+
</board>
|
|
48
|
+
)
|
|
49
|
+
export default Dp83825Usb"
|
|
50
|
+
`)
|
|
51
|
+
const generatedCircuitJson = (await runTscircuitCode(
|
|
52
|
+
generatedTsx,
|
|
53
|
+
)) as CircuitJson
|
|
54
|
+
|
|
55
|
+
for (const [snapshotName, circuitJson] of [
|
|
56
|
+
["source", sourceCircuitJson],
|
|
57
|
+
["generated", generatedCircuitJson],
|
|
58
|
+
] as const) {
|
|
59
|
+
await expectTiEvm3dSnapshot({
|
|
60
|
+
cameraReferenceCircuitJson: sourceCircuitJson,
|
|
61
|
+
circuitJson,
|
|
62
|
+
fixtureName: "dp83825evm",
|
|
63
|
+
snapshotName,
|
|
64
|
+
testPath: import.meta.path,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const generatedCadComponent = generatedCircuitJson.find(
|
|
69
|
+
(element): element is CadComponent => element.type === "cad_component",
|
|
70
|
+
)
|
|
71
|
+
for (const axis of ["x", "y", "z"] as const) {
|
|
72
|
+
expect(generatedCadComponent?.position[axis]).toBeCloseTo(
|
|
73
|
+
sourceCadComponent.position[axis],
|
|
74
|
+
8,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
expect(generatedCadComponent).toMatchObject({
|
|
78
|
+
rotation: sourceCadComponent.rotation,
|
|
79
|
+
model_step_url: sourceCadComponent.model_step_url,
|
|
80
|
+
model_origin_position: sourceCadComponent.model_origin_position,
|
|
81
|
+
})
|
|
82
|
+
}, 60_000)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "pcb_board",
|
|
4
|
+
"pcb_board_id": "pcb_pcb_board_altium",
|
|
5
|
+
"center": {
|
|
6
|
+
"x": -308.7624,
|
|
7
|
+
"y": 238.2139
|
|
8
|
+
},
|
|
9
|
+
"width": 24,
|
|
10
|
+
"height": 20,
|
|
11
|
+
"thickness": 1.6,
|
|
12
|
+
"num_layers": 8,
|
|
13
|
+
"material": "fr4",
|
|
14
|
+
"is_via_in_pad_allowed": true,
|
|
15
|
+
"min_trace_width": 0.254,
|
|
16
|
+
"min_via_hole_diameter": 0.7111999999999999,
|
|
17
|
+
"min_via_pad_diameter": 1.27
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "source_component",
|
|
21
|
+
"ftype": "simple_chip",
|
|
22
|
+
"source_component_id": "pcb_source_component_altium_633",
|
|
23
|
+
"name": "J13",
|
|
24
|
+
"display_name": "J13"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"type": "pcb_component",
|
|
28
|
+
"pcb_component_id": "pcb_pcb_component_altium_633",
|
|
29
|
+
"source_component_id": "pcb_source_component_altium_633",
|
|
30
|
+
"center": {
|
|
31
|
+
"x": -308.7624,
|
|
32
|
+
"y": 238.2139
|
|
33
|
+
},
|
|
34
|
+
"width": 8.359902000000025,
|
|
35
|
+
"height": 14.126972000000007,
|
|
36
|
+
"layer": "top",
|
|
37
|
+
"rotation": 90,
|
|
38
|
+
"position_mode": "none",
|
|
39
|
+
"obstructs_within_bounds": false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "cad_component",
|
|
43
|
+
"cad_component_id": "pcb_cad_component_altium_primitive_227",
|
|
44
|
+
"pcb_component_id": "pcb_pcb_component_altium_633",
|
|
45
|
+
"source_component_id": "pcb_source_component_altium_633",
|
|
46
|
+
"position": {
|
|
47
|
+
"x": -308.7624,
|
|
48
|
+
"y": 238.2139,
|
|
49
|
+
"z": 0.8
|
|
50
|
+
},
|
|
51
|
+
"rotation": {
|
|
52
|
+
"x": 0,
|
|
53
|
+
"y": 0,
|
|
54
|
+
"z": 0
|
|
55
|
+
},
|
|
56
|
+
"layer": "top",
|
|
57
|
+
"model_jscad": {
|
|
58
|
+
"type": "colorize",
|
|
59
|
+
"color": [0.5019607843137255, 0.5019607843137255, 0.5019607843137255],
|
|
60
|
+
"shape": {
|
|
61
|
+
"type": "extrudeLinear",
|
|
62
|
+
"options": {
|
|
63
|
+
"height": 3.81
|
|
64
|
+
},
|
|
65
|
+
"shape": {
|
|
66
|
+
"type": "fromPointsGeom2",
|
|
67
|
+
"points": [
|
|
68
|
+
[-2.7939999999999827, -6.921500000000009],
|
|
69
|
+
[2.7940000000000396, -6.921500000000009],
|
|
70
|
+
[2.7940000000000396, 6.921500000000009],
|
|
71
|
+
[-2.7939999999999827, 6.921500000000009]
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"model_unit_to_mm_scale_factor": 1,
|
|
77
|
+
"model_board_normal_direction": "z+",
|
|
78
|
+
"model_origin_position": {
|
|
79
|
+
"x": 0,
|
|
80
|
+
"y": 0,
|
|
81
|
+
"z": 0
|
|
82
|
+
},
|
|
83
|
+
"model_object_fit": "contain_within_bounds",
|
|
84
|
+
"anchor_alignment": "center"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type": "pcb_plated_hole",
|
|
88
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7085",
|
|
89
|
+
"shape": "circle",
|
|
90
|
+
"outer_diameter": 2.199894,
|
|
91
|
+
"hole_diameter": 1.400048,
|
|
92
|
+
"x": -311.842404,
|
|
93
|
+
"y": 242.24640399999998,
|
|
94
|
+
"layers": ["top", "bottom"],
|
|
95
|
+
"port_hints": ["6"],
|
|
96
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"type": "pcb_plated_hole",
|
|
100
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7086",
|
|
101
|
+
"shape": "circle",
|
|
102
|
+
"outer_diameter": 1.199896,
|
|
103
|
+
"hole_diameter": 0.649986,
|
|
104
|
+
"x": -310.86247199999997,
|
|
105
|
+
"y": 244.2464,
|
|
106
|
+
"layers": ["top", "bottom"],
|
|
107
|
+
"port_hints": ["4"],
|
|
108
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"type": "pcb_plated_hole",
|
|
112
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7087",
|
|
113
|
+
"shape": "circle",
|
|
114
|
+
"outer_diameter": 1.199896,
|
|
115
|
+
"hole_diameter": 0.649986,
|
|
116
|
+
"x": -309.462424,
|
|
117
|
+
"y": 244.2464,
|
|
118
|
+
"layers": ["top", "bottom"],
|
|
119
|
+
"port_hints": ["3"],
|
|
120
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"type": "pcb_plated_hole",
|
|
124
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7088",
|
|
125
|
+
"shape": "pill",
|
|
126
|
+
"outer_width": 2.9718,
|
|
127
|
+
"outer_height": 1.500124,
|
|
128
|
+
"hole_width": 2.9718,
|
|
129
|
+
"hole_height": 1.500124,
|
|
130
|
+
"ccw_rotation": 0,
|
|
131
|
+
"x": -308.7624,
|
|
132
|
+
"y": 236.646466,
|
|
133
|
+
"layers": ["top", "bottom"],
|
|
134
|
+
"port_hints": ["7"],
|
|
135
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"type": "pcb_plated_hole",
|
|
139
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7089",
|
|
140
|
+
"shape": "circle",
|
|
141
|
+
"outer_diameter": 1.199896,
|
|
142
|
+
"hole_diameter": 0.649986,
|
|
143
|
+
"x": -308.062376,
|
|
144
|
+
"y": 244.2464,
|
|
145
|
+
"layers": ["top", "bottom"],
|
|
146
|
+
"port_hints": ["2"],
|
|
147
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"type": "pcb_plated_hole",
|
|
151
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7090",
|
|
152
|
+
"shape": "circle",
|
|
153
|
+
"outer_diameter": 1.199896,
|
|
154
|
+
"hole_diameter": 0.649986,
|
|
155
|
+
"x": -306.662328,
|
|
156
|
+
"y": 244.2464,
|
|
157
|
+
"layers": ["top", "bottom"],
|
|
158
|
+
"port_hints": ["1"],
|
|
159
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"type": "pcb_plated_hole",
|
|
163
|
+
"pcb_plated_hole_id": "pcb_pcb_plated_hole_altium_7091",
|
|
164
|
+
"shape": "circle",
|
|
165
|
+
"outer_diameter": 2.199894,
|
|
166
|
+
"hole_diameter": 1.400048,
|
|
167
|
+
"x": -305.682396,
|
|
168
|
+
"y": 242.24640399999998,
|
|
169
|
+
"layers": ["top", "bottom"],
|
|
170
|
+
"port_hints": ["5"],
|
|
171
|
+
"pcb_component_id": "pcb_pcb_component_altium_633"
|
|
172
|
+
}
|
|
173
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { expect } from "bun:test"
|
|
2
|
+
import { basename, dirname, join } from "node:path"
|
|
3
|
+
import type { CircuitJson, PcbBoard } from "circuit-json"
|
|
4
|
+
import {
|
|
5
|
+
convertCircuitJsonToGltf,
|
|
6
|
+
getBestCameraPosition,
|
|
7
|
+
} from "circuit-json-to-gltf"
|
|
8
|
+
import { renderGLTFToPNGFromGLB } from "poppygl"
|
|
9
|
+
import { expectPngSnapshot } from "./expect-png-snapshot"
|
|
10
|
+
|
|
11
|
+
export async function expectJscadCadModel3dSnapshots({
|
|
12
|
+
sourceCircuitJson,
|
|
13
|
+
generatedCircuitJson,
|
|
14
|
+
testPath,
|
|
15
|
+
}: {
|
|
16
|
+
sourceCircuitJson: CircuitJson
|
|
17
|
+
generatedCircuitJson: CircuitJson
|
|
18
|
+
testPath: string
|
|
19
|
+
}): Promise<void> {
|
|
20
|
+
const cameraReferenceBoard = sourceCircuitJson.find(
|
|
21
|
+
(element): element is PcbBoard => element.type === "pcb_board",
|
|
22
|
+
)
|
|
23
|
+
if (!cameraReferenceBoard) throw new Error("Missing camera reference board")
|
|
24
|
+
const testName = basename(testPath).replace(/\.test\.tsx?$/u, "")
|
|
25
|
+
const cameraPosition = getBestCameraPosition([cameraReferenceBoard], {
|
|
26
|
+
aspectRatio: 4 / 3,
|
|
27
|
+
direction: [-0.7, 1.2, -0.8],
|
|
28
|
+
})
|
|
29
|
+
let sourcePng: Uint8Array | undefined
|
|
30
|
+
|
|
31
|
+
for (const [snapshotName, circuitJson] of [
|
|
32
|
+
["source", sourceCircuitJson],
|
|
33
|
+
["generated", generatedCircuitJson],
|
|
34
|
+
] as const) {
|
|
35
|
+
const glb = await convertCircuitJsonToGltf(circuitJson, {
|
|
36
|
+
boardTextureResolution: 1024,
|
|
37
|
+
format: "glb",
|
|
38
|
+
})
|
|
39
|
+
if (!(glb instanceof ArrayBuffer)) {
|
|
40
|
+
throw new Error("3D conversion did not return a GLB")
|
|
41
|
+
}
|
|
42
|
+
const renderedPng = await renderGLTFToPNGFromGLB(glb, {
|
|
43
|
+
...cameraPosition,
|
|
44
|
+
backgroundColor: "#07100c",
|
|
45
|
+
height: 750,
|
|
46
|
+
width: 1000,
|
|
47
|
+
})
|
|
48
|
+
await expectPngSnapshot({
|
|
49
|
+
renderedPng,
|
|
50
|
+
snapshotPath: join(
|
|
51
|
+
dirname(testPath),
|
|
52
|
+
"__snapshots__",
|
|
53
|
+
`${testName}-${snapshotName}-3d.snap.png`,
|
|
54
|
+
),
|
|
55
|
+
})
|
|
56
|
+
if (snapshotName === "source") {
|
|
57
|
+
sourcePng = renderedPng
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
if (!sourcePng) throw new Error("Missing source 3D render")
|
|
61
|
+
expect(
|
|
62
|
+
Buffer.from(renderedPng).equals(sourcePng),
|
|
63
|
+
"Converted 3D render must match the native source",
|
|
64
|
+
).toBe(true)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { expect } from "bun:test"
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
|
3
|
+
import { dirname } from "node:path"
|
|
4
|
+
import { decode } from "fast-png"
|
|
5
|
+
|
|
6
|
+
const MAX_SNAPSHOT_DIFFERENT_PIXEL_FRACTION = 0.06
|
|
7
|
+
const MAX_CHANNEL_DIFFERENCE = 7
|
|
8
|
+
|
|
9
|
+
export async function expectPngSnapshot({
|
|
10
|
+
renderedPng,
|
|
11
|
+
snapshotPath,
|
|
12
|
+
}: {
|
|
13
|
+
renderedPng: Uint8Array
|
|
14
|
+
snapshotPath: string
|
|
15
|
+
}): Promise<void> {
|
|
16
|
+
const shouldUpdate =
|
|
17
|
+
process.env.BUN_UPDATE_SNAPSHOTS === "1" ||
|
|
18
|
+
process.env.BUN_FORCE_UPDATE_SNAPSHOTS === "1"
|
|
19
|
+
if (shouldUpdate) {
|
|
20
|
+
await mkdir(dirname(snapshotPath), { recursive: true })
|
|
21
|
+
await writeFile(snapshotPath, renderedPng)
|
|
22
|
+
}
|
|
23
|
+
expect(await Bun.file(snapshotPath).exists()).toBe(true)
|
|
24
|
+
|
|
25
|
+
const renderedImage = decode(renderedPng)
|
|
26
|
+
const snapshotImage = decode(await readFile(snapshotPath))
|
|
27
|
+
expect(renderedImage.width).toBe(snapshotImage.width)
|
|
28
|
+
expect(renderedImage.height).toBe(snapshotImage.height)
|
|
29
|
+
expect(renderedImage.channels).toBe(snapshotImage.channels)
|
|
30
|
+
|
|
31
|
+
let differentPixelCount = 0
|
|
32
|
+
const pixelCount = renderedImage.width * renderedImage.height
|
|
33
|
+
for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex += 1) {
|
|
34
|
+
const firstChannelIndex = pixelIndex * renderedImage.channels
|
|
35
|
+
let pixelIsDifferent = false
|
|
36
|
+
for (
|
|
37
|
+
let channelIndex = 0;
|
|
38
|
+
channelIndex < renderedImage.channels;
|
|
39
|
+
channelIndex += 1
|
|
40
|
+
) {
|
|
41
|
+
const sampleIndex = firstChannelIndex + channelIndex
|
|
42
|
+
if (
|
|
43
|
+
Math.abs(
|
|
44
|
+
(renderedImage.data[sampleIndex] ?? 0) -
|
|
45
|
+
(snapshotImage.data[sampleIndex] ?? 0),
|
|
46
|
+
) > MAX_CHANNEL_DIFFERENCE
|
|
47
|
+
) {
|
|
48
|
+
pixelIsDifferent = true
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (pixelIsDifferent) differentPixelCount += 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const differentPixelFraction = differentPixelCount / pixelCount
|
|
56
|
+
expect(differentPixelFraction).toBeLessThanOrEqual(
|
|
57
|
+
MAX_SNAPSHOT_DIFFERENT_PIXEL_FRACTION,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
|
1
|
+
import { readFile } from "node:fs/promises"
|
|
3
2
|
import { basename, dirname, join } from "node:path"
|
|
4
3
|
import { gunzipSync } from "node:zlib"
|
|
5
4
|
import type { CadComponent, CircuitJson, PcbBoard } from "circuit-json"
|
|
@@ -7,16 +6,14 @@ import {
|
|
|
7
6
|
convertCircuitJsonToGltf,
|
|
8
7
|
getBestCameraPosition,
|
|
9
8
|
} from "circuit-json-to-gltf"
|
|
10
|
-
import { decode } from "fast-png"
|
|
11
9
|
import { renderGLTFToPNGFromGLB } from "poppygl"
|
|
10
|
+
import { expectPngSnapshot } from "./expect-png-snapshot"
|
|
12
11
|
|
|
13
12
|
type CadModelFileName = string
|
|
14
13
|
type EmbeddedStepModelUrl = string
|
|
15
14
|
type BoardViewName = "bottom" | "top"
|
|
16
15
|
type SnapshotName = "generated" | "source"
|
|
17
16
|
|
|
18
|
-
const MAX_SNAPSHOT_DIFFERENT_PIXEL_FRACTION = 0.06
|
|
19
|
-
const MAX_CHANNEL_DIFFERENCE = 7
|
|
20
17
|
const TOP_BOARD_CAMERA_DIRECTION = [-0.7, 1.2, -0.8] as const
|
|
21
18
|
const BOTTOM_BOARD_CAMERA_DIRECTION = [-0.7, -1.2, -0.8] as const
|
|
22
19
|
|
|
@@ -118,58 +115,6 @@ async function renderBoardView({
|
|
|
118
115
|
})
|
|
119
116
|
}
|
|
120
117
|
|
|
121
|
-
async function expectPngSnapshot({
|
|
122
|
-
renderedPng,
|
|
123
|
-
snapshotPath,
|
|
124
|
-
}: {
|
|
125
|
-
renderedPng: Uint8Array
|
|
126
|
-
snapshotPath: string
|
|
127
|
-
}): Promise<void> {
|
|
128
|
-
const shouldUpdate =
|
|
129
|
-
process.env.BUN_UPDATE_SNAPSHOTS === "1" ||
|
|
130
|
-
process.env.BUN_FORCE_UPDATE_SNAPSHOTS === "1"
|
|
131
|
-
if (shouldUpdate) {
|
|
132
|
-
await mkdir(dirname(snapshotPath), { recursive: true })
|
|
133
|
-
await writeFile(snapshotPath, renderedPng)
|
|
134
|
-
}
|
|
135
|
-
expect(await Bun.file(snapshotPath).exists()).toBe(true)
|
|
136
|
-
|
|
137
|
-
const renderedImage = decode(renderedPng)
|
|
138
|
-
const snapshotImage = decode(await readFile(snapshotPath))
|
|
139
|
-
expect(renderedImage.width).toBe(snapshotImage.width)
|
|
140
|
-
expect(renderedImage.height).toBe(snapshotImage.height)
|
|
141
|
-
expect(renderedImage.channels).toBe(snapshotImage.channels)
|
|
142
|
-
|
|
143
|
-
let differentPixelCount = 0
|
|
144
|
-
const pixelCount = renderedImage.width * renderedImage.height
|
|
145
|
-
for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex += 1) {
|
|
146
|
-
const firstChannelIndex = pixelIndex * renderedImage.channels
|
|
147
|
-
let pixelIsDifferent = false
|
|
148
|
-
for (
|
|
149
|
-
let channelIndex = 0;
|
|
150
|
-
channelIndex < renderedImage.channels;
|
|
151
|
-
channelIndex += 1
|
|
152
|
-
) {
|
|
153
|
-
const sampleIndex = firstChannelIndex + channelIndex
|
|
154
|
-
if (
|
|
155
|
-
Math.abs(
|
|
156
|
-
(renderedImage.data[sampleIndex] ?? 0) -
|
|
157
|
-
(snapshotImage.data[sampleIndex] ?? 0),
|
|
158
|
-
) > MAX_CHANNEL_DIFFERENCE
|
|
159
|
-
) {
|
|
160
|
-
pixelIsDifferent = true
|
|
161
|
-
break
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (pixelIsDifferent) differentPixelCount += 1
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const differentPixelFraction = differentPixelCount / pixelCount
|
|
168
|
-
expect(differentPixelFraction).toBeLessThanOrEqual(
|
|
169
|
-
MAX_SNAPSHOT_DIFFERENT_PIXEL_FRACTION,
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
118
|
export async function expectTiEvm3dSnapshot({
|
|
174
119
|
cameraReferenceCircuitJson,
|
|
175
120
|
circuitJson,
|
|
@@ -24,3 +24,11 @@ the same five TI EVMs. Their `cad-models` directories contain the referenced
|
|
|
24
24
|
STEP files in gzip form. The tests embed those models before rendering top and
|
|
25
25
|
bottom 3D snapshots, so the complete Circuit JSON to TSX round trip is visible
|
|
26
26
|
without network access.
|
|
27
|
+
|
|
28
|
+
`dp83825-usb-cad-origin.circuit.json.gz` isolates J1 (Molex 475890001), its
|
|
29
|
+
native pads and silkscreen, and its embedded STEP placement from the same
|
|
30
|
+
DP83825EVM PcbDoc, converted with `altium-to-circuit-json@0.0.114`. Only the
|
|
31
|
+
surrounding board is cropped, and references to omitted electrical ports are
|
|
32
|
+
removed. The model reuses `cad-models/dp83825evm/8.step.gz`, verified against
|
|
33
|
+
the PcbDoc's embedded model bytes (SHA-256
|
|
34
|
+
`5c5fe4d712828519a11d3421da933f5b610d55c80e6c82a976539dff61385191`).
|
|
Binary file
|