19 #include <wx/aboutdlg.h>
20 #include <wx/aui/auibook.h>
22 #include <wx/msgdlg.h>
29 return dynamic_cast<wxAuiNotebook*
>(wxWindow::FindWindowById(
NOTEBOOK_ID));
36 return home.
value() /
Path(
".config/opensph/recent.csv");
45 std::ifstream ifs(recentCache->native());
47 if (std::getline(ifs, line)) {
50 for (std::string& s : strings) {
55 }
catch (
const std::exception&
UNUSED(e)) {
64 static void addToRecentSessions(
const Path& sessionPath) {
67 auto sessionIter = std::find(sessions.
begin(), sessions.
end(), sessionPath);
68 if (sessionIter != sessions.
end()) {
72 sessions.
insert(0, sessionPath);
80 std::ofstream ofs(recentCache->native());
81 for (
Size i = 0; i < sessions.
size(); ++i) {
82 ofs << sessions[i].native();
83 if (i != sessions.
size() - 1) {
87 }
catch (
const std::exception&
UNUSED(e)) {
102 const std::string& name)
const override {
103 window->addPage(std::move(node), globals, name);
107 window->markSaved(
false);
116 std::string(
"OpenSPH - build: ") + __DATE__ +
" (DEBUG)",
118 std::string(
"OpenSPH - build: ") + __DATE__,
123 this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MainWindow::onClose));
126 this->SetAutoLayout(
true);
129 notebook =
new wxAuiNotebook(
this,
133 wxAUI_NB_DEFAULT_STYLE & ~wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
134 notebook->SetMinSize(wxSize(1024, 768));
135 notebook->Bind(wxEVT_CHAR_HOOK, [
this](wxKeyEvent& evt) {
136 const int code = evt.GetKeyCode();
137 if (evt.ControlDown() && code >=
int(
'1') && code <=
int(
'9')) {
138 notebook->SetSelection(code - int(
'1'));
143 nodePage =
new NodeWindow(notebook, makeShared<NodeManagerCallbacks>(
this));
144 notebook->AddPage(nodePage,
"Unnamed session");
146 wxBoxSizer* sizer =
new wxBoxSizer(wxHORIZONTAL);
147 sizer->Add(notebook, 1, wxALL | wxEXPAND);
149 this->SetSizer(sizer);
151 wxMenuBar* bar =
new wxMenuBar();
153 wxMenu* projectMenu = this->createProjectMenu();
154 bar->Append(projectMenu,
"&Project");
156 runMenu = this->createRunMenu();
157 bar->Append(runMenu,
"&Simulation");
159 wxMenu* analysisMenu = this->createAnalysisMenu();
160 bar->Append(analysisMenu,
"&Analysis");
162 wxMenu* resultMenu = this->createResultMenu();
163 bar->Append(resultMenu,
"&Result");
165 wxMenu* viewMenu =
new wxMenu();
168 viewMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [
this](wxCommandEvent& evt) {
171 bar->Append(viewMenu,
"&Window");
173 wxMenu* helpMenu =
new wxMenu();
174 bar->Append(helpMenu,
"&Help");
175 helpMenu->Append(0,
"&About");
176 helpMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [](wxCommandEvent& evt) {
177 switch (evt.GetId()) {
179 wxAboutDialogInfo info;
180 info.SetName(
"OpenSPH");
182 info.SetVersion(SPH_STR(SPH_VERSION));
184 info.SetVersion(
"unknown");
189 desc +=
"Debug build\n";
191 desc +=
"Release build\n";
194 desc +=
"Profiling enabled\n";
197 desc +=
"Parallelization: TBB\n";
199 desc +=
"Parallelization: OpenMP\n";
201 desc +=
"Parallelization: built-in thread pool\n";
204 desc +=
"Eigen: enabled\n";
206 desc +=
"Eigen: disabled\n";
209 desc +=
"OpenVDB: enabled\n";
211 desc +=
"OpenVDB: disabled\n";
213 #ifdef SPH_USE_CHAISCRIPT
214 desc +=
"Chaiscript: enabled";
216 desc +=
"Chaiscript: disabled";
218 info.SetDescription(desc);
219 info.SetCopyright(
"Pavel Sevecek <sevecek@sirrah.troja.mff.cuni.cz>");
229 this->SetMenuBar(bar);
230 this->enableMenus(0);
232 notebook->Bind(wxEVT_AUINOTEBOOK_PAGE_CLOSE, [
this](wxAuiNotebookEvent& evt) {
233 const int pageId = evt.GetSelection();
234 wxWindow* page = notebook->GetPage(pageId);
237 if (nodePage || (runPage && !closeRun(pageId))) {
242 notebook->Bind(wxEVT_AUINOTEBOOK_PAGE_CHANGED, [
this](wxAuiNotebookEvent& evt) {
243 const int pageId = evt.GetSelection();
244 this->enableMenus(pageId);
248 if (!openPath.empty()) {
250 const std::string ext = openPath.extension().native();
252 this->open(openPath,
true);
253 }
else if (ext ==
"sph") {
254 this->load(openPath);
256 wxMessageBox(
"Unrecognized file format",
"Error", wxOK);
264 void MainWindow::saveAs() {
267 this->setProjectPath(selectedPath.
value());
272 void MainWindow::save() {
279 project.
save(config);
282 nodePage->
save(config);
284 config.
save(projectPath);
286 this->markSaved(
true);
290 void MainWindow::open(
const Path& openPath,
const bool setDefaults) {
300 if (isSphSim || isMiluphSim) {
305 controller->
open(openPath);
307 const Size index = notebook->GetPageCount();
312 data.controller = std::move(controller);
314 runs.
insert(page, std::move(data));
317 notebook->AddPage(page, displayedPath.
native());
318 notebook->SetSelection(index);
320 this->enableMenus(index);
323 void MainWindow::load(
const Path& openPath) {
327 if (openPath.
empty()) {
330 pathToLoad = selectedPath.
value();
335 pathToLoad = openPath;
339 wxMessageBox(
"File '" + pathToLoad.
native() +
"' does not exist.");
343 const bool removed = this->removeAll();
350 config.
load(pathToLoad);
352 wxMessageBox(std::string(
"Cannot load: ") + e.
what(),
"Error", wxOK);
358 project.
load(config);
359 nodePage->
load(config);
361 wxMessageBox(std::string(
"Cannot load: ") + e.
what(),
"Error", wxOK);
365 this->setProjectPath(pathToLoad);
369 void MainWindow::setProjectPath(
const Path& newPath) {
370 projectPath = newPath;
371 const int pageIndex = notebook->GetPageIndex(nodePage);
372 if (!projectPath.
empty()) {
373 notebook->SetPageText(
376 notebook->SetPageText(pageIndex,
"Unnamed session");
380 void MainWindow::markSaved(
const bool saved) {
382 if (savedFlag == saved) {
388 this->setProjectPath(projectPath);
390 const int pageIndex = notebook->GetPageIndex(nodePage);
391 notebook->SetPageText(pageIndex, notebook->GetPageText(pageIndex) +
" *");
395 wxMenu* MainWindow::createProjectMenu() {
396 wxMenu* projectMenu =
new wxMenu();
397 projectMenu->Append(0,
"&New session\tCtrl+N");
398 projectMenu->Append(1,
"&Save session\tCtrl+S");
399 projectMenu->Append(2,
"&Save session as");
400 projectMenu->Append(3,
"&Open session\tCtrl+Shift+O");
402 wxMenu* recentMenu =
new wxMenu();
403 projectMenu->AppendSubMenu(recentMenu,
"&Recent");
404 projectMenu->Append(4,
"&Visualization settings...");
405 projectMenu->Append(5,
"&Shared properties...");
406 projectMenu->Append(6,
"&Batch run\tCtrl+B");
407 projectMenu->Append(7,
"&Quit");
410 *recentSessions = getRecentSessions();
411 for (
Size i = 0; i < recentSessions->size(); ++i) {
412 recentMenu->Append(i, (*recentSessions)[i].native());
416 recentMenu->Bind(wxEVT_COMMAND_MENU_SELECTED,
417 [
this, recentSessions](wxCommandEvent& evt) { this->load((*recentSessions)[evt.GetId()]); });
419 projectMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [
this](wxCommandEvent& evt) {
420 switch (evt.GetId()) {
423 if (!this->removeAll()) {
427 if (checkUnsavedSession() == wxCANCEL) {
432 if (dialog->ShowModal() == wxID_OK) {
433 this->setProjectPath(
Path());
444 if (projectPath.
empty()) {
463 notebook->SetSelection(0);
479 wxMenu* MainWindow::createResultMenu() {
480 wxMenu* fileMenu =
new wxMenu();
481 fileMenu->Append(0,
"&Open\tCtrl+O");
482 fileMenu->Append(1,
"&Close current\tCtrl+W");
484 fileMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [
this](wxCommandEvent& evt) {
485 switch (evt.GetId()) {
487 Optional<Path> path = doOpenFileDialog(
"Open file",
488 { {
"SPH state file",
"ssf" },
489 {
"SPH compressed file",
"scf" },
490 {
"miluphcuda output files",
"h5" },
491 {
"Text .tab files",
"tab" } });
493 this->open(path.value(), false);
498 wxWindow* page = notebook->GetCurrentPage();
499 if (dynamic_cast<NodeWindow*>(page)) {
504 closeRun(notebook->GetPageIndex(page));
527 wxMenu* MainWindow::createRunMenu() {
528 wxMenu* runMenu =
new wxMenu();
529 runMenu->Append(
RUN_START,
"S&tart run\tCtrl+R");
539 runMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [
this, runMenu](wxCommandEvent& evt) {
542 nodePage->selectRun();
545 #ifdef SPH_USE_CHAISCRIPT
546 Optional<Path> scriptPath = doOpenFileDialog(
"Chai script", { {
"Chai script",
"chai" } });
551 wxMessageBox(
"The code needs to be compiled with ChaiScript support.",
"No ChaiScript", wxOK);
556 RunPage* page =
dynamic_cast<RunPage*
>(notebook->GetCurrentPage());
562 switch (evt.GetId()) {
564 controller->
stop(
true);
569 wxMenuItem* item = runMenu->FindItem(
RUN_PAUSE);
572 item->SetItemLabel(
"&Pause");
575 item->SetItemLabel(
"Un&pause");
606 notebook->SetSelection(notebook->GetPageIndex(nodePage));
610 closeRun(notebook->GetPageIndex(page));
624 wxMenu* MainWindow::createAnalysisMenu() {
625 wxMenu* analysisMenu =
new wxMenu();
626 analysisMenu->Append(0,
"Current SFD");
627 analysisMenu->Append(1,
"Predicted SFD");
628 analysisMenu->Append(2,
"Velocity histogram");
629 analysisMenu->Append(3,
"Density profile");
630 analysisMenu->Append(4,
"Energy profile");
631 analysisMenu->Append(5,
"Pressure profile");
632 analysisMenu->Append(6,
"Fragment parameters");
633 analysisMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [
this](wxCommandEvent& evt) {
635 RunPage* page =
dynamic_cast<RunPage*
>(notebook->GetCurrentPage());
641 if (evt.GetId() == 6) {
644 const Size index = notebook->GetPageCount();
645 notebook->AddPage(gridPage,
"Fragments");
646 notebook->SetSelection(index);
652 switch (evt.GetId()) {
659 multiplot.
emplaceBack(makeAuto<SfdPlot>(flag, 0._f));
661 const std::string overplotSfd =
663 if (!overplotSfd.empty()) {
666 plot = makeLocking<MultiPlot>(std::move(multiplot));
680 wxMessageBox(
"No pressure data",
"Error", wxOK);
690 plot->onTimeStep(controller->
getStorage(), stats);
692 PlotPage* plotPage =
new PlotPage(notebook, wxSize(800, 600), wxSize(25, 25), plot);
694 const Size index = notebook->GetPageCount();
696 const std::string caption = plot->getCaption();
697 notebook->AddPage(plotPage, caption);
698 notebook->SetSelection(index);
706 controller->
start(std::move(node), globals);
710 data.controller = std::move(controller);
712 runs.
insert(page, std::move(data));
714 const Size index = notebook->GetPageCount();
715 notebook->AddPage(page, pageName);
716 notebook->SetSelection(index);
718 this->enableMenus(index);
721 bool MainWindow::removeAll() {
722 for (
int i = notebook->GetPageCount() - 1; i >= 0; --i) {
723 if (
dynamic_cast<RunPage*
>(notebook->GetPage(i)) && !closeRun(i)) {
730 void MainWindow::onClose(wxCloseEvent& evt) {
731 if (checkUnsavedSession() == wxCANCEL) {
738 void MainWindow::enableMenus(
const Size id) {
741 wxMenuBar* bar = this->GetMenuBar();
743 if (page ==
nullptr) {
744 enableRunMenu(
false,
false);
746 bar->EnableTop(2,
false);
751 const bool enableControls = runs[page].isRun;
752 enableRunMenu(enableControls,
true);
754 bar->EnableTop(2,
true);
757 void MainWindow::enableRunMenu(
const bool enableControls,
const bool enableCamera) {
758 wxMenuItemList& list = runMenu->GetMenuItems();
759 for (
Size i = 0; i < list.size(); ++i) {
762 list[i]->Enable(
true);
766 list[i]->Enable(enableCamera);
768 list[i]->Enable(enableControls);
773 bool MainWindow::closeRun(
const Size id) {
774 wxWindow* page = notebook->GetPage(
id);
776 if (!runPage->
close()) {
784 notebook->DeletePage(
id);
788 int MainWindow::checkUnsavedSession() {
792 const int retval = wxMessageBox(
"Save unsaved changes",
"Save?", wxYES_NO | wxCANCEL | wxCENTRE);
793 if (retval == wxYES) {
794 if (projectPath.
empty()) {
#define SPH_ASSERT(x,...)
#define NOT_IMPLEMENTED
Helper macro marking missing implementation.
@ MAIN_THREAD
Function can only be executed from main thread.
#define CHECK_FUNCTION(flags)
RunStatus
Status of the code.
@ PAUSED
Run is paused, can be continued or stopped.
uint32_t Size
Integral type used to index arrays (by default).
Helper objects allowing to iterate in reverse, iterate over multiple containeres, etc.
constexpr NAMESPACE_SPH_BEGIN int NOTEBOOK_ID
wxAuiNotebook * findNotebook()
constexpr Size MAX_CACHE_SIZE
#define NAMESPACE_SPH_END
const NothingType NOTHING
@ SPH
Main SPH simulation.
Drawing quantity values as functions of time or spatial coordinates.
AutoPtr< IPlot > getDataPlot(const Path &path, const std::string &name)
@ PRESSURE
Pressure, affected by yielding and fragmentation model, always a scalar quantity.
@ ENERGY
Specific internal energy, always a scalar quantity.
@ DENSITY
Density, always a scalar quantity.
@ RUN_TIME
Current time of the simulation in code units. Does not necessarily have to be 0 when run starts.
Array< std::string > split(const std::string &s, const char delimiter)
Splits a string into an array of string using given delimiter.
Optional< Path > doSaveFileDialog(const std::string &title, Array< FileFormat > &&formats)
Optional< Path > doOpenFileDialog(const std::string &title, Array< FileFormat > &&formats)
Random utility functions for drawing stuff to DC.
Array< IVirtualEntry::FileFormat > getOutputFormats()
Convenience function, returning the list of output file formats defined by IoEnum.
INLINE Vector translation() const
INLINE Vector row(const Size idx) const
Generic dynamically allocated resizable storage.
INLINE Iterator< StorageType > end() noexcept
StorageType & emplaceBack(TArgs &&... args)
Constructs a new element at the end of the array in place, using the provided arguments.
void remove(const TCounter idx)
Removes an element with given index from the array.
void insert(const TCounter position, U &&value)
Inserts a new element to given position in the array.
INLINE T pop()
Removes the last element from the array and return its value.
INLINE TCounter size() const noexcept
INLINE Iterator< StorageType > begin() noexcept
Provides functionality for reading and writing configuration files.
void save(const Path &path)
Serializes all nodes in the config into a file.
void load(const Path &path)
Reads content of given file and deserializes the config from the loaded string.
RawPtr< RunPage > getPage() const
AutoPtr< ICamera > getCurrentCamera() const
Returns the camera currently used for the rendering.
void saveState(const Path &path)
Saves the state of the current run to the disk.
void restart()
Starts the simulation with current setup.
RunStatus getStatus() const
Returns the current status of the run.
void open(const Path &path, const bool sequence=false)
Opens a simulation snapshot from given file.
void stop(const bool waitForFinish=false)
Stops the current simulation.
void pause()
Pause the current simulation.
void start(SharedPtr< INode > run, const RunSettings &globals)
Sets up and starts a new simulation.
const Storage & getStorage() const
virtual const char * what() const noexcept
Wrapper of type that either contains a value of given type, or an error message.
Type & value()
Returns the reference to expected value.
INLINE bool contains(const TKey &key) const
Returns true if the map contains element of given key.
INLINE void remove(const TKey &key)
Removes element with given key from the map.
INLINE TValue & insert(const TKey &key, const TValue &value)
Adds a new element into the map or sets new value of element with the same key.
INLINE TValue get(const GuiSettingsId id) const
INLINE GuiSettings & set(const GuiSettingsId id, const TValue &value)
Function< void(GuiSettingsId id)> accessor
virtual Pixel getSize() const =0
Returns the current resolution of the camera.
virtual AffineMatrix getFrame() const =0
Returns the transformation matrix converting camera space to world space.
virtual Optional< float > getWorldToPixel() const =0
Returns the world-to-pixel ratio.
MainWindow(const Path &openPath=Path())
virtual void startRun(SharedPtr< INode > node, const RunSettings &globals, const std::string &name) const override
NodeManagerCallbacks(MainWindow *window)
virtual void markUnsaved(bool UNUSED(addToUndo)) const override
SharedPtr< JobNode > createNode(AutoPtr< IJob > &&job)
void load(Config &config)
UniqueNameManager makeUniqueNameManager() const
void addNodes(JobNode &node)
void showPanel(const PanelId id)
void save(Config &config)
void startScript(const Path &file)
Wrapper of type value of which may or may not be present.
INLINE Type & value()
Returns the reference to the stored value.
Object representing a path on a filesystem.
Path & removeExtension()
Removes the extension from the path.
std::string native() const
Returns the native version of the path.
bool empty() const
Checks if the path is empty.
Path fileName() const
Returns the filename of the path.
Path parentPath() const
Returns the parent directory. If the path is empty or root, return empty path.
Path extension() const
Returns the extension of the filename.
void load(Config &config)
GuiSettings & getGuiSettings()
static Project & getInstance()
void save(Config &config)
Main frame of the application.
SharedPtr< JobNode > selectedPreset() const
Object holding various statistics about current run.
Statistics & set(const StatisticsId idx, TValue &&value)
Sets new values of a statistic.
bool has(const QuantityId key) const
Checks if the storage contains quantity with given key.
Holds a map of virtual entries, associated with a unique name.
void set(const std::string &key, const IVirtualEntry::Value &value)
Modifies an existing entry in the settings.
Optional< IoEnum > getIoEnum(const std::string &ext)
Returns the file type from file extension.
@ PARTICLE_RADIUS
Displayed radius of particle in units of smoothing length.
@ CAMERA_ORTHO_FOV
View field of view (zoom). Special value 0 means the field of view is computed from the bounding box.
bool pathExists(const Path &path)
Checks if a file or directory exists (or more precisely, if a file or directory is accessible).
Outcome createDirectory(const Path &path, const Flags< CreateDirectoryFlag > flags=CreateDirectoryFlag::ALLOW_EXISTING)
Creates a directory with given path. Creates all parent directories as well.
Expected< Path > getHomeDirectory()
Returns the home directory of the current user.
Path getAbsolutePath(const Path &relativePath)
Returns the absolute path to the file.
@ OVERLAP
Specifies that overlapping particles belong into the same component.
@ VELOCITIES
Particle velocities.
Overload of std::swap for Sph::Array.