@willcgage/module-schematic 0.11.0 → 0.13.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 +103 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.js +102 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -280,6 +280,16 @@ declare function buildPassingSiding(state: EditorState): {
|
|
|
280
280
|
turnouts: EditorTurnout[];
|
|
281
281
|
controlPoints: EditorControlPoint[];
|
|
282
282
|
};
|
|
283
|
+
/**
|
|
284
|
+
* Build a crossover as one unit: a short connector track between Main 1 and
|
|
285
|
+
* Main 2, with a turnout on each main. The two turnouts sit on different lanes,
|
|
286
|
+
* so the feature resolver draws it as a diagonal (not a lane-paralleling siding)
|
|
287
|
+
* — this is what connects the two dots (#bug2). Needs a double-track module.
|
|
288
|
+
*/
|
|
289
|
+
declare function buildCrossover(state: EditorState): {
|
|
290
|
+
track: EditorTrack;
|
|
291
|
+
turnouts: EditorTurnout[];
|
|
292
|
+
} | null;
|
|
283
293
|
interface DrawTrack {
|
|
284
294
|
id: string;
|
|
285
295
|
role: TrackRole;
|
|
@@ -291,6 +301,13 @@ interface DrawTrack {
|
|
|
291
301
|
* of the diverge diagonal. A spur off Main 2 starts at lane 1, not lane 0;
|
|
292
302
|
* without this, renderers draw what looks like a crossover. */
|
|
293
303
|
divergesFromLane: number;
|
|
304
|
+
/** The end that meets the main at its turnout (throat) and the far stub end,
|
|
305
|
+
* as fractions of length. Direction-preserving: unlike fromFrac/toFrac (always
|
|
306
|
+
* sorted West→East), these keep WHICH end joins, so an east-facing spur draws
|
|
307
|
+
* its throat on the east. A siding meets the main at both ends — there
|
|
308
|
+
* throat=fromFrac, stub=toFrac (the fields are only meaningful for spurs). */
|
|
309
|
+
throatFrac: number;
|
|
310
|
+
stubFrac: number;
|
|
294
311
|
/** Inside the balloon of a loop module (#165). */
|
|
295
312
|
inLoop: boolean;
|
|
296
313
|
}
|
|
@@ -311,6 +328,10 @@ interface DrawSignal {
|
|
|
311
328
|
/** Owning control point's id, when the signal came from a CP group — lets a
|
|
312
329
|
* renderer join the drawn signal back to interlocking-level state (aspects). */
|
|
313
330
|
cp?: string;
|
|
331
|
+
/** 0-based rank among signals that would otherwise land on the exact same
|
|
332
|
+
* spot (same lane + side + position). Renderers offset each further from the
|
|
333
|
+
* track by `stack` so a control point's signals never overlap. */
|
|
334
|
+
stack: number;
|
|
314
335
|
}
|
|
315
336
|
/** A grade crossing / diamond — draw an X spanning the two lanes (#170). */
|
|
316
337
|
interface DrawCrossing {
|
|
@@ -320,6 +341,18 @@ interface DrawCrossing {
|
|
|
320
341
|
laneA: number;
|
|
321
342
|
laneB: number;
|
|
322
343
|
}
|
|
344
|
+
/** A crossover — a connector track joining two parallel mains through a turnout
|
|
345
|
+
* on each. Draw a straight diagonal between the two turnout points (unlike a
|
|
346
|
+
* siding, which parallels a lane between its turnouts). Detected structurally:
|
|
347
|
+
* a track whose turnouts sit on two different lanes. */
|
|
348
|
+
interface DrawCrossover {
|
|
349
|
+
id: string;
|
|
350
|
+
name: string;
|
|
351
|
+
fromPosFrac: number;
|
|
352
|
+
fromLane: number;
|
|
353
|
+
toPosFrac: number;
|
|
354
|
+
toLane: number;
|
|
355
|
+
}
|
|
323
356
|
/** A branch endplate — draw a connector stub + arrow + label off the given
|
|
324
357
|
* side (the CATS/US&S off-band idiom, #170). */
|
|
325
358
|
interface BranchConnector {
|
|
@@ -348,6 +381,8 @@ interface ModuleFeatures {
|
|
|
348
381
|
signals: DrawSignal[];
|
|
349
382
|
/** Grade crossings / diamonds (#170). */
|
|
350
383
|
crossings: DrawCrossing[];
|
|
384
|
+
/** Crossovers — connector tracks between two mains (drawn as a diagonal). */
|
|
385
|
+
crossovers: DrawCrossover[];
|
|
351
386
|
/** Branch endplates — junction connectors off the module (#170). */
|
|
352
387
|
branchConnectors: BranchConnector[];
|
|
353
388
|
/** Main 2's extent when it doesn't run the full module — a single↔double
|
|
@@ -362,6 +397,17 @@ interface ModuleFeatures {
|
|
|
362
397
|
laneMin: number;
|
|
363
398
|
laneMax: number;
|
|
364
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Which side of the main a diverging track draws on, given the turnout's HAND
|
|
402
|
+
* and the direction the track body runs along the main from the turnout
|
|
403
|
+
* (`stubDir`: +1 = the body extends east / toward B, −1 = west / toward A).
|
|
404
|
+
* Returns +1 (above the main) or −1 (below); 0 for a wye or unset hand (keep the
|
|
405
|
+
* authored side). A left-hand turnout throws its route to the same side its body
|
|
406
|
+
* runs (facing the frog, left is the inside of a body running that way); a
|
|
407
|
+
* right-hand throws to the opposite side. `kind` is the source of truth for the
|
|
408
|
+
* drawn side (#bug1) — the stored lane's sign is reconciled to match it.
|
|
409
|
+
*/
|
|
410
|
+
declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number): -1 | 0 | 1;
|
|
365
411
|
/**
|
|
366
412
|
* Resolve a schematic doc into positioned drawables. `pos` (inches) becomes a
|
|
367
413
|
* fraction of the module length; endplate A = 0, B = length; turnouts sit at
|
|
@@ -433,4 +479,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
433
479
|
heading: number;
|
|
434
480
|
}>;
|
|
435
481
|
|
|
436
|
-
export { type BranchConnector, type DrawCrossing, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildPassingSiding, buildTransition, deriveEndplatePoses, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
|
|
482
|
+
export { type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
|
package/dist/index.d.ts
CHANGED
|
@@ -280,6 +280,16 @@ declare function buildPassingSiding(state: EditorState): {
|
|
|
280
280
|
turnouts: EditorTurnout[];
|
|
281
281
|
controlPoints: EditorControlPoint[];
|
|
282
282
|
};
|
|
283
|
+
/**
|
|
284
|
+
* Build a crossover as one unit: a short connector track between Main 1 and
|
|
285
|
+
* Main 2, with a turnout on each main. The two turnouts sit on different lanes,
|
|
286
|
+
* so the feature resolver draws it as a diagonal (not a lane-paralleling siding)
|
|
287
|
+
* — this is what connects the two dots (#bug2). Needs a double-track module.
|
|
288
|
+
*/
|
|
289
|
+
declare function buildCrossover(state: EditorState): {
|
|
290
|
+
track: EditorTrack;
|
|
291
|
+
turnouts: EditorTurnout[];
|
|
292
|
+
} | null;
|
|
283
293
|
interface DrawTrack {
|
|
284
294
|
id: string;
|
|
285
295
|
role: TrackRole;
|
|
@@ -291,6 +301,13 @@ interface DrawTrack {
|
|
|
291
301
|
* of the diverge diagonal. A spur off Main 2 starts at lane 1, not lane 0;
|
|
292
302
|
* without this, renderers draw what looks like a crossover. */
|
|
293
303
|
divergesFromLane: number;
|
|
304
|
+
/** The end that meets the main at its turnout (throat) and the far stub end,
|
|
305
|
+
* as fractions of length. Direction-preserving: unlike fromFrac/toFrac (always
|
|
306
|
+
* sorted West→East), these keep WHICH end joins, so an east-facing spur draws
|
|
307
|
+
* its throat on the east. A siding meets the main at both ends — there
|
|
308
|
+
* throat=fromFrac, stub=toFrac (the fields are only meaningful for spurs). */
|
|
309
|
+
throatFrac: number;
|
|
310
|
+
stubFrac: number;
|
|
294
311
|
/** Inside the balloon of a loop module (#165). */
|
|
295
312
|
inLoop: boolean;
|
|
296
313
|
}
|
|
@@ -311,6 +328,10 @@ interface DrawSignal {
|
|
|
311
328
|
/** Owning control point's id, when the signal came from a CP group — lets a
|
|
312
329
|
* renderer join the drawn signal back to interlocking-level state (aspects). */
|
|
313
330
|
cp?: string;
|
|
331
|
+
/** 0-based rank among signals that would otherwise land on the exact same
|
|
332
|
+
* spot (same lane + side + position). Renderers offset each further from the
|
|
333
|
+
* track by `stack` so a control point's signals never overlap. */
|
|
334
|
+
stack: number;
|
|
314
335
|
}
|
|
315
336
|
/** A grade crossing / diamond — draw an X spanning the two lanes (#170). */
|
|
316
337
|
interface DrawCrossing {
|
|
@@ -320,6 +341,18 @@ interface DrawCrossing {
|
|
|
320
341
|
laneA: number;
|
|
321
342
|
laneB: number;
|
|
322
343
|
}
|
|
344
|
+
/** A crossover — a connector track joining two parallel mains through a turnout
|
|
345
|
+
* on each. Draw a straight diagonal between the two turnout points (unlike a
|
|
346
|
+
* siding, which parallels a lane between its turnouts). Detected structurally:
|
|
347
|
+
* a track whose turnouts sit on two different lanes. */
|
|
348
|
+
interface DrawCrossover {
|
|
349
|
+
id: string;
|
|
350
|
+
name: string;
|
|
351
|
+
fromPosFrac: number;
|
|
352
|
+
fromLane: number;
|
|
353
|
+
toPosFrac: number;
|
|
354
|
+
toLane: number;
|
|
355
|
+
}
|
|
323
356
|
/** A branch endplate — draw a connector stub + arrow + label off the given
|
|
324
357
|
* side (the CATS/US&S off-band idiom, #170). */
|
|
325
358
|
interface BranchConnector {
|
|
@@ -348,6 +381,8 @@ interface ModuleFeatures {
|
|
|
348
381
|
signals: DrawSignal[];
|
|
349
382
|
/** Grade crossings / diamonds (#170). */
|
|
350
383
|
crossings: DrawCrossing[];
|
|
384
|
+
/** Crossovers — connector tracks between two mains (drawn as a diagonal). */
|
|
385
|
+
crossovers: DrawCrossover[];
|
|
351
386
|
/** Branch endplates — junction connectors off the module (#170). */
|
|
352
387
|
branchConnectors: BranchConnector[];
|
|
353
388
|
/** Main 2's extent when it doesn't run the full module — a single↔double
|
|
@@ -362,6 +397,17 @@ interface ModuleFeatures {
|
|
|
362
397
|
laneMin: number;
|
|
363
398
|
laneMax: number;
|
|
364
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Which side of the main a diverging track draws on, given the turnout's HAND
|
|
402
|
+
* and the direction the track body runs along the main from the turnout
|
|
403
|
+
* (`stubDir`: +1 = the body extends east / toward B, −1 = west / toward A).
|
|
404
|
+
* Returns +1 (above the main) or −1 (below); 0 for a wye or unset hand (keep the
|
|
405
|
+
* authored side). A left-hand turnout throws its route to the same side its body
|
|
406
|
+
* runs (facing the frog, left is the inside of a body running that way); a
|
|
407
|
+
* right-hand throws to the opposite side. `kind` is the source of truth for the
|
|
408
|
+
* drawn side (#bug1) — the stored lane's sign is reconciled to match it.
|
|
409
|
+
*/
|
|
410
|
+
declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number): -1 | 0 | 1;
|
|
365
411
|
/**
|
|
366
412
|
* Resolve a schematic doc into positioned drawables. `pos` (inches) becomes a
|
|
367
413
|
* fraction of the module length; endplate A = 0, B = length; turnouts sit at
|
|
@@ -433,4 +479,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
433
479
|
heading: number;
|
|
434
480
|
}>;
|
|
435
481
|
|
|
436
|
-
export { type BranchConnector, type DrawCrossing, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildPassingSiding, buildTransition, deriveEndplatePoses, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
|
|
482
|
+
export { type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type GeometryType, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_SCALE_RATIO, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,9 @@ function buildTransition(state) {
|
|
|
61
61
|
pos,
|
|
62
62
|
onTrack: MAIN_TRACK_ID,
|
|
63
63
|
divergeTrack: MAIN2_TRACK_ID,
|
|
64
|
-
|
|
64
|
+
// Main 2 sits above (lane 1); the throw that runs toward the double end is
|
|
65
|
+
// left when that end is east (single at A), right when it's west (#bug1).
|
|
66
|
+
kind: aDouble ? "right" : "left"
|
|
65
67
|
};
|
|
66
68
|
const cpId = nextId("cp", state.controlPoints.map((c) => c.id));
|
|
67
69
|
const sig = (facing) => ({
|
|
@@ -347,8 +349,8 @@ function buildPassingSiding(state) {
|
|
|
347
349
|
const swW = nextId("sw", swIds);
|
|
348
350
|
const swE = nextId("sw", [...swIds, swW]);
|
|
349
351
|
const turnouts = [
|
|
350
|
-
{ id: swW, name: "West Siding", pos: fromPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "
|
|
351
|
-
{ id: swE, name: "East Siding", pos: toPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "
|
|
352
|
+
{ id: swW, name: "West Siding", pos: fromPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "left" },
|
|
353
|
+
{ id: swE, name: "East Siding", pos: toPos, onTrack: MAIN_TRACK_ID, divergeTrack: sidId, kind: "right" }
|
|
352
354
|
];
|
|
353
355
|
const cpIds = state.controlPoints.map((c) => c.id);
|
|
354
356
|
const cpW = nextId("cp", cpIds);
|
|
@@ -367,6 +369,39 @@ function buildPassingSiding(state) {
|
|
|
367
369
|
];
|
|
368
370
|
return { track, turnouts, controlPoints };
|
|
369
371
|
}
|
|
372
|
+
function buildCrossover(state) {
|
|
373
|
+
const hasSecond = state.configA === "double" || state.configB === "double";
|
|
374
|
+
if (state.loop || !hasSecond) return null;
|
|
375
|
+
const len = state.lengthInches > 0 ? state.lengthInches : 24;
|
|
376
|
+
const mid = Math.round(len / 2);
|
|
377
|
+
const gap = Math.max(3, Math.round(len * 0.04));
|
|
378
|
+
const w = Math.round(mid - gap / 2);
|
|
379
|
+
const e = Math.round(mid + gap / 2);
|
|
380
|
+
const trackIds = [MAIN_TRACK_ID, MAIN2_TRACK_ID, ...state.extraTracks.map((t) => t.id)];
|
|
381
|
+
const xoId = nextId("xo", trackIds);
|
|
382
|
+
const track = {
|
|
383
|
+
id: xoId,
|
|
384
|
+
role: "crossover",
|
|
385
|
+
lane: 1,
|
|
386
|
+
fromPos: w,
|
|
387
|
+
toPos: e,
|
|
388
|
+
moduleTrackId: null,
|
|
389
|
+
trackName: "Crossover"
|
|
390
|
+
};
|
|
391
|
+
const swIds = state.turnouts.map((t) => t.id);
|
|
392
|
+
const sw1 = nextId("sw", swIds);
|
|
393
|
+
const sw2 = nextId("sw", [...swIds, sw1]);
|
|
394
|
+
const turnouts = [
|
|
395
|
+
{ id: sw1, name: "Crossover", pos: w, onTrack: MAIN_TRACK_ID, divergeTrack: xoId, kind: "left" },
|
|
396
|
+
{ id: sw2, name: "Crossover", pos: e, onTrack: MAIN2_TRACK_ID, divergeTrack: xoId, kind: "right" }
|
|
397
|
+
];
|
|
398
|
+
return { track, turnouts };
|
|
399
|
+
}
|
|
400
|
+
function divergeSideForHand(kind, stubDir) {
|
|
401
|
+
if (kind !== "left" && kind !== "right") return 0;
|
|
402
|
+
const s = stubDir >= 0 ? 1 : -1;
|
|
403
|
+
return kind === "left" ? s : -s;
|
|
404
|
+
}
|
|
370
405
|
function moduleFeatures(doc) {
|
|
371
406
|
const len = doc.lengthInches && doc.lengthInches > 0 ? doc.lengthInches : Math.max(
|
|
372
407
|
1,
|
|
@@ -393,23 +428,70 @@ function moduleFeatures(doc) {
|
|
|
393
428
|
const sw = (doc.turnouts ?? []).find((t) => t.divergeTrack === trackId);
|
|
394
429
|
return sw ? trackLane.get(sw.onTrack) ?? 0 : 0;
|
|
395
430
|
};
|
|
431
|
+
const turnoutsByTrack = /* @__PURE__ */ new Map();
|
|
432
|
+
for (const sw of doc.turnouts ?? []) {
|
|
433
|
+
const arr = turnoutsByTrack.get(sw.divergeTrack) ?? [];
|
|
434
|
+
arr.push(sw);
|
|
435
|
+
turnoutsByTrack.set(sw.divergeTrack, arr);
|
|
436
|
+
}
|
|
437
|
+
const isCrossover = (trackId) => {
|
|
438
|
+
const sws = turnoutsByTrack.get(trackId) ?? [];
|
|
439
|
+
if (sws.length < 2) return false;
|
|
440
|
+
return new Set(sws.map((s) => trackLane.get(s.onTrack) ?? 0)).size >= 2;
|
|
441
|
+
};
|
|
442
|
+
const extentOf = (t) => {
|
|
443
|
+
const from = t.fromPos ?? posOf(t.from);
|
|
444
|
+
const to = t.toPos ?? posOf(t.to);
|
|
445
|
+
return from == null || to == null ? null : [from, to];
|
|
446
|
+
};
|
|
447
|
+
for (const t of doc.tracks) {
|
|
448
|
+
if (t.role === "main" || isCrossover(t.id)) continue;
|
|
449
|
+
const sw = turnoutsByTrack.get(t.id)?.[0];
|
|
450
|
+
if (!sw) continue;
|
|
451
|
+
const ext = extentOf(t);
|
|
452
|
+
if (!ext) continue;
|
|
453
|
+
const [from, to] = ext;
|
|
454
|
+
const far = Math.abs(to - sw.pos) >= Math.abs(from - sw.pos) ? to : from;
|
|
455
|
+
const side = divergeSideForHand(sw.kind, far - sw.pos);
|
|
456
|
+
if (side !== 0) trackLane.set(t.id, side * Math.abs(t.lane));
|
|
457
|
+
}
|
|
396
458
|
const extraTracks = [];
|
|
397
459
|
for (const t of doc.tracks) {
|
|
398
460
|
if (t.role === "main") continue;
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
461
|
+
if (isCrossover(t.id)) continue;
|
|
462
|
+
const ext = extentOf(t);
|
|
463
|
+
if (!ext) continue;
|
|
464
|
+
const [from, to] = ext;
|
|
465
|
+
const sw = turnoutsByTrack.get(t.id)?.[0];
|
|
466
|
+
const throatAtTo = sw != null && Math.abs(to - sw.pos) < Math.abs(from - sw.pos);
|
|
467
|
+
const throat = throatAtTo ? to : from;
|
|
468
|
+
const stub = throatAtTo ? from : to;
|
|
402
469
|
extraTracks.push({
|
|
403
470
|
id: t.id,
|
|
404
471
|
role: t.role,
|
|
405
|
-
lane: t.lane,
|
|
472
|
+
lane: trackLane.get(t.id) ?? t.lane,
|
|
406
473
|
fromFrac: clampFrac(Math.min(from, to)),
|
|
407
474
|
toFrac: clampFrac(Math.max(from, to)),
|
|
475
|
+
throatFrac: clampFrac(throat),
|
|
476
|
+
stubFrac: clampFrac(stub),
|
|
408
477
|
capacityFeet: t.capacityFeet ?? null,
|
|
409
478
|
divergesFromLane: divergeOrigin(t.id),
|
|
410
479
|
inLoop: t.inLoop === true
|
|
411
480
|
});
|
|
412
481
|
}
|
|
482
|
+
const crossovers = [];
|
|
483
|
+
for (const t of doc.tracks) {
|
|
484
|
+
if (!isCrossover(t.id)) continue;
|
|
485
|
+
const [s1, s2] = turnoutsByTrack.get(t.id);
|
|
486
|
+
crossovers.push({
|
|
487
|
+
id: t.id,
|
|
488
|
+
name: t.trackName ?? "",
|
|
489
|
+
fromPosFrac: clampFrac(s1.pos),
|
|
490
|
+
fromLane: trackLane.get(s1.onTrack) ?? 0,
|
|
491
|
+
toPosFrac: clampFrac(s2.pos),
|
|
492
|
+
toLane: trackLane.get(s2.onTrack) ?? 1
|
|
493
|
+
});
|
|
494
|
+
}
|
|
413
495
|
const turnouts = (doc.turnouts ?? []).map((t) => ({
|
|
414
496
|
id: t.id,
|
|
415
497
|
name: t.name ?? "",
|
|
@@ -424,11 +506,19 @@ function moduleFeatures(doc) {
|
|
|
424
506
|
lane: s.track ? trackLane.get(s.track) ?? 0 : 0,
|
|
425
507
|
facing: s.facing ?? "AtoB",
|
|
426
508
|
side: s.side === "below" ? "below" : "above",
|
|
427
|
-
...cp ? { cp } : {}
|
|
509
|
+
...cp ? { cp } : {},
|
|
510
|
+
stack: 0
|
|
428
511
|
});
|
|
429
512
|
const signals = Array.isArray(doc.controlPoints) ? doc.controlPoints.flatMap(
|
|
430
513
|
(c) => (c.signals ?? []).map((s) => drawSignal(s, c.name ?? "", c.id))
|
|
431
514
|
) : (doc.signals ?? []).map((s) => drawSignal(s, s.name ?? ""));
|
|
515
|
+
const stackCount = /* @__PURE__ */ new Map();
|
|
516
|
+
for (const s of signals) {
|
|
517
|
+
const key = `${s.lane}|${s.side}|${Math.round(s.posFrac * 1e3)}`;
|
|
518
|
+
const n = stackCount.get(key) ?? 0;
|
|
519
|
+
s.stack = n;
|
|
520
|
+
stackCount.set(key, n + 1);
|
|
521
|
+
}
|
|
432
522
|
const crossings = (doc.crossings ?? []).map((x) => ({
|
|
433
523
|
id: x.id,
|
|
434
524
|
name: x.name ?? "",
|
|
@@ -447,7 +537,8 @@ function moduleFeatures(doc) {
|
|
|
447
537
|
doubleMain ? 1 : 0,
|
|
448
538
|
...extraTracks.map((t) => t.lane),
|
|
449
539
|
...signals.map((s) => s.lane),
|
|
450
|
-
...crossings.flatMap((x) => [x.laneA, x.laneB])
|
|
540
|
+
...crossings.flatMap((x) => [x.laneA, x.laneB]),
|
|
541
|
+
...crossovers.flatMap((x) => [x.fromLane, x.toLane])
|
|
451
542
|
];
|
|
452
543
|
const loop = isLoopDoc(doc);
|
|
453
544
|
const main2 = doc.tracks.find((t) => t.id === MAIN2_TRACK_ID);
|
|
@@ -467,6 +558,7 @@ function moduleFeatures(doc) {
|
|
|
467
558
|
turnouts,
|
|
468
559
|
signals,
|
|
469
560
|
crossings,
|
|
561
|
+
crossovers,
|
|
470
562
|
branchConnectors,
|
|
471
563
|
laneMin: Math.min(...allLanes),
|
|
472
564
|
laneMax: Math.max(...allLanes)
|
|
@@ -570,6 +662,6 @@ function poseOverridesFromDoc(doc) {
|
|
|
570
662
|
return out;
|
|
571
663
|
}
|
|
572
664
|
|
|
573
|
-
export { MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, buildPassingSiding, buildTransition, deriveEndplatePoses, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
|
|
665
|
+
export { MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, scaleFeetToInches, stateToDoc };
|
|
574
666
|
//# sourceMappingURL=index.js.map
|
|
575
667
|
//# sourceMappingURL=index.js.map
|