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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import SymbolLibrary from "./SymbolLibrary.js";
|
|
2
2
|
import fs, {promises as fsp} from "fs";
|
|
3
3
|
import Entity from "./Entity.js";
|
|
4
|
-
import {Point, pointKey, Rect} from "
|
|
5
|
-
import
|
|
6
|
-
import {isSym, sym, sexpParse, sexpStringify, symName, sexpCallName} from "
|
|
7
|
-
import {placeRect} from "
|
|
8
|
-
import {arrayUnique} from "
|
|
4
|
+
import {Point, pointKey, Rect} from "../utils/cartesian-math.js";
|
|
5
|
+
import RoutingGrid from "../utils/RoutingGrid.js";
|
|
6
|
+
import {isSym, sym, sexpParse, sexpStringify, symName, sexpCallName} from "../utils/sexp.js";
|
|
7
|
+
import {placeRect} from "../utils/place-rect.js";
|
|
8
|
+
import {arrayUnique} from "../utils/js-util.js";
|
|
9
9
|
|
|
10
10
|
export default class Schematic {
|
|
11
11
|
constructor(options) {
|
|
@@ -44,10 +44,17 @@ export default class Schematic {
|
|
|
44
44
|
this.entities.push(e);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
if (sexpCallName(o)=="uuid")
|
|
48
|
-
|
|
47
|
+
if (sexpCallName(o)=="uuid") {
|
|
48
|
+
let cand=o[1];
|
|
49
|
+
if (isSym(cand))
|
|
50
|
+
cand=symName(cand);
|
|
51
|
+
|
|
52
|
+
//console.log(cand);
|
|
53
|
+
this.uuid=cand;
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
//console.log("loaded: "+this.uuid);
|
|
51
58
|
this.sexp=this.sexp.filter(o=>!["wire","label","symbol","uuid"].includes(sexpCallName(o)));
|
|
52
59
|
}
|
|
53
60
|
|
|
@@ -63,59 +70,43 @@ export default class Schematic {
|
|
|
63
70
|
await fsp.writeFile(fn,content);
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
// filter: type connectionPoint label
|
|
74
|
+
getEntities(filter={}) {
|
|
75
|
+
if (filter.label)
|
|
76
|
+
filter.type="label";
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return e;
|
|
78
|
+
return this.entities.filter(e=>{
|
|
79
|
+
if (filter.type && e.getType()!=filter.type)
|
|
80
|
+
return false;
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
getSymbolEntities() {
|
|
79
|
-
let entities=[];
|
|
82
|
+
if (filter.label && e.getLabel()!=filter.label)
|
|
83
|
+
return false;
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if (filter.connectionPoint) {
|
|
86
|
+
let cp=Point.from(filter.connectionPoint);
|
|
87
|
+
let found=false;
|
|
88
|
+
for (let p of e.getConnectionPoints())
|
|
89
|
+
if (cp.equals(p))
|
|
90
|
+
found=true;
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
getLabelEntities(label) {
|
|
89
|
-
let entities=[];
|
|
90
|
-
|
|
91
|
-
for (let e of this.entities)
|
|
92
|
-
if (e.getType()=="label" && e.getLabel()==label)
|
|
93
|
-
entities.push(e);
|
|
92
|
+
if (!found)
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
return true;
|
|
97
|
+
});
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
getNets() {
|
|
99
|
-
|
|
100
|
-
.filter(e=>e.getType()=="label")
|
|
101
|
-
.map(e=>e.getLabel());
|
|
102
|
-
|
|
103
|
-
nets=arrayUnique(nets);
|
|
104
|
-
|
|
105
|
-
return nets;
|
|
101
|
+
return arrayUnique(this.getEntities({type: "label"}).map(e=>e.getLabel()));
|
|
106
102
|
}
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
for (let e of this.entities) {
|
|
113
|
-
for (let p of e.getConnectionPoints())
|
|
114
|
-
if (connectonPoint.equals(p))
|
|
115
|
-
entities.push(e)
|
|
116
|
-
}
|
|
104
|
+
sym(ref) {
|
|
105
|
+
for (let e of this.entities)
|
|
106
|
+
if (e.getType()=="symbol" && e.getReference()==ref)
|
|
107
|
+
return e;
|
|
117
108
|
|
|
118
|
-
|
|
109
|
+
throw new Error("Undefined symbol reference: "+ref);
|
|
119
110
|
}
|
|
120
111
|
|
|
121
112
|
getConnectionPoints() {
|
|
@@ -154,8 +145,8 @@ export default class Schematic {
|
|
|
154
145
|
if (visitedPoints.has(key)) continue;
|
|
155
146
|
visitedPoints.add(key);
|
|
156
147
|
|
|
157
|
-
// find all
|
|
158
|
-
const entities
|
|
148
|
+
// find all wires touching this point
|
|
149
|
+
const entities=this.getEntities({type: "wire", connectionPoint: point});
|
|
159
150
|
|
|
160
151
|
for (const entity of entities) {
|
|
161
152
|
const connectionPoints = entity.getConnectionPoints();
|
|
@@ -185,29 +176,45 @@ export default class Schematic {
|
|
|
185
176
|
}
|
|
186
177
|
|
|
187
178
|
addConnectionWire(fromPoint, toPoint) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
179
|
+
//console.log("add connection...");
|
|
180
|
+
|
|
181
|
+
let grid=new RoutingGrid({spacing: 1.27});
|
|
182
|
+
for (let sym of this.getEntities({type: "symbol"})) {
|
|
183
|
+
let r=sym.getBoundingRect();
|
|
184
|
+
grid.drawRect(r.getLeft(),r.getTop(),r.getRight(),r.getBottom());
|
|
185
|
+
|
|
186
|
+
for (let pin of sym.getPins()) {
|
|
187
|
+
let point=pin.getPoint();
|
|
188
|
+
let legPoint=pin.getLegPoint();
|
|
189
|
+
grid.drawLine(point[0],point[1],legPoint[0],legPoint[1]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
198
192
|
|
|
199
|
-
|
|
193
|
+
for (let wire of this.getEntities({type: "wire"})) {
|
|
194
|
+
let [p1,p2]=wire.getConnectionPoints();
|
|
195
|
+
grid.drawLine(p1[0],p1[1],p2[0],p2[1]);
|
|
196
|
+
}
|
|
200
197
|
|
|
201
|
-
let
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
198
|
+
let startPoints=[fromPoint,...this.getConnectedWirePoints(fromPoint)];
|
|
199
|
+
let goalPoints=[toPoint,...this.getConnectedWirePoints(toPoint)];
|
|
200
|
+
|
|
201
|
+
for (let p of [...startPoints,...goalPoints])
|
|
202
|
+
grid.clearPoint(p[0],p[1]);
|
|
203
|
+
|
|
204
|
+
let stats={};
|
|
205
|
+
let points=grid.findPath({
|
|
206
|
+
start: startPoints.map(p=>({x: p[0], y: p[1]})),
|
|
207
|
+
goal: goalPoints.map(p=>({x: p[0], y: p[1]})),
|
|
208
|
+
stats
|
|
207
209
|
});
|
|
208
210
|
|
|
211
|
+
//console.log("steps: "+stats.steps);
|
|
212
|
+
|
|
213
|
+
this.addJunctionIfNeeded(new Point(points[0]));
|
|
214
|
+
this.addJunctionIfNeeded(new Point(points[points.length-1]));
|
|
215
|
+
|
|
209
216
|
for (let i=0; i<points.length-1; i++) {
|
|
210
|
-
let p1=points[i], p2=points[i+1];
|
|
217
|
+
let p1=new Point(points[i]), p2=new Point(points[i+1]);
|
|
211
218
|
let expr=[sym("wire"),
|
|
212
219
|
[sym("pts"), [sym("xy"),p1[0],p1[1]], [sym("xy"),p2[0],p2[1]]],
|
|
213
220
|
[sym("stroke"), [sym("width"),0], [sym("type"), sym("default")]],
|
|
@@ -219,6 +226,98 @@ export default class Schematic {
|
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
228
|
|
|
229
|
+
addJunctionIfNeeded(p) {
|
|
230
|
+
p=new Point(p);
|
|
231
|
+
if (this.getEntities({connectionPoint: p, type: "symbol"}).length)
|
|
232
|
+
return;
|
|
233
|
+
|
|
234
|
+
this.splitWire(p);
|
|
235
|
+
this.addJunction(p);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
splitWire(p) {
|
|
239
|
+
for (let e of this.getEntities({type: "wire"})) {
|
|
240
|
+
if (e.containsPoint(p)) {
|
|
241
|
+
let cp=e.getConnectionPoints();
|
|
242
|
+
let seg1=this.drawWireLine(cp[0],p);
|
|
243
|
+
let seg2=this.drawWireLine(p,cp[1]);
|
|
244
|
+
seg1.declared=e.declared;
|
|
245
|
+
seg2.declared=e.declared;
|
|
246
|
+
this.removeEntity(e);
|
|
247
|
+
//console.log("found it!!");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
throw new Error("No wire to split");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
addJunction(p) {
|
|
256
|
+
let expr=[sym("junction"),
|
|
257
|
+
[sym("at"),p[0],p[1]],
|
|
258
|
+
[sym("diameter"),0],
|
|
259
|
+
[sym("color"),0,0,0,0],
|
|
260
|
+
[sym("uuid"),crypto.randomUUID()]
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
let e=new Entity(expr,this);
|
|
264
|
+
this.entities.push(e);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
getConnectedWires(connectionPoint) {
|
|
268
|
+
let points=[Point.from(connectionPoint)];
|
|
269
|
+
let entities=[];
|
|
270
|
+
|
|
271
|
+
while (points.length) {
|
|
272
|
+
let p=Point.from(points.pop());
|
|
273
|
+
//console.log(p);
|
|
274
|
+
for (let e of this.getEntities({type: "wire", connectionPoint: p})) {
|
|
275
|
+
if (!entities.includes(e)) {
|
|
276
|
+
entities.push(e);
|
|
277
|
+
points.push(...e.getConnectionPoints());
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return entities;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
getConnectedWirePoints(connectionPoint) {
|
|
286
|
+
let wires=this.getConnectedWires(connectionPoint);
|
|
287
|
+
let grid=new RoutingGrid({spacing: 1.27});
|
|
288
|
+
for (let w of wires) {
|
|
289
|
+
let p=w.getConnectionPoints();
|
|
290
|
+
grid.drawLine(p[0][0],p[0][1],p[1][0],p[1][1]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return grid.getPoints().map(p=>[p.x,p.y]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
drawWireLine(p1, p2) {
|
|
297
|
+
let expr=[sym("wire"),
|
|
298
|
+
[sym("pts"), [sym("xy"),p1[0],p1[1]], [sym("xy"),p2[0],p2[1]]],
|
|
299
|
+
[sym("stroke"), [sym("width"),0], [sym("type"), sym("default")]],
|
|
300
|
+
[sym("uuid"),crypto.randomUUID()]
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
let e=new Entity(expr,this);
|
|
304
|
+
this.entities.push(e);
|
|
305
|
+
|
|
306
|
+
return e;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
drawWireRect(r) {
|
|
310
|
+
this.drawWireLine([r.getLeft(),r.getTop()], [r.getRight(),r.getTop()]);
|
|
311
|
+
this.drawWireLine([r.getRight(),r.getTop()], [r.getRight(),r.getBottom()]);
|
|
312
|
+
this.drawWireLine([r.getRight(),r.getBottom()], [r.getLeft(),r.getBottom()]);
|
|
313
|
+
this.drawWireLine([r.getLeft(),r.getBottom()], [r.getLeft(),r.getTop()]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
drawWirePoint(p) {
|
|
317
|
+
p=new Point(p);
|
|
318
|
+
this.drawWireRect(new Rect(p.sub([0.25,0.25]),[0.5,0.5]));
|
|
319
|
+
}
|
|
320
|
+
|
|
222
321
|
addLabel(point, label) {
|
|
223
322
|
let expr=[sym("label"),label,
|
|
224
323
|
[sym("at"),point[0],point[1],180],
|
|
@@ -235,8 +334,6 @@ export default class Schematic {
|
|
|
235
334
|
}
|
|
236
335
|
|
|
237
336
|
getLibSymbolsExp() {
|
|
238
|
-
//return sexpFirst(this.sexpr,x=>sexpCallName(x)=="lib_symbols")
|
|
239
|
-
|
|
240
337
|
for (let exp of this.sexp)
|
|
241
338
|
if (sexpCallName(exp)=="lib_symbols")
|
|
242
339
|
return exp;
|
|
@@ -264,12 +361,6 @@ export default class Schematic {
|
|
|
264
361
|
libSymbolsExpr.push(librarySymbol.getQualifiedSexpr());
|
|
265
362
|
}
|
|
266
363
|
|
|
267
|
-
async use(...symbols) {
|
|
268
|
-
symbols=symbols.flat(Infinity);
|
|
269
|
-
for (let symbol of symbols)
|
|
270
|
-
await this.ensureLibSymbol(symbol);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
364
|
declare(ref, options) {
|
|
274
365
|
let entity=this.entities.find(e=>e.getType()=="symbol" && e.getReference()==ref);
|
|
275
366
|
if (!entity)
|
|
@@ -281,6 +372,22 @@ export default class Schematic {
|
|
|
281
372
|
this.ensureLibSymbolSync(options.symbol);
|
|
282
373
|
|
|
283
374
|
entity.setFootprint(options.footprint);
|
|
375
|
+
|
|
376
|
+
if (options.name)
|
|
377
|
+
entity.setName(options.name);
|
|
378
|
+
|
|
379
|
+
if (options.lcsc)
|
|
380
|
+
entity.setProp("lcsc",options.lcsc);
|
|
381
|
+
|
|
382
|
+
else
|
|
383
|
+
entity.removeProp("lcsc");
|
|
384
|
+
|
|
385
|
+
if (options.lcscRot)
|
|
386
|
+
entity.setProp("lcscRot",String(options.lcscRot));
|
|
387
|
+
|
|
388
|
+
else
|
|
389
|
+
entity.removeProp("lcscRot");
|
|
390
|
+
|
|
284
391
|
entity.declared=true;
|
|
285
392
|
|
|
286
393
|
return entity;
|
|
@@ -295,19 +402,22 @@ export default class Schematic {
|
|
|
295
402
|
let librarySymbol=this.symbolLibrary.loadLibrarySymbolSync(symbol);
|
|
296
403
|
|
|
297
404
|
//let librarySymbol=await this.symbolLibrary.loadLibrarySymbol(symbol);
|
|
298
|
-
let rects=this.
|
|
405
|
+
let rects=this.getEntities({type: "symbol"}).map(e=>e.getBoundingRect().pad(2.54*4));
|
|
299
406
|
|
|
300
407
|
let center=new Point(101.6,101.6);
|
|
301
408
|
if (rects.length)
|
|
302
409
|
center=rects.reduce((r,q)=>r.union(q)).getCenter().snap(2.54);
|
|
303
410
|
|
|
304
411
|
if (!at) {
|
|
412
|
+
//console.log("place: ",librarySymbol.getBoundingRect());
|
|
413
|
+
//console.log(rects);
|
|
305
414
|
at=placeRect({
|
|
306
415
|
start: center,
|
|
307
416
|
rect: librarySymbol.getBoundingRect(),
|
|
308
417
|
avoid: rects,
|
|
309
418
|
step: 2.54,
|
|
310
419
|
});
|
|
420
|
+
//console.log("placed!");
|
|
311
421
|
}
|
|
312
422
|
|
|
313
423
|
let expr=[sym("symbol"),
|
|
@@ -330,8 +440,14 @@ export default class Schematic {
|
|
|
330
440
|
]
|
|
331
441
|
]);
|
|
332
442
|
|
|
333
|
-
for (let
|
|
443
|
+
for (let p of librarySymbol.pins) {
|
|
444
|
+
expr.push([sym("pin"),p.number,[sym("uuid"),crypto.randomUUID()]]);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/*for (let i=1; i<=librarySymbol.pins.length; i++) {
|
|
448
|
+
//console.log("create pin: "+i);
|
|
334
449
|
expr.push([sym("pin"),String(i),[sym("uuid"),crypto.randomUUID()]]);
|
|
450
|
+
}*/
|
|
335
451
|
|
|
336
452
|
expr.push([sym("instances"),
|
|
337
453
|
[sym("project"),"",
|
|
@@ -351,6 +467,12 @@ export default class Schematic {
|
|
|
351
467
|
|
|
352
468
|
markConnectionDeclared(from, to) {
|
|
353
469
|
let wires=this.getConnectionPath(from,to);
|
|
470
|
+
if (!wires) {
|
|
471
|
+
console.log("wires?");
|
|
472
|
+
console.log(wires);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
354
476
|
for (let wire of wires) {
|
|
355
477
|
if (wire.getType()!="wire")
|
|
356
478
|
throw new Error("Sanity check... Wire is not a wire...");
|
|
@@ -361,6 +483,9 @@ export default class Schematic {
|
|
|
361
483
|
|
|
362
484
|
removeUndeclared() {
|
|
363
485
|
this.entities=this.entities.filter(entity=>{
|
|
486
|
+
if (entity.type=="junction")
|
|
487
|
+
return true;
|
|
488
|
+
|
|
364
489
|
return entity.declared;
|
|
365
490
|
});
|
|
366
491
|
}
|
|
@@ -368,7 +493,7 @@ export default class Schematic {
|
|
|
368
493
|
getSource() {
|
|
369
494
|
let src="";
|
|
370
495
|
src+=`export default async function(sch) {\n`;
|
|
371
|
-
for (let e of this.
|
|
496
|
+
for (let e of this.getEntities({type: "symbol"})) {
|
|
372
497
|
src+=` let ${e.getReference()}=sch.declare("${e.getReference()}",{\n`;
|
|
373
498
|
src+=` "symbol": "${e.getLibId()}",\n`
|
|
374
499
|
src+=` "footprint": "${e.getFootprint()}",\n`
|
|
@@ -376,7 +501,7 @@ export default class Schematic {
|
|
|
376
501
|
}
|
|
377
502
|
|
|
378
503
|
let allConnectionPoints=this.getConnectionPoints();
|
|
379
|
-
for (let e of this.
|
|
504
|
+
for (let e of this.getEntities({type: "symbol"})) {
|
|
380
505
|
for (let pin of e.pins) {
|
|
381
506
|
for (let c of pin.getConnections()) {
|
|
382
507
|
if (typeof c=="string") {
|
|
@@ -398,6 +523,10 @@ export default class Schematic {
|
|
|
398
523
|
|
|
399
524
|
return src;
|
|
400
525
|
}
|
|
526
|
+
|
|
527
|
+
removeEntity(removable) {
|
|
528
|
+
this.entities=this.entities.filter(e=>e!=removable);
|
|
529
|
+
}
|
|
401
530
|
}
|
|
402
531
|
|
|
403
532
|
export async function loadSchematic(fn, options) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs, {promises as fsp} from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import LibrarySymbol from "./LibrarySymbol.js";
|
|
4
|
-
import {sexpParse, sym, isSym, symEq} from "../
|
|
4
|
+
import {sexpParse, sym, isSym, symEq} from "../utils/sexp.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* SymbolLibrary represents a directory of .kicad_sym files.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import {arrayGetMinIndex, arrayGetMaxIndex} from "./js-util.js";
|
|
2
|
+
import {collapsePath, manhattanDist} from "./grid-util.js";
|
|
3
|
+
import {astar} from "./astar.js";
|
|
4
|
+
|
|
5
|
+
export default class RoutingGrid {
|
|
6
|
+
constructor({spacing}={spacing: 1}) {
|
|
7
|
+
this.grid=[];
|
|
8
|
+
this.spacing=spacing;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
snap(v) {
|
|
12
|
+
return Math.round(v/this.spacing);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
unsnap(v) {
|
|
16
|
+
return v*this.spacing;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getGrid(x, y) {
|
|
20
|
+
if (!this.grid[y] || !this.grid[y][x])
|
|
21
|
+
return {};
|
|
22
|
+
|
|
23
|
+
return this.grid[y][x];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
updateGrid(x, y, update) {
|
|
27
|
+
//console.log(x+","+y);
|
|
28
|
+
if (!this.grid[y])
|
|
29
|
+
this.grid[y]=[];
|
|
30
|
+
|
|
31
|
+
if (!this.grid[y][x])
|
|
32
|
+
this.grid[y][x]={};
|
|
33
|
+
|
|
34
|
+
this.grid[y][x]={...this.grid[y][x], ...update};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
drawPoint(x, y) {
|
|
38
|
+
x=this.snap(x);
|
|
39
|
+
y=this.snap(y);
|
|
40
|
+
this.updateGrid(x,y,{b: true});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
clearPoint(x, y) {
|
|
44
|
+
x=this.snap(x);
|
|
45
|
+
y=this.snap(y);
|
|
46
|
+
this.updateGrid(x,y,{b: false});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
drawHorizontalLine(x, y, x2) {
|
|
50
|
+
x=this.snap(x);
|
|
51
|
+
y=this.snap(y);
|
|
52
|
+
x2=this.snap(x2);
|
|
53
|
+
|
|
54
|
+
if (x2<x) { let v=x; x=x2; x2=v; }
|
|
55
|
+
for (let i=x; i<x2; i++)
|
|
56
|
+
this.updateGrid(i,y,{h: true});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
drawVerticalLine(x, y, y2) {
|
|
60
|
+
x=this.snap(x);
|
|
61
|
+
y=this.snap(y);
|
|
62
|
+
y2=this.snap(y2);
|
|
63
|
+
|
|
64
|
+
if (y2<y) { let v=y; y=y2; y2=v; }
|
|
65
|
+
for (let i=y; i<y2; i++)
|
|
66
|
+
this.updateGrid(x,i,{v: true});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
drawLine(x1, y1, x2, y2) {
|
|
70
|
+
if (y1==y2)
|
|
71
|
+
this.drawHorizontalLine(x1,y2,x2);
|
|
72
|
+
|
|
73
|
+
else if (x1==x2)
|
|
74
|
+
this.drawVerticalLine(x1,y1,y2);
|
|
75
|
+
|
|
76
|
+
else
|
|
77
|
+
throw new Error("can only draw h/v");
|
|
78
|
+
|
|
79
|
+
this.drawPoint(x1,y1);
|
|
80
|
+
this.drawPoint(x2,y2);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
drawLines(points) {
|
|
84
|
+
for (let i=0; i<points.length-1; i++) {
|
|
85
|
+
this.drawLine(
|
|
86
|
+
points[i].x,
|
|
87
|
+
points[i].y,
|
|
88
|
+
points[i+1].x,
|
|
89
|
+
points[i+1].y,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
drawRect(x1, y1, x2, y2) {
|
|
95
|
+
x1=this.snap(x1);
|
|
96
|
+
y1=this.snap(y1);
|
|
97
|
+
x2=this.snap(x2);
|
|
98
|
+
y2=this.snap(y2);
|
|
99
|
+
|
|
100
|
+
if (x2<x1) { let v=x1; x1=x2; x2=v; }
|
|
101
|
+
if (y2<y1) { let v=y1; y1=y2; y2=v; }
|
|
102
|
+
for (let y=y1; y<=y2; y++) {
|
|
103
|
+
for (let x=x1; x<=x2; x++) {
|
|
104
|
+
this.updateGrid(x,y,{b: true});
|
|
105
|
+
|
|
106
|
+
if (x!=x2)
|
|
107
|
+
this.updateGrid(x,y,{h: true});
|
|
108
|
+
|
|
109
|
+
if (y!=y2)
|
|
110
|
+
this.updateGrid(x,y,{v: true});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getTop() {
|
|
116
|
+
return arrayGetMinIndex(this.grid);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getBottom() {
|
|
120
|
+
return arrayGetMaxIndex(this.grid);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getLeft() {
|
|
124
|
+
let min;
|
|
125
|
+
for (let y=arrayGetMinIndex(this.grid); y<=arrayGetMaxIndex(this.grid); y++) {
|
|
126
|
+
if (this.grid[y]) {
|
|
127
|
+
if (min===undefined || arrayGetMinIndex(this.grid[y])<min)
|
|
128
|
+
min=arrayGetMinIndex(this.grid[y]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return min;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getRight() {
|
|
136
|
+
let max;
|
|
137
|
+
for (let y=arrayGetMinIndex(this.grid); y<=arrayGetMaxIndex(this.grid); y++) {
|
|
138
|
+
if (this.grid[y]) {
|
|
139
|
+
//console.log(y+" "+arrayGetMaxIndex(this.grid[y]),this.grid[y],Object.keys(this.grid[y]));
|
|
140
|
+
if (max===undefined || arrayGetMaxIndex(this.grid[y])>max)
|
|
141
|
+
max=arrayGetMaxIndex(this.grid[y]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return max;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
toGridString() {
|
|
149
|
+
let s="";
|
|
150
|
+
|
|
151
|
+
for (let y=this.getTop(); y<=this.getBottom(); y++) {
|
|
152
|
+
for (let x=this.getLeft(); x<=this.getRight(); x++)
|
|
153
|
+
s+=((this.getGrid(x,y).b?"*":"+")+(this.getGrid(x,y).h?"-":" "));
|
|
154
|
+
|
|
155
|
+
s+="\n";
|
|
156
|
+
|
|
157
|
+
for (let x=this.getLeft(); x<=this.getRight(); x++)
|
|
158
|
+
s+=(this.getGrid(x,y).v?"|":" ")+" ";
|
|
159
|
+
|
|
160
|
+
s+="\n";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return s;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
findPath({start, goal, stats}) {
|
|
167
|
+
start=start.map(p=>({x: this.snap(p.x), y: this.snap(p.y)}));
|
|
168
|
+
goal=goal.map(p=>({x: this.snap(p.x), y: this.snap(p.y)}));
|
|
169
|
+
|
|
170
|
+
let neighbours=(g)=>{
|
|
171
|
+
let n=[];
|
|
172
|
+
|
|
173
|
+
if (g=="start")
|
|
174
|
+
return start;
|
|
175
|
+
|
|
176
|
+
if (!this.getGrid(g.x,g.y).h &&
|
|
177
|
+
!this.getGrid(g.x+1,g.y).b)
|
|
178
|
+
n.push({x: g.x+1, y: g.y, from: "w"});
|
|
179
|
+
|
|
180
|
+
if (!this.getGrid(g.x-1,g.y).h &&
|
|
181
|
+
!this.getGrid(g.x-1,g.y).b)
|
|
182
|
+
n.push({x: g.x-1, y: g.y, from: "e"});
|
|
183
|
+
|
|
184
|
+
if (!this.getGrid(g.x,g.y).v &&
|
|
185
|
+
!this.getGrid(g.x,g.y+1).b)
|
|
186
|
+
n.push({x: g.x, y: g.y+1, from: "n"});
|
|
187
|
+
|
|
188
|
+
if (!this.getGrid(g.x,g.y-1).v &&
|
|
189
|
+
!this.getGrid(g.x,g.y-1).b)
|
|
190
|
+
n.push({x: g.x, y: g.y-1, from: "s"});
|
|
191
|
+
|
|
192
|
+
return n;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let cost=(g1, g2)=>{
|
|
196
|
+
if (g1.from==g2.from)
|
|
197
|
+
return 1;
|
|
198
|
+
|
|
199
|
+
return 20;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let heuristic=(node)=>{
|
|
203
|
+
if (node=="start")
|
|
204
|
+
return 0;
|
|
205
|
+
|
|
206
|
+
let closest;
|
|
207
|
+
for (let g of goal) {
|
|
208
|
+
let d=manhattanDist(node.x,node.y,g.x,g.y);
|
|
209
|
+
if (closest===undefined || d<closest)
|
|
210
|
+
closest=d;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return closest;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let key=(g)=>{
|
|
217
|
+
if (g=="start")
|
|
218
|
+
return "start";
|
|
219
|
+
|
|
220
|
+
return `${g.x}|${g.y}|${g.from??""}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let isGoal=g=>{
|
|
224
|
+
for (let p of goal)
|
|
225
|
+
if (p.x==g.x && p.y==g.y)
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let steps=astar({
|
|
230
|
+
start: "start",
|
|
231
|
+
neighbours,
|
|
232
|
+
isGoal, //: g=>(g.x==goal.x && g.y==goal.y),
|
|
233
|
+
cost,
|
|
234
|
+
heuristic,
|
|
235
|
+
key,
|
|
236
|
+
stats
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
steps=steps.filter(i=>i!="start");
|
|
240
|
+
|
|
241
|
+
return collapsePath(steps).map(p=>({x: this.unsnap(p.x), y: this.unsnap(p.y)}));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
getPoints() {
|
|
245
|
+
let points=[];
|
|
246
|
+
|
|
247
|
+
for (let y=arrayGetMinIndex(this.grid); y<=arrayGetMaxIndex(this.grid); y++) {
|
|
248
|
+
let row=this.grid[y];
|
|
249
|
+
if (row) {
|
|
250
|
+
for (let x=arrayGetMinIndex(row); x<=arrayGetMaxIndex(row); x++) {
|
|
251
|
+
let g=this.getGrid(x,y)
|
|
252
|
+
if (g.b || g.h || g.v)
|
|
253
|
+
points.push({x,y});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return points.map(p=>({x: this.unsnap(p.x), y: this.unsnap(p.y)}));
|
|
259
|
+
}
|
|
260
|
+
}
|