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

from ..constants.parameters import (
    AnalysisParameters,
    ParameterRanges,
    ParametersDefault,
)


[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
[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