@willcgage/module-schematic 0.29.0 → 0.30.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.d.cts CHANGED
@@ -281,6 +281,19 @@ interface SchematicSection {
281
281
  * the module outline. Absent = the section has no shape of its own yet (it's
282
282
  * described only by the joints in `sectionBreaks`). */
283
283
  outline?: BenchworkPoint[] | null;
284
+ /** How far this board runs along the main, inches. The module's length is the
285
+ * SUM of these — it isn't authored separately (#108). */
286
+ lengthInches?: number | null;
287
+ /** This board's own shape: straight | curve | corner_45 | corner_90 | offset |
288
+ * dead_end. Geometry belongs to the SECTION, not the module — a module like
289
+ * One Mile is 384″ of mostly-straight boards with two 24″ CURVED sections in
290
+ * the middle, which no single module-level geometry can describe (#108).
291
+ * Absent = straight. */
292
+ geometryType?: string | null;
293
+ /** Degrees turned, for curve/corner sections. */
294
+ geometryDegrees?: number | null;
295
+ /** Lateral jog, for offset sections. */
296
+ geometryOffsetInches?: number | null;
284
297
  }
285
298
  /** The authored benchwork outline, or null when a module hasn't drawn one
286
299
  * (renderers then fall back to a band derived from the endplate widths). A
@@ -390,6 +403,35 @@ interface ModuleFootprint {
390
403
  * An authored `mainPath` wins — the owner drew the real shape; otherwise the
391
404
  * centre-line is derived from the geometry fields (length + type/degrees/offset). */
392
405
  declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
406
+ /** The module's length as the SUM of its sections, or null when it has none
407
+ * (then the authored module length still speaks). */
408
+ declare function moduleLengthFromSections(doc: {
409
+ sections?: SchematicSection[] | null;
410
+ } | null | undefined): number | null;
411
+ /** Where each section starts and ends along the main, inches from endplate A.
412
+ * This is what `sectionBreaks` used to author by hand — now derived, so a
413
+ * length is just a number you type and nothing steals from its neighbour. */
414
+ declare function sectionSpans(doc: {
415
+ sections?: SchematicSection[] | null;
416
+ } | null | undefined): {
417
+ id: string;
418
+ name?: string;
419
+ fromPos: number;
420
+ toPos: number;
421
+ }[];
422
+ /** The joints implied by the sections — the interior boundaries, in inches from
423
+ * endplate A. Replaces the authored `sectionBreaks` for a sectioned module. */
424
+ declare function sectionBreaksFromSections(doc: {
425
+ sections?: SchematicSection[] | null;
426
+ } | null | undefined): number[];
427
+ /** The module's centre-line built by CHAINING its sections — each board starts
428
+ * where the previous one ended, at the heading it ended on. This is what makes
429
+ * a module like One Mile expressible: straight boards with two 24″ curved ones
430
+ * in the middle, which no single module-level geometry can describe (#108).
431
+ * Returns [] when the module has no sections with lengths. */
432
+ declare function sectionedCenterline(doc: {
433
+ sections?: SchematicSection[] | null;
434
+ } | null | undefined): BenchworkPoint[];
393
435
  /** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
394
436
  declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
395
437
  /** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
@@ -878,4 +920,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
878
920
  heading: number;
879
921
  }>;
880
922
 
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 };
923
+ 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, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, stateToDoc, trackPath };
package/dist/index.d.ts CHANGED
@@ -281,6 +281,19 @@ interface SchematicSection {
281
281
  * the module outline. Absent = the section has no shape of its own yet (it's
282
282
  * described only by the joints in `sectionBreaks`). */
283
283
  outline?: BenchworkPoint[] | null;
