@tscircuit/parts-engine 0.0.19 → 0.0.21

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
@@ -8869,11 +8869,11 @@ function generateArcFromSweep(startX, startY, endX, endY, radius, largeArcFlag,
8869
8869
  const midY = (startY + endY) / 2;
8870
8870
  const dx = endX - startX;
8871
8871
  const dy = endY - startY;
8872
- const distance2 = Math.sqrt(dx * dx + dy * dy);
8873
- if (distance2 === 0 || radius < distance2 / 2) {
8872
+ const distance3 = Math.sqrt(dx * dx + dy * dy);
8873
+ if (distance3 === 0 || radius < distance3 / 2) {
8874
8874
  return [start, end];
8875
8875
  }
8876
- const h = Math.sqrt(radius * radius - distance2 * distance2 / 4);
8876
+ const h = Math.sqrt(radius * radius - distance3 * distance3 / 4);
8877
8877
  const angle = Math.atan2(dy, dx);
8878
8878
  const centerX = midX + h * Math.sin(angle) * (sweepFlag ? -1 : 1);
8879
8879
  const centerY = midY - h * Math.cos(angle) * (sweepFlag ? -1 : 1);
@@ -8906,6 +8906,121 @@ function generateArcFromSweep(startX, startY, endX, endY, radius, largeArcFlag,
8906
8906
  return path;
8907
8907
  }
8908
8908
  var mil10ToMm = (value) => value * 10 * 0.0254;
8909
+ var getBoundsCenter = (bounds) => ({
8910
+ x: (bounds.minX + bounds.maxX) / 2,
8911
+ y: (bounds.minY + bounds.maxY) / 2
8912
+ });
8913
+ var getBoundsFromPoints = (points) => {
8914
+ if (points.length === 0) {
8915
+ return null;
8916
+ }
8917
+ let minX = points[0].x;
8918
+ let minY = points[0].y;
8919
+ let maxX = points[0].x;
8920
+ let maxY = points[0].y;
8921
+ for (let i = 1; i < points.length; i++) {
8922
+ const point2 = points[i];
8923
+ if (point2.x < minX) minX = point2.x;
8924
+ if (point2.y < minY) minY = point2.y;
8925
+ if (point2.x > maxX) maxX = point2.x;
8926
+ if (point2.y > maxY) maxY = point2.y;
8927
+ }
8928
+ return { minX, minY, maxX, maxY };
8929
+ };
8930
+ var getCadSvgNode = (easyEdaJson) => {
8931
+ const svgNode = easyEdaJson.packageDetail.dataStr.shape.find(
8932
+ (shape) => shape.type === "SVGNODE" && shape.svgData.attrs?.uuid
8933
+ );
8934
+ return svgNode?.type === "SVGNODE" ? svgNode : null;
8935
+ };
8936
+ var isSvgChildNodeWithAttrs = (childNode) => typeof childNode === "object" && childNode !== null;
8937
+ var getXyBoundsFromModelBounds = (bounds) => ({
8938
+ minX: bounds.min.x,
8939
+ minY: bounds.min.y,
8940
+ maxX: bounds.max.x,
8941
+ maxY: bounds.max.y
8942
+ });
8943
+ var rotateScenePoint = (point2, rotationDeg) => {
8944
+ switch (rotationDeg) {
8945
+ case 0:
8946
+ return point2;
8947
+ case 90:
8948
+ return { x: point2.y, y: -point2.x };
8949
+ case 180:
8950
+ return { x: -point2.x, y: -point2.y };
8951
+ case 270:
8952
+ return { x: -point2.y, y: point2.x };
8953
+ }
8954
+ };
8955
+ var getEasyEdaModelOriginOnBoardMm = ({
8956
+ svgNode,
8957
+ footprintCenterMm
8958
+ }) => {
8959
+ const points = [];
8960
+ for (const childNode of svgNode.svgData.childNodes ?? []) {
8961
+ if (!isSvgChildNodeWithAttrs(childNode)) continue;
8962
+ const rawPoints = childNode.attrs?.points;
8963
+ if (!rawPoints) continue;
8964
+ const values = String(rawPoints).trim().split(/\s+/).map(Number);
8965
+ for (let i = 0; i + 1 < values.length; i += 2) {
8966
+ const x = values[i];
8967
+ const y = values[i + 1];
8968
+ if (Number.isFinite(x) && Number.isFinite(y)) {
8969
+ points.push({ x: mil10ToMm(x), y: mil10ToMm(y) });
8970
+ }
8971
+ }
8972
+ }
8973
+ let modelOriginMm = null;
8974
+ if (points.length > 0) {
8975
+ const bounds = getBoundsFromPoints(points);
8976
+ modelOriginMm = bounds ? getBoundsCenter(bounds) : null;
8977
+ } else {
8978
+ const [originX, originY] = String(svgNode.svgData.attrs?.c_origin ?? "0,0").split(",").map((value) => Number(value.trim()));
8979
+ if (Number.isFinite(originX) && Number.isFinite(originY)) {
8980
+ modelOriginMm = { x: mil10ToMm(originX), y: mil10ToMm(originY) };
8981
+ }
8982
+ }
8983
+ if (!modelOriginMm) return null;
8984
+ return {
8985
+ x: modelOriginMm.x - footprintCenterMm.x,
8986
+ y: footprintCenterMm.y - modelOriginMm.y
8987
+ };
8988
+ };
8989
+ var snapZero = (value) => Math.abs(value) < 1e-6 ? 0 : value;
8990
+ var normalizePoint = (point2) => ({
8991
+ x: snapZero(point2.x),
8992
+ y: snapZero(point2.y)
8993
+ });
8994
+ var getCadModelOffsetMmFromBounds = (easyEdaJson, bounds, {
8995
+ footprintBoundsCenterMm
8996
+ } = {}) => {
8997
+ const svgNode = getCadSvgNode(easyEdaJson);
8998
+ if (!svgNode || !bounds) return null;
8999
+ const [, , rotationZRaw] = String(
9000
+ svgNode.svgData.attrs?.c_rotation ?? "0,0,0"
9001
+ ).split(",").map((value) => Number(value.trim()));
9002
+ const rotationDeg = (rotationZRaw % 360 + 360) % 360;
9003
+ if (![0, 90, 180, 270].includes(rotationDeg)) return null;
9004
+ const modelCenter = getBoundsCenter(getXyBoundsFromModelBounds(bounds));
9005
+ const footprintCenter = easyEdaJson.packageDetail.dataStr.head;
9006
+ const footprintCenterMm = footprintBoundsCenterMm ?? {
9007
+ x: mil10ToMm(footprintCenter.x),
9008
+ y: mil10ToMm(footprintCenter.y)
9009
+ };
9010
+ const targetOriginOnBoardMm = getEasyEdaModelOriginOnBoardMm({
9011
+ svgNode,
9012
+ footprintCenterMm
9013
+ });
9014
+ if (!targetOriginOnBoardMm) return null;
9015
+ const targetOriginInModelFrame = rotateScenePoint(
9016
+ targetOriginOnBoardMm,
9017
+ rotationDeg
9018
+ );
9019
+ return normalizePoint({
9020
+ x: modelCenter.x - targetOriginInModelFrame.x,
9021
+ y: modelCenter.y - targetOriginInModelFrame.y
9022
+ });
9023
+ };
8909
9024
  var normalizePinLabels = (inputPinLabels) => {
8910
9025
  const uniqueInputPinLabels = inputPinLabels.map((labels) => [
8911
9026
  ...new Set(labels)
@@ -9097,6 +9212,10 @@ var handleCutout = (solidRegion, index) => {
9097
9212
  }))
9098
9213
  });
9099
9214
  };
