ras-commander 0.1.5__py2.py3-none-any.whl → 0.20.dev0__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/README.md +187 -0
- ras_commander/RasCommander.py +456 -0
- ras_commander/RasExamples.py +304 -0
- ras_commander/RasGeo.py +83 -0
- ras_commander/RasPlan.py +1216 -0
- ras_commander/RasPrj.py +400 -0
- ras_commander/RasUnsteady.py +53 -0
- ras_commander/RasUtils.py +283 -0
- ras_commander/__init__.py +33 -0
- ras_commander/_version.py +2 -2
- ras_commander-0.20.dev0.dist-info/METADATA +342 -0
- ras_commander-0.20.dev0.dist-info/RECORD +15 -0
- {ras_commander-0.1.5.dist-info → ras_commander-0.20.dev0.dist-info}/WHEEL +1 -1
- ras_commander/execution.py +0 -315
- ras_commander/file_operations.py +0 -173
- ras_commander/geometry_operations.py +0 -184
- ras_commander/plan_operations.py +0 -307
- ras_commander/project_config.py +0 -64
- ras_commander/project_init.py +0 -174
- ras_commander/project_management.py +0 -227
- ras_commander/project_setup.py +0 -15
- ras_commander/unsteady_operations.py +0 -172
- ras_commander/utilities.py +0 -195
- ras_commander-0.1.5.dist-info/METADATA +0 -133
- ras_commander-0.1.5.dist-info/RECORD +0 -17
- {ras_commander-0.1.5.dist-info → ras_commander-0.20.dev0.dist-info}/LICENSE +0 -0
- {ras_commander-0.1.5.dist-info → ras_commander-0.20.dev0.dist-info}/top_level.txt +0 -0
ras_commander/execution.py
DELETED
@@ -1,315 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Execution operations for runningHEC-RAS simulations using subprocess.
|
3
|
-
Based on the HEC-Commander project's "Command Line is All You Need" approach, leveraging the -c compute flag to run HEC-RAS and orchestrating changes directly in the RAS input files to achieve automation outcomes.
|
4
|
-
"""
|
5
|
-
|
6
|
-
import os
|
7
|
-
import subprocess
|
8
|
-
import shutil
|
9
|
-
from pathlib import Path
|
10
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
11
|
-
from .file_operations import FileOperations
|
12
|
-
from .plan_operations import PlanOperations
|
13
|
-
import subprocess
|
14
|
-
import os
|
15
|
-
import logging
|
16
|
-
import time
|
17
|
-
from .project_config import ProjectConfig
|
18
|
-
import pandas as pd
|
19
|
-
|
20
|
-
class RasExecutor:
|
21
|
-
|
22
|
-
@staticmethod
|
23
|
-
def compute_hecras_plan(compute_plan_file):
|
24
|
-
"""
|
25
|
-
Compute a HEC-RAS plan using the provided plan file.
|
26
|
-
|
27
|
-
This method executes a HEC-RAS plan by running the HEC-RAS executable
|
28
|
-
with the specified plan file. It uses the plan file's path to determine
|
29
|
-
the corresponding project file path.
|
30
|
-
|
31
|
-
Args:
|
32
|
-
compute_plan_file (str): The full path to the HEC-RAS plan file (.p**)
|
33
|
-
|
34
|
-
Returns:
|
35
|
-
bool: True if the plan computation was successful, False otherwise.
|
36
|
-
|
37
|
-
Raises:
|
38
|
-
subprocess.CalledProcessError: If the HEC-RAS execution fails.
|
39
|
-
|
40
|
-
Note:
|
41
|
-
This method assumes that the project file (.prj) is in the same
|
42
|
-
directory as the plan file and has the same name (different extension).
|
43
|
-
"""
|
44
|
-
config = ProjectConfig()
|
45
|
-
config.check_initialized()
|
46
|
-
|
47
|
-
# Derive the project file path from the plan file path
|
48
|
-
compute_project_file = Path(compute_plan_file).with_suffix('.prj')
|
49
|
-
|
50
|
-
cmd = f'"{config.hecras_exe_path}" -c "{compute_project_file}" "{compute_plan_file}"'
|
51
|
-
print(f"Running command: {cmd}")
|
52
|
-
try:
|
53
|
-
subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
|
54
|
-
logging.info(f"HEC-RAS execution completed for plan: {Path(compute_plan_file).name}")
|
55
|
-
return True
|
56
|
-
except subprocess.CalledProcessError as e:
|
57
|
-
logging.error(f"Error running plan: {Path(compute_plan_file).name}")
|
58
|
-
logging.error(f"Error message: {e.output}")
|
59
|
-
return False
|
60
|
-
|
61
|
-
|
62
|
-
@staticmethod
|
63
|
-
def compute_hecras_plan_from_folder(plan_for_compute, folder_for_compute):
|
64
|
-
"""
|
65
|
-
Compute a HEC-RAS plan from a specified folder.
|
66
|
-
|
67
|
-
This function allows running a plan directly from a different folder than the project folder,
|
68
|
-
which is useful for the -test function and parallel runs. It uses the HEC-RAS executable path
|
69
|
-
from the ProjectConfig, but derives other paths from the provided arguments.
|
70
|
-
|
71
|
-
Args:
|
72
|
-
plan_for_compute (str): The full path to the HEC-RAS plan file (.p**) to be computed.
|
73
|
-
folder_for_compute (str): The folder containing the HEC-RAS project files.
|
74
|
-
|
75
|
-
Returns:
|
76
|
-
bool: True if the plan computation was successful, False otherwise.
|
77
|
-
|
78
|
-
Raises:
|
79
|
-
subprocess.CalledProcessError: If the HEC-RAS execution fails.
|
80
|
-
|
81
|
-
Note:
|
82
|
-
This function uses the ProjectConfig only to get the hecras_exe_path.
|
83
|
-
Other paths are derived from plan_for_compute and folder_for_compute.
|
84
|
-
"""
|
85
|
-
config = ProjectConfig()
|
86
|
-
config.check_initialized()
|
87
|
-
compute_project_file = FileOperations.find_hecras_project_file(folder_for_compute)
|
88
|
-
cmd = f'"{config.hecras_exe_path}" -c "{compute_project_file}" "{plan_for_compute}"'
|
89
|
-
print(f"Running command: {cmd}")
|
90
|
-
try:
|
91
|
-
subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
|
92
|
-
logging.info(f"HEC-RAS execution completed for plan: {Path(plan_for_compute).name}")
|
93
|
-
return True
|
94
|
-
except subprocess.CalledProcessError as e:
|
95
|
-
logging.error(f"Error running plan: {Path(plan_for_compute).name}")
|
96
|
-
logging.error(f"Error message: {e.output}")
|
97
|
-
return False
|
98
|
-
|
99
|
-
|
100
|
-
@staticmethod
|
101
|
-
def recreate_test_function(project_folder):
|
102
|
-
"""
|
103
|
-
Recreate the -test function of HEC-RAS command line.
|
104
|
-
|
105
|
-
Parameters:
|
106
|
-
project_folder (str): Path to the HEC-RAS project folder
|
107
|
-
|
108
|
-
Returns:
|
109
|
-
None
|
110
|
-
|
111
|
-
|
112
|
-
This function executes all ras plans in a separate folder defined by compute_folder='[Test]', so we need to call the individual functions and use distinct variable names
|
113
|
-
For this function, we are using "compute" in the varable names as these are the values used for the compute operations in a separate copy of the ras project folder
|
114
|
-
|
115
|
-
"""
|
116
|
-
print("Starting the recreate_test_function...")
|
117
|
-
|
118
|
-
# Create the test folder path
|
119
|
-
compute_folder='[Test]'
|
120
|
-
folder_for_compute = Path(project_folder).parent / f"{Path(project_folder).name} {compute_folder}"
|
121
|
-
print(f"Creating the test folder: {folder_for_compute}...")
|
122
|
-
|
123
|
-
# Copy the project folder to the test folder
|
124
|
-
print("Copying project folder to the test folder...")
|
125
|
-
shutil.copytree(project_folder, folder_for_compute, dirs_exist_ok=True)
|
126
|
-
print(f"Test folder created at: {folder_for_compute}")
|
127
|
-
|
128
|
-
# Find the project file
|
129
|
-
print("Finding the project file...")
|
130
|
-
compute_project_file = FileOperations.find_hecras_project_file(folder_for_compute)
|
131
|
-
|
132
|
-
if not compute_project_file:
|
133
|
-
print("Project file not found.")
|
134
|
-
return
|
135
|
-
print(f"Project file found: {compute_project_file}")
|
136
|
-
|
137
|
-
# Parse the project file to get plan entries
|
138
|
-
print("Parsing the project file to get plan entries...")
|
139
|
-
ras_compute_plan_entries = FileOperations.get_plan_entries(compute_project_file)
|
140
|
-
print("Parsed project file successfully.")
|
141
|
-
|
142
|
-
# Enforce recomputing of geometry preprocessor and IB tables
|
143
|
-
print("Enforcing recomputing of geometry preprocessor and IB tables...")
|
144
|
-
for plan_file in ras_compute_plan_entries['full_path']:
|
145
|
-
PlanOperations.update_geompre_flags(plan_file, run_htab_value=-1, use_ib_tables_value=-1)
|
146
|
-
print("Recomputing enforced successfully.")
|
147
|
-
|
148
|
-
# Change max cores to 1
|
149
|
-
print("Changing max cores to 2 for all plan files...")
|
150
|
-
for plan_file in ras_compute_plan_entries['full_path']:
|
151
|
-
PlanOperations.set_num_cores(plan_file, num_cores=2)
|
152
|
-
print("Max cores updated successfully.")
|
153
|
-
|
154
|
-
# Run all plans sequentially
|
155
|
-
print("Running all plans sequentially...")
|
156
|
-
for _, plan in ras_compute_plan_entries.iterrows():
|
157
|
-
plan_for_compute = plan["full_path"]
|
158
|
-
RasExecutor.compute_hecras_plan_from_folder(plan_for_compute, folder_for_compute)
|
159
|
-
|
160
|
-
print("All plans have been executed.")
|
161
|
-
print("recreate_test_function completed.")
|
162
|
-
|
163
|
-
@staticmethod
|
164
|
-
def run_plans_parallel(config, max_workers, cores_per_run):
|
165
|
-
"""
|
166
|
-
Run HEC-RAS plans in parallel using ThreadPoolExecutor.
|
167
|
-
|
168
|
-
Parameters:
|
169
|
-
config (ProjectConfig): Configuration object containing project information
|
170
|
-
max_workers (int): Maximum number of parallel runs
|
171
|
-
cores_per_run (int): Number of cores to use per run
|
172
|
-
|
173
|
-
Returns:
|
174
|
-
dict: Dictionary with plan numbers as keys and execution success as values
|
175
|
-
|
176
|
-
This function executes all ras plans in separate folders defined by the max_workers and the numbering of the test folders [Test 1], [Test 2], etc.
|
177
|
-
Each worker operates sequentially on its assigned folder while all workers operate in parallel.
|
178
|
-
|
179
|
-
Revisions:
|
180
|
-
1. Created a pandas DataFrame to map each folder to a plan for execution.
|
181
|
-
2. Implemented logic to assign worker numbers and compute folders.
|
182
|
-
3. Used find_hecras_project_file function to get the full path of project files.
|
183
|
-
4. Updated plan file paths to use compute folders.
|
184
|
-
5. Revised the parallel execution to use separate threads for each worker.
|
185
|
-
6. Implemented a queue system for each worker to handle plans sequentially.
|
186
|
-
7. Updated the cleanup process to use the new folder structure.
|
187
|
-
"""
|
188
|
-
import queue
|
189
|
-
from threading import Thread
|
190
|
-
|
191
|
-
project_folder = Path(config.project_file).parent
|
192
|
-
test_folders = []
|
193
|
-
|
194
|
-
# Create multiple copies of the project folder
|
195
|
-
for i in range(1, max_workers + 1):
|
196
|
-
folder_for_compute = project_folder.parent / f"{project_folder.name} [Test {i}]"
|
197
|
-
shutil.copytree(project_folder, folder_for_compute, dirs_exist_ok=True)
|
198
|
-
test_folders.append(folder_for_compute)
|
199
|
-
print(f"Created test folder: {folder_for_compute}")
|
200
|
-
|
201
|
-
compute_parallel_entries = []
|
202
|
-
for i, (_, plan_row) in enumerate(config.ras_plan_entries.iterrows()):
|
203
|
-
worker_number = i % max_workers
|
204
|
-
compute_folder = test_folders[worker_number]
|
205
|
-
compute_project_file = FileOperations.find_hecras_project_file(compute_folder)
|
206
|
-
compute_plan_file = compute_folder / Path(plan_row['full_path']).name
|
207
|
-
compute_parallel_entries.append({
|
208
|
-
'worker_number': worker_number,
|
209
|
-
'compute_folder': compute_folder,
|
210
|
-
'compute_project_file': compute_project_file,
|
211
|
-
'compute_plan_file': compute_plan_file,
|
212
|
-
'plan_number': plan_row['plan_number']
|
213
|
-
})
|
214
|
-
|
215
|
-
compute_parallel_df = pd.DataFrame(compute_parallel_entries)
|
216
|
-
print("compute_parallel_entries dataframe:")
|
217
|
-
display(compute_parallel_df)
|
218
|
-
|
219
|
-
results = {}
|
220
|
-
worker_queues = [queue.Queue() for _ in range(max_workers)]
|
221
|
-
|
222
|
-
def worker_thread(worker_id):
|
223
|
-
"""
|
224
|
-
Execute HEC-RAS plans assigned to a specific worker thread.
|
225
|
-
|
226
|
-
This function continuously processes plans from the worker's queue until it's empty.
|
227
|
-
It sets the number of cores for each plan, computes the plan, and records the result.
|
228
|
-
|
229
|
-
Parameters:
|
230
|
-
worker_id (int): The ID of the worker thread.
|
231
|
-
|
232
|
-
Notes:
|
233
|
-
- Uses PlanOperations.set_num_cores to set the number of cores for each plan.
|
234
|
-
- Uses RasExecutor.compute_hecras_plan to execute each plan.
|
235
|
-
- Records success or failure in the 'results' dictionary.
|
236
|
-
- Prints status messages for completed or failed plans.
|
237
|
-
"""
|
238
|
-
while True:
|
239
|
-
try:
|
240
|
-
row = worker_queues[worker_id].get(block=False)
|
241
|
-
PlanOperations.set_num_cores(str(row['compute_plan_file']), cores_per_run)
|
242
|
-
success = RasExecutor.compute_hecras_plan(row['compute_plan_file'])
|
243
|
-
results[row['plan_number']] = success
|
244
|
-
print(f"Completed: Plan {row['plan_number']} in worker {worker_id}")
|
245
|
-
except queue.Empty:
|
246
|
-
break
|
247
|
-
except Exception as e:
|
248
|
-
results[row['plan_number']] = False
|
249
|
-
print(f"Failed: Plan {row['plan_number']} in worker {worker_id}. Error: {str(e)}")
|
250
|
-
|
251
|
-
# Distribute plans to worker queues
|
252
|
-
for _, row in compute_parallel_df.iterrows():
|
253
|
-
worker_queues[row['worker_number']].put(row)
|
254
|
-
|
255
|
-
# Start worker threads
|
256
|
-
threads = []
|
257
|
-
for i in range(max_workers):
|
258
|
-
thread = Thread(target=worker_thread, args=(i,))
|
259
|
-
thread.start()
|
260
|
-
threads.append(thread)
|
261
|
-
|
262
|
-
# Wait for all threads to complete
|
263
|
-
for thread in threads:
|
264
|
-
thread.join()
|
265
|
-
|
266
|
-
# Clean up and consolidate results
|
267
|
-
time.sleep(3) # Allow files to close
|
268
|
-
final_test_folder = project_folder.parent / f"{project_folder.name} [Test]"
|
269
|
-
final_test_folder.mkdir(exist_ok=True)
|
270
|
-
|
271
|
-
for test_folder in test_folders:
|
272
|
-
for item in test_folder.iterdir():
|
273
|
-
dest_path = final_test_folder / item.name
|
274
|
-
if dest_path.exists():
|
275
|
-
if dest_path.is_dir():
|
276
|
-
shutil.rmtree(dest_path)
|
277
|
-
else:
|
278
|
-
dest_path.unlink()
|
279
|
-
shutil.move(str(item), final_test_folder)
|
280
|
-
shutil.rmtree(test_folder)
|
281
|
-
print(f"Moved and removed test folder: {test_folder}")
|
282
|
-
|
283
|
-
return results
|
284
|
-
|
285
|
-
@staticmethod
|
286
|
-
def run_all_plans_parallel(project_folder, hecras_exe_path):
|
287
|
-
"""
|
288
|
-
Run all plans in a project folder in parallel.
|
289
|
-
|
290
|
-
Parameters:
|
291
|
-
project_folder (str): The path to the project folder.
|
292
|
-
hecras_exe_path (str): The path to the HEC-RAS executable.
|
293
|
-
|
294
|
-
Returns:
|
295
|
-
dict: A dictionary with plan numbers as keys and execution success status as values.
|
296
|
-
"""
|
297
|
-
config = ProjectConfig.init_ras_project(project_folder, hecras_exe_path)
|
298
|
-
|
299
|
-
if config:
|
300
|
-
print("ras_plan_entries dataframe:")
|
301
|
-
display(config.ras_plan_entries)
|
302
|
-
|
303
|
-
max_workers = 2 # Number of parallel runs
|
304
|
-
cores_per_run = 2 # Number of cores per run
|
305
|
-
|
306
|
-
results = RasExecutor.run_plans_parallel(config, max_workers, cores_per_run)
|
307
|
-
|
308
|
-
print("\nExecution Results:")
|
309
|
-
for plan_number, success in results.items():
|
310
|
-
print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
|
311
|
-
|
312
|
-
return results
|
313
|
-
else:
|
314
|
-
print("Failed to initialize project configuration.")
|
315
|
-
return None
|
ras_commander/file_operations.py
DELETED
@@ -1,173 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
File operations for HEC-RAS project files.
|
3
|
-
"""
|
4
|
-
import re
|
5
|
-
from pathlib import Path
|
6
|
-
import pandas as pd
|
7
|
-
|
8
|
-
class FileOperations:
|
9
|
-
"""
|
10
|
-
A class for HEC-RAS file operations.
|
11
|
-
|
12
|
-
|
13
|
-
Revision Notes: All functions from class ProjectManager should be moved here
|
14
|
-
|
15
|
-
|
16
|
-
"""
|
17
|
-
@staticmethod
|
18
|
-
def find_hecras_project_file(folder_path):
|
19
|
-
"""
|
20
|
-
Find the appropriate HEC-RAS project file (.prj) in the given folder.
|
21
|
-
|
22
|
-
Parameters:
|
23
|
-
folder_path (str or Path): Path to the folder containing HEC-RAS files.
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
Path: The full path of the selected .prj file or None if no suitable file is found.
|
27
|
-
"""
|
28
|
-
print(f"running find_hecras_project_file with folder_path: {folder_path}")
|
29
|
-
folder_path = Path(folder_path)
|
30
|
-
print("Searching for .prj files...")
|
31
|
-
prj_files = list(folder_path.glob("*.prj"))
|
32
|
-
# print(f"Found {len(prj_files)} .prj files")
|
33
|
-
# print("Searching for .rasmap files...")
|
34
|
-
rasmap_files = list(folder_path.glob("*.rasmap"))
|
35
|
-
#print(f"Found {len(rasmap_files)} .rasmap files")
|
36
|
-
if len(prj_files) == 1:
|
37
|
-
project_file = prj_files[0]
|
38
|
-
# print(f"Only one .prj file found. Selecting: {project_file}")
|
39
|
-
# print(f"Full path: {project_file.resolve()}")
|
40
|
-
return project_file.resolve()
|
41
|
-
if len(prj_files) > 1:
|
42
|
-
print("Multiple .prj files found.")
|
43
|
-
if len(rasmap_files) == 1:
|
44
|
-
base_filename = rasmap_files[0].stem
|
45
|
-
project_file = folder_path / f"{base_filename}.prj"
|
46
|
-
# print(f"Found single .rasmap file. Using its base name: {base_filename}")
|
47
|
-
# print(f"Full path: {project_file.resolve()}")
|
48
|
-
return project_file.resolve()
|
49
|
-
print("Multiple .prj files and no single .rasmap file. Searching for 'Proj Title=' in .prj files...")
|
50
|
-
for prj_file in prj_files:
|
51
|
-
# print(f"Checking file: {prj_file.name}")
|
52
|
-
with open(prj_file, 'r') as file:
|
53
|
-
if "Proj Title=" in file.read():
|
54
|
-
# print(f"Found 'Proj Title=' in file: {prj_file.name}")
|
55
|
-
# print(f"Full path: {prj_file.resolve()}")
|
56
|
-
return prj_file.resolve()
|
57
|
-
print("No suitable .prj file found after all checks.")
|
58
|
-
return project_file
|
59
|
-
|
60
|
-
@staticmethod
|
61
|
-
def get_project_name(project_path):
|
62
|
-
"""
|
63
|
-
Extract the project name from the given project path.
|
64
|
-
|
65
|
-
Parameters:
|
66
|
-
project_path (Path): Path object representing the project file path
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
str: The project name derived from the file name without extension
|
70
|
-
"""
|
71
|
-
project_name = project_path.stem
|
72
|
-
return project_name
|
73
|
-
|
74
|
-
@staticmethod
|
75
|
-
def get_plan_entries(project_file):
|
76
|
-
"""
|
77
|
-
Parse HEC-RAS project file and create dataframe for plan entries.
|
78
|
-
|
79
|
-
Parameters:
|
80
|
-
project_file (str): Full path to HEC-RAS project file (.prj)
|
81
|
-
|
82
|
-
Returns:
|
83
|
-
pandas DataFrame: DataFrame containing plan entries
|
84
|
-
"""
|
85
|
-
project_path = Path(project_file)
|
86
|
-
project_name = FileOperations.get_project_name(project_path)
|
87
|
-
project_dir = project_path.parent
|
88
|
-
|
89
|
-
with open(project_file, 'r') as f:
|
90
|
-
content = f.read()
|
91
|
-
|
92
|
-
plan_entries = re.findall(r'Plan File=(.*?)(?:\n|$)', content)
|
93
|
-
|
94
|
-
ras_plan_entries = pd.DataFrame({
|
95
|
-
'plan_number': [re.findall(r'\d+', entry)[0] for entry in plan_entries],
|
96
|
-
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in plan_entries],
|
97
|
-
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in plan_entries],
|
98
|
-
'results_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}.hdf") for entry in plan_entries]
|
99
|
-
})
|
100
|
-
|
101
|
-
return ras_plan_entries
|
102
|
-
|
103
|
-
@staticmethod
|
104
|
-
def get_flow_entries(project_file):
|
105
|
-
"""
|
106
|
-
Parse HEC-RAS project file and create dataframe for flow entries.
|
107
|
-
|
108
|
-
Parameters:
|
109
|
-
project_file (str): Full path to HEC-RAS project file (.prj)
|
110
|
-
|
111
|
-
Returns:
|
112
|
-
pandas DataFrame: DataFrame containing flow entries
|
113
|
-
"""
|
114
|
-
project_path = Path(project_file)
|
115
|
-
project_name = FileOperations.get_project_name(project_path)
|
116
|
-
project_dir = project_path.parent
|
117
|
-
with open(project_file, 'r') as f:
|
118
|
-
content = f.read()
|
119
|
-
flow_entries = re.findall(r'Flow File=(.*?)(?:\n|$)', content)
|
120
|
-
ras_flow_entries = pd.DataFrame({
|
121
|
-
'flow_number': [re.findall(r'\d+', entry)[0] for entry in flow_entries],
|
122
|
-
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in flow_entries],
|
123
|
-
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in flow_entries]
|
124
|
-
})
|
125
|
-
return ras_flow_entries
|
126
|
-
|
127
|
-
@staticmethod
|
128
|
-
def get_unsteady_entries(project_file):
|
129
|
-
"""
|
130
|
-
Parse HEC-RAS project file and create dataframe for unsteady entries.
|
131
|
-
|
132
|
-
Parameters:
|
133
|
-
project_file (str): Full path to HEC-RAS project file (.prj)
|
134
|
-
|
135
|
-
Returns:
|
136
|
-
pandas DataFrame: DataFrame containing unsteady entries
|
137
|
-
"""
|
138
|
-
project_path = Path(project_file)
|
139
|
-
project_name = FileOperations.get_project_name(project_path)
|
140
|
-
project_dir = project_path.parent
|
141
|
-
with open(project_file, 'r') as f:
|
142
|
-
content = f.read()
|
143
|
-
unsteady_entries = re.findall(r'Unsteady File=(.*?)(?:\n|$)', content)
|
144
|
-
ras_unsteady_entries = pd.DataFrame({
|
145
|
-
'unsteady_number': [re.findall(r'\d+', entry)[0] for entry in unsteady_entries],
|
146
|
-
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in unsteady_entries],
|
147
|
-
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in unsteady_entries]
|
148
|
-
})
|
149
|
-
return ras_unsteady_entries
|
150
|
-
|
151
|
-
@staticmethod
|
152
|
-
def get_geom_entries(project_file):
|
153
|
-
"""
|
154
|
-
Parse HEC-RAS project file and create dataframe for geometry entries.
|
155
|
-
|
156
|
-
Parameters:
|
157
|
-
project_file (str): Full path to HEC-RAS project file (.prj)
|
158
|
-
|
159
|
-
Returns:
|
160
|
-
pandas DataFrame: DataFrame containing geometry entries
|
161
|
-
"""
|
162
|
-
project_path = Path(project_file)
|
163
|
-
project_name = FileOperations.get_project_name(project_path)
|
164
|
-
project_dir = project_path.parent
|
165
|
-
with open(project_file, 'r') as f:
|
166
|
-
content = f.read()
|
167
|
-
geom_entries = re.findall(r'Geom File=(.*?)(?:\n|$)', content)
|
168
|
-
ras_geom_entries = pd.DataFrame({
|
169
|
-
'geom_number': [re.findall(r'\d+', entry)[0] for entry in geom_entries],
|
170
|
-
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in geom_entries],
|
171
|
-
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in geom_entries]
|
172
|
-
})
|
173
|
-
return ras_geom_entries
|
@@ -1,184 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Operations for handling geometry files in HEC-RAS projects.
|
3
|
-
"""
|
4
|
-
from pathlib import Path
|
5
|
-
from .plan_operations import PlanOperations
|
6
|
-
from .file_operations import FileOperations
|
7
|
-
from .project_config import ProjectConfig
|
8
|
-
import re
|
9
|
-
|
10
|
-
class GeometryOperations:
|
11
|
-
"""
|
12
|
-
A class for operations on HEC-RAS geometry files.
|
13
|
-
"""
|
14
|
-
@staticmethod
|
15
|
-
def clear_geometry_preprocessor_files(plan_file):
|
16
|
-
"""
|
17
|
-
Clear HEC-RAS geometry preprocessor files for a given plan file.
|
18
|
-
|
19
|
-
Parameters:
|
20
|
-
plan_file (str): Full path to the HEC-RAS plan file (.p*)
|
21
|
-
|
22
|
-
Returns:
|
23
|
-
None
|
24
|
-
"""
|
25
|
-
config = ProjectConfig()
|
26
|
-
config.check_initialized()
|
27
|
-
|
28
|
-
plan_path = Path(plan_file)
|
29
|
-
geom_preprocessor_file = plan_path.with_suffix('.c' + plan_path.suffix[2:])
|
30
|
-
if geom_preprocessor_file.exists():
|
31
|
-
print(f"Deleting geometry preprocessor file: {geom_preprocessor_file}")
|
32
|
-
geom_preprocessor_file.unlink()
|
33
|
-
print("File deletion completed successfully.")
|
34
|
-
else:
|
35
|
-
print(f"No geometry preprocessor file found for: {plan_file}")
|
36
|
-
|
37
|
-
@staticmethod
|
38
|
-
def clear_geometry_preprocessor_files_for_all_plans():
|
39
|
-
"""
|
40
|
-
Clear HEC-RAS geometry preprocessor files for all plan files in the project directory.
|
41
|
-
|
42
|
-
Returns:
|
43
|
-
None
|
44
|
-
"""
|
45
|
-
config = ProjectConfig()
|
46
|
-
config.check_initialized()
|
47
|
-
|
48
|
-
for plan_file in config.project_folder.glob("*.p[0-9][0-9]"):
|
49
|
-
GeometryOperations.clear_geometry_preprocessor_files(plan_file)
|
50
|
-
|
51
|
-
@staticmethod
|
52
|
-
def copy_geometry_files(dst_folder, template_geom):
|
53
|
-
"""
|
54
|
-
Copy geometry files from a template to the next available geometry number
|
55
|
-
and update the destination folder accordingly.
|
56
|
-
|
57
|
-
Parameters:
|
58
|
-
dst_folder (str): Destination folder path
|
59
|
-
template_geom (str): Template geometry number (e.g., 'g01')
|
60
|
-
|
61
|
-
Returns:
|
62
|
-
str: New geometry number (e.g., 'g03')
|
63
|
-
"""
|
64
|
-
config = ProjectConfig()
|
65
|
-
config.check_initialized()
|
66
|
-
|
67
|
-
src_folder = config.project_folder
|
68
|
-
dst_folder = Path(dst_folder)
|
69
|
-
dst_folder.mkdir(parents=True, exist_ok=True)
|
70
|
-
|
71
|
-
# Determine the next available geometry number
|
72
|
-
existing_numbers = []
|
73
|
-
geom_file_pattern = re.compile(r'^Geom File=g(\d+)', re.IGNORECASE)
|
74
|
-
|
75
|
-
for plan_file in src_folder.glob("*.p[0-9][0-9]"):
|
76
|
-
with open(plan_file, 'r') as file:
|
77
|
-
lines = file.readlines()
|
78
|
-
for line in lines:
|
79
|
-
match = geom_file_pattern.match(line.strip())
|
80
|
-
if match:
|
81
|
-
existing_numbers.append(int(match.group(1)))
|
82
|
-
|
83
|
-
if existing_numbers:
|
84
|
-
existing_numbers.sort()
|
85
|
-
next_number = 1
|
86
|
-
for num in existing_numbers:
|
87
|
-
if num == next_number:
|
88
|
-
next_number += 1
|
89
|
-
else:
|
90
|
-
break
|
91
|
-
else:
|
92
|
-
next_number = 1
|
93
|
-
|
94
|
-
next_geom_number = f"{next_number:02d}"
|
95
|
-
|
96
|
-
# Copy the template geometry file to the new geometry file
|
97
|
-
template_geom_filename = f"{config.project_name}.g{template_geom}"
|
98
|
-
src_g_file = src_folder / template_geom_filename
|
99
|
-
if src_g_file.exists():
|
100
|
-
new_geom_filename = f"{config.project_name}.g{next_geom_number}"
|
101
|
-
dst_g_file = dst_folder / new_geom_filename
|
102
|
-
dst_g_file.write_bytes(src_g_file.read_bytes())
|
103
|
-
print(f"Copied {src_g_file} to {dst_g_file}")
|
104
|
-
else:
|
105
|
-
raise FileNotFoundError(f"Template geometry file '{src_g_file}' does not exist.")
|
106
|
-
|
107
|
-
# Copy the corresponding .hdf file
|
108
|
-
src_hdf_file = src_folder / f"{template_geom_filename}.hdf"
|
109
|
-
if src_hdf_file.exists():
|
110
|
-
new_hdf_filename = f"{new_geom_filename}.hdf"
|
111
|
-
dst_hdf_file = dst_folder / new_hdf_filename
|
112
|
-
dst_hdf_file.write_bytes(src_hdf_file.read_bytes())
|
113
|
-
print(f"Copied {src_hdf_file} to {dst_hdf_file}")
|
114
|
-
else:
|
115
|
-
raise FileNotFoundError(f"Template geometry .hdf file '{src_hdf_file}' does not exist.")
|
116
|
-
config = ProjectConfig()
|
117
|
-
return f"g{next_geom_number}"
|
118
|
-
|
119
|
-
@staticmethod
|
120
|
-
def rename_geometry_files(old_number, new_number):
|
121
|
-
"""
|
122
|
-
Rename geometry files in the project folder.
|
123
|
-
|
124
|
-
Parameters:
|
125
|
-
old_number (str): Old geometry number (e.g., 'g01')
|
126
|
-
new_number (str): New geometry number (e.g., 'g02')
|
127
|
-
|
128
|
-
Returns:
|
129
|
-
None
|
130
|
-
"""
|
131
|
-
config = ProjectConfig()
|
132
|
-
config.check_initialized()
|
133
|
-
|
134
|
-
folder = config.project_folder
|
135
|
-
old_g_file = next(folder.glob(f"*.{old_number}"), None)
|
136
|
-
if old_g_file:
|
137
|
-
new_g_file = folder / old_g_file.name.replace(old_number, new_number)
|
138
|
-
old_g_file.rename(new_g_file)
|
139
|
-
print(f"Renamed {old_g_file} to {new_g_file}")
|
140
|
-
else:
|
141
|
-
print(f"No .{old_number} file found in {folder}")
|
142
|
-
|
143
|
-
old_hdf_file = next(folder.glob(f"*.{old_number}.hdf"), None)
|
144
|
-
if old_hdf_file:
|
145
|
-
new_hdf_file = folder / old_hdf_file.name.replace(old_number, new_number)
|
146
|
-
old_hdf_file.rename(new_hdf_file)
|
147
|
-
print(f"Renamed {old_hdf_file} to {new_hdf_file}")
|
148
|
-
else:
|
149
|
-
print(f"No .{old_number}.hdf file found in {folder}")
|
150
|
-
|
151
|
-
@staticmethod
|
152
|
-
def update_geometry_reference_in_plan(plan_file, new_geometry_number):
|
153
|
-
"""
|
154
|
-
Update the geometry reference in a plan file.
|
155
|
-
|
156
|
-
Parameters:
|
157
|
-
plan_file (str): Full path to the plan file
|
158
|
-
new_geometry_number (str): New geometry number (e.g., 'g02')
|
159
|
-
|
160
|
-
Returns:
|
161
|
-
None
|
162
|
-
"""
|
163
|
-
config = ProjectConfig()
|
164
|
-
config.check_initialized()
|
165
|
-
|
166
|
-
geometry_full_path = next(config.project_folder.glob(f"*.g{new_geometry_number}"), None)
|
167
|
-
if not geometry_full_path:
|
168
|
-
raise FileNotFoundError(f"Geometry file '*.g{new_geometry_number}' not found in the project folder.")
|
169
|
-
|
170
|
-
plan_path = Path(plan_file)
|
171
|
-
with open(plan_path, 'r') as file:
|
172
|
-
lines = file.readlines()
|
173
|
-
updated = False
|
174
|
-
for index, line in enumerate(lines):
|
175
|
-
if line.startswith("Geom File="):
|
176
|
-
lines[index] = f"Geom File=g{new_geometry_number}\n"
|
177
|
-
updated = True
|
178
|
-
break
|
179
|
-
if updated:
|
180
|
-
with open(plan_path, 'w') as file:
|
181
|
-
file.writelines(lines)
|
182
|
-
print(f"Updated geometry reference in {plan_file} to {new_geometry_number}")
|
183
|
-
else:
|
184
|
-
print(f"No geometry reference found in {plan_file}")
|