@tscircuit/parts-engine 0.0.27 → 0.0.28

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
@@ -9449,6 +9449,7 @@ var mil2mm = (mil) => {
9449
9449
  return mm(`${mil}mil`);
9450
9450
  return mm(mil);
9451
9451
  };
9452
+ var DEFAULT_PCB_THICKNESS_MM = 1.6;
9452
9453
  function generateArcFromSweep(startX, startY, endX, endY, radius, largeArcFlag, sweepFlag) {
9453
9454
  const start = { x: startX, y: startY };
9454
9455
  const end = { x: endX, y: endY };
@@ -9493,6 +9494,159 @@ function generateArcFromSweep(startX, startY, endX, endY, radius, largeArcFlag,
9493
9494
  return path;
9494
9495
  }
9495
9496
  var mil10ToMm = (value) => value * 10 * 0.0254;
9497
+ var getPinLabelValues = (labels) => {
9498
+ if (typeof labels === "string") return [labels];
9499
+ return [...labels];
9500
+ };
9501
+ var stripEasyEdaPolarityHintDecoration = (label) => label.toLowerCase().replace(/[^a-z0-9+-]/g, "");
9502
+ var getPinKeySortValue = (pinKey) => {
9503
+ const match = /^pin(\d+)$/i.exec(pinKey);
9504
+ return match ? Number(match[1]) : Number.MAX_SAFE_INTEGER;
9505
+ };
9506
+ var getPolarizedPinMetadata = (pinLabels) => {
9507
+ const labelsByPin = Object.entries(pinLabels).map(([pin, labels]) => ({
9508
+ pin,
9509
+ labels: getPinLabelValues(labels).map(stripEasyEdaPolarityHintDecoration)
9510
+ }));
9511
+ const anodePin = labelsByPin.find(
9512
+ ({ labels }) => labels.some((label) => ["a", "anode", "pos", "+"].includes(label))
9513
+ )?.pin;
9514
+ const cathodePin = labelsByPin.find(
9515
+ ({ labels }) => labels.some((label) => ["c", "k", "cathode", "neg", "-"].includes(label))
9516
+ )?.pin;
9517
+ if (!anodePin || !cathodePin || anodePin === cathodePin) return void 0;
9518
+ const polarizedPinEntries = [
9519
+ [anodePin, ["anode", "pos"]],
9520
+ [cathodePin, ["cathode", "neg"]]
9521
+ ].sort(
9522
+ ([pinA], [pinB]) => getPinKeySortValue(pinA) - getPinKeySortValue(pinB)
9523
+ );
9524
+ return {
9525
+ portHintsMap: Object.fromEntries(
9526
+ polarizedPinEntries.map(([pin, labels]) => [pin, [pin, ...labels]])
9527
+ ),
9528
+ pinLabels: Object.fromEntries(polarizedPinEntries)
9529
+ };
9530
+ };
9531
+ var normalizePinLabels = (inputPinLabels) => {
9532
+ const uniqueInputPinLabels = inputPinLabels.map((labels) => [
9533
+ ...new Set(labels)
9534
+ ]);
9535
+ const result = uniqueInputPinLabels.map(() => []);
9536
+ const desiredNumbers = uniqueInputPinLabels.map(() => null);
9537
+ for (let i = 0; i < uniqueInputPinLabels.length; i++) {
9538
+ for (const label of uniqueInputPinLabels[i]) {
9539
+ if (/^\d+$/.test(label)) {
9540
+ desiredNumbers[i] = Number.parseInt(label);
9541
+ break;
9542
+ }
9543
+ }
9544
+ }
9545
+ let highestPinNumber = 0;
9546
+ const acceptedDesiredNumbers = /* @__PURE__ */ new Set();
9547
+ for (let i = 0; i < desiredNumbers.length; i++) {
9548
+ const desiredNumber = desiredNumbers[i];
9549
+ if (desiredNumber === null || desiredNumber < 1) continue;
9550
+ if (!acceptedDesiredNumbers.has(desiredNumber)) {
9551
+ acceptedDesiredNumbers.add(desiredNumber);
9552
+ result[i].push(`pin${desiredNumber}`);
9553
+ highestPinNumber = Math.max(highestPinNumber, desiredNumber);
9554
+ continue;
9555
+ }
9556
+ let existingAltCount = 0;
9557
+ for (const label of result[i]) {
9558
+ if (label.startsWith(`pin${desiredNumber}_alt`)) {
9559
+ existingAltCount += 1;
9560
+ }
9561
+ }
9562
+ result[i].push(`pin${desiredNumber}_alt${existingAltCount + 1}`);
9563
+ }
9564
+ for (let i = 0; i < result.length; i++) {
9565
+ const firstLabel = result[i][0];
9566
+ if (firstLabel?.includes("_alt")) {
9567
+ highestPinNumber += 1;
9568
+ result[i].unshift(`pin${highestPinNumber}`);
9569
+ }
9570
+ }
9571
+ for (let i = 0; i < result.length; i++) {
9572
+ if (result[i].length === 0) {
9573
+ highestPinNumber += 1;
9574
+ result[i].push(`pin${highestPinNumber}`);
9575
+ }
9576
+ }
9577
+ const totalLabelCounts = {};
9578
+ for (const inputLabels of uniqueInputPinLabels) {
9579
+ for (const label of inputLabels) {
9580
+ if (/^\d+$/.test(label)) continue;
9581
+ totalLabelCounts[label] = (totalLabelCounts[label] ?? 0) + 1;
9582
+ }
9583
+ }
9584
+ const incrementalLabelCounts = {};
9585
+ for (let i = 0; i < uniqueInputPinLabels.length; i++) {
9586
+ for (const label of uniqueInputPinLabels[i]) {
9587
+ if (/^\d+$/.test(label)) continue;
9588
+ if (totalLabelCounts[label] === 1) {
9589
+ result[i].push(label);
9590
+ } else {
9591
+ incrementalLabelCounts[label] = (incrementalLabelCounts[label] ?? 0) + 1;
9592
+ result[i].push(`${label}${incrementalLabelCounts[label]}`);
9593
+ }
9594
+ }
9595
+ }
9596
+ return result;
9597
+ };
9598
+ var normalizeSymbolName = (name) => {
9599
+ const trimmedName = name.trim();
9600
+ if (trimmedName === "+") return "_POS";
9601
+ if (trimmedName === "-") return "_NEG";
9602
+ return trimmedName;
9603
+ };
9604
+ var categoryValueContainsDiode = (value) => {
9605
+ if (typeof value === "string") {
9606
+ return /(^|[^a-z])diodes?([^a-z]|$)/i.test(value);
9607
+ }
9608
+ if (Array.isArray(value)) {
9609
+ return value.some(categoryValueContainsDiode);
9610
+ }
9611
+ if (value && typeof value === "object") {
9612
+ return Object.values(value).some(categoryValueContainsDiode);
9613
+ }
9614
+ return false;
9615
+ };
9616
+ var isDiodeCategoryComponent = (betterEasy) => {
9617
+ const cPara = betterEasy.dataStr.head.c_para;
9618
+ return [
9619
+ betterEasy.tags,
9620
+ cPara.category,
9621
+ cPara.Category,
9622
+ cPara["LCSC Category"],
9623
+ cPara["JLCPCB Category"],
9624
+ betterEasy.category
9625
+ ].some(categoryValueContainsDiode);
9626
+ };
9627
+ var categoryValueContainsLed = (value) => {
9628
+ if (typeof value === "string") {
9629
+ return /(^|[^a-z])leds?([^a-z]|$)/i.test(value) || /light[-\s]?emitting\s+diodes?/i.test(value);
9630
+ }
9631
+ if (Array.isArray(value)) {
9632
+ return value.some(categoryValueContainsLed);
9633
+ }
9634
+ if (value && typeof value === "object") {
9635
+ return Object.values(value).some(categoryValueContainsLed);
9636
+ }
9637
+ return false;
9638
+ };
9639
+ var isLedCategoryComponent = (betterEasy) => {
9640
+ const cPara = betterEasy.dataStr.head.c_para;
9641
+ return [
9642
+ betterEasy.tags,
9643
+ cPara.category,
9644
+ cPara.Category,
9645
+ cPara["LCSC Category"],
9646
+ cPara["JLCPCB Category"],
9647
+ betterEasy.category
9648
+ ].some(categoryValueContainsLed);
9649
+ };
9496
9650
  var getBoundsCenter = (bounds) => ({
9497
9651
  x: (bounds.minX + bounds.maxX) / 2,
9498
9652
  y: (bounds.minY + bounds.maxY) / 2
@@ -9608,80 +9762,6 @@ var getCadModelOffsetMmFromBounds = (easyEdaJson, bounds, {
9608
9762
  y: modelCenter.y - targetOriginInModelFrame.y
9609
9763
  });
9610
9764
  };
9611
- var normalizePinLabels = (inputPinLabels) => {
9612
- const uniqueInputPinLabels = inputPinLabels.map((labels) => [
9613
- ...new Set(labels)
9614
- ]);
9615
- const result = uniqueInputPinLabels.map(() => []);
9616
- const desiredNumbers = uniqueInputPinLabels.map(() => null);
9617
- for (let i = 0; i < uniqueInputPinLabels.length; i++) {
9618
- for (const label of uniqueInputPinLabels[i]) {
9619
- if (/^\d+$/.test(label)) {
9620
- desiredNumbers[i] = Number.parseInt(label);
9621
- break;
9622
- }
9623
- }
9624
- }
9625
- let highestPinNumber = 0;
9626
- const acceptedDesiredNumbers = /* @__PURE__ */ new Set();
9627
- for (let i = 0; i < desiredNumbers.length; i++) {
9628
- const desiredNumber = desiredNumbers[i];
9629
- if (desiredNumber === null || desiredNumber < 1) continue;
9630
- if (!acceptedDesiredNumbers.has(desiredNumber)) {
9631
- acceptedDesiredNumbers.add(desiredNumber);
9632
- result[i].push(`pin${desiredNumber}`);
9633
- highestPinNumber = Math.max(highestPinNumber, desiredNumber);
9634
- continue;
9635
- }
9636
- let existingAltCount = 0;
9637
- for (const label of result[i]) {
9638
- if (label.startsWith(`pin${desiredNumber}_alt`)) {
9639
- existingAltCount += 1;
9640
- }
9641
- }
9642
- result[i].push(`pin${desiredNumber}_alt${existingAltCount + 1}`);
9643
- }
9644
- for (let i = 0; i < result.length; i++) {
9645
- const firstLabel = result[i][0];
9646
- if (firstLabel?.includes("_alt")) {
9647
- highestPinNumber += 1;
9648
- result[i].unshift(`pin${highestPinNumber}`);
9649
- }
9650
- }
9651
- for (let i = 0; i < result.length; i++) {
9652
- if (result[i].length === 0) {
9653
- highestPinNumber += 1;
9654
- result[i].push(`pin${highestPinNumber}`);
9655
- }
9656
- }
9657
- const totalLabelCounts = {};
9658
- for (const inputLabels of uniqueInputPinLabels) {
9659
- for (const label of inputLabels) {
9660
- if (/^\d+$/.test(label)) continue;
9661
- totalLabelCounts[label] = (totalLabelCounts[label] ?? 0) + 1;
9662
- }
9663
- }
9664
- const incrementalLabelCounts = {};
9665
- for (let i = 0; i < uniqueInputPinLabels.length; i++) {
9666
- for (const label of uniqueInputPinLabels[i]) {
9667
- if (/^\d+$/.test(label)) continue;
9668
- if (totalLabelCounts[label] === 1) {
9669
- result[i].push(label);
9670
- } else {
9671
- incrementalLabelCounts[label] = (incrementalLabelCounts[label] ?? 0) + 1;
9672
- result[i].push(`${label}${incrementalLabelCounts[label]}`);
9673
- }
9674
- }
9675
- }
9676
- return result;
9677
- };
9678
- var normalizeSymbolName = (name) => {
9679
- const trimmedName = name.trim();
9680
- if (trimmedName === "+") return "_POS";
9681
- if (trimmedName === "-") return "_NEG";
9682
- return trimmedName;
9683
- };
9684
- var DEFAULT_PCB_THICKNESS_MM = 1.6;
9685
9765
  var EASYEDA_STEP_MODEL_URL = "https://modules.easyeda.com/qAxj6KHrDKw4blvCG8QJPs7Y";
9686
9766
  var EASYEDA_OBJ_MODEL_URL = "https://modules.easyeda.com/3dmodel";
9687
9767
  var TSCIRCUIT_MODEL_CDN_URL = "https://modelcdn.tscircuit.com/easyeda_models";
@@ -9886,11 +9966,20 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9886
9966
  return labels;
9887
9967
  });
