#include #include #include #include #include #include "boost/format.hpp" #include "mainwindow.h" #include "csv_parser.h" #include "options.h" #include "threed_beam_fea.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { createMenu(); createChooseFilesGroupBox(); createOptionsGroupBox(); createSubmitGroupBox(); createStatusBar(); readSettings(); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->setMenuBar(menuBar); mainLayout->addWidget(chooseFilesGroupBox); mainLayout->addWidget(optionsGroupBox); mainLayout->addWidget(submitGroupBox); QWidget *widget = new QWidget(); widget->setLayout(mainLayout); setCentralWidget(widget); feaProgram = QCoreApplication::applicationDirPath().toStdString() + "/fea_cmd"; feaTmpConfigFilename = QCoreApplication::applicationDirPath().toStdString() +"/tmp_config.json"; setMinimumWidth(600); setWindowTitle(tr("3D Beam FEA")); setWindowIcon(QIcon(":images/logo_64x64.png")); setUnifiedTitleAndToolBarOnMac(true); } void MainWindow::closeEvent(QCloseEvent *event) { writeSettings(); event->accept(); } void MainWindow::open() { QString filename = QFileDialog::getOpenFileName(this); if (!filename.isEmpty()) { try { rapidjson::Document config_doc = fea::parseJSONConfig(filename.toStdString()); loadOptionsFromConfig(config_doc); statusBar()->showMessage(tr("File loaded"), 2000); } catch (std::exception &e) { std::cerr << "error: " << e.what() << std::endl; QMessageBox::critical(this, QString("Error"), QString(e.what())); statusBar()->showMessage(tr("Error loading file"), 2000); } } } void MainWindow::save() { QString filename = QFileDialog::getSaveFileName(this); if (!filename.isEmpty()) { rapidjson::Document config_doc = createConfigDoc(); try { writeConfigDocToFile(config_doc, filename.toStdString()); statusBar()->showMessage(tr("File saved"), 2000); } catch (std::exception &e) { std::cerr << "error: " << e.what() << std::endl; QString message_title("Error"); QString message_text(e.what()); QMessageBox::critical(this, message_title, message_text); setEnabled(true); } } } void MainWindow::handleFinishedFEA(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit && !feaTerminated) { QString message_text; if (progress) { progress->done(QDialog::Accepted); message_text = progress->labelText(); delete progress; } removeTmpFiles(); message_text.insert(0, "
");
        message_text.append("
"); QMessageBox *message = new QMessageBox(QMessageBox::Information, "Summary", message_text, QMessageBox::Ok, this); message->exec(); setEnabled(true); } } void MainWindow::handleCanceledFEA() { feaTerminated = true; QString error_text(feaProcess->readAllStandardError()); feaProcess->kill(); removeTmpFiles(); if (progress) { progress->done(QDialog::Accepted); delete progress; } if (!error_text.isEmpty()) { QMessageBox::critical(this, "FEA exited with error(s)", error_text); } statusBar()->showMessage(tr("Analysis aborted"), 2000); setEnabled(true); } void MainWindow::submit() { if(checkFilesReady()) { progress = new QProgressDialog("Solving analysis...", "Abort", 0, 0); progress->setWindowModality(Qt::WindowModal); statusBar()->showMessage(tr("Analysis submitted"), 0); setEnabled(false); progress->show(); solveFEA(); } } void MainWindow::setNodesText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select nodes"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { nodesLineEdit->setText(filename); } } void MainWindow::setElemsText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select elements"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { elemsLineEdit->setText(filename); } } void MainWindow::setPropsText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select properties"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { propsLineEdit->setText(filename); } } void MainWindow::setBCsText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select boundary conditions"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { bcsLineEdit->setText(filename); } } void MainWindow::setForcesText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select forces"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { forcesLineEdit->setText(filename); } } void MainWindow::setTiesText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select ties"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { tiesLineEdit->setText(filename); } } void MainWindow::setEquationsText() { QString filename = QFileDialog::getOpenFileName(this, tr("Select equations"), QDir::currentPath(), tr("Data files (*.txt *.csv);; All files (*)")); if (!filename.isEmpty()) { equationsLineEdit->setText(filename); } } void MainWindow::updateProgressText() { if (progress) { progress->setLabelText(QString(feaProcess->readAllStandardOutput())); } } void MainWindow::createMenu() { menuBar = new QMenuBar; fileMenu = new QMenu(tr("&File"), this); openAction = fileMenu->addAction(QIcon(":/images/default-document-open.png"), tr("&Open")); openAction->setStatusTip(tr("Open an existing file")); saveAction = fileMenu->addAction(QIcon(":/images/document-save.png"), tr("&Save")); saveAction->setStatusTip(tr("Save configuration file")); exitAction = fileMenu->addAction(QIcon(":/images/window-close.png"), tr("E&xit")); exitAction->setStatusTip(tr("Exit the application")); menuBar->addMenu(fileMenu); connect(openAction, SIGNAL(triggered()), this, SLOT(open())); connect(saveAction, SIGNAL(triggered()), this, SLOT(save())); connect(exitAction, SIGNAL(triggered()), this, SLOT(close())); } void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Ready")); } void MainWindow::initializeChooseFilesRow(QGridLayout *glayout, QLineEdit* line_edit, QPushButton* button, int row_number) { line_edit->setPlaceholderText("No file chosen..."); glayout->addWidget(button, row_number, 0); glayout->addWidget(line_edit, row_number, 1); } void MainWindow::createChooseFilesGroupBox() { chooseFilesGroupBox = new QGroupBox(tr("Choose files")); QGridLayout *glayout = new QGridLayout; int row_counter = 0; nodesLineEdit = new QLineEdit(this); loadNodesButton = new QPushButton("Nodes"); loadNodesButton->setStatusTip(tr("Choose file containing nodal coordinates")); initializeChooseFilesRow(glayout, nodesLineEdit, loadNodesButton, row_counter++); connect(loadNodesButton, &QPushButton::clicked, this, &MainWindow::setNodesText); elemsLineEdit = new QLineEdit(); loadElemsButton = new QPushButton("Elements"); loadElemsButton->setStatusTip(tr("Choose file containing element indices")); initializeChooseFilesRow(glayout, elemsLineEdit, loadElemsButton, row_counter++); connect(loadElemsButton, &QPushButton::clicked, this, &MainWindow::setElemsText); propsLineEdit = new QLineEdit(); loadPropsButton = new QPushButton("Properties"); loadPropsButton->setStatusTip(tr("Choose file containing elemental properties")); initializeChooseFilesRow(glayout, propsLineEdit, loadPropsButton, row_counter++); connect(loadPropsButton, &QPushButton::clicked, this, &MainWindow::setPropsText); bcsLineEdit = new QLineEdit(); loadBCsButton = new QPushButton("Boundary conditions"); loadBCsButton->setStatusTip(tr("Choose file containing boundary conditions")); initializeChooseFilesRow(glayout, bcsLineEdit, loadBCsButton, row_counter++); connect(loadBCsButton, &QPushButton::clicked, this, &MainWindow::setBCsText); forcesLineEdit = new QLineEdit(); loadForcesButton = new QPushButton("Prescribed forces"); loadForcesButton->setStatusTip(tr("Choose file containing prescribed forces")); initializeChooseFilesRow(glayout, forcesLineEdit, loadForcesButton, row_counter++); connect(loadForcesButton, &QPushButton::clicked, this, &MainWindow::setForcesText); tiesLineEdit = new QLineEdit(); loadTiesButton = new QPushButton("Ties"); loadTiesButton->setStatusTip(tr("Choose file containing ties")); initializeChooseFilesRow(glayout, tiesLineEdit, loadTiesButton, row_counter++); connect(loadTiesButton, &QPushButton::clicked, this, &MainWindow::setTiesText); equationsLineEdit = new QLineEdit(); loadEquationsButton = new QPushButton("Equations"); loadEquationsButton->setStatusTip(tr("Choose file containing equation constraints")); initializeChooseFilesRow(glayout, equationsLineEdit, loadEquationsButton, row_counter++); connect(loadEquationsButton, &QPushButton::clicked, this, &MainWindow::setEquationsText); chooseFilesGroupBox->setLayout(glayout); } void MainWindow::createOptionsGroupBox() { optionsGroupBox = new QGroupBox(tr("Options")); QGridLayout *glayout = new QGridLayout; int row_counter = 0; nodalDispCheckBox = new QCheckBox(tr("Save nodal displacements")); nodalDispCheckBox->setLayoutDirection(Qt::RightToLeft); nodalDispCheckBox->setChecked(false); nodalDispLineEdit = new QLineEdit(tr("nodal_displacements.csv")); nodalDispLineEdit->setDisabled(true); connect(nodalDispCheckBox, SIGNAL(toggled(bool)), nodalDispLineEdit, SLOT(setEnabled(bool))); glayout->addWidget(nodalDispCheckBox, row_counter, 0); glayout->addWidget(nodalDispLineEdit, row_counter, 1, 1, 5); nodalForcesCheckBox = new QCheckBox(tr("Save nodal forces")); nodalForcesCheckBox->setLayoutDirection(Qt::RightToLeft); nodalForcesCheckBox->setChecked(false); nodalForcesLineEdit = new QLineEdit(tr("nodal_forces.csv")); nodalForcesLineEdit->setDisabled(true); connect(nodalForcesCheckBox, SIGNAL(toggled(bool)), nodalForcesLineEdit, SLOT(setEnabled(bool))); glayout->addWidget(nodalForcesCheckBox, ++row_counter, 0); glayout->addWidget(nodalForcesLineEdit, row_counter, 1, 1, 5); tieForcesCheckBox = new QCheckBox(tr("Save tie forces")); tieForcesCheckBox->setLayoutDirection(Qt::RightToLeft); tieForcesCheckBox->setChecked(false); tieForcesLineEdit = new QLineEdit(tr("tie_forces.csv")); tieForcesLineEdit->setDisabled(true); connect(tieForcesCheckBox, SIGNAL(toggled(bool)), tieForcesLineEdit, SLOT(setEnabled(bool))); glayout->addWidget(tieForcesCheckBox, ++row_counter, 0); glayout->addWidget(tieForcesLineEdit, row_counter, 1, 1, 5); reportCheckBox = new QCheckBox(tr("Save report")); reportCheckBox->setLayoutDirection(Qt::RightToLeft); reportCheckBox->setChecked(false); reportLineEdit = new QLineEdit(tr("report.txt")); reportLineEdit->setDisabled(true); connect(reportCheckBox, SIGNAL(toggled(bool)), reportLineEdit, SLOT(setEnabled(bool))); glayout->addWidget(reportCheckBox, ++row_counter, 0); glayout->addWidget(reportLineEdit, row_counter, 1, 1, 5); epsilonLabel = new QLabel(tr("epsilon\t1E")); epsilonSpinBox = new QSpinBox(); epsilonSpinBox->setMinimum(-16); epsilonSpinBox->setMaximum(0); epsilonSpinBox->setValue(-14); epsilonSpinBox->setMaximumWidth(50); precisionLabel = new QLabel(tr("csv precision")); precisionSpinBox = new QSpinBox(); precisionSpinBox->setMinimum(0); precisionSpinBox->setMaximum(16); precisionSpinBox->setValue(8); precisionSpinBox->setMaximumWidth(50); delimiterLabel = new QLabel(tr("csv delimiter")); delimiterLineEdit = new QLineEdit(tr(",")); delimiterLineEdit->setMaximumWidth(50); glayout->addWidget(epsilonLabel, ++row_counter, 0, Qt::AlignRight); glayout->addWidget(epsilonSpinBox, row_counter, 1); glayout->addWidget(precisionLabel, row_counter, 2, Qt::AlignRight); glayout->addWidget(precisionSpinBox, row_counter, 3); glayout->addWidget(delimiterLabel, row_counter, 4, Qt::AlignRight); glayout->addWidget(delimiterLineEdit, row_counter, 5); optionsGroupBox->setLayout(glayout); } void MainWindow::createSubmitGroupBox() { submitGroupBox = new QGroupBox(); QGridLayout *glayout = new QGridLayout(); submitButton = new QPushButton("Submit"); glayout->addWidget(submitButton, 0, 0, Qt::AlignRight); submitGroupBox->setLayout(glayout); submitGroupBox->setMaximumHeight(80); connect(submitButton, SIGNAL(clicked()), this, SLOT(submit())); } bool MainWindow::checkFileOpens(const std::string &filename) { FILE* file_ptr = fopen(filename.c_str(), "r"); if (!file_ptr) { return false; } else { fclose(file_ptr); return true; } } bool MainWindow::checkFilesReady() { int errorCounter = 0; QString message_text(""); if(!checkFileOpens(feaProgram)){ ++errorCounter; message_text.append("Unable to find command line application `fea_gui`.\n" "The command line application should be in the same" "directory as the gui.\n\n"); } if (nodesLineEdit->displayText().isEmpty()) { ++errorCounter; message_text.append("No file for nodes selected.\n"); } else { std::string filename = nodesLineEdit->displayText().toStdString(); if (!checkFileOpens(filename)) { ++errorCounter; message_text.append("Unable to open file selected for nodes.\n"); } } if (elemsLineEdit->displayText().isEmpty()) { ++errorCounter; message_text.append("No file for elements selected.\n"); } else { std::string filename = elemsLineEdit->displayText().toStdString(); if (!checkFileOpens(filename)) { ++errorCounter; message_text.append("Unable to open file selected for elements.\n"); } } if (propsLineEdit->displayText().isEmpty()) { ++errorCounter; message_text.append("No file for properties selected.\n"); } else { std::string filename = propsLineEdit->displayText().toStdString(); if (!checkFileOpens(filename)) { ++errorCounter; message_text.append("Unable to open file selected for properties.\n"); } } if (bcsLineEdit->displayText().isEmpty() && forcesLineEdit->displayText().isEmpty()) { ++errorCounter; message_text.append("No prescribed boundary conditions or forces.\n"); } if (!bcsLineEdit->displayText().isEmpty()) { std::string filename = bcsLineEdit->displayText().toStdString(); if (!checkFileOpens(filename)) { ++errorCounter; message_text.append("Unable to open file selected for boundary conditions.\n"); } } if (!forcesLineEdit->displayText().isEmpty()) { std::string filename = forcesLineEdit->displayText().toStdString(); if (!checkFileOpens(filename)) { ++errorCounter; message_text.append("Unable to open file selected for forces.\n"); } } if (!tiesLineEdit->displayText().isEmpty()) { std::string filename = tiesLineEdit->displayText().toStdString(); if (!checkFileOpens(filename)) { ++errorCounter; message_text.append("Unable to open file selected for ties.\n"); } } bool isReady = true; if(errorCounter > 0) { QString message_title(tr("%1 Error(s) found.").arg(QString::number(errorCounter))); QMessageBox::critical(this, message_title, message_text); isReady = false; } return isReady; } rapidjson::Document MainWindow::createConfigDoc() { char json[] = "{}"; rapidjson::Document config_doc; config_doc.ParseInsitu(json); addMemberToDoc(config_doc, "nodes", nodesLineEdit->displayText().toStdString()); addMemberToDoc(config_doc, "elems", elemsLineEdit->displayText().toStdString()); addMemberToDoc(config_doc, "props", propsLineEdit->displayText().toStdString()); if (!bcsLineEdit->displayText().isEmpty()) { addMemberToDoc(config_doc, "bcs", bcsLineEdit->displayText().toStdString()); } if (!forcesLineEdit->displayText().isEmpty()) { addMemberToDoc(config_doc, "forces", forcesLineEdit->displayText().toStdString()); } if (!tiesLineEdit->displayText().isEmpty()) { addMemberToDoc(config_doc, "ties", tiesLineEdit->displayText().toStdString()); } addOptionsToDoc(config_doc); return config_doc; } void MainWindow::addMemberToDoc(rapidjson::Document &doc, const std::string &key, const std::string &value) { rapidjson::Value rj_key; rj_key.SetString(key.c_str(), key.length(), doc.GetAllocator()); rapidjson::Value rj_val; rj_val.SetString(value.c_str(), value.length(), doc.GetAllocator()); doc.AddMember(rj_key, rj_val, doc.GetAllocator()); } void MainWindow::addOptionsToDoc(rapidjson::Document &doc) { rapidjson::Value options(rapidjson::kObjectType); if (nodalDispCheckBox->isChecked()) { options.AddMember("save_nodal_displacements", true, doc.GetAllocator()); rapidjson::Value rj_val; std::string val = nodalDispLineEdit->displayText().toStdString(); rj_val.SetString(val.c_str(), val.length(), doc.GetAllocator()); options.AddMember("nodal_displacements_filename", rj_val, doc.GetAllocator()); } if (nodalForcesCheckBox->isChecked()) { options.AddMember("save_nodal_forces", true, doc.GetAllocator()); rapidjson::Value rj_val; std::string val = nodalForcesLineEdit->displayText().toStdString(); rj_val.SetString(val.c_str(), val.length(), doc.GetAllocator()); options.AddMember("nodal_forces_filename", rj_val, doc.GetAllocator()); } if (tieForcesCheckBox->isChecked()) { options.AddMember("save_tie_forces", true, doc.GetAllocator()); rapidjson::Value rj_val; std::string val = tieForcesLineEdit->displayText().toStdString(); rj_val.SetString(val.c_str(), val.length(), doc.GetAllocator()); options.AddMember("ties_forces_filename", rj_val, doc.GetAllocator()); } if (reportCheckBox->isChecked()) { options.AddMember("save_report", true, doc.GetAllocator()); rapidjson::Value rj_val; std::string val = tieForcesLineEdit->displayText().toStdString(); rj_val.SetString(val.c_str(), val.length(), doc.GetAllocator()); options.AddMember("report_filename", rj_val, doc.GetAllocator()); } options.AddMember("verbose", true, doc.GetAllocator()); doc.AddMember("options", options, doc.GetAllocator()); } void MainWindow::writeConfigDocToFile(const rapidjson::Document &doc, const std::string &filename) { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); doc.Accept(writer); std::ofstream output_file; output_file.open(filename); if (!output_file.is_open()) { throw std::runtime_error( (boost::format("Could not open file %s.") % filename).str() ); } else { output_file << buffer.GetString(); output_file.close(); } } void MainWindow::readSettings() { QSettings settings("Latture", "beam-fea"); QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); QSize size = settings.value("size", QSize(640, 480)).toSize(); resize(size); move(pos); } void MainWindow::writeSettings() { QSettings settings("Latture", "beam-fea"); settings.setValue("pos", pos()); settings.setValue("size", size()); } void MainWindow::removeTmpFiles() { remove(feaTmpConfigFilename.c_str()); } void MainWindow::setLineEditTextFromConfig(QLineEdit *ledit, const std::string &variable, const rapidjson::Document &config_doc) { if (config_doc.HasMember(variable.c_str())) { if (!config_doc[variable.c_str()].IsString()){ throw std::runtime_error( (boost::format("Value associated with variable %s is not a string.") % variable).str() ); } ledit->setText(tr(config_doc[variable.c_str()].GetString())); } } void MainWindow::loadOptionsFromConfig(const rapidjson::Document &config_doc) { try { setLineEditTextFromConfig(nodesLineEdit, "nodes", config_doc); setLineEditTextFromConfig(elemsLineEdit, "elems", config_doc); setLineEditTextFromConfig(propsLineEdit, "props", config_doc); setLineEditTextFromConfig(bcsLineEdit, "bcs", config_doc); setLineEditTextFromConfig(tiesLineEdit, "ties", config_doc); setLineEditTextFromConfig(forcesLineEdit, "forces", config_doc); fea::Options options = fea::createOptionsFromJSON(config_doc); epsilonSpinBox->setValue(std::log10(options.epsilon)); precisionSpinBox->setValue(options.csv_precision); delimiterLineEdit->setText(tr(options.csv_delimiter.c_str())); if (options.save_nodal_displacements) { nodalDispCheckBox->setChecked(true); nodalDispLineEdit->setText(tr(options.nodal_displacements_filename.c_str())); } else { nodalDispCheckBox->setChecked(false); } if (options.save_nodal_forces) { nodalForcesCheckBox->setChecked(true); nodalForcesLineEdit->setText(tr(options.nodal_forces_filename.c_str())); } else { nodalForcesCheckBox->setChecked(false); } if (options.save_tie_forces) { tieForcesCheckBox->setChecked(true); tieForcesLineEdit->setText(tr(options.tie_forces_filename.c_str())); } else { tieForcesCheckBox->setChecked(false); } if (options.save_report) { reportCheckBox->setChecked(true); reportLineEdit->setText(tr(options.report_filename.c_str())); } else { reportCheckBox->setChecked(false); } } catch (std::exception &e) { throw; } } void MainWindow::solveFEA() { rapidjson::Document configDoc = createConfigDoc(); feaTerminated = false; try { writeConfigDocToFile(configDoc, feaTmpConfigFilename); QStringList feaProgramArgs; feaProgramArgs << "-c" << feaTmpConfigFilename.c_str(); feaProcess = new QProcess(this); connect(progress, SIGNAL(canceled()), this, SLOT(handleCanceledFEA())); connect(feaProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(handleFinishedFEA(int, QProcess::ExitStatus))); connect(feaProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(updateProgressText())); connect(feaProcess, SIGNAL(readyReadStandardError()), this, SLOT(handleCanceledFEA())); feaProcess->start(QString::fromStdString(feaProgram), feaProgramArgs); } catch (std::exception &e) { std::cerr << "error: " << e.what(); QString message_title("Error"); QString message_text(e.what()); QMessageBox::critical(this, message_title, message_text); setEnabled(true); } }