@mark1russell7/packing 0.1.5 → 0.1.7
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/build/index.d.ts +37 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +68 -26
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/check.ts +27 -14
- package/src/contract.ts +2 -0
- package/src/layout-input.ts +2 -0
- package/src/measures.ts +33 -15
- package/src/strategy.ts +6 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/packing",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"description": "Rectangle packing: shelf, guillotine and skyline strategies, push cascades and measures of a layout.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"rectangle-packing",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@mark1russell7/cue": "^1.0.2",
|
|
53
|
+
"@mark1russell7/layout-contract": "github:mark1russell7/layout-contract#710ec519027dbc122d111cc5e76a1c18877f4237&path:packages/layout-contract",
|
|
53
54
|
"@types/node": "^26.6.4",
|
|
54
55
|
"tsdown": "^0.23.1",
|
|
55
56
|
"typescript": "^7.0.2",
|
package/src/check.ts
CHANGED
|
@@ -26,11 +26,16 @@ function overlap(a: Box, b: Box, tolerance: number): boolean {
|
|
|
26
26
|
* This function counts the breaks of the 4 hard rules of the packer for an output.
|
|
27
27
|
*
|
|
28
28
|
* - `no-overlap`: each pair of items with their margins, and each pair of an item and an obstacle, that overlap by more than `tolerance.overlap`.
|
|
29
|
-
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`.
|
|
30
|
-
*
|
|
31
|
-
* - `
|
|
29
|
+
* - `in-container`: each item with its margin that goes out of a fixed side by more than `tolerance.overlap`. The inset of
|
|
30
|
+
* the container makes each fixed side smaller (decision 3 of `docs/PACKER.md`).
|
|
31
|
+
* - `locked-kept`: each locked item that moved by more than `RECORD_EPS`, or that has no placement.
|
|
32
|
+
* - `complete`: each item with no placement that is not in the overflow. A locked item breaks `locked-kept` in its place.
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
+
* A locked item with no placement stays at its input position for `no-overlap` and `in-container`. A locked item with no
|
|
35
|
+
* `x` or no `y` in the input is not locked for these rules.
|
|
36
|
+
*
|
|
37
|
+
* The rules read a shape of the container, of an obstacle or of an item as its bounding box (decision 5). The bounding box
|
|
38
|
+
* of an item shape is relative to the position of the item. The result has no tolerance in it.
|
|
34
39
|
*/
|
|
35
40
|
export function checkHard(input: PackInput, output: PackOutput, tolerance: Pick<Tolerance, "overlap"> = DEFAULT_TOLERANCE): HardBreaks {
|
|
36
41
|
const tol = tolerance.overlap;
|
|
@@ -41,18 +46,25 @@ export function checkHard(input: PackInput, output: PackOutput, tolerance: Pick<
|
|
|
41
46
|
|
|
42
47
|
const boxes: Box[] = [];
|
|
43
48
|
for (const item of input.items) {
|
|
44
|
-
const
|
|
49
|
+
const lockedAt = item.locked === true && item.x !== undefined && item.y !== undefined ? { x: item.x, y: item.y } : undefined;
|
|
50
|
+
let p: Pick<PackOutput["placements"][number], "x" | "y" | "turn"> | undefined = placements.get(item.id);
|
|
45
51
|
if (p === undefined) {
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
if (lockedAt === undefined) {
|
|
53
|
+
if (!overflow.has(item.id)) breaks.complete += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// A locked item with no placement breaks locked-kept, and it stays at its input position for the other rules
|
|
57
|
+
breaks["locked-kept"] += 1;
|
|
58
|
+
p = lockedAt;
|
|
59
|
+
} else if (lockedAt !== undefined) {
|
|
60
|
+
if (Math.abs(p.x - lockedAt.x) > RECORD_EPS || Math.abs(p.y - lockedAt.y) > RECORD_EPS) breaks["locked-kept"] += 1;
|
|
52
61
|
}
|
|
53
62
|
const placed: Item = p.turn === 90 ? turnItem(item) : item;
|
|
54
63
|
const m = marginOf(placed);
|
|
55
|
-
|
|
64
|
+
const area = placed.shape === undefined ? { x: 0, y: 0, w: placed.w, h: placed.h } : boundsOf(placed.shape);
|
|
65
|
+
const l = p.x + area.x;
|
|
66
|
+
const t = p.y + area.y;
|
|
67
|
+
boxes.push({ l: l - m.left, t: t - m.top, r: l + area.w + m.right, b: t + area.h + m.bottom });
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
const moved = new Map(output.moved.map((m) => [m.id, m]));
|
|
@@ -73,11 +85,12 @@ export function checkHard(input: PackInput, output: PackOutput, tolerance: Pick<
|
|
|
73
85
|
const y = shape?.y ?? 0;
|
|
74
86
|
const w = shape?.w ?? container.w ?? Infinity;
|
|
75
87
|
const h = shape?.h ?? container.h ?? Infinity;
|
|
88
|
+
const inset = container.inset ?? 0;
|
|
76
89
|
const fixedW = container.fixed === "size" || container.fixed === "width";
|
|
77
90
|
const fixedH = container.fixed === "size" || container.fixed === "height";
|
|
78
91
|
for (const box of boxes) {
|
|
79
|
-
const outX = fixedW && (box.l < x - tol || box.r > x + w + tol);
|
|
80
|
-
const outY = fixedH && (box.t < y - tol || box.b > y + h + tol);
|
|
92
|
+
const outX = fixedW && (box.l < x + inset - tol || box.r > x + w - inset + tol);
|
|
93
|
+
const outY = fixedH && (box.t < y + inset - tol || box.b > y + h - inset + tol);
|
|
81
94
|
if (outX || outY) breaks["in-container"] += 1;
|
|
82
95
|
}
|
|
83
96
|
return breaks;
|
package/src/contract.ts
CHANGED
|
@@ -72,6 +72,8 @@ export interface Item {
|
|
|
72
72
|
margin?: number | Margin;
|
|
73
73
|
/** The ports. The packer keeps them, and turns them with the item. */
|
|
74
74
|
ports?: Port[];
|
|
75
|
+
/** The free data of the item. The packer does not read it, and the turn and the converters keep it. */
|
|
76
|
+
data?: Readonly<Record<string, unknown>>;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
/** The input of a packing: a `layout-input/1` file without `edges`, with `items` for `boxes`. */
|
package/src/layout-input.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface Edge {
|
|
|
11
11
|
target: string;
|
|
12
12
|
sourcePort?: string;
|
|
13
13
|
targetPort?: string;
|
|
14
|
+
/** The free data of the edge. The packer does not read it. */
|
|
15
|
+
data?: Readonly<Record<string, unknown>>;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/** The parameters of a generator: a name and a string or a number for each parameter. */
|
package/src/measures.ts
CHANGED
|
@@ -23,6 +23,20 @@ export interface MeasureSpec {
|
|
|
23
23
|
min: number;
|
|
24
24
|
/** The highest value. The value `Infinity` tells that the measure has no upper limit. */
|
|
25
25
|
max: number;
|
|
26
|
+
/**
|
|
27
|
+
* The floor is the best value of a guard measure. The floor rule of `docs/PROMOTION.md` of `highways` reads it. Each
|
|
28
|
+
* guard of the packer has the floor 0. A measure without a floor has no floor rule.
|
|
29
|
+
*/
|
|
30
|
+
floor?: number;
|
|
31
|
+
/**
|
|
32
|
+
* The tolerance is the absolute tolerance at the floor. A candidate within the tolerance ties with a default at the floor.
|
|
33
|
+
*
|
|
34
|
+
* - `pack.order.inversions` and `pack.overflow` have the tolerance 0. Thus each new inversion and each new unplaced item is a loss.
|
|
35
|
+
* - `pack.aspectError` has 0.01, approximately 1% of the aspect. An exact tiling gives the default an error of 0.
|
|
36
|
+
* - `pack.stability.mean` has 0.005 container widths, and `pack.stability.q95` has 0.01. The default often moves no item.
|
|
37
|
+
* - `pack.stability.moved` has 0.02: 2% of the items that stay. On a small change, the default often moves no item.
|
|
38
|
+
*/
|
|
39
|
+
tol?: number;
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
/** The measures of one layout, by id. `measure` gives them. The values are not rounded. */
|
|
@@ -56,29 +70,33 @@ export interface MeasureOptions {
|
|
|
56
70
|
rowTolerance?: number;
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
const spec = (
|
|
60
|
-
id,
|
|
61
|
-
family,
|
|
62
|
-
unit,
|
|
63
|
-
direction,
|
|
64
|
-
min,
|
|
65
|
-
max,
|
|
66
|
-
}
|
|
73
|
+
const spec = (
|
|
74
|
+
id: MeasureId,
|
|
75
|
+
family: MeasureFamily,
|
|
76
|
+
unit: string,
|
|
77
|
+
direction: Direction,
|
|
78
|
+
min: number,
|
|
79
|
+
max: number,
|
|
80
|
+
guard?: { floor: number; tol: number },
|
|
81
|
+
): MeasureSpec => ({ id, family, unit, direction, min, max, ...guard });
|
|
67
82
|
|
|
68
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* The facts of each measure, by id. `docs/MEASURES.md` gives the full definitions. This record is the source of the floor
|
|
85
|
+
* and the tolerance of each guard of the packer.
|
|
86
|
+
*/
|
|
69
87
|
export const MEASURE_SPECS: Readonly<Record<MeasureId, MeasureSpec>> = Object.freeze({
|
|
70
88
|
"pack.area": spec("pack.area", "density", "square units", "lower", 0, Infinity),
|
|
71
89
|
"pack.fill": spec("pack.fill", "density", "share", "higher", 0, 1),
|
|
72
90
|
"pack.whitespace": spec("pack.whitespace", "density", "share", "lower", 0, 1),
|
|
73
91
|
"pack.aspect": spec("pack.aspect", "aspect", "ratio", "lower", 1, Infinity),
|
|
74
|
-
"pack.aspectError": spec("pack.aspectError", "aspect", "natural log", "lower", 0, Infinity),
|
|
75
|
-
"pack.order.inversions": spec("pack.order.inversions", "order", "share", "lower", 0, 1),
|
|
92
|
+
"pack.aspectError": spec("pack.aspectError", "aspect", "natural log", "lower", 0, Infinity, { floor: 0, tol: 0.01 }),
|
|
93
|
+
"pack.order.inversions": spec("pack.order.inversions", "order", "share", "lower", 0, 1, { floor: 0, tol: 0 }),
|
|
76
94
|
"pack.order.breaks": spec("pack.order.breaks", "order", "count", "lower", 0, Infinity),
|
|
77
95
|
"pack.raggedness": spec("pack.raggedness", "raggedness", "share", "lower", 0, 1),
|
|
78
|
-
"pack.overflow": spec("pack.overflow", "overflow", "count", "lower", 0, Infinity),
|
|
79
|
-
"pack.stability.moved": spec("pack.stability.moved", "stability", "share", "lower", 0, 1),
|
|
80
|
-
"pack.stability.mean": spec("pack.stability.mean", "stability", "container widths", "lower", 0, Infinity),
|
|
81
|
-
"pack.stability.q95": spec("pack.stability.q95", "stability", "container widths", "lower", 0, Infinity),
|
|
96
|
+
"pack.overflow": spec("pack.overflow", "overflow", "count", "lower", 0, Infinity, { floor: 0, tol: 0 }),
|
|
97
|
+
"pack.stability.moved": spec("pack.stability.moved", "stability", "share", "lower", 0, 1, { floor: 0, tol: 0.02 }),
|
|
98
|
+
"pack.stability.mean": spec("pack.stability.mean", "stability", "container widths", "lower", 0, Infinity, { floor: 0, tol: 0.005 }),
|
|
99
|
+
"pack.stability.q95": spec("pack.stability.q95", "stability", "container widths", "lower", 0, Infinity, { floor: 0, tol: 0.01 }),
|
|
82
100
|
"pack.deterministic": spec("pack.deterministic", "determinism", "boolean", "higher", 0, 1),
|
|
83
101
|
"pack.search.widths": spec("pack.search.widths", "speed", "count", "lower", 0, Infinity),
|
|
84
102
|
"pack.search.ms": spec("pack.search.ms", "speed", "milliseconds", "lower", 0, Infinity),
|
package/src/strategy.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Container, PackInput, PackOutput, Region } from "./contract.ts";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* For each dimension of the taxonomy, the values that a strategy supports. `docs/research/TAXONOMY.md` gives the values.
|
|
5
|
+
* The type is an alias, not an interface. Thus it fits the record `Supports` of the contract.
|
|
6
|
+
*/
|
|
7
|
+
export type Supports = {
|
|
5
8
|
containerSize: ("fixed" | "freeHeight" | "freeWidth" | "freeAspect" | "freeArea")[];
|
|
6
9
|
containerShape: ("rect" | "convex" | "polygon" | "circle" | "ellipse" | "holes")[];
|
|
7
10
|
/** The movement of the obstacles. An empty list supports only an input with no obstacle and no locked item. */
|
|
@@ -11,7 +14,7 @@ export interface Supports {
|
|
|
11
14
|
order: ("free" | "soft" | "hard")[];
|
|
12
15
|
groups: ("none" | "flat" | "nested")[];
|
|
13
16
|
mode: ("offline" | "online" | "incremental")[];
|
|
14
|
-
}
|
|
17
|
+
};
|
|
15
18
|
|
|
16
19
|
/** A dimension of `Supports`. */
|
|
17
20
|
export type Dimension = keyof Supports;
|