Skip to content

Commit

Permalink
Refs #7970 - Add alternate syntax for cuboid.
Browse files Browse the repository at this point in the history
Added unit tests to cover the default axis and centre fields.

It is not possible to enforce an "either/or" syntax rule in the schema
file without wrapping each syntax in its own pair of tags.  We have to
leave that job to the ShapeFactory when it parses the XML.

I would have preferred to keep CuboidCorners and the new parseCuboid
function in an anonymous namespace, but had to list them in the header
file to make use of the other member functions of the factory.
  • Loading branch information
PeterParker committed Oct 15, 2013
1 parent ba74c4e commit edcd1d7
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ namespace Mantid
{
class Surface;
class Object;

struct CuboidCorners
{
Kernel::V3D lfb;
Kernel::V3D lft;
Kernel::V3D lbb;
Kernel::V3D rfb;
};

/**
Expand Down Expand Up @@ -79,6 +87,8 @@ namespace Mantid
std::string parseInfiniteCylinder(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id);
std::string parseCylinder(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id);
std::string parseSegmentedCylinder(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id);

CuboidCorners ShapeFactory::parseCuboid(Poco::XML::Element* pElem);
std::string parseCuboid(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id);
std::string parseInfiniteCone(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id);
std::string parseCone(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id);
Expand Down
134 changes: 106 additions & 28 deletions Code/Mantid/Framework/Geometry/src/Objects/ShapeFactory.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/Quat.h"

#include "MantidGeometry/Objects/ShapeFactory.h"
#include "MantidGeometry/Objects/Object.h"
#include "MantidGeometry/Surfaces/Quadratic.h"
Expand Down Expand Up @@ -30,6 +32,7 @@ using Poco::XML::NodeIterator;
using Poco::XML::NodeFilter;
using Poco::XML::DOMWriter;


namespace Mantid
{
namespace Geometry
Expand Down Expand Up @@ -525,6 +528,97 @@ std::string ShapeFactory::parseSegmentedCylinder(Poco::XML::Element* pElem, std:
return retAlgebraMatch.str();
}

/**
* Get the four corners of a cuboid from an XML element. The XML may consist
* of one of the two available syntaxes. We disallow a mixture of syntaxes.
*
* @param pElem :: XML 'cuboid' element from instrument definition file.
* @return The four corners of the cuboid.
*
* @throw std::invalid_argument if XML string is invalid.
*/
CuboidCorners ShapeFactory::parseCuboid(Poco::XML::Element* pElem)
{
// Users have two syntax options when defining cuboids:

// A - "Point" syntax.
Element* pElem_lfb = getOptionalShapeElement(pElem, "left-front-bottom-point");
Element* pElem_lft = getOptionalShapeElement(pElem, "left-front-top-point");
Element* pElem_lbb = getOptionalShapeElement(pElem, "left-back-bottom-point");
Element* pElem_rfb = getOptionalShapeElement(pElem, "right-front-bottom-point");

// B - "Alternate" syntax.
Element* pElem_height = getOptionalShapeElement(pElem, "height");
Element* pElem_width = getOptionalShapeElement(pElem, "width");
Element* pElem_depth = getOptionalShapeElement(pElem, "depth");
Element* pElem_centre = getOptionalShapeElement(pElem, "centre");
Element* pElem_axis = getOptionalShapeElement(pElem, "axis");

const bool usingPointSyntax = pElem_lfb && pElem_lft && pElem_lbb && pElem_rfb;
const bool usingAlternateSyntax = pElem_height && pElem_width && pElem_depth;

const bool usedPointSyntaxField = pElem_lfb || pElem_lft || pElem_lbb || pElem_rfb;
const bool usedAlternateSyntaxField = pElem_height || pElem_width || pElem_depth || pElem_centre || pElem_axis;

const std::string SYNTAX_ERROR_MSG = "XML element: <" + pElem->tagName() +
"> may contain EITHER corner points (LFB, LFT, LBB and RFB) OR " +
"height, width, depth, centre and axis values.";

CuboidCorners result;

if( usingPointSyntax && !usingAlternateSyntax )
{
if( usedAlternateSyntaxField )
throw std::invalid_argument(SYNTAX_ERROR_MSG);

result.lfb = parsePosition(pElem_lfb);
result.lft = parsePosition(pElem_lft);
result.lbb = parsePosition(pElem_lbb);
result.rfb = parsePosition(pElem_rfb);
}
else if( usingAlternateSyntax && !usingPointSyntax )
{
if( usedPointSyntaxField )
throw std::invalid_argument(SYNTAX_ERROR_MSG);

const double deltaH = getDoubleAttribute(pElem_height, "val") / 2;
const double deltaW = getDoubleAttribute(pElem_width, "val") / 2;
const double deltaD = getDoubleAttribute(pElem_depth, "val") / 2;

const V3D centre = pElem_centre ? parsePosition(pElem_centre) : V3D(0, 0, 0);

result.lfb = V3D(-deltaW,-deltaH,-deltaD);
result.lft = V3D(-deltaW, deltaH,-deltaD);
result.lbb = V3D(-deltaW,-deltaH, deltaD);
result.rfb = V3D( deltaW,-deltaH,-deltaD);

if( pElem_axis )
{
// Use a quarternion to do a rotation for us, with respect to the default
// axis. Our "Quat" implementation requires that the vectors passed to
// it be normalised.
V3D axis = parsePosition(pElem_axis);
axis.normalize();
const V3D DEFAULT_AXIS(0, 0, 1);
const Quat rotation(axis, DEFAULT_AXIS);

rotation.rotate(result.lfb);
rotation.rotate(result.lft);
rotation.rotate(result.lbb);
rotation.rotate(result.rfb);
}

result.lfb += centre;
result.lft += centre;
result.lbb += centre;
result.rfb += centre;
}
else
throw std::invalid_argument(SYNTAX_ERROR_MSG);

return result;
}

/** Parse XML 'cuboid' element
*
* @param pElem :: XML 'cuboid' element from instrument def. file
Expand All @@ -536,22 +630,14 @@ std::string ShapeFactory::parseSegmentedCylinder(Poco::XML::Element* pElem, std:
*/
std::string ShapeFactory::parseCuboid(Poco::XML::Element* pElem, std::map<int, Surface*>& prim, int& l_id)
{
Element* pElem_lfb = getShapeElement(pElem, "left-front-bottom-point");
Element* pElem_lft = getShapeElement(pElem, "left-front-top-point");
Element* pElem_lbb = getShapeElement(pElem, "left-back-bottom-point");
Element* pElem_rfb = getShapeElement(pElem, "right-front-bottom-point");

V3D lfb = parsePosition(pElem_lfb); // left front bottom
V3D lft = parsePosition(pElem_lft); // left front top
V3D lbb = parsePosition(pElem_lbb); // left back bottom
V3D rfb = parsePosition(pElem_rfb); // right front bottom
auto corners = parseCuboid(pElem);

V3D pointTowardBack = lbb-lfb;
V3D pointTowardBack = corners.lbb-corners.lfb;
pointTowardBack.normalize();

// add front plane cutoff
Plane* pPlaneFrontCutoff = new Plane();
pPlaneFrontCutoff->setPlane(lfb, pointTowardBack);
pPlaneFrontCutoff->setPlane(corners.lfb, pointTowardBack);
prim[l_id] = pPlaneFrontCutoff;

std::stringstream retAlgebraMatch;
Expand All @@ -560,43 +646,43 @@ std::string ShapeFactory::parseCuboid(Poco::XML::Element* pElem, std::map<int, S

// add back plane cutoff
Plane* pPlaneBackCutoff = new Plane();
pPlaneBackCutoff->setPlane(lbb, pointTowardBack);
pPlaneBackCutoff->setPlane(corners.lbb, pointTowardBack);
prim[l_id] = pPlaneBackCutoff;
retAlgebraMatch << "-" << l_id << " ";
l_id++;


V3D pointTowardRight = rfb-lfb;
V3D pointTowardRight = corners.rfb-corners.lfb;
pointTowardRight.normalize();

// add left plane cutoff
Plane* pPlaneLeftCutoff = new Plane();
pPlaneLeftCutoff->setPlane(lfb, pointTowardRight);
pPlaneLeftCutoff->setPlane(corners.lfb, pointTowardRight);
prim[l_id] = pPlaneLeftCutoff;
retAlgebraMatch << "" << l_id << " ";
l_id++;

// add right plane cutoff
Plane* pPlaneRightCutoff = new Plane();
pPlaneRightCutoff->setPlane(rfb, pointTowardRight);
pPlaneRightCutoff->setPlane(corners.rfb, pointTowardRight);
prim[l_id] = pPlaneRightCutoff;
retAlgebraMatch << "-" << l_id << " ";
l_id++;


V3D pointTowardTop = lft-lfb;
V3D pointTowardTop = corners.lft-corners.lfb;
pointTowardTop.normalize();

// add bottom plane cutoff
Plane* pPlaneBottomCutoff = new Plane();
pPlaneBottomCutoff->setPlane(lfb, pointTowardTop);
pPlaneBottomCutoff->setPlane(corners.lfb, pointTowardTop);
prim[l_id] = pPlaneBottomCutoff;
retAlgebraMatch << "" << l_id << " ";
l_id++;

// add top plane cutoff
Plane* pPlaneTopCutoff = new Plane();
pPlaneTopCutoff->setPlane(lft, pointTowardTop);
pPlaneTopCutoff->setPlane(corners.lft, pointTowardTop);
prim[l_id] = pPlaneTopCutoff;
retAlgebraMatch << "-" << l_id << ")";
l_id++;
Expand Down Expand Up @@ -1050,16 +1136,8 @@ void ShapeFactory::createGeometryHandler(Poco::XML::Element* pElem,boost::shared
{
boost::shared_ptr<GeometryHandler> handler(new GluGeometryHandler(Obj));
Obj->setGeometryHandler(handler);
Element* pElem_lfb = getShapeElement(pElem, "left-front-bottom-point");
Element* pElem_lft = getShapeElement(pElem, "left-front-top-point");
Element* pElem_lbb = getShapeElement(pElem, "left-back-bottom-point");
Element* pElem_rfb = getShapeElement(pElem, "right-front-bottom-point");

V3D lfb = parsePosition(pElem_lfb); // left front bottom
V3D lft = parsePosition(pElem_lft); // left front top
V3D lbb = parsePosition(pElem_lbb); // left back bottom
V3D rfb = parsePosition(pElem_rfb); // right front bottom
((GluGeometryHandler*)(handler.get()))->setCuboid(lfb,lft,lbb,rfb);
auto corners = parseCuboid(pElem);
((GluGeometryHandler*)(handler.get()))->setCuboid(corners.lfb,corners.lft,corners.lbb,corners.rfb);
}
else if(pElem->tagName()=="sphere")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,26 @@ class InstrumentDefinitionParserTest : public CxxTest::TestSuite
TS_ASSERT( !ptrDet1001->isValid(V3D(0.0,-0.01,0.05)+ptrDet1001->getPos()) );
TS_ASSERT( !ptrDet1001->isValid(V3D(0.0,-0.01,-0.05)+ptrDet1001->getPos()) );

// test for "cuboid-alternate-test".
boost::shared_ptr<const IDetector> ptrDet18 = i->getDetector(18);

TS_ASSERT( ptrDet18->isValid(V3D( 1.05, 1.10, 1.20)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 1.05, 1.10, 0.80)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 1.05, 0.90, 1.20)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 1.05, 0.90, 0.80)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 0.95, 1.10, 1.20)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 0.95, 1.10, 0.80)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 0.95, 0.90, 1.20)+ptrDet18->getPos()) );
TS_ASSERT( ptrDet18->isValid(V3D( 0.95, 0.90, 0.80)+ptrDet18->getPos()) );

