kisch 1.0.2 → 1.0.4
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/DSL.md +209 -0
- package/README.md +4 -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 +35 -0
- package/src/{Entity.js → schematic/Entity.js} +122 -18
- package/src/{LibrarySymbol.js → schematic/LibrarySymbol.js} +37 -2
- package/src/{Schematic.js → schematic/Schematic.js} +194 -78
- 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) {
|
|
@@ -63,59 +63,43 @@ export default class Schematic {
|
|
|
63
63
|
await fsp.writeFile(fn,content);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
sym(ref) {
|
|
71
|
-
for (let e of this.entities)
|
|
72
|
-
if (e.getType()=="symbol" && e.getReference()==ref)
|
|
73
|
-
return e;
|
|
66
|
+
// filter: type connectionPoint label
|
|
67
|
+
getEntities(filter={}) {
|
|
68
|
+
if (filter.label)
|
|
69
|
+
filter.type="label";
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
getSymbolEntities() {
|
|
79
|
-
let entities=[];
|
|
80
|
-
|
|
81
|
-
for (let e of this.entities)
|
|
82
|
-
if (e.getType()=="symbol")
|
|
83
|
-
entities.push(e);
|
|
71
|
+
return this.entities.filter(e=>{
|
|
72
|
+
if (filter.type && e.getType()!=filter.type)
|
|
73
|
+
return false;
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
if (filter.label && e.getLabel()!=filter.label)
|
|
76
|
+
return false;
|
|
87
77
|
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
if (filter.connectionPoint) {
|
|
79
|
+
let cp=Point.from(filter.connectionPoint);
|
|
80
|
+
let found=false;
|
|
81
|
+
for (let p of e.getConnectionPoints())
|
|
82
|
+
if (cp.equals(p))
|
|
83
|
+
found=true;
|
|
90
84
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
if (!found)
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
94
88
|
|
|
95
|
-
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
getNets() {
|
|
99
|
-
|
|
100
|
-
.filter(e=>e.getType()=="label")
|
|
101
|
-
.map(e=>e.getLabel());
|
|
102
|
-
|
|
103
|
-
nets=arrayUnique(nets);
|
|
104
|
-
|
|
105
|
-
return nets;
|
|
94
|
+
return arrayUnique(this.getEntities({type: "label"}).map(e=>e.getLabel()));
|
|
106
95
|
}
|
|
107
96
|
|
|
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
|
-
}
|
|
97
|
+
sym(ref) {
|
|
98
|
+
for (let e of this.entities)
|
|
99
|
+
if (e.getType()=="symbol" && e.getReference()==ref)
|
|
100
|
+
return e;
|
|
117
101
|
|
|
118
|
-
|
|
102
|
+
throw new Error("Undefined symbol reference: "+ref);
|
|
119
103
|
}
|
|
120
104
|
|
|
121
105
|
getConnectionPoints() {
|
|
@@ -154,8 +138,8 @@ export default class Schematic {
|
|
|
154
138
|
if (visitedPoints.has(key)) continue;
|
|
155
139
|
visitedPoints.add(key);
|
|
156
140
|
|
|
157
|
-
// find all
|
|
158
|
-
const entities
|
|
141
|
+
// find all wires touching this point
|
|
142
|
+
const entities=this.getEntities({type: "wire", connectionPoint: point});
|
|
159
143
|
|
|
160
144
|
for (const entity of entities) {
|
|
161
145
|
const connectionPoints = entity.getConnectionPoints();
|
|
@@ -185,29 +169,45 @@ export default class Schematic {
|
|
|
185
169
|
}
|
|
186
170
|
|
|
187
171
|
addConnectionWire(fromPoint, toPoint) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
172
|
+
//console.log("add connection...");
|
|
173
|
+
|
|
174
|
+
let grid=new RoutingGrid({spacing: 1.27});
|
|
175
|
+
for (let sym of this.getEntities({type: "symbol"})) {
|
|
176
|
+
let r=sym.getBoundingRect();
|
|
177
|
+
grid.drawRect(r.getLeft(),r.getTop(),r.getRight(),r.getBottom());
|
|
178
|
+
|
|
179
|
+
for (let pin of sym.getPins()) {
|
|
180
|
+
let point=pin.getPoint();
|
|
181
|
+
let legPoint=pin.getLegPoint();
|
|
182
|
+
grid.drawLine(point[0],point[1],legPoint[0],legPoint[1]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (let wire of this.getEntities({type: "wire"})) {
|
|
187
|
+
let [p1,p2]=wire.getConnectionPoints();
|
|
188
|
+
grid.drawLine(p1[0],p1[1],p2[0],p2[1]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let startPoints=[fromPoint,...this.getConnectedWirePoints(fromPoint)];
|
|
192
|
+
let goalPoints=[toPoint,...this.getConnectedWirePoints(toPoint)];
|
|
198
193
|
|
|
199
|
-
|
|
194
|
+
for (let p of [...startPoints,...goalPoints])
|
|
195
|
+
grid.clearPoint(p[0],p[1]);
|
|
200
196
|
|
|
201
|
-
let
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
avoidLines: avoidLines
|
|
197
|
+
let stats={};
|
|
198
|
+
let points=grid.findPath({
|
|
199
|
+
start: startPoints.map(p=>({x: p[0], y: p[1]})),
|
|
200
|
+
goal: goalPoints.map(p=>({x: p[0], y: p[1]})),
|
|
201
|
+
stats
|
|
207
202
|
});
|
|
208
203
|
|
|
204
|
+
//console.log("steps: "+stats.steps);
|
|
205
|
+
|
|
206
|
+
this.addJunctionIfNeeded(new Point(points[0]));
|
|
207
|
+
this.addJunctionIfNeeded(new Point(points[points.length-1]));
|
|
208
|
+
|
|
209
209
|
for (let i=0; i<points.length-1; i++) {
|
|
210
|
-
let p1=points[i], p2=points[i+1];
|
|
210
|
+
let p1=new Point(points[i]), p2=new Point(points[i+1]);
|
|
211
211
|
let expr=[sym("wire"),
|
|
212
212
|
[sym("pts"), [sym("xy"),p1[0],p1[1]], [sym("xy"),p2[0],p2[1]]],
|
|
213
213
|
[sym("stroke"), [sym("width"),0], [sym("type"), sym("default")]],
|
|
@@ -219,6 +219,98 @@ export default class Schematic {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
addJunctionIfNeeded(p) {
|
|
223
|
+
p=new Point(p);
|
|
224
|
+
if (this.getEntities({connectionPoint: p, type: "symbol"}).length)
|
|
225
|
+
return;
|
|
226
|
+
|
|
227
|
+
this.splitWire(p);
|
|
228
|
+
this.addJunction(p);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
splitWire(p) {
|
|
232
|
+
for (let e of this.getEntities({type: "wire"})) {
|
|
233
|
+
if (e.containsPoint(p)) {
|
|
234
|
+
let cp=e.getConnectionPoints();
|
|
235
|
+
let seg1=this.drawWireLine(cp[0],p);
|
|
236
|
+
let seg2=this.drawWireLine(p,cp[1]);
|
|
237
|
+
seg1.declared=e.declared;
|
|
238
|
+
seg2.declared=e.declared;
|
|
239
|
+
this.removeEntity(e);
|
|
240
|
+
//console.log("found it!!");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
throw new Error("No wire to split");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
addJunction(p) {
|
|
249
|
+
let expr=[sym("junction"),
|
|
250
|
+
[sym("at"),p[0],p[1]],
|
|
251
|
+
[sym("diameter"),0],
|
|
252
|
+
[sym("color"),0,0,0,0],
|
|
253
|
+
[sym("uuid"),crypto.randomUUID()]
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
let e=new Entity(expr,this);
|
|
257
|
+
this.entities.push(e);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getConnectedWires(connectionPoint) {
|
|
261
|
+
let points=[Point.from(connectionPoint)];
|
|
262
|
+
let entities=[];
|
|
263
|
+
|
|
264
|
+
while (points.length) {
|
|
265
|
+
let p=Point.from(points.pop());
|
|
266
|
+
//console.log(p);
|
|
267
|
+
for (let e of this.getEntities({type: "wire", connectionPoint: p})) {
|
|
268
|
+
if (!entities.includes(e)) {
|
|
269
|
+
entities.push(e);
|
|
270
|
+
points.push(...e.getConnectionPoints());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return entities;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getConnectedWirePoints(connectionPoint) {
|
|
279
|
+
let wires=this.getConnectedWires(connectionPoint);
|
|
280
|
+
let grid=new RoutingGrid({spacing: 1.27});
|
|
281
|
+
for (let w of wires) {
|
|
282
|
+
let p=w.getConnectionPoints();
|
|
283
|
+
grid.drawLine(p[0][0],p[0][1],p[1][0],p[1][1]);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return grid.getPoints().map(p=>[p.x,p.y]);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
drawWireLine(p1, p2) {
|
|
290
|
+
let expr=[sym("wire"),
|
|
291
|
+
[sym("pts"), [sym("xy"),p1[0],p1[1]], [sym("xy"),p2[0],p2[1]]],
|
|
292
|
+
[sym("stroke"), [sym("width"),0], [sym("type"), sym("default")]],
|
|
293
|
+
[sym("uuid"),crypto.randomUUID()]
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
let e=new Entity(expr,this);
|
|
297
|
+
this.entities.push(e);
|
|
298
|
+
|
|
299
|
+
return e;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
drawWireRect(r) {
|
|
303
|
+
this.drawWireLine([r.getLeft(),r.getTop()], [r.getRight(),r.getTop()]);
|
|
304
|
+
this.drawWireLine([r.getRight(),r.getTop()], [r.getRight(),r.getBottom()]);
|
|
305
|
+
this.drawWireLine([r.getRight(),r.getBottom()], [r.getLeft(),r.getBottom()]);
|
|
306
|
+
this.drawWireLine([r.getLeft(),r.getBottom()], [r.getLeft(),r.getTop()]);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
drawWirePoint(p) {
|
|
310
|
+
p=new Point(p);
|
|
311
|
+
this.drawWireRect(new Rect(p.sub([0.25,0.25]),[0.5,0.5]));
|
|
312
|
+
}
|
|
313
|
+
|
|
222
314
|
addLabel(point, label) {
|
|
223
315
|
let expr=[sym("label"),label,
|
|
224
316
|
[sym("at"),point[0],point[1],180],
|
|
@@ -235,8 +327,6 @@ export default class Schematic {
|
|
|
235
327
|
}
|
|
236
328
|
|
|
237
329
|
getLibSymbolsExp() {
|
|
238
|
-
//return sexpFirst(this.sexpr,x=>sexpCallName(x)=="lib_symbols")
|
|
239
|
-
|
|
240
330
|
for (let exp of this.sexp)
|
|
241
331
|
if (sexpCallName(exp)=="lib_symbols")
|
|
242
332
|
return exp;
|
|
@@ -264,12 +354,6 @@ export default class Schematic {
|
|
|
264
354
|
libSymbolsExpr.push(librarySymbol.getQualifiedSexpr());
|
|
265
355
|
}
|
|
266
356
|
|
|
267
|
-
async use(...symbols) {
|
|
268
|
-
symbols=symbols.flat(Infinity);
|
|
269
|
-
for (let symbol of symbols)
|
|
270
|
-
await this.ensureLibSymbol(symbol);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
357
|
declare(ref, options) {
|
|
274
358
|
let entity=this.entities.find(e=>e.getType()=="symbol" && e.getReference()==ref);
|
|
275
359
|
if (!entity)
|
|
@@ -281,6 +365,22 @@ export default class Schematic {
|
|
|
281
365
|
this.ensureLibSymbolSync(options.symbol);
|
|
282
366
|
|
|
283
367
|
entity.setFootprint(options.footprint);
|
|
368
|
+
|
|
369
|
+
if (options.name)
|
|
370
|
+
entity.setName(options.name);
|
|
371
|
+
|
|
372
|
+
if (options.lcsc)
|
|
373
|
+
entity.setProp("lcsc",options.lcsc);
|
|
374
|
+
|
|
375
|
+
else
|
|
376
|
+
entity.removeProp("lcsc");
|
|
377
|
+
|
|
378
|
+
if (options.lcscRot)
|
|
379
|
+
entity.setProp("lcscRot",String(options.lcscRot));
|
|
380
|
+
|
|
381
|
+
else
|
|
382
|
+
entity.removeProp("lcscRot");
|
|
383
|
+
|
|
284
384
|
entity.declared=true;
|
|
285
385
|
|
|
286
386
|
return entity;
|
|
@@ -295,19 +395,22 @@ export default class Schematic {
|
|
|
295
395
|
let librarySymbol=this.symbolLibrary.loadLibrarySymbolSync(symbol);
|
|
296
396
|
|
|
297
397
|
//let librarySymbol=await this.symbolLibrary.loadLibrarySymbol(symbol);
|
|
298
|
-
let rects=this.
|
|
398
|
+
let rects=this.getEntities({type: "symbol"}).map(e=>e.getBoundingRect().pad(2.54*4));
|
|
299
399
|
|
|
300
400
|
let center=new Point(101.6,101.6);
|
|
301
401
|
if (rects.length)
|
|
302
402
|
center=rects.reduce((r,q)=>r.union(q)).getCenter().snap(2.54);
|
|
303
403
|
|
|
304
404
|
if (!at) {
|
|
405
|
+
//console.log("place: ",librarySymbol.getBoundingRect());
|
|
406
|
+
//console.log(rects);
|
|
305
407
|
at=placeRect({
|
|
306
408
|
start: center,
|
|
307
409
|
rect: librarySymbol.getBoundingRect(),
|
|
308
410
|
avoid: rects,
|
|
309
411
|
step: 2.54,
|
|
310
412
|
});
|
|
413
|
+
//console.log("placed!");
|
|
311
414
|
}
|
|
312
415
|
|
|
313
416
|
let expr=[sym("symbol"),
|
|
@@ -351,6 +454,12 @@ export default class Schematic {
|
|
|
351
454
|
|
|
352
455
|
markConnectionDeclared(from, to) {
|
|
353
456
|
let wires=this.getConnectionPath(from,to);
|
|
457
|
+
if (!wires) {
|
|
458
|
+
console.log("wires?");
|
|
459
|
+
console.log(wires);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
354
463
|
for (let wire of wires) {
|
|
355
464
|
if (wire.getType()!="wire")
|
|
356
465
|
throw new Error("Sanity check... Wire is not a wire...");
|
|
@@ -361,6 +470,9 @@ export default class Schematic {
|
|
|
361
470
|
|
|
362
471
|
removeUndeclared() {
|
|
363
472
|
this.entities=this.entities.filter(entity=>{
|
|
473
|
+
if (entity.type=="junction")
|
|
474
|
+
return true;
|
|
475
|
+
|
|
364
476
|
return entity.declared;
|
|
365
477
|
});
|
|
366
478
|
}
|
|
@@ -368,7 +480,7 @@ export default class Schematic {
|
|
|
368
480
|
getSource() {
|
|
369
481
|
let src="";
|
|
370
482
|
src+=`export default async function(sch) {\n`;
|
|
371
|
-
for (let e of this.
|
|
483
|
+
for (let e of this.getEntities({type: "symbol"})) {
|
|
372
484
|
src+=` let ${e.getReference()}=sch.declare("${e.getReference()}",{\n`;
|
|
373
485
|
src+=` "symbol": "${e.getLibId()}",\n`
|
|
374
486
|
src+=` "footprint": "${e.getFootprint()}",\n`
|
|
@@ -376,7 +488,7 @@ export default class Schematic {
|
|
|
376
488
|
}
|
|
377
489
|
|
|
378
490
|
let allConnectionPoints=this.getConnectionPoints();
|
|
379
|
-
for (let e of this.
|
|
491
|
+
for (let e of this.getEntities({type: "symbol"})) {
|
|
380
492
|
for (let pin of e.pins) {
|
|
381
493
|
for (let c of pin.getConnections()) {
|
|
382
494
|
if (typeof c=="string") {
|
|
@@ -398,6 +510,10 @@ export default class Schematic {
|
|
|
398
510
|
|
|
399
511
|
return src;
|
|
400
512
|
}
|
|
513
|
+
|
|
514
|
+
removeEntity(removable) {
|
|
515
|
+
this.entities=this.entities.filter(e=>e!=removable);
|
|
516
|
+
}
|
|
401
517
|
}
|
|
402
518
|
|
|
403
519
|
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
|
+
}
|