1919import numpy as np
2020import torch
2121
22+ try :
23+ from nerfview ._renderer import InterruptRenderException as _NerfviewInterruptRenderException
24+ except Exception : # pragma: no cover - optional import outside viewer runtime.
25+ _NerfviewInterruptRenderException = None # type: ignore[assignment]
26+
2227RenderBackend = Literal ["auto" , "gsplat" , "torch" ]
2328
2429_FALLBACK_MESSAGES_SEEN : set [str ] = set ()
@@ -33,6 +38,38 @@ def _log_once(key: str, message: str, *, enabled: bool = True) -> None:
3338 print (message )
3439
3540
41+ def _clip_message (message : str , max_chars : int = 360 ) -> str :
42+ message = " " .join (message .strip ().split ())
43+ if len (message ) <= max_chars :
44+ return message
45+ return f"{ message [: max_chars - 3 ]} ..."
46+
47+
48+ def _summarize_exception (exc : Exception ) -> str :
49+ lines = [line .strip () for line in str (exc ).splitlines () if line .strip ()]
50+ if not lines :
51+ return exc .__class__ .__name__
52+
53+ markers = (
54+ "fatal error:" ,
55+ "No CUDA toolkit found" ,
56+ "Error building extension" ,
57+ "RuntimeError:" ,
58+ "ImportError:" ,
59+ "AttributeError:" ,
60+ "error:" ,
61+ )
62+ for marker in markers :
63+ for line in lines :
64+ if marker in line :
65+ return _clip_message (line )
66+ return _clip_message (lines [0 ])
67+
68+
69+ def _is_render_interrupt (exc : Exception ) -> bool :
70+ return _NerfviewInterruptRenderException is not None and isinstance (exc , _NerfviewInterruptRenderException )
71+
72+
3673def _normalize_device (device : str | torch .device ) -> str :
3774 device_str = str (device )
3875 if device_str .startswith ("cuda" ) and not torch .cuda .is_available ():
@@ -345,7 +382,9 @@ def _try_gsplat_rasterization(
345382 return depth [..., None ].repeat (1 , 1 , 3 )
346383 return image [..., :3 ].clamp (0.0 , 1.0 )
347384 except Exception as first_error :
348- raise RuntimeError (f"gsplat rasterization failed: { first_error } " ) from first_error
385+ if _is_render_interrupt (first_error ):
386+ raise
387+ raise RuntimeError (f"gsplat rasterization failed: { _summarize_exception (first_error )} " ) from first_error
349388
350389
351390def viewer_render_fn (
@@ -410,12 +449,14 @@ def viewer_render_fn(
410449 )
411450 return image_to_uint8_numpy (image )
412451 except Exception as exc :
452+ if _is_render_interrupt (exc ):
453+ raise
413454 if fallback_to_cpu :
414455 fallback_splats = cpu_fallback_splats if cpu_fallback_splats is not None else max_cpu_splats
415456 _log_once (
416457 "gsplat_to_cpu" ,
417458 "[renderer] gsplat failed; rerendering with CPU fallback "
418- f"({ fallback_splats :,} splats max): { exc } " ,
459+ f"({ fallback_splats :,} splats max): { _summarize_exception ( exc ) } " ,
419460 enabled = log_fallbacks ,
420461 )
421462 image = _torch_point_splat_rasterization (
@@ -433,7 +474,8 @@ def viewer_render_fn(
433474
434475 _log_once (
435476 "gsplat_to_same_device_torch" ,
436- f"[renderer] gsplat failed; CPU fallback disabled, using torch renderer on { render_device } : { exc } " ,
477+ "[renderer] gsplat failed; CPU fallback disabled, using torch renderer "
478+ f"on { render_device } : { _summarize_exception (exc )} " ,
437479 enabled = log_fallbacks ,
438480 )
439481
0 commit comments