@willcgage/module-schematic 0.16.0 → 0.18.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 +130 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -1
- package/dist/index.d.ts +113 -1
- package/dist/index.js +125 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -132,6 +132,35 @@ interface SchematicControlPoint {
|
|
|
132
132
|
crossings?: string[];
|
|
133
133
|
signals?: SchematicSignal[];
|
|
134
134
|
}
|
|
135
|
+
/** What a rendered industry shows beside its name: a car count, a length in
|
|
136
|
+
* inches, or nothing (name only). Authored per industry. */
|
|
137
|
+
type IndustryLabelMode = "none" | "cars" | "inches";
|
|
138
|
+
/**
|
|
139
|
+
* An industry — a rail-served customer that spots cars, authored as a SPAN on a
|
|
140
|
+
* track (a spur/siding, or the main). Positional like everything else: it lives
|
|
141
|
+
* in the same module-local inch frame and is rendered into the shared 2-D view,
|
|
142
|
+
* offset to `side`. The span length gives its car capacity; the dispatcher and
|
|
143
|
+
* crews read where cars set out. Mirrors a `freemon_industries` row.
|
|
144
|
+
*/
|
|
145
|
+
interface SchematicIndustry {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
/** Industry type value from the lookup (e.g. "team_track", "grain"). */
|
|
149
|
+
type?: string | null;
|
|
150
|
+
/** The track this industry spots cars on (a spur/siding id, or the main). */
|
|
151
|
+
track: string;
|
|
152
|
+
/** The car-spot span along that track, inches from endplate A. */
|
|
153
|
+
fromPos: number;
|
|
154
|
+
toPos: number;
|
|
155
|
+
/** Which side of the track the building + label sit on. */
|
|
156
|
+
side?: SignalSide;
|
|
157
|
+
/** Secondary readout at the label — a car count, a length, or none. */
|
|
158
|
+
labelMode?: IndustryLabelMode;
|
|
159
|
+
/** Car types this industry receives (car-type value strings). */
|
|
160
|
+
carTypes?: string[];
|
|
161
|
+
/** The `freemon_industries` row this is (single source of truth); null = new. */
|
|
162
|
+
moduleIndustryId?: number | null;
|
|
163
|
+
}
|
|
135
164
|
interface ModuleSchematicDoc {
|
|
136
165
|
version: number;
|
|
137
166
|
module?: string;
|
|
@@ -156,6 +185,8 @@ interface ModuleSchematicDoc {
|
|
|
156
185
|
/** Grade crossings / diamonds (#170). */
|
|
157
186
|
crossings?: SchematicCrossing[];
|
|
158
187
|
controlPoints?: SchematicControlPoint[];
|
|
188
|
+
/** Rail-served industries — car-spot spans on a track (#industries). */
|
|
189
|
+
industries?: SchematicIndustry[];
|
|
159
190
|
/** Benchwork FOOTPRINT outline — the module's physical board shape as a
|
|
160
191
|
* polygon in module-local inches, in the same frame as the endplate poses
|
|
161
192
|
* (endplate A's track point at the origin, the mainline along +x, perpendicular
|
|
@@ -191,6 +222,45 @@ declare function sampleBenchworkOutline(pts: BenchworkPoint[], segsPerArc?: numb
|
|
|
191
222
|
x: number;
|
|
192
223
|
y: number;
|
|
193
224
|
}[];
|
|
225
|
+
interface ModuleFootprintInput {
|
|
226
|
+
/** Mainline length (falls back to footprint length), inches. */
|
|
227
|
+
lengthInches: number;
|
|
228
|
+
geometryType?: string | null;
|
|
229
|
+
geometryDegrees?: number | null;
|
|
230
|
+
geometryOffsetInches?: number | null;
|
|
231
|
+
/** Authored endplate face widths by id ("A"/"B"…), inches; default recommended. */
|
|
232
|
+
endplateWidths?: Record<string, number>;
|
|
233
|
+
/** Authored benchwork outline (module-local inches), or absent for the band. */
|
|
234
|
+
outline?: BenchworkPoint[] | null;
|
|
235
|
+
}
|
|
236
|
+
interface OutlineFace {
|
|
237
|
+
/** The endplate face's two corners + midpoint (the track point). */
|
|
238
|
+
p1: BenchworkPoint;
|
|
239
|
+
p2: BenchworkPoint;
|
|
240
|
+
mid: BenchworkPoint;
|
|
241
|
+
}
|
|
242
|
+
interface ModuleFootprint {
|
|
243
|
+
/** Main track centre-line A→B (arcs sampled). */
|
|
244
|
+
centerline: BenchworkPoint[];
|
|
245
|
+
/** Derived benchwork band (endplate-width ribbon); the outline fallback. */
|
|
246
|
+
band: BenchworkPoint[];
|
|
247
|
+
/** Endplate faces: [A end, B end]. */
|
|
248
|
+
endplateFaces: OutlineFace[];
|
|
249
|
+
/** Authored outline (arc-sampled closed ring) or null → render the band. */
|
|
250
|
+
outline: BenchworkPoint[] | null;
|
|
251
|
+
}
|
|
252
|
+
/** Module-local main track centre-line (A→B), sampling arcs for curves/corners. */
|
|
253
|
+
declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
|
|
254
|
+
/** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
|
|
255
|
+
declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number): BenchworkPoint[];
|
|
256
|
+
/** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
|
|
257
|
+
declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number): OutlineFace[];
|
|
258
|
+
/**
|
|
259
|
+
* The full single-module physical footprint: centre-line + derived band +
|
|
260
|
+
* endplate faces + the authored outline (arc-sampled), all in module-local
|
|
261
|
+
* inches. Renderers draw `outline ?? band`.
|
|
262
|
+
*/
|
|
263
|
+
declare function moduleFootprint(input: ModuleFootprintInput): ModuleFootprint;
|
|
194
264
|
/** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
|
|
195
265
|
declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
|
|
196
266
|
declare const MAIN_TRACK_ID = "main";
|
|
@@ -204,6 +274,13 @@ declare const N_SCALE_RATIO = 160;
|
|
|
204
274
|
declare function inchesToScaleFeet(inches: number, ratio?: number): number;
|
|
205
275
|
/** Scale feet of prototype track → real inches on the module. */
|
|
206
276
|
declare function scaleFeetToInches(feet: number, ratio?: number): number;
|
|
277
|
+
/** Length a spotted car occupies on N-scale track, inches. A 40-ft car body is
|
|
278
|
+
* ~3.0″; ~3.3″ over the couplers — the real spacing a cut of cars takes. The
|
|
279
|
+
* single constant every repo reads so a track's car count matches everywhere. */
|
|
280
|
+
declare const N_CAR_LENGTH_INCHES = 3.3;
|
|
281
|
+
/** How many cars fit in a span, from its drawn length — the derived capacity a
|
|
282
|
+
* siding or an industry spot holds (never typed). */
|
|
283
|
+
declare function carCapacity(fromPos: number, toPos: number, carLengthInches?: number): number;
|
|
207
284
|
/** Parse a jsonb value into a schematic doc, or null if it isn't one. */
|
|
208
285
|
declare function asModuleSchematic(x: unknown): ModuleSchematicDoc | null;
|
|
209
286
|
interface EditorTrack {
|
|
@@ -255,6 +332,20 @@ interface EditorCrossing {
|
|
|
255
332
|
trackA: string;
|
|
256
333
|
trackB: string;
|
|
257
334
|
}
|
|
335
|
+
/** An industry as the authoring form binds it — a car-spot span on a track. */
|
|
336
|
+
interface EditorIndustry {
|
|
337
|
+
id: string;
|
|
338
|
+
name: string;
|
|
339
|
+
type: string;
|
|
340
|
+
track: string;
|
|
341
|
+
fromPos: number;
|
|
342
|
+
toPos: number;
|
|
343
|
+
side: SignalSide;
|
|
344
|
+
labelMode: IndustryLabelMode;
|
|
345
|
+
carTypes: string[];
|
|
346
|
+
/** freemon_industries row (single source of truth), or null for a new one. */
|
|
347
|
+
moduleIndustryId: number | null;
|
|
348
|
+
}
|
|
258
349
|
/** A 3rd+ endplate — a branch/junction connection off the module (#170).
|
|
259
350
|
* A module may have several (e.g. a set carrying a second railroad through:
|
|
260
351
|
* MoPac enters at one branch endplate and leaves at another). */
|
|
@@ -300,6 +391,8 @@ interface EditorState {
|
|
|
300
391
|
* Empty = no authored outline (fall back to the endplate-width band). */
|
|
301
392
|
outline: BenchworkPoint[];
|
|
302
393
|
controlPoints: EditorControlPoint[];
|
|
394
|
+
/** Rail-served industries — car-spot spans on a track (#industries). */
|
|
395
|
+
industries: EditorIndustry[];
|
|
303
396
|
}
|
|
304
397
|
/** Build the empty editor state for a module of the given length. */
|
|
305
398
|
declare function emptyEditorState(lengthInches: number): EditorState;
|
|
@@ -430,6 +523,23 @@ interface BranchConnector {
|
|
|
430
523
|
posFrac: number;
|
|
431
524
|
side: "up" | "down";
|
|
432
525
|
}
|
|
526
|
+
/** An industry — draw a car-spot span beside its track's lane, on `side`, with
|
|
527
|
+
* a name label + an optional car/length readout (#industries). */
|
|
528
|
+
interface DrawIndustry {
|
|
529
|
+
id: string;
|
|
530
|
+
name: string;
|
|
531
|
+
type: string | null;
|
|
532
|
+
/** Span as fractions of module length (sorted West→East). */
|
|
533
|
+
fromFrac: number;
|
|
534
|
+
toFrac: number;
|
|
535
|
+
/** Lane of the track it spots on, so it draws beside the right track. */
|
|
536
|
+
lane: number;
|
|
537
|
+
side: SignalSide;
|
|
538
|
+
labelMode: IndustryLabelMode;
|
|
539
|
+
/** Cars that spot here, derived from the drawn span length. */
|
|
540
|
+
cars: number;
|
|
541
|
+
carTypes: string[];
|
|
542
|
+
}
|
|
433
543
|
interface ModuleFeatures {
|
|
434
544
|
/** Whether either endplate declares a double-track main. */
|
|
435
545
|
doubleMain: boolean;
|
|
@@ -454,6 +564,8 @@ interface ModuleFeatures {
|
|
|
454
564
|
crossovers: DrawCrossover[];
|
|
455
565
|
/** Branch endplates — junction connectors off the module (#170). */
|
|
456
566
|
branchConnectors: BranchConnector[];
|
|
567
|
+
/** Rail-served industries — car-spot spans beside their track (#industries). */
|
|
568
|
+
industries: DrawIndustry[];
|
|
457
569
|
/** Main 2's extent when it doesn't run the full module — a single↔double
|
|
458
570
|
* transition (Main 2 starts/ends at the mainline turnout). Null = full
|
|
459
571
|
* length (or no Main 2). Renderers draw the partial line + its diverge. */
|
|
@@ -559,4 +671,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
559
671
|
heading: number;
|
|
560
672
|
}>;
|
|
561
673
|
|
|
562
|
-
export { type BenchworkPoint, 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, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, 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, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
|
|
674
|
+
export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, type IndustryLabelMode, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
|
package/dist/index.d.ts
CHANGED
|
@@ -132,6 +132,35 @@ interface SchematicControlPoint {
|
|
|
132
132
|
crossings?: string[];
|
|
133
133
|
signals?: SchematicSignal[];
|
|
134
134
|
}
|
|
135
|
+
/** What a rendered industry shows beside its name: a car count, a length in
|
|
136
|
+
* inches, or nothing (name only). Authored per industry. */
|
|
137
|
+
type IndustryLabelMode = "none" | "cars" | "inches";
|
|
138
|
+
/**
|
|
139
|
+
* An industry — a rail-served customer that spots cars, authored as a SPAN on a
|
|
140
|
+
* track (a spur/siding, or the main). Positional like everything else: it lives
|
|
141
|
+
* in the same module-local inch frame and is rendered into the shared 2-D view,
|
|
142
|
+
* offset to `side`. The span length gives its car capacity; the dispatcher and
|
|
143
|
+
* crews read where cars set out. Mirrors a `freemon_industries` row.
|
|
144
|
+
*/
|
|
145
|
+
interface SchematicIndustry {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
/** Industry type value from the lookup (e.g. "team_track", "grain"). */
|
|
149
|
+
type?: string | null;
|
|
150
|
+
/** The track this industry spots cars on (a spur/siding id, or the main). */
|
|
151
|
+
track: string;
|
|
152
|
+
/** The car-spot span along that track, inches from endplate A. */
|
|
153
|
+
fromPos: number;
|
|
154
|
+
toPos: number;
|
|
155
|
+
/** Which side of the track the building + label sit on. */
|
|
156
|
+
side?: SignalSide;
|
|
157
|
+
/** Secondary readout at the label — a car count, a length, or none. */
|
|
158
|
+
labelMode?: IndustryLabelMode;
|
|
159
|
+
/** Car types this industry receives (car-type value strings). */
|
|
160
|
+
carTypes?: string[];
|
|
161
|
+
/** The `freemon_industries` row this is (single source of truth); null = new. */
|
|
162
|
+
moduleIndustryId?: number | null;
|
|
163
|
+
}
|
|
135
164
|
interface ModuleSchematicDoc {
|
|
136
165
|
version: number;
|
|
137
166
|
module?: string;
|
|
@@ -156,6 +185,8 @@ interface ModuleSchematicDoc {
|
|
|
156
185
|
/** Grade crossings / diamonds (#170). */
|
|
157
186
|
crossings?: SchematicCrossing[];
|
|
158
187
|
controlPoints?: SchematicControlPoint[];
|
|
188
|
+
/** Rail-served industries — car-spot spans on a track (#industries). */
|
|
189
|
+
industries?: SchematicIndustry[];
|
|
159
190
|
/** Benchwork FOOTPRINT outline — the module's physical board shape as a
|
|
160
191
|
* polygon in module-local inches, in the same frame as the endplate poses
|
|
161
192
|
* (endplate A's track point at the origin, the mainline along +x, perpendicular
|
|
@@ -191,6 +222,45 @@ declare function sampleBenchworkOutline(pts: BenchworkPoint[], segsPerArc?: numb
|
|
|
191
222
|
x: number;
|
|
192
223
|
y: number;
|
|
193
224
|
}[];
|
|
225
|
+
interface ModuleFootprintInput {
|
|
226
|
+
/** Mainline length (falls back to footprint length), inches. */
|
|
227
|
+
lengthInches: number;
|
|
228
|
+
geometryType?: string | null;
|
|
229
|
+
geometryDegrees?: number | null;
|
|
230
|
+
geometryOffsetInches?: number | null;
|
|
231
|
+
/** Authored endplate face widths by id ("A"/"B"…), inches; default recommended. */
|
|
232
|
+
endplateWidths?: Record<string, number>;
|
|
233
|
+
/** Authored benchwork outline (module-local inches), or absent for the band. */
|
|
234
|
+
outline?: BenchworkPoint[] | null;
|
|
235
|
+
}
|
|
236
|
+
interface OutlineFace {
|
|
237
|
+
/** The endplate face's two corners + midpoint (the track point). */
|
|
238
|
+
p1: BenchworkPoint;
|
|
239
|
+
p2: BenchworkPoint;
|
|
240
|
+
mid: BenchworkPoint;
|
|
241
|
+
}
|
|
242
|
+
interface ModuleFootprint {
|
|
243
|
+
/** Main track centre-line A→B (arcs sampled). */
|
|
244
|
+
centerline: BenchworkPoint[];
|
|
245
|
+
/** Derived benchwork band (endplate-width ribbon); the outline fallback. */
|
|
246
|
+
band: BenchworkPoint[];
|
|
247
|
+
/** Endplate faces: [A end, B end]. */
|
|
248
|
+
endplateFaces: OutlineFace[];
|
|
249
|
+
/** Authored outline (arc-sampled closed ring) or null → render the band. */
|
|
250
|
+
outline: BenchworkPoint[] | null;
|
|
251
|
+
}
|
|
252
|
+
/** Module-local main track centre-line (A→B), sampling arcs for curves/corners. */
|
|
253
|
+
declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
|
|
254
|
+
/** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
|
|
255
|
+
declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number): BenchworkPoint[];
|
|
256
|
+
/** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
|
|
257
|
+
declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number): OutlineFace[];
|
|
258
|
+
/**
|
|
259
|
+
* The full single-module physical footprint: centre-line + derived band +
|
|
260
|
+
* endplate faces + the authored outline (arc-sampled), all in module-local
|
|
261
|
+
* inches. Renderers draw `outline ?? band`.
|
|
262
|
+
*/
|
|
263
|
+
declare function moduleFootprint(input: ModuleFootprintInput): ModuleFootprint;
|
|
194
264
|
/** Whether a doc is a single-endplate turnback (explicit flag or one endplate). */
|
|
195
265
|
declare function isLoopDoc(doc: ModuleSchematicDoc): boolean;
|
|
196
266
|
declare const MAIN_TRACK_ID = "main";
|
|
@@ -204,6 +274,13 @@ declare const N_SCALE_RATIO = 160;
|
|
|
204
274
|
declare function inchesToScaleFeet(inches: number, ratio?: number): number;
|
|
205
275
|
/** Scale feet of prototype track → real inches on the module. */
|
|
206
276
|
declare function scaleFeetToInches(feet: number, ratio?: number): number;
|
|
277
|
+
/** Length a spotted car occupies on N-scale track, inches. A 40-ft car body is
|
|
278
|
+
* ~3.0″; ~3.3″ over the couplers — the real spacing a cut of cars takes. The
|
|
279
|
+
* single constant every repo reads so a track's car count matches everywhere. */
|
|
280
|
+
declare const N_CAR_LENGTH_INCHES = 3.3;
|
|
281
|
+
/** How many cars fit in a span, from its drawn length — the derived capacity a
|
|
282
|
+
* siding or an industry spot holds (never typed). */
|
|
283
|
+
declare function carCapacity(fromPos: number, toPos: number, carLengthInches?: number): number;
|
|
207
284
|
/** Parse a jsonb value into a schematic doc, or null if it isn't one. */
|
|
208
285
|
declare function asModuleSchematic(x: unknown): ModuleSchematicDoc | null;
|
|
209
286
|
interface EditorTrack {
|
|
@@ -255,6 +332,20 @@ interface EditorCrossing {
|
|
|
255
332
|
trackA: string;
|
|
256
333
|
trackB: string;
|
|
257
334
|
}
|
|
335
|
+
/** An industry as the authoring form binds it — a car-spot span on a track. */
|
|
336
|
+
interface EditorIndustry {
|
|
337
|
+
id: string;
|
|
338
|
+
name: string;
|
|
339
|
+
type: string;
|
|
340
|
+
track: string;
|
|
341
|
+
fromPos: number;
|
|
342
|
+
toPos: number;
|
|
343
|
+
side: SignalSide;
|
|
344
|
+
labelMode: IndustryLabelMode;
|
|
345
|
+
carTypes: string[];
|
|
346
|
+
/** freemon_industries row (single source of truth), or null for a new one. */
|
|
347
|
+
moduleIndustryId: number | null;
|
|
348
|
+
}
|
|
258
349
|
/** A 3rd+ endplate — a branch/junction connection off the module (#170).
|
|
259
350
|
* A module may have several (e.g. a set carrying a second railroad through:
|
|
260
351
|
* MoPac enters at one branch endplate and leaves at another). */
|
|
@@ -300,6 +391,8 @@ interface EditorState {
|
|
|
300
391
|
* Empty = no authored outline (fall back to the endplate-width band). */
|
|
301
392
|
outline: BenchworkPoint[];
|
|
302
393
|
controlPoints: EditorControlPoint[];
|
|
394
|
+
/** Rail-served industries — car-spot spans on a track (#industries). */
|
|
395
|
+
industries: EditorIndustry[];
|
|
303
396
|
}
|
|
304
397
|
/** Build the empty editor state for a module of the given length. */
|
|
305
398
|
declare function emptyEditorState(lengthInches: number): EditorState;
|
|
@@ -430,6 +523,23 @@ interface BranchConnector {
|
|
|
430
523
|
posFrac: number;
|
|
431
524
|
side: "up" | "down";
|
|
432
525
|
}
|
|
526
|
+
/** An industry — draw a car-spot span beside its track's lane, on `side`, with
|
|
527
|
+
* a name label + an optional car/length readout (#industries). */
|
|
528
|
+
interface DrawIndustry {
|
|
529
|
+
id: string;
|
|
530
|
+
name: string;
|
|
531
|
+
type: string | null;
|
|
532
|
+
/** Span as fractions of module length (sorted West→East). */
|
|
533
|
+
fromFrac: number;
|
|
534
|
+
toFrac: number;
|
|
535
|
+
/** Lane of the track it spots on, so it draws beside the right track. */
|
|
536
|
+
lane: number;
|
|
537
|
+
side: SignalSide;
|
|
538
|
+
labelMode: IndustryLabelMode;
|
|
539
|
+
/** Cars that spot here, derived from the drawn span length. */
|
|
540
|
+
cars: number;
|
|
541
|
+
carTypes: string[];
|
|
542
|
+
}
|
|
433
543
|
interface ModuleFeatures {
|
|
434
544
|
/** Whether either endplate declares a double-track main. */
|
|
435
545
|
doubleMain: boolean;
|
|
@@ -454,6 +564,8 @@ interface ModuleFeatures {
|
|
|
454
564
|
crossovers: DrawCrossover[];
|
|
455
565
|
/** Branch endplates — junction connectors off the module (#170). */
|
|
456
566
|
branchConnectors: BranchConnector[];
|
|
567
|
+
/** Rail-served industries — car-spot spans beside their track (#industries). */
|
|
568
|
+
industries: DrawIndustry[];
|
|
457
569
|
/** Main 2's extent when it doesn't run the full module — a single↔double
|
|
458
570
|
* transition (Main 2 starts/ends at the mainline turnout). Null = full
|
|
459
571
|
* length (or no Main 2). Renderers draw the partial line + its diverge. */
|
|
@@ -559,4 +671,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
559
671
|
heading: number;
|
|
560
672
|
}>;
|
|
561
673
|
|
|
562
|
-
export { type BenchworkPoint, 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, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, 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, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
|
|
674
|
+
export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, type GeometryType, type IndustryLabelMode, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OutlineFace, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,78 @@ function arcSweep(a0, a1, am) {
|
|
|
63
63
|
const one = norm(a1);
|
|
64
64
|
return m <= one ? one : one - 2 * Math.PI;
|
|
65
65
|
}
|
|
66
|
+
var DEG_FP = Math.PI / 180;
|
|
67
|
+
function moduleCenterline(input) {
|
|
68
|
+
const L = input.lengthInches > 0 ? input.lengthInches : 24;
|
|
69
|
+
const gt = input.geometryType;
|
|
70
|
+
if (gt === "dead_end") return [{ x: 0, y: 0 }];
|
|
71
|
+
if (gt === "offset") return [{ x: 0, y: 0 }, { x: L, y: input.geometryOffsetInches ?? 0 }];
|
|
72
|
+
const turn = gt === "corner_45" ? 45 : gt === "corner_90" ? 90 : gt === "curve" ? input.geometryDegrees ?? 0 : 0;
|
|
73
|
+
if (turn === 0) return [{ x: 0, y: 0 }, { x: L, y: 0 }];
|
|
74
|
+
const t = turn * DEG_FP;
|
|
75
|
+
const r = L / t;
|
|
76
|
+
const steps = 12;
|
|
77
|
+
const pts = [];
|
|
78
|
+
for (let i = 0; i <= steps; i++) {
|
|
79
|
+
const a = t * i / steps;
|
|
80
|
+
pts.push({ x: r * Math.sin(a), y: r * (1 - Math.cos(a)) });
|
|
81
|
+
}
|
|
82
|
+
return pts;
|
|
83
|
+
}
|
|
84
|
+
function centerlineNormals(center) {
|
|
85
|
+
return center.map((_, i) => {
|
|
86
|
+
const a = center[Math.max(0, i - 1)];
|
|
87
|
+
const b = center[Math.min(center.length - 1, i + 1)];
|
|
88
|
+
let dx = b.x - a.x;
|
|
89
|
+
let dy = b.y - a.y;
|
|
90
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
91
|
+
dx /= len;
|
|
92
|
+
dy /= len;
|
|
93
|
+
return { x: -dy, y: dx };
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function centerlineFractions(center) {
|
|
97
|
+
const cum = [0];
|
|
98
|
+
for (let i = 1; i < center.length; i++)
|
|
99
|
+
cum.push(cum[i - 1] + Math.hypot(center[i].x - center[i - 1].x, center[i].y - center[i - 1].y));
|
|
100
|
+
const total = cum[cum.length - 1] || 1;
|
|
101
|
+
return cum.map((d) => d / total);
|
|
102
|
+
}
|
|
103
|
+
function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
|
|
104
|
+
if (center.length < 2) return [];
|
|
105
|
+
const n = centerlineNormals(center);
|
|
106
|
+
const f = centerlineFractions(center);
|
|
107
|
+
const half = (i) => (widthA * (1 - f[i]) + widthB * f[i]) / 2;
|
|
108
|
+
const left = center.map((p, i) => ({ x: p.x + n[i].x * half(i), y: p.y + n[i].y * half(i) }));
|
|
109
|
+
const right = center.map((p, i) => ({ x: p.x - n[i].x * half(i), y: p.y - n[i].y * half(i) }));
|
|
110
|
+
return [...left, ...right.reverse()];
|
|
111
|
+
}
|
|
112
|
+
function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES) {
|
|
113
|
+
if (center.length < 2) return [];
|
|
114
|
+
const n = centerlineNormals(center);
|
|
115
|
+
const face = (i, w) => ({
|
|
116
|
+
p1: { x: center[i].x + n[i].x * (w / 2), y: center[i].y + n[i].y * (w / 2) },
|
|
117
|
+
p2: { x: center[i].x - n[i].x * (w / 2), y: center[i].y - n[i].y * (w / 2) },
|
|
118
|
+
mid: { x: center[i].x, y: center[i].y }
|
|
119
|
+
});
|
|
120
|
+
return [face(0, widthA), face(center.length - 1, widthB)];
|
|
121
|
+
}
|
|
122
|
+
function moduleFootprint(input) {
|
|
123
|
+
const centerline = moduleCenterline(input);
|
|
124
|
+
const widthA = endplateWidthFor(input.endplateWidths, "A");
|
|
125
|
+
const widthB = endplateWidthFor(input.endplateWidths, "B");
|
|
126
|
+
const authored = benchworkOutline(input);
|
|
127
|
+
return {
|
|
128
|
+
centerline,
|
|
129
|
+
band: benchworkBand(centerline, widthA, widthB),
|
|
130
|
+
endplateFaces: endplateFaceSegments(centerline, widthA, widthB),
|
|
131
|
+
outline: authored ? sampleBenchworkOutline(authored) : null
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function endplateWidthFor(widths, id) {
|
|
135
|
+
const w = widths?.[id];
|
|
136
|
+
return typeof w === "number" && w > 0 ? w : FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES;
|
|
137
|
+
}
|
|
66
138
|
function isLoopDoc(doc) {
|
|
67
139
|
return doc.loop === true || doc.endplates.length === 1;
|
|
68
140
|
}
|
|
@@ -75,6 +147,11 @@ function inchesToScaleFeet(inches, ratio = N_SCALE_RATIO) {
|
|
|
75
147
|
function scaleFeetToInches(feet, ratio = N_SCALE_RATIO) {
|
|
76
148
|
return feet * 12 / ratio;
|
|
77
149
|
}
|
|
150
|
+
var N_CAR_LENGTH_INCHES = 3.3;
|
|
151
|
+
function carCapacity(fromPos, toPos, carLengthInches = N_CAR_LENGTH_INCHES) {
|
|
152
|
+
if (!(carLengthInches > 0)) return 0;
|
|
153
|
+
return Math.max(0, Math.floor(Math.abs(toPos - fromPos) / carLengthInches));
|
|
154
|
+
}
|
|
78
155
|
function asModuleSchematic(x) {
|
|
79
156
|
if (!x || typeof x !== "object") return null;
|
|
80
157
|
const d = x;
|
|
@@ -96,7 +173,8 @@ function emptyEditorState(lengthInches) {
|
|
|
96
173
|
poseOverrides: {},
|
|
97
174
|
endplateWidths: {},
|
|
98
175
|
outline: [],
|
|
99
|
-
controlPoints: []
|
|
176
|
+
controlPoints: [],
|
|
177
|
+
industries: []
|
|
100
178
|
};
|
|
101
179
|
}
|
|
102
180
|
function isTransitionTurnout(t) {
|
|
@@ -266,6 +344,21 @@ function stateToDoc(state, recordNumber) {
|
|
|
266
344
|
side: s.side
|
|
267
345
|
}))
|
|
268
346
|
})),
|
|
347
|
+
// Industries — car-spot spans on a track; only when any are authored.
|
|
348
|
+
...state.industries.length > 0 ? {
|
|
349
|
+
industries: state.industries.map((ind) => ({
|
|
350
|
+
id: ind.id,
|
|
351
|
+
name: ind.name,
|
|
352
|
+
...ind.type ? { type: ind.type } : {},
|
|
353
|
+
track: ind.track,
|
|
354
|
+
fromPos: ind.fromPos,
|
|
355
|
+
toPos: ind.toPos,
|
|
356
|
+
side: ind.side,
|
|
357
|
+
...ind.labelMode && ind.labelMode !== "none" ? { labelMode: ind.labelMode } : {},
|
|
358
|
+
...ind.carTypes.length ? { carTypes: ind.carTypes } : {},
|
|
359
|
+
moduleIndustryId: ind.moduleIndustryId
|
|
360
|
+
}))
|
|
361
|
+
} : {},
|
|
269
362
|
// Benchwork footprint outline (module-local inches); only when it's a real
|
|
270
363
|
// ring (≥ 3 vertices).
|
|
271
364
|
...state.outline.length >= 3 ? { outline: state.outline } : {}
|
|
@@ -379,7 +472,19 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
|
|
|
379
472
|
divergeTrack: t.divergeTrack,
|
|
380
473
|
kind: t.kind ?? "right"
|
|
381
474
|
})),
|
|
382
|
-
controlPoints: readControlPoints(d, sc)
|
|
475
|
+
controlPoints: readControlPoints(d, sc),
|
|
476
|
+
industries: (d.industries ?? []).map((ind) => ({
|
|
477
|
+
id: ind.id,
|
|
478
|
+
name: ind.name ?? "",
|
|
479
|
+
type: ind.type ?? "",
|
|
480
|
+
track: ind.track,
|
|
481
|
+
fromPos: sc(ind.fromPos ?? 0),
|
|
482
|
+
toPos: ind.toPos != null ? sc(ind.toPos) : len,
|
|
483
|
+
side: ind.side ?? "above",
|
|
484
|
+
labelMode: ind.labelMode ?? "none",
|
|
485
|
+
carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : [],
|
|
486
|
+
moduleIndustryId: ind.moduleIndustryId ?? null
|
|
487
|
+
}))
|
|
383
488
|
};
|
|
384
489
|
}
|
|
385
490
|
function readControlPoints(d, sc = (p) => p) {
|
|
@@ -678,6 +783,22 @@ function moduleFeatures(doc) {
|
|
|
678
783
|
posFrac: clampFrac(e.at.pos),
|
|
679
784
|
side: e.at.side === "down" ? "down" : "up"
|
|
680
785
|
}));
|
|
786
|
+
const industries = (doc.industries ?? []).map((ind) => {
|
|
787
|
+
const from = ind.fromPos ?? 0;
|
|
788
|
+
const to = ind.toPos ?? len;
|
|
789
|
+
return {
|
|
790
|
+
id: ind.id,
|
|
791
|
+
name: ind.name ?? "",
|
|
792
|
+
type: ind.type ?? null,
|
|
793
|
+
fromFrac: clampFrac(Math.min(from, to)),
|
|
794
|
+
toFrac: clampFrac(Math.max(from, to)),
|
|
795
|
+
lane: trackLane.get(ind.track) ?? 0,
|
|
796
|
+
side: ind.side ?? "above",
|
|
797
|
+
labelMode: ind.labelMode ?? "none",
|
|
798
|
+
cars: carCapacity(from, to),
|
|
799
|
+
carTypes: Array.isArray(ind.carTypes) ? ind.carTypes : []
|
|
800
|
+
};
|
|
801
|
+
});
|
|
681
802
|
const allLanes = [
|
|
682
803
|
0,
|
|
683
804
|
doubleMain ? 1 : 0,
|
|
@@ -718,6 +839,7 @@ function moduleFeatures(doc) {
|
|
|
718
839
|
crossings,
|
|
719
840
|
crossovers,
|
|
720
841
|
branchConnectors,
|
|
842
|
+
industries,
|
|
721
843
|
laneMin: Math.min(...allLanes),
|
|
722
844
|
laneMax: Math.max(...allLanes)
|
|
723
845
|
};
|
|
@@ -820,6 +942,6 @@ function poseOverridesFromDoc(doc) {
|
|
|
820
942
|
return out;
|
|
821
943
|
}
|
|
822
944
|
|
|
823
|
-
export { FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_SCALE_RATIO, asModuleSchematic, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleFeatures, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
|
|
945
|
+
export { FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, scaleFeetToInches, stateToDoc };
|
|
824
946
|
//# sourceMappingURL=index.js.map
|
|
825
947
|
//# sourceMappingURL=index.js.map
|