ras-commander 0.81.0__py3-none-any.whl → 0.82.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ras_commander/RasControl.py +774 -0
 - ras_commander/RasPlan.py +98 -31
 - ras_commander/RasPrj.py +70 -33
 - ras_commander/__init__.py +3 -2
 - {ras_commander-0.81.0.dist-info → ras_commander-0.82.0.dist-info}/METADATA +15 -2
 - {ras_commander-0.81.0.dist-info → ras_commander-0.82.0.dist-info}/RECORD +9 -8
 - {ras_commander-0.81.0.dist-info → ras_commander-0.82.0.dist-info}/WHEEL +0 -0
 - {ras_commander-0.81.0.dist-info → ras_commander-0.82.0.dist-info}/licenses/LICENSE +0 -0
 - {ras_commander-0.81.0.dist-info → ras_commander-0.82.0.dist-info}/top_level.txt +0 -0
 
| 
         @@ -0,0 +1,774 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            """
         
     | 
| 
      
 2 
     | 
    
         
            +
            RasControl - HECRASController API Wrapper (ras-commander style)
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Provides ras-commander style API for legacy HEC-RAS versions (3.x-4.x)
         
     | 
| 
      
 5 
     | 
    
         
            +
            that use HECRASController COM interface instead of HDF files.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Public functions:
         
     | 
| 
      
 8 
     | 
    
         
            +
            - RasControl.run_plan(plan: Union[str, Path], ras_object: Optional[Any] = None) -> Tuple[bool, List[str]]
         
     | 
| 
      
 9 
     | 
    
         
            +
            - RasControl.get_steady_results(plan: Union[str, Path], ras_object: Optional[Any] = None) -> pandas.DataFrame
         
     | 
| 
      
 10 
     | 
    
         
            +
            - RasControl.get_unsteady_results(plan: Union[str, Path], max_times: Optional[int] = None, ras_object: Optional[Any] = None) -> pandas.DataFrame
         
     | 
| 
      
 11 
     | 
    
         
            +
            - RasControl.get_output_times(plan: Union[str, Path], ras_object: Optional[Any] = None) -> List[str]
         
     | 
| 
      
 12 
     | 
    
         
            +
            - RasControl.get_plans(plan: Union[str, Path], ras_object: Optional[Any] = None) -> List[dict]
         
     | 
| 
      
 13 
     | 
    
         
            +
            - RasControl.set_current_plan(plan: Union[str, Path], ras_object: Optional[Any] = None) -> bool
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            Private functions:
         
     | 
| 
      
 16 
     | 
    
         
            +
            - _terminate_ras_process() -> None
         
     | 
| 
      
 17 
     | 
    
         
            +
            - _is_ras_running() -> bool
         
     | 
| 
      
 18 
     | 
    
         
            +
            - RasControl._normalize_version(version: str) -> str
         
     | 
| 
      
 19 
     | 
    
         
            +
            - RasControl._get_project_info(plan: Union[str, Path], ras_object: Optional[Any] = None) -> Tuple[Path, str, Optional[str], Optional[str]]
         
     | 
| 
      
 20 
     | 
    
         
            +
            - RasControl._com_open_close(project_path: Path, version: str, operation_func: Callable[[Any], Any]) -> Any
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            """
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            import win32com.client
         
     | 
| 
      
 25 
     | 
    
         
            +
            import psutil
         
     | 
| 
      
 26 
     | 
    
         
            +
            import pandas as pd
         
     | 
| 
      
 27 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 28 
     | 
    
         
            +
            from typing import Optional, List, Tuple, Callable, Any, Union
         
     | 
| 
      
 29 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 30 
     | 
    
         
            +
            import time
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            logger = logging.getLogger(__name__)
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            # Import ras-commander components
         
     | 
| 
      
 35 
     | 
    
         
            +
            from .RasPrj import ras
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            def _terminate_ras_process() -> None:
         
     | 
| 
      
 39 
     | 
    
         
            +
                """Force terminate any running ras.exe processes."""
         
     | 
| 
      
 40 
     | 
    
         
            +
                for proc in psutil.process_iter(['name']):
         
     | 
| 
      
 41 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 42 
     | 
    
         
            +
                        if proc.info['name'] and proc.info['name'].lower() == 'ras.exe':
         
     | 
| 
      
 43 
     | 
    
         
            +
                            proc.terminate()
         
     | 
| 
      
 44 
     | 
    
         
            +
                            proc.wait(timeout=3)
         
     | 
| 
      
 45 
     | 
    
         
            +
                            logger.info("Terminated ras.exe process")
         
     | 
| 
      
 46 
     | 
    
         
            +
                    except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired):
         
     | 
| 
      
 47 
     | 
    
         
            +
                        pass
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            def _is_ras_running() -> bool:
         
     | 
| 
      
 51 
     | 
    
         
            +
                """Check if HEC-RAS is currently running"""
         
     | 
| 
      
 52 
     | 
    
         
            +
                for proc in psutil.process_iter(['name']):
         
     | 
| 
      
 53 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 54 
     | 
    
         
            +
                        if proc.info['name'] and proc.info['name'].lower() == 'ras.exe':
         
     | 
| 
      
 55 
     | 
    
         
            +
                            return True
         
     | 
| 
      
 56 
     | 
    
         
            +
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
         
     | 
| 
      
 57 
     | 
    
         
            +
                        pass
         
     | 
| 
      
 58 
     | 
    
         
            +
                return False
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
            class RasControl:
         
     | 
| 
      
 62 
     | 
    
         
            +
                """
         
     | 
| 
      
 63 
     | 
    
         
            +
                HECRASController API wrapper with ras-commander style interface.
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                Works with legacy HEC-RAS versions (3.x-4.x) that use COM interface
         
     | 
| 
      
 66 
     | 
    
         
            +
                instead of HDF files. Integrates with ras-commander project management.
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                Usage (ras-commander style):
         
     | 
| 
      
 69 
     | 
    
         
            +
                    >>> from ras_commander import init_ras_project, RasControl
         
     | 
| 
      
 70 
     | 
    
         
            +
                    >>>
         
     | 
| 
      
 71 
     | 
    
         
            +
                    >>> # Initialize with version (with or without periods)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    >>> init_ras_project(path, "4.1")  # or "41"
         
     | 
| 
      
 73 
     | 
    
         
            +
                    >>>
         
     | 
| 
      
 74 
     | 
    
         
            +
                    >>> # Use plan numbers like HDF methods
         
     | 
| 
      
 75 
     | 
    
         
            +
                    >>> RasControl.run_plan("02")
         
     | 
| 
      
 76 
     | 
    
         
            +
                    >>> df = RasControl.get_steady_results("02")
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                Supported Versions:
         
     | 
| 
      
 79 
     | 
    
         
            +
                    All installed versions: 3.x, 4.x, 5.0.x, 6.0-6.7+
         
     | 
| 
      
 80 
     | 
    
         
            +
                    Accepts formats: "4.1", "41", "5.0.6", "506", "6.6", "66", etc.
         
     | 
| 
      
 81 
     | 
    
         
            +
                """
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                # Version mapping based on ACTUAL COM interfaces registered on system
         
     | 
| 
      
 84 
     | 
    
         
            +
                # Only these COM interfaces exist: RAS41, RAS503, RAS505, RAS506, RAS507,
         
     | 
| 
      
 85 
     | 
    
         
            +
                # RAS60, RAS631, RAS641, RAS65, RAS66, RAS67
         
     | 
| 
      
 86 
     | 
    
         
            +
                # Other versions use nearest available fallback
         
     | 
| 
      
 87 
     | 
    
         
            +
                VERSION_MAP = {
         
     | 
| 
      
 88 
     | 
    
         
            +
                    # HEC-RAS 3.x → Use 4.1 (3.x COM not registered)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    '3.0': 'RAS41.HECRASController',
         
     | 
| 
      
 90 
     | 
    
         
            +
                    '30': 'RAS41.HECRASController',
         
     | 
| 
      
 91 
     | 
    
         
            +
                    '3.1': 'RAS41.HECRASController',
         
     | 
| 
      
 92 
     | 
    
         
            +
                    '31': 'RAS41.HECRASController',
         
     | 
| 
      
 93 
     | 
    
         
            +
                    '3.1.1': 'RAS41.HECRASController',
         
     | 
| 
      
 94 
     | 
    
         
            +
                    '311': 'RAS41.HECRASController',
         
     | 
| 
      
 95 
     | 
    
         
            +
                    '3.1.2': 'RAS41.HECRASController',
         
     | 
| 
      
 96 
     | 
    
         
            +
                    '312': 'RAS41.HECRASController',
         
     | 
| 
      
 97 
     | 
    
         
            +
                    '3.1.3': 'RAS41.HECRASController',
         
     | 
| 
      
 98 
     | 
    
         
            +
                    '313': 'RAS41.HECRASController',
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    # HEC-RAS 4.x
         
     | 
| 
      
 101 
     | 
    
         
            +
                    '4.0': 'RAS41.HECRASController',    # Use 4.1 (4.0 COM not registered)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    '40': 'RAS41.HECRASController',
         
     | 
| 
      
 103 
     | 
    
         
            +
                    '4.1': 'RAS41.HECRASController',    # ✓ EXISTS
         
     | 
| 
      
 104 
     | 
    
         
            +
                    '41': 'RAS41.HECRASController',
         
     | 
| 
      
 105 
     | 
    
         
            +
                    '4.1.0': 'RAS41.HECRASController',
         
     | 
| 
      
 106 
     | 
    
         
            +
                    '410': 'RAS41.HECRASController',
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                    # HEC-RAS 5.0.x
         
     | 
| 
      
 109 
     | 
    
         
            +
                    '5.0': 'RAS503.HECRASController',   # Use 5.0.3 (RAS50 COM not registered)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    '50': 'RAS503.HECRASController',
         
     | 
| 
      
 111 
     | 
    
         
            +
                    '5.0.1': 'RAS501.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 112 
     | 
    
         
            +
                    '501': 'RAS501.HECRASController',
         
     | 
| 
      
 113 
     | 
    
         
            +
                    '5.0.3': 'RAS503.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 114 
     | 
    
         
            +
                    '503': 'RAS503.HECRASController',
         
     | 
| 
      
 115 
     | 
    
         
            +
                    '5.0.4': 'RAS504.HECRASController', # ✓ EXISTS (newly installed)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    '504': 'RAS504.HECRASController',
         
     | 
| 
      
 117 
     | 
    
         
            +
                    '5.0.5': 'RAS505.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 118 
     | 
    
         
            +
                    '505': 'RAS505.HECRASController',
         
     | 
| 
      
 119 
     | 
    
         
            +
                    '5.0.6': 'RAS506.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 120 
     | 
    
         
            +
                    '506': 'RAS506.HECRASController',
         
     | 
| 
      
 121 
     | 
    
         
            +
                    '5.0.7': 'RAS507.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 122 
     | 
    
         
            +
                    '507': 'RAS507.HECRASController',
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                    # HEC-RAS 6.x
         
     | 
| 
      
 125 
     | 
    
         
            +
                    '6.0': 'RAS60.HECRASController',    # ✓ EXISTS
         
     | 
| 
      
 126 
     | 
    
         
            +
                    '60': 'RAS60.HECRASController',
         
     | 
| 
      
 127 
     | 
    
         
            +
                    '6.1': 'RAS60.HECRASController',    # Use 6.0 (6.1 COM not registered)
         
     | 
| 
      
 128 
     | 
    
         
            +
                    '61': 'RAS60.HECRASController',
         
     | 
| 
      
 129 
     | 
    
         
            +
                    '6.2': 'RAS60.HECRASController',    # Use 6.0 (6.2 COM not registered)
         
     | 
| 
      
 130 
     | 
    
         
            +
                    '62': 'RAS60.HECRASController',
         
     | 
| 
      
 131 
     | 
    
         
            +
                    '6.3': 'RAS631.HECRASController',   # Use 6.3.1 (6.3 COM not registered)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    '63': 'RAS631.HECRASController',
         
     | 
| 
      
 133 
     | 
    
         
            +
                    '6.3.1': 'RAS631.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 134 
     | 
    
         
            +
                    '631': 'RAS631.HECRASController',
         
     | 
| 
      
 135 
     | 
    
         
            +
                    '6.4': 'RAS641.HECRASController',   # Use 6.4.1 (6.4 COM not registered)
         
     | 
| 
      
 136 
     | 
    
         
            +
                    '64': 'RAS641.HECRASController',
         
     | 
| 
      
 137 
     | 
    
         
            +
                    '6.4.1': 'RAS641.HECRASController', # ✓ EXISTS
         
     | 
| 
      
 138 
     | 
    
         
            +
                    '641': 'RAS641.HECRASController',
         
     | 
| 
      
 139 
     | 
    
         
            +
                    '6.5': 'RAS65.HECRASController',    # ✓ EXISTS
         
     | 
| 
      
 140 
     | 
    
         
            +
                    '65': 'RAS65.HECRASController',
         
     | 
| 
      
 141 
     | 
    
         
            +
                    '6.6': 'RAS66.HECRASController',    # ✓ EXISTS
         
     | 
| 
      
 142 
     | 
    
         
            +
                    '66': 'RAS66.HECRASController',
         
     | 
| 
      
 143 
     | 
    
         
            +
                    '6.7': 'RAS67.HECRASController',    # ✓ EXISTS
         
     | 
| 
      
 144 
     | 
    
         
            +
                    '67': 'RAS67.HECRASController',
         
     | 
| 
      
 145 
     | 
    
         
            +
                    '6.7 Beta 4': 'RAS67.HECRASController',
         
     | 
| 
      
 146 
     | 
    
         
            +
                }
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                # Legacy reference (kept for backwards compatibility)
         
     | 
| 
      
 149 
     | 
    
         
            +
                SUPPORTED_VERSIONS = VERSION_MAP
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                # Output variable codes
         
     | 
| 
      
 152 
     | 
    
         
            +
                WSEL = 2
         
     | 
| 
      
 153 
     | 
    
         
            +
                ENERGY = 3
         
     | 
| 
      
 154 
     | 
    
         
            +
                MAX_CHL_DPTH = 4
         
     | 
