ras-commander 0.65.0__py3-none-any.whl → 0.66.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 +1487 -1468
- ras_commander/RasPrj.py +262 -165
- ras_commander/RasUtils.py +22 -3
- ras_commander/__init__.py +1 -1
- {ras_commander-0.65.0.dist-info → ras_commander-0.66.0.dist-info}/METADATA +1 -1
- {ras_commander-0.65.0.dist-info → ras_commander-0.66.0.dist-info}/RECORD +9 -9
- {ras_commander-0.65.0.dist-info → ras_commander-0.66.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.65.0.dist-info → ras_commander-0.66.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.65.0.dist-info → ras_commander-0.66.0.dist-info}/top_level.txt +0 -0
ras_commander/RasPrj.py
CHANGED
@@ -160,101 +160,96 @@ 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
|
+
except Exception as e:
|
179
|
+
logger.error(f"Error loading project data: {e}")
|
180
|
+
raise
|
181
|
+
|
182
|
+
def _ensure_required_columns(self):
|
183
|
+
"""Ensure all required columns exist in plan_df."""
|
184
|
+
required_columns = [
|
185
|
+
'plan_number', 'unsteady_number', 'geometry_number',
|
186
|
+
'Geom File', 'Geom Path', 'Flow File', 'Flow Path', 'full_path'
|
187
|
+
]
|
172
188
|
|
173
|
-
|
189
|
+
for col in required_columns:
|
190
|
+
if col not in self.plan_df.columns:
|
191
|
+
self.plan_df[col] = None
|
192
|
+
|
193
|
+
if not self.plan_df['full_path'].any():
|
194
|
+
self.plan_df['full_path'] = self.plan_df['plan_number'].apply(
|
195
|
+
lambda x: str(self.project_folder / f"{self.project_name}.p{x}")
|
196
|
+
)
|
197
|
+
|
198
|
+
def _set_file_paths(self):
|
199
|
+
"""Set geometry and flow paths in plan_df."""
|
174
200
|
for idx, row in self.plan_df.iterrows():
|
175
201
|
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)
|
180
|
-
|
181
|
-
# Add Flow Path
|
182
|
-
if row['Flow File'] is not None:
|
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)
|
202
|
+
self._set_geom_path(idx, row)
|
203
|
+
self._set_flow_path(idx, row)
|
189
204
|
|
190
|
-
|
191
|
-
|
205
|
+
if not self.suppress_logging:
|
206
|
+
logger.info(f"Plan {row['plan_number']} paths set up")
|
192
207
|
except Exception as e:
|
193
208
|
logger.error(f"Error processing plan file {row['plan_number']}: {e}")
|
194
209
|
|
195
|
-
def
|
196
|
-
"""
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
216
|
-
|
217
|
-
try:
|
218
|
-
match = re.search(r'Geom File=g(\d+)', content)
|
219
|
-
if match:
|
220
|
-
geom_number = match.group(1) # This gets just the number after 'g'
|
221
|
-
geom_file = f"g{geom_number}"
|
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}")
|
227
|
-
|
228
|
-
return None, None
|
210
|
+
def _set_geom_path(self, idx: int, row: pd.Series):
|
211
|
+
"""Set geometry path for a plan entry."""
|
212
|
+
if pd.notna(row['Geom File']):
|
213
|
+
geom_path = self.project_folder / f"{self.project_name}.g{row['Geom File']}"
|
214
|
+
self.plan_df.at[idx, 'Geom Path'] = str(geom_path)
|
215
|
+
|
216
|
+
def _set_flow_path(self, idx: int, row: pd.Series):
|
217
|
+
"""Set flow path for a plan entry."""
|
218
|
+
if pd.notna(row['Flow File']):
|
219
|
+
prefix = 'u' if pd.notna(row['unsteady_number']) else 'f'
|
220
|
+
flow_path = self.project_folder / f"{self.project_name}.{prefix}{row['Flow File']}"
|
221
|
+
self.plan_df.at[idx, 'Flow Path'] = str(flow_path)
|
229
222
|
|
230
|
-
def
|
223
|
+
def _get_geom_file_for_plan(self, plan_number):
|
231
224
|
"""
|
232
|
-
|
225
|
+
Get the geometry file path for a given plan number.
|
233
226
|
|
234
|
-
|
235
|
-
|
236
|
-
plan_file_path : str or Path
|
237
|
-
Path to the plan file.
|
227
|
+
Args:
|
228
|
+
plan_number (str): The plan number to find the geometry file for.
|
238
229
|
|
239
230
|
Returns:
|
240
|
-
|
241
|
-
str or None
|
242
|
-
The Flow File value or None if not found.
|
231
|
+
str: The full path to the geometry HDF file, or None if not found.
|
243
232
|
"""
|
233
|
+
plan_file_path = self.project_folder / f"{self.project_name}.p{plan_number}"
|
244
234
|
content, encoding = read_file_with_fallback_encoding(plan_file_path)
|
245
235
|
|
246
236
|
if content is None:
|
247
237
|
return None
|
248
238
|
|
249
239
|
try:
|
250
|
-
|
251
|
-
|
252
|
-
|
240
|
+
for line in content.splitlines():
|
241
|
+
if line.startswith("Geom File="):
|
242
|
+
geom_file = line.strip().split('=')[1]
|
243
|
+
geom_hdf_path = self.project_folder / f"{self.project_name}.{geom_file}.hdf"
|
244
|
+
if geom_hdf_path.exists():
|
245
|
+
return str(geom_hdf_path)
|
246
|
+
else:
|
247
|
+
return None
|
253
248
|
except Exception as e:
|
254
|
-
logger.error(f"Error
|
255
|
-
|
249
|
+
logger.error(f"Error reading plan file for geometry: {e}")
|
256
250
|
return None
|
257
251
|
|
252
|
+
|
258
253
|
@staticmethod
|
259
254
|
@log_call
|
260
255
|
def get_plan_value(
|
@@ -439,112 +434,106 @@ class RasPrj:
|
|
439
434
|
|
440
435
|
Returns:
|
441
436
|
pd.DataFrame: A DataFrame containing the extracted entries.
|
437
|
+
|
438
|
+
Raises:
|
439
|
+
Exception: If there's an error reading or processing the project file.
|
442
440
|
"""
|
443
441
|
entries = []
|
444
442
|
pattern = re.compile(rf"{entry_type} File=(\w+)")
|
445
443
|
|
446
444
|
try:
|
447
|
-
with open(self.prj_file, 'r') as file:
|
445
|
+
with open(self.prj_file, 'r', encoding='utf-8') as file:
|
448
446
|
for line in file:
|
449
447
|
match = pattern.match(line.strip())
|
450
448
|
if match:
|
451
449
|
file_name = match.group(1)
|
452
450
|
full_path = str(self.project_folder / f"{self.project_name}.{file_name}")
|
453
|
-
entry_number = file_name[1:]
|
451
|
+
entry_number = file_name[1:]
|
454
452
|
|
455
453
|
entry = {
|
456
454
|
f'{entry_type.lower()}_number': entry_number,
|
457
455
|
'full_path': full_path
|
458
456
|
}
|
459
457
|
|
458
|
+
# Handle Unsteady entries
|
460
459
|
if entry_type == 'Unsteady':
|
461
|
-
entry
|
462
|
-
unsteady_info = self._parse_unsteady_file(Path(full_path))
|
463
|
-
entry.update(unsteady_info)
|
460
|
+
entry.update(self._process_unsteady_entry(entry_number, full_path))
|
464
461
|
else:
|
465
|
-
entry.update(
|
466
|
-
'unsteady_number': None,
|
467
|
-
'geometry_number': None
|
468
|
-
})
|
462
|
+
entry.update(self._process_default_entry())
|
469
463
|
|
464
|
+
# Handle Plan entries
|
470
465
|
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
|
466
|
+
entry.update(self._process_plan_entry(entry_number, full_path))
|
495
467
|
|
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
468
|
entries.append(entry)
|
501
|
-
|
502
|
-
df = pd.DataFrame(entries)
|
503
469
|
|
504
|
-
|
505
|
-
|
506
|
-
|
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
|
-
|
543
|
-
return df
|
470
|
+
df = pd.DataFrame(entries)
|
471
|
+
return self._format_dataframe(df, entry_type)
|
472
|
+
|
544
473
|
except Exception as e:
|
545
474
|
logger.error(f"Error in _get_prj_entries for {entry_type}: {e}")
|
546
475
|
raise
|
547
476
|
|
477
|
+
def _process_unsteady_entry(self, entry_number: str, full_path: str) -> dict:
|
478
|
+
"""Process unsteady entry data."""
|
479
|
+
entry = {'unsteady_number': entry_number}
|
480
|
+
unsteady_info = self._parse_unsteady_file(Path(full_path))
|
481
|
+
entry.update(unsteady_info)
|
482
|
+
return entry
|
483
|
+
|
484
|
+
def _process_default_entry(self) -> dict:
|
485
|
+
"""Process default entry data."""
|
486
|
+
return {
|
487
|
+
'unsteady_number': None,
|
488
|
+
'geometry_number': None
|
489
|
+
}
|
490
|
+
|
491
|
+
def _process_plan_entry(self, entry_number: str, full_path: str) -> dict:
|
492
|
+
"""Process plan entry data."""
|
493
|
+
entry = {}
|
494
|
+
plan_info = self._parse_plan_file(Path(full_path))
|
495
|
+
|
496
|
+
if plan_info:
|
497
|
+
entry.update(self._process_flow_file(plan_info))
|
498
|
+
entry.update(self._process_geom_file(plan_info))
|
499
|
+
|
500
|
+
# Add remaining plan info
|
501
|
+
for key, value in plan_info.items():
|
502
|
+
if key not in ['Flow File', 'Geom File']:
|
503
|
+
entry[key] = value
|
504
|
+
|
505
|
+
# Add HDF results path
|
506
|
+
hdf_results_path = self.project_folder / f"{self.project_name}.p{entry_number}.hdf"
|
507
|
+
entry['HDF_Results_Path'] = str(hdf_results_path) if hdf_results_path.exists() else None
|
508
|
+
|
509
|
+
return entry
|
510
|
+
|
511
|
+
def _process_flow_file(self, plan_info: dict) -> dict:
|
512
|
+
"""Process flow file information from plan info."""
|
513
|
+
flow_file = plan_info.get('Flow File')
|
514
|
+
if flow_file and flow_file.startswith('u'):
|
515
|
+
return {
|
516
|
+
'unsteady_number': flow_file[1:],
|
517
|
+
'Flow File': flow_file[1:]
|
518
|
+
}
|
519
|
+
return {
|
520
|
+
'unsteady_number': None,
|
521
|
+
'Flow File': flow_file[1:] if flow_file and flow_file.startswith('f') else None
|
522
|
+
}
|
523
|
+
|
524
|
+
def _process_geom_file(self, plan_info: dict) -> dict:
|
525
|
+
"""Process geometry file information from plan info."""
|
526
|
+
geom_file = plan_info.get('Geom File')
|
527
|
+
if geom_file and geom_file.startswith('g'):
|
528
|
+
return {
|
529
|
+
'geometry_number': geom_file[1:],
|
530
|
+
'Geom File': geom_file[1:]
|
531
|
+
}
|
532
|
+
return {
|
533
|
+
'geometry_number': None,
|
534
|
+
'Geom File': None
|
535
|
+
}
|
536
|
+
|
548
537
|
def _parse_unsteady_file(self, unsteady_file_path):
|
549
538
|
"""
|
550
539
|
Parse an unsteady flow file and extract critical information.
|
@@ -935,27 +924,135 @@ class RasPrj:
|
|
935
924
|
return bc_info, unparsed_lines
|
936
925
|
|
937
926
|
@log_call
|
938
|
-
def
|
927
|
+
def _format_dataframe(self, df, entry_type):
|
939
928
|
"""
|
940
|
-
|
929
|
+
Format the DataFrame according to the desired column structure.
|
930
|
+
|
931
|
+
Args:
|
932
|
+
df (pd.DataFrame): The DataFrame to format.
|
933
|
+
entry_type (str): The type of entry (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').
|
941
934
|
|
942
935
|
Returns:
|
943
|
-
|
944
|
-
|
945
|
-
|
936
|
+
pd.DataFrame: The formatted DataFrame.
|
937
|
+
"""
|
938
|
+
if df.empty:
|
939
|
+
return df
|
946
940
|
|
947
|
-
|
948
|
-
|
949
|
-
|
941
|
+
if entry_type == 'Plan':
|
942
|
+
# Define column groups
|
943
|
+
first_cols = ['plan_number', 'unsteady_number', 'geometry_number']
|
944
|
+
plan_key_cols = [
|
945
|
+
'Plan Title', 'Program Version', 'Short Identifier', 'Simulation Date',
|
946
|
+
'Computation Interval', 'Mapping Interval', 'Run HTab', 'Run UNet',
|
947
|
+
'Run Sediment', 'Run PostProcess', 'Run WQNet', 'UNET Use Existing IB Tables',
|
948
|
+
'HDF_Results_Path', 'UNET 1D Methodology'
|
949
|
+
]
|
950
|
+
file_path_cols = ['Geom File', 'Geom Path', 'Flow File', 'Flow Path']
|
951
|
+
|
952
|
+
# Build column list
|
953
|
+
all_cols = first_cols.copy()
|
954
|
+
all_cols.extend([col for col in plan_key_cols if col in df.columns])
|
955
|
+
all_cols.extend([col for col in df.columns if col not in all_cols + file_path_cols + ['full_path']])
|
956
|
+
all_cols.extend(file_path_cols)
|
957
|
+
|
958
|
+
# Rename and fill missing columns
|
959
|
+
df = df.rename(columns={f'{entry_type.lower()}_number': 'plan_number'})
|
960
|
+
for col in all_cols:
|
961
|
+
if col not in df.columns:
|
962
|
+
df[col] = None
|
963
|
+
|
964
|
+
# Add full_path if present
|
965
|
+
if 'full_path' in df.columns:
|
966
|
+
all_cols.append('full_path')
|
967
|
+
|
968
|
+
return df[[col for col in all_cols if col in df.columns]]
|
969
|
+
|
970
|
+
return df
|
971
|
+
|
972
|
+
@log_call
|
973
|
+
def _get_prj_entries(self, entry_type):
|
950
974
|
"""
|
951
|
-
|
975
|
+
Extract entries of a specific type from the HEC-RAS project file.
|
976
|
+
"""
|
977
|
+
entries = []
|
978
|
+
pattern = re.compile(rf"{entry_type} File=(\w+)")
|
979
|
+
|
980
|
+
try:
|
981
|
+
with open(self.prj_file, 'r') as file:
|
982
|
+
for line in file:
|
983
|
+
match = pattern.match(line.strip())
|
984
|
+
if match:
|
985
|
+
file_name = match.group(1)
|
986
|
+
full_path = str(self.project_folder / f"{self.project_name}.{file_name}")
|
987
|
+
entry = self._create_entry(entry_type, file_name, full_path)
|
988
|
+
entries.append(entry)
|
952
989
|
|
953
|
-
|
954
|
-
unsteady_plans = self.plan_df[self.plan_df['unsteady_number'].notna()].copy()
|
990
|
+
return self._format_dataframe(pd.DataFrame(entries), entry_type)
|
955
991
|
|
956
|
-
|
992
|
+
except Exception as e:
|
993
|
+
logger.error(f"Error in _get_prj_entries for {entry_type}: {e}")
|
994
|
+
raise
|
995
|
+
|
996
|
+
def _create_entry(self, entry_type, file_name, full_path):
|
997
|
+
"""Helper method to create entry dictionary."""
|
998
|
+
entry_number = file_name[1:]
|
999
|
+
entry = {
|
1000
|
+
f'{entry_type.lower()}_number': entry_number,
|
1001
|
+
'full_path': full_path,
|
1002
|
+
'unsteady_number': None,
|
1003
|
+
'geometry_number': None
|
1004
|
+
}
|
1005
|
+
|
1006
|
+
if entry_type == 'Unsteady':
|
1007
|
+
entry['unsteady_number'] = entry_number
|
1008
|
+
entry.update(self._parse_unsteady_file(Path(full_path)))
|
1009
|
+
elif entry_type == 'Plan':
|
1010
|
+
self._update_plan_entry(entry, entry_number, full_path)
|
1011
|
+
|
1012
|
+
return entry
|
1013
|
+
|
1014
|
+
def _update_plan_entry(self, entry, entry_number, full_path):
|
1015
|
+
"""Helper method to update plan entry with additional information."""
|
1016
|
+
plan_info = self._parse_plan_file(Path(full_path))
|
1017
|
+
if plan_info:
|
1018
|
+
# Handle Flow File
|
1019
|
+
flow_file = plan_info.get('Flow File')
|
1020
|
+
if flow_file:
|
1021
|
+
if flow_file.startswith('u'):
|
1022
|
+
entry.update({'unsteady_number': flow_file[1:], 'Flow File': flow_file[1:]})
|
1023
|
+
else:
|
1024
|
+
entry['Flow File'] = flow_file[1:] if flow_file.startswith('f') else None
|
1025
|
+
|
1026
|
+
# Handle Geom File
|
1027
|
+
geom_file = plan_info.get('Geom File')
|
1028
|
+
if geom_file and geom_file.startswith('g'):
|
1029
|
+
entry.update({'geometry_number': geom_file[1:], 'Geom File': geom_file[1:]})
|
1030
|
+
|
1031
|
+
# Add remaining plan info
|
1032
|
+
entry.update({k: v for k, v in plan_info.items() if k not in ['Flow File', 'Geom File']})
|
1033
|
+
|
1034
|
+
# Add HDF results path
|
1035
|
+
hdf_path = self.project_folder / f"{self.project_name}.p{entry_number}.hdf"
|
1036
|
+
entry['HDF_Results_Path'] = str(hdf_path) if hdf_path.exists() else None
|
1037
|
+
|
1038
|
+
def _set_plan_paths(self):
|
1039
|
+
"""Helper method to set paths in plan_df."""
|
1040
|
+
if not self.plan_df['full_path'].any():
|
1041
|
+
self.plan_df['full_path'] = self.plan_df['plan_number'].apply(
|
1042
|
+
lambda x: str(self.project_folder / f"{self.project_name}.p{x}")
|
1043
|
+
)
|
957
1044
|
|
958
|
-
|
1045
|
+
for idx, row in self.plan_df.iterrows():
|
1046
|
+
if pd.notna(row['Geom File']) and pd.isna(row.get('Geom Path')):
|
1047
|
+
self.plan_df.at[idx, 'Geom Path'] = str(self.project_folder / f"{self.project_name}.g{row['Geom File']}")
|
1048
|
+
|
1049
|
+
if pd.notna(row['Flow File']) and pd.isna(row.get('Flow Path')):
|
1050
|
+
prefix = 'u' if pd.notna(row['unsteady_number']) else 'f'
|
1051
|
+
self.plan_df.at[idx, 'Flow Path'] = str(self.project_folder / f"{self.project_name}.{prefix}{row['Flow File']}")
|
1052
|
+
|
1053
|
+
if not self.suppress_logging:
|
1054
|
+
logger.info(f"Plan {row['plan_number']} paths set up")
|
1055
|
+
|
959
1056
|
|
960
1057
|
# Create a global instance named 'ras'
|
961
1058
|
# Defining the global instance allows the init_ras_project function to initialize the project.
|
@@ -1027,7 +1124,7 @@ def get_ras_exe(ras_version=None):
|
|
1027
1124
|
Args:
|
1028
1125
|
ras_version (str, optional): Either a version number or a full path to the HEC-RAS executable.
|
1029
1126
|
If None, the function will first check the global 'ras' object for a path.
|
1030
|
-
|
1127
|
+
or a default path.
|
1031
1128
|
|
1032
1129
|
Returns:
|
1033
1130
|
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
@@ -21,14 +21,14 @@ ras_commander/RasExamples.py,sha256=6IZ96LcAsk5LYFehdD0zDW5wyZWxQa6OQu2N9upxWXA,
|
|
21
21
|
ras_commander/RasGeo.py,sha256=M0sVNKlWmmbve8iMXLWq25WgbxqLWBo7_1oDg_rALzU,5607
|
22
22
|
ras_commander/RasGpt.py,sha256=N_7p2nucWrBBXdB2k2ZKvOeOdXNmFD9dIY3W7_5i5nw,1206
|
23
23
|
ras_commander/RasMapper.py,sha256=LO_blvQnd4pwkEU8A30-RoE-CYIoU3s_fNLHBoM8ljw,613
|
24
|
-
ras_commander/RasPlan.py,sha256=
|
25
|
-
ras_commander/RasPrj.py,sha256=
|
24
|
+
ras_commander/RasPlan.py,sha256=0THxeB9ldeDuS4of2ruYG7Abbc5jz2729y5qOHntMuI,61664
|
25
|
+
ras_commander/RasPrj.py,sha256=gQoGG1Mqsoj5W1oVlulqhA0kQ5eD69vI96uihnY-kLE,47744
|
26
26
|
ras_commander/RasToGo.py,sha256=TKujfaV1xQhFaOddF4g2ogGy6ky-CLlfelSMPD2J3Nk,1223
|
27
27
|
ras_commander/RasUnsteady.py,sha256=KfCXAag-_bPwwS3JbPZH-s4hbaoHACO0mlRnGrzbFgA,32092
|
28
|
-
ras_commander/RasUtils.py,sha256=
|
29
|
-
ras_commander/__init__.py,sha256=
|
30
|
-
ras_commander-0.
|
31
|
-
ras_commander-0.
|
32
|
-
ras_commander-0.
|
33
|
-
ras_commander-0.
|
34
|
-
ras_commander-0.
|
28
|
+
ras_commander/RasUtils.py,sha256=0fm4IIs0LH1dgDj3pGd66mR82DhWLEkRKUvIo2M_5X0,35886
|
29
|
+
ras_commander/__init__.py,sha256=KICL5peRDDjE2BXEFVDMmtDZ4TKHtNhHh5eQgTSdqqs,2001
|
30
|
+
ras_commander-0.66.0.dist-info/LICENSE,sha256=_pbd6qHnlsz1iQ-ozDW_49r86BZT6CRwO2iBtw0iN6M,457
|
31
|
+
ras_commander-0.66.0.dist-info/METADATA,sha256=-jPNaFlPs0iCcscfGjO31DwF3-LT632rbiunJQmYzoM,26233
|
32
|
+
ras_commander-0.66.0.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
33
|
+
ras_commander-0.66.0.dist-info/top_level.txt,sha256=i76S7eKLFC8doKcXDl3aiOr9RwT06G8adI6YuKbQDaA,14
|
34
|
+
ras_commander-0.66.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|