altium-to-circuit-json 0.0.82 → 0.0.84

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
@@ -465,15 +465,6 @@ function createPcbConversionContext({
465
465
  };
466
466
  }
467
467
 
468
- // lib/schematic/document/constants.ts
469
- var SCHEMATIC_SHEET_ID = "schematic_sheet_altium";
470
- var SCHEMATIC_UNIT_TO_MILLIMETERS = 10.16 / 1.1;
471
- var SCHEMATIC_SHEET_INSET = 5 / SCHEMATIC_UNIT_TO_MILLIMETERS;
472
- var SCHEMATIC_SHEET_WIDTH = 297 / SCHEMATIC_UNIT_TO_MILLIMETERS;
473
- var SCHEMATIC_SHEET_HEIGHT = 210 / SCHEMATIC_UNIT_TO_MILLIMETERS;
474
- var SCHEMATIC_SHEET_INNER_WIDTH = SCHEMATIC_SHEET_WIDTH - SCHEMATIC_SHEET_INSET * 2;
475
- var SCHEMATIC_SHEET_INNER_HEIGHT = SCHEMATIC_SHEET_HEIGHT - SCHEMATIC_SHEET_INSET * 2;
476
-
477
468
  // lib/schematic/geometry/getAveragePoint.ts
478
469
  function getAveragePoint(points) {
479
470
  if (points.length === 0) return { x: 0, y: 0 };
@@ -616,6 +607,15 @@ function unscaleLength(lengthMillimeters, altiumUnitsToMillimetersScale) {
616
607
  }).x;
617
608
  }
618
609
 
610
+ // lib/schematic/document/constants.ts
611
+ var SCHEMATIC_SHEET_ID = "schematic_sheet_altium";
612
+ var SCHEMATIC_UNIT_TO_MILLIMETERS = 10.16 / 1.1;
613
+ var SCHEMATIC_SHEET_INSET = 5 / SCHEMATIC_UNIT_TO_MILLIMETERS;
614
+ var SCHEMATIC_SHEET_WIDTH = 297 / SCHEMATIC_UNIT_TO_MILLIMETERS;
615
+ var SCHEMATIC_SHEET_HEIGHT = 210 / SCHEMATIC_UNIT_TO_MILLIMETERS;
616
+ var SCHEMATIC_SHEET_INNER_WIDTH = SCHEMATIC_SHEET_WIDTH - SCHEMATIC_SHEET_INSET * 2;
617
+ var SCHEMATIC_SHEET_INNER_HEIGHT = SCHEMATIC_SHEET_HEIGHT - SCHEMATIC_SHEET_INSET * 2;
618
+
619
619
  // lib/schematic/document/getPositiveNumber.ts
