@tscircuit/rectdiff 0.0.25 → 0.0.26

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
@@ -202,6 +202,7 @@ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver {
202
202
  }
203
203
  this.edgeSpatialIndex.finish();
204
204
  }
205
+ input;
205
206
  allEdges;
206
207
  unprocessedEdges = [];
207
208
  segmentsWithAdjacentEmptySpace = [];
@@ -361,6 +362,7 @@ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver2 {
361
362
  }))
362
363
  );
363
364
  }
365
+ input;
364
366
  unprocessedSegments = [];
365
367
  expandedSegments = [];
366
368
  lastSegment = null;
@@ -665,15 +667,98 @@ var GapFillSolverPipeline = class extends BasePipelineSolver {
665
667
  }
666
668
  };
667
669
 
668
- // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
669
- import {
670
- BasePipelineSolver as BasePipelineSolver2,
671
- definePipelineStep as definePipelineStep2
672
- } from "@tscircuit/solver-utils";
673
-
674
- // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
670
+ // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
675
671
  import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
676
672
 
673
+ // lib/solvers/RectDiffSeedingSolver/layers.ts
674
+ function layerSortKey(n) {
675
+ const L = n.toLowerCase();
676
+ if (L === "top") return -1e6;
677
+ if (L === "bottom") return 1e6;
678
+ const m = /^inner(\d+)$/i.exec(L);
679
+ if (m) return parseInt(m[1], 10) || 0;
680
+ return 100 + L.charCodeAt(0);
681
+ }
682
+ function canonicalizeLayerOrder(names) {
683
+ return Array.from(new Set(names)).sort((a, b) => {
684
+ const ka = layerSortKey(a);
685
+ const kb = layerSortKey(b);
686
+ if (ka !== kb) return ka - kb;
687
+ return a.localeCompare(b);
688
+ });
689
+ }
690
+ function buildZIndexMap(params) {
691
+ const names = canonicalizeLayerOrder(
692
+ (params.obstacles ?? []).flatMap((o) => o.layers ?? [])
693
+ );
694
+ const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
695
+ const fallback = Array.from(
696
+ { length: declaredLayerCount },
697
+ (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
698
+ );
699
+ const ordered = [];
700
+ const seen = /* @__PURE__ */ new Set();
701
+ const push = (n) => {
702
+ const key = n.toLowerCase();
703
+ if (seen.has(key)) return;
704
+ seen.add(key);
705
+ ordered.push(n);
706
+ };
707
+ fallback.forEach(push);
708
+ names.forEach(push);
709
+ const layerNames = ordered.slice(0, declaredLayerCount);
710
+ const clampIndex = (nameLower) => {
711
+ if (layerNames.length <= 1) return 0;
712
+ if (nameLower === "top") return 0;
713
+ if (nameLower === "bottom") return layerNames.length - 1;
714
+ const m = /^inner(\d+)$/i.exec(nameLower);
715
+ if (m) {
716
+ if (layerNames.length <= 2) return layerNames.length - 1;
717
+ const parsed = parseInt(m[1], 10);
718
+ const maxInner = layerNames.length - 2;
719
+ const clampedInner = Math.min(
720
+ maxInner,
721
+ Math.max(1, Number.isFinite(parsed) ? parsed : 1)
722
+ );
723
+ return clampedInner;
724
+ }
725
+ return 0;
726
+ };
727
+ const map = /* @__PURE__ */ new Map();
728
+ layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
729
+ ordered.slice(layerNames.length).forEach((n) => {
730
+ const key = n.toLowerCase();
731
+ map.set(key, clampIndex(key));
732
+ });
733
+ return { layerNames, zIndexByName: map };
734
+ }
735
+ function obstacleZs(ob, zIndexByName) {
736
+ if (ob.zLayers?.length)
737
+ return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
738
+ const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
739
+ return Array.from(new Set(fromNames)).sort((a, b) => a - b);
740
+ }
741
+ function obstacleToXYRect(ob) {
742
+ const w = ob.width;
743
+ const h = ob.height;
744
+ if (typeof w !== "number" || typeof h !== "number") return null;
745
+ return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
746
+ }
747
+
748
+ // lib/utils/getColorForZLayer.ts
749
+ var getColorForZLayer = (zLayers) => {
750
+ const minZ = Math.min(...zLayers);
751
+ const colors = [
752
+ { fill: "#dbeafe", stroke: "#3b82f6" },
753
+ { fill: "#fef3c7", stroke: "#f59e0b" },
754
+ { fill: "#d1fae5", stroke: "#10b981" },
755
+ { fill: "#e9d5ff", stroke: "#a855f7" },
756
+ { fill: "#fed7aa", stroke: "#f97316" },
757
+ { fill: "#fecaca", stroke: "#ef4444" }
758
+ ];
759
+ return colors[minZ % colors.length];
760
+ };
761
+
677
762
  // lib/utils/rectdiff-geometry.ts
678
763
  var EPS4 = 1e-9;
679
764
  var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
@@ -730,6 +815,235 @@ function subtractRect2D(A, B) {
730
815
  return out.filter((r) => r.width > EPS4 && r.height > EPS4);
731
816
  }
732
817
 
818
+ // lib/utils/padRect.ts
819
+ var padRect = (rect, clearance) => {
820
+ if (!clearance || clearance <= 0) return rect;
821
+ return {
822
+ x: rect.x - clearance,
823
+ y: rect.y - clearance,
824
+ width: rect.width + 2 * clearance,
825
+ height: rect.height + 2 * clearance
826
+ };
827
+ };
828
+
829
+ // lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts
830
+ var nodeToRect = (node) => ({
831
+ x: node.center.x - node.width / 2,
832
+ y: node.center.y - node.height / 2,
833
+ width: node.width,
834
+ height: node.height
835
+ });
836
+ var rectArea = (rect) => rect.width * rect.height;
837
+ var cloneNode = (node) => ({
838
+ ...node,
839
+ center: { ...node.center },
840
+ availableZ: [...node.availableZ]
841
+ });
842
+ var cloneNodeWithRect = (node, rect, capacityMeshNodeId) => ({
843
+ ...node,
844
+ capacityMeshNodeId,
845
+ center: {
846
+ x: rect.x + rect.width / 2,
847
+ y: rect.y + rect.height / 2
848
+ },
849
+ width: rect.width,
850
+ height: rect.height,
851
+ availableZ: [...node.availableZ],
852
+ layer: `z${node.availableZ.join(",")}`
853
+ });
854
+ var isFreeNode = (node) => !node._containsObstacle && !node._containsTarget;
855
+ var isSingletonOuterNode = (node, outerZ) => node.availableZ.length === 1 && node.availableZ[0] === outerZ;
856
+ var sameRect = (a, b) => Math.abs(a.x - b.x) <= EPS4 && Math.abs(a.y - b.y) <= EPS4 && Math.abs(a.width - b.width) <= EPS4 && Math.abs(a.height - b.height) <= EPS4;
857
+ var subtractRects = (target, cutters) => {
858
+ let remaining = [target];
859
+ for (const cutter of cutters) {
860
+ if (remaining.length === 0) return remaining;
861
+ const nextRemaining = [];
862
+ for (const piece of remaining) {
863
+ nextRemaining.push(...subtractRect2D(piece, cutter));
864
+ }
865
+ remaining = nextRemaining;
866
+ }
867
+ return remaining;
868
+ };
869
+ var isFullyCoveredByRects = (target, coveringRects) => {
870
+ return subtractRects(target, coveringRects).length === 0;
871
+ };
872
+ var OuterLayerContainmentMergeSolver = class extends BaseSolver3 {
873
+ constructor(input) {
874
+ super();
875
+ this.input = input;
876
+ }
877
+ input;
878
+ outputNodes = [];
879
+ promotedNodeIds = /* @__PURE__ */ new Set();
880
+ residualNodeIds = /* @__PURE__ */ new Set();
881
+ _setup() {
882
+ this.outputNodes = this.input.meshNodes.map(cloneNode);
883
+ this.promotedNodeIds.clear();
884
+ this.residualNodeIds.clear();
885
+ }
886
+ _step() {
887
+ this.outputNodes = this.processOuterLayerContainmentMerges();
888
+ this.solved = true;
889
+ }
890
+ processOuterLayerContainmentMerges() {
891
+ const srj = this.input.simpleRouteJson;
892
+ const layerCount = Math.max(1, srj.layerCount || 1);
893
+ if (layerCount < 3) {
894
+ return this.input.meshNodes.map(cloneNode);
895
+ }
896
+ const topZ = 0;
897
+ const bottomZ = layerCount - 1;
898
+ const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0);
899
+ const originalNodes = this.input.meshNodes.map(cloneNode);
900
+ const obstaclesByLayer = this.buildObstaclesByLayer(layerCount);
901
+ const mutableOuterNodes = originalNodes.filter(
902
+ (node) => isFreeNode(node) && (isSingletonOuterNode(node, topZ) || isSingletonOuterNode(node, bottomZ))
903
+ );
904
+ const immutableNodes = originalNodes.filter(
905
+ (node) => !mutableOuterNodes.includes(node)
906
+ );
907
+ const freeSupportRectsByOuterLayer = /* @__PURE__ */ new Map();
908
+ freeSupportRectsByOuterLayer.set(
909
+ topZ,
910
+ originalNodes.filter((node) => isFreeNode(node) && node.availableZ.includes(topZ)).map(nodeToRect)
911
+ );
912
+ freeSupportRectsByOuterLayer.set(
913
+ bottomZ,
914
+ originalNodes.filter((node) => isFreeNode(node) && node.availableZ.includes(bottomZ)).map(nodeToRect)
915
+ );
916
+ const promotedNodes = [];
917
+ const promotedRects = [];
918
+ const candidateNodes = mutableOuterNodes.filter(
919
+ (node) => node.width + EPS4 >= viaMinSize && node.height + EPS4 >= viaMinSize
920
+ ).sort((a, b) => rectArea(nodeToRect(b)) - rectArea(nodeToRect(a)));
921
+ for (const candidate of candidateNodes) {
922
+ const candidateZ = candidate.availableZ[0];
923
+ const oppositeZ = candidateZ === topZ ? bottomZ : topZ;
924
+ const candidateRect = nodeToRect(candidate);
925
+ const oppositeSupportRects = freeSupportRectsByOuterLayer.get(oppositeZ) ?? [];
926
+ if (!this.isTransitCompatibleAcrossIntermediateLayers({
927
+ rect: candidateRect,
928
+ fromZ: candidateZ,
929
+ toZ: oppositeZ,
930
+ obstaclesByLayer
931
+ })) {
932
+ continue;
933
+ }
934
+ if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
935
+ continue;
936
+ }
937
+ promotedNodes.push({
938
+ ...candidate,
939
+ availableZ: [topZ, bottomZ],
940
+ layer: `z${topZ},${bottomZ}`
941
+ });
942
+ promotedRects.push(candidateRect);
943
+ this.promotedNodeIds.add(candidate.capacityMeshNodeId);
944
+ }
945
+ let nextResidualId = 0;
946
+ const residualNodes = [];
947
+ for (const node of mutableOuterNodes) {
948
+ if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
949
+ continue;
950
+ }
951
+ const nodeRect = nodeToRect(node);
952
+ const remainingPieces = subtractRects(nodeRect, promotedRects);
953
+ if (remainingPieces.length === 1 && sameRect(remainingPieces[0], nodeRect)) {
954
+ residualNodes.push(node);
955
+ continue;
956
+ }
957
+ for (const piece of remainingPieces) {
958
+ const residualNode = cloneNodeWithRect(
959
+ node,
960
+ piece,
961
+ `${node.capacityMeshNodeId}-outer-merge-${nextResidualId++}`
962
+ );
963
+ residualNodes.push(residualNode);
964
+ this.residualNodeIds.add(residualNode.capacityMeshNodeId);
965
+ }
966
+ }
967
+ return [...immutableNodes, ...promotedNodes, ...residualNodes];
968
+ }
969
+ buildObstaclesByLayer(layerCount) {
970
+ const out = Array.from(
971
+ { length: layerCount },
972
+ () => []
973
+ );
974
+ for (const obstacle of this.input.simpleRouteJson.obstacles ?? []) {
975
+ const baseRect = obstacleToXYRect(obstacle);
976
+ if (!baseRect) continue;
977
+ const rect = padRect(baseRect, this.input.obstacleClearance ?? 0);
978
+ const zLayers = obstacleZs(obstacle, this.input.zIndexByName);
979
+ for (const z of zLayers) {
980
+ if (z < 0 || z >= layerCount) continue;
981
+ out[z].push({ obstacle, rect });
982
+ }
983
+ }
984
+ return out;
985
+ }
986
+ isTransitCompatibleAcrossIntermediateLayers(params) {
987
+ const { rect, fromZ, toZ, obstaclesByLayer } = params;
988
+ const lo = Math.min(fromZ, toZ);
989
+ const hi = Math.max(fromZ, toZ);
990
+ if (hi - lo < 2) return false;
991
+ for (let z = lo + 1; z < hi; z++) {
992
+ const overlapping = (obstaclesByLayer[z] ?? []).filter(
993
+ (entry) => overlaps(entry.rect, rect)
994
+ );
995
+ if (overlapping.length === 0) return false;
996
+ const nonCopperOverlap = overlapping.some(
997
+ (entry) => !entry.obstacle.isCopperPour
998
+ );
999
+ if (nonCopperOverlap) return false;
1000
+ const copperRects = overlapping.filter((entry) => entry.obstacle.isCopperPour).map((entry) => entry.rect);
1001
+ if (!isFullyCoveredByRects(rect, copperRects)) {
1002
+ return false;
1003
+ }
1004
+ }
1005
+ return true;
1006
+ }
1007
+ getOutput() {
1008
+ return { outputNodes: this.outputNodes };
1009
+ }
1010
+ visualize() {
1011
+ return {
1012
+ title: "OuterLayerContainmentMergeSolver",
1013
+ coordinateSystem: "cartesian",
1014
+ rects: this.outputNodes.map((node) => {
1015
+ const colors = getColorForZLayer(node.availableZ);
1016
+ const isPromoted = this.promotedNodeIds.has(node.capacityMeshNodeId);
1017
+ const isResidual = this.residualNodeIds.has(node.capacityMeshNodeId);
1018
+ return {
1019
+ center: node.center,
1020
+ width: node.width,
1021
+ height: node.height,
1022
+ stroke: isPromoted ? "rgba(22, 163, 74, 0.95)" : isResidual ? "rgba(37, 99, 235, 0.95)" : colors.stroke,
1023
+ fill: node._containsObstacle ? "rgba(239, 68, 68, 0.35)" : isPromoted ? "rgba(34, 197, 94, 0.28)" : isResidual ? "rgba(59, 130, 246, 0.18)" : colors.fill,
1024
+ layer: `z${node.availableZ.join(",")}`,
1025
+ label: [
1026
+ `node ${node.capacityMeshNodeId}`,
1027
+ `z:${node.availableZ.join(",")}`
1028
+ ].join("\n")
1029
+ };
1030
+ }),
1031
+ points: [],
1032
+ lines: [],
1033
+ texts: []
1034
+ };
1035
+ }
1036
+ };
1037
+
1038
+ // lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts
1039
+ import {
1040
+ BasePipelineSolver as BasePipelineSolver2,
1041
+ definePipelineStep as definePipelineStep2
1042
+ } from "@tscircuit/solver-utils";
1043
+
1044
+ // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1045
+ import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
1046
+
733
1047
  // lib/solvers/RectDiffSeedingSolver/isPointInPolygon.ts
