449 lines
15 KiB
C++
449 lines
15 KiB
C++
|
/**
|
||
|
* @file DialogPCLGPMesh.cpp
|
||
|
* @brief None
|
||
|
* @author 陈增辉 (3045316072@qq.com)
|
||
|
* @version 2.5.0
|
||
|
* @date 2024/4/5
|
||
|
* @copyright Copyright (c) Since 2024 中科卫星应用研究院 All rights reserved.
|
||
|
*/
|
||
|
|
||
|
// You may need to build the project (run Qt uic code generator) to get "ui_DialogPCLGPMesh.h"
|
||
|
// resolved
|
||
|
|
||
|
#include "DialogPCLGPMesh.h"
|
||
|
#include "ui_DialogPCLGPMesh.h"
|
||
|
#include "ModuleBase/ThreadTask.h"
|
||
|
#include "PythonModule/PyAgent.h"
|
||
|
#include "MeshData/meshSingleton.h"
|
||
|
#include "MeshData/meshSet.h"
|
||
|
#include <QMenu>
|
||
|
#include <QDebug>
|
||
|
#include <pcl/filters/statistical_outlier_removal.h>
|
||
|
#include <QtWidgets>
|
||
|
#include <QFileDialog>
|
||
|
#include <QFileInfo>
|
||
|
#include "MeshData/meshKernal.h"
|
||
|
#include "PointCloudOperator/PointCloudCommon.h"
|
||
|
#include "Settings/BusAPI.h"
|
||
|
#include "BaseTool.h"
|
||
|
#include "IO/IOConfig.h"
|
||
|
#include "ModuleBase/ThreadControl.h"
|
||
|
#include "ConfigOptions/ConfigOptions.h"
|
||
|
#include "ConfigOptions/MeshConfig.h"
|
||
|
|
||
|
#include "MeshData/meshSingleton.h"
|
||
|
#include "MeshData/meshKernal.h"
|
||
|
#include <vtkSmartPointer.h>
|
||
|
#include <vtkDataSetReader.h>
|
||
|
#include <vtkDataSet.h>
|
||
|
#include <vtkSTLReader.h>
|
||
|
#include <vtkTecplotReader.h>
|
||
|
#include <vtkMultiBlockDataSet.h>
|
||
|
#include <vtkAppendFilter.h>
|
||
|
#include <vtkUnstructuredGrid.h>
|
||
|
#include <vtkUnstructuredGridWriter.h>
|
||
|
#include <vtkAppendFilter.h>
|
||
|
#include <QFileInfo>
|
||
|
#include <QTextCodec>
|
||
|
#include <vtkSTLWriter.h>
|
||
|
#include "Common/DebugLogger.h"
|
||
|
|
||
|
// auto meshData = MeshData::MeshData::getInstance();
|
||
|
|
||
|
namespace MainWidget {
|
||
|
DialogPCLGPMesh::DialogPCLGPMesh(GUI::MainWindow* parent)
|
||
|
: QFDialog(parent)
|
||
|
, _ui(new Ui::DialogPCLGPMesh)
|
||
|
, _mw(parent)
|
||
|
, _selectdlg(new DialogSelectComponents(parent))
|
||
|
{
|
||
|
_ui->setupUi(this);
|
||
|
_ui->geoSelectPoint->setToolTip(tr("Clicked Button Selected Components"));
|
||
|
setWindowTitle(tr("GP Meshing"));
|
||
|
_ui->listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||
|
|
||
|
connect(_ui->geoSelectPoint, &QPushButton::clicked, [=]() {
|
||
|
_selectdlg->clearSelectItems();
|
||
|
_selectdlg->exec();
|
||
|
});
|
||
|
connect(_selectdlg, SIGNAL(selectedComponentsSig(QList<MeshData::MeshSet*>)), this,
|
||
|
SLOT(selectedComponentsSlot(QList<MeshData::MeshSet*>)));
|
||
|
connect(_ui->listWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this,
|
||
|
SLOT(customContextMenuRequestedSlot(const QPoint&)));
|
||
|
}
|
||
|
|
||
|
DialogPCLGPMesh::~DialogPCLGPMesh()
|
||
|
{
|
||
|
delete _ui;
|
||
|
_ui = NULL;
|
||
|
delete _selectdlg;
|
||
|
_selectdlg = NULL;
|
||
|
}
|
||
|
|
||
|
void DialogPCLGPMesh::accept()
|
||
|
{
|
||
|
if(_components.size() == 0)
|
||
|
return;
|
||
|
|
||
|
QString componentIds;
|
||
|
for(auto component : _components)
|
||
|
componentIds.append(QString(",%1").arg(component->getID()));
|
||
|
componentIds.remove(0, 1);
|
||
|
|
||
|
double SearchRadius = _ui->SearchRadius->value();
|
||
|
double Mu = _ui->Mu->value();
|
||
|
int MaximumNearestNeighbors = _ui->MaximumNearestNeighbors->value();
|
||
|
double MaximumSurfaceAngle = _ui->MaximumSurfaceAngle->value();
|
||
|
double MaximumAngle = _ui->MaximumAngle->value();
|
||
|
double MinimumAngle = _ui->MinimumAngle->value();
|
||
|
|
||
|
QString outfilename = "filter";
|
||
|
for(auto component : _components)
|
||
|
outfilename.append(QString("_%1").arg(component->getName()));
|
||
|
|
||
|
// 确定是否保存结果文件
|
||
|
QMessageBox::StandardButton result =
|
||
|
QMessageBox::critical(this, "info", "save as result ?");
|
||
|
QString filepath =
|
||
|
JoinPath(Setting::BusAPI::instance()->getWorkingDir(), outfilename + "_tmep.pcd");
|
||
|
QString AbFileName = filepath;
|
||
|
if(result == QMessageBox::StandardButton::Ok
|
||
|
|| result == QMessageBox::StandardButton::Yes) {
|
||
|
DebugInfo("outfilename ok ok \n");
|
||
|
QStringList suffixlist = IO::IOConfigure::getMeshExporters();
|
||
|
if(suffixlist.isEmpty()) {
|
||
|
QMessageBox::warning(this, tr("Warning"), tr("The MeshPlugin is not installed !"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
QStringList meshsuffix = ConfigOption::ConfigOption::getInstance()
|
||
|
->getMeshConfig()
|
||
|
->getExportSuffix(ConfigOption::MeshDataType::vtkMesh)
|
||
|
.split(";");
|
||
|
QStringList list;
|
||
|
|
||
|
for(QString s : meshsuffix) {
|
||
|
for(int i = 0; i < suffixlist.size(); i++) {
|
||
|
QString suffix = suffixlist.at(i);
|
||
|
if(suffix.contains(s))
|
||
|
list.append(suffix);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::sort(list.begin(), list.end());
|
||
|
QString suffixes = list.join(";;");
|
||
|
QString workDir = Setting::BusAPI::instance()->getWorkingDir();
|
||
|
|
||
|
QFileDialog dlg(this, tr("Export mesh"), workDir, suffixes);
|
||
|
dlg.setAcceptMode(QFileDialog::AcceptSave);
|
||
|
if(dlg.exec() != QFileDialog::FileName)
|
||
|
return;
|
||
|
|
||
|
QString aSuffix = dlg.selectedNameFilter();
|
||
|
QString aFileName = dlg.selectedFiles().join(",");
|
||
|
if(!(aFileName.isEmpty())) {
|
||
|
filepath = aFileName;
|
||
|
|
||
|
} else {
|
||
|
}
|
||
|
|
||
|
AbFileName = filepath;
|
||
|
|
||
|
} else { // 不保存成点云数据
|
||
|
}
|
||
|
DebugInfo("outfilename %s \n", AbFileName.toStdString().c_str());
|
||
|
// 启动线程
|
||
|
auto pclremesh =
|
||
|
new WBFZ::PCLGPMesh(AbFileName, WBFZ::PointCloudOperation::POINTCLOUD_MESH, _mainWindow,
|
||
|
componentIds, SearchRadius, Mu, MaximumNearestNeighbors,
|
||
|
MaximumSurfaceAngle, MaximumAngle, MinimumAngle);
|
||
|
ModuleBase::ThreadControl* tc = new ModuleBase::ThreadControl(pclremesh);
|
||
|
emit tc->threadStart(); // emit MSHwriter->start();
|
||
|
|
||
|
QFDialog::accept();
|
||
|
}
|
||
|
|
||
|
void DialogPCLGPMesh::selectedComponentsSlot(QList<MeshData::MeshSet*> components)
|
||
|
{
|
||
|
for(MeshData::MeshSet* set : components) {
|
||
|
if(_components.contains(set))
|
||
|
continue;
|
||
|
_components.append(set);
|
||
|
_ui->listWidget->addItem(set->getName());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DialogPCLGPMesh::customContextMenuRequestedSlot(const QPoint& point)
|
||
|
{
|
||
|
QListWidgetItem* curItem = _ui->listWidget->itemAt(point);
|
||
|
if(!curItem)
|
||
|
return;
|
||
|
|
||
|
QMenu* menu = new QMenu(this);
|
||
|
QAction* deleteItem = new QAction(tr("delete this item"));
|
||
|
menu->addAction(deleteItem);
|
||
|
connect(menu, &QMenu::triggered, [=]() { removeCurrentItem(curItem); });
|
||
|
menu->exec(QCursor::pos());
|
||
|
}
|
||
|
|
||
|
void DialogPCLGPMesh::removeCurrentItem(QListWidgetItem* curItem)
|
||
|
{
|
||
|
auto meshData = MeshData::MeshData::getInstance();
|
||
|
auto meshSet = meshData->getMeshSetByName(curItem->text());
|
||
|
if(!meshSet)
|
||
|
return;
|
||
|
_components.removeOne(meshSet);
|
||
|
_ui->listWidget->removeItemWidget(curItem);
|
||
|
delete curItem;
|
||
|
}
|
||
|
|
||
|
} // namespace MainWidget
|
||
|
|
||
|
namespace WBFZ {
|
||
|
PCLGPMesh::PCLGPMesh(const QString& fileName, WBFZ::PointCloudOperation operation,
|
||
|
GUI::MainWindow* mw, QString componentIds, double SearchRadius, double Mu,
|
||
|
int MaximumNearestNeighbors, double MaximumSurfaceAngle,
|
||
|
double MaximumAngle, double MinimumAngle)
|
||
|
: ModuleBase::ThreadTask(mw)
|
||
|
, _operation(operation)
|
||
|
, _fileName(fileName)
|
||
|
, _componentIds(componentIds)
|
||
|
, _SearchRadius(SearchRadius)
|
||
|
, _Mu(Mu)
|
||
|
, _MaximumNearestNeighbors(MaximumNearestNeighbors)
|
||
|
, _MaximumSurfaceAngle(MaximumSurfaceAngle)
|
||
|
, _MaximumAngle(MaximumAngle)
|
||
|
, _MinimumAngle(MinimumAngle)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
PCLGPMesh::~PCLGPMesh() {}
|
||
|
|
||
|
void PCLGPMesh::defaultMeshFinished()
|
||
|
{
|
||
|
if(_threadRuning) {
|
||
|
QString information{};
|
||
|
ModuleBase::Message msg;
|
||
|
if(_operation == POINTCLOUD_FILTER || _operation == POINTCLOUD_MESH) {
|
||
|
if(_success) {
|
||
|
emit _mainwindow->updateMeshTreeSig();
|
||
|
emit _mainwindow->updateSetTreeSig();
|
||
|
emit _mainwindow->updateActionStatesSig();
|
||
|
// emit _mainwindow->updateActionsStatesSig();
|
||
|
emit _mainwindow->getSubWindowManager()->openPreWindowSig();
|
||
|
emit _mainwindow->updatePreMeshActorSig();
|
||
|
information = QString("Successful Import Mesh From \"%1\"").arg(_fileName);
|
||
|
msg.type = Common::Message::Normal;
|
||
|
msg.message = information;
|
||
|
auto meshdata = MeshData::MeshData::getInstance();
|
||
|
// meshdata->generateDisplayDataSet();
|
||
|
const int nk = meshdata->getKernalCount();
|
||
|
if(nk <= 0)
|
||
|
return;
|
||
|
auto k = meshdata->getKernalAt(nk - 1);
|
||
|
if(k != nullptr)
|
||
|
k->setPath(_fileName);
|
||
|
} else {
|
||
|
information = QString("Failed Filter From \"%1\"").arg(_fileName);
|
||
|
msg.type = Common::Message::Error;
|
||
|
msg.message = information;
|
||
|
}
|
||
|
} else {
|
||
|
}
|
||
|
}
|
||
|
// emit showInformation(information);
|
||
|
// emit _mainwindow->printMessageToMessageWindow(msg);
|
||
|
ModuleBase::ThreadTask::threadTaskFinished();
|
||
|
Py::PythonAgent::getInstance()->unLock();
|
||
|
}
|
||
|
void PCLGPMesh::setThreadRunState(bool flag)
|
||
|
{
|
||
|
_success = flag;
|
||
|
}
|
||
|
|
||
|
void PCLGPMesh::run()
|
||
|
{
|
||
|
ModuleBase::ThreadTask::run();
|
||
|
bool result = false;
|
||
|
switch(_operation) {
|
||
|
case POINTCLOUD_MESH:
|
||
|
emit showInformation(tr("POINTCLOUD_MESH From \"%1\"").arg(_fileName));
|
||
|
result = remeshtaskProcess();
|
||
|
setThreadRunState(result);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
DebugInfo("run ok _success %d _threadRuning %d \n", _success, _threadRuning);
|
||
|
defaultMeshFinished();
|
||
|
}
|
||
|
|
||
|
bool PCLGPMesh::remeshtaskProcess()
|
||
|
{
|
||
|
QString componentIds = _componentIds;
|
||
|
double SearchRadius = _SearchRadius;
|
||
|
double Mu = _Mu;
|
||
|
int MaximumNearestNeighbors = _MaximumNearestNeighbors;
|
||
|
double MaximumSurfaceAngle = _MaximumSurfaceAngle;
|
||
|
double MaximumAngle = _MaximumAngle;
|
||
|
double MinimumAngle = _MinimumAngle;
|
||
|
|
||
|
emit _mainwindow->printMessage(Common::Message::Normal, "PCLGPMeshAlg");
|
||
|
// 获取vtdataset
|
||
|
QStringList qCompontIds = QString(componentIds).split(',');
|
||
|
|
||
|
MeshData::MeshSet* meshSet = NULL;
|
||
|
MeshData::MeshKernal* meshKernal = NULL;
|
||
|
MeshData::MeshData* meshData = MeshData::MeshData::getInstance();
|
||
|
QString kernalName, transformedName, setType, ids;
|
||
|
|
||
|
// 筛选其中的点云数据
|
||
|
// 创建 vtkCellDataToPointData 过滤器
|
||
|
vtkSmartPointer<vtkGeometryFilter> cellToPointFilter =
|
||
|
vtkSmartPointer<vtkGeometryFilter>::New();
|
||
|
|
||
|
QString outfilename;
|
||
|
for(QString compontId : qCompontIds) {
|
||
|
meshSet = meshData->getMeshSetByID(compontId.toInt());
|
||
|
DebugInfo("point count %d : %d \n", compontId.toInt(), meshSet == nullptr);
|
||
|
if(!meshSet)
|
||
|
continue;
|
||
|
outfilename = meshSet->getName();
|
||
|
vtkPolyData* temppolyData =
|
||
|
PointCloudOperator::PointCloudCommon::meshSetToVtkDataset(meshSet);
|
||
|
cellToPointFilter->SetInputData(temppolyData);
|
||
|
DebugInfo("point count %d : %d \n", compontId.toInt(), temppolyData == nullptr);
|
||
|
}
|
||
|
|
||
|
// 执行过滤操作
|
||
|
cellToPointFilter->Update();
|
||
|
DebugInfo("cellToPointFilter \n");
|
||
|
// 获取过滤后的 vtkPolyData
|
||
|
vtkPolyData* outpolyData = cellToPointFilter->GetOutput();
|
||
|
DebugInfo("outpolyData ");
|
||
|
|
||
|
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud_with_rgba(
|
||
|
new pcl::PointCloud<pcl::PointXYZRGBA>);
|
||
|
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud_filtered(
|
||
|
new pcl::PointCloud<pcl::PointXYZRGBA>);
|
||
|
pcl::io::vtkPolyDataToPointCloud(outpolyData, *cloud_with_rgba);
|
||
|
// 创建滤波器对象
|
||
|
|
||
|
// 计算点云滤波结果
|
||
|
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr output(
|
||
|
new pcl::PointCloud<pcl::PointXYZRGBA>); // 输出结果
|
||
|
pcl::PointCloud<pcl::PointNormal>::Ptr cloud_with_normals(
|
||
|
new pcl::PointCloud<pcl::PointNormal>);
|
||
|
pcl::PolygonMesh* mesh = new pcl::PolygonMesh;
|
||
|
|
||
|
// 估计法线
|
||
|
PointCloudOperator::PointCloudCommon::NormalEstimation(cloud_with_rgba, cloud_with_normals);
|
||
|
|
||
|
//------------------定义搜索树对象------------------------
|
||
|
pcl::search::KdTree<pcl::PointNormal>::Ptr tree2(new pcl::search::KdTree<pcl::PointNormal>);
|
||
|
tree2->setInputCloud(cloud_with_normals);
|
||
|
pcl::GreedyProjectionTriangulation<pcl::PointNormal> gp3; // 定义三角化对象
|
||
|
|
||
|
gp3.setSearchRadius(SearchRadius); // 设置连接点之间的最大距离(即三角形的最大边长)
|
||
|
gp3.setMu(Mu); // 设置被样本点搜索其临近点的最远距离,为了适应点云密度的变化
|
||
|
gp3.setMaximumNearestNeighbors(MaximumNearestNeighbors); // 设置样本点可搜索的邻域个数
|
||
|
gp3.setMaximumSurfaceAngle(
|
||
|
MaximumSurfaceAngle); // 设置某点法线方向偏离样本点法线方向的最大角度
|
||
|
gp3.setMinimumAngle(MinimumAngle); // 设置三角化后得到三角形内角的最小角度
|
||
|
gp3.setMaximumAngle(MaximumAngle); // 设置三角化后得到三角形内角的最大角度
|
||
|
gp3.setNormalConsistency(false); // 设置该参数保证法线朝向一致
|
||
|
// qDebug() << "[GP] start reconstruct :" << cloud_in->points.size();
|
||
|
// Get result
|
||
|
DebugInfo("setInputCloud point count : %d \n", cloud_with_normals->size());
|
||
|
gp3.setInputCloud(cloud_with_normals); // 设置输入点云为有向点云
|
||
|
gp3.setSearchMethod(tree2); // 设置搜索方式
|
||
|
gp3.reconstruct(*mesh); // 重建提取三角化
|
||
|
emit _mainwindow->printMessageToMessageWindow(
|
||
|
QString("edge count %1 ").arg(QString::number(mesh->polygons.size())));
|
||
|
DebugInfo("remesh over ,conver init %d \n", mesh->polygons.size());
|
||
|
// 转换处理结果
|
||
|
vtkPolyData* polydata = vtkPolyData::New(); // 创建新的指针,智能指针会释放
|
||
|
size_t pointcount = PointCloudOperator::PointCloudCommon::mesh2vtk(*mesh, polydata);
|
||
|
DebugInfo("remesh over ,conver over polydata %d , pointcount %d\n", polydata == nullptr,
|
||
|
pointcount);
|
||
|
vtkDataSet* dataset =
|
||
|
vtkDataSet::SafeDownCast(polydata); // 默认完成 vtkpolydata --> vtkdataset
|
||
|
// outpolyData->Delete(); // 释放指针 ,
|
||
|
// 千万不能释放这个指针,不然程序回调时,会直接内存崩溃
|
||
|
DebugInfo("PCLGPMeshAlg successfully!! wait for writing file \n");
|
||
|
|
||
|
// vtkDataSet*
|
||
|
// dataset=PCLGPMeshAlg(_componentIds,_SearchRadius,_Mu,_MaximumNearestNeighbors,_MaximumSurfaceAngle,_MaximumAngle,_MinimumAngle);
|
||
|
DebugInfo("remesh \n");
|
||
|
QString filepath = _fileName;
|
||
|
|
||
|
// 加载文件
|
||
|
QFileInfo info(filepath);
|
||
|
QString name = info.fileName();
|
||
|
QString path = info.filePath();
|
||
|
QString suffix = info.suffix().toLower();
|
||
|
QTextCodec* codec = QTextCodec::codecForName("GB18030");
|
||
|
QByteArray ba = codec->fromUnicode(filepath);
|
||
|
|
||
|
std::string outFileName = ba.data();
|
||
|
DebugInfo("writing %s suffix %s !! \n", ba.data(), suffix.toStdString().c_str());
|
||
|
if(suffix == "vtk") {
|
||
|
vtkSmartPointer<vtkUnstructuredGridWriter> writer =
|
||
|
vtkSmartPointer<vtkUnstructuredGridWriter>::New();
|
||
|
writer->SetInputData(dataset);
|
||
|
writer->SetFileTypeToBinary();
|
||
|
writer->SetFileName(ba);
|
||
|
writer->Write();
|
||
|
DebugInfo("writing vtk !! \n");
|
||
|
} else if(suffix == "stl") {
|
||
|
QTextCodec* codec = QTextCodec::codecForName("GB18030");
|
||
|
QByteArray ba = codec->fromUnicode(_fileName);
|
||
|
|
||
|
vtkSmartPointer<vtkSTLWriter> writer = vtkSmartPointer<vtkSTLWriter>::New();
|
||
|
writer->SetInputData(dataset);
|
||
|
writer->SetFileTypeToBinary();
|
||
|
writer->SetFileName(ba);
|
||
|
writer->Write();
|
||
|
DebugInfo("writing stl !! \n");
|
||
|
} else {
|
||
|
}
|
||
|
|
||
|
DebugInfo("writing finish !! %d \n", dataset == nullptr);
|
||
|
|
||
|
// 回调函数
|
||
|
DebugInfo("remeshtaskProcess !! %d \n", dataset == nullptr);
|
||
|
|
||
|
// QFileInfo info(_fileName);
|
||
|
// QString name = info.fileName();
|
||
|
// QString path = info.filePath();
|
||
|
// QString suffix = info.suffix().toLower();
|
||
|
DebugInfo("dataset %d _fileName %s \n", dataset == nullptr,
|
||
|
_fileName.toStdString().c_str());
|
||
|
// 保存流程文件
|
||
|
if(dataset != nullptr) {
|
||
|
DebugInfo("load result in meshKernal \n");
|
||
|
auto* k = new MeshData::MeshKernal;
|
||
|
k->setName(name);
|
||
|
k->setPath(path);
|
||
|
auto meshData = MeshData::MeshData::getInstance();
|
||
|
int nKernal = meshData->getKernalCount();
|
||
|
for(int iKernal = 0; iKernal < nKernal; ++iKernal) {
|
||
|
MeshData::MeshKernal* temp = meshData->getKernalAt(iKernal);
|
||
|
if(temp->getPath() == path) { ///< MG same file update
|
||
|
meshData->removeKernalAt(iKernal);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
k->setMeshData(dataset);
|
||
|
meshData->appendMeshKernal(k);
|
||
|
DebugInfo("load result in meshKernal \n");
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
} // namespace WBFZ
|