ras-commander 0.65.0__py3-none-any.whl → 0.67.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 +96 -56
- ras_commander/HdfPlan.py +1 -19
- ras_commander/RasPlan.py +1487 -1468
- ras_commander/RasPrj.py +309 -162
- ras_commander/RasUtils.py +22 -3
- ras_commander/__init__.py +1 -1
- {ras_commander-0.65.0.dist-info → ras_commander-0.67.0.dist-info}/METADATA +1 -1
- {ras_commander-0.65.0.dist-info → ras_commander-0.67.0.dist-info}/RECORD +11 -11
- {ras_commander-0.65.0.dist-info → ras_commander-0.67.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.65.0.dist-info → ras_commander-0.67.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.65.0.dist-info → ras_commander-0.67.0.dist-info}/top_level.txt +0 -0
ras_commander/RasPrj.py
CHANGED
@@ -160,101 +160,137 @@ class RasPrj:
|
|
160
160
|
Load project data from the HEC-RAS project file.
|
161
161
|
|
162
162
|
This method initializes DataFrames for plan, flow, unsteady, and geometry entries
|
163
|
-
|
164
|
-
Also extracts unsteady_number and geometry_number from plan files and adds them to plan_df.
|
165
|
-
Now includes Geom Path, Flow Path, and other required columns.
|
163
|
+
and ensures all required columns are present with appropriate paths.
|
166
164
|
"""
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
165
|
+
try:
|
166
|
+
# Load data frames
|
167
|
+
self.unsteady_df = self._get_prj_entries('Unsteady')
|
168
|
+
self.plan_df = self._get_prj_entries('Plan')
|
169
|
+
self.flow_df = self._get_prj_entries('Flow')
|
170
|
+
self.geom_df = self.get_geom_entries()
|
171
|
+
|
172
|
+
# Ensure required columns exist
|
173
|
+
self._ensure_required_columns()
|
174
|
+
|
175
|
+
# Set paths for geometry and flow files
|
176
|
+
self._set_file_paths()
|
177
|
+
|
178
|
+
# Make sure all plan paths are properly set
|
179
|
+
self._set_plan_paths()
|
180
|
+
|
181
|
+
except Exception as e:
|
182
|
+
logger.error(f"Error loading project data: {e}")
|
183
|
+
raise
|
184
|
+
|
185
|
+
def _ensure_required_columns(self):
|
186
|
+
"""Ensure all required columns exist in plan_df."""
|
187
|
+
required_columns = [
|
188
|
+
'plan_number', 'unsteady_number', 'geometry_number',
|
189
|
+
'Geom File', 'Geom Path', 'Flow File', 'Flow Path', 'full_path'
|
190
|
+
]
|
191
|
+
|
192
|
+
for col in required_columns:
|
193
|
+
if col not in self.plan_df.columns:
|
194
|
+
self.plan_df[col] = None
|
172
195
|
|
173
|
-
|
196
|
+
if not self.plan_df['full_path'].any():
|
197
|
+
self.plan_df['full_path'] = self.plan_df['plan_number'].apply(
|
198
|
+
lambda x: str(self.project_folder / f"{self.project_name}.p{x}")
|
199
|
+
)
|
200
|
+
|
201
|
+
def _set_file_paths(self):
|
202
|
+
"""Set geometry and flow paths in plan_df."""
|
174
203
|
for idx, row in self.plan_df.iterrows():
|
175
204
|
try:
|
176
|
-
|
177
|
-
|
178
|
-
geom_hdf_path = self.project_folder / f"{self.project_name}.g{row['Geom File']}"
|
179
|
-
self.plan_df.at[idx, 'Geom Path'] = str(geom_hdf_path)
|
205
|
+
self._set_geom_path(idx, row)
|
206
|
+
self._set_flow_path(idx, row)
|
180
207
|
|
181
|
-
|
182
|
-
|
183
|
-
# Determine if this is a steady or unsteady flow file
|
184
|
-
if row['unsteady_number'] is not None:
|
185
|
-
flow_path = self.project_folder / f"{self.project_name}.u{row['Flow File']}"
|
186
|
-
else:
|
187
|
-
flow_path = self.project_folder / f"{self.project_name}.f{row['Flow File']}"
|
188
|
-
self.plan_df.at[idx, 'Flow Path'] = str(flow_path)
|
189
|
-
|
190
|
-
if not self.suppress_logging:
|
191
|
-
logger.info(f"Plan {row['plan_number']} paths set up")
|
208
|
+
if not self.suppress_logging:
|
209
|
+
logger.info(f"Plan {row['plan_number']} paths set up")
|
192
210
|
except Exception as e:
|
193
211
|
logger.error(f"Error processing plan file {row['plan_number']}: {e}")
|
194
212
|
|
195
|
-
def
|
196
|
-
"""
|
197
|
-
|
198
|
-
|
213
|
+
def _set_geom_path(self, idx: int, row: pd.Series):
|
214
|
+
"""Set geometry path for a plan entry."""
|
215
|
+
if pd.notna(row['Geom File']):
|
216
|
+
geom_path = self.project_folder / f"{self.project_name}.g{row['Geom File']}"
|
217
|
+
self.plan_df.at[idx, 'Geom Path'] = str(geom_path)
|
218
|
+
|
219
|
+
def _set_flow_path(self, idx: int, row: pd.Series):
|
220
|
+
"""Set flow path for a plan entry."""
|
221
|
+
if pd.notna(row['Flow File']):
|
222
|
+
prefix = 'u' if pd.notna(row['unsteady_number']) else 'f'
|
223
|
+
flow_path = self.project_folder / f"{self.project_name}.{prefix}{row['Flow File']}"
|
224
|
+
self.plan_df.at[idx, 'Flow Path'] = str(flow_path)
|
225
|
+
|
226
|
+
def _set_plan_paths(self):
|
227
|
+
"""Set full path information for plan files and their associated geometry and flow files."""
|
228
|
+
if self.plan_df.empty:
|
229
|
+
logger.debug("Plan DataFrame is empty, no paths to set")
|
230
|
+
return
|
199
231
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
Returns:
|
206
|
-
--------
|
207
|
-
tuple: (str, str)
|
208
|
-
A tuple containing (geometry_number, full_hdf_path) or (None, None) if not found.
|
209
|
-
geometry_number is the number after 'g' in the Geom File value
|
210
|
-
full_hdf_path is the path to the geometry HDF file
|
211
|
-
"""
|
212
|
-
content, encoding = read_file_with_fallback_encoding(plan_file_path)
|
213
|
-
|
214
|
-
if content is None:
|
215
|
-
return None, None
|
232
|
+
# Ensure full path is set for all plan entries
|
233
|
+
if 'full_path' not in self.plan_df.columns or self.plan_df['full_path'].isna().any():
|
234
|
+
self.plan_df['full_path'] = self.plan_df['plan_number'].apply(
|
235
|
+
lambda x: str(self.project_folder / f"{self.project_name}.p{x}")
|
236
|
+
)
|
216
237
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
geom_hdf_path = self.project_folder / f"{self.project_name}.{geom_file}.hdf"
|
223
|
-
if geom_hdf_path.exists():
|
224
|
-
return geom_number, str(geom_hdf_path)
|
225
|
-
except Exception as e:
|
226
|
-
logger.error(f"Error extracting geometry number from {plan_file_path}: {e}")
|
238
|
+
# Create the Geom Path and Flow Path columns if they don't exist
|
239
|
+
if 'Geom Path' not in self.plan_df.columns:
|
240
|
+
self.plan_df['Geom Path'] = None
|
241
|
+
if 'Flow Path' not in self.plan_df.columns:
|
242
|
+
self.plan_df['Flow Path'] = None
|
227
243
|
|
228
|
-
|
244
|
+
# Update paths for each plan entry
|
245
|
+
for idx, row in self.plan_df.iterrows():
|
246
|
+
try:
|
247
|
+
# Set geometry path if Geom File exists and Geom Path is missing or invalid
|
248
|
+
if pd.notna(row['Geom File']):
|
249
|
+
geom_path = self.project_folder / f"{self.project_name}.g{row['Geom File']}"
|
250
|
+
self.plan_df.at[idx, 'Geom Path'] = str(geom_path)
|
251
|
+
|
252
|
+
# Set flow path if Flow File exists and Flow Path is missing or invalid
|
253
|
+
if pd.notna(row['Flow File']):
|
254
|
+
# Determine the prefix (u for unsteady, f for steady flow)
|
255
|
+
prefix = 'u' if pd.notna(row['unsteady_number']) else 'f'
|
256
|
+
flow_path = self.project_folder / f"{self.project_name}.{prefix}{row['Flow File']}"
|
257
|
+
self.plan_df.at[idx, 'Flow Path'] = str(flow_path)
|
258
|
+
|
259
|
+
if not self.suppress_logging:
|
260
|
+
logger.debug(f"Plan {row['plan_number']} paths set up")
|
261
|
+
except Exception as e:
|
262
|
+
logger.error(f"Error setting paths for plan {row.get('plan_number', idx)}: {e}")
|
229
263
|
|
230
|
-
def
|
264
|
+
def _get_geom_file_for_plan(self, plan_number):
|
231
265
|
"""
|
232
|
-
|
266
|
+
Get the geometry file path for a given plan number.
|
233
267
|
|
234
|
-
|
235
|
-
|
236
|
-
plan_file_path : str or Path
|
237
|
-
Path to the plan file.
|
268
|
+
Args:
|
269
|
+
plan_number (str): The plan number to find the geometry file for.
|
238
270
|
|
239
271
|
Returns:
|
240
|
-
|
241
|
-
str or None
|
242
|
-
The Flow File value or None if not found.
|
272
|
+
str: The full path to the geometry HDF file, or None if not found.
|
243
273
|
"""
|
274
|
+
plan_file_path = self.project_folder / f"{self.project_name}.p{plan_number}"
|
244
275
|
content, encoding = read_file_with_fallback_encoding(plan_file_path)
|
245
276
|
|
246
277
|
if content is None:
|
247
278
|
return None
|
248
279
|
|
249
280
|
try:
|
250
|
-
|
251
|
-
|
252
|
-
|
281
|
+
for line in content.splitlines():
|
282
|
+
if line.startswith("Geom File="):
|
283
|
+
geom_file = line.strip().split('=')[1]
|
284
|
+
geom_hdf_path = self.project_folder / f"{self.project_name}.{geom_file}.hdf"
|
285
|
+
if geom_hdf_path.exists():
|
286
|
+
return str(geom_hdf_path)
|
287
|
+
else:
|
288
|
+
return None
|
253
289
|
except Exception as e:
|
254
|
-
logger.error(f"Error
|
255
|
-
|
290
|
+
logger.error(f"Error reading plan file for geometry: {e}")
|
256
291
|
return None
|
257
292
|
|
293
|
+
|
258
294
|
@staticmethod
|
259
295
|
@log_call
|
260
296
|
def get_plan_value(
|
@@ -439,112 +475,106 @@ class RasPrj:
|
|
439
475
|
|
440
476
|
Returns:
|
441
477
|
pd.DataFrame: A DataFrame containing the extracted entries.
|
478
|
+
|
479
|
+
Raises:
|
480
|
+
Exception: If there's an error reading or processing the project file.
|
442
481
|
"""
|
443
482
|
entries = []
|
444
483
|
pattern = re.compile(rf"{entry_type} File=(\w+)")
|
445
484
|
|
446
485
|
try:
|
447
|
-
with open(self.prj_file, 'r') as file:
|
486
|
+
with open(self.prj_file, 'r', encoding='utf-8') as file:
|
448
487
|
for line in file:
|
449
488
|
match = pattern.match(line.strip())
|
450
489
|
if match:
|
451
490
|
file_name = match.group(1)
|
452
491
|
full_path = str(self.project_folder / f"{self.project_name}.{file_name}")
|
453
|
-
entry_number = file_name[1:]
|
492
|
+
entry_number = file_name[1:]
|
454
493
|
|
455
494
|
entry = {
|
456
495
|
f'{entry_type.lower()}_number': entry_number,
|
457
496
|
'full_path': full_path
|
458
497
|
}
|
459
498
|
|
499
|
+
# Handle Unsteady entries
|
460
500
|
if entry_type == 'Unsteady':
|
461
|
-
entry
|
462
|
-
unsteady_info = self._parse_unsteady_file(Path(full_path))
|
463
|
-
entry.update(unsteady_info)
|
501
|
+
entry.update(self._process_unsteady_entry(entry_number, full_path))
|
464
502
|
else:
|
465
|
-
entry.update(
|
466
|
-
'unsteady_number': None,
|
467
|
-
'geometry_number': None
|
468
|
-
})
|
503
|
+
entry.update(self._process_default_entry())
|
469
504
|
|
505
|
+
# Handle Plan entries
|
470
506
|
if entry_type == 'Plan':
|
471
|
-
|
472
|
-
if plan_info:
|
473
|
-
# Handle Flow File (unsteady) number
|
474
|
-
flow_file = plan_info.get('Flow File')
|
475
|
-
if flow_file and flow_file.startswith('u'):
|
476
|
-
entry['unsteady_number'] = flow_file[1:]
|
477
|
-
entry['Flow File'] = flow_file[1:]
|
478
|
-
else:
|
479
|
-
entry['unsteady_number'] = None
|
480
|
-
entry['Flow File'] = flow_file[1:] if flow_file and flow_file.startswith('f') else None
|
481
|
-
|
482
|
-
# Handle Geom File number
|
483
|
-
geom_file = plan_info.get('Geom File')
|
484
|
-
if geom_file and geom_file.startswith('g'):
|
485
|
-
entry['geometry_number'] = geom_file[1:]
|
486
|
-
entry['Geom File'] = geom_file[1:]
|
487
|
-
else:
|
488
|
-
entry['geometry_number'] = None
|
489
|
-
entry['Geom File'] = None
|
490
|
-
|
491
|
-
# Add all plan key information with exact same names
|
492
|
-
for key, value in plan_info.items():
|
493
|
-
if key not in ['Flow File', 'Geom File']:
|
494
|
-
entry[key] = value
|
507
|
+
entry.update(self._process_plan_entry(entry_number, full_path))
|
495
508
|
|
496
|
-
# Add HDF results path
|
497
|
-
hdf_results_path = self.project_folder / f"{self.project_name}.p{entry_number}.hdf"
|
498
|
-
entry['HDF_Results_Path'] = str(hdf_results_path) if hdf_results_path.exists() else None
|
499
|
-
|
500
509
|
entries.append(entry)
|
501
|
-
|
502
|
-
df = pd.DataFrame(entries)
|
503
|
-
|
504
|
-
if not df.empty and entry_type == 'Plan':
|
505
|
-
# Set required column order
|
506
|
-
first_cols = ['plan_number', 'unsteady_number', 'geometry_number']
|
507
|
-
|
508
|
-
# Standard plan key columns in the exact order specified
|
509
|
-
plan_key_cols = [
|
510
|
-
'Plan Title', 'Program Version', 'Short Identifier', 'Simulation Date',
|
511
|
-
'Std Step Tol', 'Computation Interval', 'Output Interval', 'Instantaneous Interval',
|
512
|
-
'Mapping Interval', 'Run HTab', 'Run UNet', 'Run Sediment', 'Run PostProcess',
|
513
|
-
'Run WQNet', 'Run RASMapper', 'UNET Use Existing IB Tables', 'HDF_Results_Path',
|
514
|
-
'UNET 1D Methodology', 'Write IC File', 'Write IC File at Fixed DateTime',
|
515
|
-
'IC Time', 'Write IC File Reoccurance', 'Write IC File at Sim End'
|
516
|
-
]
|
517
|
-
|
518
|
-
# Additional convenience columns
|
519
|
-
file_path_cols = ['Geom File', 'Geom Path', 'Flow File', 'Flow Path']
|
520
|
-
|
521
|
-
# Build the final column list
|
522
|
-
all_cols = first_cols.copy()
|
523
|
-
for col in plan_key_cols:
|
524
|
-
if col in df.columns:
|
525
|
-
all_cols.append(col)
|
526
|
-
|
527
|
-
# Add any remaining columns not explicitly specified
|
528
|
-
other_cols = [col for col in df.columns if col not in all_cols + file_path_cols + ['full_path']]
|
529
|
-
all_cols.extend(other_cols)
|
530
|
-
all_cols.extend(file_path_cols)
|
531
|
-
|
532
|
-
# Rename plan_number column
|
533
|
-
df = df.rename(columns={f'{entry_type.lower()}_number': 'plan_number'})
|
534
|
-
|
535
|
-
# Fill in missing columns with None
|
536
|
-
for col in all_cols:
|
537
|
-
if col not in df.columns:
|
538
|
-
df[col] = None
|
539
|
-
|
540
|
-
# Return DataFrame with specified column order
|
541
|
-
return df[all_cols]
|
542
510
|
|
543
|
-
|
511
|
+
df = pd.DataFrame(entries)
|
512
|
+
return self._format_dataframe(df, entry_type)
|
513
|
+
|
544
514
|
except Exception as e:
|
545
515
|
logger.error(f"Error in _get_prj_entries for {entry_type}: {e}")
|
546
516
|
raise
|
547
517
|
|
518
|
+
def _process_unsteady_entry(self, entry_number: str, full_path: str) -> dict:
|
519
|
+
"""Process unsteady entry data."""
|
520
|
+
entry = {'unsteady_number': entry_number}
|
521
|
+
unsteady_info = self._parse_unsteady_file(Path(full_path))
|
522
|
+
entry.update(unsteady_info)
|
523
|
+
return entry
|
524
|
+
|
525
|
+
def _process_default_entry(self) -> dict:
|
526
|
+
"""Process default entry data."""
|
527
|
+
return {
|
528
|
+
'unsteady_number': None,
|
529
|
+
'geometry_number': None
|
530
|
+
}
|
531
|
+
|
532
|
+
def _process_plan_entry(self, entry_number: str, full_path: str) -> dict:
|
533
|
+
"""Process plan entry data."""
|
534
|
+
entry = {}
|
535
|
+
plan_info = self._parse_plan_file(Path(full_path))
|
536
|
+
|
537
|
+
if plan_info:
|
538
|
+
entry.update(self._process_flow_file(plan_info))
|
539
|
+
entry.update(self._process_geom_file(plan_info))
|
540
|
+
|
541
|
+
# Add remaining plan info
|
542
|
+
for key, value in plan_info.items():
|
543
|
+
if key not in ['Flow File', 'Geom File']:
|
544
|
+
entry[key] = value
|
545
|
+
|
546
|
+
# Add HDF results path
|
547
|
+
hdf_results_path = self.project_folder / f"{self.project_name}.p{entry_number}.hdf"
|
548
|
+
entry['HDF_Results_Path'] = str(hdf_results_path) if hdf_results_path.exists() else None
|
549
|
+
|
550
|
+
return entry
|
551
|
+
|
552
|
+
def _process_flow_file(self, plan_info: dict) -> dict:
|
553
|
+
"""Process flow file information from plan info."""
|
554
|
+
flow_file = plan_info.get('Flow File')
|
555
|
+
if flow_file and flow_file.startswith('u'):
|
556
|
+
return {
|
557
|
+
'unsteady_number': flow_file[1:],
|
558
|
+
'Flow File': flow_file[1:]
|
559
|
+
}
|
560
|
+
return {
|
561
|
+
'unsteady_number': None,
|
562
|
+
'Flow File': flow_file[1:] if flow_file and flow_file.startswith('f') else None
|
563
|
+
}
|
564
|
+
|
565
|
+
def _process_geom_file(self, plan_info: dict) -> dict:
|
566
|
+
"""Process geometry file information from plan info."""
|
567
|
+
geom_file = plan_info.get('Geom File')
|
568
|
+
if geom_file and geom_file.startswith('g'):
|
569
|
+
return {
|
570
|
+
'geometry_number': geom_file[1:],
|
571
|
+
'Geom File': geom_file[1:]
|
572
|
+
}
|
573
|
+
return {
|
574
|
+
'geometry_number': None,
|
575
|
+
'Geom File': None
|
576
|
+
}
|
577
|
+
|
548
578
|
def _parse_unsteady_file(self, unsteady_file_path):
|
549
579
|
"""
|
550
580
|
Parse an unsteady flow file and extract critical information.
|
@@ -935,27 +965,144 @@ class RasPrj:
|
|
935
965
|
return bc_info, unparsed_lines
|
936
966
|
|
937
967
|
@log_call
|
938
|
-
def
|
968
|
+
def _format_dataframe(self, df, entry_type):
|
939
969
|
"""
|
940
|
-
|
970
|
+
Format the DataFrame according to the desired column structure.
|
971
|
+
|
972
|
+
Args:
|
973
|
+
df (pd.DataFrame): The DataFrame to format.
|
974
|
+
entry_type (str): The type of entry (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').
|
941
975
|
|
942
976
|
Returns:
|
943
|
-
|
944
|
-
|
945
|
-
|
977
|
+
pd.DataFrame: The formatted DataFrame.
|
978
|
+
"""
|
979
|
+
if df.empty:
|
980
|
+
return df
|
946
981
|
|
947
|
-
|
948
|
-
|
949
|
-
|
982
|
+
if entry_type == 'Plan':
|
983
|
+
# Set required column order
|
984
|
+
first_cols = ['plan_number', 'unsteady_number', 'geometry_number']
|
985
|
+
|
986
|
+
# Standard plan key columns in the exact order specified
|
987
|
+
plan_key_cols = [
|
988
|
+
'Plan Title', 'Program Version', 'Short Identifier', 'Simulation Date',
|
989
|
+
'Std Step Tol', 'Computation Interval', 'Output Interval', 'Instantaneous Interval',
|
990
|
+
'Mapping Interval', 'Run HTab', 'Run UNet', 'Run Sediment', 'Run PostProcess',
|
991
|
+
'Run WQNet', 'Run RASMapper', 'UNET Use Existing IB Tables', 'HDF_Results_Path',
|
992
|
+
'UNET 1D Methodology', 'Write IC File', 'Write IC File at Fixed DateTime',
|
993
|
+
'IC Time', 'Write IC File Reoccurance', 'Write IC File at Sim End'
|
994
|
+
]
|
995
|
+
|
996
|
+
# Additional convenience columns
|
997
|
+
file_path_cols = ['Geom File', 'Geom Path', 'Flow File', 'Flow Path']
|
998
|
+
|
999
|
+
# Special columns that must be preserved
|
1000
|
+
special_cols = ['HDF_Results_Path']
|
1001
|
+
|
1002
|
+
# Build the final column list
|
1003
|
+
all_cols = first_cols.copy()
|
1004
|
+
|
1005
|
+
# Add plan key columns if they exist
|
1006
|
+
for col in plan_key_cols:
|
1007
|
+
if col in df.columns and col not in all_cols and col not in special_cols:
|
1008
|
+
all_cols.append(col)
|
1009
|
+
|
1010
|
+
# Add any remaining columns not explicitly specified
|
1011
|
+
other_cols = [col for col in df.columns if col not in all_cols + file_path_cols + special_cols + ['full_path']]
|
1012
|
+
all_cols.extend(other_cols)
|
1013
|
+
|
1014
|
+
# Add HDF_Results_Path if it exists (ensure it comes before file paths)
|
1015
|
+
for special_col in special_cols:
|
1016
|
+
if special_col in df.columns and special_col not in all_cols:
|
1017
|
+
all_cols.append(special_col)
|
1018
|
+
|
1019
|
+
# Add file path columns at the end
|
1020
|
+
all_cols.extend(file_path_cols)
|
1021
|
+
|
1022
|
+
# Rename plan_number column
|
1023
|
+
df = df.rename(columns={f'{entry_type.lower()}_number': 'plan_number'})
|
1024
|
+
|
1025
|
+
# Fill in missing columns with None
|
1026
|
+
for col in all_cols:
|
1027
|
+
if col not in df.columns:
|
1028
|
+
df[col] = None
|
1029
|
+
|
1030
|
+
# Make sure full_path column is preserved and included
|
1031
|
+
if 'full_path' in df.columns and 'full_path' not in all_cols:
|
1032
|
+
all_cols.append('full_path')
|
1033
|
+
|
1034
|
+
# Return DataFrame with specified column order
|
1035
|
+
cols_to_return = [col for col in all_cols if col in df.columns]
|
1036
|
+
return df[cols_to_return]
|
1037
|
+
|
1038
|
+
return df
|
1039
|
+
|
1040
|
+
@log_call
|
1041
|
+
def _get_prj_entries(self, entry_type):
|
950
1042
|
"""
|
951
|
-
|
1043
|
+
Extract entries of a specific type from the HEC-RAS project file.
|
1044
|
+
"""
|
1045
|
+
entries = []
|
1046
|
+
pattern = re.compile(rf"{entry_type} File=(\w+)")
|
1047
|
+
|
1048
|
+
try:
|
1049
|
+
with open(self.prj_file, 'r') as file:
|
1050
|
+
for line in file:
|
1051
|
+
match = pattern.match(line.strip())
|
1052
|
+
if match:
|
1053
|
+
file_name = match.group(1)
|
1054
|
+
full_path = str(self.project_folder / f"{self.project_name}.{file_name}")
|
1055
|
+
entry = self._create_entry(entry_type, file_name, full_path)
|
1056
|
+
entries.append(entry)
|
952
1057
|
|
953
|
-
|
954
|
-
unsteady_plans = self.plan_df[self.plan_df['unsteady_number'].notna()].copy()
|
1058
|
+
return self._format_dataframe(pd.DataFrame(entries), entry_type)
|
955
1059
|
|
956
|
-
|
1060
|
+
except Exception as e:
|
1061
|
+
logger.error(f"Error in _get_prj_entries for {entry_type}: {e}")
|
1062
|
+
raise
|
1063
|
+
|
1064
|
+
def _create_entry(self, entry_type, file_name, full_path):
|
1065
|
+
"""Helper method to create entry dictionary."""
|
1066
|
+
entry_number = file_name[1:]
|
1067
|
+
entry = {
|
1068
|
+
f'{entry_type.lower()}_number': entry_number,
|
1069
|
+
'full_path': full_path,
|
1070
|
+
'unsteady_number': None,
|
1071
|
+
'geometry_number': None
|
1072
|
+
}
|
1073
|
+
|
1074
|
+
if entry_type == 'Unsteady':
|
1075
|
+
entry['unsteady_number'] = entry_number
|
1076
|
+
entry.update(self._parse_unsteady_file(Path(full_path)))
|
1077
|
+
elif entry_type == 'Plan':
|
1078
|
+
self._update_plan_entry(entry, entry_number, full_path)
|
957
1079
|
|
958
|
-
return
|
1080
|
+
return entry
|
1081
|
+
|
1082
|
+
def _update_plan_entry(self, entry, entry_number, full_path):
|
1083
|
+
"""Helper method to update plan entry with additional information."""
|
1084
|
+
plan_info = self._parse_plan_file(Path(full_path))
|
1085
|
+
if plan_info:
|
1086
|
+
# Handle Flow File
|
1087
|
+
flow_file = plan_info.get('Flow File')
|
1088
|
+
if flow_file:
|
1089
|
+
if flow_file.startswith('u'):
|
1090
|
+
entry.update({'unsteady_number': flow_file[1:], 'Flow File': flow_file[1:]})
|
1091
|
+
else:
|
1092
|
+
entry['Flow File'] = flow_file[1:] if flow_file.startswith('f') else None
|
1093
|
+
|
1094
|
+
# Handle Geom File
|
1095
|
+
geom_file = plan_info.get('Geom File')
|
1096
|
+
if geom_file and geom_file.startswith('g'):
|
1097
|
+
entry.update({'geometry_number': geom_file[1:], 'Geom File': geom_file[1:]})
|
1098
|
+
|
1099
|
+
# Add remaining plan info
|
1100
|
+
entry.update({k: v for k, v in plan_info.items() if k not in ['Flow File', 'Geom File']})
|
1101
|
+
|
1102
|
+
# Add HDF results path
|
1103
|
+
hdf_path = self.project_folder / f"{self.project_name}.p{entry_number}.hdf"
|
1104
|
+
entry['HDF_Results_Path'] = str(hdf_path) if hdf_path.exists() else None
|
1105
|
+
|
959
1106
|
|
960
1107
|
# Create a global instance named 'ras'
|
961
1108
|
# Defining the global instance allows the init_ras_project function to initialize the project.
|
@@ -1027,7 +1174,7 @@ def get_ras_exe(ras_version=None):
|
|
1027
1174
|
Args:
|
1028
1175
|
ras_version (str, optional): Either a version number or a full path to the HEC-RAS executable.
|
1029
1176
|
If None, the function will first check the global 'ras' object for a path.
|
1030
|
-
|
1177
|
+
or a default path.
|
1031
1178
|
|
1032
1179
|
Returns:
|
1033
1180
|
str: The full path to the HEC-RAS executable.
|
ras_commander/RasUtils.py
CHANGED
@@ -216,30 +216,49 @@ class RasUtils:
|
|
216
216
|
Returns:
|
217
217
|
Path: Full path to the plan file
|
218
218
|
|
219
|
+
Raises:
|
220
|
+
ValueError: If plan number is not between 1 and 99
|
221
|
+
TypeError: If input type is invalid
|
222
|
+
FileNotFoundError: If the plan file does not exist
|
223
|
+
|
219
224
|
Example:
|
220
225
|
>>> plan_path = RasUtils.get_plan_path(1)
|
221
226
|
>>> print(f"Plan file path: {plan_path}")
|
222
227
|
>>> plan_path = RasUtils.get_plan_path("path/to/plan.p01")
|
223
228
|
>>> print(f"Plan file path: {plan_path}")
|
224
229
|
"""
|
225
|
-
|
230
|
+
# Validate RAS object
|
226
231
|
ras_obj = ras_object or ras
|
227
232
|
ras_obj.check_initialized()
|
228
233
|
|
234
|
+
# Handle direct file path input
|
229
235
|
plan_path = Path(current_plan_number_or_path)
|
230
236
|
if plan_path.is_file():
|
231
237
|
logger.info(f"Using provided plan file path: {plan_path}")
|
232
238
|
return plan_path
|
233
239
|
|
240
|
+
# Handle plan number input
|
234
241
|
try:
|
235
|
-
|
242
|
+
plan_num = int(current_plan_number_or_path)
|
243
|
+
if not 1 <= plan_num <= 99:
|
244
|
+
raise ValueError(f"Plan number must be between 1 and 99, got: {plan_num}")
|
245
|
+
current_plan_number = f"{plan_num:02d}" # Ensure two-digit format
|
236
246
|
logger.debug(f"Converted plan number to two-digit format: {current_plan_number}")
|
237
|
-
except ValueError:
|
247
|
+
except (ValueError, TypeError) as e:
|
248
|
+
if isinstance(e, TypeError):
|
249
|
+
logger.error(f"Invalid input type: {type(current_plan_number_or_path)}. Expected string, number, or Path.")
|
250
|
+
raise TypeError(f"Invalid input type: {type(current_plan_number_or_path)}. Expected string, number, or Path.")
|
238
251
|
logger.error(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
|
239
252
|
raise ValueError(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
|
240
253
|
|
254
|
+
# Construct and validate plan path
|
241
255
|
plan_name = f"{ras_obj.project_name}.p{current_plan_number}"
|
242
256
|
full_plan_path = ras_obj.project_folder / plan_name
|
257
|
+
|
258
|
+
if not full_plan_path.exists():
|
259
|
+
logger.error(f"Plan file does not exist: {full_plan_path}")
|
260
|
+
raise FileNotFoundError(f"Plan file does not exist: {full_plan_path}")
|
261
|
+
|
243
262
|
logger.info(f"Constructed plan file path: {full_plan_path}")
|
244
263
|
return full_plan_path
|
245
264
|
|
ras_commander/__init__.py
CHANGED