| 
      
 155 
     | 
    
         
            +
                MIN_CH_EL = 5
         
     | 
| 
      
 156 
     | 
    
         
            +
                ENERGY_SLOPE = 6
         
     | 
| 
      
 157 
     | 
    
         
            +
                FLOW_TOTAL = 24
         
     | 
| 
      
 158 
     | 
    
         
            +
                VEL_TOTAL = 25
         
     | 
| 
      
 159 
     | 
    
         
            +
                STA_WS_LFT = 36
         
     | 
| 
      
 160 
     | 
    
         
            +
                STA_WS_RGT = 37
         
     | 
| 
      
 161 
     | 
    
         
            +
                FROUDE_CHL = 48
         
     | 
| 
      
 162 
     | 
    
         
            +
                FROUDE_XS = 49
         
     | 
| 
      
 163 
     | 
    
         
            +
                Q_WEIR = 94
         
     | 
| 
      
 164 
     | 
    
         
            +
                Q_CULVERT_TOT = 242
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                # ========== PRIVATE METHODS (HECRASController COM API) ==========
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 169 
     | 
    
         
            +
                def _normalize_version(version: str) -> str:
         
     | 
| 
      
 170 
     | 
    
         
            +
                    """
         
     | 
| 
      
 171 
     | 
    
         
            +
                    Normalize version string to match VERSION_MAP keys.
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                    Handles formats like:
         
     | 
| 
      
 174 
     | 
    
         
            +
                        "6.6", "66" → "6.6"
         
     | 
| 
      
 175 
     | 
    
         
            +
                        "4.1", "41" → "4.1"
         
     | 
| 
      
 176 
     | 
    
         
            +
                        "5.0.6", "506" → "5.0.6"
         
     | 
| 
      
 177 
     | 
    
         
            +
                        "6.7 Beta 4" → "6.7 Beta 4"
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 180 
     | 
    
         
            +
                        Normalized version string that exists in VERSION_MAP
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                    Raises:
         
     | 
| 
      
 183 
     | 
    
         
            +
                        ValueError: If version cannot be normalized or is not supported
         
     | 
| 
      
 184 
     | 
    
         
            +
                    """
         
     | 
| 
      
 185 
     | 
    
         
            +
                    version_str = str(version).strip()
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                    # Direct match
         
     | 
| 
      
 188 
     | 
    
         
            +
                    if version_str in RasControl.VERSION_MAP:
         
     | 
| 
      
 189 
     | 
    
         
            +
                        return version_str
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                    # Try common normalizations
         
     | 
| 
      
 192 
     | 
    
         
            +
                    normalized_candidates = [
         
     | 
| 
      
 193 
     | 
    
         
            +
                        version_str,
         
     | 
| 
      
 194 
     | 
    
         
            +
                        version_str.replace('.', ''),  # "6.6" → "66"
         
     | 
| 
      
 195 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                    # Try adding periods for compact formats
         
     | 
| 
      
 198 
     | 
    
         
            +
                    if len(version_str) == 2:  # "66" → "6.6"
         
     | 
| 
      
 199 
     | 
    
         
            +
                        normalized_candidates.append(f"{version_str[0]}.{version_str[1]}")
         
     | 
| 
      
 200 
     | 
    
         
            +
                    elif len(version_str) == 3 and version_str.startswith('5'):  # "506" → "5.0.6"
         
     | 
| 
      
 201 
     | 
    
         
            +
                        normalized_candidates.append(f"5.0.{version_str[2]}")
         
     | 
| 
      
 202 
     | 
    
         
            +
                    elif len(version_str) == 3:  # "631" → "6.3.1"
         
     | 
| 
      
 203 
     | 
    
         
            +
                        normalized_candidates.append(f"{version_str[0]}.{version_str[1]}.{version_str[2]}")
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                    # Check all candidates
         
     | 
| 
      
 206 
     | 
    
         
            +
                    for candidate in normalized_candidates:
         
     | 
| 
      
 207 
     | 
    
         
            +
                        if candidate in RasControl.VERSION_MAP:
         
     | 
| 
      
 208 
     | 
    
         
            +
                            logger.debug(f"Normalized version '{version}' → '{candidate}'")
         
     | 
| 
      
 209 
     | 
    
         
            +
                            return candidate
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                    # Not found
         
     | 
| 
      
 212 
     | 
    
         
            +
                    raise ValueError(
         
     | 
| 
      
 213 
     | 
    
         
            +
                        f"Version '{version}' not supported. Supported versions:\n"
         
     | 
| 
      
 214 
     | 
    
         
            +
                        f"  3.x: 3.0, 3.1 (3.1.1, 3.1.2, 3.1.3)\n"
         
     | 
| 
      
 215 
     | 
    
         
            +
                        f"  4.x: 4.0, 4.1\n"
         
     | 
| 
      
 216 
     | 
    
         
            +
                        f"  5.0.x: 5.0, 5.0.1, 5.0.3, 5.0.4, 5.0.5, 5.0.6, 5.0.7\n"
         
     | 
| 
      
 217 
     | 
    
         
            +
                        f"  6.x: 6.0, 6.1, 6.2, 6.3, 6.3.1, 6.4, 6.4.1, 6.5, 6.6, 6.7\n"
         
     | 
| 
      
 218 
     | 
    
         
            +
                        f"  Formats: Can use '6.6' or '66', '5.0.6' or '506', etc."
         
     | 
| 
      
 219 
     | 
    
         
            +
                    )
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 222 
     | 
    
         
            +
                def _get_project_info(plan: Union[str, Path], ras_object=None):
         
     | 
| 
      
 223 
     | 
    
         
            +
                    """
         
     | 
| 
      
 224 
     | 
    
         
            +
                    Resolve plan number/path to project path, version, and plan details.
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 227 
     | 
    
         
            +
                        Tuple[Path, str, str, str]: (project_path, version, plan_number, plan_name)
         
     | 
| 
      
 228 
     | 
    
         
            +
                        plan_number and plan_name are None if using direct .prj path
         
     | 
| 
      
 229 
     | 
    
         
            +
                    """
         
     | 
| 
      
 230 
     | 
    
         
            +
                    if ras_object is None:
         
     | 
| 
      
 231 
     | 
    
         
            +
                        ras_object = ras
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                    # If it's a path to .prj file
         
     | 
| 
      
 234 
     | 
    
         
            +
                    plan_path = Path(plan) if isinstance(plan, str) else plan
         
     | 
| 
      
 235 
     | 
    
         
            +
                    if plan_path.exists() and plan_path.suffix == '.prj':
         
     | 
| 
      
 236 
     | 
    
         
            +
                        # Direct path - need version from ras_object
         
     | 
| 
      
 237 
     | 
    
         
            +
                        if not hasattr(ras_object, 'ras_version') or not ras_object.ras_version:
         
     | 
| 
      
 238 
     | 
    
         
            +
                            raise ValueError(
         
     | 
| 
      
 239 
     | 
    
         
            +
                                "When using direct .prj paths, project must be initialized with version.\n"
         
     | 
| 
      
 240 
     | 
    
         
            +
                                "Use: init_ras_project(path, '4.1') or similar"
         
     | 
| 
      
 241 
     | 
    
         
            +
                            )
         
     | 
| 
      
 242 
     | 
    
         
            +
                        return plan_path, ras_object.ras_version, None, None
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                    # Otherwise treat as plan number
         
     | 
| 
      
 245 
     | 
    
         
            +
                    plan_num = str(plan).zfill(2)
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
                    # Get project path from ras_object
         
     | 
| 
      
 248 
     | 
    
         
            +
                    if not hasattr(ras_object, 'prj_file') or not ras_object.prj_file:
         
     | 
| 
      
 249 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 250 
     | 
    
         
            +
                            "No project initialized. Use init_ras_project() first.\n"
         
     | 
| 
      
 251 
     | 
    
         
            +
                            "Example: init_ras_project(path, '4.1')"
         
     | 
| 
      
 252 
     | 
    
         
            +
                        )
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                    project_path = Path(ras_object.prj_file)
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
                    # Get version
         
     | 
| 
      
 257 
     | 
    
         
            +
                    if not hasattr(ras_object, 'ras_version') or not ras_object.ras_version:
         
     | 
| 
      
 258 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 259 
     | 
    
         
            +
                            "Project initialized without version. Re-initialize with:\n"
         
     | 
| 
      
 260 
     | 
    
         
            +
                            "init_ras_project(path, '4.1')  # or '41', '501', etc."
         
     | 
| 
      
 261 
     | 
    
         
            +
                        )
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                    version = ras_object.ras_version
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                    # Get plan name from plan_df
         
     | 
| 
      
 266 
     | 
    
         
            +
                    plan_row = ras_object.plan_df[ras_object.plan_df['plan_number'] == plan_num]
         
     | 
| 
      
 267 
     | 
    
         
            +
                    if plan_row.empty:
         
     | 
| 
      
 268 
     | 
    
         
            +
                        raise ValueError(f"Plan '{plan_num}' not found in project")
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                    plan_name = plan_row['Plan Title'].iloc[0]
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
                    return project_path, version, plan_num, plan_name
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 275 
     | 
    
         
            +
                def _com_open_close(project_path: Path, version: str, operation_func: Callable[[Any], Any]) -> Any:
         
     | 
| 
      
 276 
     | 
    
         
            +
                    """
         
     | 
| 
      
 277 
     | 
    
         
            +
                    PRIVATE: Open HEC-RAS via COM, run operation, close HEC-RAS.
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
                    This is the core COM interface handler. All public methods use this.
         
     | 
| 
      
 280 
     | 
    
         
            +
                    """
         
     | 
| 
      
 281 
     | 
    
         
            +
                    # Normalize version (handles "6.6" → "6.6", "66" → "6.6", etc.)
         
     | 
| 
      
 282 
     | 
    
         
            +
                    normalized_version = RasControl._normalize_version(version)
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
                    if not project_path.exists():
         
     | 
| 
      
 285 
     | 
    
         
            +
                        raise FileNotFoundError(f"Project file not found: {project_path}")
         
     | 
| 
      
 286 
     | 
    
         
            +
             
     | 
| 
      
 287 
     | 
    
         
            +
                    com_rc = None
         
     | 
| 
      
 288 
     | 
    
         
            +
                    result = None
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 291 
     | 
    
         
            +
                        # Open HEC-RAS COM interface
         
     | 
| 
      
 292 
     | 
    
         
            +
                        com_string = RasControl.VERSION_MAP[normalized_version]
         
     | 
| 
      
 293 
     | 
    
         
            +
                        logger.info(f"Opening HEC-RAS: {com_string} (version: {version})")
         
     | 
| 
      
 294 
     | 
    
         
            +
                        com_rc = win32com.client.Dispatch(com_string)
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
                        # Open project
         
     | 
| 
      
 297 
     | 
    
         
            +
                        logger.info(f"Opening project: {project_path}")
         
     | 
| 
      
 298 
     | 
    
         
            +
                        com_rc.Project_Open(str(project_path))
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
                        # Perform operation
         
     | 
| 
      
 301 
     | 
    
         
            +
                        logger.info("Executing operation...")
         
     | 
| 
      
 302 
     | 
    
         
            +
                        result = operation_func(com_rc)
         
     | 
| 
      
 303 
     | 
    
         
            +
                        logger.info("Operation completed successfully")
         
     | 
| 
      
 304 
     | 
    
         
            +
             
     | 
| 
      
 305 
     | 
    
         
            +
                        return result
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 308 
     | 
    
         
            +
                        logger.error(f"Operation failed: {e}")
         
     | 
| 
      
 309 
     | 
    
         
            +
                        raise
         
     | 
| 
      
 310 
     | 
    
         
            +
             
     | 
| 
      
 311 
     | 
    
         
            +
                    finally:
         
     | 
| 
      
 312 
     | 
    
         
            +
                        # ALWAYS close
         
     | 
| 
      
 313 
     | 
    
         
            +
                        logger.info("Closing HEC-RAS...")
         
     | 
| 
      
 314 
     | 
    
         
            +
             
     | 
| 
      
 315 
     | 
    
         
            +
                        if com_rc is not None:
         
     | 
| 
      
 316 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 317 
     | 
    
         
            +
                                com_rc.QuitRas()
         
     | 
| 
      
 318 
     | 
    
         
            +
                                logger.info("HEC-RAS closed via QuitRas()")
         
     | 
| 
      
 319 
     | 
    
         
            +
                            except Exception as e:
         
     | 
| 
      
 320 
     | 
    
         
            +
                                logger.warning(f"QuitRas() failed: {e}")
         
     | 
| 
      
 321 
     | 
    
         
            +
             
     | 
| 
      
 322 
     | 
    
         
            +
                        _terminate_ras_process()
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
      
 324 
     | 
    
         
            +
                        if _is_ras_running():
         
     | 
| 
      
 325 
     | 
    
         
            +
                            logger.warning("HEC-RAS may still be running!")
         
     | 
| 
      
 326 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 327 
     | 
    
         
            +
                            logger.info("HEC-RAS fully closed")
         
     | 
| 
      
 328 
     | 
    
         
            +
             
     | 
| 
      
 329 
     | 
    
         
            +
                # ========== PUBLIC API (ras-commander style) ==========
         
     | 
| 
      
 330 
     | 
    
         
            +
             
     | 
| 
      
 331 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 332 
     | 
    
         
            +
                def run_plan(plan: Union[str, Path], ras_object=None) -> Tuple[bool, List[str]]:
         
     | 
