Mantid
Loading...
Searching...
No Matches
SampleEnvironmentSpecParser.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 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 +
10#ifdef ENABLE_LIB3MF
12#endif
15
18
19#include "Poco/DOM/AutoPtr.h"
20#include "Poco/DOM/DOMParser.h"
21#include "Poco/DOM/DOMWriter.h"
22#include "Poco/DOM/Document.h"
23#include "Poco/DOM/NamedNodeMap.h"
24#include "Poco/DOM/NodeFilter.h"
25#include "Poco/DOM/NodeIterator.h"
26#include "Poco/SAX/InputSource.h"
27#include "Poco/SAX/SAXException.h"
28
29#include <boost/algorithm/string.hpp>
30#include <filesystem>
31#include <memory>
32#include <sstream>
33
34using namespace Poco::XML;
35
36//------------------------------------------------------------------------------
37// Anonymous
38//------------------------------------------------------------------------------
39namespace {
40std::string MATERIALS_TAG = "materials";
41std::string COMPONENTS_TAG = "components";
42std::string FULL_SPEC_TAG = "fullspecification";
43std::string COMPONENT_TAG = "component";
44std::string CONTAINERS_TAG = "containers";
45std::string CONTAINER_TAG = "container";
46std::string COMPONENTGEOMETRY_TAG = "geometry";
47std::string SAMPLEGEOMETRY_TAG = "samplegeometry";
48std::string COMPONENTSTLFILE_TAG = "stlfile";
49std::string SAMPLESTLFILE_TAG = "samplestlfile";
50} // namespace
51
52namespace Mantid::DataHandling {
53
54namespace {
55
56std::string MATERIALS_TAG = "materials";
57std::string COMPONENTS_TAG = "components";
58std::string COMPONENT_TAG = "component";
59std::string GLOBAL_OFFSET_TAG = "globaloffset";
60std::string TRANSLATION_VECTOR_TAG = "translationvector";
61std::string STL_FILENAME_TAG = "stlfilename";
62std::string SCALE_TAG = "scale";
63std::string XDEGREES_TAG = "xdegrees";
64std::string YDEGREES_TAG = "ydegrees";
65std::string ZDEGREES_TAG = "zdegrees";
66} // namespace
67
68namespace {
69double DegreesToRadians(double angle) { return angle * M_PI / 180; }
70} // namespace
71//------------------------------------------------------------------------------
72// Public methods
73//------------------------------------------------------------------------------
83SampleEnvironmentSpec_uptr SampleEnvironmentSpecParser::parse(const std::string &name, const std::string &filename,
84 std::istream &istr) {
85 using DocumentPtr = AutoPtr<Document>;
86
87 InputSource src(istr);
88 DOMParser parser;
89 // Do not use auto here or anywhereas the Poco API returns raw pointers
90 // but in some circumstances requires AutoPtrs to manage the memory
91 DocumentPtr doc;
92 try {
93 doc = parser.parse(&src);
94 } catch (SAXParseException &exc) {
95 std::ostringstream msg;
96 msg << "SampleEnvironmentSpecParser::parse() - Error parsing content "
97 "as valid XML: "
98 << exc.what();
99 throw std::runtime_error(msg.str());
100 }
101 m_filepath = filename;
102 return parse(name, doc->documentElement());
103}
104
113SampleEnvironmentSpec_uptr SampleEnvironmentSpecParser::parse(const std::string &name, Poco::XML::Element *element) {
114 validateRootElement(element);
115
116 // Iterating is apparently much faster than getElementsByTagName
117 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
118 Node *node = nodeIter.nextNode();
119 auto spec = std::make_unique<SampleEnvironmentSpec>(name);
120
121 while (node) {
122 auto *childElement = static_cast<Element *>(node);
123 if (node->nodeName() == MATERIALS_TAG) {
124 parseMaterials(childElement);
125 } else if (node->nodeName() == COMPONENTS_TAG) {
126 parseAndAddComponents(spec.get(), childElement);
127 } else if (node->nodeName() == FULL_SPEC_TAG) {
128 loadFullSpecification(spec.get(), childElement);
129 }
130 node = nodeIter.nextNode();
131 }
132 return spec;
133}
134
135//------------------------------------------------------------------------------
136// Private methods
137//------------------------------------------------------------------------------
142void SampleEnvironmentSpecParser::validateRootElement(Poco::XML::Element *element) const {
143 if (element->nodeName() != ROOT_TAG) {
144 std::ostringstream msg;
145 msg << "SampleEnvironmentSpecParser::validateRootElement() - Element tag "
146 "does not match '"
147 << ROOT_TAG << "'. Found " << element->nodeName() << "\n";
148 throw std::invalid_argument(msg.str());
149 }
150}
151
156void SampleEnvironmentSpecParser::parseMaterials(Poco::XML::Element *element) {
158
159 m_materials.clear();
160 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
161 // Points at <materials>
162 nodeIter.nextNode();
163 // Points at first <material>
164 Node *node = nodeIter.nextNode();
165 MaterialXMLParser parser;
166 while (node) {
167 auto material = parser.parse(static_cast<Poco::XML::Element *>(node), m_filepath);
168 m_materials.emplace(material.name(), material);
169 node = nodeIter.nextNode();
170 }
171}
172
180 if (m_materials.empty()) {
181 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponents() - "
182 "Trying to parse list of components but no "
183 "materials have been found. Please ensure the "
184 "materials are defined first.");
185 }
186 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
187 // Points at <components>
188 nodeIter.nextNode();
189 // Points at first <child>
190 Node *node = nodeIter.nextNode();
191 while (node) {
192 auto *childElement = static_cast<Element *>(node);
193 const auto &nodeName = childElement->nodeName();
194 if (nodeName == CONTAINERS_TAG) {
195 parseAndAddContainers(spec, childElement);
196 } else if (nodeName == COMPONENT_TAG) {
197 spec->addComponent(parseComponent(childElement));
198 }
199 node = nodeIter.nextNode();
200 }
201}
202
205 auto filename = element->getAttribute("filename");
206 if (!filename.empty()) {
207
208 std::string stlFileName = findFile(filename);
209
210 std::filesystem::path suppliedFileName(stlFileName);
211 std::string fileExt = suppliedFileName.extension().string().substr(1); // drop the '.'
212 std::transform(fileExt.begin(), fileExt.end(), fileExt.begin(), toupper);
213
214 std::vector<std::shared_ptr<Geometry::MeshObject>> environmentMeshes;
215 std::shared_ptr<Geometry::MeshObject> sampleMesh;
216
217 if (fileExt == "3MF") {
218#ifdef ENABLE_LIB3MF
219 Mantid3MFFileIO MeshLoader;
220 MeshLoader.LoadFile(stlFileName);
221
222 MeshLoader.readMeshObjects(environmentMeshes, sampleMesh);
223#else
224 throw std::runtime_error("3MF format not supported on this platform");
225#endif
226
227 for (auto cpt : environmentMeshes) {
228 if (spec->ncans() == 0) {
229 // 3mf format doesn't nicely support multiple cans so just
230 // hardcode id to default
231 cpt->setID("default");
232 auto can = std::make_shared<Container>(cpt);
233 can->setSampleShape(sampleMesh);
234 spec->addContainer(can);
235 } else {
236
237 spec->addComponent(cpt);
238 }
239 }
240 } else {
241 throw std::runtime_error("Full specification must be a .3mf file");
242 }
243 } else {
244 throw std::runtime_error("fullspecification element supplied without a filename");
245 }
246}
247
255 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
256 nodeIter.nextNode();
257 Node *node = nodeIter.nextNode();
258 while (node) {
259 auto *childElement = static_cast<Element *>(node);
260 if (childElement->nodeName() == CONTAINER_TAG) {
261 spec->addContainer(parseContainer(childElement));
262 }
263 node = nodeIter.nextNode();
264 }
265}
266
274 auto can = std::make_shared<Container>(parseComponent(element));
275 auto sampleGeometry = element->getChildElement(SAMPLEGEOMETRY_TAG);
276 auto sampleSTLFile = element->getChildElement(SAMPLESTLFILE_TAG);
277
278 if ((sampleGeometry) && (sampleSTLFile)) {
279 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - "
280 "Cannot define sample using both a" +
281 SAMPLEGEOMETRY_TAG + " and a " + SAMPLESTLFILE_TAG + " child tag.");
282 }
283
284 if (sampleGeometry) {
285 DOMWriter writer;
286 std::stringstream sampleShapeXML;
287 writer.writeNode(sampleShapeXML, sampleGeometry);
288 can->setSampleShape(sampleShapeXML.str());
289 }
290 if (sampleSTLFile) {
291 can->setSampleShape(loadMeshFromSTL(sampleSTLFile));
292 }
293 return can;
294}
295
302void SampleEnvironmentSpecParser::LoadOptionalDoubleFromXML(Poco::XML::Element *componentElement,
303 const std::string &attributeName,
304 double &targetVariable) const {
305
306 auto attributeText = componentElement->getAttribute(attributeName);
307 if (!attributeText.empty()) {
308 try {
309 targetVariable = std::stod(attributeText);
310 } catch (std::invalid_argument &ex) {
311 throw std::invalid_argument(std::string("Invalid string supplied for " + attributeName + " ") + ex.what());
312 }
313 }
314}
315
321std::vector<double> SampleEnvironmentSpecParser::parseTranslationVector(const std::string &translationVectorStr) const {
322
323 std::vector<double> translationVector;
324
325 // Split up comma-separated properties
327 tokenizer values(translationVectorStr, ",", tokenizer::TOK_IGNORE_EMPTY | tokenizer::TOK_TRIM);
328
329 translationVector.clear();
330 translationVector.reserve(values.count());
331
332 std::transform(values.cbegin(), values.cend(), std::back_inserter(translationVector),
333 [](const std::string &str) { return boost::lexical_cast<double>(str); });
334 return translationVector;
335}
336
337std::string SampleEnvironmentSpecParser::findFile(const std::string &filename) const {
338 std::filesystem::path suppliedStlFileName(filename);
339 std::filesystem::path stlFileName;
340 if (suppliedStlFileName.is_relative()) {
341 bool useSearchDirectories = true;
342 if (!m_filepath.empty()) {
343 // if environment spec xml came from a file, search in the same
344 // directory as the file
345 stlFileName = std::filesystem::path(m_filepath).parent_path() / filename;
346 if (std::filesystem::exists(stlFileName)) {
347 useSearchDirectories = false;
348 }
349 }
350
351 if (useSearchDirectories) {
352 // ... and if that doesn't work look in the search directories
353 std::string foundFile = Mantid::API::FileFinder::Instance().getFullPath(filename);
354 if (!foundFile.empty()) {
355 stlFileName = std::filesystem::path(foundFile);
356 } else {
357 stlFileName = suppliedStlFileName;
358 }
359 }
360 } else {
361 stlFileName = suppliedStlFileName;
362 }
363 return stlFileName.string();
364}
365
372std::shared_ptr<Geometry::MeshObject> SampleEnvironmentSpecParser::loadMeshFromSTL(Element *stlFileElement) const {
373 std::string filename = stlFileElement->getAttribute("filename");
374 if (!filename.empty()) {
375
376 std::string stlFileName = findFile(filename);
377
378 if (std::filesystem::exists(stlFileName)) {
379
380 std::string scaleStr = stlFileElement->getAttribute("scale");
381 if (scaleStr.empty()) {
382 throw std::runtime_error("Scale must be supplied for stl file:" + filename);
383 }
384 const ScaleUnits scaleType = getScaleTypeFromStr(scaleStr);
385
386 std::unique_ptr<LoadStl> reader = LoadStlFactory::createReader(stlFileName, scaleType);
387
388 std::shared_ptr<Geometry::MeshObject> comp = reader->readShape();
389
390 Element *rotation = stlFileElement->getChildElement("rotation");
391 if (rotation) {
392
393 double xDegrees = 0, yDegrees = 0, zDegrees = 0;
394 LoadOptionalDoubleFromXML(rotation, XDEGREES_TAG, xDegrees);
395 LoadOptionalDoubleFromXML(rotation, YDEGREES_TAG, yDegrees);
396 LoadOptionalDoubleFromXML(rotation, ZDEGREES_TAG, zDegrees);
397
398 const double xRotation = DegreesToRadians(xDegrees);
399 const double yRotation = DegreesToRadians(yDegrees);
400 const double zRotation = DegreesToRadians(zDegrees);
401 comp = reader->rotate(comp, xRotation, yRotation, zRotation);
402 }
403 Element *translation = stlFileElement->getChildElement("translation");
404 if (translation) {
405 std::string translationVectorStr = translation->getAttribute("vector");
406 const std::vector<double> translationVector = parseTranslationVector(translationVectorStr);
407 comp = reader->translate(comp, translationVector);
408 }
409 return comp;
410 } else {
411 throw std::runtime_error("Unable to find STLFile " + filename);
412 }
413 } else {
414 throw std::runtime_error("STLFile element supplied without a filename");
415 }
416}
417
424std::shared_ptr<Geometry::IObject> SampleEnvironmentSpecParser::parseComponent(Element *element) const {
425 Element *geometry = element->getChildElement(COMPONENTGEOMETRY_TAG);
426 Element *stlfile = element->getChildElement(COMPONENTSTLFILE_TAG);
427 if ((!geometry) && (!stlfile)) {
428 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - Expected a " + COMPONENTGEOMETRY_TAG +
429 " or " + COMPONENTSTLFILE_TAG + " child tag. None found.");
430 }
431 if ((geometry) && (stlfile)) {
432 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - "
433 "Cannot define container using both a" +
434 COMPONENTGEOMETRY_TAG + " and a " + COMPONENTSTLFILE_TAG + " child tag.");
435 }
436
437 std::shared_ptr<Geometry::IObject> comp;
438 if (stlfile) {
439 comp = loadMeshFromSTL(stlfile);
440 } else {
442 comp = factory.createShape(geometry);
443 }
444 auto materialID = element->getAttribute("material");
445 auto iter = m_materials.find(materialID);
447 if (iter != m_materials.end()) {
448 mat = iter->second;
449 } else {
450 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - "
451 "Unable to find material with id=" +
452 materialID);
453 }
454 comp->setID(element->getAttribute("id"));
455 comp->setMaterial(mat);
456 return comp;
457}
458
459} // namespace Mantid::DataHandling
std::string name
Definition Run.cpp:60
Mantid::Kernel::Quat(ComponentInfo::* rotation)(const size_t) const
static std::unique_ptr< LoadStl > createReader(const std::string &filename, ScaleUnits scaleType)
Class to load and save .3mf files .3mf format is a 3D manufacturing format for storing mesh descripti...
void readMeshObjects(std::vector< MeshObject_sptr > &meshObjects, MeshObject_sptr &sample)
Read a set of mesh objects from the in memory lib3mf model object.
void LoadFile(std::string filename)
Load 3MF format file.
std::shared_ptr< Geometry::IObject > parseComponent(Poco::XML::Element *element) const
Parse a single definition of a component.
Geometry::Container_const_sptr parseContainer(Poco::XML::Element *element) const
Parse a single definition of a Can.
std::string findFile(const std::string &filename) const
void parseAndAddComponents(SampleEnvironmentSpec *spec, Poco::XML::Element *element) const
Take a <components> tag, parse the definitions and add them to the spec.
void parseMaterials(Poco::XML::Element *element)
Parse the set of materials in the document.
std::shared_ptr< Geometry::MeshObject > loadMeshFromSTL(Poco::XML::Element *stlFileElement) const
Create a mesh shape from an STL input file.
void parseAndAddContainers(SampleEnvironmentSpec *spec, Poco::XML::Element *element) const
Take a <containers> tag, parse the definitions and add them to the spec.
SampleEnvironmentSpec_uptr parse(const std::string &name, const std::string &filename, std::istream &istr)
Takes a stream that is assumed to contain a single complete SampleEnvironmentSpec definition,...
void loadFullSpecification(SampleEnvironmentSpec *spec, Poco::XML::Element *element)
void validateRootElement(Poco::XML::Element *element) const
Validate that the element points to the expected root element.
std::vector< double > parseTranslationVector(const std::string &translationVectorStr) const
Take a comma separated translation vector and return it as a std::vector.
void LoadOptionalDoubleFromXML(Poco::XML::Element *componentElement, const std::string &elementName, double &targetVariable) const
Load a double from an optional XML element.
Defines the properties of a named SampleEnvironment setup.
void addContainer(const Geometry::Container_const_sptr &can)
Adds a can definition to the known list.
void addComponent(const Geometry::IObject_const_sptr &component)
Add a non-can component to the specification.
Models a Container is used to hold a sample in the beam.
Definition Container.h:24
Class originally intended to be used with the DataHandling 'LoadInstrument' algorithm.
std::shared_ptr< CSGObject > createShape(Poco::XML::Element *pElem)
Creates a geometric object from a DOM-element-node pointing to an element whose child nodes contain t...
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
@ TOK_IGNORE_EMPTY
ignore empty tokens
@ TOK_TRIM
remove leading and trailing whitespace from tokens
ConstIterator cend() const
Const iterator referring to the past-the-end element in the container.
ConstIterator cbegin() const
Const iterator referring to first element in the container.
std::size_t count() const
Get the total number of tokens.
std::unique_ptr< SampleEnvironmentSpec > SampleEnvironmentSpec_uptr
unique_ptr to a SampleEnvironmentSpec
ScaleUnits getScaleTypeFromStr(const std::string &scaleProperty)
Definition MeshFileIO.h:73
std::shared_ptr< const Container > Container_const_sptr
Typdef for a shared pointer to a const object.
Definition Container.h:107