/*========================================================================= medInria Copyright (c) INRIA 2013 - 2019. All rights reserved. See LICENSE.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. =========================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class medRemeshingToolBoxPrivate { public: dtkSmartPointer decimateProcess; dtkSmartPointer refineProcess; dtkSmartPointer smoothProcess; medRemeshingToolBox::ProcessName currentProcess; medRemeshingToolBox::ProcessName lastManualProcess; medAbstractLayeredView *view; dtkSmartPointer input, refinedMesh; QLabel *initialCellsLabel, *outputCellsLabel; QSpinBox *trianglesSpinBox; QPushButton *decimateButton, *refineButton, *runUserNumber, *resetButton; QRadioButton *topologyRadioButton; vtkSmartPointer original_points; // Smooth tool QSpinBox *iterationsSpinBox; QDoubleSpinBox *relaxationSpinBox; QPushButton *smoothButton; }; medRemeshingToolBox::medRemeshingToolBox(QWidget *parent) : medAbstractSelectableToolBox(parent) , d(new medRemeshingToolBoxPrivate) { QVBoxLayout *displayLayout = new QVBoxLayout(); // Font used for warning, information, etc. QFont font; font.setItalic(true); // Decimate and Refine buttons QLabel* decimateRefineTitle = new QLabel("Decimate/Refine", this); decimateRefineTitle->setStyleSheet("QLabel { color : #ED6639; }"); displayLayout->addWidget(decimateRefineTitle); // Decimate QLabel *explainDecimationTxt = new QLabel("divide by 4", this); d->decimateButton = new QPushButton("Decimate", this); d->decimateButton->setToolTip("Reduce the number of cells by 4"); connect (d->decimateButton, SIGNAL(clicked()), this, SLOT(launchDecimate())); QHBoxLayout *layoutDecimate = new QHBoxLayout(); layoutDecimate->addWidget(explainDecimationTxt); layoutDecimate->addWidget(d->decimateButton); displayLayout->addLayout(layoutDecimate); d->topologyRadioButton = new QRadioButton(tr("Preserve topology during decimation")); d->topologyRadioButton->setToolTip("Turn on/off whether to preserve the mesh topology.\n If on, splitting and hole elimination will not occur.\n Can limit the maximum reduction that may be achieved."); d->topologyRadioButton->setChecked(true); d->topologyRadioButton->setObjectName("topologyRadioButton"); displayLayout->addWidget(d->topologyRadioButton); connect (d->topologyRadioButton, SIGNAL(toggled(bool)), this, SLOT(allowDecimateIfTopologyButtonUnchecked(bool))); // Refine QLabel *explainRefineTxt = new QLabel("multiply by 4", this); d->refineButton = new QPushButton("Refine", this); d->refineButton->setToolTip("Increase the number of cells by 4"); connect (d->refineButton, SIGNAL(clicked()), this, SLOT(launchRefine())); QHBoxLayout *layoutRefine = new QHBoxLayout(); layoutRefine->addWidget(explainRefineTxt); layoutRefine->addWidget(d->refineButton); displayLayout->addLayout(layoutRefine); // Separator QFrame *line1 = new QFrame(this); line1->setFrameShape(QFrame::HLine); line1->setFrameShadow(QFrame::Sunken); displayLayout->addWidget(line1); // User can choose a number of cells QLabel *numberTitle = new QLabel("Choose the number of cells that you want", this); numberTitle->setStyleSheet("QLabel { color : #ED6639; }"); displayLayout->addWidget(numberTitle); QLabel *numberTxt = new QLabel("number of cells:", this); d->trianglesSpinBox = new QSpinBox; d->trianglesSpinBox->setMaximum(30000000); d->trianglesSpinBox->setMinimum(0); d->trianglesSpinBox->setValue(0); d->runUserNumber = new QPushButton("Run", this); connect(d->runUserNumber, SIGNAL(clicked()), this, SLOT(imposeNbOfTriangles())); QHBoxLayout *numberLayout = new QHBoxLayout(); numberLayout->addWidget(numberTxt); numberLayout->addWidget(d->trianglesSpinBox); numberLayout->addWidget(d->runUserNumber); displayLayout->addLayout(numberLayout); // Separator QFrame *line2 = new QFrame(this); line2->setFrameShape(QFrame::HLine); line2->setFrameShadow(QFrame::Sunken); displayLayout->addWidget(line2); // Smooth tool QLabel *smoothTitle = new QLabel("Smooth", this); smoothTitle->setStyleSheet("QLabel { color : #ED6639; }"); displayLayout->addWidget(smoothTitle); QLabel *iterationsLabel = new QLabel("iterations: ", this); d->iterationsSpinBox = new QSpinBox; d->iterationsSpinBox->setRange(0, 100); d->iterationsSpinBox->setSingleStep(1); d->iterationsSpinBox->setValue(30); QLabel *relaxationLabel = new QLabel("relaxation factor: ", this); d->relaxationSpinBox = new QDoubleSpinBox; d->relaxationSpinBox->setRange(0.0, 1.0); d->relaxationSpinBox->setSingleStep(0.01); d->relaxationSpinBox->setDecimals(2); d->relaxationSpinBox->setValue(0.2); d->smoothButton = new QPushButton(tr("Run"), this); connect(d->smoothButton, SIGNAL(clicked()), this, SLOT(smoothMesh())); QHBoxLayout *smoothLayout = new QHBoxLayout(); smoothLayout->addWidget(iterationsLabel); smoothLayout->addStretch(1); smoothLayout->addWidget(d->iterationsSpinBox); smoothLayout->addStretch(1); smoothLayout->addWidget(relaxationLabel); smoothLayout->addStretch(1); smoothLayout->addWidget(d->relaxationSpinBox); smoothLayout->addStretch(1); smoothLayout->addWidget(d->smoothButton); smoothLayout->addStretch(1); displayLayout->addLayout(smoothLayout); // Separator QFrame *line3 = new QFrame(this); line3->setFrameShape(QFrame::HLine); line3->setFrameShadow(QFrame::Sunken); displayLayout->addWidget(line3); // Number of cells labels QVBoxLayout *numberOfCellsLayout = new QVBoxLayout(); d->initialCellsLabel = new QLabel(this); d->initialCellsLabel->setFont(font); d->outputCellsLabel = new QLabel(this); d->outputCellsLabel->setFont(font); d->outputCellsLabel->hide(); numberOfCellsLayout->addWidget(d->initialCellsLabel); numberOfCellsLayout->addWidget(d->outputCellsLabel); displayLayout->addLayout(numberOfCellsLayout); // Reset button d->resetButton = new QPushButton(tr("Reset"), this); d->resetButton->setEnabled(false); d->resetButton->setObjectName("resetButton"); connect(d->resetButton, SIGNAL(clicked()), this, SLOT(reset())); displayLayout->addWidget(d->resetButton); QWidget *widget = new QWidget(this); widget->setLayout(displayLayout); this->addWidget(widget); disableButtons(true); d->view = nullptr; d->input = nullptr; d->refinedMesh = nullptr; d->original_points = nullptr; this->hide(); } medRemeshingToolBox::~medRemeshingToolBox() { delete d; d = nullptr; } medAbstractData *medRemeshingToolBox::processOutput() { medAbstractData *res = nullptr; switch (d->currentProcess) { case REFINE: { if(d->refineProcess) { res = d->refineProcess->output(); } break; } case DECIMATE: { if(d->decimateProcess) { res = d->decimateProcess->output(); } break; } case SMOOTH: { if(d->smoothProcess) { res = d->smoothProcess->output(); } break; } case MANUAL: { if(d->lastManualProcess == REFINE) { medUtilities::setDerivedMetaData(d->refineProcess->output(), d->input, "manual"); res = d->refineProcess->output(); } else if(d->lastManualProcess == DECIMATE) { medUtilities::setDerivedMetaData(d->decimateProcess->output(), d->input, "manual"); res = d->decimateProcess->output(); } break; } } return res; } bool medRemeshingToolBox::registered() { return medToolBoxFactory::instance()->registerToolBox(); } dtkPlugin* medRemeshingToolBox::plugin() { return medPluginManager::instance().plugin("Remeshing"); } void medRemeshingToolBox::updateView() { medAbstractView *view = this->getWorkspace()->tabbedViewContainers()->getFirstSelectedContainerView(); if(!view) { clear(); return; } medAbstractLayeredView *medView = dynamic_cast (view); if ((d->view) && (d->view != medView) ) { clear(); } d->view = medView; medAbstractData *data = d->view->layerData(d->view->currentLayer()); if (data && data->identifier().contains("vtkDataMesh")) { if(!d->input) { d->input = d->view->layerData(d->view->currentLayer()); d->refinedMesh = d->view->layerData(d->view->currentLayer()); disableButtons(false); vtkMetaDataSet *_dataset = static_cast(d->input->data()); d->original_points = vtkSmartPointer::Take(_dataset->GetDataSet()->NewInstance()); d->original_points->DeepCopy(_dataset->GetDataSet()); d->trianglesSpinBox->setValue(getNumberOfCells(d->input)); d->initialCellsLabel->setText("Original number of cells: " + QString::number(getNumberOfCells(d->input))); QObject::connect(d->view, SIGNAL(currentLayerChanged()), this, SLOT(clearAndUpdate()), Qt::UniqueConnection); medUtilities::switchTo3D(d->view, medUtilities::MSR); } } } void medRemeshingToolBox::clearAndUpdate() { clear(); updateView(); } void medRemeshingToolBox::clear() { d->view = nullptr; d->input = nullptr; d->refinedMesh = nullptr; d->original_points = nullptr; d->resetButton->setEnabled(false); d->outputCellsLabel->hide(); d->initialCellsLabel->setText(""); d->trianglesSpinBox->setValue(0); // "Choose the number of cells", Decimate, Refine and Smooth are not allowed without data disableButtons(true); } void medRemeshingToolBox::reset() { if (d->input && d->input->data()) { displayChanges(d->input); allowDecimate(); } } void medRemeshingToolBox::launchDecimate() { d->currentProcess = DECIMATE; decimateMesh(0.75); // Divide the nb of triangles by 4 (reverse operation of refine) } void medRemeshingToolBox::decimateMesh(double factor, dtkSmartPointer data) { if (d->refinedMesh) { this->setToolBoxOnWaitStatus(); if (!d->decimateProcess) { d->decimateProcess = dtkAbstractProcessFactory::instance()->createSmartPointer("medDecimateMeshProcess"); } if (!data) { d->decimateProcess->setInput(d->refinedMesh, 0); } else { d->decimateProcess->setInput(data, 0); } d->decimateProcess->setParameter(factor, 0); d->decimateProcess->setParameter(d->topologyRadioButton->isChecked(), 1); connect(d->decimateProcess, SIGNAL(warning()), this, SLOT (cantDecimateMore())); connect(d->decimateProcess, SIGNAL(polyDataCastFailure()), this, SLOT (displayCastFailure())); medRunnableProcess *runProcess = new medRunnableProcess; runProcess->setProcess (d->decimateProcess); connect (runProcess, SIGNAL (success (QObject*)), this, SLOT(addMeshToView())); this->addConnectionsAndStartJob(runProcess); } } void medRemeshingToolBox::launchRefine() { d->currentProcess = REFINE; refineMesh(1.0); // Multiply the nb of triangles by 4 (due to loop algorithm) } void medRemeshingToolBox::refineMesh(double factor, dtkSmartPointer data) { if (d->refinedMesh) { this->setToolBoxOnWaitStatus(); if (!d->refineProcess) { d->refineProcess = dtkAbstractProcessFactory::instance()->createSmartPointer("medRefineMeshProcess"); } if (!data) { d->refineProcess->setInput(d->refinedMesh); } else { d->refineProcess->setInput(data); } d->refineProcess->setParameter(factor, 0); connect(d->refineProcess, SIGNAL(polyDataCastFailure()), this, SLOT (displayCastFailure())); medRunnableProcess *runProcess = new medRunnableProcess; runProcess->setProcess (d->refineProcess); connect (runProcess, SIGNAL (success (QObject*)), this, SLOT(addMeshToView())); this->addConnectionsAndStartJob(runProcess); } } void medRemeshingToolBox::imposeNbOfTriangles() { this->setToolBoxOnWaitStatusForNonRunnableProcess(); d->currentProcess = MANUAL; d->trianglesSpinBox->blockSignals(true); d->decimateProcess = dtkAbstractProcessFactory::instance()->createSmartPointer("medDecimateMeshProcess"); d->decimateProcess->setParameter(false, 2); //do not change MetaData d->refineProcess = dtkAbstractProcessFactory::instance()->createSmartPointer("medRefineMeshProcess"); d->refineProcess->setParameter(false, 2); //do not change MetaData double askedNumber = static_cast(d->trianglesSpinBox->value()); double actualNumber = getNumberOfCells(d->refinedMesh); double decimateValue = 0.0; if( askedNumber < actualNumber ) { d->lastManualProcess = DECIMATE; decimateValue = 1.0 - askedNumber/actualNumber; decimateMesh(decimateValue); } else if( askedNumber > actualNumber ) { // Refine only multiply the number of cells by x*4, so we need to // multiply by x*4 in order to have a higher number of cells than the asked number, // and decimate after. double refineValue = ceil(log(askedNumber/actualNumber)/log(4.0)); d->refineProcess->setInput(d->refinedMesh); d->refineProcess->setParameter(refineValue, 0); connect(d->refineProcess, SIGNAL(polyDataCastFailure()), this, SLOT (displayCastFailure())); d->lastManualProcess = REFINE; if(d->refineProcess->update() == medAbstractProcessLegacy::SUCCESS) { allowDecimate(); d->lastManualProcess = DECIMATE; decimateValue = 1.0 - askedNumber/getNumberOfCells(d->refineProcess->output()); decimateMesh(decimateValue, d->refineProcess->output()); } } d->decimateProcess->setParameter(true, 2); //change MetaData d->refineProcess->setParameter(true, 1); //change MetaData d->trianglesSpinBox->blockSignals(false); this->setToolBoxOnReadyToUse(); } double medRemeshingToolBox::getNumberOfCells(dtkSmartPointer data) { vtkMetaDataSet *metaDataSet = static_cast(data->data()); vtkDataSet *dataSet = dynamic_cast(metaDataSet->GetDataSet()); return dataSet->GetNumberOfCells(); } void medRemeshingToolBox::addMeshToView() { medRunnableProcess *process = static_cast(sender()); QStringList options; options << "medDecimateMeshProcess" << "medRefineMeshProcess" << "medSmoothMeshProcess"; switch (options.indexOf(process->getProcess()->description())) { case 0: { displayComputedMesh(d->decimateProcess->output()); break; } case 1: { displayComputedMesh(d->refineProcess->output()); allowDecimate(); break; } case 2: { displayComputedMesh(d->smoothProcess->output()); break; } default: { return; } } } void medRemeshingToolBox::displayComputedMesh(dtkSmartPointer output) { if (output && output->data()) { displayChanges(output); d->resetButton->setEnabled(true); } } void medRemeshingToolBox::displayChanges(dtkSmartPointer output) { d->refinedMesh = output; vtkImageView3D *view3D = static_cast(d->view->backend())->view3D; vtkImageView2D *view2D = static_cast(d->view->backend())->view2D; vtkMetaDataSet *_dataset = static_cast(output->data()); vtkPointSet *new_points = vtkPointSet::SafeDownCast(_dataset->GetDataSet()); vtkPointSet *pointsInView3d = vtkPointSet::SafeDownCast(view3D->GetDataSetCollection()->GetItem(d->view->currentLayer())); vtkPointSet *pointsInView2d = vtkPointSet::SafeDownCast(view2D->GetDataSetCollection()->GetItem(d->view->currentLayer())); pointsInView3d->ShallowCopy(new_points); pointsInView2d->ShallowCopy(new_points); vtkMetaDataSet *inputDataSet = static_cast(d->input->data()); inputDataSet->SetDataSet(d->original_points); medAbstractView *view = this->getWorkspace()->tabbedViewContainers()->getFirstSelectedContainerView(); view->render(); displayNewNumberOfCells(output); } void medRemeshingToolBox::displayNewNumberOfCells(dtkSmartPointer output) { d->outputCellsLabel->setText("New number of cells: " + QString::number(static_cast(getNumberOfCells(output)))); d->outputCellsLabel->show(); d->trianglesSpinBox->setValue(getNumberOfCells(output)); // Update in case of limit reached } void medRemeshingToolBox::cantDecimateMore() { medMessageController::instance().showError(tr("Not possible to decimate more without changing the mesh topology"), 5000); d->decimateButton->setEnabled(false); } void medRemeshingToolBox::allowDecimate() { d->decimateButton->setEnabled(true); } void medRemeshingToolBox::allowDecimateIfTopologyButtonUnchecked(bool checked) { if (!checked && d->input) { allowDecimate(); } } void medRemeshingToolBox::disableButtons(bool val) { d->runUserNumber->setDisabled(val); // "choose the number of cells" d->decimateButton->setDisabled(val); // Decimate d->refineButton->setDisabled(val); // Refine d->smoothButton->setDisabled(val); // Smooth } void medRemeshingToolBox::smoothMesh() { d->currentProcess = SMOOTH; if (d->refinedMesh) { this->setToolBoxOnWaitStatus(); if (!d->smoothProcess) { d->smoothProcess = dtkAbstractProcessFactory::instance()->createSmartPointer("medSmoothMeshProcess"); } d->smoothProcess->setInput(d->refinedMesh); d->smoothProcess->setParameter((double)d->iterationsSpinBox->value(), 0); d->smoothProcess->setParameter(d->relaxationSpinBox->value(), 1); connect(d->smoothProcess, SIGNAL(polyDataCastFailure()), this, SLOT(displayCastFailure())); medRunnableProcess *runProcess = new medRunnableProcess; runProcess->setProcess (d->smoothProcess); connect (runProcess, SIGNAL (success (QObject*)), this, SLOT(addMeshToView())); this->addConnectionsAndStartJob(runProcess); } } void medRemeshingToolBox::displayCastFailure() { displayMessageError("This toolbox is designed for vtkPolyData-based meshes"); }