/*************************************************************************** * Copyright (c) 2013 Jan Rheinländer * * * * Copyright (c) 2016 Qingfeng Xia * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ActiveAnalysisObserver.h" #include "TaskFemConstraintFluidBoundary.h" #include "ui_TaskFemConstraintFluidBoundary.h" using namespace FemGui; using namespace Gui; using namespace Fem; // also defined in FemConstrainFluidBoundary and foamcasebuilder/basicbuilder.py, please update // simultaneously the second (index 1) is the default enum, as index 0 causes compiling error static // const char* BoundaryTypes[] = {"inlet","wall","outlet","freestream", "interface", NULL}; static const char* WallSubtypes[] = {"unspecific", "fixed", "slip", "partialSlip", "moving", "rough", nullptr}; static const char* InletSubtypes[] = {"unspecific", "totalPressure", "uniformVelocity", "volumetricFlowRate", "massFlowRate", nullptr}; static const char* OutletSubtypes[] = {"unspecific", "totalPressure", "staticPressure", "uniformVelocity", "outFlow", nullptr}; static const char* InterfaceSubtypes[] = {"unspecific", "symmetry", "wedge", "cyclic", "empty", "coupled", nullptr}; static const char* FreestreamSubtypes[] = {"unspecific", "freestream", nullptr}; static const char* InterfaceSubtypeHelpTexts[] = { "invalid,select other valid interface subtype", "symmetry plane but not axis-sym axis line", "axis symmetric front and back surfaces", "periodic boundary in pair, treated as physical connected", "front and back for single layer 2D mesh, also axis-sym axis line", "exchange boundary vale with external program, need extra manual setup like file name", nullptr}; // defined in file FemConstraintFluidBoundary: // see Ansys fluet manual: Turbulence Specification method // static const char* TurbulenceSpecifications[] = {"intensity&DissipationRate", // "intensity&LengthScale","intensity&ViscosityRatio", "intensity&HydraulicDiameter",NULL}; activate // the heat transfer and radiation model in Solver object explorer static const char* TurbulenceSpecificationHelpTexts[] = { "explicitly specific intensity k [SI unit] and dissipation rate epsilon [] / omega []", "intensity (0.05 ~ 0.15) and characteristic length scale of max eddy [m]", "intensity (0.05 ~ 0.15) and turbulent viscosity ratio", "for fully developed internal flow, Turbulence intensity (0-1.0) 0.05 typical", nullptr}; // static const char* ThermalBoundaryTypes[] = {"fixedValue","zeroGradient", "fixedGradient", // "mixed", "heatFlux", "HTC","coupled", NULL}; static const char* ThermalBoundaryHelpTexts[] = {"fixed Temperature [K]", "no heat transfer on boundary", "fixed value gradient [K/m]", "mixed fixedGradient and fixedValue", "fixed heat flux [W/m2]", "Heat transfer coeff [W/(M2)/K]", "conjugate heat transfer with solid", nullptr}; // enable & disable quantityUI once valueType is selected // internal function not declared in header file void initComboBox(QComboBox* combo, const std::vector& textItems, const std::string& sItem) { combo->blockSignals(true); int iItem = 1; // the first one is "unspecific" (index 0) combo->clear(); for (unsigned int it = 0; it < textItems.size(); it++) { combo->insertItem(it, QString::fromStdString(textItems[it])); if (sItem == textItems[it]) { iItem = it; } } combo->setCurrentIndex(iItem); combo->blockSignals(false); } /* TRANSLATOR FemGui::TaskFemConstraintFluidBoundary */ TaskFemConstraintFluidBoundary::TaskFemConstraintFluidBoundary( ViewProviderFemConstraintFluidBoundary* ConstraintView, QWidget* parent) : TaskFemConstraintOnBoundary(ConstraintView, parent, "FEM_ConstraintFluidBoundary") , ui(new Ui_TaskFemConstraintFluidBoundary) , dimension(-1) { // we need a separate container widget to add all controls to proxy = new QWidget(this); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); // create a context menu for the listview of the references createActions(ui->listReferences); connect(deleteAction, &QAction::triggered, this, &TaskFemConstraintFluidBoundary::onReferenceDeleted); // setup ranges constexpr float max = std::numeric_limits::max(); ui->spinBoundaryValue->setMinimum(-max); ui->spinBoundaryValue->setMaximum(max); ui->spinTurbulentIntensityValue->setMinimum(0.0); ui->spinTurbulentIntensityValue->setMaximum(max); ui->spinTurbulentLengthValue->setMinimum(0.0); ui->spinTurbulentLengthValue->setMaximum(max); ui->spinTemperatureValue->setMinimum(-273.15); ui->spinTemperatureValue->setMaximum(max); ui->spinHeatFluxValue->setMinimum(0.0); ui->spinHeatFluxValue->setMaximum(max); ui->spinHTCoeffValue->setMinimum(0.0); ui->spinHTCoeffValue->setMaximum(max); connect(ui->comboBoundaryType, qOverload(&QComboBox::currentIndexChanged), this, &TaskFemConstraintFluidBoundary::onBoundaryTypeChanged); connect(ui->comboSubtype, qOverload(&QComboBox::currentIndexChanged), this, &TaskFemConstraintFluidBoundary::onSubtypeChanged); connect(ui->spinBoundaryValue, qOverload(&QDoubleSpinBox::valueChanged), this, &TaskFemConstraintFluidBoundary::onBoundaryValueChanged); connect(ui->comboTurbulenceSpecification, qOverload(&QComboBox::currentIndexChanged), this, &TaskFemConstraintFluidBoundary::onTurbulenceSpecificationChanged); connect(ui->comboThermalBoundaryType, qOverload(&QComboBox::currentIndexChanged), this, &TaskFemConstraintFluidBoundary::onThermalBoundaryTypeChanged); connect(ui->buttonDirection, &QPushButton::pressed, this, [this] { onButtonDirection(true); }); connect(ui->checkReverse, &QCheckBox::toggled, this, &TaskFemConstraintFluidBoundary::onCheckReverse); connect(ui->listReferences, &QListWidget::itemClicked, this, &TaskFemConstraintFluidBoundary::setSelection); this->groupLayout()->addWidget(proxy); // Temporarily prevent unnecessary feature recomputes ui->spinBoundaryValue->blockSignals(true); ui->listReferences->blockSignals(true); // boundaryType and subType combo signal is Temporarily prevented in initComboBox() ui->buttonDirection->blockSignals(true); ui->checkReverse->blockSignals(true); // Selection buttons buttonGroup->addButton(ui->btnAdd, (int)SelectionChangeModes::refAdd); buttonGroup->addButton(ui->btnRemove, (int)SelectionChangeModes::refRemove); // Get the feature data Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); Fem::FemAnalysis* pcAnalysis = nullptr; if (FemGui::ActiveAnalysisObserver::instance()->hasActiveObject()) { pcAnalysis = FemGui::ActiveAnalysisObserver::instance()->getActiveObject(); } else { App::Document* aDoc = pcConstraint->getDocument(); std::vector fem = aDoc->getObjectsOfType(Fem::FemAnalysis::getClassTypeId()); if (!fem.empty()) { pcAnalysis = static_cast(fem[0]); // get the first } } Fem::FemMeshObject* pcMesh = nullptr; if (pcAnalysis) { std::vector fem = pcAnalysis->Group.getValues(); for (auto it : fem) { if (it->isDerivedFrom()) { pcMesh = static_cast(it); } } } else { Base::Console().log("FemAnalysis object is not activated or no FemAnalysis in the active " "document, mesh dimension is unknown\n"); dimension = -1; // unknown dimension of mesh } if (pcMesh) { App::Property* prop = pcMesh->getPropertyByName("Shape"); // PropertyLink if (prop && prop->isDerivedFrom()) { App::PropertyLink* pcLink = static_cast(prop); Part::Feature* pcPart = dynamic_cast(pcLink->getValue()); if (pcPart) { // deduct dimension from part_obj.Shape.ShapeType const TopoDS_Shape& pShape = pcPart->Shape.getShape().getShape(); const TopAbs_ShapeEnum shapeType = pShape.IsNull() ? TopAbs_SHAPE : pShape.ShapeType(); if (shapeType == TopAbs_SOLID || shapeType == TopAbs_COMPSOLID) { // COMPSOLID is solids connected by faces dimension = 3; } else if (shapeType == TopAbs_FACE || shapeType == TopAbs_SHELL) { dimension = 2; } else if (shapeType == TopAbs_EDGE || shapeType == TopAbs_WIRE) { dimension = 1; } else { dimension = -1; // Vertex (0D) can not make mesh, Compound type might contain any types } } } } pcSolver = nullptr; // this is an private object of type Fem::FemSolverObject* if (pcAnalysis) { std::vector fem = pcAnalysis->Group.getValues(); for (auto it : fem) { if (it->isDerivedFrom()) { pcSolver = static_cast(it); } } } pHeatTransferring = nullptr; pTurbulenceModel = nullptr; if (pcSolver) { // if only it is CFD solver, otherwise exit by SIGSEGV error, detect getPropertyByName() != // NULL if (pcSolver->getPropertyByName("HeatTransferring")) { pHeatTransferring = static_cast(pcSolver->getPropertyByName("HeatTransferring")); if (pHeatTransferring->getValue()) { ui->tabThermalBoundary->setEnabled(true); initComboBox(ui->comboThermalBoundaryType, pcConstraint->ThermalBoundaryType.getEnumVector(), pcConstraint->ThermalBoundaryType.getValueAsString()); ui->spinHTCoeffValue->setValue(pcConstraint->HTCoeffValue.getValue()); ui->spinHeatFluxValue->setValue(pcConstraint->HeatFluxValue.getValue()); ui->spinTemperatureValue->setValue(pcConstraint->TemperatureValue.getValue()); updateThermalBoundaryUI(); } else { ui->tabThermalBoundary->setEnabled(false); // could be hidden // Base::Console().message("retrieve solver property HeatTransferring as false\n"); } } else { ui->tabThermalBoundary->setEnabled(false); } if (pcSolver->getPropertyByName("TurbulenceModel")) { pTurbulenceModel = static_cast( pcSolver->getPropertyByName("TurbulenceModel")); if (pTurbulenceModel->getValueAsString() == std::string("laminar")) { ui->tabTurbulenceBoundary->setEnabled(false); } else { ui->tabTurbulenceBoundary->setEnabled(true); ui->labelTurbulenceSpecification->setText( QString::fromStdString(pTurbulenceModel->getValueAsString())); initComboBox(ui->comboTurbulenceSpecification, pcConstraint->TurbulenceSpecification.getEnumVector(), pcConstraint->TurbulenceSpecification.getValueAsString()); ui->spinTurbulentIntensityValue->setValue( pcConstraint->TurbulentIntensityValue.getValue()); ui->spinTurbulentLengthValue->setValue( pcConstraint->TurbulentLengthValue.getValue()); updateTurbulenceUI(); } } else { ui->tabTurbulenceBoundary->setEnabled(false); } } else { Base::Console().warning( "No solver object inside FemAnalysis object, default to non-thermal, non-turbulence\n"); } ui->tabWidget->setTabText(0, tr("Basic")); ui->tabWidget->setTabText(1, tr("Turbulence")); ui->tabWidget->setTabText(2, tr("Thermal")); ui->tabWidget->setCurrentIndex(0); ui->labelHelpText->setText(tr("select boundary type, faces and set value")); initComboBox(ui->comboBoundaryType, pcConstraint->BoundaryType.getEnumVector(), pcConstraint->BoundaryType.getValueAsString()); updateBoundaryTypeUI(); std::vector subtypes = pcConstraint->Subtype.getEnumVector(); initComboBox(ui->comboSubtype, subtypes, pcConstraint->Subtype.getValueAsString()); updateSubtypeUI(); std::vector Objects = pcConstraint->References.getValues(); std::vector SubElements = pcConstraint->References.getSubValues(); std::vector dirStrings = pcConstraint->Direction.getSubValues(); QString dir; if (!dirStrings.empty()) { dir = makeRefText(pcConstraint->Direction.getValue(), dirStrings.front()); } // Fill data into dialog elements double f = pcConstraint->BoundaryValue.getValue(); ui->spinBoundaryValue->setMinimum(std::numeric_limits::min()); // ZERO is not flexible ui->spinBoundaryValue->setMaximum(std::numeric_limits::max()); ui->spinBoundaryValue->setValue(f); ui->listReferences->clear(); for (std::size_t i = 0; i < Objects.size(); i++) { ui->listReferences->addItem(makeRefText(Objects[i], SubElements[i])); } if (!Objects.empty()) { ui->listReferences->setCurrentRow(0, QItemSelectionModel::ClearAndSelect); } ui->lineDirection->setText(dir.isEmpty() ? tr("") : dir); ui->checkReverse->setVisible(true); // it is still useful to swap direction of an edge ui->listReferences->blockSignals(false); ui->spinBoundaryValue->blockSignals(false); ui->buttonDirection->blockSignals(false); ui->checkReverse->blockSignals(false); updateUI(); } const Fem::FemSolverObject* TaskFemConstraintFluidBoundary::getFemSolver() const { return pcSolver; } void TaskFemConstraintFluidBoundary::updateBoundaryTypeUI() { Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); std::string boundaryType = ui->comboBoundaryType->currentText().toStdString(); // std::string boundaryType = pcConstraint->BoundaryType.getValueAsString(); // Update subtypes, any change here should be written back to FemConstraintFluidBoundary.cpp if (boundaryType == "wall") { ui->labelBoundaryValue->setText(QStringLiteral("velocity (m/s)")); ui->tabBasicBoundary->setEnabled(false); pcConstraint->Subtype.setEnums(WallSubtypes); } else if (boundaryType == "interface") { ui->labelBoundaryValue->setText(QStringLiteral("value not needed")); ui->tabBasicBoundary->setEnabled(false); pcConstraint->Subtype.setEnums(InterfaceSubtypes); } else if (boundaryType == "freestream") { ui->tabBasicBoundary->setEnabled(false); ui->labelBoundaryValue->setText(QStringLiteral("value not needed")); ui->tabBasicBoundary->setEnabled(false); pcConstraint->Subtype.setEnums(FreestreamSubtypes); } else if (boundaryType == "inlet") { ui->tabBasicBoundary->setEnabled(true); pcConstraint->Subtype.setEnums(InletSubtypes); ui->labelBoundaryValue->setText(QStringLiteral("Pressure [Pa]")); // default to pressure pcConstraint->Reversed.setValue(true); // inlet must point into volume } else if (boundaryType == "outlet") { ui->tabBasicBoundary->setEnabled(true); pcConstraint->Subtype.setEnums(OutletSubtypes); ui->labelBoundaryValue->setText(QStringLiteral("Pressure [Pa]")); pcConstraint->Reversed.setValue(false); // outlet must point outward } else { Base::Console().error("Error: Fluid boundary type `%s` is not defined\n", boundaryType.c_str()); } // std::string subtypeLabel = boundaryType + std::string(" type"); // ui->labelSubtype->setText(QString::fromUtf8(subtypeLabel)); // too long to show in UI ui->tabWidget->setCurrentIndex(0); // activate the basic pressure-momentum setting tab std::vector subtypes = pcConstraint->Subtype.getEnumVector(); initComboBox(ui->comboSubtype, subtypes, "default to the second subtype"); updateSubtypeUI(); } void TaskFemConstraintFluidBoundary::updateSubtypeUI() { std::string boundaryType = ui->comboBoundaryType->currentText().toStdString(); std::string subtype = ui->comboSubtype->currentText().toStdString(); if (boundaryType == "inlet" || boundaryType == "outlet") { ui->tabBasicBoundary->setEnabled(true); if (subtype == "totalPressure" || subtype == "staticPressure") { ui->labelBoundaryValue->setText(QStringLiteral("pressure [Pa]")); ui->buttonDirection->setEnabled(false); ui->lineDirection->setEnabled(false); } else if (subtype == "uniformVelocity") { ui->labelBoundaryValue->setText(QStringLiteral("velocity [m/s]")); ui->buttonDirection->setEnabled(true); ui->lineDirection->setEnabled(true); } else if (subtype == "massFlowrate") { ui->labelBoundaryValue->setText(QStringLiteral("flowrate [kg/s]")); ui->buttonDirection->setEnabled(false); ui->lineDirection->setEnabled(false); } else if (subtype == "volumetricFlowRate") { ui->labelBoundaryValue->setText(QStringLiteral("flowrate [m3/s]")); ui->buttonDirection->setEnabled(false); ui->lineDirection->setEnabled(false); } else { ui->labelBoundaryValue->setText(QStringLiteral("unspecific")); ui->tabBasicBoundary->setEnabled(false); } } else if (boundaryType == "wall") { if (subtype == "moving") { ui->labelBoundaryValue->setText(QStringLiteral("moving speed (m/s)")); ui->tabBasicBoundary->setEnabled(true); ui->buttonDirection->setEnabled(false); // moving speed must be parallel to wall ui->lineDirection->setEnabled(false); } else if (subtype == "slip") { ui->labelBoundaryValue->setText(QStringLiteral("not needed")); ui->tabBasicBoundary->setEnabled(false); } else if (subtype == "partialSlip") { ui->labelBoundaryValue->setText(QStringLiteral("slip ratio(0~1)")); ui->tabBasicBoundary->setEnabled(true); ui->buttonDirection->setEnabled(false); ui->lineDirection->setEnabled(false); } else { ui->labelBoundaryValue->setText(QStringLiteral("unspecific")); ui->tabBasicBoundary->setEnabled(false); } } else if (boundaryType == "interface") { ui->tabBasicBoundary->setEnabled(false); // show help text int iInterface = ui->comboSubtype->currentIndex(); ui->labelHelpText->setText(tr(InterfaceSubtypeHelpTexts[iInterface])); } else if (boundaryType == "freestream") { ui->tabBasicBoundary->setEnabled(true); } else { Base::Console().error("Fluid boundary type `%s` is not defined\n", boundaryType.c_str()); } } void TaskFemConstraintFluidBoundary::updateTurbulenceUI() { ui->labelHelpText->setText( tr(TurbulenceSpecificationHelpTexts[ui->comboTurbulenceSpecification->currentIndex()])); /// hide/disable UI only happened in constructor, update helptext and label text here std::string turbulenceSpec = ui->comboTurbulenceSpecification->currentText().toStdString(); ui->labelTurbulentIntensityValue->setText(tr("Intensity [0~1]")); if (turbulenceSpec == "intensity&DissipationRate") { ui->labelTurbulentLengthValue->setText(tr("Dissipation Rate [m2/s3]")); } else if (turbulenceSpec == "intensity&LengthScale") { ui->labelTurbulentLengthValue->setText(tr("Length Scale [m]")); } else if (turbulenceSpec == "intensity&ViscosityRatio") { ui->labelTurbulentLengthValue->setText(tr("Viscosity Ratio [1]")); } else if (turbulenceSpec == "intensity&HydraulicDiameter") { ui->labelTurbulentLengthValue->setText(tr("Hydraulic Diameter [m]")); } else { Base::Console().error("turbulence Spec type `%s` is not defined\n", turbulenceSpec.c_str()); } } void TaskFemConstraintFluidBoundary::updateThermalBoundaryUI() { // Fem::ConstraintFluidBoundary* pcConstraint = // ConstraintView->getObject(); std::string // thermalBoundaryType = pcConstraint->ThermalBoundaryType.getValueAsString(); ui->labelHelpText->setText( tr(ThermalBoundaryHelpTexts[ui->comboThermalBoundaryType->currentIndex()])); // to hide/disable UI according to subtype std::string thermalBoundaryType = ui->comboThermalBoundaryType->currentText().toStdString(); ui->spinHTCoeffValue->setEnabled(false); ui->spinTemperatureValue->setEnabled(false); ui->spinHeatFluxValue->setEnabled(false); if (thermalBoundaryType == "zeroGradient" || thermalBoundaryType == "coupled") { return; } else if (thermalBoundaryType == "fixedValue") { ui->spinTemperatureValue->setEnabled(true); } else if (thermalBoundaryType == "fixedGradient") { ui->spinHeatFluxValue->setEnabled(true); ui->labelHeatFlux->setText(tr("Gradient [K/m]")); } else if (thermalBoundaryType == "mixed") { ui->spinTemperatureValue->setEnabled(true); ui->spinHeatFluxValue->setEnabled(true); ui->labelHeatFlux->setText(tr("Gradient [K/m]")); } else if (thermalBoundaryType == "heatFlux") { ui->spinHeatFluxValue->setEnabled(true); ui->labelHeatFlux->setText(tr("Flux [W/m2]")); } else if (thermalBoundaryType == "HTC") { ui->spinHTCoeffValue->setEnabled(true); ui->spinTemperatureValue->setEnabled(true); } else { Base::Console().error("Thermal boundary type `%s` is not defined\n", thermalBoundaryType.c_str()); } } void TaskFemConstraintFluidBoundary::onBoundaryTypeChanged() { Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); // temporarily change BoundaryType property, but command transaction should reset it back if you // 'reject' late pcConstraint->BoundaryType.setValue(ui->comboBoundaryType->currentIndex()); updateBoundaryTypeUI(); ConstraintView->updateData(&pcConstraint->BoundaryType); // force a 3D redraw // update view provider once BoundaryType changed, updateData() may be just enough // FreeCAD.getDocument(pcConstraint->Document.getName()).recompute(); bool ret = pcConstraint->recomputeFeature(); if (!ret) { std::string boundaryType = ui->comboBoundaryType->currentText().toStdString(); Base::Console().error("Fluid boundary recomputationg failed for boundaryType `%s` \n", boundaryType.c_str()); } } void TaskFemConstraintFluidBoundary::onSubtypeChanged() { updateSubtypeUI(); // todo: change color for different kind of subtype, // Fem::ConstraintFluidBoundary::onChanged() and viewProvider } void TaskFemConstraintFluidBoundary::onBoundaryValueChanged(double) { // left empty for future extension } void TaskFemConstraintFluidBoundary::onTurbulenceSpecificationChanged() { Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); pcConstraint->TurbulenceSpecification.setValue( ui->comboTurbulenceSpecification->currentIndex()); updateTurbulenceUI(); } void TaskFemConstraintFluidBoundary::onThermalBoundaryTypeChanged() { Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); pcConstraint->ThermalBoundaryType.setValue(ui->comboThermalBoundaryType->currentIndex()); updateThermalBoundaryUI(); } void TaskFemConstraintFluidBoundary::onReferenceDeleted() { TaskFemConstraintFluidBoundary::removeFromSelection(); // On right-click face is automatically // selected, so just remove } void TaskFemConstraintFluidBoundary::onButtonDirection(const bool pressed) { // sets the normal vector of the currently selecteed planar face as direction Q_UNUSED(pressed); clearButtons(SelectionChangeModes::none); // get vector of selected objects of active document std::vector selection = Gui::Selection().getSelectionEx(); if (selection.empty()) { QMessageBox::warning(this, tr("Empty selection"), tr("Select an edge or a face.")); return; } Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); // we only handle the first selected object Gui::SelectionObject& selectionElement = selection.at(0); // we can only handle part objects if (!selectionElement.isObjectTypeOf(Part::Feature::getClassTypeId())) { QMessageBox::warning(this, tr("Wrong selection"), tr("Selected object is not a part object!")); return; } // get the names of the subobjects const std::vector& subNames = selectionElement.getSubNames(); if (subNames.size() != 1) { QMessageBox::warning(this, tr("Wrong selection"), tr("Only one planar face or edge can be selected!")); return; } // we are now sure we only have one object std::string subNamesElement = subNames[0]; // vector for the direction std::vector direction(1, subNamesElement); Part::Feature* feat = static_cast(selectionElement.getObject()); TopoDS_Shape ref = feat->Shape.getShape().getSubShape(subNamesElement.c_str()); if (subNamesElement.substr(0, 4) == "Face") { if (!Fem::Tools::isPlanar(TopoDS::Face(ref))) { QMessageBox::warning(this, tr("Wrong selection"), tr("Only planar faces can be picked for 3D")); return; } } else if (subNamesElement.substr(0, 4) == "Edge") { // 2D or 3D can use edge as direction vector if (!Fem::Tools::isLinear(TopoDS::Edge(ref))) { QMessageBox::warning(this, tr("Wrong selection"), tr("Only planar edges can be picked for 2D")); return; } } else { QMessageBox::warning(this, tr("Wrong selection"), tr("Only faces for 3D part or edges for 2D can be picked")); return; } // update the direction pcConstraint->Direction.setValue(feat, direction); ui->lineDirection->setText(makeRefText(feat, subNamesElement)); // Update UI updateUI(); } void TaskFemConstraintFluidBoundary::onCheckReverse(const bool pressed) { Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); pcConstraint->Reversed.setValue(pressed); } std::string TaskFemConstraintFluidBoundary::getBoundaryType() const { return ui->comboBoundaryType->currentText().toStdString(); } std::string TaskFemConstraintFluidBoundary::getSubtype() const { return ui->comboSubtype->currentText().toStdString(); } double TaskFemConstraintFluidBoundary::getBoundaryValue() const { return ui->spinBoundaryValue->value(); } std::string TaskFemConstraintFluidBoundary::getTurbulenceModel() const { if (pTurbulenceModel) { return pTurbulenceModel->getValueAsString(); } else { return "laminar"; } } std::string TaskFemConstraintFluidBoundary::getTurbulenceSpecification() const { return ui->comboTurbulenceSpecification->currentText().toStdString(); } double TaskFemConstraintFluidBoundary::getTurbulentIntensityValue() const { return ui->spinTurbulentIntensityValue->value(); } double TaskFemConstraintFluidBoundary::getTurbulentLengthValue() const { return ui->spinTurbulentLengthValue->value(); } bool TaskFemConstraintFluidBoundary::getHeatTransferring() const { if (pHeatTransferring) { return pHeatTransferring->getValue(); } else { return false; } } std::string TaskFemConstraintFluidBoundary::getThermalBoundaryType() const { return ui->comboThermalBoundaryType->currentText().toStdString(); } double TaskFemConstraintFluidBoundary::getTemperatureValue() const { return ui->spinTemperatureValue->value(); } double TaskFemConstraintFluidBoundary::getHeatFluxValue() const { return ui->spinHeatFluxValue->value(); } double TaskFemConstraintFluidBoundary::getHTCoeffValue() const { return ui->spinHTCoeffValue->value(); } const std::string TaskFemConstraintFluidBoundary::getReferences() const { int rows = ui->listReferences->model()->rowCount(); std::vector items; for (int r = 0; r < rows; r++) { items.push_back(ui->listReferences->item(r)->text().toStdString()); } return TaskFemConstraint::getReferences(items); } const std::string TaskFemConstraintFluidBoundary::getDirectionName() const { std::string dir = ui->lineDirection->text().toStdString(); if (dir.empty()) { return ""; } int pos = dir.find_last_of(":"); return dir.substr(0, pos).c_str(); } const std::string TaskFemConstraintFluidBoundary::getDirectionObject() const { std::string dir = ui->lineDirection->text().toStdString(); if (dir.empty()) { return ""; } int pos = dir.find_last_of(":"); return dir.substr(pos + 1).c_str(); } bool TaskFemConstraintFluidBoundary::getReverse() const { return ui->checkReverse->isChecked(); } TaskFemConstraintFluidBoundary::~TaskFemConstraintFluidBoundary() = default; void TaskFemConstraintFluidBoundary::addToSelection() { std::vector selection = Gui::Selection().getSelectionEx(); // gets vector of selected objects of active document if (selection.empty()) { QMessageBox::warning(this, tr("Selection error"), tr("Nothing selected!")); return; } Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); std::vector Objects = pcConstraint->References.getValues(); std::vector SubElements = pcConstraint->References.getSubValues(); for (auto& it : selection) { // for every selected object if (!it.isObjectTypeOf(Part::Feature::getClassTypeId())) { QMessageBox::warning(this, tr("Selection error"), tr("Selected object is not a part!")); return; } const std::vector& subNames = it.getSubNames(); App::DocumentObject* obj = it.getObject(); for (const auto& subName : subNames) { // for every selected sub element bool addMe = true; for (auto itr = std::ranges::find(SubElements, subName); itr != SubElements.end(); itr = std::find(++itr, SubElements.end(), subName)) { // for every sub element in selection that // matches one in old list if (obj == Objects[std::distance( SubElements.begin(), itr)]) { // if selected sub element's object equals the one in old list // then it was added before so don't add addMe = false; } } // limit constraint such that only vertexes or faces or edges can be used depending on // what was selected first std::string searchStr; if (subName.find("Vertex") != std::string::npos) { searchStr = "Vertex"; } else if (subName.find("Edge") != std::string::npos) { searchStr = "Edge"; } else { searchStr = "Face"; } for (const auto& SubElement : SubElements) { if (SubElement.find(searchStr) == std::string::npos) { QString msg = tr("Only one type of selection (vertex, face or edge) per " "analysis feature allowed!"); QMessageBox::warning(this, tr("Selection error"), msg); addMe = false; break; } } if (addMe) { QSignalBlocker block(ui->listReferences); Objects.push_back(obj); SubElements.push_back(subName); ui->listReferences->addItem(makeRefText(obj, subName)); } } } // Update UI pcConstraint->References.setValues(Objects, SubElements); updateUI(); } void TaskFemConstraintFluidBoundary::removeFromSelection() { std::vector selection = Gui::Selection().getSelectionEx(); // gets vector of selected objects of active document if (selection.empty()) { QMessageBox::warning(this, tr("Selection error"), tr("Nothing selected!")); return; } Fem::ConstraintFluidBoundary* pcConstraint = ConstraintView->getObject(); std::vector Objects = pcConstraint->References.getValues(); std::vector SubElements = pcConstraint->References.getSubValues(); std::vector itemsToDel; for (const auto& it : selection) { // for every selected object if (!it.isObjectTypeOf(Part::Feature::getClassTypeId())) { QMessageBox::warning(this, tr("Selection error"), tr("Selected object is not a part!")); return; } const std::vector& subNames = it.getSubNames(); const App::DocumentObject* obj = it.getObject(); for (const auto& subName : subNames) { // for every selected sub element for (auto itr = std::ranges::find(SubElements, subName); itr != SubElements.end(); itr = std::find(++itr, SubElements.end(), subName)) { // for every sub element in selection that // matches one in old list if (obj == Objects[std::distance( SubElements.begin(), itr)]) { // if selected sub element's object equals the one in old list // then it was added before so mark for deletion itemsToDel.push_back(std::distance(SubElements.begin(), itr)); } } } } std::sort(itemsToDel.begin(), itemsToDel.end()); while (!itemsToDel.empty()) { Objects.erase(Objects.begin() + itemsToDel.back()); SubElements.erase(SubElements.begin() + itemsToDel.back()); itemsToDel.pop_back(); } // Update UI { QSignalBlocker block(ui->listReferences); ui->listReferences->clear(); for (size_t j = 0; j < Objects.size(); j++) { ui->listReferences->addItem(makeRefText(Objects[j], SubElements[j])); } } pcConstraint->References.setValues(Objects, SubElements); updateUI(); } void TaskFemConstraintFluidBoundary::updateUI() { if (ui->listReferences->model()->rowCount() == 0) { // Go into reference selection mode if no reference has been selected yet onButtonReference(true); return; } } void TaskFemConstraintFluidBoundary::changeEvent(QEvent* e) { TaskBox::changeEvent(e); if (e->type() == QEvent::LanguageChange) { ui->spinBoundaryValue->blockSignals(true); // more ui widget? those UI are does not support tr yet! ui->retranslateUi(proxy); ui->spinBoundaryValue->blockSignals(false); } } void TaskFemConstraintFluidBoundary::clearButtons(const SelectionChangeModes notThis) { if (notThis != SelectionChangeModes::refAdd) { ui->btnAdd->setChecked(false); } if (notThis != SelectionChangeModes::refRemove) { ui->btnRemove->setChecked(false); } } //************************************************************************** //************************************************************************** // TaskDialog //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskDlgFemConstraintFluidBoundary::TaskDlgFemConstraintFluidBoundary( ViewProviderFemConstraintFluidBoundary* ConstraintView) { this->ConstraintView = ConstraintView; assert(ConstraintView); this->parameter = new TaskFemConstraintFluidBoundary(ConstraintView); Content.push_back(parameter); } //==== calls from the TaskView =============================================================== bool TaskDlgFemConstraintFluidBoundary::accept() { std::string name = ConstraintView->getObject()->getNameInDocument(); const TaskFemConstraintFluidBoundary* boundary = static_cast(parameter); // no need to backup pcConstraint object content, if rejected, content can be recovered by // transaction manager try { // Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Fluid boundary condition // changed")); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.BoundaryType = '%s'", name.c_str(), boundary->getBoundaryType().c_str()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.Subtype = '%s'", name.c_str(), boundary->getSubtype().c_str()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.BoundaryValue = %f", name.c_str(), boundary->getBoundaryValue()); std::string dirname = boundary->getDirectionName().data(); std::string dirobj = boundary->getDirectionObject().data(); if (!dirname.empty()) { QString buf = QStringLiteral("(App.ActiveDocument.%1,[\"%2\"])"); buf = buf.arg(QString::fromStdString(dirname)); buf = buf.arg(QString::fromStdString(dirobj)); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.Direction = %s", name.c_str(), buf.toStdString().c_str()); } else { Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.Direction = None", name.c_str()); } // Reverse control is done at BoundaryType selection, this UI is hidden from user // Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Reversed = %s", // name.c_str(), boundary->getReverse() ? "True" : "False"); // solver specific setting, physical model selection const Fem::FemSolverObject* pcSolver = boundary->getFemSolver(); if (pcSolver) { App::PropertyBool* pHeatTransferring = nullptr; App::PropertyEnumeration* pTurbulenceModel = nullptr; pHeatTransferring = static_cast(pcSolver->getPropertyByName("HeatTransferring")); pTurbulenceModel = static_cast( pcSolver->getPropertyByName("TurbulenceModel")); if (pHeatTransferring && pHeatTransferring->getValue()) { Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.ThermalBoundaryType = '%s'", name.c_str(), boundary->getThermalBoundaryType().c_str()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.TemperatureValue = %f", name.c_str(), boundary->getTemperatureValue()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.HeatFluxValue = %f", name.c_str(), boundary->getHeatFluxValue()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.HTCoeffValue = %f", name.c_str(), boundary->getHTCoeffValue()); } if (pTurbulenceModel && std::string(pTurbulenceModel->getValueAsString()) != "laminar") { // Invisic and DNS flow also does not need this // update turbulence and thermal boundary settings, only if those models are // activated Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.TurbulenceSpecification = '%s'", name.c_str(), boundary->getTurbulenceSpecification().c_str()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.TurbulentIntensityValue = %f", name.c_str(), boundary->getTurbulentIntensityValue()); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.TurbulentLengthValue = %f", name.c_str(), boundary->getTurbulentLengthValue()); } } else { Base::Console().warning("FemSolverObject is not found in the FemAnalysis object, " "thermal and turbulence setting is not accepted\n"); } } catch (const Base::Exception& e) { QMessageBox::warning(parameter, tr("Input error"), QString::fromLatin1(e.what())); return false; } return TaskDlgFemConstraint::accept(); } #include "moc_TaskFemConstraintFluidBoundary.cpp"