calculate-packing 0.0.70 → 0.0.72
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.d.ts +11 -1
- package/dist/index.js +267 -94
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ interface OutputPad extends InputPad {
|
|
|
29
29
|
y: number;
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
interface ComponentCourtyard {
|
|
33
|
+
offsetFromCenter: {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
};
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}
|
|
32
40
|
interface InputComponent {
|
|
33
41
|
componentId: string;
|
|
34
42
|
/** Components marked as static are not moved by the packer */
|
|
@@ -45,6 +53,8 @@ interface InputComponent {
|
|
|
45
53
|
/** Preconfigured rotation (degrees CCW) for static components */
|
|
46
54
|
ccwRotationOffset?: number;
|
|
47
55
|
pads: InputPad[];
|
|
56
|
+
/** Optional courtyard defining the component's physical boundary */
|
|
57
|
+
courtyard?: ComponentCourtyard;
|
|
48
58
|
}
|
|
49
59
|
interface PackedComponent extends InputComponent {
|
|
50
60
|
center: {
|
|
@@ -588,4 +598,4 @@ declare class PackSolver2 extends BaseSolver$1 {
|
|
|
588
598
|
};
|
|
589
599
|
}
|
|
590
600
|
|
|
591
|
-
export { type ComponentId, type GlobalBounds, type InputComponent, type InputObstacle, type InputPad, LargestRectOutsideOutlineFromPointSolver, type NetworkId, type OutputPad, type PackInput, type PackOutput, type PackPlacementStrategy, PackSolver2, type PackedComponent, type PadId, type Point$1 as Point, type Rect, convertCircuitJsonToPackOutput, convertPackOutputToPackInput, getGraphicsFromPackOutput, pack };
|
|
601
|
+
export { type ComponentCourtyard, type ComponentId, type GlobalBounds, type InputComponent, type InputObstacle, type InputPad, LargestRectOutsideOutlineFromPointSolver, type NetworkId, type OutputPad, type PackInput, type PackOutput, type PackPlacementStrategy, PackSolver2, type PackedComponent, type PadId, type Point$1 as Point, type Rect, convertCircuitJsonToPackOutput, convertPackOutputToPackInput, getGraphicsFromPackOutput, pack };
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,30 @@ var combineBounds = (bounds) => {
|
|
|
68
68
|
return { minX, minY, maxX, maxY };
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
// lib/geometry/expandRotatedRectIntoBounds.ts
|
|
72
|
+
function expandRotatedRectIntoBounds(opts) {
|
|
73
|
+
const { bounds, center, width, height, angleRad } = opts;
|
|
74
|
+
const tx = opts.translate?.x ?? 0;
|
|
75
|
+
const ty = opts.translate?.y ?? 0;
|
|
76
|
+
const hw = width / 2;
|
|
77
|
+
const hh = height / 2;
|
|
78
|
+
const corners = [
|
|
79
|
+
{ x: center.x - hw, y: center.y - hh },
|
|
80
|
+
{ x: center.x + hw, y: center.y - hh },
|
|
81
|
+
{ x: center.x + hw, y: center.y + hh },
|
|
82
|
+
{ x: center.x - hw, y: center.y + hh }
|
|
83
|
+
];
|
|
84
|
+
for (const corner of corners) {
|
|
85
|
+
const rotated = rotatePoint(corner, angleRad);
|
|
86
|
+
const x = rotated.x + tx;
|
|
87
|
+
const y = rotated.y + ty;
|
|
88
|
+
bounds.minX = Math.min(bounds.minX, x);
|
|
89
|
+
bounds.maxX = Math.max(bounds.maxX, x);
|
|
90
|
+
bounds.minY = Math.min(bounds.minY, y);
|
|
91
|
+
bounds.maxY = Math.max(bounds.maxY, y);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
71
95
|
// lib/geometry/getComponentBounds.ts
|
|
72
96
|
var getComponentBounds = (component, minGap = 0) => {
|
|
73
97
|
const bounds = {
|
|
@@ -76,28 +100,27 @@ var getComponentBounds = (component, minGap = 0) => {
|
|
|
76
100
|
minY: Infinity,
|
|
77
101
|
maxY: -Infinity
|
|
78
102
|
};
|
|
79
|
-
component.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
localCorners.forEach((corner) => {
|
|
89
|
-
const world = rotatePoint(
|
|
90
|
-
corner,
|
|
91
|
-
component.ccwRotationOffset * Math.PI / 180
|
|
92
|
-
);
|
|
93
|
-
const x = world.x + component.center.x;
|
|
94
|
-
const y = world.y + component.center.y;
|
|
95
|
-
bounds.minX = Math.min(bounds.minX, x);
|
|
96
|
-
bounds.maxX = Math.max(bounds.maxX, x);
|
|
97
|
-
bounds.minY = Math.min(bounds.minY, y);
|
|
98
|
-
bounds.maxY = Math.max(bounds.maxY, y);
|
|
103
|
+
const angleRad = component.ccwRotationOffset * Math.PI / 180;
|
|
104
|
+
for (const pad of component.pads) {
|
|
105
|
+
expandRotatedRectIntoBounds({
|
|
106
|
+
bounds,
|
|
107
|
+
center: pad.offset,
|
|
108
|
+
width: pad.size.x,
|
|
109
|
+
height: pad.size.y,
|
|
110
|
+
angleRad,
|
|
111
|
+
translate: component.center
|
|
99
112
|
});
|
|
100
|
-
}
|
|
113
|
+
}
|
|
114
|
+
if (component.courtyard) {
|
|
115
|
+
expandRotatedRectIntoBounds({
|
|
116
|
+
bounds,
|
|
117
|
+
center: component.courtyard.offsetFromCenter,
|
|
118
|
+
width: component.courtyard.width,
|
|
119
|
+
height: component.courtyard.height,
|
|
120
|
+
angleRad,
|
|
121
|
+
translate: component.center
|
|
122
|
+
});
|
|
123
|
+
}
|
|
101
124
|
return {
|
|
102
125
|
minX: bounds.minX - minGap,
|
|
103
126
|
maxX: bounds.maxX + minGap,
|
|
@@ -251,6 +274,50 @@ function parseFlattenPolygonSegments(polygon) {
|
|
|
251
274
|
}
|
|
252
275
|
|
|
253
276
|
// lib/constructOutlinesFromPackedComponents.ts
|
|
277
|
+
var createCourtyardPolygon = (opts) => {
|
|
278
|
+
const { component, courtyard, minGap } = opts;
|
|
279
|
+
const hw = courtyard.width / 2 + minGap;
|
|
280
|
+
const hh = courtyard.height / 2 + minGap;
|
|
281
|
+
const localCorners = [
|
|
282
|
+
{
|
|
283
|
+
x: courtyard.offsetFromCenter.x - hw,
|
|
284
|
+
y: courtyard.offsetFromCenter.y - hh
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
x: courtyard.offsetFromCenter.x + hw,
|
|
288
|
+
y: courtyard.offsetFromCenter.y - hh
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
x: courtyard.offsetFromCenter.x + hw,
|
|
292
|
+
y: courtyard.offsetFromCenter.y + hh
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
x: courtyard.offsetFromCenter.x - hw,
|
|
296
|
+
y: courtyard.offsetFromCenter.y + hh
|
|
297
|
+
}
|
|
298
|
+
];
|
|
299
|
+
const worldCorners = localCorners.map((corner) => {
|
|
300
|
+
const rotated = rotatePoint(
|
|
301
|
+
corner,
|
|
302
|
+
component.ccwRotationOffset * Math.PI / 180
|
|
303
|
+
);
|
|
304
|
+
return {
|
|
305
|
+
x: rotated.x + component.center.x,
|
|
306
|
+
y: rotated.y + component.center.y
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
const arr = worldCorners.map(({ x, y }) => [x, y]);
|
|
310
|
+
const poly = new Flatten.Polygon(arr);
|
|
311
|
+
const xs = worldCorners.map((p) => p.x);
|
|
312
|
+
const ys = worldCorners.map((p) => p.y);
|
|
313
|
+
const bbox = {
|
|
314
|
+
minX: Math.min(...xs),
|
|
315
|
+
minY: Math.min(...ys),
|
|
316
|
+
maxX: Math.max(...xs),
|
|
317
|
+
maxY: Math.max(...ys)
|
|
318
|
+
};
|
|
319
|
+
return { poly, bbox };
|
|
320
|
+
};
|
|
254
321
|
var createPadPolygons = (component, minGap) => {
|
|
255
322
|
return component.pads.map((pad) => {
|
|
256
323
|
const hw = pad.size.x / 2 + minGap;
|
|
@@ -322,8 +389,18 @@ var constructOutlinesFromPackedComponents = (components, opts = {}) => {
|
|
|
322
389
|
const bounds = combineBounds([...componentBounds, ...obstacleBounds]);
|
|
323
390
|
const allPadShapes = [];
|
|
324
391
|
for (const component of components) {
|
|
325
|
-
|
|
326
|
-
|
|
392
|
+
if (component.courtyard) {
|
|
393
|
+
allPadShapes.push(
|
|
394
|
+
createCourtyardPolygon({
|
|
395
|
+
component,
|
|
396
|
+
courtyard: component.courtyard,
|
|
397
|
+
minGap
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
} else {
|
|
401
|
+
const padShapes = createPadPolygons(component, minGap);
|
|
402
|
+
allPadShapes.push(...padShapes);
|
|
403
|
+
}
|
|
327
404
|
}
|
|
328
405
|
const obstacleShapes = createObstaclePolygons(obstacles, minGap);
|
|
329
406
|
allPadShapes.push(...obstacleShapes);
|
|
@@ -1363,31 +1440,26 @@ var getInputComponentBounds = (component, { rotationDegrees = 0 }) => {
|
|
|
1363
1440
|
minY: Infinity,
|
|
1364
1441
|
maxY: -Infinity
|
|
1365
1442
|
};
|
|
1443
|
+
const angleRad = rotationDegrees * Math.PI / 180;
|
|
1366
1444
|
for (const pad of component.pads) {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
];
|
|
1375
|
-
for (const corner of localCorners) {
|
|
1376
|
-
const world = rotatePoint(corner, rotationDegrees * Math.PI / 180);
|
|
1377
|
-
const x = world.x;
|
|
1378
|
-
const y = world.y;
|
|
1379
|
-
bounds.minX = Math.min(bounds.minX, x);
|
|
1380
|
-
bounds.maxX = Math.max(bounds.maxX, x);
|
|
1381
|
-
bounds.minY = Math.min(bounds.minY, y);
|
|
1382
|
-
bounds.maxY = Math.max(bounds.maxY, y);
|
|
1383
|
-
}
|
|
1445
|
+
expandRotatedRectIntoBounds({
|
|
1446
|
+
bounds,
|
|
1447
|
+
center: pad.offset,
|
|
1448
|
+
width: pad.size.x,
|
|
1449
|
+
height: pad.size.y,
|
|
1450
|
+
angleRad
|
|
1451
|
+
});
|
|
1384
1452
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1453
|
+
if (component.courtyard) {
|
|
1454
|
+
expandRotatedRectIntoBounds({
|
|
1455
|
+
bounds,
|
|
1456
|
+
center: component.courtyard.offsetFromCenter,
|
|
1457
|
+
width: component.courtyard.width,
|
|
1458
|
+
height: component.courtyard.height,
|
|
1459
|
+
angleRad
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
return bounds;
|
|
1391
1463
|
};
|
|
1392
1464
|
|
|
1393
1465
|
// lib/math/expandSegment.ts
|
|
@@ -1757,7 +1829,8 @@ var OutlineSegmentCandidatePointSolver = class extends BaseSolver3 {
|
|
|
1757
1829
|
y: center.y + rotatedOffset.y
|
|
1758
1830
|
}
|
|
1759
1831
|
};
|
|
1760
|
-
})
|
|
1832
|
+
}),
|
|
1833
|
+
courtyard: this.componentToPack.courtyard
|
|
1761
1834
|
};
|
|
1762
1835
|
}
|
|
1763
1836
|
/**
|
|
@@ -2114,28 +2187,54 @@ var getGraphicsFromPackOutput = (packOutput) => {
|
|
|
2114
2187
|
|
|
2115
2188
|
// lib/PackSolver2/checkOverlapWithPackedComponents.ts
|
|
2116
2189
|
import { computeDistanceBetweenBoxes } from "@tscircuit/math-utils";
|
|
2190
|
+
|
|
2191
|
+
// lib/PackSolver2/getComponentCollisionBoxes.ts
|
|
2192
|
+
function getComponentCollisionBoxes(component) {
|
|
2193
|
+
if (component.courtyard) {
|
|
2194
|
+
const courtyard = component.courtyard;
|
|
2195
|
+
const angleRad = component.ccwRotationOffset * Math.PI / 180;
|
|
2196
|
+
const rotatedOffset = rotatePoint(courtyard.offsetFromCenter, angleRad);
|
|
2197
|
+
const normalizedDeg = (component.ccwRotationOffset % 360 + 360) % 360;
|
|
2198
|
+
const swapDims = normalizedDeg === 90 || normalizedDeg === 270;
|
|
2199
|
+
let width = courtyard.width;
|
|
2200
|
+
let height = courtyard.height;
|
|
2201
|
+
if (swapDims) {
|
|
2202
|
+
width = courtyard.height;
|
|
2203
|
+
height = courtyard.width;
|
|
2204
|
+
}
|
|
2205
|
+
return [
|
|
2206
|
+
{
|
|
2207
|
+
center: {
|
|
2208
|
+
x: component.center.x + rotatedOffset.x,
|
|
2209
|
+
y: component.center.y + rotatedOffset.y
|
|
2210
|
+
},
|
|
2211
|
+
width,
|
|
2212
|
+
height
|
|
2213
|
+
}
|
|
2214
|
+
];
|
|
2215
|
+
}
|
|
2216
|
+
return component.pads.map((p) => ({
|
|
2217
|
+
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
2218
|
+
width: p.size.x,
|
|
2219
|
+
height: p.size.y
|
|
2220
|
+
}));
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// lib/PackSolver2/checkOverlapWithPackedComponents.ts
|
|
2117
2224
|
function checkOverlapWithPackedComponents({
|
|
2118
2225
|
component,
|
|
2119
2226
|
packedComponents,
|
|
2120
2227
|
minGap
|
|
2121
2228
|
}) {
|
|
2122
|
-
const
|
|
2123
|
-
(c) => c
|
|
2124
|
-
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
2125
|
-
width: p.size.x,
|
|
2126
|
-
height: p.size.y
|
|
2127
|
-
}))
|
|
2229
|
+
const allPackedBoxes = packedComponents.flatMap(
|
|
2230
|
+
(c) => getComponentCollisionBoxes(c)
|
|
2128
2231
|
);
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
height: p.size.y
|
|
2133
|
-
}));
|
|
2134
|
-
for (const newComponentPadBox of newComponentPadBoxes) {
|
|
2135
|
-
for (const packedPadBox of allPackedPadBoxes) {
|
|
2232
|
+
const newComponentBoxes = getComponentCollisionBoxes(component);
|
|
2233
|
+
for (const newBox of newComponentBoxes) {
|
|
2234
|
+
for (const packedBox of allPackedBoxes) {
|
|
2136
2235
|
const { distance: boxDist } = computeDistanceBetweenBoxes(
|
|
2137
|
-
|
|
2138
|
-
|
|
2236
|
+
newBox,
|
|
2237
|
+
packedBox
|
|
2139
2238
|
);
|
|
2140
2239
|
if (boxDist + 1e-6 < minGap) {
|
|
2141
2240
|
return {
|
|
@@ -2216,19 +2315,15 @@ var SingleComponentPackSolver = class extends BaseSolver4 {
|
|
|
2216
2315
|
const position = { x: 0, y: 0 };
|
|
2217
2316
|
const rotation = availableRotations2[0] ?? 0;
|
|
2218
2317
|
const candidate = this.createPackedComponent(position, rotation);
|
|
2318
|
+
const candidateBoxes = getComponentCollisionBoxes(candidate);
|
|
2219
2319
|
const tooCloseToObstacles = (this.obstacles ?? []).some((obs) => {
|
|
2220
2320
|
const obsBox = {
|
|
2221
2321
|
center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
|
|
2222
2322
|
width: obs.width,
|
|
2223
2323
|
height: obs.height
|
|
2224
2324
|
};
|
|
2225
|
-
return
|
|
2226
|
-
const
|
|
2227
|
-
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
2228
|
-
width: p.size.x,
|
|
2229
|
-
height: p.size.y
|
|
2230
|
-
};
|
|
2231
|
-
const { distance } = computeDistanceBetweenBoxes2(padBox, obsBox);
|
|
2325
|
+
return candidateBoxes.some((box) => {
|
|
2326
|
+
const { distance } = computeDistanceBetweenBoxes2(box, obsBox);
|
|
2232
2327
|
return distance + 1e-6 < this.minGap;
|
|
2233
2328
|
});
|
|
2234
2329
|
});
|
|
@@ -2333,19 +2428,15 @@ var SingleComponentPackSolver = class extends BaseSolver4 {
|
|
|
2333
2428
|
minGap: this.minGap
|
|
2334
2429
|
});
|
|
2335
2430
|
let minObstacleGapDistance = Infinity;
|
|
2431
|
+
const candidateCollisionBoxes = getComponentCollisionBoxes(candidateComponent);
|
|
2336
2432
|
const tooCloseToObstacles = (this.obstacles ?? []).some((obs) => {
|
|
2337
2433
|
const obsBox = {
|
|
2338
2434
|
center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
|
|
2339
2435
|
width: obs.width,
|
|
2340
2436
|
height: obs.height
|
|
2341
2437
|
};
|
|
2342
|
-
return
|
|
2343
|
-
const
|
|
2344
|
-
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
2345
|
-
width: p.size.x,
|
|
2346
|
-
height: p.size.y
|
|
2347
|
-
};
|
|
2348
|
-
const { distance: distance2 } = computeDistanceBetweenBoxes2(padBox, obsBox);
|
|
2438
|
+
return candidateCollisionBoxes.some((box) => {
|
|
2439
|
+
const { distance: distance2 } = computeDistanceBetweenBoxes2(box, obsBox);
|
|
2349
2440
|
minObstacleGapDistance = Math.min(minObstacleGapDistance, distance2);
|
|
2350
2441
|
return distance2 + 1e-6 < this.minGap;
|
|
2351
2442
|
});
|
|
@@ -2795,19 +2886,15 @@ var PackSolver2 = class extends BaseSolver5 {
|
|
|
2795
2886
|
};
|
|
2796
2887
|
setPackedComponentPadCenters(newPackedComponent);
|
|
2797
2888
|
const obstacles = this.packInput.obstacles ?? [];
|
|
2889
|
+
const newComponentBoxes = getComponentCollisionBoxes(newPackedComponent);
|
|
2798
2890
|
const tooCloseToObstacles = obstacles.some((obs) => {
|
|
2799
2891
|
const obsBox = {
|
|
2800
2892
|
center: { x: obs.absoluteCenter.x, y: obs.absoluteCenter.y },
|
|
2801
2893
|
width: obs.width,
|
|
2802
2894
|
height: obs.height
|
|
2803
2895
|
};
|
|
2804
|
-
return
|
|
2805
|
-
const
|
|
2806
|
-
center: { x: p.absoluteCenter.x, y: p.absoluteCenter.y },
|
|
2807
|
-
width: p.size.x,
|
|
2808
|
-
height: p.size.y
|
|
2809
|
-
};
|
|
2810
|
-
const { distance } = computeDistanceBetweenBoxes3(padBox, obsBox);
|
|
2896
|
+
return newComponentBoxes.some((box) => {
|
|
2897
|
+
const { distance } = computeDistanceBetweenBoxes3(box, obsBox);
|
|
2811
2898
|
return distance + 1e-6 < this.packInput.minGap;
|
|
2812
2899
|
});
|
|
2813
2900
|
});
|
|
@@ -3202,10 +3289,94 @@ var getElementOutsideTree = (db, tree) => {
|
|
|
3202
3289
|
outside.push(ph);
|
|
3203
3290
|
}
|
|
3204
3291
|
}
|
|
3292
|
+
for (const hole of db.pcb_hole.list({})) {
|
|
3293
|
+
outside.push(hole);
|
|
3294
|
+
}
|
|
3205
3295
|
return outside;
|
|
3206
3296
|
};
|
|
3207
3297
|
|
|
3298
|
+
// lib/plumbing/getObstacleFromElement.ts
|
|
3299
|
+
var getObstacleFromElement = (element) => {
|
|
3300
|
+
if (element.type === "pcb_plated_hole" && element.shape === "circular_hole_with_rect_pad") {
|
|
3301
|
+
const { rect_pad_height, rect_pad_width, x, y } = element;
|
|
3302
|
+
return {
|
|
3303
|
+
obstacleId: element.pcb_plated_hole_id,
|
|
3304
|
+
absoluteCenter: { x, y },
|
|
3305
|
+
width: rect_pad_width,
|
|
3306
|
+
height: rect_pad_height
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
if (element.type === "pcb_hole") {
|
|
3310
|
+
const { x, y, pcb_hole_id } = element;
|
|
3311
|
+
const width = "hole_diameter" in element ? element.hole_diameter : element.hole_width;
|
|
3312
|
+
const height = "hole_diameter" in element ? element.hole_diameter : element.hole_height;
|
|
3313
|
+
return {
|
|
3314
|
+
obstacleId: pcb_hole_id,
|
|
3315
|
+
absoluteCenter: { x, y },
|
|
3316
|
+
width,
|
|
3317
|
+
height
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
return void 0;
|
|
3321
|
+
};
|
|
3322
|
+
|
|
3208
3323
|
// lib/plumbing/convertCircuitJsonToPackOutput.ts
|
|
3324
|
+
var extractCourtyardForComponent = (opts) => {
|
|
3325
|
+
const { db, pcbComponentIds, componentCenter } = opts;
|
|
3326
|
+
const idSet = new Set(pcbComponentIds);
|
|
3327
|
+
let minX = Infinity;
|
|
3328
|
+
let minY = Infinity;
|
|
3329
|
+
let maxX = -Infinity;
|
|
3330
|
+
let maxY = -Infinity;
|
|
3331
|
+
let found = false;
|
|
3332
|
+
for (const rect of db.pcb_courtyard_rect.list()) {
|
|
3333
|
+
if (!idSet.has(rect.pcb_component_id)) continue;
|
|
3334
|
+
minX = Math.min(minX, rect.center.x - rect.width / 2);
|
|
3335
|
+
maxX = Math.max(maxX, rect.center.x + rect.width / 2);
|
|
3336
|
+
minY = Math.min(minY, rect.center.y - rect.height / 2);
|
|
3337
|
+
maxY = Math.max(maxY, rect.center.y + rect.height / 2);
|
|
3338
|
+
found = true;
|
|
3339
|
+
}
|
|
3340
|
+
for (const polygon of db.pcb_courtyard_polygon.list()) {
|
|
3341
|
+
if (!idSet.has(polygon.pcb_component_id)) continue;
|
|
3342
|
+
for (const pt of polygon.points) {
|
|
3343
|
+
minX = Math.min(minX, pt.x);
|
|
3344
|
+
maxX = Math.max(maxX, pt.x);
|
|
3345
|
+
minY = Math.min(minY, pt.y);
|
|
3346
|
+
maxY = Math.max(maxY, pt.y);
|
|
3347
|
+
}
|
|
3348
|
+
found = true;
|
|
3349
|
+
}
|
|
3350
|
+
for (const outline of db.pcb_courtyard_outline.list()) {
|
|
3351
|
+
if (!idSet.has(outline.pcb_component_id)) continue;
|
|
3352
|
+
for (const pt of outline.outline) {
|
|
3353
|
+
minX = Math.min(minX, pt.x);
|
|
3354
|
+
maxX = Math.max(maxX, pt.x);
|
|
3355
|
+
minY = Math.min(minY, pt.y);
|
|
3356
|
+
maxY = Math.max(maxY, pt.y);
|
|
3357
|
+
}
|
|
3358
|
+
found = true;
|
|
3359
|
+
}
|
|
3360
|
+
for (const circle of db.pcb_courtyard_circle.list()) {
|
|
3361
|
+
if (!idSet.has(circle.pcb_component_id)) continue;
|
|
3362
|
+
minX = Math.min(minX, circle.center.x - circle.radius);
|
|
3363
|
+
maxX = Math.max(maxX, circle.center.x + circle.radius);
|
|
3364
|
+
minY = Math.min(minY, circle.center.y - circle.radius);
|
|
3365
|
+
maxY = Math.max(maxY, circle.center.y + circle.radius);
|
|
3366
|
+
found = true;
|
|
3367
|
+
}
|
|
3368
|
+
if (!found) return void 0;
|
|
3369
|
+
const courtyardCenterX = (minX + maxX) / 2;
|
|
3370
|
+
const courtyardCenterY = (minY + maxY) / 2;
|
|
3371
|
+
return {
|
|
3372
|
+
offsetFromCenter: {
|
|
3373
|
+
x: courtyardCenterX - componentCenter.x,
|
|
3374
|
+
y: courtyardCenterY - componentCenter.y
|
|
3375
|
+
},
|
|
3376
|
+
width: maxX - minX,
|
|
3377
|
+
height: maxY - minY
|
|
3378
|
+
};
|
|
3379
|
+
};
|
|
3209
3380
|
var buildPackedComponent = (pcbComponents, componentId, db, getNetworkId, shouldAddInnerObstacles, sourcePortToPadIds = /* @__PURE__ */ new Map(), chipMarginsMap = {}, isStatic = false) => {
|
|
3210
3381
|
const padInfos = pcbComponents.flatMap((pc) => {
|
|
3211
3382
|
const pads2 = extractPadInfos(pc, db, getNetworkId);
|
|
@@ -3264,12 +3435,18 @@ var buildPackedComponent = (pcbComponents, componentId, db, getNetworkId, should
|
|
|
3264
3435
|
};
|
|
3265
3436
|
pads.push(innerPad);
|
|
3266
3437
|
}
|
|
3438
|
+
const courtyard = extractCourtyardForComponent({
|
|
3439
|
+
db,
|
|
3440
|
+
pcbComponentIds: pcbComponents.map((pc) => pc.pcb_component_id),
|
|
3441
|
+
componentCenter: center
|
|
3442
|
+
});
|
|
3267
3443
|
return {
|
|
3268
3444
|
componentId,
|
|
3269
3445
|
isStatic,
|
|
3270
3446
|
center,
|
|
3271
3447
|
ccwRotationOffset: 0,
|
|
3272
|
-
pads
|
|
3448
|
+
pads,
|
|
3449
|
+
courtyard
|
|
3273
3450
|
};
|
|
3274
3451
|
};
|
|
3275
3452
|
var collectPcbComponents = (node, db) => {
|
|
@@ -3297,7 +3474,7 @@ var convertCircuitJsonToPackOutput = (circuitJson, opts = {}) => {
|
|
|
3297
3474
|
const pcbBoard = circuitJson.find(
|
|
3298
3475
|
(item) => item.type === "pcb_board"
|
|
3299
3476
|
);
|
|
3300
|
-
if (pcbBoard
|
|
3477
|
+
if (pcbBoard?.outline) {
|
|
3301
3478
|
packOutput.boundaryOutline = pcbBoard.outline;
|
|
3302
3479
|
}
|
|
3303
3480
|
const getNetworkId = (pcbPortId) => {
|
|
@@ -3401,14 +3578,9 @@ var convertCircuitJsonToPackOutput = (circuitJson, opts = {}) => {
|
|
|
3401
3578
|
});
|
|
3402
3579
|
}
|
|
3403
3580
|
for (const element of elementsOutsideTree) {
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
packOutput.obstacles.push(
|
|
3407
|
-
obstacleId: element.pcb_plated_hole_id,
|
|
3408
|
-
absoluteCenter: { x, y },
|
|
3409
|
-
width: rect_pad_width,
|
|
3410
|
-
height: rect_pad_height
|
|
3411
|
-
});
|
|
3581
|
+
const obstacle = getObstacleFromElement(element);
|
|
3582
|
+
if (obstacle) {
|
|
3583
|
+
packOutput.obstacles.push(obstacle);
|
|
3412
3584
|
}
|
|
3413
3585
|
}
|
|
3414
3586
|
const weightedConnections = [];
|
|
@@ -3451,7 +3623,8 @@ var convertPackOutputToPackInput = (packed) => {
|
|
|
3451
3623
|
componentId: pc.componentId,
|
|
3452
3624
|
availableRotationDegrees: pc.availableRotationDegrees,
|
|
3453
3625
|
// Preserve rotation constraints
|
|
3454
|
-
pads: pc.pads.map(({ absoluteCenter: _ac, ...rest }) => rest)
|
|
3626
|
+
pads: pc.pads.map(({ absoluteCenter: _ac, ...rest }) => rest),
|
|
3627
|
+
courtyard: pc.courtyard
|
|
3455
3628
|
}
|
|
3456
3629
|
}));
|
|
3457
3630
|
return {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "calculate-packing",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.72",
|
|
6
6
|
"description": "Calculate a packing layout with support for different strategy configurations",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "cosmos",
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
"@biomejs/biome": "^2.1.1",
|
|
20
20
|
"@flatten-js/core": "^1.6.2",
|
|
21
21
|
"@react-hook/resize-observer": "^2.0.2",
|
|
22
|
-
"@tscircuit/circuit-json-util": "^0.0.
|
|
22
|
+
"@tscircuit/circuit-json-util": "^0.0.94",
|
|
23
23
|
"@tscircuit/footprinter": "^0.0.203",
|
|
24
|
-
"@tscircuit/math-utils": "^0.0.
|
|
24
|
+
"@tscircuit/math-utils": "^0.0.36",
|
|
25
25
|
"@tscircuit/solver-utils": "^0.0.14",
|
|
26
26
|
"@types/bun": "latest",
|
|
27
27
|
"@types/react": "^19.1.8",
|
|
28
28
|
"@types/react-dom": "^19.1.6",
|
|
29
29
|
"@vitejs/plugin-react": "^5.0.0",
|
|
30
30
|
"bun-match-svg": "^0.0.12",
|
|
31
|
-
"circuit-json": "^0.0.
|
|
31
|
+
"circuit-json": "^0.0.421",
|
|
32
32
|
"framer-motion": "^12.23.12",
|
|
33
33
|
"graphics-debug": "^0.0.78",
|
|
34
34
|
"react": "^19.1.0",
|