9215
+ var LEAD_SHAPE_LAYER = 100;
9216
+ var isPcbSolidRegionCutout = (shape) => {
9217
+ return shape.fillStyle === "cutout" && shape.layermask !== LEAD_SHAPE_LAYER;
9218
+ };
9100
9219
  var getCadPositionZMmFromMetadata = (easyEdaJson) => {
9101
9220
  const svgNode = easyEdaJson.packageDetail.dataStr.shape.find(
9102
9221
  (shape) => shape.type === "SVGNODE" && shape.svgData.attrs?.uuid
@@ -9115,6 +9234,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9115
9234
  cadPositionXMm,
9116
9235
  cadPositionYMm,
9117
9236
  cadPositionZMm,
9237
+ cadModelBounds,
9118
9238
  showDesignator = false
9119
9239
  } = {}) => {
9120
9240
  const resolvedCadPositionZMm = cadPositionZMm ?? getCadPositionZMmFromMetadata(easyEdaJson);
@@ -9194,15 +9314,12 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9194
9314
  const innerWidth = largestOuterDimensionName === "width" ? largestInnerDimension : smallestInnerDimension;
9195
9315
  const innerHeight = largestOuterDimensionName === "height" ? largestInnerDimension : smallestInnerDimension;
9196
9316
  additionalPlatedHoleProps = {
9197
- shape: "pill_hole_with_rect_pad",
9198
- hole_shape: "pill",
9199
- pad_shape: "rect",
9317
+ shape: "pill",
9200
9318
  hole_width: innerWidth,
9201
9319
  hole_height: innerHeight,
9202
- rect_pad_width: mil2mm(pad.width),
9203
- rect_pad_height: mil2mm(pad.height),
9204
- hole_offset_x: 0,
9205
- hole_offset_y: 0
9320
+ outer_width: mil2mm(pad.width),
9321
+ outer_height: mil2mm(pad.height),
9322
+ ccw_rotation: pad.rotation || 0
9206
9323
  };
9207
9324
  } else if (pad.shape === "RECT") {
9208
9325
  const padWidth = mil2mm(pad.width);
@@ -9256,7 +9373,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9256
9373
  } else if (pad.shape === "ELLIPSE") {
9257
9374
  soupShape = "rect";
9258
9375
  } else if (pad.shape === "OVAL") {
9259
- soupShape = "rect";
9376
+ soupShape = "pill";
9260
9377
  } else if (pad.shape === "POLYGON") {
9261
9378
  soupShape = "polygon";
9262
9379
  }