| 
      
 333 
     | 
    
         
            +
                    """
         
     | 
| 
      
 334 
     | 
    
         
            +
                    Run a plan (steady or unsteady) and wait for completion.
         
     | 
| 
      
 335 
     | 
    
         
            +
             
     | 
| 
      
 336 
     | 
    
         
            +
                    This method starts the computation and polls Compute_Complete()
         
     | 
| 
      
 337 
     | 
    
         
            +
                    until the run finishes. It will block until completion.
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 340 
     | 
    
         
            +
                        plan: Plan number ("01", "02") or path to .prj file
         
     | 
| 
      
 341 
     | 
    
         
            +
                        ras_object: Optional RasPrj instance (uses global ras if None)
         
     | 
| 
      
 342 
     | 
    
         
            +
             
     | 
| 
      
 343 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 344 
     | 
    
         
            +
                        Tuple of (success: bool, messages: List[str])
         
     | 
| 
      
 345 
     | 
    
         
            +
             
     | 
| 
      
 346 
     | 
    
         
            +
                    Example:
         
     | 
| 
      
 347 
     | 
    
         
            +
                        >>> from ras_commander import init_ras_project, RasControl
         
     | 
| 
      
 348 
     | 
    
         
            +
                        >>> init_ras_project(path, "4.1")
         
     | 
| 
      
 349 
     | 
    
         
            +
                        >>> success, msgs = RasControl.run_plan("02")
         
     | 
| 
      
 350 
     | 
    
         
            +
                        >>> # Blocks until plan finishes running
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
                    Note:
         
     | 
| 
      
 353 
     | 
    
         
            +
                        Can take several minutes for large models or unsteady runs.
         
     | 
| 
      
 354 
     | 
    
         
            +
                        Progress is logged every 30 seconds.
         
     | 
| 
      
 355 
     | 
    
         
            +
                    """
         
     | 
| 
      
 356 
     | 
    
         
            +
                    project_path, version, plan_num, plan_name = RasControl._get_project_info(plan, ras_object)
         
     | 
| 
      
 357 
     | 
    
         
            +
             
     | 
| 
      
 358 
     | 
    
         
            +
                    def _run_operation(com_rc):
         
     | 
| 
      
 359 
     | 
    
         
            +
                        # Set current plan if we have plan_name (using plan number)
         
     | 
| 
      
 360 
     | 
    
         
            +
                        if plan_name:
         
     | 
| 
      
 361 
     | 
    
         
            +
                            logger.info(f"Setting current plan to: {plan_name}")
         
     | 
| 
      
 362 
     | 
    
         
            +
                            com_rc.Plan_SetCurrent(plan_name)
         
     | 
| 
      
 363 
     | 
    
         
            +
             
     | 
| 
      
 364 
     | 
    
         
            +
                        # Version-specific behavior (normalize for checking)
         
     | 
| 
      
 365 
     | 
    
         
            +
                        norm_version = RasControl._normalize_version(version)
         
     | 
| 
      
 366 
     | 
    
         
            +
             
     | 
| 
      
 367 
     | 
    
         
            +
                        # Start computation (returns immediately - ASYNCHRONOUS!)
         
     | 
| 
      
 368 
     | 
    
         
            +
                        logger.info("Starting computation...")
         
     | 
| 
      
 369 
     | 
    
         
            +
                        if norm_version.startswith('4') or norm_version.startswith('3'):
         
     | 
| 
      
 370 
     | 
    
         
            +
                            status, _, messages = com_rc.Compute_CurrentPlan(None, None)
         
     | 
| 
      
 371 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 372 
     | 
    
         
            +
                            status, _, messages, _ = com_rc.Compute_CurrentPlan(None, None)
         
     | 
| 
      
 373 
     | 
    
         
            +
             
     | 
| 
      
 374 
     | 
    
         
            +
                        # CRITICAL: Wait for computation to complete
         
     | 
| 
      
 375 
     | 
    
         
            +
                        # Compute_CurrentPlan is ASYNCHRONOUS - it returns before computation finishes
         
     | 
| 
      
 376 
     | 
    
         
            +
                        logger.info("Waiting for computation to complete...")
         
     | 
| 
      
 377 
     | 
    
         
            +
                        poll_count = 0
         
     | 
| 
      
 378 
     | 
    
         
            +
                        while True:
         
     | 
| 
      
 379 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 380 
     | 
    
         
            +
                                # Check if computation is complete
         
     | 
| 
      
 381 
     | 
    
         
            +
                                is_complete = com_rc.Compute_Complete()
         
     | 
| 
      
 382 
     | 
    
         
            +
             
     | 
| 
      
 383 
     | 
    
         
            +
                                if is_complete:
         
     | 
| 
      
 384 
     | 
    
         
            +
                                    logger.info(f"Computation completed (polled {poll_count} times)")
         
     | 
| 
      
 385 
     | 
    
         
            +
                                    break
         
     | 
| 
      
 386 
     | 
    
         
            +
             
     | 
| 
      
 387 
     | 
    
         
            +
                                # Still computing - wait and poll again
         
     | 
| 
      
 388 
     | 
    
         
            +
                                time.sleep(1)  # Poll every second
         
     | 
| 
      
 389 
     | 
    
         
            +
                                poll_count += 1
         
     | 
| 
      
 390 
     | 
    
         
            +
             
     | 
| 
      
 391 
     | 
    
         
            +
                                # Log progress every 30 seconds
         
     | 
| 
      
 392 
     | 
    
         
            +
                                if poll_count % 30 == 0:
         
     | 
| 
      
 393 
     | 
    
         
            +
                                    logger.info(f"Still computing... ({poll_count} seconds elapsed)")
         
     | 
| 
      
 394 
     | 
    
         
            +
             
     | 
| 
      
 395 
     | 
    
         
            +
                            except Exception as e:
         
     | 
| 
      
 396 
     | 
    
         
            +
                                logger.error(f"Error checking completion status: {e}")
         
     | 
| 
      
 397 
     | 
    
         
            +
                                # If we can't check status, break and hope for the best
         
     | 
| 
      
 398 
     | 
    
         
            +
                                break
         
     | 
| 
      
 399 
     | 
    
         
            +
             
     | 
| 
      
 400 
     | 
    
         
            +
                        return status, list(messages) if messages else []
         
     | 
| 
      
 401 
     | 
    
         
            +
             
     | 
| 
      
 402 
     | 
    
         
            +
                    return RasControl._com_open_close(project_path, version, _run_operation)
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 405 
     | 
    
         
            +
                def get_steady_results(plan: Union[str, Path], ras_object=None) -> pd.DataFrame:
         
     | 
| 
      
 406 
     | 
    
         
            +
                    """
         
     | 
| 
      
 407 
     | 
    
         
            +
                    Extract steady state profile results.
         
     | 
| 
      
 408 
     | 
    
         
            +
             
     | 
| 
      
 409 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 410 
     | 
    
         
            +
                        plan: Plan number ("01", "02") or path to .prj file
         
     | 
| 
      
 411 
     | 
    
         
            +
                        ras_object: Optional RasPrj instance (uses global ras if None)
         
     | 
| 
      
 412 
     | 
    
         
            +
             
     | 
| 
      
 413 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 414 
     | 
    
         
            +
                        DataFrame with columns: river, reach, node_id, profile, wsel,
         
     | 
| 
      
 415 
     | 
    
         
            +
                        velocity, flow, froude, energy, max_depth, min_ch_el
         
     | 
| 
      
 416 
     | 
    
         
            +
             
     | 
| 
      
 417 
     | 
    
         
            +
                    Example:
         
     | 
| 
      
 418 
     | 
    
         
            +
                        >>> from ras_commander import init_ras_project, RasControl
         
     | 
| 
      
 419 
     | 
    
         
            +
                        >>> init_ras_project(path, "4.1")
         
     | 
| 
      
 420 
     | 
    
         
            +
                        >>> df = RasControl.get_steady_results("02")
         
     | 
| 
      
 421 
     | 
    
         
            +
                        >>> df.to_csv('results.csv', index=False)
         
     | 
| 
      
 422 
     | 
    
         
            +
                    """
         
     | 
| 
      
 423 
     | 
    
         
            +
                    project_path, version, plan_num, plan_name = RasControl._get_project_info(plan, ras_object)
         
     | 
| 
      
 424 
     | 
    
         
            +
             
     | 
| 
      
 425 
     | 
    
         
            +
                    def _extract_operation(com_rc):
         
     | 
| 
      
 426 
     | 
    
         
            +
                        # Set current plan if we have plan_name (using plan number)
         
     | 
| 
      
 427 
     | 
    
         
            +
                        if plan_name:
         
     | 
| 
      
 428 
     | 
    
         
            +
                            logger.info(f"Setting current plan to: {plan_name}")
         
     | 
| 
      
 429 
     | 
    
         
            +
                            com_rc.Plan_SetCurrent(plan_name)
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
                        results = []
         
     | 
| 
      
 432 
     | 
    
         
            +
             
     | 
| 
      
 433 
     | 
    
         
            +
                        # Get profiles
         
     | 
| 
      
 434 
     | 
    
         
            +
                        _, profile_names = com_rc.Output_GetProfiles(2, None)
         
     | 
| 
      
 435 
     | 
    
         
            +
             
     | 
| 
      
 436 
     | 
    
         
            +
                        if profile_names is None:
         
     | 
| 
      
 437 
     | 
    
         
            +
                            raise RuntimeError(
         
     | 
| 
      
 438 
     | 
    
         
            +
                                "No steady state results found. Please ensure:\n"
         
     | 
| 
      
 439 
     | 
    
         
            +
                                "  1. The model has been run (use RasControl.run_plan() first)\n"
         
     | 
| 
      
 440 
     | 
    
         
            +
                                "  2. The current plan is a steady state plan\n"
         
     | 
| 
      
 441 
     | 
    
         
            +
                                "  3. Results were successfully computed"
         
     | 
| 
      
 442 
     | 
    
         
            +
                            )
         
     | 
| 
      
 443 
     | 
    
         
            +
             
     | 
| 
      
 444 
     | 
    
         
            +
                        profiles = [{'name': name, 'code': i+1} for i, name in enumerate(profile_names)]
         
     | 
| 
      
 445 
     | 
    
         
            +
                        logger.info(f"Found {len(profiles)} profiles")
         
     | 
| 
      
 446 
     | 
    
         
            +
             
     | 
| 
      
 447 
     | 
    
         
            +
                        # Get rivers
         
     | 
| 
      
 448 
     | 
    
         
            +
                        _, river_names = com_rc.Output_GetRivers(0, None)
         
     | 
| 
      
 449 
     | 
    
         
            +
             
     | 
| 
      
 450 
     | 
    
         
            +
                        if river_names is None:
         
     | 
| 
      
 451 
     | 
    
         
            +
                            raise RuntimeError("No river geometry found in model.")
         
     | 
| 
      
 452 
     | 
    
         
            +
             
     | 
| 
      
 453 
     | 
    
         
            +
                        logger.info(f"Found {len(river_names)} rivers")
         
     | 
| 
      
 454 
     | 
    
         
            +
             
     | 
| 
      
 455 
     | 
    
         
            +
                        # Extract data
         
     | 
| 
      
 456 
     | 
    
         
            +
                        for riv_code, riv_name in enumerate(river_names, start=1):
         
     | 
| 
      
 457 
     | 
    
         
            +
                            _, _, reach_names = com_rc.Geometry_GetReaches(riv_code, None, None)
         
     | 
| 
      
 458 
     | 
    
         
            +
             
     | 
| 
      
 459 
     | 
    
         
            +
                            for rch_code, rch_name in enumerate(reach_names, start=1):
         
     | 
| 
      
 460 
     | 
    
         
            +
                                _, _, _, node_ids, node_types = com_rc.Geometry_GetNodes(
         
     | 
| 
      
 461 
     | 
    
         
            +
                                    riv_code, rch_code, None, None, None
         
     | 
| 
      
 462 
     | 
    
         
            +
                                )
         
     | 
| 
      
 463 
     | 
    
         
            +
             
     | 
| 
      
 464 
     | 
    
         
            +
                                for node_code, (node_id, node_type) in enumerate(
         
     | 
| 
      
 465 
     | 
    
         
            +
                                    zip(node_ids, node_types), start=1
         
     | 
| 
      
 466 
     | 
    
         
            +
                                ):
         
     | 
| 
      
 467 
     | 
    
         
            +
                                    if node_type == '':  # Cross sections only
         
     | 
| 
      
 468 
     | 
    
         
            +
                                        for profile in profiles:
         
     | 
| 
      
 469 
     | 
    
         
            +
                                            try:
         
     | 
| 
      
 470 
     | 
    
         
            +
                                                row = {
         
     | 
| 
      
 471 
     | 
    
         
            +
                                                    'river': riv_name.strip(),
         
     | 
| 
      
 472 
     | 
    
         
            +
                                                    'reach': rch_name.strip(),
         
     | 
| 
      
 473 
     | 
    
         
            +
                                                    'node_id': node_id.strip(),
         
     | 
| 
      
 474 
     | 
    
         
            +
                                                    'profile': profile['name'].strip(),
         
     | 
| 
      
 475 
     | 
    
         
            +
                                                }
         
     | 
| 
      
 476 
     | 
    
         
            +
             
     | 
| 
      
 477 
     | 
    
         
            +
                                                # Extract output variables
         
     | 
