@tscircuit/core 0.0.1 → 0.0.3
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/dist/index.cjs +279 -24
- package/lib/Project.ts +8 -0
- package/lib/components/base-components/PrimitiveComponent.ts +12 -1
- package/lib/components/primitive-components/Port.ts +5 -0
- package/lib/components/primitive-components/Trace.ts +138 -7
- package/lib/components/primitive-components/TraceHint.ts +58 -2
- package/lib/utils/autorouting/findPossibleTraceLayerCombinations.ts +102 -0
- package/lib/utils/autorouting/mergeRoutes.ts +70 -0
- package/lib/utils/pairs.ts +10 -0
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -484,9 +484,9 @@ var ParseStatus = class _ParseStatus {
|
|
|
484
484
|
}
|
|
485
485
|
return { status: status.value, value: arrayValue };
|
|
486
486
|
}
|
|
487
|
-
static async mergeObjectAsync(status,
|
|
487
|
+
static async mergeObjectAsync(status, pairs2) {
|
|
488
488
|
const syncPairs = [];
|
|
489
|
-
for (const pair of
|
|
489
|
+
for (const pair of pairs2) {
|
|
490
490
|
const key = await pair.key;
|
|
491
491
|
const value = await pair.value;
|
|
492
492
|
syncPairs.push({
|
|
@@ -496,9 +496,9 @@ var ParseStatus = class _ParseStatus {
|
|
|
496
496
|
}
|
|
497
497
|
return _ParseStatus.mergeObjectSync(status, syncPairs);
|
|
498
498
|
}
|
|
499
|
-
static mergeObjectSync(status,
|
|
499
|
+
static mergeObjectSync(status, pairs2) {
|
|
500
500
|
const finalObject = {};
|
|
501
|
-
for (const pair of
|
|
501
|
+
for (const pair of pairs2) {
|
|
502
502
|
const { key, value } = pair;
|
|
503
503
|
if (key.status === "aborted")
|
|
504
504
|
return INVALID;
|
|
@@ -2224,11 +2224,11 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
2224
2224
|
}
|
|
2225
2225
|
}
|
|
2226
2226
|
}
|
|
2227
|
-
const
|
|
2227
|
+
const pairs2 = [];
|
|
2228
2228
|
for (const key of shapeKeys) {
|
|
2229
2229
|
const keyValidator = shape[key];
|
|
2230
2230
|
const value = ctx.data[key];
|
|
2231
|
-
|
|
2231
|
+
pairs2.push({
|
|
2232
2232
|
key: { status: "valid", value: key },
|
|
2233
2233
|
value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)),
|
|
2234
2234
|
alwaysSet: key in ctx.data
|
|
@@ -2238,7 +2238,7 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
2238
2238
|
const unknownKeys = this._def.unknownKeys;
|
|
2239
2239
|
if (unknownKeys === "passthrough") {
|
|
2240
2240
|
for (const key of extraKeys) {
|
|
2241
|
-
|
|
2241
|
+
pairs2.push({
|
|
2242
2242
|
key: { status: "valid", value: key },
|
|
2243
2243
|
value: { status: "valid", value: ctx.data[key] }
|
|
2244
2244
|
});
|
|
@@ -2259,7 +2259,7 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
2259
2259
|
const catchall = this._def.catchall;
|
|
2260
2260
|
for (const key of extraKeys) {
|
|
2261
2261
|
const value = ctx.data[key];
|
|
2262
|
-
|
|
2262
|
+
pairs2.push({
|
|
2263
2263
|
key: { status: "valid", value: key },
|
|
2264
2264
|
value: catchall._parse(
|
|
2265
2265
|
new ParseInputLazyPath(ctx, value, ctx.path, key)
|
|
@@ -2272,7 +2272,7 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
2272
2272
|
if (ctx.common.async) {
|
|
2273
2273
|
return Promise.resolve().then(async () => {
|
|
2274
2274
|
const syncPairs = [];
|
|
2275
|
-
for (const pair of
|
|
2275
|
+
for (const pair of pairs2) {
|
|
2276
2276
|
const key = await pair.key;
|
|
2277
2277
|
const value = await pair.value;
|
|
2278
2278
|
syncPairs.push({
|
|
@@ -2286,7 +2286,7 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
2286
2286
|
return ParseStatus.mergeObjectSync(status, syncPairs);
|
|
2287
2287
|
});
|
|
2288
2288
|
} else {
|
|
2289
|
-
return ParseStatus.mergeObjectSync(status,
|
|
2289
|
+
return ParseStatus.mergeObjectSync(status, pairs2);
|
|
2290
2290
|
}
|
|
2291
2291
|
}
|
|
2292
2292
|
get shape() {
|
|
@@ -2900,20 +2900,20 @@ var ZodRecord = class _ZodRecord extends ZodType {
|
|
|
2900
2900
|
});
|
|
2901
2901
|
return INVALID;
|
|
2902
2902
|
}
|
|
2903
|
-
const
|
|
2903
|
+
const pairs2 = [];
|
|
2904
2904
|
const keyType = this._def.keyType;
|
|
2905
2905
|
const valueType = this._def.valueType;
|
|
2906
2906
|
for (const key in ctx.data) {
|
|
2907
|
-
|
|
2907
|
+
pairs2.push({
|
|
2908
2908
|
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)),
|
|
2909
2909
|
value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)),
|
|
2910
2910
|
alwaysSet: key in ctx.data
|
|
2911
2911
|
});
|
|
2912
2912
|
}
|
|
2913
2913
|
if (ctx.common.async) {
|
|
2914
|
-
return ParseStatus.mergeObjectAsync(status,
|
|
2914
|
+
return ParseStatus.mergeObjectAsync(status, pairs2);
|
|
2915
2915
|
} else {
|
|
2916
|
-
return ParseStatus.mergeObjectSync(status,
|
|
2916
|
+
return ParseStatus.mergeObjectSync(status, pairs2);
|
|
2917
2917
|
}
|
|
2918
2918
|
}
|
|
2919
2919
|
get element() {
|
|
@@ -2955,7 +2955,7 @@ var ZodMap = class extends ZodType {
|
|
|
2955
2955
|
}
|
|
2956
2956
|
const keyType = this._def.keyType;
|
|
2957
2957
|
const valueType = this._def.valueType;
|
|
2958
|
-
const
|
|
2958
|
+
const pairs2 = [...ctx.data.entries()].map(([key, value], index) => {
|
|
2959
2959
|
return {
|
|
2960
2960
|
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])),
|
|
2961
2961
|
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"]))
|
|
@@ -2964,7 +2964,7 @@ var ZodMap = class extends ZodType {
|
|
|
2964
2964
|
if (ctx.common.async) {
|
|
2965
2965
|
const finalMap = /* @__PURE__ */ new Map();
|
|
2966
2966
|
return Promise.resolve().then(async () => {
|
|
2967
|
-
for (const pair of
|
|
2967
|
+
for (const pair of pairs2) {
|
|
2968
2968
|
const key = await pair.key;
|
|
2969
2969
|
const value = await pair.value;
|
|
2970
2970
|
if (key.status === "aborted" || value.status === "aborted") {
|
|
@@ -2979,7 +2979,7 @@ var ZodMap = class extends ZodType {
|
|
|
2979
2979
|
});
|
|
2980
2980
|
} else {
|
|
2981
2981
|
const finalMap = /* @__PURE__ */ new Map();
|
|
2982
|
-
for (const pair of
|
|
2982
|
+
for (const pair of pairs2) {
|
|
2983
2983
|
const key = pair.key;
|
|
2984
2984
|
const value = pair.value;
|
|
2985
2985
|
if (key.status === "aborted" || value.status === "aborted") {
|
|
@@ -4161,7 +4161,7 @@ var PrimitiveComponent = class extends Renderable {
|
|
|
4161
4161
|
* components
|
|
4162
4162
|
*/
|
|
4163
4163
|
computePcbPropsTransform() {
|
|
4164
|
-
return (0, import_transformation_matrix.compose)((0, import_transformation_matrix.translate)(this.props.pcbX, this.props.pcbY));
|
|
4164
|
+
return (0, import_transformation_matrix.compose)((0, import_transformation_matrix.translate)(this.props.pcbX ?? 0, this.props.pcbY ?? 0));
|
|
4165
4165
|
}
|
|
4166
4166
|
/**
|
|
4167
4167
|
* Compute a transformation matrix combining all parent transforms for PCB
|
|
@@ -4293,6 +4293,16 @@ var PrimitiveComponent = class extends Renderable {
|
|
|
4293
4293
|
}
|
|
4294
4294
|
return this.selectAll(selector)[0] ?? null;
|
|
4295
4295
|
}
|
|
4296
|
+
getAvailablePcbLayers() {
|
|
4297
|
+
if (this.isPcbPrimitive) {
|
|
4298
|
+
if (this.props.layer) return [this.props.layer];
|
|
4299
|
+
if (this.componentName === "PlatedHole") {
|
|
4300
|
+
return ["top", "bottom"];
|
|
4301
|
+
}
|
|
4302
|
+
return [];
|
|
4303
|
+
}
|
|
4304
|
+
return [];
|
|
4305
|
+
}
|
|
4296
4306
|
getDescendants() {
|
|
4297
4307
|
const descendants = [];
|
|
4298
4308
|
for (const child of this.children) {
|
|
@@ -4406,6 +4416,11 @@ var Port = class extends PrimitiveComponent {
|
|
|
4406
4416
|
getPortSelector() {
|
|
4407
4417
|
return `.${this.parent?.props.name} > port.${this.props.name}`;
|
|
4408
4418
|
}
|
|
4419
|
+
getAvailablePcbLayers() {
|
|
4420
|
+
return Array.from(
|
|
4421
|
+
new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers()))
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
4409
4424
|
doInitialSourceRender() {
|
|
4410
4425
|
const { db } = this.project;
|
|
4411
4426
|
const { _parsedProps: props } = this;
|
|
@@ -5104,7 +5119,119 @@ var projectPointInDirection = (point, direction, distance) => {
|
|
|
5104
5119
|
}
|
|
5105
5120
|
};
|
|
5106
5121
|
|
|
5122
|
+
// lib/utils/autorouting/findPossibleTraceLayerCombinations.ts
|
|
5123
|
+
var LAYER_SELECTION_PREFERENCE = ["top", "bottom", "inner1", "inner2"];
|
|
5124
|
+
var findPossibleTraceLayerCombinations = (hints, layer_path = []) => {
|
|
5125
|
+
const candidates = [];
|
|
5126
|
+
if (layer_path.length === 0) {
|
|
5127
|
+
const starting_layers = hints[0].layers;
|
|
5128
|
+
for (const layer of starting_layers) {
|
|
5129
|
+
candidates.push(
|
|
5130
|
+
...findPossibleTraceLayerCombinations(hints.slice(1), [layer])
|
|
5131
|
+
);
|
|
5132
|
+
}
|
|
5133
|
+
return candidates;
|
|
5134
|
+
}
|
|
5135
|
+
if (hints.length === 0) return [];
|
|
5136
|
+
const current_hint = hints[0];
|
|
5137
|
+
const is_possibly_via = current_hint.via || current_hint.optional_via;
|
|
5138
|
+
const last_layer = layer_path[layer_path.length - 1];
|
|
5139
|
+
if (hints.length === 1) {
|
|
5140
|
+
const last_hint = current_hint;
|
|
5141
|
+
if (last_hint.layers && is_possibly_via) {
|
|
5142
|
+
return last_hint.layers.map((layer) => ({
|
|
5143
|
+
layer_path: [...layer_path, layer]
|
|
5144
|
+
}));
|
|
5145
|
+
}
|
|
5146
|
+
if (last_hint.layers?.includes(last_layer)) {
|
|
5147
|
+
return [{ layer_path: [...layer_path, last_layer] }];
|
|
5148
|
+
}
|
|
5149
|
+
return [];
|
|
5150
|
+
}
|
|
5151
|
+
if (!is_possibly_via) {
|
|
5152
|
+
if (current_hint.layers) {
|
|
5153
|
+
if (!current_hint.layers.includes(last_layer)) {
|
|
5154
|
+
return [];
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
return findPossibleTraceLayerCombinations(
|
|
5158
|
+
hints.slice(1),
|
|
5159
|
+
layer_path.concat([last_layer])
|
|
5160
|
+
);
|
|
5161
|
+
}
|
|
5162
|
+
const candidate_next_layers = (current_hint.optional_via ? LAYER_SELECTION_PREFERENCE : LAYER_SELECTION_PREFERENCE.filter((layer) => layer !== last_layer)).filter(
|
|
5163
|
+
(layer) => !current_hint.layers || current_hint.layers?.includes(layer)
|
|
5164
|
+
);
|
|
5165
|
+
for (const candidate_next_layer of candidate_next_layers) {
|
|
5166
|
+
candidates.push(
|
|
5167
|
+
...findPossibleTraceLayerCombinations(
|
|
5168
|
+
hints.slice(1),
|
|
5169
|
+
layer_path.concat(candidate_next_layer)
|
|
5170
|
+
)
|
|
5171
|
+
);
|
|
5172
|
+
}
|
|
5173
|
+
return candidates;
|
|
5174
|
+
};
|
|
5175
|
+
|
|
5176
|
+
// lib/utils/pairs.ts
|
|
5177
|
+
function pairs(arr) {
|
|
5178
|
+
const result = [];
|
|
5179
|
+
for (let i = 0; i < arr.length - 1; i++) {
|
|
5180
|
+
result.push([arr[i], arr[i + 1]]);
|
|
5181
|
+
}
|
|
5182
|
+
return result;
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
// lib/utils/autorouting/mergeRoutes.ts
|
|
5186
|
+
function pdist(a, b) {
|
|
5187
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
5188
|
+
}
|
|
5189
|
+
var mergeRoutes = (routes) => {
|
|
5190
|
+
if (routes.some((r) => r.length === 0)) {
|
|
5191
|
+
throw new Error("Cannot merge routes with zero length");
|
|
5192
|
+
}
|
|
5193
|
+
const merged = [];
|
|
5194
|
+
const first_route_fp = routes[0][0];
|
|
5195
|
+
const first_route_lp = routes[0][routes[0].length - 1];
|
|
5196
|
+
const second_route_fp = routes[1][0];
|
|
5197
|
+
const second_route_lp = routes[1][routes[1].length - 1];
|
|
5198
|
+
const best_reverse_dist = Math.min(
|
|
5199
|
+
pdist(first_route_fp, second_route_fp),
|
|
5200
|
+
pdist(first_route_fp, second_route_lp)
|
|
5201
|
+
);
|
|
5202
|
+
const best_normal_dist = Math.min(
|
|
5203
|
+
pdist(first_route_lp, second_route_fp),
|
|
5204
|
+
pdist(first_route_lp, second_route_lp)
|
|
5205
|
+
);
|
|
5206
|
+
if (best_reverse_dist < best_normal_dist) {
|
|
5207
|
+
merged.push(...routes[0].reverse());
|
|
5208
|
+
} else {
|
|
5209
|
+
merged.push(...routes[0]);
|
|
5210
|
+
}
|
|
5211
|
+
for (let i = 1; i < routes.length; i++) {
|
|
5212
|
+
const last_merged_point = merged[merged.length - 1];
|
|
5213
|
+
const next_route = routes[i];
|
|
5214
|
+
const next_first_point = next_route[0];
|
|
5215
|
+
const next_last_point = next_route[next_route.length - 1];
|
|
5216
|
+
const distance_to_first = pdist(last_merged_point, next_first_point);
|
|
5217
|
+
const distance_to_last = pdist(last_merged_point, next_last_point);
|
|
5218
|
+
if (distance_to_first < distance_to_last) {
|
|
5219
|
+
merged.push(...next_route);
|
|
5220
|
+
} else {
|
|
5221
|
+
merged.push(...next_route.reverse());
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
return merged;
|
|
5225
|
+
};
|
|
5226
|
+
|
|
5107
5227
|
// lib/components/primitive-components/Trace.ts
|
|
5228
|
+
var portToObjective = (port) => {
|
|
5229
|
+
const portPosition = port.getGlobalPcbPosition();
|
|
5230
|
+
return {
|
|
5231
|
+
...portPosition,
|
|
5232
|
+
layers: port.getAvailablePcbLayers()
|
|
5233
|
+
};
|
|
5234
|
+
};
|
|
5108
5235
|
var Trace = class extends PrimitiveComponent {
|
|
5109
5236
|
source_trace_id = null;
|
|
5110
5237
|
pcb_trace_id = null;
|
|
@@ -5181,9 +5308,91 @@ searched component ${targetComponent.getString()}, which has ports:${targetCompo
|
|
|
5181
5308
|
(elm) => elm.type === "pcb_smtpad" || elm.type === "pcb_trace" || elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "source_port" || elm.type === "pcb_port"
|
|
5182
5309
|
);
|
|
5183
5310
|
const source_trace = db.source_trace.get(this.source_trace_id);
|
|
5184
|
-
const
|
|
5185
|
-
|
|
5186
|
-
|
|
5311
|
+
const hints = ports.flatMap(
|
|
5312
|
+
({ port }) => port.matchedComponents.filter((c) => c.componentName === "TraceHint")
|
|
5313
|
+
);
|
|
5314
|
+
const pcbRouteHints = (this._parsedProps.pcbRouteHints ?? []).concat(
|
|
5315
|
+
hints.flatMap((h) => h.getPcbRouteHints())
|
|
5316
|
+
);
|
|
5317
|
+
if (ports.length > 2) {
|
|
5318
|
+
this.renderError(
|
|
5319
|
+
`Trace has more than two ports (${ports.map((p) => p.port.getString()).join(
|
|
5320
|
+
", "
|
|
5321
|
+
)}), routing between more than two ports for a single trace is not implemented`
|
|
5322
|
+
);
|
|
5323
|
+
return;
|
|
5324
|
+
}
|
|
5325
|
+
if (pcbRouteHints.length === 0) {
|
|
5326
|
+
const { solution } = (0, import_infgrid_ijump_astar.autoroute)(pcbElements.concat([source_trace]));
|
|
5327
|
+
const pcb_trace2 = solution[0];
|
|
5328
|
+
db.pcb_trace.insert(pcb_trace2);
|
|
5329
|
+
this.pcb_trace_id = pcb_trace2.pcb_trace_id;
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
const orderedRouteObjectives = [
|
|
5333
|
+
portToObjective(ports[0].port),
|
|
5334
|
+
...pcbRouteHints,
|
|
5335
|
+
portToObjective(ports[1].port)
|
|
5336
|
+
];
|
|
5337
|
+
const candidateLayerCombinations = findPossibleTraceLayerCombinations(
|
|
5338
|
+
orderedRouteObjectives
|
|
5339
|
+
);
|
|
5340
|
+
if (candidateLayerCombinations.length === 0) {
|
|
5341
|
+
this.renderError(
|
|
5342
|
+
`Could not find a common layer (using hints) for trace ${this.getString()}`
|
|
5343
|
+
);
|
|
5344
|
+
}
|
|
5345
|
+
const obstacles = (0, import_infgrid_ijump_astar.getObstaclesFromSoup)(this.project.db.toArray());
|
|
5346
|
+
(0, import_infgrid_ijump_astar.markObstaclesAsConnected)(
|
|
5347
|
+
obstacles,
|
|
5348
|
+
orderedRouteObjectives,
|
|
5349
|
+
this.source_trace_id
|
|
5350
|
+
);
|
|
5351
|
+
const candidateLayerSelections = candidateLayerCombinations[0].layer_path;
|
|
5352
|
+
const orderedRoutePoints = orderedRouteObjectives.map((t, idx) => {
|
|
5353
|
+
if (t.via) {
|
|
5354
|
+
return {
|
|
5355
|
+
...t,
|
|
5356
|
+
via_to_layer: candidateLayerSelections[idx]
|
|
5357
|
+
};
|
|
5358
|
+
}
|
|
5359
|
+
return { ...t, layers: [candidateLayerSelections[idx]] };
|
|
5360
|
+
});
|
|
5361
|
+
const routes = [];
|
|
5362
|
+
for (const [a, b] of pairs(orderedRoutePoints)) {
|
|
5363
|
+
const BOUNDS_MARGIN = 2;
|
|
5364
|
+
const ijump = new import_infgrid_ijump_astar.IJumpAutorouter({
|
|
5365
|
+
input: {
|
|
5366
|
+
obstacles,
|
|
5367
|
+
connections: [
|
|
5368
|
+
{
|
|
5369
|
+
name: this.source_trace_id,
|
|
5370
|
+
pointsToConnect: [a, b]
|
|
5371
|
+
}
|
|
5372
|
+
],
|
|
5373
|
+
layerCount: 1,
|
|
5374
|
+
bounds: {
|
|
5375
|
+
minX: Math.min(a.x, b.x) - BOUNDS_MARGIN,
|
|
5376
|
+
maxX: Math.max(a.x, b.x) + BOUNDS_MARGIN,
|
|
5377
|
+
minY: Math.min(a.y, b.y) - BOUNDS_MARGIN,
|
|
5378
|
+
maxY: Math.max(a.y, b.y) + BOUNDS_MARGIN
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
});
|
|
5382
|
+
const traces = ijump.solveAndMapToTraces();
|
|
5383
|
+
if (traces.length === 0) {
|
|
5384
|
+
this.renderError(
|
|
5385
|
+
`Could not find a route between ${a.x}, ${a.y} and ${b.x}, ${b.y}`
|
|
5386
|
+
);
|
|
5387
|
+
return;
|
|
5388
|
+
}
|
|
5389
|
+
const [trace] = traces;
|
|
5390
|
+
routes.push(trace.route);
|
|
5391
|
+
}
|
|
5392
|
+
const pcb_trace = db.pcb_trace.insert({
|
|
5393
|
+
route: mergeRoutes(routes),
|
|
5394
|
+
source_trace_id: this.source_trace_id
|
|
5395
|
+
});
|
|
5187
5396
|
this.pcb_trace_id = pcb_trace.pcb_trace_id;
|
|
5188
5397
|
}
|
|
5189
5398
|
doInitialSchematicTraceRender() {
|
|
@@ -5252,7 +5461,47 @@ searched component ${targetComponent.getString()}, which has ports:${targetCompo
|
|
|
5252
5461
|
|
|
5253
5462
|
// lib/components/primitive-components/TraceHint.ts
|
|
5254
5463
|
var import_props7 = require("@tscircuit/props");
|
|
5464
|
+
var import_transformation_matrix4 = require("transformation-matrix");
|
|
5255
5465
|
var TraceHint = class extends PrimitiveComponent {
|
|
5466
|
+
matchedPort = null;
|
|
5467
|
+
doInitialPortMatching() {
|
|
5468
|
+
const { db } = this.project;
|
|
5469
|
+
const { _parsedProps: props, parent } = this;
|
|
5470
|
+
if (!parent) return;
|
|
5471
|
+
if (parent.componentName === "Trace") {
|
|
5472
|
+
this.renderError(
|
|
5473
|
+
`Port inference inside trace is not yet supported (${this})`
|
|
5474
|
+
);
|
|
5475
|
+
return;
|
|
5476
|
+
}
|
|
5477
|
+
if (!parent) throw new Error("TraceHint has no parent");
|
|
5478
|
+
if (!props.for) {
|
|
5479
|
+
this.renderError(`TraceHint has no for property (${this})`);
|
|
5480
|
+
return;
|
|
5481
|
+
}
|
|
5482
|
+
const port = parent.selectOne(props.for, { type: "port" });
|
|
5483
|
+
if (!port) {
|
|
5484
|
+
this.renderError(
|
|
5485
|
+
`${this} could not find port for selector "${props.for}"`
|
|
5486
|
+
);
|
|
5487
|
+
}
|
|
5488
|
+
this.matchedPort = port;
|
|
5489
|
+
port.registerMatch(this);
|
|
5490
|
+
}
|
|
5491
|
+
getPcbRouteHints() {
|
|
5492
|
+
const { _parsedProps: props } = this;
|
|
5493
|
+
const offsets = props.offset ? [props.offset] : props.offsets;
|
|
5494
|
+
if (!offsets) return [];
|
|
5495
|
+
const globalTransform = this.computePcbGlobalTransform();
|
|
5496
|
+
return offsets.map(
|
|
5497
|
+
(offset) => ({
|
|
5498
|
+
...(0, import_transformation_matrix4.applyToPoint)(globalTransform, offset),
|
|
5499
|
+
via: offset.via,
|
|
5500
|
+
to_layer: offset.to_layer,
|
|
5501
|
+
trace_width: offset.trace_width
|
|
5502
|
+
})
|
|
5503
|
+
);
|
|
5504
|
+
}
|
|
5256
5505
|
};
|
|
5257
5506
|
|
|
5258
5507
|
// lib/components/primitive-components/Group.ts
|
|
@@ -5268,7 +5517,7 @@ var Group = class extends PrimitiveComponent {
|
|
|
5268
5517
|
// lib/Project.ts
|
|
5269
5518
|
var import_soup_util = require("@tscircuit/soup-util");
|
|
5270
5519
|
var import_react4 = require("react");
|
|
5271
|
-
var
|
|
5520
|
+
var import_transformation_matrix5 = require("transformation-matrix");
|
|
5272
5521
|
var Project = class {
|
|
5273
5522
|
rootComponent = null;
|
|
5274
5523
|
children;
|
|
@@ -5324,10 +5573,16 @@ var Project = class {
|
|
|
5324
5573
|
return this.getSoup();
|
|
5325
5574
|
}
|
|
5326
5575
|
computeGlobalSchematicTransform() {
|
|
5327
|
-
return (0,
|
|
5576
|
+
return (0, import_transformation_matrix5.identity)();
|
|
5328
5577
|
}
|
|
5329
5578
|
computeGlobalPcbTransform() {
|
|
5330
|
-
return (0,
|
|
5579
|
+
return (0, import_transformation_matrix5.identity)();
|
|
5580
|
+
}
|
|
5581
|
+
selectAll(selector) {
|
|
5582
|
+
return this.rootComponent?.selectAll(selector) ?? [];
|
|
5583
|
+
}
|
|
5584
|
+
selectOne(selector) {
|
|
5585
|
+
return this.rootComponent?.selectOne(selector) ?? null;
|
|
5331
5586
|
}
|
|
5332
5587
|
};
|
|
5333
5588
|
|
package/lib/Project.ts
CHANGED
|
@@ -81,4 +81,12 @@ export class Project {
|
|
|
81
81
|
computeGlobalPcbTransform(): Matrix {
|
|
82
82
|
return identity()
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
selectAll(selector: string): PrimitiveComponent[] {
|
|
86
|
+
return this.rootComponent?.selectAll(selector) ?? []
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
selectOne(selector: string): PrimitiveComponent | null {
|
|
90
|
+
return this.rootComponent?.selectOne(selector) ?? null
|
|
91
|
+
}
|
|
84
92
|
}
|
|
@@ -95,7 +95,7 @@ export abstract class PrimitiveComponent<
|
|
|
95
95
|
*/
|
|
96
96
|
computePcbPropsTransform(): Matrix {
|
|
97
97
|
// TODO rotations
|
|
98
|
-
return compose(translate(this.props.pcbX, this.props.pcbY))
|
|
98
|
+
return compose(translate(this.props.pcbX ?? 0, this.props.pcbY ?? 0))
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
@@ -268,6 +268,17 @@ export abstract class PrimitiveComponent<
|
|
|
268
268
|
return this.selectAll(selector)[0] ?? null
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
getAvailablePcbLayers(): string[] {
|
|
272
|
+
if (this.isPcbPrimitive) {
|
|
273
|
+
if (this.props.layer) return [this.props.layer]
|
|
274
|
+
if (this.componentName === "PlatedHole") {
|
|
275
|
+
return ["top", "bottom"] // TODO derive layers from parent
|
|
276
|
+
}
|
|
277
|
+
return []
|
|
278
|
+
}
|
|
279
|
+
return []
|
|
280
|
+
}
|
|
281
|
+
|
|
271
282
|
getDescendants(): PrimitiveComponent[] {
|
|
272
283
|
const descendants: PrimitiveComponent[] = []
|
|
273
284
|
for (const child of this.children) {
|
|
@@ -88,6 +88,11 @@ export class Port extends PrimitiveComponent<typeof portProps> {
|
|
|
88
88
|
return `.${this.parent?.props.name} > port.${this.props.name}`
|
|
89
89
|
// return `#${this.props.id}`
|
|
90
90
|
}
|
|
91
|
+
getAvailablePcbLayers(): string[] {
|
|
92
|
+
return Array.from(
|
|
93
|
+
new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers())),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
91
96
|
|
|
92
97
|
doInitialSourceRender(): void {
|
|
93
98
|
const { db } = this.project!
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { traceProps } from "@tscircuit/props"
|
|
2
2
|
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
|
|
3
3
|
import type { Port } from "./Port"
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
IJumpAutorouter,
|
|
6
|
+
autoroute,
|
|
7
|
+
getObstaclesFromSoup,
|
|
8
|
+
markObstaclesAsConnected,
|
|
9
|
+
} from "@tscircuit/infgrid-ijump-astar"
|
|
10
|
+
import type {
|
|
11
|
+
AnySoupElement,
|
|
12
|
+
PCBTrace,
|
|
13
|
+
RouteHintPoint,
|
|
14
|
+
SchematicTrace,
|
|
15
|
+
} from "@tscircuit/soup"
|
|
6
16
|
import type {
|
|
7
17
|
Obstacle,
|
|
8
18
|
SimpleRouteConnection,
|
|
@@ -10,6 +20,22 @@ import type {
|
|
|
10
20
|
} from "lib/utils/autorouting/SimpleRouteJson"
|
|
11
21
|
import { computeObstacleBounds } from "lib/utils/autorouting/computeObstacleBounds"
|
|
12
22
|
import { projectPointInDirection } from "lib/utils/projectPointInDirection"
|
|
23
|
+
import type { TraceHint } from "./TraceHint"
|
|
24
|
+
import { findPossibleTraceLayerCombinations } from "lib/utils/autorouting/findPossibleTraceLayerCombinations"
|
|
25
|
+
import { pairs } from "lib/utils/pairs"
|
|
26
|
+
import { mergeRoutes } from "lib/utils/autorouting/mergeRoutes"
|
|
27
|
+
|
|
28
|
+
type PcbRouteObjective =
|
|
29
|
+
| RouteHintPoint
|
|
30
|
+
| { layers: string[]; x: number; y: number; via?: boolean }
|
|
31
|
+
|
|
32
|
+
const portToObjective = (port: Port): PcbRouteObjective => {
|
|
33
|
+
const portPosition = port.getGlobalPcbPosition()
|
|
34
|
+
return {
|
|
35
|
+
...portPosition,
|
|
36
|
+
layers: port.getAvailablePcbLayers(),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
13
39
|
|
|
14
40
|
export class Trace extends PrimitiveComponent<typeof traceProps> {
|
|
15
41
|
source_trace_id: string | null = null
|
|
@@ -124,14 +150,119 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
|
|
|
124
150
|
|
|
125
151
|
const source_trace = db.source_trace.get(this.source_trace_id!)!
|
|
126
152
|
|
|
127
|
-
const
|
|
153
|
+
const hints = ports.flatMap(({ port }) =>
|
|
154
|
+
port.matchedComponents.filter((c) => c.componentName === "TraceHint"),
|
|
155
|
+
) as TraceHint[]
|
|
156
|
+
|
|
157
|
+
const pcbRouteHints = (this._parsedProps.pcbRouteHints ?? []).concat(
|
|
158
|
+
hints.flatMap((h) => h.getPcbRouteHints()),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (ports.length > 2) {
|
|
162
|
+
this.renderError(
|
|
163
|
+
`Trace has more than two ports (${ports
|
|
164
|
+
.map((p) => p.port.getString())
|
|
165
|
+
.join(
|
|
166
|
+
", ",
|
|
167
|
+
)}), routing between more than two ports for a single trace is not implemented`,
|
|
168
|
+
)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
128
171
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
172
|
+
if (pcbRouteHints.length === 0) {
|
|
173
|
+
const { solution } = autoroute(pcbElements.concat([source_trace]))
|
|
174
|
+
// TODO for some reason, the solution gets duplicated inside ijump-astar
|
|
175
|
+
const pcb_trace = solution[0]
|
|
176
|
+
db.pcb_trace.insert(pcb_trace)
|
|
177
|
+
this.pcb_trace_id = pcb_trace.pcb_trace_id
|
|
178
|
+
return
|
|
179
|
+
}
|
|
132
180
|
|
|
133
|
-
|
|
181
|
+
// When we have hints, we have to order the hints then route between each
|
|
182
|
+
// terminal of the trace and the hints
|
|
183
|
+
// TODO order based on proximity to ports
|
|
184
|
+
const orderedRouteObjectives: PcbRouteObjective[] = [
|
|
185
|
+
portToObjective(ports[0].port),
|
|
186
|
+
...pcbRouteHints,
|
|
187
|
+
portToObjective(ports[1].port),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
// Hints can indicate where there should be a via, but the layer is allowed
|
|
191
|
+
// to be unspecified, therefore we need to find possible layer combinations
|
|
192
|
+
// to go to each hint and still route to the start and end points
|
|
193
|
+
const candidateLayerCombinations = findPossibleTraceLayerCombinations(
|
|
194
|
+
orderedRouteObjectives,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if (candidateLayerCombinations.length === 0) {
|
|
198
|
+
this.renderError(
|
|
199
|
+
`Could not find a common layer (using hints) for trace ${this.getString()}`,
|
|
200
|
+
)
|
|
201
|
+
}
|
|
134
202
|
|
|
203
|
+
// Cache the PCB obstacles, they'll be needed for each segment between
|
|
204
|
+
// ports/hints
|
|
205
|
+
const obstacles = getObstaclesFromSoup(this.project!.db.toArray())
|
|
206
|
+
markObstaclesAsConnected(
|
|
207
|
+
obstacles,
|
|
208
|
+
orderedRouteObjectives,
|
|
209
|
+
this.source_trace_id!,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// TODO explore all candidate layer combinations if one fails
|
|
213
|
+
const candidateLayerSelections = candidateLayerCombinations[0].layer_path
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Apply the candidate layer selections to the route objectives, now we
|
|
217
|
+
* have a set of points that have definite layers
|
|
218
|
+
*/
|
|
219
|
+
const orderedRoutePoints = orderedRouteObjectives.map((t, idx) => {
|
|
220
|
+
if (t.via) {
|
|
221
|
+
return {
|
|
222
|
+
...t,
|
|
223
|
+
via_to_layer: candidateLayerSelections[idx],
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { ...t, layers: [candidateLayerSelections[idx]] }
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const routes: PCBTrace["route"][] = []
|
|
230
|
+
for (const [a, b] of pairs(orderedRoutePoints)) {
|
|
231
|
+
const BOUNDS_MARGIN = 2 //mm
|
|
232
|
+
const ijump = new IJumpAutorouter({
|
|
233
|
+
input: {
|
|
234
|
+
obstacles,
|
|
235
|
+
connections: [
|
|
236
|
+
{
|
|
237
|
+
name: this.source_trace_id!,
|
|
238
|
+
pointsToConnect: [a, b],
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
layerCount: 1,
|
|
242
|
+
bounds: {
|
|
243
|
+
minX: Math.min(a.x, b.x) - BOUNDS_MARGIN,
|
|
244
|
+
maxX: Math.max(a.x, b.x) + BOUNDS_MARGIN,
|
|
245
|
+
minY: Math.min(a.y, b.y) - BOUNDS_MARGIN,
|
|
246
|
+
maxY: Math.max(a.y, b.y) + BOUNDS_MARGIN,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
})
|
|
250
|
+
const traces = ijump.solveAndMapToTraces()
|
|
251
|
+
if (traces.length === 0) {
|
|
252
|
+
this.renderError(
|
|
253
|
+
`Could not find a route between ${a.x}, ${a.y} and ${b.x}, ${b.y}`,
|
|
254
|
+
)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
// TODO ijump returns multiple traces for some reason
|
|
258
|
+
const [trace] = traces as PCBTrace[]
|
|
259
|
+
routes.push(trace.route)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const pcb_trace = db.pcb_trace.insert({
|
|
263
|
+
route: mergeRoutes(routes),
|
|
264
|
+
source_trace_id: this.source_trace_id!,
|
|
265
|
+
})
|
|
135
266
|
this.pcb_trace_id = pcb_trace.pcb_trace_id
|
|
136
267
|
}
|
|
137
268
|
|
|
@@ -1,4 +1,60 @@
|
|
|
1
1
|
import { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent"
|
|
2
|
-
import {
|
|
2
|
+
import { traceHintProps } from "@tscircuit/props"
|
|
3
|
+
import type { Port } from "./Port"
|
|
4
|
+
import type { RouteHintPoint } from "@tscircuit/soup"
|
|
5
|
+
import { applyToPoint } from "transformation-matrix"
|
|
3
6
|
|
|
4
|
-
export class TraceHint extends PrimitiveComponent<typeof
|
|
7
|
+
export class TraceHint extends PrimitiveComponent<typeof traceHintProps> {
|
|
8
|
+
matchedPort: Port | null = null
|
|
9
|
+
|
|
10
|
+
doInitialPortMatching(): void {
|
|
11
|
+
const { db } = this.project!
|
|
12
|
+
const { _parsedProps: props, parent } = this
|
|
13
|
+
|
|
14
|
+
if (!parent) return
|
|
15
|
+
|
|
16
|
+
if (parent.componentName === "Trace") {
|
|
17
|
+
this.renderError(
|
|
18
|
+
`Port inference inside trace is not yet supported (${this})`,
|
|
19
|
+
)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!parent) throw new Error("TraceHint has no parent")
|
|
24
|
+
|
|
25
|
+
if (!props.for) {
|
|
26
|
+
this.renderError(`TraceHint has no for property (${this})`)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const port = parent.selectOne(props.for, { type: "port" }) as Port
|
|
31
|
+
|
|
32
|
+
if (!port) {
|
|
33
|
+
this.renderError(
|
|
34
|
+
`${this} could not find port for selector "${props.for}"`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.matchedPort = port
|
|
39
|
+
port.registerMatch(this)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getPcbRouteHints(): Array<RouteHintPoint> {
|
|
43
|
+
const { _parsedProps: props } = this
|
|
44
|
+
|
|
45
|
+
const offsets = props.offset ? [props.offset] : props.offsets
|
|
46
|
+
|
|
47
|
+
if (!offsets) return []
|
|
48
|
+
|
|
49
|
+
const globalTransform = this.computePcbGlobalTransform()
|
|
50
|
+
|
|
51
|
+
return offsets.map(
|
|
52
|
+
(offset): RouteHintPoint => ({
|
|
53
|
+
...applyToPoint(globalTransform, offset),
|
|
54
|
+
via: offset.via,
|
|
55
|
+
to_layer: offset.to_layer,
|
|
56
|
+
trace_width: offset.trace_width,
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
interface Hint {
|
|
2
|
+
via?: boolean
|
|
3
|
+
optional_via?: boolean
|
|
4
|
+
layers?: Array<string>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const LAYER_SELECTION_PREFERENCE = ["top", "bottom", "inner1", "inner2"]
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ORDERING OF CANDIDATES: Example:
|
|
11
|
+
* top -> top -> bottom -> bottom
|
|
12
|
+
* bottom -> bottom -> top -> top
|
|
13
|
+
* top -> bottom -> bottom -> top
|
|
14
|
+
* bottom -> top -> top -> bottom
|
|
15
|
+
*/
|
|
16
|
+
interface CandidateTraceLayerCombination {
|
|
17
|
+
layer_path: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Given a set of hints that contain layers or indication there should be a via,
|
|
22
|
+
* this function returns all combinations of layers that could be traversed.
|
|
23
|
+
*
|
|
24
|
+
* EXAMPLE 1:
|
|
25
|
+
* INPUT:
|
|
26
|
+
* [top,bottom] -> unspecified -> unspecified/via -> [top, bottom]
|
|
27
|
+
* OUTPUT:
|
|
28
|
+
* top -> top -> bottom -> bottom
|
|
29
|
+
* bottom -> bottom -> top -> top *
|
|
30
|
+
* EXAMPLE 2:
|
|
31
|
+
* INPUT:
|
|
32
|
+
* [top,bottom] -> unspecified -> unspecified/via -> unspecified/via -> [top, bottom]
|
|
33
|
+
* OUTPUT:
|
|
34
|
+
* top -> top -> bottom -> top -> top
|
|
35
|
+
* bottom -> bottom -> top-> bottom -> bottom
|
|
36
|
+
* bottom -> bottom -> inner-1 -> inner-1 -> bottom
|
|
37
|
+
*/
|
|
38
|
+
export const findPossibleTraceLayerCombinations = (
|
|
39
|
+
hints: Hint[],
|
|
40
|
+
layer_path: string[] = [],
|
|
41
|
+
): CandidateTraceLayerCombination[] => {
|
|
42
|
+
const candidates: CandidateTraceLayerCombination[] = []
|
|
43
|
+
if (layer_path.length === 0) {
|
|
44
|
+
const starting_layers = hints[0].layers!
|
|
45
|
+
for (const layer of starting_layers) {
|
|
46
|
+
candidates.push(
|
|
47
|
+
...findPossibleTraceLayerCombinations(hints.slice(1), [layer]),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
return candidates
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (hints.length === 0) return []
|
|
54
|
+
const current_hint = hints[0]
|
|
55
|
+
const is_possibly_via = current_hint.via || current_hint.optional_via
|
|
56
|
+
const last_layer = layer_path[layer_path.length - 1]
|
|
57
|
+
|
|
58
|
+
if (hints.length === 1) {
|
|
59
|
+
const last_hint = current_hint
|
|
60
|
+
if (last_hint.layers && is_possibly_via) {
|
|
61
|
+
return last_hint.layers.map((layer) => ({
|
|
62
|
+
layer_path: [...layer_path, layer],
|
|
63
|
+
}))
|
|
64
|
+
}
|
|
65
|
+
if (last_hint.layers?.includes(last_layer)) {
|
|
66
|
+
return [{ layer_path: [...layer_path, last_layer] }]
|
|
67
|
+
}
|
|
68
|
+
return []
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!is_possibly_via) {
|
|
72
|
+
if (current_hint.layers) {
|
|
73
|
+
if (!current_hint.layers.includes(last_layer)) {
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return findPossibleTraceLayerCombinations(
|
|
79
|
+
hints.slice(1),
|
|
80
|
+
layer_path.concat([last_layer]),
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const candidate_next_layers = (
|
|
85
|
+
current_hint.optional_via
|
|
86
|
+
? LAYER_SELECTION_PREFERENCE
|
|
87
|
+
: LAYER_SELECTION_PREFERENCE.filter((layer) => layer !== last_layer)
|
|
88
|
+
).filter(
|
|
89
|
+
(layer) => !current_hint.layers || current_hint.layers?.includes(layer),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
for (const candidate_next_layer of candidate_next_layers) {
|
|
93
|
+
candidates.push(
|
|
94
|
+
...findPossibleTraceLayerCombinations(
|
|
95
|
+
hints.slice(1),
|
|
96
|
+
layer_path.concat(candidate_next_layer),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return candidates
|
|
102
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { PCBTrace } from "@tscircuit/soup"
|
|
2
|
+
|
|
3
|
+
function pdist(a: any, b: any) {
|
|
4
|
+
return Math.hypot(a.x - b.x, a.y - b.y)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Merge multiple routes into a single route.
|
|
9
|
+
*
|
|
10
|
+
* If the end of the next route is closer to the end of the previous route,
|
|
11
|
+
* reverse the next route and append it to the previous route.
|
|
12
|
+
*/
|
|
13
|
+
export const mergeRoutes = (routes: PCBTrace["route"][]) => {
|
|
14
|
+
// routes = routes.filter((route) => route.length > 0)
|
|
15
|
+
if (routes.some((r) => r.length === 0)) {
|
|
16
|
+
throw new Error("Cannot merge routes with zero length")
|
|
17
|
+
}
|
|
18
|
+
// for (const route of routes) {
|
|
19
|
+
// console.table(route)
|
|
20
|
+
// }
|
|
21
|
+
const merged: PCBTrace["route"] = []
|
|
22
|
+
// const reverse_log: boolean[] = []
|
|
23
|
+
|
|
24
|
+
// Determine if the first route should be reversed
|
|
25
|
+
const first_route_fp = routes[0][0]
|
|
26
|
+
const first_route_lp = routes[0][routes[0].length - 1]
|
|
27
|
+
|
|
28
|
+
const second_route_fp = routes[1][0]
|
|
29
|
+
const second_route_lp = routes[1][routes[1].length - 1]
|
|
30
|
+
|
|
31
|
+
const best_reverse_dist = Math.min(
|
|
32
|
+
pdist(first_route_fp, second_route_fp),
|
|
33
|
+
pdist(first_route_fp, second_route_lp),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const best_normal_dist = Math.min(
|
|
37
|
+
pdist(first_route_lp, second_route_fp),
|
|
38
|
+
pdist(first_route_lp, second_route_lp),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if (best_reverse_dist < best_normal_dist) {
|
|
42
|
+
merged.push(...routes[0].reverse())
|
|
43
|
+
// reverse_log.push(true)
|
|
44
|
+
} else {
|
|
45
|
+
merged.push(...routes[0])
|
|
46
|
+
// reverse_log.push(false)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (let i = 1; i < routes.length; i++) {
|
|
50
|
+
const last_merged_point = merged[merged.length - 1]
|
|
51
|
+
const next_route = routes[i]
|
|
52
|
+
|
|
53
|
+
const next_first_point = next_route[0]
|
|
54
|
+
const next_last_point = next_route[next_route.length - 1]
|
|
55
|
+
|
|
56
|
+
const distance_to_first = pdist(last_merged_point, next_first_point)
|
|
57
|
+
const distance_to_last = pdist(last_merged_point, next_last_point)
|
|
58
|
+
|
|
59
|
+
if (distance_to_first < distance_to_last) {
|
|
60
|
+
// reverse_log.push(false)
|
|
61
|
+
merged.push(...next_route)
|
|
62
|
+
} else {
|
|
63
|
+
// reverse_log.push(true)
|
|
64
|
+
merged.push(...next_route.reverse())
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// console.log(reverse_log)
|
|
68
|
+
// console.table(merged)
|
|
69
|
+
return merged
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "index.ts",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.3",
|
|
7
7
|
"main": "dist/index.cjs",
|
|
8
8
|
"files": [
|
|
9
9
|
"index.ts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"typescript": "^5.0.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@tscircuit/infgrid-ijump-astar": "0.0.
|
|
31
|
-
"@tscircuit/props": "^0.0.
|
|
30
|
+
"@tscircuit/infgrid-ijump-astar": "0.0.4",
|
|
31
|
+
"@tscircuit/props": "^0.0.37",
|
|
32
32
|
"@tscircuit/soup": "^0.0.52",
|
|
33
33
|
"@tscircuit/soup-util": "0.0.18",
|
|
34
34
|
"footprinter": "^0.0.44",
|