ras-commander 0.80.3__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 +170 -61
- ras_commander/__init__.py +3 -2
- {ras_commander-0.80.3.dist-info → ras_commander-0.82.0.dist-info}/METADATA +15 -2
- {ras_commander-0.80.3.dist-info → ras_commander-0.82.0.dist-info}/RECORD +9 -8
- {ras_commander-0.80.3.dist-info → ras_commander-0.82.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.80.3.dist-info → ras_commander-0.82.0.dist-info}/licenses/LICENSE +0 -0
- {ras_commander-0.80.3.dist-info → ras_commander-0.82.0.dist-info}/top_level.txt +0 -0
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
|
@@ -116,7 +116,7 @@ class RasPrj:
|
|
|
116
116
|
self.suppress_logging = False # Add suppress_logging as instance variable
|
|
117
117
|
|
|
118
118
|
@log_call
|
|
119
|
-
def initialize(self, project_folder, ras_exe_path, suppress_logging=True):
|
|
119
|
+
def initialize(self, project_folder, ras_exe_path, suppress_logging=True, prj_file=None):
|
|
120
120
|
"""
|
|
121
121
|
Initialize a RasPrj instance with project folder and RAS executable path.
|
|
122
122
|
|
|
@@ -127,13 +127,16 @@ class RasPrj:
|
|
|
127
127
|
project_folder (str or Path): Path to the HEC-RAS project folder.
|
|
128
128
|
ras_exe_path (str or Path): Path to the HEC-RAS executable.
|
|
129
129
|
suppress_logging (bool, default=True): If True, suppresses initialization logging messages.
|
|
130
|
+
prj_file (str or Path, optional): If provided, use this specific .prj file instead of searching.
|
|
131
|
+
This is used when user specifies a .prj file directly.
|
|
130
132
|
|
|
131
133
|
Raises:
|
|
132
|
-
ValueError: If no HEC-RAS project file is found in the specified folder
|
|
134
|
+
ValueError: If no HEC-RAS project file is found in the specified folder,
|
|
135
|
+
or if the specified prj_file doesn't exist or is invalid.
|
|
133
136
|
|
|
134
137
|
Note:
|
|
135
138
|
This method sets up the RasPrj instance by:
|
|
136
|
-
1. Finding the project file (.prj)
|
|
139
|
+
1. Finding the project file (.prj) or using the provided prj_file
|
|
137
140
|
2. Loading project data (plans, geometries, flows)
|
|
138
141
|
3. Extracting boundary conditions
|
|
139
142
|
4. Setting the initialization flag
|
|
@@ -141,10 +144,21 @@ class RasPrj:
|
|
|
141
144
|
"""
|
|
142
145
|
self.suppress_logging = suppress_logging # Store suppress_logging state
|
|
143
146
|
self.project_folder = Path(project_folder)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
# If user specified a .prj file directly, use it (Phase 2 optimization)
|
|
149
|
+
if prj_file is not None:
|
|
150
|
+
self.prj_file = Path(prj_file).resolve()
|
|
151
|
+
if not self.prj_file.exists():
|
|
152
|
+
logger.error(f"Specified .prj file does not exist: {self.prj_file}")
|
|
153
|
+
raise ValueError(f"Specified .prj file does not exist: {self.prj_file}. Please check the path and try again.")
|
|
154
|
+
logger.debug(f"Using specified .prj file: {self.prj_file}")
|
|
155
|
+
else:
|
|
156
|
+
# Search for .prj file (existing behavior)
|
|
157
|
+
self.prj_file = self.find_ras_prj(self.project_folder)
|
|
158
|
+
if self.prj_file is None:
|
|
159
|
+
logger.error(f"No HEC-RAS project file found in {self.project_folder}")
|
|
160
|
+
raise ValueError(f"No HEC-RAS project file found in {self.project_folder}. Please check the path and try again.")
|
|
161
|
+
|
|
148
162
|
self.project_name = Path(self.prj_file).stem
|
|
149
163
|
self.ras_exe_path = ras_exe_path
|
|
150
164
|
|
|
@@ -1274,13 +1288,18 @@ def init_ras_project(ras_project_folder, ras_version=None, ras_object=None):
|
|
|
1274
1288
|
Initialize a RAS project for use with the ras-commander library.
|
|
1275
1289
|
|
|
1276
1290
|
This is the primary function for setting up a HEC-RAS project. It:
|
|
1277
|
-
1. Finds the project file (.prj) in the specified folder
|
|
1278
|
-
2.
|
|
1279
|
-
3.
|
|
1280
|
-
4.
|
|
1291
|
+
1. Finds the project file (.prj) in the specified folder OR uses the provided .prj file
|
|
1292
|
+
2. Validates .prj files by checking for "Proj Title=" marker
|
|
1293
|
+
3. Identifies the appropriate HEC-RAS executable
|
|
1294
|
+
4. Loads project data (plans, geometries, flows)
|
|
1295
|
+
5. Creates dataframes containing project components
|
|
1281
1296
|
|
|
1282
1297
|
Args:
|
|
1283
|
-
ras_project_folder (str or Path):
|
|
1298
|
+
ras_project_folder (str or Path): Path to the RAS project folder OR direct path to a .prj file.
|
|
1299
|
+
If a .prj file is provided:
|
|
1300
|
+
- File is validated to have .prj extension
|
|
1301
|
+
- File content is checked for "Proj Title=" marker
|
|
1302
|
+
- Parent folder is used as the project folder
|
|
1284
1303
|
ras_version (str, optional): The version of RAS to use (e.g., "6.6") OR
|
|
1285
1304
|
a full path to the Ras.exe file (e.g., "D:/Programs/HEC/HEC-RAS/6.6/Ras.exe").
|
|
1286
1305
|
If None, will attempt to detect from plan files.
|
|
@@ -1290,25 +1309,71 @@ def init_ras_project(ras_project_folder, ras_version=None, ras_object=None):
|
|
|
1290
1309
|
|
|
1291
1310
|
Returns:
|
|
1292
1311
|
RasPrj: An initialized RasPrj instance.
|
|
1293
|
-
|
|
1312
|
+
|
|
1294
1313
|
Raises:
|
|
1295
|
-
FileNotFoundError: If the specified project folder doesn't exist.
|
|
1296
|
-
ValueError: If
|
|
1297
|
-
|
|
1314
|
+
FileNotFoundError: If the specified project folder or .prj file doesn't exist.
|
|
1315
|
+
ValueError: If the provided file is not a .prj file, does not contain "Proj Title=",
|
|
1316
|
+
or if no HEC-RAS project file is found in the folder.
|
|
1317
|
+
|
|
1298
1318
|
Example:
|
|
1299
|
-
>>> # Initialize using
|
|
1319
|
+
>>> # Initialize using project folder (existing behavior)
|
|
1300
1320
|
>>> init_ras_project("/path/to/project", "6.6")
|
|
1301
1321
|
>>> print(f"Initialized project: {ras.project_name}")
|
|
1302
1322
|
>>>
|
|
1303
|
-
>>> #
|
|
1304
|
-
>>>
|
|
1323
|
+
>>> # Initialize using direct .prj file path (new feature)
|
|
1324
|
+
>>> init_ras_project("/path/to/project/MyModel.prj", "6.6")
|
|
1325
|
+
>>> print(f"Initialized project: {ras.project_name}")
|
|
1326
|
+
>>>
|
|
1327
|
+
>>> # Create a new RasPrj instance with .prj file
|
|
1328
|
+
>>> my_project = init_ras_project("/path/to/project/MyModel.prj", "6.6", "new")
|
|
1305
1329
|
>>> print(f"Created project instance: {my_project.project_name}")
|
|
1306
1330
|
"""
|
|
1307
|
-
# Convert to
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1331
|
+
# Convert to Path object for consistent handling
|
|
1332
|
+
input_path = Path(ras_project_folder).resolve()
|
|
1333
|
+
|
|
1334
|
+
# Detect if input is a file or folder
|
|
1335
|
+
if input_path.is_file():
|
|
1336
|
+
# User provided a .prj file path
|
|
1337
|
+
if input_path.suffix.lower() != '.prj':
|
|
1338
|
+
error_msg = f"The provided file is not a HEC-RAS project file (.prj): {input_path}"
|
|
1339
|
+
logger.error(error_msg)
|
|
1340
|
+
raise ValueError(f"{error_msg}. Please provide either a project folder or a .prj file.")
|
|
1341
|
+
|
|
1342
|
+
# Enhanced validation: Check if file contains "Proj Title=" to verify it's a HEC-RAS project file
|
|
1343
|
+
try:
|
|
1344
|
+
content, encoding = read_file_with_fallback_encoding(input_path)
|
|
1345
|
+
if content is None or "Proj Title=" not in content:
|
|
1346
|
+
error_msg = f"The file does not appear to be a valid HEC-RAS project file (missing 'Proj Title='): {input_path}"
|
|
1347
|
+
logger.error(error_msg)
|
|
1348
|
+
raise ValueError(f"{error_msg}. Please provide a valid HEC-RAS .prj file.")
|
|
1349
|
+
logger.debug(f"Validated .prj file contains 'Proj Title=' marker")
|
|
1350
|
+
except Exception as e:
|
|
1351
|
+
error_msg = f"Error validating .prj file: {e}"
|
|
1352
|
+
logger.error(error_msg)
|
|
1353
|
+
raise ValueError(f"{error_msg}. Please ensure the file is a valid HEC-RAS project file.")
|
|
1354
|
+
|
|
1355
|
+
# Extract the parent folder to use as project_folder
|
|
1356
|
+
project_folder = input_path.parent
|
|
1357
|
+
specified_prj_file = input_path # Store for optimization
|
|
1358
|
+
logger.debug(f"User provided .prj file: {input_path}")
|
|
1359
|
+
logger.debug(f"Using parent folder as project_folder: {project_folder}")
|
|
1360
|
+
|
|
1361
|
+
elif input_path.is_dir():
|
|
1362
|
+
# User provided a folder path (existing behavior)
|
|
1363
|
+
project_folder = input_path
|
|
1364
|
+
specified_prj_file = None
|
|
1365
|
+
logger.debug(f"User provided folder path: {project_folder}")
|
|
1366
|
+
|
|
1367
|
+
else:
|
|
1368
|
+
# Path doesn't exist
|
|
1369
|
+
if input_path.suffix.lower() == '.prj':
|
|
1370
|
+
error_msg = f"The specified .prj file does not exist: {input_path}"
|
|
1371
|
+
logger.error(error_msg)
|
|
1372
|
+
raise FileNotFoundError(f"{error_msg}. Please check the path and try again.")
|
|
1373
|
+
else:
|
|
1374
|
+
error_msg = f"The specified RAS project folder does not exist: {input_path}"
|
|
1375
|
+
logger.error(error_msg)
|
|
1376
|
+
raise FileNotFoundError(f"{error_msg}. Please check the path and try again.")
|
|
1312
1377
|
|
|
1313
1378
|
# Determine which RasPrj instance to use
|
|
1314
1379
|
if ras_object is None:
|
|
@@ -1381,13 +1446,25 @@ def init_ras_project(ras_project_folder, ras_version=None, ras_object=None):
|
|
|
1381
1446
|
logger.warning("No valid HEC-RAS version was detected. Running HEC-RAS will fail.")
|
|
1382
1447
|
|
|
1383
1448
|
# Initialize or re-initialize with the determined executable path
|
|
1384
|
-
|
|
1385
|
-
|
|
1449
|
+
# Pass specified_prj_file to avoid re-searching when user provided .prj file directly
|
|
1450
|
+
if specified_prj_file is not None:
|
|
1451
|
+
ras_object.initialize(project_folder, ras_exe_path, prj_file=specified_prj_file)
|
|
1452
|
+
else:
|
|
1453
|
+
ras_object.initialize(project_folder, ras_exe_path)
|
|
1454
|
+
|
|
1455
|
+
# Store version for RasControl (legacy COM interface support)
|
|
1456
|
+
ras_object.ras_version = ras_version if ras_version else detected_version
|
|
1457
|
+
|
|
1386
1458
|
# Always update the global ras object as well
|
|
1387
1459
|
if ras_object is not ras:
|
|
1388
|
-
|
|
1460
|
+
if specified_prj_file is not None:
|
|
1461
|
+
ras.initialize(project_folder, ras_exe_path, prj_file=specified_prj_file)
|
|
1462
|
+
else:
|
|
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
|
|
1389
1466
|
logger.debug("Global 'ras' object also updated to match the new project.")
|
|
1390
|
-
|
|
1467
|
+
|
|
1391
1468
|
logger.debug(f"Project initialized. Project folder: {ras_object.project_folder}")
|
|
1392
1469
|
logger.debug(f"Using HEC-RAS executable: {ras_exe_path}")
|
|
1393
1470
|
return ras_object
|
|
@@ -1429,60 +1506,92 @@ def get_ras_exe(ras_version=None):
|
|
|
1429
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.")
|
|
1430
1507
|
return default_path
|
|
1431
1508
|
|
|
1432
|
-
|
|
1433
|
-
|
|
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",
|
|
1434
1513
|
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
|
1435
|
-
"4.1
|
|
1514
|
+
"4.1.0", "4.0"
|
|
1436
1515
|
]
|
|
1437
|
-
|
|
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
|
+
|
|
1438
1550
|
# Check if input is a direct path to an executable
|
|
1439
1551
|
hecras_path = Path(ras_version)
|
|
1440
1552
|
if hecras_path.is_file() and hecras_path.suffix.lower() == '.exe':
|
|
1441
1553
|
logger.debug(f"HEC-RAS executable found at specified path: {hecras_path}")
|
|
1442
1554
|
return str(hecras_path)
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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")
|
|
1447
1567
|
if default_path.is_file():
|
|
1448
1568
|
logger.debug(f"HEC-RAS executable found at default path: {default_path}")
|
|
1449
1569
|
return str(default_path)
|
|
1450
1570
|
else:
|
|
1451
|
-
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."
|
|
1452
1572
|
logger.error(error_msg)
|
|
1453
1573
|
return "Ras.exe"
|
|
1454
1574
|
|
|
1455
|
-
# Try to
|
|
1575
|
+
# Final fallback: Try to find a matching version from folder list
|
|
1456
1576
|
try:
|
|
1457
|
-
# First check if it's a direct version number
|
|
1458
|
-
version_str = str(ras_version)
|
|
1459
|
-
|
|
1460
|
-
# Check for paths like "C:/Path/To/Ras.exe"
|
|
1461
|
-
if os.path.sep in version_str and version_str.lower().endswith('.exe'):
|
|
1462
|
-
exe_path = Path(version_str)
|
|
1463
|
-
if exe_path.is_file():
|
|
1464
|
-
logger.debug(f"HEC-RAS executable found at specified path: {exe_path}")
|
|
1465
|
-
return str(exe_path)
|
|
1466
|
-
|
|
1467
1577
|
# Try to find a matching version from our list
|
|
1468
|
-
for
|
|
1469
|
-
|
|
1470
|
-
|
|
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")
|
|
1471
1582
|
if default_path.is_file():
|
|
1472
|
-
logger.debug(f"HEC-RAS executable found
|
|
1583
|
+
logger.debug(f"HEC-RAS executable found via fuzzy match: {default_path}")
|
|
1473
1584
|
return str(default_path)
|
|
1474
|
-
|
|
1475
|
-
#
|
|
1585
|
+
|
|
1586
|
+
# Try direct path construction for newer versions
|
|
1476
1587
|
if '.' in version_str:
|
|
1477
|
-
|
|
1478
|
-
if
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
logger.debug(f"HEC-RAS executable found at path for newer version: {default_path}")
|
|
1482
|
-
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)
|
|
1483
1592
|
except Exception as e:
|
|
1484
1593
|
logger.error(f"Error parsing version or finding path: {e}")
|
|
1485
|
-
|
|
1594
|
+
|
|
1486
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."
|
|
1487
1596
|
logger.error(error_msg)
|
|
1488
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
|