ras-commander 0.1.0__py2.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/__init__.py +24 -0
- ras_commander/execution.py +315 -0
- ras_commander/file_operations.py +173 -0
- ras_commander/geometry_operations.py +184 -0
- ras_commander/plan_operations.py +307 -0
- ras_commander/project_config.py +64 -0
- ras_commander/project_init.py +174 -0
- ras_commander/project_management.py +227 -0
- ras_commander/project_setup.py +15 -0
- ras_commander/unsteady_operations.py +172 -0
- ras_commander/utilities.py +195 -0
- ras_commander-0.1.0.dist-info/LICENSE +5 -0
- ras_commander-0.1.0.dist-info/METADATA +135 -0
- ras_commander-0.1.0.dist-info/RECORD +16 -0
- ras_commander-0.1.0.dist-info/WHEEL +6 -0
- ras_commander-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
from .project_config import ProjectConfig
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
def init_ras_project(ras_project_folder, hecras_version_or_path):
|
5
|
+
"""
|
6
|
+
Initialize a HEC-RAS project.
|
7
|
+
|
8
|
+
This function sets up a HEC-RAS project by validating the project folder,
|
9
|
+
determining the HEC-RAS executable path, and initializing the project configuration.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
ras_project_folder (str): Path to the HEC-RAS project folder.
|
13
|
+
hecras_version_or_path (str): Either the HEC-RAS version number or the full path to the HEC-RAS executable.
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
ProjectConfig: An initialized ProjectConfig object containing the project configuration.
|
17
|
+
|
18
|
+
Raises:
|
19
|
+
FileNotFoundError: If the specified RAS project folder does not exist.
|
20
|
+
|
21
|
+
Note:
|
22
|
+
This function prints a confirmation message upon successful initialization.
|
23
|
+
|
24
|
+
Future Development Roadmap:
|
25
|
+
|
26
|
+
1. Load critical keys and values from the project files into the project config
|
27
|
+
Implemented:
|
28
|
+
- Project Name
|
29
|
+
- Project Folder
|
30
|
+
- Lists of plan, flow, unsteady, and geometry files
|
31
|
+
- HEC-RAS Executable Path
|
32
|
+
|
33
|
+
Not Implemented:
|
34
|
+
- Units
|
35
|
+
- Coordinate System
|
36
|
+
- rasmap file path (replace prj file path extension with ".rasmap" and add it to the project config)
|
37
|
+
- Current Plan
|
38
|
+
- Description (including checks to see if it is a valid string and within the default max length)
|
39
|
+
- DSS Start Date=01JAN1999 (note format is MMDDYYY)
|
40
|
+
- DSS Start Time=1200 (note format is HHMM)
|
41
|
+
- DSS End Date=04JAN1999 (note format is MMDDYYY)
|
42
|
+
- DSS End Time=1200 (note format is HHMM)
|
43
|
+
- DSS File=dss
|
44
|
+
- DSS File=Bald_Eagle_Creek.dss
|
45
|
+
|
46
|
+
Other not implemented:
|
47
|
+
|
48
|
+
2. Load critical keys and lists of string values from the plan files into the project config
|
49
|
+
- Plan Title
|
50
|
+
- Plan Shortid
|
51
|
+
- Simulation Date
|
52
|
+
- Geometry File
|
53
|
+
- Flow File (may not be present) - if present, the plan is a 1D steady plan
|
54
|
+
- Unsteady File (may not be present) - if present, the plan is a 1D or 2D unsteady plan
|
55
|
+
- UNET D2 Name (may not be present) - if present, the plan is a 2D plan
|
56
|
+
- Type (1D Steady, 1D Unsteady, 1D/2D, or 2D)
|
57
|
+
- UNET 1D Methodology
|
58
|
+
|
59
|
+
3. Load critical keys and strings from the unsteady flow files into the project config
|
60
|
+
- Flow Title
|
61
|
+
- Pandas Dataframe for any Boundary Conditions present and whether they are defined in the file or whether they use a DSS file input
|
62
|
+
- One dataframe for all unsteady flow files, with each Boundary Location in each file having its own row
|
63
|
+
- For each unsteady flow filereturn an appended dataframe with each boundary condition and it's "Boundary Name", "Interval", "DSS Path", "Flow Hydrograph Slope", and whether Use DSS is True or False
|
64
|
+
- Need to incorporate knowledge from the excel methods we used for setting boundary conditions
|
65
|
+
|
66
|
+
4. Load critical keys and strings from the steady flow files into the project config
|
67
|
+
- Flow Title
|
68
|
+
- Since steady models are not as commonly used, this is a low priority integration (external contributions are welcome)
|
69
|
+
|
70
|
+
|
71
|
+
5. Load critical keys and values from the rasmap file into the project config
|
72
|
+
- rasmap_projection_string
|
73
|
+
- Version #Ex: <Version>2.0.0</Version>
|
74
|
+
- RASProjectionFilename Filename=".\Terrain\Projection.prj"
|
75
|
+
|
76
|
+
- List of ras terrains as pandas dataframes
|
77
|
+
- for each, list of tiff files and order
|
78
|
+
- flag whether terrain mods exist
|
79
|
+
|
80
|
+
- List of Infiltration hdf files as pandas dataframes
|
81
|
+
- Mapping of infiltration layers to geometries
|
82
|
+
|
83
|
+
- List of land cover hdf files as pandas dataframes
|
84
|
+
- Mapping of land cover to geometries
|
85
|
+
|
86
|
+
- List of all Mannings N layers, hdf files and mapping to geometries as pandas dataframes
|
87
|
+
|
88
|
+
6. Create a list of all valid hdf plan files are present in the project folder, and flag whether they contain a completed simulation
|
89
|
+
|
90
|
+
This roadmap for the project_init function will provide the basic information needed to support most basic hec-ras automation workflows.
|
91
|
+
|
92
|
+
Remember, this project init might be called multiple times. Every time, it should clear any previously created datafrarmes and variables and replace them. It is important that revisions can be made, init be re-run, and information is current and reflects the current state of the project.
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
"""
|
97
|
+
# Check if the provided paths exist
|
98
|
+
if not Path(ras_project_folder).exists():
|
99
|
+
raise FileNotFoundError(f"The specified RAS project folder does not exist: {ras_project_folder}, Please check the path and try again.")
|
100
|
+
|
101
|
+
hecras_exe_path = get_hecras_exe_path(hecras_version_or_path)
|
102
|
+
|
103
|
+
config = ProjectConfig()
|
104
|
+
config.initialize(ras_project_folder, hecras_exe_path)
|
105
|
+
print(f"HEC-RAS project initialized: {config.project_name}")
|
106
|
+
return config
|
107
|
+
|
108
|
+
|
109
|
+
def get_hecras_exe_path(hecras_version_or_path):
|
110
|
+
"""
|
111
|
+
Determine the HEC-RAS executable path based on the input.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
hecras_version_or_path (str): Either a version number or a full path to the HEC-RAS executable.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
str: The full path to the HEC-RAS executable.
|
118
|
+
|
119
|
+
Raises:
|
120
|
+
ValueError: If the input is neither a valid version number nor a valid file path.
|
121
|
+
FileNotFoundError: If the executable file does not exist at the specified or constructed path.
|
122
|
+
"""
|
123
|
+
|
124
|
+
|
125
|
+
# hecras_exe_path should be changed to hecras_version_or_path
|
126
|
+
# Based on whether a full path is provided, or a version number is provided, the path will be set accordingly
|
127
|
+
# By default, HEC-RAS is installed in the Program Files (x86) folder
|
128
|
+
# For example: hecras_exe_path = r"C:\Program Files (x86)\HEC\HEC-RAS\6.5\Ras.exe" for version 6.5
|
129
|
+
# an f string to build the path based on the version number
|
130
|
+
# hecras_exe_path = f"C:\Program Files (x86)\HEC\HEC-RAS\{hecras_version}\Ras.exe"
|
131
|
+
# where hecras_version is one of the following:
|
132
|
+
|
133
|
+
# List of HEC-RAS version numbers
|
134
|
+
ras_version_numbers = [
|
135
|
+
"6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
136
|
+
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
137
|
+
"4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
|
138
|
+
]
|
139
|
+
|
140
|
+
hecras_path = Path(hecras_version_or_path)
|
141
|
+
|
142
|
+
# Check if the input is a full path
|
143
|
+
if hecras_path.is_file() and hecras_path.suffix.lower() == '.exe':
|
144
|
+
return str(hecras_path)
|
145
|
+
|
146
|
+
# Check if the input is a version number
|
147
|
+
if hecras_version_or_path in ras_version_numbers:
|
148
|
+
default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{hecras_version_or_path}/Ras.exe")
|
149
|
+
if default_path.is_file():
|
150
|
+
return str(default_path)
|
151
|
+
else:
|
152
|
+
raise FileNotFoundError(f"HEC-RAS executable not found at the expected path: {default_path}")
|
153
|
+
|
154
|
+
# Check if it's a newer version
|
155
|
+
try:
|
156
|
+
version_float = float(hecras_version_or_path)
|
157
|
+
if version_float > max(float(v) for v in ras_version_numbers):
|
158
|
+
newer_version_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{hecras_version_or_path}/Ras.exe")
|
159
|
+
if newer_version_path.is_file():
|
160
|
+
return str(newer_version_path)
|
161
|
+
else:
|
162
|
+
raise FileNotFoundError(f"Newer version of HEC-RAS was specified. Check the version number or pass the full Ras.exe path as the function argument instead of the version number. The script looked for the executable at: {newer_version_path}")
|
163
|
+
except ValueError:
|
164
|
+
pass # Not a valid float, so not a version number
|
165
|
+
|
166
|
+
raise ValueError(f"Invalid HEC-RAS version or path: {hecras_version_or_path}. "
|
167
|
+
f"Please provide a valid version number from {ras_version_numbers} "
|
168
|
+
"or a full path to the HEC-RAS executable.")
|
169
|
+
# Return the validated HEC-RAS executable path
|
170
|
+
return hecras_exe_path
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
|
@@ -0,0 +1,227 @@
|
|
1
|
+
"""
|
2
|
+
Project management operations for HEC-RAS projects.
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
import re
|
6
|
+
import shutil
|
7
|
+
from pathlib import Path
|
8
|
+
from .file_operations import FileOperations
|
9
|
+
from .project_config import ProjectConfig
|
10
|
+
|
11
|
+
class ProjectManager:
|
12
|
+
"""
|
13
|
+
A class for functions that interface with the HEC-RAS project's project file.
|
14
|
+
"""
|
15
|
+
@staticmethod
|
16
|
+
def get_next_available_number(existing_numbers):
|
17
|
+
"""
|
18
|
+
Determine the first available number for plan, unsteady, steady, or geometry files from 01 to 99.
|
19
|
+
Parameters:
|
20
|
+
existing_numbers (pandas.Series): Series of existing numbers as strings (e.g., ['01', '02', '03'])
|
21
|
+
Returns:
|
22
|
+
str: First available number as a two-digit string
|
23
|
+
"""
|
24
|
+
existing_set = set(existing_numbers.astype(int))
|
25
|
+
for num in range(1, 100):
|
26
|
+
if num not in existing_set:
|
27
|
+
return f"{num:02d}"
|
28
|
+
return None
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
def copy_plan_from_template(template_plan, new_plan_shortid=None):
|
32
|
+
"""
|
33
|
+
Create a new plan file based on a template and update the project file.
|
34
|
+
Parameters:
|
35
|
+
template_plan (str): Plan file to use as template (e.g., 'p01')
|
36
|
+
new_plan_shortid (str, optional): New short identifier for the plan file
|
37
|
+
Returns:
|
38
|
+
str: New plan number
|
39
|
+
"""
|
40
|
+
config = ProjectConfig()
|
41
|
+
config.check_initialized()
|
42
|
+
|
43
|
+
new_plan_num = ProjectManager.get_next_available_number(config.ras_plan_entries['plan_number'])
|
44
|
+
template_plan_path = config.project_folder / f"{config.project_name}.{template_plan}"
|
45
|
+
new_plan_path = config.project_folder / f"{config.project_name}.p{new_plan_num}"
|
46
|
+
shutil.copy(template_plan_path, new_plan_path)
|
47
|
+
print(f"Copied {template_plan_path} to {new_plan_path}")
|
48
|
+
|
49
|
+
with open(new_plan_path, 'r') as f:
|
50
|
+
plan_lines = f.readlines()
|
51
|
+
|
52
|
+
shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
|
53
|
+
for i, line in enumerate(plan_lines):
|
54
|
+
match = shortid_pattern.match(line.strip())
|
55
|
+
if match:
|
56
|
+
current_shortid = match.group(1)
|
57
|
+
if new_plan_shortid is None:
|
58
|
+
new_shortid = (current_shortid + "_copy")[:24]
|
59
|
+
else:
|
60
|
+
new_shortid = new_plan_shortid[:24]
|
61
|
+
plan_lines[i] = f"Short Identifier={new_shortid}\n"
|
62
|
+
break
|
63
|
+
|
64
|
+
with open(new_plan_path, 'w') as f:
|
65
|
+
f.writelines(plan_lines)
|
66
|
+
|
67
|
+
print(f"Updated short identifier in {new_plan_path}")
|
68
|
+
|
69
|
+
with open(config.project_file, 'r') as f:
|
70
|
+
lines = f.readlines()
|
71
|
+
|
72
|
+
new_plan_line = f"Plan File=p{new_plan_num}\n"
|
73
|
+
plan_file_pattern = re.compile(r'^Plan File=p(\d+)', re.IGNORECASE)
|
74
|
+
insertion_index = None
|
75
|
+
for i, line in enumerate(lines):
|
76
|
+
match = plan_file_pattern.match(line.strip())
|
77
|
+
if match:
|
78
|
+
current_number = int(match.group(1))
|
79
|
+
if current_number > int(new_plan_num):
|
80
|
+
insertion_index = i
|
81
|
+
break
|
82
|
+
|
83
|
+
if insertion_index is not None:
|
84
|
+
lines.insert(insertion_index, new_plan_line)
|
85
|
+
else:
|
86
|
+
last_plan_index = max([i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())], default=-1)
|
87
|
+
if last_plan_index != -1:
|
88
|
+
lines.insert(last_plan_index + 1, new_plan_line)
|
89
|
+
else:
|
90
|
+
lines.append(new_plan_line)
|
91
|
+
|
92
|
+
with open(config.project_file, 'w') as f:
|
93
|
+
f.writelines(lines)
|
94
|
+
|
95
|
+
print(f"Updated {config.project_file} with new plan p{new_plan_num}")
|
96
|
+
return f"p{new_plan_num}"
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def copy_geometry_from_template(template_geom):
|
100
|
+
"""
|
101
|
+
Copy geometry files from a template, find the next geometry number,
|
102
|
+
and update the project file accordingly.
|
103
|
+
Parameters:
|
104
|
+
template_geom (str): Geometry number to be used as a template (e.g., 'g01')
|
105
|
+
Returns:
|
106
|
+
str: New geometry number (e.g., 'g03')
|
107
|
+
"""
|
108
|
+
config = ProjectConfig()
|
109
|
+
config.check_initialized()
|
110
|
+
|
111
|
+
template_geom_filename = f"{config.project_name}.{template_geom}"
|
112
|
+
template_geom_path = config.project_folder / template_geom_filename
|
113
|
+
|
114
|
+
if not template_geom_path.is_file():
|
115
|
+
raise FileNotFoundError(f"Template geometry file '{template_geom_path}' does not exist.")
|
116
|
+
|
117
|
+
template_hdf_path = template_geom_path.with_suffix('.hdf')
|
118
|
+
if not template_hdf_path.is_file():
|
119
|
+
raise FileNotFoundError(f"Template geometry .hdf file '{template_hdf_path}' does not exist.")
|
120
|
+
|
121
|
+
next_geom_number = ProjectManager.get_next_available_number(config.ras_geom_entries['geom_number'])
|
122
|
+
|
123
|
+
new_geom_filename = f"{config.project_name}.g{next_geom_number}"
|
124
|
+
new_geom_path = config.project_folder / new_geom_filename
|
125
|
+
|
126
|
+
shutil.copyfile(template_geom_path, new_geom_path)
|
127
|
+
print(f"Copied '{template_geom_path}' to '{new_geom_path}'.")
|
128
|
+
|
129
|
+
new_hdf_path = new_geom_path.with_suffix('.hdf')
|
130
|
+
shutil.copyfile(template_hdf_path, new_hdf_path)
|
131
|
+
print(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
|
132
|
+
|
133
|
+
with open(config.project_file, 'r') as file:
|
134
|
+
lines = file.readlines()
|
135
|
+
|
136
|
+
new_geom_line = f"Geom File=g{next_geom_number}\n"
|
137
|
+
geom_file_pattern = re.compile(r'^Geom File=g(\d+)', re.IGNORECASE)
|
138
|
+
insertion_index = None
|
139
|
+
for i, line in enumerate(lines):
|
140
|
+
match = geom_file_pattern.match(line.strip())
|
141
|
+
if match:
|
142
|
+
current_number = int(match.group(1))
|
143
|
+
if current_number > int(next_geom_number):
|
144
|
+
insertion_index = i
|
145
|
+
break
|
146
|
+
|
147
|
+
if insertion_index is not None:
|
148
|
+
lines.insert(insertion_index, new_geom_line)
|
149
|
+
else:
|
150
|
+
header_pattern = re.compile(r'^(Proj Title|Current Plan|Default Exp/Contr|English Units)', re.IGNORECASE)
|
151
|
+
header_indices = [i for i, line in enumerate(lines) if header_pattern.match(line.strip())]
|
152
|
+
if header_indices:
|
153
|
+
last_header_index = header_indices[-1]
|
154
|
+
lines.insert(last_header_index + 2, new_geom_line)
|
155
|
+
else:
|
156
|
+
lines.insert(0, new_geom_line)
|
157
|
+
|
158
|
+
with open(config.project_file, 'w') as file:
|
159
|
+
file.writelines(lines)
|
160
|
+
|
161
|
+
print(f"Inserted 'Geom File=g{next_geom_number}' into project file '{config.project_file}'.")
|
162
|
+
return f"g{next_geom_number}"
|
163
|
+
|
164
|
+
@staticmethod
|
165
|
+
def copy_unsteady_from_template(template_unsteady):
|
166
|
+
"""
|
167
|
+
Copy unsteady flow files from a template, find the next unsteady number,
|
168
|
+
and update the project file accordingly.
|
169
|
+
Parameters:
|
170
|
+
template_unsteady (str): Unsteady flow number to be used as a template (e.g., 'u01')
|
171
|
+
Returns:
|
172
|
+
str: New unsteady flow number (e.g., 'u03')
|
173
|
+
"""
|
174
|
+
config = ProjectConfig()
|
175
|
+
config.check_initialized()
|
176
|
+
|
177
|
+
template_unsteady_filename = f"{config.project_name}.{template_unsteady}"
|
178
|
+
template_unsteady_path = config.project_folder / template_unsteady_filename
|
179
|
+
|
180
|
+
if not template_unsteady_path.is_file():
|
181
|
+
raise FileNotFoundError(f"Template unsteady flow file '{template_unsteady_path}' does not exist.")
|
182
|
+
|
183
|
+
next_unsteady_number = ProjectManager.get_next_available_number(config.ras_unsteady_entries['unsteady_number'])
|
184
|
+
|
185
|
+
new_unsteady_filename = f"{config.project_name}.u{next_unsteady_number}"
|
186
|
+
new_unsteady_path = config.project_folder / new_unsteady_filename
|
187
|
+
|
188
|
+
shutil.copyfile(template_unsteady_path, new_unsteady_path)
|
189
|
+
print(f"Copied '{template_unsteady_path}' to '{new_unsteady_path}'.")
|
190
|
+
|
191
|
+
template_hdf_path = template_unsteady_path.with_suffix('.hdf')
|
192
|
+
new_hdf_path = new_unsteady_path.with_suffix('.hdf')
|
193
|
+
|
194
|
+
if template_hdf_path.is_file():
|
195
|
+
shutil.copyfile(template_hdf_path, new_hdf_path)
|
196
|
+
print(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
|
197
|
+
else:
|
198
|
+
print(f"No corresponding '.hdf' file found for '{template_unsteady_filename}'. Skipping '.hdf' copy.")
|
199
|
+
|
200
|
+
with open(config.project_file, 'r') as file:
|
201
|
+
lines = file.readlines()
|
202
|
+
|
203
|
+
new_unsteady_line = f"Unsteady File=u{next_unsteady_number}\n"
|
204
|
+
unsteady_file_pattern = re.compile(r'^Unsteady File=u(\d+)', re.IGNORECASE)
|
205
|
+
insertion_index = None
|
206
|
+
for i, line in enumerate(lines):
|
207
|
+
match = unsteady_file_pattern.match(line.strip())
|
208
|
+
if match:
|
209
|
+
current_number = int(match.group(1))
|
210
|
+
if current_number > int(next_unsteady_number):
|
211
|
+
insertion_index = i
|
212
|
+
break
|
213
|
+
|
214
|
+
if insertion_index is not None:
|
215
|
+
lines.insert(insertion_index, new_unsteady_line)
|
216
|
+
else:
|
217
|
+
last_unsteady_index = max([i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())], default=-1)
|
218
|
+
if last_unsteady_index != -1:
|
219
|
+
lines.insert(last_unsteady_index + 1, new_unsteady_line)
|
220
|
+
else:
|
221
|
+
lines.append(new_unsteady_line)
|
222
|
+
|
223
|
+
with open(config.project_file, 'w') as file:
|
224
|
+
file.writelines(lines)
|
225
|
+
|
226
|
+
print(f"Inserted 'Unsteady File=u{next_unsteady_number}' into project file '{config.project_file}'.")
|
227
|
+
return f"u{next_unsteady_number}"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# REVISION NOTE: We should be able to move these functions into the project_management.py file and delete this file.
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from .file_operations import FileOperations
|
5
|
+
|
6
|
+
def find_hecras_project_file(folder_path):
|
7
|
+
return FileOperations.find_hecras_project_file(folder_path)
|
8
|
+
|
9
|
+
def load_project_data(project_file):
|
10
|
+
return {
|
11
|
+
'ras_plan_entries': FileOperations.get_plan_entries(project_file),
|
12
|
+
'ras_flow_entries': FileOperations.get_flow_entries(project_file),
|
13
|
+
'ras_unsteady_entries': FileOperations.get_unsteady_entries(project_file),
|
14
|
+
'ras_geom_entries': FileOperations.get_geom_entries(project_file)
|
15
|
+
}
|
@@ -0,0 +1,172 @@
|
|
1
|
+
"""
|
2
|
+
Operations for handling unsteady flow files in HEC-RAS projects.
|
3
|
+
"""
|
4
|
+
from pathlib import Path
|
5
|
+
from .project_config import ProjectConfig
|
6
|
+
import re
|
7
|
+
|
8
|
+
class UnsteadyOperations:
|
9
|
+
"""
|
10
|
+
Class for all operations related to HEC-RAS unsteady flow files.
|
11
|
+
"""
|
12
|
+
@staticmethod
|
13
|
+
def copy_unsteady_files(dst_folder, template_unsteady):
|
14
|
+
"""
|
15
|
+
Copy unsteady flow files from a template to the next available unsteady number
|
16
|
+
and update the destination folder accordingly.
|
17
|
+
|
18
|
+
Parameters:
|
19
|
+
dst_folder (str): Destination folder path
|
20
|
+
template_unsteady (str): Template unsteady flow number (e.g., 'u01')
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
str: New unsteady flow number (e.g., 'u03')
|
24
|
+
"""
|
25
|
+
config = ProjectConfig()
|
26
|
+
config.check_initialized()
|
27
|
+
|
28
|
+
src_folder = config.project_folder
|
29
|
+
dst_folder = Path(dst_folder)
|
30
|
+
dst_folder.mkdir(parents=True, exist_ok=True)
|
31
|
+
|
32
|
+
# Determine the next available unsteady number
|
33
|
+
existing_numbers = []
|
34
|
+
unsteady_file_pattern = re.compile(r'^Flow File=u(\d+)', re.IGNORECASE)
|
35
|
+
|
36
|
+
for plan_file in src_folder.glob("*.p[0-9][0-9]"):
|
37
|
+
with open(plan_file, 'r') as file:
|
38
|
+
lines = file.readlines()
|
39
|
+
for line in lines:
|
40
|
+
match = unsteady_file_pattern.match(line.strip())
|
41
|
+
if match:
|
42
|
+
existing_numbers.append(int(match.group(1)))
|
43
|
+
|
44
|
+
if existing_numbers:
|
45
|
+
existing_numbers.sort()
|
46
|
+
next_number = 1
|
47
|
+
for num in existing_numbers:
|
48
|
+
if num == next_number:
|
49
|
+
next_number += 1
|
50
|
+
else:
|
51
|
+
break
|
52
|
+
else:
|
53
|
+
next_number = 1
|
54
|
+
|
55
|
+
next_unsteady_number = f"{next_number:02d}"
|
56
|
+
|
57
|
+
# Copy the template unsteady file to the new unsteady file
|
58
|
+
template_unsteady_filename = f"{config.project_name}.u{template_unsteady}"
|
59
|
+
src_u_file = src_folder / template_unsteady_filename
|
60
|
+
if src_u_file.exists():
|
61
|
+
new_unsteady_filename = f"{config.project_name}.u{next_unsteady_number}"
|
62
|
+
dst_u_file = dst_folder / new_unsteady_filename
|
63
|
+
dst_u_file.write_bytes(src_u_file.read_bytes())
|
64
|
+
print(f"Copied {src_u_file} to {dst_u_file}")
|
65
|
+
else:
|
66
|
+
raise FileNotFoundError(f"Template unsteady file '{src_u_file}' does not exist.")
|
67
|
+
|
68
|
+
# Copy the corresponding .hdf file
|
69
|
+
src_hdf_file = src_folder / f"{template_unsteady_filename}.hdf"
|
70
|
+
if src_hdf_file.exists():
|
71
|
+
new_hdf_filename = f"{new_unsteady_filename}.hdf"
|
72
|
+
dst_hdf_file = dst_folder / new_hdf_filename
|
73
|
+
dst_hdf_file.write_bytes(src_hdf_file.read_bytes())
|
74
|
+
print(f"Copied {src_hdf_file} to {dst_hdf_file}")
|
75
|
+
else:
|
76
|
+
print(f"No corresponding .hdf file found for '{template_unsteady_filename}'. Skipping '.hdf' copy.")
|
77
|
+
config = ProjectConfig()
|
78
|
+
return f"u{next_unsteady_number}"
|
79
|
+
|
80
|
+
@staticmethod
|
81
|
+
def rename_unsteady_files(old_number, new_number):
|
82
|
+
"""
|
83
|
+
Rename unsteady flow files in the project folder.
|
84
|
+
Parameters:
|
85
|
+
old_number (str): Old unsteady flow number (e.g., 'u01')
|
86
|
+
new_number (str): New unsteady flow number (e.g., 'u02')
|
87
|
+
Returns:
|
88
|
+
None
|
89
|
+
"""
|
90
|
+
config = ProjectConfig()
|
91
|
+
config.check_initialized()
|
92
|
+
|
93
|
+
folder = config.project_folder
|
94
|
+
old_u_file = next(folder.glob(f"*.{old_number}"), None)
|
95
|
+
if old_u_file:
|
96
|
+
new_u_file = folder / old_u_file.name.replace(old_number, new_number)
|
97
|
+
old_u_file.rename(new_u_file)
|
98
|
+
print(f"Renamed {old_u_file} to {new_u_file}")
|
99
|
+
else:
|
100
|
+
print(f"No .{old_number} file found in {folder}")
|
101
|
+
|
102
|
+
old_hdf_file = next(folder.glob(f"*.{old_number}.hdf"), None)
|
103
|
+
if old_hdf_file:
|
104
|
+
new_hdf_file = folder / old_hdf_file.name.replace(old_number, new_number)
|
105
|
+
old_hdf_file.rename(new_hdf_file)
|
106
|
+
print(f"Renamed {old_hdf_file} to {new_hdf_file}")
|
107
|
+
else:
|
108
|
+
print(f"No .{old_number}.hdf file found in {folder}")
|
109
|
+
|
110
|
+
@staticmethod
|
111
|
+
def update_unsteady_reference_in_plan(plan_file, new_unsteady_number):
|
112
|
+
"""
|
113
|
+
Update the unsteady flow reference in a plan file.
|
114
|
+
|
115
|
+
Parameters:
|
116
|
+
plan_file (str): Full path to the plan file
|
117
|
+
new_unsteady_number (str): New unsteady flow number (e.g., 'u02')
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
None
|
121
|
+
"""
|
122
|
+
config = ProjectConfig()
|
123
|
+
config.check_initialized()
|
124
|
+
|
125
|
+
plan_path = Path(plan_file)
|
126
|
+
if not plan_path.is_file():
|
127
|
+
raise FileNotFoundError(f"Plan file '{plan_path}' not found. Check that the file exists.")
|
128
|
+
|
129
|
+
with open(plan_path, 'r') as f:
|
130
|
+
lines = f.readlines()
|
131
|
+
updated = False
|
132
|
+
for i, line in enumerate(lines):
|
133
|
+
if line.startswith("Unsteady File=") or line.startswith("Flow File=u"):
|
134
|
+
lines[i] = f"Unsteady File={new_unsteady_number}\n"
|
135
|
+
updated = True
|
136
|
+
break
|
137
|
+
if updated:
|
138
|
+
with open(plan_path, 'w') as f:
|
139
|
+
f.writelines(lines)
|
140
|
+
print(f"Updated unsteady flow reference in {plan_file} to {new_unsteady_number}")
|
141
|
+
else:
|
142
|
+
print(f"No unsteady flow reference found in {plan_file}")
|
143
|
+
|
144
|
+
@staticmethod
|
145
|
+
def modify_unsteady_flow_parameters(unsteady_file, modifications):
|
146
|
+
"""
|
147
|
+
Modify parameters in an unsteady flow file.
|
148
|
+
Parameters:
|
149
|
+
unsteady_file (str): Full path to the unsteady flow file
|
150
|
+
modifications (dict): Dictionary of modifications to apply, where keys are parameter names and values are new values
|
151
|
+
Returns:
|
152
|
+
None
|
153
|
+
"""
|
154
|
+
config = ProjectConfig()
|
155
|
+
config.check_initialized()
|
156
|
+
|
157
|
+
unsteady_path = Path(unsteady_file)
|
158
|
+
with open(unsteady_path, 'r') as f:
|
159
|
+
lines = f.readlines()
|
160
|
+
updated = False
|
161
|
+
for i, line in enumerate(lines):
|
162
|
+
for param, new_value in modifications.items():
|
163
|
+
if line.startswith(f"{param}="):
|
164
|
+
lines[i] = f"{param}={new_value}\n"
|
165
|
+
updated = True
|
166
|
+
print(f"Updated {param} to {new_value}")
|
167
|
+
if updated:
|
168
|
+
with open(unsteady_path, 'w') as f:
|
169
|
+
f.writelines(lines)
|
170
|
+
print(f"Applied modifications to {unsteady_file}")
|
171
|
+
else:
|
172
|
+
print(f"No matching parameters found in {unsteady_file}")
|