@willcgage/module-schematic 0.74.0 → 0.76.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 +125 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -1
- package/dist/index.d.ts +160 -1
- package/dist/index.js +121 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -31,10 +31,28 @@ function moduleSections(doc) {
|
|
|
31
31
|
...typeof len === "number" && Number.isFinite(len) && len > 0 ? { lengthInches: len } : {},
|
|
32
32
|
...sec.geometryType ? { geometryType: sec.geometryType } : {},
|
|
33
33
|
...typeof deg === "number" && Number.isFinite(deg) ? { geometryDegrees: deg } : {},
|
|
34
|
-
...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {}
|
|
34
|
+
...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {},
|
|
35
|
+
// The board's two ends (#130). Written only when actually described —
|
|
36
|
+
// an absent end is an ordinary internal joint, and an empty object
|
|
37
|
+
// would claim the owner had said something about it.
|
|
38
|
+
...sectionEnd(sec.endA) ? { endA: sectionEnd(sec.endA) } : {},
|
|
39
|
+
...sectionEnd(sec.endB) ? { endB: sectionEnd(sec.endB) } : {}
|
|
35
40
|
};
|
|
36
41
|
});
|
|
37
42
|
}
|
|
43
|
+
function sectionEnd(end) {
|
|
44
|
+
if (!end) return null;
|
|
45
|
+
const config = end.config === "single" || end.config === "double" || end.config === "none" ? end.config : null;
|
|
46
|
+
const w = end.widthInches;
|
|
47
|
+
const o = end.trackOffsetInches;
|
|
48
|
+
const out = {
|
|
49
|
+
...config ? { config } : {},
|
|
50
|
+
...typeof w === "number" && Number.isFinite(w) && w > 0 ? { widthInches: w } : {},
|
|
51
|
+
// Signed, and 0 is meaningful ("explicitly centred", #93) — keep any finite.
|
|
52
|
+
...typeof o === "number" && Number.isFinite(o) ? { trackOffsetInches: o } : {}
|
|
53
|
+
};
|
|
54
|
+
return Object.keys(out).length ? out : null;
|
|
55
|
+
}
|
|
38
56
|
function sectionFootprints(doc, derive) {
|
|
39
57
|
const spans = derive ? sectionSpans(doc) : [];
|
|
40
58
|
const spanOf = new Map(spans.map((sp) => [sp.id, sp]));
|
|
@@ -577,6 +595,92 @@ function scaleFeetToInches(feet, ratio = N_SCALE_RATIO) {
|
|
|
577
595
|
return feet * 12 / ratio;
|
|
578
596
|
}
|
|
579
597
|
var N_CAR_LENGTH_INCHES = 3.3;
|
|
598
|
+
var CLEARANCE_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
|
|
599
|
+
function clearancePointPastFrogInches(size, library = BUILT_IN_TRACK_PARTS, clearanceInches = CLEARANCE_SPACING_INCHES) {
|
|
600
|
+
const N = size > 0 ? size : 6;
|
|
601
|
+
const lead = leadInchesForSize(N, library);
|
|
602
|
+
const cl = turnoutClosure(N, { leadInches: lead });
|
|
603
|
+
let lo = 0;
|
|
604
|
+
let hi = Math.max(4 * lead, 4 * clearanceInches * N);
|
|
605
|
+
if (cl.offsetAt(hi) < clearanceInches) return hi - lead;
|
|
606
|
+
for (let i = 0; i < 60; i++) {
|
|
607
|
+
const mid = (lo + hi) / 2;
|
|
608
|
+
if (cl.offsetAt(mid) < clearanceInches) lo = mid;
|
|
609
|
+
else hi = mid;
|
|
610
|
+
}
|
|
611
|
+
return Math.max(0, hi - lead);
|
|
612
|
+
}
|
|
613
|
+
function usableCapacity(input) {
|
|
614
|
+
const lo = Math.min(input.fromPos, input.toPos);
|
|
615
|
+
const hi = Math.max(input.fromPos, input.toPos);
|
|
616
|
+
const drawn = hi - lo;
|
|
617
|
+
const carLen = input.carLengthInches ?? N_CAR_LENGTH_INCHES;
|
|
618
|
+
if (typeof input.measuredUsableInches === "number" && input.measuredUsableInches >= 0) {
|
|
619
|
+
const m = input.measuredUsableInches;
|
|
620
|
+
return {
|
|
621
|
+
fromPos: lo,
|
|
622
|
+
toPos: lo + m,
|
|
623
|
+
usableInches: m,
|
|
624
|
+
drawnInches: drawn,
|
|
625
|
+
givenUpInches: Math.max(0, drawn - m),
|
|
626
|
+
cars: carCapacity(0, m, carLen),
|
|
627
|
+
scaleFeet: Math.round(inchesToScaleFeet(m))
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
let a = lo;
|
|
631
|
+
let b = hi;
|
|
632
|
+
for (const sw of input.governing ?? []) {
|
|
633
|
+
const c = clearancePointPastFrogInches(sw.size ?? 6, input.library);
|
|
634
|
+
if (Math.abs(sw.pos - lo) <= Math.abs(sw.pos - hi)) a = Math.max(a, lo + c);
|
|
635
|
+
else b = Math.min(b, hi - c);
|
|
636
|
+
}
|
|
637
|
+
const usable = Math.max(0, b - a);
|
|
638
|
+
return {
|
|
639
|
+
fromPos: a,
|
|
640
|
+
toPos: Math.max(a, b),
|
|
641
|
+
usableInches: usable,
|
|
642
|
+
drawnInches: drawn,
|
|
643
|
+
givenUpInches: Math.max(0, drawn - usable),
|
|
644
|
+
cars: carCapacity(0, usable, carLen),
|
|
645
|
+
scaleFeet: Math.round(inchesToScaleFeet(usable))
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function assessSectionEnd(end, opts = {}) {
|
|
649
|
+
const config = end?.config ?? "none";
|
|
650
|
+
const widthInches = endplateWidthInches({ widthInches: end?.widthInches });
|
|
651
|
+
const trackOffsetInches = endplateTrackOffsetInches(
|
|
652
|
+
end?.trackOffsetInches,
|
|
653
|
+
config,
|
|
654
|
+
opts.main2Below
|
|
655
|
+
);
|
|
656
|
+
const described = config === "single" || config === "double";
|
|
657
|
+
if (!described) {
|
|
658
|
+
return { described: false, conforming: false, issues: [], config, widthInches, trackOffsetInches };
|
|
659
|
+
}
|
|
660
|
+
const issues = checkEndplateWidth({
|
|
661
|
+
widthInches: end?.widthInches,
|
|
662
|
+
config,
|
|
663
|
+
trackOffsetInches: end?.trackOffsetInches,
|
|
664
|
+
main2Below: opts.main2Below
|
|
665
|
+
});
|
|
666
|
+
return { described: true, conforming: issues.length === 0, issues, config, widthInches, trackOffsetInches };
|
|
667
|
+
}
|
|
668
|
+
function assessSectionJoint(west, east, opts = {}) {
|
|
669
|
+
const w = assessSectionEnd(west, opts);
|
|
670
|
+
const e = assessSectionEnd(east, opts);
|
|
671
|
+
let reason = null;
|
|
672
|
+
if (!w.described || !e.described) {
|
|
673
|
+
reason = "an internal joint \u2014 neither end has been described as an interface";
|
|
674
|
+
if (w.described !== e.described)
|
|
675
|
+
reason = `only the ${w.described ? "west" : "east"} side is described as an endplate`;
|
|
676
|
+
} else if (!w.conforming || !e.conforming) {
|
|
677
|
+
const bad = !w.conforming ? w : e;
|
|
678
|
+
reason = bad.issues[0]?.message ?? "an end doesn't meet the standard";
|
|
679
|
+
} else if (w.config !== e.config) {
|
|
680
|
+
reason = `${w.config} meets ${e.config} \u2014 the track counts differ`;
|
|
681
|
+
}
|
|
682
|
+
return { west: w, east: e, standardInterface: reason === null, reason };
|
|
683
|
+
}
|
|
580
684
|
function spanOverhang(input) {
|
|
581
685
|
const lo = Math.min(input.fromPos, input.toPos);
|
|
582
686
|
const hi = Math.max(input.fromPos, input.toPos);
|
|
@@ -827,7 +931,17 @@ function stateToDoc(state, recordNumber) {
|
|
|
827
931
|
toPos: t.toPos,
|
|
828
932
|
moduleTrackId: t.moduleTrackId,
|
|
829
933
|
trackName: t.trackName || void 0,
|
|
830
|
-
|
|
934
|
+
// ⚠️ USABLE capacity, measured from the governing turnouts' CLEARANCE
|
|
935
|
+
// POINTS (#19) — not the drawn rail-to-rail length, which counts track
|
|
936
|
+
// a car can't stand on without fouling the route it diverged from.
|
|
937
|
+
// FMN-0040's 70″ siding is 21 cars drawn and 17 usable.
|
|
938
|
+
capacityFeet: usableCapacity({
|
|
939
|
+
fromPos: t.fromPos,
|
|
940
|
+
toPos: t.toPos,
|
|
941
|
+
governing: state.turnouts.filter((sw) => sw.divergeTrack === t.id),
|
|
942
|
+
measuredUsableInches: t.measuredUsableInches
|
|
943
|
+
}).scaleFeet,
|
|
944
|
+
...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
|
|
831
945
|
...state.loop && t.inLoop ? { inLoop: true } : {},
|
|
832
946
|
...t.path && t.path.length >= 2 ? { path: t.path } : {}
|
|
833
947
|
}))
|
|
@@ -926,7 +1040,10 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
926
1040
|
trackName: t.trackName ?? nameOf(moduleTrackId),
|
|
927
1041
|
...t.inLoop ? { inLoop: true } : {},
|
|
928
1042
|
// Authored path kept as-drawn (a physical shape, not rescaled with length).
|
|
929
|
-
...trackPath(t.path) ? { path: trackPath(t.path) } : {}
|
|
1043
|
+
...trackPath(t.path) ? { path: trackPath(t.path) } : {},
|
|
1044
|
+
// A MEASURED length is a real-world fact about the physical track, so it
|
|
1045
|
+
// rescales with the module exactly as its positions do (#20).
|
|
1046
|
+
...typeof t.measuredUsableInches === "number" && Number.isFinite(t.measuredUsableInches) ? { measuredUsableInches: sc(t.measuredUsableInches) } : {}
|
|
930
1047
|
});
|
|
931
1048
|
}
|
|
932
1049
|
}
|
|
@@ -2364,6 +2481,7 @@ function poseOverridesFromDoc(doc) {
|
|
|
2364
2481
|
|
|
2365
2482
|
exports.ATLAS_CODE55_N = ATLAS_CODE55_N;
|
|
2366
2483
|
exports.BUILT_IN_TRACK_PARTS = BUILT_IN_TRACK_PARTS;
|
|
2484
|
+
exports.CLEARANCE_SPACING_INCHES = CLEARANCE_SPACING_INCHES;
|
|
2367
2485
|
exports.CODE55_RAIL_HEIGHT_INCHES = CODE55_RAIL_HEIGHT_INCHES;
|
|
2368
2486
|
exports.DEFAULT_FLEX_PART_ID = DEFAULT_FLEX_PART_ID;
|
|
2369
2487
|
exports.ENDPLATE_FASCIA_CLEAR_INCHES = ENDPLATE_FASCIA_CLEAR_INCHES;
|
|
@@ -2382,6 +2500,8 @@ exports.TIE_HALF_LENGTH_INCHES = TIE_HALF_LENGTH_INCHES;
|
|
|
2382
2500
|
exports.TURNOUT_LEAD_INCHES_PER_FROG = TURNOUT_LEAD_INCHES_PER_FROG;
|
|
2383
2501
|
exports.WHOLE_MODULE_SECTION_ID = WHOLE_MODULE_SECTION_ID;
|
|
2384
2502
|
exports.asModuleSchematic = asModuleSchematic;
|
|
2503
|
+
exports.assessSectionEnd = assessSectionEnd;
|
|
2504
|
+
exports.assessSectionJoint = assessSectionJoint;
|
|
2385
2505
|
exports.benchworkBand = benchworkBand;
|
|
2386
2506
|
exports.benchworkOutline = benchworkOutline;
|
|
2387
2507
|
exports.buildCrossover = buildCrossover;
|
|
@@ -2389,6 +2509,7 @@ exports.buildPassingSiding = buildPassingSiding;
|
|
|
2389
2509
|
exports.buildTransition = buildTransition;
|
|
2390
2510
|
exports.carCapacity = carCapacity;
|
|
2391
2511
|
exports.checkEndplateWidth = checkEndplateWidth;
|
|
2512
|
+
exports.clearancePointPastFrogInches = clearancePointPastFrogInches;
|
|
2392
2513
|
exports.deriveEndplatePoses = deriveEndplatePoses;
|
|
2393
2514
|
exports.divergeSideForHand = divergeSideForHand;
|
|
2394
2515
|
exports.docToState = docToState;
|
|
@@ -2458,5 +2579,6 @@ exports.turnoutClosure = turnoutClosure;
|
|
|
2458
2579
|
exports.turnoutFacing = turnoutFacing;
|
|
2459
2580
|
exports.turnoutOccupiedSpan = turnoutOccupiedSpan;
|
|
2460
2581
|
exports.turnoutPartForSize = turnoutPartForSize;
|
|
2582
|
+
exports.usableCapacity = usableCapacity;
|
|
2461
2583
|
//# sourceMappingURL=index.cjs.map
|
|
2462
2584
|
//# sourceMappingURL=index.cjs.map
|