Kstars

framingassistantui.cpp
1 /*
2  SPDX-FileCopyrightText: 2022 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include <indicom.h>
8 
9 #include "framingassistantui.h"
10 #include "ui_framingassistant.h"
11 #include "mosaiccomponent.h"
12 #include "mosaictiles.h"
13 #include "kstars.h"
14 #include "Options.h"
15 #include "scheduler.h"
16 #include "skymap.h"
17 #include "ekos/manager.h"
18 #include "projections/projector.h"
19 #include "skymapcomposite.h"
20 
21 #include "ekos_scheduler_debug.h"
22 
23 namespace Ekos
24 {
25 
26 FramingAssistantUI::FramingAssistantUI(): QDialog(KStars::Instance()), ui(new Ui::FramingAssistant())
27 {
28  ui->setupUi(this);
29 
30  auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
31 
32  ui->raBox->setUnits(dmsBox::HOURS);
33 
34  // Initial optics information is taken from Ekos options
35  ui->focalLenSpin->setValue(Options::telescopeFocalLength());
36  ui->pixelWSizeSpin->setValue(Options::cameraPixelWidth());
37  ui->pixelHSizeSpin->setValue(Options::cameraPixelHeight());
38  ui->cameraWSpin->setValue(Options::cameraWidth());
39  ui->cameraHSpin->setValue(Options::cameraHeight());
40 
41  ui->positionAngleSpin->setValue(tiles->positionAngle());
42  ui->sequenceEdit->setText(tiles->sequenceFile());
43  ui->directoryEdit->setText(tiles->outputDirectory());
44  ui->targetEdit->setText(tiles->targetName());
45  ui->focusEvery->setValue(tiles->focusEveryN());
46  ui->alignEvery->setValue(tiles->alignEveryN());
47  ui->trackStepCheck->setChecked(tiles->isTrackChecked());
48  ui->focusStepCheck->setChecked(tiles->isFocusChecked());
49  ui->alignStepCheck->setChecked(tiles->isAlignChecked());
50  ui->guideStepCheck->setChecked(tiles->isGuideChecked());
51  ui->mosaicWSpin->setValue(tiles->gridSize().width());
52  ui->mosaicHSpin->setValue(tiles->gridSize().height());
53  ui->overlapSpin->setValue(tiles->overlap());
54 
55  if (tiles->operationMode() == MosaicTiles::MODE_OPERATION)
56  {
57  m_CenterPoint = *tiles.data();
58  }
59  else
60  {
61  // Focus only has JNow coords (in both ra0 and ra)
62  // so we need to get catalog coords so it can have valid coordinates.
63  m_CenterPoint = *SkyMap::Instance()->focus();
64  auto J2000Coords = m_CenterPoint.catalogueCoord(KStars::Instance()->data()->ut().djd());
65  m_CenterPoint.setRA0(J2000Coords.ra0());
66  m_CenterPoint.setDec0(J2000Coords.dec0());
67  }
68 
69  m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
70  ui->raBox->show(m_CenterPoint.ra0());
71  ui->decBox->show(m_CenterPoint.dec0());
72 
73  // Page Navigation
74  connect(ui->backToEquipmentB, &QPushButton::clicked, this, [this]()
75  {
76  ui->stackedWidget->setCurrentIndex(PAGE_EQUIPMENT);
77  });
78 
79  // Go and Solve
80  if (Ekos::Manager::Instance()->ekosStatus() == Ekos::Success)
81  {
82  ui->goSolveB->setEnabled(true);
83  connect(Ekos::Manager::Instance()->mountModule(), &Ekos::Mount::newStatus, this, &Ekos::FramingAssistantUI::setMountState,
85  connect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus, this, &Ekos::FramingAssistantUI::setAlignState,
87  }
88  connect(Ekos::Manager::Instance(), &Ekos::Manager::ekosStatusChanged, this, [this](Ekos::CommunicationStatus status)
89  {
90  ui->goSolveB->setEnabled(status == Ekos::Success);
91 
92  // GO AND SOLVE
93  if (status == Ekos::Success)
94  {
95  connect(Ekos::Manager::Instance()->mountModule(), &Ekos::Mount::newStatus, this, &Ekos::FramingAssistantUI::setMountState,
97  connect(Ekos::Manager::Instance()->alignModule(), &Ekos::Align::newStatus, this, &Ekos::FramingAssistantUI::setAlignState,
99  }
100  });
101  connect(ui->goSolveB, &QPushButton::clicked, this, &Ekos::FramingAssistantUI::goAndSolve);
102 
103  // Page Navigation Controls
104  connect(ui->nextToAdjustGrid, &QPushButton::clicked, this, [this]()
105  {
106  ui->stackedWidget->setCurrentIndex(PAGE_ADJUST_GRID);
107  });
108  connect(ui->backToAdjustGridB, &QPushButton::clicked, this, [this]()
109  {
110  ui->stackedWidget->setCurrentIndex(PAGE_ADJUST_GRID);
111  });
112  connect(ui->nextToSelectGridB, &QPushButton::clicked, this, [this]()
113  {
114  ui->stackedWidget->setCurrentIndex(PAGE_SELECT_GRID);
115  });
116  connect(ui->backToSelectGrid, &QPushButton::clicked, this, [this]()
117  {
118  ui->stackedWidget->setCurrentIndex(PAGE_SELECT_GRID);
119  });
120  connect(ui->nextToJobsB, &QPushButton::clicked, this, [this]()
121  {
122  ui->stackedWidget->setCurrentIndex(PAGE_CREATE_JOBS);
123  ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
124  !ui->directoryEdit->text().isEmpty());
125  });
126 
127  // Respond to sky map drag event that causes a shift in the ra and de coords of the center
128  connect(SkyMap::Instance(), &SkyMap::mosaicCenterChanged, this, [this](dms dRA, dms dDE)
129  {
130  m_CenterPoint.setRA0(range24(m_CenterPoint.ra0().Hours() + dRA.Hours()));
131  m_CenterPoint.setDec0(rangeDec(m_CenterPoint.dec0().Degrees() + dDE.Degrees()));
132  m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
133  ui->raBox->show(m_CenterPoint.ra0());
134  ui->decBox->show(m_CenterPoint.dec0());
135  //m_CenterPoint.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
136  m_DebounceTimer->start();
137  });
138 
139  // Update target name after edit
140  connect(ui->targetEdit, &QLineEdit::editingFinished, this, [this]()
141  {
142  QString sanitized = ui->targetEdit->text();
143  if (sanitized != i18n("unnamed"))
144  {
145  // Remove illegal characters that can be problematic
146  sanitized = sanitize(sanitized);
147  ui->targetEdit->blockSignals(true);
148  ui->targetEdit->setText(sanitized);
149  ui->targetEdit->blockSignals(false);
150 
151  if (m_JobsDirectory.isEmpty())
152  ui->directoryEdit->setText(QDir::cleanPath(QDir::homePath() + QDir::separator() + sanitized));
153  else
154  ui->directoryEdit->setText(m_JobsDirectory + QDir::separator() + sanitized);
155 
156  ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
157  !ui->directoryEdit->text().isEmpty());
158  }
159 
160  });
161 
162  // Recenter
163  connect(ui->recenterB, &QPushButton::clicked, this, [this]()
164  {
165  // Focus only has JNow coords (in both ra0 and ra)
166  // so we need to get catalog coords so it can have valid coordinates.
167  m_CenterPoint = *SkyMap::Instance()->focus();
168  auto J2000Coords = m_CenterPoint.catalogueCoord(KStars::Instance()->data()->ut().djd());
169  m_CenterPoint.setRA0(J2000Coords.ra0());
170  m_CenterPoint.setDec0(J2000Coords.dec0());
171 
172  m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
173  ui->raBox->show(m_CenterPoint.ra0());
174  ui->decBox->show(m_CenterPoint.dec0());
175  m_DebounceTimer->start();
176  });
177 
178  // Set initial target on startup
179  if (tiles->operationMode() == MosaicTiles::MODE_PLANNING && SkyMap::IsFocused())
180  {
181  auto sanitized = sanitize(SkyMap::Instance()->focusObject()->name());
182  if (sanitized != i18n("unnamed"))
183  {
184  ui->targetEdit->setText(sanitized);
185 
186  if (m_JobsDirectory.isEmpty())
187  ui->directoryEdit->setText(QDir::cleanPath(QDir::homePath() + QDir::separator() + sanitized));
188  else
189  ui->directoryEdit->setText(m_JobsDirectory + QDir::separator() + sanitized);
190  }
191  }
192 
193  // Update object name
194  connect(SkyMap::Instance(), &SkyMap::objectChanged, this, [this](SkyObject * o)
195  {
196  QString sanitized = o->name();
197  if (sanitized != i18n("unnamed"))
198  {
199  // Remove illegal characters that can be problematic
200  sanitized = sanitize(sanitized);
201  ui->targetEdit->setText(sanitized);
202 
203  if (m_JobsDirectory.isEmpty())
204  ui->directoryEdit->setText(QDir::cleanPath(QDir::homePath() + QDir::separator() + sanitized));
205  else
206  ui->directoryEdit->setText(m_JobsDirectory + QDir::separator() + sanitized);
207  }
208  });
209 
210  // Watch for manual changes in ra box
211  connect(ui->raBox, &dmsBox::editingFinished, this, [this]
212  {
213  m_CenterPoint.setRA0(ui->raBox->createDms());
214  m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
215  m_DebounceTimer->start();
216  });
217 
218  // Watch for manual hanges in de box
219  connect(ui->decBox, &dmsBox::editingFinished, this, [this]
220  {
221  m_CenterPoint.setDec0(ui->decBox->createDms());
222  m_CenterPoint.updateCoordsNow(KStarsData::Instance()->updateNum());
223  m_DebounceTimer->start();
224  });
225 
226  connect(ui->loadSequenceB, &QPushButton::clicked, this, &FramingAssistantUI::selectSequence);
227  connect(ui->selectJobsDirB, &QPushButton::clicked, this, &Ekos::FramingAssistantUI::selectDirectory);
228  // Rendering options
229  ui->transparencySlider->setValue(Options::mosaicTransparencyLevel());
230  ui->transparencySlider->setEnabled(!Options::mosaicTransparencyAuto());
231  tiles->setPainterAlpha(Options::mosaicTransparencyLevel());
232  connect(ui->transparencySlider, QOverload<int>::of(&QSlider::valueChanged), this, [&](int v)
233  {
234  ui->transparencySlider->setToolTip(QString("%1%").arg(v));
235  Options::setMosaicTransparencyLevel(v);
236  auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
237  tiles->setPainterAlpha(v);
238  m_DebounceTimer->start();
239  });
240  ui->transparencyAuto->setChecked(Options::mosaicTransparencyAuto());
241  tiles->setPainterAlphaAuto(Options::mosaicTransparencyAuto());
242  connect(ui->transparencyAuto, &QCheckBox::toggled, this, [&](bool v)
243  {
244  ui->transparencySlider->setEnabled(!v);
245  Options::setMosaicTransparencyAuto(v);
246  auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
247  tiles->setPainterAlphaAuto(v);
248  if (v)
249  m_DebounceTimer->start();
250  });
251 
252  // The update timer avoids stacking updates which crash the sky map renderer
253  m_DebounceTimer = new QTimer(this);
254  m_DebounceTimer->setSingleShot(true);
255  m_DebounceTimer->setInterval(500);
256  connect(m_DebounceTimer, &QTimer::timeout, this, &Ekos::FramingAssistantUI::constructMosaic);
257 
258  // Scope optics information
259  // - Changing the optics configuration changes the FOV, which changes the target field dimensions
260  connect(ui->focalLenSpin, &QDoubleSpinBox::editingFinished, this, &Ekos::FramingAssistantUI::calculateFOV);
261  connect(ui->cameraWSpin, &QSpinBox::editingFinished, this, &Ekos::FramingAssistantUI::calculateFOV);
262  connect(ui->cameraHSpin, &QSpinBox::editingFinished, this, &Ekos::FramingAssistantUI::calculateFOV);
263  connect(ui->pixelWSizeSpin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
264  &Ekos::FramingAssistantUI::calculateFOV);
265  connect(ui->pixelHSizeSpin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
266  &Ekos::FramingAssistantUI::calculateFOV);
267  connect(ui->positionAngleSpin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
268  &Ekos::FramingAssistantUI::calculateFOV);
269 
270  // Mosaic configuration
271  // - Changing the target field dimensions changes the grid dimensions
272  // - Changing the overlap field changes the grid dimensions (more intuitive than changing the field)
273  // - Changing the grid dimensions changes the target field dimensions
274  connect(ui->targetHFOVSpin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
275  &Ekos::FramingAssistantUI::updateGridFromTargetFOV);
276  connect(ui->targetWFOVSpin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
277  &Ekos::FramingAssistantUI::updateGridFromTargetFOV);
278  connect(ui->overlapSpin, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
279  &Ekos::FramingAssistantUI::updateGridFromTargetFOV);
280  connect(ui->mosaicWSpin, QOverload<int>::of(&QSpinBox::valueChanged), this,
281  &Ekos::FramingAssistantUI::updateTargetFOVFromGrid);
282  connect(ui->mosaicHSpin, QOverload<int>::of(&QSpinBox::valueChanged), this,
283  &Ekos::FramingAssistantUI::updateTargetFOVFromGrid);
284 
285  // Lazy update for s-shape
286  connect(ui->reverseOddRows, &QCheckBox::toggled, this, [&]()
287  {
288  renderedHFOV = 0;
289  m_DebounceTimer->start();
290  });
291 
292  // Buttons
293  connect(ui->resetB, &QPushButton::clicked, this, &Ekos::FramingAssistantUI::updateTargetFOVFromGrid);
294  connect(ui->fetchB, &QPushButton::clicked, this, &FramingAssistantUI::fetchINDIInformation);
295  connect(ui->createJobsB, &QPushButton::clicked, this, &FramingAssistantUI::createJobs);
296 
297  // Job options
298  connect(ui->alignEvery, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::FramingAssistantUI::rewordStepEvery);
299  connect(ui->focusEvery, QOverload<int>::of(&QSpinBox::valueChanged), this, &Ekos::FramingAssistantUI::rewordStepEvery);
300 
301  // Get INDI Information, if avaialble.
302  if (tiles->operationMode() == MosaicTiles::MODE_PLANNING)
303  fetchINDIInformation();
304 
305  if (isEquipmentValid())
306  ui->stackedWidget->setCurrentIndex(PAGE_SELECT_GRID);
307 
308  tiles->setOperationMode(MosaicTiles::MODE_PLANNING);
309 }
310 
311 FramingAssistantUI::~FramingAssistantUI()
312 {
313  delete m_DebounceTimer;
314 }
315 
316 bool FramingAssistantUI::isEquipmentValid() const
317 {
318  return (ui->focalLenSpin->value() > 0 && ui->cameraWSpin->value() > 0 && ui->cameraHSpin->value() > 0 &&
319  ui->pixelWSizeSpin->value() > 0 && ui->pixelHSizeSpin->value() > 0);
320 }
321 
322 double FramingAssistantUI::getTargetWFOV() const
323 {
324  double const xFOV = ui->cameraWFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
325  return ui->cameraWFOVSpin->value() + xFOV * (ui->mosaicWSpin->value() - 1);
326 }
327 
328 double FramingAssistantUI::getTargetHFOV() const
329 {
330  double const yFOV = ui->cameraHFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
331  return ui->cameraHFOVSpin->value() + yFOV * (ui->mosaicHSpin->value() - 1);
332 }
333 
334 double FramingAssistantUI::getTargetMosaicW() const
335 {
336  // If FOV is invalid, or target FOV is null, or target FOV is smaller than camera FOV, we get one tile
337  if (!isEquipmentValid() || !ui->targetWFOVSpin->value() || ui->targetWFOVSpin->value() <= ui->cameraWFOVSpin->value())
338  return 1;
339 
340  // Else we get one tile, plus as many overlapping camera FOVs in the remnant of the target FOV
341  double const xFOV = ui->cameraWFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
342  int const tiles = 1 + ceil((ui->targetWFOVSpin->value() - ui->cameraWFOVSpin->value()) / xFOV);
343  //Ekos::Manager::Instance()->schedulerModule()->appendLogText(QString("[W] Target FOV %1, camera FOV %2 after overlap %3, %4 tiles.").arg(ui->targetWFOVSpin->value()).arg(ui->cameraWFOVSpin->value()).arg(xFOV).arg(tiles));
344  return tiles;
345 }
346 
347 double FramingAssistantUI::getTargetMosaicH() const
348 {
349  // If FOV is invalid, or target FOV is null, or target FOV is smaller than camera FOV, we get one tile
350  if (!isEquipmentValid() || !ui->targetHFOVSpin->value() || ui->targetHFOVSpin->value() <= ui->cameraHFOVSpin->value())
351  return 1;
352 
353  // Else we get one tile, plus as many overlapping camera FOVs in the remnant of the target FOV
354  double const yFOV = ui->cameraHFOVSpin->value() * (1 - ui->overlapSpin->value() / 100.0);
355  int const tiles = 1 + ceil((ui->targetHFOVSpin->value() - ui->cameraHFOVSpin->value()) / yFOV);
356  //Ekos::Manager::Instance()->schedulerModule()->appendLogText(QString("[H] Target FOV %1, camera FOV %2 after overlap %3, %4 tiles.").arg(ui->targetHFOVSpin->value()).arg(ui->cameraHFOVSpin->value()).arg(yFOV).arg(tiles));
357  return tiles;
358 }
359 
360 void FramingAssistantUI::calculateFOV()
361 {
362  if (!isEquipmentValid())
363  return;
364 
365  ui->nextToSelectGridB->setEnabled(true);
366 
367  ui->targetWFOVSpin->setMinimum(ui->cameraWFOVSpin->value());
368  ui->targetHFOVSpin->setMinimum(ui->cameraHFOVSpin->value());
369 
370  Options::setTelescopeFocalLength(ui->focalLenSpin->value());
371  Options::setCameraPixelWidth(ui->pixelWSizeSpin->value());
372  Options::setCameraPixelHeight(ui->pixelHSizeSpin->value());
373  Options::setCameraWidth(ui->cameraWSpin->value());
374  Options::setCameraHeight(ui->cameraHSpin->value());
375  Options::setCameraRotation(ui->positionAngleSpin->value());
376 
377  // Calculate FOV in arcmins
378  const auto fov_x = 206264.8062470963552 * ui->cameraWSpin->value() * ui->pixelWSizeSpin->value() / 60000.0 /
379  ui->focalLenSpin->value();
380  const auto fov_y = 206264.8062470963552 * ui->cameraHSpin->value() * ui->pixelHSizeSpin->value() / 60000.0 /
381  ui->focalLenSpin->value();
382 
383  ui->cameraWFOVSpin->setValue(fov_x);
384  ui->cameraHFOVSpin->setValue(fov_y);
385 
386  double const target_fov_w = getTargetWFOV();
387  double const target_fov_h = getTargetHFOV();
388 
389  if (ui->targetWFOVSpin->value() < target_fov_w)
390  {
391  bool const sig = ui->targetWFOVSpin->blockSignals(true);
392  ui->targetWFOVSpin->setValue(target_fov_w);
393  ui->targetWFOVSpin->blockSignals(sig);
394  }
395 
396  if (ui->targetHFOVSpin->value() < target_fov_h)
397  {
398  bool const sig = ui->targetHFOVSpin->blockSignals(true);
399  ui->targetHFOVSpin->setValue(target_fov_h);
400  ui->targetHFOVSpin->blockSignals(sig);
401  }
402 
403  m_DebounceTimer->start();
404 }
405 
406 void FramingAssistantUI::resetFOV()
407 {
408  if (!isEquipmentValid())
409  return;
410 
411  ui->targetWFOVSpin->setValue(getTargetWFOV());
412  ui->targetHFOVSpin->setValue(getTargetHFOV());
413 }
414 
415 void FramingAssistantUI::updateTargetFOVFromGrid()
416 {
417  if (!isEquipmentValid())
418  return;
419 
420  double const targetWFOV = getTargetWFOV();
421  double const targetHFOV = getTargetHFOV();
422 
423  if (ui->targetWFOVSpin->value() != targetWFOV)
424  {
425  bool const sig = ui->targetWFOVSpin->blockSignals(true);
426  ui->targetWFOVSpin->setValue(targetWFOV);
427  ui->targetWFOVSpin->blockSignals(sig);
428  m_DebounceTimer->start();
429  }
430 
431  if (ui->targetHFOVSpin->value() != targetHFOV)
432  {
433  bool const sig = ui->targetHFOVSpin->blockSignals(true);
434  ui->targetHFOVSpin->setValue(targetHFOV);
435  ui->targetHFOVSpin->blockSignals(sig);
436  m_DebounceTimer->start();
437  }
438 }
439 
440 void FramingAssistantUI::updateGridFromTargetFOV()
441 {
442  if (!isEquipmentValid())
443  return;
444 
445  double const expectedW = getTargetMosaicW();
446  double const expectedH = getTargetMosaicH();
447 
448  if (expectedW != ui->mosaicWSpin->value())
449  {
450  bool const sig = ui->mosaicWSpin->blockSignals(true);
451  ui->mosaicWSpin->setValue(expectedW);
452  ui->mosaicWSpin->blockSignals(sig);
453  }
454 
455  if (expectedH != ui->mosaicHSpin->value())
456  {
457  bool const sig = ui->mosaicHSpin->blockSignals(true);
458  ui->mosaicHSpin->setValue(expectedH);
459  ui->mosaicHSpin->blockSignals(sig);
460  }
461 
462  // Update unconditionally, as we may be updating the overlap or the target FOV covered by the mosaic
463  m_DebounceTimer->start();
464 }
465 
466 void FramingAssistantUI::constructMosaic()
467 {
468  m_DebounceTimer->stop();
469 
470  if (!isEquipmentValid())
471  return;
472 
473  auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
474  // Set Basic Metadata
475 
476  // Center
477  tiles->setRA0(m_CenterPoint.ra0());
478  tiles->setDec0(m_CenterPoint.dec0());
479  tiles->updateCoordsNow(KStarsData::Instance()->updateNum());
480 
481  // Grid Size
482  tiles->setGridSize(QSize(ui->mosaicWSpin->value(), ui->mosaicHSpin->value()));
483  // Position Angle
484  tiles->setPositionAngle(ui->positionAngleSpin->value());
485  // Camera FOV in arcmins
486  tiles->setCameraFOV(QSizeF(ui->cameraWFOVSpin->value(), ui->cameraHFOVSpin->value()));
487  // Mosaic overall FOV in arcsmins
488  tiles->setMosaicFOV(QSizeF(ui->targetWFOVSpin->value(), ui->targetHFOVSpin->value()));
489  // Overlap in %
490  tiles->setOverlap(ui->overlapSpin->value());
491  // Generate Tiles
492  tiles->createTiles(ui->reverseOddRows->checkState() == Qt::CheckState::Checked);
493 }
494 
495 void FramingAssistantUI::fetchINDIInformation()
496 {
497  // Block all signals so we can set the values directly.
498  for (auto oneWidget : ui->equipment->children())
499  oneWidget->blockSignals(true);
500  for (auto oneWidget : ui->createGrid->children())
501  oneWidget->blockSignals(true);
502 
503  QDBusInterface alignInterface("org.kde.kstars",
504  "/KStars/Ekos/Align",
505  "org.kde.kstars.Ekos.Align",
507 
508  QDBusReply<QList<double>> cameraReply = alignInterface.call("cameraInfo");
509  if (cameraReply.isValid())
510  {
511  QList<double> const values = cameraReply.value();
512 
513  m_CameraSize = QSize(values[0], values[1]);
514  ui->cameraWSpin->setValue(m_CameraSize.width());
515  ui->cameraHSpin->setValue(m_CameraSize.height());
516  m_PixelSize = QSizeF(values[2], values[3]);
517  ui->pixelWSizeSpin->setValue(m_PixelSize.width());
518  ui->pixelHSizeSpin->setValue(m_PixelSize.height());
519  }
520 
521  QDBusReply<QList<double>> telescopeReply = alignInterface.call("telescopeInfo");
522  if (telescopeReply.isValid())
523  {
524  QList<double> const values = telescopeReply.value();
525  m_FocalLength = values[0];
526  ui->focalLenSpin->setValue(m_FocalLength);
527  }
528 
529  QDBusReply<QList<double>> solutionReply = alignInterface.call("getSolutionResult");
530  if (solutionReply.isValid())
531  {
532  QList<double> const values = solutionReply.value();
533  if (values[0] > INVALID_VALUE)
534  {
535  m_PA = SolverUtils::rotationToPositionAngle(values[0]);
536  ui->positionAngleSpin->setValue(m_PA);
537  }
538  }
539 
540  calculateFOV();
541 
542  // Restore all signals
543  for (auto oneWidget : ui->equipment->children())
544  oneWidget->blockSignals(false);
545  for (auto oneWidget : ui->createGrid->children())
546  oneWidget->blockSignals(false);
547 }
548 
549 void FramingAssistantUI::rewordStepEvery(int v)
550 {
551  QSpinBox * sp = dynamic_cast<QSpinBox *>(sender());
552  if (0 < v)
553  sp->setSuffix(i18np(" Scheduler job", " Scheduler jobs", v));
554  else
555  sp->setSuffix(i18n(" (first only)"));
556 }
557 
558 QString FramingAssistantUI::sanitize(const QString &name)
559 {
560  QString sanitized = name;
561  if (sanitized != i18n("unnamed"))
562  {
563  // Remove illegal characters that can be problematic
564  sanitized = sanitized.replace( QRegularExpression("\\s|/|\\(|\\)|:|\\*|~|\"" ), "_" )
565  // Remove any two or more __
566  .replace( QRegularExpression("_{2,}"), "_")
567  // Remove any _ at the end
568  .replace( QRegularExpression("_$"), "");
569  }
570  return sanitized;
571 }
572 
573 void FramingAssistantUI::goAndSolve()
574 {
575  // If user click again before solver did not start while GOTO is pending
576  // let's start solver immediately if the mount is already tracking.
577  if (m_GOTOSolvePending && m_MountState == ISD::Mount::MOUNT_TRACKING)
578  {
579  m_GOTOSolvePending = false;
580  ui->goSolveB->setStyleSheet("border: 1px outset yellow");
581  Ekos::Manager::Instance()->alignModule()->captureAndSolve();
582  }
583  // Otherwise, initiate GOTO
584  else
585  {
586  Ekos::Manager::Instance()->alignModule()->setSolverAction(Ekos::Align::GOTO_SLEW);
587  Ekos::Manager::Instance()->mountModule()->gotoTarget(m_CenterPoint);
588  ui->goSolveB->setStyleSheet("border: 1px outset magenta");
589  m_GOTOSolvePending = true;
590  }
591 }
592 
593 void FramingAssistantUI::createJobs()
594 {
595  auto scheduler = Ekos::Manager::Instance()->schedulerModule();
596  auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
597  auto sequence = ui->sequenceEdit->text();
598  auto outputDirectory = ui->directoryEdit->text();
599  auto target = ui->targetEdit->text();
600 
601  tiles->setTargetName(target);
602  tiles->setOutputDirectory(outputDirectory);
603  tiles->setSequenceFile(sequence);
604  tiles->setFocusEveryN(ui->focusEvery->value());
605  tiles->setAlignEveryN(ui->alignEvery->value());
606  tiles->setStepChecks(ui->trackStepCheck->isChecked(), ui->focusStepCheck->isChecked(),
607  ui->alignStepCheck->isChecked(), ui->guideStepCheck->isChecked());
608  tiles->setPositionAngle(ui->positionAngleSpin->value());
609  // Start by removing any jobs.
610  scheduler->removeAllJobs();
611 
612  int batchCount = 0;
613  for (auto oneTile : tiles->tiles())
614  {
615  batchCount++;
616  XMLEle *root = scheduler->getSequenceJobRoot(sequence);
617  if (root == nullptr)
618  return;
619 
620  const auto oneTarget = QString("%1-Part%2").arg(target).arg(batchCount);
621  if (scheduler->createJobSequence(root, oneTarget, outputDirectory) == false)
622  {
623  delXMLEle(root);
624  return;
625  }
626 
627  delXMLEle(root);
628  auto oneSequence = QString("%1/%2.esq").arg(outputDirectory, oneTarget);
629 
630  // First job should Always focus if possible
631  bool shouldFocus = ui->focusStepCheck->isChecked() && (batchCount == 1 || (batchCount % ui->focusEvery->value()) == 0);
632  bool shouldAlign = ui->alignStepCheck->isChecked() && (batchCount == 1 || (batchCount % ui->alignEvery->value()) == 0);
633  QJsonObject settings =
634  {
635  {"target", oneTarget},
636  {"ra", oneTile->skyCenter.ra0().toHMSString()},
637  {"dec", oneTile->skyCenter.dec0().toDMSString()},
638  {"pa", tiles->positionAngle()},
639  {"sequence", oneSequence},
640  {"track", ui->trackStepCheck->isChecked()},
641  {"focus", shouldFocus},
642  {"align", shouldAlign},
643  {"guide", ui->guideStepCheck->isChecked()}
644  };
645 
646  scheduler->setPrimarySettings(settings);
647 
648  scheduler->saveJob();
649  }
650 
651  auto schedulerListFile = QString("%1/%2.esl").arg(outputDirectory, target);
652  scheduler->saveScheduler(QUrl::fromLocalFile(schedulerListFile));
653  accept();
654  Ekos::Manager::Instance()->activateModule(i18n("Scheduler"), true);
655 
656 
657 }
658 
659 void FramingAssistantUI::setMountState(ISD::Mount::Status value)
660 {
661  m_MountState = value;
662  if (m_GOTOSolvePending && m_MountState == ISD::Mount::MOUNT_TRACKING)
663  {
664  m_GOTOSolvePending = false;
665  ui->goSolveB->setStyleSheet("border: 1px outset yellow");
666  Ekos::Manager::Instance()->alignModule()->captureAndSolve();
667  }
668 }
669 
670 void FramingAssistantUI::setAlignState(AlignState value)
671 {
672  m_AlignState = value;
673 
674  if (m_AlignState == Ekos::ALIGN_COMPLETE)
675  ui->goSolveB->setStyleSheet("border: 1px outset green");
676  else if (m_AlignState == Ekos::ALIGN_ABORTED || m_AlignState == Ekos::ALIGN_FAILED)
677  ui->goSolveB->setStyleSheet("border: 1px outset red");
678 }
679 
680 void FramingAssistantUI::selectSequence()
681 {
682  QString file = QFileDialog::getOpenFileName(Ekos::Manager::Instance(), i18nc("@title:window", "Select Sequence Queue"),
683  QDir::homePath(),
684  i18n("Ekos Sequence Queue (*.esq)"));
685 
686  if (!file.isEmpty())
687  {
688  ui->sequenceEdit->setText(file);
689  ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
690  !ui->directoryEdit->text().isEmpty());
691  }
692 }
693 
694 void FramingAssistantUI::selectDirectory()
695 {
696  m_JobsDirectory = QFileDialog::getExistingDirectory(Ekos::Manager::Instance(), i18nc("@title:window", "Select Jobs Directory"),
697  QDir::homePath());
698 
699  if (!m_JobsDirectory.isEmpty())
700  {
701  // If we already have a target specified, then append it to directory path.
702  QString sanitized = ui->targetEdit->text();
703  if (sanitized.isEmpty() == false && sanitized != i18n("unnamed"))
704  {
705  // Remove illegal characters that can be problematic
706  sanitized = sanitize(sanitized);
707  ui->directoryEdit->setText(m_JobsDirectory + QDir::separator() + sanitized);
708 
709  }
710  else
711  ui->directoryEdit->setText(m_JobsDirectory);
712 
713 
714  ui->createJobsB->setEnabled(!ui->targetEdit->text().isEmpty() && !ui->sequenceEdit->text().isEmpty() &&
715  !ui->directoryEdit->text().isEmpty());
716  }
717 }
718 
719 }
void mosaicCenterChanged(dms dRA, dms dDE)
Emitter when mosaic center is dragged in the sky map.
void setSuffix(const QString &suffix)
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:70
QChar separator()
bool isValid() const const
void clicked(bool checked)
void editingFinished()
virtual QString name(void) const
Definition: skyobject.h:145
QString homePath()
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
Definition: skypoint.h:94
void valueChanged(double d)
void toggled(bool checked)
static KStars * Instance()
Definition: kstars.h:125
QString i18n(const char *text, const TYPE &arg...)
QDBusConnection sessionBus()
SkyPoint * focus()
Retrieve the Focus point; the position on the sky at the center of the skymap.
Definition: skymap.h:122
bool blockSignals(bool block)
void timeout()
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
void newStatus(ISD::Mount::Status status)
Change in the mount status.
void objectChanged(SkyObject *)
Emitted when current object changed.
UniqueConnection
This is the main window for KStars. In addition to the GUI elements, the class contains the program c...
Definition: kstars.h:92
void setupUi(QWidget *widget)
QString & replace(int position, int n, QChar after)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
SkyMapComposite * skyComposite()
Definition: kstarsdata.h:165
QString cleanPath(const QString &path)
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void editingFinished()
const double & Degrees() const
Definition: dms.h:141
const char * name(StandardAction id)
void valueChanged(int value)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void valueChanged(int i)
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options)
Information about an object in the sky.
Definition: skyobject.h:41
QVector< V > values(const QMultiHash< K, V > &c)
double Hours() const
Definition: dms.h:168
QDBusReply::Type value() const const
SkyPoint catalogueCoord(long double jdf)
Computes the J2000.0 catalogue coordinates for this SkyPoint using the epoch removing aberration,...
Definition: skypoint.cpp:710
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:54 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.