circuit-json-to-step 0.0.1
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/.claude/settings.local.json +16 -0
- package/CLAUDE.md +10 -0
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/biome.json +100 -0
- package/bunfig.toml +5 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +705 -0
- package/lib/index.ts +545 -0
- package/lib/mesh-generation.ts +333 -0
- package/package.json +29 -0
- package/test/basics/basics01/basics01.json +40 -0
- package/test/basics/basics01/basics01.test.ts +54 -0
- package/test/basics/basics02/basics02.json +19 -0
- package/test/basics/basics02/basics02.test.ts +38 -0
- package/test/basics/basics03/basics03.json +49 -0
- package/test/basics/basics03/basics03.test.ts +38 -0
- package/test/basics/basics04/basics04.json +52 -0
- package/test/basics/basics04/basics04.test.ts +49 -0
- package/test/repros/repro01/repro01.json +2917 -0
- package/test/repros/repro01/repro01.test.ts +48 -0
- package/test/utils/occt/importer.ts +119 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import type { CircuitJson } from "circuit-json"
|
|
2
|
+
import type { Triangle as GLTFTriangle } from "circuit-json-to-gltf"
|
|
3
|
+
import { convertCircuitJsonTo3D } from "circuit-json-to-gltf"
|
|
4
|
+
import type { Ref } from "stepts"
|
|
5
|
+
import type { Repository } from "stepts"
|
|
6
|
+
import {
|
|
7
|
+
AdvancedFace,
|
|
8
|
+
Axis2Placement3D,
|
|
9
|
+
CartesianPoint,
|
|
10
|
+
ClosedShell,
|
|
11
|
+
Direction,
|
|
12
|
+
EdgeCurve,
|
|
13
|
+
EdgeLoop,
|
|
14
|
+
FaceOuterBound,
|
|
15
|
+
Line,
|
|
16
|
+
ManifoldSolidBrep,
|
|
17
|
+
OrientedEdge,
|
|
18
|
+
Plane,
|
|
19
|
+
Vector,
|
|
20
|
+
VertexPoint,
|
|
21
|
+
} from "stepts"
|
|
22
|
+
|
|
23
|
+
export interface MeshGenerationOptions {
|
|
24
|
+
/** Repository to add STEP entities to */
|
|
25
|
+
repo: Repository
|
|
26
|
+
/** Circuit JSON elements to convert */
|
|
27
|
+
circuitJson: CircuitJson
|
|
28
|
+
/** Board thickness in mm */
|
|
29
|
+
boardThickness: number
|
|
30
|
+
/** Include external model meshes from model_*_url fields (default: false) */
|
|
31
|
+
includeExternalMeshes?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generates triangles for a box mesh
|
|
36
|
+
*/
|
|
37
|
+
function createBoxTriangles(box: {
|
|
38
|
+
center: { x: number; y: number; z: number }
|
|
39
|
+
size: { x: number; y: number; z: number }
|
|
40
|
+
rotation?: { x: number; y: number; z: number }
|
|
41
|
+
}): GLTFTriangle[] {
|
|
42
|
+
const { center, size } = box
|
|
43
|
+
const halfX = size.x / 2
|
|
44
|
+
const halfY = size.y / 2
|
|
45
|
+
const halfZ = size.z / 2
|
|
46
|
+
|
|
47
|
+
// Define 8 corners of the box
|
|
48
|
+
const corners = [
|
|
49
|
+
{ x: -halfX, y: -halfY, z: -halfZ },
|
|
50
|
+
{ x: halfX, y: -halfY, z: -halfZ },
|
|
51
|
+
{ x: halfX, y: halfY, z: -halfZ },
|
|
52
|
+
{ x: -halfX, y: halfY, z: -halfZ },
|
|
53
|
+
{ x: -halfX, y: -halfY, z: halfZ },
|
|
54
|
+
{ x: halfX, y: -halfY, z: halfZ },
|
|
55
|
+
{ x: halfX, y: halfY, z: halfZ },
|
|
56
|
+
{ x: -halfX, y: halfY, z: halfZ },
|
|
57
|
+
].map((p) => ({ x: p.x + center.x, y: p.y + center.y, z: p.z + center.z }))
|
|
58
|
+
|
|
59
|
+
// Define triangles for each face (2 triangles per face)
|
|
60
|
+
const triangles: GLTFTriangle[] = [
|
|
61
|
+
// Bottom face (z = -halfZ)
|
|
62
|
+
{
|
|
63
|
+
vertices: [corners[0]!, corners[1]!, corners[2]!],
|
|
64
|
+
normal: { x: 0, y: 0, z: -1 },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
vertices: [corners[0]!, corners[2]!, corners[3]!],
|
|
68
|
+
normal: { x: 0, y: 0, z: -1 },
|
|
69
|
+
},
|
|
70
|
+
// Top face (z = halfZ)
|
|
71
|
+
{
|
|
72
|
+
vertices: [corners[4]!, corners[6]!, corners[5]!],
|
|
73
|
+
normal: { x: 0, y: 0, z: 1 },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
vertices: [corners[4]!, corners[7]!, corners[6]!],
|
|
77
|
+
normal: { x: 0, y: 0, z: 1 },
|
|
78
|
+
},
|
|
79
|
+
// Front face (y = -halfY)
|
|
80
|
+
{
|
|
81
|
+
vertices: [corners[0]!, corners[5]!, corners[1]!],
|
|
82
|
+
normal: { x: 0, y: -1, z: 0 },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
vertices: [corners[0]!, corners[4]!, corners[5]!],
|
|
86
|
+
normal: { x: 0, y: -1, z: 0 },
|
|
87
|
+
},
|
|
88
|
+
// Back face (y = halfY)
|
|
89
|
+
{
|
|
90
|
+
vertices: [corners[2]!, corners[6]!, corners[7]!],
|
|
91
|
+
normal: { x: 0, y: 1, z: 0 },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
vertices: [corners[2]!, corners[7]!, corners[3]!],
|
|
95
|
+
normal: { x: 0, y: 1, z: 0 },
|
|
96
|
+
},
|
|
97
|
+
// Left face (x = -halfX)
|
|
98
|
+
{
|
|
99
|
+
vertices: [corners[0]!, corners[3]!, corners[7]!],
|
|
100
|
+
normal: { x: -1, y: 0, z: 0 },
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
vertices: [corners[0]!, corners[7]!, corners[4]!],
|
|
104
|
+
normal: { x: -1, y: 0, z: 0 },
|
|
105
|
+
},
|
|
106
|
+
// Right face (x = halfX)
|
|
107
|
+
{
|
|
108
|
+
vertices: [corners[1]!, corners[6]!, corners[2]!],
|
|
109
|
+
normal: { x: 1, y: 0, z: 0 },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
vertices: [corners[1]!, corners[5]!, corners[6]!],
|
|
113
|
+
normal: { x: 1, y: 0, z: 0 },
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
return triangles
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates STEP faces from GLTF triangles
|
|
122
|
+
*/
|
|
123
|
+
function createStepFacesFromTriangles(
|
|
124
|
+
repo: Repository,
|
|
125
|
+
triangles: GLTFTriangle[],
|
|
126
|
+
): Ref<AdvancedFace>[] {
|
|
127
|
+
const faces: Ref<AdvancedFace>[] = []
|
|
128
|
+
|
|
129
|
+
for (const triangle of triangles) {
|
|
130
|
+
// Create vertices for triangle
|
|
131
|
+
const v1 = repo.add(
|
|
132
|
+
new VertexPoint(
|
|
133
|
+
"",
|
|
134
|
+
repo.add(
|
|
135
|
+
new CartesianPoint(
|
|
136
|
+
"",
|
|
137
|
+
triangle.vertices[0]!.x,
|
|
138
|
+
triangle.vertices[0]!.y,
|
|
139
|
+
triangle.vertices[0]!.z,
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
const v2 = repo.add(
|
|
145
|
+
new VertexPoint(
|
|
146
|
+
"",
|
|
147
|
+
repo.add(
|
|
148
|
+
new CartesianPoint(
|
|
149
|
+
"",
|
|
150
|
+
triangle.vertices[1]!.x,
|
|
151
|
+
triangle.vertices[1]!.y,
|
|
152
|
+
triangle.vertices[1]!.z,
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
const v3 = repo.add(
|
|
158
|
+
new VertexPoint(
|
|
159
|
+
"",
|
|
160
|
+
repo.add(
|
|
161
|
+
new CartesianPoint(
|
|
162
|
+
"",
|
|
163
|
+
triangle.vertices[2]!.x,
|
|
164
|
+
triangle.vertices[2]!.y,
|
|
165
|
+
triangle.vertices[2]!.z,
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// Create edges between vertices
|
|
172
|
+
const p1 = v1.resolve(repo).pnt.resolve(repo)
|
|
173
|
+
const p2 = v2.resolve(repo).pnt.resolve(repo)
|
|
174
|
+
|
|
175
|
+
const createEdge = (
|
|
176
|
+
vStart: Ref<VertexPoint>,
|
|
177
|
+
vEnd: Ref<VertexPoint>,
|
|
178
|
+
): Ref<EdgeCurve> => {
|
|
179
|
+
const pStart = vStart.resolve(repo).pnt.resolve(repo)
|
|
180
|
+
const pEnd = vEnd.resolve(repo).pnt.resolve(repo)
|
|
181
|
+
const dir = repo.add(
|
|
182
|
+
new Direction(
|
|
183
|
+
"",
|
|
184
|
+
pEnd.x - pStart.x,
|
|
185
|
+
pEnd.y - pStart.y,
|
|
186
|
+
pEnd.z - pStart.z,
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
const vec = repo.add(new Vector("", dir, 1))
|
|
190
|
+
const line = repo.add(new Line("", vStart.resolve(repo).pnt, vec))
|
|
191
|
+
return repo.add(new EdgeCurve("", vStart, vEnd, line, true))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const edge1 = createEdge(v1, v2)
|
|
195
|
+
const edge2 = createEdge(v2, v3)
|
|
196
|
+
const edge3 = createEdge(v3, v1)
|
|
197
|
+
|
|
198
|
+
// Create edge loop for triangle
|
|
199
|
+
const edgeLoop = repo.add(
|
|
200
|
+
new EdgeLoop("", [
|
|
201
|
+
repo.add(new OrientedEdge("", edge1, true)),
|
|
202
|
+
repo.add(new OrientedEdge("", edge2, true)),
|
|
203
|
+
repo.add(new OrientedEdge("", edge3, true)),
|
|
204
|
+
]),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
// Create planar surface using triangle normal
|
|
208
|
+
const normalDir = repo.add(
|
|
209
|
+
new Direction(
|
|
210
|
+
"",
|
|
211
|
+
triangle.normal.x,
|
|
212
|
+
triangle.normal.y,
|
|
213
|
+
triangle.normal.z,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
// Use first vertex as origin, calculate reference direction from first edge
|
|
218
|
+
const refX = p2.x - p1.x
|
|
219
|
+
const refY = p2.y - p1.y
|
|
220
|
+
const refZ = p2.z - p1.z
|
|
221
|
+
const refDir = repo.add(new Direction("", refX, refY, refZ))
|
|
222
|
+
|
|
223
|
+
const placement = repo.add(
|
|
224
|
+
new Axis2Placement3D("", v1.resolve(repo).pnt, normalDir, refDir),
|
|
225
|
+
)
|
|
226
|
+
const plane = repo.add(new Plane("", placement))
|
|
227
|
+
|
|
228
|
+
// Create face
|
|
229
|
+
const face = repo.add(
|
|
230
|
+
new AdvancedFace(
|
|
231
|
+
"",
|
|
232
|
+
[repo.add(new FaceOuterBound("", edgeLoop, true))],
|
|
233
|
+
plane,
|
|
234
|
+
true,
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
faces.push(face)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return faces
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Generates component meshes from circuit JSON and converts them to STEP solids
|
|
245
|
+
*
|
|
246
|
+
* By default, model_*_url fields are filtered out to prevent hanging on external
|
|
247
|
+
* model fetches during conversion. Set includeExternalMeshes to true to allow
|
|
248
|
+
* external model fetching.
|
|
249
|
+
*/
|
|
250
|
+
export async function generateComponentMeshes(
|
|
251
|
+
options: MeshGenerationOptions,
|
|
252
|
+
): Promise<Ref<ManifoldSolidBrep>[]> {
|
|
253
|
+
const {
|
|
254
|
+
repo,
|
|
255
|
+
circuitJson,
|
|
256
|
+
boardThickness,
|
|
257
|
+
includeExternalMeshes = false,
|
|
258
|
+
} = options
|
|
259
|
+
const solids: Ref<ManifoldSolidBrep>[] = []
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// Filter circuit JSON and optionally remove model URLs
|
|
263
|
+
const filteredCircuitJson = circuitJson
|
|
264
|
+
.filter((e) => e.type !== "pcb_board")
|
|
265
|
+
.map((e) => {
|
|
266
|
+
if (!includeExternalMeshes && e.type === "cad_component") {
|
|
267
|
+
// Remove model_*_url fields to avoid hanging on external model fetches
|
|
268
|
+
return {
|
|
269
|
+
...e,
|
|
270
|
+
model_3mf_url: undefined,
|
|
271
|
+
model_obj_url: undefined,
|
|
272
|
+
model_stl_url: undefined,
|
|
273
|
+
model_glb_url: undefined,
|
|
274
|
+
model_gltf_url: undefined,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return e
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Convert circuit JSON to 3D scene
|
|
281
|
+
const scene3d = await convertCircuitJsonTo3D(filteredCircuitJson, {
|
|
282
|
+
boardThickness,
|
|
283
|
+
renderBoardTextures: false,
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
// Extract or generate triangles from component boxes
|
|
287
|
+
const allTriangles: GLTFTriangle[] = []
|
|
288
|
+
for (const box of scene3d.boxes) {
|
|
289
|
+
if (box.mesh && "triangles" in box.mesh) {
|
|
290
|
+
allTriangles.push(...box.mesh.triangles)
|
|
291
|
+
} else {
|
|
292
|
+
// Generate simple box mesh for this component
|
|
293
|
+
const boxTriangles = createBoxTriangles(box)
|
|
294
|
+
allTriangles.push(...boxTriangles)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Create STEP faces from triangles if we have any
|
|
299
|
+
if (allTriangles.length > 0) {
|
|
300
|
+
// Transform triangles from GLTF XZ plane (Y=up) to STEP XY plane (Z=up)
|
|
301
|
+
const transformedTriangles = allTriangles.map((tri) => ({
|
|
302
|
+
vertices: tri.vertices.map((v) => ({
|
|
303
|
+
x: v.x,
|
|
304
|
+
y: v.z, // GLTF Z becomes STEP Y
|
|
305
|
+
z: v.y, // GLTF Y becomes STEP Z
|
|
306
|
+
})),
|
|
307
|
+
normal: {
|
|
308
|
+
x: tri.normal.x,
|
|
309
|
+
y: tri.normal.z, // GLTF Z becomes STEP Y
|
|
310
|
+
z: tri.normal.y, // GLTF Y becomes STEP Z
|
|
311
|
+
},
|
|
312
|
+
}))
|
|
313
|
+
const componentFaces = createStepFacesFromTriangles(
|
|
314
|
+
repo,
|
|
315
|
+
transformedTriangles as any,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
// Create closed shell and solid for components
|
|
319
|
+
const componentShell = repo.add(
|
|
320
|
+
new ClosedShell("", componentFaces as any),
|
|
321
|
+
)
|
|
322
|
+
const componentSolid = repo.add(
|
|
323
|
+
new ManifoldSolidBrep("Components", componentShell),
|
|
324
|
+
)
|
|
325
|
+
solids.push(componentSolid)
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.warn("Failed to generate component mesh:", error)
|
|
329
|
+
// Continue without components if generation fails
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return solids
|
|
333
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "circuit-json-to-step",
|
|
3
|
+
"main": "dist/index.js",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
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\"' {} \\;",
|
|
8
|
+
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
9
|
+
"format": "biome format --write .",
|
|
10
|
+
"format:check": "biome format ."
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/bun": "latest",
|
|
14
|
+
"circuit-json": "^0.0.272",
|
|
15
|
+
"occt-import-js": "^0.0.23",
|
|
16
|
+
"stepts": "^0.0.1",
|
|
17
|
+
"tsup": "^8.5.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"typescript": "^5"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@tscircuit/circuit-json-util": "^0.0.72",
|
|
24
|
+
"circuit-json-to-connectivity-map": "^0.0.22",
|
|
25
|
+
"circuit-json-to-gltf": "^0.0.19",
|
|
26
|
+
"circuit-to-svg": "^0.0.226",
|
|
27
|
+
"schematic-symbols": "^0.0.202"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "pcb_board",
|
|
4
|
+
"pcb_board_id": "pcb_board_1",
|
|
5
|
+
"width": 20,
|
|
6
|
+
"height": 15,
|
|
7
|
+
"thickness": 1.6,
|
|
8
|
+
"center": { "x": 10, "y": 7.5 },
|
|
9
|
+
"outline": [
|
|
10
|
+
{ "x": 0, "y": 0 },
|
|
11
|
+
{ "x": 20, "y": 0 },
|
|
12
|
+
{ "x": 20, "y": 15 },
|
|
13
|
+
{ "x": 0, "y": 15 }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "pcb_hole",
|
|
18
|
+
"pcb_hole_id": "pcb_hole_1",
|
|
19
|
+
"hole_shape": "circle",
|
|
20
|
+
"hole_diameter": 1.0,
|
|
21
|
+
"x": 5,
|
|
22
|
+
"y": 5
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "pcb_hole",
|
|
26
|
+
"pcb_hole_id": "pcb_hole_2",
|
|
27
|
+
"hole_shape": "circle",
|
|
28
|
+
"hole_diameter": 1.5,
|
|
29
|
+
"x": 15,
|
|
30
|
+
"y": 5
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "pcb_hole",
|
|
34
|
+
"pcb_hole_id": "pcb_hole_3",
|
|
35
|
+
"hole_shape": "circle",
|
|
36
|
+
"hole_diameter": 2.0,
|
|
37
|
+
"x": 10,
|
|
38
|
+
"y": 10
|
|
39
|
+
}
|
|
40
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs"
|
|
2
|
+
import { test, expect } from "bun:test"
|
|
3
|
+
import { circuitJsonToStep } from "../../../lib/index"
|
|
4
|
+
import { importStepWithOcct } from "../../utils/occt/importer"
|
|
5
|
+
import circuitJson from "./basics01.json"
|
|
6
|
+
|
|
7
|
+
test("basics01: convert circuit json with circular holes to STEP", async () => {
|
|
8
|
+
const stepText = await circuitJsonToStep(circuitJson as any, {
|
|
9
|
+
boardWidth: 20,
|
|
10
|
+
boardHeight: 15,
|
|
11
|
+
boardThickness: 1.6,
|
|
12
|
+
productName: "TestPCB",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Verify STEP format
|
|
16
|
+
expect(stepText).toContain("ISO-10303-21")
|
|
17
|
+
expect(stepText).toContain("END-ISO-10303-21")
|
|
18
|
+
|
|
19
|
+
// Verify product structure
|
|
20
|
+
expect(stepText).toContain("TestPCB")
|
|
21
|
+
expect(stepText).toContain("MANIFOLD_SOLID_BREP")
|
|
22
|
+
|
|
23
|
+
// Verify holes are created (should have CIRCLE and CYLINDRICAL_SURFACE entities)
|
|
24
|
+
expect(stepText).toContain("CIRCLE")
|
|
25
|
+
expect(stepText).toContain("CYLINDRICAL_SURFACE")
|
|
26
|
+
|
|
27
|
+
// Count CIRCLE occurrences - should have 12 (3 holes × 4 circles each: 2 for top/bottom faces, 2 for cylindrical surface)
|
|
28
|
+
const circleCount = (stepText.match(/CIRCLE/g) || []).length
|
|
29
|
+
expect(circleCount).toBe(12)
|
|
30
|
+
|
|
31
|
+
// Count CYLINDRICAL_SURFACE occurrences - should have 3 (one per hole)
|
|
32
|
+
const cylinderCount = (stepText.match(/CYLINDRICAL_SURFACE/g) || []).length
|
|
33
|
+
expect(cylinderCount).toBe(3)
|
|
34
|
+
|
|
35
|
+
// Write STEP file to debug-output
|
|
36
|
+
const outputPath = "debug-output/basics01.step"
|
|
37
|
+
writeFileSync(outputPath, stepText)
|
|
38
|
+
|
|
39
|
+
console.log("✓ STEP file generated successfully")
|
|
40
|
+
console.log(` - Circles created: ${circleCount}`)
|
|
41
|
+
console.log(` - STEP text length: ${stepText.length} bytes`)
|
|
42
|
+
console.log(` - Output: ${outputPath}`)
|
|
43
|
+
|
|
44
|
+
// Validate STEP file can be imported with occt-import-js
|
|
45
|
+
const occtResult = await importStepWithOcct(stepText)
|
|
46
|
+
expect(occtResult.success).toBe(true)
|
|
47
|
+
expect(occtResult.meshes.length).toBeGreaterThan(0)
|
|
48
|
+
|
|
49
|
+
const [firstMesh] = occtResult.meshes
|
|
50
|
+
expect(firstMesh.attributes.position.array.length).toBeGreaterThan(0)
|
|
51
|
+
expect(firstMesh.index.array.length).toBeGreaterThan(0)
|
|
52
|
+
|
|
53
|
+
console.log("✓ STEP file successfully validated with occt-import-js")
|
|
54
|
+
}, 20000)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "pcb_board",
|
|
4
|
+
"pcb_board_id": "pcb_board_1",
|
|
5
|
+
"width": 20,
|
|
6
|
+
"height": 15,
|
|
7
|
+
"thickness": 1.6,
|
|
8
|
+
"center": { "x": 10, "y": 7.5 },
|
|
9
|
+
"outline": [
|
|
10
|
+
{ "x": 2, "y": 3 },
|
|
11
|
+
{ "x": 18, "y": 3 },
|
|
12
|
+
{ "x": 18, "y": 7 },
|
|
13
|
+
{ "x": 20, "y": 7 },
|
|
14
|
+
{ "x": 10, "y": 15 },
|
|
15
|
+
{ "x": 0, "y": 7 },
|
|
16
|
+
{ "x": 2, "y": 7 }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs"
|
|
2
|
+
import { test, expect } from "bun:test"
|
|
3
|
+
import { circuitJsonToStep } from "../../../lib/index"
|
|
4
|
+
import { importStepWithOcct } from "../../utils/occt/importer"
|
|
5
|
+
import circuitJson from "./basics02.json"
|
|
6
|
+
|
|
7
|
+
test("basics02: convert pcb_board with outline only to STEP", async () => {
|
|
8
|
+
const stepText = await circuitJsonToStep(circuitJson as any, {
|
|
9
|
+
productName: "TestPCB_Outline",
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// Verify STEP format
|
|
13
|
+
expect(stepText).toContain("ISO-10303-21")
|
|
14
|
+
expect(stepText).toContain("END-ISO-10303-21")
|
|
15
|
+
|
|
16
|
+
// Verify product structure
|
|
17
|
+
expect(stepText).toContain("TestPCB_Outline")
|
|
18
|
+
expect(stepText).toContain("MANIFOLD_SOLID_BREP")
|
|
19
|
+
|
|
20
|
+
// Write STEP file to debug-output
|
|
21
|
+
const outputPath = "debug-output/basics02.step"
|
|
22
|
+
writeFileSync(outputPath, stepText)
|
|
23
|
+
|
|
24
|
+
console.log("✓ STEP file generated successfully")
|
|
25
|
+
console.log(` - STEP text length: ${stepText.length} bytes`)
|
|
26
|
+
console.log(` - Output: ${outputPath}`)
|
|
27
|
+
|
|
28
|
+
// Validate STEP file can be imported with occt-import-js
|
|
29
|
+
const occtResult = await importStepWithOcct(stepText)
|
|
30
|
+
expect(occtResult.success).toBe(true)
|
|
31
|
+
expect(occtResult.meshes.length).toBeGreaterThan(0)
|
|
32
|
+
|
|
33
|
+
const [firstMesh] = occtResult.meshes
|
|
34
|
+
expect(firstMesh.attributes.position.array.length).toBeGreaterThan(0)
|
|
35
|
+
expect(firstMesh.index.array.length).toBeGreaterThan(0)
|
|
36
|
+
|
|
37
|
+
console.log("✓ STEP file successfully validated with occt-import-js")
|
|
38
|
+
}, 20000)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "pcb_board",
|
|
4
|
+
"pcb_board_id": "pcb_board_1",
|
|
5
|
+
"width": 20,
|
|
6
|
+
"height": 15,
|
|
7
|
+
"thickness": 1.6,
|
|
8
|
+
"center": { "x": 10, "y": 7.5 },
|
|
9
|
+
"outline": [
|
|
10
|
+
{ "x": 2, "y": 3 },
|
|
11
|
+
{ "x": 18, "y": 3 },
|
|
12
|
+
{ "x": 18, "y": 7 },
|
|
13
|
+
{ "x": 20, "y": 7 },
|
|
14
|
+
{ "x": 10, "y": 15 },
|
|
15
|
+
{ "x": 0, "y": 7 },
|
|
16
|
+
{ "x": 2, "y": 7 }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "pcb_plated_hole",
|
|
21
|
+
"pcb_plated_hole_id": "pcb_plated_hole_1",
|
|
22
|
+
"shape": "circle",
|
|
23
|
+
"outer_diameter": 2.0,
|
|
24
|
+
"hole_diameter": 1.0,
|
|
25
|
+
"x": 5,
|
|
26
|
+
"y": 5,
|
|
27
|
+
"layers": ["top", "bottom"]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "pcb_plated_hole",
|
|
31
|
+
"pcb_plated_hole_id": "pcb_plated_hole_2",
|
|
32
|
+
"shape": "circle",
|
|
33
|
+
"outer_diameter": 2.5,
|
|
34
|
+
"hole_diameter": 1.5,
|
|
35
|
+
"x": 15,
|
|
36
|
+
"y": 5,
|
|
37
|
+
"layers": ["top", "bottom"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "pcb_plated_hole",
|
|
41
|
+
"pcb_plated_hole_id": "pcb_plated_hole_3",
|
|
42
|
+
"shape": "circle",
|
|
43
|
+
"outer_diameter": 3.0,
|
|
44
|
+
"hole_diameter": 2.0,
|
|
45
|
+
"x": 10,
|
|
46
|
+
"y": 10,
|
|
47
|
+
"layers": ["top", "bottom"]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { writeFileSync } from "node:fs"
|
|
3
|
+
import { circuitJsonToStep } from "../../../lib/index"
|
|
4
|
+
import { importStepWithOcct } from "../../utils/occt/importer"
|
|
5
|
+
import circuitJson from "./basics03.json"
|
|
6
|
+
|
|
7
|
+
test("basics03: convert pcb_board with arrow outline and pcb_plated_holes to STEP", async () => {
|
|
8
|
+
const stepText = await circuitJsonToStep(circuitJson as any, {
|
|
9
|
+
productName: "TestPCB_ArrowWithPlatedHoles",
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// Verify STEP format
|
|
13
|
+
expect(stepText).toContain("ISO-10303-21")
|
|
14
|
+
expect(stepText).toContain("END-ISO-10303-21")
|
|
15
|
+
|
|
16
|
+
// Verify product structure
|
|
17
|
+
expect(stepText).toContain("TestPCB_ArrowWithPlatedHoles")
|
|
18
|
+
expect(stepText).toContain("MANIFOLD_SOLID_BREP")
|
|
19
|
+
|
|
20
|
+
// Write STEP file to debug-output
|
|
21
|
+
const outputPath = "debug-output/basics03.step"
|
|
22
|
+
writeFileSync(outputPath, stepText)
|
|
23
|
+
|
|
24
|
+
console.log("✓ STEP file generated successfully")
|
|
25
|
+
console.log(` - STEP text length: ${stepText.length} bytes`)
|
|
26
|
+
console.log(` - Output: ${outputPath}`)
|
|
27
|
+
|
|
28
|
+
// Validate STEP file can be imported with occt-import-js
|
|
29
|
+
const occtResult = await importStepWithOcct(stepText)
|
|
30
|
+
expect(occtResult.success).toBe(true)
|
|
31
|
+
expect(occtResult.meshes.length).toBeGreaterThan(0)
|
|
32
|
+
|
|
33
|
+
const [firstMesh] = occtResult.meshes
|
|
34
|
+
expect(firstMesh.attributes.position.array.length).toBeGreaterThan(0)
|
|
35
|
+
expect(firstMesh.index.array.length).toBeGreaterThan(0)
|
|
36
|
+
|
|
37
|
+
console.log("✓ STEP file successfully validated with occt-import-js")
|
|
38
|
+
}, 20000)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "pcb_board",
|
|
4
|
+
"pcb_board_id": "pcb_board_1",
|
|
5
|
+
"width": 30,
|
|
6
|
+
"height": 20,
|
|
7
|
+
"thickness": 1.6,
|
|
8
|
+
"center": { "x": 15, "y": 10 }
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "pcb_hole",
|
|
12
|
+
"pcb_hole_id": "pcb_hole_1",
|
|
13
|
+
"hole_shape": "circle",
|
|
14
|
+
"hole_diameter": 1.0,
|
|
15
|
+
"x": 5,
|
|
16
|
+
"y": 5
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "source_component",
|
|
20
|
+
"source_component_id": "source_component_1",
|
|
21
|
+
"name": "R1",
|
|
22
|
+
"supplier_part_numbers": {},
|
|
23
|
+
"ftype": "simple_resistor"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "pcb_component",
|
|
27
|
+
"pcb_component_id": "pcb_component_1",
|
|
28
|
+
"source_component_id": "source_component_1",
|
|
29
|
+
"center": { "x": 15, "y": 10 },
|
|
30
|
+
"width": 3,
|
|
31
|
+
"height": 1.5,
|
|
32
|
+
"layer": "top",
|
|
33
|
+
"rotation": 0
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "source_component",
|
|
37
|
+
"source_component_id": "source_component_2",
|
|
38
|
+
"name": "C1",
|
|
39
|
+
"supplier_part_numbers": {},
|
|
40
|
+
"ftype": "simple_capacitor"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "pcb_component",
|
|
44
|
+
"pcb_component_id": "pcb_component_2",
|
|
45
|
+
"source_component_id": "source_component_2",
|
|
46
|
+
"center": { "x": 20, "y": 10 },
|
|
47
|
+
"width": 2,
|
|
48
|
+
"height": 1.25,
|
|
49
|
+
"layer": "top",
|
|
50
|
+
"rotation": 90
|
|
51
|
+
}
|
|
52
|
+
]
|