@@ -9276,12 +9393,17 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9276
9393
  x: mil2mm(pad.center.x),
9277
9394
  y: mil2mm(pad.center.y)
9278
9395
  },
9279
- ...soupShape === "rect" ? rectSize : soupShape === "polygon" && pad.points ? {
9396
+ ...soupShape === "rect" ? rectSize : soupShape === "pill" ? {
9397
+ ...rectSize,
9398
+ radius: Math.min(rectSize.width, rectSize.height) / 2
9399
+ } : soupShape === "polygon" && pad.points ? {
9280
9400
  points: pad.points.map((p) => ({
9281
9401
  x: milx10(p.x),
9282
9402
  y: milx10(p.y)
9283
9403
  }))
9284
- } : { radius: Math.min(mil2mm(pad.width), mil2mm(pad.height)) / 2 },
9404
+ } : {
9405
+ radius: Math.min(mil2mm(pad.width), mil2mm(pad.height)) / 2
9406
+ },
9285
9407
  layer: "top",
9286
9408
  port_hints: [`pin${pinNumber}`],
9287
9409
  pcb_component_id: "pcb_component_1",
@@ -9300,7 +9422,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9300
9422
  circuitElements.push(handleVia(v, index));
9301
9423
  });
9302
9424
  easyEdaJson.packageDetail.dataStr.shape.filter(
9303
- (shape) => shape.type === "SOLIDREGION" && shape.fillStyle === "cutout"
9425
+ (shape) => shape.type === "SOLIDREGION" && isPcbSolidRegionCutout(shape)
9304
9426
  ).forEach((sr, index) => {
9305
9427
  circuitElements.push(handleCutout(sr, index));
9306
9428
  });
