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/File.h"
27#include "Poco/Path.h"
28#include "Poco/SAX/InputSource.h"
29#include "Poco/SAX/SAXException.h"
30
31#include <boost/algorithm/string.hpp>
32#include <memory>
33#include <sstream>
34
35using namespace Poco::XML;
36
37//------------------------------------------------------------------------------
38// Anonymous
39//------------------------------------------------------------------------------
40namespace {
41std::string MATERIALS_TAG = "materials";
42std::string COMPONENTS_TAG = "components";
43std::string FULL_SPEC_TAG = "fullspecification";
44std::string COMPONENT_TAG = "component";
45std::string CONTAINERS_TAG = "containers";
46std::string CONTAINER_TAG = "container";
47std::string COMPONENTGEOMETRY_TAG = "geometry";
48std::string SAMPLEGEOMETRY_TAG = "samplegeometry";
49std::string COMPONENTSTLFILE_TAG = "stlfile";
50std::string SAMPLESTLFILE_TAG = "samplestlfile";
51} // namespace
52
53namespace Mantid::DataHandling {
54
55namespace {
56
57std::string MATERIALS_TAG = "materials";
58std::string COMPONENTS_TAG = "components";
59std::string COMPONENT_TAG = "component";
60std::string GLOBAL_OFFSET_TAG = "globaloffset";
61std::string TRANSLATION_VECTOR_TAG = "translationvector";
62std::string STL_FILENAME_TAG = "stlfilename";
63std::string SCALE_TAG = "scale";
64std::string XDEGREES_TAG = "xdegrees";
65std::string YDEGREES_TAG = "ydegrees";
66std::string ZDEGREES_TAG = "zdegrees";
67} // namespace
68
69namespace {
70double DegreesToRadians(double angle) { return angle * M_PI / 180; }
71} // namespace
72//------------------------------------------------------------------------------
73// Public methods
74//------------------------------------------------------------------------------
84SampleEnvironmentSpec_uptr SampleEnvironmentSpecParser::parse(const std::string &name, const std::string &filename,
85 std::istream &istr) {
86 using DocumentPtr = AutoPtr<Document>;
87
88 InputSource src(istr);
89 DOMParser parser;
90 // Do not use auto here or anywhereas the Poco API returns raw pointers
91 // but in some circumstances requires AutoPtrs to manage the memory
92 DocumentPtr doc;
93 try {
94 doc = parser.parse(&src);
95 } catch (SAXParseException &exc) {
96 std::ostringstream msg;
97 msg << "SampleEnvironmentSpecParser::parse() - Error parsing content "
98 "as valid XML: "
99 << exc.what();
100 throw std::runtime_error(msg.str());
101 }
102 m_filepath = filename;
103 return parse(name, doc->documentElement());
104}
105
114SampleEnvironmentSpec_uptr SampleEnvironmentSpecParser::parse(const std::string &name, Poco::XML::Element *element) {
115 validateRootElement(element);
116
117 // Iterating is apparently much faster than getElementsByTagName
118 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
119 Node *node = nodeIter.nextNode();
120 auto spec = std::make_unique<SampleEnvironmentSpec>(name);
121
122 while (node) {
123 auto *childElement = static_cast<Element *>(node);
124 if (node->nodeName() == MATERIALS_TAG) {
125 parseMaterials(childElement);
126 } else if (node->nodeName() == COMPONENTS_TAG) {
127 parseAndAddComponents(spec.get(), childElement);
128 } else if (node->nodeName() == FULL_SPEC_TAG) {
129 loadFullSpecification(spec.get(), childElement);
130 }
131 node = nodeIter.nextNode();
132 }
133 return spec;
134}
135
136//------------------------------------------------------------------------------
137// Private methods
138//------------------------------------------------------------------------------
143void SampleEnvironmentSpecParser::validateRootElement(Poco::XML::Element *element) const {
144 if (element->nodeName() != ROOT_TAG) {
145 std::ostringstream msg;
146 msg << "SampleEnvironmentSpecParser::validateRootElement() - Element tag "
147 "does not match '"
148 << ROOT_TAG << "'. Found " << element->nodeName() << "\n";
149 throw std::invalid_argument(msg.str());
150 }
151}
152
157void SampleEnvironmentSpecParser::parseMaterials(Poco::XML::Element *element) {
159
160 m_materials.clear();
161 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
162 // Points at <materials>
163 nodeIter.nextNode();
164 // Points at first <material>
165 Node *node = nodeIter.nextNode();
166 MaterialXMLParser parser;
167 while (node) {
168 auto material = parser.parse(static_cast<Poco::XML::Element *>(node), m_filepath);
169 m_materials.emplace(material.name(), material);
170 node = nodeIter.nextNode();
171 }
172}
173
181 if (m_materials.empty()) {
182 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponents() - "
183 "Trying to parse list of components but no "
184 "materials have been found. Please ensure the "
185 "materials are defined first.");
186 }
187 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
188 // Points at <components>
189 nodeIter.nextNode();
190 // Points at first <child>
191 Node *node = nodeIter.nextNode();
192 while (node) {
193 auto *childElement = static_cast<Element *>(node);
194 const auto &nodeName = childElement->nodeName();
195 if (nodeName == CONTAINERS_TAG) {
196 parseAndAddContainers(spec, childElement);
197 } else if (nodeName == COMPONENT_TAG) {
198 spec->addComponent(parseComponent(childElement));
199 }
200 node = nodeIter.nextNode();
201 }
202}
203
206 auto filename = element->getAttribute("filename");
207 if (!filename.empty()) {
208
209 std::string stlFileName = findFile(filename);
210
211 Poco::Path suppliedFileName(stlFileName);
212 std::string fileExt = suppliedFileName.getExtension();
213 std::transform(fileExt.begin(), fileExt.end(), fileExt.begin(), toupper);
214
215 std::vector<std::shared_ptr<Geometry::MeshObject>> environmentMeshes;
216 std::shared_ptr<Geometry::MeshObject> sampleMesh;
217
218 if (fileExt == "3MF") {
219#ifdef ENABLE_LIB3MF
220 Mantid3MFFileIO MeshLoader;
221 MeshLoader.LoadFile(stlFileName);
222
223 MeshLoader.readMeshObjects(environmentMeshes, sampleMesh);
224#else
225 throw std::runtime_error("3MF format not supported on this platform");
226#endif
227
228 for (auto cpt : environmentMeshes) {
229 if (spec->ncans() == 0) {
230 // 3mf format doesn't nicely support multiple cans so just
231 // hardcode id to default
232 cpt->setID("default");
233 auto can = std::make_shared<Container>(cpt);
234 can->setSampleShape(sampleMesh);
235 spec->addContainer(can);
236 } else {
237
238 spec->addComponent(cpt);
239 }
240 }
241 } else {
242 throw std::runtime_error("Full specification must be a .3mf file");
243 }
244 } else {
245 throw std::runtime_error("fullspecification element supplied without a filename");
246 }
247}
248
256 NodeIterator nodeIter(element, NodeFilter::SHOW_ELEMENT);
257 nodeIter.nextNode();
258 Node *node = nodeIter.nextNode();
259 while (node) {
260 auto *childElement = static_cast<Element *>(node);
261 if (childElement->nodeName() == CONTAINER_TAG) {
262 spec->addContainer(parseContainer(childElement));
263 }
264 node = nodeIter.nextNode();
265 }
266}
267
275 auto can = std::make_shared<Container>(parseComponent(element));
276 auto sampleGeometry = element->getChildElement(SAMPLEGEOMETRY_TAG);
277 auto sampleSTLFile = element->getChildElement(SAMPLESTLFILE_TAG);
278
279 if ((sampleGeometry) && (sampleSTLFile)) {
280 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - "
281 "Cannot define sample using both a" +
282 SAMPLEGEOMETRY_TAG + " and a " + SAMPLESTLFILE_TAG + " child tag.");
283 }
284
285 if (sampleGeometry) {
286 DOMWriter writer;
287 std::stringstream sampleShapeXML;
288 writer.writeNode(sampleShapeXML, sampleGeometry);
289 can->setSampleShape(sampleShapeXML.str());
290 }
291 if (sampleSTLFile) {
292 can->setSampleShape(loadMeshFromSTL(sampleSTLFile));
293 }
294 return can;
295}
296
303void SampleEnvironmentSpecParser::LoadOptionalDoubleFromXML(Poco::XML::Element *componentElement,
304 const std::string &attributeName,
305 double &targetVariable) const {
306
307 auto attributeText = componentElement->getAttribute(attributeName);
308 if (!attributeText.empty()) {
309 try {
310 targetVariable = std::stod(attributeText);
311 } catch (std::invalid_argument &ex) {
312 throw std::invalid_argument(std::string("Invalid string supplied for " + attributeName + " ") + ex.what());
313 }
314 }
315}
316
322std::vector<double> SampleEnvironmentSpecParser::parseTranslationVector(const std::string &translationVectorStr) const {
323
324 std::vector<double> translationVector;
325
326 // Split up comma-separated properties
328 tokenizer values(translationVectorStr, ",", tokenizer::TOK_IGNORE_EMPTY | tokenizer::TOK_TRIM);
329
330 translationVector.clear();
331 translationVector.reserve(values.count());
332
333 std::transform(values.cbegin(), values.cend(), std::back_inserter(translationVector),
334 [](const std::string &str) { return boost::lexical_cast<double>(str); });
335 return translationVector;
336}
337
338std::string SampleEnvironmentSpecParser::findFile(const std::string &filename) const {
339 Poco::Path suppliedStlFileName(filename);
340 Poco::Path stlFileName;
341 if (suppliedStlFileName.isRelative()) {
342 bool useSearchDirectories = true;
343 if (!m_filepath.empty()) {
344 // if environment spec xml came from a file, search in the same
345 // directory as the file
346 stlFileName = Poco::Path(Poco::Path(m_filepath).parent(), filename);
347 if (Poco::File(stlFileName).exists()) {
348 useSearchDirectories = false;
349 }
350 }
351
352 if (useSearchDirectories) {
353 // ... and if that doesn't work look in the search directories
354 std::string foundFile = Mantid::API::FileFinder::Instance().getFullPath(filename);
355 if (!foundFile.empty()) {
356 stlFileName = Poco::Path(foundFile);
357 } else {
358 stlFileName = suppliedStlFileName;
359 }
360 }
361 } else {
362 stlFileName = suppliedStlFileName;
363 }
364 return stlFileName.toString();
365}
366
373std::shared_ptr<Geometry::MeshObject> SampleEnvironmentSpecParser::loadMeshFromSTL(Element *stlFileElement) const {
374 std::string filename = stlFileElement->getAttribute("filename");
375 if (!filename.empty()) {
376
377 std::string stlFileName = findFile(filename);
378
379 if (Poco::File(stlFileName).exists()) {
380
381 std::string scaleStr = stlFileElement->getAttribute("scale");
382 if (scaleStr.empty()) {
383 throw std::runtime_error("Scale must be supplied for stl file:" + filename);
384 }
385 const ScaleUnits scaleType = getScaleTypeFromStr(scaleStr);
386
387 std::unique_ptr<LoadStl> reader = LoadStlFactory::createReader(stlFileName, scaleType);
388
389 std::shared_ptr<Geometry::MeshObject> comp = reader->readShape();
390
391 Element *rotation = stlFileElement->getChildElement("rotation");
392 if (rotation) {
393
394 double xDegrees = 0, yDegrees = 0, zDegrees = 0;
395 LoadOptionalDoubleFromXML(rotation, XDEGREES_TAG, xDegrees);
396 LoadOptionalDoubleFromXML(rotation, YDEGREES_TAG, yDegrees);
397 LoadOptionalDoubleFromXML(rotation, ZDEGREES_TAG, zDegrees);
398
399 const double xRotation = DegreesToRadians(xDegrees);
400 const double yRotation = DegreesToRadians(yDegrees);
401 const double zRotation = DegreesToRadians(zDegrees);
402 comp = reader->rotate(comp, xRotation, yRotation, zRotation);
403 }
404 Element *translation = stlFileElement->getChildElement("translation");
405 if (translation) {
406 std::string translationVectorStr = translation->getAttribute("vector");
407 const std::vector<double> translationVector = parseTranslationVector(translationVectorStr);
408 comp = reader->translate(comp, translationVector);
409 }
410 return comp;
411 } else {
412 throw std::runtime_error("Unable to find STLFile " + filename);
413 }
414 } else {
415 throw std::runtime_error("STLFile element supplied without a filename");
416 }
417}
418
425std::shared_ptr<Geometry::IObject> SampleEnvironmentSpecParser::parseComponent(Element *element) const {
426 Element *geometry = element->getChildElement(COMPONENTGEOMETRY_TAG);
427 Element *stlfile = element->getChildElement(COMPONENTSTLFILE_TAG);
428 if ((!geometry) && (!stlfile)) {
429 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - Expected a " + COMPONENTGEOMETRY_TAG +
430 " or " + COMPONENTSTLFILE_TAG + " child tag. None found.");
431 }
432 if ((geometry) && (stlfile)) {
433 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - "
434 "Cannot define container using both a" +
435 COMPONENTGEOMETRY_TAG + " and a " + COMPONENTSTLFILE_TAG + " child tag.");
436 }
437
438 std::shared_ptr<Geometry::IObject> comp;
439 if (stlfile) {
440 comp = loadMeshFromSTL(stlfile);
441 } else {
443 comp = factory.createShape(geometry);
444 }
445 auto materialID = element->getAttribute("material");
446 auto iter = m_materials.find(materialID);
448 if (iter != m_materials.end()) {
449 mat = iter->second;
450 } else {
451 throw std::runtime_error("SampleEnvironmentSpecParser::parseComponent() - "
452 "Unable to find material with id=" +
453 materialID);
454 }
455 comp->setID(element->getAttribute("id"));
456 comp->setMaterial(mat);
457 return comp;
458}
459
460} // namespace Mantid::DataHandling
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.
Definition: ShapeFactory.h:89
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
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
@ 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
bool exists(::NeXus::File &file, const std::string &name)
Based on the current group in the file, does the named sub-entry exist?
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