Skip to content

Commit 2db832f

Browse files
authored
Merge pull request #76 from dav-og/fix/CLI_bugs
CLI fixes - progress bar, parsing wave inputs, adding some irregular data to .h5 files & GUI button bugfix
2 parents d3689d9 + 315578c commit 2db832f

12 files changed

Lines changed: 221 additions & 61 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ endif()
2020
# ═══════════════════════════════════════════════════════════════════════════════
2121

2222
project(HydroChrono
23-
VERSION 0.3.1
23+
VERSION 0.3.2
2424
DESCRIPTION "Hydrodynamics for Project Chrono."
2525
LANGUAGES CXX
2626
)

include/hydroc/hydro_forces.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ class TestHydro {
218218
* @return 6N dimensional force for 6 DOF and N bodies in system (already Eigen type).
219219
*/
220220
Eigen::VectorXd ComputeForceWaves();
221+
// Expose the wave object (read-only) so callers can query inputs if needed
222+
std::shared_ptr<WaveBase> GetWave() const { return user_waves_; }
221223

222224
/**
223225
* @brief Fetches the RIRF value from the h5 file based on the provided indices.

include/hydroc/simulation_exporter.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ class SimulationExporter {
133133
*/
134134
void Finalize();
135135

136+
/**
137+
* @brief Write irregular wave input arrays under /inputs/simulation/waves/irregular.
138+
*
139+
* All arrays are 1D and written with attributes documenting units.
140+
*/
141+
void WriteIrregularInputs(const std::vector<double>& frequencies_hz,
142+
const std::vector<double>& spectral_densities,
143+
const std::vector<double>& free_surface_time,
144+
const std::vector<double>& free_surface_eta);
145+
136146

137147

138148
// Runtime metadata (written into /meta/run at Finalize)

include/hydroc/wave_types.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,14 @@ class IrregularWaves : public WaveBase {
297297
void Initialize() override {}
298298

299299
void CreateSpectrum();
300+
// Returns spectral density values S(f) (one per frequency bin)
300301
std::vector<double> GetSpectrum();
302+
// Returns precomputed free-surface elevation eta(t) samples
301303
std::vector<double> GetFreeSurfaceElevation();
302-
std::vector<double> GetEtaTimeData();
304+
// Returns time samples corresponding to eta(t)
305+
std::vector<double> GetFreeSurfaceTime() const;
306+
// Returns the frequency bins (Hz)
307+
std::vector<double> GetFrequenciesHz() const;
303308

304309
Eigen::VectorXd GetForceAtTime(double t) override;
305310

src/gui/guihelper.cpp

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ class hydroc::gui::GUIImpl::MyActionReceiver : public irr::IEventReceiver {
7777
private:
7878
chrono::irrlicht::ChVisualSystemIrrlicht* vis;
7979
irr::gui::IGUIButton* pauseButton;
80-
irr::gui::IGUIStaticText* buttonText;
8180

8281
bool& pressed;
8382
};
@@ -131,11 +130,24 @@ void GUIImpl::Init(UI& ui, chrono::ChSystem* system, const char* title) {
131130
}
132131

133132
try {
134-
hydroc::debug::LogDebug("🔍 GUIImpl::Init - Setting up event receiver...");
133+
hydroc::debug::LogDebug("🔍 GUIImpl::Init - Setting up GUI skin and event receiver...");
134+
// Improve GUI readability: use built-in font for crisper text
135+
try {
136+
auto* env = pVis->GetGUIEnvironment();
137+
auto* skin = env ? env->getSkin() : nullptr;
138+
if (env && skin) {
139+
auto* builtin = env->getBuiltInFont();
140+
if (builtin) {
141+
skin->setFont(builtin);
142+
}
143+
}
144+
} catch (...) {
145+
// non-fatal if font override fails
146+
}
135147
InitReceiver(ui.simulationStarted);
136148
receiver->Init(pVis.get());
137149
pVis->AddUserEventReceiver(receiver.get());
138-
hydroc::debug::LogDebug("Event receiver set up successfully");
150+
hydroc::debug::LogDebug("GUI skin and event receiver set up successfully");
139151
} catch (const std::exception& e) {
140152
hydroc::cli::LogError(std::string("🔥 Exception during receiver setup: ") + e.what());
141153
// Don't re-throw here, receiver is optional
@@ -288,8 +300,18 @@ void hydroc::gui::GUIImpl::MyActionReceiver::Init(chrono::irrlicht::ChVisualSyst
288300
vis = vsys;
289301

290302
// ..add a GUI button to control pause/play
291-
pauseButton = vis->GetGUIEnvironment()->addButton(rect<s32>(510, 20, 650, 35));
292-
buttonText = vis->GetGUIEnvironment()->addStaticText(L"Paused", rect<s32>(560, 20, 600, 35), false);
303+
pauseButton = vis->GetGUIEnvironment()->addButton(rect<s32>(500, 18, 700, 50));
304+
pauseButton->setIsPushButton(true);
305+
pauseButton->setScaleImage(true);
306+
// Increase text visibility by adjusting the skin font was done in Init; ensure border size is sane
307+
if (auto* skin = vis->GetGUIEnvironment()->getSkin()) {
308+
skin->setSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X, 2);
309+
skin->setSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y, 2);
310+
// Optional: increase button text distance to border for clarity
311+
skin->setSize(irr::gui::EGDS_TEXT_DISTANCE_X, 6);
312+
skin->setSize(irr::gui::EGDS_TEXT_DISTANCE_Y, 3);
313+
}
314+
pauseButton->setText(L"Paused");
293315
}
294316

295317
bool hydroc::gui::GUIImpl::MyActionReceiver::OnEvent(const irr::SEvent& event) {
@@ -299,9 +321,9 @@ bool hydroc::gui::GUIImpl::MyActionReceiver::OnEvent(const irr::SEvent& event) {
299321
case EGUI_EVENT_TYPE::EGET_BUTTON_CLICKED:
300322
pressed = !pressed;
301323
if (pressed) {
302-
buttonText->setText(L"Playing");
324+
pauseButton->setText(L"Playing");
303325
} else {
304-
buttonText->setText(L"Paused");
326+
pauseButton->setText(L"Paused");
305327
}
306328
return pressed;
307329
break;

src/hydro_forces.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ TestHydro::TestHydro(std::vector<std::shared_ptr<ChBody>> user_bodies,
236236
// Set up hydro inputs
237237
user_waves_ = waves;
238238
AddWaves(user_waves_);
239+
240+
// If irregular waves are active, publish spectrum and free-surface inputs into HDF5 if an exporter is present.
241+
// The exporter is managed by the runner; here we only expose getters via the waves object.
239242
}
240243

241244
void TestHydro::AddWaves(std::shared_ptr<WaveBase> waves) {

src/hydro_yaml_parser.cpp

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
178178
bool period_form_linspace = false;
179179
bool period_form_range = false;
180180

181+
// Wave convenience fields
182+
bool waves_amplitude_set = false;
183+
double waves_amplitude = 0.0;
184+
bool period_set_by_synonym = false; // scalar period given via t/p/tp
185+
181186
auto parse_inline_brace_kv = [](const std::string& v) -> std::vector<std::pair<std::string, std::string>> {
182187
// Expect something like: { start: 6.0, stop: 9.0, num: 4 }
183188
std::vector<std::pair<std::string, std::string>> out;
@@ -390,11 +395,18 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
390395
}
391396
} else if (in_waves) {
392397
// Parse wave properties
393-
if (!in_period_block && key == "type") {
398+
// normalize key for shorthand handling
399+
std::string key_lower = key;
400+
std::transform(key_lower.begin(), key_lower.end(), key_lower.begin(), ::tolower);
401+
402+
if (!in_period_block && key_lower == "type") {
394403
data.waves.type = value;
395-
} else if (!in_period_block && key == "height") {
404+
} else if (!in_period_block && (key_lower == "height" || key_lower == "h")) {
396405
data.waves.height = ParseDouble(value, 0.0);
397-
} else if (!in_period_block && key == "period") {
406+
} else if (!in_period_block && (key_lower == "amplitude" || key_lower == "a")) {
407+
waves_amplitude = ParseDouble(value, 0.0);
408+
waves_amplitude_set = true;
409+
} else if (!in_period_block && (key_lower == "period" || key_lower == "t" || key_lower == "tp" || key_lower == "p")) {
398410
period_block_seen = true;
399411
period_form_values = period_form_linspace = period_form_range = false;
400412
data.waves.period_values.clear();
@@ -403,6 +415,7 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
403415
if (!looks_structured) {
404416
data.waves.period = ParseDouble(value, 0.0);
405417
data.waves.period_values.push_back(data.waves.period);
418+
period_set_by_synonym = (key_lower != "period");
406419
} else {
407420
// Inline forms on same line
408421
// values inline list inside braces
@@ -509,13 +522,13 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
509522
throw std::runtime_error("waves.period: range produced no values");
510523
}
511524
data.waves.period = data.waves.period_values.front();
512-
} else if (!in_period_block && key == "direction") {
525+
} else if (!in_period_block && key_lower == "direction") {
513526
data.waves.direction = ParseDouble(value, 0.0);
514-
} else if (!in_period_block && key == "phase") {
527+
} else if (!in_period_block && key_lower == "phase") {
515528
data.waves.phase = ParseDouble(value, 0.0);
516-
} else if (!in_period_block && key == "spectrum") {
529+
} else if (!in_period_block && key_lower == "spectrum") {
517530
data.waves.spectrum = value;
518-
} else if (!in_period_block && key == "seed") {
531+
} else if (!in_period_block && key_lower == "seed") {
519532
try { data.waves.seed = std::stoi(value); } catch (...) { data.waves.seed = -1; }
520533
}
521534
}
@@ -556,6 +569,34 @@ YAMLHydroData ReadHydroYAML(const std::string& hydro_file_path) {
556569
}
557570
}
558571

572+
// Apply amplitude to height if provided
573+
if (waves_amplitude_set) {
574+
const double derived_height = 2.0 * waves_amplitude;
575+
if (data.waves.height > 0.0) {
576+
const double eps = 1e-9;
577+
if (std::abs(data.waves.height - derived_height) > eps) {
578+
throw std::runtime_error("waves: both height and amplitude provided but inconsistent (expected height = 2*amplitude)");
579+
}
580+
} else {
581+
data.waves.height = derived_height;
582+
}
583+
}
584+
585+
// Validation and helpful errors for regular waves
586+
{
587+
std::string type_lower = data.waves.type;
588+
std::transform(type_lower.begin(), type_lower.end(), type_lower.begin(), ::tolower);
589+
if (type_lower == "regular") {
590+
if (data.waves.height <= 0.0) {
591+
throw std::runtime_error("waves: regular requires wave height (use 'height' or 'h', or 'amplitude'/'a')");
592+
}
593+
bool has_period = (data.waves.period > 0.0) || !data.waves.period_values.empty();
594+
if (!has_period) {
595+
throw std::runtime_error("waves: regular requires wave period (use 'period' or shorthand 't', 'tp', or 'p')");
596+
}
597+
}
598+
}
599+
559600
// Validate that we found the required sections
560601
if (!in_hydrodynamics) {
561602
throw std::runtime_error("No 'hydrodynamics:' section found in hydro file: " + hydro_file_path);

src/hydrochrono_runner/run_hydrochrono_from_yaml.cpp

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../hydro_yaml_parser.h"
66
#include <hydroc/hydro_forces.h>
77
#include <hydroc/simulation_exporter.h>
8+
#include <hydroc/wave_types.h>
89

910
#include <chrono_parsers/yaml/ChParserMbsYAML.h>
1011
#include <chrono/physics/ChSystem.h>
@@ -23,6 +24,7 @@
2324
#include <sstream>
2425
#include <chrono>
2526
#include <iomanip>
27+
#include <cmath>
2628

2729
#ifdef _WIN32
2830
#include <windows.h>
@@ -449,8 +451,10 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
449451

450452
// Setup hydrodynamic forces
451453
hydroc::debug::LogDebug("Initializing TestHydro...");
452-
// Provide a neutral horizon (Chrono governs runtime via YAML/UI)
453-
test_hydro = SetupHydroFromYAML(hydro_data, bodies, loop_dt, /*time_end_hint*/ 0.0, 0.0);
454+
// Provide simulation horizon from YAML end_time (important for irregular waves spectrum)
455+
double sim_duration_hint = 0.0;
456+
TryFindYamlDouble(sim_file, "end_time", sim_duration_hint);
457+
test_hydro = SetupHydroFromYAML(hydro_data, bodies, loop_dt, sim_duration_hint, 0.0);
454458
hydroc::debug::LogDebug("Hydrodynamic forces initialized successfully");
455459

456460
// Inform location for diagnostics CSVs: write to hydro file directory
@@ -656,9 +660,23 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
656660
exporter = std::make_unique<hydroc::SimulationExporter>(exp_opts);
657661

658662
// Write static info and model before stepping
659-
exporter->WriteSimulationInfo(system.get(), std::string(""), std::filesystem::path(model_file).filename().generic_string(), loop_dt, /*duration_seconds*/ 0.0);
663+
double duration_hint = 0.0; TryFindYamlDouble(sim_file, "end_time", duration_hint);
664+
exporter->WriteSimulationInfo(system.get(), std::string(""), std::filesystem::path(model_file).filename().generic_string(), loop_dt, duration_hint);
660665
exporter->WriteModel(system.get());
661666
exporter->BeginResults(system.get(), /*expected_steps*/ 0);
667+
668+
// If irregular waves are configured, persist spectrum and eta(t) inputs to HDF5
669+
if (test_hydro) {
670+
auto wave_ptr = test_hydro->GetWave();
671+
if (wave_ptr && wave_ptr->GetWaveMode() == WaveMode::irregular) {
672+
auto irreg = std::static_pointer_cast<IrregularWaves>(wave_ptr);
673+
std::vector<double> f = irreg->GetFrequenciesHz();
674+
std::vector<double> S = irreg->GetSpectrum();
675+
std::vector<double> tvec = irreg->GetFreeSurfaceTime();
676+
std::vector<double> eta = irreg->GetFreeSurfaceElevation();
677+
exporter->WriteIrregularInputs(f, S, tvec, eta);
678+
}
679+
}
662680
} catch (const std::exception& e) {
663681
hydroc::cli::LogWarning(std::string("HDF5 exporter disabled: ") + e.what());
664682
exporter.reset();
@@ -697,6 +715,14 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
697715

698716
if (nogui) {
699717
const double end_time_bound = (yaml_end_time > 0.0) ? yaml_end_time : 40.0;
718+
// Estimate total steps for progress bar; ensure at least 1
719+
const double remaining_time = std::max(0.0, end_time_bound - initial_time);
720+
const size_t total_steps_est = static_cast<size_t>(std::max(1.0, std::ceil(remaining_time / std::max(1e-12, loop_dt))));
721+
size_t last_progress_step = 0;
722+
723+
// Initial progress line
724+
hydroc::cli::ShowProgress(0, total_steps_est, std::string("t=") + hydroc::FormatNumber(initial_time, 2) + " / " + hydroc::FormatNumber(end_time_bound, 2) + " s");
725+
700726
while (system->GetChTime() < end_time_bound) {
701727
double current_time = system->GetChTime();
702728
try {
@@ -709,18 +735,33 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
709735
exporter->RecordStep(system.get());
710736
if (profile_mode) { prof_export_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - t).count(); }
711737
}
738+
// Update progress periodically to reduce console churn
739+
if (step_count == 1 || step_count - static_cast<int>(last_progress_step) >= 25) {
740+
const size_t current_steps = static_cast<size_t>(std::min<double>(total_steps_est, std::ceil((system->GetChTime() - initial_time) / std::max(1e-12, loop_dt))));
741+
std::string msg = std::string("t=") + hydroc::FormatNumber(system->GetChTime(), 2) + " / " + hydroc::FormatNumber(end_time_bound, 2) + " s";
742+
hydroc::cli::ShowProgress(current_steps, total_steps_est, msg);
743+
last_progress_step = static_cast<size_t>(step_count);
744+
}
712745
previous_time = current_time;
713746
} catch (const std::exception& e) {
747+
hydroc::cli::StopProgress();
714748
hydroc::cli::LogError(std::string("🔥 Exception during DoStepDynamics at step ") + std::to_string(step_count) + ": " + e.what());
715749
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
716750
hydroc::cli::LogError(std::string("Step size: ") + hydroc::FormatNumber(loop_dt, 6) + " s");
717751
break;
718752
} catch (...) {
753+
hydroc::cli::StopProgress();
719754
hydroc::cli::LogError(std::string("🔥 Unknown exception during DoStepDynamics at step ") + std::to_string(step_count));
720755
hydroc::cli::LogError(std::string("Simulation time: ") + hydroc::FormatNumber(current_time, 6) + " s");
721756
break;
722757
}
723758
}
759+
// Finalize progress line
760+
if (system->GetChTime() >= end_time_bound - 1e-9) {
761+
hydroc::cli::ShowProgress(total_steps_est, total_steps_est, "Completed");
762+
} else {
763+
hydroc::cli::StopProgress();
764+
}
724765
} else {
725766
// GUI-driven loop: respects pause via ui.simulationStarted and closes when window stops
726767
while (ui.IsRunning(loop_dt)) {

src/setup_hydro_from_yaml.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ std::shared_ptr<WaveBase> CreateWaveFromSettings(const WaveSettings& wave_settin
5656
params.ramp_duration_ = ramp_duration;
5757
params.wave_height_ = wave_settings.height;
5858
params.wave_period_ = wave_settings.period;
59-
params.seed_ = 1; // Default seed, could be made configurable
59+
// Use YAML-provided seed if available; fall back to a default deterministic seed
60+
params.seed_ = (wave_settings.seed > 0 ? wave_settings.seed : 1);
6061

6162
auto irregular_wave = std::make_shared<IrregularWaves>(params);
6263

0 commit comments

Comments
 (0)