/
klischa
/
AstraScanner2
Обзор
Документация
Войти
/
klischa
/
AstraScanner2
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/controllers/ProjectWorkflow.cpp
321 строка
12 KB
k k
refactor: capture subsystem overhaul
15 июл 2026, 11:17
15 июл 2026, 11:17
1123c30
Код
Авторство
О чём код?
#include "ProjectWorkflow.h" #include "../project/ProjectManager.h" #include "../export/ExportManager.h" #include "../settings/SettingsManager.h" #include <QWidget> #include <QStatusBar> #include <QListWidget> #include <QComboBox> #include <QLabel> #include <QMenu> #include <QAction> #include <QMessageBox> #include <QInputDialog> #include <QFileDialog> #include <QFileInfo> #include <QDir> #include <QDebug> namespace { QString trackingModeToData(TrackingMode mode) { switch (mode) { case TrackingMode::MarkerBased: return QStringLiteral("marker_based"); case TrackingMode::NeuralBased: return QStringLiteral("neural_based"); case TrackingMode::ICP: default: return QStringLiteral("icp"); } } } ProjectWorkflow::ProjectWorkflow(QWidget *parentWidget, ProjectManager *projectManager, ExportManager *exportManager, QObject *parent) : QObject(parent) , m_parentWidget(parentWidget) , m_project(projectManager) , m_exporter(exportManager) { } ProjectWorkflow::~ProjectWorkflow() = default; void ProjectWorkflow::bindWidgets(const Widgets &w) { m_w = w; } void ProjectWorkflow::setStatusBar(QStatusBar *sb) { m_statusBar = sb; } // ========== Project CRUD ========== void ProjectWorkflow::onNewProject() { if (!confirmDiscardDirtyProject("Новый проект", "Текущий проект не сохранён. Создать новый и отбросить изменения?")) { return; } const QString dir = chooseProjectDirectory("Создать новый проект (укажите папку)", false); if (dir.isEmpty()) return; if (!m_project->newProject(dir)) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); return; } SettingsManager::instance().setProjectsDirectory(QFileInfo(dir).absolutePath()); refreshScansList(); emit projectOpened(); } void ProjectWorkflow::onOpenProject() { if (!confirmDiscardDirtyProject("Открыть проект", "Текущий проект не сохранён. Открыть другой и отбросить изменения?")) { return; } const QString dir = chooseProjectDirectory("Выберите папку проекта", true); if (dir.isEmpty()) return; if (!m_project->openProject(dir)) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); return; } SettingsManager::instance().setProjectsDirectory(QFileInfo(dir).absolutePath()); refreshScansList(); emit projectOpened(); } void ProjectWorkflow::onSaveProject() { if (!ensureProjectOpenForSave()) return; if (!m_project->saveProject()) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); } else { if (m_statusBar) m_statusBar->showMessage("Проект сохранён", 3000); emit projectSaved(); } } void ProjectWorkflow::onSaveProjectAs() { const QString dir = chooseProjectDirectory("Сохранить проект как…", false); if (dir.isEmpty()) return; if (!m_project->isOpen()) { if (!m_project->newProject(dir)) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); return; } } else if (!m_project->saveProjectAs(dir)) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); return; } SettingsManager::instance().setProjectsDirectory(QFileInfo(dir).absolutePath()); refreshScansList(); emit projectSaved(); } // ========== Scan Management ========== void ProjectWorkflow::onAddCurrentCloudToProject() { pcl::PointCloud<pcl::PointXYZRGB>::Ptr snapshot = currentCloudSnapshot(); if (!snapshot) { QMessageBox::information(m_parentWidget, "Проект", "Текущее облако пустое."); return; } if (!m_project->isOpen()) { auto r = QMessageBox::question(m_parentWidget, "Проект не открыт", "Проект не открыт. Создать новый проект?"); if (r != QMessageBox::Yes) return; onNewProject(); if (!m_project->isOpen()) return; } bool ok = false; const QString name = QInputDialog::getText(m_parentWidget, "Имя скана", "Введите имя скана:", QLineEdit::Normal, QString("Scan %1").arg(m_project->scanCount() + 1), &ok); if (!ok) return; const int idx = m_project->addScan(snapshot, name); if (idx < 0) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); return; } if (m_statusBar) m_statusBar->showMessage( QString("Скан добавлен в проект (%1 точек)").arg(snapshot->size()), 3000); refreshScansList(); emit scanAdded(idx); } void ProjectWorkflow::onScansListContextMenu(const QPoint &pos) { if (!m_w.scansList || !m_project) return; QListWidgetItem *item = m_w.scansList->itemAt(pos); if (!item) return; const int idx = m_w.scansList->row(item); QMenu menu(m_parentWidget); QAction *loadAct = menu.addAction("Загрузить как текущее облако"); QAction *renameAct = menu.addAction("Переименовать…"); QAction *exportAct = menu.addAction("Экспорт…"); menu.addSeparator(); QAction *removeAct = menu.addAction("Удалить"); QAction *chosen = menu.exec(m_w.scansList->mapToGlobal(pos)); if (!chosen) return; if (chosen == loadAct) { loadProjectScanIntoCurrentCloud(idx); } else if (chosen == renameAct) { bool ok = false; const QString newName = QInputDialog::getText(m_parentWidget, "Переименовать скан", "Новое имя:", QLineEdit::Normal, m_project->scans().at(idx).name, &ok); if (ok && !newName.isEmpty()) { m_project->renameScan(idx, newName); refreshScansList(); } } else if (chosen == exportAct) { auto cloud = m_project->scanCloud(idx); if (!cloud) { QMessageBox::warning(m_parentWidget, "Ошибка", m_project->lastError()); return; } tryExportPointCloud(cloud, "Экспорт скана"); } else if (chosen == removeAct) { auto r = QMessageBox::question(m_parentWidget, "Удалить скан", QString("Удалить скан «%1»?").arg(m_project->scans().at(idx).name)); if (r == QMessageBox::Yes) { m_project->removeScan(idx); refreshScansList(); } } } void ProjectWorkflow::onScansListDoubleClicked(int index) { if (index >= 0) loadProjectScanIntoCurrentCloud(index); } void ProjectWorkflow::refreshScansList() { if (!m_w.scansList || !m_project) return; m_w.scansList->clear(); for (const ScanItem &item : m_project->scans()) { const QString label = QString("%1 (%2 точек, %3)") .arg(item.name) .arg(item.pointCount) .arg(item.relativeFilePath); m_w.scansList->addItem(label); } updateProjectStatusUi(); } void ProjectWorkflow::updateProjectStatusUi() { if (!m_project) return; const QString base = m_project->isOpen() ? QString("Проект: %1").arg(m_project->projectName()) : QString("Проект: (не открыт)"); const QString text = m_project->isDirty() ? base + " *" : base; if (m_w.projectStatusLabel) m_w.projectStatusLabel->setText(text); if (m_w.homeProjectStatusLabel) m_w.homeProjectStatusLabel->setText(text); } void ProjectWorkflow::syncTrackingModeComboFromProject() { if (!m_w.trackingModeCombo || !m_project) return; const QString modeStr = trackingModeToData(m_project->getTrackingMode()); const int idx = m_w.trackingModeCombo->findData(modeStr); if (idx >= 0 && idx != m_w.trackingModeCombo->currentIndex()) { QSignalBlocker blocker(m_w.trackingModeCombo); m_w.trackingModeCombo->setCurrentIndex(idx); } } // ========== Export ========== void ProjectWorkflow::onExportCurrentCloud() { tryExportPointCloud(currentCloudSnapshot(), "Экспорт облака"); } void ProjectWorkflow::onExportMesh() { QMessageBox::information(m_parentWidget, "Экспорт меша", "Экспорт меша доступен через ProcessingWorkflow."); } // ========== Helpers ========== pcl::PointCloud<pcl::PointXYZRGB>::Ptr ProjectWorkflow::currentCloudSnapshot() { // This will be connected to ScanWorkflow's accumulatedCloud via signal return nullptr; } bool ProjectWorkflow::loadProjectScanIntoCurrentCloud(int index) { if (!m_project || index < 0 || index >= m_project->scanCount()) return false; auto cloud = m_project->scanCloud(index); if (!cloud || cloud->empty()) return false; emit cloudLoaded(cloud); return true; } bool ProjectWorkflow::confirmDiscardDirtyProject(const QString &title, const QString &question) { if (!m_project || !m_project->isDirty()) return true; return QMessageBox::question(m_parentWidget, title, question, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; } QString ProjectWorkflow::chooseProjectDirectory(const QString &title, bool existingOnly) { const QString startDir = SettingsManager::instance().projectsDirectory(); if (existingOnly) { return QFileDialog::getExistingDirectory(m_parentWidget, title, startDir); } // For "New Project" / "Save As": let user type a new folder name. // getExistingDirectory doesn't allow creating new folders, so we use // getSaveFileName which permits typing arbitrary names. const QString filename = QFileDialog::getSaveFileName( m_parentWidget, title, startDir + QStringLiteral("/new_project"), QString(), nullptr, QFileDialog::DontUseNativeDialog); if (filename.isEmpty()) return QString(); QDir().mkpath(filename); return filename; } bool ProjectWorkflow::ensureProjectOpenForSave() { if (m_project && m_project->isOpen()) return true; QMessageBox::warning(m_parentWidget, "Сохранение", "Проект не открыт."); return false; } QString ProjectWorkflow::chooseExportFilename(const QString &title, const QStringList &filters) { return QFileDialog::getSaveFileName(m_parentWidget, title, QString(), filters.join(";;")); } void ProjectWorkflow::rememberExportDirectory(const QString &filename) { if (!filename.isEmpty()) { SettingsManager::instance().setLastExportDirectory(QFileInfo(filename).absolutePath()); } } bool ProjectWorkflow::tryExportPointCloud(const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr &cloud, const QString &dialogTitle) { if (!cloud || cloud->empty()) { QMessageBox::information(m_parentWidget, dialogTitle, "Облако пустое."); return false; } const QString filename = chooseExportFilename(dialogTitle, ExportManager::cloudFileFilters()); if (filename.isEmpty()) return false; rememberExportDirectory(filename); if (!m_exporter->savePointCloud(cloud, filename)) { QMessageBox::warning(m_parentWidget, "Ошибка экспорта", m_exporter->lastError()); return false; } if (m_statusBar) m_statusBar->showMessage(QString("Экспортировано: %1").arg(filename), 5000); return true; }