ras-commander 0.33.0__py3-none-any.whl → 0.35.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/RasPlan.py CHANGED
@@ -1,8 +1,5 @@
1
- """
2
- Operations for modifying and updating HEC-RAS plan files.
3
-
4
- """
5
1
  import re
2
+ import logging
6
3
  from pathlib import Path
7
4
  import shutil
8
5
  from typing import Union, Optional
@@ -10,11 +7,27 @@ import pandas as pd
10
7
  from .RasPrj import RasPrj, ras
11
8
  from .RasUtils import RasUtils
12
9
 
10
+
11
+ from pathlib import Path
12
+ from typing import Union, Any
13
+ import logging
14
+ import re
15
+
16
+
17
+ # Configure logging
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(asctime)s - %(levelname)s - %(message)s',
21
+ handlers=[
22
+ logging.StreamHandler()
23
+ ]
24
+ )
25
+
13
26
  class RasPlan:
14
27
  """
15
28
  A class for operations on HEC-RAS plan files.
16
29
  """
17
-
30
+
18
31
  @staticmethod
19
32
  def set_geom(plan_number: Union[str, int], new_geom: Union[str, int], ras_object=None) -> pd.DataFrame:
20
33
  """
@@ -47,24 +60,29 @@ class RasPlan:
47
60
  ras_obj.flow_df = ras_obj.get_flow_entries()
48
61
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
49
62
 
50
- # List the geom_df for debugging
51
- print("Current geometry DataFrame within the function:")
52
- print(ras_obj.geom_df)
53
-
63
+ # Log the current geometry DataFrame for debugging
64
+ logging.debug("Current geometry DataFrame within the function:")
65
+ logging.debug(ras_obj.geom_df)
66
+
54
67
  if new_geom not in ras_obj.geom_df['geom_number'].values:
68
+ logging.error(f"Geometry {new_geom} not found in project.")
55
69
  raise ValueError(f"Geometry {new_geom} not found in project.")
56
70
 
57
71
  # Update the geometry for the specified plan
58
72
  ras_obj.plan_df.loc[ras_obj.plan_df['plan_number'] == plan_number, 'geom_number'] = new_geom
59
73
 
60
- print(f"Geometry for plan {plan_number} set to {new_geom}")
61
- print("Updated plan DataFrame:")
62
- display(ras_obj.plan_df)
74
+ logging.info(f"Geometry for plan {plan_number} set to {new_geom}")
75
+ logging.debug("Updated plan DataFrame:")
76
+ logging.debug(ras_obj.plan_df)
63
77
 
64
78
  # Update the project file
65
79
  prj_file_path = ras_obj.prj_file
66
- with open(prj_file_path, 'r') as f:
67
- lines = f.readlines()
80
+ try:
81
+ with open(prj_file_path, 'r') as f:
82
+ lines = f.readlines()
83
+ except FileNotFoundError:
84
+ logging.error(f"Project file not found: {prj_file_path}")
85
+ raise
68
86
 
69
87
  plan_pattern = re.compile(rf"^Plan File=p{plan_number}", re.IGNORECASE)
70
88
  geom_pattern = re.compile(r"^Geom File=g\d+", re.IGNORECASE)
@@ -74,13 +92,17 @@ class RasPlan:
74
92
  for j in range(i+1, len(lines)):
75
93
  if geom_pattern.match(lines[j]):
76
94
  lines[j] = f"Geom File=g{new_geom}\n"
95
+ logging.info(f"Updated Geom File in project file to g{new_geom} for plan {plan_number}")
77
96
  break
78
97
  break
79
98
 
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}")
99
+ try:
100
+ with open(prj_file_path, 'w') as f:
101
+ f.writelines(lines)
102
+ logging.info(f"Updated project file with new geometry for plan {plan_number}")
103
+ except IOError as e:
104
+ logging.error(f"Failed to write to project file: {e}")
105
+ raise
84
106
 
85
107
  # Re-initialize the ras object to reflect changes
86
108
  ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
@@ -118,15 +140,22 @@ class RasPlan:
118
140
  ras_obj.flow_df = ras_obj.get_flow_entries()
119
141
 
120
142
  if new_steady_flow_number not in ras_obj.flow_df['flow_number'].values:
143
+ logging.error(f"Steady flow number {new_steady_flow_number} not found in project file.")
121
144
  raise ValueError(f"Steady flow number {new_steady_flow_number} not found in project file.")
122
145
 
123
146
  # Resolve the full path of the plan file
124
147
  plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
125
148
  if not plan_file_path:
149
+ logging.error(f"Plan file not found: {plan_number}")
126
150
  raise FileNotFoundError(f"Plan file not found: {plan_number}")
127
151
 
128
- with open(plan_file_path, 'r') as f:
129
- lines = f.readlines()
152
+ try:
153
+ with open(plan_file_path, 'r') as f:
154
+ lines = f.readlines()
155
+ except FileNotFoundError:
156
+ logging.error(f"Plan file not found: {plan_file_path}")
157
+ raise
158
+
130
159
  with open(plan_file_path, 'w') as f:
131
160
  for line in lines:
132
161
  if line.startswith("Flow File=f"):
@@ -135,6 +164,7 @@ class RasPlan:
135
164
  else:
136
165
  f.write(line)
137
166
 
167
+ # Update the ras object's dataframes
138
168
  ras_obj.plan_df = ras_obj.get_plan_entries()
139
169
  ras_obj.geom_df = ras_obj.get_geom_entries()
140
170
  ras_obj.flow_df = ras_obj.get_flow_entries()
@@ -163,7 +193,7 @@ class RasPlan:
163
193
  Note:
164
194
  This function updates the ras object's dataframes after modifying the project structure.
165
195
  """
