Commit 5914bfee authored by Martyn Gigg's avatar Martyn Gigg
Browse files

Allow parts of sample geometry in a can to be altered

This is quite basic at the moment. It assumes a single primitive shape
for the sample geometry whose dimension is specified by a tag in the XML
definition.
Refs #16044
parent 2cdc3eea
......@@ -3,6 +3,7 @@
#include "MantidGeometry/DllConfig.h"
#include "MantidGeometry/Objects/Object.h"
#include <unordered_map>
namespace Mantid {
namespace Geometry {
......@@ -36,13 +37,18 @@ namespace Geometry {
*/
class MANTID_GEOMETRY_DLL Can final : public Object {
public:
typedef std::unordered_map<std::string, double> ShapeArgs;
Can() = default;
Can(std::string sampleTemplateXML);
Can(std::string canXML);
bool hasSampleShape() const;
Object_sptr createSampleShape(const ShapeArgs &args) const;
const std::string &sampleShapeTemplate() const;
void setSampleShape(const std::string &sampleShapeXML);
private:
std::string m_sampleShapeTemplate;
std::string m_sampleShapeXML;
};
} // namespace Geometry
......
#include "MantidGeometry/Instrument/Can.h"
#include "MantidGeometry/Objects/ShapeFactory.h"
#include "Poco/DOM/AutoPtr.h"
#include "Poco/DOM/DOMParser.h"
#include "Poco/DOM/Document.h"
#include "Poco/DOM/NodeIterator.h"
#include "Poco/DOM/NodeFilter.h"
#include "Poco/SAX/InputSource.h"
#include "Poco/SAX/SAXException.h"
namespace Mantid {
namespace Geometry {
namespace {
constexpr const char *SAMPLEGEOMETRY_TAG = "samplegeometry";
constexpr const char *VAL_ATT = "val";
//------------------------------------------------------------------------------
// Anonymous methods
//------------------------------------------------------------------------------
/**
* Update the values of the XML tree tags specified. It assumes that the
* value is specifed using an attribute named 'val'
* @param root A pointer to an element whose childnodes contain "val" attributes
* to be updated
* @param args A hash of tag names to values
*/
void updateTreeValues(Poco::XML::Element *root, const Can::ShapeArgs &args) {
using namespace Poco::XML;
NodeIterator nodeIter(root, NodeFilter::SHOW_ELEMENT);
Node *node = nodeIter.nextNode();
while (node) {
Element *element = static_cast<Element *>(node);
auto argIter = args.find(node->nodeName());
if (argIter != args.end()) {
element->setAttribute(VAL_ATT, std::to_string(argIter->second));
}
node = nodeIter.nextNode();
}
}
}
//------------------------------------------------------------------------------
// Public methods
//------------------------------------------------------------------------------
/**
* Construct a Can providing an XML definition of the default sample shape
* @param sampleTemplateXML XML definition of the sample shape
* Construct a Can providing an XML definition shape
* @param canXML Definition of the can shape in xml
*/
Can::Can(std::string canXML) : Object(canXML) {}
/**
* @return True if the can contains a defintion of the sample shape
*/
bool Can::hasSampleShape() const { return !m_sampleShapeXML.empty(); }
/**
* Return an object that represents the sample shape from the current
* definition but override the default values with the given values.
* It throws a std::runtime_error if no sample shape is defined.
* @param args A hash of tag values to use in place of the default
* @return A pointer to a object modeling the sample shape
*/
Can::Can(std::string sampleTemplateXML)
: m_sampleShapeTemplate(sampleTemplateXML) {}
Object_sptr Can::createSampleShape(const Can::ShapeArgs &args) const {
using namespace Poco::XML;
if (!hasSampleShape()) {
throw std::runtime_error("Can::createSampleShape() - No definition found "
"for the sample geometry.");
}
// Parse XML
std::istringstream instrm(m_sampleShapeXML);
InputSource src(instrm);
DOMParser parser;
AutoPtr<Document> doc;
try {
doc = parser.parse(&src);
} catch (SAXParseException &exc) {
std::ostringstream os;
os << "Can::createSampleShape() - Error parsing XML: " << exc.what();
throw std::invalid_argument(os.str());
}
Element *root = doc->documentElement();
if (!args.empty())
updateTreeValues(root, args);
const std::string &Can::sampleShapeTemplate() const {
return m_sampleShapeTemplate;
ShapeFactory factory;
return factory.createShape<Object>(root);
}
/**
* Set the definition of the sample shape for this can
* @param sampleShapeXML
*/
void Can::setSampleShape(const std::string &sampleShapeXML) {
using namespace Poco::XML;
std::istringstream instrm(sampleShapeXML);
InputSource src(instrm);
DOMParser parser;
AutoPtr<Document> doc = parser.parse(&src);
if (doc->documentElement()->nodeName() != SAMPLEGEOMETRY_TAG) {
std::ostringstream msg;
msg << "Can::setSampleShape() - XML definition "
"expected to contained within a <" << SAMPLEGEOMETRY_TAG
<< "> tag. Found " << doc->documentElement()->nodeName() << "instead.";
throw std::invalid_argument(msg.str());
}
m_sampleShapeXML = sampleShapeXML;
}
//------------------------------------------------------------------------------
// Private methods
//------------------------------------------------------------------------------
} // namespace Geometry
} // namespace Mantid
......@@ -4,6 +4,10 @@
#include <cxxtest/TestSuite.h>
#include "MantidGeometry/Instrument/Can.h"
#include "MantidGeometry/Objects/Rules.h"
#include "MantidGeometry/Surfaces/Sphere.h"
#include <boost/make_shared.hpp>
using Mantid::Geometry::Can;
......@@ -19,18 +23,96 @@ public:
// ---------------------------------------------------------------------------
void test_Default_Constructor_Has_No_Sample_Shape() {
Can can;
TS_ASSERT(can.sampleShapeTemplate().empty());
TS_ASSERT_EQUALS(false, can.hasSampleShape());
TS_ASSERT_THROWS(can.createSampleShape(Can::ShapeArgs()),
std::runtime_error);
}
void test_Constructing_With_Default_Shape_Stores_Same_Default() {
void test_Construction_With_XML_Assumes_XML_For_Can_Itself() {
std::string xml = "<cylinder>"
"<centre-of-bottom-base x=\"0.0\" y=\"0.0\" z=\"0.0\" />"
"<axis x =\"0.0\" y=\"1.0\" z=\"0\" />"
"<radius val =\"0.0030\" />"
"<height val =\"0.05\" />"
"<axis x=\"0.0\" y=\"1.0\" z=\"0\" />"
"<radius val=\"0.0030\" />"
"<height val=\"0.05\" />"
"</cylinder>";
Can can(xml);
TS_ASSERT_EQUALS(xml, can.sampleShapeTemplate());
TS_ASSERT_EQUALS(false, can.hasSampleShape());
TS_ASSERT_EQUALS(xml, can.getShapeXML());
}
void test_SetSampleShape_Allows_Creating_Sample_Shape_Object() {
using Mantid::Geometry::Object_sptr;
auto can = createTestCan();
can->setSampleShape("<samplegeometry><sphere id=\"shape\"> "
"<radius val=\"1.0\" /> "
"</sphere></samplegeometry>");
Object_sptr sampleShape;
TS_ASSERT_THROWS_NOTHING(sampleShape =
can->createSampleShape(Can::ShapeArgs()));
TS_ASSERT(sampleShape->hasValidShape());
TS_ASSERT_DELTA(1.0, getSphereRadius(*sampleShape), 1e-10);
}
void test_CreateSampleShape_Args_Override_Defaults() {
using Mantid::Geometry::Object_sptr;
auto can = createTestCan();
can->setSampleShape("<samplegeometry><sphere id=\"shape\"> "
"<radius val=\"1.0\" /> "
"</sphere></samplegeometry>");
Object_sptr sampleShape;
Can::ShapeArgs args = {{"radius", 0.5}};
TS_ASSERT_THROWS_NOTHING(sampleShape = can->createSampleShape(args));
TS_ASSERT(sampleShape->hasValidShape());
TS_ASSERT_DELTA(0.5, getSphereRadius(*sampleShape), 1e-10);
}
void test_CreateSampleShape_Args_Not_Matching_Do_Nothing() {
using Mantid::Geometry::Object_sptr;
auto can = createTestCan();
can->setSampleShape("<samplegeometry><sphere id=\"shape\"> "
"<radius val=\"1.0\" /> "
"</sphere></samplegeometry>");
Object_sptr sampleShape;
Can::ShapeArgs args = {{"height", 0.5}};
TS_ASSERT_THROWS_NOTHING(sampleShape = can->createSampleShape(args));
TS_ASSERT(sampleShape->hasValidShape());
TS_ASSERT_DELTA(1.0, getSphereRadius(*sampleShape), 1e-10);
}
// ---------------------------------------------------------------------------
// Failure tests
// ---------------------------------------------------------------------------
void test_SetSampleShape_Throws_If_Top_Tag_Not_SampleGeometry() {
auto can = createTestCan();
TS_ASSERT_THROWS(can->setSampleShape("<sphere id=\"shape\"> "
"<radius val=\"1.0\" /> "
"</sphere>"),
std::invalid_argument);
}
private:
Mantid::Geometry::Can_sptr createTestCan() {
return boost::make_shared<Can>(
"<type name=\"usertype\"><cylinder>"
"<centre-of-bottom-base x=\"0.0\" y=\"0.0\" z=\"0.0\" />"
"<axis x=\"0.0\" y=\"1.0\" z=\"0\" />"
"<radius val =\"0.0030\" />"
"<height val =\"0.05\" />"
"</cylinder></type>");
}
double getSphereRadius(const Mantid::Geometry::Object &shape) {
using Mantid::Geometry::SurfPoint;
using Mantid::Geometry::Sphere;
auto topRule = shape.topRule();
if (auto surfpoint = dynamic_cast<const SurfPoint *>(topRule)) {
if (auto sphere = dynamic_cast<Sphere *>(surfpoint->getKey())) {
return sphere->getRadius();
} else {
throw std::runtime_error("Expected Sphere as SurfPoint key.");
}
} else {
throw std::runtime_error("Expected SurfPoint as top rule");
}
}
};
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment