34 #include <QApplication>
38 #include <QFileDialog>
39 #include <QInputDialog>
40 #include <QMessageBox>
41 #include <QProgressDialog>
42 #include <QRegularExpression>
43 #include <QResizeEvent>
58 #include <vtkAbstractArray.h>
61 #include <vtkCellData.h>
62 #include <vtkDataArray.h>
63 #include <vtkDataSetMapper.h>
64 #include <vtkFieldData.h>
65 #include <vtkIdTypeArray.h>
66 #include <vtkMapper.h>
67 #include <vtkPointData.h>
68 #include <vtkPolyData.h>
69 #include <vtkPolyDataMapper.h>
71 #include <vtkPropCollection.h>
72 #include <vtkRenderer.h>
73 #include <vtkSmartPointer.h>
74 #include <vtkStringArray.h>
75 #include <vtkVariant.h>
78 #include <QAbstractButton>
79 #include <QApplication>
83 #include <QColorDialog>
85 #include <QDoubleSpinBox>
87 #include <QFileDialog>
88 #include <QFormLayout>
89 #include <QGridLayout>
91 #include <QHBoxLayout>
92 #include <QHeaderView>
97 #include <QMessageBox>
100 #include <QPushButton>
101 #include <QResizeEvent>
102 #include <QScrollArea>
104 #include <QTabWidget>
105 #include <QTableWidget>
106 #include <QToolButton>
107 #include <QVBoxLayout>
110 const QColor cvSelectionPropertiesWidget::s_selectionColors[] = {
122 const int cvSelectionPropertiesWidget::s_selectionColorsCount = 10;
128 m_highlighter(nullptr),
130 m_selectionManager(nullptr),
133 m_selectionNameCounter(0),
135 m_lastHighlightedId(-1),
137 m_selectedDataExpander(nullptr),
138 m_selectedDataContainer(nullptr),
139 m_selectionDisplayExpander(nullptr),
140 m_selectionDisplayContainer(nullptr),
141 m_selectionEditorExpander(nullptr),
142 m_selectionEditorContainer(nullptr),
143 m_createSelectionExpander(nullptr),
144 m_createSelectionContainer(nullptr),
145 m_selectedDataSpreadsheetExpander(nullptr),
146 m_selectedDataSpreadsheetContainer(nullptr),
147 m_compactStatsExpander(nullptr),
148 m_compactStatsContainer(nullptr),
150 m_freezeButton(nullptr),
151 m_extractButton(nullptr),
152 m_plotOverTimeButton(nullptr),
154 m_createSelectionGroup(nullptr),
155 m_dataProducerCombo(nullptr),
156 m_elementTypeCombo(nullptr),
157 m_attributeCombo(nullptr),
158 m_operatorCombo(nullptr),
159 m_valueEdit(nullptr),
160 m_processIdSpinBox(nullptr),
161 m_findDataButton(nullptr),
162 m_resetButton(nullptr),
163 m_clearButton(nullptr),
164 m_queriesLayout(nullptr),
165 m_tabWidget(nullptr),
167 m_selectionTableWidget(nullptr),
168 m_listInfoLabel(nullptr),
169 m_algebraOpCombo(nullptr),
170 m_applyAlgebraButton(nullptr),
171 m_extractBoundaryButton(nullptr),
173 m_addAnnotationButton(nullptr),
175 m_hoverColorButton(nullptr),
176 m_preselectedColorButton(nullptr),
177 m_selectedColorButton(nullptr),
178 m_boundaryColorButton(nullptr),
179 m_hoverOpacitySpin(nullptr),
180 m_preselectedOpacitySpin(nullptr),
181 m_selectedOpacitySpin(nullptr),
182 m_boundaryOpacitySpin(nullptr) {
184 m_savedPreselectedColor[0] = 1.0;
185 m_savedPreselectedColor[1] = 1.0;
186 m_savedPreselectedColor[2] = 0.0;
192 for (
int i = 0; i < 6; ++i) {
195 for (
int i = 0; i < 3; ++i) {
204 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
207 "[cvSelectionPropertiesWidget] Initialized with ParaView-style UI");
212 delete m_tooltipFormatter;
217 void cvSelectionPropertiesWidget::updateScrollContentWidth() {
220 if (m_scrollContent && m_scrollArea && m_scrollArea->viewport()) {
221 int viewportWidth = m_scrollArea->viewport()->width();
222 if (viewportWidth > 0) {
225 m_scrollContent->setFixedWidth(viewportWidth);
227 m_scrollContent->updateGeometry();
230 m_scrollContent->adjustSize();
232 if (m_scrollContent->width() != viewportWidth) {
233 m_scrollContent->setFixedWidth(viewportWidth);
243 updateScrollContentWidth();
246 else if (obj == m_scrollArea->viewport() &&
248 updateScrollContentWidth();
253 if (obj == m_selectionColorButton && m_highlighter) {
256 updateColorButtonIcon(m_selectionColorButton,
color);
257 }
else if (obj == m_interactiveSelectionColorButton && m_highlighter) {
260 updateColorButtonIcon(m_interactiveSelectionColorButton,
color);
264 return QWidget::eventFilter(obj,
event);
269 QWidget::resizeEvent(
event);
273 updateScrollContentWidth();
282 if (m_highlighter && m_highlighter != highlighter) {
283 disconnect(m_highlighter,
nullptr,
this,
nullptr);
290 m_highlighter = highlighter;
296 &cvSelectionPropertiesWidget::onHighlighterColorChanged,
297 Qt::UniqueConnection);
299 &cvSelectionPropertiesWidget::onHighlighterOpacityChanged,
300 Qt::UniqueConnection);
304 onHighlighterLabelPropertiesChanged,
305 Qt::UniqueConnection);
311 [
this](
double r,
double g,
double b,
int mode) {
312 if (!m_highlighter)
return;
319 m_highlighter->blockSignals(
true);
321 m_highlighter->blockSignals(
false);
329 Qt::UniqueConnection);
334 [
this](
double opacity,
int mode) {
335 if (!m_highlighter)
return;
342 m_highlighter->blockSignals(
true);
344 m_highlighter->blockSignals(
false);
352 Qt::UniqueConnection);
360 void cvSelectionPropertiesWidget::syncInternalColorArray(
double r,
368 QColor
color = QColor::fromRgbF(r, g, b);
372 if (m_interactiveSelectionColorButton) {
373 updateColorButtonIcon(m_interactiveSelectionColorButton,
color);
375 if (m_hoverColorButton) {
376 updateColorButtonIcon(m_hoverColorButton,
color);
380 if (m_preselectedColorButton) {
381 updateColorButtonIcon(m_preselectedColorButton,
color);
385 if (m_selectionColorButton) {
386 updateColorButtonIcon(m_selectionColorButton,
color);
388 if (m_selectedColorButton) {
389 updateColorButtonIcon(m_selectedColorButton,
color);
393 if (m_boundaryColorButton) {
394 updateColorButtonIcon(m_boundaryColorButton,
color);
403 void cvSelectionPropertiesWidget::updateColorButtonIcon(QAbstractButton* button,
404 const QColor&
color) {
409 int buttonHeight = button->height();
410 if (buttonHeight <= 0) {
411 button->adjustSize();
412 buttonHeight = button->height();
414 if (buttonHeight <= 0) {
415 buttonHeight = button->sizeHint().height();
417 if (buttonHeight <= 0) {
423 int radius = qRound(buttonHeight * 0.75);
424 radius = std::max(radius, 10);
428 QPixmap pix(radius, radius);
429 pix.fill(QColor(0, 0, 0, 0));
431 QPainter painter(&pix);
432 painter.setRenderHint(QPainter::Antialiasing,
true);
433 painter.setBrush(QBrush(
color));
434 painter.drawEllipse(1, 1, radius - 2, radius - 2);
440 QPixmap pix2x(radius * 2, radius * 2);
441 pix2x.setDevicePixelRatio(2.0);
442 pix2x.fill(QColor(0, 0, 0, 0));
444 QPainter painter2x(&pix2x);
445 painter2x.setRenderHint(QPainter::Antialiasing,
true);
446 painter2x.setBrush(QBrush(
color));
448 painter2x.drawEllipse(2, 2, radius - 4, radius - 4);
451 icon.addPixmap(pix2x);
453 button->setIcon(icon);
456 if (QToolButton* toolButton = qobject_cast<QToolButton*>(button)) {
457 toolButton->setIconSize(QSize(radius, radius));
462 void cvSelectionPropertiesWidget::onHighlighterColorChanged(
int mode) {
465 if (!m_highlighter)
return;
473 if (m_hoverColorButton)
474 updateColorButtonIcon(m_hoverColorButton,
color);
475 if (m_interactiveSelectionColorButton)
476 updateColorButtonIcon(m_interactiveSelectionColorButton,
color);
479 if (m_preselectedColorButton)
480 updateColorButtonIcon(m_preselectedColorButton,
color);
483 if (m_selectedColorButton)
484 updateColorButtonIcon(m_selectedColorButton,
color);
485 if (m_selectionColorButton)
486 updateColorButtonIcon(m_selectionColorButton,
color);
489 if (m_boundaryColorButton)
490 updateColorButtonIcon(m_boundaryColorButton,
color);
495 "external color change (mode=%1)")
500 void cvSelectionPropertiesWidget::onHighlighterOpacityChanged(
int mode) {
503 if (!m_highlighter)
return;
509 QDoubleSpinBox* spinbox =
nullptr;
512 spinbox = m_hoverOpacitySpin;
515 spinbox = m_preselectedOpacitySpin;
518 spinbox = m_selectedOpacitySpin;
521 spinbox = m_boundaryOpacitySpin;
526 spinbox->blockSignals(
true);
527 spinbox->setValue(opacity);
528 spinbox->blockSignals(
false);
533 void cvSelectionPropertiesWidget::onHighlighterLabelPropertiesChanged(
538 QString(
"[cvSelectionPropertiesWidget] Label properties "
539 "changed externally (interactive=%1)")
546 QColor cvSelectionPropertiesWidget::getSelectionColor()
const {
551 return QColor(255, 0, 255);
555 QColor cvSelectionPropertiesWidget::getInteractiveSelectionColor()
const {
559 return QColor(0, 255, 0);
564 if (!m_highlighter) {
573 QColor selectedColor =
575 QColor boundaryColor =
578 double hoverOpacity =
589 if (m_hoverColorButton) {
590 updateColorButtonIcon(m_hoverColorButton, hoverColor);
592 if (m_preselectedColorButton) {
593 updateColorButtonIcon(m_preselectedColorButton, preselectedColor);
595 if (m_selectedColorButton) {
596 updateColorButtonIcon(m_selectedColorButton, selectedColor);
598 if (m_boundaryColorButton) {
599 updateColorButtonIcon(m_boundaryColorButton, boundaryColor);
603 if (m_hoverOpacitySpin) {
604 m_hoverOpacitySpin->blockSignals(
true);
605 m_hoverOpacitySpin->setValue(hoverOpacity);
606 m_hoverOpacitySpin->blockSignals(
false);
608 if (m_preselectedOpacitySpin) {
609 m_preselectedOpacitySpin->blockSignals(
true);
610 m_preselectedOpacitySpin->setValue(preselectedOpacity);
611 m_preselectedOpacitySpin->blockSignals(
false);
613 if (m_selectedOpacitySpin) {
614 m_selectedOpacitySpin->blockSignals(
true);
615 m_selectedOpacitySpin->setValue(selectedOpacity);
616 m_selectedOpacitySpin->blockSignals(
false);
618 if (m_boundaryOpacitySpin) {
619 m_boundaryOpacitySpin->blockSignals(
true);
620 m_boundaryOpacitySpin->setValue(boundaryOpacity);
621 m_boundaryOpacitySpin->blockSignals(
false);
628 if (m_selectionColorButton) {
629 updateColorButtonIcon(m_selectionColorButton, selectedColor);
631 if (m_interactiveSelectionColorButton) {
632 updateColorButtonIcon(m_interactiveSelectionColorButton, hoverColor);
636 "[cvSelectionPropertiesWidget] UI synchronized with highlighter "
641 void cvSelectionPropertiesWidget::setupUi() {
642 QVBoxLayout* mainLayout =
new QVBoxLayout(
this);
643 mainLayout->setContentsMargins(0, 0, 0, 0);
644 mainLayout->setSpacing(0);
647 m_scrollArea =
new QScrollArea(
this);
650 m_scrollArea->setWidgetResizable(
false);
651 m_scrollArea->setFrameShape(QFrame::NoFrame);
652 m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
653 m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
655 m_scrollContent =
new QWidget();
658 m_scrollContent->setSizePolicy(QSizePolicy::Preferred,
660 QVBoxLayout* scrollLayout =
new QVBoxLayout(m_scrollContent);
661 scrollLayout->setContentsMargins(5, 5, 5, 5);
662 scrollLayout->setSpacing(
665 scrollLayout->setSizeConstraint(QLayout::SetMinimumSize);
671 m_createSelectionExpander->
setText(tr(
"Create Selection"));
673 scrollLayout->addWidget(m_createSelectionExpander);
675 m_createSelectionContainer =
new QWidget(m_scrollContent);
676 m_createSelectionContainer->setMinimumHeight(50);
677 setupCreateSelectionSection();
678 scrollLayout->addWidget(m_createSelectionContainer);
681 m_createSelectionContainer, &QWidget::setVisible);
684 [
this](
bool checked) {
685 if (m_findDataButton) m_findDataButton->setVisible(checked);
686 if (m_resetButton) m_resetButton->setVisible(checked);
687 if (m_clearButton) m_clearButton->setVisible(checked);
690 QTimer::singleShot(0,
this, [
this]() {
692 updateScrollContentWidth();
698 m_selectedDataSpreadsheetExpander->setText(tr(
"Selected Data (none)"));
699 m_selectedDataSpreadsheetExpander->setChecked(
true);
700 scrollLayout->addWidget(m_selectedDataSpreadsheetExpander);
702 m_selectedDataSpreadsheetContainer =
new QWidget(m_scrollContent);
703 m_selectedDataSpreadsheetContainer->setMinimumHeight(200);
704 setupSelectedDataSpreadsheet();
705 scrollLayout->addWidget(m_selectedDataSpreadsheetContainer);
708 m_selectedDataSpreadsheetContainer, &QWidget::setVisible);
712 QTimer::singleShot(0,
this, [
this]() {
714 updateScrollContentWidth();
719 setupSelectedDataHeader();
720 QHBoxLayout* buttonLayout =
new QHBoxLayout();
721 buttonLayout->setSpacing(2);
722 buttonLayout->addWidget(m_freezeButton);
723 buttonLayout->addWidget(m_extractButton);
724 if (m_plotOverTimeButton) {
725 buttonLayout->addWidget(m_plotOverTimeButton);
727 QWidget* buttonContainer =
new QWidget(m_scrollContent);
728 buttonContainer->setLayout(buttonLayout);
729 scrollLayout->addWidget(buttonContainer);
733 m_selectionDisplayExpander->
setText(tr(
"Selection Display"));
735 scrollLayout->addWidget(m_selectionDisplayExpander);
737 m_selectionDisplayContainer =
new QWidget(m_scrollContent);
738 m_selectionDisplayContainer->setMinimumHeight(50);
739 setupSelectionDisplaySection();
740 scrollLayout->addWidget(m_selectionDisplayContainer);
743 m_selectionDisplayContainer, &QWidget::setVisible);
747 QTimer::singleShot(0,
this, [
this]() {
749 updateScrollContentWidth();
755 m_selectionEditorExpander->setText(tr(
"Selection Editor"));
756 m_selectionEditorExpander->setChecked(
false);
757 scrollLayout->addWidget(m_selectionEditorExpander);
759 m_selectionEditorContainer =
new QWidget(m_scrollContent);
760 m_selectionEditorContainer->setMinimumHeight(50);
761 m_selectionEditorContainer->setVisible(
false);
762 setupSelectionEditorSection();
763 scrollLayout->addWidget(m_selectionEditorContainer);
766 m_selectionEditorContainer, &QWidget::setVisible);
770 QTimer::singleShot(0,
this, [
this]() {
772 updateScrollContentWidth();
778 m_compactStatsExpander->setText(tr(
"Selection Statistics"));
779 m_compactStatsExpander->setChecked(
false);
780 scrollLayout->addWidget(m_compactStatsExpander);
782 m_compactStatsContainer =
new QWidget(m_scrollContent);
783 m_compactStatsContainer->setMinimumHeight(50);
784 m_compactStatsContainer->setVisible(
false);
785 setupCompactStatisticsSection();
786 scrollLayout->addWidget(m_compactStatsContainer);
789 m_compactStatsContainer, &QWidget::setVisible);
792 QTimer::singleShot(0,
this, [
this]() {
794 updateScrollContentWidth();
800 m_tabWidget =
nullptr;
802 scrollLayout->addStretch();
804 m_scrollContent->setLayout(scrollLayout);
806 m_scrollArea->setWidget(m_scrollContent);
808 m_scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
809 mainLayout->addWidget(m_scrollArea);
815 m_scrollArea->installEventFilter(
this);
816 if (m_scrollArea->viewport()) {
817 m_scrollArea->viewport()->installEventFilter(
this);
822 QTimer::singleShot(0,
this, [
this]() { updateScrollContentWidth(); });
824 setLayout(mainLayout);
828 void cvSelectionPropertiesWidget::setupSelectedDataHeader() {
830 m_selectedDataLabel =
new QLabel(tr(
"<b>Selected Data</b>"));
831 m_selectedDataLabel->setStyleSheet(
832 "QLabel { background-color: #e0e0e0; padding: 5px; border-radius: "
838 m_freezeButton =
new QPushButton(QIcon(
":/Resources/images/svg/pqLock.svg"),
840 m_freezeButton->setToolTip(tr(
841 "Freeze the current selection (convert to independent dataset)"));
842 m_freezeButton->setFixedHeight(25);
843 m_freezeButton->setEnabled(
false);
844 connect(m_freezeButton, &QPushButton::clicked,
this,
845 &cvSelectionPropertiesWidget::onFreezeClicked);
849 QIcon extractIcon(
":/Resources/images/svg/pqExtractSelection.svg");
850 if (extractIcon.isNull()) {
851 extractIcon = QIcon(
":/Resources/images/exportCloud.png");
853 m_extractButton =
new QPushButton(extractIcon, tr(
"Extract"));
854 m_extractButton->setToolTip(
855 tr(
"Extract selected elements to a new dataset and add to scene"));
856 m_extractButton->setFixedHeight(25);
857 m_extractButton->setEnabled(
false);
858 connect(m_extractButton, &QPushButton::clicked,
this,
859 &cvSelectionPropertiesWidget::onExtractClicked);
866 void cvSelectionPropertiesWidget::setupCompactStatisticsSection() {
871 QFormLayout* statsLayout =
new QFormLayout(m_compactStatsContainer);
872 statsLayout->setSpacing(4);
873 statsLayout->setContentsMargins(8, 8, 8, 8);
875 m_countLabel =
new QLabel(tr(
"0"));
876 m_countLabel->setStyleSheet(
"font-weight: bold;");
877 statsLayout->addRow(tr(
"Count:"), m_countLabel);
879 m_typeLabel =
new QLabel(tr(
"None"));
880 statsLayout->addRow(tr(
"Type:"), m_typeLabel);
882 m_boundsLabel =
new QLabel(tr(
"N/A"));
883 m_boundsLabel->setWordWrap(
true);
884 m_boundsLabel->setStyleSheet(
"font-size: 9pt;");
885 statsLayout->addRow(tr(
"Bounds:"), m_boundsLabel);
887 m_centerLabel =
new QLabel(tr(
"N/A"));
888 m_centerLabel->setStyleSheet(
"font-size: 9pt;");
889 statsLayout->addRow(tr(
"Center:"), m_centerLabel);
891 m_volumeLabel =
new QLabel(tr(
"N/A"));
892 m_volumeLabel->setStyleSheet(
"font-size: 9pt;");
893 statsLayout->addRow(tr(
"Volume:"), m_volumeLabel);
899 void cvSelectionPropertiesWidget::setupCreateSelectionSection() {
903 m_createSelectionGroup =
nullptr;
905 QVBoxLayout* mainLayout =
new QVBoxLayout(m_createSelectionContainer);
906 mainLayout->setSpacing(5);
907 mainLayout->setContentsMargins(8, 8, 8, 8);
910 QLabel* criteriaLabel =
new QLabel(tr(
"<b>Selection Criteria</b>"));
911 mainLayout->addWidget(criteriaLabel);
913 QFormLayout* criteriaLayout =
new QFormLayout();
914 criteriaLayout->setSpacing(3);
917 m_dataProducerCombo =
new QComboBox();
918 m_dataProducerCombo->setToolTip(tr(
"Select the data source"));
919 connect(m_dataProducerCombo,
920 QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
921 &cvSelectionPropertiesWidget::onDataProducerChanged);
922 criteriaLayout->addRow(tr(
"Data Producer"), m_dataProducerCombo);
925 m_elementTypeCombo =
new QComboBox();
926 QIcon pointIcon(
":/Resources/images/svg/pqPointData.svg");
927 m_elementTypeCombo->addItem(pointIcon, tr(
"Point"), 0);
929 QIcon cellIcon(
":/Resources/images/svg/pqCellData.svg");
930 m_elementTypeCombo->addItem(cellIcon, tr(
"Cell"), 1);
931 m_elementTypeCombo->setToolTip(tr(
"Select element type (Point or Cell)"));
932 connect(m_elementTypeCombo,
933 QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
934 &cvSelectionPropertiesWidget::onElementTypeChanged);
935 criteriaLayout->addRow(tr(
"Element Type"), m_elementTypeCombo);
937 mainLayout->addLayout(criteriaLayout);
940 m_queriesLayout =
new QVBoxLayout();
941 m_queriesLayout->setSpacing(3);
942 mainLayout->addLayout(m_queriesLayout);
948 if (!m_queryRows.isEmpty()) {
949 m_attributeCombo = m_queryRows[0].attributeCombo;
950 m_operatorCombo = m_queryRows[0].operatorCombo;
951 m_valueEdit = m_queryRows[0].valueEdit;
953 m_attributeCombo =
nullptr;
954 m_operatorCombo =
nullptr;
955 m_valueEdit =
nullptr;
959 QLabel* qualifiersLabel =
new QLabel(tr(
"<b>Selection Qualifiers</b>"));
960 mainLayout->addWidget(qualifiersLabel);
962 QFormLayout* qualifiersLayout =
new QFormLayout();
963 qualifiersLayout->setSpacing(3);
966 m_processIdSpinBox =
new QSpinBox();
967 m_processIdSpinBox->setRange(-1, 9999);
968 m_processIdSpinBox->setValue(-1);
969 m_processIdSpinBox->setToolTip(tr(
"Process ID (-1 for all)"));
970 qualifiersLayout->addRow(tr(
"Process ID"), m_processIdSpinBox);
972 mainLayout->addLayout(qualifiersLayout);
975 QHBoxLayout* buttonLayout =
new QHBoxLayout();
976 buttonLayout->setSpacing(5);
979 QIcon findIcon(
":/Resources/images/svg/pqApply.svg");
980 if (findIcon.isNull()) {
981 findIcon = QIcon(
":/Resources/images/svg/pqApply.png");
983 m_findDataButton =
new QPushButton(findIcon, tr(
"Find Data"));
984 m_findDataButton->setToolTip(tr(
"Find data using selection criteria"));
985 connect(m_findDataButton, &QPushButton::clicked,
this,
986 &cvSelectionPropertiesWidget::onFindDataClicked);
987 buttonLayout->addWidget(m_findDataButton);
989 QIcon resetIcon(
":/Resources/images/svg/pqCancel.svg");
990 if (resetIcon.isNull()) {
991 resetIcon = QIcon(
":/Resources/images/svg/pqCancel.png");
993 m_resetButton =
new QPushButton(resetIcon, tr(
"Reset"));
994 m_resetButton->setToolTip(tr(
"Reset any unaccepted changes"));
995 connect(m_resetButton, &QPushButton::clicked,
this,
996 &cvSelectionPropertiesWidget::onResetClicked);
997 buttonLayout->addWidget(m_resetButton);
999 QIcon clearIcon(
":/Resources/images/svg/pqReset.svg");
1000 if (clearIcon.isNull()) {
1001 clearIcon = QIcon(
":/Resources/images/svg/pqReset.png");
1003 m_clearButton =
new QPushButton(clearIcon, tr(
"Clear"));
1004 m_clearButton->setToolTip(tr(
"Clear selection criteria and qualifiers"));
1005 connect(m_clearButton, &QPushButton::clicked,
this,
1006 &cvSelectionPropertiesWidget::onClearClicked);
1007 buttonLayout->addWidget(m_clearButton);
1009 mainLayout->addLayout(buttonLayout);
1016 void cvSelectionPropertiesWidget::setupSelectionDisplaySection() {
1019 m_selectionDisplayGroup =
nullptr;
1021 QVBoxLayout* displayLayout =
new QVBoxLayout(m_selectionDisplayContainer);
1022 displayLayout->setSpacing(0);
1023 displayLayout->setContentsMargins(8, 8, 8, 8);
1026 QVBoxLayout* labelsHeaderLayout =
new QVBoxLayout();
1027 labelsHeaderLayout->setSpacing(0);
1028 QLabel* labelsHeader =
new QLabel(
1029 tr(
"<html><body><p><span style=\"font-weight:600;\">Selection "
1030 "Labels</span></p></body></html>"));
1031 labelsHeaderLayout->addWidget(labelsHeader);
1032 QFrame* labelsSeparator =
new QFrame();
1033 labelsSeparator->setFrameShape(QFrame::HLine);
1034 labelsSeparator->setFrameShadow(QFrame::Sunken);
1035 labelsHeaderLayout->addWidget(labelsSeparator);
1036 displayLayout->addLayout(labelsHeaderLayout);
1040 QHBoxLayout* labelsLayout =
new QHBoxLayout();
1041 labelsLayout->setSpacing(2);
1044 m_cellLabelsButton =
new QPushButton(tr(
"Cell Labels"));
1045 m_cellLabelsButton->setIcon(QIcon(
":/Resources/images/svg/pqCellData.svg"));
1046 m_cellLabelsButton->setToolTip(
1047 tr(
"Set the array to label selected cells with"));
1048 m_cellLabelsMenu =
new QMenu(
this);
1049 m_cellLabelsButton->setMenu(m_cellLabelsMenu);
1051 connect(m_cellLabelsMenu, &QMenu::aboutToShow,
this,
1052 &cvSelectionPropertiesWidget::onCellLabelsClicked);
1053 labelsLayout->addWidget(m_cellLabelsButton);
1056 m_pointLabelsButton =
new QPushButton(tr(
"Point Labels"));
1057 m_pointLabelsButton->setIcon(
1058 QIcon(
":/Resources/images/svg/pqPointData.svg"));
1059 m_pointLabelsButton->setToolTip(
1060 tr(
"Set the array to label selected points with"));
1061 m_pointLabelsMenu =
new QMenu(
this);
1062 m_pointLabelsButton->setMenu(m_pointLabelsMenu);
1064 connect(m_pointLabelsMenu, &QMenu::aboutToShow,
this,
1065 &cvSelectionPropertiesWidget::onPointLabelsClicked);
1066 labelsLayout->addWidget(m_pointLabelsButton);
1068 displayLayout->addLayout(labelsLayout);
1071 m_editLabelPropertiesButton =
new QPushButton(tr(
"Edit Label Properties"));
1072 m_editLabelPropertiesButton->setIcon(
1073 QIcon(
":/Resources/images/svg/pqAdvanced.png"));
1074 m_editLabelPropertiesButton->setToolTip(
1075 tr(
"Edit selection label properties"));
1076 connect(m_editLabelPropertiesButton, &QPushButton::clicked,
this,
1077 &cvSelectionPropertiesWidget::onEditLabelPropertiesClicked);
1078 displayLayout->addWidget(m_editLabelPropertiesButton);
1081 QVBoxLayout* appearanceHeaderLayout =
new QVBoxLayout();
1082 appearanceHeaderLayout->setSpacing(0);
1083 QLabel* appearanceHeader =
new QLabel(
1084 tr(
"<html><body><p><span style=\"font-weight:600;\">Selection "
1085 "Appearance</span></p></body></html>"));
1086 appearanceHeaderLayout->addWidget(appearanceHeader);
1087 QFrame* appearanceSeparator =
new QFrame();
1088 appearanceSeparator->setFrameShape(QFrame::HLine);
1089 appearanceSeparator->setFrameShadow(QFrame::Sunken);
1090 appearanceHeaderLayout->addWidget(appearanceSeparator);
1091 displayLayout->addLayout(appearanceHeaderLayout);
1096 m_selectionColorButton =
new QToolButton();
1097 m_selectionColorButton->setText(tr(
"Selection Color"));
1098 m_selectionColorButton->setToolTip(
1099 tr(
"Set the color to use to show selected elements"));
1101 QSizePolicy::Fixed);
1103 m_selectionColorButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
1106 m_selectionColorButton->installEventFilter(
this);
1110 updateColorButtonIcon(m_selectionColorButton,
1111 QColor(255, 0, 255));
1112 connect(m_selectionColorButton, &QToolButton::clicked,
this,
1113 &cvSelectionPropertiesWidget::onSelectionColorClicked);
1114 displayLayout->addWidget(m_selectionColorButton);
1117 QVBoxLayout* interactiveHeaderLayout =
new QVBoxLayout();
1118 interactiveHeaderLayout->setSpacing(0);
1119 QLabel* interactiveHeader =
new QLabel(
1120 tr(
"<html><body><p><span style=\"font-weight:600;\">Interactive "
1121 "Selection</span></p></body></html>"));
1122 interactiveHeaderLayout->addWidget(interactiveHeader);
1123 QFrame* interactiveSeparator =
new QFrame();
1124 interactiveSeparator->setFrameShape(QFrame::HLine);
1125 interactiveSeparator->setFrameShadow(QFrame::Sunken);
1126 interactiveHeaderLayout->addWidget(interactiveSeparator);
1127 displayLayout->addLayout(interactiveHeaderLayout);
1132 m_interactiveSelectionColorButton =
new QToolButton();
1133 m_interactiveSelectionColorButton->setText(
1134 tr(
"Interactive Selection Color"));
1135 m_interactiveSelectionColorButton->setToolTip(
1136 tr(
"Set the color to use to show selected elements during "
1139 QSizePolicy::Fixed);
1141 m_interactiveSelectionColorButton->setToolButtonStyle(
1142 Qt::ToolButtonTextBesideIcon);
1145 m_interactiveSelectionColorButton->installEventFilter(
this);
1149 updateColorButtonIcon(m_interactiveSelectionColorButton,
1150 QColor(0, 255, 255));
1151 connect(m_interactiveSelectionColorButton, &QToolButton::clicked,
this,
1152 &cvSelectionPropertiesWidget::onInteractiveSelectionColorClicked);
1153 displayLayout->addWidget(m_interactiveSelectionColorButton);
1156 m_editInteractiveLabelPropertiesButton =
1157 new QPushButton(tr(
"Edit Interactive Label Properties"));
1158 m_editInteractiveLabelPropertiesButton->setIcon(
1159 QIcon(
":/Resources/images/svg/pqAdvanced.png"));
1160 m_editInteractiveLabelPropertiesButton->setToolTip(
1161 tr(
"Edit interactive selection label properties"));
1162 connect(m_editInteractiveLabelPropertiesButton, &QPushButton::clicked,
this,
1164 onEditInteractiveLabelPropertiesClicked);
1165 displayLayout->addWidget(m_editInteractiveLabelPropertiesButton);
1168 displayLayout->addStretch();
1175 void cvSelectionPropertiesWidget::setupSelectionEditorSection() {
1179 QVBoxLayout* editorLayout =
new QVBoxLayout(m_selectionEditorContainer);
1180 editorLayout->setSpacing(5);
1181 editorLayout->setContentsMargins(8, 8, 8, 8);
1184 QHBoxLayout* producerLayout =
new QHBoxLayout();
1185 producerLayout->setSpacing(5);
1186 m_dataProducerLabel =
new QLabel(tr(
"Data Producer"));
1188 QSizePolicy::Preferred);
1189 m_dataProducerLabel->setToolTip(
1190 tr(
"The dataset for which selections are saved"));
1191 m_dataProducerValue =
new QLabel();
1192 m_dataProducerValue->setStyleSheet(
1193 "QLabel { background-color: snow; border: 1px inset grey; "
1195 m_dataProducerValue->setToolTip(
1196 tr(
"The dataset for which selections are saved"));
1197 m_dataProducerValue->setText(
1198 m_dataProducerName.isEmpty() ? QString() : m_dataProducerName);
1199 producerLayout->addWidget(m_dataProducerLabel);
1200 producerLayout->addWidget(m_dataProducerValue, 1);
1201 editorLayout->addLayout(producerLayout);
1204 QHBoxLayout* elementTypeLayout =
new QHBoxLayout();
1205 elementTypeLayout->setSpacing(9);
1206 m_elementTypeLabel =
new QLabel(tr(
"Element Type"));
1208 QSizePolicy::Preferred);
1209 m_elementTypeLabel->setToolTip(
1210 tr(
"The element type of the saved selections"));
1211 m_elementTypeValue =
new QLabel();
1212 m_elementTypeValue->setStyleSheet(
1213 "QLabel { background-color: snow; border: 1px inset grey; "
1215 m_elementTypeValue->setToolTip(
1216 tr(
"The element type of the saved selections"));
1217 elementTypeLayout->addWidget(m_elementTypeLabel);
1218 elementTypeLayout->addWidget(m_elementTypeValue, 1);
1219 editorLayout->addLayout(elementTypeLayout);
1222 QHBoxLayout* expressionLayout =
new QHBoxLayout();
1223 expressionLayout->setSpacing(29);
1224 m_expressionLabel =
new QLabel(tr(
"Expression"));
1225 m_expressionLabel->setToolTip(
1226 tr(
"Specify the expression which defines the relation between "
1227 "saved selections using boolean operators: !(NOT), &(AND), "
1228 "|(OR), ^(XOR) and ()."));
1229 m_expressionEdit =
new QLineEdit();
1230 m_expressionEdit->setPlaceholderText(
1231 tr(
"e.g., (s0|s1)&s2|(s3&s4)|s5|s6|s7"));
1232 m_expressionEdit->setToolTip(
1233 tr(
"Specify the expression which defines the relation between "
1234 "saved selections using boolean operators: !(NOT), &(AND), "
1235 "|(OR), ^(XOR) and ()."));
1236 connect(m_expressionEdit, &QLineEdit::textChanged,
this,
1237 &cvSelectionPropertiesWidget::onExpressionChanged);
1238 expressionLayout->addWidget(m_expressionLabel);
1239 expressionLayout->addWidget(m_expressionEdit, 1);
1240 editorLayout->addLayout(expressionLayout);
1243 QHBoxLayout* tableLayout =
new QHBoxLayout();
1244 tableLayout->setContentsMargins(0, 0, 0, 0);
1247 m_selectionEditorTable =
new QTableWidget();
1248 m_selectionEditorTable->setColumnCount(3);
1249 m_selectionEditorTable->setHorizontalHeaderLabels(
1250 {tr(
"Name"), tr(
"Type"), tr(
"Color")});
1251 m_selectionEditorTable->setSelectionBehavior(QAbstractItemView::SelectRows);
1252 m_selectionEditorTable->setSelectionMode(
1253 QAbstractItemView::ExtendedSelection);
1254 m_selectionEditorTable->setAlternatingRowColors(
true);
1255 m_selectionEditorTable->horizontalHeader()->setStretchLastSection(
true);
1256 m_selectionEditorTable->verticalHeader()->setVisible(
false);
1257 m_selectionEditorTable->setMinimumHeight(120);
1258 connect(m_selectionEditorTable, &QTableWidget::itemSelectionChanged,
this,
1260 onSelectionEditorTableSelectionChanged);
1262 connect(m_selectionEditorTable, &QTableWidget::cellClicked,
this,
1263 &cvSelectionPropertiesWidget::onSelectionEditorCellClicked);
1264 connect(m_selectionEditorTable, &QTableWidget::cellDoubleClicked,
this,
1265 &cvSelectionPropertiesWidget::onSelectionEditorCellDoubleClicked);
1266 tableLayout->addWidget(m_selectionEditorTable);
1269 QVBoxLayout* toolbarLayout =
new QVBoxLayout();
1270 toolbarLayout->setSpacing(0);
1273 m_addSelectionButton =
new QToolButton();
1275 QIcon addIcon(
":/Resources/images/svg/pqPlus.svg");
1276 if (addIcon.isNull()) {
1277 addIcon = QIcon(
":/Resources/images/svg/pqPlus.png");
1279 if (addIcon.isNull()) {
1280 addIcon = QIcon(
":/Resources/images/ecvPlus.png");
1282 m_addSelectionButton->setIcon(addIcon);
1283 m_addSelectionButton->setToolTip(tr(
"Add active selection"));
1284 m_addSelectionButton->setIconSize(QSize(16, 16));
1285 m_addSelectionButton->setEnabled(
1287 connect(m_addSelectionButton, &QToolButton::clicked,
this,
1288 &cvSelectionPropertiesWidget::onAddActiveSelectionClicked);
1289 toolbarLayout->addWidget(m_addSelectionButton);
1292 m_removeSelectionButton =
new QToolButton();
1293 QIcon removeIcon(
":/Resources/images/svg/pqMinus.svg");
1294 if (removeIcon.isNull()) {
1295 removeIcon = QIcon(
":/Resources/images/ecvMinus.png");
1297 m_removeSelectionButton->setIcon(removeIcon);
1298 m_removeSelectionButton->setToolTip(
1299 tr(
"Remove selected selection from the saved selections. Remember "
1300 "to edit the Expression."));
1301 m_removeSelectionButton->setIconSize(QSize(16, 16));
1302 m_removeSelectionButton->setEnabled(
false);
1303 connect(m_removeSelectionButton, &QToolButton::clicked,
this,
1304 &cvSelectionPropertiesWidget::onRemoveSelectedSelectionClicked);
1305 toolbarLayout->addWidget(m_removeSelectionButton);
1308 toolbarLayout->addStretch();
1312 m_removeAllSelectionsButton =
new QToolButton();
1313 QIcon trashIcon(
":/Resources/images/smallTrash.png");
1314 if (trashIcon.isNull()) {
1315 trashIcon = QIcon(
":/Resources/images/ecvdelete.png");
1317 m_removeAllSelectionsButton->setIcon(trashIcon);
1318 m_removeAllSelectionsButton->setToolTip(tr(
"Remove all saved selections"));
1319 m_removeAllSelectionsButton->setIconSize(
1321 m_removeAllSelectionsButton->setEnabled(
false);
1322 connect(m_removeAllSelectionsButton, &QToolButton::clicked,
this,
1323 &cvSelectionPropertiesWidget::onRemoveAllSelectionsClicked);
1324 toolbarLayout->addWidget(m_removeAllSelectionsButton);
1326 tableLayout->addLayout(toolbarLayout);
1327 editorLayout->addLayout(tableLayout);
1330 QHBoxLayout* activateLayout =
new QHBoxLayout();
1331 activateLayout->setSpacing(2);
1332 m_activateCombinedSelectionsButton =
1333 new QPushButton(tr(
"Activate Combined Selections"));
1334 m_activateCombinedSelectionsButton->setIcon(
1335 QIcon(
":/Resources/images/smallValidate.png"));
1336 m_activateCombinedSelectionsButton->setToolTip(
1337 tr(
"Set the combined saved selections as the active selection"));
1338 m_activateCombinedSelectionsButton->setFocusPolicy(Qt::TabFocus);
1339 m_activateCombinedSelectionsButton->setDefault(
true);
1340 m_activateCombinedSelectionsButton->setEnabled(
false);
1341 connect(m_activateCombinedSelectionsButton, &QPushButton::clicked,
this,
1342 &cvSelectionPropertiesWidget::onActivateCombinedSelectionsClicked);
1343 activateLayout->addWidget(m_activateCombinedSelectionsButton);
1344 editorLayout->addLayout(activateLayout);
1350 void cvSelectionPropertiesWidget::setupSelectedDataSpreadsheet() {
1353 m_selectedDataGroup =
nullptr;
1355 QGridLayout* dataLayout =
1356 new QGridLayout(m_selectedDataSpreadsheetContainer);
1357 dataLayout->setSpacing(3);
1358 dataLayout->setContentsMargins(8, 8, 8, 8);
1362 QLabel* attributeLabel =
new QLabel(tr(
1363 "<html><body><p><span "
1364 "style=\"font-weight:600;\">Attribute:</span></p></body></html>"));
1366 dataLayout->addWidget(attributeLabel, 0, 0);
1369 m_attributeTypeCombo =
new QComboBox();
1372 QIcon pointDataIcon(
":/Resources/images/svg/pqPointData.svg");
1373 m_attributeTypeCombo->addItem(pointDataIcon, tr(
"Point Data"), 0);
1376 QIcon cellDataIcon(
":/Resources/images/svg/pqCellData.svg");
1377 m_attributeTypeCombo->addItem(cellDataIcon, tr(
"Cell Data"), 1);
1379 m_attributeTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
1380 m_attributeTypeCombo->setIconSize(
1382 connect(m_attributeTypeCombo,
1383 QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
1384 &cvSelectionPropertiesWidget::onAttributeTypeChanged);
1385 dataLayout->addWidget(m_attributeTypeCombo, 0, 1);
1389 m_toggleColumnVisibilityButton =
new QToolButton();
1390 QIcon colVisIcon(
":/Resources/images/interactors.png");
1391 if (colVisIcon.isNull()) {
1392 colVisIcon = QIcon(
":/Resources/images/settings.png");
1394 m_toggleColumnVisibilityButton->setIcon(colVisIcon);
1395 m_toggleColumnVisibilityButton->setToolTip(tr(
"Toggle column visibility"));
1396 m_toggleColumnVisibilityButton->setStatusTip(
1397 tr(
"Toggle column visibility"));
1398 m_toggleColumnVisibilityButton->setIconSize(
1400 m_toggleColumnVisibilityButton->setPopupMode(QToolButton::InstantPopup);
1401 connect(m_toggleColumnVisibilityButton, &QToolButton::clicked,
this,
1402 &cvSelectionPropertiesWidget::onToggleColumnVisibility);
1403 dataLayout->addWidget(m_toggleColumnVisibilityButton, 0, 2);
1406 m_toggleFieldDataButton =
new QToolButton();
1407 QIcon fieldDataIcon(
":/Resources/images/svg/pqGlobalData.svg");
1408 m_toggleFieldDataButton->setIcon(fieldDataIcon);
1409 m_toggleFieldDataButton->setToolTip(
1410 tr(
"Toggle field data visibility (show raw data arrays)"));
1411 m_toggleFieldDataButton->setIconSize(QSize(16, 16));
1412 m_toggleFieldDataButton->setCheckable(
true);
1413 connect(m_toggleFieldDataButton, &QToolButton::toggled,
this,
1414 &cvSelectionPropertiesWidget::onToggleFieldDataClicked);
1415 dataLayout->addWidget(m_toggleFieldDataButton, 0, 3);
1418 m_invertSelectionCheck =
new QCheckBox(tr(
"Invert selection"));
1419 m_invertSelectionCheck->setToolTip(tr(
"Invert the selection"));
1420 m_invertSelectionCheck->setEnabled(
1422 connect(m_invertSelectionCheck, &QCheckBox::toggled,
this,
1423 &cvSelectionPropertiesWidget::onInvertSelectionToggled);
1424 dataLayout->addWidget(m_invertSelectionCheck, 0, 4);
1428 m_spreadsheetTable =
new QTableWidget();
1429 m_spreadsheetTable->setSizePolicy(QSizePolicy::Preferred,
1430 QSizePolicy::MinimumExpanding);
1431 m_spreadsheetTable->setMinimumHeight(120);
1432 m_spreadsheetTable->setSelectionBehavior(QAbstractItemView::SelectRows);
1433 m_spreadsheetTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
1434 m_spreadsheetTable->setAlternatingRowColors(
true);
1435 m_spreadsheetTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
1442 multiColHeader->setStretchLastSection(
true);
1443 multiColHeader->setDefaultSectionSize(100);
1444 m_spreadsheetTable->setHorizontalHeader(multiColHeader);
1446 m_spreadsheetTable->verticalHeader()->setDefaultSectionSize(20);
1448 QFont headerFont = m_spreadsheetTable->horizontalHeader()->font();
1449 headerFont.setBold(
false);
1450 m_spreadsheetTable->horizontalHeader()->setFont(headerFont);
1451 connect(m_spreadsheetTable, &QTableWidget::itemClicked,
this,
1452 &cvSelectionPropertiesWidget::onSpreadsheetItemClicked);
1453 dataLayout->addWidget(m_spreadsheetTable, 2, 0, 1,
1458 QHBoxLayout* actionLayout =
new QHBoxLayout();
1459 actionLayout->setSpacing(3);
1462 actionLayout->addWidget(m_freezeButton);
1465 actionLayout->addWidget(m_extractButton);
1468 m_plotOverTimeButton =
new QPushButton(tr(
"Plot Over Time"));
1469 m_plotOverTimeButton->setToolTip(tr(
"Plot selection over time"));
1470 m_plotOverTimeButton->setEnabled(
false);
1471 connect(m_plotOverTimeButton, &QPushButton::clicked,
this,
1472 &cvSelectionPropertiesWidget::onPlotOverTimeClicked);
1473 actionLayout->addWidget(m_plotOverTimeButton);
1475 dataLayout->addLayout(actionLayout, 3, 0, 1,
1485 m_selectionManager = manager;
1491 updateDataProducerCombo();
1496 if (m_selectionManager) {
1499 if (sharedHighlighter && sharedHighlighter != m_highlighter) {
1507 if (annotations && m_highlighter) {
1547 bool isNewSelection = m_originalSelectionIds.isEmpty() ||
1550 if (isNewSelection) {
1553 m_originalSelectionIds.clear();
1554 if (m_invertSelectionCheck) {
1555 m_invertSelectionCheck->blockSignals(
true);
1556 m_invertSelectionCheck->setChecked(
false);
1557 m_invertSelectionCheck->blockSignals(
false);
1563 if (m_selectionData.
isEmpty()) {
1569 if (m_invertSelectionCheck) {
1570 m_invertSelectionCheck->setEnabled(
true);
1575 if (m_elementTypeCombo) {
1580 if (m_elementTypeCombo->currentIndex() != expectedIndex) {
1582 m_elementTypeCombo->blockSignals(
true);
1583 m_elementTypeCombo->setCurrentIndex(expectedIndex);
1584 m_elementTypeCombo->blockSignals(
false);
1586 QString(
"[cvSelectionPropertiesWidget] Auto-switched "
1587 "element type to %1")
1588 .arg(expectedIndex == 0 ?
"Point" :
"Cell"));
1594 if (m_elementTypeValue) {
1595 QString elementTypeStr =
1599 m_elementTypeValue->setText(elementTypeStr);
1607 QString sourceName = sourceObj->
getName();
1611 if (m_dataProducerCombo) {
1612 int comboIndex = m_dataProducerCombo->findText(sourceName);
1613 if (comboIndex >= 0) {
1614 m_dataProducerCombo->blockSignals(
true);
1615 m_dataProducerCombo->setCurrentIndex(comboIndex);
1616 m_dataProducerCombo->blockSignals(
false);
1619 onDataProducerChanged(comboIndex);
1624 "Data Producer to '%1'")
1645 "[cvSelectionPropertiesWidget] No polyData available for "
1652 vtkIdType numPoints = polyData->GetNumberOfPoints();
1653 vtkIdType numCells = polyData->GetNumberOfCells();
1654 if (numPoints < 0 || numCells < 0) {
1655 CVLog::Warning(
"[cvSelectionPropertiesWidget] Invalid polyData");
1660 "[cvSelectionPropertiesWidget] polyData validation failed");
1665 updateStatistics(polyData);
1666 updateSelectionList(polyData);
1669 updateSpreadsheetData(polyData);
1678 if (m_applyAlgebraButton) {
1679 m_applyAlgebraButton->setEnabled(m_selectionCount > 0);
1681 if (m_extractBoundaryButton) {
1682 m_extractBoundaryButton->setEnabled(isCells && m_selectionCount > 0);
1686 if (m_addAnnotationButton) {
1687 m_addAnnotationButton->setEnabled(m_selectionCount > 0);
1692 if (m_addSelectionButton && m_selectionCount > 0) {
1693 m_addSelectionButton->setEnabled(
true);
1697 if (m_freezeButton) {
1698 m_freezeButton->setEnabled(m_selectionCount > 0);
1700 if (m_extractButton) {
1701 m_extractButton->setEnabled(m_selectionCount > 0);
1703 if (m_plotOverTimeButton) {
1704 m_plotOverTimeButton->setEnabled(m_selectionCount > 0);
1709 if (m_addSelectionButton) {
1710 m_addSelectionButton->setEnabled(m_selectionCount > 0);
1718 m_selectionData.
clear();
1719 m_selectionCount = 0;
1720 m_selectionType = tr(
"None");
1723 m_originalSelectionIds.clear();
1726 if (m_invertSelectionCheck) {
1727 m_invertSelectionCheck->blockSignals(
true);
1728 m_invertSelectionCheck->setChecked(
false);
1729 m_invertSelectionCheck->setEnabled(
false);
1730 m_invertSelectionCheck->blockSignals(
false);
1734 if (m_highlighter) {
1749 m_countLabel->setText(QString::number(m_selectionCount));
1752 m_typeLabel->setText(m_selectionType);
1754 if (m_boundsLabel) {
1755 m_boundsLabel->setText(tr(
"N/A"));
1757 if (m_centerLabel) {
1758 m_centerLabel->setText(tr(
"N/A"));
1760 if (m_volumeLabel) {
1761 m_volumeLabel->setText(tr(
"N/A"));
1765 if (m_selectionTableWidget) {
1766 m_selectionTableWidget->clear();
1767 m_selectionTableWidget->setRowCount(0);
1768 m_selectionTableWidget->setColumnCount(0);
1770 if (m_listInfoLabel) {
1771 m_listInfoLabel->setText(tr(
"No selection"));
1772 m_listInfoLabel->setStyleSheet(
"font-style: italic; color: gray;");
1776 if (m_spreadsheetTable) {
1777 m_spreadsheetTable->clear();
1778 m_spreadsheetTable->setRowCount(0);
1779 m_spreadsheetTable->setColumnCount(0);
1783 if (m_freezeButton) {
1784 m_freezeButton->setEnabled(
false);
1786 if (m_extractButton) {
1787 m_extractButton->setEnabled(
false);
1789 if (m_plotOverTimeButton) {
1790 m_plotOverTimeButton->setEnabled(
false);
1794 if (m_addSelectionButton) {
1795 m_addSelectionButton->setEnabled(
false);
1800 void cvSelectionPropertiesWidget::updateStatistics(
1804 "[cvSelectionPropertiesWidget::updateStatistics] polyData is "
1812 if (polyData->GetNumberOfPoints() < 0 ||
1813 polyData->GetNumberOfCells() < 0) {
1815 "[cvSelectionPropertiesWidget::updateStatistics] Invalid "
1821 "[cvSelectionPropertiesWidget::updateStatistics] polyData "
1822 "access failed - possible dangling pointer");
1828 customSelection ? *customSelection : m_selectionData;
1830 m_selectionCount = selection.
count();
1835 m_countLabel->setText(QString::number(m_selectionCount));
1838 m_typeLabel->setText(m_selectionType);
1842 if (m_selectionCount > 0) {
1843 computeBoundingBox(polyData, m_bounds);
1846 if (m_boundsLabel) {
1847 m_boundsLabel->setText(formatBounds(m_bounds));
1851 m_center[0] = (m_bounds[0] + m_bounds[1]) / 2.0;
1852 m_center[1] = (m_bounds[2] + m_bounds[3]) / 2.0;
1853 m_center[2] = (m_bounds[4] + m_bounds[5]) / 2.0;
1854 if (m_centerLabel) {
1855 m_centerLabel->setText(QString(
"(%1, %2, %3)")
1856 .arg(m_center[0], 0,
'g', 6)
1857 .arg(m_center[1], 0,
'g', 6)
1858 .arg(m_center[2], 0,
'g', 6));
1862 double dx = m_bounds[1] - m_bounds[0];
1863 double dy = m_bounds[3] - m_bounds[2];
1864 double dz = m_bounds[5] - m_bounds[4];
1865 m_volume = dx * dy * dz;
1866 if (m_volumeLabel) {
1867 m_volumeLabel->setText(QString(
"%1").arg(m_volume, 0,
'g', 6));
1870 if (m_boundsLabel) {
1871 m_boundsLabel->setText(tr(
"N/A"));
1873 if (m_centerLabel) {
1874 m_centerLabel->setText(tr(
"N/A"));
1876 if (m_volumeLabel) {
1877 m_volumeLabel->setText(tr(
"N/A"));
1883 void cvSelectionPropertiesWidget::updateSelectionList(vtkPolyData* polyData) {
1885 if (!m_selectionTableWidget) {
1889 m_selectionTableWidget->clear();
1890 m_selectionTableWidget->setRowCount(0);
1892 QVector<qint64> ids = m_selectionData.
ids();
1893 if (ids.isEmpty()) {
1894 m_selectionTableWidget->setColumnCount(0);
1895 if (m_listInfoLabel) {
1896 m_listInfoLabel->setText(tr(
"No selection"));
1897 m_listInfoLabel->setStyleSheet(
"font-style: italic; color: gray;");
1906 if (m_listInfoLabel) {
1907 m_listInfoLabel->setText(
1911 m_listInfoLabel->setStyleSheet(
"font-weight: bold;");
1915 QStringList headers;
1917 headers << tr(
"Point ID") << tr(
"X") << tr(
"Y") << tr(
"Z");
1919 if (polyData && polyData->GetPointData()) {
1920 vtkPointData* pd = polyData->GetPointData();
1921 for (
int i = 0; i < pd->GetNumberOfArrays(); ++i) {
1922 vtkDataArray* arr = pd->GetArray(i);
1923 if (arr && arr->GetName()) {
1924 QString
name = QString::fromUtf8(arr->GetName());
1926 if (
name !=
"Points" &&
name !=
"Normals") {
1934 headers << tr(
"Cell ID") << tr(
"Type") << tr(
"Num Points");
1936 if (polyData && polyData->GetCellData()) {
1937 vtkCellData* cd = polyData->GetCellData();
1938 for (
int i = 0; i < cd->GetNumberOfArrays(); ++i) {
1939 vtkDataArray* arr = cd->GetArray(i);
1940 if (arr && arr->GetName()) {
1941 headers << QString::fromUtf8(arr->GetName());
1947 m_selectionTableWidget->setColumnCount(headers.size());
1948 m_selectionTableWidget->setHorizontalHeaderLabels(headers);
1951 int maxDisplay = qMin(ids.size(), 500);
1952 m_selectionTableWidget->setRowCount(maxDisplay);
1954 for (
int row = 0; row < maxDisplay; ++row) {
1955 qint64
id = ids[row];
1959 QTableWidgetItem* idItem =
new QTableWidgetItem(QString::number(
id));
1962 QVariant::fromValue(
id));
1963 m_selectionTableWidget->setItem(row, col++, idItem);
1965 if (isPoints && polyData) {
1966 if (
id >= 0 && id < polyData->GetNumberOfPoints()) {
1968 polyData->GetPoint(
id, pt);
1971 m_selectionTableWidget->setItem(
1973 new QTableWidgetItem(QString::number(pt[0],
'g', 6)));
1974 m_selectionTableWidget->setItem(
1976 new QTableWidgetItem(QString::number(pt[1],
'g', 6)));
1977 m_selectionTableWidget->setItem(
1979 new QTableWidgetItem(QString::number(pt[2],
'g', 6)));
1982 if (polyData->GetPointData()) {
1983 vtkPointData* pd = polyData->GetPointData();
1984 for (
int i = 0; i < pd->GetNumberOfArrays(); ++i) {
1985 vtkDataArray* arr = pd->GetArray(i);
1986 if (arr && arr->GetName()) {
1987 QString
name = QString::fromUtf8(arr->GetName());
1988 if (
name !=
"Points" &&
name !=
"Normals") {
1989 double val = arr->GetComponent(
id, 0);
1990 m_selectionTableWidget->setItem(
1992 new QTableWidgetItem(
1993 QString::number(val,
'g', 6)));
1999 }
else if (!isPoints && polyData) {
2001 if (
id >= 0 && id < polyData->GetNumberOfCells()) {
2002 vtkCell* cell = polyData->GetCell(
id);
2005 m_selectionTableWidget->setItem(
2007 new QTableWidgetItem(
2008 QString::number(cell->GetCellType())));
2010 m_selectionTableWidget->setItem(
2012 new QTableWidgetItem(QString::number(
2013 cell->GetNumberOfPoints())));
2016 if (polyData->GetCellData()) {
2017 vtkCellData* cd = polyData->GetCellData();
2018 for (
int i = 0; i < cd->GetNumberOfArrays(); ++i) {
2019 vtkDataArray* arr = cd->GetArray(i);
2020 if (arr && arr->GetName()) {
2021 double val = arr->GetComponent(
id, 0);
2022 m_selectionTableWidget->setItem(
2024 new QTableWidgetItem(
2025 QString::number(val,
'g', 6)));
2035 m_selectionTableWidget->resizeColumnsToContents();
2038 if (ids.size() > maxDisplay && m_listInfoLabel) {
2039 m_listInfoLabel->setText(
2040 tr(
"Showing %1 of %2 %3")
2048 void cvSelectionPropertiesWidget::computeBoundingBox(vtkPolyData* polyData,
2051 bounds[0] = bounds[2] = bounds[4] = std::numeric_limits<double>::max();
2052 bounds[1] = bounds[3] = bounds[5] = std::numeric_limits<double>::lowest();
2054 QVector<qint64> ids = m_selectionData.
ids();
2058 for (qint64
id : ids) {
2059 if (
id >= 0 && id < polyData->GetNumberOfPoints()) {
2061 polyData->GetPoint(
id, pt);
2063 bounds[0] = qMin(bounds[0], pt[0]);
2064 bounds[1] = qMax(bounds[1], pt[0]);
2065 bounds[2] = qMin(bounds[2], pt[1]);
2066 bounds[3] = qMax(bounds[3], pt[1]);
2067 bounds[4] = qMin(bounds[4], pt[2]);
2068 bounds[5] = qMax(bounds[5], pt[2]);
2073 for (qint64
id : ids) {
2074 if (
id >= 0 && id < polyData->GetNumberOfCells()) {
2075 vtkCell* cell = polyData->GetCell(
id);
2077 vtkIdType npts = cell->GetNumberOfPoints();
2078 for (vtkIdType i = 0; i < npts; ++i) {
2080 polyData->GetPoint(cell->GetPointId(i), pt);
2082 bounds[0] = qMin(bounds[0], pt[0]);
2083 bounds[1] = qMax(bounds[1], pt[0]);
2084 bounds[2] = qMin(bounds[2], pt[1]);
2085 bounds[3] = qMax(bounds[3], pt[1]);
2086 bounds[4] = qMin(bounds[4], pt[2]);
2087 bounds[5] = qMax(bounds[5], pt[2]);
2096 QString cvSelectionPropertiesWidget::formatBounds(
const double bounds[6]) {
2097 return QString(
"X: [%1, %2]\nY: [%3, %4]\nZ: [%5, %6]")
2098 .arg(bounds[0], 0,
'g', 6)
2099 .arg(bounds[1], 0,
'g', 6)
2100 .arg(bounds[2], 0,
'g', 6)
2101 .arg(bounds[3], 0,
'g', 6)
2102 .arg(bounds[4], 0,
'g', 6)
2103 .arg(bounds[5], 0,
'g', 6);
2107 void cvSelectionPropertiesWidget::showColorDialog(
const QString& title,
2108 double currentColor[3],
2114 QColor initialColor;
2115 if (m_highlighter) {
2120 QColor(
int(currentColor[0] * 255),
int(currentColor[1] * 255),
2121 int(currentColor[2] * 255));
2124 QColor newColor = QColorDialog::getColor(initialColor,
this, title);
2126 if (newColor.isValid()) {
2128 if (m_highlighter) {
2137 newColor.blueF(), mode);
2145 void cvSelectionPropertiesWidget::onHoverColorClicked() {
2146 double dummy[3] = {0, 1, 1};
2147 showColorDialog(tr(
"Select Hover Highlight Color"), dummy,
2151 void cvSelectionPropertiesWidget::onPreselectedColorClicked() {
2152 double dummy[3] = {1, 1, 0};
2153 showColorDialog(tr(
"Select Pre-selected Highlight Color"), dummy,
2157 void cvSelectionPropertiesWidget::onSelectedColorClicked() {
2158 double dummy[3] = {1, 0, 1};
2159 showColorDialog(tr(
"Select Selected Highlight Color"), dummy,
2163 void cvSelectionPropertiesWidget::onBoundaryColorClicked() {
2164 double dummy[3] = {1, 0.65, 0};
2165 showColorDialog(tr(
"Select Boundary Highlight Color"), dummy,
2169 void cvSelectionPropertiesWidget::onHoverOpacityChanged(
double value) {
2173 void cvSelectionPropertiesWidget::onPreselectedOpacityChanged(
double value) {
2177 void cvSelectionPropertiesWidget::onSelectedOpacityChanged(
double value) {
2181 void cvSelectionPropertiesWidget::onBoundaryOpacityChanged(
double value) {
2185 void cvSelectionPropertiesWidget::onExportToMeshClicked() {
2186 if (m_selectionData.
isEmpty()) {
2187 CVLog::Warning(
"[cvSelectionPropertiesWidget] No selection to export");
2188 QMessageBox::warning(
this, tr(
"Export Failed"),
2189 tr(
"No selection to export. Please select some "
2196 "[cvSelectionPropertiesWidget] Can only export cells as mesh");
2197 QMessageBox::warning(
this, tr(
"Export Failed"),
2198 tr(
"Can only export cell selections as mesh. "
2199 "Current selection is points."));
2219 sourceMesh, m_selectionData, options);
2220 }
catch (
const std::exception&
e) {
2221 CVLog::Error(QString(
"[cvSelectionPropertiesWidget] Exception "
2222 "during direct export: %1")
2227 "[cvSelectionPropertiesWidget] Unknown exception "
2228 "during direct export");
2232 if (mesh && mesh->
size() > 0) {
2237 QString(
"ExtractSelection%1").arg(m_extractCounter);
2253 "[cvSelectionPropertiesWidget] Direct "
2254 "extraction failed, falling back to VTK-based "
2262 "[cvSelectionPropertiesWidget] Using VTK-based mesh extraction");
2270 "[cvSelectionPropertiesWidget] Failed to get polyData from "
2272 QMessageBox::critical(
this, tr(
"Export Failed"),
2273 tr(
"Failed to get data from visualizer. Please "
2274 "ensure a mesh is loaded."));
2279 vtkIdType polyDataCells = polyData->GetNumberOfCells();
2280 QVector<qint64> selectionIds = m_selectionData.
ids();
2281 bool hasValidIds =
false;
2282 for (qint64
id : selectionIds) {
2283 if (
id >= 0 &&
id < polyDataCells) {
2290 CVLog::Error(QString(
"[cvSelectionPropertiesWidget] Selection IDs "
2291 "(%1 items) are not valid for polyData (%2 cells)")
2292 .arg(selectionIds.size())
2293 .arg(polyDataCells));
2294 QMessageBox::critical(
2295 this, tr(
"Export Failed"),
2296 tr(
"Selection IDs are not valid for the current data. "
2297 "The selection may have been made on a different object."));
2302 "[cvSelectionPropertiesWidget] Creating mesh from selection...");
2310 }
catch (
const std::exception&
e) {
2311 CVLog::Error(QString(
"[cvSelectionPropertiesWidget] Exception during "
2314 QMessageBox::critical(
2315 this, tr(
"Export Failed"),
2316 tr(
"An error occurred during export: %1").arg(
e.what()));
2320 "[cvSelectionPropertiesWidget] Unknown exception during "
2322 QMessageBox::critical(
this, tr(
"Export Failed"),
2323 tr(
"An unknown error occurred during export."));
2327 if (mesh && mesh->
size() > 0) {
2330 QString meshName = QString(
"ExtractSelection%1").arg(m_extractCounter);
2348 "[cvSelectionPropertiesWidget] Failed to export selection as "
2349 "mesh - no cells extracted");
2350 QMessageBox::critical(
2351 this, tr(
"Export Failed"),
2352 tr(
"Failed to export selection. No cells could be extracted "
2353 "from the selection."));
2357 void cvSelectionPropertiesWidget::onExportToPointCloudClicked() {
2358 if (m_selectionData.
isEmpty()) {
2359 CVLog::Warning(
"[cvSelectionPropertiesWidget] No selection to export");
2360 QMessageBox::warning(
this, tr(
"Export Failed"),
2361 tr(
"No selection to export. Please select some "
2369 QString(
"[cvSelectionPropertiesWidget] Extract point cloud check: "
2370 "manager=%1, sourceValid=%2")
2371 .arg(m_selectionManager !=
nullptr ?
"yes" :
"no")
2372 .arg(m_selectionManager
2381 "getSourcePointCloud returned: %1")
2400 }
catch (
const std::exception&
e) {
2401 CVLog::Error(QString(
"[cvSelectionPropertiesWidget] Exception "
2402 "during direct export: %1")
2407 "[cvSelectionPropertiesWidget] Unknown exception "
2408 "during direct export");
2412 if (cloud && cloud->
size() > 0) {
2417 QString(
"ExtractSelection%1").arg(m_extractCounter);
2425 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Created "
2426 "point cloud '%1' with %2 points (direct "
2429 .arg(cloud->
size()));
2440 "[cvSelectionPropertiesWidget] Direct "
2441 "extraction failed, falling back to VTK-based "
2453 bool isSourcePointCloud = polyData && (polyData->GetNumberOfPolys() == 0);
2456 !isSourcePointCloud) {
2458 "[cvSelectionPropertiesWidget] Can only export points as point "
2460 QMessageBox::warning(
2461 this, tr(
"Export Failed"),
2462 tr(
"Can only export point selections as point "
2463 "cloud. Current selection is cells on a mesh."));
2472 "[cvSelectionPropertiesWidget] Failed to get polyData from "
2474 QMessageBox::critical(
this, tr(
"Export Failed"),
2475 tr(
"Failed to get data from visualizer. Please "
2476 "ensure a point cloud is loaded."));
2482 bool isCellSelectionOnPointCloud =
2484 (polyData->GetNumberOfPolys() == 0);
2487 vtkIdType maxId = isCellSelectionOnPointCloud
2488 ? polyData->GetNumberOfCells()
2489 : polyData->GetNumberOfPoints();
2490 QVector<qint64> selectionIds = m_selectionData.
ids();
2491 bool hasValidIds =
false;
2493 int invalidCount = 0;
2494 for (qint64
id : selectionIds) {
2495 if (
id >= 0 &&
id < maxId) {
2514 QString(
"[cvSelectionPropertiesWidget] Selection IDs "
2515 "(%1 items) are not valid for polyData (%2 elements)")
2516 .arg(selectionIds.size())
2518 QMessageBox::critical(
2519 this, tr(
"Export Failed"),
2520 tr(
"Selection IDs are not valid for the current data. "
2521 "The selection may have been made on a different object."));
2526 "[cvSelectionPropertiesWidget] Creating point cloud from "
2532 if (isCellSelectionOnPointCloud) {
2534 "[cvSelectionPropertiesWidget] Converting cell selection to "
2535 "point selection for point cloud export");
2545 polyData, exportSelection, options);
2546 }
catch (
const std::exception&
e) {
2547 CVLog::Error(QString(
"[cvSelectionPropertiesWidget] Exception during "
2550 QMessageBox::critical(
2551 this, tr(
"Export Failed"),
2552 tr(
"An error occurred during export: %1").arg(
e.what()));
2556 "[cvSelectionPropertiesWidget] Unknown exception during "
2558 QMessageBox::critical(
this, tr(
"Export Failed"),
2559 tr(
"An unknown error occurred during export."));
2563 if (cloud && cloud->
size() > 0) {
2566 QString cloudName = QString(
"ExtractSelection%1").arg(m_extractCounter);
2573 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Created point "
2574 "cloud '%1' with %2 points")
2576 .arg(cloud->
size()));
2585 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Exported %1 points "
2586 "as point cloud (selection preserved)")
2587 .arg(cloud->
size()));
2595 "[cvSelectionPropertiesWidget] Failed to export selection as "
2596 "point cloud - no points extracted");
2597 QMessageBox::critical(
2598 this, tr(
"Export Failed"),
2599 tr(
"Failed to export selection. No points could be extracted "
2600 "from the selection."));
2604 void cvSelectionPropertiesWidget::onSelectionTableItemClicked(
2605 QTableWidgetItem* item) {
2606 if (!item || !m_selectionTableWidget) {
2611 int row = item->row();
2612 QTableWidgetItem* idItem = m_selectionTableWidget->item(row, 0);
2618 QVariant idData = idItem->data(Qt::UserRole);
2619 if (!idData.isValid()) {
2623 qint64
id = idData.toLongLong();
2626 highlightSingleItem(
id);
2628 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Highlighting %1 ID: %2")
2634 qint64 cvSelectionPropertiesWidget::extractIdFromItemText(
2635 const QString& itemText) {
2637 QRegularExpression idRegex(
"ID:\\s*(\\d+)");
2638 QRegularExpressionMatch match = idRegex.match(itemText);
2640 if (match.hasMatch()) {
2642 qint64
id = match.captured(1).toLongLong(&ok);
2652 void cvSelectionPropertiesWidget::highlightSingleItem(qint64
id) {
2653 if (!m_highlighter) {
2655 "[cvSelectionPropertiesWidget] Highlighter not available");
2664 "[cvSelectionPropertiesWidget] No polyData available for "
2672 QString dataType = isPointData ? tr(
"Point") : tr(
"Cell");
2675 if (id < 0 || id >= polyData->GetNumberOfPoints()) {
2676 CVLog::Warning(QString(
"[cvSelectionPropertiesWidget] Point ID %1 "
2682 if (id < 0 || id >= polyData->GetNumberOfCells()) {
2683 CVLog::Warning(QString(
"[cvSelectionPropertiesWidget] Cell ID %1 "
2693 singleIdArray->InsertNextValue(
id);
2699 double savedColor[3] = {originalColor[0], originalColor[1],
2713 m_savedPreselectedColor[0] = savedColor[0];
2714 m_savedPreselectedColor[1] = savedColor[1];
2715 m_savedPreselectedColor[2] = savedColor[2];
2716 m_lastHighlightedId = id;
2721 polyData->GetPoint(
id, pt);
2723 QString(
"[cvSelectionPropertiesWidget] RED highlight: %1 "
2724 "ID=%2 at (%3, %4, %5)")
2727 .arg(pt[0], 0,
'f', 4)
2728 .arg(pt[1], 0,
'f', 4)
2729 .arg(pt[2], 0,
'f', 4));
2731 vtkCell* cell = polyData->GetCell(
id);
2733 double center[3] = {0, 0, 0};
2734 double* weights =
new double[cell->GetNumberOfPoints()];
2735 double pcoords[3] = {0.5, 0.5, 0.5};
2737 cell->EvaluateLocation(subId, pcoords, center, weights);
2740 QString(
"[cvSelectionPropertiesWidget] RED highlight: "
2742 "(Type:%3, Points:%4) center=(%5, %6, %7)")
2745 .arg(cell->GetCellType())
2746 .arg(cell->GetNumberOfPoints())
2747 .arg(center[0], 0,
'f', 4)
2748 .arg(center[1], 0,
'f', 4)
2749 .arg(center[2], 0,
'f', 4));
2760 QTimer::singleShot(3000,
this, [
this]() {
2761 if (m_highlighter) {
2764 this->m_savedPreselectedColor[0],
2765 this->m_savedPreselectedColor[1],
2766 this->m_savedPreselectedColor[2],
2769 if (!m_selectionData.
isEmpty()) {
2772 m_highlighter->highlightSelection(
2773 m_selectionData.vtkArray(),
2774 m_selectionData.fieldAssociation(),
2775 cvSelectionHighlighter::SELECTED);
2790 void cvSelectionPropertiesWidget::onAlgebraOperationTriggered() {
2791 if (!m_selectionManager || m_selectionData.
isEmpty() || !m_algebraOpCombo) {
2797 m_algebraOpCombo->currentData().toInt());
2803 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Algebra operation %1 "
2805 .arg(
static_cast<int>(op)));
2809 void cvSelectionPropertiesWidget::onExtractBoundaryClicked() {
2810 if (!m_selectionManager || m_selectionData.
isEmpty()) {
2824 "[cvSelectionPropertiesWidget] No polyData for boundary "
2834 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Extracted "
2835 "boundary: %1 -> %2 cells")
2836 .arg(m_selectionData.
count())
2837 .arg(boundary.
count()));
2849 void cvSelectionPropertiesWidget::onAddAnnotationClicked() {
2850 if (!m_selectionManager || m_selectionData.
isEmpty()) {
2862 QString text = QInputDialog::getText(
this, tr(
"Add Annotation"),
2863 tr(
"Annotation text:"),
2864 QLineEdit::Normal, QString(), &ok);
2866 if (ok && !text.isEmpty()) {
2867 QString
id = annotations->
addAnnotation(m_selectionData, text,
true);
2868 if (!
id.isEmpty()) {
2870 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Added "
2882 void cvSelectionPropertiesWidget::onCellLabelsClicked() {
2885 if (!m_cellLabelsMenu)
return;
2887 m_cellLabelsMenu->clear();
2890 QAction* noneAction = m_cellLabelsMenu->addAction(tr(
"None"));
2891 noneAction->setCheckable(
true);
2892 noneAction->setChecked(m_currentCellLabelArray.isEmpty());
2893 connect(noneAction, &QAction::triggered, [
this]() {
2894 m_currentCellLabelArray.clear();
2896 if (m_highlighter) {
2899 CVLog::Print(
"[cvSelectionPropertiesWidget] Cell labels disabled");
2903 QAction* idAction = m_cellLabelsMenu->addAction(tr(
"ID"));
2904 idAction->setCheckable(
true);
2905 idAction->setChecked(m_currentCellLabelArray ==
"ID");
2906 connect(idAction, &QAction::triggered, [
this]() {
2907 m_currentCellLabelArray =
"ID";
2909 if (m_highlighter) {
2912 CVLog::Print(
"[cvSelectionPropertiesWidget] Cell labels set to ID");
2915 m_cellLabelsMenu->addSeparator();
2919 if (polyData && polyData->GetCellData()) {
2920 vtkCellData* cellData = polyData->GetCellData();
2921 for (
int i = 0; i < cellData->GetNumberOfArrays(); ++i) {
2922 vtkDataArray* arr = cellData->GetArray(i);
2923 if (arr && arr->GetName()) {
2924 QString
name = QString::fromUtf8(arr->GetName());
2929 QAction* action = m_cellLabelsMenu->addAction(
name);
2930 action->setCheckable(
true);
2931 action->setChecked(m_currentCellLabelArray ==
name);
2932 connect(action, &QAction::triggered, [
this,
name]() {
2933 m_currentCellLabelArray =
name;
2935 if (m_highlighter) {
2939 "Cell labels set to %1")
2951 void cvSelectionPropertiesWidget::onPointLabelsClicked() {
2954 if (!m_pointLabelsMenu)
return;
2956 m_pointLabelsMenu->clear();
2959 QAction* noneAction = m_pointLabelsMenu->addAction(tr(
"None"));
2960 noneAction->setCheckable(
true);
2961 noneAction->setChecked(m_currentPointLabelArray.isEmpty());
2962 connect(noneAction, &QAction::triggered, [
this]() {
2963 m_currentPointLabelArray.clear();
2965 if (m_highlighter) {
2968 CVLog::Print(
"[cvSelectionPropertiesWidget] Point labels disabled");
2972 QAction* idAction = m_pointLabelsMenu->addAction(tr(
"ID"));
2973 idAction->setCheckable(
true);
2974 idAction->setChecked(m_currentPointLabelArray ==
"ID");
2975 connect(idAction, &QAction::triggered, [
this]() {
2976 m_currentPointLabelArray =
"ID";
2978 if (m_highlighter) {
2983 m_pointLabelsMenu->addSeparator();
2987 if (polyData && polyData->GetPointData()) {
2988 vtkPointData* pointData = polyData->GetPointData();
2989 for (
int i = 0; i < pointData->GetNumberOfArrays(); ++i) {
2990 vtkDataArray* arr = pointData->GetArray(i);
2991 if (arr && arr->GetName()) {
2992 QString
name = QString::fromUtf8(arr->GetName());
2998 QAction* action = m_pointLabelsMenu->addAction(
name);
2999 action->setCheckable(
true);
3000 action->setChecked(m_currentPointLabelArray ==
name);
3001 connect(action, &QAction::triggered, [
this,
name]() {
3002 m_currentPointLabelArray =
name;
3004 if (m_highlighter) {
3008 "Point labels set to %1")
3020 void cvSelectionPropertiesWidget::onEditLabelPropertiesClicked() {
3025 if (m_highlighter) {
3051 dialog.setProperties(dialogProps);
3053 &cvSelectionPropertiesWidget::onLabelPropertiesApplied);
3058 void cvSelectionPropertiesWidget::onSelectionColorClicked() {
3059 QColor currentColor = getSelectionColor();
3060 QColor
color = QColorDialog::getColor(currentColor,
this, tr(
"Set Color"));
3061 if (
color.isValid() && m_highlighter) {
3074 "color changed to %1")
3075 .arg(
color.name()));
3080 void cvSelectionPropertiesWidget::onInteractiveSelectionColorClicked() {
3081 QColor currentColor = getInteractiveSelectionColor();
3082 QColor
color = QColorDialog::getColor(currentColor,
this, tr(
"Set Color"));
3083 if (
color.isValid() && m_highlighter) {
3095 "selection color changed to %1")
3096 .arg(
color.name()));
3101 void cvSelectionPropertiesWidget::onEditInteractiveLabelPropertiesClicked() {
3106 if (m_highlighter) {
3132 dialog.setProperties(dialogProps);
3134 &cvSelectionPropertiesWidget::onInteractiveLabelPropertiesApplied);
3139 void cvSelectionPropertiesWidget::onLabelPropertiesApplied(
3166 if (m_highlighter) {
3178 if (m_selectionManager) {
3193 QString(
"[cvSelectionPropertiesWidget] Label properties applied: "
3194 "opacity=%1, pointSize=%2, lineWidth=%3")
3201 void cvSelectionPropertiesWidget::onInteractiveLabelPropertiesApplied(
3229 if (m_selectionManager) {
3244 if (m_highlighter) {
3255 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Interactive label "
3256 "properties applied: "
3257 "opacity=%1, pointSize=%2, lineWidth=%3")
3268 void cvSelectionPropertiesWidget::onExpressionChanged(
const QString& text) {
3272 m_activateCombinedSelectionsButton->setEnabled(
3273 !text.isEmpty() && !m_savedSelections.isEmpty());
3277 void cvSelectionPropertiesWidget::onAddActiveSelectionClicked() {
3278 if (m_selectionData.
isEmpty()) {
3279 QMessageBox::information(
this, tr(
"Add Selection"),
3280 tr(
"No active selection to add."));
3285 SavedSelection saved;
3286 saved.name = generateSelectionName();
3287 saved.type = tr(
"ID Selection");
3288 saved.color = generateSelectionColor();
3289 saved.data = m_selectionData;
3291 m_savedSelections.append(saved);
3292 updateSelectionEditorTable();
3297 QString expr = m_expressionEdit->text();
3298 if (!expr.isEmpty()) {
3301 if (m_savedSelections.size() > 2) {
3302 expr = QString(
"(%1)").arg(expr);
3304 expr +=
"|" + saved.name;
3308 m_expressionEdit->setText(expr);
3311 m_removeAllSelectionsButton->setEnabled(
true);
3312 m_activateCombinedSelectionsButton->setEnabled(
3313 !m_expressionEdit->text().isEmpty());
3318 if (m_addSelectionButton) {
3319 m_addSelectionButton->setEnabled(
false);
3324 m_selectionData.
clear();
3329 QString(
"[cvSelectionPropertiesWidget] Added selection: %1 "
3330 "(+ button disabled until new selection)")
3335 void cvSelectionPropertiesWidget::onRemoveSelectedSelectionClicked() {
3336 QList<QTableWidgetItem*> selectedItems =
3337 m_selectionEditorTable->selectedItems();
3338 if (selectedItems.isEmpty()) {
3344 for (QTableWidgetItem* item : selectedItems) {
3345 rows.insert(item->row());
3349 QList<int> sortedRows = rows.values();
3350 std::sort(sortedRows.begin(), sortedRows.end(), std::greater<int>());
3352 for (
int row : sortedRows) {
3353 if (row >= 0 && row < m_savedSelections.size()) {
3354 QString
name = m_savedSelections[row].name;
3355 m_savedSelections.removeAt(row);
3363 updateSelectionEditorTable();
3366 m_removeAllSelectionsButton->setEnabled(!m_savedSelections.isEmpty());
3367 m_activateCombinedSelectionsButton->setEnabled(
3368 !m_expressionEdit->text().isEmpty() &&
3369 !m_savedSelections.isEmpty());
3373 void cvSelectionPropertiesWidget::onRemoveAllSelectionsClicked() {
3374 if (m_savedSelections.isEmpty()) {
3378 int result = QMessageBox::question(
this, tr(
"Remove All Selections"),
3379 tr(
"Remove all saved selections?"),
3380 QMessageBox::Yes | QMessageBox::No);
3381 if (
result != QMessageBox::Yes) {
3385 m_savedSelections.clear();
3386 m_selectionNameCounter = 0;
3387 m_expressionEdit->clear();
3388 updateSelectionEditorTable();
3391 m_removeSelectionButton->setEnabled(
false);
3392 m_removeAllSelectionsButton->setEnabled(
false);
3393 m_activateCombinedSelectionsButton->setEnabled(
false);
3397 CVLog::Print(
"[cvSelectionPropertiesWidget] Removed all saved selections");
3401 void cvSelectionPropertiesWidget::onActivateCombinedSelectionsClicked() {
3402 QString expression = m_expressionEdit->text().trimmed();
3403 if (expression.isEmpty()) {
3404 QMessageBox::warning(
this, tr(
"Activate Combined Selections"),
3405 tr(
"Expression is empty. Please enter an "
3406 "expression like: s0 & s1"));
3410 if (m_savedSelections.isEmpty()) {
3411 QMessageBox::warning(
3412 this, tr(
"Activate Combined Selections"),
3413 tr(
"No saved selections. Please add selections first."));
3418 QString(
"[cvSelectionPropertiesWidget] Evaluating expression: '%1'")
3424 result = evaluateExpression(expression);
3425 }
catch (
const std::exception&
e) {
3427 QString(
"[cvSelectionPropertiesWidget] Expression evaluation "
3430 QMessageBox::warning(
3431 this, tr(
"Activate Combined Selections"),
3432 tr(
"Expression evaluation failed: %1").arg(
e.what()));
3437 QMessageBox::warning(
3438 this, tr(
"Activate Combined Selections"),
3439 tr(
"Expression evaluation resulted in empty selection.\n"
3440 "Please check your expression syntax."));
3444 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Expression evaluated: "
3449 if (m_selectionManager) {
3458 QString(
"[cvSelectionPropertiesWidget] Activated combined "
3459 "selection: %1 elements from expression '%2'")
3464 "[cvSelectionPropertiesWidget] Could not get polyData "
3465 "for combined selection");
3467 m_selectionData =
result;
3468 m_selectionCount =
result.count();
3473 "[cvSelectionPropertiesWidget] No selection manager available");
3481 const QString& expression) {
3491 QString expr = expression.simplified();
3492 if (expr.isEmpty()) {
3497 QStringList tokens = tokenizeExpression(expr);
3498 if (tokens.isEmpty()) {
3500 "[cvSelectionPropertiesWidget] Failed to tokenize expression");
3508 if (pos < tokens.size()) {
3509 CVLog::Warning(QString(
"[cvSelectionPropertiesWidget] Unexpected token "
3510 "at position %1: %2")
3519 QStringList cvSelectionPropertiesWidget::tokenizeExpression(
3520 const QString& expression) {
3524 for (
int i = 0; i < expression.length(); ++i) {
3525 QChar c = expression[i];
3528 if (!current.isEmpty()) {
3529 tokens.append(current);
3532 }
else if (c ==
'(' || c ==
')' || c ==
'!' || c ==
'&' || c ==
'|' ||
3534 if (!current.isEmpty()) {
3535 tokens.append(current);
3538 tokens.append(QString(c));
3544 if (!current.isEmpty()) {
3545 tokens.append(current);
3553 const QStringList& tokens,
int& pos) {
3557 while (pos < tokens.size() && tokens[pos] ==
"|") {
3568 const QStringList& tokens,
int& pos) {
3572 while (pos < tokens.size() && tokens[pos] ==
"^") {
3583 const QStringList& tokens,
int& pos) {
3587 while (pos < tokens.size() && tokens[pos] ==
"&") {
3598 const QStringList& tokens,
int& pos) {
3600 if (pos < tokens.size() && tokens[pos] ==
"!") {
3610 "[cvSelectionPropertiesWidget] Cannot compute complement: "
3616 return parsePrimaryExpression(tokens, pos);
3621 const QStringList& tokens,
int& pos) {
3623 if (pos >= tokens.size()) {
3625 "[cvSelectionPropertiesWidget] Unexpected end of expression");
3629 if (tokens[pos] ==
"(") {
3633 if (pos < tokens.size() && tokens[pos] ==
")") {
3637 "[cvSelectionPropertiesWidget] Missing closing "
3645 QString
name = tokens[pos];
3649 for (
const SavedSelection& saved : m_savedSelections) {
3650 if (saved.name ==
name) {
3656 QString(
"[cvSelectionPropertiesWidget] Unknown selection: %1")
3662 void cvSelectionPropertiesWidget::onSelectionEditorTableSelectionChanged() {
3663 bool hasSelection = !m_selectionEditorTable->selectedItems().isEmpty();
3664 m_removeSelectionButton->setEnabled(hasSelection);
3667 QList<QTableWidgetItem*> selectedItems =
3668 m_selectionEditorTable->selectedItems();
3669 if (!selectedItems.isEmpty()) {
3670 int row = selectedItems.first()->row();
3671 if (row >= 0 && row < m_savedSelections.size()) {
3672 const SavedSelection& sel = m_savedSelections[row];
3675 if (m_highlighter && !sel.data.isEmpty()) {
3690 void cvSelectionPropertiesWidget::onSelectionEditorCellClicked(
int row,
3693 if (column == 2 && row >= 0 && row < m_savedSelections.size()) {
3695 QColor currentColor = m_savedSelections[row].color;
3698 QColor newColor = QColorDialog::getColor(
3700 tr(
"Select Color for %1").arg(m_savedSelections[row].
name));
3702 if (newColor.isValid() && newColor != currentColor) {
3704 m_savedSelections[row].color = newColor;
3707 QTableWidgetItem* colorItem = m_selectionEditorTable->item(row, 2);
3709 colorItem->setText(newColor.name());
3710 colorItem->setBackground(newColor);
3711 colorItem->setForeground(
3715 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Changed color "
3717 .arg(m_savedSelections[row].
name)
3718 .arg(newColor.name()));
3723 if (row >= 0 && row < m_savedSelections.size()) {
3724 const SavedSelection& sel = m_savedSelections[row];
3725 if (m_highlighter && !sel.data.isEmpty()) {
3738 void cvSelectionPropertiesWidget::onSelectionEditorCellDoubleClicked(
3739 int row,
int column) {
3742 onSelectionEditorCellClicked(row, column);
3752 void cvSelectionPropertiesWidget::onAttributeTypeChanged(
int index) {
3756 updateSpreadsheetData(polyData);
3761 void cvSelectionPropertiesWidget::onInvertSelectionToggled(
bool checked) {
3771 QString(
"[cvSelectionPropertiesWidget] Invert selection: %1")
3772 .arg(checked ?
"ON" :
"OFF"));
3775 if (m_originalSelectionIds.isEmpty()) {
3776 m_originalSelectionIds = m_selectionData.
ids();
3779 if (m_originalSelectionIds.isEmpty()) {
3781 "[cvSelectionPropertiesWidget] No original selection for "
3790 "[cvSelectionPropertiesWidget] No polyData for inversion");
3795 QVector<qint64> displayIds;
3797 bool isCellSelection =
3799 vtkIdType totalCount = isCellSelection ? polyData->GetNumberOfCells()
3800 : polyData->GetNumberOfPoints();
3804 QSet<qint64> originalIdSet =
qSetFromVector(m_originalSelectionIds);
3806 displayIds.reserve(
static_cast<int>(totalCount) -
3807 m_originalSelectionIds.size());
3808 for (vtkIdType i = 0; i < totalCount; ++i) {
3809 if (!originalIdSet.contains(
static_cast<qint64
>(i))) {
3810 displayIds.append(
static_cast<qint64
>(i));
3815 displayIds = m_originalSelectionIds;
3819 if (m_highlighter) {
3822 idArray->SetNumberOfTuples(displayIds.size());
3823 for (
int i = 0; i < displayIds.size(); ++i) {
3824 idArray->SetValue(i,
static_cast<vtkIdType
>(displayIds[i]));
3837 if (!pointLabelArray.isEmpty() &&
3851 displaySelection.setActorInfo(m_selectionData.
primaryActor(),
3857 updateSpreadsheetData(polyData, &displaySelection);
3858 updateStatistics(polyData, &displaySelection);
3864 void cvSelectionPropertiesWidget::onFreezeClicked() {
3865 if (m_selectionData.
isEmpty()) {
3866 CVLog::Warning(
"[cvSelectionPropertiesWidget] No selection to freeze");
3875 QString frozenName = QString(
"Frozen_%1")
3876 .arg(QDateTime::currentDateTime().
toString(
3877 "yyyyMMdd_HHmmss"));
3881 QString(
"[cvSelectionPropertiesWidget] Selection frozen as: %1")
3884 QMessageBox::information(
this, tr(
"Freeze Selection"),
3885 tr(
"Selection frozen as: %1").arg(frozenName));
3891 void cvSelectionPropertiesWidget::onExtractClicked() {
3892 if (m_selectionData.
isEmpty()) {
3893 CVLog::Warning(
"[cvSelectionPropertiesWidget] No selection to extract");
3912 bool isSourceMesh =
false;
3916 isSourceMesh = (polyData->GetNumberOfPolys() > 0);
3918 QString(
"[cvSelectionPropertiesWidget::onExtractClicked] "
3919 "Source: %1 points, %2 cells, %3 polys -> %4")
3920 .arg(polyData->GetNumberOfPoints())
3921 .arg(polyData->GetNumberOfCells())
3922 .arg(polyData->GetNumberOfPolys())
3923 .arg(isSourceMesh ?
"mesh" :
"point cloud"));
3926 if (isCells && isSourceMesh) {
3928 onExportToMeshClicked();
3933 onExportToPointCloudClicked();
3940 void cvSelectionPropertiesWidget::onPlotOverTimeClicked() {
3943 if (m_selectionData.
isEmpty()) {
3944 QMessageBox::information(
this, tr(
"Plot Over Time"),
3945 tr(
"No selection data to plot."));
3952 QMessageBox::warning(
this, tr(
"Plot Over Time"),
3953 tr(
"Cannot access selection data for plotting."));
3961 QDialog* plotDialog =
new QDialog(
this);
3962 plotDialog->setWindowTitle(tr(
"Selection Data Distribution"));
3963 plotDialog->setMinimumSize(700, 500);
3964 plotDialog->setAttribute(Qt::WA_DeleteOnClose);
3966 QVBoxLayout* dialogLayout =
new QVBoxLayout(plotDialog);
3969 QHBoxLayout* controlLayout =
new QHBoxLayout();
3970 controlLayout->addWidget(
new QLabel(tr(
"Attribute:")));
3972 QComboBox* attributeCombo =
new QComboBox();
3978 attributeCombo->addItem(tr(
"X Coordinate"),
3979 QVariant::fromValue(QString(
"__X__")));
3980 attributeCombo->addItem(tr(
"Y Coordinate"),
3981 QVariant::fromValue(QString(
"__Y__")));
3982 attributeCombo->addItem(tr(
"Z Coordinate"),
3983 QVariant::fromValue(QString(
"__Z__")));
3987 vtkDataSetAttributes*
data = isPointData
3988 ?
static_cast<vtkDataSetAttributes*
>(
3989 polyData->GetPointData())
3990 :
static_cast<vtkDataSetAttributes*
>(
3991 polyData->GetCellData());
3994 for (
int i = 0; i <
data->GetNumberOfArrays(); ++i) {
3995 vtkDataArray* arr =
data->GetArray(i);
3996 if (arr && arr->GetName()) {
3997 attributeCombo->addItem(
3998 QString::fromUtf8(arr->GetName()),
3999 QVariant::fromValue(QString::fromUtf8(arr->GetName())));
4004 controlLayout->addWidget(attributeCombo);
4005 controlLayout->addStretch();
4006 dialogLayout->addLayout(controlLayout);
4010 customPlot->setMinimumHeight(400);
4017 dialogLayout->addWidget(customPlot);
4020 QVector<qint64> selectionIds = m_selectionData.
ids();
4023 auto updatePlot = [customPlot, polyData, selectionIds,
4024 isPointData](
const QString& attrName) {
4031 if (selectionIds.isEmpty()) {
4036 const QVector<qint64>& ids = selectionIds;
4039 QVector<double> xData;
4040 QVector<double> yData;
4041 xData.reserve(ids.size());
4042 yData.reserve(ids.size());
4045 vtkDataSetAttributes*
data =
4046 isPointData ?
static_cast<vtkDataSetAttributes*
>(
4047 polyData->GetPointData())
4048 :
static_cast<vtkDataSetAttributes*
>(
4049 polyData->GetCellData());
4051 vtkDataArray* timestampArray =
nullptr;
4054 const char* timestampNames[] = {
"TimeValue",
"Time",
4055 "timestamp",
"time_value",
4056 "TimeStep",
nullptr};
4057 for (
int i = 0; timestampNames[i] !=
nullptr; ++i) {
4058 timestampArray =
data->GetArray(timestampNames[i]);
4059 if (timestampArray)
break;
4064 for (
int idx = 0; idx < ids.size(); ++idx) {
4065 qint64
id = ids[idx];
4066 if (
id < 0)
continue;
4069 if (attrName ==
"__X__" || attrName ==
"__Y__" ||
4070 attrName ==
"__Z__") {
4071 if (id < polyData->GetNumberOfPoints()) {
4073 polyData->GetPoint(
id, pt);
4074 yVal = (attrName ==
"__X__") ? pt[0]
4075 : (attrName ==
"__Y__") ? pt[1]
4081 data->GetArray(attrName.toUtf8().constData());
4082 if (arr &&
id >= 0 && id < arr->GetNumberOfTuples()) {
4083 yVal = arr->GetTuple1(
id);
4090 if (timestampArray &&
id >= 0 &&
4091 id < timestampArray->GetNumberOfTuples()) {
4092 xVal = timestampArray->GetTuple1(
id);
4099 if (yData.isEmpty())
return;
4104 graph->
setPen(QPen(QColor(0, 100, 180), 2));
4106 graph->
setName(attrName.startsWith(
"__") ? attrName.mid(2, 1) +
" Coord"
4110 customPlot->
xAxis->
setLabel(timestampArray ? tr(
"Timestamp")
4113 ? attrName.mid(2, 1) +
" Coordinate"
4119 connect(attributeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
4120 [updatePlot, attributeCombo]() {
4121 QString attrName = attributeCombo->currentData().
toString();
4122 updatePlot(attrName);
4126 if (attributeCombo->count() > 0) {
4127 QString attrName = attributeCombo->currentData().toString();
4128 updatePlot(attrName);
4132 QDialogButtonBox* buttonBox =
new QDialogButtonBox(QDialogButtonBox::Close);
4133 connect(buttonBox, &QDialogButtonBox::rejected, plotDialog,
4135 dialogLayout->addWidget(buttonBox);
4139 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Plot Over Time: "
4140 "Showing distribution for %1 %2")
4141 .arg(m_selectionData.
count())
4146 void cvSelectionPropertiesWidget::onToggleColumnVisibility() {
4150 for (
int col = 0; col < m_spreadsheetTable->columnCount(); ++col) {
4151 QString header = m_spreadsheetTable->horizontalHeaderItem(col)->text();
4152 QAction* action = menu.addAction(header);
4153 action->setCheckable(
true);
4154 action->setChecked(!m_spreadsheetTable->isColumnHidden(col));
4155 action->setData(col);
4156 connect(action, &QAction::toggled, [
this, col](
bool visible) {
4157 m_spreadsheetTable->setColumnHidden(col, !visible);
4161 menu.exec(QCursor::pos());
4165 void cvSelectionPropertiesWidget::onToggleFieldDataClicked(
bool checked) {
4170 if (!m_spreadsheetTable) {
4180 m_spreadsheetTable->clear();
4181 m_spreadsheetTable->setRowCount(0);
4185 vtkFieldData* fieldData = polyData->GetFieldData();
4186 if (!fieldData || fieldData->GetNumberOfArrays() == 0) {
4188 m_spreadsheetTable->setColumnCount(1);
4189 m_spreadsheetTable->setHorizontalHeaderLabels(QStringList()
4190 << tr(
"Field Data"));
4191 m_spreadsheetTable->setRowCount(1);
4192 m_spreadsheetTable->setItem(
4193 0, 0,
new QTableWidgetItem(tr(
"No field data available")));
4198 QStringList headers;
4199 headers << tr(
"Index");
4200 for (
int i = 0; i < fieldData->GetNumberOfArrays(); ++i) {
4201 vtkAbstractArray* arr = fieldData->GetAbstractArray(i);
4202 if (arr && arr->GetName()) {
4203 headers << QString::fromStdString(arr->GetName());
4207 m_spreadsheetTable->setColumnCount(headers.size());
4208 m_spreadsheetTable->setHorizontalHeaderLabels(headers);
4211 vtkIdType maxTuples = 0;
4212 for (
int i = 0; i < fieldData->GetNumberOfArrays(); ++i) {
4213 vtkAbstractArray* arr = fieldData->GetAbstractArray(i);
4215 maxTuples = std::max(maxTuples, arr->GetNumberOfTuples());
4220 int rowCount = std::min(1000,
static_cast<int>(maxTuples));
4221 m_spreadsheetTable->setRowCount(rowCount);
4224 for (
int row = 0; row < rowCount; ++row) {
4228 m_spreadsheetTable->setItem(
4229 row, col++,
new QTableWidgetItem(QString::number(row)));
4232 for (
int i = 0; i < fieldData->GetNumberOfArrays(); ++i) {
4233 vtkAbstractArray* arr = fieldData->GetAbstractArray(i);
4234 if (arr && arr->GetName()) {
4236 if (row < arr->GetNumberOfTuples()) {
4237 vtkDataArray* dataArr = vtkDataArray::SafeDownCast(arr);
4239 valueStr = QString::number(dataArr->GetTuple1(row),
4243 vtkVariant v = arr->GetVariantValue(row);
4244 valueStr = QString::fromStdString(v.ToString());
4247 m_spreadsheetTable->setItem(row, col++,
4248 new QTableWidgetItem(valueStr));
4254 updateSpreadsheetData(polyData);
4263 void cvSelectionPropertiesWidget::onDataProducerChanged(
int index) {
4266 bool hasProducer = (index > 0);
4269 if (m_elementTypeCombo) m_elementTypeCombo->setEnabled(hasProducer);
4270 if (m_attributeCombo) m_attributeCombo->setEnabled(hasProducer);
4271 if (m_operatorCombo) m_operatorCombo->setEnabled(hasProducer);
4272 if (m_valueEdit) m_valueEdit->setEnabled(hasProducer);
4275 updateAttributeCombo();
4279 void cvSelectionPropertiesWidget::onElementTypeChanged(
int index) {
4282 updateAttributeCombo();
4286 void cvSelectionPropertiesWidget::onFindDataClicked() {
4288 if (m_queryRows.isEmpty()) {
4289 CVLog::Warning(
"[cvSelectionPropertiesWidget] No query rows available");
4293 QString dataProducer = m_dataProducerCombo
4294 ? m_dataProducerCombo->currentText()
4296 QString elementType = m_elementTypeCombo ? m_elementTypeCombo->currentText()
4298 bool isCell = (elementType == tr(
"Cell"));
4301 QStringList noValueOps = {tr(
"is min"), tr(
"is max"), tr(
"is <= mean"),
4305 QVector<QPair<QString, QString>>
4307 QStringList queryDescriptions;
4309 for (
int i = 0; i < m_queryRows.size(); ++i) {
4310 const QueryRow& row = m_queryRows[i];
4311 QString attribute = row.attributeCombo->currentText();
4312 QString op = row.operatorCombo->currentText();
4313 QString value = row.valueEdit->text();
4315 if (attribute.isEmpty()) {
4316 QMessageBox::warning(
4317 this, tr(
"Find Data"),
4318 tr(
"Please select an attribute in query row %1.")
4323 if (value.isEmpty() && !noValueOps.contains(op)) {
4324 QMessageBox::warning(
4325 this, tr(
"Find Data"),
4326 tr(
"Please enter a value in query row %1.").arg(i + 1));
4330 queryDescriptions.append(
4331 QString(
"%1 %2 %3").arg(attribute).arg(op).arg(value));
4334 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Find Data with %1 "
4335 "condition(s): %2 (Element: %3)")
4336 .arg(m_queryRows.size())
4337 .arg(queryDescriptions.join(
" AND "))
4341 if (!m_queryRows.isEmpty()) {
4342 const QueryRow& firstRow = m_queryRows[0];
4344 firstRow.attributeCombo->currentText(),
4345 firstRow.operatorCombo->currentText(),
4346 firstRow.valueEdit->text());
4351 if (!m_queryRows.isEmpty()) {
4352 const QueryRow& firstRow = m_queryRows[0];
4353 performFindData(firstRow.attributeCombo->currentText(),
4354 firstRow.operatorCombo->currentText(),
4355 firstRow.valueEdit->text(), isCell);
4362 if (m_queryRows.size() > 1) {
4364 QString(
"[cvSelectionPropertiesWidget] Multiple query "
4365 "conditions detected (%1 rows). "
4366 "Currently only the first condition is applied. "
4367 "Full AND logic implementation is pending.")
4368 .arg(m_queryRows.size()));
4374 void cvSelectionPropertiesWidget::onResetClicked() {
4376 for (
auto& row : m_queryRows) {
4377 if (row.attributeCombo && row.attributeCombo->count() > 0) {
4378 row.attributeCombo->setCurrentIndex(0);
4380 if (row.operatorCombo) {
4381 row.operatorCombo->setCurrentIndex(0);
4383 if (row.valueEdit) {
4384 row.valueEdit->clear();
4388 if (m_processIdSpinBox) {
4389 m_processIdSpinBox->setValue(-1);
4393 while (m_queriesLayout && m_queriesLayout->count() > 0) {
4394 QLayoutItem* item = m_queriesLayout->takeAt(0);
4395 if (item->widget()) {
4396 delete item->widget();
4401 CVLog::Print(
"[cvSelectionPropertiesWidget] Query reset to default.");
4405 void cvSelectionPropertiesWidget::onClearClicked() {
4412 CVLog::Print(
"[cvSelectionPropertiesWidget] Selection and query cleared.");
4416 void cvSelectionPropertiesWidget::addQueryRow(
int index,
4417 const QString& attribute,
4419 const QString& value) {
4421 index = m_queryRows.size();
4427 row.container =
new QWidget(m_createSelectionContainer);
4428 QHBoxLayout* rowLayout =
new QHBoxLayout(row.container);
4429 rowLayout->setContentsMargins(0, 0, 0, 0);
4430 rowLayout->setSpacing(3);
4433 row.attributeCombo =
new QComboBox(row.container);
4434 row.attributeCombo->setMinimumWidth(80);
4435 row.attributeCombo->setToolTip(tr(
"Select attribute to query"));
4436 rowLayout->addWidget(row.attributeCombo);
4439 row.operatorCombo =
new QComboBox(row.container);
4440 row.operatorCombo->addItems({tr(
"is"), tr(
">="), tr(
"<="), tr(
">"), tr(
"<"),
4441 tr(
"!="), tr(
"is min"), tr(
"is max"),
4442 tr(
"is <= mean"), tr(
"is >= mean")});
4443 row.operatorCombo->setToolTip(tr(
"Select comparison operator"));
4444 if (!op.isEmpty()) {
4445 int opIndex = row.operatorCombo->findText(op);
4447 row.operatorCombo->setCurrentIndex(opIndex);
4450 rowLayout->addWidget(row.operatorCombo);
4453 row.valueEdit =
new QLineEdit(row.container);
4454 row.valueEdit->setPlaceholderText(tr(
"value"));
4455 row.valueEdit->setToolTip(tr(
"Enter comparison value"));
4456 if (!value.isEmpty()) {
4457 row.valueEdit->setText(value);
4459 rowLayout->addWidget(row.valueEdit, 1);
4462 row.plusButton =
new QPushButton(row.container);
4463 QIcon plusIcon(
":/Resources/images/svg/pqPlus.svg");
4464 if (plusIcon.isNull()) {
4465 row.plusButton->setText(
"+");
4467 row.plusButton->setIcon(plusIcon);
4469 row.plusButton->setToolTip(tr(
"Add query condition"));
4470 row.plusButton->setMaximumWidth(32);
4471 rowLayout->addWidget(row.plusButton);
4474 row.minusButton =
new QPushButton(row.container);
4475 QIcon minusIcon(
":/Resources/images/svg/pqMinus.svg");
4476 if (minusIcon.isNull()) {
4477 row.minusButton->setText(
"-");
4479 row.minusButton->setIcon(minusIcon);
4481 row.minusButton->setToolTip(tr(
"Remove query condition"));
4482 row.minusButton->setMaximumWidth(32);
4483 rowLayout->addWidget(row.minusButton);
4485 row.container->setLayout(rowLayout);
4488 m_queriesLayout->insertWidget(index, row.container);
4489 m_queryRows.insert(index, row);
4492 connect(row.plusButton, &QPushButton::clicked, [
this, row]() {
4493 int idx = m_queryRows.indexOf(row);
4495 addQueryRow(idx + 1);
4499 connect(row.minusButton, &QPushButton::clicked, [
this, row]() {
4500 int idx = m_queryRows.indexOf(row);
4502 removeQueryRow(idx);
4507 if (index == 0 && !attribute.isEmpty()) {
4508 int attrIndex = row.attributeCombo->findText(attribute);
4509 if (attrIndex >= 0) {
4510 row.attributeCombo->setCurrentIndex(attrIndex);
4515 updateQueryRowButtons();
4518 QString(
"[cvSelectionPropertiesWidget] Added query row at index %1")
4523 void cvSelectionPropertiesWidget::removeQueryRow(
int index) {
4524 if (index < 0 || index >= m_queryRows.size()) {
4529 if (m_queryRows.size() <= 1) {
4531 "[cvSelectionPropertiesWidget] Cannot remove the last query "
4536 QueryRow row = m_queryRows.takeAt(index);
4539 m_queriesLayout->removeWidget(row.container);
4540 delete row.container;
4543 if (index == 0 && !m_queryRows.isEmpty()) {
4544 m_attributeCombo = m_queryRows[0].attributeCombo;
4545 m_operatorCombo = m_queryRows[0].operatorCombo;
4546 m_valueEdit = m_queryRows[0].valueEdit;
4550 updateQueryRowButtons();
4553 QString(
"[cvSelectionPropertiesWidget] Removed query row "
4559 void cvSelectionPropertiesWidget::updateQueryRowButtons() {
4560 bool canRemove = (m_queryRows.size() > 1);
4561 for (
auto& row : m_queryRows) {
4562 row.minusButton->setEnabled(canRemove);
4567 void cvSelectionPropertiesWidget::updateDataProducerCombo() {
4568 if (!m_dataProducerCombo) {
4570 "[cvSelectionPropertiesWidget] m_dataProducerCombo is null!");
4574 m_dataProducerCombo->clear();
4577 m_dataProducerCombo->addItem(tr(
"(none)"));
4583 vtkRendererCollection* renderers = pclVis->getRendererCollection();
4585 QSet<QString> addedNames;
4588 renderers->InitTraversal();
4589 vtkRenderer* renderer;
4590 while ((renderer = renderers->GetNextItem()) !=
nullptr) {
4591 vtkActorCollection* actors = renderer->GetActors();
4592 if (!actors)
continue;
4594 actors->InitTraversal();
4596 while ((actor = actors->GetNextActor()) !=
nullptr) {
4598 if (!actor->GetVisibility()) {
4606 vtkPolyData* polyData = vtkPolyData::SafeDownCast(
4607 actor->GetMapper() ? actor->GetMapper()->GetInput()
4610 vtkFieldData* fieldData = polyData->GetFieldData();
4613 vtkStringArray* datasetNameArray =
4614 vtkStringArray::SafeDownCast(
4615 fieldData->GetAbstractArray(
4617 if (datasetNameArray &&
4618 datasetNameArray->GetNumberOfTuples() > 0) {
4619 name = QString::fromStdString(
4620 datasetNameArray->GetValue(0));
4624 if (
name.isEmpty()) {
4625 vtkAbstractArray* nameArray =
4626 fieldData->GetAbstractArray(
"Name");
4628 nameArray->GetNumberOfTuples() > 0) {
4630 nameArray->GetVariantValue(0);
4631 name = QString::fromStdString(v.ToString());
4638 if (
name.isEmpty()) {
4643 if (!addedNames.contains(
name)) {
4644 m_dataProducerCombo->addItem(
name);
4645 addedNames.insert(
name);
4651 QString(
"[cvSelectionPropertiesWidget] Found %1 "
4653 .arg(addedNames.size()));
4658 updateAttributeCombo();
4664 onDataProducerChanged(m_dataProducerCombo->currentIndex());
4668 void cvSelectionPropertiesWidget::updateAttributeCombo() {
4670 if (m_queryRows.isEmpty()) {
4675 QStringList currentAttributes;
4676 for (
const auto& row : m_queryRows) {
4677 if (row.attributeCombo && row.attributeCombo->count() > 0) {
4678 currentAttributes.append(row.attributeCombo->currentText());
4680 currentAttributes.append(QString());
4685 for (
auto& row : m_queryRows) {
4686 if (row.attributeCombo) {
4687 row.attributeCombo->clear();
4694 vtkPolyData* polyData =
nullptr;
4696 bool hasExplicitProducer =
4697 m_dataProducerCombo && m_dataProducerCombo->currentIndex() > 0;
4700 if (pclVis && hasExplicitProducer) {
4701 QString producerName = m_dataProducerCombo->currentText();
4702 vtkRenderer* renderer =
4703 pclVis->getRendererCollection()->GetFirstRenderer();
4705 vtkActorCollection* actors = renderer->GetActors();
4706 actors->InitTraversal();
4708 while ((actor = actors->GetNextActor()) !=
nullptr) {
4709 if (!actor->GetVisibility() || !actor->GetPickable()) {
4712 vtkPolyData* actorPolyData = vtkPolyData::SafeDownCast(
4713 actor->GetMapper() ? actor->GetMapper()->GetInput()
4715 if (actorPolyData) {
4716 vtkFieldData* fieldData = actorPolyData->GetFieldData();
4718 vtkStringArray* nameArray =
4719 vtkStringArray::SafeDownCast(
4720 fieldData->GetAbstractArray(
4722 if (nameArray && nameArray->GetNumberOfTuples() > 0) {
4723 QString
name = QString::fromStdString(
4724 nameArray->GetValue(0));
4725 if (
name == producerName) {
4726 polyData = actorPolyData;
4740 .arg(producerName));
4742 m_attributeCombo->addItem(tr(
"ID"));
4749 if (!polyData && !hasExplicitProducer) {
4755 m_attributeCombo->addItem(tr(
"ID"));
4759 bool isCell = m_elementTypeCombo && m_elementTypeCombo->currentIndex() == 1;
4760 vtkDataSetAttributes* attrData =
4761 isCell ?
static_cast<vtkDataSetAttributes*
>(polyData->GetCellData())
4762 :
static_cast<vtkDataSetAttributes*
>(
4763 polyData->GetPointData());
4766 m_attributeCombo->addItem(tr(
"ID"));
4770 if (polyData && polyData->GetPoints()) {
4771 m_attributeCombo->addItem(tr(
"Points (Magnitude)"));
4772 m_attributeCombo->addItem(tr(
"Points (X)"));
4773 m_attributeCombo->addItem(tr(
"Points (Y)"));
4774 m_attributeCombo->addItem(tr(
"Points (Z)"));
4780 auto addArrayToCombo = [
this](vtkDataArray* array,
const QString&
name,
4781 const char* compNames[],
int numCompNames,
4784 int numComponents = array->GetNumberOfComponents();
4785 if (numComponents == 1) {
4786 m_attributeCombo->addItem(
name);
4787 }
else if (numComponents > 1) {
4791 m_attributeCombo->addItem(
name);
4794 m_attributeCombo->addItem(QString(
"%1 (Magnitude)").arg(
name));
4797 for (
int c = 0; c < numComponents; ++c) {
4799 const char* vtkCompName = array->GetComponentName(c);
4800 if (vtkCompName && strlen(vtkCompName) > 0) {
4801 compName = QString::fromUtf8(vtkCompName);
4802 }
else if (c < numCompNames && compNames) {
4803 compName = compNames[c];
4805 compName = QString::number(c);
4807 m_attributeCombo->addItem(
4808 QString(
"%1 (%2)").arg(
name, compName));
4814 QSet<QString> addedArrays;
4819 QSet<QString> colorArrayVariants = {
"RGB",
"Colors",
"rgba",
"rgb",
"RGBA"};
4833 vtkDataArray*
normals = attrData->GetNormals();
4834 bool hasNormals = (
normals !=
nullptr);
4842 for (
int i = 0; i < attrData->GetNumberOfArrays(); ++i) {
4843 vtkDataArray* arr = attrData->GetArray(i);
4844 if (arr && arr->GetNumberOfComponents() == 3) {
4845 const char*
name = arr->GetName();
4847 QString qname = QString::fromUtf8(
name).toLower();
4848 if (qname.contains(
"normal")) {
4861 vtkDataArray* nx = attrData->GetArray(
"normal_x");
4862 vtkDataArray* ny = attrData->GetArray(
"normal_y");
4863 vtkDataArray* nz = attrData->GetArray(
"normal_z");
4864 if (nx && ny && nz) {
4867 addedArrays.insert(
"normal_x");
4868 addedArrays.insert(
"normal_y");
4869 addedArrays.insert(
"normal_z");
4871 "[updateAttributeCombo] Found PCL-style separate "
4877 QString normalsName =
4880 ? QString::fromUtf8(
normals->GetName())
4883 m_attributeCombo->addItem(
4884 QString(
"%1 (Magnitude)").arg(normalsName));
4885 m_attributeCombo->addItem(QString(
"%1 (X)").arg(normalsName));
4886 m_attributeCombo->addItem(QString(
"%1 (Y)").arg(normalsName));
4887 m_attributeCombo->addItem(QString(
"%1 (Z)").arg(normalsName));
4888 addedArrays.insert(normalsName);
4889 addedArrays.insert(
"Normals");
4891 addedArrays.insert(QString::fromUtf8(
normals->GetName()));
4897 vtkDataArray* tcoords = attrData->GetTCoords();
4899 QString tcoordsName =
4900 (tcoords->GetName() && strlen(tcoords->GetName()))
4901 ? QString::fromUtf8(tcoords->GetName())
4903 static const char* tcoordsComps[] = {
"U",
"V",
"W"};
4904 addArrayToCombo(tcoords, tcoordsName, tcoordsComps, 3,
false);
4905 addedArrays.insert(tcoordsName);
4911 vtkDataArray* scalars = attrData->GetScalars();
4913 QString scalarsName =
4914 (scalars->GetName() && strlen(scalars->GetName()))
4915 ? QString::fromUtf8(scalars->GetName())
4921 int numComp = scalars->GetNumberOfComponents();
4923 static const char* rgbComps[] = {
"R",
"G",
"B"};
4924 addArrayToCombo(scalars, scalarsName, rgbComps, 3,
true);
4925 }
else if (numComp == 4) {
4926 static const char* rgbaComps[] = {
"R",
"G",
"B",
"A"};
4927 addArrayToCombo(scalars, scalarsName, rgbaComps, 4,
true);
4929 addArrayToCombo(scalars, scalarsName,
nullptr, 0,
false);
4931 addedArrays.insert(scalarsName);
4936 for (
const QString& variant : colorArrayVariants) {
4937 addedArrays.insert(variant);
4946 for (
int i = 0; i < attrData->GetNumberOfArrays(); ++i) {
4947 vtkDataArray* array = attrData->GetArray(i);
4948 if (!array)
continue;
4950 const char* arrayName = array->GetName();
4951 if (!arrayName || strlen(arrayName) == 0)
continue;
4953 QString
name = QString::fromUtf8(arrayName);
4958 name ==
"vtkOriginalPointIds" ||
name ==
"vtkOriginalCellIds" ||
4959 name ==
"vtkCompositeIndex" ||
name ==
"vtkBlockColors" ||
4960 name ==
"vtkGhostType") {
4965 if (addedArrays.contains(
name))
continue;
4966 addedArrays.insert(
name);
4968 int numComponents = array->GetNumberOfComponents();
4971 bool isColorArray =
false;
4972 QString lowerName =
name.toLower();
4973 if ((numComponents == 3 || numComponents == 4) &&
4974 (lowerName.contains(
"color") || lowerName.contains(
"rgb") ||
4975 lowerName.contains(
"rgba"))) {
4976 isColorArray =
true;
4981 bool alreadyHasColors =
false;
4982 for (
const QString& variant : colorArrayVariants) {
4983 if (addedArrays.contains(variant)) {
4984 alreadyHasColors =
true;
4988 if (alreadyHasColors) {
4990 addedArrays.insert(
name);
4995 for (
const QString& variant : colorArrayVariants) {
4996 addedArrays.insert(variant);
5000 if (numComponents == 1) {
5002 m_attributeCombo->addItem(
name);
5003 }
else if (numComponents > 1) {
5006 m_attributeCombo->addItem(
name);
5010 m_attributeCombo->addItem(
5011 QString(
"%1 (Magnitude)").arg(
name));
5015 for (
int c = 0; c < numComponents; ++c) {
5017 const char* vtkCompName = array->GetComponentName(c);
5018 if (vtkCompName && strlen(vtkCompName) > 0) {
5019 compName = QString::fromUtf8(vtkCompName);
5022 if (isColorArray && numComponents >= 3) {
5023 static const char* rgbaComps[] = {
"R",
"G",
"B",
5025 compName = rgbaComps[c];
5026 }
else if (numComponents == 3) {
5027 static const char* xyz[] = {
"X",
"Y",
"Z"};
5029 }
else if (numComponents == 4) {
5030 static const char* xyzw[] = {
"X",
"Y",
"Z",
"W"};
5032 }
else if (numComponents == 2) {
5033 static const char* xy[] = {
"X",
"Y"};
5036 compName = QString::number(c);
5039 m_attributeCombo->addItem(
5040 QString(
"%1 (%2)").arg(
name, compName));
5047 if (!isCell && polyData->GetPoints()) {
5048 m_attributeCombo->addItem(tr(
"Point"));
5049 }
else if (isCell) {
5050 m_attributeCombo->addItem(tr(
"Cell"));
5055 QStringList attributes;
5056 if (!m_queryRows.isEmpty() && m_queryRows[0].attributeCombo) {
5057 QComboBox* firstCombo = m_queryRows[0].attributeCombo;
5058 for (
int i = 0; i < firstCombo->count(); ++i) {
5059 attributes.append(firstCombo->itemText(i));
5063 for (
int rowIdx = 1; rowIdx < m_queryRows.size(); ++rowIdx) {
5064 if (m_queryRows[rowIdx].attributeCombo) {
5066 m_queryRows[rowIdx].attributeCombo->currentText();
5067 m_queryRows[rowIdx].attributeCombo->clear();
5068 m_queryRows[rowIdx].attributeCombo->addItems(attributes);
5071 if (!current.isEmpty()) {
5072 int idx = m_queryRows[rowIdx].attributeCombo->findText(
5075 m_queryRows[rowIdx].attributeCombo->setCurrentIndex(
5084 for (
auto& row : m_queryRows) {
5085 if (row.attributeCombo && row.attributeCombo->count() > 0 &&
5086 row.attributeCombo->currentIndex() < 0) {
5087 row.attributeCombo->setCurrentIndex(0);
5093 void cvSelectionPropertiesWidget::performFindData(
const QString& attribute,
5095 const QString& value,
5100 QMessageBox::warning(
this, tr(
"Find Data"),
5101 tr(
"No data available to query."));
5109 QString arrayName = attribute;
5110 int componentIndex = -1;
5111 bool isMagnitude =
false;
5112 bool isIdQuery = (attribute == tr(
"ID"));
5115 bool isPointQuery = (attribute == tr(
"Point"));
5116 bool isCellQuery = (attribute == tr(
"Cell"));
5120 static QRegularExpression componentRegex(R
"((.*)\s+\((.*)\)\s*$)");
5121 QRegularExpressionMatch match = componentRegex.match(attribute);
5123 if (match.hasMatch()) {
5124 arrayName = match.captured(1).trimmed();
5125 QString componentStr = match.captured(2).trimmed();
5128 if (componentStr.toLower() ==
"magnitude") {
5130 }
else if (componentStr ==
"X" || componentStr ==
"R" ||
5131 componentStr ==
"U" || componentStr ==
"0") {
5133 }
else if (componentStr ==
"Y" || componentStr ==
"G" ||
5134 componentStr ==
"V" || componentStr ==
"1") {
5136 }
else if (componentStr ==
"Z" || componentStr ==
"B" ||
5137 componentStr ==
"W" || componentStr ==
"2") {
5139 }
else if (componentStr ==
"A" || componentStr ==
"3") {
5144 componentIndex = componentStr.toInt(&ok);
5145 if (!ok) componentIndex = 0;
5150 vtkPoints*
points = polyData->GetPoints();
5153 vtkDataArray* dataArray =
nullptr;
5155 if (!isIdQuery && !isPointQuery && !isCellQuery) {
5156 vtkDataSetAttributes* attrData =
5157 isCell ?
static_cast<vtkDataSetAttributes*
>(
5158 polyData->GetCellData())
5159 :
static_cast<vtkDataSetAttributes*
>(
5160 polyData->GetPointData());
5163 dataArray = attrData->GetArray(arrayName.toUtf8().constData());
5170 vtkDataArray*
normals = attrData->GetNormals();
5172 QString normalsName =
5174 ? QString::fromUtf8(
normals->GetName())
5176 if (arrayName == normalsName || arrayName == tr(
"Normals")) {
5183 vtkDataArray* tcoords = attrData->GetTCoords();
5185 QString tcoordsName =
5186 tcoords->GetName() && strlen(tcoords->GetName())
5187 ? QString::fromUtf8(tcoords->GetName())
5189 if (arrayName == tcoordsName ||
5190 arrayName == tr(
"TCoords")) {
5191 dataArray = tcoords;
5198 vtkDataArray* scalars = attrData->GetScalars();
5200 QString scalarsName =
5201 scalars->GetName() && strlen(scalars->GetName())
5202 ? QString::fromUtf8(scalars->GetName())
5209 if (arrayName == scalarsName || arrayName == tr(
"RGB") ||
5210 arrayName == tr(
"RGBA") || arrayName == tr(
"Colors")) {
5211 dataArray = scalars;
5218 QMessageBox::warning(
5219 this, tr(
"Find Data"),
5220 tr(
"Attribute '%1' not found.").arg(arrayName));
5226 double queryValue = 0.0;
5227 if (!value.isEmpty()) {
5229 queryValue = value.toDouble(&ok);
5231 QMessageBox::warning(
this, tr(
"Find Data"),
5232 tr(
"Invalid numeric value: %1").arg(value));
5238 auto getValue = [&](vtkIdType i) ->
double {
5240 return static_cast<double>(i);
5241 }
else if (isPointQuery &&
points) {
5245 return std::sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2]);
5246 }
else if (isCellQuery) {
5249 return static_cast<double>(i);
5250 }
else if (dataArray) {
5251 int numComponents = dataArray->GetNumberOfComponents();
5252 if (isMagnitude && numComponents > 1) {
5253 double* tuple = dataArray->GetTuple(i);
5255 for (
int c = 0; c < numComponents; ++c) {
5256 sum += tuple[c] * tuple[c];
5258 return std::sqrt(sum);
5259 }
else if (componentIndex >= 0 && componentIndex < numComponents) {
5260 return dataArray->GetComponent(i, componentIndex);
5262 return dataArray->GetTuple1(i);
5269 double minVal = 0.0, maxVal = 0.0, meanVal = 0.0;
5270 vtkIdType numElements = isCell ? polyData->GetNumberOfCells()
5271 : polyData->GetNumberOfPoints();
5273 if ((dataArray || isPointQuery || isCellQuery) &&
5274 (op == tr(
"is min") || op == tr(
"is max") || op == tr(
"is <= mean") ||
5275 op == tr(
"is >= mean"))) {
5277 minVal = std::numeric_limits<double>::max();
5278 maxVal = std::numeric_limits<double>::lowest();
5280 for (vtkIdType i = 0; i < numElements; ++i) {
5281 double val = getValue(i);
5283 minVal = std::min(minVal, val);
5284 maxVal = std::max(maxVal, val);
5286 meanVal = sum / numElements;
5290 QVector<qint64> matchingIds;
5292 for (vtkIdType i = 0; i < numElements; ++i) {
5293 double val = getValue(i);
5294 bool matchResult =
false;
5296 if (op == tr(
"is") || op == tr(
"==")) {
5297 matchResult = (std::abs(val - queryValue) < 1
e-9);
5298 }
else if (op == tr(
">=") || op ==
"is >=") {
5299 matchResult = (val >= queryValue);
5300 }
else if (op == tr(
"<=") || op ==
"is <=") {
5301 matchResult = (val <= queryValue);
5302 }
else if (op == tr(
">")) {
5303 matchResult = (val > queryValue);
5304 }
else if (op == tr(
"<")) {
5305 matchResult = (val < queryValue);
5306 }
else if (op == tr(
"!=")) {
5307 matchResult = (std::abs(val - queryValue) >= 1
e-9);
5308 }
else if (op == tr(
"is min")) {
5309 matchResult = (std::abs(val - minVal) < 1
e-9);
5310 }
else if (op == tr(
"is max")) {
5311 matchResult = (std::abs(val - maxVal) < 1
e-9);
5312 }
else if (op == tr(
"is <= mean")) {
5313 matchResult = (val <= meanVal);
5314 }
else if (op == tr(
"is >= mean")) {
5315 matchResult = (val >= meanVal);
5319 matchingIds.append(
static_cast<qint64
>(i));
5323 CVLog::Print(QString(
"[cvSelectionPropertiesWidget] Find Data: Found %1 "
5324 "matching elements")
5325 .arg(matchingIds.size()));
5327 if (matchingIds.isEmpty()) {
5328 QMessageBox::information(
this, tr(
"Find Data"),
5329 tr(
"No elements match the query criteria."));
5339 m_selectionData = newSelection;
5343 if (m_highlighter) {
5355 updateSpreadsheetData(polyData);
5357 QMessageBox::information(
this, tr(
"Find Data"),
5358 tr(
"Selected %1 %2(s) matching '%3 %4 %5'")
5359 .arg(matchingIds.size())
5360 .arg(isCell ? tr(
"cell") : tr(
"point"))
5363 .arg(value.isEmpty() ? QString() : value));
5367 void cvSelectionPropertiesWidget::onSpreadsheetItemClicked(
5368 QTableWidgetItem* item) {
5371 int row = item->row();
5374 QTableWidgetItem* idItem = m_spreadsheetTable->item(row, 0);
5376 qint64
id = idItem->data(Qt::UserRole).toLongLong();
5377 highlightSingleItem(
id);
5386 void cvSelectionPropertiesWidget::updateSelectionEditorTable() {
5387 m_selectionEditorTable->setRowCount(m_savedSelections.size());
5389 for (
int i = 0; i < m_savedSelections.size(); ++i) {
5390 const SavedSelection& sel = m_savedSelections[i];
5393 QTableWidgetItem* nameItem =
new QTableWidgetItem(sel.name);
5394 m_selectionEditorTable->setItem(i, 0, nameItem);
5397 QTableWidgetItem* typeItem =
new QTableWidgetItem(sel.type);
5398 m_selectionEditorTable->setItem(i, 1, typeItem);
5401 QTableWidgetItem* colorItem =
new QTableWidgetItem(sel.color.name());
5402 colorItem->setBackground(sel.color);
5403 colorItem->setForeground(sel.color.lightness() > 128 ?
Qt::black
5405 m_selectionEditorTable->setItem(i, 2, colorItem);
5408 m_selectionEditorTable->resizeColumnsToContents();
5412 QString cvSelectionPropertiesWidget::generateSelectionName() {
5413 return QString(
"s%1").arg(m_selectionNameCounter++);
5417 QColor cvSelectionPropertiesWidget::generateSelectionColor()
const {
5418 int index = m_savedSelections.size() % s_selectionColorsCount;
5419 return s_selectionColors[index];
5424 m_dataProducerName =
name;
5425 if (m_dataProducerValue) {
5426 m_dataProducerValue->setText(
name.isEmpty() ? tr(
"(none)") :
name);
5430 if (m_selectedDataLabel) {
5431 m_selectedDataLabel->setText(
5432 QString(
"<b>Selected Data (%1)</b>")
5433 .arg(
name.isEmpty() ? tr(
"none") :
name));
5439 updateDataProducerCombo();
5443 void cvSelectionPropertiesWidget::updateSpreadsheetData(
5445 if (!polyData || !m_spreadsheetTable) {
5449 bool isPointData = m_attributeTypeCombo
5450 ? (m_attributeTypeCombo->currentIndex() == 0)
5454 m_spreadsheetTable->clear();
5455 m_spreadsheetTable->setRowCount(0);
5459 customSelection ? *customSelection : m_selectionData;
5466 const QVector<qint64>& ids = selection.
ids();
5467 if (ids.isEmpty()) {
5474 auto getArrayHeaders = [](vtkDataArray* arr) -> QStringList {
5475 QStringList headers;
5476 if (!arr || !arr->GetName())
return headers;
5478 QString baseName = QString::fromStdString(arr->GetName());
5479 int numComponents = arr->GetNumberOfComponents();
5486 if (numComponents == 1) {
5488 headers << baseName;
5494 for (
int c = 0; c < numComponents; ++c) {
5495 headers << baseName;
5498 headers << QString(
"%1_Magnitude").arg(baseName);
5504 QStringList headers;
5505 headers << (isPointData ? tr(
"Point ID") : tr(
"Cell ID"));
5509 vtkPointData* pointData = polyData->GetPointData();
5511 for (
int i = 0; i < pointData->GetNumberOfArrays(); ++i) {
5512 vtkDataArray* arr = pointData->GetArray(i);
5513 headers << getArrayHeaders(arr);
5519 headers << tr(
"Points") << tr(
"Points") << tr(
"Points")
5520 << tr(
"Points_Magnitude");
5522 headers << tr(
"Type") << tr(
"Num Points");
5525 vtkCellData* cellData = polyData->GetCellData();
5527 for (
int i = 0; i < cellData->GetNumberOfArrays(); ++i) {
5528 vtkDataArray* arr = cellData->GetArray(i);
5529 headers << getArrayHeaders(arr);
5534 m_spreadsheetTable->setColumnCount(headers.size());
5535 m_spreadsheetTable->setHorizontalHeaderLabels(headers);
5538 m_spreadsheetTable->horizontalHeader()->setSectionResizeMode(
5539 QHeaderView::ResizeToContents);
5543 std::min(1000,
static_cast<int>(ids.size()));
5544 m_spreadsheetTable->setRowCount(rowCount);
5546 for (
int row = 0; row < rowCount; ++row) {
5547 qint64
id = ids[row];
5551 QTableWidgetItem* idItem =
new QTableWidgetItem(QString::number(
id));
5552 idItem->setData(Qt::UserRole,
static_cast<qlonglong
>(
id));
5553 m_spreadsheetTable->setItem(row, col++, idItem);
5558 if (
id >= 0 && id < polyData->GetNumberOfPoints()) {
5559 vtkPointData* pointData = polyData->GetPointData();
5561 for (
int i = 0; i < pointData->GetNumberOfArrays(); ++i) {
5562 vtkDataArray* arr = pointData->GetArray(i);
5563 if (arr && arr->GetName() &&
5564 id < arr->GetNumberOfTuples()) {
5565 int numComponents = arr->GetNumberOfComponents();
5567 QString::fromStdString(arr->GetName());
5570 if (baseName.startsWith(
"vtk",
5575 if (numComponents == 1) {
5576 double value = arr->GetTuple1(
id);
5577 m_spreadsheetTable->setItem(
5579 new QTableWidgetItem(QString::number(
5584 double* tuple = arr->GetTuple(
id);
5587 for (
int c = 0; c < numComponents; ++c) {
5588 m_spreadsheetTable->setItem(
5590 new QTableWidgetItem(
5591 QString::number(tuple[c],
5596 double magnitude = 0.0;
5597 for (
int c = 0; c < numComponents; ++c) {
5598 magnitude += tuple[c] * tuple[c];
5600 magnitude = std::sqrt(magnitude);
5601 m_spreadsheetTable->setItem(
5603 new QTableWidgetItem(QString::number(
5604 magnitude,
'g', 6)));
5606 }
else if (arr && arr->GetName()) {
5608 QString::fromStdString(arr->GetName());
5610 if (baseName.startsWith(
"vtk",
5615 QStringList arrayHeaders = getArrayHeaders(arr);
5616 for (
int c = 0; c < arrayHeaders.size(); ++c) {
5617 m_spreadsheetTable->setItem(
5619 new QTableWidgetItem(tr(
"N/A")));
5628 polyData->GetPoint(
id, pt);
5630 m_spreadsheetTable->setItem(
5632 new QTableWidgetItem(QString::number(pt[0],
'g', 6)));
5633 m_spreadsheetTable->setItem(
5635 new QTableWidgetItem(QString::number(pt[1],
'g', 6)));
5636 m_spreadsheetTable->setItem(
5638 new QTableWidgetItem(QString::number(pt[2],
'g', 6)));
5640 double mag = std::sqrt(pt[0] * pt[0] + pt[1] * pt[1] +
5642 m_spreadsheetTable->setItem(
5644 new QTableWidgetItem(QString::number(mag,
'g', 6)));
5648 if (
id >= 0 && id < polyData->GetNumberOfCells()) {
5649 vtkCell* cell = polyData->GetCell(
id);
5653 switch (cell->GetCellType()) {
5655 typeName = tr(
"Triangle");
5658 typeName = tr(
"Quad");
5661 typeName = tr(
"Polygon");
5664 typeName = tr(
"Line");
5667 typeName = tr(
"Vertex");
5670 typeName = tr(
"Unknown");
5673 m_spreadsheetTable->setItem(row, col++,
5674 new QTableWidgetItem(typeName));
5677 m_spreadsheetTable->setItem(
5679 new QTableWidgetItem(QString::number(
5680 cell->GetNumberOfPoints())));
5684 vtkCellData* cellData = polyData->GetCellData();
5686 for (
int i = 0; i < cellData->GetNumberOfArrays(); ++i) {
5687 vtkDataArray* arr = cellData->GetArray(i);
5688 if (arr && arr->GetName() &&
5689 id < arr->GetNumberOfTuples()) {
5690 int numComponents = arr->GetNumberOfComponents();
5691 if (numComponents == 1) {
5692 double value = arr->GetTuple1(
id);
5693 m_spreadsheetTable->setItem(
5695 new QTableWidgetItem(QString::number(
5701 for (
int c = 0; c < numComponents; ++c) {
5702 values << QString::number(
5703 arr->GetComponent(
id, c),
'g', 6);
5705 m_spreadsheetTable->setItem(
5707 new QTableWidgetItem(
5708 QString(
"(%1)").arg(
5709 values.join(
", "))));
5711 }
else if (arr && arr->GetName()) {
5713 m_spreadsheetTable->setItem(
5715 new QTableWidgetItem(tr(
"N/A")));
5723 m_spreadsheetTable->resizeColumnsToContents();
5726 "spreadsheet with %1 rows")
5731 void cvSelectionPropertiesWidget::setupCollapsibleGroupBox(
5732 QGroupBox* groupBox) {
5733 if (!groupBox)
return;
5737 connect(groupBox, &QGroupBox::toggled, [groupBox](
bool checked) {
5738 QLayout* layout = groupBox->layout();
5739 if (!layout)
return;
5741 for (
int i = 0; i < layout->count(); ++i) {
5742 QLayoutItem* item = layout->itemAt(i);
5743 if (item->widget()) {
5744 item->widget()->setVisible(checked);
5746 if (item->layout()) {
5747 for (int j = 0; j < item->layout()->count(); ++j) {
5748 QLayoutItem* subItem = item->layout()->itemAt(j);
5749 if (subItem->widget()) {
5750 subItem->widget()->setVisible(checked);
QSet< T > qSetFromVector(const QVector< T > &vec)
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
static bool Print(const char *format,...)
Prints out a formatted message in console.
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
static bool Error(const char *format,...)
Display an error dialog with formatted message.
void UpdateScreen()
UpdateScreen - Updates/refreshes the render window This method forces a render update after actor cha...
void setPen(const QPen &pen)
void setName(const QString &name)
void setLabel(const QString &str)
A plottable representing a graph in a plot.
void setScatterStyle(const QCPScatterStyle &style)
void setData(QSharedPointer< QCPGraphDataContainer > data)
void setFont(const QFont &font)
Represents the visual appearance of scatter points.
@ ssCircle
\enumimage{ssCircle.png} a circle
The central class of the library. This is the QWidget which displays the plot and interacts with the ...
void setInteractions(const QCP::Interactions &interactions)
int plottableCount() const
QCPGraph * addGraph(QCPAxis *keyAxis=0, QCPAxis *valueAxis=0)
Q_SLOT void replot(QCustomPlot::RefreshPriority refreshPriority=QCustomPlot::rpRefreshHint)
Q_SLOT void rescaleAxes(bool onlyVisiblePlottables=false)
bool removePlottable(QCPAbstractPlottable *plottable)
virtual void setVisible(bool state)
Sets entity visibility.
Hierarchical CLOUDVIEWER Object.
virtual unsigned size() const override
Returns the number of triangles.
virtual QString getName() const
Returns object name.
virtual void setName(const QString &name)
Sets object name.
virtual void setEnabled(bool state)
Sets the "enabled" property.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
unsigned size() const override
QHeaderView that supports showing multiple sections as one.
Selection algebra operations.
static cvSelectionData complementOf(vtkPolyData *polyData, const cvSelectionData &input)
Compute complement of a selection.
static cvSelectionData extractBoundary(vtkPolyData *polyData, const cvSelectionData &input)
Extract boundary elements of selection.
static cvSelectionData unionOf(const cvSelectionData &a, const cvSelectionData &b)
Compute union of two selections.
Operation
Algebra operations Using enum class to avoid macro conflicts (e.g., DIFFERENCE may be defined as a ma...
static cvSelectionData symmetricDifferenceOf(const cvSelectionData &a, const cvSelectionData &b)
Compute symmetric difference of two selections.
static cvSelectionData intersectionOf(const cvSelectionData &a, const cvSelectionData &b)
Compute intersection of two selections.
Selection annotation manager.
void applyLabelProperties(const cvSelectionLabelPropertiesDialog::LabelProperties &props, bool isCellLabel)
Apply label properties to all annotations (for cell or point labels)
void setDefaultLabelProperties(const cvSelectionLabelPropertiesDialog::LabelProperties &props, bool isCellLabel)
Set default label properties for new annotations.
QString addAnnotation(const cvSelectionData &selection, const QString &text, bool autoPosition=true)
Add annotation for a selection.
Lightweight base class for all selection-related components.
PclUtils::PCLVis * getPCLVis() const
Get PCLVis instance (for VTK-specific operations)
virtual vtkPolyData * getPolyDataForSelection(const cvSelectionData *selectionData=nullptr)
Get polyData using ParaView-style priority (centralized method)
Encapsulates selection data without exposing VTK types.
FieldAssociation fieldAssociation() const
Get field association.
QString fieldTypeString() const
Get human-readable field type string.
FieldAssociation
Field association for selection.
@ CELLS
Selection applies to cells.
@ POINTS
Selection applies to points.
bool isEmpty() const
Check if selection is empty.
QVector< qint64 > ids() const
Get selected IDs as a vector (copy)
bool hasActorInfo() const
Check if actor information is available.
int count() const
Get number of selected items.
void clear()
Clear the selection.
vtkPolyData * primaryPolyData() const
Get the primary (front-most) polyData.
vtkActor * primaryActor() const
Get the primary (front-most) actor.
static ccPointCloud * exportToPointCloud(vtkPolyData *polyData, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected points to ccPointCloud.
static ccMesh * exportFromSourceMesh(ccMesh *sourceMesh, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected cells directly from source ccMesh.
static ccMesh * exportToMesh(vtkPolyData *polyData, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected cells to ccMesh.
static ccPointCloud * exportFromSourceCloud(ccPointCloud *sourceCloud, const cvSelectionData &selectionData, const ExportOptions &options=ExportOptions())
Export selected points directly from source ccPointCloud.
Helper class for highlighting selected elements in the visualizer.
void setHighlightQColor(const QColor &color, HighlightMode mode=SELECTED)
Set highlight color from QColor.
QString getCellLabelArrayName() const
Get current cell label array name.
bool highlightSelection(const vtkSmartPointer< vtkIdTypeArray > &selection, int fieldAssociation, HighlightMode mode=SELECTED)
Highlight selected elements (automatically gets polyData from visualizer)
const SelectionLabelProperties & getLabelProperties(bool interactive=false) const
Get label properties for selection mode.
void colorChanged(int mode)
Emitted when any highlight color changes.
void opacityChanged(int mode)
Emitted when any opacity changes.
void labelPropertiesChanged(bool interactive)
Emitted when label properties change.
HighlightMode
Highlight mode (Enhanced multi-level highlighting)
void setHighlightColor(double r, double g, double b, HighlightMode mode=SELECTED)
Set highlight color.
const double * getHighlightColor(HighlightMode mode) const
Get highlight color for a specific mode.
void setCellLabelArray(const QString &arrayName, bool visible=true)
Set the cell label array name.
void setLabelProperties(const SelectionLabelProperties &props, bool interactive=false)
Set label properties for selection mode.
QString getPointLabelArrayName() const
Get current point label array name.
bool isCellLabelVisible() const
Check if cell labels are visible.
double getHighlightOpacity(HighlightMode mode) const
Get highlight opacity for a specific mode.
QColor getHighlightQColor(HighlightMode mode) const
Get highlight color as QColor.
bool isPointLabelVisible() const
Check if point labels are visible.
void setPointLabelArray(const QString &arrayName, bool visible=true)
Set the point label array name.
void setHighlightOpacity(double opacity, HighlightMode mode=SELECTED)
Set highlight opacity.
Dialog for editing selection label properties.
void propertiesApplied(const LabelProperties &props)
Emitted when Apply is clicked.
Central manager for all selection operations in the view.
ccPointCloud * getSourcePointCloud() const
Get the source object as ccPointCloud.
void setCurrentSelection(const cvSelectionData &selectionData, bool resetLayers=true)
Set the current selection data.
bool isSourceObjectValid() const
Check if the source object is still valid.
cvSelectionAlgebra * getAlgebra()
Get the selection algebra utility.
vtkPolyData * getPolyData() const
Get the underlying polyData for selection operations.
ccHObject * getSourceObject() const
Get the source object for the current selection.
static cvViewSelectionManager * instance()
Get the singleton instance.
ccMesh * getSourceMesh() const
Get the source object as ccMesh.
cvSelectionAnnotationManager * getAnnotations()
Get the annotation manager.
cvSelectionHighlighter * getHighlighter()
Get the shared highlighter.
constexpr QRegularExpression::PatternOption CaseInsensitive
Tensor Minimum(const Tensor &input, const Tensor &other)
Computes the element-wise minimum of input and other. The tensors must have same data type and device...
Tensor Maximum(const Tensor &input, const Tensor &other)
Computes the element-wise maximum of input and other. The tensors must have same data type and device...
void Resize(const core::Tensor &src_im, core::Tensor &dst_im, Image::InterpType interp_type)
std::string toString(T x)
constexpr Rgb black(0, 0, 0)
constexpr Rgb white(MAX, MAX, MAX)
Label properties for selection annotations.
QString pointLabelFontFamily
QString cellLabelFontFamily
Options for exporting selections.
Label properties structure.
QString pointLabelFontFamily
QString cellLabelFontFamily