284
+ /** How far this board runs along the main, inches. The module's length is the
285
+ * SUM of these — it isn't authored separately (#108). */
286
+ lengthInches?: number | null;
287
+ /** This board's own shape: straight | curve | corner_45 | corner_90 | offset |
288
+ * dead_end. Geometry belongs to the SECTION, not the module — a module like
289
+ * One Mile is 384″ of mostly-straight boards with two 24″ CURVED sections in
290
+ * the middle, which no single module-level geometry can describe (#108).
291
+ * Absent = straight. */
292
+ geometryType?: string | null;
293
+ /** Degrees turned, for curve/corner sections. */
294
+ geometryDegrees?: number | null;
295
+ /** Lateral jog, for offset sections. */
296
+ geometryOffsetInches?: number | null;
284
297
  }
285
298
  /** The authored benchwork outline, or null when a module hasn't drawn one
286
299
  * (renderers then fall back to a band derived from the endplate widths). A
@@ -390,6 +403,35 @@ interface ModuleFootprint {
390
403
  * An authored `mainPath` wins — the owner drew the real shape; otherwise the
391
404
  * centre-line is derived from the geometry fields (length + type/degrees/offset). */
392
405
  declare function moduleCenterline(input: ModuleFootprintInput): BenchworkPoint[];
406
+ /** The module's length as the SUM of its sections, or null when it has none
407
+ * (then the authored module length still speaks). */
408
+ declare function moduleLengthFromSections(doc: {
409
+ sections?: SchematicSection[] | null;
410
+ } | null | undefined): number | null;
411
+ /** Where each section starts and ends along the main, inches from endplate A.
412
+ * This is what `sectionBreaks` used to author by hand — now derived, so a
413
+ * length is just a number you type and nothing steals from its neighbour. */
414
+ declare function sectionSpans(doc: {
415
+ sections?: SchematicSection[] | null;
416
+ } | null | undefined): {
417
+ id: string;
418
+ name?: string;
419
+ fromPos: number;
420
+ toPos: number;
421
+ }[];
422
+ /** The joints implied by the sections — the interior boundaries, in inches from
423
+ * endplate A. Replaces the authored `sectionBreaks` for a sectioned module. */
424
+ declare function sectionBreaksFromSections(doc: {
425
+ sections?: SchematicSection[] | null;
426
+ } | null | undefined): number[];
427
+ /** The module's centre-line built by CHAINING its sections — each board starts
428
+ * where the previous one ended, at the heading it ended on. This is what makes
429
+ * a module like One Mile expressible: straight boards with two 24″ curved ones
430
+ * in the middle, which no single module-level geometry can describe (#108).
431
+ * Returns [] when the module has no sections with lengths. */
432
+ declare function sectionedCenterline(doc: {
433
+ sections?: SchematicSection[] | null;
434
+ } | null | undefined): BenchworkPoint[];
393
435
  /** Benchwork band: the centre-line offset ±half-width, tapering widthA→widthB. */
394
436
  declare function benchworkBand(center: BenchworkPoint[], widthA?: number, widthB?: number, offsetA?: number, offsetB?: number): BenchworkPoint[];
395
437
  /** The two endplate faces (the band's flat ends): [A end at widthA, B end at widthB]. */
@@ -878,4 +920,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
878
920
  heading: number;
879
921
  }>;
880
922
 
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 };
923
+ 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, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, stateToDoc, trackPath };
package/dist/index.js CHANGED
@@ -19,10 +19,17 @@ 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
  }
@@ -126,6 +133,8 @@ var DEG_FP = Math.PI / 180;
126
133
  function moduleCenterline(input) {
127
134
  const drawn = trackPath(input.mainPath);
128
135
  if (drawn) return samplePath(drawn);
136
+ const chained = sectionedCenterline(input);
137
+ if (chained.length >= 2) return chained;
129
138
  if (!input.geometryType) return [];
130
139
  const L = input.lengthInches > 0 ? input.lengthInches : 24;
131
140
  const gt = input.geometryType;
@@ -143,6 +152,73 @@ function moduleCenterline(input) {
143
152
  }
144
153
  return pts;
145
154
  }
