/
klischa
/
AstraScanner2
Обзор
Документация
Войти
/
klischa
/
AstraScanner2
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/controllers/ProcessingWorkflow.cpp
336 строк
12 KB
k k
refactor(P0): Phase 3 — ProcessingWorkflow extracted from MainWindow
14 июл 2026, 13:26
14 июл 2026, 13:26
ce87e14
Код
Авторство
О чём код?
#include "ProcessingWorkflow.h" #include "ProcessingController.h" #include "../project/ProjectManager.h" #include "ViewerCoordinator.h" #include <QLabel> #include <QPushButton> #include <QProgressBar> #include <QTextEdit> #include <QDebug> #include <QMessageBox> #include <QInputDialog> #include <QMutexLocker> #include <QStatusBar> #include <pcl/filters/voxel_grid.h> ProcessingWorkflow::ProcessingWorkflow(ProcessingController *processingController, ProjectManager *project, ViewerCoordinator *viewer, QObject *parent) : QObject(parent) , m_processingController(processingController) , m_project(project) , m_viewerCoordinator(viewer) { } ProcessingWorkflow::~ProcessingWorkflow() = default; void ProcessingWorkflow::bindWidgets(const Widgets &w) { m_w = w; } // ========== Cloud Processing ========== void ProcessingWorkflow::runCloudProcessingAction( const pcl::PointCloud<pcl::PointXYZRGB>::Ptr &snapshot, const ProcessingController::CloudOperation &operation, const QString &cancelMessage, bool usesProcessingProgress) { if (!snapshot) return; setFilterButtonsEnabled(false); if (usesProcessingProgress) { if (m_w.processingProgress) m_w.processingProgress->setValue(0); m_activeProgressBar = m_w.processingProgress; } m_processingController->runCloudOperation( this, operation, [this, usesProcessingProgress](pcl::PointCloud<pcl::PointXYZRGB>::Ptr filtered) { applyProcessedCloudResult(filtered, usesProcessingProgress); }, [this, cancelMessage, usesProcessingProgress]() { handleCloudProcessingCanceled(cancelMessage, usesProcessingProgress); }); } void ProcessingWorkflow::applyProcessedCloudResult( const pcl::PointCloud<pcl::PointXYZRGB>::Ptr &filtered, bool usesProcessingProgress) { if (!filtered || filtered->empty()) { if (usesProcessingProgress && m_w.processingProgress) m_w.processingProgress->setValue(0); if (usesProcessingProgress) m_activeProgressBar = nullptr; setFilterButtonsEnabled(true); QMessageBox::warning(nullptr, "Обработка", "Операция вернула пустой результат."); return; } emit cloudProcessed(filtered); emit cloudSizeChanged(static_cast<int>(filtered->size())); { QMutexLocker locker(&m_meshMutex); m_meshSaved = false; } if (usesProcessingProgress && m_w.processingProgress) m_w.processingProgress->setValue(100); if (usesProcessingProgress) m_activeProgressBar = nullptr; setFilterButtonsEnabled(true); } void ProcessingWorkflow::handleCloudProcessingCanceled(const QString &logMessage, bool usesProcessingProgress) { if (m_w.logTextEdit && !logMessage.isEmpty()) appendLog(logMessage); if (usesProcessingProgress && m_w.processingProgress) m_w.processingProgress->setValue(0); if (usesProcessingProgress) m_activeProgressBar = nullptr; setFilterButtonsEnabled(true); } // ========== Mesh Processing ========== void ProcessingWorkflow::runMeshProcessingAction( const pcl::PolygonMesh &meshCopy, const ProcessingController::MeshOperation &operation, const QString &cancelMessage) { setMeshFilterButtonsEnabled(false); if (m_w.processingProgress) m_w.processingProgress->setValue(0); m_activeProgressBar = m_w.processingProgress; m_processingController->runMeshOperation( this, operation, [this](const pcl::PolygonMesh &mesh) { applyProcessedMeshResult(mesh); }, [this, cancelMessage]() { handleMeshProcessingCanceled(cancelMessage); }); } void ProcessingWorkflow::applyProcessedMeshResult(const pcl::PolygonMesh &mesh) { { QMutexLocker locker(&m_meshMutex); m_lastMesh = mesh; m_meshSaved = true; } emit meshProcessed(mesh); if (m_w.processingProgress) m_w.processingProgress->setValue(100); m_activeProgressBar = nullptr; setMeshFilterButtonsEnabled(true); } void ProcessingWorkflow::handleMeshProcessingCanceled(const QString &logMessage) { if (m_w.logTextEdit && !logMessage.isEmpty()) appendLog(logMessage); if (m_w.processingProgress) m_w.processingProgress->setValue(0); m_activeProgressBar = nullptr; setMeshFilterButtonsEnabled(true); } bool ProcessingWorkflow::currentMeshSnapshot(pcl::PolygonMesh &meshCopy) { if (!m_meshSaved) { QMessageBox::warning(nullptr, "Постобработка", "Сначала постройте меш через Poisson-реконструкцию"); setMeshFilterButtonsEnabled(true); return false; } { QMutexLocker locker(&m_meshMutex); meshCopy = m_lastMesh; } if (meshCopy.cloud.data.empty() || meshCopy.polygons.empty()) { QMessageBox::warning(nullptr, "Постобработка", "Меш пустой"); setMeshFilterButtonsEnabled(true); return false; } return true; } // ========== Poisson Reconstruction ========== void ProcessingWorkflow::onReconstructMeshClicked(const PointCloudFilters::PoissonParams ¶ms) { pcl::PointCloud<pcl::PointXYZRGB>::Ptr snapshot; { QMutexLocker locker(&m_meshMutex); // Use accumulated cloud from ScanWorkflow via signal emit cloudSizeChanged(0); // trigger snapshot request } if (m_processingController->isReconstructionRunning()) { QMessageBox::information(nullptr, "Реконструкция", "Реконструкция уже запущена."); return; } if (m_w.meshStatusLabel) m_w.meshStatusLabel->setText( QString("Реконструкция… (%1 точек)").arg(snapshot ? snapshot->size() : 0)); if (m_w.poissonProgress) m_w.poissonProgress->setValue(0); m_activeProgressBar = m_w.poissonProgress; m_processingController->runReconstruction( this, snapshot, params, [this](const pcl::PolygonMesh &mesh) { handlePoissonResult(mesh); }, [this]() { handlePoissonCanceled(); }); } void ProcessingWorkflow::handlePoissonResult(const pcl::PolygonMesh &mesh) { for (QPushButton *btn : findChildren<QPushButton*>()) { if (btn->text() == "Построить меш") btn->setEnabled(true); } if (mesh.polygons.empty()) { if (m_w.meshStatusLabel) m_w.meshStatusLabel->setText("Меш не построен"); if (m_w.poissonProgress) m_w.poissonProgress->setValue(0); m_activeProgressBar = nullptr; QMessageBox::warning(nullptr, "Реконструкция", "Poisson не смог построить меш."); return; } { QMutexLocker locker(&m_meshMutex); m_lastMesh = mesh; m_meshSaved = true; } const qulonglong nPoly = static_cast<qulonglong>(mesh.polygons.size()); if (m_w.meshStatusLabel) m_w.meshStatusLabel->setText(QString("Меш: %1 полигонов").arg(nPoly)); if (m_w.poissonProgress) m_w.poissonProgress->setValue(100); m_activeProgressBar = nullptr; if (m_viewerCoordinator) { m_viewerCoordinator->showMesh(m_lastMesh, "poisson_mesh"); } } void ProcessingWorkflow::handlePoissonCanceled() { for (QPushButton *btn : findChildren<QPushButton*>()) { if (btn->text() == "Построить меш") btn->setEnabled(true); } if (m_w.meshStatusLabel) m_w.meshStatusLabel->setText("Poisson: отменено"); if (m_w.poissonProgress) m_w.poissonProgress->setValue(0); m_activeProgressBar = nullptr; } // ========== ICP Merge ========== void ProcessingWorkflow::onMergeScansClicked(const PointCloudFilters::MergeParams ¶ms) { if (!m_project || !m_project->isOpen() || m_project->scanCount() < 2) { QMessageBox::information(nullptr, "Объединение", "Для объединения нужен открытый проект с ≥ 2 сканами."); return; } if (m_processingController->isMergeRunning()) { QMessageBox::information(nullptr, "Объединение", "Объединение уже запущено."); return; } std::vector<pcl::PointCloud<pcl::PointXYZRGB>::Ptr> scans; scans.reserve(m_project->scanCount()); for (int i = 0; i < m_project->scanCount(); ++i) { auto c = m_project->scanCloud(i); if (c && !c->empty()) scans.push_back(c); } if (scans.size() < 2) { QMessageBox::information(nullptr, "Объединение", "Меньше 2 непустых сканов."); return; } if (m_w.mergeBtn) m_w.mergeBtn->setEnabled(false); if (m_w.addMergedBtn) m_w.addMergedBtn->setEnabled(false); if (m_w.icpStatusLabel) m_w.icpStatusLabel->setText( QString("ICP: %1 сканов…").arg(scans.size())); if (m_w.icpProgress) m_w.icpProgress->setValue(0); m_activeProgressBar = m_w.icpProgress; m_processingController->runMergeScans( this, scans, params, [this](pcl::PointCloud<pcl::PointXYZRGB>::Ptr merged) { handleMergeResult(merged); }, [this]() { handleMergeCanceled(); }); } void ProcessingWorkflow::handleMergeResult(const pcl::PointCloud<pcl::PointXYZRGB>::Ptr &merged) { if (m_w.mergeBtn) m_w.mergeBtn->setEnabled(true); if (!merged || merged->empty()) { if (m_w.icpStatusLabel) m_w.icpStatusLabel->setText("Объединение не удалось"); if (m_w.icpProgress) m_w.icpProgress->setValue(0); m_activeProgressBar = nullptr; QMessageBox::warning(nullptr, "Объединение", "ICP вернул пустое облако."); return; } m_lastMerged = merged; const int npts = static_cast<int>(merged->size()); if (m_w.icpStatusLabel) m_w.icpStatusLabel->setText(QString("Готово: %1 точек").arg(npts)); if (m_w.icpProgress) m_w.icpProgress->setValue(100); m_activeProgressBar = nullptr; if (m_w.addMergedBtn) m_w.addMergedBtn->setEnabled(true); emit cloudSizeChanged(npts); } void ProcessingWorkflow::handleMergeCanceled() { if (m_w.icpStatusLabel) m_w.icpStatusLabel->setText("Объединение отменено"); if (m_w.icpProgress) m_w.icpProgress->setValue(0); m_activeProgressBar = nullptr; if (m_w.mergeBtn) m_w.mergeBtn->setEnabled(true); if (m_w.addMergedBtn) m_w.addMergedBtn->setEnabled(false); } void ProcessingWorkflow::onSaveMergedToProject() { if (!m_lastMerged || m_lastMerged->empty()) { QMessageBox::information(nullptr, "Сохранение", "Нет объединённого облака."); return; } if (!m_project || !m_project->isOpen()) { QMessageBox::warning(nullptr, "Сохранение", "Проект не открыт."); return; } bool ok = false; const QString defaultName = QString("merged_%1").arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss")); const QString name = QInputDialog::getText(nullptr, "Новый скан", "Имя:", QLineEdit::Normal, defaultName, &ok); if (!ok || name.trimmed().isEmpty()) return; const int index = m_project->addScan(m_lastMerged, name.trimmed()); if (index < 0) { QMessageBox::critical(nullptr, "Сохранение", QString("Ошибка: %1").arg(m_project->lastError())); } } // ========== Show Cloud ========== void ProcessingWorkflow::onShowCloudClicked() { if (m_viewerCoordinator) { // Request accumulated cloud from ScanWorkflow via signal emit cloudSizeChanged(0); } } // ========== Button State ========== void ProcessingWorkflow::setFilterButtonsEnabled(bool enabled) { if (m_sorBtn) m_sorBtn->setEnabled(enabled); if (m_rorBtn) m_rorBtn->setEnabled(enabled); if (m_voxelBtn) m_voxelBtn->setEnabled(enabled); if (m_magicWandBtn) m_magicWandBtn->setEnabled(enabled); if (m_bgBtn) m_bgBtn->setEnabled(enabled); if (m_segBtn) m_segBtn->setEnabled(enabled); } void ProcessingWorkflow::setMeshFilterButtonsEnabled(bool enabled) { if (m_smoothBtn) m_smoothBtn->setEnabled(enabled); if (m_simplBtn) m_simplBtn->setEnabled(enabled); if (m_holeBtn) m_holeBtn->setEnabled(enabled); } void ProcessingWorkflow::appendLog(const QString &text) { if (m_w.logTextEdit) m_w.logTextEdit->append(text); }