circuit-json-to-step 0.0.21 → 0.0.23

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/lib/index.ts CHANGED
@@ -91,6 +91,7 @@ export async function circuitJsonToStep(
91
91
  // Get board center position (defaults to 0, 0 if not specified)
92
92
  const boardCenterX = pcbBoard?.center?.x ?? 0
93
93
  const boardCenterY = pcbBoard?.center?.y ?? 0
94
+ const halfBoardThickness = boardThickness / 2
94
95
 
95
96
  if (!boardWidth || !boardHeight) {
96
97
  throw new Error(
@@ -193,7 +194,9 @@ export async function circuitJsonToStep(
193
194
  repo.add(
194
195
  new VertexPoint(
195
196
  "",
196
- repo.add(new CartesianPoint("", point.x, point.y, 0)),
197
+ repo.add(
198
+ new CartesianPoint("", point.x, point.y, -halfBoardThickness),
199
+ ),
197
200
  ),
198
201
  ),
199
202
  )
@@ -201,7 +204,9 @@ export async function circuitJsonToStep(
201
204
  repo.add(
202
205
  new VertexPoint(
203
206
  "",
204
- repo.add(new CartesianPoint("", point.x, point.y, boardThickness)),
207
+ repo.add(
208
+ new CartesianPoint("", point.x, point.y, halfBoardThickness),
209
+ ),
205
210
  ),
206
211
  ),
207
212
  )
@@ -210,14 +215,30 @@ export async function circuitJsonToStep(
210
215
  const halfWidth = boardWidth / 2
211
216
  const halfHeight = boardHeight / 2
212
217
  const corners = [
213
- [boardCenterX - halfWidth, boardCenterY - halfHeight, 0],
214
- [boardCenterX + halfWidth, boardCenterY - halfHeight, 0],
215
- [boardCenterX + halfWidth, boardCenterY + halfHeight, 0],
216
- [boardCenterX - halfWidth, boardCenterY + halfHeight, 0],
217
- [boardCenterX - halfWidth, boardCenterY - halfHeight, boardThickness],
218
- [boardCenterX + halfWidth, boardCenterY - halfHeight, boardThickness],
219
- [boardCenterX + halfWidth, boardCenterY + halfHeight, boardThickness],
220
- [boardCenterX - halfWidth, boardCenterY + halfHeight, boardThickness],
218
+ [
219
+ boardCenterX - halfWidth,
220
+ boardCenterY - halfHeight,
221
+ -halfBoardThickness,
222
+ ],
223
+ [
224
+ boardCenterX + halfWidth,
225
+ boardCenterY - halfHeight,
226
+ -halfBoardThickness,
227
+ ],
228
+ [
229
+ boardCenterX + halfWidth,
230
+ boardCenterY + halfHeight,
231
+ -halfBoardThickness,
232
+ ],
233
+ [
234
+ boardCenterX - halfWidth,
235
+ boardCenterY + halfHeight,
236
+ -halfBoardThickness,
237
+ ],
238
+ [boardCenterX - halfWidth, boardCenterY - halfHeight, halfBoardThickness],
239
+ [boardCenterX + halfWidth, boardCenterY - halfHeight, halfBoardThickness],
240
+ [boardCenterX + halfWidth, boardCenterY + halfHeight, halfBoardThickness],
241
+ [boardCenterX - halfWidth, boardCenterY + halfHeight, halfBoardThickness],
221
242
  ]
222
243
  const vertices = corners.map(([x, y, z]) =>
223
244
  repo.add(
@@ -283,11 +304,11 @@ export async function circuitJsonToStep(
283
304
  verticalEdges.push(createEdge(bottomVertices[i]!, topVertices[i]!))
284
305
  }
285
306
 
286
- const origin = repo.add(new CartesianPoint("", 0, 0, 0))
307
+ const origin = repo.add(new CartesianPoint("", 0, 0, -halfBoardThickness))
287
308
  const xDir = repo.add(new Direction("", 1, 0, 0))
288
309
  const zDir = repo.add(new Direction("", 0, 0, 1))
289
310
 
290
- // Bottom face (z=0, normal pointing down)
311
+ // Bottom face (z=-boardThickness/2, normal pointing down)
291
312
  const bottomFrame = repo.add(
292
313
  new Axis2Placement3D(
293
314
  "",
@@ -314,11 +335,15 @@ export async function circuitJsonToStep(
314
335
  const holeY = typeof hole.y === "number" ? hole.y : (hole.y as any).value
315
336
  const radius = hole.hole_diameter / 2
316
337
 
317
- const holeCenter = repo.add(new CartesianPoint("", holeX, holeY, 0))
338
+ const holeCenter = repo.add(
339
+ new CartesianPoint("", holeX, holeY, -halfBoardThickness),
340
+ )
318
341
  const holeVertex = repo.add(
319
342
  new VertexPoint(
320
343
  "",
321
- repo.add(new CartesianPoint("", holeX + radius, holeY, 0)),
344
+ repo.add(
345
+ new CartesianPoint("", holeX + radius, holeY, -halfBoardThickness),
346
+ ),
322
347
  ),
323
348
  )
324
349
  const holePlacement = repo.add(
@@ -338,8 +363,8 @@ export async function circuitJsonToStep(
338
363
  )
339
364
  bottomHoleLoops.push(repo.add(new FaceBound("", holeLoop, true)))
340
365
  } else if (holeShape === "rotated_pill" || holeShape === "pill") {
341
- // Create rotated pill-shaped hole boundary at z=0
342
- const pillLoop = createPillHoleLoop(repo, hole, 0, xDir)
366
+ // Create rotated pill-shaped hole boundary at z=-boardThickness/2
367
+ const pillLoop = createPillHoleLoop(repo, hole, -halfBoardThickness, xDir)
343
368
  bottomHoleLoops.push(repo.add(new FaceBound("", pillLoop, true)))
344
369
  }
345
370
  }
@@ -356,8 +381,8 @@ export async function circuitJsonToStep(
356
381
  ),
357
382
  )
358
383
 
359
- // Top face (z=boardThickness, normal pointing up)
360
- const topOrigin = repo.add(new CartesianPoint("", 0, 0, boardThickness))
384
+ // Top face (z=boardThickness/2, normal pointing up)
385
+ const topOrigin = repo.add(new CartesianPoint("", 0, 0, halfBoardThickness))
361
386
  const topFrame = repo.add(new Axis2Placement3D("", topOrigin, zDir, xDir))
362
387
  const topPlane = repo.add(new Plane("", topFrame))
363
388
  const topLoop = repo.add(
@@ -378,13 +403,13 @@ export async function circuitJsonToStep(
378
403
  const radius = hole.hole_diameter / 2
379
404
 
380
405
  const holeCenter = repo.add(
381
- new CartesianPoint("", holeX, holeY, boardThickness),
406
+ new CartesianPoint("", holeX, holeY, halfBoardThickness),
382
407
  )
383
408
  const holeVertex = repo.add(
384
409
  new VertexPoint(
385
410
  "",
386
411
  repo.add(
387
- new CartesianPoint("", holeX + radius, holeY, boardThickness),
412
+ new CartesianPoint("", holeX + radius, holeY, halfBoardThickness),
388
413
  ),
389
414
  ),
390
415
  )
@@ -400,8 +425,8 @@ export async function circuitJsonToStep(
400
425
  )
401
426
  topHoleLoops.push(repo.add(new FaceBound("", holeLoop, true)))
402
427
  } else if (holeShape === "rotated_pill" || holeShape === "pill") {
403
- // Create rotated pill-shaped hole boundary at z=boardThickness
404
- const pillLoop = createPillHoleLoop(repo, hole, boardThickness, xDir)
428
+ // Create rotated pill-shaped hole boundary at z=boardThickness/2
429
+ const pillLoop = createPillHoleLoop(repo, hole, halfBoardThickness, xDir)
405
430
  topHoleLoops.push(repo.add(new FaceBound("", pillLoop, true)))
406
431
  }
407
432
  }
@@ -430,7 +455,7 @@ export async function circuitJsonToStep(
430
455
  const edgeDir = {
431
456
  x: bottomV2.x - bottomV1.x,
432
457
  y: bottomV2.y - bottomV1.y,
433
- z: 0,
458
+ z: -halfBoardThickness,
434
459
  }
435
460
  // Normal is perpendicular (rotate 90 degrees clockwise in XY plane for outward facing)
436
461
  const normalDir = repo.add(new Direction("", edgeDir.y, -edgeDir.x, 0))
@@ -471,11 +496,15 @@ export async function circuitJsonToStep(
471
496
  const radius = hole.hole_diameter / 2
472
497
 
473
498
  // Create circular edges at bottom and top
474
- const bottomHoleCenter = repo.add(new CartesianPoint("", holeX, holeY, 0))
499
+ const bottomHoleCenter = repo.add(
500
+ new CartesianPoint("", holeX, holeY, -halfBoardThickness),
501
+ )
475
502
  const bottomHoleVertex = repo.add(
476
503
  new VertexPoint(
477
504
  "",
478
- repo.add(new CartesianPoint("", holeX + radius, holeY, 0)),
505
+ repo.add(
506
+ new CartesianPoint("", holeX + radius, holeY, -halfBoardThickness),
507
+ ),
479
508
  ),
480
509
  )
481
510
  const bottomHolePlacement = repo.add(
@@ -500,13 +529,13 @@ export async function circuitJsonToStep(
500
529
  )
501
530
 
502
531
  const topHoleCenter = repo.add(
503
- new CartesianPoint("", holeX, holeY, boardThickness),
532
+ new CartesianPoint("", holeX, holeY, halfBoardThickness),
504
533
  )
505
534
  const topHoleVertex = repo.add(
506
535
  new VertexPoint(
507
536
  "",
508
537
  repo.add(
509
- new CartesianPoint("", holeX + radius, holeY, boardThickness),
538
+ new CartesianPoint("", holeX + radius, holeY, halfBoardThickness),
510
539
  ),
511
540
  ),
512
541
  )
@@ -547,7 +576,8 @@ export async function circuitJsonToStep(
547
576
  const pillFaces = createPillCylindricalFaces(
548
577
  repo,
549
578
  hole,
550
- boardThickness,
579
+ -halfBoardThickness,
580
+ halfBoardThickness,
551
581
  xDir,
552
582
  zDir,
553
583
  )
@@ -1,23 +1,8 @@
1
1
  import type { CircuitJson } from "circuit-json"
2
- import type { Ref } from "stepts"
3
- import type { Triangle as GLTFTriangle } from "circuit-json-to-gltf"
4
- import type { Repository } from "stepts"
5
- import {
6
- AdvancedFace,
7
- Axis2Placement3D,
8
- CartesianPoint,
9
- ClosedShell,
10
- Direction,
11
- EdgeCurve,
12
- EdgeLoop,
13
- FaceOuterBound,
14
- Line,
15
- ManifoldSolidBrep,
16
- OrientedEdge,
17
- Plane,
18
- Vector,
19
- VertexPoint,
20
- } from "stepts"
2
+ import type { Ref, Repository } from "stepts"
3
+ import { ManifoldSolidBrep } from "stepts"
4
+ import { createSceneBoxSolid } from "./scene-box-to-step"
5
+ import type { SceneBox } from "./scene-geometry"
21
6
 
22
7
  export interface MeshGenerationOptions {
23
8
  /** Repository to add STEP entities to */
@@ -36,215 +21,6 @@ export interface MeshGenerationOptions {
36
21
  pcbComponentIdsWithStepUrl?: Set<string>
37
22
  }
38
23
 
39
- /**
40
- * Generates triangles for a box mesh
41
- */
42
- function createBoxTriangles(box: {
43
- center: { x: number; y: number; z: number }
44
- size: { x: number; y: number; z: number }
45
- rotation?: { x: number; y: number; z: number }
46
- }): GLTFTriangle[] {
47
- const { center, size } = box
48
- const halfX = size.x / 2
49
- const halfY = size.y / 2
50
- const halfZ = size.z / 2
51
-
52
- // Define 8 corners of the box
53
- const corners = [
54
- { x: -halfX, y: -halfY, z: -halfZ },
55
- { x: halfX, y: -halfY, z: -halfZ },
56
- { x: halfX, y: halfY, z: -halfZ },
57
- { x: -halfX, y: halfY, z: -halfZ },
58
- { x: -halfX, y: -halfY, z: halfZ },
59
- { x: halfX, y: -halfY, z: halfZ },
60
- { x: halfX, y: halfY, z: halfZ },
61
- { x: -halfX, y: halfY, z: halfZ },
62
- ].map((p) => ({ x: p.x + center.x, y: p.y + center.y, z: p.z + center.z }))
63
-
64
- // Define triangles for each face (2 triangles per face)
65
- const triangles: GLTFTriangle[] = [
66
- // Bottom face (z = -halfZ)
67
- {
68
- vertices: [corners[0]!, corners[1]!, corners[2]!],
69
- normal: { x: 0, y: 0, z: -1 },
70
- },
71
- {
72
- vertices: [corners[0]!, corners[2]!, corners[3]!],
73
- normal: { x: 0, y: 0, z: -1 },
74
- },
75
- // Top face (z = halfZ)
76
- {
77
- vertices: [corners[4]!, corners[6]!, corners[5]!],
78
- normal: { x: 0, y: 0, z: 1 },
79
- },
80
- {
81
- vertices: [corners[4]!, corners[7]!, corners[6]!],
82
- normal: { x: 0, y: 0, z: 1 },
83
- },
84
- // Front face (y = -halfY)
85
- {
86
- vertices: [corners[0]!, corners[5]!, corners[1]!],
87
- normal: { x: 0, y: -1, z: 0 },
88
- },
89
- {
90
- vertices: [corners[0]!, corners[4]!, corners[5]!],
91
- normal: { x: 0, y: -1, z: 0 },
92
- },
93
- // Back face (y = halfY)
94
- {
95
- vertices: [corners[2]!, corners[6]!, corners[7]!],
96
- normal: { x: 0, y: 1, z: 0 },
97
- },
98
- {
99
- vertices: [corners[2]!, corners[7]!, corners[3]!],
100
- normal: { x: 0, y: 1, z: 0 },
101
- },
102
- // Left face (x = -halfX)
103
- {
104
- vertices: [corners[0]!, corners[3]!, corners[7]!],
105
- normal: { x: -1, y: 0, z: 0 },
106
- },
107
- {
108
- vertices: [corners[0]!, corners[7]!, corners[4]!],
109
- normal: { x: -1, y: 0, z: 0 },
110
- },
111
- // Right face (x = halfX)
112
- {
113
- vertices: [corners[1]!, corners[6]!, corners[2]!],
114
- normal: { x: 1, y: 0, z: 0 },
115
- },
116
- {
117
- vertices: [corners[1]!, corners[5]!, corners[6]!],
118
- normal: { x: 1, y: 0, z: 0 },
119
- },
120
- ]
121
-
122
- return triangles
123
- }
124
-
125
- /**
126
- * Creates STEP faces from GLTF triangles
127
- */
128
- function createStepFacesFromTriangles(
129
- repo: Repository,
130
- triangles: GLTFTriangle[],
131
- ): Ref<AdvancedFace>[] {
132
- const faces: Ref<AdvancedFace>[] = []
133
-
134
- for (const triangle of triangles) {
135
- // Create vertices for triangle
136
- const v1 = repo.add(
137
- new VertexPoint(
138
- "",
139
- repo.add(
140
- new CartesianPoint(
141
- "",
142
- triangle.vertices[0]!.x,
143
- triangle.vertices[0]!.y,
144
- triangle.vertices[0]!.z,
145
- ),
146
- ),
147
- ),
148
- )
149
- const v2 = repo.add(
150
- new VertexPoint(
151
- "",
152
- repo.add(
153
- new CartesianPoint(
154
- "",
155
- triangle.vertices[1]!.x,
156
- triangle.vertices[1]!.y,
157
- triangle.vertices[1]!.z,
158
- ),
159
- ),
160
- ),
161
- )
162
- const v3 = repo.add(
163
- new VertexPoint(
164
- "",
165
- repo.add(
166
- new CartesianPoint(
167
- "",
168
- triangle.vertices[2]!.x,
169
- triangle.vertices[2]!.y,
170
- triangle.vertices[2]!.z,
171
- ),
172
- ),
173
- ),
174
- )
175
-
176
- // Create edges between vertices
177
- const p1 = v1.resolve(repo).pnt.resolve(repo)
178
- const p2 = v2.resolve(repo).pnt.resolve(repo)
179
-
180
- const createEdge = (
181
- vStart: Ref<VertexPoint>,
182
- vEnd: Ref<VertexPoint>,
183
- ): Ref<EdgeCurve> => {
184
- const pStart = vStart.resolve(repo).pnt.resolve(repo)
185
- const pEnd = vEnd.resolve(repo).pnt.resolve(repo)
186
- const dir = repo.add(
187
- new Direction(
188
- "",
189
- pEnd.x - pStart.x,
190
- pEnd.y - pStart.y,
191
- pEnd.z - pStart.z,
192
- ),
193
- )
194
- const vec = repo.add(new Vector("", dir, 1))
195
- const line = repo.add(new Line("", vStart.resolve(repo).pnt, vec))
196
- return repo.add(new EdgeCurve("", vStart, vEnd, line, true))
197
- }
198
-
199
- const edge1 = createEdge(v1, v2)
200
- const edge2 = createEdge(v2, v3)
201
- const edge3 = createEdge(v3, v1)
202
-
203
- // Create edge loop for triangle
204
- const edgeLoop = repo.add(
205
- new EdgeLoop("", [
206
- repo.add(new OrientedEdge("", edge1, true)),
207
- repo.add(new OrientedEdge("", edge2, true)),
208
- repo.add(new OrientedEdge("", edge3, true)),
209
- ]),
210
- )
211
-
212
- // Create planar surface using triangle normal
213
- const normalDir = repo.add(
214
- new Direction(
215
- "",
216
- triangle.normal.x,
217
- triangle.normal.y,
218
- triangle.normal.z,
219
- ),
220
- )
221
-
222
- // Use first vertex as origin, calculate reference direction from first edge
223
- const refX = p2.x - p1.x
224
- const refY = p2.y - p1.y
225
- const refZ = p2.z - p1.z
226
- const refDir = repo.add(new Direction("", refX, refY, refZ))
227
-
228
- const placement = repo.add(
229
- new Axis2Placement3D("", v1.resolve(repo).pnt, normalDir, refDir),
230
- )
231
- const plane = repo.add(new Plane("", placement))
232
-
233
- // Create face
234
- const face = repo.add(
235
- new AdvancedFace(
236
- "",
237
- [repo.add(new FaceOuterBound("", edgeLoop, true))],
238
- plane,
239
- true,
240
- ),
241
- )
242
- faces.push(face)
243
- }
244
-
245
- return faces
246
- }
247
-
248
24
  /**
249
25
  * Generates component meshes from circuit JSON and converts them to STEP solids
250
26
  *
@@ -264,46 +40,48 @@ export async function generateComponentMeshes(
264
40
  excludePcbComponentIds,
265
41
  pcbComponentIdsWithStepUrl,
266
42
  } = options
43
+
267
44
  const solids: Ref<ManifoldSolidBrep>[] = []
45
+
268
46
  try {
269
- // Filter circuit JSON and optionally remove model URLs
270
47
  const filteredCircuitJson = circuitJson
271
- .filter((e) => {
272
- if (e.type === "pcb_board") return false
48
+ .filter((element) => {
49
+ if (element.type === "pcb_board") return false
50
+
273
51
  if (
274
- e.type === "cad_component" &&
275
- e.cad_component_id &&
276
- excludeCadComponentIds?.has(e.cad_component_id)
52
+ element.type === "cad_component" &&
53
+ element.cad_component_id &&
54
+ excludeCadComponentIds?.has(element.cad_component_id)
277
55
  ) {
278
56
  return false
279
57
  }
58
+
280
59
  if (
281
- e.type === "pcb_component" &&
282
- e.pcb_component_id &&
283
- excludePcbComponentIds?.has(e.pcb_component_id)
60
+ element.type === "pcb_component" &&
61
+ element.pcb_component_id &&
62
+ excludePcbComponentIds?.has(element.pcb_component_id)
284
63
  ) {
285
64
  return false
286
65
  }
287
- // Skip cad_components that have model_step_url
288
- // (they should be handled by mergeExternalStepModels, not mesh generation)
289
- if (e.type === "cad_component" && e.model_step_url) {
66
+
67
+ if (element.type === "cad_component" && element.model_step_url) {
290
68
  return false
291
69
  }
292
- // Skip cad_components whose pcb_component_id is covered by another cad_component with STEP URL
70
+
293
71
  if (
294
- e.type === "cad_component" &&
295
- e.pcb_component_id &&
296
- pcbComponentIdsWithStepUrl?.has(e.pcb_component_id)
72
+ element.type === "cad_component" &&
73
+ element.pcb_component_id &&
74
+ pcbComponentIdsWithStepUrl?.has(element.pcb_component_id)
297
75
  ) {
298
76
  return false
299
77
  }
78
+
300
79
  return true
301
80
  })
302
- .map((e) => {
303
- if (!includeExternalMeshes && e.type === "cad_component") {
304
- // Remove model_*_url fields to avoid hanging on external model fetches
81
+ .map((element) => {
82
+ if (!includeExternalMeshes && element.type === "cad_component") {
305
83
  return {
306
- ...e,
84
+ ...element,
307
85
  model_3mf_url: undefined,
308
86
  model_obj_url: undefined,
309
87
  model_stl_url: undefined,
@@ -311,66 +89,25 @@ export async function generateComponentMeshes(
311
89
  model_gltf_url: undefined,
312
90
  }
313
91
  }
314
- return e
92
+
93
+ return element
315
94
  })
316
95
 
317
- // Dynamically import circuit-json-to-gltf to avoid bundling native dependencies
318
- // Use a variable to prevent the bundler from statically analyzing the import
319
96
  const gltfModule = "circuit-json-to-gltf"
320
97
  const { convertCircuitJsonTo3D } = await import(
321
98
  /* @vite-ignore */ gltfModule
322
99
  )
323
100
 
324
- // Convert circuit JSON to 3D scene
325
101
  const scene3d = await convertCircuitJsonTo3D(filteredCircuitJson, {
326
102
  boardThickness,
327
103
  renderBoardTextures: false,
328
104
  })
329
105
 
330
- // Extract or generate triangles from component boxes
331
- const allTriangles: GLTFTriangle[] = []
332
- for (const box of scene3d.boxes) {
333
- if (box.mesh && "triangles" in box.mesh) {
334
- allTriangles.push(...box.mesh.triangles)
335
- } else {
336
- // Generate simple box mesh for this component
337
- const boxTriangles = createBoxTriangles(box)
338
- allTriangles.push(...boxTriangles)
339
- }
340
- }
341
-
342
- // Create STEP faces from triangles if we have any
343
- if (allTriangles.length > 0) {
344
- // Transform triangles from GLTF XZ plane (Y=up) to STEP XY plane (Z=up)
345
- const transformedTriangles = allTriangles.map((tri) => ({
346
- vertices: tri.vertices.map((v) => ({
347
- x: v.x,
348
- y: v.z, // GLTF Z becomes STEP Y
349
- z: v.y, // GLTF Y becomes STEP Z
350
- })),
351
- normal: {
352
- x: tri.normal.x,
353
- y: tri.normal.z, // GLTF Z becomes STEP Y
354
- z: tri.normal.y, // GLTF Y becomes STEP Z
355
- },
356
- }))
357
- const componentFaces = createStepFacesFromTriangles(
358
- repo,
359
- transformedTriangles as any,
360
- )
361
-
362
- // Create closed shell and solid for components
363
- const componentShell = repo.add(
364
- new ClosedShell("", componentFaces as any),
365
- )
366
- const componentSolid = repo.add(
367
- new ManifoldSolidBrep("Components", componentShell),
368
- )
369
- solids.push(componentSolid)
106
+ for (const box of scene3d.boxes as SceneBox[]) {
107
+ solids.push(createSceneBoxSolid(repo, box))
370
108
  }
371
109
  } catch (error) {
372
110
  console.warn("Failed to generate component mesh:", error)
373
- // Continue without components if generation fails
374
111
  }
375
112
 
376
113
  return solids