155
+ function sectionCenterlineLocal(sec) {
156
+ const L = typeof sec.lengthInches === "number" && sec.lengthInches > 0 ? sec.lengthInches : 0;
157
+ const gt = sec.geometryType || "straight";
158
+ if (L <= 0) return { points: [{ x: 0, y: 0 }], endX: 0, endY: 0, endHeadingDeg: 0 };
159
+ if (gt === "offset") {
160
+ const dy = sec.geometryOffsetInches ?? 0;
161
+ return { points: [{ x: 0, y: 0 }, { x: L, y: dy }], endX: L, endY: dy, endHeadingDeg: 0 };
162
+ }
163
+ const turn = gt === "corner_45" ? 45 : gt === "corner_90" ? 90 : gt === "curve" ? sec.geometryDegrees ?? 0 : 0;
164
+ if (turn === 0) return { points: [{ x: 0, y: 0 }, { x: L, y: 0 }], endX: L, endY: 0, endHeadingDeg: 0 };
165
+ const t = turn * DEG_FP;
166
+ const r = L / t;
167
+ const steps = 12;
168
+ const points = [];
169
+ for (let i = 0; i <= steps; i++) {
170
+ const a = t * i / steps;
171
+ points.push({ x: r * Math.sin(a), y: r * (1 - Math.cos(a)) });
172
+ }
173
+ const last = points[points.length - 1];
174
+ return { points, endX: last.x, endY: last.y, endHeadingDeg: turn };
175
+ }
176
+ function moduleLengthFromSections(doc) {
177
+ const secs = moduleSections(doc).filter(
178
+ (sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
179
+ );
180
+ if (!secs.length) return null;
181
+ return secs.reduce((a, sec) => a + sec.lengthInches, 0);
182
+ }
183
+ function sectionSpans(doc) {
184
+ let acc = 0;
185
+ const out = [];
186
+ for (const sec of moduleSections(doc)) {
187
+ const L = typeof sec.lengthInches === "number" && sec.lengthInches > 0 ? sec.lengthInches : 0;
188
+ if (L <= 0) continue;
189
+ out.push({ id: sec.id, ...sec.name ? { name: sec.name } : {}, fromPos: acc, toPos: acc + L });
190
+ acc += L;
191
+ }
192
+ return out;
193
+ }
194
+ function sectionBreaksFromSections(doc) {
195
+ const spans = sectionSpans(doc);
196
+ return spans.slice(0, -1).map((sp) => sp.toPos);
197
+ }
198
+ function sectionedCenterline(doc) {
199
+ const secs = moduleSections(doc).filter(
200
+ (sec) => typeof sec.lengthInches === "number" && sec.lengthInches > 0
201
+ );
202
+ if (!secs.length) return [];
203
+ const out = [];
204
+ let ox = 0;
205
+ let oy = 0;
206
+ let heading = 0;
207
+ for (const sec of secs) {
208
+ const local = sectionCenterlineLocal(sec);
209
+ const c = Math.cos(heading * DEG_FP);
210
+ const sn = Math.sin(heading * DEG_FP);
211
+ for (let i = 0; i < local.points.length; i++) {
212
+ if (i === 0 && out.length) continue;
213
+ const p = local.points[i];
214
+ out.push({ x: ox + p.x * c - p.y * sn, y: oy + p.x * sn + p.y * c });
215
+ }
216
+ ox += local.endX * c - local.endY * sn;
217
+ oy += local.endX * sn + local.endY * c;
218
+ heading += local.endHeadingDeg;
219
+ }
220
+ return out;
221
+ }
146
222
  function centerlineNormals(center) {
147
223
  return center.map((_, i) => {
148
224
  const a = center[Math.max(0, i - 1)];
@@ -1116,6 +1192,6 @@ function poseOverridesFromDoc(doc) {
1116
1192
  return out;
1117
1193
  }
1118
1194
 
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 };
1195
+ 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, sectionBreaksFromSections, sectionFootprints, sectionSpans, sectionedCenterline, stateToDoc, trackPath };
1120
1196
  //# sourceMappingURL=index.js.map
1121
1197
  //# sourceMappingURL=index.js.map