@@ -9352,7 +9474,7 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9352
9474
  circuitElements.push(
9353
9475
  pcb_silkscreen_text.parse({
9354
9476
  type: "pcb_silkscreen_text",
9355
- pcb_silkscreen_text_id: `pcb_silkscreen_text_designator_fallback`,
9477
+ pcb_silkscreen_text_id: "pcb_silkscreen_text_designator_fallback",
9356
9478
  pcb_component_id: "pcb_component_1",
9357
9479
  text: "{NAME}",
9358
9480
  anchor_position: {
@@ -9470,6 +9592,14 @@ var convertEasyEdaJsonToCircuitJson = (easyEdaJson, {
9470
9592
  if (!cad.model_origin_position) {
9471
9593
  cad.model_origin_position = { x: 0, y: 0, z: 0 };
9472
9594
  }
9595
+ const resolvedCadModelBounds = cadModelBounds ?? easyEdaJson._objMetadata?.bounds;
9596
+ const recenteredCadOffset = resolvedCadModelBounds && getCadModelOffsetMmFromBounds(easyEdaJson, resolvedCadModelBounds, {
9597
+ footprintBoundsCenterMm: bounds.center
9598
+ });
9599
+ if (recenteredCadOffset) {
9600
+ cad.model_origin_position.x = recenteredCadOffset.x;
9601
+ cad.model_origin_position.y = recenteredCadOffset.y;
9602
+ }
9473
9603
  const side = pcb_component2.layer ?? "top";
9474
9604
  const t = DEFAULT_PCB_THICKNESS_MM / 2;
9475
9605
  const attrs = svgNode?.svgData?.attrs ?? {};
@@ -10035,6 +10165,12 @@ var parsePath = (str) => {
10035
10165
  };
10036
10166
  };
10037
10167
  var PathShapeSchema = external_exports.string().startsWith("PT~").transform(parsePath).pipe(PathShapeOutputSchema);
10168
+ var optionalEasyEdaTextField = (fieldSchema) => external_exports.preprocess((textField) => {
10169
+ if (textField == null || textField === "" || textField === "undefined") {
10170
+ return void 0;
10171
+ }
10172
+ return textField;
10173
+ }, fieldSchema);
10038
10174
  var TextShapeOutputSchema = external_exports.object({
10039
10175
  type: external_exports.literal("TEXT"),
10040
10176
  alignment: external_exports.enum(["L", "C", "R"]),
@@ -10042,11 +10178,13 @@ var TextShapeOutputSchema = external_exports.object({
10042
10178
  y: external_exports.number(),
10043
10179
  rotation: external_exports.number(),
10044
10180
  fontColor: external_exports.string(),
10045
- backgroundColor: external_exports.string().optional(),
10181
+ backgroundColor: optionalEasyEdaTextField(external_exports.string().optional()),
10046
10182
  fontSize: external_exports.string(),
10047
- fontWeight: external_exports.string().optional().default("normal"),
10048
- fontStyle: external_exports.enum(["normal", "italic"]).optional().default("normal"),
10049
- fontDecoration: external_exports.string().optional().default(""),
10183
+ fontWeight: optionalEasyEdaTextField(external_exports.string().optional().default("normal")),
10184
+ fontStyle: optionalEasyEdaTextField(
10185
+ external_exports.enum(["normal", "italic"]).optional().default("normal")
10186
+ ),
10187
+ fontDecoration: optionalEasyEdaTextField(external_exports.string().optional().default("")),
10050
10188
  content: external_exports.string(),
10051
10189
  textType: external_exports.string(),
10052
10190
  visibility: external_exports.enum(["0", "1"]),
@@ -10079,11 +10217,11 @@ var parseText = (str) => {
10079
10217
  y: Number(y),
10080
10218
  rotation: Number(rotation2),
10081
10219
  fontColor,
10082
- backgroundColor: backgroundColor || void 0,
10220
+ backgroundColor,
10083
10221
  fontSize,
10084
- fontWeight: fontWeight || "normal",
10085
- fontStyle: fontStyle || "normal",
10086
- fontDecoration: fontDecoration || "",
10222
+ fontWeight,
10223
+ fontStyle,
10224
+ fontDecoration,
10087
10225
  content,
10088
10226
  textType,
10089
10227
  visibility,
@@ -10250,6 +10388,7 @@ var EasyEdaJsonSchema = external_exports.object({
10250
10388
  lcsc: LcscSchema,
10251
10389
  owner: OwnerSchema,
10252
10390
  tags: external_exports.array(external_exports.string()),
10391
+ category: external_exports.string().optional(),
10253
10392
  updateTime: external_exports.number(),
10254
10393
  updated_at: external_exports.string(),
10255
10394
  dataStr: DataStrSchema,
@@ -10551,6 +10690,7 @@ var JlcPcbPartsEngine = class {
10551
10690
  } = {}) {
10552
10691
  this.defaultPlatformFetch = defaultPlatformFetch;
10553
10692
  this.easyEdaProxyConfig = easyEdaProxyConfig;
10693
+ this.fetchPartCircuitJson = this.fetchPartCircuitJson.bind(this);
10554
10694
  }
10555
10695
  getEasyEdaPlatformFetch(platformFetchOverride) {
10556
10696
  const resolvedPlatformFetch = platformFetchOverride ?? this.defaultPlatformFetch ?? globalThis.fetch;