166
- print(f"Setting unsteady flow file from {new_unsteady_flow_number} to {plan_number}")
196
+ logging.info(f"Setting unsteady flow file to {new_unsteady_flow_number} in Plan {plan_number}")
167
197
 
168
198
  ras_obj = ras_object or ras
169
199
  ras_obj.check_initialized()
@@ -172,20 +202,23 @@ class RasPlan:
172
202
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
173
203
 
174
204
  if new_unsteady_flow_number not in ras_obj.unsteady_df['unsteady_number'].values:
205
+ logging.error(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
175
206
  raise ValueError(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
176
207
 
177
208
  # Get the full path of the plan file
178
209
  plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
179
210
  if not plan_file_path:
211
+ logging.error(f"Plan file not found: {plan_number}")
180
212
  raise FileNotFoundError(f"Plan file not found: {plan_number}")
181
213
 
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}")
214
+ try:
215
+ RasUtils.update_plan_file(plan_file_path, 'Unsteady', new_unsteady_flow_number)
216
+ logging.info(f"Updated unsteady flow file in {plan_file_path} to u{new_unsteady_flow_number}")
217
+ except Exception as e:
218
+ logging.error(f"Failed to update unsteady flow file: {e}")
219
+ raise
188
220
 
221
+ # Update the ras object's dataframes
189
222
  ras_obj.plan_df = ras_obj.get_plan_entries()
190
223
  ras_obj.geom_df = ras_obj.get_geom_entries()
191
224
  ras_obj.flow_df = ras_obj.get_flow_entries()
@@ -220,7 +253,7 @@ class RasPlan:
220
253
  Note:
221
254
  This function updates the ras object's dataframes after modifying the project structure.
222
255
  """
223
- print(f"Setting num_cores to {num_cores} in Plan {plan_number}")
256
+ logging.info(f"Setting num_cores to {num_cores} in Plan {plan_number}")
224
257
 
225
258
  ras_obj = ras_object or ras
226
259
  ras_obj.check_initialized()
@@ -229,6 +262,7 @@ class RasPlan:
229
262
  if Path(plan_number).is_file():
230
263
  plan_file_path = Path(plan_number)
231
264
  if not plan_file_path.exists():
265
+ logging.error(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
232
266
  raise FileNotFoundError(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
233
267
  else:
234
268
  # Update the plan dataframe in the ras instance to ensure it is current
@@ -237,22 +271,32 @@ class RasPlan:
237
271
  # Get the full path of the plan file
238
272
  plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
239
273
  if not plan_file_path:
274
+ logging.error(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
240
275
  raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
241
276
 
242
277
  cores_pattern = re.compile(r"(UNET D1 Cores= )\d+")
243
- with open(plan_file_path, 'r') as file:
244
- content = file.read()
278
+ try:
279
+ with open(plan_file_path, 'r') as file:
280
+ content = file.read()
281
+ except FileNotFoundError:
282
+ logging.error(f"Plan file not found: {plan_file_path}")
283
+ raise
284
+
245
285
  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.")
286
+ try:
287
+ with open(plan_file_path, 'w') as file:
288
+ file.write(new_content)
289
+ logging.info(f"Updated {plan_file_path} with {num_cores} cores.")
290
+ except IOError as e:
291
+ logging.error(f"Failed to write to plan file: {e}")
292
+ raise
249
293
 
294
+ # Update the ras object's dataframes
250
295
  ras_obj.plan_df = ras_obj.get_plan_entries()
251
296
  ras_obj.geom_df = ras_obj.get_geom_entries()
252
297
  ras_obj.flow_df = ras_obj.get_flow_entries()
253
298
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
254
-
255
-
299
+
256
300
  @staticmethod
257
301
  def set_geom_preprocessor(file_path, run_htab, use_ib_tables, ras_object=None):
258
302
  """
@@ -286,41 +330,46 @@ class RasPlan:
286
330
  ras_obj.check_initialized()
287
331
 
288
332
  if run_htab not in [-1, 0]:
333
+ logging.error("Invalid value for `Run HTab`. Expected `0` or `-1`.")
289
334
  raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
290
335
  if use_ib_tables not in [-1, 0]:
336
+ logging.error("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
291
337
  raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
292
338
  try:
293
- print(f"Reading the file: {file_path}")
339
+ logging.info(f"Reading the file: {file_path}")
294
340
  with open(file_path, 'r') as file:
295
341
  lines = file.readlines()
296
- print("Updating the file with new settings...")
342
+ logging.info("Updating the file with new settings...")
297
343
  updated_lines = []
298
344
  for line in lines:
299
345
  if line.lstrip().startswith("Run HTab="):
300
346
  updated_line = f"Run HTab= {run_htab} \n"
301
347
  updated_lines.append(updated_line)
302
- print(f"Updated 'Run HTab' to {run_htab}")
348
+ logging.info(f"Updated 'Run HTab' to {run_htab}")
303
349
  elif line.lstrip().startswith("UNET Use Existing IB Tables="):
304
350
  updated_line = f"UNET Use Existing IB Tables= {use_ib_tables} \n"
305
351
  updated_lines.append(updated_line)
306
- print(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables}")
352
+ logging.info(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables}")
307
353
  else:
308
354
  updated_lines.append(line)
309
- print(f"Writing the updated settings back to the file: {file_path}")
355
+ logging.info(f"Writing the updated settings back to the file: {file_path}")
310
356
  with open(file_path, 'w') as file:
311
357
  file.writelines(updated_lines)
312
- print("File update completed successfully.")
358
+ logging.info("File update completed successfully.")
313
359
  except FileNotFoundError:
360
+ logging.error(f"The file '{file_path}' does not exist.")
314
361
  raise FileNotFoundError(f"The file '{file_path}' does not exist.")
315
362
  except IOError as e:
363
+ logging.error(f"An error occurred while reading or writing the file: {e}")
316
364
  raise IOError(f"An error occurred while reading or writing the file: {e}")
317
365
 
366
+ # Update the ras object's dataframes
318
367
  ras_obj.plan_df = ras_obj.get_plan_entries()
319
368
  ras_obj.geom_df = ras_obj.get_geom_entries()
320
369
  ras_obj.flow_df = ras_obj.get_flow_entries()
321
370
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
322
371
 
323
- # Get Functions to retrieve file paths for plan, flow, unsteady, geometry and results files
372
+ # Get Functions to retrieve file paths for plan, flow, unsteady, geometry and results files
324
373
 
325
374
  @staticmethod
326
375
  def get_results_path(plan_number: str, ras_object=None) -> Optional[str]:
@@ -352,28 +401,25 @@ class RasPlan:
352
401
  ras_obj.plan_df = ras_obj.get_plan_entries()
353
402
 
354
403
  # 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)
404
+ plan_number = str(plan_number).zfill(2)
359
405
 
360
- # print the ras_obj.plan_df dataframe
361
- print("Plan DataFrame:")
362
- display(ras_obj.plan_df)
406
+ # Log the plan dataframe for debugging
407
+ logging.debug("Plan DataFrame:")
408
+ logging.debug(ras_obj.plan_df)
363
409
 
364
410
  plan_entry = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == plan_number]
365
411
  if not plan_entry.empty:
366
412
  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}")
