diff --git a/app/streamlit_app.py b/app/streamlit_app.py index e2fff89..e4ed6bb 100644 --- a/app/streamlit_app.py +++ b/app/streamlit_app.py @@ -10,8 +10,18 @@ from __future__ import annotations +import asyncio from pathlib import Path +# Force stdlib asyncio policy before any subprocess (stpyvista's trame +# export runs in multiprocessing.Process and inherits the parent's +# policy). uvicorn ships uvloop as a transitive of Streamlit; if the +# parent's policy is uvloop, the 3-D Twin tab's subprocess hits +# `nest_asyncio2._patch_loop` which raises `ValueError: Can't patch loop +# of type `. The fix is to reset the policy at +# import time — Streamlit's tornado server is unaffected. +asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy()) + import numpy as np import streamlit as st @@ -368,7 +378,23 @@ def _badge(ok: bool, label: str, detail: str): patch, edited_alignment, segments, z_exaggeration=float(z_exag), off_screen=True, ) - stpyvista(plotter, key="ropeway_twin") + # Graceful fallback: stpyvista spawns a multiprocessing.Process for + # trame's HTML export. On systems where uvloop is loaded that + # subprocess can still fail (the asyncio policy reset at import + # time covers the common case but not e.g. pre-imported uvloop in + # the parent). Catch any subprocess crash, log it, and offer the + # alignment plot as a fallback so the rest of the tab stays usable. + try: + stpyvista(plotter, key="ropeway_twin") + except Exception as exc: # noqa: BLE001 — surface any backend crash + st.warning( + "3-D Twin renderer crashed in its subprocess " + f"(`{type(exc).__name__}`). Falling back to the 2-D " + "alignment plot. Run `ropeway view3d --dem ... --out twin.html` " + "from a shell to get an interactive 3-D HTML you can open " + "directly in a browser." + ) + st.exception(exc) # Persist edited layout into session for the Projects tab st.session_state["edited_alignment"] = edited_alignment