TS_ASSERT( !ptrDet18->isValid(V3D( 1.06, 1.11, 1.21)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 1.06, 1.11, 0.79)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 1.06, 0.89, 1.21)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 1.06, 0.89, 0.79)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 0.94, 1.11, 1.21)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 0.94, 1.11, 0.79)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 0.94, 0.89, 1.21)+ptrDet18->getPos()) );
TS_ASSERT( !ptrDet18->isValid(V3D( 0.94, 0.89, 0.79)+ptrDet18->getPos()) );

// test for "infinite-cylinder-test".
boost::shared_ptr<const IDetector> ptrDet12 = i->getDetector(12);
Expand Down
97 changes: 97 additions & 0 deletions Code/Mantid/Framework/Geometry/test/ShapeFactoryTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,103 @@ class ShapeFactoryTest : public CxxTest::TestSuite
TS_ASSERT( shape_sptr->isValid(V3D(0.0,0.09, 0.00001)) );
}

void testAlternateCuboid()
{
std::string xmlShape;
xmlShape += "<cuboid id=\"some-shape\">";
xmlShape += "<height val=\"0.2\" />";
xmlShape += "<width val=\"0.1\" />";
xmlShape += "<depth val=\"0.4\" />";
xmlShape += "<centre x=\"1.0\" y=\"1.0\" z=\"1.0\" />";
xmlShape += "<axis x=\"1\" y=\"0\" z=\"0\" />"; // Note non-default axis.
xmlShape += "</cuboid>";
xmlShape += "<algebra val=\"some-shape\" />";

auto cuboid = getObject(xmlShape);

TS_ASSERT( cuboid->isValid(V3D(1.20, 1.10, 0.95)) );
TS_ASSERT( cuboid->isValid(V3D(0.80, 1.10, 0.95)) );
TS_ASSERT( cuboid->isValid(V3D(1.20, 0.90, 0.95)) );
TS_ASSERT( cuboid->isValid(V3D(0.80, 0.90, 0.95)) );
TS_ASSERT( cuboid->isValid(V3D(1.20, 1.10, 1.05)) );
TS_ASSERT( cuboid->isValid(V3D(0.80, 1.10, 1.05)) );
TS_ASSERT( cuboid->isValid(V3D(1.20, 0.90, 1.05)) );
TS_ASSERT( cuboid->isValid(V3D(0.80, 0.90, 1.05)) );

TS_ASSERT( !cuboid->isValid(V3D(1.21, 1.11, 0.94)) );
TS_ASSERT( !cuboid->isValid(V3D(0.79, 1.11, 0.94)) );
TS_ASSERT( !cuboid->isValid(V3D(1.21, 0.89, 0.94)) );
TS_ASSERT( !cuboid->isValid(V3D(0.79, 0.89, 0.94)) );
TS_ASSERT( !cuboid->isValid(V3D(1.21, 1.11, 1.06)) );
TS_ASSERT( !cuboid->isValid(V3D(0.79, 1.11, 1.06)) );
TS_ASSERT( !cuboid->isValid(V3D(1.21, 0.89, 1.06)) );
TS_ASSERT( !cuboid->isValid(V3D(0.79, 0.89, 1.06)) );
}

