1111from mfc .printer import cons
1212
1313
14+ _CMAP_POPULAR = (
15+ 'viridis, plasma, inferno, magma, turbo, '
16+ 'coolwarm, RdBu_r, bwr, hot, jet, gray, seismic'
17+ )
18+
19+
20+ def _validate_cmap (name ):
21+ """Raise a helpful MFCException if *name* is not a known matplotlib colormap."""
22+ import matplotlib # pylint: disable=import-outside-toplevel
23+ if name in matplotlib .colormaps :
24+ return
25+ try :
26+ from rapidfuzz import process # pylint: disable=import-outside-toplevel
27+ matches = process .extract (name , list (matplotlib .colormaps ), limit = 3 )
28+ suggestions = ', ' .join (m [0 ] for m in matches )
29+ hint = f" Did you mean: { suggestions } ?"
30+ except ImportError :
31+ hint = f" Popular choices: { _CMAP_POPULAR } ."
32+ raise MFCException (f"Unknown colormap '{ name } '.{ hint } " )
33+
34+
35+ def _steps_hint (steps , n = 8 ):
36+ """Short inline preview of available steps for error messages."""
37+ if not steps :
38+ return "none found"
39+ if len (steps ) <= n :
40+ return ', ' .join (str (s ) for s in steps )
41+ half = n // 2
42+ head = ', ' .join (str (s ) for s in steps [:half ])
43+ tail = ', ' .join (str (s ) for s in steps [- half :])
44+ return f"{ head } , ... [{ len (steps )} total] ..., { tail } "
45+
46+
1447def _parse_steps (step_arg , available_steps ):
1548 """
1649 Parse the --step argument into a list of timestep integers.
@@ -37,8 +70,10 @@ def _parse_steps(step_arg, available_steps):
3770
3871 single = int (step_arg )
3972 except ValueError as exc :
40- raise MFCException (f"Invalid --step value '{ step_arg } '. "
41- "Expected an integer, a range (start:end:stride), 'last', or 'all'." ) from exc
73+ raise MFCException (
74+ f"Invalid --step value '{ step_arg } '. "
75+ "Expected an integer, a range (start:end:stride), 'last', or 'all'."
76+ ) from exc
4277
4378 if available_steps and single not in set (available_steps ):
4479 return []
@@ -69,7 +104,11 @@ def viz(): # pylint: disable=too-many-locals,too-many-statements,too-many-branc
69104 try :
70105 fmt = discover_format (case_dir )
71106 except FileNotFoundError as exc :
72- raise MFCException (str (exc )) from exc
107+ msg = str (exc )
108+ if os .path .isfile (os .path .join (case_dir , 'case.py' )):
109+ msg += (" This looks like an MFC case directory. "
110+ "Did you forget to run post_process first?" )
111+ raise MFCException (msg ) from exc
73112
74113 cons .print (f"[bold]Format:[/bold] { fmt } " )
75114
@@ -96,7 +135,9 @@ def viz(): # pylint: disable=too-many-locals,too-many-statements,too-many-branc
96135 step_arg = ARG ('step' )
97136 steps = discover_timesteps (case_dir , fmt )
98137 if not steps :
99- raise MFCException ("No timesteps found." )
138+ raise MFCException (
139+ f"No timesteps found in '{ case_dir } ' ({ fmt } format). "
140+ "Ensure post_process has been run and produced output files." )
100141
101142 if step_arg is None or step_arg == 'all' :
102143 step = steps [0 ]
@@ -112,8 +153,8 @@ def viz(): # pylint: disable=too-many-locals,too-many-statements,too-many-branc
112153 "Expected an integer, 'last', or 'all'." ) from exc
113154 if step not in steps :
114155 raise MFCException (
115- f"Timestep { step } not found. Available range: "
116- f"{ steps [ 0 ] } to { steps [ - 1 ] } ( { len (steps )} timesteps) " )
156+ f"Timestep { step } not found. "
157+ f"Available steps: { _steps_hint (steps )} " )
117158
118159 if fmt == 'silo' :
119160 from .silo_reader import assemble_silo # pylint: disable=import-outside-toplevel
@@ -138,14 +179,15 @@ def viz(): # pylint: disable=too-many-locals,too-many-statements,too-many-branc
138179
139180 steps = discover_timesteps (case_dir , fmt )
140181 if not steps :
141- raise MFCException ("No timesteps found." )
182+ raise MFCException (
183+ f"No timesteps found in '{ case_dir } ' ({ fmt } format). "
184+ "Ensure post_process has been run and produced output files." )
142185
143186 requested_steps = _parse_steps (step_arg , steps )
144187 if not requested_steps :
145- msg = f"No matching timesteps for --step { step_arg } "
146- if steps :
147- msg += f". Available range: { steps [0 ]} to { steps [- 1 ]} ({ len (steps )} timesteps)"
148- raise MFCException (msg )
188+ raise MFCException (
189+ f"No matching timesteps for --step { step_arg !r} . "
190+ f"Available steps: { _steps_hint (steps )} " )
149191
150192 # Collect rendering options
151193 render_opts = {
@@ -222,6 +264,11 @@ def read_step(step):
222264 run_interactive (init_var , requested_steps , read_step , port = int (port ))
223265 return
224266
267+ # Validate colormap before any rendering
268+ cmap_name = ARG ('cmap' )
269+ if cmap_name :
270+ _validate_cmap (cmap_name )
271+
225272 # Create output directory
226273 output_base = ARG ('output' )
227274 if output_base is None :
0 commit comments