413
+ if results_path and Path(results_path).exists():
414
+ logging.info(f"Results file for Plan number {plan_number} exists at: {results_path}")
369
415
  return results_path
370
416
  else:
371
- print(f"Results file for Plan number {plan_number} does not exist.")
417
+ logging.warning(f"Results file for Plan number {plan_number} does not exist.")
372
418
  return None
373
419
  else:
374
- print(f"Plan number {plan_number} not found in the entries.")
420
+ logging.warning(f"Plan number {plan_number} not found in the entries.")
375
421
  return None
376
-
422
+
377
423
  @staticmethod
378
424
  def get_plan_path(plan_number: str, ras_object=None) -> Optional[str]:
379
425
  """
@@ -403,8 +449,6 @@ class RasPlan:
403
449
  ras_obj = ras_object or ras
404
450
  ras_obj.check_initialized()
405
451
 
406
- project_name = ras_obj.project_name
407
-
408
452
  # Use updated plan dataframe
409
453
  plan_df = ras_obj.get_plan_entries()
410
454
 
@@ -412,9 +456,10 @@ class RasPlan:
412
456
 
413
457
  if not plan_path.empty:
414
458
  full_path = plan_path['full_path'].iloc[0]
459
+ logging.info(f"Plan file for Plan number {plan_number} found at: {full_path}")
415
460
  return full_path
416
461
  else:
417
- print(f"Plan number {plan_number} not found in the updated plan entries.")
462
+ logging.warning(f"Plan number {plan_number} not found in the updated plan entries.")
418
463
  return None
419
464
 
420
465
  @staticmethod
@@ -449,9 +494,10 @@ class RasPlan:
449
494
  flow_path = ras_obj.flow_df[ras_obj.flow_df['flow_number'] == flow_number]
450
495
  if not flow_path.empty:
451
496
  full_path = flow_path['full_path'].iloc[0]
497
+ logging.info(f"Flow file for Flow number {flow_number} found at: {full_path}")
452
498
  return full_path
453
499
  else:
454
- print(f"Flow number {flow_number} not found in the updated flow entries.")
500
+ logging.warning(f"Flow number {flow_number} not found in the updated flow entries.")
455
501
  return None
456
502
 
457
503
  @staticmethod
@@ -486,9 +532,10 @@ class RasPlan:
486
532
  unsteady_path = ras_obj.unsteady_df[ras_obj.unsteady_df['unsteady_number'] == unsteady_number]
487
533
  if not unsteady_path.empty:
488
534
  full_path = unsteady_path['full_path'].iloc[0]
535
+ logging.info(f"Unsteady file for Unsteady number {unsteady_number} found at: {full_path}")
489
536
  return full_path
490
537
  else:
491
- print(f"Unsteady number {unsteady_number} not found in the updated unsteady entries.")
538
+ logging.warning(f"Unsteady number {unsteady_number} not found in the updated unsteady entries.")
492
539
  return None
493
540
 
494
541
  @staticmethod
@@ -523,12 +570,14 @@ class RasPlan:
523
570
  geom_path = ras_obj.geom_df[ras_obj.geom_df['geom_number'] == geom_number]
524
571
  if not geom_path.empty:
525
572
  full_path = geom_path['full_path'].iloc[0]
573
+ logging.info(f"Geometry file for Geom number {geom_number} found at: {full_path}")
526
574
  return full_path
527
575
  else:
528
- print(f"Geometry number {geom_number} not found in the updated geometry entries.")
576
+ logging.warning(f"Geometry number {geom_number} not found in the updated geometry entries.")
529
577
  return None
530
- # Clone Functions to copy unsteady, flow, and geometry files from templates
531
-
578
+
579
+ # Clone Functions to copy unsteady, flow, and geometry files from templates
580
+
532
581
  @staticmethod
533
582
  def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
534
583
  """
