//########################################################################## //# # //# CLOUDCOMPARE # //# # //# This program is free software; you can redistribute it and/or modify # //# it under the terms of the GNU General Public License as published by # //# the Free Software Foundation; version 2 or later of the License. # //# # //# This program 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 General Public License for more details. # //# # //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) # //# # //########################################################################## #include "ccClippingBoxTool.h" //Local #include "ccBoundingBoxEditorDlg.h" #include "ccClippingBoxRepeatDlg.h" #include "ccContourLinesGenerator.h" #include "ccCropTool.h" #include "ccGLWindowInterface.h" #include "ccReservedIDs.h" #include "mainwindow.h" #include "db_tree/ccDBRoot.h" //qCC_db #include #include #include #include //Qt #include namespace { //Last envelope or contour unique ID std::vector s_lastContourUniqueIDs; //Envelope extraction parameters (global) double s_maxEnvelopeEdgeLength = -1.0; //Meta-data key: origin entity UUID constexpr char s_originEntityUUID[] = "OriginEntityUUID"; //Meta-data key: slice (unique) ID constexpr char s_sliceID[] = "SliceID"; } // persistent map of the previous box used for each entity struct ccClipBoxParams { ccBBox box; ccGLMatrix trans; }; static QMap< unsigned, ccClipBoxParams > s_lastBoxParams; ccClippingBoxTool::ccClippingBoxTool(QWidget* parent) : ccOverlayDialog(parent) , Ui::ClippingBoxDlg() , m_clipBox(nullptr) { setupUi(this); connect(editBoxToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::editBox); connect(extractContourToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::extractContour); connect(removeLastContourToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::removeLastContour); connect(exportButton, &QToolButton::clicked, this, &ccClippingBoxTool::exportSlice); connect(exportMultButton, &QToolButton::clicked, this, &ccClippingBoxTool::exportMultSlices); connect(resetButton, &QToolButton::clicked, this, &ccClippingBoxTool::reset); connect(restoreToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::restoreLastBox); connect(closeButton, &QToolButton::clicked, this, &ccClippingBoxTool::closeDialog); connect(showBoxToolButton, &QToolButton::toggled, this, &ccClippingBoxTool::toggleBox); connect(showInteractorsToolButton, &QToolButton::toggled, this, &ccClippingBoxTool::toggleInteractors); connect(thickXDoubleSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &ccClippingBoxTool::thicknessChanged); connect(thickYDoubleSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &ccClippingBoxTool::thicknessChanged); connect(thickZDoubleSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &ccClippingBoxTool::thicknessChanged); connect(minusXShiftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::shiftXMinus); connect(plusXShiftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::shiftXPlus); connect(minusYShiftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::shiftYMinus); connect(plusYShiftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::shiftYPlus); connect(minusZShiftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::shiftZMinus); connect(plusZShiftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::shiftZPlus); viewButtonsFrame->setEnabled(true); connect(viewUpToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::setTopView); connect(viewDownToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::setBottomView); connect(viewFrontToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::setFrontView); connect(viewBackToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::setBackView); connect(viewLeftToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::setLeftView); connect(viewRightToolButton, &QToolButton::clicked, this, &ccClippingBoxTool::setRightView); s_maxEnvelopeEdgeLength = -1.0; //s_lastContourUniqueIDs.clear(); removeLastContourToolButton->setEnabled(false); } ccClippingBoxTool::~ccClippingBoxTool() { if (m_clipBox) delete m_clipBox; m_clipBox = nullptr; } void ccClippingBoxTool::editBox() { if (!m_clipBox) return; ccBBox box; ccGLMatrix transformation; m_clipBox->get(box, transformation); //shift the box to its real center { CCVector3 C = box.getCenter(); CCVector3 realC = transformation * C; box += (realC - C); ccGLMatrix transMat; transMat.setTranslation(-realC); transformation.clearTranslation(); transMat = transformation * transMat; transMat.setTranslation(transMat.getTranslationAsVec3D() + realC); transformation = transMat; } ccBoundingBoxEditorDlg bbeDlg(true, false, this); //show the box 'axes' (orientation) bbeDlg.setBaseBBox(box, false); bbeDlg.showInclusionWarning(false); bbeDlg.setWindowTitle("Edit clipping box"); //transformation.invert(); bbeDlg.setBoxAxes( transformation.getColumnAsVec3D(0), transformation.getColumnAsVec3D(1), transformation.getColumnAsVec3D(2) ); if (!bbeDlg.exec()) return; box = bbeDlg.getBox(); //construct the local box orientation matrix { CCVector3d X; CCVector3d Y; CCVector3d Z; bbeDlg.getBoxAxes(X, Y, Z); //make sure the vectors define an orthogonal basis Z = X.cross(Y); Y = Z.cross(X); X.normalize(); Y.normalize(); Z.normalize(); ccGLMatrixd rotMat; rotMat.setColumn(0, X); rotMat.setColumn(1, Y); rotMat.setColumn(2, Z); CCVector3 C = box.getCenter(); ccGLMatrixd transMat; transMat.setTranslation(-C); transMat = rotMat * transMat; transMat.setTranslation(transMat.getTranslationAsVec3D() + C); m_clipBox->setGLTransformation(ccGLMatrix(transMat.data())); } m_clipBox->setBox(box); //onBoxModified(&box); //DGM: automatically called by 'm_clipBox' if (m_associatedWin) { m_associatedWin->redraw(); } } void ccClippingBoxTool::toggleInteractors(bool state) { if (m_clipBox) m_clipBox->setSelected(state); if (m_associatedWin) m_associatedWin->redraw(); } void ccClippingBoxTool::toggleBox(bool state) { if (m_clipBox) m_clipBox->showBox(state); if (m_associatedWin) m_associatedWin->redraw(); } bool ccClippingBoxTool::addAssociatedEntity(ccHObject* entity) { if (!entity) { assert(false); return false; } //special case if (entity->isGroup()) { for (unsigned i = 0; i < entity->getChildrenNumber(); ++i) { if (!addAssociatedEntity(entity->getChild(i))) { return false; } } return true; } if (!m_associatedWin || !m_clipBox) { ccLog::Error(QString("[Clipping box] No associated 3D view or no valid clipping box!")); return false; } //we don't handle entities associated to another context if (entity->getDisplay() != m_associatedWin) { ccLog::Warning(QString("[Clipping box] Can't use entity '%1' cause it's not displayed in the active 3D view!").arg(entity->getName())); return false; } bool firstEntity = (m_clipBox && m_clipBox->getContainer().getChildrenNumber() == 0); if (firstEntity) { restoreToolButton->setEnabled(false); contourGroupBox->setEnabled(false); } if (!m_clipBox->addAssociatedEntity(entity)) { //error message already issued ccLog::Error("An error occurred (see Console)"); return false; } if (s_lastBoxParams.contains(entity->getUniqueID())) { restoreToolButton->setEnabled(true); } if (entity->isKindOf(CC_TYPES::POINT_CLOUD)) { contourGroupBox->setEnabled(true); } //force visibility entity->setVisible(true); entity->setEnabled(true); if (m_associatedWin) { m_associatedWin->redraw(); } //set proper "steps" value for slice thickness editors { CCVector3 diag = m_clipBox->getBox().getDiagVec(); thickXDoubleSpinBox->setSingleStep(diag.x / 100.0); thickYDoubleSpinBox->setSingleStep(diag.y / 100.0); thickZDoubleSpinBox->setSingleStep(diag.z / 100.0); } s_maxEnvelopeEdgeLength = -1.0; s_lastContourUniqueIDs.resize(0); removeLastContourToolButton->setEnabled(false); return true; } unsigned ccClippingBoxTool::getNumberOfAssociatedEntity() const { return m_clipBox ? m_clipBox->getContainer().getChildrenNumber() : 0; } bool ccClippingBoxTool::linkWith(ccGLWindowInterface* win) { if (m_associatedWin && m_clipBox) { // restore the the clipping planes m_associatedWin->setClippingPlanesEnabled(true); //remove clipping box from previous window m_associatedWin->removeFromOwnDB(m_clipBox); m_clipBox->disconnect(this); delete m_clipBox; m_clipBox = nullptr; m_associatedWin->redraw(); } if (!ccOverlayDialog::linkWith(win)) { return false; } if (win) { if (!m_clipBox) { m_clipBox = new ccClipBox(QString(), static_cast(ReservedIDs::CLIPPING_BOX)); m_clipBox->setVisible(true); m_clipBox->setEnabled(true); m_clipBox->setSelected(showInteractorsToolButton->isChecked()); connect(m_clipBox, &ccClipBox::boxModified, this, &ccClippingBoxTool::onBoxModified); } // the clipping box tool doesn't tak the clipping planes into account // so we'll disable them temporarily m_associatedWin->setClippingPlanesEnabled(false); m_associatedWin->addToOwnDB(m_clipBox); } return true; } bool ccClippingBoxTool::start() { assert(!m_processing); assert(m_associatedWin); if (!m_associatedWin || !m_clipBox) return false; m_clipBox->reset(); //the user must not close this window! m_associatedWin->setUnclosable(true); //m_associatedWin->displayNewMessage(QString(), ccGLWindowInterface::UPPER_CENTER_MESSAGE); //clear the area //m_associatedWin->displayNewMessage("[Rotation/Translation mode]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); m_associatedWin->redraw(); return ccOverlayDialog::start(); } void ccClippingBoxTool::stop(bool state) { if (m_clipBox) { if (state && m_clipBox->getContainer().getChildrenNumber()) { //save clip box parameters ccClipBoxParams params; m_clipBox->get(params.box, params.trans); for (unsigned ci = 0; ci != m_clipBox->getContainer().getChildrenNumber(); ++ci) { s_lastBoxParams[m_clipBox->getContainer().getChild(ci)->getUniqueID()] = params; } } m_clipBox->releaseAssociatedEntities(); } if (m_associatedWin) { m_associatedWin->setUnclosable(false); //m_associatedWin->displayNewMessage("[Rotation/Translation mode OFF]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 2, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); m_associatedWin->redraw(); } ccOverlayDialog::stop(state); } void ccClippingBoxTool::removeLastContour() { if (s_lastContourUniqueIDs.empty()) return; MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow) { for (size_t i = 0; i < s_lastContourUniqueIDs.size(); ++i) { ccHObject* obj = mainWindow->db()->find(s_lastContourUniqueIDs[i]); if (obj) { //obj->prepareDisplayForRefresh(); mainWindow->removeFromDB(obj); ccGLWindowInterface* win = mainWindow->getActiveGLWindow(); if (win) win->redraw(); } } } s_lastContourUniqueIDs.resize(0); removeLastContourToolButton->setEnabled(false); } ccHObject* GetSlice(ccHObject* obj, ccClipBox* clipBox, bool silent) { assert(clipBox); if (!obj) { assert(false); return nullptr; } if (obj->isKindOf(CC_TYPES::POINT_CLOUD)) { ccGenericPointCloud* inputCloud = ccHObjectCaster::ToGenericPointCloud(obj); ccGenericPointCloud::VisibilityTableType selectionTable; try { selectionTable.resize(inputCloud->size()); } catch (const std::bad_alloc&) { if (!silent) { ccLog::Error("Not enough memory!"); } return nullptr; } clipBox->flagPointsInside(inputCloud, &selectionTable); ccGenericPointCloud* sliceCloud = inputCloud->createNewCloudFromVisibilitySelection(false, &selectionTable, nullptr, true); // specific case: all points were selected if (sliceCloud == inputCloud) { sliceCloud = inputCloud->clone(); } if (!sliceCloud) { if (!silent) ccLog::Error("Not enough memory!"); } else if (sliceCloud->size() == 0) { //ccLog::Warning("Empty slice"); // too many logs delete sliceCloud; sliceCloud = nullptr; } return sliceCloud; } else if (obj->isKindOf(CC_TYPES::MESH)) { const ccGLMatrix* _transformation = nullptr; ccGLMatrix transformation; if (clipBox->isGLTransEnabled()) { transformation = clipBox->getGLTransformation().inverse(); _transformation = &transformation; } const ccBBox& cropBox = clipBox->getBox(); ccHObject* mesh = ccCropTool::Crop(obj, cropBox, true, _transformation); if (!mesh) { if (!silent) ccLog::Error("Failed to segment the mesh!"); return nullptr; } return mesh; } return nullptr; } void ccClippingBoxTool::exportSlice() { if (!m_clipBox || !MainWindow::TheInstance()) return; for (unsigned ci = 0; ci != m_clipBox->getContainer().getChildrenNumber(); ++ci) { ccHObject* obj = m_clipBox->getContainer().getChild(ci); if (!obj) { assert(false); continue; } ccHObject* result = GetSlice(obj, m_clipBox, false); if (result) { result->setName(obj->getName() + QString(".section")); result->setDisplay(obj->getDisplay()); result->prepareDisplayForRefresh(); if (obj->getParent()) obj->getParent()->addChild(result); MainWindow::TheInstance()->addToDB(result); } } } void ccClippingBoxTool::extractContour() { extractSlicesAndContours(/*singleSliceMode=*/true); } void ccClippingBoxTool::exportMultSlices() { extractSlicesAndContours(/*singleSliceMode=*/false); } static unsigned ComputeGridDimensions( const ccBBox& localBox, const bool processDim[3], int indexMins[3], int indexMaxs[3], int gridDim[3], const CCVector3& gridOrigin, const CCVector3& cellSizePlusGap) { //compute 'grid' extents in the local clipping box ref. for (int i = 0; i < 3; ++i) { indexMins[i] = 0; indexMaxs[i] = 0; gridDim[i] = 1; } unsigned cellCount = 1; for (unsigned char d = 0; d < 3; ++d) { if (processDim[d]) { if (CCCoreLib::LessThanEpsilon(cellSizePlusGap.u[d])) { ccLog::Error("Box size (plus gap) is null! Can't apply repetitive process!"); return 0; } PointCoordinateType a = (localBox.minCorner().u[d] - gridOrigin.u[d]) / cellSizePlusGap.u[d]; //don't forget the user defined gap between 'cells' PointCoordinateType b = (localBox.maxCorner().u[d] - gridOrigin.u[d]) / cellSizePlusGap.u[d]; indexMins[d] = static_cast(floor(a + static_cast(1.0e-6))); indexMaxs[d] = static_cast(ceil(b - static_cast(1.0e-6))) - 1; assert(indexMaxs[d] >= indexMins[d]); gridDim[d] = std::max(indexMaxs[d] - indexMins[d] + 1, 1); cellCount *= static_cast(gridDim[d]); } } return cellCount; } bool ccClippingBoxTool::ExtractSlicesAndContours ( const std::vector& clouds, const std::vector& meshes, ccClipBox& clipBox, bool singleSliceMode, bool repeatDimensions[3], std::vector& outputSlices, bool extractEnvelopes, PointCoordinateType maxEdgeLength, ccEnvelopeExtractor::EnvelopeType envelopeType, std::vector& outputEnvelopes, bool extractLevelSet, double levelSetGridStep, int levelSetMinVertCount, std::vector& levelSet, PointCoordinateType gap/*=0*/, bool multiPass/*=false*/, bool splitEnvelopes/*=false*/, bool projectOnBestFitPlane/*=false*/, bool visualDebugMode/*=false*/, bool generateRandomColors/*=false*/, ccProgressDialog* progressDialog/*=nullptr*/) { //check input if (clouds.empty() && meshes.empty()) { assert(false); return false; } //repeat dimensions int repeatDimensionsSum = static_cast(repeatDimensions[0]) + static_cast(repeatDimensions[1]) + static_cast(repeatDimensions[2]); if (!singleSliceMode && repeatDimensionsSum == 0) { assert(false); ccLog::Error("No dimension selected to repeat the segmentation process?!"); return false; } if (extractLevelSet && repeatDimensionsSum != 1) { assert(false); ccLog::Error("Only one repeat/flat dimension should be defined for level set extraction"); extractLevelSet = false; } //compute the cloud bounding box in the local clipping box ref. ccGLMatrix localTrans; { if (clipBox.isGLTransEnabled()) localTrans = clipBox.getGLTransformation().inverse(); else localTrans.toIdentity(); } CCVector3 gridOrigin = clipBox.getOwnBB().minCorner(); CCVector3 cellSize = clipBox.getOwnBB().getDiagVec(); CCVector3 cellSizePlusGap = cellSize + CCVector3(gap, gap, gap); //apply process try { bool error = false; bool warningsIssued = false; size_t cloudSliceCount = 0; if (singleSliceMode) { //single slice: easy outputSlices.reserve(clouds.size()); for (size_t ci = 0; ci != clouds.size(); ++ci) { ccHObject* slice = GetSlice(clouds[ci], &clipBox, false); if (slice) { slice->setName(clouds[ci]->getName() + QString(".slice")); //set meta-data slice->setMetaData(s_originEntityUUID, clouds[ci]->getUniqueID()); slice->setMetaData(s_sliceID, "slice"); if (slice->isKindOf(CC_TYPES::POINT_CLOUD)) { slice->setMetaData("slice.origin.dim(0)", gridOrigin.x); slice->setMetaData("slice.origin.dim(1)", gridOrigin.y); slice->setMetaData("slice.origin.dim(2)", gridOrigin.z); } outputSlices.push_back(slice); } } if (outputSlices.empty()) { //error message already issued return false; } cloudSliceCount = outputSlices.size(); } else //repeat mode { if (!clouds.empty()) //extract sections from clouds { //compute 'grid' extents in the local clipping box ref. ccBBox localBox; for (ccGenericPointCloud* cloud : clouds) { for (unsigned i = 0; i < cloud->size(); ++i) { CCVector3 P = *cloud->getPoint(i); localTrans.apply(P); localBox.add(P); } } int indexMins[3]{ 0, 0, 0 }; int indexMaxs[3]{ 0, 0, 0 }; int gridDim[3]{ 0, 0, 0 }; unsigned cellCount = ComputeGridDimensions(localBox, repeatDimensions, indexMins, indexMaxs, gridDim, gridOrigin, cellSizePlusGap); //we'll potentially create up to one (ref.) cloud per input loud and per cell std::vector refClouds; refClouds.resize(cellCount * clouds.size(), nullptr); if (progressDialog) { progressDialog->setWindowTitle(tr("Preparing extraction")); progressDialog->start(); progressDialog->show(); progressDialog->setAutoClose(false); } unsigned subCloudsCount = 0; //project points into grid for (size_t ci = 0; ci != clouds.size(); ++ci) { ccGenericPointCloud* cloud = clouds[ci]; unsigned pointCount = cloud->size(); QString infos = tr("Cloud '%1").arg(cloud->getName()); infos += tr("Points: %L1").arg( pointCount ); if (progressDialog) { progressDialog->setInfo(infos); } QApplication::processEvents(); CCCoreLib::NormalizedProgress nProgress(progressDialog, pointCount); for (unsigned i = 0; i < pointCount; ++i) { CCVector3 P = *cloud->getPoint(i); localTrans.apply(P); //relative coordinates (between 0 and 1) P -= gridOrigin; P.x /= cellSizePlusGap.x; P.y /= cellSizePlusGap.y; P.z /= cellSizePlusGap.z; int xi = static_cast(floor(P.x)); xi = std::min(std::max(xi, indexMins[0]), indexMaxs[0]); int yi = static_cast(floor(P.y)); yi = std::min(std::max(yi, indexMins[1]), indexMaxs[1]); int zi = static_cast(floor(P.z)); zi = std::min(std::max(zi, indexMins[2]), indexMaxs[2]); if (gap == 0 || ( (P.x - static_cast(xi))*cellSizePlusGap.x <= cellSize.x && (P.y - static_cast(yi))*cellSizePlusGap.y <= cellSize.y && (P.z - static_cast(zi))*cellSizePlusGap.z <= cellSize.z)) { int cloudIndex = ((zi - indexMins[2]) * static_cast(gridDim[1]) + (yi - indexMins[1])) * static_cast(gridDim[0]) + (xi - indexMins[0]); assert(cloudIndex >= 0 && static_cast(cloudIndex)* clouds.size() + ci < refClouds.size()); CCCoreLib::ReferenceCloud*& destCloud = refClouds[cloudIndex * clouds.size() + ci]; if (!destCloud) { destCloud = new CCCoreLib::ReferenceCloud(cloud); ++subCloudsCount; } if (!destCloud->addPointIndex(i)) { ccLog::Error("Not enough memory!"); error = true; break; } } } if (!nProgress.oneStep()) { // sadly, we can't cancel in the middle of this process! } } //project points into grid if (progressDialog) { progressDialog->setWindowTitle(QObject::tr("Section extraction")); progressDialog->setInfo(QObject::tr("Section(s): %L1").arg(subCloudsCount)); progressDialog->setMaximum(static_cast(subCloudsCount)); progressDialog->setValue(0); QApplication::processEvents(); } //reset count subCloudsCount = 0; //now create the real clouds for (int i = indexMins[0]; i <= indexMaxs[0]; ++i) { for (int j = indexMins[1]; j <= indexMaxs[1]; ++j) { for (int k = indexMins[2]; k <= indexMaxs[2]; ++k) { int cloudIndex = ((k - indexMins[2]) * static_cast(gridDim[1]) + (j - indexMins[1])) * static_cast(gridDim[0]) + (i - indexMins[0]); assert(cloudIndex >= 0 && static_cast(cloudIndex)* clouds.size() < refClouds.size()); for (size_t ci = 0; ci != clouds.size(); ++ci) { ccGenericPointCloud* cloud = clouds[ci]; CCCoreLib::ReferenceCloud* destCloud = refClouds[cloudIndex * clouds.size() + ci]; if (destCloud) //some slices can be empty! { //generate slice from previous selection int warnings = 0; ccPointCloud* sliceCloud = cloud->isA(CC_TYPES::POINT_CLOUD) ? static_cast(cloud)->partialClone(destCloud, &warnings) : ccPointCloud::From(destCloud, cloud); warningsIssued |= (warnings != 0); if (sliceCloud) { if (generateRandomColors) { ccColor::Rgb col = ccColor::Generator::Random(); if (!sliceCloud->setColor(col)) { ccLog::Error("Not enough memory!"); error = true; i = indexMaxs[0]; j = indexMaxs[1]; k = indexMaxs[2]; } sliceCloud->showColors(true); } sliceCloud->setEnabled(true); sliceCloud->setVisible(true); sliceCloud->setDisplay(cloud->getDisplay()); CCVector3 cellOrigin( gridOrigin.x + i * cellSizePlusGap.x, gridOrigin.y + j * cellSizePlusGap.y, gridOrigin.z + k * cellSizePlusGap.z); QString slicePosStr = QString("(%1 ; %2 ; %3)").arg(cellOrigin.x).arg(cellOrigin.y).arg(cellOrigin.z); sliceCloud->setName(cloud->getName() + QString(".slice @ ") + slicePosStr); //set meta-data sliceCloud->setMetaData(s_originEntityUUID, cloud->getUniqueID()); sliceCloud->setMetaData(s_sliceID, slicePosStr); sliceCloud->setMetaData("slice.origin.dim(0)", cellOrigin.x); sliceCloud->setMetaData("slice.origin.dim(1)", cellOrigin.y); sliceCloud->setMetaData("slice.origin.dim(2)", cellOrigin.z); //add slice to group outputSlices.push_back(sliceCloud); ++subCloudsCount; if (progressDialog) { progressDialog->setValue(static_cast(subCloudsCount)); } } if (progressDialog && progressDialog->wasCanceled()) { error = true; ccLog::Warning(QString("[ExtractSlicesAndContours] Process canceled by user")); //early stop i = indexMaxs[0]; j = indexMaxs[1]; k = indexMaxs[2]; break; } } } } } } //now create the real clouds //release memory { for (size_t i = 0; i < refClouds.size(); ++i) if (refClouds[i]) delete refClouds[i]; refClouds.clear(); } cloudSliceCount = outputSlices.size(); } //extract sections from clouds if (!meshes.empty()) //extract sections from meshes { //compute 'grid' extents in the local clipping box ref. ccBBox localBox; for (ccGenericMesh* mesh : meshes) { ccGenericPointCloud* cloud = mesh->getAssociatedCloud(); for (unsigned i = 0; i < cloud->size(); ++i) { CCVector3 P = *cloud->getPoint(i); localTrans.apply(P); localBox.add(P); } } int indexMins[3]{ 0, 0, 0 }; int indexMaxs[3]{ 0, 0, 0 }; int gridDim[3]{ 0, 0, 0 }; unsigned cellCount = ComputeGridDimensions(localBox, repeatDimensions, indexMins, indexMaxs, gridDim, gridOrigin, cellSizePlusGap); const ccGLMatrix* _transformation = nullptr; ccGLMatrix transformation; if (clipBox.isGLTransEnabled()) { transformation = clipBox.getGLTransformation().inverse(); _transformation = &transformation; } if (progressDialog) { progressDialog->setWindowTitle("Section extraction"); progressDialog->setInfo(QObject::tr("Up to (%1 x %2 x %3) = %4 section(s)").arg(gridDim[0]).arg(gridDim[1]).arg(gridDim[2]).arg(cellCount)); progressDialog->setMaximum(static_cast(cellCount * meshes.size())); progressDialog->show(); QApplication::processEvents(); } //now extract the slices for (int i = indexMins[0]; i <= indexMaxs[0]; ++i) { for (int j = indexMins[1]; j <= indexMaxs[1]; ++j) { for (int k = indexMins[2]; k <= indexMaxs[2]; ++k) { int sliceIndex = ((k - indexMins[2]) * static_cast(gridDim[1]) + (j - indexMins[1])) * static_cast(gridDim[0]) + (i - indexMins[0]); CCVector3 C = gridOrigin + CCVector3(i*cellSizePlusGap.x, j*cellSizePlusGap.y, k*cellSizePlusGap.z); ccBBox cropBox(C, C + cellSize, true); for (size_t mi = 0; mi != meshes.size(); ++mi) { ccGenericMesh* mesh = meshes[mi]; ccHObject* croppedEnt = ccCropTool::Crop(mesh, cropBox, true, _transformation); if (croppedEnt) { if (generateRandomColors) { ccPointCloud* croppedVertices = ccHObjectCaster::ToPointCloud(mesh->getAssociatedCloud()); if (croppedVertices) { ccColor::Rgb col = ccColor::Generator::Random(); if (!croppedVertices->setColor(col)) { ccLog::Error("Not enough memory!"); error = true; i = indexMaxs[0]; j = indexMaxs[1]; k = indexMaxs[2]; } croppedVertices->showColors(true); mesh->showColors(true); } } croppedEnt->setEnabled(true); croppedEnt->setVisible(true); croppedEnt->setDisplay(mesh->getDisplay()); QString slicePosStr = QString("(%1 ; %2 ; %3)").arg(C.x).arg(C.y).arg(C.z); croppedEnt->setName(mesh->getName() + QString(".slice @ ") + slicePosStr); //set meta-data croppedEnt->setMetaData(s_originEntityUUID, mesh->getUniqueID()); croppedEnt->setMetaData(s_sliceID, slicePosStr); croppedEnt->setMetaData("slice.origin.dim(0)", C.x); croppedEnt->setMetaData("slice.origin.dim(1)", C.y); croppedEnt->setMetaData("slice.origin.dim(2)", C.z); //add slice to group outputSlices.push_back(croppedEnt); } if (progressDialog) { if (progressDialog->wasCanceled()) { error = true; ccLog::Warning(QString("[ExtractSlicesAndContours] Process canceled by user")); //early stop i = indexMaxs[0]; j = indexMaxs[1]; k = indexMaxs[2]; break; } progressDialog->setValue(sliceIndex * static_cast(meshes.size()) + static_cast(mi)); } } } } } } //extract sections from meshes } //repeat mode //extract level set (optionaly) if (!error && extractLevelSet && cloudSliceCount != 0) { for (int iteration = 0; iteration < 1; ++iteration) //fake loop for easy break { if (progressDialog) { progressDialog->setWindowTitle("Level set extraction"); progressDialog->setInfo(QObject::tr("Level(s): %L1").arg(cloudSliceCount)); progressDialog->setMaximum(static_cast(cloudSliceCount)); progressDialog->show(); QApplication::processEvents(); } int Z = 2; assert(repeatDimensionsSum == 1); { for (int i = 0; i < 3; ++i) { if (repeatDimensions[i]) { Z = i; break; } } } int X = (Z == 2 ? 0 : Z + 1); int Y = (X == 2 ? 0 : X + 1); CCVector3 gridOrigin = clipBox.getOwnBB().minCorner(); CCVector3 gridSize = clipBox.getOwnBB().getDiagVec(); ccGLMatrix globalTrans = localTrans.inverse(); assert(false == CCCoreLib::LessThanEpsilon(levelSetGridStep)); unsigned gridWidth = 1 + static_cast(gridSize.u[X] / levelSetGridStep + 0.5); unsigned gridHeight = 1 + static_cast(gridSize.u[Y] / levelSetGridStep + 0.5); //add a margin to avoid issues in the level set generation gridWidth += 2; gridHeight += 2; gridOrigin.u[X] -= levelSetGridStep; gridOrigin.u[Y] -= levelSetGridStep; ccRasterGrid grid; if (!grid.init(gridWidth, gridHeight, levelSetGridStep, CCVector3d(0, 0, 0))) { ccLog::Error("Not enough memory!"); error = true; break; } //process all the slices originating from point clouds assert(cloudSliceCount <= outputSlices.size()); for (size_t i = 0; i < cloudSliceCount; ++i) { ccPointCloud* sliceCloud = ccHObjectCaster::ToPointCloud(outputSlices[i]); assert(sliceCloud); double sliceZ = sliceCloud->getMetaData(QString("slice.origin.dim(%1)").arg(Z)).toDouble(); sliceZ += gridSize.u[Z] / 2; //grid.reset(); for (ccRasterGrid::Row& row : grid.rows) { for (ccRasterCell& cell : row) { cell.h = 0.0; cell.nbPoints = 0; } } //project the slice in 2D for (unsigned pi = 0; pi != sliceCloud->size(); ++pi) { CCVector3 relativePos = *sliceCloud->getPoint(pi); localTrans.apply(relativePos); relativePos -= gridOrigin; int i = static_cast(relativePos.u[X] / levelSetGridStep + 0.5); int j = static_cast(relativePos.u[Y] / levelSetGridStep + 0.5); //we skip points that fall outside of the grid! if ( i < 0 || i >= static_cast(gridWidth) || j < 0 || j >= static_cast(gridHeight)) { //there shouldn't be any actually assert(false); continue; } ccRasterCell& cell = grid.rows[j][i]; cell.h = 1.0; ++cell.nbPoints; } grid.updateNonEmptyCellCount(); grid.updateCellStats(); grid.setValid(true); //now extract the contour lines ccContourLinesGenerator::Parameters params; params.emptyCellsValue = std::numeric_limits::quiet_NaN(); params.minVertexCount = levelSetMinVertCount; params.parentWidget = progressDialog->parentWidget(); params.startAltitude = 0.0; params.maxAltitude = 1.0; params.step = 1.0; std::vector contours; if (ccContourLinesGenerator::GenerateContourLines(&grid, CCVector2d(gridOrigin.u[X], gridOrigin.u[Y]), params, contours)) { for (size_t k = 0; k < contours.size(); ++k) { ccPolyline* poly = contours[k]; CCCoreLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); for (unsigned pi = 0; pi < vertices->size(); ++pi) { //convert the vertices from the local coordinate system to the global one const CCVector3* Pconst = vertices->getPoint(pi); CCVector3 P; P.u[X] = Pconst->x; P.u[Y] = Pconst->y; P.u[Z] = sliceZ; *const_cast(Pconst) = globalTrans * P; } static char s_dimNames[3] = { 'X', 'Y', 'Z' }; poly->setName(QString("Contour line %1=%2 (#%3)").arg(s_dimNames[Z]).arg(sliceZ).arg(k + 1)); poly->copyGlobalShiftAndScale(*sliceCloud); poly->setMetaData(ccPolyline::MetaKeyConstAltitude(), QVariant(sliceZ)); //replace the 'altitude' meta-data by the right value //set meta-data poly->setMetaData(s_originEntityUUID, sliceCloud->getMetaData(s_originEntityUUID)); poly->setMetaData(s_sliceID, sliceCloud->getMetaData(s_sliceID)); poly->setMetaData("slice.origin.dim(0)", sliceCloud->getMetaData("slice.origin.dim(0)")); poly->setMetaData("slice.origin.dim(1)", sliceCloud->getMetaData("slice.origin.dim(1)")); poly->setMetaData("slice.origin.dim(2)", sliceCloud->getMetaData("slice.origin.dim(2)")); levelSet.push_back(poly); } } else { ccLog::Warning(tr("Failed to generate contour lines for cloud #%1").arg(i + 1)); } if (progressDialog) { if (progressDialog->wasCanceled()) { error = true; ccLog::Warning(tr("[ExtractSlicesAndContours] Process canceled by user")); break; } progressDialog->setValue(static_cast(i) + 1); } } } } //extract envelopes as polylines (optionaly) if (!error && extractEnvelopes && cloudSliceCount != 0) { if (progressDialog) { progressDialog->setWindowTitle(tr("Envelope extraction")); progressDialog->setInfo(tr("Envelope(s): %L1").arg(cloudSliceCount)); progressDialog->setMaximum(static_cast(cloudSliceCount)); if (!visualDebugMode) { progressDialog->show(); QApplication::processEvents(); } } //preferred dimension? PointCoordinateType* preferredNormDir = nullptr; PointCoordinateType* preferredUpDir = nullptr; if (repeatDimensionsSum == 1) { for (int i = 0; i < 3; ++i) { if (repeatDimensions[i]) { ccGLMatrix invLocalTrans = localTrans.inverse(); if (!projectOnBestFitPlane) //otherwise the normal will be automatically computed preferredNormDir = invLocalTrans.getColumn(i); preferredUpDir = invLocalTrans.getColumn(i < 2 ? 2 : 0); break; } } } assert(cloudSliceCount <= outputSlices.size()); //process all the slices originating from point clouds for (size_t i = 0; i < cloudSliceCount; ++i) { ccPointCloud* sliceCloud = ccHObjectCaster::ToPointCloud(outputSlices[i]); assert(sliceCloud); std::vector polys; if (ccEnvelopeExtractor::ExtractFlatEnvelope(sliceCloud, multiPass, maxEdgeLength, polys, envelopeType, splitEnvelopes, preferredNormDir, preferredUpDir, visualDebugMode)) { if (!polys.empty()) { for (size_t p = 0; p < polys.size(); ++p) { ccPolyline* poly = polys[p]; poly->setColor(ccColor::green); poly->showColors(true); poly->setGlobalScale(sliceCloud->getGlobalScale()); poly->setGlobalShift(sliceCloud->getGlobalShift()); QString envelopeName = sliceCloud->getName(); envelopeName.replace("slice", "envelope"); if (polys.size() > 1) { envelopeName += QString(" (part %1)").arg(p + 1); } poly->setName(envelopeName); //set meta-data poly->setMetaData(s_originEntityUUID, sliceCloud->getMetaData(s_originEntityUUID)); poly->setMetaData(s_sliceID, sliceCloud->getMetaData(s_sliceID)); poly->setMetaData("slice.origin.dim(0)", sliceCloud->getMetaData("slice.origin.dim(0)")); poly->setMetaData("slice.origin.dim(1)", sliceCloud->getMetaData("slice.origin.dim(1)")); poly->setMetaData("slice.origin.dim(2)", sliceCloud->getMetaData("slice.origin.dim(2)")); outputEnvelopes.push_back(poly); } } else { ccLog::Warning(tr("%1: points are too far from each other! Increase the max edge length").arg(sliceCloud->getName())); warningsIssued = true; } } else { ccLog::Warning(tr("%1: envelope extraction failed!").arg(sliceCloud->getName())); warningsIssued = true; } if (progressDialog && !visualDebugMode) { if (progressDialog->wasCanceled()) { error = true; ccLog::Warning(tr("[ExtractSlicesAndContours] Process canceled by user")); //early stop break; } progressDialog->setValue(static_cast(i)); } } } //extract envelope polylines //release memory if (error) { for (ccHObject* slice : outputSlices) { delete slice; } outputSlices.resize(0); for (ccPolyline* poly : outputEnvelopes) { delete poly; } return false; } else if (warningsIssued) { ccLog::Warning(tr("[ExtractSlicesAndContours] Warnings were issued during the process! (result may be incomplete)")); } } catch (const std::bad_alloc&) { ccLog::Error(tr("Not enough memory!")); return false; } return true; } static ccHObject* FindOrCreateChildren(ccHObject* parent, QString name) { assert(parent && !name.isEmpty()); for (unsigned i = 0; i < parent->getChildrenNumber(); ++i) { ccHObject* child = parent->getChild(i); if (child->getName() == name) { return child; } } ccHObject* child = new ccHObject(name); parent->addChild(child); return child; } void ccClippingBoxTool::extractSlicesAndContours(bool singleSliceMode) { if (!m_clipBox || m_clipBox->getContainer().getChildrenNumber() == 0) { assert(false); return; } std::vector clouds; std::vector meshes; try { for (unsigned ci = 0; ci != m_clipBox->getContainer().getChildrenNumber(); ++ci) { ccHObject* obj = m_clipBox->getContainer().getChild(ci); if (obj->isKindOf(CC_TYPES::POINT_CLOUD)) { ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(obj); clouds.push_back(cloud); } else if (obj->isKindOf(CC_TYPES::MESH)) { ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(obj); meshes.push_back(mesh); } } } catch (const std::bad_alloc&) { ccLog::Warning("Not enough memory"); return; } if (clouds.empty() && meshes.empty()) { ccLog::Warning("Only works with point clouds or meshes!"); return; } ccClippingBoxRepeatDlg repeatDlg(singleSliceMode, MainWindow::TheInstance()); repeatDlg.extractEnvelopesGroupBox->setEnabled(!clouds.empty()); repeatDlg.extractLevelSetGroupBox->setEnabled(!clouds.empty()); //by default we set the 'flat/repeat' dimension to the smallest box dimension { CCVector3 diagVec = m_clipBox->getOwnBB().getDiagVec(); unsigned char flatDim = 0; if (diagVec.y < diagVec.x) flatDim = 1; if (diagVec.z < diagVec.u[flatDim]) flatDim = 2; if (singleSliceMode) repeatDlg.setFlatDim(flatDim); else repeatDlg.setRepeatDim(flatDim); } //random colors is only useful for mutliple slices mode repeatDlg.randomColorCheckBox->setEnabled(!singleSliceMode); //Semi persistent parameters static bool s_extractSliceCloudsOrMeshes = true; static bool s_generateRandomColors = false; static bool s_extractEnvelopes = false; static int s_envelopeTypeIndex = 2; //full static bool s_multiPassEnvelope = false; static bool s_envProjectPointsOnBestFitPlane = false; static bool s_splitEnvelopes = false; static bool s_envelopeDebugMode = false; static bool s_extractLevelSet = false; static double s_levelSetGridStep = 0.0; static int s_levelSetMinVertCount = 3; static double s_defaultGap = 0.0; static int s_groupByIndex = 0; //set default max edge length if (s_maxEnvelopeEdgeLength < 0) s_maxEnvelopeEdgeLength = m_clipBox->getBox().getDiagNorm() / 100.0; if (s_levelSetGridStep <= 0.0) s_levelSetGridStep = m_clipBox->getBox().getDiagNorm() / 300.0; //Extract slice cloud(s) or mesh(es) repeatDlg.extractSliceEntitiesGroupBox->setChecked(s_extractSliceCloudsOrMeshes); repeatDlg.randomColorCheckBox->setChecked(s_generateRandomColors); //Extract envelope(s) repeatDlg.extractEnvelopesGroupBox->setChecked(s_extractEnvelopes); repeatDlg.envelopeTypeComboBox->setCurrentIndex(s_envelopeTypeIndex); repeatDlg.maxEnvelopeEdgeLengthDoubleSpinBox->setValue(s_maxEnvelopeEdgeLength); repeatDlg.multiPassEnvelopeCheckBox->setChecked(s_multiPassEnvelope); repeatDlg.envProjectPointsOnBestFitCheckBox->setChecked(s_envProjectPointsOnBestFitPlane); repeatDlg.splitEnvelopeCheckBox->setChecked(s_splitEnvelopes); repeatDlg.envDebugModeCheckBox->setChecked(s_envelopeDebugMode); //Extract contours repeatDlg.extractLevelSetGroupBox->setChecked(s_extractLevelSet); repeatDlg.lsGridStepDoubleSpinBox->setValue(s_levelSetGridStep); repeatDlg.minLSVertexCountSpinBox->setValue(s_levelSetMinVertCount); //Other options repeatDlg.gapDoubleSpinBox->setValue(s_defaultGap); repeatDlg.groupByTypeComboBox->setCurrentIndex(s_groupByIndex); if (!repeatDlg.exec()) { //cancelled by user return; } //repeat dimensions bool processDim[3] = { repeatDlg.xRepeatCheckBox->isChecked(), repeatDlg.yRepeatCheckBox->isChecked(), repeatDlg.zRepeatCheckBox->isChecked() }; //whether to use random colors for (multiple) generated slices s_extractSliceCloudsOrMeshes = repeatDlg.extractSliceEntitiesGroupBox->isChecked(); s_generateRandomColors = repeatDlg.randomColorCheckBox->isChecked(); s_extractEnvelopes = repeatDlg.extractEnvelopesGroupBox->isChecked(); s_envelopeTypeIndex = repeatDlg.envelopeTypeComboBox->currentIndex(); s_maxEnvelopeEdgeLength = repeatDlg.maxEnvelopeEdgeLengthDoubleSpinBox->value(); s_multiPassEnvelope = repeatDlg.multiPassEnvelopeCheckBox->isChecked(); s_envProjectPointsOnBestFitPlane = repeatDlg.envProjectPointsOnBestFitCheckBox->isChecked(); s_splitEnvelopes = repeatDlg.splitEnvelopeCheckBox->isChecked(); s_envelopeDebugMode = repeatDlg.envDebugModeCheckBox->isChecked(); s_extractLevelSet = repeatDlg.extractLevelSetGroupBox->isChecked(); s_levelSetGridStep = repeatDlg.lsGridStepDoubleSpinBox->value(); s_levelSetMinVertCount = repeatDlg.minLSVertexCountSpinBox->value(); s_defaultGap = repeatDlg.gapDoubleSpinBox->value(); s_groupByIndex = repeatDlg.groupByTypeComboBox->currentIndex(); ccEnvelopeExtractor::EnvelopeType envelopeType = ccEnvelopeExtractor::EnvelopeType::FULL; switch (s_envelopeTypeIndex) { case 0: envelopeType = ccEnvelopeExtractor::EnvelopeType::LOWER; break; case 1: envelopeType = ccEnvelopeExtractor::EnvelopeType::UPPER; break; case 2: envelopeType = ccEnvelopeExtractor::EnvelopeType::FULL; break; default: assert(false); ccLog::Warning("Internal error: unhandled envelope type"); break; } ccProgressDialog pDlg(false, this); std::vector outputSlices; std::vector outputEnvelopes; std::vector outputLevelSet; QElapsedTimer eTimer; eTimer.start(); if (!ExtractSlicesAndContours( clouds, meshes, *m_clipBox, singleSliceMode, processDim, outputSlices, s_extractEnvelopes, static_cast(s_maxEnvelopeEdgeLength), envelopeType, outputEnvelopes, s_extractLevelSet, s_levelSetGridStep, s_levelSetMinVertCount, outputLevelSet, static_cast(s_defaultGap), s_multiPassEnvelope, s_splitEnvelopes, s_envProjectPointsOnBestFitPlane, s_envelopeDebugMode, s_generateRandomColors, &pDlg )) { //process failed (error message has already been issued) return; } ccLog::Print("[ccClippingBoxTool] Processed finished in %.2f s.", eTimer.elapsed() / 1.0e3); //possible outputs ccHObject* sliceGroup = nullptr; ccHObject* envelopeGroup = nullptr; ccHObject* levelSetGroup = nullptr; QMap perSliceGroups; QMap perEntityGroups; QMap perEntityGroupNames; ccHObject* garbageGroup = new ccHObject("Extracted entities"); enum OutputFormat { BY_TYPE = 0, BY_ENTITY = 1, BY_SLICE = 2, BY_ENTITY_THEN_SLICE = 3, BY_SLICE_THEN_ENTITY = 4, }; if ( s_groupByIndex == OutputFormat::BY_ENTITY || s_groupByIndex == OutputFormat::BY_ENTITY_THEN_SLICE || s_groupByIndex == OutputFormat::BY_SLICE_THEN_ENTITY ) { for (const ccGenericPointCloud* cloud : clouds) { QString name = cloud->getName() + ".slices"; perEntityGroupNames[cloud->getUniqueID()] = name; if (s_groupByIndex != OutputFormat::BY_SLICE_THEN_ENTITY) { perEntityGroups[cloud->getUniqueID()] = new ccHObject(name); } } for (const ccGenericMesh* mesh : meshes) { QString name = mesh->getName() + ".slices"; perEntityGroupNames[mesh->getUniqueID()] = name; if (s_groupByIndex != OutputFormat::BY_SLICE_THEN_ENTITY) { perEntityGroups[mesh->getUniqueID()] = new ccHObject(name); } } } if (s_groupByIndex == OutputFormat::BY_SLICE) { for (const ccHObject* slice : outputSlices) { QString sliceID = slice->getMetaData(s_sliceID).toString(); perSliceGroups[sliceID] = new ccHObject(sliceID); } } auto dispatchEntity = [&](ccHObject* entity, ccHObject* sliceGroup) { if (entity == nullptr) { assert(false); return; } ccHObject* destGroup = nullptr; switch (s_groupByIndex) { case OutputFormat::BY_TYPE: { destGroup = sliceGroup; } break; case OutputFormat::BY_ENTITY: case OutputFormat::BY_ENTITY_THEN_SLICE: { //first level: entity unsigned entityUUID = entity->getMetaData(s_originEntityUUID).toUInt(); if (perEntityGroups.contains(entityUUID)) { destGroup = perEntityGroups[entityUUID]; } else { assert(false); destGroup = garbageGroup; } if (s_groupByIndex == OutputFormat::BY_ENTITY_THEN_SLICE) { //second level: slice QString sliceID = entity->getMetaData(s_sliceID).toString(); destGroup = FindOrCreateChildren(destGroup, sliceID); } destGroup->addChild(entity); } break; case OutputFormat::BY_SLICE: case OutputFormat::BY_SLICE_THEN_ENTITY: { //first level: slice QString sliceID = entity->getMetaData(s_sliceID).toString(); if (perSliceGroups.contains(sliceID)) { destGroup = perSliceGroups[sliceID]; } else { assert(false); destGroup = garbageGroup; } if (s_groupByIndex == OutputFormat::BY_SLICE_THEN_ENTITY) { //second level: entity unsigned entityUUID = entity->getMetaData(s_originEntityUUID).toUInt(); if (perEntityGroupNames.contains(entityUUID)) { destGroup = FindOrCreateChildren(destGroup, perEntityGroupNames[entityUUID]); } } } break; default: assert(false); return; } if (destGroup) { destGroup->addChild(entity); } else { assert(false); } }; //get the default output display ccGenericGLDisplay* defaultDisplay = nullptr; if (m_clipBox->getContainer().getFirstChild()) { defaultDisplay = m_clipBox->getContainer().getFirstChild()->getDisplay(); } //base name QString baseName; if (m_clipBox->getContainer().getChildrenNumber() == 1) { baseName = m_clipBox->getContainer().getFirstChild()->getName(); } //slices (clouds or meshes) size_t sliceCount = outputSlices.size(); if (sliceCount) { if (s_extractSliceCloudsOrMeshes) { if (s_groupByIndex == OutputFormat::BY_TYPE) { //we have to create the destination group sliceGroup = new ccHObject(baseName.isEmpty() ? QString("Slices") : baseName + QString(".slices")); } for (ccHObject* slice : outputSlices) { dispatchEntity(slice, sliceGroup); } } else { for (ccHObject* slice : outputSlices) { delete slice; } outputSlices.clear(); } } if (!outputEnvelopes.empty() || !outputLevelSet.empty()) { s_lastContourUniqueIDs.clear(); } //envelopes if (!outputEnvelopes.empty()) { if (s_groupByIndex == OutputFormat::BY_TYPE) { //we have to create the destination group envelopeGroup = new ccHObject(baseName.isEmpty() ? QString("Envelopes") : baseName + QString(".envelopes")); } for (ccPolyline* poly : outputEnvelopes) { dispatchEntity(poly, envelopeGroup); s_lastContourUniqueIDs.push_back(poly->getUniqueID()); } if (envelopeGroup) { s_lastContourUniqueIDs.push_back(envelopeGroup->getUniqueID()); } } //level set if (!outputLevelSet.empty()) { if (s_groupByIndex == OutputFormat::BY_TYPE) { //we have to create the destination group levelSetGroup = new ccHObject(baseName.isEmpty() ? QString("Level set") : baseName + QString(".levelSet")); } for (ccPolyline* poly : outputLevelSet) { dispatchEntity(poly, levelSetGroup); s_lastContourUniqueIDs.push_back(poly->getUniqueID()); } if (levelSetGroup) { s_lastContourUniqueIDs.push_back(levelSetGroup->getUniqueID()); } } removeLastContourToolButton->setEnabled(!s_lastContourUniqueIDs.empty()); //now take care of the 'output' groups { if (sliceGroup) { sliceGroup->setDisplay_recursive(defaultDisplay); MainWindow::TheInstance()->addToDB(sliceGroup); } if (envelopeGroup) { envelopeGroup->setDisplay_recursive(defaultDisplay); MainWindow::TheInstance()->addToDB(envelopeGroup); } if (levelSetGroup) { levelSetGroup->setDisplay_recursive(defaultDisplay); MainWindow::TheInstance()->addToDB(levelSetGroup); } for (ccHObject* group : perSliceGroups) { group->setDisplay_recursive(defaultDisplay); MainWindow::TheInstance()->addToDB(group); } for (ccHObject* group : perEntityGroups) { group->setDisplay_recursive(defaultDisplay); MainWindow::TheInstance()->addToDB(group); } //don't forget the 'garbage' group (just in case) if (garbageGroup) { if (garbageGroup->getChildrenNumber() != 0) { garbageGroup->setDisplay_recursive(defaultDisplay); MainWindow::TheInstance()->addToDB(garbageGroup); } else { delete garbageGroup; garbageGroup = nullptr; } } } if (m_associatedWin) { m_associatedWin->redraw(); } if (sliceCount != 0) { QMessageBox::warning(nullptr, tr("Process finished"), tr("%1 slices have been generated.\n(you may have to close the tool and hide the initial cloud to see them...)").arg(sliceCount)); } else if (s_extractSliceCloudsOrMeshes) { QMessageBox::warning(nullptr, tr("Process finished"), tr("The process has generated no output")); } } void ccClippingBoxTool::onBoxModified(const ccBBox* box) { if (box && box->isValid()) { CCVector3 dd = box->maxCorner() - box->minCorner(); thickXDoubleSpinBox->setValue(dd.x); thickYDoubleSpinBox->setValue(dd.y); thickZDoubleSpinBox->setValue(dd.z); thicknessGroupBox->setEnabled(true); } else { thicknessGroupBox->setEnabled(false); } } void ccClippingBoxTool::thicknessChanged(double) { if (!m_clipBox || !m_clipBox->getBox().isValid()) return; CCVector3 th(static_cast(thickXDoubleSpinBox->value()), static_cast(thickYDoubleSpinBox->value()), static_cast(thickZDoubleSpinBox->value())); ccBBox box = m_clipBox->getBox(); CCVector3 boxCenter = (box.maxCorner() + box.minCorner()) / 2; box.minCorner() = boxCenter - th/2; box.maxCorner() = boxCenter + th/2; m_clipBox->setBox(box); if (m_associatedWin) m_associatedWin->redraw(); } void ccClippingBoxTool::shiftBox(unsigned char dim, bool minus) { if (!m_clipBox || !m_clipBox->getBox().isValid()) return; assert(dim<3); PointCoordinateType width = (m_clipBox->getBox().maxCorner() - m_clipBox->getBox().minCorner()).u[dim]; CCVector3 shiftVec(0, 0, 0); shiftVec.u[dim] = (minus ? -width : width); m_clipBox->shift(shiftVec); if (m_associatedWin) m_associatedWin->redraw(); } void ccClippingBoxTool::reset() { if (m_clipBox) m_clipBox->reset(); if (m_associatedWin) m_associatedWin->redraw(); } void ccClippingBoxTool::restoreLastBox() { if (!m_clipBox || m_clipBox->getContainer().getChildrenNumber() == 0) { assert(false); return; } unsigned uniqueID = m_clipBox->getContainer().getFirstChild()->getUniqueID(); if (!s_lastBoxParams.contains(uniqueID)) { assert(false); return; } const ccClipBoxParams& params = s_lastBoxParams[uniqueID]; m_clipBox->set(params.box, params.trans); } void ccClippingBoxTool::closeDialog() { stop(true); } void ccClippingBoxTool::setTopView() { setView(CC_TOP_VIEW); } void ccClippingBoxTool::setBottomView() { setView(CC_BOTTOM_VIEW); } void ccClippingBoxTool::setFrontView() { setView(CC_FRONT_VIEW); } void ccClippingBoxTool::setBackView() { setView(CC_BACK_VIEW); } void ccClippingBoxTool::setLeftView() { setView(CC_LEFT_VIEW); } void ccClippingBoxTool::setRightView() { setView(CC_RIGHT_VIEW); } void ccClippingBoxTool::setView(CC_VIEW_ORIENTATION orientation) { if (!m_associatedWin) return; //m_associatedWin->blockSignals(true); m_associatedWin->setView(orientation,false); if (m_clipBox && m_clipBox->isGLTransEnabled()) { ccViewportParameters params = m_associatedWin->getViewportParameters(); const ccGLMatrix& glMat = m_clipBox->getGLTransformation(); ccGLMatrixd rotMat(glMat.data()); rotMat.clearTranslation(); params.viewMat = params.viewMat * rotMat.inverse(); m_associatedWin->setViewportParameters(params); } //m_associatedWin->blockSignals(false); m_associatedWin->redraw(); }