circuit-json-to-step 0.0.13 → 0.0.15

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.js CHANGED
@@ -722,7 +722,7 @@ function normalizeStepNumericExponents(stepText) {
722
722
  var package_default = {
723
723
  name: "circuit-json-to-step",
724
724
  main: "dist/index.js",
725
- version: "0.0.12",
725
+ version: "0.0.13",
726
726
  type: "module",
727
727
  scripts: {
728
728
  "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"' {} \\;`,
@@ -743,7 +743,7 @@ var package_default = {
743
743
  "circuit-json": "^0.0.286",
744
744
  "looks-same": "^10.0.1",
745
745
  "occt-import-js": "^0.0.23",
746
- poppygl: "^0.0.13",
746
+ poppygl: "^0.0.17",
747
747
  stepts: "^0.0.2",
748
748
  tsup: "^8.5.0"
749
749
  },
@@ -753,8 +753,8 @@ var package_default = {
753
753
  },
754
754
  dependencies: {
755
755
  "circuit-json-to-connectivity-map": "^0.0.22",
756
- "circuit-json-to-gltf": "^0.0.19",
757
- "circuit-to-svg": "^0.0.248",
756
+ "circuit-json-to-gltf": "^0.0.62",
757
+ "circuit-to-svg": "^0.0.327",
758
758
  "schematic-symbols": "^0.0.202"
759
759
  }
760
760
  };
@@ -844,7 +844,23 @@ async function circuitJsonToStep(circuitJson, options = {}) {
844
844
  let bottomVertices;
845
845
  let topVertices;
846
846
  if (outline && Array.isArray(outline) && outline.length >= 3) {
847
- bottomVertices = outline.map(
847
+ const cleanedOutline = [];
848
+ for (let i = 0; i < outline.length; i++) {
849
+ const current = outline[i];
850
+ const next = outline[(i + 1) % outline.length];
851
+ const dx = next.x - current.x;
852
+ const dy = next.y - current.y;
853
+ const dist = Math.sqrt(dx * dx + dy * dy);
854
+ if (dist > 1e-6) {
855
+ cleanedOutline.push(current);
856
+ }
857
+ }
858
+ if (cleanedOutline.length < 3) {
859
+ throw new Error(
860
+ `Outline has too few unique vertices after removing duplicates (${cleanedOutline.length}). Need at least 3.`
861
+ );
862
+ }
863
+ bottomVertices = cleanedOutline.map(
848
864
  (point) => repo.add(
849
865
  new VertexPoint2(
850
866
  "",
@@ -852,7 +868,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
852
868
  )
853
869
  )
854
870
  );
855
- topVertices = outline.map(
871
+ topVertices = cleanedOutline.map(
856
872
  (point) => repo.add(
857
873
  new VertexPoint2(
858
874
  "",
@@ -884,10 +900,20 @@ async function circuitJsonToStep(circuitJson, options = {}) {
884
900
  function createEdge(v1, v2) {
885
901
  const p1 = v1.resolve(repo).pnt.resolve(repo);
886
902
  const p2 = v2.resolve(repo).pnt.resolve(repo);
903
+ const dx = p2.x - p1.x;
904
+ const dy = p2.y - p1.y;
905
+ const dz = p2.z - p1.z;
906
+ const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
907
+ if (length < 1e-10) {
908
+ const dir2 = repo.add(new Direction3("", 1, 0, 0));
909
+ const vec2 = repo.add(new Vector2("", dir2, 1e-10));
910
+ const line2 = repo.add(new Line2("", v1.resolve(repo).pnt, vec2));
911
+ return repo.add(new EdgeCurve2("", v1, v2, line2, true));
912
+ }
887
913
  const dir = repo.add(
888
- new Direction3("", p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
914
+ new Direction3("", dx / length, dy / length, dz / length)
889
915
  );
890
- const vec = repo.add(new Vector2("", dir, 1));
916
+ const vec = repo.add(new Vector2("", dir, length));
891
917
  const line = repo.add(new Line2("", v1.resolve(repo).pnt, vec));
892
918
  return repo.add(new EdgeCurve2("", v1, v2, line, true));
893
919
  }
package/lib/index.ts CHANGED
@@ -168,7 +168,27 @@ export async function circuitJsonToStep(
168
168
 
169
169
  if (outline && Array.isArray(outline) && outline.length >= 3) {
170
170
  // Use custom outline (points are already relative to board center)
171
- bottomVertices = outline.map((point) =>
171
+ // Filter out duplicate consecutive vertices (including first/last wrap-around)
172
+ const cleanedOutline: { x: number; y: number }[] = []
173
+ for (let i = 0; i < outline.length; i++) {
174
+ const current = outline[i]!
175
+ const next = outline[(i + 1) % outline.length]!
176
+ const dx = next.x - current.x
177
+ const dy = next.y - current.y
178
+ const dist = Math.sqrt(dx * dx + dy * dy)
179
+ // Only add vertex if it's not a duplicate of the next one
180
+ if (dist > 1e-6) {
181
+ cleanedOutline.push(current)
182
+ }
183
+ }
184
+
185
+ if (cleanedOutline.length < 3) {
186
+ throw new Error(
187
+ `Outline has too few unique vertices after removing duplicates (${cleanedOutline.length}). Need at least 3.`,
188
+ )
189
+ }
190
+
191
+ bottomVertices = cleanedOutline.map((point) =>
172
192
  repo.add(
173
193
  new VertexPoint(
174
194
  "",
@@ -176,7 +196,7 @@ export async function circuitJsonToStep(
176
196
  ),
177
197
  ),
178
198
  )
179
- topVertices = outline.map((point) =>
199
+ topVertices = cleanedOutline.map((point) =>
180
200
  repo.add(
181
201
  new VertexPoint(
182
202
  "",
@@ -214,10 +234,26 @@ export async function circuitJsonToStep(
214
234
  ): Ref<EdgeCurve> {
215
235
  const p1 = v1.resolve(repo).pnt.resolve(repo)
216
236
  const p2 = v2.resolve(repo).pnt.resolve(repo)
237
+ const dx = p2.x - p1.x
238
+ const dy = p2.y - p1.y
239
+ const dz = p2.z - p1.z
240
+ const length = Math.sqrt(dx * dx + dy * dy + dz * dz)
241
+
242
+ // Handle zero-length edges (duplicate vertices)
243
+ if (length < 1e-10) {
244
+ // Use arbitrary direction for degenerate edge
245
+ const dir = repo.add(new Direction("", 1, 0, 0))
246
+ const vec = repo.add(new Vector("", dir, 1e-10))
247
+ const line = repo.add(new Line("", v1.resolve(repo).pnt, vec))
248
+ return repo.add(new EdgeCurve("", v1, v2, line, true))
249
+ }
250
+
251
+ // Direction must be normalized (unit vector)
217
252
  const dir = repo.add(
218
- new Direction("", p2.x - p1.x, p2.y - p1.y, p2.z - p1.z),
253
+ new Direction("", dx / length, dy / length, dz / length),
219
254
  )
220
- const vec = repo.add(new Vector("", dir, 1))
255
+ // Vector magnitude is the actual edge length
256
+ const vec = repo.add(new Vector("", dir, length))
221
257
  const line = repo.add(new Line("", v1.resolve(repo).pnt, vec))
222
258
  return repo.add(new EdgeCurve("", v1, v2, line, true))
223
259
  }
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.13",
4
+ "version": "0.0.15",
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\"' {} \\;",
@@ -22,7 +22,7 @@
22
22
  "circuit-json": "^0.0.286",
23
23
  "looks-same": "^10.0.1",
24
24
  "occt-import-js": "^0.0.23",
25
- "poppygl": "^0.0.13",
25
+ "poppygl": "^0.0.17",
26
26
  "stepts": "^0.0.2",
27
27
  "tsup": "^8.5.0"
28
28
  },
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "circuit-json-to-connectivity-map": "^0.0.22",
35
- "circuit-json-to-gltf": "^0.0.19",
36
- "circuit-to-svg": "^0.0.248",
35
+ "circuit-json-to-gltf": "^0.0.62",
36
+ "circuit-to-svg": "^0.0.327",
37
37
  "schematic-symbols": "^0.0.202"
38
38
  }
39
39
  }
@@ -0,0 +1,816 @@
1
+ [
2
+ {
3
+ "type": "source_project_metadata",
4
+ "source_project_metadata_id": "source_project_metadata_0",
5
+ "software_used_string": "@tscircuit/core@0.0.1010"
6
+ },
7
+ {
8
+ "type": "source_group",
9
+ "source_group_id": "source_group_0",
10
+ "is_subcircuit": true,
11
+ "was_automatically_named": true,
12
+ "subcircuit_id": "subcircuit_source_group_0"
13
+ },
14
+ {
15
+ "type": "source_component",
16
+ "source_component_id": "source_component_0",
17
+ "ftype": "simple_chip",
18
+ "name": "MP1",
19
+ "supplier_part_numbers": {},
20
+ "source_group_id": "source_group_0"
21
+ },
22
+ {
23
+ "type": "source_component",
24
+ "source_component_id": "source_component_1",
25
+ "ftype": "simple_chip",
26
+ "name": "MP2",
27
+ "supplier_part_numbers": {},
28
+ "source_group_id": "source_group_0"
29
+ },
30
+
31
+ {
32
+ "type": "source_component",
33
+ "source_component_id": "source_component_2",
34
+ "ftype": "simple_chip",
35
+ "name": "MP3",
36
+ "supplier_part_numbers": {},
37
+ "source_group_id": "source_group_0"
38
+ },
39
+ {
40
+ "type": "source_component",
41
+ "source_component_id": "source_component_3",
42
+ "ftype": "simple_chip",
43
+ "name": "MP4",
44
+ "supplier_part_numbers": {},
45
+ "source_group_id": "source_group_0"
46
+ },
47
+ {
48
+ "type": "source_component",
49
+ "source_component_id": "source_component_4",
50
+ "ftype": "simple_chip",
51
+ "name": "Xpattern1",
52
+ "supplier_part_numbers": {},
53
+ "source_group_id": "source_group_0"
54
+ },
55
+ {
56
+ "type": "source_component",
57
+ "source_component_id": "source_component_5",
58
+ "ftype": "simple_chip",
59
+ "name": "Xpattern2",
60
+ "supplier_part_numbers": {},
61
+ "source_group_id": "source_group_0"
62
+ },
63
+ {
64
+ "type": "source_component",
65
+ "source_component_id": "source_component_6",
66
+ "ftype": "simple_chip",
67
+ "name": "Xpattern3",
68
+ "supplier_part_numbers": {},
69
+ "source_group_id": "source_group_0"
70
+ },
71
+ {
72
+ "type": "source_component",
73
+ "source_component_id": "source_component_7",
74
+ "ftype": "simple_chip",
75
+ "name": "Xpattern4",
76
+ "supplier_part_numbers": {},
77
+ "source_group_id": "source_group_0"
78
+ },
79
+ {
80
+ "type": "source_board",
81
+ "source_board_id": "source_board_0",
82
+ "source_group_id": "source_group_0"
83
+ },
84
+ {
85
+ "type": "pcb_board",
86
+ "pcb_board_id": "pcb_board_0",
87
+ "source_board_id": "source_board_0",
88
+ "center": {
89
+ "x": 0,
90
+ "y": 0
91
+ },
92
+ "thickness": 1.4,
93
+ "num_layers": 2,
94
+ "width": 63,
95
+ "height": 63,
96
+ "outline": [
97
+ {
98
+ "x": -30.5,
99
+ "y": -31.5
100
+ },
101
+ {
102
+ "x": 30.5,
103
+ "y": -31.5
104
+ },
105
+ {
106
+ "x": 30.59801714032956,
107
+ "y": -31.495184726672196
108
+ },
109
+ {
110
+ "x": 30.695090322016128,
111
+ "y": -31.480785280403232
112
+ },
113
+ {
114
+ "x": 30.790284677254462,
115
+ "y": -31.45694033573221
116
+ },
117
+ {
118
+ "x": 30.88268343236509,
119
+ "y": -31.423879532511286
120
+ },
121
+ {
122
+ "x": 30.971396736825998,
123
+ "y": -31.381921264348357
124
+ },
125
+ {
126
+ "x": 31.0555702330196,
127
+ "y": -31.331469612302545
128
+ },
129
+ {
130
+ "x": 31.134393284163647,
131
+ "y": -31.273010453362737
132
+ },
133
+ {
134
+ "x": 31.207106781186546,
135
+ "y": -31.207106781186546
136
+ },
137
+ {
138
+ "x": 31.273010453362737,
139
+ "y": -31.134393284163647
140
+ },
141
+ {
142
+ "x": 31.331469612302545,
143
+ "y": -31.0555702330196
144
+ },
145
+ {
146
+ "x": 31.381921264348357,
147
+ "y": -30.971396736825998
148
+ },
149
+ {
150
+ "x": 31.423879532511286,
151
+ "y": -30.88268343236509
152
+ },
153
+ {
154
+ "x": 31.45694033573221,
155
+ "y": -30.790284677254462
156
+ },
157
+ {
158
+ "x": 31.480785280403232,
159
+ "y": -30.695090322016128
160
+ },
161
+ {
162
+ "x": 31.495184726672196,
163
+ "y": -30.59801714032956
164
+ },
165
+ {
166
+ "x": 31.5,
167
+ "y": -30.5
168
+ },
169
+ {
170
+ "x": 31.5,
171
+ "y": 30.5
172
+ },
173
+ {
174
+ "x": 31.495184726672196,
175
+ "y": 30.59801714032956
176
+ },
177
+ {
178
+ "x": 31.480785280403232,
179
+ "y": 30.695090322016128
180
+ },
181
+ {
182
+ "x": 31.45694033573221,
183
+ "y": 30.790284677254462
184
+ },
185
+ {
186
+ "x": 31.423879532511286,
187
+ "y": 30.88268343236509
188
+ },
189
+ {
190
+ "x": 31.381921264348357,
191
+ "y": 30.971396736825998
192
+ },
193
+ {
194
+ "x": 31.331469612302545,
195
+ "y": 31.0555702330196
196
+ },
197
+ {
198
+ "x": 31.273010453362737,
199
+ "y": 31.134393284163647
200
+ },
201
+ {
202
+ "x": 31.207106781186546,
203
+ "y": 31.207106781186546
204
+ },
205
+ {
206
+ "x": 31.134393284163647,
207
+ "y": 31.273010453362737
208
+ },
209
+ {
210
+ "x": 31.0555702330196,
211
+ "y": 31.331469612302545
212
+ },
213
+ {
214
+ "x": 30.971396736825998,
215
+ "y": 31.381921264348357
216
+ },
217
+ {
218
+ "x": 30.88268343236509,
219
+ "y": 31.423879532511286
220
+ },
221
+ {
222
+ "x": 30.790284677254462,
223
+ "y": 31.45694033573221
224
+ },
225
+ {
226
+ "x": 30.695090322016128,
227
+ "y": 31.480785280403232
228
+ },
229
+ {
230
+ "x": 30.59801714032956,
231
+ "y": 31.495184726672196
232
+ },
233
+ {
234
+ "x": 30.5,
235
+ "y": 31.5
236
+ },
237
+ {
238
+ "x": -30.5,
239
+ "y": 31.5
240
+ },
241
+ {
242
+ "x": -30.59801714032956,
243
+ "y": 31.495184726672196
244
+ },
245
+ {
246
+ "x": -30.695090322016128,
247
+ "y": 31.480785280403232
248
+ },
249
+ {
250
+ "x": -30.790284677254462,
251
+ "y": 31.45694033573221
252
+ },
253
+ {
254
+ "x": -30.88268343236509,
255
+ "y": 31.423879532511286
256
+ },
257
+ {
258
+ "x": -30.971396736825998,
259
+ "y": 31.381921264348357
260
+ },
261
+ {
262
+ "x": -31.0555702330196,
263
+ "y": 31.331469612302545
264
+ },
265
+ {
266
+ "x": -31.134393284163647,
267
+ "y": 31.273010453362737
268
+ },
269
+ {
270
+ "x": -31.207106781186546,
271
+ "y": 31.207106781186546
272
+ },
273
+ {
274
+ "x": -31.273010453362737,
275
+ "y": 31.134393284163647
276
+ },
277
+ {
278
+ "x": -31.331469612302545,
279
+ "y": 31.0555702330196
280
+ },
281
+ {
282
+ "x": -31.381921264348357,
283
+ "y": 30.971396736825998
284
+ },
285
+ {
286
+ "x": -31.423879532511286,
287
+ "y": 30.88268343236509
288
+ },
289
+ {
290
+ "x": -31.45694033573221,
291
+ "y": 30.790284677254462
292
+ },
293
+ {
294
+ "x": -31.480785280403232,
295
+ "y": 30.695090322016128
296
+ },
297
+ {
298
+ "x": -31.495184726672196,
299
+ "y": 30.59801714032956
300
+ },
301
+ {
302
+ "x": -31.5,
303
+ "y": 30.5
304
+ },
305
+ {
306
+ "x": -31.5,
307
+ "y": -30.5
308
+ },
309
+ {
310
+ "x": -31.495184726672196,
311
+ "y": -30.59801714032956
312
+ },
313
+ {
314
+ "x": -31.480785280403232,
315
+ "y": -30.695090322016128
316
+ },
317
+ {
318
+ "x": -31.45694033573221,
319
+ "y": -30.790284677254462
320
+ },
321
+ {
322
+ "x": -31.423879532511286,
323
+ "y": -30.88268343236509
324
+ },
325
+ {
326
+ "x": -31.381921264348357,
327
+ "y": -30.971396736825998
328
+ },
329
+ {
330
+ "x": -31.331469612302545,
331
+ "y": -31.0555702330196
332
+ },
333
+ {
334
+ "x": -31.273010453362737,
335
+ "y": -31.134393284163647
336
+ },
337
+ {
338
+ "x": -31.207106781186546,
339
+ "y": -31.207106781186546
340
+ },
341
+ {
342
+ "x": -31.134393284163647,
343
+ "y": -31.273010453362737
344
+ },
345
+ {
346
+ "x": -31.0555702330196,
347
+ "y": -31.331469612302545
348
+ },
349
+ {
350
+ "x": -30.971396736825998,
351
+ "y": -31.381921264348357
352
+ },
353
+ {
354
+ "x": -30.88268343236509,
355
+ "y": -31.423879532511286
356
+ },
357
+ {
358
+ "x": -30.790284677254462,
359
+ "y": -31.45694033573221
360
+ },
361
+ {
362
+ "x": -30.695090322016128,
363
+ "y": -31.48078528040323
364
+ },
365
+ {
366
+ "x": -30.59801714032956,
367
+ "y": -31.495184726672196
368
+ },
369
+ {
370
+ "x": -30.5,
371
+ "y": -31.5
372
+ }
373
+ ],
374
+ "material": "fr4"
375
+ },
376
+ {
377
+ "type": "pcb_plated_hole",
378
+ "pcb_plated_hole_id": "pcb_plated_hole_0",
379
+ "outer_diameter": 5.2,
380
+ "hole_diameter": 3.45,
381
+ "shape": "circle",
382
+ "port_hints": ["unnamed_platedhole1", "1"],
383
+ "x": -16,
384
+ "y": -16,
385
+ "layers": ["top", "bottom"],
386
+ "is_covered_with_solder_mask": false,
387
+ "subcircuit_id": "subcircuit_source_group_0"
388
+ },
389
+ {
390
+ "type": "pcb_solder_paste",
391
+ "pcb_solder_paste_id": "pcb_solder_paste_0",
392
+ "layer": "top",
393
+ "shape": "circle",
394
+ "radius": 2.6,
395
+ "x": -16,
396
+ "y": -16,
397
+ "subcircuit_id": "subcircuit_source_group_0"
398
+ },
399
+ {
400
+ "type": "pcb_solder_paste",
401
+ "pcb_solder_paste_id": "pcb_solder_paste_1",
402
+ "layer": "bottom",
403
+ "shape": "circle",
404
+ "radius": 2.6,
405
+ "x": -16,
406
+ "y": -16,
407
+ "subcircuit_id": "subcircuit_source_group_0"
408
+ },
409
+ {
410
+ "type": "pcb_note_text",
411
+ "pcb_note_text_id": "pcb_note_text_0",
412
+ "subcircuit_id": "subcircuit_source_group_0",
413
+ "font": "tscircuit2024",
414
+ "font_size": 0.5,
415
+ "text": "MP1",
416
+ "anchor_position": {
417
+ "x": -16,
418
+ "y": -16
419
+ },
420
+ "anchor_alignment": "center"
421
+ },
422
+ {
423
+ "type": "pcb_note_rect",
424
+ "pcb_note_rect_id": "pcb_note_rect_0",
425
+ "subcircuit_id": "subcircuit_source_group_0",
426
+ "center": {
427
+ "x": -16,
428
+ "y": -16
429
+ },
430
+ "width": 5.95,
431
+ "height": 5.95,
432
+ "stroke_width": 0.05,
433
+ "is_filled": false,
434
+ "has_stroke": true,
435
+ "is_stroke_dashed": false,
436
+ "color": "#ffd700"
437
+ },
438
+ {
439
+ "type": "pcb_plated_hole",
440
+ "pcb_plated_hole_id": "pcb_plated_hole_1",
441
+ "outer_diameter": 5.2,
442
+ "hole_diameter": 3.45,
443
+ "shape": "circle",
444
+ "port_hints": ["unnamed_platedhole2", "1"],
445
+ "x": -16,
446
+ "y": 16,
447
+ "layers": ["top", "bottom"],
448
+ "is_covered_with_solder_mask": false,
449
+ "subcircuit_id": "subcircuit_source_group_0"
450
+ },
451
+ {
452
+ "type": "pcb_solder_paste",
453
+ "pcb_solder_paste_id": "pcb_solder_paste_2",
454
+ "layer": "top",
455
+ "shape": "circle",
456
+ "radius": 2.6,
457
+ "x": -16,
458
+ "y": 16,
459
+ "subcircuit_id": "subcircuit_source_group_0"
460
+ },
461
+ {
462
+ "type": "pcb_solder_paste",
463
+ "pcb_solder_paste_id": "pcb_solder_paste_3",
464
+ "layer": "bottom",
465
+ "shape": "circle",
466
+ "radius": 2.6,
467
+ "x": -16,
468
+ "y": 16,
469
+ "subcircuit_id": "subcircuit_source_group_0"
470
+ },
471
+ {
472
+ "type": "pcb_note_text",
473
+ "pcb_note_text_id": "pcb_note_text_1",
474
+ "subcircuit_id": "subcircuit_source_group_0",
475
+ "font": "tscircuit2024",
476
+ "font_size": 0.5,
477
+ "text": "MP2",
478
+ "anchor_position": {
479
+ "x": -16,
480
+ "y": 16
481
+ },
482
+ "anchor_alignment": "center"
483
+ },
484
+ {
485
+ "type": "pcb_note_rect",
486
+ "pcb_note_rect_id": "pcb_note_rect_1",
487
+ "subcircuit_id": "subcircuit_source_group_0",
488
+ "center": {
489
+ "x": -16,
490
+ "y": 16
491
+ },
492
+ "width": 5.95,
493
+ "height": 5.95,
494
+ "stroke_width": 0.05,
495
+ "is_filled": false,
496
+ "has_stroke": true,
497
+ "is_stroke_dashed": false,
498
+ "color": "#ffd700"
499
+ },
500
+ {
501
+ "type": "pcb_plated_hole",
502
+ "pcb_plated_hole_id": "pcb_plated_hole_2",
503
+ "outer_diameter": 5.2,
504
+ "hole_diameter": 3.45,
505
+ "shape": "circle",
506
+ "port_hints": ["unnamed_platedhole3", "1"],
507
+ "x": 16,
508
+ "y": 16,
509
+ "layers": ["top", "bottom"],
510
+ "is_covered_with_solder_mask": false,
511
+ "subcircuit_id": "subcircuit_source_group_0"
512
+ },
513
+ {
514
+ "type": "pcb_solder_paste",
515
+ "pcb_solder_paste_id": "pcb_solder_paste_4",
516
+ "layer": "top",
517
+ "shape": "circle",
518
+ "radius": 2.6,
519
+ "x": 16,
520
+ "y": 16,
521
+ "subcircuit_id": "subcircuit_source_group_0"
522
+ },
523
+ {
524
+ "type": "pcb_solder_paste",
525
+ "pcb_solder_paste_id": "pcb_solder_paste_5",
526
+ "layer": "bottom",
527
+ "shape": "circle",
528
+ "radius": 2.6,
529
+ "x": 16,
530
+ "y": 16,
531
+ "subcircuit_id": "subcircuit_source_group_0"
532
+ },
533
+ {
534
+ "type": "pcb_note_text",
535
+ "pcb_note_text_id": "pcb_note_text_2",
536
+ "subcircuit_id": "subcircuit_source_group_0",
537
+ "font": "tscircuit2024",
538
+ "font_size": 0.5,
539
+ "text": "MP3",
540
+ "anchor_position": {
541
+ "x": 16,
542
+ "y": 16
543
+ },
544
+ "anchor_alignment": "center"
545
+ },
546
+ {
547
+ "type": "pcb_note_rect",
548
+ "pcb_note_rect_id": "pcb_note_rect_2",
549
+ "subcircuit_id": "subcircuit_source_group_0",
550
+ "center": {
551
+ "x": 16,
552
+ "y": 16
553
+ },
554
+ "width": 5.95,
555
+ "height": 5.95,
556
+ "stroke_width": 0.05,
557
+ "is_filled": false,
558
+ "has_stroke": true,
559
+ "is_stroke_dashed": false,
560
+ "color": "#ffd700"
561
+ },
562
+ {
563
+ "type": "pcb_plated_hole",
564
+ "pcb_plated_hole_id": "pcb_plated_hole_3",
565
+ "outer_diameter": 5.2,
566
+ "hole_diameter": 3.45,
567
+ "shape": "circle",
568
+ "port_hints": ["unnamed_platedhole4", "1"],
569
+ "x": 16,
570
+ "y": -16,
571
+ "layers": ["top", "bottom"],
572
+ "is_covered_with_solder_mask": false,
573
+ "subcircuit_id": "subcircuit_source_group_0"
574
+ },
575
+ {
576
+ "type": "pcb_solder_paste",
577
+ "pcb_solder_paste_id": "pcb_solder_paste_6",
578
+ "layer": "top",
579
+ "shape": "circle",
580
+ "radius": 2.6,
581
+ "x": 16,
582
+ "y": -16,
583
+ "subcircuit_id": "subcircuit_source_group_0"
584
+ },
585
+ {
586
+ "type": "pcb_solder_paste",
587
+ "pcb_solder_paste_id": "pcb_solder_paste_7",
588
+ "layer": "bottom",
589
+ "shape": "circle",
590
+ "radius": 2.6,
591
+ "x": 16,
592
+ "y": -16,
593
+ "subcircuit_id": "subcircuit_source_group_0"
594
+ },
595
+ {
596
+ "type": "pcb_note_text",
597
+ "pcb_note_text_id": "pcb_note_text_3",
598
+ "subcircuit_id": "subcircuit_source_group_0",
599
+ "font": "tscircuit2024",
600
+ "font_size": 0.5,
601
+ "text": "MP4",
602
+ "anchor_position": {
603
+ "x": 16,
604
+ "y": -16
605
+ },
606
+ "anchor_alignment": "center"
607
+ },
608
+ {
609
+ "type": "pcb_note_rect",
610
+ "pcb_note_rect_id": "pcb_note_rect_3",
611
+ "subcircuit_id": "subcircuit_source_group_0",
612
+ "center": {
613
+ "x": 16,
614
+ "y": -16
615
+ },
616
+ "width": 5.95,
617
+ "height": 5.95,
618
+ "stroke_width": 0.05,
619
+ "is_filled": false,
620
+ "has_stroke": true,
621
+ "is_stroke_dashed": false,
622
+ "color": "#ffd700"
623
+ },
624
+ {
625
+ "type": "pcb_hole",
626
+ "pcb_hole_id": "pcb_hole_0",
627
+ "hole_shape": "rotated_pill",
628
+ "hole_width": 8,
629
+ "hole_height": 4,
630
+ "x": -8,
631
+ "y": -8,
632
+ "ccw_rotation": 45,
633
+ "is_covered_with_solder_mask": false,
634
+ "subcircuit_id": "subcircuit_source_group_0"
635
+ },
636
+ {
637
+ "type": "pcb_hole",
638
+ "pcb_hole_id": "pcb_hole_1",
639
+ "hole_shape": "rotated_pill",
640
+ "hole_width": 8,
641
+ "hole_height": 4,
642
+ "x": -24,
643
+ "y": -8,
644
+ "ccw_rotation": -45,
645
+ "is_covered_with_solder_mask": false,
646
+ "subcircuit_id": "subcircuit_source_group_0"
647
+ },
648
+ {
649
+ "type": "pcb_hole",
650
+ "pcb_hole_id": "pcb_hole_2",
651
+ "hole_shape": "rotated_pill",
652
+ "hole_width": 8,
653
+ "hole_height": 4,
654
+ "x": -24,
655
+ "y": -24,
656
+ "ccw_rotation": 45,
657
+ "is_covered_with_solder_mask": false,
658
+ "subcircuit_id": "subcircuit_source_group_0"
659
+ },
660
+ {
661
+ "type": "pcb_hole",
662
+ "pcb_hole_id": "pcb_hole_3",
663
+ "hole_shape": "rotated_pill",
664
+ "hole_width": 8,
665
+ "hole_height": 4,
666
+ "x": -8,
667
+ "y": -24,
668
+ "ccw_rotation": -45,
669
+ "is_covered_with_solder_mask": false,
670
+ "subcircuit_id": "subcircuit_source_group_0"
671
+ },
672
+ {
673
+ "type": "pcb_hole",
674
+ "pcb_hole_id": "pcb_hole_4",
675
+ "hole_shape": "rotated_pill",
676
+ "hole_width": 8,
677
+ "hole_height": 4,
678
+ "x": 24,
679
+ "y": -8,
680
+ "ccw_rotation": 45,
681
+ "is_covered_with_solder_mask": false,
682
+ "subcircuit_id": "subcircuit_source_group_0"
683
+ },
684
+ {
685
+ "type": "pcb_hole",
686
+ "pcb_hole_id": "pcb_hole_5",
687
+ "hole_shape": "rotated_pill",
688
+ "hole_width": 8,
689
+ "hole_height": 4,
690
+ "x": 8,
691
+ "y": -8,
692
+ "ccw_rotation": -45,
693
+ "is_covered_with_solder_mask": false,
694
+ "subcircuit_id": "subcircuit_source_group_0"
695
+ },
696
+ {
697
+ "type": "pcb_hole",
698
+ "pcb_hole_id": "pcb_hole_6",
699
+ "hole_shape": "rotated_pill",
700
+ "hole_width": 8,
701
+ "hole_height": 4,
702
+ "x": 8,
703
+ "y": -24,
704
+ "ccw_rotation": 45,
705
+ "is_covered_with_solder_mask": false,
706
+ "subcircuit_id": "subcircuit_source_group_0"
707
+ },
708
+ {
709
+ "type": "pcb_hole",
710
+ "pcb_hole_id": "pcb_hole_7",
711
+ "hole_shape": "rotated_pill",
712
+ "hole_width": 8,
713
+ "hole_height": 4,
714
+ "x": 24,
715
+ "y": -24,
716
+ "ccw_rotation": -45,
717
+ "is_covered_with_solder_mask": false,
718
+ "subcircuit_id": "subcircuit_source_group_0"
719
+ },
720
+ {
721
+ "type": "pcb_hole",
722
+ "pcb_hole_id": "pcb_hole_8",
723
+ "hole_shape": "rotated_pill",
724
+ "hole_width": 8,
725
+ "hole_height": 4,
726
+ "x": -8,
727
+ "y": 24,
728
+ "ccw_rotation": 45,
729
+ "is_covered_with_solder_mask": false,
730
+ "subcircuit_id": "subcircuit_source_group_0"
731
+ },
732
+ {
733
+ "type": "pcb_hole",
734
+ "pcb_hole_id": "pcb_hole_9",
735
+ "hole_shape": "rotated_pill",
736
+ "hole_width": 8,
737
+ "hole_height": 4,
738
+ "x": -24,
739
+ "y": 24,
740
+ "ccw_rotation": -45,
741
+ "is_covered_with_solder_mask": false,
742
+ "subcircuit_id": "subcircuit_source_group_0"
743
+ },
744
+ {
745
+ "type": "pcb_hole",
746
+ "pcb_hole_id": "pcb_hole_10",
747
+ "hole_shape": "rotated_pill",
748
+ "hole_width": 8,
749
+ "hole_height": 4,
750
+ "x": -24,
751
+ "y": 8,
752
+ "ccw_rotation": 45,
753
+ "is_covered_with_solder_mask": false,
754
+ "subcircuit_id": "subcircuit_source_group_0"
755
+ },
756
+ {
757
+ "type": "pcb_hole",
758
+ "pcb_hole_id": "pcb_hole_11",
759
+ "hole_shape": "rotated_pill",
760
+ "hole_width": 8,
761
+ "hole_height": 4,
762
+ "x": -8,
763
+ "y": 8,
764
+ "ccw_rotation": -45,
765
+ "is_covered_with_solder_mask": false,
766
+ "subcircuit_id": "subcircuit_source_group_0"
767
+ },
768
+ {
769
+ "type": "pcb_hole",
770
+ "pcb_hole_id": "pcb_hole_12",
771
+ "hole_shape": "rotated_pill",
772
+ "hole_width": 8,
773
+ "hole_height": 4,
774
+ "x": 24,
775
+ "y": 24,
776
+ "ccw_rotation": 45,
777
+ "is_covered_with_solder_mask": false,
778
+ "subcircuit_id": "subcircuit_source_group_0"
779
+ },
780
+ {
781
+ "type": "pcb_hole",
782
+ "pcb_hole_id": "pcb_hole_13",
783
+ "hole_shape": "rotated_pill",
784
+ "hole_width": 8,
785
+ "hole_height": 4,
786
+ "x": 8,
787
+ "y": 24,
788
+ "ccw_rotation": -45,
789
+ "is_covered_with_solder_mask": false,
790
+ "subcircuit_id": "subcircuit_source_group_0"
791
+ },
792
+ {
793
+ "type": "pcb_hole",
794
+ "pcb_hole_id": "pcb_hole_14",
795
+ "hole_shape": "rotated_pill",
796
+ "hole_width": 8,
797
+ "hole_height": 4,
798
+ "x": 8,
799
+ "y": 8,
800
+ "ccw_rotation": 45,
801
+ "is_covered_with_solder_mask": false,
802
+ "subcircuit_id": "subcircuit_source_group_0"
803
+ },
804
+ {
805
+ "type": "pcb_hole",
806
+ "pcb_hole_id": "pcb_hole_15",
807
+ "hole_shape": "rotated_pill",
808
+ "hole_width": 8,
809
+ "hole_height": 4,
810
+ "x": 24,
811
+ "y": 8,
812
+ "ccw_rotation": -45,
813
+ "is_covered_with_solder_mask": false,
814
+ "subcircuit_id": "subcircuit_source_group_0"
815
+ }
816
+ ]
@@ -0,0 +1,78 @@
1
+ import { expect, test } from "bun:test"
2
+ import { renderGLTFToPNGBufferFromGLBBuffer } from "poppygl"
3
+ import { circuitJsonToStep } from "../../../lib/index"
4
+ import { importStepWithOcct } from "../../utils/occt/importer"
5
+ import circuitJson from "./repro02.json"
6
+
7
+ test("repro02: convert circuit json with rotated pill holes to STEP", async () => {
8
+ const stepText = await circuitJsonToStep(circuitJson as any, {
9
+ includeComponents: true,
10
+ productName: "Repro02",
11
+ })
12
+
13
+ const gltfModule = "circuit-json-to-gltf"
14
+ const { convertCircuitJsonTo3D, convertSceneToGLTF } = await import(
15
+ /* @vite-ignore */ gltfModule
16
+ )
17
+ const scene3d = await convertCircuitJsonTo3D(circuitJson as any, {
18
+ renderBoardTextures: false,
19
+ showBoundingBoxes: false,
20
+ })
21
+ const glb = await convertSceneToGLTF(scene3d, { binary: true })
22
+ expect(glb).toBeInstanceOf(ArrayBuffer)
23
+ expect((glb as ArrayBuffer).byteLength).toBeGreaterThan(0)
24
+
25
+ const cameraOptions = scene3d.camera
26
+ ? {
27
+ camPos: [
28
+ scene3d.camera.position.x,
29
+ scene3d.camera.position.y,
30
+ scene3d.camera.position.z,
31
+ ] as const,
32
+ lookAt: [
33
+ scene3d.camera.target.x,
34
+ scene3d.camera.target.y,
35
+ scene3d.camera.target.z,
36
+ ] as const,
37
+ fov: scene3d.camera.fov ?? 60,
38
+ }
39
+ : undefined
40
+ const gltfPng = await renderGLTFToPNGBufferFromGLBBuffer(
41
+ glb as ArrayBuffer,
42
+ cameraOptions,
43
+ )
44
+ await expect(gltfPng).toMatchPngSnapshot(import.meta.path, "repro02-gltf")
45
+
46
+ // Verify STEP format
47
+ expect(stepText).toContain("ISO-10303-21")
48
+ expect(stepText).toContain("END-ISO-10303-21")
49
+
50
+ // Verify product structure
51
+ expect(stepText).toContain("Repro02")
52
+ expect(stepText).toContain("MANIFOLD_SOLID_BREP")
53
+
54
+ // Verify holes are created
55
+ expect(stepText).toContain("CIRCLE")
56
+ expect(stepText).toContain("CYLINDRICAL_SURFACE")
57
+
58
+ // Write STEP file to debug-output
59
+ const outputPath = "debug-output/repro02.step"
60
+ await Bun.write(outputPath, stepText)
61
+
62
+ console.log("✓ STEP file generated successfully")
63
+ console.log(` - STEP text length: ${stepText.length} bytes`)
64
+ console.log(` - Output: ${outputPath}`)
65
+
66
+ // Validate STEP file can be imported with occt-import-js
67
+ const occtResult = await importStepWithOcct(stepText)
68
+ expect(occtResult.success).toBe(true)
69
+ expect(occtResult.meshes.length).toBeGreaterThan(0)
70
+
71
+ const [firstMesh] = occtResult.meshes
72
+ expect(firstMesh.attributes.position.array.length).toBeGreaterThan(0)
73
+ expect(firstMesh.index.array.length).toBeGreaterThan(0)
74
+
75
+ console.log("✓ STEP file successfully validated with occt-import-js")
76
+
77
+ await expect(stepText).toMatchStepSnapshot(import.meta.path, "repro02")
78
+ }, 30000)