@@ -33,6 +33,16 @@ foucaultView::foucaultView(QWidget *parent, SurfaceManager *sm) :
3333 connect (this , &QWidget::customContextMenuRequested, this ,
3434 &foucaultView::showContextMenu);
3535 setContextMenuPolicy (Qt::CustomContextMenu);
36+
37+ // Load Grid Settings with the "ronchiGrid" key
38+ m_gridMode = static_cast <GridMode>(set.value (" ronchiGrid/mode" , (int )GridMode::None).toInt ());
39+ m_gridSpacing = set.value (" ronchiGrid/spacing" , 10.0 ).toDouble ();
40+ m_gridLineWidth = set.value (" ronchiGrid/lineWidth" , 1 ).toInt ();
41+ m_showUnitLabels = set.value (" ronchiGrid/showLabels" , true ).toBool ();
42+
43+ // Using name() and string check for robust color persistence
44+ m_gridColor = QColor (set.value (" ronchiGrid/color" , " #00FFFF" ).toString ()); // Default Cyan
45+ m_textColor = QColor (set.value (" ronchiGrid/textColor" , " #FFFFFF" ).toString ()); // Default White
3646}
3747
3848
@@ -78,10 +88,176 @@ void foucaultView::showContextMenu(QPoint pos)
7888 connect (showAllRonchi, &QAction::triggered,this , &foucaultView::showSelectedRonchiImages);
7989 myMenu.addAction (showAllRonchi);
8090
91+ QAction *showGrid = new QAction (" Show Circular Grid" );
92+ connect (showGrid, &QAction::triggered, this , &foucaultView::showGrid);
93+ myMenu.addSeparator ();
94+ myMenu.addAction (showGrid);
95+
8196 // Show context menu at handling position
8297 myMenu.exec (globalPos);
8398}
8499
100+ void foucaultView::showGrid () {
101+ QDialog dlg (this );
102+ dlg.setWindowTitle (" Ronchi Grid Settings" );
103+ QFormLayout form (&dlg);
104+
105+ // Create UI Elements
106+ QComboBox *unitCombo = new QComboBox (&dlg);
107+ unitCombo->addItems ({" None" , " Inches" , " Millimeters" , " Percentage" });
108+ unitCombo->setCurrentIndex ((int )m_gridMode);
109+
110+ QDoubleSpinBox *spacingSpin = new QDoubleSpinBox (&dlg);
111+ spacingSpin->setRange (0.01 , 1000.0 );
112+ spacingSpin->setValue (m_gridSpacing);
113+
114+ QSpinBox *widthSpin = new QSpinBox (&dlg);
115+ widthSpin->setRange (1 , 10 );
116+ widthSpin->setValue (m_gridLineWidth);
117+
118+ QCheckBox *textToggle = new QCheckBox (" Show unit labels" , &dlg);
119+ textToggle->setChecked (m_showUnitLabels);
120+
121+ // Color storage (using local temps for the dialog session)
122+ struct State { QColor grid; QColor text; } colors = { m_gridColor, m_textColor };
123+
124+ QPushButton *btnGridCol = new QPushButton (" Grid Color" );
125+ QPushButton *btnTextCol = new QPushButton (" Text Color" );
126+
127+ connect (btnGridCol, &QPushButton::clicked, [&]() {
128+ QColor c = QColorDialog::getColor (colors.grid , this );
129+ if (c.isValid ()) colors.grid = c;
130+ });
131+ connect (btnTextCol, &QPushButton::clicked, [&]() {
132+ QColor c = QColorDialog::getColor (colors.text , this );
133+ if (c.isValid ()) colors.text = c;
134+ });
135+
136+ form.addRow (" Grid Units:" , unitCombo);
137+ form.addRow (" Spacing Value:" , spacingSpin);
138+ form.addRow (" Line Width (px):" , widthSpin);
139+ form.addRow (" Labels:" , textToggle);
140+ form.addRow (" Grid Color:" , btnGridCol);
141+ form.addRow (" Text Color:" , btnTextCol);
142+
143+ // Button Box with Reset
144+ QDialogButtonBox buttonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dlg);
145+ QPushButton *resetBtn = buttonBox.addButton (" Reset to Defaults" , QDialogButtonBox::ResetRole);
146+ form.addRow (&buttonBox);
147+
148+ // Reset Logic
149+ connect (resetBtn, &QPushButton::clicked, [&]() {
150+ unitCombo->setCurrentIndex (0 ); // None
151+ spacingSpin->setValue (10.0 );
152+ widthSpin->setValue (1 );
153+ textToggle->setChecked (true );
154+ colors.grid = Qt::cyan;
155+ colors.text = Qt::white;
156+ });
157+
158+ connect (&buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
159+ connect (&buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
160+
161+ if (dlg.exec () == QDialog::Accepted) {
162+ m_gridMode = (GridMode)unitCombo->currentIndex ();
163+ m_gridSpacing = spacingSpin->value ();
164+ m_gridLineWidth = widthSpin->value ();
165+ m_showUnitLabels = textToggle->isChecked ();
166+ m_gridColor = colors.grid ;
167+ m_textColor = colors.text ;
168+
169+ // Save to QSettings with "ronchiGrid" prefix
170+ QSettings set;
171+ set.setValue (" ronchiGrid/mode" , (int )m_gridMode);
172+ set.setValue (" ronchiGrid/spacing" , m_gridSpacing);
173+ set.setValue (" ronchiGrid/lineWidth" , m_gridLineWidth);
174+ set.setValue (" ronchiGrid/showLabels" , m_showUnitLabels);
175+ set.setValue (" ronchiGrid/color" , m_gridColor.name ());
176+ set.setValue (" ronchiGrid/textColor" , m_textColor.name ());
177+
178+ on_makePb_clicked ();
179+ }
180+ }
181+
182+ void foucaultView::drawGridOverlay (QImage &img) {
183+ if (m_gridMode == GridMode::None || !m_wf || m_gridSpacing <= 0 ) return ;
184+
185+ QPainter painter (&img);
186+ painter.setRenderHint (QPainter::Antialiasing);
187+ painter.setRenderHint (QPainter::TextAntialiasing);
188+
189+ // 1. Grid Pen (Using your saved settings)
190+ QPen gridPen (m_gridColor, m_gridLineWidth, Qt::DotLine);
191+ painter.setPen (gridPen);
192+
193+ // 2. Dynamic Font Scaling
194+ // Scales between 10pt and 16pt based on image height to stay proportional
195+ int dynamicSize = qBound (10 , img.height () / 33 , 16 );
196+ QFont font (" Arial" , dynamicSize, QFont::Bold);
197+ painter.setFont (font);
198+
199+ int w = img.width ();
200+ int h = img.height ();
201+ int centerX = w / 2 ;
202+ int centerY = h / 2 ;
203+ int maxPixelRadius = w / 2 ;
204+
205+ mirrorDlg *md = mirrorDlg::get_Instance ();
206+ double mirrorRadiusMM = md->diameter / 2.0 ;
207+
208+ // 3. Determine physics-to-pixel scale
209+ double stepSizeMM = 0 ;
210+ if (m_gridMode == GridMode::Millimeters) stepSizeMM = m_gridSpacing;
211+ else if (m_gridMode == GridMode::Inches) stepSizeMM = m_gridSpacing * 25.4 ;
212+ else if (m_gridMode == GridMode::Percentage) stepSizeMM = (m_gridSpacing / 100.0 ) * mirrorRadiusMM;
213+
214+ if (stepSizeMM <= 0 ) return ;
215+
216+ // 4. Draw Rings and Labels
217+ for (double currentMM = stepSizeMM; currentMM <= mirrorRadiusMM; currentMM += stepSizeMM) {
218+ int rPx = (int )((currentMM / mirrorRadiusMM) * maxPixelRadius);
219+
220+ painter.setPen (gridPen);
221+ painter.drawEllipse (QPoint (centerX, centerY), rPx, rPx);
222+
223+ if (m_showUnitLabels) {
224+ QString label;
225+ if (m_gridMode == GridMode::Millimeters)
226+ label = QString::number (currentMM, ' f' , 1 );
227+ else if (m_gridMode == GridMode::Inches)
228+ label = QString::number (currentMM / 25.4 , ' f' , 2 );
229+ else
230+ label = QString::number ((currentMM / mirrorRadiusMM) * 100.0 , ' f' , 0 ) + " %" ;
231+
232+ // Determine initial Y position at the top of the ring
233+ int yPos = centerY - rPx;
234+
235+ // EDGE CLIPPING FIX:
236+ // If the label is too close to the top edge (within 30 pixels),
237+ // shift it down so the text box doesn't get cut off.
238+ if (yPos < 30 ) {
239+ yPos = 30 ;
240+ }
241+
242+ // Define a wide bounding box for the text
243+ // Centered on X, and centered vertically on our adjusted yPos
244+ QRect textRect (centerX - 60 , yPos - 15 , 120 , 30 );
245+
246+ // Draw shadow for legibility (Black offset)
247+ painter.setPen (Qt::black);
248+ painter.drawText (textRect.translated (1 , 1 ), Qt::AlignCenter, label);
249+
250+ // Draw actual colored text from your settings
251+ painter.setPen (m_textColor);
252+ painter.drawText (textRect, Qt::AlignCenter, label);
253+ }
254+ }
255+
256+ // 5. Center Crosshair
257+ painter.setPen (QPen (m_gridColor, 1 , Qt::SolidLine));
258+ painter.drawLine (centerX - 12 , centerY, centerX + 12 , centerY);
259+ painter.drawLine (centerX, centerY - 12 , centerX, centerY + 12 );
260+ }
85261void foucaultView::showSelectedRonchiImages (){
86262
87263 surfaceAnalysisTools *saTools = surfaceAnalysisTools::get_Instance ();
@@ -312,8 +488,12 @@ void foucaultView::on_makePb_clicked()
312488 if (img.isNull ()) return ;
313489
314490 QSize s = label->size ();
315- QPixmap pix = QPixmap::fromImage (img.scaledToWidth (s.width ()));
491+ QImage displayImg = img.scaled (s, Qt::KeepAspectRatio, Qt::SmoothTransformation);
492+
493+ // Overlay grid
494+ drawGridOverlay (displayImg);
316495
496+ QPixmap pix = QPixmap::fromImage (displayImg);
317497 QPainter painter (&pix);
318498 painter.save ();
319499 painter.setPen (QPen (QColor (Qt::white)));
@@ -375,21 +555,19 @@ void foucaultView::generateBatchRonchiImage(const QList<wavefront*>& wavefrontLi
375555
376556 int headerHeight = 70 ;
377557 int textBuffer = 40 ;
378-
379558 int cellW = imgDim;
380559 int cellH = imgDim + textBuffer;
381560
382- // Total Canvas size
383561 QImage canvas (cellW * cols, (cellH * rows) + headerHeight, QImage::Format_RGB32);
384562 canvas.fill (Qt::black);
385563
386564 QPainter painter (&canvas);
387565 painter.setRenderHint (QPainter::Antialiasing);
388566 QApplication::setOverrideCursor (Qt::WaitCursor);
389567
390- // NEW: Container for individual Ronchi images to be used in comparison
391568 QList<QImage> individualRonchis;
392569 QList<QString> names;
570+
393571 // 5. Draw Simulation Header
394572 painter.setPen (Qt::white);
395573 painter.setFont (QFont (" Arial" , 12 , QFont::Bold));
@@ -410,20 +588,30 @@ void foucaultView::generateBatchRonchiImage(const QList<wavefront*>& wavefrontLi
410588 int row = i / cols;
411589 int col = i % cols;
412590
591+ // Generate the raw Ronchi image
413592 QImage ronchi = generateOpticalTestImage (OpticalTestType::Ronchi, currentWf, s, ui->autocollimation ->isChecked ());
414593
415594 if (!ronchi.isNull ()) {
416- // Store a copy for the comparison feature
595+ // STORE RAW: Add to list for the Compare Dialog (Prevents crash & artifacting)
417596 individualRonchis.append (ronchi);
418597
598+ // STORE NAME: Essential for the Compare Dialog to avoid out-of-bounds crash
599+ QFileInfo fileInfo (currentWf->name );
600+ QString displayName = fileInfo.baseName ();
601+ names.append (displayName);
602+
603+ // GRID OVERLAY: Apply only to a copy for the batch preview canvas
604+ QImage displayCopy = ronchi;
605+ if (m_gridMode != GridMode::None) {
606+ drawGridOverlay (displayCopy);
607+ }
608+
419609 int xPos = col * cellW;
420610 int yPos = headerHeight + (row * cellH);
421611
422- painter.drawImage (xPos, yPos, ronchi );
612+ painter.drawImage (xPos, yPos, displayCopy );
423613
424- QFileInfo fileInfo (currentWf->name );
425- QString displayName = fileInfo.baseName ();
426- names << displayName;
614+ // Draw Label on Canvas
427615 int textWidth = fm.horizontalAdvance (displayName);
428616 int xText = xPos + (cellW - textWidth) / 2 ;
429617 int yText = yPos + imgDim + (textBuffer / 2 ) + (fm.ascent () / 2 );
@@ -448,14 +636,13 @@ void foucaultView::generateBatchRonchiImage(const QList<wavefront*>& wavefrontLi
448636 QScrollArea *scroll = new QScrollArea (&previewDlg);
449637 scroll->setWidgetResizable (true );
450638 scroll->setAlignment (Qt::AlignCenter);
451- // scroll->setStyleSheet("background-color: #1a1a1a;");
452639
453640 QLabel *imgLabel = new QLabel ();
454641 imgLabel->setAlignment (Qt::AlignCenter);
455642 scroll->setWidget (imgLabel);
456643 layout->addWidget (scroll);
457644
458- // 8. Zoom Slider Integration
645+ // 8. Zoom Slider
459646 QPixmap previewPixmap = QPixmap::fromImage (canvas);
460647 QHBoxLayout *zoomLayout = new QHBoxLayout ();
461648 QSlider *slider = new QSlider (Qt::Horizontal);
@@ -479,13 +666,10 @@ void foucaultView::generateBatchRonchiImage(const QList<wavefront*>& wavefrontLi
479666 connect (slider, &QSlider::valueChanged, updateZoom);
480667 updateZoom (100 );
481668
482- // 9. Navigation and Comparison Buttons
669+ // 9. Buttons
483670 QHBoxLayout *btns = new QHBoxLayout ();
484-
485- // NEW: Comparison button
486671 QPushButton *compareBtn = new QPushButton (tr (" Compare Top Two Patterns" ));
487672 compareBtn->setIcon (style ()->standardIcon (QStyle::SP_BrowserReload));
488- // Feature only enabled if 2 or more wavefronts were processed
489673 compareBtn->setEnabled (individualRonchis.size () >= 2 );
490674
491675 QPushButton *saveBtn = new QPushButton (tr (" Save Grid Image" ));
@@ -497,23 +681,18 @@ void foucaultView::generateBatchRonchiImage(const QList<wavefront*>& wavefrontLi
497681 btns->addWidget (cancelBtn);
498682 layout->addLayout (btns);
499683
500- // Connect the comparison trigger
684+ // Comparison Trigger (Uses captured lists)
501685 connect (compareBtn, &QPushButton::clicked, &previewDlg, [=, &previewDlg]() {
502- // [=] copies individualRonchis and names so they stay
503- // valid even after generateBatchRonchiImage() returns.
504- if (individualRonchis.size () >= 2 ) {
686+ if (individualRonchis.size () >= 2 && names.size () >= 2 ) {
505687 RonchiCompareDialog compDlg (individualRonchis[0 ], names[0 ],
506688 individualRonchis[1 ], names[1 ], &previewDlg);
507689 compDlg.exec ();
508690 }
509691 });
510692
511-
512693 connect (saveBtn, &QPushButton::clicked, &previewDlg, &QDialog::accept);
513694 connect (cancelBtn, &QPushButton::clicked, &previewDlg, &QDialog::reject);
514695
515-
516- // 10. Execute Dialog and Save Grid
517696 if (previewDlg.exec () == QDialog::Accepted) {
518697 QString path = QFileDialog::getSaveFileName (this , tr (" Save Ronchi Grid" ),
519698 imageDir, tr (" Images (*.png *.jpg)" ));
0 commit comments