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);
72 source->SetScale(2, 3, 4);
73 renderer->AddActor(source);
74 auto overlay = vtkSmartPointer<vtkActor2D>::New();
75 renderer->AddActor2D(overlay);
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)
84 for(
int col = 0; col < 4; ++col)
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");
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");
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;
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");
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))
126 if(
auto* actor = vtkActor::SafeDownCast(prop))
128 std::array<int, 3> shift;
129 for(
int row = 0; row < 3; ++row)
131 shift[row] = std::lround(actor->GetMatrix()->GetElement(row, 3) - source->GetMatrix()->GetElement(row, 3));
133 actual.insert(shift);
136 for(
int i = -1;
i <= 1; ++
i)
138 for(
int j = -1;
j <= 1; ++
j)
140 check(actual.count({10*i + 3*j, 20*j, 4*j}) == 1,
"Missing oblique lattice image");
143 group->setChecked(
false);
144 check(renderer->GetViewProps()->GetNumberOfItems() == 2,
"Disabling images did not restore original scene");
169 check(
argc == 2,
"Expected repository root");
170 const std::filesystem::path root(
argv[1]);
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"})
176 std::filesystem::create_directories(dir / name);
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");
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";
196 std::ofstream(dir /
"F/F_0.txt").close();
197 std::ofstream(dir /
"F/F_labels.txt").close();
200 check(base.simulationParameters.runID == 0 && base.simulationParameters.totalTime == 0.0,
201 "Empty history did not retain the default restart state");
205 for(
size_t frame : {0, 10, 20})
207 config.
write(frame,
false);
208 aux.write(frame,
false);
210 auto window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
211 window->SetReadyForRendering(
false);
212 auto renderer = vtkSmartPointer<vtkRenderer>::New();
213 window->AddRenderer(renderer);
215 QPointer<model::DefectiveCrystalActor> guardedViewer(viewer);
217 check(viewer->updateConfiguration(0),
"Initial frame load failed");
220 viewer->ddField->findChildren<QSpinBox*>().front()->setValue(1);
222 check(plane && !plane->dataPnts().empty(),
"Missing field sampling plane");
223 int notifications = 0;
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");
230 auto poisonField = [&]()
232 plane->dataPnts().front().displacement.front().setConstant(std::numeric_limits<double>::quiet_NaN());
234 viewer->frameIncrementEdit->setText(
"10");
236 viewer->plusFrameButton->click();
237 check(notifications == 1 && plane->dataPnts().front().displacement.front().allFinite(),
"Next did not update Fields");
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");
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");
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);
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)
290 viewer->frameIDedit->setText(
text);
291 viewer->frameIDedit->setModified(
true);
292 QMetaObject::invokeMethod(viewer->frameIDedit,
"editingFinished",Qt::DirectConnection);
295 check(Eigen::Vector3d(camera->GetPosition()).isApprox(center),
296 "Manual frame selection did not advance camera to center");
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");
304 check(Eigen::Vector3d(camera->GetPosition()).isApprox(initialPosition),
305 "Manual backward selection did not restore camera position");
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");
316 std::filesystem::remove_all(dir);
317 std::cout <<
"PASS: frame navigation, Fields ordering, playback, failed loads, capture failure and tab cleanup\n";
320 catch(
const std::exception &error)
322 std::cerr << error.what() <<
'\n';