734
1048
  function isPointInPolygon(p, polygon) {
735
1049
  let inside = false;
@@ -836,81 +1150,6 @@ function computeInverseRects(bounds, polygon) {
836
1150
  return mergedVertical;
837
1151
  }
838
1152
 
839
- // lib/solvers/RectDiffSeedingSolver/layers.ts
840
- function layerSortKey(n) {
841
- const L = n.toLowerCase();
842
- if (L === "top") return -1e6;
843
- if (L === "bottom") return 1e6;
844
- const m = /^inner(\d+)$/i.exec(L);
845
- if (m) return parseInt(m[1], 10) || 0;
846
- return 100 + L.charCodeAt(0);
847
- }
848
- function canonicalizeLayerOrder(names) {
849
- return Array.from(new Set(names)).sort((a, b) => {
850
- const ka = layerSortKey(a);
851
- const kb = layerSortKey(b);
852
- if (ka !== kb) return ka - kb;
853
- return a.localeCompare(b);
854
- });
855
- }
856
- function buildZIndexMap(params) {
857
- const names = canonicalizeLayerOrder(
858
- (params.obstacles ?? []).flatMap((o) => o.layers ?? [])
859
- );
860
- const declaredLayerCount = Math.max(1, params.layerCount || names.length || 1);
861
- const fallback = Array.from(
862
- { length: declaredLayerCount },
863
- (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
864
- );
865
- const ordered = [];
866
- const seen = /* @__PURE__ */ new Set();
867
- const push = (n) => {
868
- const key = n.toLowerCase();
869
- if (seen.has(key)) return;
870
- seen.add(key);
871
- ordered.push(n);
872
- };
873
- fallback.forEach(push);
874
- names.forEach(push);
875
- const layerNames = ordered.slice(0, declaredLayerCount);
876
- const clampIndex = (nameLower) => {
877
- if (layerNames.length <= 1) return 0;
878
- if (nameLower === "top") return 0;
879
- if (nameLower === "bottom") return layerNames.length - 1;
880
- const m = /^inner(\d+)$/i.exec(nameLower);
881
- if (m) {
882
- if (layerNames.length <= 2) return layerNames.length - 1;
883
- const parsed = parseInt(m[1], 10);
884
- const maxInner = layerNames.length - 2;
885
- const clampedInner = Math.min(
886
- maxInner,
887
- Math.max(1, Number.isFinite(parsed) ? parsed : 1)
888
- );
889
- return clampedInner;
890
- }
891
- return 0;
892
- };
893
- const map = /* @__PURE__ */ new Map();
894
- layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
895
- ordered.slice(layerNames.length).forEach((n) => {
896
- const key = n.toLowerCase();
897
- map.set(key, clampIndex(key));
898
- });
899
- return { layerNames, zIndexByName: map };
900
- }
901
- function obstacleZs(ob, zIndexByName) {
902
- if (ob.zLayers?.length)
903
- return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
904
- const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
905
- return Array.from(new Set(fromNames)).sort((a, b) => a - b);
906
- }
907
- function obstacleToXYRect(ob) {
908
- const w = ob.width;
909
- const h = ob.height;
910
- if (typeof w !== "number" || typeof h !== "number") return null;
911
- return { x: ob.center.x - w / 2, y: ob.center.y - h / 2, width: w, height: h };
912
- }
913
-
914
1153
  // lib/utils/isSelfRect.ts
915
1154
  var EPS5 = 1e-9;
916
1155
  var isSelfRect = (params) => Math.abs(params.rect.x + params.rect.width / 2 - params.startX) < EPS5 && Math.abs(params.rect.y + params.rect.height / 2 - params.startY) < EPS5 && Math.abs(params.rect.width - params.initialW) < EPS5 && Math.abs(params.rect.height - params.initialH) < EPS5;
@@ -1676,7 +1915,7 @@ function resizeSoftOverlaps(params, newIndex) {
1676
1915
  }
1677
1916
  }
1678
1917
  }
