@willcgage/module-schematic 0.47.0 → 0.48.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
@@ -1042,6 +1042,31 @@ interface EndplatePose {
1042
1042
  /** True when the pose was hand-entered, not derived (wye/loop/other). */
1043
1043
  manual?: boolean;
1044
1044
  }
1045
+ type ReturnLoopShape = "circle" | "teardrop" | "offset-teardrop" | "square";
1046
+ /** A computed return-loop: a mainline LEAD to the throat, a WYE that splits into
1047
+ * two legs, and the LOOP track those legs form, closing back at the throat. Pure
1048
+ * geometry, module-local inches, endplate A at the origin, lead heading +x. All
1049
+ * shapes are built so the loop CLOSES exactly at the throat and the two wye legs
1050
+ * diverge symmetrically about the lead — the piece the earlier eyeballed versions
1051
+ * kept getting wrong (#loop). */
1052
+ interface ReturnLoopGeometry {
1053
+ throat: BenchworkPoint;
1054
+ /** The loop track, throat → around → throat (starts and ends at the throat). */
1055
+ loop: BenchworkPoint[];
1056
+ /** The two diverging wye legs at the throat (each throat → its tangent point). */
1057
+ wyeLegs: [BenchworkPoint[], BenchworkPoint[]];
1058
+ /** Half the angle between the two wye legs, degrees (0 = tangent/no wye). */
1059
+ wyeHalfAngleDeg: number;
1060
+ /** Benchwork fascia: outer boundary, and an inner boundary for a donut (empty
1061
+ * when the board is solid). */
1062
+ outlineOuter: BenchworkPoint[];
1063
+ outlineInner: BenchworkPoint[];
1064
+ }
1065
+ declare function returnLoop(shape: ReturnLoopShape, opts: {
1066
+ leadInches: number;
1067
+ radius: number;
1068
+ boardHalfWidth?: number;
1069
+ }): ReturnLoopGeometry;
1045
1070
  interface ModuleGeometryInput {
1046
1071
  lengthInches: number;
1047
1072
  geometryType?: string | null;
@@ -1141,4 +1166,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1141
1166
  heading: number;
1142
1167
  }>;
1143
1168
 
1144
- export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, 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 SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPath };
1169
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, 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 ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPath };
package/dist/index.d.ts CHANGED
@@ -1042,6 +1042,31 @@ interface EndplatePose {
1042
1042
  /** True when the pose was hand-entered, not derived (wye/loop/other). */
1043
1043
  manual?: boolean;
1044
1044
  }
1045
+ type ReturnLoopShape = "circle" | "teardrop" | "offset-teardrop" | "square";
1046
+ /** A computed return-loop: a mainline LEAD to the throat, a WYE that splits into
1047
+ * two legs, and the LOOP track those legs form, closing back at the throat. Pure
1048
+ * geometry, module-local inches, endplate A at the origin, lead heading +x. All
1049
+ * shapes are built so the loop CLOSES exactly at the throat and the two wye legs
1050
+ * diverge symmetrically about the lead — the piece the earlier eyeballed versions
1051
+ * kept getting wrong (#loop). */
1052
+ interface ReturnLoopGeometry {
1053
+ throat: BenchworkPoint;
1054
+ /** The loop track, throat → around → throat (starts and ends at the throat). */
1055
+ loop: BenchworkPoint[];
1056
+ /** The two diverging wye legs at the throat (each throat → its tangent point). */
1057
+ wyeLegs: [BenchworkPoint[], BenchworkPoint[]];
1058
+ /** Half the angle between the two wye legs, degrees (0 = tangent/no wye). */
1059
+ wyeHalfAngleDeg: number;
1060
+ /** Benchwork fascia: outer boundary, and an inner boundary for a donut (empty
1061
+ * when the board is solid). */
1062
+ outlineOuter: BenchworkPoint[];
1063
+ outlineInner: BenchworkPoint[];
1064
+ }
1065
+ declare function returnLoop(shape: ReturnLoopShape, opts: {
1066
+ leadInches: number;
1067
+ radius: number;
1068
+ boardHalfWidth?: number;
1069
+ }): ReturnLoopGeometry;
1045
1070
  interface ModuleGeometryInput {
1046
1071
  lengthInches: number;
1047
1072
  geometryType?: string | null;
@@ -1141,4 +1166,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
1141
1166
  heading: number;
1142
1167
  }>;
1143
1168
 
1144
- export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, 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 SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPath };
1169
+ export { type BenchworkPoint, type BranchConnector, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, 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 ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionFootprint, type SectionRelativePos, type SignalFacing, type SignalSide, type TrackConfig, type TrackRole, type TurnoutKind, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPath };
package/dist/index.js CHANGED
@@ -1320,6 +1320,83 @@ function moduleFeatures(doc) {
1320
1320
  laneMax: Math.max(...allLanes)
1321
1321
  };
1322
1322
  }
1323
+ function returnLoop(shape, opts) {
1324
+ const L = Math.max(1, opts.leadInches);
1325
+ const R = Math.max(1, opts.radius);
1326
+ const hw = Math.min(opts.boardHalfWidth ?? 6, R - 1);
1327
+ const T = { x: L, y: 0 };
1328
+ const r2 = (v) => Math.round(v * 100) / 100;
1329
+ const rd = (pts) => pts.map((p) => ({ x: r2(p.x), y: r2(p.y) }));
1330
+ const arc = (cx, cy, r, a0, a12, steps) => {
1331
+ const out = [];
1332
+ for (let i = 0; i <= steps; i++) out.push({ x: cx + r * Math.cos(a0 + (a12 - a0) * i / steps), y: cy + r * Math.sin(a0 + (a12 - a0) * i / steps) });
1333
+ return out;
1334
+ };
1335
+ if (shape === "square") {
1336
+ const s = 2 * R;
1337
+ const loop2 = [
1338
+ { x: L, y: 0 },
1339
+ { x: L, y: R },
1340
+ { x: L + s, y: R },
1341
+ { x: L + s, y: -R },
1342
+ { x: L, y: -R },
1343
+ { x: L, y: 0 }
1344
+ ];
1345
+ return {
1346
+ throat: T,
1347
+ loop: rd(loop2),
1348
+ wyeLegs: [rd([T, { x: L, y: R }]), rd([T, { x: L, y: -R }])],
1349
+ wyeHalfAngleDeg: 90,
1350
+ outlineOuter: rd([
1351
+ { x: 0, y: hw },
1352
+ { x: L + s + hw, y: hw },
1353
+ { x: L + s + hw, y: -R - hw },
1354
+ { x: 0, y: -R - hw }
1355
+ ]),
1356
+ outlineInner: rd([
1357
+ { x: L + hw, y: R - hw },
1358
+ { x: L + s - hw, y: R - hw },
1359
+ { x: L + s - hw, y: -R + hw },
1360
+ { x: L + hw, y: -R + hw }
1361
+ ])
1362
+ };
1363
+ }
1364
+ const D = shape === "circle" ? R * 1.15 : R * 1.6;
1365
+ const offY = shape === "offset-teardrop" ? R * 0.7 : 0;
1366
+ const C = { x: L + D, y: offY };
1367
+ const dist = Math.hypot(C.x - T.x, C.y - T.y);
1368
+ const ac = Math.acos(Math.min(1, R / dist));
1369
+ const base = Math.atan2(T.y - C.y, T.x - C.x);
1370
+ const a1 = base + ac;
1371
+ const a2 = base - ac;
1372
+ const P1 = { x: C.x + R * Math.cos(a1), y: C.y + R * Math.sin(a1) };
1373
+ const P2 = { x: C.x + R * Math.cos(a2), y: C.y + R * Math.sin(a2) };
1374
+ const loop = [T, P1, ...arc(C.x, C.y, R, a1, a2 + 2 * Math.PI, 64).slice(1), T];
1375
+ const legDir = (P) => Math.atan2(P.y - T.y, P.x - T.x);
1376
+ const half = Math.abs((legDir(P1) - legDir(P2) + Math.PI) % (2 * Math.PI) - Math.PI) / 2;
1377
+ const ringOuter = (rr) => {
1378
+ const xTop = C.x - Math.sqrt(Math.max(0, rr * rr - (hw - C.y) ** 2));
1379
+ const xBot = C.x - Math.sqrt(Math.max(0, rr * rr - (-hw - C.y) ** 2));
1380
+ const tTop = Math.atan2(hw - C.y, xTop - C.x);
1381
+ const tBot = Math.atan2(-hw - C.y, xBot - C.x);
1382
+ return [
1383
+ { x: 0, y: hw },
1384
+ { x: xTop, y: hw },
1385
+ ...arc(C.x, C.y, rr, tTop, tBot - 2 * Math.PI, 48).slice(1, -1),
1386
+ { x: xBot, y: -hw },
1387
+ { x: 0, y: -hw }
1388
+ ];
1389
+ };
1390
+ const Ri = R - hw;
1391
+ return {
1392
+ throat: T,
1393
+ loop: rd(loop),
1394
+ wyeLegs: [rd([T, P1]), rd([T, P2])],
1395
+ wyeHalfAngleDeg: r2(half * 180 / Math.PI),
1396
+ outlineOuter: rd(ringOuter(R + hw)),
1397
+ outlineInner: Ri > 2 ? rd(arc(C.x, C.y, Ri, 0, 2 * Math.PI, 40)) : []
1398
+ };
1399
+ }
1323
1400
  function geometryTurnDegrees(geometryType, geometryDegrees) {
1324
1401
  switch (geometryType) {
1325
1402
  case "corner_45":
@@ -1491,6 +1568,6 @@ function poseOverridesFromDoc(doc) {
1491
1568
  return out;
1492
1569
  }
1493
1570
 
1494
- export { ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, 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, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPath };
1571
+ export { ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, 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, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateFaceSegments, endplateLead, endplateTrackOffsetFor, endplateTrackOffsetInches, endplateWidthInches, fromSectionRelative, geometryTurnDegrees, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, poseNeedsManual, poseOverridesFromDoc, remapPos, returnLoop, sampleBenchworkOutline, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, stateToDoc, toSectionRelative, trackMeetsEndplateIssues, trackPath };
1495
1572
  //# sourceMappingURL=index.js.map
1496
1573
  //# sourceMappingURL=index.js.map