jscad-electronics 0.0.175 → 0.0.176

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/vanilla.js CHANGED
@@ -193,6 +193,300 @@ var ExtrudedPads = ({
193
193
  ] });
194
194
  };
195
195
 
196
+ // lib/LGA.tsx
197
+ function LGA(p) {
198
+ for (const k of [
199
+ "bodyWidth",
200
+ "bodyLength",
201
+ "bodyHeight",
202
+ "pitch",
203
+ "landWidth",
204
+ "landLength",
205
+ "landThickness"
206
+ ])
207
+ if (!Number.isFinite(p[k]) || p[k] <= 0)
208
+ throw new Error(`${k} must be finite and positive`);
209
+ for (const k of ["landsPerSideX", "landsPerSideY"])
210
+ if (!Number.isInteger(p[k]) || p[k] < 0)
211
+ throw new Error(`${k} must be a nonnegative integer`);
212
+ if (!Number.isFinite(p.edgeInset) || p.edgeInset < 0 || p.landThickness >= p.bodyHeight || p.landWidth >= p.pitch || p.landsPerSideX + p.landsPerSideY < 1)
213
+ throw new Error("Invalid land layout or thickness");
214
+ const layouts = [
215
+ {
216
+ count: p.landsPerSideX,
217
+ across: p.bodyWidth,
218
+ along: p.bodyLength,
219
+ horizontal: true
220
+ },
221
+ {
222
+ count: p.landsPerSideY,
223
+ across: p.bodyLength,
224
+ along: p.bodyWidth,
225
+ horizontal: false
226
+ }
227
+ ];
228
+ for (const l of layouts) {
229
+ if (l.count === 0) continue;
230
+ const row = (l.count - 1) * p.pitch + p.landWidth;
231
+ if (row >= l.along - 2 * p.edgeInset || 2 * (p.edgeInset + p.landLength) >= l.across)
232
+ throw new Error(
233
+ "Lands must fit below the body without opposite-row overlap"
234
+ );
235
+ const other = l.horizontal ? p.landsPerSideY : p.landsPerSideX;
236
+ if (other > 0 && row >= l.along - 2 * (p.edgeInset + p.landLength) && (other - 1) * p.pitch + p.landWidth >= l.across - 2 * (p.edgeInset + p.landLength))
237
+ throw new Error("Adjacent land rows overlap at corners");
238
+ }
239
+ const r = Math.min(p.bodyWidth, p.bodyLength) * 0.035;
240
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
241
+ /* @__PURE__ */ jsx(Colorize, { color: "#353535", children: /* @__PURE__ */ jsxs(Subtract, { children: [
242
+ /* @__PURE__ */ jsx(Translate, { z: (p.bodyHeight + p.landThickness / 2) / 2, children: /* @__PURE__ */ jsx(
243
+ Cuboid,
244
+ {
245
+ size: [
246
+ p.bodyWidth,
247
+ p.bodyLength,
248
+ p.bodyHeight - p.landThickness / 2
249
+ ]
250
+ }
251
+ ) }),
252
+ /* @__PURE__ */ jsx(
253
+ Translate,
254
+ {
255
+ offset: [
256
+ -p.bodyWidth / 2 + 2 * r,
257
+ p.bodyLength / 2 - 2 * r,
258
+ p.bodyHeight
259
+ ],
260
+ children: /* @__PURE__ */ jsx(Cylinder, { radius: r, height: Math.min(0.04, p.bodyHeight / 10) })
261
+ }
262
+ )
263
+ ] }) }),
264
+ /* @__PURE__ */ jsx(Colorize, { color: "#c5b987", children: layouts.flatMap(
265
+ (l) => [-1, 1].flatMap(
266
+ (sign) => Array.from({ length: l.count }, (_, i) => {
267
+ const across = sign * (l.across / 2 - p.edgeInset - p.landLength / 2), along = ((l.count - 1) / 2 - i) * p.pitch;
268
+ return /* @__PURE__ */ jsx(
269
+ Translate,
270
+ {
271
+ offset: [
272
+ l.horizontal ? across : along,
273
+ l.horizontal ? along : across,
274
+ p.landThickness / 2
275
+ ],
276
+ children: /* @__PURE__ */ jsx(
277
+ Cuboid,
278
+ {
279
+ size: l.horizontal ? [p.landLength, p.landWidth, p.landThickness] : [p.landWidth, p.landLength, p.landThickness]
280
+ }
281
+ )
282
+ },
283
+ `${l.horizontal}-${sign}-${i}`
284
+ );
285
+ })
286
+ )
287
+ ) })
288
+ ] });
289
+ }
290
+
291
+ // lib/utils/DualRowGullWing.tsx
292
+ function createDualRowGullWing(p) {
293
+ const {
294
+ pinCount,
295
+ pitch,
296
+ bodyWidth,
297
+ bodyLength: bodyLength10,
298
+ bodyHeight,
299
+ standoff,
300
+ leadSpan,
301
+ leadWidth,
302
+ leadThickness,
303
+ contactLength,
304
+ taperInset,
305
+ exposedPadWidth = 0,
306
+ exposedPadLength = 0
307
+ } = p;
308
+ for (const key of [
309
+ "pitch",
310
+ "bodyWidth",
311
+ "bodyLength",
312
+ "bodyHeight",
313
+ "leadSpan",
314
+ "leadWidth",
315
+ "leadThickness",
316
+ "contactLength"
317
+ ])
318
+ if (!Number.isFinite(p[key]) || p[key] <= 0)
319
+ throw new Error(`${key} must be finite and positive`);
320
+ for (const [name, value] of Object.entries({
321
+ standoff,
322
+ taperInset,
323
+ exposedPadWidth,
324
+ exposedPadLength
325
+ }))
326
+ if (!Number.isFinite(value) || value < 0)
327
+ throw new Error(`${name} must be finite and nonnegative`);
328
+ if (!Number.isInteger(pinCount) || pinCount < 4 || pinCount % 2)
329
+ throw new Error("pinCount must be even and at least 4");
330
+ const moldHeight = bodyHeight - standoff;
331
+ const reach = (leadSpan - bodyWidth) / 2;
332
+ const rowLength = (pinCount / 2 - 1) * pitch + leadWidth;
333
+ if (moldHeight <= leadThickness * 2 || leadWidth >= pitch || rowLength >= bodyLength10 - 2 * taperInset || taperInset * 2 >= Math.min(bodyWidth, bodyLength10) || contactLength >= reach)
334
+ throw new Error("Invalid mold, lead spacing, or terminal reach");
335
+ if (exposedPadWidth === 0 !== (exposedPadLength === 0) || exposedPadWidth >= bodyWidth - 2 * taperInset || exposedPadLength >= bodyLength10 - 2 * taperInset)
336
+ throw new Error("Exposed pad must fit inside the bottom mold face");
337
+ const midZ = standoff + moldHeight / 2;
338
+ const slice = Math.min(2e-3, moldHeight / 100);
339
+ const body = /* @__PURE__ */ jsxs(Hull, { children: [
340
+ /* @__PURE__ */ jsx(Translate, { z: standoff + slice / 2, children: /* @__PURE__ */ jsx(
341
+ Cuboid,
342
+ {
343
+ size: [
344
+ bodyWidth - 2 * taperInset,
345
+ bodyLength10 - 2 * taperInset,
346
+ slice
347
+ ]
348
+ }
349
+ ) }),
350
+ /* @__PURE__ */ jsx(Translate, { z: midZ, children: /* @__PURE__ */ jsx(Cuboid, { size: [bodyWidth, bodyLength10, slice] }) }),
351
+ /* @__PURE__ */ jsx(Translate, { z: bodyHeight - slice / 2, children: /* @__PURE__ */ jsx(
352
+ Cuboid,
353
+ {
354
+ size: [
355
+ bodyWidth - 2 * taperInset,
356
+ bodyLength10 - 2 * taperInset,
357
+ slice
358
+ ]
359
+ }
360
+ ) })
361
+ ] });
362
+ const dimpleRadius = Math.min(bodyWidth, bodyLength10) * 0.045;
363
+ const dimpleDepth = Math.min(0.04, moldHeight / 10);
364
+ const lead = (side, y) => {
365
+ const outer = leadSpan / 2;
366
+ const root = bodyWidth / 2 - Math.min(bodyWidth / 8, 0.15);
367
+ const bendStart = outer - contactLength;
368
+ const bendEnd = bodyWidth / 2 + Math.min(0.08, (reach - contactLength) / 4);
369
+ const points = [
370
+ [outer - slice / 2, leadThickness / 2],
371
+ [bendStart, leadThickness / 2]
372
+ ];
373
+ for (let i = 1; i <= 12; i++) {
374
+ const t = i / 12;
375
+ points.push([
376
+ bendStart + (bendEnd - bendStart) * t,
377
+ leadThickness / 2 + (midZ - leadThickness / 2) * t * t * (3 - 2 * t)
378
+ ]);
379
+ }
380
+ points.push([root, midZ]);
381
+ const section = ([x, z]) => /* @__PURE__ */ jsx(Translate, { offset: { x: side * x, y, z }, children: /* @__PURE__ */ jsx(Cuboid, { size: [slice, leadWidth, leadThickness] }) });
382
+ return /* @__PURE__ */ jsx(Union, { children: points.slice(1).map((point, i) => /* @__PURE__ */ jsxs(Hull, { children: [
383
+ section(points[i]),
384
+ section(point)
385
+ ] }, i)) });
386
+ };
387
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
388
+ /* @__PURE__ */ jsx(Colorize, { color: "#353535", children: /* @__PURE__ */ jsxs(Subtract, { children: [
389
+ body,
390
+ /* @__PURE__ */ jsx(
391
+ Translate,
392
+ {
393
+ offset: {
394
+ x: -bodyWidth / 2 + taperInset + dimpleRadius * 2,
395
+ y: bodyLength10 / 2 - taperInset - dimpleRadius * 2,
396
+ z: bodyHeight
397
+ },
398
+ children: /* @__PURE__ */ jsx(Cylinder, { radius: dimpleRadius, height: dimpleDepth * 2 })
399
+ }
400
+ )
401
+ ] }) }),
402
+ /* @__PURE__ */ jsx(Colorize, { color: "#bfc1c4", children: Array.from(
403
+ { length: pinCount },
404
+ (_, i) => lead(
405
+ i < pinCount / 2 ? -1 : 1,
406
+ ((pinCount / 2 - 1) / 2 - i % (pinCount / 2)) * pitch
407
+ )
408
+ ) }),
409
+ exposedPadWidth > 0 && /* @__PURE__ */ jsx(Colorize, { color: "#bfc1c4", children: /* @__PURE__ */ jsx(Translate, { z: (standoff + leadThickness) / 2, children: /* @__PURE__ */ jsx(
410
+ Cuboid,
411
+ {
412
+ size: [
413
+ exposedPadWidth,
414
+ exposedPadLength,
415
+ standoff + leadThickness
416
+ ]
417
+ }
418
+ ) }) })
419
+ ] });
420
+ }
421
+
422
+ // lib/SSOP.tsx
423
+ var SSOP = (props) => createDualRowGullWing(props);
424
+
425
+ // lib/utils/FootprinterBodyModels.tsx
426
+ function renderFootprinterBodyModel(p) {
427
+ if (p.bodywidth === void 0 && p.bodyheight === void 0 && p.bodythickness === void 0)
428
+ return null;
429
+ if (p.fn === "ssop") {
430
+ const bodyWidth = p.bodywidth ?? p.w * 0.55;
431
+ const bodyLength10 = p.bodyheight ?? (p.num_pins / 2 - 1) * p.p + p.pw + 0.4;
432
+ const thickness = p.bodythickness ?? 1;
433
+ const leadWidth = Math.min(p.pw, p.p * 0.5);
434
+ const rowLength = (p.num_pins / 2 - 1) * p.p + leadWidth;
435
+ if (bodyLength10 <= rowLength)
436
+ throw new Error("SSOP bodyheight must contain the lead row");
437
+ const leadSpan = Math.max(p.w + 0.2 + p.pl / 2, bodyWidth + p.p);
438
+ const standoff = Math.min(0.1, thickness / 10);
439
+ return /* @__PURE__ */ jsx(
440
+ SSOP,
441
+ {
442
+ pinCount: p.num_pins,
443
+ pitch: p.p,
444
+ bodyWidth,
445
+ bodyLength: bodyLength10,
446
+ bodyHeight: thickness + standoff,
447
+ standoff,
448
+ leadSpan,
449
+ leadWidth,
450
+ leadThickness: Math.min(0.15, thickness / 4),
451
+ contactLength: Math.min(p.pl / 2, (leadSpan - bodyWidth) / 4),
452
+ taperInset: Math.min(0.12, (bodyLength10 - rowLength) / 4, bodyWidth / 8)
453
+ }
454
+ );
455
+ }
456
+ if (p.fn === "lga") {
457
+ const bodyWidth = p.bodywidth ?? p.w;
458
+ const bodyLength10 = p.bodyheight ?? p.h;
459
+ const thickness = p.bodythickness ?? 1;
460
+ const grid = p.grid;
461
+ const landWidth = Math.min(p.pw, p.p / 2);
462
+ const roomX = bodyWidth - ((grid.y - 1) * p.p + landWidth);
463
+ const roomY = bodyLength10 - ((grid.x - 1) * p.p + landWidth);
464
+ const landLength = Math.min(
465
+ p.pl / 2,
466
+ bodyWidth / 8,
467
+ bodyLength10 / 8,
468
+ roomX / 4,
469
+ roomY / 4
470
+ );
471
+ return /* @__PURE__ */ jsx(
472
+ LGA,
473
+ {
474
+ bodyWidth,
475
+ bodyLength: bodyLength10,
476
+ bodyHeight: thickness,
477
+ landsPerSideX: grid.x,
478
+ landsPerSideY: grid.y,
479
+ pitch: p.p,
480
+ landWidth,
481
+ landLength,
482
+ edgeInset: 0,
483
+ landThickness: Math.min(0.015, thickness / 10)
484
+ }
485
+ );
486
+ }
487
+ return null;
488
+ }
489
+
196
490
  // lib/Footprinter3d.tsx
