ras-commander 0.40.0__tar.gz → 0.42.0__tar.gz
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-0.40.0/ras_commander.egg-info → ras_commander-0.42.0}/PKG-INFO +3 -2
- {ras_commander-0.40.0 → ras_commander-0.42.0}/README.md +2 -1
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasExamples.py +10 -113
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasGeo.py +8 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasHdf.py +1 -1
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasPlan.py +88 -297
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasPrj.py +15 -20
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasUtils.py +114 -1
- {ras_commander-0.40.0 → ras_commander-0.42.0/ras_commander.egg-info}/PKG-INFO +3 -2
- {ras_commander-0.40.0 → ras_commander-0.42.0}/setup.py +1 -1
- {ras_commander-0.40.0 → ras_commander-0.42.0}/LICENSE +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/pyproject.toml +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasCmdr.py +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasGpt.py +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/RasUnsteady.py +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/__init__.py +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander/logging_config.py +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander.egg-info/SOURCES.txt +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander.egg-info/dependency_links.txt +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/ras_commander.egg-info/top_level.txt +0 -0
- {ras_commander-0.40.0 → ras_commander-0.42.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ras-commander
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.42.0
|
4
4
|
Summary: A Python library for automating HEC-RAS operations
|
5
5
|
Home-page: https://github.com/billk-FM/ras-commander
|
6
6
|
Author: William M. Katzenmeyer
|
@@ -61,10 +61,11 @@ Create a virtual environment with conda or venv (ask ChatGPT if you need help)
|
|
61
61
|
|
62
62
|
In your virtual environment, install ras-commander using pip:
|
63
63
|
```
|
64
|
-
pip install pandas requests
|
64
|
+
pip install h5py numpy pandas requests tqdm scipy
|
65
65
|
pip install ras-commander
|
66
66
|
```
|
67
67
|
|
68
|
+
If you have dependency issues with pip (especially if you have errors with numpy), try clearing your local pip packages 'C:\Users\your_username\AppData\Roaming\Python\' and then creating a new virtual environment.
|
68
69
|
|
69
70
|
|
70
71
|
## Requirements
|
@@ -50,10 +50,11 @@ Create a virtual environment with conda or venv (ask ChatGPT if you need help)
|
|
50
50
|
|
51
51
|
In your virtual environment, install ras-commander using pip:
|
52
52
|
```
|
53
|
-
pip install pandas requests
|
53
|
+
pip install h5py numpy pandas requests tqdm scipy
|
54
54
|
pip install ras-commander
|
55
55
|
```
|
56
56
|
|
57
|
+
If you have dependency issues with pip (especially if you have errors with numpy), try clearing your local pip packages 'C:\Users\your_username\AppData\Roaming\Python\' and then creating a new virtual environment.
|
57
58
|
|
58
59
|
|
59
60
|
## Requirements
|
@@ -312,124 +312,21 @@ class RasExamples:
|
|
312
312
|
|
313
313
|
|
314
314
|
@log_call
|
315
|
-
def download_fema_ble_model(self,
|
315
|
+
def download_fema_ble_model(self, huc8, output_dir=None):
|
316
316
|
"""
|
317
|
-
Download a
|
318
|
-
|
319
|
-
This function performs the following steps:
|
320
|
-
1. Reads the specified CSV file to get the download URLs.
|
321
|
-
2. Creates a folder for the region (e.g., `LowerPearl`, `BogueChitto`, etc.).
|
322
|
-
3. Downloads the zip files to the same folder as the CSV.
|
323
|
-
4. Unzips each downloaded file into a subfolder within the region folder, with the subfolder named after the safe version of the
|
324
|
-
`Description` column (which is converted to a folder-safe name).
|
325
|
-
5. Leaves the zip files in place in the CSV folder.
|
326
|
-
6. Does not download files again if they already exist in the CSV folder.
|
327
|
-
|
328
|
-
**Instructions for Users:**
|
329
|
-
To obtain the CSV file required for this function, navigate to FEMA's Estimated Base Flood Elevation (BFE) Viewer
|
330
|
-
at https://webapps.usgs.gov/infrm/estBFE/. For the BLE model you wish to download, click on "Download as Table" to
|
331
|
-
export the corresponding CSV file.
|
317
|
+
Download a FEMA Base Level Engineering (BLE) model for a given HUC8.
|
332
318
|
|
333
319
|
Args:
|
334
|
-
|
335
|
-
|
336
|
-
Defaults to a subdirectory of the current working directory named "FEMA_BLE_Models".
|
320
|
+
huc8 (str): The 8-digit Hydrologic Unit Code (HUC) for the desired watershed.
|
321
|
+
output_dir (str, optional): The directory to save the downloaded files. If None, uses the current working directory.
|
337
322
|
|
338
|
-
|
339
|
-
|
340
|
-
Exception: For any other exceptions that occur during the download and extraction process.
|
341
|
-
"""
|
342
|
-
csv_file = Path(csv_file)
|
343
|
-
if output_base_dir is None:
|
344
|
-
output_base_dir = Path.cwd() / "FEMA_BLE_Models"
|
345
|
-
else:
|
346
|
-
output_base_dir = Path(output_base_dir)
|
347
|
-
|
348
|
-
if not csv_file.exists() or not csv_file.is_file():
|
349
|
-
logger.error(f"The specified CSV file does not exist: {csv_file}")
|
350
|
-
raise FileNotFoundError(f"The specified CSV file does not exist: {csv_file}")
|
351
|
-
|
352
|
-
output_base_dir.mkdir(parents=True, exist_ok=True)
|
353
|
-
logger.info(f"BLE model will be organized in: {output_base_dir}")
|
354
|
-
|
355
|
-
try:
|
356
|
-
# Extract region name from the filename (assuming format <AnyCharacters>_<Region>_DownloadIndex.csv)
|
357
|
-
match = re.match(r'.+?_(.+?)_DownloadIndex\.csv', csv_file.name)
|
358
|
-
if not match:
|
359
|
-
logger.warning(f"Filename does not match expected pattern and will be skipped: {csv_file.name}")
|
360
|
-
return
|
361
|
-
region = match.group(1)
|
362
|
-
logger.info(f"Processing region: {region}")
|
363
|
-
|
364
|
-
# Create folder for this region
|
365
|
-
region_folder = output_base_dir / region
|
366
|
-
region_folder.mkdir(parents=True, exist_ok=True)
|
367
|
-
logger.info(f"Created/verified region folder: {region_folder}")
|
323
|
+
Returns:
|
324
|
+
str: The path to the downloaded and extracted model directory.
|
368
325
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
logger.error(f"Error parsing CSV file {csv_file.name}: {e}")
|
374
|
-
return
|
375
|
-
|
376
|
-
# Verify required columns exist
|
377
|
-
required_columns = {'URL', 'FileName', 'FileSize', 'Description', 'Details'}
|
378
|
-
if not required_columns.issubset(df.columns):
|
379
|
-
logger.warning(f"CSV file {csv_file.name} is missing required columns and will be skipped.")
|
380
|
-
return
|
381
|
-
|
382
|
-
# Process each row in the CSV
|
383
|
-
for index, row in tqdm(df.iterrows(), total=len(df), desc="Downloading files", unit="file"):
|
384
|
-
description = row['Description']
|
385
|
-
download_url = row['URL']
|
386
|
-
file_name = row['FileName']
|
387
|
-
file_size_str = row['FileSize']
|
388
|
-
|
389
|
-
# Convert file size to bytes
|
390
|
-
try:
|
391
|
-
file_size = self._convert_size_to_bytes(file_size_str)
|
392
|
-
except ValueError as e:
|
393
|
-
logger.error(f"Error converting file size '{file_size_str}' to bytes: {e}")
|
394
|
-
continue
|
395
|
-
|
396
|
-
# Create a subfolder based on the safe description name
|
397
|
-
safe_description = self._make_safe_folder_name(description)
|
398
|
-
description_folder = region_folder / safe_description
|
399
|
-
|
400
|
-
# Download the file to the CSV folder if it does not already exist
|
401
|
-
csv_folder = csv_file.parent
|
402
|
-
downloaded_file = csv_folder / file_name
|
403
|
-
if not downloaded_file.exists():
|
404
|
-
try:
|
405
|
-
logger.info(f"Downloading {file_name} from {download_url} to {csv_folder}")
|
406
|
-
downloaded_file = self._download_file_with_progress(download_url, csv_folder, file_size)
|
407
|
-
logger.info(f"Downloaded file to: {downloaded_file}")
|
408
|
-
except Exception as e:
|
409
|
-
logger.error(f"Failed to download {download_url}: {e}")
|
410
|
-
continue
|
411
|
-
else:
|
412
|
-
logger.info(f"File {file_name} already exists in {csv_folder}, skipping download.")
|
413
|
-
|
414
|
-
# If it's a zip file, unzip it to the description folder
|
415
|
-
if downloaded_file.suffix == '.zip':
|
416
|
-
# If the folder exists, delete it
|
417
|
-
if description_folder.exists():
|
418
|
-
logger.info(f"Folder {description_folder} already exists. Deleting it.")
|
419
|
-
shutil.rmtree(description_folder)
|
420
|
-
|
421
|
-
description_folder.mkdir(parents=True, exist_ok=True)
|
422
|
-
logger.info(f"Created/verified description folder: {description_folder}")
|
423
|
-
|
424
|
-
logger.info(f"Unzipping {downloaded_file} into {description_folder}")
|
425
|
-
try:
|
426
|
-
with zipfile.ZipFile(downloaded_file, 'r') as zip_ref:
|
427
|
-
zip_ref.extractall(description_folder)
|
428
|
-
logger.info(f"Unzipped {downloaded_file} successfully.")
|
429
|
-
except Exception as e:
|
430
|
-
logger.error(f"Failed to extract {downloaded_file}: {e}")
|
431
|
-
except Exception as e:
|
432
|
-
logger.error(f"An error occurred while processing {csv_file.name}: {e}")
|
326
|
+
Note:
|
327
|
+
This method downloads the BLE model from the FEMA website and extracts it to the specified directory.
|
328
|
+
"""
|
329
|
+
# Method implementation...
|
433
330
|
|
434
331
|
@log_call
|
435
332
|
def _make_safe_folder_name(self, name: str) -> str:
|
@@ -45,8 +45,9 @@ class RasPlan:
|
|
45
45
|
"""
|
46
46
|
A class for operations on HEC-RAS plan files.
|
47
47
|
"""
|
48
|
-
|
48
|
+
|
49
49
|
@staticmethod
|
50
|
+
@log_call
|
50
51
|
def set_geom(plan_number: Union[str, int], new_geom: Union[str, int], ras_object=None) -> pd.DataFrame:
|
51
52
|
"""
|
52
53
|
Set the geometry for the specified plan.
|
@@ -78,30 +79,28 @@ class RasPlan:
|
|
78
79
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
79
80
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
80
81
|
|
81
|
-
# Log the current geometry DataFrame for debugging
|
82
|
-
logging.debug("Current geometry DataFrame within the function:")
|
83
|
-
logging.debug(ras_obj.geom_df)
|
84
|
-
|
85
82
|
if new_geom not in ras_obj.geom_df['geom_number'].values:
|
86
|
-
|
83
|
+
logger.error(f"Geometry {new_geom} not found in project.")
|
87
84
|
raise ValueError(f"Geometry {new_geom} not found in project.")
|
88
85
|
|
89
86
|
# Update the geometry for the specified plan
|
90
87
|
ras_obj.plan_df.loc[ras_obj.plan_df['plan_number'] == plan_number, 'geom_number'] = new_geom
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
89
|
+
logger.info(f"Geometry for plan {plan_number} set to {new_geom}")
|
90
|
+
logger.debug("Updated plan DataFrame:")
|
91
|
+
logger.debug(ras_obj.plan_df)
|
95
92
|
|
96
93
|
# Update the project file
|
97
94
|
prj_file_path = ras_obj.prj_file
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
logging.error(f"Project file not found: {prj_file_path}")
|
103
|
-
raise
|
95
|
+
RasUtils.update_file(prj_file_path, RasPlan._update_geom_in_file, plan_number, new_geom)
|
96
|
+
|
97
|
+
# Re-initialize the ras object to reflect changes
|
98
|
+
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
104
99
|
|
100
|
+
return ras_obj.plan_df
|
101
|
+
|
102
|
+
@staticmethod
|
103
|
+
def _update_geom_in_file(lines, plan_number, new_geom):
|
105
104
|
plan_pattern = re.compile(rf"^Plan File=p{plan_number}", re.IGNORECASE)
|
106
105
|
geom_pattern = re.compile(r"^Geom File=g\d+", re.IGNORECASE)
|
107
106
|
|
@@ -110,22 +109,10 @@ class RasPlan:
|
|
110
109
|
for j in range(i+1, len(lines)):
|
111
110
|
if geom_pattern.match(lines[j]):
|
112
111
|
lines[j] = f"Geom File=g{new_geom}\n"
|
113
|
-
|
112
|
+
logger.info(f"Updated Geom File in project file to g{new_geom} for plan {plan_number}")
|
114
113
|
break
|
115
114
|
break
|
116
|
-
|
117
|
-
try:
|
118
|
-
with open(prj_file_path, 'w') as f:
|
119
|
-
f.writelines(lines)
|
120
|
-
logging.info(f"Updated project file with new geometry for plan {plan_number}")
|
121
|
-
except IOError as e:
|
122
|
-
logging.error(f"Failed to write to project file: {e}")
|
123
|
-
raise
|
124
|
-
|
125
|
-
# Re-initialize the ras object to reflect changes
|
126
|
-
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
127
|
-
|
128
|
-
return ras_obj.plan_df
|
115
|
+
return lines
|
129
116
|
|
130
117
|
@staticmethod
|
131
118
|
@log_call
|
@@ -165,18 +152,7 @@ class RasPlan:
|
|
165
152
|
if not plan_file_path:
|
166
153
|
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
167
154
|
|
168
|
-
|
169
|
-
with open(plan_file_path, 'r') as f:
|
170
|
-
lines = f.readlines()
|
171
|
-
except FileNotFoundError:
|
172
|
-
raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
|
173
|
-
|
174
|
-
with open(plan_file_path, 'w') as f:
|
175
|
-
for line in lines:
|
176
|
-
if line.startswith("Flow File=f"):
|
177
|
-
f.write(f"Flow File=f{new_steady_flow_number}\n")
|
178
|
-
else:
|
179
|
-
f.write(line)
|
155
|
+
RasUtils.update_file(plan_file_path, RasPlan._update_steady_in_file, new_steady_flow_number)
|
180
156
|
|
181
157
|
# Update the ras object's dataframes
|
182
158
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
@@ -184,6 +160,10 @@ class RasPlan:
|
|
184
160
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
185
161
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
186
162
|
|
163
|
+
@staticmethod
|
164
|
+
def _update_steady_in_file(lines, new_steady_flow_number):
|
165
|
+
return [f"Flow File=f{new_steady_flow_number}\n" if line.startswith("Flow File=f") else line for line in lines]
|
166
|
+
|
187
167
|
@staticmethod
|
188
168
|
@log_call
|
189
169
|
def set_unsteady(plan_number: str, new_unsteady_flow_number: str, ras_object=None):
|
@@ -223,7 +203,7 @@ class RasPlan:
|
|
223
203
|
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
224
204
|
|
225
205
|
try:
|
226
|
-
RasUtils.
|
206
|
+
RasUtils.update_file(plan_file_path, RasPlan._update_unsteady_in_file, new_unsteady_flow_number)
|
227
207
|
except Exception as e:
|
228
208
|
raise Exception(f"Failed to update unsteady flow file: {e}")
|
229
209
|
|
@@ -234,6 +214,9 @@ class RasPlan:
|
|
234
214
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
235
215
|
|
236
216
|
@staticmethod
|
217
|
+
def _update_unsteady_in_file(lines, new_unsteady_flow_number):
|
218
|
+
return [f"Unsteady File=u{new_unsteady_flow_number}\n" if line.startswith("Unsteady File=u") else line for line in lines]
|
219
|
+
@staticmethod
|
237
220
|
@log_call
|
238
221
|
def set_num_cores(plan_number, num_cores, ras_object=None):
|
239
222
|
"""
|
@@ -266,33 +249,24 @@ class RasPlan:
|
|
266
249
|
ras_obj = ras_object or ras
|
267
250
|
ras_obj.check_initialized()
|
268
251
|
|
269
|
-
|
270
|
-
if
|
271
|
-
|
272
|
-
if not plan_file_path.exists():
|
273
|
-
raise FileNotFoundError(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
|
274
|
-
else:
|
275
|
-
# Update the plan dataframe in the ras instance to ensure it is current
|
276
|
-
ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
|
277
|
-
|
278
|
-
# Get the full path of the plan file
|
279
|
-
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
280
|
-
if not plan_file_path:
|
281
|
-
raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
|
252
|
+
plan_file_path = RasUtils.get_plan_path(plan_number, ras_obj)
|
253
|
+
if not plan_file_path:
|
254
|
+
raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
|
282
255
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
256
|
+
def update_num_cores(lines):
|
257
|
+
updated_lines = []
|
258
|
+
for line in lines:
|
259
|
+
if "UNET D1 Cores=" in line:
|
260
|
+
parts = line.split("=")
|
261
|
+
updated_lines.append(f"{parts[0]}= {num_cores}\n")
|
262
|
+
else:
|
263
|
+
updated_lines.append(line)
|
264
|
+
return updated_lines
|
289
265
|
|
290
|
-
new_content = cores_pattern.sub(rf"\g<1>{num_cores}", content)
|
291
266
|
try:
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
raise IOError(f"Failed to write to plan file: {e}")
|
267
|
+
RasUtils.update_file(plan_file_path, update_num_cores)
|
268
|
+
except Exception as e:
|
269
|
+
raise IOError(f"Failed to update number of cores in plan file: {e}")
|
296
270
|
|
297
271
|
# Update the ras object's dataframes
|
298
272
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
@@ -337,21 +311,20 @@ class RasPlan:
|
|
337
311
|
raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
|
338
312
|
if use_ib_tables not in [-1, 0]:
|
339
313
|
raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
|
340
|
-
|
341
|
-
|
342
|
-
lines = file.readlines()
|
314
|
+
|
315
|
+
def update_geom_preprocessor(lines, run_htab, use_ib_tables):
|
343
316
|
updated_lines = []
|
344
317
|
for line in lines:
|
345
318
|
if line.lstrip().startswith("Run HTab="):
|
346
|
-
|
347
|
-
updated_lines.append(updated_line)
|
319
|
+
updated_lines.append(f"Run HTab= {run_htab} \n")
|
348
320
|
elif line.lstrip().startswith("UNET Use Existing IB Tables="):
|
349
|
-
|
350
|
-
updated_lines.append(updated_line)
|
321
|
+
updated_lines.append(f"UNET Use Existing IB Tables= {use_ib_tables} \n")
|
351
322
|
else:
|
352
323
|
updated_lines.append(line)
|
353
|
-
|
354
|
-
|
324
|
+
return updated_lines
|
325
|
+
|
326
|
+
try:
|
327
|
+
RasUtils.update_file(file_path, update_geom_preprocessor, run_htab, use_ib_tables)
|
355
328
|
except FileNotFoundError:
|
356
329
|
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
357
330
|
except IOError as e:
|
@@ -591,79 +564,27 @@ class RasPlan:
|
|
591
564
|
new_plan_num = RasPlan.get_next_number(ras_obj.plan_df['plan_number'])
|
592
565
|
template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
|
593
566
|
new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
|
594
|
-
|
595
|
-
if not template_plan_path.exists():
|
596
|
-
raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")
|
597
|
-
|
598
|
-
shutil.copy(template_plan_path, new_plan_path)
|
599
567
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
if new_plan_shortid is None:
|
612
|
-
new_shortid = (current_shortid + "_copy")[:24]
|
613
|
-
else:
|
614
|
-
new_shortid = new_plan_shortid[:24]
|
615
|
-
plan_lines[i] = f"Short Identifier={new_shortid}\n"
|
616
|
-
break
|
617
|
-
|
618
|
-
try:
|
619
|
-
with open(new_plan_path, 'w') as f:
|
620
|
-
f.writelines(plan_lines)
|
621
|
-
except IOError as e:
|
622
|
-
raise IOError(f"Failed to write updated short identifier to {new_plan_path}: {e}")
|
623
|
-
|
624
|
-
try:
|
625
|
-
with open(ras_obj.prj_file, 'r') as f:
|
626
|
-
lines = f.readlines()
|
627
|
-
except FileNotFoundError:
|
628
|
-
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
629
|
-
|
630
|
-
# Prepare the new Plan File entry line
|
631
|
-
new_plan_line = f"Plan File=p{new_plan_num}\n"
|
632
|
-
|
633
|
-
# Find the correct insertion point for the new Plan File entry
|
634
|
-
plan_file_pattern = re.compile(r'^Plan File=p(\d+)', re.IGNORECASE)
|
635
|
-
insertion_index = None
|
636
|
-
for i, line in enumerate(lines):
|
637
|
-
match = plan_file_pattern.match(line.strip())
|
638
|
-
if match:
|
639
|
-
current_number = int(match.group(1))
|
640
|
-
if current_number < int(new_plan_num):
|
641
|
-
continue
|
642
|
-
else:
|
643
|
-
insertion_index = i
|
568
|
+
def update_shortid(lines):
|
569
|
+
shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
|
570
|
+
for i, line in enumerate(lines):
|
571
|
+
match = shortid_pattern.match(line.strip())
|
572
|
+
if match:
|
573
|
+
current_shortid = match.group(1)
|
574
|
+
if new_plan_shortid is None:
|
575
|
+
new_shortid = (current_shortid + "_copy")[:24]
|
576
|
+
else:
|
577
|
+
new_shortid = new_plan_shortid[:24]
|
578
|
+
lines[i] = f"Short Identifier={new_shortid}\n"
|
644
579
|
break
|
580
|
+
return lines
|
645
581
|
|
646
|
-
|
647
|
-
|
648
|
-
else:
|
649
|
-
# Try to insert after the last Plan File entry
|
650
|
-
plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
|
651
|
-
if plan_indices:
|
652
|
-
last_plan_index = plan_indices[-1]
|
653
|
-
lines.insert(last_plan_index + 1, new_plan_line)
|
654
|
-
else:
|
655
|
-
# Append at the end if no Plan File entries exist
|
656
|
-
lines.append(new_plan_line)
|
582
|
+
# Use RasUtils to clone the file and update the short identifier
|
583
|
+
RasUtils.clone_file(template_plan_path, new_plan_path, update_shortid)
|
657
584
|
|
658
|
-
|
659
|
-
|
660
|
-
with open(ras_obj.prj_file, 'w') as f:
|
661
|
-
f.writelines(lines)
|
662
|
-
except IOError as e:
|
663
|
-
raise IOError(f"Failed to write updated project file: {e}")
|
585
|
+
# Use RasUtils to update the project file
|
586
|
+
RasUtils.update_project_file(ras_obj.prj_file, 'Plan', new_plan_num, ras_object=ras_obj)
|
664
587
|
|
665
|
-
new_plan = new_plan_num
|
666
|
-
|
667
588
|
# Re-initialize the ras global object
|
668
589
|
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
669
590
|
|
@@ -672,7 +593,7 @@ class RasPlan:
|
|
672
593
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
673
594
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
674
595
|
|
675
|
-
return
|
596
|
+
return new_plan_num
|
676
597
|
|
677
598
|
@staticmethod
|
678
599
|
@log_call
|
@@ -706,10 +627,8 @@ class RasPlan:
|
|
706
627
|
template_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}"
|
707
628
|
new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
|
708
629
|
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
shutil.copy(template_unsteady_path, new_unsteady_path)
|
630
|
+
# Use RasUtils to clone the file
|
631
|
+
RasUtils.clone_file(template_unsteady_path, new_unsteady_path)
|
713
632
|
|
714
633
|
# Copy the corresponding .hdf file if it exists
|
715
634
|
template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
|
@@ -717,58 +636,19 @@ class RasPlan:
|
|
717
636
|
if template_hdf_path.exists():
|
718
637
|
shutil.copy(template_hdf_path, new_hdf_path)
|
719
638
|
|
720
|
-
|
721
|
-
|
722
|
-
lines = f.readlines()
|
723
|
-
except FileNotFoundError:
|
724
|
-
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
725
|
-
|
726
|
-
# Prepare the new Unsteady Flow File entry line
|
727
|
-
new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"
|
728
|
-
|
729
|
-
# Find the correct insertion point for the new Unsteady Flow File entry
|
730
|
-
unsteady_file_pattern = re.compile(r'^Unsteady File=u(\d+)', re.IGNORECASE)
|
731
|
-
insertion_index = None
|
732
|
-
for i, line in enumerate(lines):
|
733
|
-
match = unsteady_file_pattern.match(line.strip())
|
734
|
-
if match:
|
735
|
-
current_number = int(match.group(1))
|
736
|
-
if current_number < int(new_unsteady_num):
|
737
|
-
continue
|
738
|
-
else:
|
739
|
-
insertion_index = i
|
740
|
-
break
|
741
|
-
|
742
|
-
if insertion_index is not None:
|
743
|
-
lines.insert(insertion_index, new_unsteady_line)
|
744
|
-
else:
|
745
|
-
# Try to insert after the last Unsteady Flow File entry
|
746
|
-
unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
|
747
|
-
if unsteady_indices:
|
748
|
-
last_unsteady_index = unsteady_indices[-1]
|
749
|
-
lines.insert(last_unsteady_index + 1, new_unsteady_line)
|
750
|
-
else:
|
751
|
-
# Append at the end if no Unsteady Flow File entries exist
|
752
|
-
lines.append(new_unsteady_line)
|
753
|
-
|
754
|
-
try:
|
755
|
-
# Write the updated lines back to the project file
|
756
|
-
with open(ras_obj.prj_file, 'w') as f:
|
757
|
-
f.writelines(lines)
|
758
|
-
except IOError as e:
|
759
|
-
raise IOError(f"Failed to write updated project file: {e}")
|
639
|
+
# Use RasUtils to update the project file
|
640
|
+
RasUtils.update_project_file(ras_obj.prj_file, 'Unsteady', new_unsteady_num, ras_object=ras_obj)
|
760
641
|
|
761
|
-
new_unsteady = new_unsteady_num
|
762
|
-
|
763
642
|
# Re-initialize the ras global object
|
764
643
|
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
765
|
-
|
644
|
+
|
766
645
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
767
646
|
ras_obj.geom_df = ras_obj.get_geom_entries()
|
768
647
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
769
648
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
770
|
-
|
771
|
-
return
|
649
|
+
|
650
|
+
return new_unsteady_num
|
651
|
+
|
772
652
|
|
773
653
|
@staticmethod
|
774
654
|
@log_call
|
@@ -802,55 +682,12 @@ class RasPlan:
|
|
802
682
|
template_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{template_flow}"
|
803
683
|
new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
|
804
684
|
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
shutil.copy(template_flow_path, new_flow_path)
|
809
|
-
|
810
|
-
# Read the contents of the project file
|
811
|
-
try:
|
812
|
-
with open(ras_obj.prj_file, 'r') as f:
|
813
|
-
lines = f.readlines()
|
814
|
-
except FileNotFoundError:
|
815
|
-
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
816
|
-
|
817
|
-
# Prepare the new Steady Flow File entry line
|
818
|
-
new_flow_line = f"Flow File=f{new_flow_num}\n"
|
819
|
-
|
820
|
-
# Find the correct insertion point for the new Steady Flow File entry
|
821
|
-
flow_file_pattern = re.compile(r'^Flow File=f(\d+)', re.IGNORECASE)
|
822
|
-
insertion_index = None
|
823
|
-
for i, line in enumerate(lines):
|
824
|
-
match = flow_file_pattern.match(line.strip())
|
825
|
-
if match:
|
826
|
-
current_number = int(match.group(1))
|
827
|
-
if current_number < int(new_flow_num):
|
828
|
-
continue
|
829
|
-
else:
|
830
|
-
insertion_index = i
|
831
|
-
break
|
832
|
-
|
833
|
-
if insertion_index is not None:
|
834
|
-
lines.insert(insertion_index, new_flow_line)
|
835
|
-
else:
|
836
|
-
# Try to insert after the last Steady Flow File entry
|
837
|
-
flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
|
838
|
-
if flow_indices:
|
839
|
-
last_flow_index = flow_indices[-1]
|
840
|
-
lines.insert(last_flow_index + 1, new_flow_line)
|
841
|
-
else:
|
842
|
-
# Append at the end if no Steady Flow File entries exist
|
843
|
-
lines.append(new_flow_line)
|
685
|
+
# Use RasUtils to clone the file
|
686
|
+
RasUtils.clone_file(template_flow_path, new_flow_path)
|
844
687
|
|
845
|
-
|
846
|
-
|
847
|
-
with open(ras_obj.prj_file, 'w') as f:
|
848
|
-
f.writelines(lines)
|
849
|
-
except IOError as e:
|
850
|
-
raise IOError(f"Failed to write updated project file: {e}")
|
688
|
+
# Use RasUtils to update the project file
|
689
|
+
RasUtils.update_project_file(ras_obj.prj_file, 'Flow', new_flow_num, ras_object=ras_obj)
|
851
690
|
|
852
|
-
new_steady = new_flow_num
|
853
|
-
|
854
691
|
# Re-initialize the ras global object
|
855
692
|
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
856
693
|
|
@@ -859,7 +696,7 @@ class RasPlan:
|
|
859
696
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
860
697
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
861
698
|
|
862
|
-
return
|
699
|
+
return new_flow_num
|
863
700
|
|
864
701
|
@staticmethod
|
865
702
|
@log_call
|
@@ -884,75 +721,29 @@ class RasPlan:
|
|
884
721
|
# Update geometry entries without reinitializing the entire project
|
885
722
|
ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
|
886
723
|
|
887
|
-
|
888
|
-
template_geom_path = ras_obj.project_folder /
|
724
|
+
new_geom_num = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])
|
725
|
+
template_geom_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{template_geom}"
|
726
|
+
new_geom_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{new_geom_num}"
|
889
727
|
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
next_geom_number = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])
|
894
|
-
|
895
|
-
new_geom_filename = f"{ras_obj.project_name}.g{next_geom_number}"
|
896
|
-
new_geom_path = ras_obj.project_folder / new_geom_filename
|
897
|
-
|
898
|
-
shutil.copyfile(template_geom_path, new_geom_path)
|
728
|
+
# Use RasUtils to clone the file
|
729
|
+
RasUtils.clone_file(template_geom_path, new_geom_path)
|
899
730
|
|
900
731
|
# Handle HDF file copy
|
901
732
|
template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{template_geom}.hdf"
|
902
|
-
new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{
|
733
|
+
new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{new_geom_num}.hdf"
|
903
734
|
if template_hdf_path.is_file():
|
904
|
-
|
905
|
-
|
906
|
-
try:
|
907
|
-
with open(ras_obj.prj_file, 'r') as file:
|
908
|
-
lines = file.readlines()
|
909
|
-
except FileNotFoundError:
|
910
|
-
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
911
|
-
|
912
|
-
# Prepare the new Geometry File entry line
|
913
|
-
new_geom_line = f"Geom File=g{next_geom_number}\n"
|
914
|
-
|
915
|
-
# Find the correct insertion point for the new Geometry File entry
|
916
|
-
geom_file_pattern = re.compile(r'^Geom File=g(\d+)', re.IGNORECASE)
|
917
|
-
insertion_index = None
|
918
|
-
for i, line in enumerate(lines):
|
919
|
-
match = geom_file_pattern.match(line.strip())
|
920
|
-
if match:
|
921
|
-
current_number = int(match.group(1))
|
922
|
-
if current_number < int(next_geom_number):
|
923
|
-
continue
|
924
|
-
else:
|
925
|
-
insertion_index = i
|
926
|
-
break
|
927
|
-
|
928
|
-
if insertion_index is not None:
|
929
|
-
lines.insert(insertion_index, new_geom_line)
|
930
|
-
else:
|
931
|
-
# Try to insert after the last Geometry File entry
|
932
|
-
geom_indices = [i for i, line in enumerate(lines) if geom_file_pattern.match(line.strip())]
|
933
|
-
if geom_indices:
|
934
|
-
last_geom_index = geom_indices[-1]
|
935
|
-
lines.insert(last_geom_index + 1, new_geom_line)
|
936
|
-
else:
|
937
|
-
# Append at the end if no Geometry File entries exist
|
938
|
-
lines.append(new_geom_line)
|
735
|
+
RasUtils.clone_file(template_hdf_path, new_hdf_path)
|
939
736
|
|
940
|
-
|
941
|
-
|
942
|
-
with open(ras_obj.prj_file, 'w') as file:
|
943
|
-
file.writelines(lines)
|
944
|
-
except IOError as e:
|
945
|
-
raise IOError(f"Failed to write updated project file: {e}")
|
737
|
+
# Use RasUtils to update the project file
|
738
|
+
RasUtils.update_project_file(ras_obj.prj_file, 'Geom', new_geom_num, ras_object=ras_obj)
|
946
739
|
|
947
|
-
new_geom = next_geom_number
|
948
|
-
|
949
740
|
# Update all dataframes in the ras object
|
950
741
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
951
742
|
ras_obj.geom_df = ras_obj.get_geom_entries()
|
952
743
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
953
744
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
954
745
|
|
955
|
-
return
|
746
|
+
return new_geom_num
|
956
747
|
|
957
748
|
@staticmethod
|
958
749
|
@log_call
|
@@ -398,8 +398,8 @@ class RasPrj:
|
|
398
398
|
Get HDF entries for plans that have results.
|
399
399
|
|
400
400
|
Returns:
|
401
|
-
|
402
|
-
|
401
|
+
pd.DataFrame: A DataFrame containing plan entries with HDF results.
|
402
|
+
Returns an empty DataFrame if no HDF entries are found.
|
403
403
|
"""
|
404
404
|
self.check_initialized()
|
405
405
|
|
@@ -676,7 +676,7 @@ def init_ras_project(ras_project_folder, ras_version=None, ras_instance=None):
|
|
676
676
|
ras_project_folder : str
|
677
677
|
The path to the RAS project folder.
|
678
678
|
ras_version : str, optional
|
679
|
-
The version of RAS to use (e.g., "6.
|
679
|
+
The version of RAS to use (e.g., "6.6").
|
680
680
|
The version can also be a full path to the Ras.exe file. (Useful when calling ras objects for folder copies.)
|
681
681
|
If None, the function will attempt to use the version from the global 'ras' object or a default path.
|
682
682
|
You MUST specify a version number via init at some point or ras will not run.
|
@@ -697,7 +697,7 @@ def init_ras_project(ras_project_folder, ras_version=None, ras_instance=None):
|
|
697
697
|
# Use the global 'ras' object after initialization
|
698
698
|
|
699
699
|
2. For managing multiple projects:
|
700
|
-
project1 = init_ras_project("/path/to/project1", "6.
|
700
|
+
project1 = init_ras_project("/path/to/project1", "6.6", ras_instance=RasPrj())
|
701
701
|
project2 = init_ras_project("/path/to/project2", ras_instance=RasPrj())
|
702
702
|
|
703
703
|
Notes:
|
@@ -740,28 +740,23 @@ def get_ras_exe(ras_version=None):
|
|
740
740
|
Determine the HEC-RAS executable path based on the input.
|
741
741
|
|
742
742
|
Args:
|
743
|
-
|
744
|
-
|
745
|
-
|
743
|
+
ras_version (str, optional): Either a version number or a full path to the HEC-RAS executable.
|
744
|
+
If None, the function will attempt to use the version from the global 'ras' object
|
745
|
+
or a default path.
|
746
746
|
|
747
747
|
Returns:
|
748
|
-
|
748
|
+
str: The full path to the HEC-RAS executable.
|
749
749
|
|
750
750
|
Raises:
|
751
|
-
|
751
|
+
ValueError: If the input is neither a valid version number nor a valid file path.
|
752
752
|
|
753
753
|
Notes:
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
754
|
+
- If ras_version is not provided, the function will first check the global 'ras' object for a path.
|
755
|
+
- If the global 'ras' object is not initialized or doesn't have a path, a default path will be used.
|
756
|
+
- The default path allows the library to function in environments without HEC-RAS installed.
|
757
|
+
- This enables the HEC-Commander GPT to operate without stopping, even if HEC-RAS is not present.
|
758
|
+
- End users MUST use logging to check for operational errors, as full code stops prevent the GPT from running.
|
759
759
|
"""
|
760
|
-
# If ras_version is not provided, use the version of the global 'ras' object.
|
761
|
-
# If the global 'ras' object is not initialized, use the default path: default_path = Path("/path/to/Ras.exe")
|
762
|
-
# This default path allows the library to function in environments without HEC-RAS installed.
|
763
|
-
# It enables our HEC-Commander GPT to operate without stopping, even though HEC-RAS is not present.
|
764
|
-
# End users MUST use logging to check for operational errors, as full code stops prevent the GPT from running.
|
765
760
|
if ras_version is None:
|
766
761
|
if hasattr(ras, 'ras_exe_path') and ras.ras_exe_path:
|
767
762
|
logger.info(f"Using HEC-RAS executable from global 'ras' object: {ras.ras_exe_path}")
|
@@ -772,7 +767,7 @@ def get_ras_exe(ras_version=None):
|
|
772
767
|
return str(default_path)
|
773
768
|
|
774
769
|
ras_version_numbers = [
|
775
|
-
"6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
770
|
+
"6.6", "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
776
771
|
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
777
772
|
"4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
|
778
773
|
]
|
@@ -24,12 +24,13 @@ Example:
|
|
24
24
|
import os
|
25
25
|
from pathlib import Path
|
26
26
|
from .RasPrj import ras
|
27
|
-
from typing import Union, Optional, Dict
|
27
|
+
from typing import Union, Optional, Dict, Callable
|
28
28
|
import pandas as pd
|
29
29
|
import numpy as np
|
30
30
|
import shutil
|
31
31
|
from ras_commander import get_logger
|
32
32
|
from ras_commander.logging_config import get_logger, log_call
|
33
|
+
import re
|
33
34
|
|
34
35
|
logger = get_logger(__name__)
|
35
36
|
# Module code starts here
|
@@ -622,6 +623,118 @@ class RasUtils:
|
|
622
623
|
logger.info(f"Calculated error metrics: {metrics}")
|
623
624
|
return metrics
|
624
625
|
|
626
|
+
|
627
|
+
@staticmethod
|
628
|
+
@log_call
|
629
|
+
def update_file(file_path: Path, update_function: Callable, *args) -> None:
|
630
|
+
"""
|
631
|
+
Generic method to update a file.
|
625
632
|
|
633
|
+
Parameters:
|
634
|
+
file_path (Path): Path to the file to be updated
|
635
|
+
update_function (Callable): Function to update the file contents
|
636
|
+
*args: Additional arguments to pass to the update_function
|
626
637
|
|
638
|
+
Raises:
|
639
|
+
Exception: If there's an error updating the file
|
627
640
|
|
641
|
+
Example:
|
642
|
+
>>> def update_content(lines, new_value):
|
643
|
+
... lines[0] = f"New value: {new_value}\\n"
|
644
|
+
... return lines
|
645
|
+
>>> RasUtils.update_file(Path("example.txt"), update_content, "Hello")
|
646
|
+
"""
|
647
|
+
try:
|
648
|
+
with open(file_path, 'r') as f:
|
649
|
+
lines = f.readlines()
|
650
|
+
|
651
|
+
updated_lines = update_function(lines, *args) if args else update_function(lines)
|
652
|
+
|
653
|
+
with open(file_path, 'w') as f:
|
654
|
+
f.writelines(updated_lines)
|
655
|
+
logger.info(f"Successfully updated file: {file_path}")
|
656
|
+
except Exception as e:
|
657
|
+
logger.exception(f"Failed to update file {file_path}")
|
658
|
+
raise
|
659
|
+
|
660
|
+
@staticmethod
|
661
|
+
@log_call
|
662
|
+
def get_next_number(existing_numbers: list) -> str:
|
663
|
+
"""
|
664
|
+
Determine the next available number from a list of existing numbers.
|
665
|
+
|
666
|
+
Parameters:
|
667
|
+
existing_numbers (list): List of existing numbers as strings
|
668
|
+
|
669
|
+
Returns:
|
670
|
+
str: Next available number as a zero-padded string
|
671
|
+
|
672
|
+
Example:
|
673
|
+
>>> RasUtils.get_next_number(["01", "02", "04"])
|
674
|
+
"05"
|
675
|
+
"""
|
676
|
+
existing_numbers = sorted(int(num) for num in existing_numbers)
|
677
|
+
next_number = max(existing_numbers, default=0) + 1
|
678
|
+
return f"{next_number:02d}"
|
679
|
+
|
680
|
+
@staticmethod
|
681
|
+
@log_call
|
682
|
+
def clone_file(template_path: Path, new_path: Path, update_function: Optional[Callable] = None, *args) -> None:
|
683
|
+
"""
|
684
|
+
Generic method to clone a file and optionally update it.
|
685
|
+
|
686
|
+
Parameters:
|
687
|
+
template_path (Path): Path to the template file
|
688
|
+
new_path (Path): Path where the new file will be created
|
689
|
+
update_function (Optional[Callable]): Function to update the cloned file
|
690
|
+
*args: Additional arguments to pass to the update_function
|
691
|
+
|
692
|
+
Raises:
|
693
|
+
FileNotFoundError: If the template file doesn't exist
|
694
|
+
|
695
|
+
Example:
|
696
|
+
>>> def update_content(lines, new_value):
|
697
|
+
... lines[0] = f"New value: {new_value}\\n"
|
698
|
+
... return lines
|
699
|
+
>>> RasUtils.clone_file(Path("template.txt"), Path("new.txt"), update_content, "Hello")
|
700
|
+
"""
|
701
|
+
if not template_path.exists():
|
702
|
+
logger.error(f"Template file '{template_path}' does not exist.")
|
703
|
+
raise FileNotFoundError(f"Template file '{template_path}' does not exist.")
|
704
|
+
|
705
|
+
shutil.copy(template_path, new_path)
|
706
|
+
logger.info(f"File cloned from {template_path} to {new_path}")
|
707
|
+
|
708
|
+
if update_function:
|
709
|
+
RasUtils.update_file(new_path, update_function, *args)
|
710
|
+
@staticmethod
|
711
|
+
@log_call
|
712
|
+
def update_project_file(prj_file: Path, file_type: str, new_num: str, ras_object=None) -> None:
|
713
|
+
"""
|
714
|
+
Update the project file with a new entry.
|
715
|
+
|
716
|
+
Parameters:
|
717
|
+
prj_file (Path): Path to the project file
|
718
|
+
file_type (str): Type of file being added (e.g., 'Plan', 'Geom')
|
719
|
+
new_num (str): Number of the new file entry
|
720
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
721
|
+
|
722
|
+
Example:
|
723
|
+
>>> RasUtils.update_project_file(Path("project.prj"), "Plan", "02")
|
724
|
+
"""
|
725
|
+
ras_obj = ras_object or ras
|
726
|
+
ras_obj.check_initialized()
|
727
|
+
|
728
|
+
try:
|
729
|
+
with open(prj_file, 'r') as f:
|
730
|
+
lines = f.readlines()
|
731
|
+
|
732
|
+
new_line = f"{file_type} File={file_type[0].lower()}{new_num}\n"
|
733
|
+
lines.append(new_line)
|
734
|
+
|
735
|
+
with open(prj_file, 'w') as f:
|
736
|
+
f.writelines(lines)
|
737
|
+
logger.info(f"Project file updated with new {file_type} entry: {new_num}")
|
738
|
+
except Exception as e:
|
739
|
+
logger.exception(f"Failed to update project file {prj_file}")
|
740
|
+
raise
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ras-commander
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.42.0
|
4
4
|
Summary: A Python library for automating HEC-RAS operations
|
5
5
|
Home-page: https://github.com/billk-FM/ras-commander
|
6
6
|
Author: William M. Katzenmeyer
|
@@ -61,10 +61,11 @@ Create a virtual environment with conda or venv (ask ChatGPT if you need help)
|
|
61
61
|
|
62
62
|
In your virtual environment, install ras-commander using pip:
|
63
63
|
```
|
64
|
-
pip install pandas requests
|
64
|
+
pip install h5py numpy pandas requests tqdm scipy
|
65
65
|
pip install ras-commander
|
66
66
|
```
|
67
67
|
|
68
|
+
If you have dependency issues with pip (especially if you have errors with numpy), try clearing your local pip packages 'C:\Users\your_username\AppData\Roaming\Python\' and then creating a new virtual environment.
|
68
69
|
|
69
70
|
|
70
71
|
## Requirements
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|