batplot 1.8.5__py3-none-any.whl → 1.8.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -374,7 +374,7 @@ def _apply_stored_smooth_settings(cycle_lines: Dict[int, Dict[str, Optional[obje
374
374
  ln._smooth_applied = True
375
375
 
376
376
 
377
- def _print_menu(n_cycles: int, is_dqdv: bool = False):
377
+ def _print_menu(n_cycles: int, is_dqdv: bool = False, fig=None):
378
378
  # Three-column menu similar to operando: Styles | Geometries | Options
379
379
  # Use dynamic column widths for clean alignment.
380
380
  col1 = [
@@ -405,6 +405,19 @@ def _print_menu(n_cycles: int, is_dqdv: bool = False):
405
405
  "b: undo",
406
406
  "q: quit",
407
407
  ]
408
+
409
+ # Conditional overwrite shortcuts under (Options)
410
+ if fig is not None:
411
+ last_session = getattr(fig, "_last_session_save_path", None)
412
+ last_style = getattr(fig, "_last_style_export_path", None)
413
+ last_figure = getattr(fig, "_last_figure_export_path", None)
414
+ if last_session:
415
+ col3.append("os: overwrite session")
416
+ if last_style:
417
+ col3.append("ops: overwrite style")
418
+ col3.append("opsg: overwrite style+geom")
419
+ if last_figure:
420
+ col3.append("oe: overwrite figure")
408
421
  # Compute widths (min width prevents overly narrow columns)
409
422
  w1 = max(len("(Styles)"), *(len(s) for s in col1), 18)
410
423
  w2 = max(len("(Geometries)"), *(len(s) for s in col2), 12)
@@ -1640,7 +1653,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1640
1653
  print("Undo: restored previous state.")
1641
1654
  except Exception as e:
1642
1655
  print(f"Undo failed: {e}")
1643
- _print_menu(len(all_cycles), is_dqdv)
1656
+ _print_menu(len(all_cycles), is_dqdv, fig)
1644
1657
  while True:
1645
1658
  try:
1646
1659
  key = _safe_input("Press a key: ").strip().lower()
@@ -1657,18 +1670,18 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1657
1670
  if confirm == 'y':
1658
1671
  break
1659
1672
  else:
1660
- _print_menu(len(all_cycles), is_dqdv)
1673
+ _print_menu(len(all_cycles), is_dqdv, fig)
1661
1674
  continue
1662
1675
  elif key == 'b':
1663
1676
  restore_state()
1664
- _print_menu(len(all_cycles), is_dqdv)
1677
+ _print_menu(len(all_cycles), is_dqdv, fig)
1665
1678
  continue
1666
1679
  elif key == 'e':
1667
1680
  # Export current figure to a file; default extension .svg if missing
1668
1681
  try:
1669
1682
  base_path = choose_save_path(source_paths, purpose="figure export")
1670
1683
  if not base_path:
1671
- _print_menu(len(all_cycles), is_dqdv)
1684
+ _print_menu(len(all_cycles), is_dqdv, fig)
1672
1685
  continue
1673
1686
  # List existing figure files in Figures/ subdirectory
1674
1687
  fig_extensions = ('.svg', '.png', '.jpg', '.jpeg', '.pdf', '.eps', '.tif', '.tiff')
@@ -1690,7 +1703,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1690
1703
  else:
1691
1704
  fname = _safe_input("Export filename (default .svg if no extension) or number to overwrite (q=cancel): ").strip()
1692
1705
  if not fname or fname.lower() == 'q':
1693
- _print_menu(len(all_cycles), is_dqdv)
1706
+ _print_menu(len(all_cycles), is_dqdv, fig)
1694
1707
  continue
1695
1708
 
1696
1709
  already_confirmed = False # Initialize for new filename case
@@ -1698,15 +1711,15 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1698
1711
  if fname.lower() == 'o':
1699
1712
  if not last_figure_path:
1700
1713
  print("No previous export found.")
1701
- _print_menu(len(all_cycles), is_dqdv)
1714
+ _print_menu(len(all_cycles), is_dqdv, fig)
1702
1715
  continue
1703
1716
  if not os.path.exists(last_figure_path):
1704
1717
  print(f"Previous export file not found: {last_figure_path}")
1705
- _print_menu(len(all_cycles), is_dqdv)
1718
+ _print_menu(len(all_cycles), is_dqdv, fig)
1706
1719
  continue
1707
1720
  yn = _safe_input(f"Overwrite '{os.path.basename(last_figure_path)}'? (y/n): ").strip().lower()
1708
1721
  if yn != 'y':
1709
- _print_menu(len(all_cycles), is_dqdv)
1722
+ _print_menu(len(all_cycles), is_dqdv, fig)
1710
1723
  continue
1711
1724
  target = last_figure_path
1712
1725
  already_confirmed = True
@@ -1718,13 +1731,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1718
1731
  name = files[idx-1]
1719
1732
  yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
1720
1733
  if yn != 'y':
1721
- _print_menu(len(all_cycles), is_dqdv)
1734
+ _print_menu(len(all_cycles), is_dqdv, fig)
1722
1735
  continue
1723
1736
  target = file_list[idx-1][1] # Full path from list
1724
1737
  already_confirmed = True
1725
1738
  else:
1726
1739
  print("Invalid number.")
1727
- _print_menu(len(all_cycles), is_dqdv)
1740
+ _print_menu(len(all_cycles), is_dqdv, fig)
1728
1741
  continue
1729
1742
  else:
1730
1743
  root, ext = os.path.splitext(fname)
@@ -1807,7 +1820,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1807
1820
  print(f"Export failed: {e}")
1808
1821
  except Exception as e:
1809
1822
  print(f"Export failed: {e}")
1810
- _print_menu(len(all_cycles), is_dqdv)
1823
+ _print_menu(len(all_cycles), is_dqdv, fig)
1811
1824
  continue
1812
1825
  elif key == 'h':
1813
1826
  # Legend submenu: toggle visibility and move legend in inches relative to canvas center
@@ -1989,7 +2002,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
1989
2002
  print("Unknown option.")
1990
2003
  except Exception:
1991
2004
  pass
1992
- _print_menu(len(all_cycles), is_dqdv)
2005
+ _print_menu(len(all_cycles), is_dqdv, fig)
1993
2006
  continue
1994
2007
  elif key == 'p':
1995
2008
  # Print/export style or style+geometry
@@ -2097,14 +2110,14 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2097
2110
  print("Unknown choice.")
2098
2111
  except Exception as e:
2099
2112
  print(f"Error in style submenu: {e}")
2100
- _print_menu(len(all_cycles), is_dqdv)
2113
+ _print_menu(len(all_cycles), is_dqdv, fig)
2101
2114
  continue
2102
2115
  elif key == 'i':
2103
2116
  # Import style from .bps/.bpsg/.bpcfg
2104
2117
  try:
2105
2118
  path = choose_style_file(source_paths, purpose="style import")
2106
2119
  if not path:
2107
- _print_menu(len(all_cycles), is_dqdv)
2120
+ _print_menu(len(all_cycles), is_dqdv, fig)
2108
2121
  continue
2109
2122
  push_state("import-style")
2110
2123
  with open(path, 'r', encoding='utf-8') as f:
@@ -2114,7 +2127,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2114
2127
  kind = cfg.get('kind', '')
2115
2128
  if kind not in ('ec_style', 'ec_style_geom'):
2116
2129
  print("Not an EC style file.")
2117
- _print_menu(len(all_cycles), is_dqdv)
2130
+ _print_menu(len(all_cycles), is_dqdv, fig)
2118
2131
  continue
2119
2132
 
2120
2133
  # Enforce compatibility between style/geom ro state and current figure ro state
@@ -2126,7 +2139,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2126
2139
  else:
2127
2140
  print("Warning: EC style/geometry file was saved without --ro; current plot was created with --ro.")
2128
2141
  print("Not applying EC style/geometry to avoid corrupting axis orientation.")
2129
- _print_menu(len(all_cycles), is_dqdv)
2142
+ _print_menu(len(all_cycles), is_dqdv, fig)
2130
2143
  continue
2131
2144
 
2132
2145
  has_geometry = (kind == 'ec_style_geom' and 'geometry' in cfg)
@@ -2463,7 +2476,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2463
2476
 
2464
2477
  except Exception as e:
2465
2478
  print(f"Error importing style: {e}")
2466
- _print_menu(len(all_cycles), is_dqdv)
2479
+ _print_menu(len(all_cycles), is_dqdv, fig)
2467
2480
  continue
2468
2481
  elif key == 'l':
2469
2482
  # Line widths submenu: curves vs frame/ticks
@@ -2691,7 +2704,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2691
2704
  print("Unknown option.")
2692
2705
  except Exception as e:
2693
2706
  print(f"Error in line submenu: {e}")
2694
- _print_menu(len(all_cycles), is_dqdv)
2707
+ _print_menu(len(all_cycles), is_dqdv, fig)
2695
2708
  continue
2696
2709
  elif key == 'k':
2697
2710
  # Spine colors (w=top, a=left, s=bottom, d=right)
@@ -2749,7 +2762,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2749
2762
  fig.canvas.draw()
2750
2763
  except Exception as e:
2751
2764
  print(f"Error in spine color menu: {e}")
2752
- _print_menu(len(all_cycles), is_dqdv)
2765
+ _print_menu(len(all_cycles), is_dqdv, fig)
2753
2766
  continue
2754
2767
  elif key == 'r':
2755
2768
  # Rename axis labels
@@ -2819,7 +2832,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
2819
2832
  fig.canvas.draw_idle()
2820
2833
  except Exception as e:
2821
2834
  print(f"Error renaming axes: {e}")
2822
- _print_menu(len(all_cycles), is_dqdv)
2835
+ _print_menu(len(all_cycles), is_dqdv, fig)
2823
2836
  continue
2824
2837
  elif key == 't':
2825
2838
  # Unified WASD: w/a/s/d x 1..5 => spine, ticks, minor, labels, title
@@ -3015,7 +3028,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3015
3028
  fig.canvas.draw_idle()
3016
3029
  except Exception as e:
3017
3030
  print(f"Error in WASD tick visibility menu: {e}")
3018
- _print_menu(len(all_cycles), is_dqdv)
3031
+ _print_menu(len(all_cycles), is_dqdv, fig)
3019
3032
  continue
3020
3033
  elif key == 's':
3021
3034
  try:
@@ -3023,7 +3036,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3023
3036
  last_session_path = getattr(fig, '_last_session_save_path', None)
3024
3037
  folder = choose_save_path(source_paths, purpose="EC session save")
3025
3038
  if not folder:
3026
- _print_menu(len(all_cycles), is_dqdv); continue
3039
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3027
3040
  print(f"\nChosen path: {folder}")
3028
3041
  try:
3029
3042
  files = sorted([f for f in os.listdir(folder) if f.lower().endswith('.pkl')])
@@ -3044,35 +3057,35 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3044
3057
  prompt = "Enter new filename (no ext needed) or number to overwrite (q=cancel): "
3045
3058
  choice = _safe_input(prompt).strip()
3046
3059
  if not choice or choice.lower() == 'q':
3047
- _print_menu(len(all_cycles), is_dqdv); continue
3060
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3048
3061
  if choice.lower() == 'o':
3049
3062
  # Overwrite last saved session
3050
3063
  if not last_session_path:
3051
3064
  print("No previous save found.")
3052
- _print_menu(len(all_cycles), is_dqdv); continue
3065
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3053
3066
  if not os.path.exists(last_session_path):
3054
3067
  print(f"Previous save file not found: {last_session_path}")
3055
- _print_menu(len(all_cycles), is_dqdv); continue
3068
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3056
3069
  yn = _safe_input(f"Overwrite '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
3057
3070
  if yn != 'y':
3058
- _print_menu(len(all_cycles), is_dqdv); continue
3071
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3059
3072
  dump_ec_session(last_session_path, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
3060
3073
  print(f"Overwritten session to {last_session_path}")
3061
- _print_menu(len(all_cycles), is_dqdv); continue
3074
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3062
3075
  if choice.isdigit() and files:
3063
3076
  idx = int(choice)
3064
3077
  if 1 <= idx <= len(files):
3065
3078
  name = files[idx-1]
3066
3079
  yn = _safe_input(f"Overwrite '{name}'? (y/n): ").strip().lower()
3067
3080
  if yn != 'y':
3068
- _print_menu(len(all_cycles), is_dqdv); continue
3081
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3069
3082
  target = os.path.join(folder, name)
3070
3083
  dump_ec_session(target, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
3071
3084
  fig._last_session_save_path = target
3072
- _print_menu(len(all_cycles), is_dqdv); continue
3085
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3073
3086
  else:
3074
3087
  print("Invalid number.")
3075
- _print_menu(len(all_cycles), is_dqdv); continue
3088
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3076
3089
  if choice.lower() != 'o':
3077
3090
  name = choice
3078
3091
  root, ext = os.path.splitext(name)
@@ -3082,12 +3095,12 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3082
3095
  if os.path.exists(target):
3083
3096
  yn = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
3084
3097
  if yn != 'y':
3085
- _print_menu(len(all_cycles), is_dqdv); continue
3098
+ _print_menu(len(all_cycles), is_dqdv, fig); continue
3086
3099
  dump_ec_session(target, fig=fig, ax=ax, cycle_lines=cycle_lines, skip_confirm=True)
3087
3100
  fig._last_session_save_path = target
3088
3101
  except Exception as e:
3089
3102
  print(f"Save failed: {e}")
3090
- _print_menu(len(all_cycles), is_dqdv)
3103
+ _print_menu(len(all_cycles), is_dqdv, fig)
3091
3104
  continue
3092
3105
  elif key == 'c':
3093
3106
  # Show current palette if one is applied (this is informational only)
@@ -3123,7 +3136,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3123
3136
  continue
3124
3137
  if line.lower() == 'u':
3125
3138
  manage_user_colors(fig)
3126
- _print_menu(len(all_cycles), is_dqdv)
3139
+ _print_menu(len(all_cycles), is_dqdv, fig)
3127
3140
  continue
3128
3141
  tokens = line.replace(',', ' ').split()
3129
3142
  mode, cycles, mapping, palette, use_all = _parse_cycle_tokens(tokens, fig)
@@ -3252,13 +3265,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3252
3265
  if ignored:
3253
3266
  print("Ignored cycles:", ", ".join(str(c) for c in ignored))
3254
3267
  # Show the menu again after completing the command
3255
- _print_menu(len(all_cycles), is_dqdv)
3268
+ _print_menu(len(all_cycles), is_dqdv, fig)
3256
3269
  continue
3257
3270
  elif key == 'a':
3258
3271
  # X-axis submenu: number-of-ions vs capacity (not available in dQdV mode)
3259
3272
  if is_dqdv:
3260
3273
  print("Capacity/ion conversion is not available in dQ/dV mode.")
3261
- _print_menu(len(all_cycles), is_dqdv)
3274
+ _print_menu(len(all_cycles), is_dqdv, fig)
3262
3275
  continue
3263
3276
  # X-axis submenu: number-of-ions vs capacity
3264
3277
  while True:
@@ -3399,7 +3412,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3399
3412
  fig.canvas.draw()
3400
3413
  except Exception:
3401
3414
  fig.canvas.draw_idle()
3402
- _print_menu(len(all_cycles), is_dqdv)
3415
+ _print_menu(len(all_cycles), is_dqdv, fig)
3403
3416
  continue
3404
3417
  elif key == 'f':
3405
3418
  # Font submenu with numbered options
@@ -3470,7 +3483,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3470
3483
  print("Size must be positive.")
3471
3484
  except Exception:
3472
3485
  print("Invalid size.")
3473
- _print_menu(len(all_cycles), is_dqdv)
3486
+ _print_menu(len(all_cycles), is_dqdv, fig)
3474
3487
  continue
3475
3488
  elif key == 'x':
3476
3489
  # X-axis: set limits only
@@ -3554,7 +3567,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3554
3567
  fig.canvas.draw()
3555
3568
  except Exception:
3556
3569
  print("Invalid limits, ignored.")
3557
- _print_menu(len(all_cycles), is_dqdv)
3570
+ _print_menu(len(all_cycles), is_dqdv, fig)
3558
3571
  continue
3559
3572
  elif key == 'y':
3560
3573
  # Y-axis: set limits only
@@ -3638,7 +3651,7 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3638
3651
  fig.canvas.draw()
3639
3652
  except Exception:
3640
3653
  print("Invalid limits, ignored.")
3641
- _print_menu(len(all_cycles), is_dqdv)
3654
+ _print_menu(len(all_cycles), is_dqdv, fig)
3642
3655
  continue
3643
3656
  elif key == 'g':
3644
3657
  # Geometry submenu: plot frame vs canvas (scales moved to separate keys)
@@ -3667,13 +3680,13 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
3667
3680
  fig.canvas.draw()
3668
3681
  except Exception:
3669
3682
  fig.canvas.draw_idle()
3670
- _print_menu(len(all_cycles), is_dqdv)
3683
+ _print_menu(len(all_cycles), is_dqdv, fig)
3671
3684
  continue
3672
3685
  elif key == 'sm':
3673
3686
  # dQ/dV smoothing utilities (only available in dQdV mode)
3674
3687
  if not is_dqdv:
3675
3688
  print("Smoothing is only available in dQ/dV mode.")
3676
- _print_menu(len(all_cycles), is_dqdv)
3689
+ _print_menu(len(all_cycles), is_dqdv, fig)
3677
3690
  continue
3678
3691
  while True:
3679
3692
  print("\n\033[1mdQ/dV Data Filtering (Neware method)\033[0m")
@@ -4033,11 +4046,11 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
4033
4046
  print("Invalid number.")
4034
4047
  continue
4035
4048
  print("Unknown command. Use a/o/r/q.")
4036
- _print_menu(len(all_cycles), is_dqdv)
4049
+ _print_menu(len(all_cycles), is_dqdv, fig)
4037
4050
  continue
4038
4051
  else:
4039
4052
  print("Unknown command.")
4040
- _print_menu(len(all_cycles), is_dqdv)
4053
+ _print_menu(len(all_cycles), is_dqdv, fig)
4041
4054
 
4042
4055
 
4043
4056
  def _get_geometry_snapshot(fig, ax) -> Dict:
batplot/interactive.py CHANGED
@@ -260,6 +260,18 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
260
260
  col1.append("j: CIF titles")
261
261
  col2 = ["a: rearrange", "o: offset", "r: rename", "x: change X", "y: change Y", "d: derivative"]
262
262
  col3 = ["v: find peaks", "n: crosshair", "p: print(export) style/geom", "i: import style/geom", "e: export figure", "s: save project", "b: undo", "q: quit"]
263
+
264
+ # Conditional overwrite shortcuts under (Options)
265
+ last_session = getattr(fig, "_last_session_save_path", None)
266
+ last_style = getattr(fig, "_last_style_export_path", None)
267
+ last_figure = getattr(fig, "_last_figure_export_path", None)
268
+ if last_session:
269
+ col3.append("os: overwrite session")
270
+ if last_style:
271
+ col3.append("ops: overwrite style")
272
+ col3.append("opsg: overwrite style+geom")
273
+ if last_figure:
274
+ col3.append("oe: overwrite figure")
263
275
 
264
276
  # Hide offset/y-range in stack mode
265
277
  if args.stack:
@@ -909,7 +921,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
909
921
  )
910
922
 
911
923
  # NEW: export current style to .bpcfg
912
- def export_style_config(filename, base_path=None, overwrite_path=None):
924
+ def export_style_config(filename, base_path=None, overwrite_path=None, force_kind=None):
913
925
  cts = getattr(_bp, 'cif_tick_series', None) if _bp is not None else None
914
926
  show_titles = bool(getattr(_bp, 'show_cif_titles', True)) if _bp is not None else True
915
927
  from .style import export_style_config as _export_style_config
@@ -928,6 +940,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
928
940
  base_path,
929
941
  show_cif_titles=show_titles,
930
942
  overwrite_path=overwrite_path,
943
+ force_kind=force_kind,
931
944
  )
932
945
 
933
946
  # NEW: apply imported style config (restricted application)
@@ -2079,6 +2092,141 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
2079
2092
  except Exception as e:
2080
2093
  print(f"Error toggling crosshair: {e}")
2081
2094
  continue
2095
+ elif key == 'os':
2096
+ # Quick overwrite of last saved session (.pkl)
2097
+ try:
2098
+ last_session_path = getattr(fig, '_last_session_save_path', None)
2099
+ if not last_session_path:
2100
+ print("No previous session save found.")
2101
+ continue
2102
+ if not os.path.exists(last_session_path):
2103
+ print(f"Previous save file not found: {last_session_path}")
2104
+ continue
2105
+ yn = _safe_input(f"Overwrite session '{os.path.basename(last_session_path)}'? (y/n): ").strip().lower()
2106
+ if yn != 'y':
2107
+ print("Canceled.")
2108
+ continue
2109
+ _bp_dump_session(
2110
+ last_session_path,
2111
+ fig=fig,
2112
+ ax=ax,
2113
+ x_data_list=x_data_list,
2114
+ y_data_list=y_data_list,
2115
+ orig_y=orig_y,
2116
+ offsets_list=offsets_list,
2117
+ labels=labels,
2118
+ delta=delta,
2119
+ args=args,
2120
+ tick_state=tick_state,
2121
+ cif_tick_series=(getattr(_bp, 'cif_tick_series', None) if _bp is not None else None),
2122
+ cif_hkl_map=(getattr(_bp, 'cif_hkl_map', None) if _bp is not None else None),
2123
+ cif_hkl_label_map=(getattr(_bp, 'cif_hkl_label_map', None) if _bp is not None else None),
2124
+ show_cif_hkl=(bool(getattr(_bp, 'show_cif_hkl', False)) if _bp is not None else False),
2125
+ show_cif_titles=(bool(getattr(_bp, 'show_cif_titles', True)) if _bp is not None else True),
2126
+ skip_confirm=True,
2127
+ )
2128
+ fig._last_session_save_path = last_session_path
2129
+ print(f"Overwritten session to {last_session_path}")
2130
+ except Exception as e:
2131
+ print(f"Error overwriting session: {e}")
2132
+ continue
2133
+ elif key in ('ops', 'opsg'):
2134
+ # Quick overwrite of last exported style file (.bps / .bpsg)
2135
+ try:
2136
+ last_style_path = getattr(fig, '_last_style_export_path', None)
2137
+ if not last_style_path:
2138
+ print("No previous style export found.")
2139
+ continue
2140
+ if not os.path.exists(last_style_path):
2141
+ print(f"Previous style file not found: {last_style_path}")
2142
+ continue
2143
+ if key == 'ops':
2144
+ mode = 'ps'
2145
+ label = "style-only"
2146
+ else:
2147
+ mode = 'psg'
2148
+ label = "style+geometry"
2149
+ yn = _safe_input(
2150
+ f"Overwrite {label} file '{os.path.basename(last_style_path)}'? (y/n): "
2151
+ ).strip().lower()
2152
+ if yn != 'y':
2153
+ print("Canceled.")
2154
+ continue
2155
+ exported = export_style_config(
2156
+ None,
2157
+ base_path=None,
2158
+ overwrite_path=last_style_path,
2159
+ force_kind=mode,
2160
+ )
2161
+ if exported:
2162
+ fig._last_style_export_path = exported
2163
+ print(f"Overwritten {label} style to {exported}")
2164
+ except Exception as e:
2165
+ print(f"Error overwriting style: {e}")
2166
+ continue
2167
+ elif key == 'oe':
2168
+ # Quick overwrite of last exported figure
2169
+ try:
2170
+ last_figure_path = getattr(fig, '_last_figure_export_path', None)
2171
+ if not last_figure_path:
2172
+ print("No previous figure export found.")
2173
+ continue
2174
+ if not os.path.exists(last_figure_path):
2175
+ print(f"Previous export file not found: {last_figure_path}")
2176
+ continue
2177
+ yn = _safe_input(
2178
+ f"Overwrite figure '{os.path.basename(last_figure_path)}'? (y/n): "
2179
+ ).strip().lower()
2180
+ if yn != 'y':
2181
+ print("Canceled.")
2182
+ continue
2183
+ export_target = last_figure_path
2184
+ from .utils import ensure_exact_case_filename
2185
+ export_target = ensure_exact_case_filename(export_target)
2186
+ # Temporarily remove numbering for export
2187
+ for i, txt in enumerate(label_text_objects):
2188
+ txt.set_text(labels[i])
2189
+ _, _ext = os.path.splitext(export_target)
2190
+ if _ext.lower() == '.svg':
2191
+ try:
2192
+ _fig_fc = fig.get_facecolor()
2193
+ except Exception:
2194
+ _fig_fc = None
2195
+ try:
2196
+ _ax_fc = ax.get_facecolor()
2197
+ except Exception:
2198
+ _ax_fc = None
2199
+ try:
2200
+ if getattr(fig, 'patch', None) is not None:
2201
+ fig.patch.set_alpha(0.0); fig.patch.set_facecolor('none')
2202
+ if getattr(ax, 'patch', None) is not None:
2203
+ ax.patch.set_alpha(0.0); ax.patch.set_facecolor('none')
2204
+ except Exception:
2205
+ pass
2206
+ try:
2207
+ fig.savefig(export_target, dpi=300, transparent=True, facecolor='none', edgecolor='none')
2208
+ finally:
2209
+ try:
2210
+ if _fig_fc is not None and getattr(fig, 'patch', None) is not None:
2211
+ fig.patch.set_alpha(1.0); fig.patch.set_facecolor(_fig_fc)
2212
+ except Exception:
2213
+ pass
2214
+ try:
2215
+ if _ax_fc is not None and getattr(ax, 'patch', None) is not None:
2216
+ ax.patch.set_alpha(1.0); ax.patch.set_facecolor(_ax_fc)
2217
+ except Exception:
2218
+ pass
2219
+ else:
2220
+ fig.savefig(export_target, dpi=300)
2221
+ print(f"Figure saved to {export_target}")
2222
+ fig._last_figure_export_path = export_target
2223
+ # Restore numbering
2224
+ for i, txt in enumerate(label_text_objects):
2225
+ txt.set_text(f"{i+1}: {labels[i]}")
2226
+ fig.canvas.draw()
2227
+ except Exception as e:
2228
+ print(f"Error overwriting figure: {e}")
2229
+ continue
2082
2230
  elif key == 's':
2083
2231
  # Save current interactive session with numbered overwrite picker
2084
2232
  try:
@@ -4434,8 +4582,13 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
4434
4582
  else:
4435
4583
  print(f" {_i}: {fname}")
4436
4584
  last_style_path = getattr(fig, '_last_style_export_path', None)
4437
- if last_style_path:
4585
+ n_style = len(style_file_list) if style_file_list else 0
4586
+ if last_style_path and n_style:
4587
+ sub = _safe_input(colorize_prompt("Style submenu: (e=export, o=overwrite last, q=return, r=refresh). Press number to overwrite: ")).strip().lower()
4588
+ elif last_style_path:
4438
4589
  sub = _safe_input(colorize_prompt("Style submenu: (e=export, o=overwrite last, q=return, r=refresh): ")).strip().lower()
4590
+ elif n_style:
4591
+ sub = _safe_input(colorize_prompt("Style submenu: (e=export, q=return, r=refresh). Press number to overwrite: ")).strip().lower()
4439
4592
  else:
4440
4593
  sub = _safe_input(colorize_prompt("Style submenu: (e=export, q=return, r=refresh): ")).strip().lower()
4441
4594
  if sub == 'q':
@@ -4459,6 +4612,19 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
4459
4612
  fig._last_style_export_path = exported_path
4460
4613
  style_menu_active = False
4461
4614
  break
4615
+ if sub.isdigit() and n_style and 1 <= int(sub) <= n_style:
4616
+ # Overwrite listed style file by number (same as e → export → number)
4617
+ idx = int(sub) - 1
4618
+ target_path = style_file_list[idx][1]
4619
+ fname = style_file_list[idx][0]
4620
+ yn = _safe_input(f"Overwrite '{fname}'? (y/n): ").strip().lower()
4621
+ if yn != 'y':
4622
+ continue
4623
+ exported_path = export_style_config(None, base_path=None, overwrite_path=target_path)
4624
+ if exported_path:
4625
+ fig._last_style_export_path = exported_path
4626
+ style_menu_active = False
4627
+ break
4462
4628
  if sub == 'e':
4463
4629
  save_base = choose_save_path(source_file_paths, purpose="style export")
4464
4630
  if not save_base:
@@ -4481,6 +4647,12 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
4481
4647
  if not fname:
4482
4648
  print("Style import canceled.")
4483
4649
  continue
4650
+ import os
4651
+ bname = os.path.basename(fname)
4652
+ yn = _safe_input(colorize_prompt(f"Apply style '{bname}'? (y/n): ")).strip().lower()
4653
+ if yn != 'y':
4654
+ print("Style import canceled.")
4655
+ continue
4484
4656
  push_state("style-import")
4485
4657
  apply_style_config(fname)
4486
4658
  except Exception as e:
@@ -5310,6 +5482,7 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
5310
5482
 
5311
5483
  print("\n--- Peak Report ---")
5312
5484
  print(f"X range used: {x_min} .. {x_max} (relative height threshold={min_frac})")
5485
+ all_peak_results = [] # list of (curve_index, label, [(x, y), ...])
5313
5486
  for i, (x_arr, y_off) in enumerate(zip(x_data_list, y_data_list)):
5314
5487
  # Recover original curve (remove vertical offset)
5315
5488
  if i < len(offsets_list):
@@ -5363,9 +5536,50 @@ def interactive_menu(fig, ax, y_data_list, x_data_list, labels, orig_y,
5363
5536
  last_idx = pi
5364
5537
 
5365
5538
  print(" Peaks (x, y):")
5539
+ peak_xy_list = []
5366
5540
  for pi in peaks:
5541
+ px, py = float(x_sel[pi]), float(y_sel[pi])
5542
+ peak_xy_list.append((px, py))
5367
5543
  print(f" x={x_sel[pi]:.6g}, y={y_sel[pi]:.6g}")
5544
+ if peak_xy_list:
5545
+ all_peak_results.append((i + 1, label, peak_xy_list))
5368
5546
  print("\n--- End Peak Report ---\n")
5547
+
5548
+ # Export peaks to file
5549
+ if all_peak_results:
5550
+ export_yn = _safe_input("Export peaks to file? (y/n): ").strip().lower()
5551
+ if export_yn == 'y':
5552
+ folder = choose_save_path(source_file_paths, purpose="peak export")
5553
+ if folder:
5554
+ print(f"\nChosen path: {folder}")
5555
+ fname = _safe_input("Export filename (default: peaks.txt): ").strip()
5556
+ if not fname:
5557
+ fname = "peaks.txt"
5558
+ if not fname.endswith('.txt'):
5559
+ fname += '.txt'
5560
+ import os
5561
+ target = fname if os.path.isabs(fname) else os.path.join(folder, fname)
5562
+ do_write = not os.path.exists(target)
5563
+ if os.path.exists(target):
5564
+ ow = _safe_input(f"'{os.path.basename(target)}' exists. Overwrite? (y/n): ").strip().lower()
5565
+ if ow == 'y':
5566
+ do_write = True
5567
+ else:
5568
+ print("Export canceled.")
5569
+ if do_write:
5570
+ try:
5571
+ with open(target, 'w') as f:
5572
+ f.write("# Curve\tLabel\tPeak x\tPeak y\n")
5573
+ for curve_idx, label, peak_xy_list in all_peak_results:
5574
+ for px, py in peak_xy_list:
5575
+ f.write(f"{curve_idx}\t{label}\t{px:.6g}\t{py:.6g}\n")
5576
+ total_peaks = sum(len(pairs) for _, _, pairs in all_peak_results)
5577
+ print(f"Peak positions exported to {target}")
5578
+ print(f"Found {total_peaks} peaks across {len(all_peak_results)} curves.")
5579
+ except Exception as e:
5580
+ print(f"Error saving file: {e}")
5581
+ else:
5582
+ print("Export canceled.")
5369
5583
  except Exception as e:
5370
5584
  print(f"Error finding peaks: {e}")
5371
5585