9888
9968
  const normalizedPinLabels = normalizePinLabels(pinLabelSets);
9969
+ const normalizedPinLabelsByPin = Object.fromEntries(
9970
+ normalizedPinLabels.map((labels) => {
9971
+ const pin = labels.find((label) => /^pin\d+$/i.test(label));
9972
+ return [pin, labels.filter((label) => label !== pin)];
9973
+ })
9974
+ );
9975
+ const polarizedPinMetadata = pads.length === 2 && (isDiodeCategoryComponent(easyEdaJson) || isLedCategoryComponent(easyEdaJson)) ? getPolarizedPinMetadata(normalizedPinLabelsByPin) : void 0;
9889
9976
  pads.forEach((pad, index) => {
9890
9977
  const portHints = normalizedPinLabels[index];
9891
9978
  const pinNumber = Number.parseInt(
9892
9979
  portHints.find((hint) => hint.match(/pin\d+/)).replace("pin", "")
9893
9980
  );
9981
+ const canonicalPinName = `pin${pinNumber}`;
9982
+ const pcbPortHints = polarizedPinMetadata?.portHintsMap[canonicalPinName] ?? [canonicalPinName];
9894
9983
  circuitElements.push({
9895
9984
  type: "source_port",
9896
9985
  source_port_id: `source_port_${index + 1}`,
@@ -9907,7 +9996,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9907
9996
  x: mil2mm(pad.center.x),
9908
9997
  y: mil2mm(pad.center.y),
9909
9998
  layers: ["top"],
9910
- port_hints: [`pin${pinNumber}`],
9999
+ port_hints: pcbPortHints,
9911
10000
  pcb_component_id: "pcb_component_1",
9912
10001
  pcb_port_id: `pcb_port_${index + 1}`
9913
10002
  };
@@ -10019,7 +10108,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
10019
10108
  radius: Math.min(mil2mm(pad.width), mil2mm(pad.height)) / 2
10020
10109
  },
10021
10110
  layer: "top",
10022
- port_hints: [`pin${pinNumber}`],
10111
+ port_hints: pcbPortHints,
10023
10112
  pcb_component_id: "pcb_component_1",
10024
10113
  pcb_port_id: `pcb_port_${index + 1}`
10025
10114
  });
