Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
DefectiveCrystalActor.cpp
Go to the documentation of this file.
1/* This file is part of MODEL, the Mechanics Of Defect Evolution Library.
2 *
3 * Copyright (C) 2011 by Giacomo Po <gpo@ucla.edu>.
4 *
5 * model is distributed without any warranty under the
6 * GNU General Public License (GPL) v2 <http://www.gnu.org/licenses/>.
7 */
8
9#ifndef model_DefectiveCrystalActor_cpp_
10#define model_DefectiveCrystalActor_cpp_
11
12#include <DislocationNetwork.h>
13#include <algorithm>
14#include <QString>
15#include <QApplication>
16
17#include <QtCore>
18
19#include <QImage>
20#include <QStyle>
21#include <QScopedValueRollback>
22#include <limits>
23
25#include <SimplicialMesh.h>
26
27#include <DDqtControls.h>
28#include <QHBoxLayout>
29#include <sstream>
30
31namespace model
32{
33
34 DefectiveCrystalActor::DefectiveCrystalActor(vtkGenericOpenGLRenderWindow* const renWin,vtkRenderer* const ren,
35 QVTKOpenGLStereoWidget* const qvtkGLwidget_in,
36 DefectiveCrystalBase<3>& ddBase_in) :
37 /* init */ renderWindow(renWin)
38 /* init */,qvtkGLwidget(qvtkGLwidget_in)
39 /* init */,ddBase(ddBase_in)
40// /* init */,configFields(ddBase,*this)
41 /* init */,defectiveCrystal(ddBase)
42 /* init */,nodes(new NetworkNodeActor(renWin,ren))
43 /* init */,segments(new NetworkLinkActor(renWin,ren,defectiveCrystal))
44 /* init */,loops(new NetworkLoopActor(renWin,ren,defectiveCrystal))
45 /* init */,inclusions(new InclusionActor(renWin,ren,defectiveCrystal))
46// /* init */,glidePlanes(new GlidePlaneActor(renWin,ren,ddBase.poly,ddBase.simulationParameters.traitsIO))
47 /* init */,glidePlanes(new GlidePlaneActor(renWin,ren,defectiveCrystal))
48 /* init */,quadrature(new QuadratureActor(renWin,ren,ddBase.poly,ddBase.simulationParameters.traitsIO))
49 /* init */,chartActor(new ChartActor(ddBase.simulationParameters.traitsIO,renderWindow,ren))
53 /* init */,configuration(std::make_unique<DefectiveCrystalConfigIO<3>>(ddBase.simulationParameters.traitsIO.configFolder))
54 /* init */,auxiliary(std::make_unique<DefectiveCrystalAuxIO<3>>(ddBase.simulationParameters.traitsIO.auxFolder))
55 /* init */,playbackTimer(new QTimer(this))
56 /* init */,renderer(ren)
57 /* init */,mainLayout(new QGridLayout(this))
58 /* init */,frameIDedit(new QLineEdit("0"))
59 /* init */,plusFrameButton(new QToolButton(this))
60 /* init */,playFrameButton(new QToolButton(this))
61 /* init */,stopFrameButton(new QToolButton(this))
62 /* init */,minusFrameButton(new QToolButton(this))
63 /* init */,frameIncrementEdit(new QLineEdit(QString::number(ddBase.simulationParameters.outputFrequency)))
64 /* init */,rotationAxisEdit(new QLineEdit("0 0 1"))
65 /* init */,rotationAngleEdit(new QLineEdit("0"))
66 /* init */,cameraAdvanceEdit(new QLineEdit("0"))
67 /* init */,saveImage(new QCheckBox(this))
68 /* init */,frameStatus(new QLabel(this))
69 /* init */,tabWidget(new WrappingTabWidget())
70 {
71
72 tabWidget->addTab(nodes, tr(std::string("Nodes").c_str()));
73 tabWidget->addTab(segments, tr(std::string("Segments").c_str()));
74 tabWidget->addTab(loops, tr(std::string("Loops").c_str()));
75 tabWidget->addTab(inclusions, tr(std::string("Inclusions").c_str()));
76 tabWidget->addTab(glidePlanes, tr(std::string("GlidePlanes").c_str()));
77 tabWidget->addTab(quadrature, tr(std::string("Quadrature").c_str()));
78 tabWidget->addTab(chartActor, tr(std::string("Chart").c_str()));
79 tabWidget->addTab(ddField, tr(std::string("Fields").c_str()));
80 tabWidget->addTab(surfaceMesh, tr("SurfaceMesh"));
81 tabWidget->addTab(slipSystemTab, tr(std::string("SlipSystems").c_str()));
82
83 saveImage->setText("save PNG");
84
85 auto *framesBox =
86 controls::box("Navigation", this, "Select simulation outputs, control playback and capture images.");
87 auto *framesLayout = controls::grid(framesBox);
88 auto *frameIDLayout = new QGridLayout();
89 auto *incrementLayout = new QGridLayout();
90 framesLayout->addLayout(frameIDLayout, 0, 0);
91 framesLayout->addLayout(incrementLayout, 0, 1);
92 framesLayout->setColumnStretch(0, 1);
93 framesLayout->setColumnStretch(1, 1);
95 frameIDLayout, 0, "Frame ID", frameIDedit,
96 "Simulation output runID to display. Press Enter or leave this field to load it and apply camera rotation and advance; output IDs may not be consecutive.");
98 incrementLayout, 0, "Increment", frameIncrementEdit,
99 "RunID increment used by previous, next and playback controls. Normally matches outputFrequency.");
100 auto *axisLayout = new QGridLayout();
101 auto *angleLayout = new QGridLayout();
102 framesLayout->addLayout(axisLayout, 1, 0);
103 framesLayout->addLayout(angleLayout, 1, 1);
104 rotationAxisEdit->setObjectName("frameRotationAxis");
105 rotationAngleEdit->setObjectName("frameRotationAngle");
106 controls::field(axisLayout, 0, "Axis", rotationAxisEdit,
107 "Global x y z rotation axis through the simulation-box center. Automatic rotation keeps the box centered. Enter a nonzero vector; it is normalized automatically. Applies on Enter or when loading a frame.");
108 controls::field(angleLayout, 0, "Angle/frame (°)", rotationAngleEdit,
109 "View rotation in degrees equals this value times the simulation frameID, including skipped IDs. Positive angles follow the right-hand rule. Zero disables rotation. Applies on Enter or when loading a frame.");
110 connect(rotationAxisEdit, &QLineEdit::returnPressed, this, &DefectiveCrystalActor::updateRotationSettings);
111 connect(rotationAngleEdit, &QLineEdit::returnPressed, this, &DefectiveCrystalActor::updateRotationSettings);
112 auto *advanceLayout = new QGridLayout();
113 framesLayout->addLayout(advanceLayout, 2, 0, 1, 2);
114 cameraAdvanceEdit->setObjectName("frameCameraAdvance");
115 controls::field(advanceLayout, 0, "Advance/frame (b)", cameraAdvanceEdit,
116 "Camera travel toward and through the box center, in simulation length units b times frameID, including skipped IDs. Positive advances, negative retreats, zero disables travel. Combines with rotation and uses perspective projection. Applies on Enter or when loading a frame.");
117 connect(cameraAdvanceEdit, &QLineEdit::returnPressed, this, &DefectiveCrystalActor::updateRotationSettings);
118 auto *playback = new QWidget(this);
119 auto *playbackLayout = new QHBoxLayout(playback);
120 playbackLayout->setContentsMargins(0, 0, 0, 0);
121 minusFrameButton->setIcon(style()->standardIcon(QStyle::SP_MediaSkipBackward));
122 playFrameButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay));
123 stopFrameButton->setIcon(style()->standardIcon(QStyle::SP_MediaStop));
124 plusFrameButton->setIcon(style()->standardIcon(QStyle::SP_MediaSkipForward));
125 minusFrameButton->setAccessibleName("Previous frame");
126 playFrameButton->setAccessibleName("Play");
127 stopFrameButton->setAccessibleName("Stop");
128 plusFrameButton->setAccessibleName("Next frame");
130 {
131 button->setToolButtonStyle(Qt::ToolButtonIconOnly);
132 button->setIconSize(QSize(20, 20));
133 button->setFixedSize(32, 32);
134 if(button == playFrameButton)
135 {
136 playbackLayout->addSpacing(12);
137 }
138 playbackLayout->addWidget(button);
139 }
140 playbackLayout->addStretch();
141 minusFrameButton->setToolTip("Load the previous output using the frame increment.");
142 plusFrameButton->setToolTip("Load the next output using the frame increment.");
143 playFrameButton->setToolTip("Advance through available frames until stopped.");
144 stopFrameButton->setToolTip("Stop automatic playback.");
145 framesLayout->addWidget(playback, 3, 0, 1, 2);
146 framesLayout->addWidget(saveImage, 4, 0, 1, 2);
147 frameStatus->setObjectName("frameStatus");
148 frameStatus->setWordWrap(true);
149 framesLayout->addWidget(frameStatus, 5, 0, 1, 2);
150 saveImage->setToolTip("Save a PNG of each loaded frame to the simulation output folder.");
151 mainLayout->addWidget(framesBox, 0, 0, 1, 6);
152 periodicImages = new PeriodicImagesWidget(renderWindow, renderer, ddBase.mesh.periodicBasis(), this);
153 mainLayout->addWidget(periodicImages, 1, 0, 1, 6);
154 mainLayout->addWidget(tabWidget,2,0,1,6);
155 this->setLayout(mainLayout);
156
158 [this]()
159 {
160 loops->updateColors(slipSystemTab->slipSystemColorMap);
161 if(renderWindow->GetReadyForRendering())
162 {
163 renderWindow->Render();
164 }
165 });
166
167 connect(frameIDedit, &QLineEdit::editingFinished, this, [this]()
168 {
169 if(frameIDedit->isModified())
170 {
171 frameIDedit->setModified(false);
172 updateConfiguration();
173 }
174 });
175 connect(plusFrameButton,SIGNAL(pressed()), this, SLOT(nextConfiguration()));
176 connect(minusFrameButton,SIGNAL(pressed()), this, SLOT(prevConfiguration()));
177 connect(playFrameButton,SIGNAL(pressed()), this, SLOT(playConfigurations()));
178 connect(stopFrameButton,SIGNAL(pressed()), this, SLOT(stopConfigurations()));
179
180 playbackTimer->setObjectName("framePlaybackTimer");
181 playbackTimer->setSingleShot(true);
182 playbackTimer->setInterval(100);
183 stopFrameButton->setEnabled(false);
184 connect(playbackTimer, &QTimer::timeout, this, [this]()
185 {
186 if(playing && nextConfiguration() && playing)
187 {
188 playbackTimer->start();
189 }
190 else
191 {
193 }
194 });
195
196 }
197
198 bool DefectiveCrystalActor::readRotationSettings()
199 {
200 Eigen::Vector3d axis;
201 std::istringstream input(rotationAxisEdit->text().toStdString());
202 std::string extra;
203 const bool validAxis = static_cast<bool>(input >> axis[0] >> axis[1] >> axis[2]) &&
204 !(input >> extra) && axis.allFinite() && axis.stableNorm() > 0.0;
205 bool validAngle = false;
206 const double angle = rotationAngleEdit->text().toDouble(&validAngle);
207 validAngle = validAngle && std::isfinite(angle);
208 rotationAxisEdit->setStyleSheet(validAxis ? "" : "QLineEdit { border: 1px solid #c44; }");
209 rotationAngleEdit->setStyleSheet(validAngle ? "" : "QLineEdit { border: 1px solid #c44; }");
210 bool validAdvance = false;
211 const double advance = cameraAdvanceEdit->text().toDouble(&validAdvance);
212 validAdvance = validAdvance && std::isfinite(advance);
213 cameraAdvanceEdit->setStyleSheet(validAdvance ? "" : "QLineEdit { border: 1px solid #c44; }");
214 if(!validAxis || !validAngle || !validAdvance)
215 {
216 return false;
217 }
218 rotationAxis = axis.stableNormalized();
219 rotationDegreesPerFrame = angle;
220 cameraAdvancePerFrame = advance;
221 return true;
222 }
223
224 void DefectiveCrystalActor::updateRotationSettings()
225 {
226 if(readRotationSettings() && frameLoaded)
227 {
228 frameRotation.apply(renderer->GetActiveCamera(),
229 0.5 * (ddBase.mesh.xMin() + ddBase.mesh.xMax()),
230 rotationAxis, rotationDegreesPerFrame, loadedFrameID, cameraAdvancePerFrame);
231 renderer->ResetCameraClippingRange();
232 if(renderWindow->GetReadyForRendering())
233 {
234 renderWindow->Render();
235 }
236 }
237 }
238
240 {
241 return *configuration;
242 }
243
245 {
246 return *configuration;
247 }
248
250 {
251 return *auxiliary;
252 }
253
255 {
256 return *auxiliary;
257 }
258
260 {
262 // Actors and field samplers reference defectiveCrystal and ddBase.
263 // Destroy them before those members and their data are released.
264 delete periodicImages;
265 delete tabWidget;
266 }
267
268 void DefectiveCrystalActor::playConfigurations()
269 {
270 if(playing)
271 {
272 return;
273 }
274 playing = true;
275 playFrameButton->setEnabled(false);
276 stopFrameButton->setEnabled(true);
277 playbackTimer->start();
278 }
279
281 {
282 playing = false;
283 playbackTimer->stop();
284 playFrameButton->setEnabled(true);
285 stopFrameButton->setEnabled(false);
286 }
287
288 bool DefectiveCrystalActor::nextConfiguration()
289 {
290 bool ok = false;
291 const auto increment = frameIncrementEdit->text().toULongLong(&ok);
292 const size_t current = frameLoaded ? loadedFrameID : 0;
293 if(!ok || increment == 0 || increment > std::numeric_limits<size_t>::max() - current)
294 {
295 frameStatus->setText("Enter a positive frame increment within the runID range.");
297 return false;
298 }
299 return updateConfiguration(current + static_cast<size_t>(increment));
300 }
301
302 bool DefectiveCrystalActor::prevConfiguration()
303 {
304 bool ok = false;
305 const auto increment = frameIncrementEdit->text().toULongLong(&ok);
306 if(!ok || increment == 0)
307 {
308 frameStatus->setText("Enter a positive frame increment.");
310 return false;
311 }
312 const size_t current = frameLoaded ? loadedFrameID : 0;
313 return updateConfiguration(increment > current ? 0 : current - static_cast<size_t>(increment));
314 }
315
316 bool DefectiveCrystalActor::updateConfiguration()
317 {
318 bool ok = false;
319 const auto requested = frameIDedit->text().toULongLong(&ok);
320 if(!ok || requested > std::numeric_limits<size_t>::max())
321 {
322 frameStatus->setText("Enter a nonnegative integer frame ID.");
323 frameIDedit->setText(QString::number(loadedFrameID));
325 return false;
326 }
327 return updateConfiguration(static_cast<size_t>(requested));
328 }
329
330 void DefectiveCrystalActor::refreshFrame(size_t frameID)
331 {
336 defectiveCrystal.initializeConfiguration(configIO());
337 nodes->updateConfiguration(configIO(), loops->dislocationNetwork.get());
338 segments->updateConfiguration(nodes->nodePolyData, slipSystemTab->slipSystemColorMap);
339 loops->updateConfiguration(slipSystemTab->slipSystemColorMap);
340 inclusions->updateConfiguration();
341 surfaceMesh->updateConfiguration();
342 glidePlanes->updateConfiguration();
343 quadrature->updateConfiguration(auxIO());
344 chartActor->updateConfiguration(frameID);
345 ddField->compute();
346 }
347
348 bool DefectiveCrystalActor::updateConfiguration(const size_t& frameID)
349 {
350 if(updatingFrame)
351 {
352 return false;
353 }
354 QScopedValueRollback<bool> updating(updatingFrame, true);
355 auto nextConfig = std::make_unique<DefectiveCrystalConfigIO<3>>(ddBase.simulationParameters.traitsIO.configFolder);
356 auto nextAux = std::make_unique<DefectiveCrystalAuxIO<3>>(ddBase.simulationParameters.traitsIO.auxFolder);
357 try
358 {
359 // Read both files before touching the displayed frame. DefectiveCrystalAuxIO's
360 // missing-file path asserts, so check availability explicitly.
361 nextConfig->read(frameID);
362 if(!nextAux->isBinGood(frameID) && !nextAux->isTxtGood(frameID))
363 {
364 throw std::runtime_error("No auxiliary file for frame " + std::to_string(frameID));
365 }
366 nextAux->read(frameID);
367 }
368 catch(const std::exception& error)
369 {
370 frameStatus->setText(QString::fromStdString(error.what()));
371 frameIDedit->setText(QString::number(loadedFrameID));
373 return false;
374 }
375
376 const bool ready = renderWindow->GetReadyForRendering();
377 renderWindow->SetReadyForRendering(false);
378 configuration.swap(nextConfig);
379 auxiliary.swap(nextAux);
380 try
381 {
382 refreshFrame(frameID);
383 }
384 catch(const std::exception& error)
385 {
386 configuration.swap(nextConfig);
387 auxiliary.swap(nextAux);
388 QString message = QString::fromStdString(error.what());
389 try
390 {
391 refreshFrame(loadedFrameID);
392 }
393 catch(const std::exception& restoreError)
394 {
395 message += " Restoration failed: " + QString::fromStdString(restoreError.what());
396 }
397 renderWindow->SetReadyForRendering(ready);
398 frameIDedit->setText(QString::number(loadedFrameID));
399 frameStatus->setText(message);
401 return false;
402 }
403
404 // Commit the frame only after every actor and Fields has been updated.
405 loadedFrameID = frameID;
406 frameLoaded = true;
407 frameIDedit->setText(QString::number(frameID));
408 readRotationSettings();
409 frameRotation.apply(renderer->GetActiveCamera(),
410 0.5 * (ddBase.mesh.xMin() + ddBase.mesh.xMax()),
411 rotationAxis, rotationDegreesPerFrame, frameID, cameraAdvancePerFrame);
412 renderer->ResetCameraClippingRange();
413 renderWindow->SetReadyForRendering(ready);
414 frameStatus->clear();
415 if(ready)
416 {
417 renderWindow->Render();
418 }
419 // Capture only the completed frame, after Fields and camera updates.
420 // A capture failure does not undo an otherwise valid frame load.
421 if(saveImage->isChecked())
422 {
423 const QString filename = QString::fromStdString(ddBase.simulationParameters.traitsIO.configFolder +
424 "/img_" + std::to_string(frameID) + ".png");
425 if(!ready || !qvtkGLwidget || !qvtkGLwidget->grabFramebuffer().save(filename, "PNG"))
426 {
427 frameStatus->setText("Frame loaded, but PNG capture failed.");
429 }
430 }
431 emit frameChanged(static_cast<qulonglong>(frameID));
432 return true;
433 }
434
436 {
437// nodes.modify();
438// segments.modify();
439// loops.modify();
440 }
441
442}
443#endif
static void force_count(const size_t &newCount)
Definition StaticID.h:79
QGridLayout * grid(QGroupBox *group)
QGroupBox * box(const QString &title, QWidget *parent, const QString &description, QCheckBox *toggle=nullptr)
void field(QGridLayout *layout, int row, const QString &title, QWidget *input, const QString &description)
size_t StaticID< Derived >::increment
Definition StaticID.h:103
QVTKOpenGLStereoWidget *const qvtkGLwidget
vtkGenericOpenGLRenderWindow *const renderWindow
void frameChanged(qulonglong frameID)
DefectiveCrystal< 3 > defectiveCrystal
DefectiveCrystalBase< 3 > & ddBase
DefectiveCrystalActor(vtkGenericOpenGLRenderWindow *const, vtkRenderer *const ren, QVTKOpenGLStereoWidget *const qvtkGLwidget_in, DefectiveCrystalBase< 3 > &ddBase_in)
const DefectiveCrystalConfigIO< 3 > & configIO() const
const DefectiveCrystalAuxIO< 3 > & auxIO() const
REAL angle
int number
increment
int axis
b poly
int current