ras-commander 0.33.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/RasCmdr.py +449 -0
- ras_commander/RasExamples.py +304 -0
- ras_commander/RasGeo.py +88 -0
- ras_commander/RasPlan.py +1266 -0
- ras_commander/RasPrj.py +400 -0
- ras_commander/RasUnsteady.py +73 -0
- ras_commander/RasUtils.py +310 -0
- ras_commander/__init__.py +40 -0
- ras_commander/_version.py +16 -0
- ras_commander-0.33.0.dist-info/LICENSE +5 -0
- ras_commander-0.33.0.dist-info/METADATA +5 -0
- ras_commander-0.33.0.dist-info/RECORD +14 -0
- ras_commander-0.33.0.dist-info/WHEEL +5 -0
- ras_commander-0.33.0.dist-info/top_level.txt +1 -0
ras_commander/RasPlan.py
ADDED
@@ -0,0 +1,1266 @@
|
|
1
|
+
"""
|
2
|
+
Operations for modifying and updating HEC-RAS plan files.
|
3
|
+
|
4
|
+
"""
|
5
|
+
import re
|
6
|
+
from pathlib import Path
|
7
|
+
import shutil
|
8
|
+
from typing import Union, Optional
|
9
|
+
import pandas as pd
|
10
|
+
from .RasPrj import RasPrj, ras
|
11
|
+
from .RasUtils import RasUtils
|
12
|
+
|
13
|
+
class RasPlan:
|
14
|
+
"""
|
15
|
+
A class for operations on HEC-RAS plan files.
|
16
|
+
"""
|
17
|
+
|
18
|
+
@staticmethod
|
19
|
+
def set_geom(plan_number: Union[str, int], new_geom: Union[str, int], ras_object=None) -> pd.DataFrame:
|
20
|
+
"""
|
21
|
+
Set the geometry for the specified plan.
|
22
|
+
|
23
|
+
Parameters:
|
24
|
+
plan_number (Union[str, int]): The plan number to update.
|
25
|
+
new_geom (Union[str, int]): The new geometry number to set.
|
26
|
+
ras_object: An optional RAS object instance.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
pd.DataFrame: The updated geometry DataFrame.
|
30
|
+
|
31
|
+
Example:
|
32
|
+
updated_geom_df = RasPlan.set_geom('02', '03')
|
33
|
+
|
34
|
+
Note:
|
35
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
36
|
+
"""
|
37
|
+
ras_obj = ras_object or ras
|
38
|
+
ras_obj.check_initialized()
|
39
|
+
|
40
|
+
# Ensure plan_number and new_geom are strings
|
41
|
+
plan_number = str(plan_number).zfill(2)
|
42
|
+
new_geom = str(new_geom).zfill(2)
|
43
|
+
|
44
|
+
# Before doing anything, make sure the plan, geom, flow, and unsteady dataframes are current
|
45
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
46
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
47
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
48
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
49
|
+
|
50
|
+
# List the geom_df for debugging
|
51
|
+
print("Current geometry DataFrame within the function:")
|
52
|
+
print(ras_obj.geom_df)
|
53
|
+
|
54
|
+
if new_geom not in ras_obj.geom_df['geom_number'].values:
|
55
|
+
raise ValueError(f"Geometry {new_geom} not found in project.")
|
56
|
+
|
57
|
+
# Update the geometry for the specified plan
|
58
|
+
ras_obj.plan_df.loc[ras_obj.plan_df['plan_number'] == plan_number, 'geom_number'] = new_geom
|
59
|
+
|
60
|
+
print(f"Geometry for plan {plan_number} set to {new_geom}")
|
61
|
+
print("Updated plan DataFrame:")
|
62
|
+
display(ras_obj.plan_df)
|
63
|
+
|
64
|
+
# Update the project file
|
65
|
+
prj_file_path = ras_obj.prj_file
|
66
|
+
with open(prj_file_path, 'r') as f:
|
67
|
+
lines = f.readlines()
|
68
|
+
|
69
|
+
plan_pattern = re.compile(rf"^Plan File=p{plan_number}", re.IGNORECASE)
|
70
|
+
geom_pattern = re.compile(r"^Geom File=g\d+", re.IGNORECASE)
|
71
|
+
|
72
|
+
for i, line in enumerate(lines):
|
73
|
+
if plan_pattern.match(line):
|
74
|
+
for j in range(i+1, len(lines)):
|
75
|
+
if geom_pattern.match(lines[j]):
|
76
|
+
lines[j] = f"Geom File=g{new_geom}\n"
|
77
|
+
break
|
78
|
+
break
|
79
|
+
|
80
|
+
with open(prj_file_path, 'w') as f:
|
81
|
+
f.writelines(lines)
|
82
|
+
|
83
|
+
print(f"Updated project file with new geometry for plan {plan_number}")
|
84
|
+
|
85
|
+
# Re-initialize the ras object to reflect changes
|
86
|
+
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
87
|
+
|
88
|
+
return ras_obj.plan_df
|
89
|
+
|
90
|
+
@staticmethod
|
91
|
+
def set_steady(plan_number: str, new_steady_flow_number: str, ras_object=None):
|
92
|
+
"""
|
93
|
+
Apply a steady flow file to a plan file.
|
94
|
+
|
95
|
+
Parameters:
|
96
|
+
plan_number (str): Plan number (e.g., '02')
|
97
|
+
new_steady_flow_number (str): Steady flow number to apply (e.g., '01')
|
98
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
None
|
102
|
+
|
103
|
+
Raises:
|
104
|
+
ValueError: If the specified steady flow number is not found in the project file
|
105
|
+
FileNotFoundError: If the specified plan file is not found
|
106
|
+
|
107
|
+
Example:
|
108
|
+
>>> RasPlan.set_steady('02', '01')
|
109
|
+
|
110
|
+
Note:
|
111
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
112
|
+
"""
|
113
|
+
logging.info(f"Setting steady flow file to {new_steady_flow_number} in Plan {plan_number}")
|
114
|
+
ras_obj = ras_object or ras
|
115
|
+
ras_obj.check_initialized()
|
116
|
+
|
117
|
+
# Update the flow dataframe in the ras instance to ensure it is current
|
118
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
119
|
+
|
120
|
+
if new_steady_flow_number not in ras_obj.flow_df['flow_number'].values:
|
121
|
+
raise ValueError(f"Steady flow number {new_steady_flow_number} not found in project file.")
|
122
|
+
|
123
|
+
# Resolve the full path of the plan file
|
124
|
+
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
125
|
+
if not plan_file_path:
|
126
|
+
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
127
|
+
|
128
|
+
with open(plan_file_path, 'r') as f:
|
129
|
+
lines = f.readlines()
|
130
|
+
with open(plan_file_path, 'w') as f:
|
131
|
+
for line in lines:
|
132
|
+
if line.startswith("Flow File=f"):
|
133
|
+
f.write(f"Flow File=f{new_steady_flow_number}\n")
|
134
|
+
logging.info(f"Updated Flow File in {plan_file_path} to f{new_steady_flow_number}")
|
135
|
+
else:
|
136
|
+
f.write(line)
|
137
|
+
|
138
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
139
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
140
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
141
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
142
|
+
|
143
|
+
@staticmethod
|
144
|
+
def set_unsteady(plan_number: str, new_unsteady_flow_number: str, ras_object=None):
|
145
|
+
"""
|
146
|
+
Apply an unsteady flow file to a plan file.
|
147
|
+
|
148
|
+
Parameters:
|
149
|
+
plan_number (str): Plan number (e.g., '04')
|
150
|
+
new_unsteady_flow_number (str): Unsteady flow number to apply (e.g., '01')
|
151
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
None
|
155
|
+
|
156
|
+
Raises:
|
157
|
+
ValueError: If the specified unsteady number is not found in the project file
|
158
|
+
FileNotFoundError: If the specified plan file is not found
|
159
|
+
|
160
|
+
Example:
|
161
|
+
>>> RasPlan.set_unsteady('04', '01')
|
162
|
+
|
163
|
+
Note:
|
164
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
165
|
+
"""
|
166
|
+
print(f"Setting unsteady flow file from {new_unsteady_flow_number} to {plan_number}")
|
167
|
+
|
168
|
+
ras_obj = ras_object or ras
|
169
|
+
ras_obj.check_initialized()
|
170
|
+
|
171
|
+
# Update the unsteady dataframe in the ras instance to ensure it is current
|
172
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
173
|
+
|
174
|
+
if new_unsteady_flow_number not in ras_obj.unsteady_df['unsteady_number'].values:
|
175
|
+
raise ValueError(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
|
176
|
+
|
177
|
+
# Get the full path of the plan file
|
178
|
+
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
179
|
+
if not plan_file_path:
|
180
|
+
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
181
|
+
|
182
|
+
|
183
|
+
# DEV NOTE: THIS WORKS HERE, BUT IN OTHER FUNCTIONS WE DO THIS MANUALLY.
|
184
|
+
# UPDATE OTHER FUNCTIONS TO USE RasUtils.update_plan_file INSTEAD OF REPLICATING THIS CODE.
|
185
|
+
|
186
|
+
RasUtils.update_plan_file(plan_file_path, 'Unsteady', new_unsteady_flow_number)
|
187
|
+
print(f"Updated unsteady flow file in {plan_file_path} to u{new_unsteady_flow_number}")
|
188
|
+
|
189
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
190
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
191
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
192
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
193
|
+
|
194
|
+
@staticmethod
|
195
|
+
def set_num_cores(plan_number, num_cores, ras_object=None):
|
196
|
+
"""
|
197
|
+
Update the maximum number of cores to use in the HEC-RAS plan file.
|
198
|
+
|
199
|
+
Parameters:
|
200
|
+
plan_number (str): Plan number (e.g., '02') or full path to the plan file
|
201
|
+
num_cores (int): Maximum number of cores to use
|
202
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
None
|
206
|
+
|
207
|
+
Notes on setting num_cores in HEC-RAS:
|
208
|
+
The recommended setting for num_cores is 2 (most efficient) to 8 (most performant)
|
209
|
+
More details in the HEC-Commander Repository Blog "Benchmarking is All You Need"
|
210
|
+
https://github.com/billk-FM/HEC-Commander/blob/main/Blog/7._Benchmarking_Is_All_You_Need.md
|
211
|
+
|
212
|
+
Microsoft Windows has a maximum of 64 cores that can be allocated to a single Ras.exe process.
|
213
|
+
|
214
|
+
Example:
|
215
|
+
>>> # Using plan number
|
216
|
+
>>> RasPlan.set_num_cores('02', 4)
|
217
|
+
>>> # Using full path to plan file
|
218
|
+
>>> RasPlan.set_num_cores('/path/to/project.p02', 4)
|
219
|
+
|
220
|
+
Note:
|
221
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
222
|
+
"""
|
223
|
+
print(f"Setting num_cores to {num_cores} in Plan {plan_number}")
|
224
|
+
|
225
|
+
ras_obj = ras_object or ras
|
226
|
+
ras_obj.check_initialized()
|
227
|
+
|
228
|
+
# Determine if plan_number is a path or a plan number
|
229
|
+
if Path(plan_number).is_file():
|
230
|
+
plan_file_path = Path(plan_number)
|
231
|
+
if not plan_file_path.exists():
|
232
|
+
raise FileNotFoundError(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
|
233
|
+
else:
|
234
|
+
# Update the plan dataframe in the ras instance to ensure it is current
|
235
|
+
ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
|
236
|
+
|
237
|
+
# Get the full path of the plan file
|
238
|
+
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
239
|
+
if not plan_file_path:
|
240
|
+
raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
|
241
|
+
|
242
|
+
cores_pattern = re.compile(r"(UNET D1 Cores= )\d+")
|
243
|
+
with open(plan_file_path, 'r') as file:
|
244
|
+
content = file.read()
|
245
|
+
new_content = cores_pattern.sub(rf"\g<1>{num_cores}", content)
|
246
|
+
with open(plan_file_path, 'w') as file:
|
247
|
+
file.write(new_content)
|
248
|
+
print(f"Updated {plan_file_path} with {num_cores} cores.")
|
249
|
+
|
250
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
251
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
252
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
253
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
254
|
+
|
255
|
+
|
256
|
+
@staticmethod
|
257
|
+
def set_geom_preprocessor(file_path, run_htab, use_ib_tables, ras_object=None):
|
258
|
+
"""
|
259
|
+
Update the simulation plan file to modify the `Run HTab` and `UNET Use Existing IB Tables` settings.
|
260
|
+
|
261
|
+
Parameters:
|
262
|
+
file_path (str): Path to the simulation plan file (.p06 or similar) that you want to modify.
|
263
|
+
run_htab (int): Value for the `Run HTab` setting:
|
264
|
+
- `0` : Do not run the geometry preprocessor, use existing geometry tables.
|
265
|
+
- `-1` : Run the geometry preprocessor, forcing a recomputation of the geometry tables.
|
266
|
+
use_ib_tables (int): Value for the `UNET Use Existing IB Tables` setting:
|
267
|
+
- `0` : Use existing interpolation/boundary (IB) tables without recomputing them.
|
268
|
+
- `-1` : Do not use existing IB tables, force a recomputation.
|
269
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
270
|
+
|
271
|
+
Returns:
|
272
|
+
None
|
273
|
+
|
274
|
+
Raises:
|
275
|
+
ValueError: If `run_htab` or `use_ib_tables` are not integers or not within the accepted values (`0` or `-1`).
|
276
|
+
FileNotFoundError: If the specified file does not exist.
|
277
|
+
IOError: If there is an error reading or writing the file.
|
278
|
+
|
279
|
+
Example:
|
280
|
+
>>> RasPlan.set_geom_preprocessor('/path/to/project.p06', run_htab=-1, use_ib_tables=0)
|
281
|
+
|
282
|
+
Note:
|
283
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
284
|
+
"""
|
285
|
+
ras_obj = ras_object or ras
|
286
|
+
ras_obj.check_initialized()
|
287
|
+
|
288
|
+
if run_htab not in [-1, 0]:
|
289
|
+
raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
|
290
|
+
if use_ib_tables not in [-1, 0]:
|
291
|
+
raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
|
292
|
+
try:
|
293
|
+
print(f"Reading the file: {file_path}")
|
294
|
+
with open(file_path, 'r') as file:
|
295
|
+
lines = file.readlines()
|
296
|
+
print("Updating the file with new settings...")
|
297
|
+
updated_lines = []
|
298
|
+
for line in lines:
|
299
|
+
if line.lstrip().startswith("Run HTab="):
|
300
|
+
updated_line = f"Run HTab= {run_htab} \n"
|
301
|
+
updated_lines.append(updated_line)
|
302
|
+
print(f"Updated 'Run HTab' to {run_htab}")
|
303
|
+
elif line.lstrip().startswith("UNET Use Existing IB Tables="):
|
304
|
+
updated_line = f"UNET Use Existing IB Tables= {use_ib_tables} \n"
|
305
|
+
updated_lines.append(updated_line)
|
306
|
+
print(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables}")
|
307
|
+
else:
|
308
|
+
updated_lines.append(line)
|
309
|
+
print(f"Writing the updated settings back to the file: {file_path}")
|
310
|
+
with open(file_path, 'w') as file:
|
311
|
+
file.writelines(updated_lines)
|
312
|
+
print("File update completed successfully.")
|
313
|
+
except FileNotFoundError:
|
314
|
+
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
315
|
+
except IOError as e:
|
316
|
+
raise IOError(f"An error occurred while reading or writing the file: {e}")
|
317
|
+
|
318
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
319
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
320
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
321
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
322
|
+
|
323
|
+
# Get Functions to retrieve file paths for plan, flow, unsteady, geometry and results files
|
324
|
+
|
325
|
+
@staticmethod
|
326
|
+
def get_results_path(plan_number: str, ras_object=None) -> Optional[str]:
|
327
|
+
"""
|
328
|
+
Retrieve the results file path for a given HEC-RAS plan number.
|
329
|
+
|
330
|
+
Args:
|
331
|
+
plan_number (str): The HEC-RAS plan number for which to find the results path.
|
332
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
Optional[str]: The full path to the results file if found and the file exists, or None if not found.
|
336
|
+
|
337
|
+
Raises:
|
338
|
+
RuntimeError: If the project is not initialized.
|
339
|
+
|
340
|
+
Example:
|
341
|
+
>>> ras_plan = RasPlan()
|
342
|
+
>>> results_path = ras_plan.get_results_path('01')
|
343
|
+
>>> if results_path:
|
344
|
+
... print(f"Results file found at: {results_path}")
|
345
|
+
... else:
|
346
|
+
... print("Results file not found.")
|
347
|
+
"""
|
348
|
+
ras_obj = ras_object or ras
|
349
|
+
ras_obj.check_initialized()
|
350
|
+
|
351
|
+
# Update the plan dataframe in the ras instance to ensure it is current
|
352
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
353
|
+
|
354
|
+
# Ensure plan_number is a string
|
355
|
+
plan_number = str(plan_number)
|
356
|
+
|
357
|
+
# Ensure plan_number is formatted as '01', '02', etc.
|
358
|
+
plan_number = plan_number.zfill(2)
|
359
|
+
|
360
|
+
# print the ras_obj.plan_df dataframe
|
361
|
+
print("Plan DataFrame:")
|
362
|
+
display(ras_obj.plan_df)
|
363
|
+
|
364
|
+
plan_entry = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == plan_number]
|
365
|
+
if not plan_entry.empty:
|
366
|
+
results_path = plan_entry['HDF_Results_Path'].iloc[0]
|
367
|
+
if results_path:
|
368
|
+
print(f"Results file for Plan number {plan_number} exists at: {results_path}")
|
369
|
+
return results_path
|
370
|
+
else:
|
371
|
+
print(f"Results file for Plan number {plan_number} does not exist.")
|
372
|
+
return None
|
373
|
+
else:
|
374
|
+
print(f"Plan number {plan_number} not found in the entries.")
|
375
|
+
return None
|
376
|
+
|
377
|
+
@staticmethod
|
378
|
+
def get_plan_path(plan_number: str, ras_object=None) -> Optional[str]:
|
379
|
+
"""
|
380
|
+
Return the full path for a given plan number.
|
381
|
+
|
382
|
+
This method ensures that the latest plan entries are included by refreshing
|
383
|
+
the plan dataframe before searching for the requested plan number.
|
384
|
+
|
385
|
+
Args:
|
386
|
+
plan_number (str): The plan number to search for.
|
387
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
388
|
+
|
389
|
+
Returns:
|
390
|
+
Optional[str]: The full path of the plan file if found, None otherwise.
|
391
|
+
|
392
|
+
Raises:
|
393
|
+
RuntimeError: If the project is not initialized.
|
394
|
+
|
395
|
+
Example:
|
396
|
+
>>> ras_plan = RasPlan()
|
397
|
+
>>> plan_path = ras_plan.get_plan_path('01')
|
398
|
+
>>> if plan_path:
|
399
|
+
... print(f"Plan file found at: {plan_path}")
|
400
|
+
... else:
|
401
|
+
... print("Plan file not found.")
|
402
|
+
"""
|
403
|
+
ras_obj = ras_object or ras
|
404
|
+
ras_obj.check_initialized()
|
405
|
+
|
406
|
+
project_name = ras_obj.project_name
|
407
|
+
|
408
|
+
# Use updated plan dataframe
|
409
|
+
plan_df = ras_obj.get_plan_entries()
|
410
|
+
|
411
|
+
plan_path = plan_df[plan_df['plan_number'] == plan_number]
|
412
|
+
|
413
|
+
if not plan_path.empty:
|
414
|
+
full_path = plan_path['full_path'].iloc[0]
|
415
|
+
return full_path
|
416
|
+
else:
|
417
|
+
print(f"Plan number {plan_number} not found in the updated plan entries.")
|
418
|
+
return None
|
419
|
+
|
420
|
+
@staticmethod
|
421
|
+
def get_flow_path(flow_number: str, ras_object=None) -> Optional[str]:
|
422
|
+
"""
|
423
|
+
Return the full path for a given flow number.
|
424
|
+
|
425
|
+
Args:
|
426
|
+
flow_number (str): The flow number to search for.
|
427
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
428
|
+
|
429
|
+
Returns:
|
430
|
+
Optional[str]: The full path of the flow file if found, None otherwise.
|
431
|
+
|
432
|
+
Raises:
|
433
|
+
RuntimeError: If the project is not initialized.
|
434
|
+
|
435
|
+
Example:
|
436
|
+
>>> ras_plan = RasPlan()
|
437
|
+
>>> flow_path = ras_plan.get_flow_path('01')
|
438
|
+
>>> if flow_path:
|
439
|
+
... print(f"Flow file found at: {flow_path}")
|
440
|
+
... else:
|
441
|
+
... print("Flow file not found.")
|
442
|
+
"""
|
443
|
+
ras_obj = ras_object or ras
|
444
|
+
ras_obj.check_initialized()
|
445
|
+
|
446
|
+
# Use updated flow dataframe
|
447
|
+
ras_obj.flow_df = ras_obj.get_prj_entries('Flow')
|
448
|
+
|
449
|
+
flow_path = ras_obj.flow_df[ras_obj.flow_df['flow_number'] == flow_number]
|
450
|
+
if not flow_path.empty:
|
451
|
+
full_path = flow_path['full_path'].iloc[0]
|
452
|
+
return full_path
|
453
|
+
else:
|
454
|
+
print(f"Flow number {flow_number} not found in the updated flow entries.")
|
455
|
+
return None
|
456
|
+
|
457
|
+
@staticmethod
|
458
|
+
def get_unsteady_path(unsteady_number: str, ras_object=None) -> Optional[str]:
|
459
|
+
"""
|
460
|
+
Return the full path for a given unsteady number.
|
461
|
+
|
462
|
+
Args:
|
463
|
+
unsteady_number (str): The unsteady number to search for.
|
464
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
465
|
+
|
466
|
+
Returns:
|
467
|
+
Optional[str]: The full path of the unsteady file if found, None otherwise.
|
468
|
+
|
469
|
+
Raises:
|
470
|
+
RuntimeError: If the project is not initialized.
|
471
|
+
|
472
|
+
Example:
|
473
|
+
>>> ras_plan = RasPlan()
|
474
|
+
>>> unsteady_path = ras_plan.get_unsteady_path('01')
|
475
|
+
>>> if unsteady_path:
|
476
|
+
... print(f"Unsteady file found at: {unsteady_path}")
|
477
|
+
... else:
|
478
|
+
... print("Unsteady file not found.")
|
479
|
+
"""
|
480
|
+
ras_obj = ras_object or ras
|
481
|
+
ras_obj.check_initialized()
|
482
|
+
|
483
|
+
# Use updated unsteady dataframe
|
484
|
+
ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')
|
485
|
+
|
486
|
+
unsteady_path = ras_obj.unsteady_df[ras_obj.unsteady_df['unsteady_number'] == unsteady_number]
|
487
|
+
if not unsteady_path.empty:
|
488
|
+
full_path = unsteady_path['full_path'].iloc[0]
|
489
|
+
return full_path
|
490
|
+
else:
|
491
|
+
print(f"Unsteady number {unsteady_number} not found in the updated unsteady entries.")
|
492
|
+
return None
|
493
|
+
|
494
|
+
@staticmethod
|
495
|
+
def get_geom_path(geom_number: str, ras_object=None) -> Optional[str]:
|
496
|
+
"""
|
497
|
+
Return the full path for a given geometry number.
|
498
|
+
|
499
|
+
Args:
|
500
|
+
geom_number (str): The geometry number to search for.
|
501
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
502
|
+
|
503
|
+
Returns:
|
504
|
+
Optional[str]: The full path of the geometry file if found, None otherwise.
|
505
|
+
|
506
|
+
Raises:
|
507
|
+
RuntimeError: If the project is not initialized.
|
508
|
+
|
509
|
+
Example:
|
510
|
+
>>> ras_plan = RasPlan()
|
511
|
+
>>> geom_path = ras_plan.get_geom_path('01')
|
512
|
+
>>> if geom_path:
|
513
|
+
... print(f"Geometry file found at: {geom_path}")
|
514
|
+
... else:
|
515
|
+
... print("Geometry file not found.")
|
516
|
+
"""
|
517
|
+
ras_obj = ras_object or ras
|
518
|
+
ras_obj.check_initialized()
|
519
|
+
|
520
|
+
# Use updated geom dataframe
|
521
|
+
ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
|
522
|
+
|
523
|
+
geom_path = ras_obj.geom_df[ras_obj.geom_df['geom_number'] == geom_number]
|
524
|
+
if not geom_path.empty:
|
525
|
+
full_path = geom_path['full_path'].iloc[0]
|
526
|
+
return full_path
|
527
|
+
else:
|
528
|
+
print(f"Geometry number {geom_number} not found in the updated geometry entries.")
|
529
|
+
return None
|
530
|
+
# Clone Functions to copy unsteady, flow, and geometry files from templates
|
531
|
+
|
532
|
+
@staticmethod
|
533
|
+
def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
|
534
|
+
"""
|
535
|
+
Create a new plan file based on a template and update the project file.
|
536
|
+
|
537
|
+
Parameters:
|
538
|
+
template_plan (str): Plan number to use as template (e.g., '01')
|
539
|
+
new_plan_shortid (str, optional): New short identifier for the plan file
|
540
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
541
|
+
|
542
|
+
Returns:
|
543
|
+
str: New plan number
|
544
|
+
|
545
|
+
Example:
|
546
|
+
>>> ras_plan = RasPlan()
|
547
|
+
>>> new_plan_number = ras_plan.clone_plan('01', new_plan_shortid='New Plan')
|
548
|
+
>>> print(f"New plan created with number: {new_plan_number}")
|
549
|
+
|
550
|
+
Note:
|
551
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
552
|
+
"""
|
553
|
+
ras_obj = ras_object or ras
|
554
|
+
ras_obj.check_initialized()
|
555
|
+
|
556
|
+
# Update plan entries without reinitializing the entire project
|
557
|
+
ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
|
558
|
+
|
559
|
+
new_plan_num = RasPlan.get_next_number(ras_obj.plan_df['plan_number'])
|
560
|
+
template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
|
561
|
+
new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
|
562
|
+
|
563
|
+
if not template_plan_path.exists():
|
564
|
+
raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")
|
565
|
+
|
566
|
+
shutil.copy(template_plan_path, new_plan_path)
|
567
|
+
print(f"Copied {template_plan_path} to {new_plan_path}")
|
568
|
+
|
569
|
+
with open(new_plan_path, 'r') as f:
|
570
|
+
plan_lines = f.readlines()
|
571
|
+
|
572
|
+
shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
|
573
|
+
for i, line in enumerate(plan_lines):
|
574
|
+
match = shortid_pattern.match(line.strip())
|
575
|
+
if match:
|
576
|
+
current_shortid = match.group(1)
|
577
|
+
if new_plan_shortid is None:
|
578
|
+
new_shortid = (current_shortid + "_copy")[:24]
|
579
|
+
else:
|
580
|
+
new_shortid = new_plan_shortid[:24]
|
581
|
+
plan_lines[i] = f"Short Identifier={new_shortid}\n"
|
582
|
+
break
|
583
|
+
|
584
|
+
with open(new_plan_path, 'w') as f:
|
585
|
+
f.writelines(plan_lines)
|
586
|
+
|
587
|
+
print(f"Updated short identifier in {new_plan_path}")
|
588
|
+
|
589
|
+
with open(ras_obj.prj_file, 'r') as f:
|
590
|
+
lines = f.readlines()
|
591
|
+
|
592
|
+
# Prepare the new Plan File entry line
|
593
|
+
new_plan_line = f"Plan File=p{new_plan_num}\n"
|
594
|
+
|
595
|
+
# Find the correct insertion point for the new Plan File entry
|
596
|
+
plan_file_pattern = re.compile(r'^Plan File=p(\d+)', re.IGNORECASE)
|
597
|
+
insertion_index = None
|
598
|
+
for i, line in enumerate(lines):
|
599
|
+
match = plan_file_pattern.match(line.strip())
|
600
|
+
if match:
|
601
|
+
current_number = int(match.group(1))
|
602
|
+
if current_number < int(new_plan_num):
|
603
|
+
continue
|
604
|
+
else:
|
605
|
+
insertion_index = i
|
606
|
+
break
|
607
|
+
|
608
|
+
if insertion_index is not None:
|
609
|
+
lines.insert(insertion_index, new_plan_line)
|
610
|
+
else:
|
611
|
+
# Try to insert after the last Plan File entry
|
612
|
+
plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
|
613
|
+
if plan_indices:
|
614
|
+
last_plan_index = plan_indices[-1]
|
615
|
+
lines.insert(last_plan_index + 1, new_plan_line)
|
616
|
+
else:
|
617
|
+
# Append at the end if no Plan File entries exist
|
618
|
+
lines.append(new_plan_line)
|
619
|
+
|
620
|
+
# Write the updated lines back to the project file
|
621
|
+
with open(ras_obj.prj_file, 'w') as f:
|
622
|
+
f.writelines(lines)
|
623
|
+
|
624
|
+
print(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
|
625
|
+
new_plan = new_plan_num
|
626
|
+
|
627
|
+
# Store the project folder path
|
628
|
+
project_folder = ras_obj.project_folder
|
629
|
+
|
630
|
+
# Re-initialize the ras global object
|
631
|
+
ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
|
632
|
+
|
633
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
634
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
635
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
636
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
637
|
+
|
638
|
+
return new_plan
|
639
|
+
|
640
|
+
|
641
|
+
@staticmethod
|
642
|
+
def clone_unsteady(template_unsteady, ras_object=None):
|
643
|
+
"""
|
644
|
+
Copy unsteady flow files from a template, find the next unsteady number,
|
645
|
+
and update the project file accordingly.
|
646
|
+
|
647
|
+
Parameters:
|
648
|
+
template_unsteady (str): Unsteady flow number to be used as a template (e.g., '01')
|
649
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
650
|
+
|
651
|
+
Returns:
|
652
|
+
str: New unsteady flow number (e.g., '03')
|
653
|
+
|
654
|
+
Example:
|
655
|
+
>>> ras_plan = RasPlan()
|
656
|
+
>>> new_unsteady_num = ras_plan.clone_unsteady('01')
|
657
|
+
>>> print(f"New unsteady flow file created: u{new_unsteady_num}")
|
658
|
+
|
659
|
+
Note:
|
660
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
661
|
+
"""
|
662
|
+
ras_obj = ras_object or ras
|
663
|
+
ras_obj.check_initialized()
|
664
|
+
|
665
|
+
# Update unsteady entries without reinitializing the entire project
|
666
|
+
ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')
|
667
|
+
|
668
|
+
new_unsteady_num = RasPlan.get_next_number(ras_obj.unsteady_df['unsteady_number'])
|
669
|
+
template_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}"
|
670
|
+
new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
|
671
|
+
|
672
|
+
if not template_unsteady_path.exists():
|
673
|
+
raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")
|
674
|
+
|
675
|
+
shutil.copy(template_unsteady_path, new_unsteady_path)
|
676
|
+
print(f"Copied {template_unsteady_path} to {new_unsteady_path}")
|
677
|
+
|
678
|
+
# Copy the corresponding .hdf file if it exists
|
679
|
+
template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
|
680
|
+
new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
|
681
|
+
if template_hdf_path.exists():
|
682
|
+
shutil.copy(template_hdf_path, new_hdf_path)
|
683
|
+
print(f"Copied {template_hdf_path} to {new_hdf_path}")
|
684
|
+
else:
|
685
|
+
print(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")
|
686
|
+
|
687
|
+
with open(ras_obj.prj_file, 'r') as f:
|
688
|
+
lines = f.readlines()
|
689
|
+
|
690
|
+
# Prepare the new Unsteady Flow File entry line
|
691
|
+
new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"
|
692
|
+
|
693
|
+
# Find the correct insertion point for the new Unsteady Flow File entry
|
694
|
+
unsteady_file_pattern = re.compile(r'^Unsteady File=u(\d+)', re.IGNORECASE)
|
695
|
+
insertion_index = None
|
696
|
+
for i, line in enumerate(lines):
|
697
|
+
match = unsteady_file_pattern.match(line.strip())
|
698
|
+
if match:
|
699
|
+
current_number = int(match.group(1))
|
700
|
+
if current_number < int(new_unsteady_num):
|
701
|
+
continue
|
702
|
+
else:
|
703
|
+
insertion_index = i
|
704
|
+
break
|
705
|
+
|
706
|
+
if insertion_index is not None:
|
707
|
+
lines.insert(insertion_index, new_unsteady_line)
|
708
|
+
else:
|
709
|
+
# Try to insert after the last Unsteady Flow File entry
|
710
|
+
unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
|
711
|
+
if unsteady_indices:
|
712
|
+
last_unsteady_index = unsteady_indices[-1]
|
713
|
+
lines.insert(last_unsteady_index + 1, new_unsteady_line)
|
714
|
+
else:
|
715
|
+
# Append at the end if no Unsteady Flow File entries exist
|
716
|
+
lines.append(new_unsteady_line)
|
717
|
+
|
718
|
+
# Write the updated lines back to the project file
|
719
|
+
with open(ras_obj.prj_file, 'w') as f:
|
720
|
+
f.writelines(lines)
|
721
|
+
|
722
|
+
print(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
|
723
|
+
new_unsteady = new_unsteady_num
|
724
|
+
|
725
|
+
# Store the project folder path
|
726
|
+
project_folder = ras_obj.project_folder
|
727
|
+
hecras_path = ras_obj.ras_exe_path
|
728
|
+
|
729
|
+
# Re-initialize the ras global object
|
730
|
+
ras_obj.initialize(project_folder, hecras_path)
|
731
|
+
|
732
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
733
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
734
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
735
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
736
|
+
|
737
|
+
return new_unsteady
|
738
|
+
|
739
|
+
@staticmethod
|
740
|
+
def clone_steady(template_flow, ras_object=None):
|
741
|
+
"""
|
742
|
+
Copy steady flow files from a template, find the next flow number,
|
743
|
+
and update the project file accordingly.
|
744
|
+
|
745
|
+
Parameters:
|
746
|
+
template_flow (str): Flow number to be used as a template (e.g., '01')
|
747
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
748
|
+
|
749
|
+
Returns:
|
750
|
+
str: New flow number (e.g., '03')
|
751
|
+
|
752
|
+
Example:
|
753
|
+
>>> ras_plan = RasPlan()
|
754
|
+
>>> new_flow_num = ras_plan.clone_steady('01')
|
755
|
+
>>> print(f"New steady flow file created: f{new_flow_num}")
|
756
|
+
|
757
|
+
Note:
|
758
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
759
|
+
"""
|
760
|
+
ras_obj = ras_object or ras
|
761
|
+
ras_obj.check_initialized()
|
762
|
+
|
763
|
+
# Update flow entries without reinitializing the entire project
|
764
|
+
ras_obj.flow_df = ras_obj.get_prj_entries('Flow')
|
765
|
+
|
766
|
+
new_flow_num = RasPlan.get_next_number(ras_obj.flow_df['flow_number'])
|
767
|
+
template_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{template_flow}"
|
768
|
+
new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
|
769
|
+
|
770
|
+
if not template_flow_path.exists():
|
771
|
+
raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")
|
772
|
+
|
773
|
+
shutil.copy(template_flow_path, new_flow_path)
|
774
|
+
print(f"Copied {template_flow_path} to {new_flow_path}")
|
775
|
+
|
776
|
+
# Read the contents of the project file
|
777
|
+
with open(ras_obj.prj_file, 'r') as f:
|
778
|
+
lines = f.readlines()
|
779
|
+
|
780
|
+
# Prepare the new Steady Flow File entry line
|
781
|
+
new_flow_line = f"Flow File=f{new_flow_num}\n"
|
782
|
+
|
783
|
+
# Find the correct insertion point for the new Steady Flow File entry
|
784
|
+
flow_file_pattern = re.compile(r'^Flow File=f(\d+)', re.IGNORECASE)
|
785
|
+
insertion_index = None
|
786
|
+
for i, line in enumerate(lines):
|
787
|
+
match = flow_file_pattern.match(line.strip())
|
788
|
+
if match:
|
789
|
+
current_number = int(match.group(1))
|
790
|
+
if current_number < int(new_flow_num):
|
791
|
+
continue
|
792
|
+
else:
|
793
|
+
insertion_index = i
|
794
|
+
break
|
795
|
+
|
796
|
+
if insertion_index is not None:
|
797
|
+
lines.insert(insertion_index, new_flow_line)
|
798
|
+
else:
|
799
|
+
# Try to insert after the last Steady Flow File entry
|
800
|
+
flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
|
801
|
+
if flow_indices:
|
802
|
+
last_flow_index = flow_indices[-1]
|
803
|
+
lines.insert(last_flow_index + 1, new_flow_line)
|
804
|
+
else:
|
805
|
+
# Append at the end if no Steady Flow File entries exist
|
806
|
+
lines.append(new_flow_line)
|
807
|
+
|
808
|
+
# Write the updated lines back to the project file
|
809
|
+
with open(ras_obj.prj_file, 'w') as f:
|
810
|
+
f.writelines(lines)
|
811
|
+
|
812
|
+
print(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
|
813
|
+
new_steady = new_flow_num
|
814
|
+
|
815
|
+
# Store the project folder path
|
816
|
+
project_folder = ras_obj.project_folder
|
817
|
+
|
818
|
+
# Re-initialize the ras global object
|
819
|
+
ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
|
820
|
+
|
821
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
822
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
823
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
824
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
825
|
+
|
826
|
+
return new_steady
|
827
|
+
|
828
|
+
|
829
|
+
@staticmethod
|
830
|
+
def clone_geom(template_geom, ras_object=None):
|
831
|
+
"""
|
832
|
+
Copy geometry files from a template, find the next geometry number,
|
833
|
+
and update the project file accordingly.
|
834
|
+
|
835
|
+
Parameters:
|
836
|
+
template_geom (str): Geometry number to be used as a template (e.g., '01')
|
837
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
838
|
+
|
839
|
+
Returns:
|
840
|
+
str: New geometry number (e.g., '03')
|
841
|
+
|
842
|
+
Note:
|
843
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
844
|
+
"""
|
845
|
+
ras_obj = ras_object or ras
|
846
|
+
ras_obj.check_initialized()
|
847
|
+
|
848
|
+
# Update geometry entries without reinitializing the entire project
|
849
|
+
ras_obj.geom_df = ras_obj.get_prj_entries('Geom') # Call the correct function to get updated geometry entries
|
850
|
+
print(f"Updated geometry entries:\n{ras_obj.geom_df}")
|
851
|
+
|
852
|
+
# Clone Functions to copy unsteady, flow, and geometry files from templates
|
853
|
+
|
854
|
+
@staticmethod
|
855
|
+
def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
|
856
|
+
"""
|
857
|
+
Create a new plan file based on a template and update the project file.
|
858
|
+
|
859
|
+
Parameters:
|
860
|
+
template_plan (str): Plan number to use as template (e.g., '01')
|
861
|
+
new_plan_shortid (str, optional): New short identifier for the plan file
|
862
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
863
|
+
|
864
|
+
Returns:
|
865
|
+
str: New plan number
|
866
|
+
|
867
|
+
Revision Notes:
|
868
|
+
- Updated to insert new plan entry in the correct position
|
869
|
+
- Improved error handling and logging
|
870
|
+
- Updated to use get_prj_entries('Plan') for the latest entries
|
871
|
+
- Added print statements for progress tracking
|
872
|
+
|
873
|
+
Example:
|
874
|
+
>>> ras_plan = RasPlan()
|
875
|
+
>>> new_plan_number = ras_plan.clone_plan('01', new_plan_shortid='New Plan')
|
876
|
+
>>> print(f"New plan created with number: {new_plan_number}")
|
877
|
+
"""
|
878
|
+
ras_obj = ras_object or ras
|
879
|
+
ras_obj.check_initialized()
|
880
|
+
|
881
|
+
# Update plan entries without reinitializing the entire project
|
882
|
+
ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
|
883
|
+
|
884
|
+
new_plan_num = RasPlan.get_next_number(ras_obj.plan_df['plan_number'])
|
885
|
+
template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
|
886
|
+
new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
|
887
|
+
|
888
|
+
if not template_plan_path.exists():
|
889
|
+
raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")
|
890
|
+
|
891
|
+
shutil.copy(template_plan_path, new_plan_path)
|
892
|
+
print(f"Copied {template_plan_path} to {new_plan_path}")
|
893
|
+
|
894
|
+
with open(new_plan_path, 'r') as f:
|
895
|
+
plan_lines = f.readlines()
|
896
|
+
|
897
|
+
shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
|
898
|
+
for i, line in enumerate(plan_lines):
|
899
|
+
match = shortid_pattern.match(line.strip())
|
900
|
+
if match:
|
901
|
+
current_shortid = match.group(1)
|
902
|
+
if new_plan_shortid is None:
|
903
|
+
new_shortid = (current_shortid + "_copy")[:24]
|
904
|
+
else:
|
905
|
+
new_shortid = new_plan_shortid[:24]
|
906
|
+
plan_lines[i] = f"Short Identifier={new_shortid}\n"
|
907
|
+
break
|
908
|
+
|
909
|
+
with open(new_plan_path, 'w') as f:
|
910
|
+
f.writelines(plan_lines)
|
911
|
+
|
912
|
+
print(f"Updated short identifier in {new_plan_path}")
|
913
|
+
|
914
|
+
with open(ras_obj.prj_file, 'r') as f:
|
915
|
+
lines = f.readlines()
|
916
|
+
|
917
|
+
# Prepare the new Plan File entry line
|
918
|
+
new_plan_line = f"Plan File=p{new_plan_num}\n"
|
919
|
+
|
920
|
+
# Find the correct insertion point for the new Plan File entry
|
921
|
+
plan_file_pattern = re.compile(r'^Plan File=p(\d+)', re.IGNORECASE)
|
922
|
+
insertion_index = None
|
923
|
+
for i, line in enumerate(lines):
|
924
|
+
match = plan_file_pattern.match(line.strip())
|
925
|
+
if match:
|
926
|
+
current_number = int(match.group(1))
|
927
|
+
if current_number < int(new_plan_num):
|
928
|
+
continue
|
929
|
+
else:
|
930
|
+
insertion_index = i
|
931
|
+
break
|
932
|
+
|
933
|
+
if insertion_index is not None:
|
934
|
+
lines.insert(insertion_index, new_plan_line)
|
935
|
+
else:
|
936
|
+
# Try to insert after the last Plan File entry
|
937
|
+
plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
|
938
|
+
if plan_indices:
|
939
|
+
last_plan_index = plan_indices[-1]
|
940
|
+
lines.insert(last_plan_index + 1, new_plan_line)
|
941
|
+
else:
|
942
|
+
# Append at the end if no Plan File entries exist
|
943
|
+
lines.append(new_plan_line)
|
944
|
+
|
945
|
+
# Write the updated lines back to the project file
|
946
|
+
with open(ras_obj.prj_file, 'w') as f:
|
947
|
+
f.writelines(lines)
|
948
|
+
|
949
|
+
print(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
|
950
|
+
new_plan = new_plan_num
|
951
|
+
|
952
|
+
# Store the project folder path
|
953
|
+
project_folder = ras_obj.project_folder
|
954
|
+
|
955
|
+
# Re-initialize the ras global object
|
956
|
+
ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
|
957
|
+
return new_plan
|
958
|
+
|
959
|
+
|
960
|
+
@staticmethod
|
961
|
+
def clone_unsteady(template_unsteady, ras_object=None):
|
962
|
+
"""
|
963
|
+
Copy unsteady flow files from a template, find the next unsteady number,
|
964
|
+
and update the project file accordingly.
|
965
|
+
|
966
|
+
Parameters:
|
967
|
+
template_unsteady (str): Unsteady flow number to be used as a template (e.g., '01')
|
968
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
969
|
+
|
970
|
+
Returns:
|
971
|
+
str: New unsteady flow number (e.g., '03')
|
972
|
+
|
973
|
+
Example:
|
974
|
+
>>> ras_plan = RasPlan()
|
975
|
+
>>> new_unsteady_num = ras_plan.clone_unsteady('01')
|
976
|
+
>>> print(f"New unsteady flow file created: u{new_unsteady_num}")
|
977
|
+
|
978
|
+
Revision Notes:
|
979
|
+
- Updated to insert new unsteady flow entry in the correct position
|
980
|
+
- Improved error handling and logging
|
981
|
+
- Removed dst_folder parameter as it's not needed (using project folder)
|
982
|
+
- Added handling for corresponding .hdf files
|
983
|
+
- Updated to use get_prj_entries('Unsteady') for the latest entries
|
984
|
+
"""
|
985
|
+
ras_obj = ras_object or ras
|
986
|
+
ras_obj.check_initialized()
|
987
|
+
|
988
|
+
# Update unsteady entries without reinitializing the entire project
|
989
|
+
ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')
|
990
|
+
|
991
|
+
new_unsteady_num = RasPlan.get_next_number(ras_obj.unsteady_df['unsteady_number'])
|
992
|
+
template_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}"
|
993
|
+
new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
|
994
|
+
|
995
|
+
if not template_unsteady_path.exists():
|
996
|
+
raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")
|
997
|
+
|
998
|
+
shutil.copy(template_unsteady_path, new_unsteady_path)
|
999
|
+
print(f"Copied {template_unsteady_path} to {new_unsteady_path}")
|
1000
|
+
|
1001
|
+
# Copy the corresponding .hdf file if it exists
|
1002
|
+
template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
|
1003
|
+
new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
|
1004
|
+
if template_hdf_path.exists():
|
1005
|
+
shutil.copy(template_hdf_path, new_hdf_path)
|
1006
|
+
print(f"Copied {template_hdf_path} to {new_hdf_path}")
|
1007
|
+
else:
|
1008
|
+
print(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")
|
1009
|
+
|
1010
|
+
with open(ras_obj.prj_file, 'r') as f:
|
1011
|
+
lines = f.readlines()
|
1012
|
+
|
1013
|
+
# Prepare the new Unsteady Flow File entry line
|
1014
|
+
new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"
|
1015
|
+
|
1016
|
+
# Find the correct insertion point for the new Unsteady Flow File entry
|
1017
|
+
unsteady_file_pattern = re.compile(r'^Unsteady File=u(\d+)', re.IGNORECASE)
|
1018
|
+
insertion_index = None
|
1019
|
+
for i, line in enumerate(lines):
|
1020
|
+
match = unsteady_file_pattern.match(line.strip())
|
1021
|
+
if match:
|
1022
|
+
current_number = int(match.group(1))
|
1023
|
+
if current_number < int(new_unsteady_num):
|
1024
|
+
continue
|
1025
|
+
else:
|
1026
|
+
insertion_index = i
|
1027
|
+
break
|
1028
|
+
|
1029
|
+
if insertion_index is not None:
|
1030
|
+
lines.insert(insertion_index, new_unsteady_line)
|
1031
|
+
else:
|
1032
|
+
# Try to insert after the last Unsteady Flow File entry
|
1033
|
+
unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
|
1034
|
+
if unsteady_indices:
|
1035
|
+
last_unsteady_index = unsteady_indices[-1]
|
1036
|
+
lines.insert(last_unsteady_index + 1, new_unsteady_line)
|
1037
|
+
else:
|
1038
|
+
# Append at the end if no Unsteady Flow File entries exist
|
1039
|
+
lines.append(new_unsteady_line)
|
1040
|
+
|
1041
|
+
# Write the updated lines back to the project file
|
1042
|
+
with open(ras_obj.prj_file, 'w') as f:
|
1043
|
+
f.writelines(lines)
|
1044
|
+
|
1045
|
+
print(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
|
1046
|
+
new_unsteady = new_unsteady_num
|
1047
|
+
|
1048
|
+
# Store the project folder path
|
1049
|
+
project_folder = ras_obj.project_folder
|
1050
|
+
hecras_path = ras_obj.ras_exe_path
|
1051
|
+
|
1052
|
+
# Re-initialize the ras global object
|
1053
|
+
ras_obj.initialize(project_folder, hecras_path)
|
1054
|
+
|
1055
|
+
return new_unsteady
|
1056
|
+
|
1057
|
+
@staticmethod
|
1058
|
+
def clone_steady(template_flow, ras_object=None):
|
1059
|
+
"""
|
1060
|
+
Copy steady flow files from a template, find the next flow number,
|
1061
|
+
and update the project file accordingly.
|
1062
|
+
|
1063
|
+
Parameters:
|
1064
|
+
template_flow (str): Flow number to be used as a template (e.g., '01')
|
1065
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
1066
|
+
|
1067
|
+
Returns:
|
1068
|
+
str: New flow number (e.g., '03')
|
1069
|
+
|
1070
|
+
Example:
|
1071
|
+
>>> ras_plan = RasPlan()
|
1072
|
+
>>> new_flow_num = ras_plan.clone_steady('01')
|
1073
|
+
>>> print(f"New steady flow file created: f{new_flow_num}")
|
1074
|
+
|
1075
|
+
Revision Notes:
|
1076
|
+
- Updated to insert new steady flow entry in the correct position
|
1077
|
+
- Improved error handling and logging
|
1078
|
+
- Added handling for corresponding .hdf files
|
1079
|
+
- Updated to use get_prj_entries('Flow') for the latest entries
|
1080
|
+
"""
|
1081
|
+
ras_obj = ras_object or ras
|
1082
|
+
ras_obj.check_initialized()
|
1083
|
+
|
1084
|
+
# Update flow entries without reinitializing the entire project
|
1085
|
+
ras_obj.flow_df = ras_obj.get_prj_entries('Flow')
|
1086
|
+
|
1087
|
+
new_flow_num = RasPlan.get_next_number(ras_obj.flow_df['flow_number'])
|
1088
|
+
template_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{template_flow}"
|
1089
|
+
new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
|
1090
|
+
|
1091
|
+
if not template_flow_path.exists():
|
1092
|
+
raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")
|
1093
|
+
|
1094
|
+
shutil.copy(template_flow_path, new_flow_path)
|
1095
|
+
print(f"Copied {template_flow_path} to {new_flow_path}")
|
1096
|
+
|
1097
|
+
# Read the contents of the project file
|
1098
|
+
with open(ras_obj.prj_file, 'r') as f:
|
1099
|
+
lines = f.readlines()
|
1100
|
+
|
1101
|
+
# Prepare the new Steady Flow File entry line
|
1102
|
+
new_flow_line = f"Flow File=f{new_flow_num}\n"
|
1103
|
+
|
1104
|
+
# Find the correct insertion point for the new Steady Flow File entry
|
1105
|
+
flow_file_pattern = re.compile(r'^Flow File=f(\d+)', re.IGNORECASE)
|
1106
|
+
insertion_index = None
|
1107
|
+
for i, line in enumerate(lines):
|
1108
|
+
match = flow_file_pattern.match(line.strip())
|
1109
|
+
if match:
|
1110
|
+
current_number = int(match.group(1))
|
1111
|
+
if current_number < int(new_flow_num):
|
1112
|
+
continue
|
1113
|
+
else:
|
1114
|
+
insertion_index = i
|
1115
|
+
break
|
1116
|
+
|
1117
|
+
if insertion_index is not None:
|
1118
|
+
lines.insert(insertion_index, new_flow_line)
|
1119
|
+
else:
|
1120
|
+
# Try to insert after the last Steady Flow File entry
|
1121
|
+
flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
|
1122
|
+
if flow_indices:
|
1123
|
+
last_flow_index = flow_indices[-1]
|
1124
|
+
lines.insert(last_flow_index + 1, new_flow_line)
|
1125
|
+
else:
|
1126
|
+
# Append at the end if no Steady Flow File entries exist
|
1127
|
+
lines.append(new_flow_line)
|
1128
|
+
|
1129
|
+
# Write the updated lines back to the project file
|
1130
|
+
with open(ras_obj.prj_file, 'w') as f:
|
1131
|
+
f.writelines(lines)
|
1132
|
+
|
1133
|
+
print(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
|
1134
|
+
new_steady = new_flow_num
|
1135
|
+
|
1136
|
+
# Store the project folder path
|
1137
|
+
project_folder = ras_obj.project_folder
|
1138
|
+
|
1139
|
+
# Re-initialize the ras global object
|
1140
|
+
ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
|
1141
|
+
|
1142
|
+
return new_steady
|
1143
|
+
|
1144
|
+
@staticmethod
|
1145
|
+
def clone_geom(template_geom, ras_object=None):
|
1146
|
+
"""
|
1147
|
+
Copy geometry files from a template, find the next geometry number,
|
1148
|
+
and update the project file accordingly.
|
1149
|
+
|
1150
|
+
Parameters:
|
1151
|
+
template_geom (str): Geometry number to be used as a template (e.g., '01')
|
1152
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
1153
|
+
|
1154
|
+
Returns:
|
1155
|
+
str: New geometry number (e.g., '03')
|
1156
|
+
|
1157
|
+
Note:
|
1158
|
+
This function updates the ras object's dataframes after modifying the project structure.
|
1159
|
+
"""
|
1160
|
+
ras_obj = ras_object or ras
|
1161
|
+
ras_obj.check_initialized()
|
1162
|
+
|
1163
|
+
# Update geometry entries without reinitializing the entire project
|
1164
|
+
ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
|
1165
|
+
|
1166
|
+
template_geom_filename = f"{ras_obj.project_name}.g{template_geom}"
|
1167
|
+
template_geom_path = ras_obj.project_folder / template_geom_filename
|
1168
|
+
|
1169
|
+
if not template_geom_path.is_file():
|
1170
|
+
raise FileNotFoundError(f"Template geometry file '{template_geom_path}' does not exist.")
|
1171
|
+
|
1172
|
+
next_geom_number = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])
|
1173
|
+
|
1174
|
+
new_geom_filename = f"{ras_obj.project_name}.g{next_geom_number}"
|
1175
|
+
new_geom_path = ras_obj.project_folder / new_geom_filename
|
1176
|
+
|
1177
|
+
shutil.copyfile(template_geom_path, new_geom_path)
|
1178
|
+
print(f"Copied '{template_geom_path}' to '{new_geom_path}'.")
|
1179
|
+
|
1180
|
+
# Handle HDF file copy
|
1181
|
+
template_hdf_path = template_geom_path.with_suffix('.g' + template_geom + '.hdf')
|
1182
|
+
new_hdf_path = new_geom_path.with_suffix('.g' + next_geom_number + '.hdf')
|
1183
|
+
if template_hdf_path.is_file():
|
1184
|
+
shutil.copyfile(template_hdf_path, new_hdf_path)
|
1185
|
+
print(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
|
1186
|
+
else:
|
1187
|
+
print(f"Warning: Template geometry HDF file '{template_hdf_path}' does not exist. This is common, and not critical. Continuing without it.")
|
1188
|
+
|
1189
|
+
with open(ras_obj.prj_file, 'r') as file:
|
1190
|
+
lines = file.readlines()
|
1191
|
+
|
1192
|
+
# Prepare the new Geometry File entry line
|
1193
|
+
new_geom_line = f"Geom File=g{next_geom_number}\n"
|
1194
|
+
|
1195
|
+
# Find the correct insertion point for the new Geometry File entry
|
1196
|
+
geom_file_pattern = re.compile(r'^Geom File=g(\d+)', re.IGNORECASE)
|
1197
|
+
insertion_index = None
|
1198
|
+
for i, line in enumerate(lines):
|
1199
|
+
match = geom_file_pattern.match(line.strip())
|
1200
|
+
if match:
|
1201
|
+
current_number = int(match.group(1))
|
1202
|
+
if current_number < int(next_geom_number):
|
1203
|
+
continue
|
1204
|
+
else:
|
1205
|
+
insertion_index = i
|
1206
|
+
break
|
1207
|
+
|
1208
|
+
if insertion_index is not None:
|
1209
|
+
lines.insert(insertion_index, new_geom_line)
|
1210
|
+
else:
|
1211
|
+
# Try to insert after the last Geometry File entry
|
1212
|
+
geom_indices = [i for i, line in enumerate(lines) if geom_file_pattern.match(line.strip())]
|
1213
|
+
if geom_indices:
|
1214
|
+
last_geom_index = geom_indices[-1]
|
1215
|
+
lines.insert(last_geom_index + 1, new_geom_line)
|
1216
|
+
else:
|
1217
|
+
# Append at the end if no Geometry File entries exist
|
1218
|
+
lines.append(new_geom_line)
|
1219
|
+
|
1220
|
+
# Write the updated lines back to the project file
|
1221
|
+
with open(ras_obj.prj_file, 'w') as file:
|
1222
|
+
file.writelines(lines)
|
1223
|
+
|
1224
|
+
print(f"Updated {ras_obj.prj_file} with new geometry file g{next_geom_number}")
|
1225
|
+
new_geom = next_geom_number
|
1226
|
+
|
1227
|
+
# Update all dataframes in the ras object
|
1228
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
1229
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
1230
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
1231
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
1232
|
+
|
1233
|
+
print(f"Updated geometry entries:\n{ras_obj.geom_df}")
|
1234
|
+
|
1235
|
+
return new_geom
|
1236
|
+
|
1237
|
+
|
1238
|
+
|
1239
|
+
|
1240
|
+
@staticmethod
|
1241
|
+
def get_next_number(existing_numbers):
|
1242
|
+
"""
|
1243
|
+
Determine the next available number from a list of existing numbers.
|
1244
|
+
|
1245
|
+
Parameters:
|
1246
|
+
existing_numbers (list): List of existing numbers as strings
|
1247
|
+
|
1248
|
+
Returns:
|
1249
|
+
str: Next available number as a zero-padded string
|
1250
|
+
|
1251
|
+
Example:
|
1252
|
+
>>> existing_numbers = ['01', '02', '04']
|
1253
|
+
>>> RasPlan.get_next_number(existing_numbers)
|
1254
|
+
'03'
|
1255
|
+
>>> existing_numbers = ['01', '02', '03']
|
1256
|
+
>>> RasPlan.get_next_number(existing_numbers)
|
1257
|
+
'04'
|
1258
|
+
"""
|
1259
|
+
existing_numbers = sorted(int(num) for num in existing_numbers)
|
1260
|
+
next_number = 1
|
1261
|
+
for num in existing_numbers:
|
1262
|
+
if num == next_number:
|
1263
|
+
next_number += 1
|
1264
|
+
else:
|
1265
|
+
break
|
1266
|
+
return f"{next_number:02d}"
|