void testAlternateCuboidDefaultAxis()
{
std::string xmlShape;
xmlShape += "<cuboid id=\"some-shape\">";
xmlShape += "<height val=\"0.2\" />";
xmlShape += "<width val=\"0.1\" />";
xmlShape += "<depth val=\"0.4\" />";
xmlShape += "<centre x=\"1.0\" y=\"1.0\" z=\"1.0\" />";
xmlShape += "</cuboid>";
xmlShape += "<algebra val=\"some-shape\" />";

auto cuboid = getObject(xmlShape);

TS_ASSERT( cuboid->isValid(V3D( 1.05, 1.10, 1.20)) );
TS_ASSERT( cuboid->isValid(V3D( 1.05, 1.10, 0.80)) );
TS_ASSERT( cuboid->isValid(V3D( 1.05, 0.90, 1.20)) );
TS_ASSERT( cuboid->isValid(V3D( 1.05, 0.90, 0.80)) );
TS_ASSERT( cuboid->isValid(V3D( 0.95, 1.10, 1.20)) );
TS_ASSERT( cuboid->isValid(V3D( 0.95, 1.10, 0.80)) );
TS_ASSERT( cuboid->isValid(V3D( 0.95, 0.90, 1.20)) );
TS_ASSERT( cuboid->isValid(V3D( 0.95, 0.90, 0.80)) );

TS_ASSERT( !cuboid->isValid(V3D( 1.06, 1.11, 1.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 1.06, 1.11, 0.79)) );
TS_ASSERT( !cuboid->isValid(V3D( 1.06, 0.89, 1.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 1.06, 0.89, 0.79)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.94, 1.11, 1.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.94, 1.11, 0.79)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.94, 0.89, 1.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.94, 0.89, 0.79)) );
}

