type Vector3Tuple = readonly [number, number, number]; type MachineDifficulty = 'Beginner' | 'Intermediate' | 'Advanced'; type MechanismCategory = 'Mechanisms'; interface DossierSpec { readonly label: string; readonly value: string; readonly note?: string; } interface EngineeringFact { readonly label: string; readonly value: string; readonly context: string; } interface DossierPart { readonly id: string; readonly name: string; readonly role: string; readonly description: string; readonly parentId?: string; readonly materialHint: string; readonly defaultVisible: boolean; readonly defaultOpacity: number; readonly explodeVector: Vector3Tuple; readonly labelPosition: Vector3Tuple; readonly replacementNodeNames: readonly string[]; readonly specs?: readonly DossierSpec[]; readonly animationNotes?: readonly string[]; } interface DossierCameraPreset { readonly id: string; readonly label: string; readonly position: Vector3Tuple; readonly target: Vector3Tuple; readonly fov: number; readonly description: string; } interface DossierLabel { readonly id: string; readonly partId: string; readonly text: string; readonly position: Vector3Tuple; readonly priority: 'primary' | 'secondary' | 'detail'; } interface DossierTourStep { readonly id: string; readonly title: string; readonly body: string; readonly durationSeconds: number; readonly cameraPreset: string; readonly focusPartIds: readonly string[]; readonly animationCue?: string; } interface DossierAnimation { readonly loopSeconds: number; readonly defaultRpm: number; readonly rpmRange: readonly [number, number]; readonly stepCount: number; readonly principalMotion: string; readonly physicallyBelievableNotes: readonly string[]; readonly moduleHooks: { readonly cyclePhase: string; readonly transformChannels: readonly string[]; }; } interface DossierExplodedView { readonly defaultDistance: number; readonly maxDistance: number; readonly groups: readonly { readonly id: string; readonly name: string; readonly partIds: readonly string[]; readonly direction: Vector3Tuple; readonly order: number; readonly note: string; }[]; } interface ProceduralVisualization { readonly assemblyStyle: string; readonly primitives: readonly string[]; readonly motionReference: string; readonly approximationNotes: readonly string[]; readonly swapReadyNodes: readonly string[]; } interface AssetReplacementPlan { readonly primaryGlbPath: string; readonly nodeNamingConvention: string; readonly scaleMetersPerUnit: number; readonly pivotRequirements: readonly string[]; readonly textureSets: readonly string[]; readonly validationChecklist: readonly string[]; } interface ThumbnailStrategy { readonly strategy: string; readonly cameraPreset: string; readonly highlightedPartIds: readonly string[]; readonly background: string; readonly filenameSuggestion: string; } interface MechanismDossier { readonly machineId: string; readonly title: string; readonly category: MechanismCategory; readonly difficulty: MachineDifficulty; readonly description: string; readonly learningObjectives: readonly string[]; readonly engineeringFacts: readonly EngineeringFact[]; readonly relatedMachines: readonly string[]; readonly components: readonly DossierPart[]; readonly cameraPresets: readonly DossierCameraPreset[]; readonly explodedView: DossierExplodedView; readonly labels: readonly DossierLabel[]; readonly guidedTour: readonly DossierTourStep[]; readonly animation: DossierAnimation; readonly proceduralVisualization: ProceduralVisualization; readonly assetReplacement: AssetReplacementPlan; readonly thumbnail: ThumbnailStrategy; readonly accessibilitySummary: string; } const compactMechanismCameraPresets = [ { id: 'isometric', label: 'Isometric', position: [4.6, 3.2, 5.2], target: [0, 0.25, 0], fov: 42, description: 'Balanced engineering view showing input, output, and frame references.', }, { id: 'front', label: 'Front elevation', position: [0, 1.4, 6.2], target: [0, 0.15, 0], fov: 38, description: 'Orthographic-style front view for reading planar motion geometry.', }, { id: 'top', label: 'Top plan', position: [0, 6.5, 0.05], target: [0, 0, 0], fov: 40, description: 'Plan view for seeing pivots, clearances, and swept paths.', }, { id: 'right', label: 'Right side', position: [6, 1.5, 0], target: [0, 0.2, 0], fov: 40, description: 'Side view exposing component layering and bearing supports.', }, { id: 'exploded', label: 'Exploded reference', position: [5.4, 3.8, 6], target: [0, 0.2, 0], fov: 46, description: 'Camera preset tuned for the default exploded separation distance.', }, ] as const satisfies readonly DossierCameraPreset[]; const linearMechanismCameraPresets = [ { id: 'isometric', label: 'Isometric', position: [6.8, 3.4, 5.4], target: [0, 0.2, 0], fov: 42, description: 'Wide isometric view for long-stroke mechanisms.', }, { id: 'front', label: 'Front elevation', position: [0, 1.25, 7], target: [0, 0.1, 0], fov: 35, description: 'Primary kinematic view with translation and rotation in the same plane.', }, { id: 'top', label: 'Top plan', position: [0, 7, 0.05], target: [0, 0, 0], fov: 42, description: 'Plan view for measuring pitch, stroke, or guide alignment.', }, { id: 'left', label: 'Left side', position: [-7, 1.6, 0], target: [0, 0.15, 0], fov: 38, description: 'Side view emphasizing rail and bearing stack-up.', }, { id: 'exploded', label: 'Exploded reference', position: [7, 4, 6], target: [0, 0.2, 0], fov: 45, description: 'Wide exploded-view preset with clearance around linear guides.', }, ] as const satisfies readonly DossierCameraPreset[]; export const mechanismDossiers = [ { machineId: 'scotch-yoke', title: 'Scotch Yoke', category: 'Mechanisms', difficulty: 'Intermediate', description: 'The Scotch yoke converts continuous rotary motion into near-perfect sinusoidal linear reciprocation by driving a slotted yoke with an offset crank pin. It is compact and mechanically direct, but the sliding slot carries high side load and needs careful lubrication.', learningObjectives: [ 'Relate crank angle to slider displacement, velocity, and acceleration.', 'Identify why the yoke slot sees reversing side forces during every half turn.', 'Compare sinusoidal Scotch-yoke motion with the non-sinusoidal motion of a slider-crank linkage.', 'Recognize practical design trade-offs around wear, lubrication, and compact packaging.', ], engineeringFacts: [ { label: 'Ideal displacement law', value: 'x = r cos θ', context: 'For an ideal slot and crank pin, slider displacement is exactly sinusoidal with crank radius r and crank angle θ.', }, { label: 'Stroke length', value: '2 × crank radius', context: 'The yoke reaches its two travel limits when the crank pin is aligned horizontally with the shaft center.', }, { label: 'Common uses', value: 'Actuators, pumps, valve drives', context: 'The mechanism is used when compact, symmetric reciprocating motion is more important than minimizing sliding friction.', }, { label: 'Primary limitation', value: 'Slot wear and side loading', context: 'The crank pin rubs along the slot face, concentrating load in a small contact patch unless roller followers or hardened inserts are used.', }, ], relatedMachines: ['slider-crank', 'cam-and-follower', 'piston-pump', 'steam-engine'], components: [ { id: 'frame', name: 'Base frame', role: 'Fixed datum and bearing support', description: 'A rigid base keeps the shaft centerline, guide rails, and stroke scale aligned. In real machines this frame must resist alternating inertial loads from the reciprocating yoke.', materialHint: 'matte gunmetal casting', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.35, 0], labelPosition: [0, -0.55, 0.35], replacementNodeNames: ['Frame', 'BaseFrame', 'GroundPlate'], specs: [ { label: 'Datum', value: 'shaft axis + guide rail centerline' }, { label: 'Design concern', value: 'bearing alignment under reversing load' }, ], }, { id: 'input-shaft', name: 'Input shaft', role: 'Carries rotary input torque', description: 'The shaft rotates at the commanded RPM and supports the crank disc. Its pivot is the origin for the displacement law.', parentId: 'frame', materialHint: 'brushed steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.45, 0, 0], labelPosition: [-0.7, 0.42, 0], replacementNodeNames: ['InputShaft', 'CrankShaft', 'Shaft_Input'], animationNotes: ['Rotate about local Z at one revolution per cycle.'], }, { id: 'crank-disc', name: 'Crank disc', role: 'Offset carrier for crank pin', description: 'The disc makes the crank radius visible and gives the viewer a stable reference for the driving angle.', parentId: 'input-shaft', materialHint: 'anodized warm steel with radius mark', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.2, 0.15, 0.15], labelPosition: [-0.38, 0.95, 0.15], replacementNodeNames: ['CrankDisc', 'CrankWeb', 'DrivePlate'], specs: [{ label: 'Displayed radius', value: 'r, from shaft center to pin center' }], animationNotes: ['Shares shaft rotation and carries crank-pin world transform.'], }, { id: 'crank-pin', name: 'Crank pin / roller', role: 'Driving contact inside the yoke slot', description: 'The offset pin pushes against alternating slot faces. A roller is shown diagrammatically to communicate how contact friction can be reduced.', parentId: 'crank-disc', materialHint: 'polished bearing steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.1, 0.25, 0.35], labelPosition: [0.1, 1.25, 0.35], replacementNodeNames: ['CrankPin', 'DriveRoller', 'Pin_Roller'], specs: [ { label: 'Contact', value: 'line/roller contact with slot faces' }, { label: 'Load reversal', value: 'twice per revolution' }, ], animationNotes: ['Orbit at crank radius while remaining inside the slot window.'], }, { id: 'yoke-slider', name: 'Slotted yoke slider', role: 'Reciprocating output member', description: 'The yoke translates horizontally as the pin orbits. Its slotted opening allows vertical pin motion while constraining horizontal motion to the output stroke.', parentId: 'frame', materialHint: 'blue-black machined steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.55, 0, 0], labelPosition: [0.9, 0.38, 0.1], replacementNodeNames: ['Yoke', 'SliderYoke', 'SlottedSlider'], specs: [ { label: 'Stroke', value: '2r' }, { label: 'Output motion', value: 'sinusoidal reciprocation' }, ], animationNotes: ['Translate along local X as x = r cos θ.'], }, { id: 'slot-faces', name: 'Replaceable slot faces', role: 'Wear surfaces for the drive pin', description: 'Hardened inserts or bearing strips are called out separately because they are the highest-wear service parts in many Scotch-yoke designs.', parentId: 'yoke-slider', materialHint: 'warm brass wear strips', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.2, 0.35, 0], labelPosition: [0.35, 0.8, 0.25], replacementNodeNames: ['SlotFaces', 'WearStrips', 'YokeInserts'], }, { id: 'output-rod', name: 'Output rod', role: 'Transfers linear motion to the driven load', description: 'The rod exposes how the internal yoke motion becomes useful external reciprocating motion for a pump piston, valve, or actuator.', parentId: 'yoke-slider', materialHint: 'satin steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.85, 0, 0], labelPosition: [1.85, 0.22, 0], replacementNodeNames: ['OutputRod', 'PistonRod', 'DrivenRod'], animationNotes: ['Translate with the yoke.'], }, { id: 'linear-guides', name: 'Linear guide rails', role: 'Constrain the yoke to one degree of freedom', description: 'Twin guide rails prevent the yoke from rotating under pin side load. The procedural model keeps the rails visible even when the yoke is opaque.', parentId: 'frame', materialHint: 'dark nitrided rail steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.2, 0.3], labelPosition: [0, -0.05, 0.55], replacementNodeNames: ['GuideRails', 'LinearGuides', 'YokeRails'], }, { id: 'stroke-scale', name: 'Stroke and dead-centre scale', role: 'Educational measurement overlay', description: 'Tick marks identify mid-stroke and both dead-centre positions so the viewer can connect the animation to the displacement graph.', parentId: 'frame', materialHint: 'engraved glass overlay', defaultVisible: true, defaultOpacity: 0.78, explodeVector: [0, 0.15, 0.45], labelPosition: [0, 0.92, 0.6], replacementNodeNames: ['StrokeScale', 'DeadCentreScale', 'MeasurementOverlay'], }, ], cameraPresets: compactMechanismCameraPresets, explodedView: { defaultDistance: 0.5, maxDistance: 2.25, groups: [ { id: 'drive-train', name: 'Drive train', partIds: ['input-shaft', 'crank-disc', 'crank-pin'], direction: [-1, 0.2, 0.25], order: 1, note: 'Separates the rotating crank assembly from the yoke slot.', }, { id: 'reciprocating-member', name: 'Reciprocating member', partIds: ['yoke-slider', 'slot-faces', 'output-rod'], direction: [1, 0.08, 0], order: 2, note: 'Shows the slider, wear faces, and output rod as a serviceable subassembly.', }, { id: 'measurement-overlay', name: 'Measurement overlay', partIds: ['stroke-scale'], direction: [0, 0.35, 0.55], order: 3, note: 'Lifts educational overlays away from the mechanism for readability.', }, ], }, labels: [ { id: 'scotch-yoke-label-crank', partId: 'crank-disc', text: 'Crank radius sets half-stroke', position: [-0.45, 1.05, 0.25], priority: 'primary', }, { id: 'scotch-yoke-label-pin', partId: 'crank-pin', text: 'Pin slides vertically in the slot', position: [0.1, 1.3, 0.45], priority: 'primary', }, { id: 'scotch-yoke-label-yoke', partId: 'yoke-slider', text: 'Yoke translates sinusoidally', position: [0.9, 0.52, 0.2], priority: 'primary', }, { id: 'scotch-yoke-label-guides', partId: 'linear-guides', text: 'Guides react side load', position: [0, -0.02, 0.75], priority: 'secondary', }, { id: 'scotch-yoke-label-scale', partId: 'stroke-scale', text: 'Dead-centre markers', position: [0, 1, 0.7], priority: 'detail', }, ], guidedTour: [ { id: 'motion-law', title: 'Pure sinusoidal stroke', body: 'Watch the crank pin orbit while the yoke remains constrained by the rails. The output displacement follows the horizontal projection of the crank radius.', durationSeconds: 7, cameraPreset: 'front', focusPartIds: ['crank-disc', 'crank-pin', 'yoke-slider', 'stroke-scale'], animationCue: 'run one slow revolution with the displacement scale highlighted', }, { id: 'contact-loads', title: 'Where the wear happens', body: 'The drive pin alternates between the upper and lower slot faces. This is why practical Scotch yokes use hardened wear strips, rollers, or generous lubrication.', durationSeconds: 6, cameraPreset: 'isometric', focusPartIds: ['crank-pin', 'slot-faces', 'linear-guides'], animationCue: 'pulse contact face highlight at each load reversal', }, { id: 'service-view', title: 'Serviceable subassemblies', body: 'Exploded view separates the crank, yoke, and guide system so the component hierarchy can be inspected without losing the kinematic relationship.', durationSeconds: 6, cameraPreset: 'exploded', focusPartIds: ['input-shaft', 'crank-disc', 'yoke-slider', 'linear-guides'], animationCue: 'pause at mid-stroke and ramp exploded distance to default', }, ], animation: { loopSeconds: 4, defaultRpm: 60, rpmRange: [5, 240], stepCount: 8, principalMotion: 'Input shaft and crank disc rotate continuously; crank pin orbits at radius r; yoke, output rod, and slot faces translate along X using x = r cos θ.', physicallyBelievableNotes: [ 'Yoke displacement is exactly sinusoidal in the procedural rig.', 'Velocity is highest through mid-stroke and zero at the two dead-centre positions.', 'Contact highlight swaps between slot faces as the pin crosses the vertical centerline.', ], moduleHooks: { cyclePhase: 'theta = TAU * normalizedCycleTime', transformChannels: [ 'input-shaft.rotation.z', 'crank-disc.rotation.z', 'crank-pin.position.xy', 'yoke-slider.position.x', 'output-rod.position.x', 'slot-contact-highlight.opacity', ], }, }, proceduralVisualization: { assemblyStyle: 'Planar educational mechanism with solid machined parts, transparent slot reveal, and measurement overlays.', primitives: [ 'cylinders for shaft, pin, and output rod', 'extruded rounded boxes for yoke and frame', 'thin rails for linear guide constraints', 'engraved tick marks and dead-centre badges', ], motionReference: 'Use crank radius as the single source of truth for pin orbit and yoke stroke.', approximationNotes: [ 'The slot clearance is exaggerated so the pin path remains readable at catalogue-card scale.', 'Wear strips are color-separated for learning clarity even though they may be integral inserts in compact products.', ], swapReadyNodes: [ 'Frame', 'InputShaft', 'CrankDisc', 'CrankPin', 'SliderYoke', 'SlotFaces', 'OutputRod', 'GuideRails', 'StrokeScale', ], }, assetReplacement: { primaryGlbPath: '/assets/machines/scotch-yoke/scotch-yoke.glb', nodeNamingConvention: 'Use stable PascalCase node names matching replacementNodeNames; moving parts must keep pivots at their physical axes.', scaleMetersPerUnit: 1, pivotRequirements: [ 'InputShaft and CrankDisc pivot at shaft centerline.', 'CrankPin origin at pin center so orbit can be controlled by parent transform.', 'SliderYoke and OutputRod origins on the guide centerline for clean linear translation.', ], textureSets: ['dark-painted-frame', 'brushed-steel-drive', 'brass-wear-strip', 'engraved-overlay'], validationChecklist: [ 'At θ = 0 the yoke reaches positive dead centre.', 'At θ = π the yoke reaches negative dead centre.', 'Exploded groups separate without breaking parent/child visual readability.', 'Slot faces remain selectable independently from the yoke body.', ], }, thumbnail: { strategy: 'Render front-isometric at one-quarter stroke with the crank pin highlighted warm amber and the yoke path ghosted in blue.', cameraPreset: 'isometric', highlightedPartIds: ['crank-pin', 'yoke-slider', 'stroke-scale'], background: 'dark radial grid with subtle horizontal stroke trail', filenameSuggestion: 'scotch-yoke-card.webp', }, accessibilitySummary: 'Keyboard step mode should announce crank quadrant, yoke direction, and dead-centre events so the sinusoidal motion can be understood without relying on continuous animation.', }, { machineId: 'geneva-drive', title: 'Geneva Drive', category: 'Mechanisms', difficulty: 'Intermediate', description: 'A Geneva drive converts continuous rotation into indexed intermittent rotation. A drive pin enters a slot in the Geneva star to advance it by a fixed angle, while locking surfaces hold the output stationary during dwell.', learningObjectives: [ 'Distinguish index motion from dwell motion in an intermittent mechanism.', 'Identify the role of the locking disk in preventing output drift between indexes.', 'Understand why slot count determines output step angle.', 'Recognize acceleration spikes and design considerations at pin entry and exit.', ], engineeringFacts: [ { label: 'Output step angle', value: '360° / number of slots', context: 'The procedural core uses a four-slot star, producing a 90° output index per drive revolution.', }, { label: 'Cycle behavior', value: 'index + dwell', context: 'The output only moves while the drive pin is engaged with a slot; for the rest of the input rotation it is mechanically locked.', }, { label: 'Typical applications', value: 'Film projectors, packaging, indexing tables', context: 'Geneva mechanisms are useful when a process needs repeatable stops without electronic servo control.', }, { label: 'Design concern', value: 'Impact at engagement', context: 'Pin entry creates rapid acceleration; real designs use rounded slots, careful timing, and sometimes dampers.', }, ], relatedMachines: ['cam-and-follower', 'rack-and-pinion', 'toggle-clamp', 'manual-gearbox'], components: [ { id: 'base-frame', name: 'Base frame', role: 'Holds input and output shafts at fixed spacing', description: 'The frame establishes shaft spacing and resists intermittent torque pulses during engagement.', materialHint: 'dark cast base with machined pads', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.35, 0], labelPosition: [0, -0.45, 0.45], replacementNodeNames: ['BaseFrame', 'Frame', 'GroundPlate'], }, { id: 'drive-shaft', name: 'Drive shaft', role: 'Continuous rotary input', description: 'The drive shaft rotates steadily and carries both the drive wheel and the locking disk geometry.', parentId: 'base-frame', materialHint: 'brushed shaft steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.55, 0, 0], labelPosition: [-1.15, 0.42, 0], replacementNodeNames: ['DriveShaft', 'InputShaft'], animationNotes: ['Rotate continuously about local Z.'], }, { id: 'drive-wheel', name: 'Drive wheel', role: 'Carries the indexing pin', description: 'A rotating wheel makes the pin path obvious. The wheel itself does not mesh with the star; only the pin engages the slots.', parentId: 'drive-shaft', materialHint: 'blue anodized disk', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.35, 0.1, 0.15], labelPosition: [-0.9, 1.05, 0.2], replacementNodeNames: ['DriveWheel', 'InputDisk', 'PinCarrier'], animationNotes: ['Shares drive shaft rotation.'], }, { id: 'drive-pin', name: 'Drive pin', role: 'Indexes the Geneva star', description: 'The pin enters a star slot, transfers torque, then exits cleanly before the locking disk holds the next dwell position.', parentId: 'drive-wheel', materialHint: 'polished steel with amber highlight', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.15, 0.25, 0.35], labelPosition: [-0.25, 1.25, 0.35], replacementNodeNames: ['DrivePin', 'IndexPin'], specs: [{ label: 'Engagement', value: 'one slot per input revolution' }], animationNotes: ['Pin world position controls engagement cue and slot highlight.'], }, { id: 'locking-disk', name: 'Locking disk', role: 'Maintains output dwell', description: 'The circular locking surface fits into concave arcs on the star to prevent the output from rotating when the drive pin is not engaged.', parentId: 'drive-shaft', materialHint: 'semi-matte graphite steel', defaultVisible: true, defaultOpacity: 0.92, explodeVector: [-0.25, -0.05, 0.25], labelPosition: [-1.1, 0.1, 0.45], replacementNodeNames: ['LockingDisk', 'DwellDisk', 'StopDisk'], }, { id: 'geneva-star', name: 'Geneva star', role: 'Intermittently rotating output member', description: 'The slotted star rotates only during pin engagement. With four slots, each index advances the output shaft by one quarter turn.', parentId: 'base-frame', materialHint: 'dark steel star with bright slot bevels', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.55, 0.05, 0], labelPosition: [1.0, 0.8, 0.1], replacementNodeNames: ['GenevaStar', 'DrivenStar', 'OutputStar'], specs: [ { label: 'Slots shown', value: '4' }, { label: 'Step angle', value: '90°' }, ], animationNotes: ['Hold during dwell; rotate by one step with eased physically timed index segment.'], }, { id: 'output-shaft', name: 'Output shaft', role: 'Indexed rotary output', description: 'The output shaft is rigidly attached to the star and would drive an indexing table, conveyor stop, or film sprocket.', parentId: 'geneva-star', materialHint: 'brushed steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.8, 0, 0], labelPosition: [1.55, 0.35, 0], replacementNodeNames: ['OutputShaft', 'IndexedShaft'], animationNotes: ['Shares star rotation; reports current index number to status UI.'], }, { id: 'dwell-arcs', name: 'Dwell locking arcs', role: 'Concave locking surfaces on the star', description: 'The arcs receive the locking disk and define the stationary dwell period between indexing events.', parentId: 'geneva-star', materialHint: 'warm highlighted bevels', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.3, 0.25, 0.1], labelPosition: [1.25, 1.2, 0.25], replacementNodeNames: ['DwellArcs', 'LockingArcs', 'StarLockSurfaces'], }, { id: 'index-detent', name: 'Index detent indicator', role: 'Educational index-position marker', description: 'A non-contact overlay shows the active station number and makes dwell intervals clear when the animation is slowed down.', parentId: 'base-frame', materialHint: 'translucent blue overlay', defaultVisible: true, defaultOpacity: 0.78, explodeVector: [0, 0.35, 0.35], labelPosition: [1.2, 1.55, 0.5], replacementNodeNames: ['IndexDetent', 'StationMarker', 'IndexOverlay'], }, { id: 'safety-guard', name: 'Transparent safety guard', role: 'Contextual guard volume', description: 'A clear cover communicates that the pin/star interface is a pinch point in production machinery.', parentId: 'base-frame', materialHint: 'smoked transparent polycarbonate', defaultVisible: true, defaultOpacity: 0.28, explodeVector: [0, 0.65, 0.3], labelPosition: [0, 1.85, 0.55], replacementNodeNames: ['SafetyGuard', 'TransparentCover'], }, ], cameraPresets: compactMechanismCameraPresets, explodedView: { defaultDistance: 0.55, maxDistance: 2.35, groups: [ { id: 'input-assembly', name: 'Input pin carrier', partIds: ['drive-shaft', 'drive-wheel', 'drive-pin', 'locking-disk'], direction: [-1, 0.15, 0.25], order: 1, note: 'Moves the drive components left to expose the pin and locking surfaces.', }, { id: 'output-assembly', name: 'Indexed output', partIds: ['geneva-star', 'output-shaft', 'dwell-arcs', 'index-detent'], direction: [1, 0.15, 0], order: 2, note: 'Separates the star and index marker from the input wheel.', }, { id: 'guard', name: 'Safety guard', partIds: ['safety-guard'], direction: [0, 0.75, 0.35], order: 3, note: 'Raises the transparent guard to show the hazardous pinch region.', }, ], }, labels: [ { id: 'geneva-label-pin', partId: 'drive-pin', text: 'Pin engages one slot', position: [-0.25, 1.32, 0.45], priority: 'primary', }, { id: 'geneva-label-star', partId: 'geneva-star', text: 'Star advances 90°', position: [1, 0.92, 0.18], priority: 'primary', }, { id: 'geneva-label-lock', partId: 'locking-disk', text: 'Disk locks dwell', position: [-1.1, 0.12, 0.55], priority: 'primary', }, { id: 'geneva-label-arcs', partId: 'dwell-arcs', text: 'Concave dwell arcs', position: [1.25, 1.28, 0.32], priority: 'secondary', }, { id: 'geneva-label-guard', partId: 'safety-guard', text: 'Pinch-point guard', position: [0, 1.95, 0.65], priority: 'detail', }, ], guidedTour: [ { id: 'continuous-to-intermittent', title: 'Continuous input, intermittent output', body: 'The input shaft rotates without stopping, but the Geneva star only moves while the pin is inside a slot. The rest of the time is dwell.', durationSeconds: 8, cameraPreset: 'front', focusPartIds: ['drive-wheel', 'drive-pin', 'geneva-star', 'index-detent'], animationCue: 'play one full input revolution with dwell periods shaded', }, { id: 'locking-action', title: 'Why the output holds position', body: 'When the pin exits, the locking disk nests against the star arcs. This mechanical constraint prevents unintended rotation between index events.', durationSeconds: 6, cameraPreset: 'isometric', focusPartIds: ['locking-disk', 'dwell-arcs', 'geneva-star'], animationCue: 'pause during dwell and pulse the locking contact', }, { id: 'indexing-service-view', title: 'Indexing subassemblies', body: 'Exploded view separates the pin carrier from the star so the user can inspect entry clearance, slot shape, and the protective guard.', durationSeconds: 6, cameraPreset: 'exploded', focusPartIds: ['drive-pin', 'geneva-star', 'safety-guard'], animationCue: 'show exploded view at the moment before pin entry', }, ], animation: { loopSeconds: 5, defaultRpm: 45, rpmRange: [2, 180], stepCount: 4, principalMotion: 'Drive wheel rotates continuously. The star remains stationary during dwell, then advances by one slot when the pin traverses the active slot.', physicallyBelievableNotes: [ 'The star index event uses a smooth acceleration/deceleration envelope rather than constant angular velocity.', 'The lock highlight is active during dwell and fades during pin engagement.', 'Station number increments only after the pin exits the slot.', ], moduleHooks: { cyclePhase: 'inputTheta = TAU * normalizedCycleTime; starIndex = floor(cycleCount) mod slotCount', transformChannels: [ 'drive-shaft.rotation.z', 'drive-wheel.rotation.z', 'drive-pin.position.xy', 'geneva-star.rotation.z', 'output-shaft.rotation.z', 'locking-contact-highlight.opacity', 'index-detent.station', ], }, }, proceduralVisualization: { assemblyStyle: 'Planar indexing mechanism with exaggerated slot bevels, clear dwell overlays, and an optional transparent guard.', primitives: [ 'cylinders for shafts and pins', 'extruded star polygon with rounded slot cutouts', 'disks and arcs for locking geometry', 'transparent guard shell', 'station-number overlay planes', ], motionReference: 'Use slotCount = 4 for Phase 1 and calculate index angle as TAU / slotCount.', approximationNotes: [ 'Slot roots are rounded diagrammatically to communicate impact reduction without modeling manufacturing fillets exactly.', 'Locking disk clearances are visually enlarged to remain legible in wireframe and cross-section modes.', ], swapReadyNodes: [ 'BaseFrame', 'DriveShaft', 'DriveWheel', 'DrivePin', 'LockingDisk', 'GenevaStar', 'OutputShaft', 'DwellArcs', 'IndexDetent', 'SafetyGuard', ], }, assetReplacement: { primaryGlbPath: '/assets/machines/geneva-drive/geneva-drive.glb', nodeNamingConvention: 'Model input carrier and output star as separate named nodes; avoid merged geometry for pin, star, and locking disk.', scaleMetersPerUnit: 1, pivotRequirements: [ 'DriveWheel, LockingDisk, and DriveShaft share the input shaft origin.', 'GenevaStar and OutputShaft share the output shaft origin.', 'DrivePin origin at its cylindrical center for hover highlighting.', ], textureSets: ['dark-cast-frame', 'blue-pin-carrier', 'polished-pin', 'transparent-guard'], validationChecklist: [ 'Pin enters a slot without visual clipping at the engagement frame.', 'Star rotation advances one quarter turn per input revolution.', 'Lock highlight is visible only during dwell.', 'SafetyGuard remains transparent and selectable independently.', ], }, thumbnail: { strategy: 'Capture the instant the warm drive pin enters a blue-highlighted Geneva slot with a faint dwell arc behind the star.', cameraPreset: 'front', highlightedPartIds: ['drive-pin', 'geneva-star', 'dwell-arcs'], background: 'dark circular indexing grid with four station ticks', filenameSuggestion: 'geneva-drive-card.webp', }, accessibilitySummary: 'Step mode should expose four named station states and announce whether the mechanism is indexing or dwelling at each step.', }, { machineId: 'cam-and-follower', title: 'Cam and Follower', category: 'Mechanisms', difficulty: 'Intermediate', description: 'A cam and follower transforms rotary camshaft motion into a programmed lift profile. By changing the cam shape, engineers can create dwell, rise, fall, quick return, or symmetric reciprocating motion.', learningObjectives: [ 'Connect cam radius at the contact angle to follower lift.', 'Compare eccentric, heart, and snail cam profiles.', 'Understand the need for spring preload or positive constraint to maintain contact.', 'Identify why cam profiles are designed for acceleration and jerk, not just displacement.', ], engineeringFacts: [ { label: 'Output variable', value: 'Follower lift', context: 'Follower position is controlled by the cam surface radius at the current cam angle plus the follower geometry.', }, { label: 'Profile families', value: 'Eccentric, heart, snail', context: 'The Phase 1 procedural viewer can switch between a smooth eccentric, constant-velocity heart-like profile, and quick-drop snail profile.', }, { label: 'Common applications', value: 'Valve trains, pumps, automation', context: 'Cams provide repeatable motion programs in compact mechanical systems.', }, { label: 'Design concern', value: 'Contact stress and jerk', context: 'Abrupt lift changes can cause follower bounce, noise, and fatigue, so real profiles blend acceleration carefully.', }, ], relatedMachines: ['four-stroke-petrol-engine', 'diesel-engine', 'steam-engine', 'scotch-yoke'], components: [ { id: 'base-frame', name: 'Base frame', role: 'Supports camshaft and follower guide', description: 'The frame fixes the camshaft bearing axis relative to the follower guide so lift can be measured repeatably.', materialHint: 'dark machined fixture', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.35, 0], labelPosition: [0, -0.5, 0.35], replacementNodeNames: ['BaseFrame', 'Fixture', 'GroundPlate'], }, { id: 'camshaft', name: 'Camshaft', role: 'Rotary input', description: 'The camshaft carries torque into the cam body. In engine valve trains, camshaft timing is synchronized to crankshaft rotation.', parentId: 'base-frame', materialHint: 'brushed shaft steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.45, 0, 0], labelPosition: [-0.7, 0.35, 0], replacementNodeNames: ['Camshaft', 'InputShaft'], specs: [{ label: 'Input', value: 'continuous rotation' }], animationNotes: ['Rotate about local Z at commanded RPM.'], }, { id: 'cam-body', name: 'Cam body', role: 'Programmed lift surface', description: 'The visible profile defines follower displacement. The procedural model supports multiple profile cartridges without changing the viewer core.', parentId: 'camshaft', materialHint: 'warm case-hardened steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.2, 0.2, 0.2], labelPosition: [-0.25, 1.05, 0.25], replacementNodeNames: ['CamBody', 'CamLobe', 'ProfileCam'], specs: [ { label: 'Profiles', value: 'eccentric / heart / snail' }, { label: 'Output curve', value: 'lift vs. angle' }, ], animationNotes: ['Shares camshaft rotation; active profile drives follower lift curve.'], }, { id: 'roller-follower', name: 'Roller follower', role: 'Low-friction contact element', description: 'The roller reduces sliding friction and makes the cam/contact point easy to see. Flat-faced followers can be substituted in the asset pipeline.', parentId: 'follower-stem', materialHint: 'polished bearing steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.2, 0.3, 0.3], labelPosition: [0.25, 1.45, 0.35], replacementNodeNames: ['RollerFollower', 'FollowerRoller', 'ContactRoller'], animationNotes: ['Translate with follower; rotate visually according to surface travel.'], }, { id: 'follower-stem', name: 'Follower stem', role: 'Linear output member', description: 'The stem converts the cam surface motion into useful straight-line lift, such as opening a valve or driving a small pump plunger.', parentId: 'base-frame', materialHint: 'satin steel stem', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.45, 0.05, 0], labelPosition: [0.75, 1.8, 0.15], replacementNodeNames: ['FollowerStem', 'Pushrod', 'Follower'], specs: [{ label: 'Output', value: 'linear lift' }], animationNotes: ['Translate along local Y according to active lift curve.'], }, { id: 'return-spring', name: 'Return spring', role: 'Maintains cam contact', description: 'Spring preload keeps the follower against the cam surface during the falling portion of the lift curve.', parentId: 'follower-stem', materialHint: 'blue spring steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.65, 0, 0], labelPosition: [1.05, 1.35, 0], replacementNodeNames: ['ReturnSpring', 'FollowerSpring', 'ValveSpring'], animationNotes: ['Compress and extend inversely with follower lift.'], }, { id: 'guide-bushing', name: 'Guide bushing', role: 'Constrains follower to linear motion', description: 'The guide prevents follower side load from cocking the stem and concentrates wear in a replaceable bearing surface.', parentId: 'base-frame', materialHint: 'bronze bushing', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.25, -0.05, 0.25], labelPosition: [0.45, 0.85, 0.35], replacementNodeNames: ['GuideBushing', 'FollowerGuide', 'StemGuide'], }, { id: 'lift-scale', name: 'Lift scale', role: 'Measurement overlay', description: 'A vertical scale and live marker make the displacement profile visible even when viewing the cam from an angle.', parentId: 'base-frame', materialHint: 'transparent glass with blue ticks', defaultVisible: true, defaultOpacity: 0.78, explodeVector: [0, 0.25, 0.45], labelPosition: [1.25, 1.75, 0.55], replacementNodeNames: ['LiftScale', 'MeasurementScale'], }, { id: 'profile-cartridge', name: 'Profile selector cartridge', role: 'Shows alternate cam profiles', description: 'A small carousel displays the available profiles so users can switch lift behavior without leaving the machine.', parentId: 'base-frame', materialHint: 'transparent selector with profile chips', defaultVisible: true, defaultOpacity: 0.82, explodeVector: [0, 0.5, 0.15], labelPosition: [-1.2, 1.7, 0.2], replacementNodeNames: ['ProfileCartridge', 'ProfileSelector', 'CamProfiles'], }, { id: 'contact-point', name: 'Live contact point', role: 'Educational force/contact marker', description: 'A warm marker follows the theoretical cam/follower contact position and reveals where load transfers between the two bodies.', parentId: 'cam-body', materialHint: 'emissive amber marker', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.35, 0.35], labelPosition: [0.05, 1.18, 0.45], replacementNodeNames: ['ContactPoint', 'ContactMarker'], animationNotes: ['Move around cam surface according to active profile and cam angle.'], }, ], cameraPresets: compactMechanismCameraPresets, explodedView: { defaultDistance: 0.52, maxDistance: 2.2, groups: [ { id: 'cam-input', name: 'Cam input', partIds: ['camshaft', 'cam-body', 'contact-point'], direction: [-1, 0.18, 0.25], order: 1, note: 'Separates the profile surface from the follower stack.', }, { id: 'follower-stack', name: 'Follower stack', partIds: ['roller-follower', 'follower-stem', 'return-spring', 'guide-bushing'], direction: [0.8, 0.25, 0], order: 2, note: 'Moves the guided output elements aside for inspection.', }, { id: 'educational-overlays', name: 'Profile and lift overlays', partIds: ['lift-scale', 'profile-cartridge'], direction: [0, 0.55, 0.45], order: 3, note: 'Raises overlays above the mechanical contact region.', }, ], }, labels: [ { id: 'cam-label-profile', partId: 'cam-body', text: 'Cam profile programs lift', position: [-0.25, 1.12, 0.35], priority: 'primary', }, { id: 'cam-label-contact', partId: 'contact-point', text: 'Live contact point', position: [0.05, 1.25, 0.55], priority: 'primary', }, { id: 'cam-label-follower', partId: 'follower-stem', text: 'Follower moves linearly', position: [0.75, 1.88, 0.25], priority: 'primary', }, { id: 'cam-label-spring', partId: 'return-spring', text: 'Spring keeps contact', position: [1.05, 1.45, 0.1], priority: 'secondary', }, { id: 'cam-label-scale', partId: 'lift-scale', text: 'Lift scale', position: [1.25, 1.85, 0.65], priority: 'detail', }, ], guidedTour: [ { id: 'profile-to-lift', title: 'Profile becomes motion', body: 'As the cam rotates, the follower height follows the cam radius under the roller. The lift scale traces the resulting motion curve.', durationSeconds: 8, cameraPreset: 'front', focusPartIds: ['cam-body', 'roller-follower', 'follower-stem', 'lift-scale'], animationCue: 'play one slow cam revolution with live lift marker enabled', }, { id: 'profile-comparison', title: 'Changing the motion program', body: 'Switching between eccentric, heart, and snail profiles changes dwell, rise, and fall behavior without changing the follower guide.', durationSeconds: 7, cameraPreset: 'isometric', focusPartIds: ['profile-cartridge', 'cam-body', 'lift-scale'], animationCue: 'cycle profile selector and overlay each lift curve', }, { id: 'contact-control', title: 'Maintaining contact', body: 'The return spring compresses as lift rises and extends as the cam falls, maintaining contact so the follower does not bounce away.', durationSeconds: 6, cameraPreset: 'right', focusPartIds: ['return-spring', 'roller-follower', 'contact-point'], animationCue: 'highlight contact marker and spring compression during the falling flank', }, ], animation: { loopSeconds: 4.5, defaultRpm: 50, rpmRange: [5, 300], stepCount: 12, principalMotion: 'Camshaft and cam body rotate continuously; follower stem translates along the guide according to the selected lift curve; spring scale changes inversely with lift.', physicallyBelievableNotes: [ 'Lift curves are periodic and loop without discontinuity for eccentric and heart profiles.', 'The snail profile includes an intentionally sharp but smoothed return to demonstrate quick-drop behavior without numerical snapping.', 'Contact marker remains on the loaded side of the follower roller.', ], moduleHooks: { cyclePhase: 'theta = TAU * normalizedCycleTime; lift = activeProfile(theta)', transformChannels: [ 'camshaft.rotation.z', 'cam-body.rotation.z', 'follower-stem.position.y', 'roller-follower.position.y', 'return-spring.scale.y', 'contact-point.position.xy', 'lift-scale.markerPosition', 'profile-cartridge.activeProfile', ], }, }, proceduralVisualization: { assemblyStyle: 'Educational cam rig with switchable profile chips, transparent lift scale, and live contact marker.', primitives: [ 'lathed/extruded cam profile mesh', 'cylinders for shaft and roller', 'helix or repeated torus segments for spring', 'transparent planes for lift and profile overlays', 'rounded boxes for frame and guide', ], motionReference: 'Treat the cam profile as a radial function sampled by animation and mesh generation so geometry and follower lift remain synchronized.', approximationNotes: [ 'The heart profile is diagrammatic and prioritizes constant-return visualization over exact conjugate cam synthesis.', 'Spring coils are simplified for performance and may be replaced with a skinned or morph-target asset.', ], swapReadyNodes: [ 'BaseFrame', 'Camshaft', 'CamBody', 'RollerFollower', 'FollowerStem', 'ReturnSpring', 'GuideBushing', 'LiftScale', 'ProfileCartridge', 'ContactPoint', ], }, assetReplacement: { primaryGlbPath: '/assets/machines/cam-and-follower/cam-and-follower.glb', nodeNamingConvention: 'Keep each selectable profile and follower element as a named node; alternate cam profiles may be meshes under ProfileCartridge.', scaleMetersPerUnit: 1, pivotRequirements: [ 'CamBody origin at camshaft center.', 'RollerFollower origin at roller center.', 'FollowerStem origin aligned to guide axis so Y translation controls lift.', 'ReturnSpring origin at lower seat with scalable Y axis.', ], textureSets: ['case-hardened-cam', 'polished-roller', 'bronze-bushing', 'transparent-measurement'], validationChecklist: [ 'Follower never visually penetrates the cam profile.', 'Lift scale marker reaches zero at base-circle dwell.', 'Profile selector changes both mesh visibility and lift curve.', 'Cross-section mode leaves contact marker visible.', ], }, thumbnail: { strategy: 'Show the cam at high lift with the follower raised, amber contact marker active, and the lift scale glowing in blue.', cameraPreset: 'isometric', highlightedPartIds: ['cam-body', 'roller-follower', 'lift-scale'], background: 'dark grid with faint lift-curve sparkline', filenameSuggestion: 'cam-and-follower-card.webp', }, accessibilitySummary: 'Profile selector controls should expose text descriptions of lift behavior, and step mode should announce rise, dwell, fall, and return phases.', }, { machineId: 'rack-and-pinion', title: 'Rack and Pinion', category: 'Mechanisms', difficulty: 'Beginner', description: 'A rack and pinion converts rotation into linear motion by meshing a round gear with a straight toothed bar. It is one of the clearest examples of pitch-circle motion becoming straight-line travel.', learningObjectives: [ 'Relate pinion rotation angle to rack displacement through pitch radius.', 'Identify pitch line, tooth contact, backlash, and guide constraints.', 'Understand reversible operation: rotary-to-linear or linear-to-rotary.', 'Recognize why steering racks, actuators, and CNC axes use this mechanism.', ], engineeringFacts: [ { label: 'Displacement law', value: 's = r × θ', context: 'Rack travel equals pinion pitch radius times angular rotation when there is no slip at the pitch line.', }, { label: 'Transmission type', value: 'Reversible positive drive', context: 'The mechanism can convert motor rotation to linear travel or external linear force back into rotation.', }, { label: 'Key tolerance', value: 'Backlash', context: 'Clearance between teeth prevents binding but reduces positioning precision and can cause rattle.', }, { label: 'Common applications', value: 'Steering, linear axes, gates', context: 'Rack and pinion drives are compact, durable, and easy to inspect compared with hidden belt or screw drives.', }, ], relatedMachines: ['worm-gear-drive', 'bevel-gear-set', 'toggle-clamp', 'hydraulic-cylinder'], components: [ { id: 'base-rail', name: 'Base rail', role: 'Fixed machine datum', description: 'The base rail supports the rack guide and bearing blocks while keeping the pitch line straight.', materialHint: 'matte black structural rail', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.35, 0], labelPosition: [0, -0.48, 0.35], replacementNodeNames: ['BaseRail', 'FrameRail', 'GroundRail'], }, { id: 'rack', name: 'Toothed rack', role: 'Linear gear member', description: 'The rack is a gear unwrapped into a straight line. Its tooth pitch matches the pinion pitch so the two roll without slip at the pitch line.', parentId: 'linear-guide', materialHint: 'hardened steel teeth with blue body', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.25, 0], labelPosition: [0, 0.75, 0.2], replacementNodeNames: ['Rack', 'ToothedRack', 'LinearGear'], specs: [ { label: 'Travel law', value: 's = rθ' }, { label: 'Motion', value: 'linear translation' }, ], animationNotes: ['Translate along local X based on pinion pitch radius and angle.'], }, { id: 'pinion', name: 'Pinion gear', role: 'Rotary gear member', description: 'The pinion converts shaft torque into tooth force at the rack. Its pitch radius determines the travel per revolution.', parentId: 'input-shaft', materialHint: 'warm steel gear with blue pitch circle', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.35, 0.25], labelPosition: [0, 1.55, 0.35], replacementNodeNames: ['Pinion', 'PinionGear', 'DriveGear'], specs: [ { label: 'Travel per rev', value: '2πr' }, { label: 'Contact', value: 'involute tooth mesh' }, ], animationNotes: ['Rotate about local Z; rack translation follows pitch-circle arc length.'], }, { id: 'input-shaft', name: 'Input shaft', role: 'Carries pinion torque', description: 'The shaft can be driven by a handwheel, motor, or steering column. Bearings hold it at the correct mesh depth.', parentId: 'bearings', materialHint: 'brushed steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.15, 0.5], labelPosition: [0, 1.15, 0.85], replacementNodeNames: ['InputShaft', 'PinionShaft'], animationNotes: ['Shares pinion rotation.'], }, { id: 'bearings', name: 'Bearing blocks', role: 'Locate pinion shaft', description: 'Bearing blocks maintain center distance between the pinion and rack so teeth share load properly.', parentId: 'base-rail', materialHint: 'dark bearing housings', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.15, 0.35], labelPosition: [-0.85, 1.05, 0.55], replacementNodeNames: ['BearingBlocks', 'PinionBearings'], }, { id: 'backlash-adjuster', name: 'Backlash adjuster', role: 'Controls tooth clearance', description: 'A spring-loaded or eccentric adjuster can bias the rack and pinion together to reduce lost motion while avoiding binding.', parentId: 'base-rail', materialHint: 'amber adjustment screw', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.25, 0.25, 0.45], labelPosition: [1.15, 1.0, 0.65], replacementNodeNames: ['BacklashAdjuster', 'MeshAdjuster', 'PreloadScrew'], }, { id: 'travel-stops', name: 'Travel stops', role: 'Limit rack stroke', description: 'Stops prevent the rack from running out of tooth engagement or striking downstream structure.', parentId: 'base-rail', materialHint: 'rubber-capped steel stops', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.15, 0.25], labelPosition: [2.9, 0.55, 0.35], replacementNodeNames: ['TravelStops', 'EndStops', 'LimitStops'], }, { id: 'linear-guide', name: 'Linear guide', role: 'Constrains rack to straight travel', description: 'The guide takes side load from tooth pressure and prevents rack rotation or lift-off.', parentId: 'base-rail', materialHint: 'dark precision guide rail', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.1, 0.25], labelPosition: [0, 0.05, 0.55], replacementNodeNames: ['LinearGuide', 'RackGuide', 'SlideRail'], }, { id: 'pitch-line', name: 'Pitch-line overlay', role: 'Educational rolling reference', description: 'A glowing line and pitch circle make the no-slip relationship visible between pinion circumference and rack travel.', parentId: 'base-rail', materialHint: 'transparent blue overlay', defaultVisible: true, defaultOpacity: 0.82, explodeVector: [0, 0.45, 0.2], labelPosition: [0.5, 1.25, 0.45], replacementNodeNames: ['PitchLine', 'PitchOverlay', 'PitchCircle'], animationNotes: ['Scroll tick marks with rack displacement.'], }, { id: 'force-arrows', name: 'Force arrows', role: 'Shows tooth force and output thrust', description: 'Vector overlays show tangential force at the pinion and equal/opposite thrust along the rack.', parentId: 'base-rail', materialHint: 'emissive amber and blue arrows', defaultVisible: true, defaultOpacity: 0.86, explodeVector: [0, 0.55, 0.35], labelPosition: [-1.2, 1.65, 0.55], replacementNodeNames: ['ForceArrows', 'ThrustArrows'], }, ], cameraPresets: linearMechanismCameraPresets, explodedView: { defaultDistance: 0.58, maxDistance: 2.5, groups: [ { id: 'linear-axis', name: 'Linear axis', partIds: ['rack', 'linear-guide', 'travel-stops'], direction: [0, 0.28, 0], order: 1, note: 'Lifts the rack axis slightly to reveal the guide and stop relationship.', }, { id: 'pinion-support', name: 'Pinion support', partIds: ['pinion', 'input-shaft', 'bearings', 'backlash-adjuster'], direction: [0, 0.3, 0.55], order: 2, note: 'Separates the rotary support stack from the rack teeth.', }, { id: 'learning-overlays', name: 'Learning overlays', partIds: ['pitch-line', 'force-arrows'], direction: [0, 0.65, 0.4], order: 3, note: 'Moves pitch and force overlays above the physical parts.', }, ], }, labels: [ { id: 'rack-label-rack', partId: 'rack', text: 'Rack travels linearly', position: [0, 0.85, 0.3], priority: 'primary', }, { id: 'rack-label-pinion', partId: 'pinion', text: 'Pinion pitch radius sets travel', position: [0, 1.65, 0.45], priority: 'primary', }, { id: 'rack-label-pitch', partId: 'pitch-line', text: 'No-slip pitch line', position: [0.5, 1.35, 0.55], priority: 'primary', }, { id: 'rack-label-backlash', partId: 'backlash-adjuster', text: 'Backlash adjuster', position: [1.15, 1.1, 0.75], priority: 'secondary', }, { id: 'rack-label-stops', partId: 'travel-stops', text: 'Stroke limits', position: [2.9, 0.65, 0.45], priority: 'detail', }, ], guidedTour: [ { id: 'rolling-without-slip', title: 'Rolling distance becomes travel', body: 'One pinion revolution moves the rack by the pinion pitch circumference. The pitch overlay makes this arc-length relationship visible.', durationSeconds: 7, cameraPreset: 'front', focusPartIds: ['pinion', 'rack', 'pitch-line'], animationCue: 'play exactly one pinion revolution and draw matching rack travel', }, { id: 'tooth-force', title: 'Tooth force and reaction', body: 'Tangential force at the pinion becomes thrust along the rack, while the guide rail reacts separating and side loads.', durationSeconds: 6, cameraPreset: 'isometric', focusPartIds: ['force-arrows', 'linear-guide', 'bearings'], animationCue: 'pulse force vectors during loaded half revolution', }, { id: 'precision-concerns', title: 'Backlash and travel limits', body: 'Clearance is required for free running, but too much backlash causes lost motion. End stops protect the teeth at travel limits.', durationSeconds: 6, cameraPreset: 'top', focusPartIds: ['backlash-adjuster', 'travel-stops', 'rack'], animationCue: 'pause near direction reversal and show backlash gap overlay', }, ], animation: { loopSeconds: 5, defaultRpm: 35, rpmRange: [2, 220], stepCount: 10, principalMotion: 'Pinion and input shaft rotate; rack translates along X using s = pitchRadius × θ with wraparound for the looping demo; pitch ticks scroll at the same rate.', physicallyBelievableNotes: [ 'Rack and pitch-line movement remain phase-locked to pinion angle.', 'Direction reversal includes a small optional backlash dead-band overlay but no actual desynchronization in default educational mode.', 'Travel stops are approached but not penetrated during the loop.', ], moduleHooks: { cyclePhase: 'theta = TAU * normalizedCycleTime; rackX = pitchRadius * theta mapped into stroke window', transformChannels: [ 'pinion.rotation.z', 'input-shaft.rotation.z', 'rack.position.x', 'pitch-line.tickOffset', 'force-arrows.opacity', 'backlash-adjuster.clearanceIndicator', ], }, }, proceduralVisualization: { assemblyStyle: 'Long linear-axis demonstration with simplified involute-like teeth, pitch overlays, and force vectors.', primitives: [ 'repeated tooth prisms or merged rack mesh', 'gear mesh with visible pitch circle', 'cylinders for shaft and bearings', 'transparent line overlays for pitch and force', 'rounded blocks for stops and adjusters', ], motionReference: 'Use pinion pitchRadius as the source of truth for rack displacement and pitch-circle overlay circumference.', approximationNotes: [ 'Tooth involutes are simplified to performance-friendly trapezoidal teeth in the procedural fallback.', 'Rack travel loops by resetting at the ends for catalogue animation; professional assets can use longer rails or end-stop reversal.', ], swapReadyNodes: [ 'BaseRail', 'Rack', 'Pinion', 'InputShaft', 'BearingBlocks', 'BacklashAdjuster', 'TravelStops', 'LinearGuide', 'PitchLine', 'ForceArrows', ], }, assetReplacement: { primaryGlbPath: '/assets/machines/rack-and-pinion/rack-and-pinion.glb', nodeNamingConvention: 'Separate rack, pinion, guide, overlays, and adjustment hardware; teeth may be merged within Rack and Pinion nodes.', scaleMetersPerUnit: 1, pivotRequirements: [ 'Pinion origin at pitch circle center on shaft axis.', 'Rack origin on pitch line so X translation aligns with tooth mesh.', 'PitchLine overlay origin matched to pinion center and rack pitch line.', ], textureSets: ['hardened-teeth', 'dark-linear-rail', 'amber-adjuster', 'blue-educational-overlay'], validationChecklist: [ 'Rack tooth pitch visually matches pinion tooth pitch.', 'One displayed pinion revolution equals one pitch circumference of rack travel.', 'BacklashAdjuster remains selectable without blocking pinion hover.', 'Wireframe mode preserves pitch overlay contrast.', ], }, thumbnail: { strategy: 'Frame the pinion centered over the rack with blue pitch line and amber force arrow, using a wide crop that hints at linear travel.', cameraPreset: 'isometric', highlightedPartIds: ['pinion', 'rack', 'pitch-line'], background: 'dark horizontal engineering grid with travel ticks', filenameSuggestion: 'rack-and-pinion-card.webp', }, accessibilitySummary: 'Keyboard controls should allow one-tooth or one-quarter-turn stepping and announce equivalent rack travel using tabular numeric units.', }, { machineId: 'slider-crank', title: 'Slider Crank', category: 'Mechanisms', difficulty: 'Beginner', description: 'The slider-crank mechanism converts rotary motion to reciprocating linear motion through a crank, connecting rod, and guided slider. It is the core geometry behind piston engines, compressors, pumps, and many presses.', learningObjectives: [ 'Identify crank, connecting rod, wrist pin, and guided slider roles.', 'Understand top dead centre and bottom dead centre positions.', 'Compare slider-crank displacement with ideal sinusoidal Scotch-yoke motion.', 'Observe how rod angle introduces side thrust into the guide or cylinder wall.', ], engineeringFacts: [ { label: 'Stroke length', value: '2 × crank radius', context: 'The slider reaches its two dead-centre positions when crank and rod align on the line of stroke.', }, { label: 'Motion law', value: 'Non-sinusoidal with finite rod length', context: 'Connecting rod angularity makes acceleration and dwell near dead centres differ from a pure sine wave.', }, { label: 'Applications', value: 'Engines, pumps, compressors, presses', context: 'It is among the most common mechanisms for converting pressure or motor torque into reciprocating motion.', }, { label: 'Design concern', value: 'Side thrust', context: 'The angled rod pushes the slider into the guide surface, increasing friction and wear.', }, ], relatedMachines: ['four-stroke-petrol-engine', 'diesel-engine', 'steam-engine', 'scotch-yoke', 'piston-pump'], components: [ { id: 'frame', name: 'Frame / crankcase', role: 'Fixed support structure', description: 'The frame locates the crankshaft bearings and the slider guide. In engines this structure becomes the crankcase and cylinder block.', materialHint: 'dark cast housing', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.35, 0], labelPosition: [0, -0.52, 0.35], replacementNodeNames: ['Frame', 'Crankcase', 'Base'], }, { id: 'crankshaft', name: 'Crankshaft', role: 'Rotary input/output shaft', description: 'The crankshaft converts rod force into torque or supplies torque to drive the slider, depending on application.', parentId: 'frame', materialHint: 'brushed steel shaft', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.5, 0, 0], labelPosition: [-0.8, 0.35, 0], replacementNodeNames: ['Crankshaft', 'MainShaft'], animationNotes: ['Rotate about main bearing axis at commanded RPM.'], }, { id: 'crank-web', name: 'Crank web', role: 'Creates crank throw radius', description: 'The crank web offsets the crank pin from the main shaft centerline, establishing the mechanism stroke.', parentId: 'crankshaft', materialHint: 'warm machined steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.25, 0.18, 0.2], labelPosition: [-0.45, 0.92, 0.25], replacementNodeNames: ['CrankWeb', 'CrankThrow', 'CrankArm'], specs: [{ label: 'Crank radius', value: 'half of stroke' }], animationNotes: ['Shares crankshaft rotation.'], }, { id: 'crank-pin', name: 'Crank pin', role: 'Joint between crank and connecting rod', description: 'The crank pin follows a circular path and drives the big end of the connecting rod.', parentId: 'crank-web', materialHint: 'polished bearing journal', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.1, 0.25, 0.35], labelPosition: [0.05, 1.18, 0.4], replacementNodeNames: ['CrankPin', 'RodJournal'], animationNotes: ['Orbit with crank throw; connecting rod big end follows this point.'], }, { id: 'connecting-rod', name: 'Connecting rod', role: 'Links circular crank motion to slider motion', description: 'The rod carries alternating tensile and compressive load while changing angle throughout the cycle.', parentId: 'crank-pin', materialHint: 'forged steel with highlighted beam', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.2, 0.2, 0.25], labelPosition: [0.9, 0.95, 0.35], replacementNodeNames: ['ConnectingRod', 'ConRod', 'Rod'], specs: [{ label: 'Design variable', value: 'rod length / crank radius ratio' }], animationNotes: ['Aim from crank pin to wrist pin; rotate and translate as a rigid link.'], }, { id: 'wrist-pin', name: 'Wrist pin', role: 'Joint between rod and slider', description: 'The wrist pin lets the connecting rod swing while the slider remains constrained to the guide axis.', parentId: 'piston-slider', materialHint: 'polished pin steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.3, 0.2, 0.3], labelPosition: [1.55, 0.8, 0.35], replacementNodeNames: ['WristPin', 'GudgeonPin', 'SmallEndPin'], animationNotes: ['Translate with slider; rod small end aims at this point.'], }, { id: 'piston-slider', name: 'Slider / piston', role: 'Guided reciprocating output', description: 'The slider moves along one straight axis, representing a piston, crosshead, or press ram.', parentId: 'cylinder-guide', materialHint: 'blue machined slider', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.65, 0, 0], labelPosition: [1.95, 0.45, 0.15], replacementNodeNames: ['PistonSlider', 'Slider', 'Piston'], specs: [{ label: 'Stroke', value: '2r' }], animationNotes: ['Translate along local X based on crank angle and rod length.'], }, { id: 'cylinder-guide', name: 'Cylinder guide', role: 'Constrains slider to straight line', description: 'The guide or cylinder reacts side thrust from the angled rod and keeps the slider on the line of stroke.', parentId: 'frame', materialHint: 'transparent dark cylinder/guide', defaultVisible: true, defaultOpacity: 0.72, explodeVector: [0.35, -0.05, 0.25], labelPosition: [1.75, 0.05, 0.5], replacementNodeNames: ['CylinderGuide', 'SliderGuide', 'Cylinder'], }, { id: 'flywheel', name: 'Flywheel', role: 'Smooths cyclic speed variation', description: 'The flywheel stores rotational energy and carries the crank through dead centres where torque transfer is poor.', parentId: 'crankshaft', materialHint: 'dark rim with blue spoke highlights', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.85, 0, 0], labelPosition: [-1.65, 0.65, 0], replacementNodeNames: ['Flywheel', 'InertiaWheel'], animationNotes: ['Rotate with crankshaft.'], }, { id: 'dead-center-markers', name: 'Dead-centre markers', role: 'Educational timing overlay', description: 'Markers identify top and bottom dead centre, where the slider changes direction and crank torque arm momentarily vanishes.', parentId: 'frame', materialHint: 'transparent amber/blue markers', defaultVisible: true, defaultOpacity: 0.82, explodeVector: [0, 0.45, 0.45], labelPosition: [1.15, 1.35, 0.55], replacementNodeNames: ['DeadCenterMarkers', 'TimingMarkers', 'TdcBdcOverlay'], }, ], cameraPresets: linearMechanismCameraPresets, explodedView: { defaultDistance: 0.56, maxDistance: 2.4, groups: [ { id: 'rotating-crank', name: 'Rotating crank group', partIds: ['crankshaft', 'crank-web', 'crank-pin', 'flywheel'], direction: [-1, 0.12, 0.2], order: 1, note: 'Separates the crank throw and flywheel from the reciprocating group.', }, { id: 'linkage', name: 'Connecting linkage', partIds: ['connecting-rod', 'wrist-pin'], direction: [0.25, 0.35, 0.35], order: 2, note: 'Lifts the rod to inspect the two pin joints.', }, { id: 'slider-guide', name: 'Slider and guide', partIds: ['piston-slider', 'cylinder-guide', 'dead-center-markers'], direction: [1, 0.12, 0], order: 3, note: 'Moves the guided output assembly along the line of stroke.', }, ], }, labels: [ { id: 'slider-label-crank', partId: 'crank-web', text: 'Crank throw sets stroke', position: [-0.45, 1, 0.35], priority: 'primary', }, { id: 'slider-label-rod', partId: 'connecting-rod', text: 'Rod angle creates side thrust', position: [0.9, 1.05, 0.45], priority: 'primary', }, { id: 'slider-label-piston', partId: 'piston-slider', text: 'Slider reciprocates', position: [1.95, 0.55, 0.25], priority: 'primary', }, { id: 'slider-label-flywheel', partId: 'flywheel', text: 'Flywheel carries dead centres', position: [-1.65, 0.75, 0.12], priority: 'secondary', }, { id: 'slider-label-dead-center', partId: 'dead-center-markers', text: 'TDC / BDC markers', position: [1.15, 1.45, 0.65], priority: 'detail', }, ], guidedTour: [ { id: 'rotary-to-linear', title: 'Rotation becomes reciprocation', body: 'The crank pin follows a circle, the connecting rod changes angle, and the slider is constrained to straight-line motion.', durationSeconds: 8, cameraPreset: 'front', focusPartIds: ['crank-web', 'crank-pin', 'connecting-rod', 'piston-slider'], animationCue: 'play one full revolution with rod centerline ghosted', }, { id: 'dead-centres', title: 'Dead-centre positions', body: 'At each dead centre the crank, rod, and slider align. Slider velocity reaches zero and the flywheel helps carry the mechanism through.', durationSeconds: 6, cameraPreset: 'front', focusPartIds: ['dead-center-markers', 'flywheel', 'piston-slider'], animationCue: 'pause briefly at TDC and BDC with marker labels announced', }, { id: 'side-thrust', title: 'Why guides wear', body: 'When the rod is angled, part of its force pushes the slider into the guide. This side thrust is a key engine and compressor design concern.', durationSeconds: 6, cameraPreset: 'isometric', focusPartIds: ['connecting-rod', 'cylinder-guide', 'piston-slider'], animationCue: 'show side-thrust vector near maximum rod angle', }, ], animation: { loopSeconds: 4.2, defaultRpm: 70, rpmRange: [5, 420], stepCount: 12, principalMotion: 'Crankshaft and flywheel rotate; crank pin orbits; connecting rod aims between crank pin and wrist pin; slider translates according to finite rod length geometry.', physicallyBelievableNotes: [ 'Slider displacement uses the finite-length connecting rod equation rather than a pure sine wave.', 'Dead-centre pauses are optional tour cues; normal playback loops continuously.', 'Rod rotation and slider translation are solved from the same crank angle to avoid visual drift.', ], moduleHooks: { cyclePhase: 'theta = TAU * normalizedCycleTime; sliderX = r*cos(theta) + sqrt(l*l - r*r*sin(theta)*sin(theta))', transformChannels: [ 'crankshaft.rotation.z', 'flywheel.rotation.z', 'crank-pin.position.xy', 'connecting-rod.position.xy', 'connecting-rod.rotation.z', 'wrist-pin.position.x', 'piston-slider.position.x', 'dead-center-markers.activeState', ], }, }, proceduralVisualization: { assemblyStyle: 'Planar linkage with transparent guide, highlighted pin joints, flywheel, and dead-centre overlays.', primitives: [ 'cylinders for crankshaft, crank pin, wrist pin, and flywheel', 'capsule/rounded box for connecting rod', 'transparent tube or rail for cylinder guide', 'rectangular slider piston', 'overlay ticks for TDC and BDC', ], motionReference: 'Use crank radius and rod length as independent parameters so the displacement curve can demonstrate rod-ratio effects.', approximationNotes: [ 'The piston is diagrammatic and omits rings unless a replacement engine asset supplies them.', 'The flywheel is simplified but shares exact crankshaft phase.', ], swapReadyNodes: [ 'Frame', 'Crankshaft', 'CrankWeb', 'CrankPin', 'ConnectingRod', 'WristPin', 'PistonSlider', 'CylinderGuide', 'Flywheel', 'DeadCenterMarkers', ], }, assetReplacement: { primaryGlbPath: '/assets/machines/slider-crank/slider-crank.glb', nodeNamingConvention: 'Use separate nodes for crank web, rod, slider, guide, and flywheel; keep pin centers as sensible origins or helper empties.', scaleMetersPerUnit: 1, pivotRequirements: [ 'Crankshaft/Flywheel origin at main shaft axis.', 'CrankWeb origin at shaft axis; CrankPin helper at crank throw center.', 'ConnectingRod origin may be at its midpoint if animation receives both joint coordinates.', 'PistonSlider origin on guide axis at wrist-pin center.', ], textureSets: ['dark-crankcase', 'forged-rod', 'polished-journals', 'transparent-guide'], validationChecklist: [ 'Crank pin, rod big end, and crank web remain coincident through a full cycle.', 'Wrist pin remains coincident with slider and rod small end.', 'TDC and BDC markers align to actual slider extrema.', 'Exploded mode does not invalidate part selection IDs.', ], }, thumbnail: { strategy: 'Show the crank at roughly 60° with rod angle visible, piston mid-stroke, and dead-centre markers faintly glowing.', cameraPreset: 'isometric', highlightedPartIds: ['crank-web', 'connecting-rod', 'piston-slider'], background: 'dark grid with ghosted crank circle', filenameSuggestion: 'slider-crank-card.webp', }, accessibilitySummary: 'Step-through should announce crank angle quadrant, slider direction, and whether the mechanism is approaching or leaving a dead-centre position.', }, { machineId: 'toggle-clamp', title: 'Toggle Clamp', category: 'Mechanisms', difficulty: 'Intermediate', description: 'A toggle clamp uses linked levers that pass slightly over center to lock a workpiece with high mechanical advantage. Near the locked position, a small handle force can generate a large clamping force.', learningObjectives: [ 'Understand over-centre locking and why the clamp remains closed under load.', 'See how mechanical advantage rises as toggle links approach alignment.', 'Identify pivots, handle, clamp arm, pressure pad, and adjustable stop.', 'Relate force-vector direction to safe and unsafe clamp states.', ], engineeringFacts: [ { label: 'Locking principle', value: 'Over-centre geometry', context: 'The linkage passes beyond a straight-line toggle position so clamp reaction force pushes it more firmly against the stop.', }, { label: 'Mechanical advantage', value: 'Rises near link alignment', context: 'The theoretical force ratio can become very large as the toggle angle approaches zero, limited by stiffness and friction.', }, { label: 'Common applications', value: 'Jigs, fixtures, welding, inspection', context: 'Toggle clamps provide quick, repeatable manual holding force in manufacturing fixtures.', }, { label: 'Design concern', value: 'Over-travel and finger safety', context: 'Stops, handles, and guards prevent excessive link stress and protect operators from pinch points.', }, ], relatedMachines: ['geneva-drive', 'rack-and-pinion', 'hydraulic-cylinder', 'disc-brake-caliper'], components: [ { id: 'base', name: 'Mounting base', role: 'Fixed fixture body', description: 'The base bolts to a jig or machine table and carries all pivot loads back into the fixture.', materialHint: 'dark stamped steel base', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, -0.35, 0], labelPosition: [0, -0.45, 0.35], replacementNodeNames: ['Base', 'MountingBase', 'FixtureBase'], }, { id: 'handle', name: 'Operating handle', role: 'Human input lever', description: 'The handle gives the operator leverage and sweeps through an arc to drive the toggle links past center.', parentId: 'center-pivot', materialHint: 'black handle with blue grip', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.55, 0.25, 0.15], labelPosition: [-1.05, 1.45, 0.25], replacementNodeNames: ['Handle', 'OperatingHandle', 'GripLever'], animationNotes: ['Rotate about handle pivot from open to locked angle.'], }, { id: 'front-link', name: 'Front toggle link', role: 'Transfers handle motion to clamp arm', description: 'The front link is one half of the toggle pair. Its angle relative to the rear link controls mechanical advantage.', parentId: 'center-pivot', materialHint: 'brushed link steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.15, 0.28, 0.25], labelPosition: [0.25, 1.1, 0.35], replacementNodeNames: ['FrontLink', 'ToggleLinkFront', 'ClampLinkA'], animationNotes: ['Rotate between handle pivot and clamp-arm joint.'], }, { id: 'rear-link', name: 'Rear toggle link', role: 'Completes over-centre linkage', description: 'Together with the front link, this member passes just beyond straight alignment to create the self-locking clamp state.', parentId: 'center-pivot', materialHint: 'brushed link steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [-0.15, 0.28, 0.25], labelPosition: [-0.2, 0.95, 0.35], replacementNodeNames: ['RearLink', 'ToggleLinkRear', 'ClampLinkB'], animationNotes: ['Rotate as part of the four-bar linkage solve.'], }, { id: 'center-pivot', name: 'Pivot pins', role: 'Rotational joints for the linkage', description: 'Pinned joints allow the clamp to fold and lock. Wear here changes clamp feel and repeatability.', parentId: 'base', materialHint: 'polished pin steel with amber washers', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.18, 0.45], labelPosition: [0, 0.72, 0.65], replacementNodeNames: ['PivotPins', 'CenterPivot', 'JointPins'], }, { id: 'clamp-arm', name: 'Clamp arm', role: 'Output lever applying force to workpiece', description: 'The clamp arm rotates down toward the workpiece and converts toggle-link force into pad force.', parentId: 'base', materialHint: 'blue-black lever steel', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.55, 0.12, 0], labelPosition: [1.35, 0.78, 0.2], replacementNodeNames: ['ClampArm', 'HoldingArm', 'OutputArm'], animationNotes: ['Rotate from open to closed angle with linkage.'], }, { id: 'pressure-pad', name: 'Adjustable pressure pad', role: 'Contact point on workpiece', description: 'The threaded pad accommodates different workpiece thicknesses and spreads clamping load across a replaceable contact surface.', parentId: 'clamp-arm', materialHint: 'rubber pad on zinc screw', defaultVisible: true, defaultOpacity: 1, explodeVector: [0.85, -0.05, 0.15], labelPosition: [2.1, 0.28, 0.25], replacementNodeNames: ['PressurePad', 'ClampPad', 'AdjustablePad'], specs: [{ label: 'Adjustment', value: 'threaded contact screw' }], animationNotes: ['Moves with clamp arm; force indicator grows near lock.'], }, { id: 'workpiece', name: 'Workpiece', role: 'Object being clamped', description: 'A neutral workpiece block provides context for pad compression and contact force.', parentId: 'base', materialHint: 'matte warm workpiece material', defaultVisible: true, defaultOpacity: 1, explodeVector: [1, -0.05, 0], labelPosition: [2.05, -0.05, 0.35], replacementNodeNames: ['Workpiece', 'SampleBlock', 'FixturePart'], }, { id: 'over-center-stop', name: 'Over-centre stop', role: 'Defines locked travel limit', description: 'The stop prevents excessive over-travel while guaranteeing the linkage remains beyond the unstable centerline.', parentId: 'base', materialHint: 'amber stop screw and bumper', defaultVisible: true, defaultOpacity: 1, explodeVector: [0, 0.3, 0.35], labelPosition: [-0.55, 0.45, 0.55], replacementNodeNames: ['OverCenterStop', 'StopScrew', 'LockStop'], }, { id: 'force-vector', name: 'Force and advantage overlay', role: 'Educational vector overlay', description: 'Animated vectors show handle input, clamp output, and the rapidly increasing mechanical advantage near the toggle point.', parentId: 'base', materialHint: 'emissive blue and amber arrows', defaultVisible: true, defaultOpacity: 0.88, explodeVector: [0, 0.65, 0.45], labelPosition: [0.95, 1.75, 0.65], replacementNodeNames: ['ForceVector', 'MechanicalAdvantageOverlay', 'ForceArrows'], animationNotes: ['Scale output-force arrow according to toggle angle and clamp state.'], }, ], cameraPresets: compactMechanismCameraPresets, explodedView: { defaultDistance: 0.55, maxDistance: 2.35, groups: [ { id: 'operator-input', name: 'Handle assembly', partIds: ['handle', 'rear-link'], direction: [-1, 0.35, 0.2], order: 1, note: 'Pulls the operator input linkage away from the clamp body.', }, { id: 'toggle-links', name: 'Toggle links and pivots', partIds: ['front-link', 'center-pivot', 'over-center-stop'], direction: [0, 0.45, 0.45], order: 2, note: 'Lifts the critical over-centre joint stack for inspection.', }, { id: 'clamping-output', name: 'Clamping output', partIds: ['clamp-arm', 'pressure-pad', 'workpiece', 'force-vector'], direction: [1, 0.2, 0.15], order: 3, note: 'Separates the clamping arm, pad, and workpiece contact region.', }, ], }, labels: [ { id: 'toggle-label-handle', partId: 'handle', text: 'Input handle', position: [-1.05, 1.55, 0.35], priority: 'primary', }, { id: 'toggle-label-links', partId: 'front-link', text: 'Toggle links approach straight line', position: [0.25, 1.2, 0.45], priority: 'primary', }, { id: 'toggle-label-stop', partId: 'over-center-stop', text: 'Over-centre stop locks clamp', position: [-0.55, 0.55, 0.65], priority: 'primary', }, { id: 'toggle-label-pad', partId: 'pressure-pad', text: 'Adjustable pressure pad', position: [2.1, 0.38, 0.35], priority: 'secondary', }, { id: 'toggle-label-force', partId: 'force-vector', text: 'Mechanical advantage rises here', position: [0.95, 1.85, 0.75], priority: 'detail', }, ], guidedTour: [ { id: 'closing-motion', title: 'Closing the clamp', body: 'The handle rotates the toggle links, pulling the clamp arm down until the pressure pad contacts the workpiece.', durationSeconds: 7, cameraPreset: 'isometric', focusPartIds: ['handle', 'front-link', 'rear-link', 'clamp-arm', 'pressure-pad'], animationCue: 'animate from open to just before contact', }, { id: 'mechanical-advantage', title: 'Force multiplication near centre', body: 'As the links approach alignment, a small handle movement creates a much larger pad force. The overlay shows the force ratio rising.', durationSeconds: 7, cameraPreset: 'front', focusPartIds: ['front-link', 'rear-link', 'force-vector', 'pressure-pad'], animationCue: 'slow down near toggle center and scale force arrow', }, { id: 'self-locking', title: 'Over-centre lock', body: 'The linkage passes slightly beyond center and rests against the stop. Workpiece reaction now pushes the clamp into the locked state instead of opening it.', durationSeconds: 7, cameraPreset: 'front', focusPartIds: ['over-center-stop', 'center-pivot', 'clamp-arm'], animationCue: 'pause at locked state and pulse stop contact', }, ], animation: { loopSeconds: 6, defaultRpm: 25, rpmRange: [1, 120], stepCount: 8, principalMotion: 'Handle sweeps from open to locked and back; four-bar linkage positions front and rear links; clamp arm rotates toward workpiece; force overlay scales sharply near over-centre.', physicallyBelievableNotes: [ 'The clamp pauses briefly at locked over-centre state before reopening to make the self-locking condition readable.', 'Mechanical advantage overlay is capped to avoid infinite values at perfect alignment.', 'Pressure-pad compression begins only after contact with the workpiece.', ], moduleHooks: { cyclePhase: 'phase = pingPong(normalizedCycleTime); toggleAngle = solveFourBar(phase)', transformChannels: [ 'handle.rotation.z', 'front-link.rotation.z', 'rear-link.rotation.z', 'clamp-arm.rotation.z', 'pressure-pad.position.xy', 'force-vector.outputScale', 'over-center-stop.contactHighlight', ], }, }, proceduralVisualization: { assemblyStyle: 'Compact four-bar clamp with colored pivots, force-vector overlays, workpiece context, and over-centre stop marker.', primitives: [ 'rounded boxes/capsules for handle and links', 'cylinders for pivot pins and pressure pad screw', 'blocks for base and workpiece', 'arrow meshes for input/output force', 'arc overlay for handle travel', ], motionReference: 'Use a deterministic four-bar approximation with named joint coordinates so links, pivots, and force vectors remain coherent.', approximationNotes: [ 'The procedural rig emphasizes linkage geometry and force direction rather than exact commercial clamp proportions.', 'Rubber pad compression is represented with a small scale change and amber contact glow.', ], swapReadyNodes: [ 'Base', 'Handle', 'FrontLink', 'RearLink', 'PivotPins', 'ClampArm', 'PressurePad', 'Workpiece', 'OverCenterStop', 'ForceVector', ], }, assetReplacement: { primaryGlbPath: '/assets/machines/toggle-clamp/toggle-clamp.glb', nodeNamingConvention: 'Name every pivoted link separately and include optional helper empties for joint centers if mesh origins are not located at pivots.', scaleMetersPerUnit: 1, pivotRequirements: [ 'Handle origin at handle pivot.', 'ClampArm origin at base pivot.', 'FrontLink and RearLink either origin at one joint or controlled by two-point link solver.', 'PressurePad origin at contact face center for compression and highlighting.', ], textureSets: ['black-stamped-base', 'blue-handle-grip', 'zinc-linkage', 'rubber-pad', 'amber-overlays'], validationChecklist: [ 'Locked position passes slightly over centre and contacts stop.', 'Pad touches workpiece before force overlay reaches maximum.', 'Pivot pins are independently selectable.', 'Reduced-motion mode can show open, centre, and locked states without continuous looping.', ], }, thumbnail: { strategy: 'Show the clamp locked over a warm workpiece with force arrows rising from the pressure pad and the handle in blue.', cameraPreset: 'isometric', highlightedPartIds: ['handle', 'pressure-pad', 'force-vector'], background: 'dark fixture table grid with subtle clamp-force glow', filenameSuggestion: 'toggle-clamp-card.webp', }, accessibilitySummary: 'Accessible step states should be open, approaching contact, near centre, locked over centre, and reopening, with force-ratio text announced at each state.', }, ] as const satisfies readonly MechanismDossier[]; export type MechanismDossierRecord = (typeof mechanismDossiers)[number]; export type MechanismMachineId = MechanismDossierRecord['machineId']; export const mechanismDossiersById = Object.fromEntries( mechanismDossiers.map((dossier) => [dossier.machineId, dossier]), ) as Record;