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.
@@ -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 "./cartesian-math.js";
5
- import {findGridPath} from "../src/manhattan-router.js";
6
- import {isSym, sym, sexpParse, sexpStringify, symName, sexpCallName} from "./sexp.js";
7
- import {placeRect} from "./place-rect.js";
8
- import {arrayUnique} from "./js-util.js";
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
- this.uuid=o[1];
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
- getEntities() {
67
- return this.entities;
68
- }
73
+ // filter: type connectionPoint label
74
+ getEntities(filter={}) {
75
+ if (filter.label)
76
+ filter.type="label";
69
77
 
70
- sym(ref) {
71
- for (let e of this.entities)
72
- if (e.getType()=="symbol" && e.getReference()==ref)
73
- return e;
78
+ return this.entities.filter(e=>{
79
+ if (filter.type && e.getType()!=filter.type)
80
+ return false;
74
81
 
75
- throw new Error("Undefined symbol reference: "+ref);
76
- }
77
-
78
- getSymbolEntities() {
79
- let entities=[];
82
+ if (filter.label && e.getLabel()!=filter.label)
83
+ return false;
80
84
 
81
- for (let e of this.entities)
82
- if (e.getType()=="symbol")
83
- entities.push(e);
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
- return entities;
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
- return entities;
96
+ return true;
97
+ });
96
98
  }
97
99
 
98
100
  getNets() {
99
- let nets=this.getEntities()
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
- getEntitiesByConnectionPoint(connectonPoint) {
109
- connectonPoint=Point.from(connectonPoint);
110
- let entities=[];
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
- return entities;
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 entities touching this point
158
- const entities = this.getEntitiesByConnectionPoint(point).filter(e=>e.getType()=="wire");
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
- let connectionPoints=this.getConnectionPoints();
189
- connectionPoints=connectionPoints.filter(p=>!p.equals(fromPoint) && !p.equals(toPoint));
190
- let avoidRects=connectionPoints.map(p=>new Rect(p.sub([0.635,0.635]),[1.27,1.27]));
191
-
192
- let avoidLines=this.entities.filter(e=>e.getType()=="wire").map(e=>{
193
- return ({
194
- a: e.getConnectionPoints()[0],
195
- b: e.getConnectionPoints()[1],
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
- //console.log(avoidLines);
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 points=findGridPath({
202
- from: fromPoint,
203
- to: toPoint,
204
- gridSize: 1.27,
205
- avoidRects: avoidRects,
206
- avoidLines: avoidLines
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.getSymbolEntities().map(e=>e.getBoundingRect().pad(2.54*4));
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 i=1; i<=librarySymbol.pins.length; i++)
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.getSymbolEntities()) {
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.getSymbolEntities()) {
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 "../src/sexp.js";
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
+ }