@willcgage/module-schematic 0.89.0 → 0.91.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 CHANGED
@@ -2248,6 +2248,54 @@ function partGeometry(part, library = BUILT_IN_TRACK_PARTS) {
2248
2248
  divergingEndMeasured: de.measured
2249
2249
  };
2250
2250
  }
2251
+ function pieceRoutePaths(piece, library = BUILT_IN_TRACK_PARTS) {
2252
+ const part = library.find((p) => p.id === piece.partId);
2253
+ if (!part) return [];
2254
+ const geo = partGeometry(part, library);
2255
+ if (!geo) return [];
2256
+ const joints = placedJoints([piece], library);
2257
+ const at = (id) => joints.find((j) => j.joint === id);
2258
+ const RAD = Math.PI / 180;
2259
+ const c = Math.cos(piece.rotationDeg * RAD);
2260
+ const s = Math.sin(piece.rotationDeg * RAD);
2261
+ const place = (x, y) => {
2262
+ const ly = piece.flipped ? -y : y;
2263
+ return { x: piece.x + x * c - ly * s, y: piece.y + x * s + ly * c };
2264
+ };
2265
+ const out = [];
2266
+ for (const route of geo.routes) {
2267
+ const a = at(route[0]);
2268
+ const b = at(route[1]);
2269
+ if (!a || !b) continue;
2270
+ const ends = [{ x: a.x, y: a.y }, { x: b.x, y: b.y }];
2271
+ const diverging = route.some((r) => r === "diverge" || r === "legA" || r === "legB");
2272
+ if (!diverging || part.kind === "flex") {
2273
+ out.push({ route, points: ends });
2274
+ continue;
2275
+ }
2276
+ const lead = part.lead?.inches ?? 0;
2277
+ const pts0 = part.pointsOffset?.inches ?? 0;
2278
+ const far = geo.joints.find((j) => j.id === route[1]) ?? geo.joints.find((j) => j.id === route[0]);
2279
+ if (!far || !(lead > 0)) {
2280
+ out.push({ route, points: ends });
2281
+ continue;
2282
+ }
2283
+ const isWye = part.kind === "wye";
2284
+ const closure = turnoutClosure(isWye ? part.frogNumber * 2 : part.frogNumber, {
2285
+ leadInches: lead
2286
+ });
2287
+ const mirror = route.includes("legB") ? -1 : 1;
2288
+ const pts = [place(0, 0), place(pts0, 0)];
2289
+ const steps = 12;
2290
+ for (let i = 1; i <= steps; i++) {
2291
+ const x = pts0 + (far.x - pts0) * i / steps;
2292
+ pts.push(place(x, mirror * closure.offsetAt(x - pts0)));
2293
+ }
2294
+ pts[pts.length - 1] = { x: b.x, y: b.y };
2295
+ out.push({ route, points: route[0] === "throat" ? pts : pts.slice().reverse() });
2296
+ }
2297
+ return out;
2298
+ }
2251
2299
  function partsPlaceable(library = BUILT_IN_TRACK_PARTS) {
2252
2300
  const placeable = [];
2253
2301
  const blocked = [];
@@ -2332,6 +2380,37 @@ function buildTrackGraph(pieces, library = BUILT_IN_TRACK_PARTS, snapInches = JO
2332
2380
  }
2333
2381
  return { joints, connections, open, conflicts, unplaceable };
2334
2382
  }
2383
+ function snapPiece(moving, others, library = BUILT_IN_TRACK_PARTS, withinInches = 0.5) {
2384
+ const mine = placedJoints([moving], library);
2385
+ if (!mine.length) return null;
2386
+ const graph = buildTrackGraph(others, library);
2387
+ const taken = new Set(graph.connections.flatMap((c2) => [c2.a, c2.b]));
2388
+ const open = graph.joints.filter((j) => !taken.has(j.key));
2389
+ let best = null;
2390
+ for (const m of mine)
2391
+ for (const t of open) {
2392
+ const d = Math.hypot(t.x - m.x, t.y - m.y);
2393
+ if (d <= withinInches && (!best || d < best.d)) best = { m, t, d };
2394
+ }
2395
+ if (!best) return null;
2396
+ const RAD = Math.PI / 180;
2397
+ const dRot = norm360(best.t.headingDeg + 180 - best.m.headingDeg);
2398
+ const c = Math.cos(dRot * RAD);
2399
+ const s = Math.sin(dRot * RAD);
2400
+ const ox = moving.x - best.m.x;
2401
+ const oy = moving.y - best.m.y;
2402
+ return {
2403
+ piece: {
2404
+ ...moving,
2405
+ rotationDeg: norm360(moving.rotationDeg + dRot),
2406
+ x: best.t.x + ox * c - oy * s,
2407
+ y: best.t.y + ox * s + oy * c
2408
+ },
2409
+ from: best.m.key,
2410
+ to: best.t.key
2411
+ };
2412
+ }
2413
+ var danglingDivergeWarning = (id) => `the route diverging at ${id} is not connected to anything`;
2335
2414
  function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
