@willcgage/module-schematic 0.89.0 → 0.90.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,36 @@ 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
+ }
2335
2413
  function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
2336
2414
  const byKey = new Map(graph.joints.map((j) => [j.key, j]));
2337
2415
  const pieceById = new Map(pieces.map((p) => [p.id, p]));
@@ -3327,6 +3405,7 @@ exports.partOutlineAtFrog = partOutlineAtFrog;
3327
3405
  exports.partsPlaceable = partsPlaceable;
3328
3406
  exports.pastFrogInchesForSize = pastFrogInchesForSize;
3329
3407
  exports.pathLengthInches = pathLengthInches;
3408
+ exports.pieceRoutePaths = pieceRoutePaths;
3330
3409
  exports.placedJoints = placedJoints;
3331
3410
  exports.poseNeedsManual = poseNeedsManual;
3332
3411
  exports.poseOverridesFromDoc = poseOverridesFromDoc;
@@ -3349,6 +3428,7 @@ exports.sectionSpansOrWhole = sectionSpansOrWhole;
3349
3428
  exports.sectionedCenterline = sectionedCenterline;
3350
3429
  exports.sectionedEndPose = sectionedEndPose;
3351
3430
  exports.sliceCenterline = sliceCenterline;
3431
+ exports.snapPiece = snapPiece;
3352
3432
  exports.spanOverhang = spanOverhang;
3353
3433
  exports.stateToDoc = stateToDoc;
3354
3434
  exports.storedPartToTrackPart = storedPartToTrackPart;