@willcgage/module-schematic 0.98.0 → 0.102.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.js CHANGED
@@ -1797,8 +1797,8 @@ function turnoutFacing(input) {
1797
1797
  function turnoutOccupiedSpan(input) {
1798
1798
  const e = input.extent;
1799
1799
  if (!e) return null;
1800
- const a = input.pos - input.facing * e.behindPoints;
1801
- const b = input.pos + input.facing * e.aheadOfPoints;
1800
+ const a = input.pos - input.facing * e.behindFrog;
1801
+ const b = input.pos + input.facing * e.pastFrog;
1802
1802
  return { fromPos: Math.min(a, b), toPos: Math.max(a, b) };
1803
1803
  }
1804
1804
  function resizeFlexPiece(pieces, index, nextLengthInches) {
@@ -1831,10 +1831,12 @@ function partExtent(part) {
1831
1831
  if (pts.source !== "measured" || overall.source !== "measured") return null;
1832
1832
  const frog = part?.frogOffset;
1833
1833
  const aheadOfPoints = overall.inches - pts.inches;
1834
+ const frogMeasured = frog && frog.source === "measured";
1834
1835
  return {
1835
1836
  behindPoints: pts.inches,
1836
1837
  aheadOfPoints,
1837
- pastFrog: frog && frog.source === "measured" ? overall.inches - frog.inches : aheadOfPoints
1838
+ pastFrog: frogMeasured ? overall.inches - frog.inches : aheadOfPoints,
1839
+ behindFrog: frogMeasured ? frog.inches : pts.inches
1838
1840
  };
1839
1841
  }
1840
1842
  function partExtentForSize(size, library = BUILT_IN_TRACK_PARTS) {
@@ -2698,12 +2700,13 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
2698
2700
  const through = byKey.get(`${here.piece}.through`) ?? byKey.get(`${here.piece}.legA`);
2699
2701
  const body = gap(throat, through);
2700
2702
  const lead = part.lead?.inches ?? body / 2;
2703
+ const frogAxial = part.frogOffset?.inches ?? (part.pointsOffset?.inches ?? 0) + lead;
2701
2704
  const dvj = byKey.get(`${here.piece}.diverge`) ?? byKey.get(`${here.piece}.legB`);
2702
2705
  const frogPt = throat && body > 0 && dvj ? {
2703
- x: throat.x + (dvj.x - throat.x) * lead / body,
2704
- y: throat.y + (dvj.y - throat.y) * lead / body
2706
+ x: throat.x + (dvj.x - throat.x) * frogAxial / body,
2707
+ y: throat.y + (dvj.y - throat.y) * frogAxial / body
2705
2708
  } : null;
2706
- 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);
2709
+ const toFrog = here.joint === "throat" ? frogAxial : here.joint === "diverge" || here.joint === "legB" ? frogPt && dvj ? Math.hypot(dvj.x - frogPt.x, dvj.y - frogPt.y) : Math.max(0, body - frogAxial) : Math.max(0, body - frogAxial);
2707
2710
  const frogPos = pos + toFrog;
2708
2711
  if (here.joint === "diverge" || here.joint === "legB") {
2709
2712
  route.endsAt = here.piece;
@@ -2718,8 +2721,8 @@ function walkTrackGraph(graph, pieces, startAt, library = BUILT_IN_TRACK_PARTS)
2718
2721
  if (!d || queued.has(dk)) continue;
2719
2722
  queued.add(dk);
2720
2723
  const fp = throat && body > 0 ? {
2721
- x: throat.x + (d.x - throat.x) * lead / body,
2722
- y: throat.y + (d.y - throat.y) * lead / body
2724
+ x: throat.x + (d.x - throat.x) * frogAxial / body,
2725
+ y: throat.y + (d.y - throat.y) * frogAxial / body
2723
2726
  } : null;
2724
2727
  pending.push({
2725
2728
  from: here.piece,
@@ -3005,6 +3008,346 @@ function deriveGraphDoc(doc, library = BUILT_IN_TRACK_PARTS) {
3005
3008
  });
3006
3009
  return { doc: out.doc, warnings: out.warnings };
3007
3010
  }
3011
+ function placeableTurnoutParts(library, kind) {
3012
+ return library.filter((p) => kind.includes(p.kind) && partGeometryGap(p) == null);
3013
+ }
3014
+ function moduleConversionReport(doc, library = BUILT_IN_TRACK_PARTS) {
3015
+ if (doc.graph?.pieces?.length) {
3016
+ return {
3017
+ alreadyGraph: true,
3018
+ offerable: false,
3019
+ readyWithoutAsking: false,
3020
+ turnouts: [],
3021
+ unanswered: [],
3022
+ orphanTracks: [],
3023
+ blockers: []
3024
+ };
3025
+ }
3026
+ const straight = placeableTurnoutParts(library, ["turnout"]);
3027
+ const curved = placeableTurnoutParts(library, ["curved-turnout"]);
3028
+ const blockers = [];
3029
+ const turnouts = (doc.turnouts ?? []).map((t) => {
3030
+ const wantCurved = t.curved === true;
3031
+ const pool = wantCurved ? curved : straight;
3032
+ const base = {
3033
+ id: t.id,
3034
+ name: t.name,
3035
+ pos: t.pos,
3036
+ size: t.size,
3037
+ statedPartId: t.partId
3038
+ };
3039
+ const exact = t.size == null ? [] : pool.filter((p) => p.frogNumber === t.size);
3040
+ const candidates = [...exact, ...pool.filter((p) => !exact.includes(p))].map((p) => p.id);
3041
+ const resolve = (part, from) => ({
3042
+ ...base,
3043
+ partId: part.id,
3044
+ from,
3045
+ source: partGeometry(part, library)?.source ?? null,
3046
+ why: null,
3047
+ candidates
3048
+ });
3049
+ if (t.partId) {
3050
+ const named = library.find((p) => p.id === t.partId);
3051
+ if (named && partGeometryGap(named) == null) return resolve(named, "named");
3052
+ return {
3053
+ ...base,
3054
+ partId: null,
3055
+ from: "unresolved",
3056
+ source: null,
3057
+ why: named ? `this turnout names ${named.id}, which cannot be placed yet \u2014 ${partGeometryGap(named)}` : `this turnout names a part the library does not have (${t.partId})`,
3058
+ candidates
3059
+ };
3060
+ }
3061
+ if (t.size != null && exact.length) return resolve(exact[0], "frog-number");
3062
+ return {
3063
+ ...base,
3064
+ partId: null,
3065
+ from: "unresolved",
3066
+ source: null,
3067
+ why: t.size == null ? "the document never says what this turnout is \u2014 no part, no frog number" : wantCurved ? `no curved turnout is placeable yet, so a curved #${t.size} cannot be converted` : `no measured #${t.size} in the parts library yet`,
3068
+ candidates
3069
+ };
3070
+ });
3071
+ for (const c of doc.crossings ?? [])
3072
+ blockers.push({
3073
+ kind: "crossing",
3074
+ ref: c.id,
3075
+ why: "crossing geometry is not modelled yet, so a diamond has no piece to become"
3076
+ });
3077
+ if (doc.loop)
3078
+ blockers.push({
3079
+ kind: "loop",
3080
+ why: "a balloon's curve radii were never recorded, and laying the loop would mean inventing them \u2014 the one thing ADR 0001 forbids"
3081
+ });
3082
+ const reached = new Set((doc.turnouts ?? []).map((t) => t.divergeTrack));
3083
+ const orphanTracks = (doc.tracks ?? []).filter((t) => t.role !== "main" && !reached.has(t.id)).map((t) => ({
3084
+ id: t.id,
3085
+ ...t.trackName ? { trackName: t.trackName } : {},
3086
+ role: t.role,
3087
+ why: "no turnout in the document diverges onto this track, so there is nothing to join it to"
3088
+ }));
3089
+ const unanswered = turnouts.filter((t) => !t.partId).map((t) => t.id);
3090
+ return {
3091
+ alreadyGraph: false,
3092
+ offerable: blockers.length === 0,
3093
+ readyWithoutAsking: blockers.length === 0 && unanswered.length === 0 && orphanTracks.length === 0,
3094
+ turnouts,
3095
+ unanswered,
3096
+ orphanTracks,
3097
+ blockers
3098
+ };
3099
+ }
3100
+ var CONVERSION_TIGHT_RADIUS_INCHES = 12;
3101
+ function docToGraph(doc, answers = {}, library = BUILT_IN_TRACK_PARTS) {
3102
+ const refuse = (why) => ({
3103
+ graph: null,
3104
+ refused: why,
3105
+ notLaid: [],
3106
+ warnings: []
3107
+ });
3108
+ const report = moduleConversionReport(doc, library);
3109
+ if (report.alreadyGraph) return refuse("this module is already authored as pieces");
3110
+ if (!report.offerable) return refuse(report.blockers.map((b) => b.why).join("; "));
3111
+ if ((doc.mainPath?.length ?? 0) > 2)
3112
+ return refuse(
3113
+ "this module's mainline is drawn with bends, and laying it as pieces would need each bend's radius \u2014 convert it once curved runs are supported"
3114
+ );
3115
+ const partById = new Map(library.map((p) => [p.id, p]));
3116
+ const chosen = /* @__PURE__ */ new Map();
3117
+ const unknown = [];
3118
+ for (const t of report.turnouts) {
3119
+ const id = answers.overrides?.[t.id] ?? (t.partId ? t.partId : answers.turnoutPartId) ?? null;
3120
+ const part = id ? partById.get(id) : void 0;
3121
+ if (!part || partGeometryGap(part)) unknown.push(t.id);
3122
+ else chosen.set(t.id, part);
3123
+ }
3124
+ if (unknown.length)
3125
+ return refuse(
3126
+ `${unknown.length} turnout${unknown.length === 1 ? "" : "s"} still need identifying: ${unknown.join(", ")}`
3127
+ );
3128
+ const flexId = DEFAULT_FLEX_PART_ID;
3129
+ if (!partById.get(flexId)) return refuse("the parts library has no flex track to lay plain track with");
3130
+ const maxPiece = maxFlexPieceInches(flexId, library);
3131
+ const tracks = doc.tracks ?? [];
3132
+ const trackById = new Map(tracks.map((t) => [t.id, t]));
3133
+ const turnouts = doc.turnouts ?? [];
3134
+ const mainLen = doc.lengthInches ?? Math.max(0, ...tracks.map((t) => t.toPos ?? 0), ...turnouts.map((t) => t.pos));
3135
+ if (!(mainLen > 0)) return refuse("this module has no length to lay track along");
3136
+ const pieces = [];
3137
+ const notLaid = [];
3138
+ const warnings = [];
3139
+ const rad = (d) => d * Math.PI / 180;
3140
+ const norm180 = (d) => ((d + 180) % 360 + 360) % 360 - 180;
3141
+ const r3 = (n) => Math.round(n * 1e3) / 1e3;
3142
+ const jointsOf = (p) => placedJoints([p], library);
3143
+ const jointAt = (p, id) => jointsOf(p).find((j) => j.joint === id) ?? null;
3144
+ const transition = (id, from, toY, dirSign = 1) => {
3145
+ const theta = rad(norm180(from.headingDeg - (dirSign > 0 ? 0 : 180)));
3146
+ const dy = (toY - from.y) * dirSign;
3147
+ if (Math.abs(theta) < 1e-9 || Math.abs(dy) < 1e-9) return null;
3148
+ const R = dy / (1 - Math.cos(theta));
3149
+ if (!Number.isFinite(R) || R === 0) return null;
3150
+ const mag = Math.abs(R);
3151
+ if (mag < CONVERSION_TIGHT_RADIUS_INCHES)
3152
+ warnings.push(
3153
+ `the curve bringing ${id} back parallel works out at ${mag.toFixed(1)}\u2033 radius \u2014 tighter than anyone lays by hand, which usually means the document's lane and its turnout disagree`
3154
+ );
3155
+ return {
3156
+ id,
3157
+ partId: flexId,
3158
+ x: from.x,
3159
+ y: from.y,
3160
+ rotationDeg: from.headingDeg,
3161
+ // Curving BACK toward the host: opposite the way we are already heading.
3162
+ // `radiusInches` is signed and the sign IS the side.
3163
+ radiusInches: r3(-Math.sign(theta) * mag),
3164
+ lengthInches: r3(mag * Math.abs(theta))
3165
+ };
3166
+ };
3167
+ const layTurnout = (t, hostY) => {
3168
+ const part = chosen.get(t.id);
3169
+ const geom = partGeometry(part, library);
3170
+ const branch = trackById.get(t.divergeTrack);
3171
+ const ends = [branch?.fromPos, branch?.toPos].filter(
3172
+ (n) => typeof n === "number" && Number.isFinite(n)
3173
+ );
3174
+ const divergeFarPos = ends.length ? ends.reduce((a, b) => Math.abs(b - t.pos) > Math.abs(a - t.pos) ? b : a) : void 0;
3175
+ const facing = turnoutFacing({
3176
+ pos: t.pos,
3177
+ divergeFarPos,
3178
+ flipped: t.flipped ?? false
3179
+ });
3180
+ const body = part.overallLength.inches;
3181
+ const lead = part.lead?.inches ?? body / 2;
3182
+ const frogAxial = part.frogOffset?.inches ?? (part.pointsOffset?.inches ?? 0) + lead;
3183
+ const throatPos = facing > 0 ? t.pos - frogAxial : t.pos + frogAxial;
3184
+ const hostLane = trackById.get(t.onTrack)?.lane ?? 0;
3185
+ const want = Math.sign((branch?.lane ?? hostLane) - hostLane) || 1;
3186
+ const unflipped = facing > 0 ? 1 : -1;
3187
+ const piece = {
3188
+ id: `t-${t.id}`,
3189
+ partId: part.id,
3190
+ x: throatPos,
3191
+ y: hostY,
3192
+ rotationDeg: facing > 0 ? 0 : 180,
3193
+ ...unflipped !== want ? { flipped: true } : {},
3194
+ ...t.name ? { name: t.name } : {}
3195
+ };
3196
+ const bodyEnds = [throatPos, facing > 0 ? throatPos + body : throatPos - body];
3197
+ const span = { fromPos: Math.min(...bodyEnds), toPos: Math.max(...bodyEnds) };
3198
+ const divergeId = geom.joints.find((j) => j.role === "diverge")?.id ?? "diverge";
3199
+ return { t, piece, span, divergeId };
3200
+ };
3201
+ const layFlex = (label, hostY, fromPos, toPos, occupied, cuts) => {
3202
+ const out = flexPieces({ fromPos, toPos, maxPieceInches: maxPiece, occupied, cuts: cuts ?? null });
3203
+ out.forEach(
3204
+ (f, i) => pieces.push({
3205
+ id: `f-${label}-${i}`,
3206
+ partId: flexId,
3207
+ x: f.fromPos,
3208
+ y: hostY,
3209
+ rotationDeg: 0,
3210
+ lengthInches: r3(f.lengthInches)
3211
+ })
3212
+ );
3213
+ return out;
3214
+ };
3215
+ const turnoutsOn = (trackId, hostY) => turnouts.filter((t) => t.onTrack === trackId && chosen.has(t.id)).sort((a, b) => a.pos - b.pos).map((t) => layTurnout(t, hostY));
3216
+ const main = trackById.get(MAIN_TRACK_ID) ?? tracks.find((t) => t.role === "main");
3217
+ if (!main) return refuse("this module's document has no mainline to convert");
3218
+ const mains = [main, ...tracks.filter((t) => t.role === "main" && t.id !== main.id)];
3219
+ const laidTurnouts = /* @__PURE__ */ new Map();
3220
+ const done = /* @__PURE__ */ new Set();
3221
+ for (const m of mains) {
3222
+ const y = laneOffsetAt(m.lane ?? 0, 0);
3223
+ const on = turnoutsOn(m.id, y);
3224
+ for (const l of on) {
3225
+ pieces.push(l.piece);
3226
+ laidTurnouts.set(l.t.id, l);
3227
+ }
3228
+ layFlex(m.id, y, m.fromPos ?? 0, m.toPos ?? mainLen, on.map((l) => l.span), m.flexCuts);
3229
+ done.add(m.id);
3230
+ }
3231
+ let progressed = true;
3232
+ while (progressed) {
3233
+ progressed = false;
3234
+ for (const t of turnouts) {
3235
+ if (!done.has(t.onTrack)) continue;
3236
+ const branch = trackById.get(t.divergeTrack);
3237
+ if (!branch || done.has(branch.id)) continue;
3238
+ const near = laidTurnouts.get(t.id);
3239
+ if (!near) continue;
3240
+ done.add(branch.id);
3241
+ progressed = true;
3242
+ const dj = jointAt(near.piece, near.divergeId);
3243
+ if (!dj) {
3244
+ notLaid.push({ id: branch.id, why: `turnout ${t.id} has no diverging end to leave from` });
3245
+ continue;
3246
+ }
3247
+ const farSw0 = turnouts.find(
3248
+ (o) => o.id !== t.id && o.divergeTrack === branch.id && laidTurnouts.has(o.id)
3249
+ );
3250
+ const farJoint0 = farSw0 ? (() => {
3251
+ const f = laidTurnouts.get(farSw0.id);
3252
+ return jointAt(f.piece, f.divergeId);
3253
+ })() : null;
3254
+ if (branch.role === "crossover" && farJoint0) {
3255
+ const dx = farJoint0.x - dj.x;
3256
+ const dy = farJoint0.y - dj.y;
3257
+ const len = Math.hypot(dx, dy);
3258
+ if (len > 1e-6) {
3259
+ pieces.push({
3260
+ id: `x-${branch.id}`,
3261
+ partId: flexId,
3262
+ x: dj.x,
3263
+ y: dj.y,
3264
+ rotationDeg: Math.atan2(dy, dx) * 180 / Math.PI,
3265
+ lengthInches: r3(len),
3266
+ ...branch.trackName ? { name: branch.trackName } : {}
3267
+ });
3268
+ } else {
3269
+ notLaid.push({
3270
+ id: branch.id,
3271
+ why: "this crossover's two turnouts meet at the same point, so there is no connector between them to lay"
3272
+ });
3273
+ }
3274
+ continue;
3275
+ }
3276
+ const branchY = laneOffsetAt(branch.lane ?? 0, 0);
3277
+ const curve = transition(branch.id, { x: dj.x, y: dj.y, headingDeg: dj.headingDeg }, branchY);
3278
+ let start = { x: dj.x, y: dj.y, headingDeg: dj.headingDeg };
3279
+ if (curve) {
3280
+ pieces.push(curve);
3281
+ const e = jointAt(curve, "b");
3282
+ if (e) start = { x: e.x, y: e.y, headingDeg: e.headingDeg };
3283
+ }
3284
+ const farJoint = farJoint0;
3285
+ let endX = farJoint != null ? farJoint.x : Math.max(branch.fromPos ?? t.pos, branch.toPos ?? t.pos);
3286
+ let farCurve = null;
3287
+ if (farJoint) {
3288
+ farCurve = transition(
3289
+ `${branch.id}-far`,
3290
+ { x: farJoint.x, y: farJoint.y, headingDeg: farJoint.headingDeg },
3291
+ branchY,
3292
+ -1
3293
+ );
3294
+ if (farCurve) {
3295
+ pieces.push(farCurve);
3296
+ const e = jointAt(farCurve, "b");
3297
+ if (e) endX = e.x;
3298
+ }
3299
+ }
3300
+ if (endX - start.x <= 1e-6) {
3301
+ notLaid.push({
3302
+ id: branch.id,
3303
+ why: `the turnout and the curve bringing this track parallel already reach ${start.x.toFixed(1)}\u2033, past where it has to end at ${endX.toFixed(1)}\u2033 \u2014 with the turnout chosen there is no room for this track where the document places it`
3304
+ });
3305
+ continue;
3306
+ }
3307
+ const on = turnoutsOn(branch.id, branchY).filter((l) => {
3308
+ if (l.span.fromPos >= start.x - 1e-6) return true;
3309
+ notLaid.push({
3310
+ id: l.t.divergeTrack,
3311
+ why: `turnout ${l.t.id} sits at ${l.t.pos}\u2033 but this track has only reached ${start.x.toFixed(1)}\u2033 by then \u2014 the ladder is pitched tighter than the chosen turnout allows`
3312
+ });
3313
+ return false;
3314
+ });
3315
+ for (const l of on) {
3316
+ pieces.push(l.piece);
3317
+ laidTurnouts.set(l.t.id, l);
3318
+ }
3319
+ layFlex(branch.id, branchY, start.x, endX, on.map((l) => l.span), branch.flexCuts);
3320
+ }
3321
+ }
3322
+ const orphanWhy = new Map(report.orphanTracks.map((o) => [o.id, o.why]));
3323
+ const alreadySaid = new Set(notLaid.map((n) => n.id));
3324
+ for (const t of tracks) {
3325
+ if (t.role === "main" || done.has(t.id) || alreadySaid.has(t.id)) continue;
3326
+ notLaid.push({
3327
+ id: t.id,
3328
+ why: orphanWhy.get(t.id) ?? "the track it branches from could not be laid, so there is nothing to join it to"
3329
+ });
3330
+ }
3331
+ const startOf = (t) => {
3332
+ const y = laneOffsetAt(t.lane ?? 0, 0);
3333
+ let best = null;
3334
+ for (const p of pieces)
3335
+ for (const j of jointsOf(p)) {
3336
+ if (Math.abs(j.y - y) > 0.05) continue;
3337
+ if (!best || j.x < best.x) best = { piece: p.id, joint: j.joint, x: j.x };
3338
+ }
3339
+ return best ? { piece: best.piece, joint: best.joint } : null;
3340
+ };
3341
+ const startAt = startOf(main);
3342
+ if (!startAt) return refuse("nothing was laid on the mainline, so the module has no starting point");
3343
+ const second = mains[1] ? startOf(mains[1]) : null;
3344
+ return {
3345
+ graph: { pieces, startAt, ...second ? { start2: second } : {} },
3346
+ refused: null,
3347
+ notLaid,
3348
+ warnings
3349
+ };
3350
+ }
3008
3351
  function frogCasting(cl, opts = {}) {
3009
3352
  const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
3010
3353
  const w = opts.reachInches ?? g * 1.5;
@@ -3638,6 +3981,6 @@ function poseOverridesFromDoc(doc) {
3638
3981
  return out;
3639
3982
  }
3640
3983
 
3641
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, GENERIC_END_OF_TRACK, JOINT_SNAP_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
3984
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, GENERIC_END_OF_TRACK, JOINT_SNAP_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, PINCH_EASE_INCHES, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
3642
3985
  //# sourceMappingURL=index.js.map
3643
3986
  //# sourceMappingURL=index.js.map