1679
- const sameRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
1918
+ const sameRect2 = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
1680
1919
  removeIdx.sort((a, b) => b - a).forEach((idx) => {
1681
1920
  const rem = params.placed.splice(idx, 1)[0];
1682
1921
  if (params.placedIndexByLayer) {
@@ -1685,7 +1924,7 @@ function resizeSoftOverlaps(params, newIndex) {
1685
1924
  if (tree)
1686
1925
  tree.remove(
1687
1926
  rectToTree(rem.rect, { zLayers: rem.zLayers }),
1688
- sameRect
1927
+ sameRect2
1689
1928
  );
1690
1929
  }
1691
1930
  }
@@ -1703,27 +1942,14 @@ function resizeSoftOverlaps(params, newIndex) {
1703
1942
  }
1704
1943
  }
1705
1944
 
1706
- // lib/utils/getColorForZLayer.ts
1707
- var getColorForZLayer = (zLayers) => {
1708
- const minZ = Math.min(...zLayers);
1709
- const colors = [
1710
- { fill: "#dbeafe", stroke: "#3b82f6" },
1711
- { fill: "#fef3c7", stroke: "#f59e0b" },
1712
- { fill: "#d1fae5", stroke: "#10b981" },
1713
- { fill: "#e9d5ff", stroke: "#a855f7" },
1714
- { fill: "#fed7aa", stroke: "#f97316" },
1715
- { fill: "#fecaca", stroke: "#ef4444" }
1716
- ];
1717
- return colors[minZ % colors.length];
1718
- };
1719
-
1720
1945
  // lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts
1721
1946
  import RBush3 from "rbush";
1722
- var RectDiffSeedingSolver = class extends BaseSolver3 {
1947
+ var RectDiffSeedingSolver = class extends BaseSolver4 {
1723
1948
  constructor(input) {
1724
1949
  super();
1725
1950
  this.input = input;
1726
1951
  }
1952
+ input;
1727
1953
  // Engine fields (mirrors initState / engine.ts)
1728
1954
  srj;
1729
1955
  layerNames;
@@ -2077,7 +2303,7 @@ z:${placement.zLayers.join(",")}`
2077
2303
  };
2078
2304
 
2079
2305
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2080
- import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
2306
+ import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
2081
2307
 
2082
2308
  // lib/utils/finalizeRects.ts
2083
2309
  function finalizeRects(params) {
@@ -2149,11 +2375,12 @@ import RBush4 from "rbush";
2149
2375
  var sameTreeRect = (a, b) => a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
2150
2376
 
2151
2377
  // lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts
2152
- var RectDiffExpansionSolver = class extends BaseSolver4 {
2378
+ var RectDiffExpansionSolver = class extends BaseSolver5 {
2153
2379
  constructor(input) {
2154
2380
  super();
2155
2381
  this.input = input;
2156
2382
  }
2383
+ input;
2157
2384
  placedIndexByLayer = [];
2158
2385
  _meshNodes = [];
2159
2386
  _setup() {
@@ -2294,19 +2521,6 @@ import "rbush";
2294
2521
 
2295
2522
  // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2296
2523
  import RBush5 from "rbush";
2297
-
2298
- // lib/utils/padRect.ts
2299
- var padRect = (rect, clearance) => {
2300
- if (!clearance || clearance <= 0) return rect;
2301
- return {
2302
- x: rect.x - clearance,
2303
- y: rect.y - clearance,
2304
- width: rect.width + 2 * clearance,
2305
- height: rect.height + 2 * clearance
2306
- };
2307
- };
2308
-
2309
- // lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts
2310
2524
  var buildObstacleIndexesByLayer = (params) => {
2311
2525
  const { srj, boardVoidRects, obstacleClearance } = params;
2312
2526
  const { layerNames, zIndexByName } = buildZIndexMap({
@@ -2650,6 +2864,7 @@ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
2650
2864
  var RectDiffPipeline = class extends BasePipelineSolver3 {
2651
2865
  rectDiffGridSolverPipeline;
2652
2866
  gapFillSolver;
2867
+ outerLayerContainmentMergeSolver;
2653
2868
  boardVoidRects;
2654
2869
  zIndexByName;
2655
2870
  layerNames;
@@ -2685,6 +2900,18 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2685
2900
  }
2686
2901
  }
2687
2902
  ]
2903
+ ),
2904
+ definePipelineStep3(
2905
+ "outerLayerContainmentMergeSolver",
2906
+ OuterLayerContainmentMergeSolver,
2907
+ (rectDiffPipeline) => [
2908
+ {
2909
+ meshNodes: rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ?? rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput().meshNodes ?? [],
2910
+ simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
2911
+ zIndexByName: rectDiffPipeline.zIndexByName ?? /* @__PURE__ */ new Map(),
2912
+ obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance
2913
+ }
2914
+ ]
2688
2915
  )
2689
2916
  ];
2690
2917
  _setup() {
@@ -2710,6 +2937,10 @@ var RectDiffPipeline = class extends BasePipelineSolver3 {
2710
2937
  return [this.inputProblem];
2711
2938
  }
2712
2939
  getOutput() {
2940
+ const outerLayerMergeOutput = this.outerLayerContainmentMergeSolver?.getOutput();
2941
+ if (outerLayerMergeOutput) {
2942
+ return { meshNodes: outerLayerMergeOutput.outputNodes };
2943
+ }
2713
2944
  const gapFillOutput = this.gapFillSolver?.getOutput();
2714
2945
  if (gapFillOutput) {
2715
2946
  return { meshNodes: gapFillOutput.outputNodes };
@@ -8,6 +8,7 @@ import type { GridFill3DOptions, XYRect } from "./rectdiff-types"
8
8
  import type { CapacityMeshNode } from "./types/capacity-mesh-types"
9
9
  import type { GraphicsObject } from "graphics-debug"
10
10
  import { GapFillSolverPipeline } from "./solvers/GapFillSolver/GapFillSolverPipeline"
11
+ import { OuterLayerContainmentMergeSolver } from "./solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver"
11
12
  import { RectDiffGridSolverPipeline } from "./solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline"
12
13
  import { createBaseVisualization } from "./rectdiff-visualization"
13
14
  import { buildFinalRectDiffVisualization } from "./buildFinalRectDiffVisualization"
@@ -25,6 +26,7 @@ export interface RectDiffPipelineInput {
25
26
  export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
26
27
  rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline
27
28
  gapFillSolver?: GapFillSolverPipeline
29
+ outerLayerContainmentMergeSolver?: OuterLayerContainmentMergeSolver
28
30
  boardVoidRects: XYRect[] | undefined
29
31
  zIndexByName?: Map<string, number>
30
32
  layerNames?: string[]
@@ -69,6 +71,22 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
69
71
  },
70
72
  ],
71
73
  ),
74
+ definePipelineStep(
75
+ "outerLayerContainmentMergeSolver",
76
+ OuterLayerContainmentMergeSolver,
77
+ (rectDiffPipeline: RectDiffPipeline) => [
78
+ {
79
+ meshNodes:
80
+ rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ??
81
+ rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput()
82
+ .meshNodes ??
83
+ [],
84
+ simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
85
+ zIndexByName: rectDiffPipeline.zIndexByName ?? new Map(),
86
+ obstacleClearance: rectDiffPipeline.inputProblem.obstacleClearance,
87
+ },
88
+ ],
89
+ ),
72
90
  ]
73
91
 
74
92
  override _setup(): void {
@@ -100,6 +118,11 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
100
118
  }
101
119
 
102
120
  override getOutput(): { meshNodes: CapacityMeshNode[] } {
121
+ const outerLayerMergeOutput =
122
+ this.outerLayerContainmentMergeSolver?.getOutput()
123
+ if (outerLayerMergeOutput) {
124
+ return { meshNodes: outerLayerMergeOutput.outputNodes }
125
+ }
103
126
  const gapFillOutput = this.gapFillSolver?.getOutput()
104
127
  if (gapFillOutput) {
105
128
  return { meshNodes: gapFillOutput.outputNodes }