@@ -10281,7 +10370,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
10281
10370
  };
10282
10371
  var safeNumber = (defaultValue = 0) => external_exports.union([external_exports.number(), external_exports.string()]).transform((val) => {
10283
10372
  const num = Number(val);
10284
- return isNaN(num) ? defaultValue : num;
10373
+ return Number.isNaN(num) ? defaultValue : num;
10285
10374
  }).default(defaultValue);
10286
10375
  var tenthmil = external_exports.union([external_exports.number(), external_exports.string()]).optional().transform(
10287
10376
  (n) => typeof n === "string" && n.endsWith("mil") ? n : `${Number.parseFloat(n) * 10}mil`
@@ -10428,6 +10517,7 @@ var ShapeItemSchema = external_exports.object({
10428
10517
  }
10429
10518
  case "PAD": {
10430
10519
  const [padShape, ...params] = shape.data.split("~");
10520
+ const rawPadNumber = params[6];
10431
10521
  const [
10432
10522
  centerX,
10433
10523
  centerY,
@@ -10435,10 +10525,11 @@ var ShapeItemSchema = external_exports.object({
10435
10525
  height,
10436
10526
  layermask,
10437
10527
  net,
10438
- number,
10528
+ numericPadNumber,
10439
10529
  holeRadius,
10440
10530
  ...rest
10441
10531
  ] = params.map((p) => Number.isNaN(Number(p)) ? p : Number(p));
10532
+ const padNumber = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(rawPadNumber) ? numericPadNumber : rawPadNumber;
10442
10533
  const center = { x: centerX, y: centerY };
10443
10534
  let points;
10444
10535
  if (padShape === "RECT" || padShape === "POLYGON") {
@@ -10454,7 +10545,7 @@ var ShapeItemSchema = external_exports.object({
10454
10545
  height,
10455
10546
  layermask,
10456
10547
  net,
10457
- number,
10548
+ number: padNumber,
10458
10549
  holeRadius,
10459
10550
  points,
10460
10551
  rotation: rotation2,
@@ -10787,8 +10878,12 @@ var parsePath = (str) => {
10787
10878
  };
10788
10879
  var PathShapeSchema = external_exports.string().startsWith("PT~").transform(parsePath).pipe(PathShapeOutputSchema);
10789
10880
  var optionalEasyEdaTextField = (fieldSchema) => external_exports.preprocess((textField) => {
10790
- if (textField == null || textField === "" || textField === "undefined") {
10791
- return void 0;
10881
+ if (textField == null) return void 0;
10882
+ if (typeof textField === "string") {
10883
+ const trimmedTextField = textField.trim();
10884
+ if (trimmedTextField === "" || trimmedTextField === "undefined") {
10885
+ return void 0;
10886
+ }
10792
10887
  }
10793
10888
  return textField;
10794
10889
  }, fieldSchema);
@@ -10901,7 +10996,7 @@ var OwnerSchema = external_exports.object({
10901
10996
  });
10902
10997
  var HeadSchema = external_exports.object({
10903
10998
  docType: external_exports.preprocess((val) => val == null ? val : String(val), external_exports.string()),
10904
- editorVersion: external_exports.string(),
10999
+ editorVersion: external_exports.string().default(""),
10905
11000
  c_para: external_exports.record(external_exports.string(), external_exports.string().nullable()),
10906
11001
  x: external_exports.number(),
10907
11002
  y: external_exports.number(),
@@ -10914,7 +11009,7 @@ var HeadSchema = external_exports.object({
10914
11009
  }, external_exports.number()),
10915
11010
  importFlag: external_exports.number().optional(),
10916
11011
  c_spiceCmd: external_exports.any().optional(),
10917
- hasIdFlag: external_exports.boolean()
11012
+ hasIdFlag: external_exports.boolean().default(false)
10918
11013
  });
10919
11014
  var BBoxSchema = external_exports.object({
10920
11015
  x: external_exports.number(),
@@ -10939,9 +11034,17 @@ var DataStrSchema = external_exports.object({
10939
11034
  head: HeadSchema,
10940
11035
  canvas: external_exports.string(),
10941
11036
  shape: external_exports.array(SingleLetterShapeSchema),
10942
- BBox: BBoxSchema,
10943
- colors: external_exports.union([external_exports.array(external_exports.string()), external_exports.record(external_exports.string())])
10944
- });
11037
+ BBox: BBoxSchema.optional(),
11038
+ colors: external_exports.union([external_exports.array(external_exports.string()), external_exports.record(external_exports.string())]).default([])
11039
+ }).transform((data) => ({
11040
+ ...data,
11041
+ BBox: data.BBox ?? {
11042
+ x: data.head.x,
11043
+ y: data.head.y,
11044
+ width: 0,
11045
+ height: 0
11046
+ }
11047
+ }));
10945
11048
  var PackageDetailDataStrSchema = external_exports.object({
10946
11049
  head: HeadSchema,
10947
11050
  canvas: external_exports.string(),
@@ -11122,9 +11225,24 @@ async function fetchEasyEDAComponent(jlcpcbPartNumber, {
11122
11225
  if (!searchResult.success || !searchResult.result.lists.lcsc.length) {
11123
11226
  throw new Error("Component not found");
11124
11227
  }
11228
+ const requestedPartNumber = jlcpcbPartNumber.trim().toUpperCase();
11125
11229
  const bestMatchComponent = searchResult.result.lists.lcsc.find(
11126
- (component) => component.dataStr.head.c_para["Supplier Part"] === jlcpcbPartNumber
11127
- ) ?? searchResult.result.lists.lcsc[0];
11230
+ (component) => {
11231
+ const candidatePartNumbers = [
11232
+ component.dataStr?.head?.c_para?.["Supplier Part"],
11233
+ component.lcsc?.number,
11234
+ component.szlcsc?.number
11235
+ ];
11236
+ return candidatePartNumbers.some(
11237
+ (partNumber) => typeof partNumber === "string" && partNumber.trim().toUpperCase() === requestedPartNumber
11238
+ );
11239
+ }
11240
+ );
11241
+ if (!bestMatchComponent) {
11242
+ throw new Error(
11243
+ `No exact EasyEDA component match for "${jlcpcbPartNumber}"`
11244
+ );
11245
+ }
11128
11246
  const componentUUID = bestMatchComponent.uuid;
11129
11247
  const componentResponse = await fetch2(componentUrl(componentUUID), {
11130
11248
  method: "GET",