2336
2415
  const byKey = new Map(graph.joints.map((j) => [j.key, j]));
2337
2416
  const pieceById = new Map(pieces.map((p) => [p.id, p]));
@@ -2352,6 +2431,7 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
2352
2431
  const routes = [];
2353
2432
  const turnouts = [];
2354
2433
  const warnings = [];
2434
+ const danglingDiverges = [];
2355
2435
  const queued = /* @__PURE__ */ new Set();
2356
2436
  const pending = [];
2357
2437
  const walk = (startKey, startPos, id, bornAt) => {
@@ -2442,7 +2522,8 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
2442
2522
  const b = pending.shift();
2443
2523
  const start = link.get(b.joint);
2444
2524
  if (!start) {
2445
- warnings.push(`the route diverging at ${b.from} is not connected to anything`);
2525
+ warnings.push(danglingDivergeWarning(b.from));
2526
+ danglingDiverges.push(b.from);
2446
2527
  continue;
2447
2528
  }
2448
2529
  const already = routes.find((r2) => r2.pieces.includes(byKey.get(start).piece));
@@ -2463,7 +2544,7 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
2463
2544
  if (!reached.has(p.id) && !graph.unplaceable.some((u) => u.piece === p.id))
2464
2545
  warnings.push(`${p.id} is not reachable from the endplate \u2014 nothing connects it`);
2465
2546
  for (const c of graph.conflicts) warnings.push(c.reason);
2466
- return { routes, turnouts, warnings };
2547
+ return { routes, turnouts, warnings, danglingDiverges };
2467
2548
  }
2468
2549
  function resolveGraphAnchor(anchor, walk, pieces, library = BUILT_IN_TRACK_PARTS) {
2469
2550
  const piece = pieces.find((p) => p.id === anchor.piece);
@@ -2484,7 +2565,8 @@ function graphToDoc(pieces, input) {
2484
2565
  const library = input.library ?? BUILT_IN_TRACK_PARTS;
2485
2566
  const graph = buildTrackGraph(pieces, library);
2486
2567
  const walk = walkTrackGraph(graph, pieces, input.startAt, library);
2487
- const warnings = [...walk.warnings];
2568
+ const dangling = new Set(walk.danglingDiverges.map(danglingDivergeWarning));
2569
+ const warnings = walk.warnings.filter((w) => !dangling.has(w));
2488
2570
  const round = (n) => Math.round(n * 100) / 100;
2489
2571
  const base = input.base ?? {};
2490
2572
  const endplates = base.endplates && base.endplates.length ? base.endplates : [{ id: "A", label: "West" }, { id: "B", label: "East" }];
@@ -3327,6 +3409,7 @@ exports.partOutlineAtFrog = partOutlineAtFrog;
3327
3409
  exports.partsPlaceable = partsPlaceable;
3328
3410
  exports.pastFrogInchesForSize = pastFrogInchesForSize;
3329
3411
  exports.pathLengthInches = pathLengthInches;
3412
+ exports.pieceRoutePaths = pieceRoutePaths;
3330
3413
  exports.placedJoints = placedJoints;
3331
3414
  exports.poseNeedsManual = poseNeedsManual;
3332
3415
  exports.poseOverridesFromDoc = poseOverridesFromDoc;
@@ -3349,6 +3432,7 @@ exports.sectionSpansOrWhole = sectionSpansOrWhole;
3349
3432
  exports.sectionedCenterline = sectionedCenterline;
3350
3433
  exports.sectionedEndPose = sectionedEndPose;
3351
3434
  exports.sliceCenterline = sliceCenterline;
3435
+ exports.snapPiece = snapPiece;
3352
3436
  exports.spanOverhang = spanOverhang;
3353
3437
  exports.stateToDoc = stateToDoc;
3354
3438
  exports.storedPartToTrackPart = storedPartToTrackPart;