@@ -561,13 +610,18 @@ class RasPlan:
561
610
  new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
562
611
 
563
612
  if not template_plan_path.exists():
613
+ logging.error(f"Template plan file '{template_plan_path}' does not exist.")
564
614
  raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")
565
615
 
566
616
  shutil.copy(template_plan_path, new_plan_path)
567
- print(f"Copied {template_plan_path} to {new_plan_path}")
617
+ logging.info(f"Copied {template_plan_path} to {new_plan_path}")
568
618
 
569
- with open(new_plan_path, 'r') as f:
570
- plan_lines = f.readlines()
619
+ try:
620
+ with open(new_plan_path, 'r') as f:
621
+ plan_lines = f.readlines()
622
+ except FileNotFoundError:
623
+ logging.error(f"New plan file not found after copying: {new_plan_path}")
624
+ raise
571
625
 
572
626
  shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
573
627
  for i, line in enumerate(plan_lines):
@@ -579,15 +633,23 @@ class RasPlan:
579
633
  else:
580
634
  new_shortid = new_plan_shortid[:24]
581
635
  plan_lines[i] = f"Short Identifier={new_shortid}\n"
636
+ logging.info(f"Updated 'Short Identifier' to '{new_shortid}' in {new_plan_path}")
582
637
  break
583
638
 
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}")
639
+ try:
640
+ with open(new_plan_path, 'w') as f:
641
+ f.writelines(plan_lines)
642
+ logging.info(f"Updated short identifier in {new_plan_path}")
643
+ except IOError as e:
644
+ logging.error(f"Failed to write updated short identifier to {new_plan_path}: {e}")
645
+ raise
588
646
 
589
- with open(ras_obj.prj_file, 'r') as f:
590
- lines = f.readlines()
647
+ try:
648
+ with open(ras_obj.prj_file, 'r') as f:
649
+ lines = f.readlines()
650
+ except FileNotFoundError:
651
+ logging.error(f"Project file not found: {ras_obj.prj_file}")
652
+ raise
591
653
 
592
654
  # Prepare the new Plan File entry line
593
655
  new_plan_line = f"Plan File=p{new_plan_num}\n"
@@ -607,28 +669,32 @@ class RasPlan:
607
669
 
608
670
  if insertion_index is not None:
609
671
  lines.insert(insertion_index, new_plan_line)
672
+ logging.info(f"Inserted new plan line at index {insertion_index}")
610
673
  else:
611
674
  # Try to insert after the last Plan File entry
612
675
  plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
613
676
  if plan_indices:
614
677
  last_plan_index = plan_indices[-1]
615
678
  lines.insert(last_plan_index + 1, new_plan_line)
679
+ logging.info(f"Inserted new plan line after index {last_plan_index}")
616
680
  else:
617
681
  # Append at the end if no Plan File entries exist
618
682
  lines.append(new_plan_line)
683
+ logging.info(f"Appended new plan line at the end of the project file")
619
684
 