620
620
  function getPositiveNumber(candidate, fallback) {
621
621
  const parsed = Number(candidate);
@@ -623,11 +623,43 @@ function getPositiveNumber(candidate, fallback) {
623
623
  }
624
624
 
625
625
  // lib/schematic/document/getAltiumSheetDimensions.ts
626
+ var STANDARD_SHEET_SIZES = [
627
+ { width: 1150, height: 760 },
628
+ { width: 1550, height: 1110 },
629
+ { width: 2230, height: 1570 },
630
+ { width: 3150, height: 2230 },
631
+ { width: 4460, height: 3150 },
632
+ { width: 950, height: 750 },
633
+ { width: 1500, height: 950 },
634
+ { width: 2e3, height: 1500 },
635
+ { width: 3200, height: 2e3 },
636
+ { width: 4200, height: 3200 },
637
+ { width: 1100, height: 850 },
638
+ { width: 1400, height: 850 },
639
+ { width: 1700, height: 1100 },
640
+ { width: 990, height: 790 },
641
+ { width: 1540, height: 990 },
642
+ { width: 2060, height: 1560 },
643
+ { width: 3260, height: 2060 },
644
+ { width: 4280, height: 3280 }
645
+ ];
626
646
  function getAltiumSheetDimensions(sheetRecord) {
627
- return {
647
+ const customDimensions = {
628
648
  width: getPositiveNumber(sheetRecord?.getCaseInsensitive("CUSTOMX"), 1e3),
629
649
  height: getPositiveNumber(sheetRecord?.getCaseInsensitive("CUSTOMY"), 800)
630
650
  };
651
+ const sheetStyle = sheetRecord?.getCaseInsensitive("SHEETSTYLE");
652
+ if (sheetRecord?.getCaseInsensitive("USECUSTOMSHEET") === "T" || sheetStyle === void 0) {
653
+ return customDimensions;
654
+ }
655
+ const standardDimensions = STANDARD_SHEET_SIZES[Number(sheetStyle)];
656
+ if (!standardDimensions) {
657
+ throw new RangeError(
658
+ `Unsupported Altium schematic sheet style: ${sheetStyle}`
659
+ );
660
+ }
661
+ const isPortrait = Number(sheetRecord?.getCaseInsensitive("WORKSPACEORIENTATION") ?? 0) !== 0;
662
+ return isPortrait ? { width: standardDimensions.height, height: standardDimensions.width } : standardDimensions;
631
663
  }
632
664
 
633
665
  // lib/schematic/document/createSheetBorder.ts
@@ -650,12 +682,26 @@ function createSheetBorder(sheetRecord, scale5) {
650
682
 
651
683
  // lib/schematic/document/addSchematicSheet.ts
652
684
  function addSchematicSheet(context) {
685
+ const scaledSheetWidth = scaleLength(
686
+ context.sheetDimensions.width,
687
+ context.scale
688
+ );
689
+ const scaledSheetHeight = scaleLength(
690
+ context.sheetDimensions.height,
691
+ context.scale
692
+ );
693
+ const usesCustomSheet = context.sheetRecord?.getCaseInsensitive("USECUSTOMSHEET") === "T";
694
+ const sheetDimensions = usesCustomSheet ? {
695
+ sheet_width: scaledSheetWidth * SCHEMATIC_UNIT_TO_MILLIMETERS,
696
+ sheet_height: scaledSheetHeight * SCHEMATIC_UNIT_TO_MILLIMETERS
697
+ } : {};
653
698
  context.elements.push({
654
699
  type: "schematic_sheet",
655
700
  schematic_sheet_id: SCHEMATIC_SHEET_ID,
656
701
  name: context.options.sheetName ?? "Altium schematic",
657
702
  outline_color: "#334155",
658
- sheet_index: 0
703
+ sheet_index: 0,
704
+ ...sheetDimensions
659
705
  });
660
706
  if (context.options.includeSheetBorder === true) {
661
707
  context.elements.push(createSheetBorder(context.sheetRecord, context.scale));
@@ -5181,7 +5227,10 @@ function contourToPoints(contour) {
5181
5227
  import {
5182
5228
  AltiumFillRecord,
5183
5229
  AltiumPolygonRecord,
5184
- AltiumRegionRecord as AltiumRegionRecord3
5230
+ AltiumRegionRecord as AltiumRegionRecord3,
5231
+ getPcbContour as getPcbContour2,
5232
+ getPcbRecordPolygonIndex,
5233
+ getPcbRegionGeometry as getPcbRegionGeometry5
5185
5234
  } from "altiumts";
5186
5235
 
5187
5236
  // lib/pcb/copperAreas/convertCopperFill.ts
@@ -5216,8 +5265,13 @@ function convertCopperFill({
5216
5265
  }
5217
5266
 
5218
5267
  // lib/pcb/copperAreas/convertCopperPolygon.ts
5219
- import { getPcbContour } from "altiumts";
5268
+ import {
5269
+ getPcbContour,
5270
+ getPcbRegionGeometry as getPcbRegionGeometry3
5271
+ } from "altiumts";
5272
+ import polygonClipping from "polygon-clipping";
5220
5273
  function convertCopperPolygon({
5274
+ cutouts,
5221
5275
  polygonIndex,
5222
5276
  layerMap,
5223
5277
  record,
@@ -5227,21 +5281,45 @@ function convertCopperPolygon({
5227
5281
  if (!layer) return [];
5228
5282
  const points = contourToPoints(getPcbContour(record));
5229
5283
  if (points.length < 3) return [];
5230
- return [
5231
- {
5232
- type: "pcb_copper_pour",
5233
- pcb_copper_pour_id: `pcb_copper_pour_altium_polygon_${polygonIndex}`,
5234
- ...sourceNetId ? { source_net_id: sourceNetId } : {},
5235
- covered_with_solder_mask: true,
5236
- layer,
5237
- shape: "polygon",
5238
- points
5284
+ const cutoutPolygons = cutouts.map((cutout) => {
5285
+ const geometry = getPcbRegionGeometry3(cutout);
5286
+ return [geometry.outline, ...geometry.holes].map(
5287
+ (contour) => contourToPoints(contour).map((point) => [point.x, point.y])
5288
+ );
5289
+ });
5290
+ const copperPolygons = cutouts.length === 0 ? [[points]] : polygonClipping.difference(
5291
+ [points.map((point) => [point.x, point.y])],
5292
+ cutoutPolygons
5293
+ ).map(
5294
+ (polygon) => polygon.map(
5295
+ (ring) => ring.slice(0, -1).map(([x, y]) => ({ x, y }))
5296
+ )
5297
+ );
5298
+ return copperPolygons.flatMap(
5299
+ ([outerRing, ...holes], partIndex) => {
5300
+ if (!outerRing || outerRing.length < 3) return [];
5301
+ return [
5302
+ {
5303
+ type: "pcb_copper_pour",
5304
+ pcb_copper_pour_id: `pcb_copper_pour_altium_polygon_${polygonIndex}${partIndex === 0 ? "" : `_island_${partIndex}`}`,
5305
+ ...sourceNetId ? { source_net_id: sourceNetId } : {},
5306
+ covered_with_solder_mask: true,
5307
+ layer,
5308
+ ...holes.length === 0 ? { shape: "polygon", points: outerRing } : {
5309
+ shape: "brep",
5310
+ brep_shape: {
5311
+ outer_ring: { vertices: outerRing },
5312
+ inner_rings: holes.map((vertices) => ({ vertices }))
5313
+ }
5314
+ }
5315
+ }
5316
+ ];
5239
5317
  }
5240
- ];
5318
+ );
5241
5319
  }
5242
5320
 
5243
5321
  // lib/pcb/copperAreas/convertCopperRegion.ts
5244
- import { getPcbRegionGeometry as getPcbRegionGeometry3 } from "altiumts";
5322
+ import { getPcbRegionGeometry as getPcbRegionGeometry4 } from "altiumts";
5245
5323
  function convertCopperRegion({
5246
5324
  layerMap,
5247
5325
  record,
@@ -5253,7 +5331,7 @@ function convertCopperRegion({
5253
5331
  }
5254
5332
  const layer = layerMap.getLayer(record.layer);
5255
5333
  if (!layer) return [];
5256
- const geometry = getPcbRegionGeometry3(record);
5334
+ const geometry = getPcbRegionGeometry4(record);
5257
5335
  const outerVertices = contourToPoints(geometry.outline);
5258
5336
  if (outerVertices.length < 3) return [];
5259
5337
  const innerRings = geometry.holes.map((hole) => ({ vertices: contourToPoints(hole) })).filter((ring) => ring.vertices.length >= 3);
@@ -5286,20 +5364,97 @@ function convertCopperRegion({
5286
5364
  ];
5287
5365
  }
5288
5366
 
5367
+ // lib/pcb/copperAreas/isContourInsideContour.ts
5368
+ import { doesLineIntersectLine } from "@tscircuit/math-utils";
5369
+ function isContourInsideContour({
5370
+ innerContour,
5371
+ outerContour
5372
+ }) {
5373
+ const innerBounds = innerContour.bounds;
5374
+ const outerBounds = outerContour.bounds;
5375
+ if (!innerBounds || !outerBounds || innerBounds.minX <= outerBounds.minX || innerBounds.maxX >= outerBounds.maxX || innerBounds.minY <= outerBounds.minY || innerBounds.maxY >= outerBounds.maxY) {
5376
+ return false;
5377
+ }
5378
+ const innerPoints = innerContour.isExplicitlyClosed ? innerContour.points.slice(0, -1) : innerContour.points;
5379
+ const outerPoints = outerContour.isExplicitlyClosed ? outerContour.points.slice(0, -1) : outerContour.points;
5380
+ if (innerPoints.length < 3 || outerPoints.length < 3) return false;
5381
+ const toleranceMils = 8 * Number.EPSILON * Math.max(
5382
+ 1,
5383
+ Math.abs(outerBounds.minX),
5384
+ Math.abs(outerBounds.maxX),
5385
+ Math.abs(outerBounds.minY),
5386
+ Math.abs(outerBounds.maxY)
5387
+ );
5388
+ for (const point of innerPoints) {
5389
+ let isInside = false;
5390
+ for (let index = 0; index < outerPoints.length; index++) {
5391
+ const start = outerPoints[index];
5392
+ const end = outerPoints[(index + 1) % outerPoints.length];
5393
+ if (!start || !end) continue;
5394
+ if (start.y > point.y !== end.y > point.y && point.x < start.x + (point.y - start.y) * (end.x - start.x) / (end.y - start.y)) {
5395
+ isInside = !isInside;
5396
+ }
5397
+ }
5398
+ if (!isInside) return false;
5399
+ }
5400
+ for (let innerIndex = 0; innerIndex < innerPoints.length; innerIndex++) {
5401
+ const innerStart = innerPoints[innerIndex];
5402
+ const innerEnd = innerPoints[(innerIndex + 1) % innerPoints.length];
5403
+ if (!innerStart || !innerEnd) continue;
5404
+ for (let outerIndex = 0; outerIndex < outerPoints.length; outerIndex++) {
5405
+ const outerStart = outerPoints[outerIndex];
5406
+ const outerEnd = outerPoints[(outerIndex + 1) % outerPoints.length];
5407
+ if (!outerStart || !outerEnd) continue;
5408
+ if (doesLineIntersectLine([innerStart, innerEnd], [outerStart, outerEnd], {
5409
+ lineThickness: toleranceMils
5410
+ })) {
5411
+ return false;
5412
+ }
5413
+ }
5414
+ }
5415
+ return true;
5416
+ }
5417
+
5289
5418
  // lib/pcb/copperAreas/convertAltiumCopperAreas.ts
5290
5419
  function convertAltiumCopperAreas(document, {
5291
5420
  layerMap = new PcbCopperLayerMap(document),
5292
5421
  getSourceNetId = () => void 0
5293
5422
  } = {}) {
5294
- const polygonsWithRegions = new Set(
5295
- document.records.flatMap((record) => {
5296
- if (!(record instanceof AltiumRegionRecord3) || record.recordKind !== "Region") {
5297
- return [];
5423
+ const polygonsWithCopperRegions = /* @__PURE__ */ new Set();
5424
+ const cutoutsByPolygon = /* @__PURE__ */ new Map();
5425
+ const polygonContours = /* @__PURE__ */ new Map();
5426
+ for (const record of document.records) {
5427
+ if (!(record instanceof AltiumRegionRecord3) || record.recordKind !== "Region") {
5428
+ continue;
5429
+ }
5430
+ const polygonIndex = getPcbRecordPolygonIndex(document, record);
5431
+ const linkedPolygon = document.getPolygonForRecord(record);
5432
+ if (record.regionKind === "COPPER") {
5433
+ if (linkedPolygon) polygonsWithCopperRegions.add(linkedPolygon);
5434
+ } else if (record.regionKind === "POLYGON_CUTOUT") {
5435
+ const cutoutGeometry = getPcbRegionGeometry5(record);
5436
+ const candidatePolygons = polygonIndex === void 0 ? document.polygons : linkedPolygon ? [linkedPolygon] : [];
5437
+ const polygons = candidatePolygons.filter((polygon) => {
5438
+ if (record.layer !== polygon.layer && record.layer !== "MULTILAYER") {
5439
+ return false;
5440
+ }
5441
+ let polygonContour = polygonContours.get(polygon);
5442
+ if (!polygonContour) {
5443
+ polygonContour = getPcbContour2(polygon);
5444
+ polygonContours.set(polygon, polygonContour);
5445
+ }
5446
+ return isContourInsideContour({
5447
+ innerContour: cutoutGeometry.outline,
5448
+ outerContour: polygonContour
5449
+ });
5450
+ });
5451
+ for (const polygon of polygons) {
5452
+ const cutouts = cutoutsByPolygon.get(polygon) ?? [];
5453
+ cutouts.push(record);
5454
+ cutoutsByPolygon.set(polygon, cutouts);
5298
5455
  }
5299
- const polygon = document.getPolygonForRecord(record);
5300
- return polygon ? [polygon] : [];
5301
- })
5302
- );
5456
+ }
5457
+ }
5303
5458
  const polygonIndexes = new Map(
5304
5459
  document.polygons.map((polygon, index) => [polygon, index])
5305
5460
  );
@@ -5314,10 +5469,11 @@ function convertAltiumCopperAreas(document, {
5314
5469
  }
5315
5470
  if (record instanceof AltiumPolygonRecord) {
5316
5471
  const polygonIndex = polygonIndexes.get(record);
5317
- if (record.shelved === true || polygonIndex === void 0 || polygonsWithRegions.has(record)) {
5472
+ if (record.shelved === true || polygonIndex === void 0 || polygonsWithCopperRegions.has(record)) {
5318
5473
  return [];
5319
5474
  }
5320
5475
  return convertCopperPolygon({
5476
+ cutouts: cutoutsByPolygon.get(record) ?? [],
5321
5477
  layerMap,
5322
5478
  polygonIndex,
5323
5479
  record,
@@ -5763,12 +5919,12 @@ function convertPcbSilkscreenLine({
5763
5919
  }
5764
5920
 
5765
5921
  // lib/pcb/silkscreen/convertPcbSilkscreenRegion.ts
5766
- import { getPcbRegionGeometry as getPcbRegionGeometry4 } from "altiumts";
5922
+ import { getPcbRegionGeometry as getPcbRegionGeometry6 } from "altiumts";
5767
5923
  function convertPcbSilkscreenRegion({
5768
5924
  record,
5769
5925
  recordIndex
5770
5926
  }) {
5771
- const geometry = getPcbRegionGeometry4(record);
5927
+ const geometry = getPcbRegionGeometry6(record);
5772
5928
  const outerVertices = geometry.outline.points.map(toMillimeterPoint);
5773
5929
  if (outerVertices.length < 3) return void 0;
5774
5930
  const innerRings = geometry.holes.map((hole) => ({ vertices: hole.points.map(toMillimeterPoint) })).filter((ring) => ring.vertices.length >= 3);