@willcgage/module-schematic 0.29.0 → 0.31.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 +167 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -18
- package/dist/index.d.ts +81 -18
- package/dist/index.js +162 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -273,6 +273,18 @@ interface BenchworkPoint {
|
|
|
273
273
|
bulge?: number;
|
|
274
274
|
}
|
|
275
275
|
/** One bench-work section of a module (#96 phase 2). */
|
|
276
|
+
interface SectionFootprint {
|
|
277
|
+
id: string;
|
|
278
|
+
name?: string;
|
|
279
|
+
outline: {
|
|
280
|
+
x: number;
|
|
281
|
+
y: number;
|
|
282
|
+
}[];
|
|
283
|
+
/** True when this shape is DERIVED from the section's span rather than
|
|
284
|
+
* authored — a derived outline follows the board when it's resized, an
|
|
285
|
+
* authored one stays exactly as drawn (#96 phase 2b). */
|
|
286
|
+
derived: boolean;
|
|
287
|
+
}
|
|
276
288
|
interface SchematicSection {
|
|
277
289
|
id: string;
|
|
278
290
|
/** What the owner calls this board — "west transition", "double #3". */
|
|
@@ -281,6 +293,19 @@ interface SchematicSection {
|
|
|
281
293
|
* the module outline. Absent = the section has no shape of its own yet (it's
|
|
282
294
|
* described only by the joints in `sectionBreaks`). */
|
|
283
295
|
outline?: BenchworkPoint[] | null;
|
|
296
|
+
/** How far this board runs along the main, inches. The module's length is the
|
|
297
|
+
* SUM of these — it isn't authored separately (#108). */
|
|
298
|
+
lengthInches?: number | null;
|
|
299
|
+
/** This board's own shape: straight | curve | corner_45 | corner_90 | offset |
|
|
300
|
+
* dead_end. Geometry belongs to the SECTION, not the module — a module like
|
|
301
|
+
* One Mile is 384″ of mostly-straight boards with two 24″ CURVED sections in
|
|
302
|
+
* the middle, which no single module-level geometry can describe (#108).
|
|
303
|
+
* Absent = straight. */
|
|
304
|
+
geometryType?: string | null;
|
|
305
|
+
/** Degrees turned, for curve/corner sections. */
|
|
306
|
+
geometryDegrees?: number | null;
|
|
307
|
+
/** Lateral jog, for offset sections. */
|
|
308
|
+
geometryOffsetInches?: number | null;
|
|
284
309
|
}
|
|
285
310
|
/** The authored benchwork outline, or null when a module hasn't drawn one
|
|
286
311
|
* (renderers then fall back to a band derived from the endplate widths). A
|
|
@@ -301,14 +326,18 @@ declare function moduleSections(doc: {
|
|
|
301
326
|
* bring in real clipping. */
|
|
302
327
|
declare function sectionFootprints(doc: {
|
|
303
328
|
sections?: SchematicSection[] | null;
|
|
304
|
-
} | null | undefined
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
329
|
+
} | null | undefined,
|
|
330
|
+
/** The module's spine and dimensions. Given these, a section with no
|
|
331
|
+
* authored polygon gets a band derived from its own span, so every section
|
|
332
|
+
* has a shape and a resized board reshapes with it (#96 phase 2b). Omit to
|
|
333
|
+
* get authored outlines only. */
|
|
334
|
+
derive?: {
|
|
335
|
+
centerline: BenchworkPoint[];
|
|
336
|
+
widthA: number;
|
|
337
|
+
widthB: number;
|
|
338
|
+
offsetA: number;
|
|
339
|
+
offsetB: number;
|
|
340
|
+
}): SectionFootprint[];
|
|
312
341
|
/**
|
|
313
342
|
* Expand a benchwork outline (whose edges may be arcs) into a dense closed
|
|
314
343
|
* polyline for rendering — the SAME sampling both the Repository preview and
|
|
@@ -374,24 +403,58 @@ interface ModuleFootprint {
|
|
|
374
403
|
/** Authored outline (arc-sampled closed ring) or null → render the band.
|
|
375
404
|
* Null too when `sectionOutlines` is non-empty — the sections ARE the shape. */
|
|
376
405
|
outline: BenchworkPoint[] | null;
|
|
377
|
-
/** Per-section footprints, arc-sampled (#96 phase
|
|
406
|
+
/** Per-section footprints, arc-sampled (#96 phase 2b). Draw every one: together
|
|
378
407
|
* they are the module's footprint. Empty = this module doesn't use sections,
|
|
379
408
|
* so fall back to `outline ?? band` exactly as before. */
|
|
380
|
-
sectionOutlines:
|
|
381
|
-
id: string;
|
|
382
|
-
name?: string;
|
|
383
|
-
outline: {
|
|
384
|
-
x: number;
|
|
385
|
-
y: number;
|
|
386
|
-
}[];
|
|
387
|
-
}[];
|
|
409
|
+
sectionOutlines: SectionFootprint[];
|
|
388
410
|
}
|
|
389
411
|
/** Module-local main track centre-line (A→B), sampling arcs for curves/corners.
|
|
390
412
|
* An authored `mainPath` wins — the owner drew the real shape; otherwise the
|
|
391
413
|
* centre-line is derived from the geometry fields (length + type/degrees/offset). */
|
|
392
414
|
declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
|
|
415
|
+
/** The module's length as the SUM of its sections, or null when it has none
|
|
416
|
+
* (then the authored module length still speaks). */
|
|
417
|
+
declare function moduleLengthFromSections(doc: {
|
|
418
|
+
sections?: SchematicSection[] | null;
|
|
419
|
+
} | null | undefined): number | null;
|
|
420
|
+
/** Where each section starts and ends along the main, inches from endplate A.
|
|
421
|
+
* This is what `sectionBreaks` used to author by hand — now derived, so a
|
|
422
|
+
* length is just a number you type and nothing steals from its neighbour. */
|
|
423
|
+
declare function sectionSpans(doc: {
|
|
424
|
+
sections?: SchematicSection[] | null;
|
|
425
|
+
} | null | undefined): {
|
|
426
|
+
id: string;
|
|
427
|
+
name?: string;
|
|
428
|
+
fromPos: number;
|
|
429
|
+
toPos: number;
|
|
430
|
+
}[];
|
|
431
|
+
/** The joints implied by the sections — the interior boundaries, in inches from
|
|
432
|
+
* endplate A. Replaces the authored `sectionBreaks` for a sectioned module. */
|
|
433
|
+
declare function sectionBreaksFromSections(doc: {
|
|
434
|
+
sections?: SchematicSection[] | null;
|
|
435
|
+
} | null | undefined): number[];
|
|
436
|
+
/** The module's centre-line built by CHAINING its sections — each board starts
|
|
437
|
+
* where the previous one ended, at the heading it ended on. This is what makes
|
|
438
|
+
* a module like One Mile expressible: straight boards with two 24″ curved ones
|
|
439
|
+
* in the middle, which no single module-level geometry can describe (#108).
|
|
440
|
+
* Returns [] when the module has no sections with lengths. */
|
|
441
|
+
declare function sectionedCenterline(doc: {
|
|
442
|
+
sections?: SchematicSection[] | null;
|
|
443
|
+
} | null | undefined): BenchworkPoint[];
|
|
393
444
|
/** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
|
|
394
445
|
declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
|
|
446
|
+
/** The sub-polyline of a centre-line between two arc-length positions, with
|
|
447
|
+
* the cut ends interpolated so a slice starts and finishes exactly on them. */
|
|
448
|
+
declare function sliceCenterline(center: BenchworkPoint[], fromPos: number, toPos: number): BenchworkPoint[];
|
|
449
|
+
/** One section's bench-work as a band over its own stretch of centre-line —
|
|
450
|
+
* the per-section equivalent of `benchworkBand` (#96 phase 2b). Width and
|
|
451
|
+
* plate offset are interpolated from the module's ends exactly as the whole
|
|
452
|
+
* band does, so a section's derived shape lines up with its neighbours.
|
|
453
|
+
*
|
|
454
|
+
* This is what makes an outline BELONG to the section: a section without an
|
|
455
|
+
* authored polygon gets one derived from its span, so resizing the board
|
|
456
|
+
* reshapes it instead of leaving a hand-drawn outline stranded. */
|
|
457
|
+
declare function sectionBand(center: BenchworkPoint[], fromPos: number, toPos: number, widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
|
|
395
458
|
/** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
|
|
396
459
|
declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): OutlineFace[];
|
|
397
460
|
/**
|
|
@@ -878,4 +941,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
878
941
|
heading: number;
|
|
879
942
|
}>;
|
|
880
943
|
|
|
881
|
-
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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionFootprints, stateToDoc, trackPath };
|
|
944
|
+
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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBand, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
|
package/dist/index.d.ts
CHANGED
|
@@ -273,6 +273,18 @@ interface BenchworkPoint {
|
|
|
273
273
|
bulge?: number;
|
|
274
274
|
}
|
|
275
275
|
/** One bench-work section of a module (#96 phase 2). */
|
|
276
|
+
interface SectionFootprint {
|
|
277
|
+
id: string;
|
|
278
|
+
name?: string;
|
|
279
|
+
outline: {
|
|
280
|
+
x: number;
|
|
281
|
+
y: number;
|
|
282
|
+
}[];
|
|
283
|
+
/** True when this shape is DERIVED from the section's span rather than
|
|
284
|
+
* authored — a derived outline follows the board when it's resized, an
|
|
285
|
+
* authored one stays exactly as drawn (#96 phase 2b). */
|
|
286
|
+
derived: boolean;
|
|
287
|
+
}
|
|
276
288
|
interface SchematicSection {
|
|
277
289
|
id: string;
|
|
278
290
|
/** What the owner calls this board — "west transition", "double #3". */
|
|
@@ -281,6 +293,19 @@ interface SchematicSection {
|
|
|
281
293
|
* the module outline. Absent = the section has no shape of its own yet (it's
|
|
282
294
|
* described only by the joints in `sectionBreaks`). */
|
|
283
295
|
outline?: BenchworkPoint[] | null;
|
|
296
|
+
/** How far this board runs along the main, inches. The module's length is the
|
|
297
|
+
* SUM of these — it isn't authored separately (#108). */
|
|
298
|
+
lengthInches?: number | null;
|
|
299
|
+
/** This board's own shape: straight | curve | corner_45 | corner_90 | offset |
|
|
300
|
+
* dead_end. Geometry belongs to the SECTION, not the module — a module like
|
|
301
|
+
* One Mile is 384″ of mostly-straight boards with two 24″ CURVED sections in
|
|
302
|
+
* the middle, which no single module-level geometry can describe (#108).
|
|
303
|
+
* Absent = straight. */
|
|
304
|
+
geometryType?: string | null;
|
|
305
|
+
/** Degrees turned, for curve/corner sections. */
|
|
306
|
+
geometryDegrees?: number | null;
|
|
307
|
+
/** Lateral jog, for offset sections. */
|
|
308
|
+
geometryOffsetInches?: number | null;
|
|
284
309
|
}
|
|
285
310
|
/** The authored benchwork outline, or null when a module hasn't drawn one
|
|
286
311
|
* (renderers then fall back to a band derived from the endplate widths). A
|
|
@@ -301,14 +326,18 @@ declare function moduleSections(doc: {
|
|
|
301
326
|
* bring in real clipping. */
|
|
302
327
|
declare function sectionFootprints(doc: {
|
|
303
328
|
sections?: SchematicSection[] | null;
|
|
304
|
-
} | null | undefined
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
329
|
+
} | null | undefined,
|
|
330
|
+
/** The module's spine and dimensions. Given these, a section with no
|
|
331
|
+
* authored polygon gets a band derived from its own span, so every section
|
|
332
|
+
* has a shape and a resized board reshapes with it (#96 phase 2b). Omit to
|
|
333
|
+
* get authored outlines only. */
|
|
334
|
+
derive?: {
|
|
335
|
+
centerline: BenchworkPoint[];
|
|
336
|
+
widthA: number;
|
|
337
|
+
widthB: number;
|
|
338
|
+
offsetA: number;
|
|
339
|
+
offsetB: number;
|
|
340
|
+
}): SectionFootprint[];
|
|
312
341
|
/**
|
|
313
342
|
* Expand a benchwork outline (whose edges may be arcs) into a dense closed
|
|
314
343
|
* polyline for rendering — the SAME sampling both the Repository preview and
|
|
@@ -374,24 +403,58 @@ interface ModuleFootprint {
|
|
|
374
403
|
/** Authored outline (arc-sampled closed ring) or null → render the band.
|
|
375
404
|
* Null too when `sectionOutlines` is non-empty — the sections ARE the shape. */
|
|
376
405
|
outline: BenchworkPoint[] | null;
|
|
377
|
-
/** Per-section footprints, arc-sampled (#96 phase
|
|
406
|
+
/** Per-section footprints, arc-sampled (#96 phase 2b). Draw every one: together
|
|
378
407
|
* they are the module's footprint. Empty = this module doesn't use sections,
|
|
379
408
|
* so fall back to `outline ?? band` exactly as before. */
|
|
380
|
-
sectionOutlines:
|
|
381
|
-
id: string;
|
|
382
|
-
name?: string;
|
|
383
|
-
outline: {
|
|
384
|
-
x: number;
|
|
385
|
-
y: number;
|
|
386
|
-
}[];
|
|
387
|
-
}[];
|
|
409
|
+
sectionOutlines: SectionFootprint[];
|
|
388
410
|
}
|
|
389
411
|
/** Module-local main track centre-line (A→B), sampling arcs for curves/corners.
|
|
390
412
|
* An authored `mainPath` wins — the owner drew the real shape; otherwise the
|
|
391
413
|
* centre-line is derived from the geometry fields (length + type/degrees/offset). */
|
|
392
414
|
declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
|
|
415
|
+
/** The module's length as the SUM of its sections, or null when it has none
|
|
416
|
+
* (then the authored module length still speaks). */
|
|
417
|
+
declare function moduleLengthFromSections(doc: {
|
|
418
|
+
sections?: SchematicSection[] | null;
|
|
419
|
+
} | null | undefined): number | null;
|
|
420
|
+
/** Where each section starts and ends along the main, inches from endplate A.
|
|
421
|
+
* This is what `sectionBreaks` used to author by hand — now derived, so a
|
|
422
|
+
* length is just a number you type and nothing steals from its neighbour. */
|
|
423
|
+
declare function sectionSpans(doc: {
|
|
424
|
+
sections?: SchematicSection[] | null;
|
|
425
|
+
} | null | undefined): {
|
|
426
|
+
id: string;
|
|
427
|
+
name?: string;
|
|
428
|
+
fromPos: number;
|
|
429
|
+
toPos: number;
|
|
430
|
+
}[];
|
|
431
|
+
/** The joints implied by the sections — the interior boundaries, in inches from
|
|
432
|
+
* endplate A. Replaces the authored `sectionBreaks` for a sectioned module. */
|
|
433
|
+
declare function sectionBreaksFromSections(doc: {
|
|
434
|
+
sections?: SchematicSection[] | null;
|
|
435
|
+
} | null | undefined): number[];
|
|
436
|
+
/** The module's centre-line built by CHAINING its sections — each board starts
|
|
437
|
+
* where the previous one ended, at the heading it ended on. This is what makes
|
|
438
|
+
* a module like One Mile expressible: straight boards with two 24″ curved ones
|
|
439
|
+
* in the middle, which no single module-level geometry can describe (#108).
|
|
440
|
+
* Returns [] when the module has no sections with lengths. */
|
|
441
|
+
declare function sectionedCenterline(doc: {
|
|
442
|
+
sections?: SchematicSection[] | null;
|
|
443
|
+
} | null | undefined): BenchworkPoint[];
|
|
393
444
|
/** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
|
|
394
445
|
declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
|
|
446
|
+
/** The sub-polyline of a centre-line between two arc-length positions, with
|
|
447
|
+
* the cut ends interpolated so a slice starts and finishes exactly on them. */
|
|
448
|
+
declare function sliceCenterline(center: BenchworkPoint[], fromPos: number, toPos: number): BenchworkPoint[];
|
|
449
|
+
/** One section's bench-work as a band over its own stretch of centre-line —
|
|
450
|
+
* the per-section equivalent of `benchworkBand` (#96 phase 2b). Width and
|
|
451
|
+
* plate offset are interpolated from the module's ends exactly as the whole
|
|
452
|
+
* band does, so a section's derived shape lines up with its neighbours.
|
|
453
|
+
*
|
|
454
|
+
* This is what makes an outline BELONG to the section: a section without an
|
|
455
|
+
* authored polygon gets one derived from its span, so resizing the board
|
|
456
|
+
* reshapes it instead of leaving a hand-drawn outline stranded. */
|
|
457
|
+
declare function sectionBand(center: BenchworkPoint[], fromPos: number, toPos: number, widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
|
|
395
458
|
/** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
|
|
396
459
|
declare function endplateFaceSegments(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): OutlineFace[];
|
|
397
460
|
/**
|
|
@@ -878,4 +941,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
|
|
|
878
941
|
heading: number;
|
|
879
942
|
}>;
|
|
880
943
|
|
|
881
|
-
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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionFootprints, stateToDoc, trackPath };
|
|
944
|
+
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, type EndplateWidthIssue, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type GeometryType, type IndustryLabelMode, type IndustrySpot, 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 SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionFootprint, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBand, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
|
package/dist/index.js
CHANGED
|
@@ -19,19 +19,40 @@ function moduleSections(doc) {
|
|
|
19
19
|
return (doc?.sections ?? []).filter((sec) => sec && typeof sec.id === "string" && sec.id !== "").map((sec) => {
|
|
20
20
|
const outline = benchworkOutline({ outline: sec.outline });
|
|
21
21
|
const name = typeof sec.name === "string" ? sec.name.trim() : "";
|
|
22
|
+
const len = sec.lengthInches;
|
|
23
|
+
const deg = sec.geometryDegrees;
|
|
24
|
+
const off = sec.geometryOffsetInches;
|
|
22
25
|
return {
|
|
23
26
|
id: sec.id,
|
|
24
27
|
...name ? { name } : {},
|
|
25
|
-
...outline ? { outline } : {}
|
|
28
|
+
...outline ? { outline } : {},
|
|
29
|
+
...typeof len === "number" && Number.isFinite(len) && len > 0 ? { lengthInches: len } : {},
|
|
30
|
+
...sec.geometryType ? { geometryType: sec.geometryType } : {},
|
|
31
|
+
...typeof deg === "number" && Number.isFinite(deg) ? { geometryDegrees: deg } : {},
|
|
32
|
+
...typeof off === "number" && Number.isFinite(off) ? { geometryOffsetInches: off } : {}
|
|
26
33
|
};
|
|
27
34
|
});
|
|
28
35
|
}
|
|
29
|
-
function sectionFootprints(doc) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
function sectionFootprints(doc, derive) {
|
|
37
|
+
const spans = derive ? sectionSpans(doc) : [];
|
|
38
|
+
const spanOf = new Map(spans.map((sp) => [sp.id, sp]));
|
|
39
|
+
return moduleSections(doc).map((sec) => {
|
|
40
|
+
const name = sec.name ? { name: sec.name } : {};
|
|
41
|
+
if (sec.outline)
|
|
42
|
+
return { id: sec.id, ...name, outline: sampleBenchworkOutline(sec.outline), derived: false };
|
|
43
|
+
const sp = spanOf.get(sec.id);
|
|
44
|
+
if (!sp || !derive) return null;
|
|
45
|
+
const band = sectionBand(
|
|
46
|
+
derive.centerline,
|
|
47
|
+
sp.fromPos,
|
|
48
|
+
sp.toPos,
|
|
49
|
+
derive.widthA,
|
|
50
|
+
derive.widthB,
|
|
51
|
+
derive.offsetA,
|
|
52
|
+
derive.offsetB
|
|
53
|
+
);
|
|
54
|
+
return band.length >= 3 ? { id: sec.id, ...name, outline: band, derived: true } : null;
|
|
55
|
+
}).filter((x) => x !== null);
|
|
35
56
|
}
|
|
36
57
|
function sampleBenchworkOutline(pts, segsPerArc = 20) {
|
|
37
58
|
const n = pts.length;
|
|
@@ -126,6 +147,8 @@ var DEG_FP = Math.PI / 180;
|
|
|
126
147
|
function moduleCenterline(input) {
|
|
127
148
|
const drawn = trackPath(input.mainPath);
|
|
128
149
|
if (drawn) return samplePath(drawn);
|
|
150
|
+
const chained = sectionedCenterline(input);
|
|
151
|
+
if (chained.length >= 2) return chained;
|
|
129
152
|
if (!input.geometryType) return [];
|
|
130
153
|
const L = input.lengthInches > 0 ? input.lengthInches : 24;
|
|
131
154
|
const gt = input.geometryType;
|
|
@@ -143,6 +166,73 @@ function moduleCenterline(input) {
|
|
|
143
166
|
}
|
|
144
167
|
return pts;
|
|
145
168
|
}
|
|
169
|
+
function sectionCenterlineLocal(sec) {
|
|
170
|
+
const L = typeof sec.lengthInches === "number" && sec.lengthInches > 0 ? sec.lengthInches : 0;
|
|
171
|
+
const gt = sec.geometryType || "straight";
|
|
172
|
+
if (L <= 0) return { points: [{ x: 0, y: 0 }], endX: 0, endY: 0, endHeadingDeg: 0 };
|
|
173
|
+
if (gt === "offset") {
|
|
174
|
+
const dy = sec.geometryOffsetInches ?? 0;
|
|
175
|
+
return { points: [{ x: 0, y: 0 }, { x: L, y: dy }], endX: L, endY: dy, endHeadingDeg: 0 };
|
|
176
|
+
}
|
|
177
|
+
const turn = gt === "corner_45" ? 45 : gt === "corner_90" ? 90 : gt === "curve" ? sec.geometryDegrees ?? 0 : 0;
|
|
178
|
+
if (turn === 0) return { points: [{ x: 0, y: 0 }, { x: L, y: 0 }], endX: L, endY: 0, endHeadingDeg: 0 };
|
|
179
|
+
const t = turn * DEG_FP;
|
|
180
|
+
const r = L / t;
|
|
181
|
+
const steps = 12;
|
|
182
|
+
const points = [];
|
|
183
|
+
for (let i = 0; i <= steps; i++) {
|
|
184
|
+
const a = t * i / steps;
|
|
185
|
+
points.push({ x: r * Math.sin(a), y: r * (1 - Math.cos(a)) });
|
|
186
|
+
}
|
|
187
|
+
const last = points[points.length - 1];
|
|
188
|
+
return { points, endX: last.x, endY: last.y, endHeadingDeg: turn };
|
|
189
|
+
}
|
|
190
|
+
function moduleLengthFromSections(doc) {
|
|
191
|
+
const secs = moduleSections(doc).filter(
|
|
192
|
+
(sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
|
|
193
|
+
);
|
|
194
|
+
if (!secs.length) return null;
|
|
195
|
+
return secs.reduce((a, sec) => a + sec.lengthInches, 0);
|
|
196
|
+
}
|
|
197
|
+
function sectionSpans(doc) {
|
|
198
|
+
let acc = 0;
|
|
199
|
+
const out = [];
|
|
200
|
+
for (const sec of moduleSections(doc)) {
|
|
201
|
+
const L = typeof sec.lengthInches === "number" && sec.lengthInches > 0 ? sec.lengthInches : 0;
|
|
202
|
+
if (L <= 0) continue;
|
|
203
|
+
out.push({ id: sec.id, ...sec.name ? { name: sec.name } : {}, fromPos: acc, toPos: acc + L });
|
|
204
|
+
acc += L;
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
function sectionBreaksFromSections(doc) {
|
|
209
|
+
const spans = sectionSpans(doc);
|
|
210
|
+
return spans.slice(0, -1).map((sp) => sp.toPos);
|
|
211
|
+
}
|
|
212
|
+
function sectionedCenterline(doc) {
|
|
213
|
+
const secs = moduleSections(doc).filter(
|
|
214
|
+
(sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
|
|
215
|
+
);
|
|
216
|
+
if (!secs.length) return [];
|
|
217
|
+
const out = [];
|
|
218
|
+
let ox = 0;
|
|
219
|
+
let oy = 0;
|
|
220
|
+
let heading = 0;
|
|
221
|
+
for (const sec of secs) {
|
|
222
|
+
const local = sectionCenterlineLocal(sec);
|
|
223
|
+
const c = Math.cos(heading * DEG_FP);
|
|
224
|
+
const sn = Math.sin(heading * DEG_FP);
|
|
225
|
+
for (let i = 0; i < local.points.length; i++) {
|
|
226
|
+
if (i === 0 && out.length) continue;
|
|
227
|
+
const p = local.points[i];
|
|
228
|
+
out.push({ x: ox + p.x * c - p.y * sn, y: oy + p.x * sn + p.y * c });
|
|
229
|
+
}
|
|
230
|
+
ox += local.endX * c - local.endY * sn;
|
|
231
|
+
oy += local.endX * sn + local.endY * c;
|
|
232
|
+
heading += local.endHeadingDeg;
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
146
236
|
function centerlineNormals(center) {
|
|
147
237
|
return center.map((_, i) => {
|
|
148
238
|
const a = center[Math.max(0, i - 1)];
|
|
@@ -178,6 +268,63 @@ function benchworkBand(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES
|
|
|
178
268
|
}));
|
|
179
269
|
return [...left, ...right.reverse()];
|
|
180
270
|
}
|
|
271
|
+
function sliceCenterline(center, fromPos, toPos) {
|
|
272
|
+
if (center.length < 2) return [];
|
|
273
|
+
const cum = [0];
|
|
274
|
+
for (let i = 1; i < center.length; i++)
|
|
275
|
+
cum.push(cum[i - 1] + Math.hypot(center[i].x - center[i - 1].x, center[i].y - center[i - 1].y));
|
|
276
|
+
const total = cum[cum.length - 1];
|
|
277
|
+
const a = Math.max(0, Math.min(total, Math.min(fromPos, toPos)));
|
|
278
|
+
const b = Math.max(0, Math.min(total, Math.max(fromPos, toPos)));
|
|
279
|
+
if (b - a <= 0) return [];
|
|
280
|
+
const at = (d) => {
|
|
281
|
+
for (let i = 1; i < center.length; i++) {
|
|
282
|
+
if (d <= cum[i] || i === center.length - 1) {
|
|
283
|
+
const seg = cum[i] - cum[i - 1] || 1;
|
|
284
|
+
const t = Math.max(0, Math.min(1, (d - cum[i - 1]) / seg));
|
|
285
|
+
return {
|
|
286
|
+
x: center[i - 1].x + (center[i].x - center[i - 1].x) * t,
|
|
287
|
+
y: center[i - 1].y + (center[i].y - center[i - 1].y) * t
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return center[center.length - 1];
|
|
292
|
+
};
|
|
293
|
+
const out = [at(a)];
|
|
294
|
+
for (let i = 0; i < center.length; i++) {
|
|
295
|
+
if (cum[i] > a && cum[i] < b) out.push({ x: center[i].x, y: center[i].y });
|
|
296
|
+
}
|
|
297
|
+
out.push(at(b));
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
function sectionBand(center, fromPos, toPos, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
|
|
301
|
+
const slice = sliceCenterline(center, fromPos, toPos);
|
|
302
|
+
if (slice.length < 2) return [];
|
|
303
|
+
let total = 0;
|
|
304
|
+
for (let i = 1; i < center.length; i++)
|
|
305
|
+
total += Math.hypot(center[i].x - center[i - 1].x, center[i].y - center[i - 1].y);
|
|
306
|
+
total = total || 1;
|
|
307
|
+
const lo = Math.min(fromPos, toPos);
|
|
308
|
+
let acc = 0;
|
|
309
|
+
const fr = [0];
|
|
310
|
+
for (let i = 1; i < slice.length; i++) {
|
|
311
|
+
acc += Math.hypot(slice[i].x - slice[i - 1].x, slice[i].y - slice[i - 1].y);
|
|
312
|
+
fr.push(acc);
|
|
313
|
+
}
|
|
314
|
+
const f = fr.map((d) => Math.max(0, Math.min(1, (lo + d) / total)));
|
|
315
|
+
const n = centerlineNormals(slice);
|
|
316
|
+
const half = (i) => (widthA * (1 - f[i]) + widthB * f[i]) / 2;
|
|
317
|
+
const off = (i) => offsetA * (1 - f[i]) + offsetB * f[i];
|
|
318
|
+
const left = slice.map((p, i) => ({
|
|
319
|
+
x: p.x + n[i].x * (off(i) + half(i)),
|
|
320
|
+
y: p.y + n[i].y * (off(i) + half(i))
|
|
321
|
+
}));
|
|
322
|
+
const right = slice.map((p, i) => ({
|
|
323
|
+
x: p.x + n[i].x * (off(i) - half(i)),
|
|
324
|
+
y: p.y + n[i].y * (off(i) - half(i))
|
|
325
|
+
}));
|
|
326
|
+
return [...left, ...right.reverse()];
|
|
327
|
+
}
|
|
181
328
|
function endplateFaceSegments(center, widthA = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, widthB = FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, offsetA = 0, offsetB = 0) {
|
|
182
329
|
if (center.length < 2) return [];
|
|
183
330
|
const n = centerlineNormals(center);
|
|
@@ -195,7 +342,13 @@ function moduleFootprint(input) {
|
|
|
195
342
|
const authored = benchworkOutline(input);
|
|
196
343
|
const offA = input.endplateTrackOffsets?.["A"] ?? 0;
|
|
197
344
|
const offB = input.endplateTrackOffsets?.["B"] ?? 0;
|
|
198
|
-
const sectionOutlines = sectionFootprints(input
|
|
345
|
+
const sectionOutlines = sectionFootprints(input, {
|
|
346
|
+
centerline,
|
|
347
|
+
widthA,
|
|
348
|
+
widthB,
|
|
349
|
+
offsetA: offA,
|
|
350
|
+
offsetB: offB
|
|
351
|
+
});
|
|
199
352
|
return {
|
|
200
353
|
centerline,
|
|
201
354
|
band: benchworkBand(centerline, widthA, widthB, offA, offB),
|
|
@@ -1116,6 +1269,6 @@ function poseOverridesFromDoc(doc) {
|
|
|
1116
1269
|
return out;
|
|
1117
1270
|
}
|
|
1118
1271
|
|
|
1119
|
-
export { FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionFootprints, stateToDoc, trackPath };
|
|
1272
|
+
export { FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBand, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, sliceCenterline, stateToDoc, trackPath };
|
|
1120
1273
|
//# sourceMappingURL=index.js.map
|
|
1121
1274
|
//# sourceMappingURL=index.js.map
|