@willcgage/module-schematic 0.97.0 → 0.98.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
@@ -229,7 +229,7 @@ function moduleCenterline(input) {
229
229
  if (drawn) return samplePath(drawn);
230
230
  const chained = sectionedCenterline(input);
231
231
  if (chained.length >= 2) return chained;
232
- if (!input.geometryType) return [];
232
+ if (!input.geometryType && !input.hasPlacedTrack) return [];
233
233
  const L = input.lengthInches > 0 ? input.lengthInches : 24;
234
234
  const gt = input.geometryType;
235
235
  if (gt === "dead_end") return [{ x: 0, y: 0 }];
@@ -2313,6 +2313,16 @@ function pieceRoutePaths(piece, library = BUILT_IN_TRACK_PARTS) {
2313
2313
  const len = part.overallLength?.inches ?? BUMPER_DRAWN_INCHES;
2314
2314
  return [{ route: ["a", "a"], points: [{ x: at2.x, y: at2.y }, place(len, 0)] }];
2315
2315
  }
2316
+ if (part.kind === "curve") {
2317
+ const a = joints.find((j) => j.joint === "a");
2318
+ const b = joints.find((j) => j.joint === "b");
2319
+ if (!a || !b) return out;
2320
+ const pts = flexRunPoints(sectionalArcInches(part), part.radius.inches).map(
2321
+ (q) => place(q.x, q.y)
2322
+ );
2323
+ pts[pts.length - 1] = { x: b.x, y: b.y };
2324
+ return [{ route: ["a", "b"], points: pts }];
2325
+ }
2316
2326
  for (const route of geo.routes) {
2317
2327
  const a = at(route[0]);
2318
2328
  const b = at(route[1]);
@@ -2543,6 +2553,83 @@ function fitFlexBetween(piece, others, library = BUILT_IN_TRACK_PARTS, withinInc
2543
2553
  lengthInches: Math.round(len * 1e3) / 1e3
2544
2554
  };
2545
2555
  }
2556
+ function insertIntoRun(pieces, fresh, at, library = BUILT_IN_TRACK_PARTS, withinInches = 1) {
2557
+ let host = null;
2558
+ let bestD = Infinity;
2559
+ for (const piece of pieces) {
2560
+ const part = library.find((x) => x.id === piece.partId);
2561
+ if (part?.kind !== "flex") continue;
2562
+ for (const { points } of pieceRoutePaths(piece, library))
2563
+ for (let i = 1; i < points.length; i++) {
2564
+ const d = distanceToSegment(at, points[i - 1], points[i]);
2565
+ if (d < bestD) {
2566
+ bestD = d;
2567
+ host = piece;
2568
+ }
2569
+ }
2570
+ }
2571
+ if (!host || bestD > withinInches) return null;
2572
+ const L = host.lengthInches ?? 0;
2573
+ const R = host.radiusInches;
2574
+ const steps = 200;
2575
+ let s = 0;
2576
+ let closest = Infinity;
2577
+ for (let i = 0; i <= steps; i++) {
2578
+ const t = L * i / steps;
2579
+ const local = flexRunEnd(t, R);
2580
+ const world = placeLocal(host, local.x, local.y);
2581
+ const d = Math.hypot(world.x - at.x, world.y - at.y);
2582
+ if (d < closest) {
2583
+ closest = d;
2584
+ s = t;
2585
+ }
2586
+ }
2587
+ const cutLocal = flexRunEnd(s, R);
2588
+ const cut = placeLocal(host, cutLocal.x, cutLocal.y);
2589
+ const heading = norm360(host.rotationDeg + (host.flipped ? -cutLocal.headingDeg : cutLocal.headingDeg));
2590
+ const placed = { ...fresh, x: cut.x, y: cut.y, rotationDeg: heading };
2591
+ const js = placedJoints([placed], library);
2592
+ const entry = js.find((j) => j.joint === "throat" || j.joint === "a");
2593
+ const exit = js.find((j) => j.joint === "through" || j.joint === "b");
2594
+ if (!entry || !exit) return null;
2595
+ const body = Math.hypot(exit.x - entry.x, exit.y - entry.y);
2596
+ const rest = L - s - body;
2597
+ if (s < 0 || rest < 0) return null;
2598
+ const ids = new Set(pieces.map((p) => p.id));
2599
+ let n = 1;
2600
+ while (ids.has(`${host.id}b${n}`)) n += 1;
2601
+ const tail = {
2602
+ ...host,
2603
+ id: `${host.id}b${n}`,
2604
+ x: exit.x,
2605
+ y: exit.y,
2606
+ rotationDeg: exit.headingDeg,
2607
+ lengthInches: rest
2608
+ };
2609
+ const out = pieces.map((p) => p.id === host.id ? { ...p, lengthInches: s } : p);
2610
+ const kept = out.filter((p) => p.id !== host.id || s > 0);
2611
+ return {
2612
+ pieces: [...kept, placed, ...rest > 0 ? [tail] : []],
2613
+ hostId: host.id,
2614
+ insertedId: placed.id
2615
+ };
2616
+ }
2617
+ function placeLocal(piece, x, y) {
2618
+ const rad = piece.rotationDeg * Math.PI / 180;
2619
+ const ly = piece.flipped ? -y : y;
2620
+ return {
2621
+ x: piece.x + x * Math.cos(rad) - ly * Math.sin(rad),
2622
+ y: piece.y + x * Math.sin(rad) + ly * Math.cos(rad)
2623
+ };
2624
+ }
2625
+ function distanceToSegment(p, a, b) {
2626
+ const vx = b.x - a.x;
2627
+ const vy = b.y - a.y;
2628
+ const len2 = vx * vx + vy * vy;
2629
+ if (len2 === 0) return Math.hypot(p.x - a.x, p.y - a.y);
2630
+ const t = Math.max(0, Math.min(1, ((p.x - a.x) * vx + (p.y - a.y) * vy) / len2));
2631
+ return Math.hypot(p.x - (a.x + t * vx), p.y - (a.y + t * vy));
2632
+ }
2546
2633
  var danglingDivergeWarning = (id) => `the route diverging at ${id} is not connected to anything`;
2547
2634
  var unreachedWarning = (id) => `${id} is not reachable from the endplate \u2014 nothing connects it`;
2548
2635
  function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS) {
@@ -3620,6 +3707,7 @@ exports.graphToDoc = graphToDoc;
3620
3707
  exports.hasNoFarEndplate = hasNoFarEndplate;
3621
3708
  exports.importedPartToTrackPart = importedPartToTrackPart;
3622
3709
  exports.inchesToScaleFeet = inchesToScaleFeet;
3710
+ exports.insertIntoRun = insertIntoRun;
3623
3711
  exports.isLoopDoc = isLoopDoc;
3624
3712
  exports.isTransitionTurnout = isTransitionTurnout;
3625
3713
  exports.laneOffsetAt = laneOffsetAt;