620
- # Write the updated lines back to the project file
621
- with open(ras_obj.prj_file, 'w') as f:
622
- f.writelines(lines)
685
+ try:
686
+ # Write the updated lines back to the project file
687
+ with open(ras_obj.prj_file, 'w') as f:
688
+ f.writelines(lines)
689
+ logging.info(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
690
+ except IOError as e:
691
+ logging.error(f"Failed to write updated project file: {e}")
692
+ raise
623
693
 
624
- print(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
625
694
  new_plan = new_plan_num
626
695
 
627
- # Store the project folder path
628
- project_folder = ras_obj.project_folder
629
-
630
696
  # Re-initialize the ras global object
631
- ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
697
+ ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
632
698
 
633
699
  ras_obj.plan_df = ras_obj.get_plan_entries()
634
700
  ras_obj.geom_df = ras_obj.get_geom_entries()
@@ -637,7 +703,6 @@ class RasPlan:
637
703
 
638
704
  return new_plan
639
705
 
640
-
641
706
  @staticmethod
642
707
  def clone_unsteady(template_unsteady, ras_object=None):
643
708
  """
@@ -670,22 +735,27 @@ class RasPlan:
670
735
  new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
671
736
 
672
737
  if not template_unsteady_path.exists():
738
+ logging.error(f"Template unsteady file '{template_unsteady_path}' does not exist.")
673
739
  raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")
674
740
 
675
741
  shutil.copy(template_unsteady_path, new_unsteady_path)
676
- print(f"Copied {template_unsteady_path} to {new_unsteady_path}")
742
+ logging.info(f"Copied {template_unsteady_path} to {new_unsteady_path}")
677
743
 
678
744
  # Copy the corresponding .hdf file if it exists
679
745
  template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
680
746
  new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
681
747
  if template_hdf_path.exists():
682
748
  shutil.copy(template_hdf_path, new_hdf_path)
683
- print(f"Copied {template_hdf_path} to {new_hdf_path}")
749
+ logging.info(f"Copied {template_hdf_path} to {new_hdf_path}")
684
750
  else:
685
- print(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")
751
+ logging.warning(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")
686
752
 
687
- with open(ras_obj.prj_file, 'r') as f:
688
- lines = f.readlines()
753
+ try:
754
+ with open(ras_obj.prj_file, 'r') as f:
755
+ lines = f.readlines()
756
+ except FileNotFoundError:
757
+ logging.error(f"Project file not found: {ras_obj.prj_file}")
758
+ raise
689
759
 
690
760
  # Prepare the new Unsteady Flow File entry line
691
761
  new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"
@@ -705,29 +775,32 @@ class RasPlan:
705
775
 
706
776
  if insertion_index is not None:
707
777
  lines.insert(insertion_index, new_unsteady_line)
778
+ logging.info(f"Inserted new unsteady flow line at index {insertion_index}")
708
779
  else:
709
780
  # Try to insert after the last Unsteady Flow File entry
710
781
  unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
711
782
  if unsteady_indices:
712
783
  last_unsteady_index = unsteady_indices[-1]
713
784
  lines.insert(last_unsteady_index + 1, new_unsteady_line)
785
+ logging.info(f"Inserted new unsteady flow line after index {last_unsteady_index}")
714
786
  else:
715
787
  # Append at the end if no Unsteady Flow File entries exist
716
788
  lines.append(new_unsteady_line)
789
+ logging.info(f"Appended new unsteady flow line at the end of the project file")
717
790
 
718
- # Write the updated lines back to the project file
719
- with open(ras_obj.prj_file, 'w') as f:
720
- f.writelines(lines)
791
+ try:
792
+ # Write the updated lines back to the project file
793
+ with open(ras_obj.prj_file, 'w') as f:
794
+ f.writelines(lines)
795
+ logging.info(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
796
+ except IOError as e:
797
+ logging.error(f"Failed to write updated project file: {e}")
798
+ raise
721
799
 
722
- print(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
723
800
  new_unsteady = new_unsteady_num
724
801
 
725
- # Store the project folder path
726
- project_folder = ras_obj.project_folder
727
- hecras_path = ras_obj.ras_exe_path
728
-
729
802
  # Re-initialize the ras global object
730
- ras_obj.initialize(project_folder, hecras_path)
803
+ ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
731
804
 
732
805
  ras_obj.plan_df = ras_obj.get_plan_entries()
733
806
  ras_obj.geom_df = ras_obj.get_geom_entries()
@@ -768,14 +841,19 @@ class RasPlan:
768
841
  new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
769
842
 
770
843
  if not template_flow_path.exists():
844
+ logging.error(f"Template steady flow file '{template_flow_path}' does not exist.")
771
845
  raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")
772
846
 
773
847
  shutil.copy(template_flow_path, new_flow_path)
774
- print(f"Copied {template_flow_path} to {new_flow_path}")
848
+ logging.info(f"Copied {template_flow_path} to {new_flow_path}")
775
849
 
776
850
  # Read the contents of the project file
777
- with open(ras_obj.prj_file, 'r') as f:
778
- lines = f.readlines()
851
+ try:
852
+ with open(ras_obj.prj_file, 'r') as f:
853
+ lines = f.readlines()
854
+ except FileNotFoundError:
855
+ logging.error(f"Project file not found: {ras_obj.prj_file}")
856
+ raise
779
857
 
780
858
  # Prepare the new Steady Flow File entry line
781
859
  new_flow_line = f"Flow File=f{new_flow_num}\n"
@@ -795,28 +873,32 @@ class RasPlan:
795
873
 
796
874
  if insertion_index is not None:
797
875
  lines.insert(insertion_index, new_flow_line)
876
+ logging.info(f"Inserted new steady flow line at index {insertion_index}")
798
877
  else:
799
878
  # Try to insert after the last Steady Flow File entry
800
879
  flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
801
880
  if flow_indices:
802
881
  last_flow_index = flow_indices[-1]
803
882
  lines.insert(last_flow_index + 1, new_flow_line)
883
+ logging.info(f"Inserted new steady flow line after index {last_flow_index}")
804
884
  else:
805
885
  # Append at the end if no Steady Flow File entries exist
806
886
  lines.append(new_flow_line)
887
+ logging.info(f"Appended new steady flow line at the end of the project file")
807
888
 
808
- # Write the updated lines back to the project file
809
- with open(ras_obj.prj_file, 'w') as f:
810
- f.writelines(lines)
889
+ try:
890
+ # Write the updated lines back to the project file
891
+ with open(ras_obj.prj_file, 'w') as f:
892
+ f.writelines(lines)
893
+ logging.info(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
894
+ except IOError as e:
895
+ logging.error(f"Failed to write updated project file: {e}")
896
+ raise
811
897
 
812
- print(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
813
898
  new_steady = new_flow_num
814
899
 
815
- # Store the project folder path
816
- project_folder = ras_obj.project_folder
817
-
818
900
  # Re-initialize the ras global object
819
- ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
901
+ ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
820
902
 
821
903
  ras_obj.plan_df = ras_obj.get_plan_entries()
822
904
  ras_obj.geom_df = ras_obj.get_geom_entries()
@@ -825,322 +907,6 @@ class RasPlan:
825
907
 
826
908
  return new_steady
827
909
 
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
910
  @staticmethod
1145
911
  def clone_geom(template_geom, ras_object=None):
1146
912
  """
@@ -1162,11 +928,13 @@ class RasPlan:
1162
928
 
1163
929
  # Update geometry entries without reinitializing the entire project
1164
930
  ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
931
+ logging.debug(f"Updated geometry entries:\n{ras_obj.geom_df}")
1165
932
 
1166
933
  template_geom_filename = f"{ras_obj.project_name}.g{template_geom}"
1167
934
  template_geom_path = ras_obj.project_folder / template_geom_filename
1168
935
 
1169
936
  if not template_geom_path.is_file():
937
+ logging.error(f"Template geometry file '{template_geom_path}' does not exist.")
1170
938
  raise FileNotFoundError(f"Template geometry file '{template_geom_path}' does not exist.")
1171
939
 
1172
940
  next_geom_number = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])