197
491
  import { fp as fp10 } from "@tscircuit/footprinter";
198
492
  import { mp } from "@tscircuit/modelprinter";
@@ -426,137 +720,6 @@ var Dip = ({
426
720
  ] });
427
721
  };
428
722
 
429
- // lib/utils/DualRowGullWing.tsx
430
- function createDualRowGullWing(p) {
431
- const {
432
- pinCount,
433
- pitch,
434
- bodyWidth,
435
- bodyLength: bodyLength10,
436
- bodyHeight,
437
- standoff,
438
- leadSpan,
439
- leadWidth,
440
- leadThickness,
441
- contactLength,
442
- taperInset,
443
- exposedPadWidth = 0,
444
- exposedPadLength = 0
445
- } = p;
446
- for (const key of [
447
- "pitch",
448
- "bodyWidth",
449
- "bodyLength",
450
- "bodyHeight",
451
- "leadSpan",
452
- "leadWidth",
453
- "leadThickness",
454
- "contactLength"
455
- ])
456
- if (!Number.isFinite(p[key]) || p[key] <= 0)
457
- throw new Error(`${key} must be finite and positive`);
458
- for (const [name, value] of Object.entries({
459
- standoff,
460
- taperInset,
461
- exposedPadWidth,
462
- exposedPadLength
463
- }))
464
- if (!Number.isFinite(value) || value < 0)
465
- throw new Error(`${name} must be finite and nonnegative`);
466
- if (!Number.isInteger(pinCount) || pinCount < 4 || pinCount % 2)
467
- throw new Error("pinCount must be even and at least 4");
468
- const moldHeight = bodyHeight - standoff;
469
- const reach = (leadSpan - bodyWidth) / 2;
470
- const rowLength = (pinCount / 2 - 1) * pitch + leadWidth;
471
- if (moldHeight <= leadThickness * 2 || leadWidth >= pitch || rowLength >= bodyLength10 - 2 * taperInset || taperInset * 2 >= Math.min(bodyWidth, bodyLength10) || contactLength >= reach)
472
- throw new Error("Invalid mold, lead spacing, or terminal reach");
473
- if (exposedPadWidth === 0 !== (exposedPadLength === 0) || exposedPadWidth >= bodyWidth - 2 * taperInset || exposedPadLength >= bodyLength10 - 2 * taperInset)
474
- throw new Error("Exposed pad must fit inside the bottom mold face");
475
- const midZ = standoff + moldHeight / 2;
476
- const slice = Math.min(2e-3, moldHeight / 100);
477
- const body = /* @__PURE__ */ jsxs(Hull, { children: [
478
- /* @__PURE__ */ jsx(Translate, { z: standoff + slice / 2, children: /* @__PURE__ */ jsx(
479
- Cuboid,
480
- {
481
- size: [
482
- bodyWidth - 2 * taperInset,
483
- bodyLength10 - 2 * taperInset,
484
- slice
485
- ]
486
- }
487
- ) }),
488
- /* @__PURE__ */ jsx(Translate, { z: midZ, children: /* @__PURE__ */ jsx(Cuboid, { size: [bodyWidth, bodyLength10, slice] }) }),
489
- /* @__PURE__ */ jsx(Translate, { z: bodyHeight - slice / 2, children: /* @__PURE__ */ jsx(
490
- Cuboid,
491
- {
492
- size: [
493
- bodyWidth - 2 * taperInset,
494
- bodyLength10 - 2 * taperInset,
495
- slice
496
- ]
497
- }
498
- ) })
499
- ] });
500
- const dimpleRadius = Math.min(bodyWidth, bodyLength10) * 0.045;
501
- const dimpleDepth = Math.min(0.04, moldHeight / 10);
502
- const lead = (side, y) => {
503
- const outer = leadSpan / 2;
504
- const root = bodyWidth / 2 - Math.min(bodyWidth / 8, 0.15);
505
- const bendStart = outer - contactLength;
506
- const bendEnd = bodyWidth / 2 + Math.min(0.08, (reach - contactLength) / 4);
507
- const points = [
508
- [outer - slice / 2, leadThickness / 2],
509
- [bendStart, leadThickness / 2]
510
- ];
511
- for (let i = 1; i <= 12; i++) {
512
- const t = i / 12;
513
- points.push([
514
- bendStart + (bendEnd - bendStart) * t,
515
- leadThickness / 2 + (midZ - leadThickness / 2) * t * t * (3 - 2 * t)
516
- ]);
517
- }
518
- points.push([root, midZ]);
519
- const section = ([x, z]) => /* @__PURE__ */ jsx(Translate, { offset: { x: side * x, y, z }, children: /* @__PURE__ */ jsx(Cuboid, { size: [slice, leadWidth, leadThickness] }) });
520
- return /* @__PURE__ */ jsx(Union, { children: points.slice(1).map((point, i) => /* @__PURE__ */ jsxs(Hull, { children: [
521
- section(points[i]),
522
- section(point)
523
- ] }, i)) });
524
- };
525
- return /* @__PURE__ */ jsxs(Fragment2, { children: [
526
- /* @__PURE__ */ jsx(Colorize, { color: "#353535", children: /* @__PURE__ */ jsxs(Subtract, { children: [
527
- body,
528
- /* @__PURE__ */ jsx(
529
- Translate,
530
- {
531
- offset: {
532
- x: -bodyWidth / 2 + taperInset + dimpleRadius * 2,
533
- y: bodyLength10 / 2 - taperInset - dimpleRadius * 2,
534
- z: bodyHeight
535
- },
536
- children: /* @__PURE__ */ jsx(Cylinder, { radius: dimpleRadius, height: dimpleDepth * 2 })
537
- }
538
- )
539
- ] }) }),
540
- /* @__PURE__ */ jsx(Colorize, { color: "#bfc1c4", children: Array.from(
541
- { length: pinCount },
542
- (_, i) => lead(
543
- i < pinCount / 2 ? -1 : 1,
544
- ((pinCount / 2 - 1) / 2 - i % (pinCount / 2)) * pitch
545
- )
546
- ) }),
547
- exposedPadWidth > 0 && /* @__PURE__ */ jsx(Colorize, { color: "#bfc1c4", children: /* @__PURE__ */ jsx(Translate, { z: (standoff + leadThickness) / 2, children: /* @__PURE__ */ jsx(
548
- Cuboid,
549
- {
550
- size: [
551
- exposedPadWidth,
552
- exposedPadLength,
553
- standoff + leadThickness
554
- ]
555
- }
556
- ) }) })
557
- ] });
558
- }
559
-
560
723
  // lib/SmdChipLead.tsx
561
724
  function calculateSCurve(x, {
562
725
  height: height10,
@@ -7990,6 +8153,8 @@ var Footprinter3d = ({ footprint }) => {
7990
8153
  const parsed = mm2(value);
7991
8154
  return Number.isFinite(parsed) ? parsed : fallback;
7992
8155
  };
8156
+ const bodyModel = renderFootprinterBodyModel(fpJson);
8157
+ if (bodyModel) return bodyModel;
7993
8158
  switch (fpJson.fn) {
7994
8159
  case "crystal":
7995
8160
  return /* @__PURE__ */ jsx(