ras-commander 0.51.0__py3-none-any.whl → 0.53.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/Decorators.py +137 -127
- ras_commander/HdfBase.py +359 -307
- ras_commander/HdfFluvialPluvial.py +304 -59
- ras_commander/HdfMesh.py +461 -461
- ras_commander/HdfResultsPlan.py +192 -84
- ras_commander/HdfStruc.py +1 -1
- ras_commander/HdfUtils.py +434 -434
- ras_commander/HdfXsec.py +58 -40
- ras_commander/LoggingConfig.py +2 -1
- ras_commander/RasCmdr.py +45 -20
- ras_commander/RasPlan.py +74 -65
- ras_commander/RasPrj.py +934 -879
- ras_commander/RasUnsteady.py +38 -19
- {ras_commander-0.51.0.dist-info → ras_commander-0.53.0.dist-info}/METADATA +99 -53
- ras_commander-0.53.0.dist-info/RECORD +34 -0
- {ras_commander-0.51.0.dist-info → ras_commander-0.53.0.dist-info}/WHEEL +1 -1
- ras_commander-0.51.0.dist-info/RECORD +0 -34
- {ras_commander-0.51.0.dist-info → ras_commander-0.53.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.51.0.dist-info → ras_commander-0.53.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfXsec.py
CHANGED
@@ -5,7 +5,7 @@ Attribution: A substantial amount of code in this file is sourced or derived
|
|
5
5
|
from the https://github.com/fema-ffrd/rashdf library,
|
6
6
|
released under MIT license and Copyright (c) 2024 fema-ffrd
|
7
7
|
|
8
|
-
|
8
|
+
This source code has been forked and modified for use in RAS Commander.
|
9
9
|
|
10
10
|
-----
|
11
11
|
|
@@ -99,7 +99,7 @@ class HdfXsec:
|
|
99
99
|
"""
|
100
100
|
try:
|
101
101
|
with h5py.File(hdf_path, 'r') as hdf:
|
102
|
-
# Extract datasets
|
102
|
+
# Extract required datasets
|
103
103
|
poly_info = hdf['/Geometry/Cross Sections/Polyline Info'][:]
|
104
104
|
poly_parts = hdf['/Geometry/Cross Sections/Polyline Parts'][:]
|
105
105
|
poly_points = hdf['/Geometry/Cross Sections/Polyline Points'][:]
|
@@ -182,43 +182,61 @@ class HdfXsec:
|
|
182
182
|
else:
|
183
183
|
ineffective_blocks.append([])
|
184
184
|
|
185
|
-
# Create
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
185
|
+
# Create base dictionary with required fields
|
186
|
+
data = {
|
187
|
+
'geometry': geometries,
|
188
|
+
'station_elevation': station_elevations,
|
189
|
+
'mannings_n': mannings_n,
|
190
|
+
'ineffective_blocks': ineffective_blocks,
|
191
|
+
}
|
192
|
+
|
193
|
+
# Define field mappings with default values
|
194
|
+
field_mappings = {
|
195
|
+
'River': ('River', ''),
|
196
|
+
'Reach': ('Reach', ''),
|
197
|
+
'RS': ('RS', ''),
|
198
|
+
'Name': ('Name', ''),
|
199
|
+
'Description': ('Description', ''),
|
200
|
+
'Len Left': ('Len Left', 0.0),
|
201
|
+
'Len Channel': ('Len Channel', 0.0),
|
202
|
+
'Len Right': ('Len Right', 0.0),
|
203
|
+
'Left Bank': ('Left Bank', 0.0),
|
204
|
+
'Right Bank': ('Right Bank', 0.0),
|
205
|
+
'Friction Mode': ('Friction Mode', ''),
|
206
|
+
'Contr': ('Contr', 0.0),
|
207
|
+
'Expan': ('Expan', 0.0),
|
208
|
+
'Left Levee Sta': ('Left Levee Sta', None),
|
209
|
+
'Left Levee Elev': ('Left Levee Elev', None),
|
210
|
+
'Right Levee Sta': ('Right Levee Sta', None),
|
211
|
+
'Right Levee Elev': ('Right Levee Elev', None),
|
212
|
+
'HP Count': ('HP Count', 0),
|
213
|
+
'HP Start Elev': ('HP Start Elev', 0.0),
|
214
|
+
'HP Vert Incr': ('HP Vert Incr', 0.0),
|
215
|
+
'HP LOB Slices': ('HP LOB Slices', 0),
|
216
|
+
'HP Chan Slices': ('HP Chan Slices', 0),
|
217
|
+
'HP ROB Slices': ('HP ROB Slices', 0),
|
218
|
+
'Ineff Block Mode': ('Ineff Block Mode', 0),
|
219
|
+
'Obstr Block Mode': ('Obstr Block Mode', 0),
|
220
|
+
'Default Centerline': ('Default Centerline', 0),
|
221
|
+
'Last Edited': ('Last Edited', '')
|
222
|
+
}
|
221
223
|
|
224
|
+
# Add fields that exist in xs_attrs
|
225
|
+
for field_name, (attr_name, default_value) in field_mappings.items():
|
226
|
+
if attr_name in xs_attrs.dtype.names:
|
227
|
+
if xs_attrs[attr_name].dtype.kind == 'S':
|
228
|
+
# Handle string fields
|
229
|
+
data[field_name] = [x[attr_name].decode('utf-8').strip()
|
230
|
+
for x in xs_attrs]
|
231
|
+
else:
|
232
|
+
# Handle numeric fields
|
233
|
+
data[field_name] = xs_attrs[attr_name]
|
234
|
+
else:
|
235
|
+
# Use default value if field doesn't exist
|
236
|
+
data[field_name] = [default_value] * len(geometries)
|
237
|
+
logger.debug(f"Field {attr_name} not found in attributes, using default value")
|
238
|
+
|
239
|
+
if geometries:
|
222
240
|
gdf = gpd.GeoDataFrame(data)
|
223
241
|
|
224
242
|
# Set CRS if available
|
@@ -233,7 +251,7 @@ class HdfXsec:
|
|
233
251
|
return gpd.GeoDataFrame()
|
234
252
|
|
235
253
|
except Exception as e:
|
236
|
-
|
254
|
+
logger.error(f"Error processing cross-section data: {str(e)}")
|
237
255
|
return gpd.GeoDataFrame()
|
238
256
|
|
239
257
|
@staticmethod
|
@@ -391,7 +409,7 @@ class HdfXsec:
|
|
391
409
|
result_gdf.at[idx, 'points'] = points
|
392
410
|
|
393
411
|
# Add stationing direction based on upstream/downstream info
|
394
|
-
if row['
|
412
|
+
if row['US Type'] == 'Junction' and row['DS Type'] != 'Junction':
|
395
413
|
# Reverse stationing if upstream is junction
|
396
414
|
result_gdf.at[idx, 'station_start'] = total_length
|
397
415
|
result_gdf.at[idx, 'station_end'] = 0.0
|
ras_commander/LoggingConfig.py
CHANGED
@@ -23,7 +23,8 @@ def setup_logging(log_file=None, log_level=logging.INFO):
|
|
23
23
|
|
24
24
|
# Define log format
|
25
25
|
log_format = logging.Formatter(
|
26
|
-
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
26
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
27
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
27
28
|
)
|
28
29
|
|
29
30
|
# Configure console handler
|
ras_commander/RasCmdr.py
CHANGED
@@ -271,13 +271,13 @@ class RasCmdr:
|
|
271
271
|
logger.info(f"Created worker folder: {worker_folder}")
|
272
272
|
|
273
273
|
try:
|
274
|
-
|
275
|
-
|
274
|
+
worker_ras = RasPrj()
|
275
|
+
worker_ras_object = init_ras_project(
|
276
276
|
ras_project_folder=worker_folder,
|
277
277
|
ras_version=ras_obj.ras_exe_path,
|
278
|
-
|
278
|
+
ras_object=worker_ras
|
279
279
|
)
|
280
|
-
worker_ras_objects[worker_id] =
|
280
|
+
worker_ras_objects[worker_id] = worker_ras_object
|
281
281
|
except Exception as e:
|
282
282
|
logger.critical(f"Failed to initialize RAS project for worker {worker_id}: {str(e)}")
|
283
283
|
worker_ras_objects[worker_id] = None
|
@@ -317,28 +317,53 @@ class RasCmdr:
|
|
317
317
|
continue
|
318
318
|
worker_folder = Path(worker_ras.project_folder)
|
319
319
|
try:
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
320
|
+
# First, close any open resources in the worker RAS object
|
321
|
+
worker_ras.close() if hasattr(worker_ras, 'close') else None
|
322
|
+
|
323
|
+
# Add a small delay to ensure file handles are released
|
324
|
+
time.sleep(1)
|
325
|
+
|
326
|
+
# Move files with retry mechanism
|
327
|
+
max_retries = 3
|
328
|
+
for retry in range(max_retries):
|
329
|
+
try:
|
330
|
+
for item in worker_folder.iterdir():
|
331
|
+
dest_path = final_dest_folder / item.name
|
332
|
+
if dest_path.exists():
|
333
|
+
if dest_path.is_dir():
|
334
|
+
shutil.rmtree(dest_path)
|
335
|
+
else:
|
336
|
+
dest_path.unlink()
|
337
|
+
# Use copy instead of move for more reliability
|
338
|
+
if item.is_dir():
|
339
|
+
shutil.copytree(item, dest_path)
|
340
|
+
else:
|
341
|
+
shutil.copy2(item, dest_path)
|
342
|
+
|
343
|
+
# Add another small delay before removal
|
344
|
+
time.sleep(1)
|
345
|
+
|
346
|
+
# Try to remove the worker folder
|
347
|
+
if worker_folder.exists():
|
348
|
+
shutil.rmtree(worker_folder)
|
349
|
+
break # If successful, break the retry loop
|
350
|
+
|
351
|
+
except PermissionError as pe:
|
352
|
+
if retry == max_retries - 1: # If this was the last retry
|
353
|
+
logger.error(f"Failed to move/remove files after {max_retries} attempts: {str(pe)}")
|
354
|
+
raise
|
355
|
+
time.sleep(2 ** retry) # Exponential backoff
|
356
|
+
continue
|
357
|
+
|
333
358
|
except Exception as e:
|
334
359
|
logger.error(f"Error moving results from {worker_folder} to {final_dest_folder}: {str(e)}")
|
335
360
|
|
336
361
|
try:
|
337
|
-
|
362
|
+
final_dest_folder_ras = RasPrj()
|
338
363
|
final_dest_folder_ras_obj = init_ras_project(
|
339
364
|
ras_project_folder=final_dest_folder,
|
340
365
|
ras_version=ras_obj.ras_exe_path,
|
341
|
-
|
366
|
+
ras_object=final_dest_folder_ras
|
342
367
|
)
|
343
368
|
final_dest_folder_ras_obj.check_initialized()
|
344
369
|
except Exception as e:
|
@@ -379,7 +404,7 @@ class RasCmdr:
|
|
379
404
|
other two compute_ functions. Per the original HEC-RAS test flag, it creates a separate test folder,
|
380
405
|
copies the project there, and executes the specified plans in sequential order.
|
381
406
|
|
382
|
-
For most purposes, just copying
|
407
|
+
For most purposes, just copying the project folder, initing that new folder, then running each plan
|
383
408
|
with compute_plan is a simpler and more flexible approach. This is shown in the examples provided
|
384
409
|
in the ras-commander library.
|
385
410
|
|
ras_commander/RasPlan.py
CHANGED
@@ -249,7 +249,6 @@ class RasPlan:
|
|
249
249
|
def _update_unsteady_in_file(lines, new_unsteady_flow_number):
|
250
250
|
return [f"Unsteady File=u{new_unsteady_flow_number}\n" if line.startswith("Unsteady File=u") else line for line in lines]
|
251
251
|
|
252
|
-
|
253
252
|
@staticmethod
|
254
253
|
@log_call
|
255
254
|
def set_num_cores(plan_number, num_cores, ras_object=None):
|
@@ -264,6 +263,16 @@ class RasPlan:
|
|
264
263
|
Returns:
|
265
264
|
None
|
266
265
|
|
266
|
+
Number of cores is controlled by the following parameters in the plan file corresponding to 1D, 2D, Pipe Systems and Pump Stations:
|
267
|
+
UNET D1 Cores=
|
268
|
+
UNET D2 Cores=
|
269
|
+
PS Cores=
|
270
|
+
|
271
|
+
Where a value of "0" is used for "All Available" cores, and values of 1 or more are used to specify the number of cores to use.
|
272
|
+
For complex 1D/2D models with pipe systems, a more complex approach may be needed to optimize performance. (Suggest writing a custom function based on this code).
|
273
|
+
This function simply sets the "num_cores" parameter for ALL instances of the above parameters in the plan file.
|
274
|
+
|
275
|
+
|
267
276
|
Notes on setting num_cores in HEC-RAS:
|
268
277
|
The recommended setting for num_cores is 2 (most efficient) to 8 (most performant)
|
269
278
|
More details in the HEC-Commander Repository Blog "Benchmarking is All You Need"
|
@@ -290,9 +299,9 @@ class RasPlan:
|
|
290
299
|
def update_num_cores(lines):
|
291
300
|
updated_lines = []
|
292
301
|
for line in lines:
|
293
|
-
if "UNET D1 Cores="
|
294
|
-
|
295
|
-
updated_lines.append(f"{
|
302
|
+
if any(param in line for param in ["UNET D1 Cores=", "UNET D2 Cores=", "PS Cores="]):
|
303
|
+
param_name = line.split("=")[0]
|
304
|
+
updated_lines.append(f"{param_name}= {num_cores}\n")
|
296
305
|
else:
|
297
306
|
updated_lines.append(line)
|
298
307
|
return updated_lines
|
@@ -840,7 +849,7 @@ class RasPlan:
|
|
840
849
|
- 'Plan File' (str): Name of the plan file
|
841
850
|
- 'Plan Title' (str): Title of the simulation plan
|
842
851
|
- 'Program Version' (str): Version number of HEC-RAS
|
843
|
-
- 'Run
|
852
|
+
- 'Run HTab' (int): Flag to run HTab module (-1 or 1)
|
844
853
|
- 'Run Post Process' (int): Flag to run post-processing (-1 or 1)
|
845
854
|
- 'Run Sediment' (int): Flag to run sediment transport module (0 or 1)
|
846
855
|
- 'Run UNET' (int): Flag to run unsteady network module (-1 or 1)
|
@@ -848,13 +857,14 @@ class RasPlan:
|
|
848
857
|
- 'Short Identifier' (str): Short name or ID for the plan
|
849
858
|
- 'Simulation Date' (str): Start and end dates/times for simulation
|
850
859
|
- 'UNET D1 Cores' (int): Number of cores used in 1D calculations
|
860
|
+
- 'UNET D2 Cores' (int): Number of cores used in 2D calculations
|
861
|
+
- 'PS Cores' (int): Number of cores used in parallel simulation
|
851
862
|
- 'UNET Use Existing IB Tables' (int): Flag for using existing internal boundary tables (-1, 0, or 1)
|
852
863
|
- 'UNET 1D Methodology' (str): 1D calculation methodology
|
853
864
|
- 'UNET D2 Solver Type' (str): 2D solver type
|
854
865
|
- 'UNET D2 Name' (str): Name of the 2D area
|
855
866
|
- 'Run RASMapper' (int): Flag to run RASMapper for floodplain mapping (-1 for off, 0 for on)
|
856
867
|
|
857
|
-
|
858
868
|
Note:
|
859
869
|
Writing Multi line keys like 'Description' are not supported by this function.
|
860
870
|
|
@@ -868,9 +878,10 @@ class RasPlan:
|
|
868
878
|
supported_plan_keys = {
|
869
879
|
'Description', 'Computation Interval', 'DSS File', 'Flow File', 'Friction Slope Method',
|
870
880
|
'Geom File', 'Mapping Interval', 'Plan File', 'Plan Title', 'Program Version',
|
871
|
-
'Run
|
872
|
-
'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET
|
873
|
-
'UNET
|
881
|
+
'Run HTab', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
|
882
|
+
'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET D2 Cores', 'PS Cores',
|
883
|
+
'UNET Use Existing IB Tables', 'UNET 1D Methodology', 'UNET D2 Solver Type',
|
884
|
+
'UNET D2 Name', 'Run RASMapper', 'Run HTab', 'Run UNET'
|
874
885
|
}
|
875
886
|
|
876
887
|
if key not in supported_plan_keys:
|
@@ -891,7 +902,23 @@ class RasPlan:
|
|
891
902
|
logger.error(f"Error reading plan file {plan_file_path}: {e}")
|
892
903
|
raise
|
893
904
|
|
894
|
-
|
905
|
+
# Handle core settings specially to convert to integers
|
906
|
+
core_keys = {'UNET D1 Cores', 'UNET D2 Cores', 'PS Cores'}
|
907
|
+
if key in core_keys:
|
908
|
+
pattern = f"{key}=(.*)"
|
909
|
+
match = re.search(pattern, content)
|
910
|
+
if match:
|
911
|
+
try:
|
912
|
+
return int(match.group(1).strip())
|
913
|
+
except ValueError:
|
914
|
+
logger = logging.getLogger(__name__)
|
915
|
+
logger.error(f"Could not convert {key} value to integer")
|
916
|
+
return None
|
917
|
+
else:
|
918
|
+
logger = logging.getLogger(__name__)
|
919
|
+
logger.error(f"Key '{key}' not found in the plan file.")
|
920
|
+
return None
|
921
|
+
elif key == 'Description':
|
895
922
|
match = re.search(r'Begin DESCRIPTION(.*?)END DESCRIPTION', content, re.DOTALL)
|
896
923
|
return match.group(1).strip() if match else None
|
897
924
|
else:
|
@@ -1060,81 +1087,63 @@ class RasPlan:
|
|
1060
1087
|
@log_call
|
1061
1088
|
def update_plan_description(plan_number_or_path: Union[str, Path], description: str, ras_object: Optional['RasPrj'] = None) -> None:
|
1062
1089
|
"""
|
1063
|
-
Update the description in
|
1090
|
+
Update the description block in a HEC-RAS plan file.
|
1064
1091
|
|
1065
1092
|
Args:
|
1066
|
-
plan_number_or_path (Union[str, Path]): The plan number or path to the plan file
|
1067
|
-
description (str): The new description
|
1068
|
-
ras_object (Optional[RasPrj]):
|
1093
|
+
plan_number_or_path (Union[str, Path]): The plan number or full path to the plan file
|
1094
|
+
description (str): The new description text to set
|
1095
|
+
ras_object (Optional[RasPrj]): Specific RAS object to use. If None, uses the global ras instance.
|
1069
1096
|
|
1070
1097
|
Raises:
|
1071
|
-
ValueError: If the plan file is not found
|
1072
|
-
IOError: If there's an error reading
|
1098
|
+
ValueError: If the plan file is not found
|
1099
|
+
IOError: If there's an error reading or writing the plan file
|
1073
1100
|
"""
|
1074
|
-
logger =
|
1101
|
+
logger = get_logger(__name__)
|
1102
|
+
ras_obj = ras_object or ras
|
1103
|
+
ras_obj.check_initialized()
|
1075
1104
|
|
1076
1105
|
plan_file_path = Path(plan_number_or_path)
|
1077
1106
|
if not plan_file_path.is_file():
|
1078
|
-
plan_file_path =
|
1079
|
-
if
|
1107
|
+
plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_object)
|
1108
|
+
if not plan_file_path.exists():
|
1109
|
+
logger.error(f"Plan file not found: {plan_file_path}")
|
1080
1110
|
raise ValueError(f"Plan file not found: {plan_file_path}")
|
1081
1111
|
|
1082
1112
|
try:
|
1083
1113
|
with open(plan_file_path, 'r') as file:
|
1084
|
-
|
1085
|
-
except IOError as e:
|
1086
|
-
logger.error(f"Error reading plan file {plan_file_path}: {e}")
|
1087
|
-
raise
|
1114
|
+
content = file.read()
|
1088
1115
|
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
if
|
1094
|
-
|
1095
|
-
|
1096
|
-
end_index = i
|
1097
|
-
elif line.strip().startswith("Computation Interval="):
|
1098
|
-
comp_interval_index = i
|
1099
|
-
|
1100
|
-
if start_index is not None and end_index is not None:
|
1101
|
-
# Description exists, update it
|
1102
|
-
new_lines = lines[:start_index + 1]
|
1103
|
-
if description:
|
1104
|
-
new_lines.extend(description.split('\n'))
|
1105
|
-
else:
|
1106
|
-
new_lines.append('\n')
|
1107
|
-
new_lines.extend(lines[end_index:])
|
1108
|
-
else:
|
1109
|
-
# Description doesn't exist, insert before Computation Interval
|
1110
|
-
if comp_interval_index is None:
|
1111
|
-
logger.warning("Neither description tags nor Computation Interval found in plan file. Appending to end of file.")
|
1112
|
-
comp_interval_index = len(lines)
|
1113
|
-
|
1114
|
-
new_lines = lines[:comp_interval_index]
|
1115
|
-
new_lines.append("BEGIN DESCRIPTION:\n")
|
1116
|
-
if description:
|
1117
|
-
new_lines.extend(f"{line}\n" for line in description.split('\n'))
|
1116
|
+
# Find the description block
|
1117
|
+
desc_pattern = r'Begin DESCRIPTION.*?END DESCRIPTION'
|
1118
|
+
new_desc_block = f'Begin DESCRIPTION\n{description}\nEND DESCRIPTION'
|
1119
|
+
|
1120
|
+
if re.search(desc_pattern, content, re.DOTALL):
|
1121
|
+
# Replace existing description block
|
1122
|
+
new_content = re.sub(desc_pattern, new_desc_block, content, flags=re.DOTALL)
|
1118
1123
|
else:
|
1119
|
-
|
1120
|
-
|
1121
|
-
new_lines.extend(lines[comp_interval_index:])
|
1124
|
+
# Add new description block at the start of the file
|
1125
|
+
new_content = new_desc_block + '\n' + content
|
1122
1126
|
|
1123
|
-
|
1127
|
+
# Write the updated content back to the file
|
1124
1128
|
with open(plan_file_path, 'w') as file:
|
1125
|
-
file.
|
1129
|
+
file.write(new_content)
|
1130
|
+
|
1126
1131
|
logger.info(f"Updated description in plan file: {plan_file_path}")
|
1132
|
+
|
1133
|
+
# Update the dataframes in the RAS object to reflect changes
|
1134
|
+
if ras_object:
|
1135
|
+
ras_object.plan_df = ras_object.get_plan_entries()
|
1136
|
+
ras_object.geom_df = ras_object.get_geom_entries()
|
1137
|
+
ras_object.flow_df = ras_object.get_flow_entries()
|
1138
|
+
ras_object.unsteady_df = ras_object.get_unsteady_entries()
|
1139
|
+
|
1127
1140
|
except IOError as e:
|
1128
|
-
logger.error(f"Error
|
1141
|
+
logger.error(f"Error updating plan description in {plan_file_path}: {e}")
|
1142
|
+
raise
|
1143
|
+
except Exception as e:
|
1144
|
+
logger.error(f"Unexpected error updating plan description: {e}")
|
1129
1145
|
raise
|
1130
1146
|
|
1131
|
-
# Refresh RasPrj dataframes
|
1132
|
-
if ras_object:
|
1133
|
-
ras_object.plan_df = ras_object.get_plan_entries()
|
1134
|
-
ras_object.geom_df = ras_object.get_geom_entries()
|
1135
|
-
ras_object.flow_df = ras_object.get_flow_entries()
|
1136
|
-
ras_object.unsteady_df = ras_object.get_unsteady_entries()
|
1137
|
-
|
1138
1147
|
@staticmethod
|
1139
1148
|
@log_call
|
1140
1149
|
def read_plan_description(plan_number_or_path: Union[str, Path], ras_object: Optional['RasPrj'] = None) -> str:
|