kisch 1.0.3 → 1.0.5
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/README.md +2 -0
- package/package.json +5 -2
- package/src/app/exports.js +1 -0
- package/src/{kisch-cli.js → app/kisch-cli.js} +25 -9
- package/src/schematic/CompoundSymbol.js +36 -0
- package/src/{Entity.js → schematic/Entity.js} +149 -19
- package/src/{LibrarySymbol.js → schematic/LibrarySymbol.js} +37 -2
- package/src/{Schematic.js → schematic/Schematic.js} +210 -81
- package/src/{SymbolLibrary.js → schematic/SymbolLibrary.js} +1 -1
- package/src/utils/RoutingGrid.js +260 -0
- package/src/utils/astar.js +113 -0
- package/src/{cartesian-math.js → utils/cartesian-math.js} +44 -3
- package/src/utils/grid-util.js +39 -0
- package/src/utils/js-util.js +33 -0
- package/src/{node-util.js → utils/node-util.js} +5 -0
- package/src/js-util.js +0 -15
- package/src/manhattan-router.js +0 -176
- /package/src/{place-rect.js → utils/place-rect.js} +0 -0
- /package/src/{sexp.js → utils/sexp.js} +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export function astar({
|
|
2
|
+
start,
|
|
3
|
+
goal,
|
|
4
|
+
isGoal,
|
|
5
|
+
neighbours,
|
|
6
|
+
cost,
|
|
7
|
+
heuristic = () => 0,
|
|
8
|
+
key,
|
|
9
|
+
stats
|
|
10
|
+
}) {
|
|
11
|
+
if (!key) throw new Error("key(n) is required");
|
|
12
|
+
if (!stats)
|
|
13
|
+
stats={};
|
|
14
|
+
|
|
15
|
+
// Goal resolution
|
|
16
|
+
const goalKey = goal !== undefined ? key(goal) : null;
|
|
17
|
+
const isGoalFn =
|
|
18
|
+
isGoal ||
|
|
19
|
+
((n) => {
|
|
20
|
+
if (goalKey === null) throw new Error("Provide goal or isGoal");
|
|
21
|
+
return key(n) === goalKey;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Open set (priority queue via simple array for clarity)
|
|
25
|
+
const open = [];
|
|
26
|
+
const push = (node, f) => {
|
|
27
|
+
open.push({ node, f });
|
|
28
|
+
};
|
|
29
|
+
const pop = () => {
|
|
30
|
+
// naive priority queue (can replace with binary heap later)
|
|
31
|
+
let bestIdx = 0;
|
|
32
|
+
for (let i = 1; i < open.length; i++) {
|
|
33
|
+
if (open[i].f < open[bestIdx].f) bestIdx = i;
|
|
34
|
+
}
|
|
35
|
+
const item = open[bestIdx];
|
|
36
|
+
open.splice(bestIdx, 1);
|
|
37
|
+
return item.node;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Caches
|
|
41
|
+
const gScore = new Map(); // key(n) -> best cost
|
|
42
|
+
const cameFrom = new Map(); // key(n) -> previous key
|
|
43
|
+
const nodeCache = new Map(); // key(n) -> node (for reconstruction)
|
|
44
|
+
|
|
45
|
+
// Optional memoization (cheap + safe)
|
|
46
|
+
const heuristicCache = new Map(); // key(n) -> h(n)
|
|
47
|
+
const costCache = new Map(); // "k1|k2" -> cost
|
|
48
|
+
|
|
49
|
+
const getHeuristic = (n) => {
|
|
50
|
+
const k = key(n);
|
|
51
|
+
if (heuristicCache.has(k)) return heuristicCache.get(k);
|
|
52
|
+
const h = heuristic(n);
|
|
53
|
+
heuristicCache.set(k, h);
|
|
54
|
+
return h;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const getCost = (a, b) => {
|
|
58
|
+
const ka = key(a);
|
|
59
|
+
const kb = key(b);
|
|
60
|
+
const ck = ka + "|" + kb;
|
|
61
|
+
if (costCache.has(ck)) return costCache.get(ck);
|
|
62
|
+
const c = cost(a, b);
|
|
63
|
+
costCache.set(ck, c);
|
|
64
|
+
return c;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const startKey = key(start);
|
|
68
|
+
gScore.set(startKey, 0);
|
|
69
|
+
nodeCache.set(startKey, start);
|
|
70
|
+
|
|
71
|
+
push(start, getHeuristic(start));
|
|
72
|
+
|
|
73
|
+
stats.steps=0;
|
|
74
|
+
|
|
75
|
+
while (open.length > 0) {
|
|
76
|
+
stats.steps++;
|
|
77
|
+
const current = pop();
|
|
78
|
+
const ck = key(current);
|
|
79
|
+
|
|
80
|
+
if (isGoalFn(current)) {
|
|
81
|
+
return reconstructPath(cameFrom, nodeCache, ck);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const next of neighbours(current)) {
|
|
85
|
+
const nk = key(next);
|
|
86
|
+
nodeCache.set(nk, next);
|
|
87
|
+
|
|
88
|
+
const tentative =
|
|
89
|
+
gScore.get(ck) + getCost(current, next);
|
|
90
|
+
|
|
91
|
+
if (!gScore.has(nk) || tentative < gScore.get(nk)) {
|
|
92
|
+
cameFrom.set(nk, ck);
|
|
93
|
+
gScore.set(nk, tentative);
|
|
94
|
+
|
|
95
|
+
const f = tentative + getHeuristic(next);
|
|
96
|
+
push(next, f);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null; // no path
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Helper
|
|
105
|
+
function reconstructPath(cameFrom, nodeCache, currentKey) {
|
|
106
|
+
const path = [];
|
|
107
|
+
while (currentKey) {
|
|
108
|
+
path.push(nodeCache.get(currentKey));
|
|
109
|
+
currentKey = cameFrom.get(currentKey);
|
|
110
|
+
}
|
|
111
|
+
path.reverse();
|
|
112
|
+
return path;
|
|
113
|
+
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
export class Point extends Array {
|
|
2
2
|
constructor(...a) {
|
|
3
|
-
if (a
|
|
3
|
+
if (a.length==1 &&
|
|
4
|
+
!Array.isArray(a[0]) &&
|
|
5
|
+
Object.hasOwn(a[0],"x") &&
|
|
6
|
+
Object.hasOwn(a[0],"y"))
|
|
7
|
+
super(a[0].x,a[0].y);
|
|
8
|
+
|
|
9
|
+
else if (a)
|
|
4
10
|
super(...[a].flat(Infinity));
|
|
5
11
|
|
|
6
12
|
else
|
|
7
13
|
super();
|
|
14
|
+
|
|
15
|
+
if (this.length && (isNaN(this[0]) || isNaN(this[1])))
|
|
16
|
+
throw new Error("NaN point: l="+this.length+" from: "+a);
|
|
8
17
|
}
|
|
9
18
|
|
|
10
19
|
add(p) {
|
|
@@ -15,6 +24,8 @@ export class Point extends Array {
|
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
sub(p) {
|
|
27
|
+
//console.log("sub ",this,p);
|
|
28
|
+
|
|
18
29
|
return Point.from([
|
|
19
30
|
this[0]-p[0],
|
|
20
31
|
this[1]-p[1]
|
|
@@ -27,10 +38,26 @@ export class Point extends Array {
|
|
|
27
38
|
|
|
28
39
|
snap(gridSize) {
|
|
29
40
|
return new Point([
|
|
30
|
-
Math.round(this[0]/gridSize)*gridSize,
|
|
31
|
-
Math.round(this[1]/gridSize)*gridSize,
|
|
41
|
+
Number((Math.round(this[0]/gridSize)*gridSize).toFixed(2)),
|
|
42
|
+
Number((Math.round(this[1]/gridSize)*gridSize).toFixed(2)),
|
|
32
43
|
]);
|
|
33
44
|
}
|
|
45
|
+
|
|
46
|
+
len() {
|
|
47
|
+
return Math.sqrt(this[0]*this[0]+this[1]*this[1]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
rotateDegrees(deg) {
|
|
51
|
+
const rad = deg * Math.PI / 180;
|
|
52
|
+
|
|
53
|
+
const cos = Math.cos(rad);
|
|
54
|
+
const sin = Math.sin(rad);
|
|
55
|
+
|
|
56
|
+
return new Point(
|
|
57
|
+
this[0] * cos - this[1] * sin,
|
|
58
|
+
this[0] * sin + this[1] * cos
|
|
59
|
+
);
|
|
60
|
+
}
|
|
34
61
|
}
|
|
35
62
|
|
|
36
63
|
export class Rect {
|
|
@@ -40,9 +67,14 @@ export class Rect {
|
|
|
40
67
|
}
|
|
41
68
|
|
|
42
69
|
static fromCorners(p1, p2) {
|
|
70
|
+
//console.log("p1 ",p1);
|
|
71
|
+
//console.log("p2 ",p2);
|
|
72
|
+
|
|
43
73
|
p1=new Point(p1);
|
|
44
74
|
p2=new Point(p2);
|
|
45
75
|
|
|
76
|
+
//console.log("created...");
|
|
77
|
+
|
|
46
78
|
return new Rect(p1,p2.sub(p1));
|
|
47
79
|
}
|
|
48
80
|
|
|
@@ -91,6 +123,15 @@ export class Rect {
|
|
|
91
123
|
return Rect.fromCorners([left,top],[right,bottom]);
|
|
92
124
|
}
|
|
93
125
|
|
|
126
|
+
includePoint(p) {
|
|
127
|
+
let left=Math.min(this.getLeft(),p[0]);
|
|
128
|
+
let top=Math.min(this.getTop(),p[1]);
|
|
129
|
+
let right=Math.max(this.getRight(),p[0]);
|
|
130
|
+
let bottom=Math.max(this.getBottom(),p[1]);
|
|
131
|
+
|
|
132
|
+
return Rect.fromCorners([left,top],[right,bottom]);
|
|
133
|
+
}
|
|
134
|
+
|
|
94
135
|
overlaps(other) {
|
|
95
136
|
return !(
|
|
96
137
|
this.getRight() <= other.getLeft() ||
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function collapsePath(path) {
|
|
2
|
+
if (!path || path.length < 2) return path;
|
|
3
|
+
|
|
4
|
+
const result = [path[0]];
|
|
5
|
+
|
|
6
|
+
let prevDir = null;
|
|
7
|
+
|
|
8
|
+
for (let i = 1; i < path.length - 1; i++) {
|
|
9
|
+
const prev = path[i - 1];
|
|
10
|
+
const cur = path[i];
|
|
11
|
+
const next = path[i + 1];
|
|
12
|
+
|
|
13
|
+
const dir1 = {
|
|
14
|
+
x: cur.x - prev.x,
|
|
15
|
+
y: cur.y - prev.y,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const dir2 = {
|
|
19
|
+
x: next.x - cur.x,
|
|
20
|
+
y: next.y - cur.y,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sameDirection =
|
|
24
|
+
dir1.x === dir2.x && dir1.y === dir2.y;
|
|
25
|
+
|
|
26
|
+
// keep only if direction changes
|
|
27
|
+
if (!sameDirection) {
|
|
28
|
+
result.push(cur);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
result.push(path[path.length - 1]);
|
|
33
|
+
|
|
34
|
+
return result.map(p=>({x: p.x, y: p.y}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function manhattanDist(x1, y1, x2, y2) {
|
|
38
|
+
return (Math.abs(x2-x1)+Math.abs(y2-y1));
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class DeclaredError extends Error {
|
|
2
|
+
constructor(...args) {
|
|
3
|
+
super(...args);
|
|
4
|
+
this.declared=true;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function arrayUnique(array) {
|
|
9
|
+
let result=new Set();
|
|
10
|
+
|
|
11
|
+
for (let item of array)
|
|
12
|
+
result.add(item);
|
|
13
|
+
|
|
14
|
+
return Array.from(result);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function arrayGetMinIndex(array) {
|
|
18
|
+
let min;
|
|
19
|
+
for (let k of Object.keys(array))
|
|
20
|
+
if (min===undefined || Number(k)<min)
|
|
21
|
+
min=Number(k);
|
|
22
|
+
|
|
23
|
+
return min;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function arrayGetMaxIndex(array) {
|
|
27
|
+
let max;
|
|
28
|
+
for (let k of Object.keys(array))
|
|
29
|
+
if (max===undefined || Number(k)>max)
|
|
30
|
+
max=Number(k);
|
|
31
|
+
|
|
32
|
+
return max;
|
|
33
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {spawn} from 'node:child_process';
|
|
2
|
+
import {fileURLToPath} from 'url';
|
|
2
3
|
|
|
3
4
|
export function runCommand(cmd, args = []) {
|
|
4
5
|
return new Promise((resolve, reject) => {
|
|
@@ -15,3 +16,7 @@ export function runCommand(cmd, args = []) {
|
|
|
15
16
|
child.on('error', reject);
|
|
16
17
|
});
|
|
17
18
|
}
|
|
19
|
+
|
|
20
|
+
export function dirnameFromImportMeta(meta) {
|
|
21
|
+
return fileURLToPath(new URL('.', meta.url));
|
|
22
|
+
}
|
package/src/js-util.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export class DeclaredError extends Error {
|
|
2
|
-
constructor(...args) {
|
|
3
|
-
super(...args);
|
|
4
|
-
this.declared=true;
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function arrayUnique(array) {
|
|
9
|
-
let result=new Set();
|
|
10
|
-
|
|
11
|
-
for (let item of array)
|
|
12
|
-
result.add(item);
|
|
13
|
-
|
|
14
|
-
return Array.from(result);
|
|
15
|
-
}
|
package/src/manhattan-router.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { Point, pointKey } from "../src/cartesian-math.js";
|
|
2
|
-
|
|
3
|
-
function manhattan(a, b) {
|
|
4
|
-
return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function rangeOverlap(a1, a2, b1, b2) {
|
|
8
|
-
const minA = Math.min(a1, a2);
|
|
9
|
-
const maxA = Math.max(a1, a2);
|
|
10
|
-
const minB = Math.min(b1, b2);
|
|
11
|
-
const maxB = Math.max(b1, b2);
|
|
12
|
-
return maxA > minB && maxB > minA;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* ───────────────────────── Rect blocking (unchanged) ───────────────────────── */
|
|
16
|
-
|
|
17
|
-
export function segmentBlocked(a, b, rects) {
|
|
18
|
-
const ax = a[0], ay = a[1];
|
|
19
|
-
const bx = b[0], by = b[1];
|
|
20
|
-
|
|
21
|
-
for (const r of rects) {
|
|
22
|
-
const rx = r.corner[0];
|
|
23
|
-
const ry = r.corner[1];
|
|
24
|
-
const rw = r.size[0];
|
|
25
|
-
const rh = r.size[1];
|
|
26
|
-
|
|
27
|
-
// horizontal
|
|
28
|
-
if (ay === by) {
|
|
29
|
-
if (
|
|
30
|
-
ay >= ry && ay <= ry + rh &&
|
|
31
|
-
rangeOverlap(ax, bx, rx, rx + rw)
|
|
32
|
-
) return true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// vertical
|
|
36
|
-
if (ax === bx) {
|
|
37
|
-
if (
|
|
38
|
-
ax >= rx && ax <= rx + rw &&
|
|
39
|
-
rangeOverlap(ay, by, ry, ry + rh)
|
|
40
|
-
) return true;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* ───────────────────────── Line blocking (NEW) ───────────────────────── */
|
|
48
|
-
|
|
49
|
-
function similar(a,b) {
|
|
50
|
-
return Math.abs(a-b)<0.0000001
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function segmentOverlapsLine(a1, a2, b1, b2) {
|
|
54
|
-
// horizontal
|
|
55
|
-
// if (a1[1] === a2[1] && b1[1] === b2[1] && a1[1] === b1[1]) {
|
|
56
|
-
if (similar(a1[1],a2[1]) && similar(b1[1],b2[1]) && similar(a1[1],b1[1])) {
|
|
57
|
-
return rangeOverlap(a1[0], a2[0], b1[0], b2[0]);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// vertical
|
|
61
|
-
// if (a1[0] === a2[0] && b1[0] === b2[0] && a1[0] === b1[0]) {
|
|
62
|
-
if (similar(a1[0],a2[0]) && similar(b1[0],b2[0]) && similar(a1[0],b1[0])) {
|
|
63
|
-
return rangeOverlap(a1[1], a2[1], b1[1], b2[1]);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function segmentBlockedByLines(a, b, lines) {
|
|
70
|
-
for (const { a: l1, b: l2 } of lines) {
|
|
71
|
-
if (segmentOverlapsLine(a, b, l1, l2)) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/* ───────────────────────── Path compression (unchanged) ───────────────────────── */
|
|
79
|
-
|
|
80
|
-
function compressPath(points) {
|
|
81
|
-
if (points.length <= 2) return points;
|
|
82
|
-
|
|
83
|
-
const out = [points[0]];
|
|
84
|
-
|
|
85
|
-
for (let i = 1; i < points.length - 1; i++) {
|
|
86
|
-
const a = out[out.length - 1];
|
|
87
|
-
const b = points[i];
|
|
88
|
-
const c = points[i + 1];
|
|
89
|
-
|
|
90
|
-
const abx = b[0] - a[0];
|
|
91
|
-
const aby = b[1] - a[1];
|
|
92
|
-
const bcx = c[0] - b[0];
|
|
93
|
-
const bcy = c[1] - b[1];
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
(abx === 0 && bcx === 0) ||
|
|
97
|
-
(aby === 0 && bcy === 0)
|
|
98
|
-
) continue;
|
|
99
|
-
|
|
100
|
-
out.push(b);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
out.push(points[points.length - 1]);
|
|
104
|
-
return out; //.map(p=>p.snap(2.54));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* ───────────────────────── Main router ───────────────────────── */
|
|
108
|
-
|
|
109
|
-
export function findGridPath({
|
|
110
|
-
from,
|
|
111
|
-
to,
|
|
112
|
-
gridSize,
|
|
113
|
-
avoidRects = [],
|
|
114
|
-
avoidLines = []
|
|
115
|
-
}) {
|
|
116
|
-
const open = new Map();
|
|
117
|
-
const closed = new Set();
|
|
118
|
-
|
|
119
|
-
open.set(pointKey(from), {
|
|
120
|
-
point: from,
|
|
121
|
-
g: 0,
|
|
122
|
-
f: manhattan(from, to),
|
|
123
|
-
parent: null
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const steps = [
|
|
127
|
-
new Point([ gridSize, 0 ]),
|
|
128
|
-
new Point([ -gridSize, 0 ]),
|
|
129
|
-
new Point([ 0, gridSize ]),
|
|
130
|
-
new Point([ 0, -gridSize ])
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
while (open.size) {
|
|
134
|
-
let current = null;
|
|
135
|
-
for (const n of open.values()) {
|
|
136
|
-
if (!current || n.f < current.f) current = n;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (current.point.equals(to)) {
|
|
140
|
-
const path = [];
|
|
141
|
-
let c = current;
|
|
142
|
-
while (c) {
|
|
143
|
-
path.push(c.point);
|
|
144
|
-
c = c.parent;
|
|
145
|
-
}
|
|
146
|
-
return compressPath(path.reverse());
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
open.delete(pointKey(current.point));
|
|
150
|
-
closed.add(pointKey(current.point));
|
|
151
|
-
|
|
152
|
-
for (const step of steps) {
|
|
153
|
-
const next = current.point.add(step);
|
|
154
|
-
const key = pointKey(next);
|
|
155
|
-
|
|
156
|
-
if (closed.has(key)) continue;
|
|
157
|
-
if (segmentBlocked(current.point, next, avoidRects)) continue;
|
|
158
|
-
if (segmentBlockedByLines(current.point, next, avoidLines)) continue;
|
|
159
|
-
|
|
160
|
-
const g = current.g + gridSize;
|
|
161
|
-
const f = g + manhattan(next, to);
|
|
162
|
-
|
|
163
|
-
const existing = open.get(key);
|
|
164
|
-
if (!existing || g < existing.g) {
|
|
165
|
-
open.set(key, {
|
|
166
|
-
point: next,
|
|
167
|
-
g,
|
|
168
|
-
f,
|
|
169
|
-
parent: current
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
throw new Error("No path found");
|
|
176
|
-
}
|
|
File without changes
|
|
File without changes
|