Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testDDqtFrameWorkflow.cpp
Go to the documentation of this file.
3#include <DDqtMainWindow.h>
4#include <QApplication>
5#include <QPointer>
6#include <QEventLoop>
7#include <vtkRenderer.h>
8#include <vtkPolyDataMapper.h>
9#include <vtkActor2D.h>
10#include <vtkActorCollection.h>
11#include <vtkDataSetMapper.h>
12#include <vtkCallbackCommand.h>
13#include <vtkExecutive.h>
14#include <vtkProperty.h>
15#include <filesystem>
16#include <fstream>
17#include <chrono>
18#include <iostream>
19#include <atomic>
20#include <limits>
21
22void check(bool condition, const char *message)
23{
24 if(!condition)
25 {
26 throw std::runtime_error(message);
27 }
28}
29
30void checkDataSetMapperInputs(vtkRenderer* renderer)
31{
32 size_t checked=0;
33 auto* actors=renderer->GetActors();
34 actors->InitTraversal();
35 while(auto* actor=actors->GetNextActor())
36 {
37 auto* mapper=vtkDataSetMapper::SafeDownCast(actor->GetMapper());
38 if(mapper)
39 {
40 ++checked;
41 check(mapper->GetNumberOfInputConnections(0)==1,
42 "Dataset mapper has no input when inclusion physics is absent");
43 bool error=false;
44 auto callback=vtkSmartPointer<vtkCallbackCommand>::New();
45 callback->SetClientData(&error);
46 callback->SetCallback([](vtkObject*,unsigned long,void* data,void*)
47 {
48 *static_cast<bool*>(data)=true;
49 });
50 const auto observer=mapper->GetExecutive()->AddObserver(vtkCommand::ErrorEvent,callback);
51 mapper->Update();
52 mapper->GetExecutive()->RemoveObserver(observer);
53 check(!error,"Dataset mapper reported a pipeline error");
54 }
55 }
56 check(checked>0,"Expected at least the polyhedral inclusion dataset mapper");
57}
58
60{
61 auto window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
62 window->SetReadyForRendering(false);
63 auto renderer = vtkSmartPointer<vtkRenderer>::New();
64 window->AddRenderer(renderer);
65 auto source = vtkSmartPointer<vtkActor>::New();
66 auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
67 auto data = vtkSmartPointer<vtkPolyData>::New();
68 mapper->SetInputData(data);
69 source->SetMapper(mapper);
70 source->SetPosition(1, 2, 3);
71 source->RotateZ(30);
72 source->SetScale(2, 3, 4);
73 renderer->AddActor(source);
74 auto overlay = vtkSmartPointer<vtkActor2D>::New();
75 renderer->AddActor2D(overlay);
76 {
77 model::PeriodicImages images(renderer);
78 images.setTranslations({Eigen::Vector3d(10, 20, 30)});
79 check(renderer->GetViewProps()->GetNumberOfItems() == 3, "Incorrect periodic copy count");
80 auto* copy = vtkActor::SafeDownCast(renderer->GetViewProps()->GetLastProp());
81 check(copy && copy != source && copy->GetMapper() == mapper, "Copy does not share source geometry");
82 for(int row = 0; row < 4; ++row)
83 {
84 for(int col = 0; col < 4; ++col)
85 {
86 const double shift = col == 3 && row < 3 ? 10.0 * (row + 1) : 0.0;
87 check(std::abs(copy->GetMatrix()->GetElement(row, col) -
88 source->GetMatrix()->GetElement(row, col) - shift) < 1.e-12,
89 "Periodic translation corrupted source transformation");
90 }
91 }
92 source->VisibilityOff();
93 source->GetProperty()->SetOpacity(0.25);
94 auto replacement = vtkSmartPointer<vtkPolyDataMapper>::New();
95 replacement->SetInputData(data);
96 source->SetMapper(replacement);
97 renderer->InvokeEvent(vtkCommand::StartEvent);
98 check(!copy->GetVisibility() && copy->GetProperty()->GetOpacity() == 0.25 &&
99 copy->GetMapper() == replacement, "Render callback did not synchronize actor settings");
100 check(renderer->GetViewProps()->GetNumberOfItems() == 3, "Copies recursively replicated");
101 renderer->RemoveActor(source);
102 renderer->InvokeEvent(vtkCommand::StartEvent);
103 check(renderer->GetViewProps()->GetNumberOfItems() == 1, "Removed source retained its copies");
104 renderer->AddActor(source);
105 renderer->InvokeEvent(vtkCommand::StartEvent);
106 check(renderer->GetViewProps()->GetNumberOfItems() == 3, "New actor was not replicated");
107 }
108 check(renderer->GetViewProps()->GetNumberOfItems() == 2, "Periodic images leaked on destruction");
109 Eigen::Matrix<double, 3, Eigen::Dynamic> basis(3, 2);
110 basis << 10, 3, 0, 20, 0, 4;
111 {
112 model::PeriodicImagesWidget controls(window, renderer, basis, nullptr);
113 auto* group = controls.findChild<QGroupBox*>("periodicImagesGroup");
114 auto* first = controls.findChild<QSpinBox*>("periodicImageCount1");
115 auto* second = controls.findChild<QSpinBox*>("periodicImageCount2");
116 check(group && first && second && !group->isChecked(), "Missing periodic controls or wrong default");
117 first->setValue(1);
118 second->setValue(1);
119 group->setChecked(true);
120 check(renderer->GetViewProps()->GetNumberOfItems() == 10, "Two directions did not create nine cells");
121 std::set<std::array<int, 3>> actual;
122 vtkCollectionSimpleIterator iterator;
123 renderer->GetViewProps()->InitTraversal(iterator);
124 while(auto* prop = renderer->GetViewProps()->GetNextProp(iterator))
125 {
126 if(auto* actor = vtkActor::SafeDownCast(prop))
127 {
128 std::array<int, 3> shift;
129 for(int row = 0; row < 3; ++row)
130 {
131 shift[row] = std::lround(actor->GetMatrix()->GetElement(row, 3) - source->GetMatrix()->GetElement(row, 3));
132 }
133 actual.insert(shift);
134 }
135 }
136 for(int i = -1; i <= 1; ++i)
137 {
138 for(int j = -1; j <= 1; ++j)
139 {
140 check(actual.count({10*i + 3*j, 20*j, 4*j}) == 1, "Missing oblique lattice image");
141 }
142 }
143 group->setChecked(false);
144 check(renderer->GetViewProps()->GetNumberOfItems() == 2, "Disabling images did not restore original scene");
145 }
146}
147
149{
151 mutable std::atomic<bool> failNext{false};
152 VectorDim displacement(const VectorDim& point, const NodeType* node, const ElementType* element,
153 const SimplexDim* guess) const override
154 {
155 if(failNext.exchange(false))
156 {
157 throw std::runtime_error("Injected field failure");
158 }
159 return model::ElasticDeformation<3>::displacement(point, node, element, guess);
160 }
161};
162
163int main(int argc, char **argv)
164{
165 QApplication app(argc, argv);
166 try
167 {
169 check(argc == 2, "Expected repository root");
170 const std::filesystem::path root(argv[1]);
171 const auto dir =
172 std::filesystem::temp_directory_path() /
173 ("model-surface-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
174 for(const auto *name : {"inputFiles", "config", "F"})
175 {
176 std::filesystem::create_directories(dir / name);
177 }
178 const auto input = dir / "inputFiles";
179 std::filesystem::copy_file(root / "Library/Materials/W.txt", input / "W.txt");
180 std::filesystem::copy_file(root / "Library/Meshes/unitCube24.msh", input / "mesh.msh");
181 {
182 std::ofstream f(input / "DefectiveCrystal.txt");
183 f << "physics=ElasticDeformation;\nuseFEM=0;\nNsteps=1;\nmaxResolveSteps=0;\ndtMax=1;\n"
184 << "outputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\n";
185 std::ofstream p(input / "polycrystal.txt");
186 p << "materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\n"
187 << "C2G1=1 0 0 0 1 0 0 0 1;\nF=100 0 0 0 100 0 0 0 100;\nX0=0 0 0;\nperiodicFaceIDs=;\n";
188 std::ofstream e(input / "ElasticDeformation.txt");
189 e << "useElasticDeformationFEM=0;\ninertiaReliefPenaltyFactor=1e-3;\n"
190 << "ExternalStress0=0 0 .01 0 0 0;\nExternalStressRate=0 0 .01 0 0 0;\n"
191 << "ExternalStrain0=0 0 0 0 0 0;\nExternalStrainRate=0 0 0 0 0 0;\n"
192 << "stiffnessRatio=0 0 0 0 0 0;\n";
193 }
194 // Reopening a visualization-only tutorial leaves empty history files.
195 // Explicit startAtTimeStep=0 must not attempt to dereference a missing row.
196 std::ofstream(dir / "F/F_0.txt").close();
197 std::ofstream(dir / "F/F_labels.txt").close();
198 {
199 model::DefectiveCrystalBase<3> base(dir.string());
200 check(base.simulationParameters.runID == 0 && base.simulationParameters.totalTime == 0.0,
201 "Empty history did not retain the default restart state");
202
203 model::DefectiveCrystalConfigIO<3> config((dir / "config").string());
204 model::DefectiveCrystalAuxIO<3> aux((dir / "config").string());
205 for(size_t frame : {0, 10, 20})
206 {
207 config.write(frame, false);
208 aux.write(frame, false);
209 }
210 auto window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
211 window->SetReadyForRendering(false);
212 auto renderer = vtkSmartPointer<vtkRenderer>::New();
213 window->AddRenderer(renderer);
214 auto *viewer = new model::DefectiveCrystalActor(window, renderer, nullptr, base);
215 QPointer<model::DefectiveCrystalActor> guardedViewer(viewer);
216 checkDataSetMapperInputs(renderer);
217 check(viewer->updateConfiguration(0), "Initial frame load failed");
218 checkDataSetMapperInputs(renderer);
219 // Add a sampling plane so each successful load must refresh Fields.
220 viewer->ddField->findChildren<QSpinBox*>().front()->setValue(1);
221 auto *plane = viewer->ddField->findChild<model::DDPlaneField*>();
222 check(plane && !plane->dataPnts().empty(), "Missing field sampling plane");
223 int notifications = 0;
224 QObject::connect(viewer, &model::DefectiveCrystalActor::frameChanged, viewer, [&](qulonglong frame)
225 {
226 ++notifications;
227 check(viewer->frameIDedit->text().toULongLong() == frame, "Frame ID not committed before notification");
228 check(plane->dataPnts().front().displacement.front().allFinite(), "Fields not ready at frame notification");
229 });
230 auto poisonField = [&]()
231 {
232 plane->dataPnts().front().displacement.front().setConstant(std::numeric_limits<double>::quiet_NaN());
233 };
234 viewer->frameIncrementEdit->setText("10");
235 poisonField();
236 viewer->plusFrameButton->click();
237 check(notifications == 1 && plane->dataPnts().front().displacement.front().allFinite(), "Next did not update Fields");
238 poisonField();
239 viewer->frameIDedit->setText("0");
240 viewer->frameIDedit->setModified(true);
241 QMetaObject::invokeMethod(viewer->frameIDedit, "editingFinished", Qt::DirectConnection);
242 check(notifications == 2 && plane->dataPnts().front().displacement.front().allFinite(), "Frame edit did not update Fields");
243 poisonField();
244 viewer->stopFrameButton->click();
245 QMetaObject::invokeMethod(viewer, "stopConfigurations", Qt::DirectConnection);
246 check(!plane->dataPnts().front().displacement.front().allFinite(), "Stop recomputed Fields");
247 QMetaObject::invokeMethod(viewer, "playConfigurations", Qt::DirectConnection);
248 QMetaObject::invokeMethod(viewer, "playConfigurations", Qt::DirectConnection);
249 auto timers = viewer->findChildren<QTimer*>("framePlaybackTimer");
250 check(timers.size() == 1 && timers.front()->isActive(), "Play created duplicate timers");
251 QMetaObject::invokeMethod(timers.front(), "timeout", Qt::DirectConnection);
252 check(viewer->frameIDedit->text() == "10" && plane->dataPnts().front().displacement.front().allFinite(), "Playback did not update Fields");
253 checkDataSetMapperInputs(renderer);
254 viewer->stopConfigurations();
255 check(!timers.front()->isActive(), "Stop did not stop the timer immediately");
256 const int committed = notifications;
257 const auto pointsTime = viewer->nodes->nodePolyData->GetMTime();
258 check(!viewer->updateConfiguration(999), "Missing frame unexpectedly loaded");
259 check(notifications == committed && viewer->frameIDedit->text() == "10" &&
260 viewer->nodes->nodePolyData->GetMTime() == pointsTime, "Failed load changed the scene or frame ID");
261 config.write(30, false); // Missing auxiliary data must fail before touching actors.
262 check(!viewer->updateConfiguration(30) && viewer->nodes->nodePolyData->GetMTime() == pointsTime,
263 "Missing auxiliary file partially updated the scene");
264 auto failingPhysics = std::make_shared<FailingElastic>(viewer->defectiveCrystal);
265 viewer->defectiveCrystal.microstructures().front() = failingPhysics;
266 const auto *previousConfig = &viewer->configIO();
267 const auto previousCamera = Eigen::Vector3d(renderer->GetActiveCamera()->GetPosition());
268 failingPhysics->failNext = true;
269 check(!viewer->updateConfiguration(20), "Field failure did not abort the frame");
270 check(&viewer->configIO() == previousConfig && viewer->frameIDedit->text() == "10" &&
271 notifications == committed, "Field failure did not roll back configuration and frame ID");
272 check(plane->dataPnts().front().displacement.front().allFinite(), "Rollback did not restore field samples");
273 check(Eigen::Vector3d(renderer->GetActiveCamera()->GetPosition()).isApprox(previousCamera),
274 "Failed frame changed the camera");
275 viewer->saveImage->setChecked(true);
276 check(viewer->updateConfiguration(20) && viewer->frameStatus->text().contains("capture failed"),
277 "Capture failure incorrectly undid a valid frame");
278 check(plane->dataPnts().front().displacement.front().allFinite(), "Capture preceded field refresh");
279 viewer->saveImage->setChecked(false);
280 const Eigen::Vector3d center=0.5*(base.mesh.xMin()+base.mesh.xMax());
281 auto* camera=renderer->GetActiveCamera();
282 const Eigen::Vector3d initialPosition=center+Eigen::Vector3d(10,0,0);
283 camera->SetPosition(initialPosition.data());
284 camera->SetFocalPoint(center.data());
285 camera->SetViewUp(0,0,1);
286 viewer->cameraAdvanceEdit->setText("1");
287 viewer->rotationAngleEdit->setText("0");
288 auto manualFrame=[&](const char* text)
289 {
290 viewer->frameIDedit->setText(text);
291 viewer->frameIDedit->setModified(true);
292 QMetaObject::invokeMethod(viewer->frameIDedit,"editingFinished",Qt::DirectConnection);
293 };
294 manualFrame("10");
295 check(Eigen::Vector3d(camera->GetPosition()).isApprox(center),
296 "Manual frame selection did not advance camera to center");
297 manualFrame("20");
298 check(Eigen::Vector3d(camera->GetPosition()).isApprox(center-Eigen::Vector3d(10,0,0)),
299 "Manual frame selection did not advance camera past center");
300 const int beforeRepeatedFinish=notifications;
301 QMetaObject::invokeMethod(viewer->frameIDedit,"editingFinished",Qt::DirectConnection);
302 check(notifications==beforeRepeatedFinish,"Focus loss reloaded an already committed frame");
303 manualFrame("0");
304 check(Eigen::Vector3d(camera->GetPosition()).isApprox(initialPosition),
305 "Manual backward selection did not restore camera position");
306 // The main window must actually destroy removed pages and their timers.
307 model::DDqtMainWindow mainWindow;
308 auto *tabs = qobject_cast<QTabWidget*>(mainWindow.centralWidget());
309 const int index = tabs->addTab(viewer, "Test viewer");
310 QMetaObject::invokeMethod(viewer, "playConfigurations", Qt::DirectConnection);
311 QPointer<QTimer> timer(timers.front());
312 QMetaObject::invokeMethod(&mainWindow, "closeTab", Qt::DirectConnection, Q_ARG(int, index));
313 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
314 check(guardedViewer.isNull() && timer.isNull(), "Closing the tab retained the viewer or timer");
315 }
316 std::filesystem::remove_all(dir);
317 std::cout << "PASS: frame navigation, Fields ordering, playback, failed loads, capture failure and tab cleanup\n";
318 return 0;
319 }
320 catch(const std::exception &error)
321 {
322 std::cerr << error.what() << '\n';
323 return 1;
324 }
325}
void write(const size_t &runID, const bool &outputBinary)
function int_R syms a b c u real f
Definition int_R.m:4
VectorDim displacement(const VectorDim &point, const NodeType *node, const ElementType *element, const SimplexDim *guess) const override
std::atomic< bool > failNext
void frameChanged(qulonglong frameID)
MicrostructureBase< dim >::VectorDim VectorDim
MicrostructureBase< dim >::SimplexDim SimplexDim
ElasticDeformation(MicrostructureContainerType &mc)
MicrostructureBase< dim >::NodeType NodeType
VectorDim displacement(const VectorDim &, const NodeType *const, const ElementType *const, const SimplexDim *const) const override
MicrostructureBase< dim >::ElementType ElementType
std::string text(Config &config)
void checkPeriodicImages()
void check(bool condition, const char *message)
void checkDataSetMapperInputs(vtkRenderer *renderer)
int j
void argv
int index
void b int argc
vertex p[3]
int i
int e
vertex first