Source code for hbat.gui.geometry_cutoffs_dialog

"""
Geometry cutoffs configuration dialog for HBAT GUI.

This module provides a dialog for configuring molecular interaction
analysis parameters (distances, angles, etc.) without PDB fixing options.
"""

import tkinter as tk
from tkinter import messagebox, ttk
from typing import Any, Dict, Optional


[docs] class ToolTip: """Simple tooltip widget for providing help text on hover."""
[docs] def __init__(self, widget, text: str, delay: int = 500): self.widget = widget self.text = text self.delay = delay self.tooltip = None self.id = None self.widget.bind("<Enter>", self.enter, "+") self.widget.bind("<Leave>", self.leave, "+") self.widget.bind("<Motion>", self.motion, "+")
[docs] def enter(self, event=None): self.schedule()
[docs] def leave(self, event=None): self.unschedule() self.hide()
[docs] def motion(self, event=None): self.unschedule() self.schedule()
[docs] def schedule(self): self.unschedule() self.id = self.widget.after(self.delay, self.show)
[docs] def unschedule(self): if self.id: self.widget.after_cancel(self.id) self.id = None
[docs] def show(self): if self.tooltip: return x, y, _, _ = ( self.widget.bbox("insert") if hasattr(self.widget, "bbox") else (0, 0, 0, 0) ) x += self.widget.winfo_rootx() + 25 y += self.widget.winfo_rooty() + 25 self.tooltip = tk.Toplevel(self.widget) self.tooltip.wm_overrideredirect(True) self.tooltip.wm_geometry(f"+{x}+{y}") label = tk.Label( self.tooltip, text=self.text, background="lightyellow", relief="solid", borderwidth=1, font=("TkDefaultFont", "8"), wraplength=300, justify="left", ) label.pack()
[docs] def hide(self): if self.tooltip: self.tooltip.destroy() self.tooltip = None
from ..constants.parameters import ( AnalysisModes, AnalysisParameters, ParameterRanges, ParametersDefault, )
[docs] class GeometryCutoffsDialog: """Dialog for configuring comprehensive molecular interaction parameters. Provides GUI interface for setting parameters for multiple interaction types: - **Hydrogen Bonds:** Classical strong interactions (N/O-H···O/N) - **Weak Hydrogen Bonds:** C-H···O interactions (important for binding) - **Halogen Bonds:** C-X···A interactions (X = Cl, Br, I) with 150° default - **π Interactions:** Multiple subtypes including: - Hydrogen-π: C-H···π, N-H···π, O-H···π, S-H···π - Halogen-π: C-Cl···π, C-Br···π, C-I···π Uses tabbed interface to organize parameters by interaction type. """
[docs] def __init__( self, parent: tk.Tk, current_params: Optional[AnalysisParameters] = None ): """Initialize geometry cutoffs dialog. :param parent: Parent window :type parent: tk.Tk :param current_params: Current analysis parameters :type current_params: Optional[AnalysisParameters] """ self.parent = parent self.current_params = current_params or AnalysisParameters() self.result = None # Create dialog window self.dialog = tk.Toplevel(parent) self.dialog.title("Geometry Cutoffs") self.dialog.geometry("1200x600") self.dialog.resizable(True, True) # Make dialog modal self.dialog.transient(parent) self.dialog.grab_set() # Initialize variables self._init_variables() # Create widgets self._create_widgets() # Center the dialog self.dialog.update_idletasks() x = (self.dialog.winfo_screenwidth() // 2) - (self.dialog.winfo_width() // 2) y = (self.dialog.winfo_screenheight() // 2) - (self.dialog.winfo_height() // 2) self.dialog.geometry(f"+{x}+{y}") # Handle window closing self.dialog.protocol("WM_DELETE_WINDOW", self._cancel) # Set initial values self.set_parameters(self.current_params)
def _init_variables(self): """Initialize tkinter variables with default values.""" self.analysis_mode = tk.StringVar(value=ParametersDefault.ANALYSIS_MODE) self.covalent_factor = tk.DoubleVar( value=ParametersDefault.COVALENT_CUTOFF_FACTOR ) self.hb_distance = tk.DoubleVar(value=ParametersDefault.HB_DISTANCE_CUTOFF) self.hb_angle = tk.DoubleVar(value=ParametersDefault.HB_ANGLE_CUTOFF) self.da_distance = tk.DoubleVar(value=ParametersDefault.HB_DA_DISTANCE) self.whb_distance = tk.DoubleVar(value=ParametersDefault.WHB_DISTANCE_CUTOFF) self.whb_angle = tk.DoubleVar(value=ParametersDefault.WHB_ANGLE_CUTOFF) self.whb_da_distance = tk.DoubleVar(value=ParametersDefault.WHB_DA_DISTANCE) self.xb_distance = tk.DoubleVar(value=ParametersDefault.XB_DISTANCE_CUTOFF) self.xb_angle = tk.DoubleVar(value=ParametersDefault.XB_ANGLE_CUTOFF) self.pi_distance = tk.DoubleVar(value=ParametersDefault.PI_DISTANCE_CUTOFF) self.pi_angle = tk.DoubleVar(value=ParametersDefault.PI_ANGLE_CUTOFF) # Initialize π interaction subtype variables (needed for set_parameters) self.pi_ccl_distance = None self.pi_ccl_angle = None self.pi_cbr_distance = None self.pi_cbr_angle = None self.pi_ci_distance = None self.pi_ci_angle = None self.pi_ch_distance = None self.pi_ch_angle = None self.pi_nh_distance = None self.pi_nh_angle = None self.pi_oh_distance = None self.pi_oh_angle = None self.pi_sh_distance = None self.pi_sh_angle = None # New interaction type variables self.pi_pi_distance = tk.DoubleVar( value=ParametersDefault.PI_PI_DISTANCE_CUTOFF ) self.pi_pi_parallel_angle = tk.DoubleVar( value=ParametersDefault.PI_PI_PARALLEL_ANGLE_CUTOFF ) self.pi_pi_tshaped_angle_min = tk.DoubleVar( value=ParametersDefault.PI_PI_TSHAPED_ANGLE_MIN ) self.pi_pi_tshaped_angle_max = tk.DoubleVar( value=ParametersDefault.PI_PI_TSHAPED_ANGLE_MAX ) self.pi_pi_offset = tk.DoubleVar(value=ParametersDefault.PI_PI_OFFSET_CUTOFF) self.carbonyl_distance = tk.DoubleVar( value=ParametersDefault.CARBONYL_DISTANCE_CUTOFF ) self.carbonyl_angle_min = tk.DoubleVar( value=ParametersDefault.CARBONYL_ANGLE_MIN ) self.carbonyl_angle_max = tk.DoubleVar( value=ParametersDefault.CARBONYL_ANGLE_MAX ) self.n_pi_distance = tk.DoubleVar(value=ParametersDefault.N_PI_DISTANCE_CUTOFF) self.n_pi_sulfur_distance = tk.DoubleVar( value=ParametersDefault.N_PI_SULFUR_DISTANCE_CUTOFF ) self.n_pi_angle_min = tk.DoubleVar(value=ParametersDefault.N_PI_ANGLE_MIN) self.n_pi_angle_max = tk.DoubleVar(value=ParametersDefault.N_PI_ANGLE_MAX) # Store parameter values directly self._param_values = {} def _store_current_values(self): """Store current parameter values before switching categories.""" try: self._param_values.update( { "hb_distance": ( self.hb_distance.get() if hasattr(self, "hb_distance") else ParametersDefault.HB_DISTANCE_CUTOFF ), "hb_angle": ( self.hb_angle.get() if hasattr(self, "hb_angle") else ParametersDefault.HB_ANGLE_CUTOFF ), "da_distance": ( self.da_distance.get() if hasattr(self, "da_distance") else ParametersDefault.HB_DA_DISTANCE ), "whb_distance": ( self.whb_distance.get() if hasattr(self, "whb_distance") else ParametersDefault.WHB_DISTANCE_CUTOFF ), "whb_angle": ( self.whb_angle.get() if hasattr(self, "whb_angle") else ParametersDefault.WHB_ANGLE_CUTOFF ), "whb_da_distance": ( self.whb_da_distance.get() if hasattr(self, "whb_da_distance") else ParametersDefault.WHB_DA_DISTANCE ), "xb_distance": ( self.xb_distance.get() if hasattr(self, "xb_distance") else ParametersDefault.XB_DISTANCE_CUTOFF ), "xb_angle": ( self.xb_angle.get() if hasattr(self, "xb_angle") else ParametersDefault.XB_ANGLE_CUTOFF ), "pi_distance": ( self.pi_distance.get() if hasattr(self, "pi_distance") else ParametersDefault.PI_DISTANCE_CUTOFF ), "pi_angle": ( self.pi_angle.get() if hasattr(self, "pi_angle") else ParametersDefault.PI_ANGLE_CUTOFF ), "covalent_factor": ( self.covalent_factor.get() if hasattr(self, "covalent_factor") else ParametersDefault.COVALENT_CUTOFF_FACTOR ), "analysis_mode": ( self.analysis_mode.get() if hasattr(self, "analysis_mode") else ParametersDefault.ANALYSIS_MODE ), } ) # Store π interaction subtype values if they exist pi_vars = [ "pi_ccl_distance", "pi_ccl_angle", "pi_cbr_distance", "pi_cbr_angle", "pi_ci_distance", "pi_ci_angle", "pi_ch_distance", "pi_ch_angle", "pi_nh_distance", "pi_nh_angle", "pi_oh_distance", "pi_oh_angle", "pi_sh_distance", "pi_sh_angle", "pi_pi_distance", "pi_pi_parallel_angle", "pi_pi_tshaped_angle_min", "pi_pi_tshaped_angle_max", "pi_pi_offset", "carbonyl_distance", "carbonyl_angle_min", "carbonyl_angle_max", "n_pi_distance", "n_pi_sulfur_distance", "n_pi_angle_min", "n_pi_angle_max", ] for var_name in pi_vars: if hasattr(self, var_name): var = getattr(self, var_name) if var: self._param_values[var_name] = var.get() except tk.TclError: pass # Variables destroyed, ignore def _create_widgets(self) -> None: """Create and layout all parameter widgets with list selection interface. :returns: None :rtype: None """ # Main frame with padding main_frame = ttk.Frame(self.dialog, padding="20") main_frame.pack(fill=tk.BOTH, expand=True) # Create container for content content_frame = ttk.Frame(main_frame) content_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15)) # Create paned window for list and content paned = ttk.PanedWindow(content_frame, orient=tk.HORIZONTAL) paned.pack(fill=tk.BOTH, expand=True) # Left side - Category list list_frame = ttk.Frame(paned, relief=tk.GROOVE, borderwidth=1) paned.add(list_frame, weight=1) # List label ttk.Label( list_frame, text="Parameter Categories", font=("TkDefaultFont", 10, "bold") ).pack(pady=(10, 5)) # Create listbox for categories self.category_listbox = tk.Listbox(list_frame, selectmode=tk.SINGLE, height=10) self.category_listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # Add categories categories = [ "General Parameters", "Hydrogen Bonds", "Weak Hydrogen Bonds", "Halogen Bonds", "π Interactions", "π-π Stacking", "Carbonyl Interactions", "n→π* Interactions", ] for cat in categories: self.category_listbox.insert(tk.END, cat) # Bind selection event self.category_listbox.bind("<<ListboxSelect>>", self._on_category_selected) # Right side - Content area self.content_container = ttk.Frame(paned) paned.add(self.content_container, weight=3) # Create scrollable area for content self.content_canvas = tk.Canvas(self.content_container) scrollbar = ttk.Scrollbar( self.content_container, orient="vertical", command=self.content_canvas.yview ) self.content_frame = ttk.Frame(self.content_canvas) self.content_frame.bind( "<Configure>", lambda e: self.content_canvas.configure( scrollregion=self.content_canvas.bbox("all") ), ) self.content_canvas.create_window( (0, 0), window=self.content_frame, anchor="nw" ) self.content_canvas.configure(yscrollcommand=scrollbar.set) # Enable mouse wheel scrolling def _on_mousewheel(event): self.content_canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") self.content_canvas.bind("<MouseWheel>", _on_mousewheel) # Windows self.content_canvas.bind( "<Button-4>", lambda e: self.content_canvas.yview_scroll(-1, "units") ) # Linux self.content_canvas.bind( "<Button-5>", lambda e: self.content_canvas.yview_scroll(1, "units") ) # Linux # Pack canvas and scrollbar self.content_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Store current content widget self.current_content = None # Select first category by default self.category_listbox.selection_set(0) self._on_category_selected(None) # Buttons at bottom button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, side=tk.BOTTOM) ttk.Button(button_frame, text="OK", command=self._ok).pack( side=tk.RIGHT, padx=(5, 0) ) ttk.Button(button_frame, text="Cancel", command=self._cancel).pack( side=tk.RIGHT ) ttk.Button( button_frame, text="Reset to Defaults", command=self._set_defaults ).pack(side=tk.LEFT, padx=(0, 5)) ttk.Button( button_frame, text="Manage Presets...", command=self._open_preset_manager ).pack(side=tk.LEFT, padx=5) def _create_general_parameters(self, parent): """Create general analysis parameters.""" group = ttk.LabelFrame(parent, text="General Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # Analysis mode ttk.Label(group, text="Analysis Mode:").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_mode = self._param_values.get( "analysis_mode", ParametersDefault.ANALYSIS_MODE ) self.analysis_mode = tk.StringVar(value=stored_mode) mode_frame = ttk.Frame(group) mode_frame.grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) ttk.Radiobutton( mode_frame, text="Complete PDB Analysis", variable=self.analysis_mode, value="complete", ).pack(anchor=tk.W) ttk.Radiobutton( mode_frame, text="Local Interactions Only", variable=self.analysis_mode, value="local", ).pack(anchor=tk.W) # Covalent bond cutoff factor ttk.Label(group, text="Covalent Bond Factor:").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_factor = self._param_values.get( "covalent_factor", ParametersDefault.COVALENT_CUTOFF_FACTOR ) self.covalent_factor = tk.DoubleVar(value=stored_factor) ttk.Scale( group, from_=ParameterRanges.MIN_COVALENT_FACTOR, to=ParameterRanges.MAX_COVALENT_FACTOR, variable=self.covalent_factor, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) # Value display factor_label = ttk.Label(group, text="") factor_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_factor_label(*args): try: factor_label.config(text=f"{self.covalent_factor.get():.2f}") except tk.TclError: pass # Widget destroyed, ignore self.covalent_factor.trace("w", update_factor_label) update_factor_label() def _on_category_selected(self, event): """Handle category selection from list.""" selection = self.category_listbox.curselection() if not selection: return # Store current values before switching self._store_current_values() # Clear current content if self.current_content: self.current_content.destroy() # Create new content frame self.current_content = ttk.Frame(self.content_frame, padding="20") self.current_content.pack(fill=tk.BOTH, expand=True) # Show appropriate content based on selection category_index = selection[0] if category_index == 0: # General Parameters self._create_general_parameters(self.current_content) elif category_index == 1: # Hydrogen Bonds self._create_hydrogen_bond_parameters(self.current_content) elif category_index == 2: # Weak Hydrogen Bonds self._create_weak_hydrogen_bond_parameters(self.current_content) elif category_index == 3: # Halogen Bonds self._create_halogen_bond_parameters(self.current_content) elif category_index == 4: # π Interactions self._create_pi_interaction_parameters(self.current_content) elif category_index == 5: # π-π Stacking self._create_pi_pi_stacking_parameters(self.current_content) elif category_index == 6: # Carbonyl Interactions self._create_carbonyl_interaction_parameters(self.current_content) elif category_index == 7: # n→π* Interactions self._create_n_pi_interaction_parameters(self.current_content) # Reset scroll position self.content_canvas.yview_moveto(0) def _create_hydrogen_bond_parameters(self, parent): """Create hydrogen bond parameter controls.""" group = ttk.LabelFrame(parent, text="Hydrogen Bond Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # H...A distance ttk.Label(group, text="H...A Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_dist = self._param_values.get( "hb_distance", ParametersDefault.HB_DISTANCE_CUTOFF ) self.hb_distance = tk.DoubleVar(value=stored_dist) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.hb_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) hb_dist_label = ttk.Label(group, text="") hb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_hb_dist(*args): try: hb_dist_label.config(text=f"{self.hb_distance.get():.1f}") except tk.TclError: pass self.hb_distance.trace("w", update_hb_dist) update_hb_dist() # D-H...A angle ttk.Label(group, text="D-H...A Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_angle = self._param_values.get( "hb_angle", ParametersDefault.HB_ANGLE_CUTOFF ) self.hb_angle = tk.DoubleVar(value=stored_angle) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.hb_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) hb_angle_label = ttk.Label(group, text="") hb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_hb_angle(*args): try: hb_angle_label.config(text=f"{self.hb_angle.get():.0f}") except tk.TclError: pass self.hb_angle.trace("w", update_hb_angle) update_hb_angle() # D...A distance ttk.Label(group, text="D...A Distance (Å):").grid( row=2, column=0, sticky=tk.W, pady=2 ) stored_da = self._param_values.get( "da_distance", ParametersDefault.HB_DA_DISTANCE ) self.da_distance = tk.DoubleVar(value=stored_da) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.da_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) da_dist_label = ttk.Label(group, text="") da_dist_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_da_dist(*args): try: da_dist_label.config(text=f"{self.da_distance.get():.1f}") except tk.TclError: pass self.da_distance.trace("w", update_da_dist) update_da_dist() def _create_weak_hydrogen_bond_parameters(self, parent): """Create weak hydrogen bond parameter controls for carbon donors.""" group = ttk.LabelFrame( parent, text="Weak Hydrogen Bond Parameters (Carbon Donors)", padding=10 ) group.pack(fill=tk.X, padx=10, pady=5) # WHB H...A distance ttk.Label(group, text="H...A Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_whb_dist = self._param_values.get( "whb_distance", ParametersDefault.WHB_DISTANCE_CUTOFF ) self.whb_distance = tk.DoubleVar(value=stored_whb_dist) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.whb_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) whb_dist_label = ttk.Label(group, text="") whb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_whb_dist(*args): try: whb_dist_label.config(text=f"{self.whb_distance.get():.1f}") except tk.TclError: pass self.whb_distance.trace("w", update_whb_dist) update_whb_dist() # WHB D-H...A angle ttk.Label(group, text="D-H...A Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_whb_angle = self._param_values.get( "whb_angle", ParametersDefault.WHB_ANGLE_CUTOFF ) self.whb_angle = tk.DoubleVar(value=stored_whb_angle) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.whb_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) whb_angle_label = ttk.Label(group, text="") whb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_whb_angle(*args): try: whb_angle_label.config(text=f"{self.whb_angle.get():.0f}") except tk.TclError: pass self.whb_angle.trace("w", update_whb_angle) update_whb_angle() # WHB D...A distance ttk.Label(group, text="D...A Distance (Å):").grid( row=2, column=0, sticky=tk.W, pady=2 ) stored_whb_da = self._param_values.get( "whb_da_distance", ParametersDefault.WHB_DA_DISTANCE ) self.whb_da_distance = tk.DoubleVar(value=stored_whb_da) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.whb_da_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) whb_da_dist_label = ttk.Label(group, text="") whb_da_dist_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_whb_da_dist(*args): try: whb_da_dist_label.config(text=f"{self.whb_da_distance.get():.1f}") except tk.TclError: pass self.whb_da_distance.trace("w", update_whb_da_dist) update_whb_da_dist() def _create_halogen_bond_parameters(self, parent): """Create halogen bond parameter controls.""" group = ttk.LabelFrame(parent, text="Halogen Bond Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # X...A distance ttk.Label(group, text="X...A Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_xb_dist = self._param_values.get( "xb_distance", ParametersDefault.XB_DISTANCE_CUTOFF ) self.xb_distance = tk.DoubleVar(value=stored_xb_dist) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.xb_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) xb_dist_label = ttk.Label(group, text="") xb_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_xb_dist(*args): try: xb_dist_label.config(text=f"{self.xb_distance.get():.1f}") except tk.TclError: pass self.xb_distance.trace("w", update_xb_dist) update_xb_dist() # C-X...A angle ttk.Label(group, text="C-X...A Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_xb_angle = self._param_values.get( "xb_angle", ParametersDefault.XB_ANGLE_CUTOFF ) self.xb_angle = tk.DoubleVar(value=stored_xb_angle) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.xb_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) xb_angle_label = ttk.Label(group, text="") xb_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_xb_angle(*args): try: xb_angle_label.config(text=f"{self.xb_angle.get():.0f}") except tk.TclError: pass self.xb_angle.trace("w", update_xb_angle) update_xb_angle() def _create_pi_interaction_parameters(self, parent): """Create π interaction parameter controls.""" # General π interaction parameters general_group = ttk.LabelFrame( parent, text="General π Interaction Parameters", padding=10 ) general_group.pack(fill=tk.X, padx=10, pady=5) # H...π distance ttk.Label(general_group, text="H...π Distance (Å):").grid( row=0, column=0, sticky=tk.W, pady=2 ) stored_pi_dist = self._param_values.get( "pi_distance", ParametersDefault.PI_DISTANCE_CUTOFF ) self.pi_distance = tk.DoubleVar(value=stored_pi_dist) ttk.Scale( general_group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.pi_distance, orient=tk.HORIZONTAL, length=200, ).grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) pi_dist_label = ttk.Label(general_group, text="") pi_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_dist(*args): try: pi_dist_label.config(text=f"{self.pi_distance.get():.1f}") except tk.TclError: pass self.pi_distance.trace("w", update_pi_dist) update_pi_dist() # D-H...π angle ttk.Label(general_group, text="D-H...π Angle (°):").grid( row=1, column=0, sticky=tk.W, pady=2 ) stored_pi_angle = self._param_values.get( "pi_angle", ParametersDefault.PI_ANGLE_CUTOFF ) self.pi_angle = tk.DoubleVar(value=stored_pi_angle) ttk.Scale( general_group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.pi_angle, orient=tk.HORIZONTAL, length=200, ).grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) pi_angle_label = ttk.Label(general_group, text="") pi_angle_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_angle(*args): try: pi_angle_label.config(text=f"{self.pi_angle.get():.0f}") except tk.TclError: pass self.pi_angle.trace("w", update_pi_angle) update_pi_angle() # π interaction subtype parameters subtypes_group = ttk.LabelFrame( parent, text="π Interaction Subtype Parameters", padding=10 ) subtypes_group.pack(fill=tk.X, padx=10, pady=5) # Helper function to create parameter pair def create_parameter_pair( parent_frame, row, label_text, dist_var_name, angle_var_name, dist_default, angle_default, ): # Get stored values or use defaults stored_dist = self._param_values.get(dist_var_name, dist_default) stored_angle = self._param_values.get(angle_var_name, angle_default) # Distance parameter ttk.Label(parent_frame, text=f"{label_text} Distance (Å):").grid( row=row, column=0, sticky=tk.W, pady=2 ) dist_var = tk.DoubleVar(value=stored_dist) setattr(self, dist_var_name, dist_var) ttk.Scale( parent_frame, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=dist_var, orient=tk.HORIZONTAL, length=150, ).grid(row=row, column=1, sticky=tk.W, padx=5, pady=2) dist_label = ttk.Label(parent_frame, text="") dist_label.grid(row=row, column=2, sticky=tk.W, padx=5, pady=2) # Angle parameter ttk.Label(parent_frame, text=f"{label_text} Angle (°):").grid( row=row, column=3, sticky=tk.W, pady=2, padx=(20, 0) ) angle_var = tk.DoubleVar(value=stored_angle) setattr(self, angle_var_name, angle_var) ttk.Scale( parent_frame, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=angle_var, orient=tk.HORIZONTAL, length=150, ).grid(row=row, column=4, sticky=tk.W, padx=5, pady=2) angle_label = ttk.Label(parent_frame, text="") angle_label.grid(row=row, column=5, sticky=tk.W, padx=5, pady=2) # Update functions def update_dist(*args): try: dist_label.config(text=f"{dist_var.get():.1f}") except tk.TclError: pass def update_angle(*args): try: angle_label.config(text=f"{angle_var.get():.0f}") except tk.TclError: pass dist_var.trace("w", update_dist) angle_var.trace("w", update_angle) update_dist() update_angle() # Create all subtype parameters create_parameter_pair( subtypes_group, 0, "C-Cl...π", "pi_ccl_distance", "pi_ccl_angle", ParametersDefault.PI_CCL_DISTANCE_CUTOFF, ParametersDefault.PI_CCL_ANGLE_CUTOFF, ) create_parameter_pair( subtypes_group, 1, "C-Br...π", "pi_cbr_distance", "pi_cbr_angle", ParametersDefault.PI_CBR_DISTANCE_CUTOFF, ParametersDefault.PI_CBR_ANGLE_CUTOFF, ) create_parameter_pair( subtypes_group, 2, "C-I...π", "pi_ci_distance", "pi_ci_angle", ParametersDefault.PI_CI_DISTANCE_CUTOFF, ParametersDefault.PI_CI_ANGLE_CUTOFF, ) create_parameter_pair( subtypes_group, 3, "C-H...π", "pi_ch_distance", "pi_ch_angle", ParametersDefault.PI_CH_DISTANCE_CUTOFF, ParametersDefault.PI_CH_ANGLE_CUTOFF, ) create_parameter_pair( subtypes_group, 4, "N-H...π", "pi_nh_distance", "pi_nh_angle", ParametersDefault.PI_NH_DISTANCE_CUTOFF, ParametersDefault.PI_NH_ANGLE_CUTOFF, ) create_parameter_pair( subtypes_group, 5, "O-H...π", "pi_oh_distance", "pi_oh_angle", ParametersDefault.PI_OH_DISTANCE_CUTOFF, ParametersDefault.PI_OH_ANGLE_CUTOFF, ) create_parameter_pair( subtypes_group, 6, "S-H...π", "pi_sh_distance", "pi_sh_angle", ParametersDefault.PI_SH_DISTANCE_CUTOFF, ParametersDefault.PI_SH_ANGLE_CUTOFF, ) def _create_pi_pi_stacking_parameters(self, parent): """Create π-π stacking parameter controls.""" group = ttk.LabelFrame(parent, text="π-π Stacking Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # Distance cutoff dist_label = ttk.Label(group, text="Distance Cutoff (Å):") dist_label.grid(row=0, column=0, sticky=tk.W, pady=2) ToolTip( dist_label, "Maximum distance between aromatic ring centroids for π-π stacking interactions.\n" "Typical range: 3.0-4.5 Å\n" "• Parallel stacking: ~3.5-4.0 Å\n" "• T-shaped stacking: ~3.5-4.5 Å\n" "Based on McGaughey et al. (1998) and crystallographic data.", ) self.pi_pi_distance.set( self._param_values.get( "pi_pi_distance", ParametersDefault.PI_PI_DISTANCE_CUTOFF ) ) pi_pi_scale = ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.pi_pi_distance, orient=tk.HORIZONTAL, length=200, ) pi_pi_scale.grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( pi_pi_scale, "Adjust the maximum distance between aromatic ring centroids.\n" "Lower values = stricter detection, higher values = more permissive.", ) pi_pi_dist_label = ttk.Label(group, text="") pi_pi_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_pi_dist(*args): try: pi_pi_dist_label.config(text=f"{self.pi_pi_distance.get():.1f}") except tk.TclError: pass self.pi_pi_distance.trace("w", update_pi_pi_dist) update_pi_pi_dist() # Parallel angle cutoff parallel_label = ttk.Label(group, text="Parallel Angle Cutoff (°):") parallel_label.grid(row=1, column=0, sticky=tk.W, pady=2) ToolTip( parallel_label, "Maximum angle between aromatic ring planes for parallel π-π stacking.\n" "• 0° = perfectly parallel rings\n" "• Typical cutoff: 20-30°\n" "• Values >30° indicate T-shaped or edge-to-face interactions\n" "Based on Hunter & Sanders (1990) π-π interaction classification.", ) self.pi_pi_parallel_angle.set( self._param_values.get( "pi_pi_parallel_angle", ParametersDefault.PI_PI_PARALLEL_ANGLE_CUTOFF ) ) parallel_scale = ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.pi_pi_parallel_angle, orient=tk.HORIZONTAL, length=200, ) parallel_scale.grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( parallel_scale, "Adjust angle tolerance for parallel stacking.\n" "Lower values = more strict parallel geometry required.", ) pi_pi_parallel_label = ttk.Label(group, text="") pi_pi_parallel_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_pi_parallel(*args): try: pi_pi_parallel_label.config( text=f"{self.pi_pi_parallel_angle.get():.0f}" ) except tk.TclError: pass self.pi_pi_parallel_angle.trace("w", update_pi_pi_parallel) update_pi_pi_parallel() # T-shaped angle minimum tmin_label = ttk.Label(group, text="T-shaped Angle Min (°):") tmin_label.grid(row=2, column=0, sticky=tk.W, pady=2) ToolTip( tmin_label, "Minimum angle between ring planes for T-shaped (edge-to-face) stacking.\n" "• Typical range: 60-90°\n" "• T-shaped geometry involves one ring edge interacting with another ring face\n" "• Complementary to parallel stacking interactions\n" "Based on Burley & Petsko (1985) aromatic interaction studies.", ) self.pi_pi_tshaped_angle_min.set( self._param_values.get( "pi_pi_tshaped_angle_min", ParametersDefault.PI_PI_TSHAPED_ANGLE_MIN ) ) tmin_scale = ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.pi_pi_tshaped_angle_min, orient=tk.HORIZONTAL, length=200, ) tmin_scale.grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( tmin_scale, "Adjust minimum angle for T-shaped interactions.\n" "Higher values = more perpendicular geometry required.", ) pi_pi_tmin_label = ttk.Label(group, text="") pi_pi_tmin_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_pi_tmin(*args): try: pi_pi_tmin_label.config( text=f"{self.pi_pi_tshaped_angle_min.get():.0f}" ) except tk.TclError: pass self.pi_pi_tshaped_angle_min.trace("w", update_pi_pi_tmin) update_pi_pi_tmin() # T-shaped angle maximum ttk.Label(group, text="T-shaped Angle Max (°):").grid( row=3, column=0, sticky=tk.W, pady=2 ) self.pi_pi_tshaped_angle_max.set( self._param_values.get( "pi_pi_tshaped_angle_max", ParametersDefault.PI_PI_TSHAPED_ANGLE_MAX ) ) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.pi_pi_tshaped_angle_max, orient=tk.HORIZONTAL, length=200, ).grid(row=3, column=1, sticky=tk.W, padx=10, pady=2) pi_pi_tmax_label = ttk.Label(group, text="") pi_pi_tmax_label.grid(row=3, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_pi_tmax(*args): try: pi_pi_tmax_label.config( text=f"{self.pi_pi_tshaped_angle_max.get():.0f}" ) except tk.TclError: pass self.pi_pi_tshaped_angle_max.trace("w", update_pi_pi_tmax) update_pi_pi_tmax() # Offset cutoff ttk.Label(group, text="Offset Cutoff (Å):").grid( row=4, column=0, sticky=tk.W, pady=2 ) self.pi_pi_offset.set( self._param_values.get( "pi_pi_offset", ParametersDefault.PI_PI_OFFSET_CUTOFF ) ) ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.pi_pi_offset, orient=tk.HORIZONTAL, length=200, ).grid(row=4, column=1, sticky=tk.W, padx=10, pady=2) pi_pi_offset_label = ttk.Label(group, text="") pi_pi_offset_label.grid(row=4, column=2, sticky=tk.W, padx=5, pady=2) def update_pi_pi_offset(*args): try: pi_pi_offset_label.config(text=f"{self.pi_pi_offset.get():.1f}") except tk.TclError: pass self.pi_pi_offset.trace("w", update_pi_pi_offset) update_pi_pi_offset() def _create_carbonyl_interaction_parameters(self, parent): """Create carbonyl interaction parameter controls.""" group = ttk.LabelFrame( parent, text="Carbonyl n→π* Interaction Parameters", padding=10 ) group.pack(fill=tk.X, padx=10, pady=5) # Distance cutoff carb_dist_label = ttk.Label(group, text="O···C Distance Cutoff (Å):") carb_dist_label.grid(row=0, column=0, sticky=tk.W, pady=2) ToolTip( carb_dist_label, "Maximum distance between lone pair donor oxygen and carbonyl carbon.\n" "• Typical range: 2.8-3.2 Å\n" "• Represents n→π* orbital interaction\n" "• Distance based on crystallographic surveys of protein structures\n" "• Critical for protein backbone stability and folding", ) self.carbonyl_distance.set( self._param_values.get( "carbonyl_distance", ParametersDefault.CARBONYL_DISTANCE_CUTOFF ) ) carb_scale = ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.carbonyl_distance, orient=tk.HORIZONTAL, length=200, ) carb_scale.grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( carb_scale, "Adjust O···C distance cutoff for carbonyl interactions.\n" "Based on van der Waals radii and quantum mechanical calculations.", ) carbonyl_dist_label = ttk.Label(group, text="") carbonyl_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_carbonyl_dist(*args): try: carbonyl_dist_label.config(text=f"{self.carbonyl_distance.get():.1f}") except tk.TclError: pass self.carbonyl_distance.trace("w", update_carbonyl_dist) update_carbonyl_dist() # Angle minimum angle_min_label = ttk.Label(group, text="Bürgi-Dunitz Angle Min (°):") angle_min_label.grid(row=1, column=0, sticky=tk.W, pady=2) ToolTip( angle_min_label, "Minimum Bürgi-Dunitz approach angle for nucleophilic attack on carbonyl.\n" "• Optimal angle: ~107° (tetrahedral trajectory)\n" "• Range: 95-125° based on crystal structures\n" "• Named after Bürgi & Dunitz (1983) crystallographic studies\n" "• Represents stereoelectronically favored approach geometry", ) self.carbonyl_angle_min.set( self._param_values.get( "carbonyl_angle_min", ParametersDefault.CARBONYL_ANGLE_MIN ) ) angle_min_scale = ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.carbonyl_angle_min, orient=tk.HORIZONTAL, length=200, ) angle_min_scale.grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( angle_min_scale, "Adjust minimum Bürgi-Dunitz angle.\n" "Based on ab initio calculations and crystallographic analysis.", ) carbonyl_min_label = ttk.Label(group, text="") carbonyl_min_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_carbonyl_min(*args): try: carbonyl_min_label.config(text=f"{self.carbonyl_angle_min.get():.0f}") except tk.TclError: pass self.carbonyl_angle_min.trace("w", update_carbonyl_min) update_carbonyl_min() # Angle maximum ttk.Label(group, text="Bürgi-Dunitz Angle Max (°):").grid( row=2, column=0, sticky=tk.W, pady=2 ) self.carbonyl_angle_max.set( self._param_values.get( "carbonyl_angle_max", ParametersDefault.CARBONYL_ANGLE_MAX ) ) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.carbonyl_angle_max, orient=tk.HORIZONTAL, length=200, ).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) carbonyl_max_label = ttk.Label(group, text="") carbonyl_max_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_carbonyl_max(*args): try: carbonyl_max_label.config(text=f"{self.carbonyl_angle_max.get():.0f}") except tk.TclError: pass self.carbonyl_angle_max.trace("w", update_carbonyl_max) update_carbonyl_max() def _create_n_pi_interaction_parameters(self, parent): """Create n→π* interaction parameter controls.""" group = ttk.LabelFrame(parent, text="n→π* Interaction Parameters", padding=10) group.pack(fill=tk.X, padx=10, pady=5) # Distance cutoff n_pi_dist_label = ttk.Label(group, text="Distance Cutoff (Å):") n_pi_dist_label.grid(row=0, column=0, sticky=tk.W, pady=2) ToolTip( n_pi_dist_label, "Maximum distance between lone pair donor atom and aromatic ring centroid.\n" "• Typical range: 3.0-4.0 Å for O/N donors\n" "• Represents n→π* orbital interaction\n" "• Common in protein-ligand and protein-protein interactions\n" "• Important for molecular recognition and binding affinity", ) self.n_pi_distance.set( self._param_values.get( "n_pi_distance", ParametersDefault.N_PI_DISTANCE_CUTOFF ) ) n_pi_scale = ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.n_pi_distance, orient=tk.HORIZONTAL, length=200, ) n_pi_scale.grid(row=0, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( n_pi_scale, "Adjust distance cutoff for n→π* interactions.\n" "Based on computational studies and structural databases.", ) n_pi_dist_label = ttk.Label(group, text="") n_pi_dist_label.grid(row=0, column=2, sticky=tk.W, padx=5, pady=2) def update_n_pi_dist(*args): try: n_pi_dist_label.config(text=f"{self.n_pi_distance.get():.1f}") except tk.TclError: pass self.n_pi_distance.trace("w", update_n_pi_dist) update_n_pi_dist() # Sulfur distance cutoff sulfur_label = ttk.Label(group, text="Sulfur Distance Cutoff (Å):") sulfur_label.grid(row=1, column=0, sticky=tk.W, pady=2) ToolTip( sulfur_label, "Maximum distance for sulfur n→π* interactions (S···aromatic ring).\n" "• Typically larger than O/N due to sulfur's larger atomic radius\n" "• Range: 3.5-4.5 Å\n" "• Important in cysteine-aromatic interactions\n" "• Weaker than O/N interactions but geometrically significant", ) self.n_pi_sulfur_distance.set( self._param_values.get( "n_pi_sulfur_distance", ParametersDefault.N_PI_SULFUR_DISTANCE_CUTOFF ) ) sulfur_scale = ttk.Scale( group, from_=ParameterRanges.MIN_DISTANCE, to=ParameterRanges.MAX_DISTANCE, variable=self.n_pi_sulfur_distance, orient=tk.HORIZONTAL, length=200, ) sulfur_scale.grid(row=1, column=1, sticky=tk.W, padx=10, pady=2) ToolTip( sulfur_scale, "Adjust sulfur-aromatic distance cutoff.\n" "Larger values account for sulfur's extended electron cloud.", ) n_pi_sulfur_label = ttk.Label(group, text="") n_pi_sulfur_label.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2) def update_n_pi_sulfur(*args): try: n_pi_sulfur_label.config(text=f"{self.n_pi_sulfur_distance.get():.1f}") except tk.TclError: pass self.n_pi_sulfur_distance.trace("w", update_n_pi_sulfur) update_n_pi_sulfur() # Angle minimum ttk.Label(group, text="Angle Min (°):").grid( row=2, column=0, sticky=tk.W, pady=2 ) self.n_pi_angle_min.set( self._param_values.get("n_pi_angle_min", ParametersDefault.N_PI_ANGLE_MIN) ) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.n_pi_angle_min, orient=tk.HORIZONTAL, length=200, ).grid(row=2, column=1, sticky=tk.W, padx=10, pady=2) n_pi_min_label = ttk.Label(group, text="") n_pi_min_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=2) def update_n_pi_min(*args): try: n_pi_min_label.config(text=f"{self.n_pi_angle_min.get():.0f}") except tk.TclError: pass self.n_pi_angle_min.trace("w", update_n_pi_min) update_n_pi_min() # Angle maximum ttk.Label(group, text="Angle Max (°):").grid( row=3, column=0, sticky=tk.W, pady=2 ) self.n_pi_angle_max.set( self._param_values.get("n_pi_angle_max", ParametersDefault.N_PI_ANGLE_MAX) ) ttk.Scale( group, from_=ParameterRanges.MIN_ANGLE, to=ParameterRanges.MAX_ANGLE, variable=self.n_pi_angle_max, orient=tk.HORIZONTAL, length=200, ).grid(row=3, column=1, sticky=tk.W, padx=10, pady=2) n_pi_max_label = ttk.Label(group, text="") n_pi_max_label.grid(row=3, column=2, sticky=tk.W, padx=5, pady=2) def update_n_pi_max(*args): try: n_pi_max_label.config(text=f"{self.n_pi_angle_max.get():.0f}") except tk.TclError: pass self.n_pi_angle_max.trace("w", update_n_pi_max) update_n_pi_max()
[docs] def get_parameters(self) -> AnalysisParameters: """Get current parameter values. :returns: Current analysis parameters :rtype: AnalysisParameters """ # Store current values first self._store_current_values() # Get values from stored parameters or current variables def get_value(var_name, default_value): if var_name in self._param_values: return self._param_values[var_name] elif hasattr(self, var_name): var = getattr(self, var_name) if var: try: return var.get() except tk.TclError: pass return default_value return AnalysisParameters( hb_distance_cutoff=get_value( "hb_distance", ParametersDefault.HB_DISTANCE_CUTOFF ), hb_angle_cutoff=get_value("hb_angle", ParametersDefault.HB_ANGLE_CUTOFF), hb_donor_acceptor_cutoff=get_value( "da_distance", ParametersDefault.HB_DA_DISTANCE ), whb_distance_cutoff=get_value( "whb_distance", ParametersDefault.WHB_DISTANCE_CUTOFF ), whb_angle_cutoff=get_value("whb_angle", ParametersDefault.WHB_ANGLE_CUTOFF), whb_donor_acceptor_cutoff=get_value( "whb_da_distance", ParametersDefault.WHB_DA_DISTANCE ), xb_distance_cutoff=get_value( "xb_distance", ParametersDefault.XB_DISTANCE_CUTOFF ), xb_angle_cutoff=get_value("xb_angle", ParametersDefault.XB_ANGLE_CUTOFF), pi_distance_cutoff=get_value( "pi_distance", ParametersDefault.PI_DISTANCE_CUTOFF ), pi_angle_cutoff=get_value("pi_angle", ParametersDefault.PI_ANGLE_CUTOFF), # π interaction subtype parameters pi_ccl_distance_cutoff=get_value( "pi_ccl_distance", ParametersDefault.PI_CCL_DISTANCE_CUTOFF ), pi_ccl_angle_cutoff=get_value( "pi_ccl_angle", ParametersDefault.PI_CCL_ANGLE_CUTOFF ), pi_cbr_distance_cutoff=get_value( "pi_cbr_distance", ParametersDefault.PI_CBR_DISTANCE_CUTOFF ), pi_cbr_angle_cutoff=get_value( "pi_cbr_angle", ParametersDefault.PI_CBR_ANGLE_CUTOFF ), pi_ci_distance_cutoff=get_value( "pi_ci_distance", ParametersDefault.PI_CI_DISTANCE_CUTOFF ), pi_ci_angle_cutoff=get_value( "pi_ci_angle", ParametersDefault.PI_CI_ANGLE_CUTOFF ), pi_ch_distance_cutoff=get_value( "pi_ch_distance", ParametersDefault.PI_CH_DISTANCE_CUTOFF ), pi_ch_angle_cutoff=get_value( "pi_ch_angle", ParametersDefault.PI_CH_ANGLE_CUTOFF ), pi_nh_distance_cutoff=get_value( "pi_nh_distance", ParametersDefault.PI_NH_DISTANCE_CUTOFF ), pi_nh_angle_cutoff=get_value( "pi_nh_angle", ParametersDefault.PI_NH_ANGLE_CUTOFF ), pi_oh_distance_cutoff=get_value( "pi_oh_distance", ParametersDefault.PI_OH_DISTANCE_CUTOFF ), pi_oh_angle_cutoff=get_value( "pi_oh_angle", ParametersDefault.PI_OH_ANGLE_CUTOFF ), pi_sh_distance_cutoff=get_value( "pi_sh_distance", ParametersDefault.PI_SH_DISTANCE_CUTOFF ), pi_sh_angle_cutoff=get_value( "pi_sh_angle", ParametersDefault.PI_SH_ANGLE_CUTOFF ), # New interaction parameters pi_pi_distance_cutoff=get_value( "pi_pi_distance", ParametersDefault.PI_PI_DISTANCE_CUTOFF ), pi_pi_parallel_angle_cutoff=get_value( "pi_pi_parallel_angle", ParametersDefault.PI_PI_PARALLEL_ANGLE_CUTOFF ), pi_pi_tshaped_angle_min=get_value( "pi_pi_tshaped_angle_min", ParametersDefault.PI_PI_TSHAPED_ANGLE_MIN ), pi_pi_tshaped_angle_max=get_value( "pi_pi_tshaped_angle_max", ParametersDefault.PI_PI_TSHAPED_ANGLE_MAX ), pi_pi_offset_cutoff=get_value( "pi_pi_offset", ParametersDefault.PI_PI_OFFSET_CUTOFF ), carbonyl_distance_cutoff=get_value( "carbonyl_distance", ParametersDefault.CARBONYL_DISTANCE_CUTOFF ), carbonyl_angle_min=get_value( "carbonyl_angle_min", ParametersDefault.CARBONYL_ANGLE_MIN ), carbonyl_angle_max=get_value( "carbonyl_angle_max", ParametersDefault.CARBONYL_ANGLE_MAX ), n_pi_distance_cutoff=get_value( "n_pi_distance", ParametersDefault.N_PI_DISTANCE_CUTOFF ), n_pi_sulfur_distance_cutoff=get_value( "n_pi_sulfur_distance", ParametersDefault.N_PI_SULFUR_DISTANCE_CUTOFF ), n_pi_angle_min=get_value( "n_pi_angle_min", ParametersDefault.N_PI_ANGLE_MIN ), n_pi_angle_max=get_value( "n_pi_angle_max", ParametersDefault.N_PI_ANGLE_MAX ), covalent_cutoff_factor=get_value( "covalent_factor", ParametersDefault.COVALENT_CUTOFF_FACTOR ), analysis_mode=get_value("analysis_mode", ParametersDefault.ANALYSIS_MODE), )
[docs] def set_parameters(self, params: AnalysisParameters) -> None: """Set parameter values from AnalysisParameters object. :param params: Analysis parameters to set :type params: AnalysisParameters """ # Store values directly in our parameter storage self._param_values.update( { "hb_distance": params.hb_distance_cutoff, "hb_angle": params.hb_angle_cutoff, "da_distance": params.hb_donor_acceptor_cutoff, "whb_distance": params.whb_distance_cutoff, "whb_angle": params.whb_angle_cutoff, "whb_da_distance": params.whb_donor_acceptor_cutoff, "xb_distance": params.xb_distance_cutoff, "xb_angle": params.xb_angle_cutoff, "pi_distance": params.pi_distance_cutoff, "pi_angle": params.pi_angle_cutoff, "pi_ccl_distance": params.pi_ccl_distance_cutoff, "pi_ccl_angle": params.pi_ccl_angle_cutoff, "pi_cbr_distance": params.pi_cbr_distance_cutoff, "pi_cbr_angle": params.pi_cbr_angle_cutoff, "pi_ci_distance": params.pi_ci_distance_cutoff, "pi_ci_angle": params.pi_ci_angle_cutoff, "pi_ch_distance": params.pi_ch_distance_cutoff, "pi_ch_angle": params.pi_ch_angle_cutoff, "pi_nh_distance": params.pi_nh_distance_cutoff, "pi_nh_angle": params.pi_nh_angle_cutoff, "pi_oh_distance": params.pi_oh_distance_cutoff, "pi_oh_angle": params.pi_oh_angle_cutoff, "pi_sh_distance": params.pi_sh_distance_cutoff, "pi_sh_angle": params.pi_sh_angle_cutoff, # New interaction parameters "pi_pi_distance": params.pi_pi_distance_cutoff, "pi_pi_parallel_angle": params.pi_pi_parallel_angle_cutoff, "pi_pi_tshaped_angle_min": params.pi_pi_tshaped_angle_min, "pi_pi_tshaped_angle_max": params.pi_pi_tshaped_angle_max, "pi_pi_offset": params.pi_pi_offset_cutoff, "carbonyl_distance": params.carbonyl_distance_cutoff, "carbonyl_angle_min": params.carbonyl_angle_min, "carbonyl_angle_max": params.carbonyl_angle_max, "n_pi_distance": params.n_pi_distance_cutoff, "n_pi_sulfur_distance": params.n_pi_sulfur_distance_cutoff, "n_pi_angle_min": params.n_pi_angle_min, "n_pi_angle_max": params.n_pi_angle_max, "covalent_factor": params.covalent_cutoff_factor, "analysis_mode": params.analysis_mode, } ) # Set values on existing variables if they exist def safe_set(var_name, value): if hasattr(self, var_name): var = getattr(self, var_name) if var: try: var.set(value) except tk.TclError: pass # Variable destroyed, ignore safe_set("hb_distance", params.hb_distance_cutoff) safe_set("hb_angle", params.hb_angle_cutoff) safe_set("da_distance", params.hb_donor_acceptor_cutoff) safe_set("whb_distance", params.whb_distance_cutoff) safe_set("whb_angle", params.whb_angle_cutoff) safe_set("whb_da_distance", params.whb_donor_acceptor_cutoff) safe_set("xb_distance", params.xb_distance_cutoff) safe_set("xb_angle", params.xb_angle_cutoff) safe_set("pi_distance", params.pi_distance_cutoff) safe_set("pi_angle", params.pi_angle_cutoff) safe_set("pi_ccl_distance", params.pi_ccl_distance_cutoff) safe_set("pi_ccl_angle", params.pi_ccl_angle_cutoff) safe_set("pi_cbr_distance", params.pi_cbr_distance_cutoff) safe_set("pi_cbr_angle", params.pi_cbr_angle_cutoff) safe_set("pi_ci_distance", params.pi_ci_distance_cutoff) safe_set("pi_ci_angle", params.pi_ci_angle_cutoff) safe_set("pi_ch_distance", params.pi_ch_distance_cutoff) safe_set("pi_ch_angle", params.pi_ch_angle_cutoff) safe_set("pi_nh_distance", params.pi_nh_distance_cutoff) safe_set("pi_nh_angle", params.pi_nh_angle_cutoff) safe_set("pi_oh_distance", params.pi_oh_distance_cutoff) safe_set("pi_oh_angle", params.pi_oh_angle_cutoff) safe_set("pi_sh_distance", params.pi_sh_distance_cutoff) safe_set("pi_sh_angle", params.pi_sh_angle_cutoff) # New interaction parameters safe_set("pi_pi_distance", params.pi_pi_distance_cutoff) safe_set("pi_pi_parallel_angle", params.pi_pi_parallel_angle_cutoff) safe_set("pi_pi_tshaped_angle_min", params.pi_pi_tshaped_angle_min) safe_set("pi_pi_tshaped_angle_max", params.pi_pi_tshaped_angle_max) safe_set("pi_pi_offset", params.pi_pi_offset_cutoff) safe_set("carbonyl_distance", params.carbonyl_distance_cutoff) safe_set("carbonyl_angle_min", params.carbonyl_angle_min) safe_set("carbonyl_angle_max", params.carbonyl_angle_max) safe_set("n_pi_distance", params.n_pi_distance_cutoff) safe_set("n_pi_sulfur_distance", params.n_pi_sulfur_distance_cutoff) safe_set("n_pi_angle_min", params.n_pi_angle_min) safe_set("n_pi_angle_max", params.n_pi_angle_max) safe_set("covalent_factor", params.covalent_cutoff_factor) safe_set("analysis_mode", params.analysis_mode)
def _set_defaults(self): """Reset all parameters to default values.""" default_params = AnalysisParameters() self.set_parameters(default_params)
[docs] def reset_to_defaults(self) -> None: """Public method to reset parameters to defaults.""" self._set_defaults()
def _open_preset_manager(self): """Open the preset manager dialog.""" from .preset_manager_dialog import PresetManagerDialog # Get current parameters current_params = self.get_parameters() # Open preset manager dialog = PresetManagerDialog(self.dialog, current_params) result = dialog.get_result() if result: # Apply the loaded preset self._apply_preset_data(result) messagebox.showinfo("Success", "Preset loaded successfully") def _apply_preset_data(self, data: Dict[str, Any]) -> None: """Apply preset data to parameters.""" if "parameters" not in data: raise ValueError("Invalid preset format: missing 'parameters' section") params = data["parameters"] # Apply hydrogen bond parameters if "hydrogen_bonds" in params: hb = params["hydrogen_bonds"] self.hb_distance.set( hb.get("h_a_distance_cutoff", ParametersDefault.HB_DISTANCE_CUTOFF) ) self.hb_angle.set( hb.get("dha_angle_cutoff", ParametersDefault.HB_ANGLE_CUTOFF) ) self.da_distance.set( hb.get("d_a_distance_cutoff", ParametersDefault.HB_DA_DISTANCE) ) # Apply halogen bond parameters if "halogen_bonds" in params: xb = params["halogen_bonds"] self.xb_distance.set( xb.get("x_a_distance_cutoff", ParametersDefault.XB_DISTANCE_CUTOFF) ) self.xb_angle.set( xb.get("dxa_angle_cutoff", ParametersDefault.XB_ANGLE_CUTOFF) ) # Apply π interaction parameters if "pi_interactions" in params: pi = params["pi_interactions"] self.pi_distance.set( pi.get("h_pi_distance_cutoff", ParametersDefault.PI_DISTANCE_CUTOFF) ) self.pi_angle.set( pi.get("dh_pi_angle_cutoff", ParametersDefault.PI_ANGLE_CUTOFF) ) # Apply π interaction subtype parameters self.pi_ccl_distance.set( pi.get( "ccl_pi_distance_cutoff", ParametersDefault.PI_CCL_DISTANCE_CUTOFF ) ) self.pi_ccl_angle.set( pi.get("ccl_pi_angle_cutoff", ParametersDefault.PI_CCL_ANGLE_CUTOFF) ) self.pi_cbr_distance.set( pi.get( "cbr_pi_distance_cutoff", ParametersDefault.PI_CBR_DISTANCE_CUTOFF ) ) self.pi_cbr_angle.set( pi.get("cbr_pi_angle_cutoff", ParametersDefault.PI_CBR_ANGLE_CUTOFF) ) self.pi_ci_distance.set( pi.get("ci_pi_distance_cutoff", ParametersDefault.PI_CI_DISTANCE_CUTOFF) ) self.pi_ci_angle.set( pi.get("ci_pi_angle_cutoff", ParametersDefault.PI_CI_ANGLE_CUTOFF) ) self.pi_ch_distance.set( pi.get("ch_pi_distance_cutoff", ParametersDefault.PI_CH_DISTANCE_CUTOFF) ) self.pi_ch_angle.set( pi.get("ch_pi_angle_cutoff", ParametersDefault.PI_CH_ANGLE_CUTOFF) ) self.pi_nh_distance.set( pi.get("nh_pi_distance_cutoff", ParametersDefault.PI_NH_DISTANCE_CUTOFF) ) self.pi_nh_angle.set( pi.get("nh_pi_angle_cutoff", ParametersDefault.PI_NH_ANGLE_CUTOFF) ) self.pi_oh_distance.set( pi.get("oh_pi_distance_cutoff", ParametersDefault.PI_OH_DISTANCE_CUTOFF) ) self.pi_oh_angle.set( pi.get("oh_pi_angle_cutoff", ParametersDefault.PI_OH_ANGLE_CUTOFF) ) self.pi_sh_distance.set( pi.get("sh_pi_distance_cutoff", ParametersDefault.PI_SH_DISTANCE_CUTOFF) ) self.pi_sh_angle.set( pi.get("sh_pi_angle_cutoff", ParametersDefault.PI_SH_ANGLE_CUTOFF) ) # Apply general parameters if "general" in params: gen = params["general"] self.covalent_factor.set( gen.get( "covalent_cutoff_factor", ParametersDefault.COVALENT_CUTOFF_FACTOR ) ) self.analysis_mode.set( gen.get("analysis_mode", ParametersDefault.ANALYSIS_MODE) ) def _ok(self): """Handle OK button - save settings and close.""" self.result = self.get_parameters() self.dialog.destroy() def _cancel(self): """Handle Cancel button - close without saving.""" self.result = None self.dialog.destroy()
[docs] def get_result(self) -> Optional[AnalysisParameters]: """Get the configured parameters. :returns: Analysis parameters or None if cancelled :rtype: Optional[AnalysisParameters] """ self.dialog.wait_window() return self.result