{"id":49337985,"url":"https://github.com/londopy/ropesim","last_synced_at":"2026-04-29T04:01:00.207Z","repository":{"id":353661918,"uuid":"1219613755","full_name":"Londopy/ropesim","owner":"Londopy","description":"RopeSim models lead-fall dynamics using a damped spring / RK4 integration in Rust, exposed to Python via PyO3/Maturin. It ships a full Python API, a five-command CLI, and a PySide6 desktop GUI.","archived":false,"fork":false,"pushed_at":"2026-04-27T01:12:35.000Z","size":250,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T02:02:35.768Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://pypi.org/project/ropesim/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Londopy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T03:44:05.000Z","updated_at":"2026-04-27T00:54:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Londopy/ropesim","commit_stats":null,"previous_names":["londopy/ropesim"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Londopy/ropesim","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Londopy%2Fropesim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Londopy%2Fropesim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Londopy%2Fropesim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Londopy%2Fropesim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Londopy","download_url":"https://codeload.github.com/Londopy/ropesim/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Londopy%2Fropesim/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32364093,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T20:07:02.737Z","status":"online","status_checked_at":"2026-04-28T02:00:07.250Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-27T02:00:18.922Z","updated_at":"2026-04-29T04:01:00.152Z","avatar_url":"https://github.com/Londopy.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RopeSim\r\n\r\n**Climbing rope physics engine and GUI simulator**\r\n\r\nRopeSim models lead-fall dynamics using a damped spring / RK4 integration in\r\nRust, exposed to Python via PyO3/Maturin.  It ships a full Python API, an\r\nexpanded CLI with 20+ commands, a PySide6 desktop GUI with a 3D Vispy viewport,\r\nJupyter notebook integration, and an optional Rapier3D full-physics simulation\r\nmode.\r\n\r\n[![CI](https://github.com/Londopy/ropesim/actions/workflows/ci.yml/badge.svg)](https://github.com/Londopy/ropesim/actions/workflows/ci.yml)\r\n[![PyPI](https://img.shields.io/pypi/v/ropesim)](https://pypi.org/project/ropesim/)\r\n[![Python 3.10+](https://img.shields.io/pypi/pyversions/ropesim)](https://pypi.org/project/ropesim/)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\r\n\r\n---\r\n\r\n## Features\r\n\r\n- **UIAA 101 / EN 892** impact-force model with belay-device friction, wet-rope modifier, and temperature correction\r\n- **RK4 force-time curve** — full damped spring integration in Rust for accurate energy modelling\r\n- **Rapier3D full-physics mode** — rope modelled as a capsule-chain rigid-body simulation (optional)\r\n- **Parallel batch sweeps** via Rayon — sweep 200 fall positions in milliseconds\r\n- **Anchor system physics** — sliding-X, quad, cordelette, trad gear with load distribution and progressive failure\r\n- **Guide-mode self-locking belay devices** — Reverso Guide, Mega Jul, Giga Jul, Click Up, I-Device, Sum with load-dependent friction model\r\n- **Mechanical advantage / haul systems** — 3:1, 5:1, 6:1, piggyback with friction-corrected MA\r\n- **Top-rope and rappel models** — catch and anchor-load estimation\r\n- **Rope diameter under load** — estimates radial compression at any applied force\r\n- **Rope degradation model** — stiffness and impact-force drift with falls taken\r\n- **25-rope database** covering Beal, Mammut, Sterling, Petzl, Edelrid, Black Diamond, and more\r\n- **PySide6 GUI** — drag-and-drop route builder, live simulation, 2D/3D viewport toggle, fall animation, matplotlib plots, PDF/CSV export\r\n- **3D Vispy viewport** — rope tension heatmap, gear load markers, climber tracker, force arrows, turntable camera, frame-scrubber playback\r\n- **Jupyter notebook integration** — rich HTML/SVG repr for Rope, FallResult, AnchorSystem; five example notebooks\r\n- **CLI** — 20+ commands across `rope`, `scenario`, `validate`, `report`, `toprope`, `rappel`, `haul`, and `interactive` subcommand groups\r\n\r\n---\r\n\r\n## Installation\r\n\r\n### Pre-built wheel (recommended)\r\n\r\n```bash\r\npip install ropesim                  # physics library + CLI only\r\npip install \"ropesim[gui]\"           # + PySide6 GUI (2D canvas)\r\npip install \"ropesim[gui,vispy]\"     # + 3D Vispy viewport\r\n```\r\n\r\nOr grab everything at once:\r\n\r\n```bash\r\npip install \"ropesim[all]\"\r\n```\r\n\r\n### From source (requires Rust toolchain)\r\n\r\n```bash\r\ngit clone https://github.com/Londopy/ropesim.git\r\ncd ropesim\r\npip install maturin\r\nmaturin develop --release      # compiles Rust, installs in editable mode\r\npip install -e \".[all]\"        # optional GUI + notebook deps\r\n```\r\n\r\n\u003e **Python 3.14+** users building from source need one extra step:\r\n\u003e `set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` (Windows) or\r\n\u003e `export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` (Linux/macOS) before running\r\n\u003e `maturin develop`. PyPI wheel installs are unaffected.\r\n\r\n---\r\n\r\n## Demo\r\n\r\nA self-contained demo script exercises every feature of the library and saves\r\neight matplotlib plots to your working directory:\r\n\r\n```bash\r\npython demo.py\r\n```\r\n\r\nThis covers: units, standards lookup, rope database search, rope physics\r\nhelpers, fall simulation, anchor systems, scenario builder, sweep and zipper\r\nanalysis, visualisations, and the low-level Rust core API.\r\n\r\n---\r\n\r\n## Quick start — Python API\r\n\r\n```python\r\nfrom ropesim.rope import RopeDatabase\r\nfrom ropesim.fall import FallConditions, Fall, BelayDevice\r\n\r\n# Load a rope from the bundled database\r\nspec = RopeDatabase().get(\"Mammut Crag Classic 10.2\")\r\n\r\n# Simulate a factor-0.5 fall on 20 m of rope\r\nconditions = FallConditions(\r\n    climber_mass_kg=80.0,\r\n    fall_distance_m=10.0,   # fell 2 x 5 m above last pro\r\n    rope_out_m=20.0,\r\n    belay_device=BelayDevice.GRIGRI,\r\n    rope=spec,\r\n)\r\nresult = Fall(conditions).simulate()\r\n\r\nprint(f\"Peak impact force : {result.peak_force_kn:.2f} kN\")\r\nprint(f\"Fall factor       : {result.fall_factor:.3f}\")\r\nprint(f\"Energy absorbed   : {result.energy_budget.rope_absorption_j:.0f} J\")\r\nprint(f\"Warnings          : {result.warnings or 'none'}\")\r\n```\r\n\r\n### Guide-mode self-locking belay devices\r\n\r\n```python\r\nfrom ropesim.fall import BelayDevice, FallConditions, Fall\r\nfrom ropesim.simulate import compute_effective_friction, is_guide_mode\r\n\r\ndevice = BelayDevice.REVERSO_GUIDE\r\n\r\n# Check if it uses the self-locking model\r\nprint(is_guide_mode(device))  # True\r\n\r\n# Effective friction increases with load\r\nfor kn in [3.0, 6.0, 9.0, 12.0]:\r\n    mu = compute_effective_friction(device, kn)\r\n    print(f\"{kn:.0f} kN -\u003e mu_eff = {mu:.3f}\")\r\n\r\n# Simulate — guide-mode friction solved automatically\r\nconditions = FallConditions(\r\n    climber_mass_kg=80.0,\r\n    fall_distance_m=8.0,\r\n    rope_out_m=18.0,\r\n    belay_device=device,\r\n    rope=RopeDatabase().get(\"Beal Opera 8.5 Dry\"),\r\n)\r\nresult = Fall(conditions).simulate()\r\nprint(f\"Guide-mode active : {result.guide_mode_used}\")\r\nprint(f\"Peak force        : {result.peak_force_kn:.2f} kN\")\r\n```\r\n\r\n### Rapier3D full-physics mode\r\n\r\n```python\r\nfrom ropesim.simulate import Scenario, PhysicsMode, ScenarioType\r\nfrom ropesim.rope import Rope, RopeDatabase\r\nfrom ropesim.anchor import AnchorSystem, AnchorType, Bolt\r\n\r\nrope = Rope(RopeDatabase().get(\"Mammut Crag Classic 10.2\"))\r\nscenario = Scenario(rope=rope, climber_mass_kg=80.0)\r\n\r\nfor h in [4.0, 9.0, 14.0]:\r\n    scenario.add_protection(h, AnchorSystem(AnchorType.SINGLE_POINT, [Bolt(rated_mbs_kn=25.0)]))\r\n\r\n# Full Rapier3D rigid-body simulation\r\nresult = scenario.simulate_fall(\r\n    climber_height_m=16.0,\r\n    mode=PhysicsMode.RAPIER_3D,\r\n)\r\nprint(f\"Physics mode : {result.mode}\")\r\nprint(f\"Peak force   : {result.peak_force_kn:.2f} kN\")\r\nprint(f\"Frames       : {len(result.frames)}\")\r\n\r\n# Frame-by-frame playback\r\nfrom ropesim.replay import SimulationReplay\r\nreplay = SimulationReplay(result.frames)\r\nprint(f\"Duration     : {replay.total_time_seconds:.2f} s\")\r\nprint(f\"Peak anchor  : {replay.peak_anchor_force_kn():.2f} kN\")\r\npeak_frame = replay.peak_force_frame()\r\nprint(f\"Rope shape at peak: {replay.frame(peak_frame).link_positions[:3]}\")\r\n```\r\n\r\n### Rope physics helpers\r\n\r\n```python\r\nfrom ropesim.rope import Rope, RopeDatabase\r\n\r\nrope = Rope(RopeDatabase().get(\"Beal Opera 8.5 Dry\"))\r\n\r\n# Elongation at a given force\r\nprint(rope.elongation_at_force(9.0))         # metres\r\n\r\n# Estimated rope diameter under load\r\nfor kn in [0, 3, 6, 9, 12]:\r\n    print(f\"{kn} kN -\u003e {rope.diameter_under_load(kn):.3f} mm\")\r\n\r\n# Degradation after repeated falls\r\nworn = rope.degrade(falls_taken=10)\r\nprint(worn.retirement_warning(falls_taken=10))\r\n\r\n# EN 892 / UIAA 101 compliance check\r\nprint(rope.validate_standard_compliance())\r\n```\r\n\r\n### Scenario builder (multi-pitch / gear placement)\r\n\r\n```python\r\nfrom ropesim.rope import Rope, RopeDatabase\r\nfrom ropesim.anchor import AnchorSystem, AnchorType, Bolt\r\nfrom ropesim.simulate import Scenario\r\n\r\nrope = Rope(RopeDatabase().get(\"Beal Opera 8.5 Dry\"))\r\nscenario = Scenario(rope=rope, climber_mass_kg=75.0)\r\n\r\nfor height in [3.0, 7.0, 12.0]:\r\n    anchor = AnchorSystem(AnchorType.SINGLE_POINT, [Bolt(rated_mbs_kn=25.0)])\r\n    scenario.add_protection(height, anchor, label=f\"B{int(height)}\")\r\n\r\n# Simulate a fall from 15 m\r\nresult = scenario.simulate_fall(climber_height_m=15.0)\r\nprint(f\"Peak: {result.peak_force_kn:.2f} kN  FF: {result.fall_factor:.3f}\")\r\n\r\n# Sweep all positions\r\nsweep = scenario.sweep_fall_positions(steps=60)\r\nprint(f\"Worst position: {sweep.worst_height_m:.1f} m -\u003e {sweep.worst_peak_kn:.2f} kN\")\r\n\r\n# Zipper failure cascade\r\nzipper = scenario.simulate_zipper(climber_height_m=15.0)\r\nprint(f\"Pieces failed: {zipper.total_pieces_failed}  ground fall: {zipper.ground_fall_reached}\")\r\n```\r\n\r\n### Haul systems and top-rope / rappel models\r\n\r\n```python\r\nfrom ropesim._rustcore import (\r\n    compute_haul_system_force, HaulSystem,\r\n    compute_top_rope_impact,\r\n    compute_rappel_load,\r\n)\r\n\r\n# 3:1 Z-pulley haul system\r\nhaul = compute_haul_system_force(load_kg=80.0, system=HaulSystem.ThreeToOne, friction_loss=0.12)\r\nprint(f\"Theoretical MA : {haul.theoretical_ma}\")\r\nprint(f\"Actual MA      : {haul.actual_ma:.2f}  (friction corrected)\")\r\nprint(f\"Hauler effort  : {haul.hauler_effort_n / 1000:.2f} kN\")\r\n\r\n# Top-rope catch\r\ntr = compute_top_rope_impact(stiffness_kn=18.0, mass_kg=75.0, slack_m=0.5,\r\n                              rope_length_m=25.0, friction=0.35)\r\nprint(f\"Top-rope peak  : {tr:.2f} kN\")\r\n\r\n# Rappel anchor load\r\nrappel = compute_rappel_load(mass_kg=80.0, friction=0.25, speed_mps=1.2, sudden_stop=False)\r\nprint(f\"Rappel load    : {rappel:.2f} kN\")\r\n```\r\n\r\n### Anchor systems\r\n\r\n```python\r\nfrom ropesim.anchor import AnchorSystem, AnchorType, Bolt, BoltType, RockType\r\n\r\nbolt = Bolt(bolt_type=BoltType.GLUE_IN, rated_mbs_kn=25.0,\r\n            age_years=3, rock_type=RockType.GRANITE)\r\nanchor = AnchorSystem(AnchorType.SLIDING_X, [bolt, bolt])\r\n\r\n# Force on each component vs load angle\r\ndist = anchor.load_distribution(load_kn=9.0, load_angle_deg=30)\r\nprint(dist)\r\n\r\n# How much of each bolt's MBS is left\r\nmargins = anchor.safety_margins(load_kn=9.0)\r\nprint(margins)\r\n\r\n# Progressive failure under extreme load\r\nfailure = anchor.simulate_failure(load_kn=40.0)\r\nprint(f\"Cascade: {failure.cascade_occurred}  failed: {failure.failed_indices}\")\r\n```\r\n\r\n### Batch parallel sweep (Rust/Rayon)\r\n\r\n```python\r\nfrom ropesim._rustcore import batch_sweep_fall_factors\r\nimport numpy as np\r\n\r\nfall_factors = np.linspace(0.1, 2.0, 200).tolist()\r\npeak_forces  = batch_sweep_fall_factors(\r\n    mass_kg=80.0,\r\n    ff_values=fall_factors,\r\n    stiffness_kn=20.0,\r\n    belay_friction=0.35,\r\n)\r\nprint(f\"Max peak: {max(peak_forces):.2f} kN at FF {fall_factors[peak_forces.index(max(peak_forces))]:.2f}\")\r\n```\r\n\r\n### Visualisations\r\n\r\n```python\r\nfrom ropesim import viz\r\nimport matplotlib.pyplot as plt\r\n\r\n# Force-time curve\r\nfig, ax = viz.plot_force_curve(result, dark=True)\r\n\r\n# Energy budget breakdown\r\nfig, ax = viz.plot_energy_budget(result, dark=True)\r\n\r\n# Rope elongation vs applied force\r\nfig, ax = viz.plot_rope_elongation(rope, force_range=(0, 15), dark=True)\r\n\r\n# Rope diameter under load\r\nfig, ax = viz.plot_diameter_under_load(rope, force_range=(0, 12), dark=True)\r\n\r\n# Anchor force distribution vs load angle\r\nfig, ax = viz.plot_anchor_distribution(anchor, load_kn=9.0, dark=True)\r\n\r\n# Compare multiple ropes / scenarios on one chart\r\nfig, ax = viz.plot_comparison([result1, result2], [\"Rope A\", \"Rope B\"], dark=True)\r\n\r\nplt.show()\r\n```\r\n\r\n### Jupyter notebook integration\r\n\r\nRopeSim ships rich HTML reprs for all major objects — they render automatically\r\nin JupyterLab and VS Code notebooks without any extra calls.\r\n\r\n```python\r\nimport ropesim.notebook  # activates _repr_html_ patches\r\n\r\nrope     # renders as HTML spec card with EN 892 compliance badge\r\nresult   # renders as summary table + inline force-time curve PNG\r\nanchor   # renders as inline SVG bolt-and-sling diagram\r\n```\r\n\r\nFive example notebooks are included in `notebooks/`:\r\n\r\n| Notebook | Contents |\r\n|----------|----------|\r\n| `01_basic_fall_simulation.ipynb` | Fundamentals, device comparison, force curve |\r\n| `02_anchor_comparison.ipynb` | Sliding-X / quad / cordelette angle sweep, heatmaps |\r\n| `03_rope_database_exploration.ipynb` | Scatter plots, bar charts, retirement calculator |\r\n| `04_scenario_builder.ipynb` | Trad pitch, position sweep, zipper analysis |\r\n| `05_rapier_3d_simulation.ipynb` | PyRopeSimWorld, SimulationReplay, 3D link plots |\r\n\r\n---\r\n\r\n## CLI\r\n\r\nRopeSim ships a unified `ropesim-cli` entry point with subcommand groups.\r\n\r\n```bash\r\n# ---- Rope database ----\r\nropesim-cli rope list                              # all ropes\r\nropesim-cli rope list --type dry_single --diameter 9.5\r\nropesim-cli rope show \"Beal Opera 8.5 Dry\"        # full spec card\r\nropesim-cli rope compare \"Beal Opera 8.5 Dry\" \"Mammut Crag Classic 10.2\"\r\nropesim-cli rope add                               # interactive prompt\r\nropesim-cli rope import --file my_rope.json\r\nropesim-cli rope retire \"Mammut Crag Classic 10.2\" --falls-taken 40\r\n\r\n# ---- Scenario runner ----\r\nropesim-cli scenario run    --file pitch.json --height 15\r\nropesim-cli scenario sweep  --file pitch.json\r\nropesim-cli scenario zipper --file pitch.json --height 15\r\nropesim-cli scenario build                         # interactive builder\r\n\r\n# ---- Validation ----\r\nropesim-cli validate rope     --name \"Mammut Crag Classic 10.2\"\r\nropesim-cli validate scenario --file pitch.json\r\nropesim-cli validate system   --rope \"Beal Opera 8.5 Dry\" --load 80\r\n\r\n# ---- Reports ----\r\nropesim-cli report --scenario pitch.json           # multi-page PDF\r\n\r\n# ---- Specialty calculations ----\r\nropesim-cli toprope --rope \"Beal Opera 8.5 Dry\" --slack 0.5\r\nropesim-cli rappel  --mass 80\r\nropesim-cli haul    --system 3:1 --load 80\r\n\r\n# ---- Classic commands (still available) ----\r\nropesim-cli simulate --mass 80 --fall-dist 8 --rope-out 20 \\\r\n    --rope \"Beal Opera 8.5 Dry\" --device grigri\r\nropesim-cli anchor --type sliding_x --load 9.5 --angle 60\r\nropesim-cli sweep  --rope \"Mammut Crag Classic 10.2\" --mass 80 --steps 20\r\nropesim-cli validate-rope --name \"Mammut Crag Classic 10.2\"\r\nropesim-cli list-ropes\r\n\r\n# ---- REPL ----\r\nropesim-cli interactive        # Python REPL with all ropesim symbols pre-loaded\r\n```\r\n\r\nAdd `--format json` (or `--json` on classic commands) to any command for\r\nmachine-readable output.\r\n\r\n---\r\n\r\n## GUI\r\n\r\n```bash\r\nropesim          # launch the desktop GUI (requires pip install \"ropesim[gui]\")\r\n```\r\n\r\n### Demo mode\r\n\r\nThe fastest way to see everything in action: click **Demo Route** in the left\r\npanel (or press `F8`).\r\n\r\nIt automatically builds a realistic mixed trad/sport route, runs a fall\r\nsimulation and a full position sweep, and mirrors the result to the 3D viewport.\r\n\r\n**Manual workflow:**\r\n1. Select a rope from the left panel\r\n2. Click **+ Bolt**, **+ Cam**, or **+ Nut** to place protection on the wall\r\n3. Set climber mass and height\r\n4. Press **Run Fall Simulation** — watch the animation, results appear in the right panel\r\n5. Press **Sweep All Positions** to see peak force vs climber height across the whole route\r\n6. **Zipper Analysis** models sequential gear-ripping under high loads\r\n7. Toggle **[2D] / [3D]** in the toolbar to switch between the 2D canvas and the 3D Vispy viewport\r\n8. Toggle **[Analytical] / [Rapier 3D]** to switch physics modes (3D mode mirrors to 3D viewport automatically)\r\n9. Export results as PDF or CSV from the File menu\r\n\r\n**Keyboard shortcuts:**\r\n\r\n| Key | Action |\r\n|-----|--------|\r\n| `F8` | Demo route (auto-build + simulate) |\r\n| `F5` | Run fall simulation |\r\n| `F6` | Sweep all positions |\r\n| `F7` | Zipper analysis |\r\n| `B` / `C` / `N` | Add bolt / cam / nut |\r\n| `F` | Fit canvas to view |\r\n| `Ctrl+Scroll` | Zoom canvas |\r\n| `Middle-drag` | Pan canvas |\r\n| `Delete` | Remove selected gear |\r\n| `R` (3D view) | Reset camera |\r\n\r\n**3D viewport controls (when 3D tab is active):**\r\n\r\n| Input | Action |\r\n|-------|--------|\r\n| Left-drag | Orbit / turntable rotate |\r\n| Middle-drag | Pan |\r\n| Scroll | Zoom |\r\n| `R` | Reset to default view |\r\n| Front / Side / Top / Iso buttons | Preset camera angles |\r\n| Play / Pause / Stop bar | Frame-by-frame Rapier playback |\r\n| Speed selector | 0.1x to 2x playback speed |\r\n\r\n---\r\n\r\n## Physics model\r\n\r\n### Analytical mode (default)\r\n\r\nThe impact force is computed using the UIAA 101 analytic formula:\r\n\r\n```\r\nF = mg + sqrt((mg)^2 + 2*mg*ff*k_eff)\r\n```\r\n\r\nwhere `k_eff` is the length-normalised rope stiffness back-calculated from the\r\nEN 892 test-mass drop (80 kg, fall factor 1.77).  The full force-time curve is\r\nobtained by integrating the damped spring equation with a 4th-order\r\nRunge-Kutta solver at 1 ms resolution.\r\n\r\nModifiers applied:\r\n\r\n- **Belay device friction** (Grigri: 55 %, ATC: 35 %, Munter: 45 % ...)\r\n- **Guide-mode self-locking friction** for Reverso Guide / Mega Jul / Giga Jul / Click Up / I-Device / Sum: `mu_eff(F) = min(mu_base + k_lock * F_kN, mu_max)`, solved by fixed-point iteration\r\n- **Wet rope** +12 % impact force (EN 892 s.6.1.3)\r\n- **Temperature** — stiffness increases ~2 % per 10 deg C below 20 deg C\r\n- **Rope age / degradation** — elongation and stiffness drift modelled from published UIAA fatigue data\r\n- **Edge friction** — rope running over a ledge reduces effective belay friction\r\n\r\n### Rapier 3D mode (optional)\r\n\r\nWhen `PhysicsMode.RAPIER_3D` is requested, the rope is modelled as a chain of\r\ncapsule rigid bodies connected by `SphericalJoint` constraints inside a full\r\nRapier3D 0.21 pipeline (broad phase, narrow phase, CCD, island manager).\r\n\r\nForce estimation uses momentum change: `F = m*(dv/dt - g)`.  Results are\r\nreturned as a `SimulationResult` carrying per-frame `SimFrame` snapshots that\r\ncan be replayed with `SimulationReplay`.\r\n\r\n---\r\n\r\n## Development\r\n\r\n```bash\r\n# Install dev dependencies\r\npip install -e \".[dev,gui]\"\r\n\r\n# Run tests (no Rust required)\r\npytest -m \"not requires_rust\"\r\n\r\n# Run full suite (after maturin develop)\r\npytest\r\n\r\n# Benchmarks\r\npytest -m benchmark --benchmark-only\r\n```\r\n\r\n---\r\n\r\n## License\r\n\r\nMIT — see [LICENSE](LICENSE).\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flondopy%2Fropesim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flondopy%2Fropesim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flondopy%2Fropesim/lists"}