Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testSurfaceMesh.cpp
Go to the documentation of this file.
2#include <SurfaceMeshActor.h>
4#include <vtkLightCollection.h>
6#include <QApplication>
7#include <vtkCellData.h>
8#include <vtkProperty.h>
9#include <vtkPointData.h>
10#include <vtkPoints.h>
11#include <vtkCellArray.h>
12#include <filesystem>
13#include <fstream>
14#include <chrono>
15#include <iostream>
16#include <limits>
17#ifdef _OPENMP
18#include <omp.h>
19#endif
20
21using V = Eigen::Vector3d;
22using M = Eigen::Matrix3d;
23void check(bool condition, const char *message)
24{
25 if(!condition)
26 {
27 throw std::runtime_error(message);
28 }
29}
30
31int main(int argc, char **argv)
32{
33 QApplication app(argc, argv);
34 try
35 {
36 {
37 auto camera = vtkSmartPointer<vtkCamera>::New();
38 const V boxCenter(1, 2, 3);
39 camera->SetFocalPoint(101, 202, 303);
40 camera->SetPosition(111, 202, 303);
41 camera->SetViewUp(0, 0, 1);
43 rotation.apply(camera, boxCenter, V(0, 0, 2), 0.5, 180);
44 check(V(camera->GetPosition()).isApprox(V(1, 12, 3)), "Frame rotation ignored runID or axis normalization");
45 rotation.apply(camera, boxCenter, V::UnitZ(), 0.5, 360);
46 check(V(camera->GetPosition()).isApprox(V(-9, 2, 3)), "Sparse frame rotation is incorrect");
47 rotation.apply(camera, boxCenter, V::UnitZ(), 0.5, 180);
48 rotation.apply(camera, boxCenter, V::UnitZ(), 0.5, 180);
49 check(V(camera->GetPosition()).isApprox(V(1, 12, 3)), "Backward or repeated frame accumulated rotation");
50 rotation.apply(camera, boxCenter, V::UnitZ(), 0.0, 180);
51 check(V(camera->GetPosition()).isApprox(V(11, 2, 3)), "Disabling rotation did not restore the view");
52 rotation.apply(camera, boxCenter, V::UnitY(), -0.5, 180);
53 check(V(camera->GetPosition()).isApprox(V(1, 2, 13)), "Axis change or negative angle is incorrect");
54 check(V(camera->GetViewUp()).isApprox(V(-1, 0, 0)), "Camera up vector did not rotate");
55 camera->Dolly(2.0);
56 rotation.apply(camera, boxCenter, V::UnitY(), -0.5, 360);
57 check(std::abs(camera->GetDistance() - 5.0) < 1.e-10, "Frame rotation lost interactive zoom");
58 check(V(camera->GetFocalPoint()).isApprox(boxCenter), "Rotation did not target the box center");
59 const V offset = V(camera->GetPosition()) - boxCenter;
60 const V pan(7, 8, 9);
61 const V pannedPosition = boxCenter + offset + pan;
62 const V pannedFocal = boxCenter + pan;
63 camera->SetPosition(pannedPosition.data());
64 camera->SetFocalPoint(pannedFocal.data());
65 rotation.apply(camera, boxCenter, V::UnitY(), -0.5, 360);
66 check(V(camera->GetFocalPoint()).isApprox(boxCenter) &&
67 V(camera->GetPosition()).isApprox(boxCenter + offset),
68 "Manual panning moved the automatic rotation center");
69 rotation.apply(camera, boxCenter, V::UnitY(), -0.5, 0);
70 check(V(camera->GetFocalPoint()).isApprox(boxCenter), "Frame zero lost the box center");
71 }
72 {
73 auto camera = vtkSmartPointer<vtkCamera>::New();
74 camera->SetPosition(10,0,0);
75 camera->SetFocalPoint(0,0,0);
76 camera->SetViewUp(0,0,1);
77 camera->ParallelProjectionOn();
79 motion.apply(camera,V::Zero(),V::UnitZ(),0,10,1);
80 check(V(camera->GetPosition()).norm()<1.e-10, "Camera did not reach center");
81 check(camera->GetDistance()>9.99 && !camera->GetParallelProjection(), "Fly-through collapsed view or retained parallel projection");
82 motion.apply(camera,V::Zero(),V::UnitZ(),0,15,1);
83 check(V(camera->GetPosition()).isApprox(V(-5,0,0)), "Camera did not pass through center");
84 motion.apply(camera,V::Zero(),V::UnitZ(),18,5,1);
85 motion.apply(camera,V::Zero(),V::UnitZ(),18,5,1);
86 check(V(camera->GetPosition()).isApprox(V(0,5,0)), "Combined rotation/travel or repeated frame incorrect");
87 motion.apply(camera,V::Zero(),V::UnitZ(),0,15,0);
88 check(V(camera->GetPosition()).isApprox(V(10,0,0)), "Disabling camera motion did not restore position");
89 }
90 check(argc == 2 || argc == 3, "Expected repository root and optional --without-dd");
91 const bool withDD=argc==2;
92 const std::filesystem::path root(argv[1]);
93 const auto dir =
94 std::filesystem::temp_directory_path() /
95 ("model-surface-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
96 for(const auto *name : {"inputFiles", "config", "F"})
97 {
98 std::filesystem::create_directories(dir / name);
99 }
100 const auto input = dir / "inputFiles";
101 std::filesystem::copy_file(root / "Library/Materials/W.txt", input / "W.txt");
102 std::filesystem::copy_file(root / "Library/DislocationDynamics/DD.txt", input / "DD.txt");
103 std::filesystem::copy_file(root / "Library/Meshes/unitCube24.msh", input / "mesh.msh");
104 {
105 std::ofstream f(input / "DefectiveCrystal.txt");
106 f << "physics=" << (withDD ? "ElasticDeformation DislocationDynamics" : "ElasticDeformation") << ";\nuseFEM=0;\nNsteps=1;\nmaxResolveSteps=0;\ndtMax=1;\n"
107 << "outputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\n";
108 std::ofstream p(input / "polycrystal.txt");
109 p << "materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\n"
110 << "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";
111 std::ofstream e(input / "ElasticDeformation.txt");
112 e << "useElasticDeformationFEM=0;\ninertiaReliefPenaltyFactor=1e-3;\n"
113 << "ExternalStress0=0 0 .01 0 0 0;\nExternalStressRate=0 0 .01 0 0 0;\n"
114 << "ExternalStrain0=0 0 0 0 0 0;\nExternalStrainRate=0 0 0 0 0 0;\n"
115 << "stiffnessRatio=0 0 0 0 0 0;\n";
116 }
117 {
118 model::DefectiveCrystalBase<3> base(dir.string());
119 const auto coarse = model::SurfaceMeshGeometry::build(base.mesh, 2500);
120 const auto fine = model::SurfaceMeshGeometry::build(base.mesh, 625);
121 std::set<size_t> allExternalIDs;
122 std::set<const model::Simplex<3, 2> *> firstFaceSimplices;
123 size_t firstFaceID = 0;
124 for(const auto &region : base.mesh.regions())
125 {
126 for(const auto &face : region.second->faces())
127 {
128 if(face.second->isExternal())
129 {
130 if(allExternalIDs.empty())
131 {
132 firstFaceID = face.second->sID;
133 firstFaceSimplices = face.second->simplices();
134 }
135 allExternalIDs.insert(face.second->sID);
136 }
137 }
138 }
139 auto selectedIDs=allExternalIDs;
140 selectedIDs.erase(firstFaceID);
141 const auto excluded = model::SurfaceMeshGeometry::build(base.mesh, 2500, selectedIDs);
142 check(excluded.triangles.size() == coarse.triangles.size() - firstFaceSimplices.size(),
143 "Exclusion did not remove the Mesh-tab face's actual simplices");
144 for(const auto *face : excluded.boundaryFaces)
145 {
146 check(!firstFaceSimplices.count(face), "Excluded simplex remains in the surface");
147 }
148 check(model::SurfaceMeshGeometry::build(base.mesh, 2500, {}).triangles.empty(),
149 "Excluding every face did not produce an empty surface");
150 check(coarse.triangles.size() == 24 && fine.triangles.size() == 96, "Incorrect boundary refinement");
151 // Nonaligned, aligned, and intersecting traces must produce true seams.
152 for(const double offset : {0.0, 7.0})
153 {
154 model::SurfaceMeshGeometry::Trace xTrace{V(offset, 0, 0), V::UnitX(), {}};
155 model::SurfaceMeshGeometry::Trace yTrace{V(0, -9, 0), V::UnitY(), {}};
156 for(const auto *face : fine.boundaryFaces)
157 {
158 xTrace.faces.insert(face);
159 yTrace.faces.insert(face);
160 }
161 const auto cut = fine.split({xTrace, yTrace, xTrace});
162 check(!cut.walls.empty(), "Missing slip walls");
163 // Exercise the actual solid-angle branch jump, including its
164 // relative on-plane tolerance, rather than only a synthetic step.
166 patch.globalPatches()[nullptr] = {V(offset, -100, -100), V(offset, 100, -100), V(offset, 100, 100),
167 V(offset, -100, 100)};
168 bool checkedJump = false;
169 for(const auto &wall : cut.walls)
170 {
171 const size_t a = wall[1], b = wall[2];
172 if((cut.referencePoints[a] - cut.referencePoints[b]).norm() < 1.e-8 &&
173 std::abs(cut.referencePoints[a].x() - offset) < 1.e-8 &&
174 (cut.samplePoints[a].x() - offset) * (cut.samplePoints[b].x() - offset) < 0.0)
175 {
176 const double jump =
177 std::abs(patch.solidAngle(cut.samplePoints[a]) - patch.solidAngle(cut.samplePoints[b])) /
178 (4 * std::acos(-1.0));
179 check(jump > 0.9 && jump < 1.1, "One-sided samples lost the solid-angle displacement jump");
180 checkedJump = true;
181 }
182 }
183 check(checkedJump, "No paired trace samples tested");
184 double cutArea = 0;
185 for(const auto &t : cut.triangles)
186 {
187 const V a = cut.referencePoints[t[0]], b = cut.referencePoints[t[1]], c = cut.referencePoints[t[2]];
188 const V cross = (b - a).cross(c - a);
189 cutArea += cross.norm() / 2;
190 check(cross.dot((a + b + c) / 3) > 0, "Cut reversed or degenerated surface triangle");
191 for(const auto &trace : {xTrace, yTrace})
192 {
193 double lo = 0, hi = 0;
194 for(const auto id : t)
195 {
196 const double d = trace.normal.dot(cut.referencePoints[id] - trace.point);
197 lo = std::min(lo, d);
198 hi = std::max(hi, d);
199 }
200 check(lo > -1.e-8 || hi < 1.e-8, "Triangle bridges a slip trace");
201 }
202 }
203 check(std::abs(cutArea - 60000) < 1.e-7, "Trace cuts changed exterior area");
204 std::vector<V> jump;
205 for(const auto &sample : cut.samplePoints)
206 {
207 jump.push_back(V(0, 0, (sample.x() > offset ? 1.0 : 0.0) + (sample.y() > -9 ? 2.0 : 0.0)));
208 }
209 const auto moved = cut.displaced(jump, 10);
210 double wallArea = 0;
211 double topWallArea = 0;
212 for(const auto &t : cut.walls)
213 {
214 const V a = cut.referencePoints[t[0]], b = cut.referencePoints[t[1]], c = cut.referencePoints[t[2]];
215 check((b - a).cross(c - a).norm() < 1.e-8, "Wall has finite reference width");
216 const double area = (moved[t[1]] - moved[t[0]]).cross(moved[t[2]] - moved[t[0]]).norm() / 2;
217 wallArea += area;
218 if(std::abs(a.z() - 50) < 1.e-8 && std::abs(b.z() - 50) < 1.e-8 && std::abs(c.z() - 50) < 1.e-8)
219 {
220 topWallArea += area;
221 }
222 }
223 check(wallArea > 0, "Step walls collapsed after displacement");
224 check(std::abs(topWallArea - 3000) < 1.e-6, "Missing or duplicate wall segments at trace crossings");
225 }
226 std::map<std::pair<size_t, size_t>, size_t> edges;
227 double area = 0;
228 for(const auto &t : fine.triangles)
229 {
230 const V a = fine.referencePoints[t[0]], b = fine.referencePoints[t[1]], c = fine.referencePoints[t[2]];
231 const V cross = (b - a).cross(c - a);
232 check(cross.dot((a + b + c) / 3) > 0, "Inward surface triangle");
233 check(cross.norm() / 2 <= 625 * (1 + 1.e-12), "Triangle area exceeds requested limit");
234 area += cross.norm() / 2;
235 for(int k = 0; k < 3; ++k)
236 {
237 const auto endpoints = std::minmax(t[k], t[(k + 1) % 3]);
238 ++edges[{endpoints.first, endpoints.second}];
239 }
240 }
241 check(std::abs(area - 60000) < 1.e-8, "Incorrect exterior area or internal faces included");
242 for(const auto &edge : edges)
243 {
244 check(edge.second == 2, "Crack or duplicate triangles in reference surface");
245 }
246 for(const auto &p : fine.referencePoints)
247 {
248 check(std::abs(p.cwiseAbs().maxCoeff() - 50) < 1.e-10, "Surface point off mesh boundary");
249 }
250 std::vector<V> step;
251 for(const auto &p : fine.referencePoints)
252 {
253 step.push_back(V(0, 0, p.x() > 0 ? 1 : 0));
254 }
255 const auto stepped = fine.displaced(step, 10);
256 const auto reference = fine.displaced(step, 0);
257 for(size_t k = 0; k < step.size(); ++k)
258 {
259 check((stepped[k] - fine.referencePoints[k]).isApprox(10 * step[k]), "Step amplitude lost");
260 check(reference[k].isApprox(fine.referencePoints[k]), "Zero amplification did not restore surface");
261 }
262 for(double badArea : {0.0, -1.0, 1.e-20})
263 {
264 bool rejected = false;
265 try
266 {
267 model::SurfaceMeshGeometry::build(base.mesh, badArea);
268 }
269 catch(const std::invalid_argument &)
270 {
271 rejected = true;
272 }
273 check(rejected, "Invalid or excessive refinement accepted");
274 }
275 model::DefectiveCrystal<3> crystal(base);
276 model::DefectiveCrystalConfigIO<3> config((dir / "config").string());
277 crystal.initializeConfiguration(config);
278 auto window = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
279 window->SetReadyForRendering(false);
280 auto renderer = vtkSmartPointer<vtkRenderer>::New();
281 window->AddRenderer(renderer);
282 const int originalLights = renderer->GetLights()->GetNumberOfItems();
283 {
284 model::SurfaceMeshActor surface(window, renderer, crystal);
285 auto *ambient = surface.findChild<QDoubleSpinBox*>("surfaceAmbient");
286 auto *diffuse = surface.findChild<QDoubleSpinBox*>("surfaceDiffuse");
287 auto *specular = surface.findChild<QDoubleSpinBox*>("surfaceSpecular");
288 check(ambient && diffuse && specular, "Missing shading controls");
289 check(ambient->value() == 0.25 && diffuse->value() == 0.75 && specular->value() == 0.0,
290 "Incorrect shading defaults");
291 const auto meshTime = surface.polyData->GetMTime();
292 ambient->setValue(0.4);
293 diffuse->setValue(0.5);
294 specular->setValue(0.2);
295 check(surface.actor->GetProperty()->GetAmbient() == 0.4 &&
296 surface.actor->GetProperty()->GetDiffuse() == 0.5 &&
297 surface.actor->GetProperty()->GetSpecular() == 0.2,
298 "Shading controls did not update the material immediately");
299 check(surface.polyData->GetMTime() == meshTime, "Shading change rebuilt the surface");
300 surface.meshSizeEdit->setText("625");
301 surface.externalFaces->setChecked(true);
302 surface.updateConfiguration();
303 check(surface.polyData->GetNumberOfPoints() == 0, "Hidden surface computed eagerly");
304 // Cut polygons have separate vertex IDs, even away from a step.
305 // Rendering must smooth coincident neighbors without welding a tiny gap.
306 auto sample = vtkSmartPointer<vtkPolyData>::New();
307 auto samplePoints = vtkSmartPointer<vtkPoints>::New();
308 samplePoints->SetDataTypeToDouble();
309 for(const V& point : {V(0,0,0), V(1,0,0), V(0,1,0),
310 V(1,0,0), V(0,0,0), V(0,-1,0.02),
311 V(0,0,1.e-9), V(1,0,1.e-9), V(0,1,1.e-9)})
312 {
313 samplePoints->InsertNextPoint(point.data());
314 }
315 auto sampleCells = vtkSmartPointer<vtkCellArray>::New();
316 for(vtkIdType first = 0; first < 9; first += 3)
317 {
318 const vtkIdType ids[3] = {first, first + 1, first + 2};
319 sampleCells->InsertNextCell(3, ids);
320 }
321 sample->SetPoints(samplePoints);
322 sample->SetPolys(sampleCells);
323 surface.shadingMesh->SetInputData(sample);
324 surface.normals->Update();
325 check(surface.shadingMesh->GetOutput()->GetNumberOfPoints() == 7,
326 "Shading failed to merge duplicates or closed a small step");
327 auto* shaded = surface.normals->GetOutput();
328 bool smooth = false;
329 for(vtkIdType k = 0; k < shaded->GetNumberOfPoints(); ++k)
330 {
331 double xyz[3], normal[3];
332 shaded->GetPoint(k, xyz);
333 if(V(xyz).norm() == 0.0)
334 {
335 shaded->GetPointData()->GetNormals()->GetTuple(k, normal);
336 smooth = normal[1] > 0.001 && normal[1] < 0.019;
337 }
338 }
339 check(smooth, "Shading retained a normal jump across nearly coplanar triangles");
340 surface.shadingMesh->SetInputData(surface.polyData);
341 surface.showSurface->setChecked(true);
342 check(surface.actor->GetVisibility() && surface.polyData->GetNumberOfCells() == 96,
343 "Surface tab did not build after enable");
344 check(surface.normals->GetOutput()->GetCellData()->GetNormals() != nullptr, "Missing updated normals");
345 check(renderer->GetLights()->IsItemPresent(surface.reliefLight), "Missing surface relief light");
346 for(size_t k = 0; k < fine.referencePoints.size(); ++k)
347 {
348 double xyz[3];
349 surface.polyData->GetPoint(k, xyz);
350 const V expected =
351 fine.referencePoints[k] +
352 surface.amplification->value() *
353 crystal.displacement(fine.referencePoints[k], nullptr, nullptr, fine.parents[k]);
354 check(V(xyz[0], xyz[1], xyz[2]).isApprox(expected, 1.e-12),
355 "Surface did not use displacement field");
356 }
357 check(surface.microstructuresCheck.size() == crystal.microstructures().size(),
358 "Missing physics selectors");
359 auto *physics = surface.microstructuresCheck.front();
360 check(physics->isChecked() && physics->text().toStdString() == crystal.microstructures().front()->tag,
361 "Physics selector does not match Fields defaults");
362 physics->setChecked(false);
363 for(size_t k = 0; k < fine.referencePoints.size(); ++k)
364 {
365 double xyz[3];
366 surface.polyData->GetPoint(k, xyz);
367 check(V(xyz[0], xyz[1], xyz[2]).isApprox(fine.referencePoints[k]),
368 "Unselected physics still contributes displacement");
369 }
370 surface.updateConfiguration();
371 check(!physics->isChecked(), "Frame update lost physics selection");
372 surface.showSurface->setChecked(false);
373 physics->setChecked(true);
374 surface.showSurface->setChecked(true);
375 for(size_t k = 0; k < fine.referencePoints.size(); ++k)
376 {
377 double xyz[3];
378 surface.polyData->GetPoint(k, xyz);
379 const V expected =
380 fine.referencePoints[k] +
381 surface.amplification->value() *
382 crystal.displacement(fine.referencePoints[k], nullptr, nullptr, fine.parents[k]);
383 check(V(xyz[0], xyz[1], xyz[2]).isApprox(expected, 1.e-12),
384 "Hidden physics selection did not refresh on enable");
385 }
386 double first[3], second[3];
387 surface.polyData->GetPoint(0, first);
388 base.simulationParameters.totalTime = 1.0;
389 surface.updateConfiguration();
390 surface.polyData->GetPoint(0, second);
391 check((V(first[0], first[1], first[2]) - V(second[0], second[1], second[2])).norm() > 0,
392 "Frame update did not recompute surface displacement");
393 surface.updateConfiguration();
394 surface.polyData->GetPoint(0, first);
395 check(V(first[0], first[1], first[2]).isApprox(V(second[0], second[1], second[2])),
396 "Repeated frame accumulated deformation");
397 surface.amplification->setValue(0);
398 surface.polyData->GetPoint(0, first);
399 check(V(first[0], first[1], first[2]).isApprox(fine.referencePoints[0]),
400 "Amplification control failed");
401 check(!surface.clipSurface->isChecked(), "Clipping should be disabled initially");
402 surface.clipSurface->setChecked(true);
403 const auto checkClipped = [&](const V &center, const V &normal)
404 {
405 surface.normals->Update();
406 auto *output = surface.normals->GetOutput();
407 check(output->GetNumberOfPoints() > 0, "Clipping unexpectedly removed the entire surface");
408 for(vtkIdType k = 0; k < output->GetNumberOfPoints(); ++k)
409 {
410 double xyz[3];
411 output->GetPoint(k, xyz);
412 check(normal.dot(V(xyz[0], xyz[1], xyz[2]) - center) >= -1.e-6,
413 "Rendered surface contains points on discarded side");
414 }
415 };
416 checkClipped(V::Zero(), V::UnitX());
417 surface.clipCenterEdit->setText("10 0 0");
418 surface.clipNormalEdit->setText("-2 0 0");
419 surface.clipNormalEdit->returnPressed();
420 checkClipped(V(10, 0, 0), -V::UnitX());
421 surface.updateConfiguration();
422 checkClipped(V(10, 0, 0), -V::UnitX());
423 check(surface.clipPositionSlider->isEnabled(), "Clip slider not enabled with clipping");
424 surface.clipPositionSlider->setValue(100);
425 const double offset = (base.mesh.xMax() - base.mesh.xMin()).norm() * 0.1;
426 const V shifted = V(10, 0, 0) - offset * V::UnitX();
427 check(V(surface.clipPlane->GetOrigin()).isApprox(shifted), "Slider moved off the clip normal");
428 check(surface.clipCenterEdit->text() == "10 0 0", "Slider overwrote reference center");
429 checkClipped(shifted, -V::UnitX());
430 surface.updateConfiguration();
431 check(V(surface.clipPlane->GetOrigin()).isApprox(shifted), "Frame update lost slider offset");
432 surface.clipSurface->setChecked(false);
433 check(!surface.clipPositionSlider->isEnabled(), "Disabled clip slider remains active");
434 surface.clipSurface->setChecked(true);
435 check(V(surface.clipPlane->GetOrigin()).isApprox(shifted), "Clip toggle lost slider offset");
436 surface.clipNormalEdit->setText("1 1 0");
437 surface.clipNormalEdit->returnPressed();
438 check(surface.clipPositionSlider->value() == 0, "New plane did not reset slider offset");
439 surface.clipPositionSlider->setValue(-100);
440 const V diagonalNormal = V(1, 1, 0).normalized();
441 check(V(surface.clipPlane->GetOrigin()).isApprox(V(10, 0, 0) - offset * diagonalNormal),
442 "Slider failed for oblique normal");
443 surface.clipNormalEdit->setText("-2 0 0");
444 surface.clipNormalEdit->returnPressed();
445 surface.clipNormalEdit->setText("0 0 0");
446 surface.updateClipping();
447 check(!surface.clipStatus->text().isEmpty() && surface.clipSurface->isChecked(),
448 "Invalid clipping normal was not rejected");
449 checkClipped(V(10, 0, 0), -V::UnitX());
450 surface.clipSurface->setChecked(false);
451 check(surface.normals->GetOutput()->GetNumberOfCells() == 96,
452 "Disabling clipping did not restore complete surface");
453
454 surface.showSurface->setChecked(false);
455 surface.updateConfiguration();
456 check(!surface.actor->GetVisibility(), "Hidden surface remains visible");
457 check(!renderer->GetLights()->IsItemPresent(surface.reliefLight),
458 "Hidden surface changed scene lighting");
459 surface.showSurface->setChecked(true);
460 check(surface.actor->GetVisibility(), "Surface failed to refresh after re-enable");
461 surface.meshSizeEdit->setText("invalid");
462 surface.rebuild();
463 check(!surface.actor->GetVisibility(), "Invalid mesh input left stale surface visible");
464 surface.meshSizeEdit->setText("2500");
465 surface.rebuild();
466 check(surface.actor->GetVisibility() && surface.polyData->GetNumberOfCells() == 24,
467 "Surface did not recover after correcting mesh input");
468 }
469 if(withDD)
470 {
471 auto plane = base.dislocationDynamics().glidePlaneFactory.getFromKey(
472 model::GlidePlaneKey<3>(V::Zero(), base.poly.grain(1)->reciprocalLatticeDirection(V(1, 1, 0))));
473 model::SurfaceMeshActor surface(window, renderer, crystal);
474 surface.meshSizeEdit->setText("625");
475 surface.externalFaces->setChecked(true);
476 surface.updateConfiguration();
477 surface.showSurface->setChecked(true);
478 check(surface.actor->GetVisibility(), "Factory trace extraction failed");
479 check(surface.polyData->GetNumberOfPoints() > static_cast<vtkIdType>(fine.referencePoints.size()),
480 "Live glide plane did not split surface");
481 plane.reset();
482 surface.updateConfiguration();
483 check(surface.polyData->GetNumberOfPoints() == static_cast<vtkIdType>(fine.referencePoints.size()),
484 "Expired glide plane cuts survived frame update");
485 }
486 if(withDD)
487 {
488#ifdef _OPENMP
489 const int savedThreads = omp_get_max_threads();
490 const int savedDynamic = omp_get_dynamic();
491 omp_set_dynamic(0);
492 omp_set_num_threads(1);
493#endif
494 // Enough samples to exercise the parallel branch, including
495 // one-sided samples where a live glide plane cuts the surface.
496 auto plane = base.dislocationDynamics().glidePlaneFactory.getFromKey(
497 model::GlidePlaneKey<3>(V::Zero(), base.poly.grain(1)->reciprocalLatticeDirection(V(1, 1, 0))));
498 model::SurfaceMeshActor surface(window, renderer, crystal);
499 surface.meshSizeEdit->setText("39.0625");
500 surface.externalFaces->setChecked(true);
501 surface.updateConfiguration();
502 surface.showSurface->setChecked(true);
503 check(surface.actor->GetVisibility() && surface.polyData->GetNumberOfPoints() >= 256,
504 "Parallel sampling fixture failed");
505 std::vector<V> serial;
506 for(vtkIdType k = 0; k < surface.polyData->GetNumberOfPoints(); ++k)
507 {
508 double xyz[3];
509 surface.polyData->GetPoint(k, xyz);
510 serial.emplace_back(xyz[0], xyz[1], xyz[2]);
511 }
512#ifdef _OPENMP
513 omp_set_num_threads(4);
514#endif
515 surface.updateConfiguration();
516 check(surface.actor->GetVisibility() &&
517 surface.polyData->GetNumberOfPoints() == static_cast<vtkIdType>(serial.size()),
518 "Parallel update failed");
519 for(size_t k = 0; k < serial.size(); ++k)
520 {
521 double xyz[3];
522 surface.polyData->GetPoint(k, xyz);
523 check(V(xyz[0], xyz[1], xyz[2]).isApprox(serial[k], 1.e-12),
524 "Serial and parallel displacement differ");
525 }
526 const double savedTime = base.simulationParameters.totalTime;
527 base.simulationParameters.totalTime = std::numeric_limits<double>::quiet_NaN();
528 surface.updateConfiguration();
529 check(!surface.actor->GetVisibility() && surface.status->text().contains("Nonfinite displacement"),
530 "Worker failure was not reported safely");
531 base.simulationParameters.totalTime = savedTime;
532 surface.updateConfiguration();
533 check(surface.actor->GetVisibility(), "Surface did not recover after worker failure");
534 surface.meshSizeEdit->setText("2500");
535 surface.rebuild();
536 auto *faceCheck = surface.findChild<QCheckBox*>(QString("surfaceExternalFace_%1").arg(firstFaceID));
537 check(faceCheck != nullptr, "Missing external face selection");
538 check(faceCheck->isChecked() && surface.externalFaces->isChecked() && !surface.internalFaces->isChecked(),
539 "Incorrect default face selections");
540 faceCheck->setChecked(false);
541 check(surface.polyData->GetNumberOfCells() == static_cast<vtkIdType>(excluded.triangles.size()),
542 "Face checkbox did not rebuild the surface");
543 surface.updateConfiguration();
544 check(surface.polyData->GetNumberOfCells() == static_cast<vtkIdType>(excluded.triangles.size()),
545 "Frame update lost face exclusions");
546 for(const auto id : allExternalIDs)
547 {
548 surface.findChild<QCheckBox*>(QString("surfaceExternalFace_%1").arg(id))->setChecked(false);
549 }
550 check(surface.polyData->GetNumberOfCells() == 0, "Selecting all faces left stale geometry");
551 surface.externalFaces->setChecked(false);
552 surface.externalFaces->setChecked(true);
553 check(surface.polyData->GetNumberOfCells()==0,"External group lost the empty selection");
554 faceCheck->setChecked(true);
555 surface.externalFaces->setChecked(false);
556 check(surface.polyData->GetNumberOfCells()==0,"Disabled external group still renders faces");
557 surface.externalFaces->setChecked(true);
558 check(faceCheck->isChecked() && surface.polyData->GetNumberOfCells()==static_cast<vtkIdType>(firstFaceSimplices.size()),
559 "External group did not restore the individual selection");
560#ifdef _OPENMP
561 omp_set_num_threads(savedThreads);
562 omp_set_dynamic(savedDynamic);
563#endif
564 }
565 check(renderer->GetActors()->GetNumberOfItems() == 0, "Surface actor leaked after destruction");
566 check(renderer->GetLights()->GetNumberOfItems() == originalLights,
567 "Surface relief light leaked after destruction");
568 }
569 std::filesystem::remove_all(dir);
570 std::cout
571 << "PASS: surface mesh topology, refinement, displacement steps, frame updates, controls and lifecycle\n";
572 return 0;
573 }
574 catch(const std::exception &error)
575 {
576 std::cerr << error.what() << '\n';
577 return 1;
578 }
579}
Eigen::Matrix3d M
Eigen::Vector3d V
void initializeConfiguration(const DefectiveCrystalConfigIO< dim > &configIO)
void apply(vtkCamera *camera, const Eigen::Vector3d &center, const Eigen::Vector3d &axis, double degreesPerFrame, size_t frameID, double advancePerFrame=0.0)
const vtkSmartPointer< vtkLight > reliefLight
std::vector< QCheckBox * > microstructuresCheck
QLineEdit *const clipNormalEdit
QSlider *const clipPositionSlider
const vtkSmartPointer< vtkPolyData > polyData
QGroupBox *const externalFaces
QCheckBox *const clipSurface
const vtkSmartPointer< vtkPlane > clipPlane
const vtkSmartPointer< vtkPolyDataNormals > normals
QGroupBox *const internalFaces
QLineEdit *const clipCenterEdit
const vtkSmartPointer< vtkCleanPolyData > shadingMesh
QCheckBox *const showSurface
QLineEdit *const meshSizeEdit
const vtkSmartPointer< vtkActor > actor
QDoubleSpinBox *const amplification
final point of the source segment t
c
Definition int_R.m:15
function int_R syms a b c u real f
Definition int_R.m:4
a
Definition int_R.m:14
b
Definition int_R.m:16
LatticePlaneKey< dim > GlidePlaneKey
const GlobalPatchPositionType & globalPatches() const
double solidAngle(const VectorDim &x) const
VectorDim displacement(const VectorDim &, const NodeType *const, const ElementType *const, const SimplexDim *const) const override
std::set< const Simplex< 3, 2 > * > faces
static SurfaceMeshGeometry build(const SimplicialMesh< 3 > &mesh, double maxArea)
void check(bool condition, const char *message)
m edges
void argv
void b int argc
vertex p[3]
int k
int e
REAL area
vertex first