void testAlternateCuboidDefaultCentre()
{
std::string xmlShape;
xmlShape += "<cuboid id=\"some-shape\">";
xmlShape += "<height val=\"0.2\" />";
xmlShape += "<width val=\"0.1\" />";
xmlShape += "<depth val=\"0.4\" />";
xmlShape += "<axis x=\"0\" y=\"0\" z=\"1\" />";
xmlShape += "</cuboid>";
xmlShape += "<algebra val=\"some-shape\" />";

auto cuboid = getObject(xmlShape);

TS_ASSERT( cuboid->isValid(V3D( 0.05, 0.10, 0.20)) );
TS_ASSERT( cuboid->isValid(V3D( 0.05, 0.10,-0.20)) );
TS_ASSERT( cuboid->isValid(V3D( 0.05,-0.10, 0.20)) );
TS_ASSERT( cuboid->isValid(V3D( 0.05,-0.10,-0.20)) );
TS_ASSERT( cuboid->isValid(V3D(-0.05, 0.10, 0.20)) );
TS_ASSERT( cuboid->isValid(V3D(-0.05, 0.10,-0.20)) );
TS_ASSERT( cuboid->isValid(V3D(-0.05,-0.10, 0.20)) );
TS_ASSERT( cuboid->isValid(V3D(-0.05,-0.10,-0.20)) );

TS_ASSERT( !cuboid->isValid(V3D( 0.06, 0.11, 0.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.06, 0.11,-0.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.06,-0.11, 0.21)) );
TS_ASSERT( !cuboid->isValid(V3D( 0.06,-0.11,-0.21)) );
TS_ASSERT( !cuboid->isValid(V3D(-0.06, 0.11, 0.21)) );
TS_ASSERT( !cuboid->isValid(V3D(-0.06, 0.11,-0.21)) );
TS_ASSERT( !cuboid->isValid(V3D(-0.06,-0.11, 0.21)) );
TS_ASSERT( !cuboid->isValid(V3D(-0.06,-0.11,-0.21)) );
}

void testRelayShapeXML()
{
//Create a cuboid.
Expand Down

0 comments on commit edcd1d7

Please sign in to comment.