/
klischa
/
AstraScanner2
Обзор
Документация
Войти
/
klischa
/
AstraScanner2
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/controllers/ProcessingTabBinder.cpp
468 строк
22 KB
Arena Agent
ui: add contextual help shortcuts across scanner ui
18 июн 2026, 21:46
18 июн 2026, 21:46
79d6112
Код
Авторство
О чём код?
#include "ProcessingTabBinder.h" #include "../settings/SettingsManager.h" #include <QTabWidget> #include <QWidget> #include <QVBoxLayout> #include <QHBoxLayout> #include <QGroupBox> #include <QLabel> #include <QFrame> #include <QSlider> #include <QCheckBox> #include <QSpinBox> #include <QDoubleSpinBox> #include <QPushButton> #include <QProgressBar> #include <QScrollArea> #include <QSplitter> #include <QSizePolicy> #include <QVTKOpenGLNativeWidget.h> ProcessingTabBinder::ProcessingTabBinder(QObject *parent) : QObject(parent) { } ProcessingTabBinder::Widgets ProcessingTabBinder::bind(QTabWidget *tabWidget, QWidget *owner) { Widgets w; QWidget* processingTab = new QWidget(); tabWidget->addTab(processingTab, "Обработка"); QVBoxLayout* processingLayout = new QVBoxLayout(processingTab); processingLayout->setContentsMargins(6, 6, 6, 6); processingLayout->setSpacing(6); QSplitter* splitter = new QSplitter(Qt::Horizontal, processingTab); splitter->setChildrenCollapsible(false); processingLayout->addWidget(splitter, 1); QScrollArea* toolsScroll = new QScrollArea(processingTab); toolsScroll->setWidgetResizable(true); toolsScroll->setFrameShape(QFrame::NoFrame); toolsScroll->setMinimumWidth(420); QWidget* toolsPanel = new QWidget(toolsScroll); QVBoxLayout* toolsLayout = new QVBoxLayout(toolsPanel); toolsLayout->setContentsMargins(0, 0, 0, 0); toolsLayout->setSpacing(8); toolsScroll->setWidget(toolsPanel); QWidget* viewerPanel = new QWidget(processingTab); QVBoxLayout* viewerLayout = new QVBoxLayout(viewerPanel); viewerLayout->setContentsMargins(0, 0, 0, 0); viewerLayout->setSpacing(0); w.procVtkWidget = new QVTKOpenGLNativeWidget(processingTab); w.procVtkWidget->setMinimumSize(640, 480); w.procVtkWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); viewerLayout->addWidget(w.procVtkWidget); splitter->addWidget(toolsScroll); splitter->addWidget(viewerPanel); splitter->setStretchFactor(0, 0); splitter->setStretchFactor(1, 1); splitter->setSizes({460, 1040}); QGroupBox* statusGroup = new QGroupBox("Статус локальных операций", owner); QVBoxLayout* statusLayout = new QVBoxLayout(statusGroup); w.processingProgress = new QProgressBar(owner); w.processingProgress->setRange(0, 100); w.processingProgress->setValue(0); statusLayout->addWidget(w.processingProgress); toolsLayout->addWidget(statusGroup); QGroupBox* filterGroup = new QGroupBox("1. Очистка облака", owner); QVBoxLayout* filterLayout = new QVBoxLayout(filterGroup); QHBoxLayout* cleanupHelpLayout = new QHBoxLayout(); cleanupHelpLayout->addStretch(); w.cleanupHelpBtn = new QPushButton("?", owner); w.cleanupHelpBtn->setFixedWidth(28); w.cleanupHelpBtn->setToolTip("Открыть справку по очистке облака"); cleanupHelpLayout->addWidget(w.cleanupHelpBtn); filterLayout->addLayout(cleanupHelpLayout); QHBoxLayout* sorLayout = new QHBoxLayout(); QLabel* sorLabel = new QLabel("SOR (mean K):", owner); w.sorMeanKSpin = new QSpinBox(owner); w.sorMeanKSpin->setRange(5, 500); w.sorMeanKSpin->setValue(SettingsManager::instance().sorMeanK()); QLabel* sorThreshLabel = new QLabel("порог:", owner); w.sorThreshSpin = new QDoubleSpinBox(owner); w.sorThreshSpin->setRange(0.1, 10.0); w.sorThreshSpin->setSingleStep(0.1); w.sorThreshSpin->setValue(SettingsManager::instance().sorStddevMul()); w.sorBtn = new QPushButton("Применить SOR", owner); sorLayout->addWidget(sorLabel); sorLayout->addWidget(w.sorMeanKSpin); sorLayout->addWidget(sorThreshLabel); sorLayout->addWidget(w.sorThreshSpin); sorLayout->addWidget(w.sorBtn); sorLayout->addStretch(); filterLayout->addLayout(sorLayout); QHBoxLayout* rorLayout = new QHBoxLayout(); QLabel* rorLabel = new QLabel("ROR (радиус):", owner); w.rorRadiusSpin = new QDoubleSpinBox(owner); w.rorRadiusSpin->setDecimals(4); w.rorRadiusSpin->setRange(0.001, 0.5); w.rorRadiusSpin->setSingleStep(0.001); w.rorRadiusSpin->setValue(SettingsManager::instance().rorRadius()); QLabel* rorNeighborsLabel = new QLabel("мин. соседей:", owner); w.rorNeighborsSpin = new QSpinBox(owner); w.rorNeighborsSpin->setRange(1, 200); w.rorNeighborsSpin->setValue(SettingsManager::instance().rorMinNeighbors()); w.rorBtn = new QPushButton("Применить ROR", owner); rorLayout->addWidget(rorLabel); rorLayout->addWidget(w.rorRadiusSpin); rorLayout->addWidget(rorNeighborsLabel); rorLayout->addWidget(w.rorNeighborsSpin); rorLayout->addWidget(w.rorBtn); rorLayout->addStretch(); filterLayout->addLayout(rorLayout); QHBoxLayout* voxelLayout = new QHBoxLayout(); QLabel* voxelLabel = new QLabel("Воксель (размер):", owner); w.voxelSizeSpin = new QDoubleSpinBox(owner); w.voxelSizeSpin->setDecimals(4); w.voxelSizeSpin->setRange(0.0001, 0.05); w.voxelSizeSpin->setSingleStep(0.001); w.voxelSizeSpin->setValue(SettingsManager::instance().voxelLeafSize()); w.voxelBtn = new QPushButton("Применить воксель", owner); voxelLayout->addWidget(voxelLabel); voxelLayout->addWidget(w.voxelSizeSpin); voxelLayout->addWidget(w.voxelBtn); voxelLayout->addStretch(); filterLayout->addLayout(voxelLayout); w.magicWandBtn = new QPushButton("Magic Wand (быстрая очистка)", owner); filterLayout->addWidget(w.magicWandBtn); toolsLayout->addWidget(filterGroup); QGroupBox* objectPrepGroup = new QGroupBox("2. Подготовка объекта", owner); QVBoxLayout* objectPrepLayout = new QVBoxLayout(objectPrepGroup); QHBoxLayout* objectPrepHelpLayout = new QHBoxLayout(); objectPrepHelpLayout->addStretch(); w.objectPrepHelpBtn = new QPushButton("?", owner); w.objectPrepHelpBtn->setFixedWidth(28); w.objectPrepHelpBtn->setToolTip("Открыть справку по удалению фона и выделению объекта"); objectPrepHelpLayout->addWidget(w.objectPrepHelpBtn); objectPrepLayout->addLayout(objectPrepHelpLayout); QHBoxLayout* bgLayout = new QHBoxLayout(); QLabel* bgDistLabel = new QLabel("Расстояние (м):", owner); w.bgDistSpin = new QDoubleSpinBox(owner); w.bgDistSpin->setDecimals(3); w.bgDistSpin->setRange(0.001, 0.1); w.bgDistSpin->setSingleStep(0.001); w.bgDistSpin->setValue(SettingsManager::instance().backgroundDistanceThreshold()); QLabel* bgIterLabel = new QLabel("Итерации:", owner); w.bgIterSpin = new QSpinBox(owner); w.bgIterSpin->setRange(100, 10000); w.bgIterSpin->setValue(SettingsManager::instance().backgroundMaxIterations()); w.bgBtn = new QPushButton("Удалить фон", owner); bgLayout->addWidget(bgDistLabel); bgLayout->addWidget(w.bgDistSpin); bgLayout->addWidget(bgIterLabel); bgLayout->addWidget(w.bgIterSpin); bgLayout->addWidget(w.bgBtn); bgLayout->addStretch(); objectPrepLayout->addLayout(bgLayout); QHBoxLayout* segLayout = new QHBoxLayout(); QLabel* segTolLabel = new QLabel("Кластер (м):", owner); w.segTolSpin = new QDoubleSpinBox(owner); w.segTolSpin->setDecimals(3); w.segTolSpin->setRange(0.001, 0.1); w.segTolSpin->setSingleStep(0.001); w.segTolSpin->setValue(SettingsManager::instance().segmentationClusterTolerance()); QLabel* segMinLabel = new QLabel("Мин:", owner); w.segMinSpin = new QSpinBox(owner); w.segMinSpin->setRange(10, 10000); w.segMinSpin->setValue(SettingsManager::instance().segmentationMinClusterSize()); QLabel* segMaxLabel = new QLabel("Макс:", owner); w.segMaxSpin = new QSpinBox(owner); w.segMaxSpin->setRange(100, 500000); w.segMaxSpin->setValue(SettingsManager::instance().segmentationMaxClusterSize()); w.segBtn = new QPushButton("Выделить объект", owner); segLayout->addWidget(segTolLabel); segLayout->addWidget(w.segTolSpin); segLayout->addWidget(segMinLabel); segLayout->addWidget(w.segMinSpin); segLayout->addWidget(segMaxLabel); segLayout->addWidget(w.segMaxSpin); segLayout->addWidget(w.segBtn); segLayout->addStretch(); objectPrepLayout->addLayout(segLayout); toolsLayout->addWidget(objectPrepGroup); QGroupBox* registrationGroup = new QGroupBox("3. Регистрация сканов (ICP)", owner); QVBoxLayout* registrationLayout = new QVBoxLayout(registrationGroup); QHBoxLayout* icpHelpLayout = new QHBoxLayout(); icpHelpLayout->addStretch(); w.icpHelpBtn = new QPushButton("?", owner); w.icpHelpBtn->setFixedWidth(28); w.icpHelpBtn->setToolTip("Открыть справку по объединению сканов через ICP"); icpHelpLayout->addWidget(w.icpHelpBtn); registrationLayout->addLayout(icpHelpLayout); SettingsManager &settingsIcp = SettingsManager::instance(); QHBoxLayout *icpParamsRow = new QHBoxLayout(); QLabel *icpMaxCorrLabel = new QLabel("Max correspondence (м):", owner); w.icpMaxCorrSpin = new QDoubleSpinBox(owner); w.icpMaxCorrSpin->setDecimals(3); w.icpMaxCorrSpin->setRange(0.001, 1.0); w.icpMaxCorrSpin->setSingleStep(0.005); w.icpMaxCorrSpin->setValue(settingsIcp.icpMaxCorrespondenceDistance()); w.icpMaxCorrSpin->setToolTip("Максимальное расстояние, на котором ICP ищет пары точек. Начните с 5 см; уменьшайте до 1–2 см после грубого выравнивания."); QLabel *icpIterLabel = new QLabel("Итерации:", owner); w.icpIterSpin = new QSpinBox(owner); w.icpIterSpin->setRange(5, 500); w.icpIterSpin->setValue(settingsIcp.icpMaxIterations()); QLabel *icpVoxelLabel = new QLabel("Финальный воксель (м):", owner); w.icpVoxelSpin = new QDoubleSpinBox(owner); w.icpVoxelSpin->setDecimals(4); w.icpVoxelSpin->setRange(0.0, 0.05); w.icpVoxelSpin->setSingleStep(0.0005); w.icpVoxelSpin->setValue(settingsIcp.icpVoxelLeafOut()); w.icpVoxelSpin->setToolTip("0 → без децимации. Полезно после мержа — N сканов дают N×point density, а voxel этот дубль снимает."); w.icpSkipCheck = new QCheckBox("Пропускать несошедшиеся", owner); w.icpSkipCheck->setChecked(settingsIcp.icpSkipNonConverged()); w.icpSkipCheck->setToolTip("Если ICP не сошёлся на скане — пропустить его, а не добавлять «как есть». Безопаснее при плохих данных."); icpParamsRow->addWidget(icpMaxCorrLabel); icpParamsRow->addWidget(w.icpMaxCorrSpin); icpParamsRow->addSpacing(8); icpParamsRow->addWidget(icpIterLabel); icpParamsRow->addWidget(w.icpIterSpin); icpParamsRow->addSpacing(8); icpParamsRow->addWidget(icpVoxelLabel); icpParamsRow->addWidget(w.icpVoxelSpin); icpParamsRow->addStretch(); registrationLayout->addLayout(icpParamsRow); registrationLayout->addWidget(w.icpSkipCheck); QHBoxLayout *mergeBtnRow = new QHBoxLayout(); w.mergeScansBtn = new QPushButton("Объединить все сканы проекта", owner); w.mergeScansBtn->setToolTip("Последовательная ICP-регистрация: scans[0] как опорный, каждый следующий выравнивается относительно накопленного результата."); w.addMergedBtn = new QPushButton("Сохранить результат в проект…", owner); w.addMergedBtn->setEnabled(false); mergeBtnRow->addWidget(w.mergeScansBtn); mergeBtnRow->addWidget(w.addMergedBtn); mergeBtnRow->addStretch(); registrationLayout->addLayout(mergeBtnRow); w.icpProgress = new QProgressBar(owner); w.icpProgress->setRange(0, 100); w.icpProgress->setValue(0); registrationLayout->addWidget(w.icpProgress); w.icpStatusLabel = new QLabel("", owner); registrationLayout->addWidget(w.icpStatusLabel); toolsLayout->addWidget(registrationGroup); QGroupBox *meshGroup = new QGroupBox("4. Реконструкция поверхности (Poisson)", owner); QVBoxLayout *meshLayout = new QVBoxLayout(meshGroup); QHBoxLayout *poissonHelpLayout = new QHBoxLayout(); poissonHelpLayout->addStretch(); w.poissonHelpBtn = new QPushButton("?", owner); w.poissonHelpBtn->setFixedWidth(28); w.poissonHelpBtn->setToolTip("Открыть справку по Poisson-реконструкции и нормалям"); poissonHelpLayout->addWidget(w.poissonHelpBtn); meshLayout->addLayout(poissonHelpLayout); SettingsManager &settings = SettingsManager::instance(); QHBoxLayout *poissonParamsRow = new QHBoxLayout(); QLabel *depthLabel = new QLabel("Depth:", owner); w.depthSpin = new QSpinBox(owner); w.depthSpin->setRange(5, 12); w.depthSpin->setValue(settings.poissonDepth()); w.depthSpin->setToolTip("Глубина октодерева. 8≈256^3, 9≈512^3, 10≈1024^3. Больше = детальнее, но квадратично больше памяти и времени."); QLabel *pointWeightLabel = new QLabel("Point weight:", owner); w.pointWeightSpin = new QDoubleSpinBox(owner); w.pointWeightSpin->setRange(0.0, 20.0); w.pointWeightSpin->setSingleStep(0.5); w.pointWeightSpin->setValue(settings.poissonPointWeight()); w.pointWeightSpin->setToolTip("Screened-Poisson weight. 0 = классический Poisson, ≥4 = screened (плотнее следует точкам)."); QLabel *samplesLabel = new QLabel("Samples/node:", owner); w.samplesSpin = new QDoubleSpinBox(owner); w.samplesSpin->setRange(1.0, 20.0); w.samplesSpin->setSingleStep(0.5); w.samplesSpin->setValue(settings.poissonSamplesPerNode()); w.samplesSpin->setToolTip("Сколько точек должно попасть в лист октодерева. Больше = меньше шумных нод."); poissonParamsRow->addWidget(depthLabel); poissonParamsRow->addWidget(w.depthSpin); poissonParamsRow->addSpacing(12); poissonParamsRow->addWidget(pointWeightLabel); poissonParamsRow->addWidget(w.pointWeightSpin); poissonParamsRow->addSpacing(12); poissonParamsRow->addWidget(samplesLabel); poissonParamsRow->addWidget(w.samplesSpin); poissonParamsRow->addStretch(); meshLayout->addLayout(poissonParamsRow); QHBoxLayout *normalRow = new QHBoxLayout(); QLabel *normalRadiusLabel = new QLabel("Normal radius (м):", owner); w.normalRadiusSpin = new QDoubleSpinBox(owner); w.normalRadiusSpin->setDecimals(4); w.normalRadiusSpin->setRange(0.0, 0.1); w.normalRadiusSpin->setSingleStep(0.001); w.normalRadiusSpin->setValue(settings.poissonNormalRadius()); w.normalRadiusSpin->setToolTip("Радиус поиска соседей для оценки нормалей. 0 → использовать k ближайших (см. поле k)."); QLabel *kNearestLabel = new QLabel("k ближайших:", owner); w.kNearestSpin = new QSpinBox(owner); w.kNearestSpin->setRange(5, 100); w.kNearestSpin->setValue(settings.poissonKNearest()); normalRow->addWidget(normalRadiusLabel); normalRow->addWidget(w.normalRadiusSpin); normalRow->addSpacing(12); normalRow->addWidget(kNearestLabel); normalRow->addWidget(w.kNearestSpin); normalRow->addStretch(); meshLayout->addLayout(normalRow); QGroupBox *normalOrientGroup = new QGroupBox("Ориентация нормалей", owner); QVBoxLayout *normalOrientLayout = new QVBoxLayout(normalOrientGroup); w.poissonFlipNormalsChk = new QCheckBox("Инвертировать нормали (flip)", owner); w.poissonFlipNormalsChk->setToolTip("Развернуть все нормали на 180°. Помогает, если меш получился «вывернут наизнанку»."); normalOrientLayout->addWidget(w.poissonFlipNormalsChk); w.poissonConsistentOrientChk = new QCheckBox("Согласованная ориентация (BFS по k-соседям)", owner); w.poissonConsistentOrientChk->setToolTip("Распространяет ориентацию от seed-точки по ближайшим соседям так, чтобы нормали были сонаправлены."); normalOrientLayout->addWidget(w.poissonConsistentOrientChk); QHBoxLayout *vpRow = new QHBoxLayout(); w.poissonCustomVpChk = new QCheckBox("Custom view-point:", owner); vpRow->addWidget(w.poissonCustomVpChk); auto *vpXLabel = new QLabel("X:", owner); w.poissonVpX = new QDoubleSpinBox(owner); w.poissonVpX->setDecimals(3); w.poissonVpX->setRange(-10.0, 10.0); w.poissonVpX->setSingleStep(0.05); w.poissonVpX->setSuffix(" м"); w.poissonVpX->setEnabled(false); auto *vpYLabel = new QLabel("Y:", owner); w.poissonVpY = new QDoubleSpinBox(owner); w.poissonVpY->setDecimals(3); w.poissonVpY->setRange(-10.0, 10.0); w.poissonVpY->setSingleStep(0.05); w.poissonVpY->setSuffix(" м"); w.poissonVpY->setEnabled(false); auto *vpZLabel = new QLabel("Z:", owner); w.poissonVpZ = new QDoubleSpinBox(owner); w.poissonVpZ->setDecimals(3); w.poissonVpZ->setRange(-10.0, 10.0); w.poissonVpZ->setSingleStep(0.05); w.poissonVpZ->setSuffix(" м"); w.poissonVpZ->setValue(-1.0); w.poissonVpZ->setEnabled(false); vpRow->addWidget(vpXLabel); vpRow->addWidget(w.poissonVpX); vpRow->addWidget(vpYLabel); vpRow->addWidget(w.poissonVpY); vpRow->addWidget(vpZLabel); vpRow->addWidget(w.poissonVpZ); vpRow->addStretch(); normalOrientLayout->addLayout(vpRow); meshLayout->addWidget(normalOrientGroup); w.reconstructBtn = new QPushButton("Построить меш", owner); w.showCloudBtn = new QPushButton("Показать облако", owner); w.exportMeshBtn = new QPushButton("Экспорт меша…", owner); w.reconstructLightweightBtn = new QPushButton("Построить лёгкий меш", owner); QHBoxLayout *meshBtnRow = new QHBoxLayout(); meshBtnRow->addWidget(w.reconstructBtn); meshBtnRow->addWidget(w.reconstructLightweightBtn); meshBtnRow->addWidget(w.showCloudBtn); meshBtnRow->addWidget(w.exportMeshBtn); meshBtnRow->addStretch(); meshLayout->addLayout(meshBtnRow); w.poissonProgress = new QProgressBar(owner); w.poissonProgress->setRange(0, 100); w.poissonProgress->setValue(0); meshLayout->addWidget(w.poissonProgress); w.meshStatusLabel = new QLabel("Меш не построен", owner); meshLayout->addWidget(w.meshStatusLabel); toolsLayout->addWidget(meshGroup); QGroupBox* meshPostGroup = new QGroupBox("5. Постобработка меша", owner); QVBoxLayout* meshPostLayout = new QVBoxLayout(meshPostGroup); QHBoxLayout* meshPostHelpLayout = new QHBoxLayout(); meshPostHelpLayout->addStretch(); w.meshPostHelpBtn = new QPushButton("?", owner); w.meshPostHelpBtn->setFixedWidth(28); w.meshPostHelpBtn->setToolTip("Открыть справку по сглаживанию, упрощению и закрытию дыр"); meshPostHelpLayout->addWidget(w.meshPostHelpBtn); meshPostLayout->addLayout(meshPostHelpLayout); QHBoxLayout* smoothLayout = new QHBoxLayout(); QLabel* smoothIterLabel = new QLabel("Итерации:", owner); w.smoothIterSpin = new QSpinBox(owner); w.smoothIterSpin->setRange(1, 50); w.smoothIterSpin->setValue(SettingsManager::instance().smoothingNumIterations()); QLabel* smoothConvLabel = new QLabel("Сходимость:", owner); w.smoothConvSpin = new QDoubleSpinBox(owner); w.smoothConvSpin->setDecimals(4); w.smoothConvSpin->setRange(0.0001, 0.1); w.smoothConvSpin->setSingleStep(0.001); w.smoothConvSpin->setValue(SettingsManager::instance().smoothingConvergence()); w.smoothBtn = new QPushButton("Сгладить меш", owner); smoothLayout->addWidget(smoothIterLabel); smoothLayout->addWidget(w.smoothIterSpin); smoothLayout->addWidget(smoothConvLabel); smoothLayout->addWidget(w.smoothConvSpin); smoothLayout->addWidget(w.smoothBtn); smoothLayout->addStretch(); meshPostLayout->addLayout(smoothLayout); QHBoxLayout* simplLayout = new QHBoxLayout(); QLabel* simplLabel = new QLabel("Упрощение (%):", owner); w.simplSlider = new QSlider(Qt::Horizontal, owner); w.simplSlider->setRange(10, 90); w.simplSlider->setValue(50); w.simplSlider->setToolTip("Процент упрощения меша (10-90%). 50% = уменьшение полигонов в 2 раза."); w.simplValueLabel = new QLabel("50%", owner); w.simplBtn = new QPushButton("Упростить меш", owner); simplLayout->addWidget(simplLabel); simplLayout->addWidget(w.simplSlider); simplLayout->addWidget(w.simplValueLabel); simplLayout->addWidget(w.simplBtn); simplLayout->addStretch(); meshPostLayout->addLayout(simplLayout); QHBoxLayout* holeLayout = new QHBoxLayout(); QLabel* holeSizeLabel = new QLabel("Макс. дыра (м):", owner); w.holeSizeSpin = new QDoubleSpinBox(owner); w.holeSizeSpin->setDecimals(3); w.holeSizeSpin->setRange(0.001, 0.5); w.holeSizeSpin->setSingleStep(0.005); w.holeSizeSpin->setValue(SettingsManager::instance().meshHoleFillingMaxHoleSize()); w.holeBtn = new QPushButton("Закрыть дыры", owner); holeLayout->addWidget(holeSizeLabel); holeLayout->addWidget(w.holeSizeSpin); holeLayout->addWidget(w.holeBtn); holeLayout->addStretch(); meshPostLayout->addLayout(holeLayout); toolsLayout->addWidget(meshPostGroup); toolsLayout->addStretch(); return w; }