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.
@@ -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) {
@@ -63,59 +63,43 @@ export default class Schematic {
63
63
  await fsp.writeFile(fn,content);
64
64
  }
65
65
 
66
- getEntities() {
67
- return this.entities;
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
- throw new Error("Undefined symbol reference: "+ref);
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
- return entities;
86
- }
75
+ if (filter.label && e.getLabel()!=filter.label)
76
+ return false;
87
77
 
88
- getLabelEntities(label) {
89
- let entities=[];
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
- for (let e of this.entities)
92
- if (e.getType()=="label" && e.getLabel()==label)
93
- entities.push(e);
85
+ if (!found)
86
+ return false;
87
+ }
94
88
 
95
- return entities;
89
+ return true;
90
+ });
96
91
  }
97
92
 
98
93
  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;
94
+ return arrayUnique(this.getEntities({type: "label"}).map(e=>e.getLabel()));
106
95
  }
107
96
 
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
- }
97
+ sym(ref) {
98
+ for (let e of this.entities)
99
+ if (e.getType()=="symbol" && e.getReference()==ref)
100
+ return e;
117
101
 
118
- return entities;
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 entities touching this point
158
- const entities = this.getEntitiesByConnectionPoint(point).filter(e=>e.getType()=="wire");
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
- 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
- });
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
- //console.log(avoidLines);
194
+ for (let p of [...startPoints,...goalPoints])
195
+ grid.clearPoint(p[0],p[1]);
200
196
 
201
- let points=findGridPath({
202
- from: fromPoint,
203
- to: toPoint,
204
- gridSize: 1.27,
205
- avoidRects: avoidRects,
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.getSymbolEntities().map(e=>e.getBoundingRect().pad(2.54*4));
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.getSymbolEntities()) {
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.getSymbolEntities()) {
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 "../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
+ }