@@ -1175,19 +943,23 @@ class RasPlan:
1175
943
  new_geom_path = ras_obj.project_folder / new_geom_filename
1176
944
 
1177
945
  shutil.copyfile(template_geom_path, new_geom_path)
1178
- print(f"Copied '{template_geom_path}' to '{new_geom_path}'.")
946
+ logging.info(f"Copied '{template_geom_path}' to '{new_geom_path}'.")
1179
947
 
1180
948
  # 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')
949
+ template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{template_geom}.hdf"
950
+ new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{next_geom_number}.hdf"
1183
951
  if template_hdf_path.is_file():
1184
952
  shutil.copyfile(template_hdf_path, new_hdf_path)
1185
- print(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
953
+ logging.info(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
1186
954
  else:
1187
- print(f"Warning: Template geometry HDF file '{template_hdf_path}' does not exist. This is common, and not critical. Continuing without it.")
955
+ logging.warning(f"Template geometry HDF file '{template_hdf_path}' does not exist. Skipping '.hdf' copy.")
1188
956
 
1189
- with open(ras_obj.prj_file, 'r') as file:
1190
- lines = file.readlines()
957
+ try:
958
+ with open(ras_obj.prj_file, 'r') as file:
959
+ lines = file.readlines()
960
+ except FileNotFoundError:
961
+ logging.error(f"Project file not found: {ras_obj.prj_file}")
962
+ raise
1191
963
 
1192
964
  # Prepare the new Geometry File entry line
1193
965
  new_geom_line = f"Geom File=g{next_geom_number}\n"
@@ -1207,21 +979,28 @@ class RasPlan:
1207
979
 
1208
980
  if insertion_index is not None:
1209
981
  lines.insert(insertion_index, new_geom_line)
982
+ logging.info(f"Inserted new geometry line at index {insertion_index}")
1210
983
  else:
1211
984
  # Try to insert after the last Geometry File entry
1212
985
  geom_indices = [i for i, line in enumerate(lines) if geom_file_pattern.match(line.strip())]
1213
986
  if geom_indices:
1214
987
  last_geom_index = geom_indices[-1]
1215
988
  lines.insert(last_geom_index + 1, new_geom_line)
989
+ logging.info(f"Inserted new geometry line after index {last_geom_index}")
1216
990
  else:
1217
991
  # Append at the end if no Geometry File entries exist
1218
992
  lines.append(new_geom_line)
993
+ logging.info(f"Appended new geometry line at the end of the project file")
1219
994
 
1220
- # Write the updated lines back to the project file
1221
- with open(ras_obj.prj_file, 'w') as file:
1222
- file.writelines(lines)
995
+ try:
996
+ # Write the updated lines back to the project file
997
+ with open(ras_obj.prj_file, 'w') as file:
998
+ file.writelines(lines)
999
+ logging.info(f"Updated {ras_obj.prj_file} with new geometry file g{next_geom_number}")
1000
+ except IOError as e:
1001
+ logging.error(f"Failed to write updated project file: {e}")
1002
+ raise
1223
1003
 
1224
- print(f"Updated {ras_obj.prj_file} with new geometry file g{next_geom_number}")
1225
1004
  new_geom = next_geom_number
1226
1005
 
1227
1006
  # Update all dataframes in the ras object
@@ -1230,13 +1009,10 @@ class RasPlan:
1230
1009
  ras_obj.flow_df = ras_obj.get_flow_entries()
1231
1010
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
1232
1011
 
1233
- print(f"Updated geometry entries:\n{ras_obj.geom_df}")
1012
+ logging.debug(f"Updated geometry entries:\n{ras_obj.geom_df}")
1234
1013
 
1235
1014
  return new_geom
1236
-
1237
-
1238
-
1239
-
1015
+
1240
1016
  @staticmethod
1241
1017
  def get_next_number(existing_numbers):
1242
1018
  """
@@ -1264,3 +1040,188 @@ class RasPlan:
1264
1040
  else:
1265
1041
  break
1266
1042
  return f"{next_number:02d}"
1043
+
1044
+
1045
+ @staticmethod
1046
+ def get_plan_value(
1047
+ plan_number_or_path: Union[str, Path],
1048
+ key: str,
1049
+ ras_object=None
1050
+ ) -> Any:
1051
+ """
1052
+ Retrieve a specific value from a HEC-RAS plan file.
1053
+
1054
+ Parameters:
1055
+ plan_number_or_path (Union[str, Path]): The plan number (1 to 99) or full path to the plan file
1056
+ key (str): The key to retrieve from the plan file
1057
+ ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
1058
+
1059
+ Returns:
1060
+ Any: The value associated with the specified key
1061
+
1062
+ Raises:
1063
+ ValueError: If the plan file is not found
1064
+ IOError: If there's an error reading the plan file
1065
+
1066
+ Available keys and their expected types:
1067
+ - 'description' (str): Plan description
1068
+ - 'computation_interval' (str): Time value for computational time step (e.g., '5SEC', '2MIN')
1069
+ - 'dss_file' (str): Name of the DSS file used
1070
+ - 'flow_file' (str): Name of the flow input file
1071
+ - 'friction_slope_method' (int): Method selection for friction slope (e.g., 1, 2)
1072
+ - 'geom_file' (str): Name of the geometry input file
1073
+ - 'mapping_interval' (str): Time interval for mapping output
1074
+ - 'plan_file' (str): Name of the plan file
1075
+ - 'plan_title' (str): Title of the simulation plan
1076
+ - 'program_version' (str): Version number of HEC-RAS
1077
+ - 'run_htab' (int): Flag to run HTab module (-1 or 1)
1078
+ - 'run_post_process' (int): Flag to run post-processing (-1 or 1)
1079
+ - 'run_sediment' (int): Flag to run sediment transport module (0 or 1)
1080
+ - 'run_unet' (int): Flag to run unsteady network module (-1 or 1)
1081
+ - 'run_wqnet' (int): Flag to run water quality module (0 or 1)
1082
+ - 'short_identifier' (str): Short name or ID for the plan
1083
+ - 'simulation_date' (str): Start and end dates/times for simulation
1084
+ - 'unet_d1_cores' (int): Number of cores used in 1D calculations
1085
+ - 'unet_use_existing_ib_tables' (int): Flag for using existing internal boundary tables (-1, 0, or 1)
1086
+ - 'unet_1d_methodology' (str): 1D calculation methodology
1087
+ - 'unet_d2_solver_type' (str): 2D solver type
1088
+ - 'unet_d2_name' (str): Name of the 2D area
1089
+ - 'run_rasmapper' (int): Flag to run RASMapper for floodplain mapping (-1 for off, 0 for on)
1090
+
1091
+ Example:
1092
+ >>> computation_interval = RasPlan.get_plan_value("01", "computation_interval")
1093
+ >>> print(f"Computation interval: {computation_interval}")
1094
+ """
1095
+ ras_obj = ras_object or ras
1096
+ ras_obj.check_initialized()
1097
+
1098
+ valid_keys = {
1099
+ 'description', 'computation_interval', 'dss_file', 'flow_file', 'friction_slope_method',
1100
+ 'geom_file', 'mapping_interval', 'plan_file', 'plan_title', 'program_version',
1101
+ 'run_htab', 'run_post_process', 'run_sediment', 'run_unet', 'run_wqnet',
1102
+ 'short_identifier', 'simulation_date', 'unet_d1_cores', 'unet_use_existing_ib_tables',
1103
+ 'unet_1d_methodology', 'unet_d2_solver_type', 'unet_d2_name', 'run_rasmapper'
1104
+ }
1105
+
1106
+ if key not in valid_keys:
1107
+ logging.warning(f"Unknown key: {key}. Valid keys are: {', '.join(valid_keys)}\n Add more keys and explanations in get_plan_value() as needed.")
1108
+
1109
+ plan_file_path = Path(plan_number_or_path)
1110
+ if not plan_file_path.is_file():
1111
+ plan_file_path = RasPlan.get_plan_path(plan_number_or_path, ras_object=ras_obj)
1112
+ if plan_file_path is None or not Path(plan_file_path).exists():
1113
+ raise ValueError(f"Plan file not found: {plan_file_path}")
1114
+
1115
+ try:
1116
+ with open(plan_file_path, 'r') as file:
1117
+ content = file.read()
1118
+ except IOError as e:
1119
+ logging.error(f"Error reading plan file {plan_file_path}: {e}")
1120
+ raise
1121
+
1122
+ if key == 'description':
1123
+ match = re.search(r'Begin DESCRIPTION(.*?)END DESCRIPTION', content, re.DOTALL)
1124
+ return match.group(1).strip() if match else None
1125
+ else:
1126
+ pattern = f"{key.replace('_', ' ').title()}=(.*)"
1127
+ match = re.search(pattern, content)
1128
+ if match:
1129
+ return match.group(1).strip()
1130
+ else:
1131
+ logging.error(f"Key '{key}' not found in the plan file.")
1132
+ return None
1133
+
1134
+ @staticmethod
1135
+ def update_plan_value(
1136
+ plan_number_or_path: Union[str, Path],
1137
+ key: str,
1138
+ value: Any,
1139
+ ras_object=None
1140
+ ) -> None:
1141
+ """
1142
+ Update a specific key-value pair in a HEC-RAS plan file.
1143
+
1144
+ Parameters:
1145
+ plan_number_or_path (Union[str, Path]): The plan number (1 to 99) or full path to the plan file
1146
+ key (str): The key to update in the plan file
1147
+ value (Any): The new value to set for the key
1148
+ ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
1149
+
1150
+ Raises:
1151
+ ValueError: If the plan file is not found
1152
+ IOError: If there's an error reading or writing the plan file
1153
+
1154
+ Note: See the docstring of get_plan_value for a full list of available keys and their types.
1155
+
1156
+ Example:
1157
+ >>> RasPlan.update_plan_value("01", "computation_interval", "10SEC")
1158
+ >>> RasPlan.update_plan_value("/path/to/plan.p01", "run_htab", 1)
1159
+ >>> RasPlan.update_plan_value("01", "run_rasmapper", 0) # Turn on Floodplain Mapping
1160
+ """
1161
+ ras_obj = ras_object or ras
1162
+ ras_obj.check_initialized()
1163
+
1164
+ valid_keys = {
1165
+ 'description', 'computation_interval', 'dss_file', 'flow_file', 'friction_slope_method',
1166
+ 'geom_file', 'mapping_interval', 'plan_file', 'plan_title', 'program_version',
1167
+ 'run_htab', 'run_post_process', 'run_sediment', 'run_unet', 'run_wqnet',
1168
+ 'short_identifier', 'simulation_date', 'unet_d1_cores', 'unet_use_existing_ib_tables',
1169
+ 'unet_1d_methodology', 'unet_d2_solver_type', 'unet_d2_name', 'run_rasmapper'
1170
+ }
1171
+
1172
+ if key not in valid_keys:
1173
+ logging.warning(f"Unknown key: {key}. Valid keys are: {', '.join(valid_keys)}")
1174
+
1175
+ plan_file_path = Path(plan_number_or_path)
1176
+ if not plan_file_path.is_file():
1177
+ plan_file_path = RasPlan.get_plan_path(plan_number_or_path, ras_object)
1178
+ if plan_file_path is None or not Path(plan_file_path).exists():
1179
+ raise ValueError(f"Plan file not found: {plan_file_path}")
1180
+
1181
+ try:
1182
+ with open(plan_file_path, 'r') as file:
1183
+ lines = file.readlines()
1184
+ except IOError as e:
1185
+ logging.error(f"Error reading plan file {plan_file_path}: {e}")
1186
+ raise
1187
+
1188
+ # Special handling for description
1189
+ if key == 'description':
1190
+ description_start = None
1191
+ description_end = None
1192
+ for i, line in enumerate(lines):
1193
+ if line.strip() == 'Begin DESCRIPTION':
1194
+ description_start = i
1195
+ elif line.strip() == 'END DESCRIPTION':
1196
+ description_end = i
1197
+ break
1198
+ if description_start is not None and description_end is not None:
1199
+ lines[description_start+1:description_end] = [f"{value}\n"]
1200
+ else:
1201
+ lines.append(f"Begin DESCRIPTION\n{value}\nEND DESCRIPTION\n")
1202
+ else:
1203
+ # For other keys
1204
+ pattern = f"{key.replace('_', ' ').title()}="
1205
+ updated = False
1206
+ for i, line in enumerate(lines):
1207
+ if line.startswith(pattern):
1208
+ lines[i] = f"{pattern}{value}\n"
1209
+ updated = True
1210
+ break
1211
+ if not updated:
1212
+ logging.error(f"Key '{key}' not found in the plan file.")
1213
+ return
1214
+
1215
+ try:
1216
+ with open(plan_file_path, 'w') as file:
1217
+ file.writelines(lines)
1218
+ logging.info(f"Updated {key} in plan file: {plan_file_path}")
1219
+ except IOError as e:
1220
+ logging.error(f"Error writing to plan file {plan_file_path}: {e}")
1221
+ raise
1222
+
1223
+ # Refresh RasPrj dataframes
1224
+ ras_obj.plan_df = ras_obj.get_plan_entries()
1225
+ ras_obj.geom_df = ras_obj.get_geom_entries()
1226
+ ras_obj.flow_df = ras_obj.get_flow_entries()
1227
+ ras_obj.unsteady_df = ras_obj.get_unsteady_entries()