| 
      
 478 
     | 
    
         
            +
                                                row['wsel'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 479 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 480 
     | 
    
         
            +
                                                    profile['code'], RasControl.WSEL
         
     | 
| 
      
 481 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 482 
     | 
    
         
            +
             
     | 
| 
      
 483 
     | 
    
         
            +
                                                row['min_ch_el'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 484 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 485 
     | 
    
         
            +
                                                    profile['code'], RasControl.MIN_CH_EL
         
     | 
| 
      
 486 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 487 
     | 
    
         
            +
             
     | 
| 
      
 488 
     | 
    
         
            +
                                                row['velocity'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 489 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 490 
     | 
    
         
            +
                                                    profile['code'], RasControl.VEL_TOTAL
         
     | 
| 
      
 491 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 492 
     | 
    
         
            +
             
     | 
| 
      
 493 
     | 
    
         
            +
                                                row['flow'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 494 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 495 
     | 
    
         
            +
                                                    profile['code'], RasControl.FLOW_TOTAL
         
     | 
| 
      
 496 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 497 
     | 
    
         
            +
             
     | 
| 
      
 498 
     | 
    
         
            +
                                                row['froude'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 499 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 500 
     | 
    
         
            +
                                                    profile['code'], RasControl.FROUDE_CHL
         
     | 
| 
      
 501 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 502 
     | 
    
         
            +
             
     | 
| 
      
 503 
     | 
    
         
            +
                                                row['energy'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 504 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 505 
     | 
    
         
            +
                                                    profile['code'], RasControl.ENERGY
         
     | 
| 
      
 506 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 507 
     | 
    
         
            +
             
     | 
| 
      
 508 
     | 
    
         
            +
                                                row['max_depth'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 509 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 510 
     | 
    
         
            +
                                                    profile['code'], RasControl.MAX_CHL_DPTH
         
     | 
| 
      
 511 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 512 
     | 
    
         
            +
             
     | 
| 
      
 513 
     | 
    
         
            +
                                                results.append(row)
         
     | 
| 
      
 514 
     | 
    
         
            +
             
     | 
| 
      
 515 
     | 
    
         
            +
                                            except Exception as e:
         
     | 
| 
      
 516 
     | 
    
         
            +
                                                logger.warning(
         
     | 
| 
      
 517 
     | 
    
         
            +
                                                    f"Failed to extract {riv_name}/{rch_name}/"
         
     | 
| 
      
 518 
     | 
    
         
            +
                                                    f"{node_id} profile {profile['name']}: {e}"
         
     | 
| 
      
 519 
     | 
    
         
            +
                                                )
         
     | 
| 
      
 520 
     | 
    
         
            +
             
     | 
| 
      
 521 
     | 
    
         
            +
                        logger.info(f"Extracted {len(results)} result rows")
         
     | 
| 
      
 522 
     | 
    
         
            +
                        return pd.DataFrame(results)
         
     | 
| 
      
 523 
     | 
    
         
            +
             
     | 
| 
      
 524 
     | 
    
         
            +
                    return RasControl._com_open_close(project_path, version, _extract_operation)
         
     | 
| 
      
 525 
     | 
    
         
            +
             
     | 
| 
      
 526 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 527 
     | 
    
         
            +
                def get_unsteady_results(plan: Union[str, Path], max_times: int = None,
         
     | 
| 
      
 528 
     | 
    
         
            +
                                        ras_object=None) -> pd.DataFrame:
         
     | 
| 
      
 529 
     | 
    
         
            +
                    """
         
     | 
| 
      
 530 
     | 
    
         
            +
                    Extract unsteady flow time series results.
         
     | 
| 
      
 531 
     | 
    
         
            +
             
     | 
| 
      
 532 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 533 
     | 
    
         
            +
                        plan: Plan number ("01", "02") or path to .prj file
         
     | 
| 
      
 534 
     | 
    
         
            +
                        max_times: Optional limit on timesteps (for testing/large datasets)
         
     | 
| 
      
 535 
     | 
    
         
            +
                        ras_object: Optional RasPrj instance (uses global ras if None)
         
     | 
| 
      
 536 
     | 
    
         
            +
             
     | 
| 
      
 537 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 538 
     | 
    
         
            +
                        DataFrame with columns: river, reach, node_id, time_index, time_string,
         
     | 
| 
      
 539 
     | 
    
         
            +
                        wsel, velocity, flow, froude, energy, max_depth, min_ch_el
         
     | 
| 
      
 540 
     | 
    
         
            +
             
     | 
| 
      
 541 
     | 
    
         
            +
                    Important - Understanding "Max WS":
         
     | 
| 
      
 542 
     | 
    
         
            +
                        The first row (time_index=1, time_string="Max WS") contains the MAXIMUM
         
     | 
| 
      
 543 
     | 
    
         
            +
                        values that occurred at ANY computational timestep during the simulation,
         
     | 
| 
      
 544 
     | 
    
         
            +
                        not just at output intervals. This is critical data for identifying peaks.
         
     | 
| 
      
 545 
     | 
    
         
            +
             
     | 
| 
      
 546 
     | 
    
         
            +
                        For time series plotting, filter to actual timesteps:
         
     | 
| 
      
 547 
     | 
    
         
            +
                            df_timeseries = df[df['time_string'] != 'Max WS']
         
     | 
| 
      
 548 
     | 
    
         
            +
             
     | 
| 
      
 549 
     | 
    
         
            +
                        For showing maximum as reference line:
         
     | 
| 
      
 550 
     | 
    
         
            +
                            max_wse = df[df['time_string'] == 'Max WS']['wsel'].iloc[0]
         
     | 
| 
      
 551 
     | 
    
         
            +
                            plt.axhline(max_wse, color='r', linestyle='--', label='Max WS')
         
     | 
| 
      
 552 
     | 
    
         
            +
             
     | 
| 
      
 553 
     | 
    
         
            +
                    Example:
         
     | 
| 
      
 554 
     | 
    
         
            +
                        >>> from ras_commander import init_ras_project, RasControl
         
     | 
| 
      
 555 
     | 
    
         
            +
                        >>> init_ras_project(path, "4.1")
         
     | 
| 
      
 556 
     | 
    
         
            +
                        >>> df = RasControl.get_unsteady_results("01", max_times=10)
         
     | 
| 
      
 557 
     | 
    
         
            +
                        >>> # Includes "Max WS" as first row
         
     | 
| 
      
 558 
     | 
    
         
            +
                    """
         
     | 
| 
      
 559 
     | 
    
         
            +
                    project_path, version, plan_num, plan_name = RasControl._get_project_info(plan, ras_object)
         
     | 
| 
      
 560 
     | 
    
         
            +
             
     | 
| 
      
 561 
     | 
    
         
            +
                    def _extract_operation(com_rc):
         
     | 
| 
      
 562 
     | 
    
         
            +
                        # Set current plan if we have plan_name (using plan number)
         
     | 
| 
      
 563 
     | 
    
         
            +
                        if plan_name:
         
     | 
| 
      
 564 
     | 
    
         
            +
                            logger.info(f"Setting current plan to: {plan_name}")
         
     | 
| 
      
 565 
     | 
    
         
            +
                            com_rc.Plan_SetCurrent(plan_name)
         
     | 
| 
      
 566 
     | 
    
         
            +
             
     | 
| 
      
 567 
     | 
    
         
            +
                        results = []
         
     | 
| 
      
 568 
     | 
    
         
            +
             
     | 
| 
      
 569 
     | 
    
         
            +
                        # Get output times
         
     | 
| 
      
 570 
     | 
    
         
            +
                        _, time_strings = com_rc.Output_GetProfiles(0, None)
         
     | 
| 
      
 571 
     | 
    
         
            +
             
     | 
| 
      
 572 
     | 
    
         
            +
                        if time_strings is None:
         
     | 
| 
      
 573 
     | 
    
         
            +
                            raise RuntimeError(
         
     | 
| 
      
 574 
     | 
    
         
            +
                                "No unsteady results found. Please ensure:\n"
         
     | 
| 
      
 575 
     | 
    
         
            +
                                "  1. The model has been run (use RasControl.run_plan() first)\n"
         
     | 
| 
      
 576 
     | 
    
         
            +
                                "  2. The current plan is an unsteady flow plan\n"
         
     | 
| 
      
 577 
     | 
    
         
            +
                                "  3. Results were successfully computed"
         
     | 
| 
      
 578 
     | 
    
         
            +
                            )
         
     | 
| 
      
 579 
     | 
    
         
            +
             
     | 
| 
      
 580 
     | 
    
         
            +
                        times = list(time_strings)
         
     | 
| 
      
 581 
     | 
    
         
            +
                        if max_times:
         
     | 
| 
      
 582 
     | 
    
         
            +
                            times = times[:max_times]
         
     | 
| 
      
 583 
     | 
    
         
            +
             
     | 
| 
      
 584 
     | 
    
         
            +
                        logger.info(f"Extracting {len(times)} time steps")
         
     | 
| 
      
 585 
     | 
    
         
            +
             
     | 
| 
      
 586 
     | 
    
         
            +
                        # Get rivers
         
     | 
| 
      
 587 
     | 
    
         
            +
                        _, river_names = com_rc.Output_GetRivers(0, None)
         
     | 
| 
      
 588 
     | 
    
         
            +
             
     | 
| 
      
 589 
     | 
    
         
            +
                        if river_names is None:
         
     | 
| 
      
 590 
     | 
    
         
            +
                            raise RuntimeError("No river geometry found in model.")
         
     | 
| 
      
 591 
     | 
    
         
            +
             
     | 
| 
      
 592 
     | 
    
         
            +
                        logger.info(f"Found {len(river_names)} rivers")
         
     | 
| 
      
 593 
     | 
    
         
            +
             
     | 
| 
      
 594 
     | 
    
         
            +
                        # Extract data
         
     | 
| 
      
 595 
     | 
    
         
            +
                        for riv_code, riv_name in enumerate(river_names, start=1):
         
     | 
| 
      
 596 
     | 
    
         
            +
                            _, _, reach_names = com_rc.Geometry_GetReaches(riv_code, None, None)
         
     | 
| 
      
 597 
     | 
    
         
            +
             
     | 
| 
      
 598 
     | 
    
         
            +
                            for rch_code, rch_name in enumerate(reach_names, start=1):
         
     | 
| 
      
 599 
     | 
    
         
            +
                                _, _, _, node_ids, node_types = com_rc.Geometry_GetNodes(
         
     | 
| 
      
 600 
     | 
    
         
            +
                                    riv_code, rch_code, None, None, None
         
     | 
| 
      
 601 
     | 
    
         
            +
                                )
         
     | 
| 
      
 602 
     | 
    
         
            +
             
     | 
| 
      
 603 
     | 
    
         
            +
                                for node_code, (node_id, node_type) in enumerate(
         
     | 
| 
      
 604 
     | 
    
         
            +
                                    zip(node_ids, node_types), start=1
         
     | 
| 
      
 605 
     | 
    
         
            +
                                ):
         
     | 
| 
      
 606 
     | 
    
         
            +
                                    if node_type == '':  # Cross sections only
         
     | 
| 
      
 607 
     | 
    
         
            +
                                        for time_idx, time_str in enumerate(times, start=1):
         
     | 
| 
      
 608 
     | 
    
         
            +
                                            try:
         
     | 
| 
      
 609 
     | 
    
         
            +
                                                row = {
         
     | 
| 
      
 610 
     | 
    
         
            +
                                                    'river': riv_name.strip(),
         
     | 
| 
      
 611 
     | 
    
         
            +
                                                    'reach': rch_name.strip(),
         
     | 
| 
      
 612 
     | 
    
         
            +
                                                    'node_id': node_id.strip(),
         
     | 
| 
      
 613 
     | 
    
         
            +
                                                    'time_index': time_idx,
         
     | 
| 
      
 614 
     | 
    
         
            +
                                                    'time_string': time_str.strip(),
         
     | 
| 
      
 615 
     | 
    
         
            +
                                                }
         
     | 
| 
      
 616 
     | 
    
         
            +
             
     | 
| 
      
 617 
     | 
    
         
            +
                                                # Extract output variables (time_idx is profile code for unsteady)
         
     | 
| 
      
 618 
     | 
    
         
            +
                                                row['wsel'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 619 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 620 
     | 
    
         
            +
                                                    time_idx, RasControl.WSEL
         
     | 
| 
      
 621 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 622 
     | 
    
         
            +
             
     | 
| 
      
 623 
     | 
    
         
            +
                                                row['min_ch_el'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 624 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 625 
     | 
    
         
            +
                                                    time_idx, RasControl.MIN_CH_EL
         
     | 
| 
      
 626 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 627 
     | 
    
         
            +
             
     | 
| 
      
 628 
     | 
    
         
            +
                                                row['velocity'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 629 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 630 
     | 
    
         
            +
                                                    time_idx, RasControl.VEL_TOTAL
         
     | 
| 
      
 631 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 632 
     | 
    
         
            +
             
     | 
| 
      
 633 
     | 
    
         
            +
                                                row['flow'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 634 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 635 
     | 
    
         
            +
                                                    time_idx, RasControl.FLOW_TOTAL
         
     | 
| 
      
 636 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 637 
     | 
    
         
            +
             
     | 
| 
      
 638 
     | 
    
         
            +
                                                row['froude'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 639 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 640 
     | 
    
         
            +
                                                    time_idx, RasControl.FROUDE_CHL
         
     | 
| 
      
 641 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 642 
     | 
    
         
            +
             
     | 
| 
      
 643 
     | 
    
         
            +
                                                row['energy'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 644 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 645 
     | 
    
         
            +
                                                    time_idx, RasControl.ENERGY
         
     | 
| 
      
 646 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 647 
     | 
    
         
            +
             
     | 
| 
      
 648 
     | 
    
         
            +
                                                row['max_depth'] = com_rc.Output_NodeOutput(
         
     | 
| 
      
 649 
     | 
    
         
            +
                                                    riv_code, rch_code, node_code, 0,
         
     | 
| 
      
 650 
     | 
    
         
            +
                                                    time_idx, RasControl.MAX_CHL_DPTH
         
     | 
| 
      
 651 
     | 
    
         
            +
                                                )[0]
         
     | 
| 
      
 652 
     | 
    
         
            +
             
     | 
| 
      
 653 
     | 
    
         
            +
                                                results.append(row)
         
     | 
| 
      
 654 
     | 
    
         
            +
             
     | 
| 
      
 655 
     | 
    
         
            +
                                            except Exception as e:
         
     | 
| 
      
 656 
     | 
    
         
            +
                                                logger.warning(
         
     | 
| 
      
 657 
     | 
    
         
            +
                                                    f"Failed to extract {riv_name}/{rch_name}/"
         
     | 
| 
      
 658 
     | 
    
         
            +
                                                    f"{node_id} time {time_str}: {e}"
         
     | 
| 
      
 659 
     | 
    
         
            +
                                                )
         
     | 
| 
      
 660 
     | 
    
         
            +
             
     | 
| 
      
 661 
     | 
    
         
            +
                        logger.info(f"Extracted {len(results)} result rows")
         
     | 
| 
      
 662 
     | 
    
         
            +
                        return pd.DataFrame(results)
         
     | 
| 
      
 663 
     | 
    
         
            +
             
     | 
| 
      
 664 
     | 
    
         
            +
                    return RasControl._com_open_close(project_path, version, _extract_operation)
         
     | 
| 
      
 665 
     | 
    
         
            +
             
     | 
| 
      
 666 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 667 
     | 
    
         
            +
                def get_output_times(plan: Union[str, Path], ras_object=None) -> List[str]:
         
     | 
| 
      
 668 
     | 
    
         
            +
                    """
         
     | 
| 
      
 669 
     | 
    
         
            +
                    Get list of output times for unsteady run.
         
     | 
| 
      
 670 
     | 
    
         
            +
             
     | 
| 
      
 671 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 672 
     | 
    
         
            +
                        plan: Plan number ("01", "02") or path to .prj file
         
     | 
| 
      
 673 
     | 
    
         
            +
                        ras_object: Optional RasPrj instance (uses global ras if None)
         
     | 
| 
      
 674 
     | 
    
         
            +
             
     | 
| 
      
 675 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 676 
     | 
    
         
            +
                        List of time strings (e.g., ["01JAN2000 0000", ...])
         
     | 
| 
      
 677 
     | 
    
         
            +
             
     | 
| 
      
 678 
     | 
    
         
            +
                    Example:
         
     | 
| 
      
 679 
     | 
    
         
            +
                        >>> times = RasControl.get_output_times("01")
         
     | 
| 
      
 680 
     | 
    
         
            +
                        >>> print(f"Found {len(times)} output times")
         
     | 
| 
      
 681 
     | 
    
         
            +
                    """
         
     | 
| 
      
 682 
     | 
    
         
            +
                    project_path, version, plan_num, plan_name = RasControl._get_project_info(plan, ras_object)
         
     | 
| 
      
 683 
     | 
    
         
            +
             
     | 
| 
      
 684 
     | 
    
         
            +
                    def _get_times(com_rc):
         
     | 
| 
      
 685 
     | 
    
         
            +
                        # Set current plan if we have plan_name (using plan number)
         
     | 
| 
      
 686 
     | 
    
         
            +
                        if plan_name:
         
     | 
| 
      
 687 
     | 
    
         
            +
                            logger.info(f"Setting current plan to: {plan_name}")
         
     | 
| 
      
 688 
     | 
    
         
            +
                            com_rc.Plan_SetCurrent(plan_name)
         
     | 
| 
      
 689 
     | 
    
         
            +
             
     | 
| 
      
 690 
     | 
    
         
            +
                        _, time_strings = com_rc.Output_GetProfiles(0, None)
         
     | 
| 
      
 691 
     | 
    
         
            +
             
     | 
| 
      
 692 
     | 
    
         
            +
                        if time_strings is None:
         
     | 
| 
      
 693 
     | 
    
         
            +
                            raise RuntimeError(
         
     | 
| 
      
 694 
     | 
    
         
            +
                                "No unsteady output times found. Ensure plan has been run."
         
     | 
| 
      
 695 
     | 
    
         
            +
                            )
         
     | 
| 
      
 696 
     | 
    
         
            +
             
     | 
| 
      
 697 
     | 
    
         
            +
                        times = list(time_strings)
         
     | 
| 
      
 698 
     | 
    
         
            +
                        logger.info(f"Found {len(times)} output times")
         
     | 
| 
      
 699 
     | 
    
         
            +
                        return times
         
     | 
| 
      
 700 
     | 
    
         
            +
             
     | 
| 
      
 701 
     | 
    
         
            +
                    return RasControl._com_open_close(project_path, version, _get_times)
         
     | 
| 
      
 702 
     | 
    
         
            +
             
     | 
| 
      
 703 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 704 
     | 
    
         
            +
                def get_plans(plan: Union[str, Path], ras_object=None) -> List[dict]:
         
     | 
| 
      
 705 
     | 
    
         
            +
                    """
         
     | 
| 
      
 706 
     | 
    
         
            +
                    Get list of plans in project.
         
     | 
| 
      
 707 
     | 
    
         
            +
             
     | 
| 
      
 708 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 709 
     | 
    
         
            +
                        plan: Plan number or path to .prj file
         
     | 
| 
      
 710 
     | 
    
         
            +
                        ras_object: Optional RasPrj instance
         
     | 
| 
      
 711 
     | 
    
         
            +
             
     | 
| 
      
 712 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 713 
     | 
    
         
            +
                        List of dicts with 'name' and 'filename' keys
         
     | 
| 
      
 714 
     | 
    
         
            +
                    """
         
     | 
| 
      
 715 
     | 
    
         
            +
                    project_path, version, _, _ = RasControl._get_project_info(plan, ras_object)
         
     | 
| 
      
 716 
     | 
    
         
            +
             
     | 
| 
      
 717 
     | 
    
         
            +
                    def _get_plans(com_rc):
         
     | 
| 
      
 718 
     | 
    
         
            +
                        # Don't set current plan - just getting list
         
     | 
| 
      
 719 
     | 
    
         
            +
                        _, plan_names, _ = com_rc.Plan_Names(None, None, None)
         
     | 
| 
      
 720 
     | 
    
         
            +
             
     | 
| 
      
 721 
     | 
    
         
            +
                        plans = []
         
     | 
| 
      
 722 
     | 
    
         
            +
                        for name in plan_names:
         
     | 
| 
      
 723 
     | 
    
         
            +
                            filename, _ = com_rc.Plan_GetFilename(name)
         
     | 
| 
      
 724 
     | 
    
         
            +
                            plans.append({'name': name, 'filename': filename})
         
     | 
| 
      
 725 
     | 
    
         
            +
             
     | 
| 
      
 726 
     | 
    
         
            +
                        logger.info(f"Found {len(plans)} plans")
         
     | 
| 
      
 727 
     | 
    
         
            +
                        return plans
         
     | 
| 
      
 728 
     | 
    
         
            +
             
     | 
| 
      
 729 
     | 
    
         
            +
                    return RasControl._com_open_close(project_path, version, _get_plans)
         
     | 
| 
      
 730 
     | 
    
         
            +
             
     | 
| 
      
 731 
     | 
    
         
            +
                @staticmethod
         
     | 
| 
      
 732 
     | 
    
         
            +
                def set_current_plan(plan: Union[str, Path], ras_object=None) -> bool:
         
     | 
| 
      
 733 
     | 
    
         
            +
                    """
         
     | 
| 
      
 734 
     | 
    
         
            +
                    Set the current/active plan by plan number.
         
     | 
| 
      
 735 
     | 
    
         
            +
             
     | 
| 
      
 736 
     | 
    
         
            +
                    Note: This is rarely needed - run_plan() and get_*_results()
         
     | 
| 
      
 737 
     | 
    
         
            +
                    automatically set the correct plan. This is provided for
         
     | 
| 
      
 738 
     | 
    
         
            +
                    advanced use cases.
         
     | 
| 
      
 739 
     | 
    
         
            +
             
     | 
| 
      
 740 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 741 
     | 
    
         
            +
                        plan: Plan number ("01", "02") or path to .prj file
         
     | 
| 
      
 742 
     | 
    
         
            +
                        ras_object: Optional RasPrj instance
         
     | 
| 
      
 743 
     | 
    
         
            +
             
     | 
| 
      
 744 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 745 
     | 
    
         
            +
                        True if successful
         
     | 
| 
      
 746 
     | 
    
         
            +
             
     | 
| 
      
 747 
     | 
    
         
            +
                    Example:
         
     | 
| 
      
 748 
     | 
    
         
            +
                        >>> RasControl.set_current_plan("02")  # Set to Plan 02
         
     | 
| 
      
 749 
     | 
    
         
            +
                    """
         
     | 
| 
      
 750 
     | 
    
         
            +
                    project_path, version, plan_num, plan_name = RasControl._get_project_info(plan, ras_object)
         
     | 
| 
      
 751 
     | 
    
         
            +
             
     | 
| 
      
 752 
     | 
    
         
            +
                    if not plan_name:
         
     | 
| 
      
 753 
     | 
    
         
            +
                        raise ValueError("Cannot set current plan - plan name could not be determined")
         
     | 
| 
      
 754 
     | 
    
         
            +
             
     | 
| 
      
 755 
     | 
    
         
            +
                    def _set_plan(com_rc):
         
     | 
| 
      
 756 
     | 
    
         
            +
                        com_rc.Plan_SetCurrent(plan_name)
         
     | 
| 
      
 757 
     | 
    
         
            +
                        logger.info(f"Set current plan to Plan {plan_num}: {plan_name}")
         
     | 
| 
      
 758 
     | 
    
         
            +
                        return True
         
     | 
| 
      
 759 
     | 
    
         
            +
             
     | 
| 
      
 760 
     | 
    
         
            +
                    return RasControl._com_open_close(project_path, version, _set_plan)
         
     | 
| 
      
 761 
     | 
    
         
            +
             
     | 
| 
      
 762 
     | 
    
         
            +
             
     | 
| 
      
 763 
     | 
    
         
            +
            if __name__ == '__main__':
         
     | 
| 
      
 764 
     | 
    
         
            +
                logging.basicConfig(
         
     | 
| 
      
 765 
     | 
    
         
            +
                    level=logging.INFO,
         
     | 
| 
      
 766 
     | 
    
         
            +
                    format='%(asctime)s - %(levelname)s - %(message)s'
         
     | 
| 
      
 767 
     | 
    
         
            +
                )
         
     | 
| 
      
 768 
     | 
    
         
            +
             
     | 
| 
      
 769 
     | 
    
         
            +
                print("RasControl (ras-commander API) loaded successfully")
         
     | 
| 
      
 770 
     | 
    
         
            +
                print(f"Supported versions: {list(RasControl.SUPPORTED_VERSIONS.keys())}")
         
     | 
| 
      
 771 
     | 
    
         
            +
                print("\nUsage example:")
         
     | 
| 
      
 772 
     | 
    
         
            +
                print("  from ras_commander import init_ras_project, RasControl")
         
     | 
| 
      
 773 
     | 
    
         
            +
                print("  init_ras_project(path, '4.1')")
         
     | 
| 
      
 774 
     | 
    
         
            +
                print("  df = RasControl.get_steady_results('02')")
         
     | 
    
        ras_commander/RasPlan.py
    CHANGED
    
    | 
         @@ -652,29 +652,44 @@ class RasPlan: 
     | 
|
| 
       652 
652 
     | 
    
         | 
| 
       653 
653 
     | 
    
         
             
                @staticmethod
         
     | 
| 
       654 
654 
     | 
    
         
             
                @log_call
         
     | 
| 
       655 
     | 
    
         
            -
                def clone_plan(template_plan,  
     | 
| 
      
 655 
     | 
    
         
            +
                def clone_plan(template_plan, new_shortid=None, new_title=None, ras_object=None):
         
     | 
| 
       656 
656 
     | 
    
         
             
                    """
         
     | 
| 
       657 
657 
     | 
    
         
             
                    Create a new plan file based on a template and update the project file.
         
     | 
| 
       658 
     | 
    
         
            -
             
     | 
| 
      
 658 
     | 
    
         
            +
             
     | 
| 
       659 
659 
     | 
    
         
             
                    Parameters:
         
     | 
| 
       660 
660 
     | 
    
         
             
                    template_plan (str): Plan number to use as template (e.g., '01')
         
     | 
| 
       661 
     | 
    
         
            -
                     
     | 
| 
      
 661 
     | 
    
         
            +
                    new_shortid (str, optional): New short identifier for the plan file (max 24 chars).
         
     | 
| 
      
 662 
     | 
    
         
            +
                                                 If not provided, appends '_copy' to original.
         
     | 
| 
      
 663 
     | 
    
         
            +
                    new_title (str, optional): New plan title (max 32 chars, updates "Plan Title=" line).
         
     | 
| 
      
 664 
     | 
    
         
            +
                                               If not provided, keeps original title.
         
     | 
| 
       662 
665 
     | 
    
         
             
                    ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
         
     | 
| 
       663 
     | 
    
         
            -
             
     | 
| 
      
 666 
     | 
    
         
            +
             
     | 
| 
       664 
667 
     | 
    
         
             
                    Returns:
         
     | 
| 
       665 
668 
     | 
    
         
             
                    str: New plan number
         
     | 
| 
       666 
     | 
    
         
            -
             
     | 
| 
      
 669 
     | 
    
         
            +
             
     | 
| 
       667 
670 
     | 
    
         
             
                    Example:
         
     | 
| 
       668 
     | 
    
         
            -
                    >>>  
     | 
| 
       669 
     | 
    
         
            -
                    >>>  
     | 
| 
       670 
     | 
    
         
            -
                    >>> 
     | 
| 
      
 671 
     | 
    
         
            +
                    >>> # Clone with default shortid and title
         
     | 
| 
      
 672 
     | 
    
         
            +
                    >>> new_plan = RasPlan.clone_plan('01')
         
     | 
| 
      
 673 
     | 
    
         
            +
                    >>>
         
     | 
| 
      
 674 
     | 
    
         
            +
                    >>> # Clone with custom shortid and title
         
     | 
| 
      
 675 
     | 
    
         
            +
                    >>> new_plan = RasPlan.clone_plan('01',
         
     | 
| 
      
 676 
     | 
    
         
            +
                    ...                               new_shortid='Steady_v41',
         
     | 
| 
      
 677 
     | 
    
         
            +
                    ...                               new_title='Steady Flow - HEC-RAS 4.1')
         
     | 
| 
       671 
678 
     | 
    
         | 
| 
       672 
679 
     | 
    
         
             
                    Note:
         
     | 
| 
      
 680 
     | 
    
         
            +
                        Both new_shortid and new_title are optional.
         
     | 
| 
       673 
681 
     | 
    
         
             
                        This function updates the ras object's dataframes after modifying the project structure.
         
     | 
| 
       674 
682 
     | 
    
         
             
                    """
         
     | 
| 
       675 
683 
     | 
    
         
             
                    ras_obj = ras_object or ras
         
     | 
| 
       676 
684 
     | 
    
         
             
                    ras_obj.check_initialized()
         
     | 
| 
       677 
685 
     | 
    
         | 
| 
      
 686 
     | 
    
         
            +
                    # Validate new_title length if provided
         
     | 
| 
      
 687 
     | 
    
         
            +
                    if new_title is not None and len(new_title) > 32:
         
     | 
| 
      
 688 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 689 
     | 
    
         
            +
                            f"Plan title must be 32 characters or less. "
         
     | 
| 
      
 690 
     | 
    
         
            +
                            f"Got {len(new_title)} characters: '{new_title}'"
         
     | 
| 
      
 691 
     | 
    
         
            +
                        )
         
     | 
| 
      
 692 
     | 
    
         
            +
             
     | 
| 
       678 
693 
     | 
    
         
             
                    # Update plan entries without reinitializing the entire project
         
     | 
| 
       679 
694 
     | 
    
         
             
                    ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
         
     | 
| 
       680 
695 
     | 
    
         | 
| 
         @@ -682,22 +697,32 @@ class RasPlan: 
     | 
|
| 
       682 
697 
     | 
    
         
             
                    template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
         
     | 
| 
       683 
698 
     | 
    
         
             
                    new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
         
     | 
| 
       684 
699 
     | 
    
         | 
| 
       685 
     | 
    
         
            -
                    def  
     | 
| 
      
 700 
     | 
    
         
            +
                    def update_plan_metadata(lines):
         
     | 
| 
      
 701 
     | 
    
         
            +
                        """Update both Plan Title and Short Identifier"""
         
     | 
| 
      
 702 
     | 
    
         
            +
                        title_pattern = re.compile(r'^Plan Title=(.*)$', re.IGNORECASE)
         
     | 
| 
       686 
703 
     | 
    
         
             
                        shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
         
     | 
| 
      
 704 
     | 
    
         
            +
             
     | 
| 
       687 
705 
     | 
    
         
             
                        for i, line in enumerate(lines):
         
     | 
| 
       688 
     | 
    
         
            -
                             
     | 
| 
       689 
     | 
    
         
            -
                             
     | 
| 
       690 
     | 
    
         
            -
             
     | 
| 
       691 
     | 
    
         
            -
                                 
     | 
| 
       692 
     | 
    
         
            -
             
     | 
| 
      
 706 
     | 
    
         
            +
                            # Update Plan Title if new_title provided
         
     | 
| 
      
 707 
     | 
    
         
            +
                            title_match = title_pattern.match(line.strip())
         
     | 
| 
      
 708 
     | 
    
         
            +
                            if title_match and new_title is not None:
         
     | 
| 
      
 709 
     | 
    
         
            +
                                lines[i] = f"Plan Title={new_title[:32]}\n"
         
     | 
| 
      
 710 
     | 
    
         
            +
                                continue
         
     | 
| 
      
 711 
     | 
    
         
            +
             
     | 
| 
      
 712 
     | 
    
         
            +
                            # Update Short Identifier
         
     | 
| 
      
 713 
     | 
    
         
            +
                            shortid_match = shortid_pattern.match(line.strip())
         
     | 
| 
      
 714 
     | 
    
         
            +
                            if shortid_match:
         
     | 
| 
      
 715 
     | 
    
         
            +
                                current_shortid = shortid_match.group(1)
         
     | 
| 
      
 716 
     | 
    
         
            +
                                if new_shortid is None:
         
     | 
| 
      
 717 
     | 
    
         
            +
                                    new_shortid_value = (current_shortid + "_copy")[:24]
         
     | 
| 
       693 
718 
     | 
    
         
             
                                else:
         
     | 
| 
       694 
     | 
    
         
            -
                                     
     | 
| 
       695 
     | 
    
         
            -
                                lines[i] = f"Short Identifier={ 
     | 
| 
       696 
     | 
    
         
            -
             
     | 
| 
      
 719 
     | 
    
         
            +
                                    new_shortid_value = new_shortid[:24]
         
     | 
| 
      
 720 
     | 
    
         
            +
                                lines[i] = f"Short Identifier={new_shortid_value}\n"
         
     | 
| 
      
 721 
     | 
    
         
            +
             
     | 
| 
       697 
722 
     | 
    
         
             
                        return lines
         
     | 
| 
       698 
723 
     | 
    
         | 
| 
       699 
     | 
    
         
            -
                    # Use RasUtils to clone the file and update  
     | 
| 
       700 
     | 
    
         
            -
                    RasUtils.clone_file(template_plan_path, new_plan_path,  
     | 
| 
      
 724 
     | 
    
         
            +
                    # Use RasUtils to clone the file and update metadata
         
     | 
| 
      
 725 
     | 
    
         
            +
                    RasUtils.clone_file(template_plan_path, new_plan_path, update_plan_metadata)
         
     | 
| 
       701 
726 
     | 
    
         | 
| 
       702 
727 
     | 
    
         
             
                    # Use RasUtils to update the project file
         
     | 
| 
       703 
728 
     | 
    
         
             
                    RasUtils.update_project_file(ras_obj.prj_file, 'Plan', new_plan_num, ras_object=ras_obj)
         
     | 
| 
         @@ -714,21 +739,22 @@ class RasPlan: 
     | 
|
| 
       714 
739 
     | 
    
         | 
| 
       715 
740 
     | 
    
         
             
                @staticmethod
         
     | 
| 
       716 
741 
     | 
    
         
             
                @log_call
         
     | 
| 
       717 
     | 
    
         
            -
                def clone_unsteady(template_unsteady, ras_object=None):
         
     | 
| 
      
 742 
     | 
    
         
            +
                def clone_unsteady(template_unsteady, new_title=None, ras_object=None):
         
     | 
| 
       718 
743 
     | 
    
         
             
                    """
         
     | 
| 
       719 
744 
     | 
    
         
             
                    Copy unsteady flow files from a template, find the next unsteady number,
         
     | 
| 
       720 
745 
     | 
    
         
             
                    and update the project file accordingly.
         
     | 
| 
       721 
746 
     | 
    
         | 
| 
       722 
747 
     | 
    
         
             
                    Parameters:
         
     | 
| 
       723 
748 
     | 
    
         
             
                    template_unsteady (str): Unsteady flow number to be used as a template (e.g., '01')
         
     | 
| 
      
 749 
     | 
    
         
            +
                    new_title (str, optional): New flow title (max 32 chars, updates "Flow Title=" line)
         
     | 
| 
       724 
750 
     | 
    
         
             
                    ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
         
     | 
| 
       725 
751 
     | 
    
         | 
| 
       726 
752 
     | 
    
         
             
                    Returns:
         
     | 
| 
       727 
753 
     | 
    
         
             
                    str: New unsteady flow number (e.g., '03')
         
     | 
| 
       728 
754 
     | 
    
         | 
| 
       729 
755 
     | 
    
         
             
                    Example:
         
     | 
| 
       730 
     | 
    
         
            -
                    >>>  
     | 
| 
       731 
     | 
    
         
            -
                     
     | 
| 
      
 756 
     | 
    
         
            +
                    >>> new_unsteady_num = RasPlan.clone_unsteady('01',
         
     | 
| 
      
 757 
     | 
    
         
            +
                    ...                                           new_title='Unsteady - HEC-RAS 4.1')
         
     | 
| 
       732 
758 
     | 
    
         
             
                    >>> print(f"New unsteady flow file created: u{new_unsteady_num}")
         
     | 
| 
       733 
759 
     | 
    
         | 
| 
       734 
760 
     | 
    
         
             
                    Note:
         
     | 
| 
         @@ -737,6 +763,13 @@ class RasPlan: 
     | 
|
| 
       737 
763 
     | 
    
         
             
                    ras_obj = ras_object or ras
         
     | 
| 
       738 
764 
     | 
    
         
             
                    ras_obj.check_initialized()
         
     | 
| 
       739 
765 
     | 
    
         | 
| 
      
 766 
     | 
    
         
            +
                    # Validate new_title length if provided
         
     | 
| 
      
 767 
     | 
    
         
            +
                    if new_title is not None and len(new_title) > 32:
         
     | 
| 
      
 768 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 769 
     | 
    
         
            +
                            f"Flow title must be 32 characters or less. "
         
     | 
| 
      
 770 
     | 
    
         
            +
                            f"Got {len(new_title)} characters: '{new_title}'"
         
     | 
| 
      
 771 
     | 
    
         
            +
                        )
         
     | 
| 
      
 772 
     | 
    
         
            +
             
     | 
| 
       740 
773 
     | 
    
         
             
                    # Update unsteady entries without reinitializing the entire project
         
     | 
| 
       741 
774 
     | 
    
         
             
                    ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')
         
     | 
| 
       742 
775 
     | 
    
         | 
| 
         @@ -744,8 +777,21 @@ class RasPlan: 
     | 
|
| 
       744 
777 
     | 
    
         
             
                    template_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}"
         
     | 
| 
       745 
778 
     | 
    
         
             
                    new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
         
     | 
| 
       746 
779 
     | 
    
         | 
| 
       747 
     | 
    
         
            -
                     
     | 
| 
       748 
     | 
    
         
            -
             
     | 
| 
      
 780 
     | 
    
         
            +
                    def update_flow_title(lines):
         
     | 
| 
      
 781 
     | 
    
         
            +
                        """Update Flow Title if new_title provided"""
         
     | 
| 
      
 782 
     | 
    
         
            +
                        if new_title is None:
         
     | 
| 
      
 783 
     | 
    
         
            +
                            return lines
         
     | 
| 
      
 784 
     | 
    
         
            +
             
     | 
| 
      
 785 
     | 
    
         
            +
                        title_pattern = re.compile(r'^Flow Title=(.*)$', re.IGNORECASE)
         
     | 
| 
      
 786 
     | 
    
         
            +
                        for i, line in enumerate(lines):
         
     | 
| 
      
 787 
     | 
    
         
            +
                            title_match = title_pattern.match(line.strip())
         
     | 
| 
      
 788 
     | 
    
         
            +
                            if title_match:
         
     | 
| 
      
 789 
     | 
    
         
            +
                                lines[i] = f"Flow Title={new_title[:32]}\n"
         
     | 
| 
      
 790 
     | 
    
         
            +
                                break
         
     | 
| 
      
 791 
     | 
    
         
            +
                        return lines
         
     | 
| 
      
 792 
     | 
    
         
            +
             
     | 
| 
      
 793 
     | 
    
         
            +
                    # Use RasUtils to clone the file and update flow title
         
     | 
| 
      
 794 
     | 
    
         
            +
                    RasUtils.clone_file(template_unsteady_path, new_unsteady_path, update_flow_title)
         
     | 
| 
       749 
795 
     | 
    
         | 
| 
       750 
796 
     | 
    
         
             
                    # Copy the corresponding .hdf file if it exists
         
     | 
| 
       751 
797 
     | 
    
         
             
                    template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
         
     | 
| 
         @@ -769,21 +815,22 @@ class RasPlan: 
     | 
|
| 
       769 
815 
     | 
    
         | 
| 
       770 
816 
     | 
    
         
             
                @staticmethod
         
     | 
| 
       771 
817 
     | 
    
         
             
                @log_call
         
     | 
| 
       772 
     | 
    
         
            -
                def clone_steady(template_flow, ras_object=None):
         
     | 
| 
      
 818 
     | 
    
         
            +
                def clone_steady(template_flow, new_title=None, ras_object=None):
         
     | 
| 
       773 
819 
     | 
    
         
             
                    """
         
     | 
| 
       774 
820 
     | 
    
         
             
                    Copy steady flow files from a template, find the next flow number,
         
     | 
| 
       775 
821 
     | 
    
         
             
                    and update the project file accordingly.
         
     | 
| 
       776 
     | 
    
         
            -
             
     | 
| 
      
 822 
     | 
    
         
            +
             
     | 
| 
       777 
823 
     | 
    
         
             
                    Parameters:
         
     | 
| 
       778 
824 
     | 
    
         
             
                    template_flow (str): Flow number to be used as a template (e.g., '01')
         
     | 
| 
      
 825 
     | 
    
         
            +
                    new_title (str, optional): New flow title (max 32 chars, updates "Flow Title=" line)
         
     | 
| 
       779 
826 
     | 
    
         
             
                    ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
         
     | 
| 
       780 
     | 
    
         
            -
             
     | 
| 
      
 827 
     | 
    
         
            +
             
     | 
| 
       781 
828 
     | 
    
         
             
                    Returns:
         
     | 
| 
       782 
829 
     | 
    
         
             
                    str: New flow number (e.g., '03')
         
     | 
| 
       783 
830 
     | 
    
         | 
| 
       784 
831 
     | 
    
         
             
                    Example:
         
     | 
| 
       785 
     | 
    
         
            -
                    >>>  
     | 
| 
       786 
     | 
    
         
            -
                     
     | 
| 
      
 832 
     | 
    
         
            +
                    >>> new_flow_num = RasPlan.clone_steady('01',
         
     | 
| 
      
 833 
     | 
    
         
            +
                    ...                                      new_title='Steady Flow - HEC-RAS 4.1')
         
     | 
| 
       787 
834 
     | 
    
         
             
                    >>> print(f"New steady flow file created: f{new_flow_num}")
         
     | 
| 
       788 
835 
     | 
    
         | 
| 
       789 
836 
     | 
    
         
             
                    Note:
         
     | 
| 
         @@ -792,6 +839,13 @@ class RasPlan: 
     | 
|
| 
       792 
839 
     | 
    
         
             
                    ras_obj = ras_object or ras
         
     | 
| 
       793 
840 
     | 
    
         
             
                    ras_obj.check_initialized()
         
     | 
| 
       794 
841 
     | 
    
         | 
| 
      
 842 
     | 
    
         
            +
                    # Validate new_title length if provided
         
     | 
| 
      
 843 
     | 
    
         
            +
                    if new_title is not None and len(new_title) > 32:
         
     | 
| 
      
 844 
     | 
    
         
            +
                        raise ValueError(
         
     | 
| 
      
 845 
     | 
    
         
            +
                            f"Flow title must be 32 characters or less. "
         
     | 
| 
      
 846 
     | 
    
         
            +
                            f"Got {len(new_title)} characters: '{new_title}'"
         
     | 
| 
      
 847 
     | 
    
         
            +
                        )
         
     | 
| 
      
 848 
     | 
    
         
            +
             
     | 
| 
       795 
849 
     | 
    
         
             
                    # Update flow entries without reinitializing the entire project
         
     | 
| 
       796 
850 
     | 
    
         
             
                    ras_obj.flow_df = ras_obj.get_prj_entries('Flow')
         
     | 
| 
       797 
851 
     | 
    
         | 
| 
         @@ -799,8 +853,21 @@ class RasPlan: 
     | 
|
| 
       799 
853 
     | 
    
         
             
                    template_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{template_flow}"
         
     | 
| 
       800 
854 
     | 
    
         
             
                    new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
         
     | 
| 
       801 
855 
     | 
    
         | 
| 
       802 
     | 
    
         
            -
                     
     | 
| 
       803 
     | 
    
         
            -
             
     | 
| 
      
 856 
     | 
    
         
            +
                    def update_flow_title(lines):
         
     | 
| 
      
 857 
     | 
    
         
            +
                        """Update Flow Title if new_title provided"""
         
     | 
| 
      
 858 
     | 
    
         
            +
                        if new_title is None:
         
     | 
| 
      
 859 
     | 
    
         
            +
                            return lines
         
     | 
| 
      
 860 
     | 
    
         
            +
             
     | 
| 
      
 861 
     | 
    
         
            +
                        title_pattern = re.compile(r'^Flow Title=(.*)$', re.IGNORECASE)
         
     | 
| 
      
 862 
     | 
    
         
            +
                        for i, line in enumerate(lines):
         
     | 
| 
      
 863 
     | 
    
         
            +
                            title_match = title_pattern.match(line.strip())
         
     | 
| 
      
 864 
     | 
    
         
            +
                            if title_match:
         
     | 
| 
      
 865 
     | 
    
         
            +
                                lines[i] = f"Flow Title={new_title[:32]}\n"
         
     | 
| 
      
 866 
     | 
    
         
            +
                                break
         
     | 
| 
      
 867 
     | 
    
         
            +
                        return lines
         
     | 
| 
      
 868 
     | 
    
         
            +
             
     | 
| 
      
 869 
     | 
    
         
            +
                    # Use RasUtils to clone the file and update flow title
         
     | 
| 
      
 870 
     | 
    
         
            +
                    RasUtils.clone_file(template_flow_path, new_flow_path, update_flow_title)
         
     | 
| 
       804 
871 
     | 
    
         | 
| 
       805 
872 
     | 
    
         
             
                    # Use RasUtils to update the project file
         
     | 
| 
       806 
873 
     | 
    
         
             
                    RasUtils.update_project_file(ras_obj.prj_file, 'Flow', new_flow_num, ras_object=ras_obj)
         
     | 
    
        ras_commander/RasPrj.py
    CHANGED
    
    | 
         @@ -1452,12 +1452,17 @@ def init_ras_project(ras_project_folder, ras_version=None, ras_object=None): 
     | 
|
| 
       1452 
1452 
     | 
    
         
             
                else:
         
     | 
| 
       1453 
1453 
     | 
    
         
             
                    ras_object.initialize(project_folder, ras_exe_path)
         
     | 
| 
       1454 
1454 
     | 
    
         | 
| 
      
 1455 
     | 
    
         
            +
                # Store version for RasControl (legacy COM interface support)
         
     | 
| 
      
 1456 
     | 
    
         
            +
                ras_object.ras_version = ras_version if ras_version else detected_version
         
     | 
| 
      
 1457 
     | 
    
         
            +
             
     | 
| 
       1455 
1458 
     | 
    
         
             
                # Always update the global ras object as well
         
     | 
| 
       1456 
1459 
     | 
    
         
             
                if ras_object is not ras:
         
     | 
| 
       1457 
1460 
     | 
    
         
             
                    if specified_prj_file is not None:
         
     | 
| 
       1458 
1461 
     | 
    
         
             
                        ras.initialize(project_folder, ras_exe_path, prj_file=specified_prj_file)
         
     | 
| 
       1459 
1462 
     | 
    
         
             
                    else:
         
     | 
| 
       1460 
1463 
     | 
    
         
             
                        ras.initialize(project_folder, ras_exe_path)
         
     | 
| 
      
 1464 
     | 
    
         
            +
                    # Also store version in global ras object
         
     | 
| 
      
 1465 
     | 
    
         
            +
                    ras.ras_version = ras_version if ras_version else detected_version
         
     | 
| 
       1461 
1466 
     | 
    
         
             
                    logger.debug("Global 'ras' object also updated to match the new project.")
         
     | 
| 
       1462 
1467 
     | 
    
         | 
| 
       1463 
1468 
     | 
    
         
             
                logger.debug(f"Project initialized. Project folder: {ras_object.project_folder}")
         
     | 
| 
         @@ -1501,60 +1506,92 @@ def get_ras_exe(ras_version=None): 
     | 
|
| 
       1501 
1506 
     | 
    
         
             
                        logger.warning(f"HEC-RAS is not installed or version not specified. Running HEC-RAS will fail unless a valid installed version is specified.")
         
     | 
| 
       1502 
1507 
     | 
    
         
             
                        return default_path
         
     | 
| 
       1503 
1508 
     | 
    
         | 
| 
       1504 
     | 
    
         
            -
                 
     | 
| 
       1505 
     | 
    
         
            -
             
     | 
| 
      
 1509 
     | 
    
         
            +
                # ACTUAL folder names in C:/Program Files (x86)/HEC/HEC-RAS/
         
     | 
| 
      
 1510 
     | 
    
         
            +
                # This list matches the exact folder names on disk (verified 2025-10-30)
         
     | 
| 
      
 1511 
     | 
    
         
            +
                ras_version_folders = [
         
     | 
| 
      
 1512 
     | 
    
         
            +
                    "6.7 Beta 4", "6.6", "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
         
     | 
| 
       1506 
1513 
     | 
    
         
             
                    "5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
         
     | 
| 
       1507 
     | 
    
         
            -
                    "4.1 
     | 
| 
      
 1514 
     | 
    
         
            +
                    "4.1.0", "4.0"
         
     | 
| 
       1508 
1515 
     | 
    
         
             
                ]
         
     | 
| 
       1509 
     | 
    
         
            -
             
     | 
| 
      
 1516 
     | 
    
         
            +
             
     | 
| 
      
 1517 
     | 
    
         
            +
                # User-friendly aliases (user_input → actual_folder_name)
         
     | 
| 
      
 1518 
     | 
    
         
            +
                # Allows users to pass "4.1" and find "4.1.0" folder, or "66" to find "6.6"
         
     | 
| 
      
 1519 
     | 
    
         
            +
                version_aliases = {
         
     | 
| 
      
 1520 
     | 
    
         
            +
                    # 4.x aliases
         
     | 
| 
      
 1521 
     | 
    
         
            +
                    "4.1": "4.1.0",      # User passes "4.1" → finds "4.1.0" folder
         
     | 
| 
      
 1522 
     | 
    
         
            +
                    "41": "4.1.0",       # Compact format
         
     | 
| 
      
 1523 
     | 
    
         
            +
                    "410": "4.1.0",      # Full compact
         
     | 
| 
      
 1524 
     | 
    
         
            +
                    "40": "4.0",         # Compact format for 4.0
         
     | 
| 
      
 1525 
     | 
    
         
            +
             
     | 
| 
      
 1526 
     | 
    
         
            +
                    # 5.0.x aliases
         
     | 
| 
      
 1527 
     | 
    
         
            +
                    "50": "5.0",         # Compact format
         
     | 
| 
      
 1528 
     | 
    
         
            +
                    "501": "5.0.1",      # Compact format for 5.0.1
         
     | 
| 
      
 1529 
     | 
    
         
            +
                    "503": "5.0.3",      # Compact format
         
     | 
| 
      
 1530 
     | 
    
         
            +
                    "504": "5.0.4",      # Compact format for 5.0.4
         
     | 
| 
      
 1531 
     | 
    
         
            +
                    "505": "5.0.5",
         
     | 
| 
      
 1532 
     | 
    
         
            +
                    "506": "5.0.6",
         
     | 
| 
      
 1533 
     | 
    
         
            +
                    "507": "5.0.7",
         
     | 
| 
      
 1534 
     | 
    
         
            +
             
     | 
| 
      
 1535 
     | 
    
         
            +
                    # 6.x aliases
         
     | 
| 
      
 1536 
     | 
    
         
            +
                    "60": "6.0",
         
     | 
| 
      
 1537 
     | 
    
         
            +
                    "61": "6.1",
         
     | 
| 
      
 1538 
     | 
    
         
            +
                    "62": "6.2",
         
     | 
| 
      
 1539 
     | 
    
         
            +
                    "63": "6.3",
         
     | 
| 
      
 1540 
     | 
    
         
            +
                    "631": "6.3.1",
         
     | 
| 
      
 1541 
     | 
    
         
            +
                    "6.4": "6.4.1",      # No 6.4 folder, use 6.4.1
         
     | 
| 
      
 1542 
     | 
    
         
            +
                    "64": "6.4.1",
         
     | 
| 
      
 1543 
     | 
    
         
            +
                    "641": "6.4.1",
         
     | 
| 
      
 1544 
     | 
    
         
            +
                    "65": "6.5",
         
     | 
| 
      
 1545 
     | 
    
         
            +
                    "66": "6.6",
         
     | 
| 
      
 1546 
     | 
    
         
            +
                    "6.7": "6.7 Beta 4", # User passes "6.7" → finds "6.7 Beta 4"
         
     | 
| 
      
 1547 
     | 
    
         
            +
                    "67": "6.7 Beta 4",
         
     | 
| 
      
 1548 
     | 
    
         
            +
                }
         
     | 
| 
      
 1549 
     | 
    
         
            +
             
     | 
| 
       1510 
1550 
     | 
    
         
             
                # Check if input is a direct path to an executable
         
     | 
| 
       1511 
1551 
     | 
    
         
             
                hecras_path = Path(ras_version)
         
     | 
| 
       1512 
1552 
     | 
    
         
             
                if hecras_path.is_file() and hecras_path.suffix.lower() == '.exe':
         
     | 
| 
       1513 
1553 
     | 
    
         
             
                    logger.debug(f"HEC-RAS executable found at specified path: {hecras_path}")
         
     | 
| 
       1514 
1554 
     | 
    
         
             
                    return str(hecras_path)
         
     | 
| 
       1515 
     | 
    
         
            -
             
     | 
| 
       1516 
     | 
    
         
            -
                 
     | 
| 
       1517 
     | 
    
         
            -
             
     | 
| 
       1518 
     | 
    
         
            -
             
     | 
| 
      
 1555 
     | 
    
         
            +
             
     | 
| 
      
 1556 
     | 
    
         
            +
                version_str = str(ras_version)
         
     | 
| 
      
 1557 
     | 
    
         
            +
             
     | 
| 
      
 1558 
     | 
    
         
            +
                # Check if there's an alias for this version
         
     | 
| 
      
 1559 
     | 
    
         
            +
                if version_str in version_aliases:
         
     | 
| 
      
 1560 
     | 
    
         
            +
                    actual_folder = version_aliases[version_str]
         
     | 
| 
      
 1561 
     | 
    
         
            +
                    logger.debug(f"Mapped version '{version_str}' to folder '{actual_folder}'")
         
     | 
| 
      
 1562 
     | 
    
         
            +
                    version_str = actual_folder
         
     | 
| 
      
 1563 
     | 
    
         
            +
             
     | 
| 
      
 1564 
     | 
    
         
            +
                # Check if this is a known folder name
         
     | 
| 
      
 1565 
     | 
    
         
            +
                if version_str in ras_version_folders:
         
     | 
| 
      
 1566 
     | 
    
         
            +
                    default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{version_str}/Ras.exe")
         
     | 
| 
       1519 
1567 
     | 
    
         
             
                    if default_path.is_file():
         
     | 
| 
       1520 
1568 
     | 
    
         
             
                        logger.debug(f"HEC-RAS executable found at default path: {default_path}")
         
     | 
| 
       1521 
1569 
     | 
    
         
             
                        return str(default_path)
         
     | 
| 
       1522 
1570 
     | 
    
         
             
                    else:
         
     | 
| 
       1523 
     | 
    
         
            -
                        error_msg = f"HEC-RAS Version { 
     | 
| 
      
 1571 
     | 
    
         
            +
                        error_msg = f"HEC-RAS Version {version_str} folder exists but Ras.exe not found at expected path. Running HEC-RAS will fail."
         
     | 
| 
       1524 
1572 
     | 
    
         
             
                        logger.error(error_msg)
         
     | 
| 
       1525 
1573 
     | 
    
         
             
                        return "Ras.exe"
         
     | 
| 
       1526 
1574 
     | 
    
         | 
| 
       1527 
     | 
    
         
            -
                # Try to  
     | 
| 
      
 1575 
     | 
    
         
            +
                # Final fallback: Try to find a matching version from folder list
         
     | 
| 
       1528 
1576 
     | 
    
         
             
                try:
         
     | 
| 
       1529 
     | 
    
         
            -
                    # First check if it's a direct version number
         
     | 
| 
       1530 
     | 
    
         
            -
                    version_str = str(ras_version)
         
     | 
| 
       1531 
     | 
    
         
            -
                    
         
     | 
| 
       1532 
     | 
    
         
            -
                    # Check for paths like "C:/Path/To/Ras.exe"
         
     | 
| 
       1533 
     | 
    
         
            -
                    if os.path.sep in version_str and version_str.lower().endswith('.exe'):
         
     | 
| 
       1534 
     | 
    
         
            -
                        exe_path = Path(version_str)
         
     | 
| 
       1535 
     | 
    
         
            -
                        if exe_path.is_file():
         
     | 
| 
       1536 
     | 
    
         
            -
                            logger.debug(f"HEC-RAS executable found at specified path: {exe_path}")
         
     | 
| 
       1537 
     | 
    
         
            -
                            return str(exe_path)
         
     | 
| 
       1538 
     | 
    
         
            -
                    
         
     | 
| 
       1539 
1577 
     | 
    
         
             
                    # Try to find a matching version from our list
         
     | 
| 
       1540 
     | 
    
         
            -
                    for  
     | 
| 
       1541 
     | 
    
         
            -
                         
     | 
| 
       1542 
     | 
    
         
            -
             
     | 
| 
      
 1578 
     | 
    
         
            +
                    for known_folder in ras_version_folders:
         
     | 
| 
      
 1579 
     | 
    
         
            +
                        # Check for partial matches or compact formats
         
     | 
| 
      
 1580 
     | 
    
         
            +
                        if version_str in known_folder or known_folder.replace('.', '') == version_str:
         
     | 
| 
      
 1581 
     | 
    
         
            +
                            default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{known_folder}/Ras.exe")
         
     | 
| 
       1543 
1582 
     | 
    
         
             
                            if default_path.is_file():
         
     | 
| 
       1544 
     | 
    
         
            -
                                logger.debug(f"HEC-RAS executable found  
     | 
| 
      
 1583 
     | 
    
         
            +
                                logger.debug(f"HEC-RAS executable found via fuzzy match: {default_path}")
         
     | 
| 
       1545 
1584 
     | 
    
         
             
                                return str(default_path)
         
     | 
| 
       1546 
     | 
    
         
            -
             
     | 
| 
       1547 
     | 
    
         
            -
                    #  
     | 
| 
      
 1585 
     | 
    
         
            +
             
     | 
| 
      
 1586 
     | 
    
         
            +
                    # Try direct path construction for newer versions
         
     | 
| 
       1548 
1587 
     | 
    
         
             
                    if '.' in version_str:
         
     | 
| 
       1549 
     | 
    
         
            -
                         
     | 
| 
       1550 
     | 
    
         
            -
                        if  
     | 
| 
       1551 
     | 
    
         
            -
                             
     | 
| 
       1552 
     | 
    
         
            -
                             
     | 
| 
       1553 
     | 
    
         
            -
                                logger.debug(f"HEC-RAS executable found at path for newer version: {default_path}")
         
     | 
| 
       1554 
     | 
    
         
            -
                                return str(default_path)
         
     | 
| 
      
 1588 
     | 
    
         
            +
                        default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{version_str}/Ras.exe")
         
     | 
| 
      
 1589 
     | 
    
         
            +
                        if default_path.is_file():
         
     | 
| 
      
 1590 
     | 
    
         
            +
                            logger.debug(f"HEC-RAS executable found at path: {default_path}")
         
     | 
| 
      
 1591 
     | 
    
         
            +
                            return str(default_path)
         
     | 
| 
       1555 
1592 
     | 
    
         
             
                except Exception as e:
         
     | 
| 
       1556 
1593 
     | 
    
         
             
                    logger.error(f"Error parsing version or finding path: {e}")
         
     | 
| 
       1557 
     | 
    
         
            -
             
     | 
| 
      
 1594 
     | 
    
         
            +
             
     | 
| 
       1558 
1595 
     | 
    
         
             
                error_msg = f"HEC-RAS Version {ras_version} is not recognized or installed. Running HEC-RAS will fail unless a valid installed version is specified."
         
     | 
| 
       1559 
1596 
     | 
    
         
             
                logger.error(error_msg)
         
     | 
| 
       1560 
1597 
     | 
    
         
             
                return "Ras.exe"
         
     | 
    
        ras_commander/__init__.py
    CHANGED
    
    | 
         @@ -10,7 +10,7 @@ try: 
     | 
|
| 
       10 
10 
     | 
    
         
             
                __version__ = version("ras-commander")
         
     | 
| 
       11 
11 
     | 
    
         
             
            except PackageNotFoundError:
         
     | 
| 
       12 
12 
     | 
    
         
             
                # package is not installed
         
     | 
| 
       13 
     | 
    
         
            -
                __version__ = "0. 
     | 
| 
      
 13 
     | 
    
         
            +
                __version__ = "0.82.0"
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
            # Set up logging
         
     | 
| 
       16 
16 
     | 
    
         
             
            setup_logging()
         
     | 
| 
         @@ -23,6 +23,7 @@ from .RasUnsteady import RasUnsteady 
     | 
|
| 
       23 
23 
     | 
    
         
             
            from .RasUtils import RasUtils
         
     | 
| 
       24 
24 
     | 
    
         
             
            from .RasExamples import RasExamples
         
     | 
| 
       25 
25 
     | 
    
         
             
            from .RasCmdr import RasCmdr
         
     | 
| 
      
 26 
     | 
    
         
            +
            from .RasControl import RasControl
         
     | 
| 
       26 
27 
     | 
    
         
             
            from .RasMap import RasMap
         
     | 
| 
       27 
28 
     | 
    
         
             
            from .HdfFluvialPluvial import HdfFluvialPluvial
         
     | 
| 
       28 
29 
     | 
    
         | 
| 
         @@ -50,7 +51,7 @@ __all__ = [ 
     | 
|
| 
       50 
51 
     | 
    
         
             
                # Core functionality
         
     | 
| 
       51 
52 
     | 
    
         
             
                'RasPrj', 'init_ras_project', 'get_ras_exe', 'ras',
         
     | 
| 
       52 
53 
     | 
    
         
             
                'RasPlan', 'RasGeo', 'RasUnsteady', 'RasUtils',
         
     | 
| 
       53 
     | 
    
         
            -
                'RasExamples', 'RasCmdr', 'RasMap', 'HdfFluvialPluvial',
         
     | 
| 
      
 54 
     | 
    
         
            +
                'RasExamples', 'RasCmdr', 'RasControl', 'RasMap', 'HdfFluvialPluvial',
         
     | 
| 
       54 
55 
     | 
    
         | 
| 
       55 
56 
     | 
    
         
             
                # HDF handling
         
     | 
| 
       56 
57 
     | 
    
         
             
                'HdfBase', 'HdfBndry', 'HdfMesh', 'HdfPlan',
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            Metadata-Version: 2.4
         
     | 
| 
       2 
2 
     | 
    
         
             
            Name: ras-commander
         
     | 
| 
       3 
     | 
    
         
            -
            Version: 0. 
     | 
| 
      
 3 
     | 
    
         
            +
            Version: 0.82.0
         
     | 
| 
       4 
4 
     | 
    
         
             
            Summary: A Python library for automating HEC-RAS 6.x operations
         
     | 
| 
       5 
5 
     | 
    
         
             
            Home-page: https://github.com/gpt-cmdr/ras-commander
         
     | 
| 
       6 
6 
     | 
    
         
             
            Author: William M. Katzenmeyer, P.E., C.F.M.
         
     | 
| 
         @@ -21,6 +21,8 @@ Requires-Dist: shapely 
     | 
|
| 
       21 
21 
     | 
    
         
             
            Requires-Dist: pathlib
         
     | 
| 
       22 
22 
     | 
    
         
             
            Requires-Dist: rasterstats
         
     | 
| 
       23 
23 
     | 
    
         
             
            Requires-Dist: rtree
         
     | 
| 
      
 24 
     | 
    
         
            +
            Requires-Dist: pywin32>=227
         
     | 
| 
      
 25 
     | 
    
         
            +
            Requires-Dist: psutil>=5.6.6
         
     | 
| 
       24 
26 
     | 
    
         
             
            Dynamic: author
         
     | 
| 
       25 
27 
     | 
    
         
             
            Dynamic: author-email
         
     | 
| 
       26 
28 
     | 
    
         
             
            Dynamic: description
         
     | 
| 
         @@ -81,6 +83,15 @@ HEC-RAS Project Management & Execution 
     | 
|
| 
       81 
83 
     | 
    
         
             
            - Progress tracking and logging
         
     | 
| 
       82 
84 
     | 
    
         
             
            - Execution error handling and recovery
         
     | 
| 
       83 
85 
     | 
    
         | 
| 
      
 86 
     | 
    
         
            +
            Legacy Version Support (NEW in v0.81.0)
         
     | 
| 
      
 87 
     | 
    
         
            +
            - RasControl class for HEC-RAS 3.x-4.x via COM interface
         
     | 
| 
      
 88 
     | 
    
         
            +
            - ras-commander style API - use plan numbers, not file paths
         
     | 
| 
      
 89 
     | 
    
         
            +
            - Extract steady state profiles AND unsteady time series
         
     | 
| 
      
 90 
     | 
    
         
            +
            - Supports versions: 3.1, 4.1, 5.0.x, 6.0, 6.3, 6.6
         
     | 
| 
      
 91 
     | 
    
         
            +
            - Version migration validation and comparison
         
     | 
| 
      
 92 
     | 
    
         
            +
            - Open-operate-close pattern prevents conflicts with modern workflows
         
     | 
| 
      
 93 
     | 
    
         
            +
            - Seamless integration with ras-commander project management
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
       84 
95 
     | 
    
         
             
            HDF Data Access & Analysis
         
     | 
| 
       85 
96 
     | 
    
         
             
            - 2D mesh results processing (depths, velocities, WSE)
         
     | 
| 
       86 
97 
     | 
    
         
             
            - Cross-section data extraction
         
     | 
| 
         @@ -115,7 +126,9 @@ RAS ASCII File Operations 
     | 
|
| 
       115 
126 
     | 
    
         
             
            - Unsteady flow file management
         
     | 
| 
       116 
127 
     | 
    
         
             
            - Project file updates and validation  
         
     | 
| 
       117 
128 
     | 
    
         | 
| 
       118 
     | 
    
         
            -
            Note about support for Pipe Networks:  As a relatively new feature, only read access to Pipe Network geometry and results data has been included.  Users will need to code their own methods to modify/add pipe network data, and pull requests are always welcome to incorporate this capability. 
     | 
| 
      
 129 
     | 
    
         
            +
            Note about support for Pipe Networks:  As a relatively new feature, only read access to Pipe Network geometry and results data has been included.  Users will need to code their own methods to modify/add pipe network data, and pull requests are always welcome to incorporate this capability.
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            Note about version support: The modern HDF-based features target HEC-RAS 6.2+ for optimal compatibility. For legacy versions (3.1, 4.1, 5.0.x), use the RasControl class which provides COM-based access to steady state profile extraction and plan execution (see example notebook 17).
         
     | 
| 
       119 
132 
     | 
    
         | 
| 
       120 
133 
     | 
    
         
             
            ## Installation
         
     | 
| 
       121 
134 
     | 
    
         | 
| 
         @@ -17,16 +17,17 @@ ras_commander/HdfUtils.py,sha256=VkIKAXBrLwTlk2VtXSO-W3RU-NHpfHbE1QcZUZgl-t8,152 
     | 
|
| 
       17 
17 
     | 
    
         
             
            ras_commander/HdfXsec.py,sha256=4DuJvzTTtn4zGcf1lv_TyWyRnYRnR_SE-iWFKix5QzM,27776
         
     | 
| 
       18 
18 
     | 
    
         
             
            ras_commander/LoggingConfig.py,sha256=gWe5K5XTmMQpSczsTysAqpC9my24i_IyM8dvD85fxYg,2704
         
     | 
| 
       19 
19 
     | 
    
         
             
            ras_commander/RasCmdr.py,sha256=37GnchoQ0fIAkPnssnCr1mRUXY8gm-hIMTmuHZlnYP8,34591
         
     | 
| 
      
 20 
     | 
    
         
            +
            ras_commander/RasControl.py,sha256=OGEgY_Zw9J3qjXjIm9P9CkSaGK2Z_N1LrXHvEJ2O1fo,32396
         
     | 
| 
       20 
21 
     | 
    
         
             
            ras_commander/RasExamples.py,sha256=QFWnWnxACpQzewzA3QFMp4z4iEkg5PWf9cPDdMay7MA,24556
         
     | 
| 
       21 
22 
     | 
    
         
             
            ras_commander/RasGeo.py,sha256=Wy5N1yP7_St3cA3ENJliojQ2sb2w2dL8Fy8L_sZsykc,22208
         
     | 
| 
       22 
23 
     | 
    
         
             
            ras_commander/RasMap.py,sha256=20db61KkUz2CgjfCCYY8F-IYy5doHOtdnTKChiK0ENs,20257
         
     | 
| 
       23 
     | 
    
         
            -
            ras_commander/RasPlan.py,sha256= 
     | 
| 
       24 
     | 
    
         
            -
            ras_commander/RasPrj.py,sha256= 
     | 
| 
      
 24 
     | 
    
         
            +
            ras_commander/RasPlan.py,sha256=_aDVD3WmncGKmMGDahTQ_KBIMV0OIpfEUABFt5DcbMs,68630
         
     | 
| 
      
 25 
     | 
    
         
            +
            ras_commander/RasPrj.py,sha256=OkFClPPwZdFU9nTCP9ytZ1pk0H18pZXT3jEiML9R9Hk,69298
         
     | 
| 
       25 
26 
     | 
    
         
             
            ras_commander/RasUnsteady.py,sha256=PdQQMiY7Mz1EsOQk6ygFQtlC2sFEa96Ntg-pznWVpLQ,37187
         
     | 
| 
       26 
27 
     | 
    
         
             
            ras_commander/RasUtils.py,sha256=0fm4IIs0LH1dgDj3pGd66mR82DhWLEkRKUvIo2M_5X0,35886
         
     | 
| 
       27 
     | 
    
         
            -
            ras_commander/__init__.py,sha256= 
     | 
| 
       28 
     | 
    
         
            -
            ras_commander-0. 
     | 
| 
       29 
     | 
    
         
            -
            ras_commander-0. 
     | 
| 
       30 
     | 
    
         
            -
            ras_commander-0. 
     | 
| 
       31 
     | 
    
         
            -
            ras_commander-0. 
     | 
| 
       32 
     | 
    
         
            -
            ras_commander-0. 
     | 
| 
      
 28 
     | 
    
         
            +
            ras_commander/__init__.py,sha256=cs-SaZXXgyDR0c30577Ay-IlDzi1qAgQmcIWjFnlL6c,2089
         
     | 
| 
      
 29 
     | 
    
         
            +
            ras_commander-0.82.0.dist-info/licenses/LICENSE,sha256=_pbd6qHnlsz1iQ-ozDW_49r86BZT6CRwO2iBtw0iN6M,457
         
     | 
| 
      
 30 
     | 
    
         
            +
            ras_commander-0.82.0.dist-info/METADATA,sha256=TuEymRIjnn9mOV80U8thii1GYucRwLt1AbsMkiQjB4c,28653
         
     | 
| 
      
 31 
     | 
    
         
            +
            ras_commander-0.82.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
         
     | 
| 
      
 32 
     | 
    
         
            +
            ras_commander-0.82.0.dist-info/top_level.txt,sha256=i76S7eKLFC8doKcXDl3aiOr9RwT06G8adI6YuKbQDaA,14
         
     | 
| 
      
 33 
     | 
    
         
            +
            ras_commander-0.82.0.dist-info/RECORD,,
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |