Mantid
Loading...
Searching...
No Matches
Mantid3MFFileIO.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
12#include "MantidKernel/Matrix.h"
14#include "Poco/DOM/AutoPtr.h"
15#include "Poco/DOM/DOMParser.h"
16#include "Poco/DOM/Document.h"
17#include "Poco/DOM/NodeList.h"
18#include <Poco/DOM/Element.h>
19#include <boost/algorithm/string/trim.hpp>
20#include <iostream>
21
22namespace {
23constexpr auto SAMPLE_OBJECT_NAME = "SAMPLE";
24constexpr double M_TOLERANCE = 0.000001;
25} // namespace
26
27namespace Mantid {
28namespace DataHandling {
29
34void Mantid3MFFileIO::LoadFile(std::string filename) {
35
36 // Import Model from 3MF File
37 {
38 Lib3MF::PReader reader = model->QueryReader("3mf");
39 // And deactivate the strict mode (default is "false", anyway. This just
40 // demonstrates where/how to use it).
41 reader->SetStrictModeActive(false);
42 reader->ReadFromFile(filename);
43 m_filename = filename;
44
45 ScaleUnits scale;
46 switch (model->GetUnit()) {
47 case Lib3MF::eModelUnit::MilliMeter:
49 break;
50 case Lib3MF::eModelUnit::CentiMeter:
52 break;
53 case Lib3MF::eModelUnit::Meter:
54 scale = ScaleUnits::metres;
55 break;
56 default:
57 // reader defaults to mm for unknown units. For non-metric do likewise
58 // here
59 g_log.warning("Only m, cm and mm are supported in Mantid");
61 };
62 setScaleType(scale);
63
64 for (Lib3MF_uint32 iWarning = 0; iWarning < reader->GetWarningCount(); iWarning++) {
65 Lib3MF_uint32 nErrorCode;
66 std::string sWarningMessage = reader->GetWarning(iWarning, nErrorCode);
67 std::stringstream ss;
68 ss << "Encountered warning #" << nErrorCode << " : " << sWarningMessage << std::endl;
69 g_log.warning(ss.str());
70 }
71 }
72}
73
80MeshObject_sptr Mantid3MFFileIO::loadMeshObject(Lib3MF::PMeshObject meshObject, sLib3MFTransform buildTransform) {
81
82 Lib3MF_uint64 nVertexCount = meshObject->GetVertexCount();
83 Lib3MF_uint64 nTriangleCount = meshObject->GetTriangleCount();
84
85 g_log.debug("Mesh loaded");
86
87 g_log.debug("Name: \"" + meshObject->GetName() + "\"");
88 g_log.debug("PartNumber: \"" + meshObject->GetPartNumber() + "\"");
89 g_log.debug("Vertex count: " + std::to_string(nVertexCount));
90 g_log.debug("Triangle count: " + std::to_string(nTriangleCount));
91
92 uint32_t vertexCount = 0;
93 std::vector<Lib3MF::sTriangle> triangles;
94 meshObject->GetTriangleIndices(triangles);
95
96 m_triangle.clear();
97 m_vertices.clear();
98
99 for (auto i : triangles) {
100 m_triangle.push_back(i.m_Indices[0]);
101 m_triangle.push_back(i.m_Indices[1]);
102 m_triangle.push_back(i.m_Indices[2]);
103 }
104 std::vector<Lib3MF::sPosition> vertices;
105 meshObject->GetVertices(vertices);
106 for (auto i : vertices) {
107 Mantid::Kernel::V3D vertex = createScaledV3D(i.m_Coordinates[0], i.m_Coordinates[1], i.m_Coordinates[2]);
108 m_vertices.push_back(vertex);
109 }
110
112
113 Lib3MF_uint32 nResourceID;
114 Lib3MF_uint32 nPropertyID;
115
116 // load the material from the pid\pindex attributes on the mesh object
117 if (meshObject->GetObjectLevelProperty(nResourceID, nPropertyID)) {
118
119 Lib3MF::ePropertyType propType = model->GetPropertyTypeByID(nResourceID);
120
121 if (propType == Lib3MF::ePropertyType::BaseMaterial) {
122
123 Lib3MF::PBaseMaterialGroup baseMaterialGroup = model->GetBaseMaterialGroupByID(nResourceID);
125 std::string fullMaterialName = baseMaterialGroup->GetName(nPropertyID);
126 std::string materialName;
127 size_t openBracket = fullMaterialName.find("(");
128 size_t closeBracket = fullMaterialName.find(")");
129
130 materialName = fullMaterialName.substr(0, openBracket);
131 boost::algorithm::trim(materialName);
132 std::string xmlString;
133
134 xmlString = "<material id=\"" + materialName + "\" formula=\"" + materialName + "\"";
135 if ((openBracket != std::string::npos) && (closeBracket != std::string::npos)) {
136 std::string materialSpec = fullMaterialName.substr(openBracket + 1, closeBracket - openBracket - 1);
137 xmlString += " " + materialSpec;
138 }
139 xmlString += "></material>";
140 Poco::XML::DOMParser parser;
141 Poco::XML::AutoPtr<Poco::XML::Document> doc;
142 try {
143 doc = parser.parseString(xmlString);
144 Poco::XML::AutoPtr<Poco::XML::NodeList> materialElements = doc->getElementsByTagName("material");
145 Kernel::MaterialXMLParser materialParser;
146 material = materialParser.parse(static_cast<Poco::XML::Element *>(materialElements->item(0)), m_filename);
147 } catch (std::exception &e) {
148 g_log.warning("Unable to parse material properties for " + fullMaterialName +
149 " so material will be ignored: " + e.what());
150 };
151 }
152 };
153
154 auto mesh = std::make_shared<Geometry::MeshObject>(std::move(m_triangle), std::move(m_vertices), material);
155 mesh->setID(meshObject->GetName());
156
157 // 3MF stores transformation as a 4 x 3 matrix using row major convention
158 // The 4th column is implicit (0,0,0,1)
159 Kernel::Matrix<double> transformMatrix(4, 4);
160
161 // copy data into Mantid matrix and transpose
162 for (size_t i = 0; i < 4; i++) {
163 for (size_t j = 0; j < 3; j++) {
164 transformMatrix[j][i] = buildTransform.m_Fields[i][j];
165 }
166 }
167 // fill in the implicit row and make explicit
168 transformMatrix.setRow(3, {0, 0, 0, 1});
169 // scale the translations into metres
170 for (size_t i = 0; i < 3; i++) {
171 transformMatrix[i][3] = scaleValue(transformMatrix[i][3]);
172 }
173
174 mesh->multiply(transformMatrix);
175
176 return mesh;
177}
178
184void Mantid3MFFileIO::readMeshObjects(std::vector<MeshObject_sptr> &meshObjects, MeshObject_sptr &sample) {
185 Lib3MF::PBuildItemIterator buildItemIterator = model->GetBuildItems();
186 while (buildItemIterator->MoveNext()) {
187 Lib3MF::PBuildItem buildItem = buildItemIterator->GetCurrent();
188 uint32_t objectResourceID = buildItem->GetObjectResourceID();
189 sLib3MFTransform transform = buildItem->GetObjectTransform();
190 readMeshObject(meshObjects, sample, objectResourceID, transform);
191 }
192}
193
202void Mantid3MFFileIO::readMeshObject(std::vector<MeshObject_sptr> &meshObjects, MeshObject_sptr &sample,
203 uint32_t objectResourceID, sLib3MFTransform transform) {
204 // no general GetObjectByID in the lib3MF library??
205 try {
206 Lib3MF::PMeshObject meshObject = model->GetMeshObjectByID(objectResourceID);
207 std::string objectName = meshObject->GetName();
208 std::transform(objectName.begin(), objectName.end(), objectName.begin(), toupper);
209 if (objectName == SAMPLE_OBJECT_NAME) {
210 sample = loadMeshObject(meshObject, transform);
211 } else {
212 meshObjects.push_back(loadMeshObject(meshObject, transform));
213 }
214 } catch (std::exception) {
215 readComponents(meshObjects, sample, objectResourceID, transform);
216 }
217}
218/*
219 * Read in the set of mesh objects pointed to by a component object
220 * @param meshObjects A vector to store the meshes for env components
221 * @param sample A parameter to store a mesh for the sample
222 * @param objectResourceID Integer identifier of the object to read in
223 * @param transform Rotation/translation matrix for the object
224 */
225void Mantid3MFFileIO::readComponents(std::vector<MeshObject_sptr> &meshObjects, MeshObject_sptr &sample,
226 uint32_t objectResourceID, sLib3MFTransform transform) {
227 Lib3MF::PComponentsObject componentsObject = model->GetComponentsObjectByID(objectResourceID);
228 for (Lib3MF_uint32 nIndex = 0; nIndex < componentsObject->GetComponentCount(); nIndex++) {
229 Lib3MF::PComponent component = componentsObject->GetComponent(nIndex);
230 readMeshObject(meshObjects, sample, component->GetObjectResourceID(), transform);
231 }
232}
233
234/*
235 * Write a Mantid Geometry::MeshObjects to the lib3mf model object as part
236 * of processing of writing to a 3mf format file
237 * @param mantidMeshObject MeshObject to write out
238 * @param name Name of the mesh object
239 */
240void Mantid3MFFileIO::writeMeshObject(const Geometry::MeshObject &mantidMeshObject, std::string name) {
241 Lib3MF::PMeshObject meshObject = model->AddMeshObject();
242 meshObject->SetName(name);
243 std::vector<uint32_t> mantidTriangles = mantidMeshObject.getTriangles();
244 std::vector<Kernel::V3D> mantidVertices = mantidMeshObject.getV3Ds();
245 std::vector<sLib3MFTriangle> triangles;
246 std::vector<sLib3MFPosition> vertices;
247
248 // convert vertices from V3D to the Lib3MF struct
249 for (auto i : mantidVertices) {
250 sLib3MFPosition vertex = {
251 {static_cast<Lib3MF_single>(i.X()), static_cast<Lib3MF_single>(i.Y()), static_cast<Lib3MF_single>(i.Z())}};
252 vertices.push_back(vertex);
253 }
254
255 auto numTriangles = mantidMeshObject.numberOfTriangles();
256 for (size_t i = 0; i < numTriangles; i++) {
257 sLib3MFTriangle triangle;
258
259 Kernel::V3D a = mantidVertices[mantidTriangles[3 * i]];
260 Kernel::V3D b = mantidVertices[mantidTriangles[3 * i + 1]];
261 Kernel::V3D c = mantidVertices[mantidTriangles[3 * i + 2]];
262 Kernel::V3D centroid = (a + b + c) / 3;
263 Kernel::V3D b_minus_a = b - a;
264 Kernel::V3D c_minus_a = c - a;
265 Kernel::V3D faceNormal = normalize(b_minus_a.cross_prod(c_minus_a));
266 Geometry::Track faceNormalTrack = Geometry::Track(centroid, faceNormal);
267 mantidMeshObject.interceptSurface(faceNormalTrack);
268
269 // check if first link has any distance inside object
270 auto firstLink = faceNormalTrack.front();
271
272 if (firstLink.distInsideObject > M_TOLERANCE) {
273 g_log.debug("Face normal pointing to interior of object on object " + mantidMeshObject.id() +
274 ". Vertices swapped");
275 // swap order of b and c
276 mantidVertices[3 * i + 1] = c;
277 mantidVertices[3 * i + 2] = b;
278 }
279
280 for (int j = 0; j < 3; j++) {
281 triangle.m_Indices[j] = mantidTriangles[3 * i + j];
282 }
283
284 triangles.push_back(triangle);
285 }
286 meshObject->SetGeometry(vertices, triangles);
287
288 if (!mantidMeshObject.material().name().empty()) {
289 Lib3MF_uint32 materialPropertyID;
290 int baseMaterialsResourceID;
291
292 AddBaseMaterial(mantidMeshObject.material().name(), generateRandomColor(), baseMaterialsResourceID,
293 materialPropertyID);
294 meshObject->SetObjectLevelProperty(baseMaterialsResourceID, materialPropertyID);
295 }
296
297 // Set up one to one mapping between build items and mesh objects
298 // Don't bother setting up any components
299 sLib3MFTransform mMatrix = {{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, 0.0}}};
300 model->AddBuildItem(meshObject.get(), mMatrix);
301}
302
303/*
304 * Generate a random colour - to be used for a mesh object
305 * being written out to a .3mf format file
306 * @return RGB colour value
307 */
308int Mantid3MFFileIO ::generateRandomColor() {
309
310 int rColor = (rand() % 256) << 16;
311 int gColor = (rand() % 256) << 8;
312 int bColor = (rand() % 256) << 0;
313
314 return rColor | gColor | bColor;
315}
316
317/* Basic write to 3MF. Since Mantid is storing each instance of a shape
318 * as a separate mesh there's no possiblity of reusing meshes as supported
319 * by 3MF
320 * @param meshObjects vector of Mantid mesh objects for environment components
321 * to write out to file
322 * @param sample Mesh Object representing sample
323 * @param scaleType Units to write out coordinates in eg mm
324 */
325void Mantid3MFFileIO::writeMeshObjects(std::vector<const Geometry::MeshObject *> meshObjects,
327
328 Lib3MF::eModelUnit scale;
329 switch (scaleType) {
331 scale = Lib3MF::eModelUnit::MilliMeter;
332 break;
334 scale = Lib3MF::eModelUnit::CentiMeter;
335 break;
337 scale = Lib3MF::eModelUnit::Meter;
338 break;
339 default:
340 throw "Units not supported";
341 };
342 model->SetUnit(scale);
343
344 writeMeshObject(*sample, SAMPLE_OBJECT_NAME);
345
346 for (auto mantidMeshObject : meshObjects) {
347 writeMeshObject(*mantidMeshObject, mantidMeshObject->id());
348 }
349}
350
351/*
352 * Add a new material to the lib3mf model object in preparation for writing
353 * a 3mf format file
354 * @param materialName Name of material to be added
355 * @param materialColor RGB colour to be used to display material in CAD
356 * applications
357 * @param resourceID Out parameter representing object id of material created
358 * @param materialPropertyID Out parameter representing id of material created
359 */
360void Mantid3MFFileIO::AddBaseMaterial(std::string materialName, int materialColor, int &resourceID,
361 Lib3MF_uint32 &materialPropertyID) {
362 // check if master material definition exists
363 bool materialNameExists = false;
364
365 Lib3MF::PBaseMaterialGroupIterator materialIterator = model->GetBaseMaterialGroups();
366 Lib3MF::PBaseMaterialGroup groupToAddTo;
367
368 if (materialIterator->Count() == 0) {
369 groupToAddTo = model->AddBaseMaterialGroup();
370 } else {
371 while (materialIterator->MoveNext() && !materialNameExists) {
372 Lib3MF::PBaseMaterialGroup materialGroup = materialIterator->GetCurrentBaseMaterialGroup();
373 // by default add the new material to the 1st material group unless
374 // another group is found with the supplied materialName
375 if (!groupToAddTo) {
376 groupToAddTo = materialGroup;
377 }
378 std::vector<Lib3MF_uint32> materialPropertyIDs;
379 materialGroup->GetAllPropertyIDs(materialPropertyIDs);
380 for (auto i : materialPropertyIDs) {
381 std::string existingMaterialName = materialGroup->GetName(i);
382 if (materialName == existingMaterialName) {
383 materialNameExists = true;
384 groupToAddTo = materialGroup;
385 materialPropertyID = i;
386 break;
387 }
388 }
389 }
390 }
391 Lib3MF_uint8 rColor = (materialColor & 0xFF0000) >> 16;
392 Lib3MF_uint8 gColor = (materialColor & 0x00FF00) >> 8;
393 Lib3MF_uint8 bColor = (materialColor & 0x0000FF) >> 0;
394 if (!materialNameExists) {
395 materialPropertyID = groupToAddTo->AddMaterial(materialName, Lib3MF::sColor{rColor, gColor, bColor, 255});
396 }
397 resourceID = groupToAddTo->GetResourceID();
398}
399
400/*
401 * Assign a material to a mesh object. If master material definition doesn't
402 * already exist then create it first. Note that we don't write out the full
403 * material properties (eg density), only the colour
404 * @param objectName Name of object that material is to be assigned to
405 * @param materialName Name of material to be assigned to the object
406 * @param materialColor Colour of the material to be written into 3mf file
407 */
408void Mantid3MFFileIO::setMaterialOnObject(std::string objectName, std::string materialName, int materialColor) {
409
410 Lib3MF_uint32 materialPropertyID;
411 int baseMaterialsResourceID;
412 AddBaseMaterial(materialName, materialColor, baseMaterialsResourceID, materialPropertyID);
413
414 Lib3MF::PMeshObjectIterator meshObjectIterator = model->GetMeshObjects();
415 bool meshObjectFound = false;
416 while (meshObjectIterator->MoveNext()) {
417 Lib3MF::PMeshObject meshObject = meshObjectIterator->GetCurrentMeshObject();
418 // check if material already assigned to object
419 if (meshObject->GetName() == objectName) {
420 meshObjectFound = true;
421 Lib3MF_uint32 resourceID, propertyID;
422 bool propExists = meshObject->GetObjectLevelProperty(resourceID, propertyID);
423 // currently just setObjectLevelProperty in all 3 branches of the
424 // following so it's a bit verbose but different debug messages
425 if (propExists) {
426 Lib3MF::ePropertyType propType = model->GetPropertyTypeByID(resourceID);
427 if (baseMaterialsResourceID == resourceID) {
428 // update the material with the one in the input csv file
429 g_log.debug("Existing material found for object " + objectName + ". Overwriting with material " +
430 materialName + "supplied in csv file");
431 meshObject->SetObjectLevelProperty(baseMaterialsResourceID, materialPropertyID);
432 } else {
433 // if there's already a different property there (eg color) then
434 // overwrite it
435 g_log.debug("Existing non-material property found for object " + objectName +
436 ". Overwriting with material property with value " + materialName + " supplied in csv file");
437 // clear properties first because lib3mf seems to write existing
438 // color property down onto the individual triangles when you
439 // overwrite the object level property. Don't want this
440 meshObject->ClearAllProperties();
441 meshObject->SetObjectLevelProperty(baseMaterialsResourceID, materialPropertyID);
442 }
443 } else {
444 // add the material
445 meshObject->SetObjectLevelProperty(baseMaterialsResourceID, materialPropertyID);
446 }
447 }
448 }
449 if (!meshObjectFound) {
450 g_log.debug("Object " + objectName + " not found in 3MF file");
451 }
452}
453
454/*
455 * Write the 3mf data in the lib3mf model object out to a 3mf file
456 * @param filename Name of the 3mf file to be created
457 */
458void Mantid3MFFileIO::saveFile(std::string filename) {
459 Lib3MF::PWriter writer = model->QueryWriter("3mf");
460 writer->WriteToFile(filename);
461}
462
463} // namespace DataHandling
464} // namespace Mantid
void readMeshObject(std::vector< MeshObject_sptr > &meshObjects, MeshObject_sptr &sample, uint32_t objectResourceID, sLib3MFTransform transform)
Attempt to read a single mesh from a specified lib3mf resource id.
MeshObject_sptr loadMeshObject(Lib3MF::PMeshObject meshObject, sLib3MFTransform buildTransform)
Load a single mesh object into a Mantid Geometry::MeshObject.
void AddBaseMaterial(std::string materialName, int materialColor, int &resourceID, Lib3MF_uint32 &materialPropertyID)
void readComponents(std::vector< MeshObject_sptr > &meshObjects, MeshObject_sptr &sample, uint32_t objectResourceID, sLib3MFTransform transform)
void readMeshObjects(std::vector< MeshObject_sptr > &meshObjects, MeshObject_sptr &sample)
Read a set of mesh objects from the in memory lib3mf model object.
void writeMeshObjects(std::vector< const Geometry::MeshObject * > meshObjects, MeshObject_const_sptr &sample, DataHandling::ScaleUnits scale)
void writeMeshObject(const Geometry::MeshObject &meshObject, std::string name)
void LoadFile(std::string filename)
Load 3MF format file.
void saveFile(std::string filename)
void setMaterialOnObject(std::string objectName, std::string materialName, int materialColor)
std::vector< uint32_t > m_triangle
Definition: MeshFileIO.h:64
Kernel::V3D createScaledV3D(double xVal, double yVal, double zVal)
scales a 3D point according the units defined in the MeshFileIO class
Definition: MeshFileIO.cpp:53
double scaleValue(double val)
Definition: MeshFileIO.h:39
std::vector< Kernel::V3D > m_vertices
Definition: MeshFileIO.h:65
void setScaleType(const ScaleUnits scaleType)
Definition: MeshFileIO.h:67
Triangular Mesh Object.
Definition: MeshObject.h:50
size_t numberOfTriangles() const
Output functions for rendering, may also be used internally.
Definition: MeshObject.cpp:534
int interceptSurface(Geometry::Track &) const override
Given a track, fill the track with valid section.
Definition: MeshObject.cpp:147
std::vector< uint32_t > getTriangles() const
get faces
Definition: MeshObject.cpp:539
const std::vector< Kernel::V3D > & getV3Ds() const
get vertices in V3D form
Definition: MeshObject.cpp:554
const Kernel::Material & material() const override
Definition: MeshObject.cpp:48
const std::string & id() const override
Definition: MeshObject.h:70
Defines a track as a start point and a direction.
Definition: Track.h:165
LType::reference front()
Returns a reference to the first link.
Definition: Track.h:211
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
Read an XML definition of a Material and produce a new Material object.
Material parse(std::istream &istr) const
Takes a stream that is assumed to contain a single complete material definition, reads the definition...
A material is defined as being composed of a given element, defined as a PhysicalConstants::NeutronAt...
Definition: Material.h:50
const std::string & name() const
Returns the name of the material.
Definition: Material.cpp:181
Numerical Matrix class.
Definition: Matrix.h:42
void setRow(const size_t nRow, const std::vector< T > &newRow)
Definition: Matrix.cpp:686
Class for 3D vectors.
Definition: V3D.h:34
constexpr V3D cross_prod(const V3D &v) const noexcept
Cross product (this * argument)
Definition: V3D.h:278
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< Geometry::MeshObject > MeshObject_sptr
Typdef for a shared pointer.
std::shared_ptr< const Geometry::MeshObject > MeshObject_const_sptr
Typdef for a shared pointer to a const object.
MANTID_KERNEL_DLL V3D normalize(V3D v)
Normalizes a V3D.
Definition: V3D.h:341
Helper class which provides the Collimation Length for SANS instruments.
std::string to_string(const wide_integer< Bits, Signed > &n)
This struct contains the parameters for constructing a material, and gives them a default value for e...
Definition: ReadMaterial.h:32