Skip to content

Commit fb065bc

Browse files
sbryngelsonclaude
andcommitted
Improve viz error messages: colormap validation, step hints, missing output guidance
- Add _validate_cmap() with rapidfuzz fuzzy suggestions for unknown colormaps - Add _steps_hint() usage in --step not-found and --list-vars errors - Improve "no timesteps found" error with format name and post_process reminder - Improve discover_format failure: detect case.py and suggest running post_process - Fix line-too-long in _parse_steps error message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 42d0923 commit fb065bc

1 file changed

Lines changed: 58 additions & 11 deletions

File tree

toolchain/mfc/viz/viz.py

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,39 @@
1111
from 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+
1447
def _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

Comments
 (0)