@willcgage/module-schematic 0.85.0 → 0.86.0
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 +195 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -1
- package/dist/index.d.ts +120 -1
- package/dist/index.js +192 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2245,6 +2245,197 @@ function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
|
|
|
2245
2245
|
}
|
|
2246
2246
|
return { placeable, blocked };
|
|
2247
2247
|
}
|
|
2248
|
+
var JOINT_SNAP_INCHES = 0.01;
|
|
2249
|
+
function placedJoints(pieces, library = BUILT_IN_TRACK_PARTS) {
|
|
2250
|
+
const out = [];
|
|
2251
|
+
const RAD = Math.PI / 180;
|
|
2252
|
+
for (const p of pieces) {
|
|
2253
|
+
const part = library.find((x) => x.id === p.partId);
|
|
2254
|
+
if (!part) continue;
|
|
2255
|
+
const geo = partGeometry(part, library);
|
|
2256
|
+
if (!geo) continue;
|
|
2257
|
+
const c = Math.cos(p.rotationDeg * RAD);
|
|
2258
|
+
const s = Math.sin(p.rotationDeg * RAD);
|
|
2259
|
+
for (const j of geo.joints) {
|
|
2260
|
+
const lx = part.kind === "flex" && j.id === "b" ? p.lengthInches ?? 0 : j.x;
|
|
2261
|
+
const ly = p.flipped ? -j.y : j.y;
|
|
2262
|
+
const h = (p.flipped ? -j.angleDeg : j.angleDeg) + p.rotationDeg;
|
|
2263
|
+
out.push({
|
|
2264
|
+
key: `${p.id}.${j.id}`,
|
|
2265
|
+
piece: p.id,
|
|
2266
|
+
joint: j.id,
|
|
2267
|
+
role: j.role,
|
|
2268
|
+
x: p.x + lx * c - ly * s,
|
|
2269
|
+
y: p.y + lx * s + ly * c,
|
|
2270
|
+
headingDeg: norm360(h)
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
return out;
|
|
2275
|
+
}
|
|
2276
|
+
function buildTrackGraph(pieces, library = BUILT_IN_TRACK_PARTS, snapInches = JOINT_SNAP_INCHES) {
|
|
2277
|
+
const joints = placedJoints(pieces, library);
|
|
2278
|
+
const unplaceable = [];
|
|
2279
|
+
for (const p of pieces) {
|
|
2280
|
+
const part = library.find((x) => x.id === p.partId);
|
|
2281
|
+
if (!part) {
|
|
2282
|
+
unplaceable.push({ piece: p.id, partId: p.partId, why: "no such part in the library" });
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
const why = partGeometryGap(part);
|
|
2286
|
+
if (why) unplaceable.push({ piece: p.id, partId: p.partId, why });
|
|
2287
|
+
}
|
|
2288
|
+
const groups = [];
|
|
2289
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2290
|
+
for (const j of joints) {
|
|
2291
|
+
if (taken.has(j.key)) continue;
|
|
2292
|
+
const g = [j];
|
|
2293
|
+
taken.add(j.key);
|
|
2294
|
+
for (const k of joints) {
|
|
2295
|
+
if (taken.has(k.key) || k.piece === j.piece) continue;
|
|
2296
|
+
if (Math.hypot(k.x - j.x, k.y - j.y) <= snapInches) {
|
|
2297
|
+
g.push(k);
|
|
2298
|
+
taken.add(k.key);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
groups.push(g);
|
|
2302
|
+
}
|
|
2303
|
+
const connections = [];
|
|
2304
|
+
const open = [];
|
|
2305
|
+
const conflicts = [];
|
|
2306
|
+
for (const g of groups) {
|
|
2307
|
+
if (g.length === 1) open.push(g[0].key);
|
|
2308
|
+
else if (g.length === 2) connections.push({ a: g[0].key, b: g[1].key });
|
|
2309
|
+
else {
|
|
2310
|
+
conflicts.push({
|
|
2311
|
+
x: g[0].x,
|
|
2312
|
+
y: g[0].y,
|
|
2313
|
+
joints: g.map((j) => j.key),
|
|
2314
|
+
reason: `${g.length} track ends are stacked in one place. Rail has two ends, so a junction of three is a turnout, not a joint \u2014 none of these are joined until one is moved.`
|
|
2315
|
+
});
|
|
2316
|
+
for (const j of g) open.push(j.key);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
return { joints, connections, open, conflicts, unplaceable };
|
|
2320
|
+
}
|
|
2321
|
+
function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
|
|
2322
|
+
const byKey = new Map(graph.joints.map((j) => [j.key, j]));
|
|
2323
|
+
const pieceById = new Map(pieces.map((p) => [p.id, p]));
|
|
2324
|
+
const link = /* @__PURE__ */ new Map();
|
|
2325
|
+
for (const c of graph.connections) {
|
|
2326
|
+
link.set(c.a, c.b);
|
|
2327
|
+
link.set(c.b, c.a);
|
|
2328
|
+
}
|
|
2329
|
+
const partOf = (pid) => {
|
|
2330
|
+
const p = pieceById.get(pid);
|
|
2331
|
+
return p ? library.find((x) => x.id === p.partId) : void 0;
|
|
2332
|
+
};
|
|
2333
|
+
const geoOf = (pid) => {
|
|
2334
|
+
const part = partOf(pid);
|
|
2335
|
+
return part ? partGeometry(part, library) : null;
|
|
2336
|
+
};
|
|
2337
|
+
const gap = (a, b) => a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
|
|
2338
|
+
const routes = [];
|
|
2339
|
+
const turnouts = [];
|
|
2340
|
+
const warnings = [];
|
|
2341
|
+
const queued = /* @__PURE__ */ new Set();
|
|
2342
|
+
const pending = [];
|
|
2343
|
+
const walk = (startKey, startPos, id, bornAt) => {
|
|
2344
|
+
const route = {
|
|
2345
|
+
id,
|
|
2346
|
+
fromPos: startPos,
|
|
2347
|
+
toPos: startPos,
|
|
2348
|
+
bornAt,
|
|
2349
|
+
endsAt: null,
|
|
2350
|
+
pieces: [],
|
|
2351
|
+
lateral: 0
|
|
2352
|
+
};
|
|
2353
|
+
let cur = startKey;
|
|
2354
|
+
let pos = startPos;
|
|
2355
|
+
const guard = /* @__PURE__ */ new Set();
|
|
2356
|
+
while (cur && !guard.has(cur)) {
|
|
2357
|
+
guard.add(cur);
|
|
2358
|
+
const here = byKey.get(cur);
|
|
2359
|
+
if (!here) break;
|
|
2360
|
+
const geo = geoOf(here.piece);
|
|
2361
|
+
if (!geo) break;
|
|
2362
|
+
const opts = geo.routes.filter((r) => r.includes(here.joint));
|
|
2363
|
+
if (!opts.length) break;
|
|
2364
|
+
const pick = opts.find((r) => r.includes("through")) ?? opts[0];
|
|
2365
|
+
const exitJoint = pick[0] === here.joint ? pick[1] : pick[0];
|
|
2366
|
+
const exit = byKey.get(`${here.piece}.${exitJoint}`);
|
|
2367
|
+
const part = partOf(here.piece);
|
|
2368
|
+
if (part && (part.kind === "turnout" || part.kind === "wye")) {
|
|
2369
|
+
const throat = byKey.get(`${here.piece}.throat`);
|
|
2370
|
+
const through = byKey.get(`${here.piece}.through`) ?? byKey.get(`${here.piece}.legA`);
|
|
2371
|
+
const body = gap(throat, through);
|
|
2372
|
+
const lead = part.lead?.inches ?? body / 2;
|
|
2373
|
+
const dvj = byKey.get(`${here.piece}.diverge`) ?? byKey.get(`${here.piece}.legB`);
|
|
2374
|
+
const frogPt = throat && body > 0 && dvj ? {
|
|
2375
|
+
x: throat.x + (dvj.x - throat.x) * lead / body,
|
|
2376
|
+
y: throat.y + (dvj.y - throat.y) * lead / body
|
|
2377
|
+
} : null;
|
|
2378
|
+
const toFrog = here.joint === "throat" ? lead : here.joint === "diverge" || here.joint === "legB" ? frogPt && dvj ? Math.hypot(dvj.x - frogPt.x, dvj.y - frogPt.y) : Math.max(0, body - lead) : Math.max(0, body - lead);
|
|
2379
|
+
const frogPos = pos + toFrog;
|
|
2380
|
+
if (here.joint === "diverge" || here.joint === "legB") {
|
|
2381
|
+
route.endsAt = here.piece;
|
|
2382
|
+
const known = turnouts.find((t) => t.id === here.piece);
|
|
2383
|
+
route.toPos = known ? known.pos : frogPos;
|
|
2384
|
+
break;
|
|
2385
|
+
}
|
|
2386
|
+
turnouts.push({ id: here.piece, pos: frogPos, onRoute: id, divergeRoute: null });
|
|
2387
|
+
for (const dj of ["diverge", "legB"]) {
|
|
2388
|
+
const dk = `${here.piece}.${dj}`;
|
|
2389
|
+
const d = byKey.get(dk);
|
|
2390
|
+
if (!d || queued.has(dk)) continue;
|
|
2391
|
+
queued.add(dk);
|
|
2392
|
+
const fp = throat && body > 0 ? {
|
|
2393
|
+
x: throat.x + (d.x - throat.x) * lead / body,
|
|
2394
|
+
y: throat.y + (d.y - throat.y) * lead / body
|
|
2395
|
+
} : null;
|
|
2396
|
+
pending.push({
|
|
2397
|
+
from: here.piece,
|
|
2398
|
+
at: frogPos,
|
|
2399
|
+
joint: dk,
|
|
2400
|
+
skew: fp ? Math.hypot(d.x - fp.x, d.y - fp.y) : 0
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
route.pieces.push(here.piece);
|
|
2405
|
+
for (const j of graph.joints)
|
|
2406
|
+
if (j.piece === here.piece && Math.abs(j.y) > Math.abs(route.lateral))
|
|
2407
|
+
route.lateral = j.y;
|
|
2408
|
+
pos += gap(here, exit);
|
|
2409
|
+
route.toPos = pos;
|
|
2410
|
+
const next = exit ? link.get(exit.key) : void 0;
|
|
2411
|
+
if (!next) break;
|
|
2412
|
+
cur = next;
|
|
2413
|
+
}
|
|
2414
|
+
return route;
|
|
2415
|
+
};
|
|
2416
|
+
routes.push(walk(`${startAt.piece}.${startAt.joint}`, 0, "main", null));
|
|
2417
|
+
let n = 0;
|
|
2418
|
+
while (pending.length) {
|
|
2419
|
+
const b = pending.shift();
|
|
2420
|
+
const start = link.get(b.joint);
|
|
2421
|
+
if (!start) {
|
|
2422
|
+
warnings.push(`the route diverging at ${b.from} is not connected to anything`);
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
n += 1;
|
|
2426
|
+
const r = walk(start, b.at + b.skew, `route${n}`, b.from);
|
|
2427
|
+
r.fromPos = b.at;
|
|
2428
|
+
routes.push(r);
|
|
2429
|
+
const sw = turnouts.find((t) => t.id === b.from);
|
|
2430
|
+
if (sw && !sw.divergeRoute) sw.divergeRoute = r.id;
|
|
2431
|
+
}
|
|
2432
|
+
const reached = new Set(routes.flatMap((r) => r.pieces));
|
|
2433
|
+
for (const p of pieces)
|
|
2434
|
+
if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
|
|
2435
|
+
warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
|
|
2436
|
+
for (const c of graph.conflicts) warnings.push(c.reason);
|
|
2437
|
+
return { routes, turnouts, warnings };
|
|
2438
|
+
}
|
|
2248
2439
|
function frogCasting(cl, opts = {}) {
|
|
2249
2440
|
const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
|
|
2250
2441
|
const w = opts.reachInches ?? g * 1.5;
|
|
@@ -2892,6 +3083,7 @@ exports.FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES = FREEMO_ENDPLATE_TRACK_FA
|
|
|
2892
3083
|
exports.FREEMO_ENDPLATE_WIDTH_MIN_INCHES = FREEMO_ENDPLATE_WIDTH_MIN_INCHES;
|
|
2893
3084
|
exports.FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
|
|
2894
3085
|
exports.FREEMO_TRACK_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
|
|
3086
|
+
exports.JOINT_SNAP_INCHES = JOINT_SNAP_INCHES;
|
|
2895
3087
|
exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
|
|
2896
3088
|
exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
|
|
2897
3089
|
exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
|
|
@@ -2908,6 +3100,7 @@ exports.benchworkBand = benchworkBand;
|
|
|
2908
3100
|
exports.benchworkOutline = benchworkOutline;
|
|
2909
3101
|
exports.buildCrossover = buildCrossover;
|
|
2910
3102
|
exports.buildPassingSiding = buildPassingSiding;
|
|
3103
|
+
exports.buildTrackGraph = buildTrackGraph;
|
|
2911
3104
|
exports.buildTransition = buildTransition;
|
|
2912
3105
|
exports.carCapacity = carCapacity;
|
|
2913
3106
|
exports.checkEndplateWidth = checkEndplateWidth;
|
|
@@ -2956,6 +3149,7 @@ exports.partOutlineAtFrog = partOutlineAtFrog;
|
|
|
2956
3149
|
exports.partsPlaceable = partsPlaceable;
|
|
2957
3150
|
exports.pastFrogInchesForSize = pastFrogInchesForSize;
|
|
2958
3151
|
exports.pathLengthInches = pathLengthInches;
|
|
3152
|
+
exports.placedJoints = placedJoints;
|
|
2959
3153
|
exports.poseNeedsManual = poseNeedsManual;
|
|
2960
3154
|
exports.poseOverridesFromDoc = poseOverridesFromDoc;
|
|
2961
3155
|
exports.remapPos = remapPos;
|
|
@@ -2988,5 +3182,6 @@ exports.turnoutFacing = turnoutFacing;
|
|
|
2988
3182
|
exports.turnoutOccupiedSpan = turnoutOccupiedSpan;
|
|
2989
3183
|
exports.turnoutPartForSize = turnoutPartForSize;
|
|
2990
3184
|
exports.usableCapacity = usableCapacity;
|
|
3185
|
+
exports.walkTrackGraph = walkTrackGraph;
|
|
2991
3186
|
//# sourceMappingURL=index.cjs.map
|
|
2992
3187
|
//# sourceMappingURL=index.cjs.map
|