circuit-json-to-step 0.0.3 → 0.0.5
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/index.d.ts +6 -0
- package/dist/index.js +16 -21
- package/lib/index.ts +7 -0
- package/lib/step-model-merger/read-step-file.ts +18 -24
- package/lib/step-model-merger/types.ts +6 -0
- package/lib/step-model-merger.ts +2 -2
- package/package.json +2 -2
- package/test/repros/kicad-step/kicad-step.test.ts +3 -0
- package/test/utils/load-step-files.ts +41 -0
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ interface CircuitJsonToStepOptions {
|
|
|
13
13
|
includeComponents?: boolean;
|
|
14
14
|
/** Include external model meshes from model_*_url fields (default: false). Only applicable when includeComponents is true. */
|
|
15
15
|
includeExternalMeshes?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Pre-loaded STEP file contents, keyed by URL/path.
|
|
18
|
+
* If a URL is found here, the content is used directly instead of fetching.
|
|
19
|
+
* Useful for tests that need to load local files.
|
|
20
|
+
*/
|
|
21
|
+
fsMap?: Record<string, string>;
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
18
24
|
* Converts circuit JSON to STEP format, creating holes in a PCB board
|
package/dist/index.js
CHANGED
|
@@ -409,32 +409,26 @@ function rotateVector(vector, rotation) {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
// lib/step-model-merger/read-step-file.ts
|
|
412
|
-
import { promises as fs } from "fs";
|
|
413
|
-
import path from "path";
|
|
414
|
-
import { fileURLToPath } from "url";
|
|
415
412
|
async function readStepFile(modelUrl) {
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
return await res.text();
|
|
413
|
+
if (!/^https?:\/\//i.test(modelUrl)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Only HTTP(S) URLs are supported. For local files, read the file content and pass it directly. Received: ${modelUrl}`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const globalFetch = globalThis.fetch;
|
|
419
|
+
if (!globalFetch) {
|
|
420
|
+
throw new Error("fetch is not available in this environment");
|
|
426
421
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
422
|
+
const res = await globalFetch(modelUrl);
|
|
423
|
+
if (!res.ok) {
|
|
424
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
430
425
|
}
|
|
431
|
-
|
|
432
|
-
return await fs.readFile(resolvedPath, "utf8");
|
|
426
|
+
return await res.text();
|
|
433
427
|
}
|
|
434
428
|
|
|
435
429
|
// lib/step-model-merger.ts
|
|
436
430
|
async function mergeExternalStepModels(options) {
|
|
437
|
-
const { repo, circuitJson, boardThickness } = options;
|
|
431
|
+
const { repo, circuitJson, boardThickness, fsMap } = options;
|
|
438
432
|
const cadComponents = circuitJson.filter(
|
|
439
433
|
(item) => item?.type === "cad_component" && typeof item.model_step_url === "string"
|
|
440
434
|
);
|
|
@@ -451,7 +445,7 @@ async function mergeExternalStepModels(options) {
|
|
|
451
445
|
const componentId = component.cad_component_id ?? "";
|
|
452
446
|
const stepUrl = component.model_step_url;
|
|
453
447
|
try {
|
|
454
|
-
const stepText = await readStepFile(stepUrl);
|
|
448
|
+
const stepText = fsMap?.[stepUrl] ?? await readStepFile(stepUrl);
|
|
455
449
|
if (!stepText.trim()) {
|
|
456
450
|
throw new Error("STEP file is empty");
|
|
457
451
|
}
|
|
@@ -1085,7 +1079,8 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1085
1079
|
const mergeResult = await mergeExternalStepModels({
|
|
1086
1080
|
repo,
|
|
1087
1081
|
circuitJson,
|
|
1088
|
-
boardThickness
|
|
1082
|
+
boardThickness,
|
|
1083
|
+
fsMap: options.fsMap
|
|
1089
1084
|
});
|
|
1090
1085
|
handledComponentIds = mergeResult.handledComponentIds;
|
|
1091
1086
|
handledPcbComponentIds = mergeResult.handledPcbComponentIds;
|
package/lib/index.ts
CHANGED
|
@@ -57,6 +57,12 @@ export interface CircuitJsonToStepOptions {
|
|
|
57
57
|
includeComponents?: boolean
|
|
58
58
|
/** Include external model meshes from model_*_url fields (default: false). Only applicable when includeComponents is true. */
|
|
59
59
|
includeExternalMeshes?: boolean
|
|
60
|
+
/**
|
|
61
|
+
* Pre-loaded STEP file contents, keyed by URL/path.
|
|
62
|
+
* If a URL is found here, the content is used directly instead of fetching.
|
|
63
|
+
* Useful for tests that need to load local files.
|
|
64
|
+
*/
|
|
65
|
+
fsMap?: Record<string, string>
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
/**
|
|
@@ -510,6 +516,7 @@ export async function circuitJsonToStep(
|
|
|
510
516
|
repo,
|
|
511
517
|
circuitJson,
|
|
512
518
|
boardThickness,
|
|
519
|
+
fsMap: options.fsMap,
|
|
513
520
|
})
|
|
514
521
|
handledComponentIds = mergeResult.handledComponentIds
|
|
515
522
|
handledPcbComponentIds = mergeResult.handledPcbComponentIds
|
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Fetches STEP file content from an HTTP(S) URL.
|
|
3
|
+
* For local files, the caller must read the file and pass the content directly.
|
|
4
|
+
*/
|
|
5
5
|
export async function readStepFile(modelUrl: string): Promise<string> {
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (!globalFetch) {
|
|
11
|
-
throw new Error("fetch is not available in this environment")
|
|
12
|
-
}
|
|
13
|
-
const res = await globalFetch(modelUrl)
|
|
14
|
-
if (!res.ok) {
|
|
15
|
-
throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
16
|
-
}
|
|
17
|
-
return await res.text()
|
|
6
|
+
if (!/^https?:\/\//i.test(modelUrl)) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
`Only HTTP(S) URLs are supported. For local files, read the file content and pass it directly. Received: ${modelUrl}`,
|
|
9
|
+
)
|
|
18
10
|
}
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
const globalFetch = (globalThis as any).fetch as
|
|
13
|
+
| ((input: string, init?: unknown) => Promise<any>)
|
|
14
|
+
| undefined
|
|
15
|
+
if (!globalFetch) {
|
|
16
|
+
throw new Error("fetch is not available in this environment")
|
|
23
17
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return await
|
|
18
|
+
const res = await globalFetch(modelUrl)
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`)
|
|
21
|
+
}
|
|
22
|
+
return await res.text()
|
|
29
23
|
}
|
|
@@ -37,4 +37,10 @@ export interface MergeStepModelOptions {
|
|
|
37
37
|
repo: Repository
|
|
38
38
|
circuitJson: CircuitJson
|
|
39
39
|
boardThickness: number
|
|
40
|
+
/**
|
|
41
|
+
* Pre-loaded STEP file contents, keyed by file path.
|
|
42
|
+
* If a path is found here, the content is used directly instead of reading from disk.
|
|
43
|
+
* Useful for browser environments where fs is not available.
|
|
44
|
+
*/
|
|
45
|
+
fsMap?: Record<string, string>
|
|
40
46
|
}
|
package/lib/step-model-merger.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type {
|
|
|
33
33
|
export async function mergeExternalStepModels(
|
|
34
34
|
options: MergeStepModelOptions,
|
|
35
35
|
): Promise<MergeStepModelResult> {
|
|
36
|
-
const { repo, circuitJson, boardThickness } = options
|
|
36
|
+
const { repo, circuitJson, boardThickness, fsMap } = options
|
|
37
37
|
const cadComponents = (circuitJson as CadComponent[]).filter(
|
|
38
38
|
(item) =>
|
|
39
39
|
item?.type === "cad_component" && typeof item.model_step_url === "string",
|
|
@@ -55,7 +55,7 @@ export async function mergeExternalStepModels(
|
|
|
55
55
|
const stepUrl = component.model_step_url!
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
|
-
const stepText = await readStepFile(stepUrl)
|
|
58
|
+
const stepText = fsMap?.[stepUrl] ?? (await readStepFile(stepUrl))
|
|
59
59
|
if (!stepText.trim()) {
|
|
60
60
|
throw new Error("STEP file is empty")
|
|
61
61
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuit-json-to-step",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"pull-reference": "git clone https://github.com/tscircuit/circuit-json.git && find circuit-json/tests -name '*.test.ts' -exec bash -c 'mv \"$0\" \"${0%.test.ts}.ts\"' {} \\; && git clone https://github.com/tscircuit/stepts.git && find stepts/tests -name '*.test.ts' -exec bash -c 'mv \"$0\" \"${0%.test.ts}.ts\"' {} \\;",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"looks-same": "^10.0.1",
|
|
19
19
|
"occt-import-js": "^0.0.23",
|
|
20
20
|
"poppygl": "^0.0.13",
|
|
21
|
-
"stepts": "^0.0.
|
|
21
|
+
"stepts": "^0.0.2",
|
|
22
22
|
"tsup": "^8.5.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path"
|
|
|
4
4
|
import { circuitJsonToStep } from "../../../lib/index"
|
|
5
5
|
import { importStepWithOcct } from "../../utils/occt/importer"
|
|
6
6
|
import type { OcctMesh } from "../../utils/occt/importer"
|
|
7
|
+
import { loadStepFilesFromCircuitJson } from "../../utils/load-step-files"
|
|
7
8
|
import { parseRepository, ManifoldSolidBrep } from "stepts"
|
|
8
9
|
import circuitJson from "./kicad-step.json"
|
|
9
10
|
|
|
@@ -90,10 +91,12 @@ test("kicad-step: switch fixture renders consistently", async () => {
|
|
|
90
91
|
}, 30000)
|
|
91
92
|
|
|
92
93
|
test("kicad-step: merges KiCad STEP models referenced via model_step_url", async () => {
|
|
94
|
+
const fsMap = await loadStepFilesFromCircuitJson(circuitJson)
|
|
93
95
|
const stepText = await circuitJsonToStep(circuitJson as any, {
|
|
94
96
|
includeComponents: true,
|
|
95
97
|
includeExternalMeshes: true,
|
|
96
98
|
productName: "KiCadStepMerge",
|
|
99
|
+
fsMap,
|
|
97
100
|
})
|
|
98
101
|
|
|
99
102
|
expect(stepText).toContain("KiCadStepMerge")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utility for loading local STEP files.
|
|
3
|
+
* This file uses Node APIs and should only be imported in tests, not in lib.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "node:fs/promises"
|
|
6
|
+
|
|
7
|
+
type CadComponent = {
|
|
8
|
+
type: string
|
|
9
|
+
model_step_url?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isFilePath(value: string): boolean {
|
|
13
|
+
return !/^https?:\/\//i.test(value)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads all local STEP files referenced by cad_components in circuit JSON.
|
|
18
|
+
* Returns a map of file path to file content.
|
|
19
|
+
*/
|
|
20
|
+
export async function loadStepFilesFromCircuitJson(
|
|
21
|
+
circuitJson: unknown[],
|
|
22
|
+
): Promise<Record<string, string>> {
|
|
23
|
+
const stepContents: Record<string, string> = {}
|
|
24
|
+
const stepFilePaths = new Set<string>()
|
|
25
|
+
|
|
26
|
+
for (const item of circuitJson as CadComponent[]) {
|
|
27
|
+
if (
|
|
28
|
+
item?.type === "cad_component" &&
|
|
29
|
+
item.model_step_url &&
|
|
30
|
+
isFilePath(item.model_step_url)
|
|
31
|
+
) {
|
|
32
|
+
stepFilePaths.add(item.model_step_url)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const filePath of stepFilePaths) {
|
|
37
|
+
stepContents[filePath] = await readFile(filePath, "utf8")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return stepContents
|
|
41
|
+
}
|