Mantid
Loading...
Searching...
No Matches
TranslateSampleShape.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2025 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 +
8
12#include "MantidAPI/Run.h"
13#include "MantidAPI/Sample.h"
14#include "MantidAPI/Workspace.h"
16
18
22
25#include "MantidKernel/V3D.h"
26
27#include <Poco/AutoPtr.h>
28#include <Poco/DOM/DOMParser.h>
29#include <Poco/DOM/DOMWriter.h>
30#include <Poco/DOM/Document.h>
31#include <Poco/DOM/Element.h>
32#include <Poco/DOM/Node.h>
33#include <Poco/DOM/NodeFilter.h>
34#include <Poco/DOM/NodeIterator.h>
35#include <Poco/DOM/NodeList.h>
36
37#include <sstream>
38
39namespace {
40
41// we need some xml parsing and altering functions, which we will just define here
42
43using namespace Poco::XML;
44
45// Decide if an element has attributes that should be shifted
46bool isPointLike(const std::string &tag) {
47 if (tag == "axis" || tag == "normal-to-plane")
48 // ignore vector like tags
49 return false;
50 if (tag == "centre" || tag == "centre-of-bottom-base" || tag == "tip" || tag == "point-in-plane")
51 return true;
52 // Cuboids/hexahedra can be defined by corners which end with the six characters: "-point"
53 if (tag.size() >= 6 && tag.rfind("-point") == tag.size() - 6)
54 return true;
55 // all other tags should also be ignored
56 return false;
57}
58
59void shiftAttribute(Element *e, const std::string &attr, double d) {
60 if (!e)
61 return;
62 if (!e->hasAttribute(attr))
63 return;
64 const double v = std::stod(e->getAttribute(attr));
65 e->setAttribute(attr, std::to_string(v + d));
66};
67
68Element *firstElem(Element *parent, const std::string &tag) {
69 if (!parent)
70 return nullptr;
71 Poco::AutoPtr<NodeList> nl = parent->getElementsByTagName(tag);
72 if (nl && nl->length() > 0)
73 return static_cast<Poco::XML::Element *>(nl->item(0));
74 return nullptr;
75};
76
77// Shift x/y/z attributes if present
78void addXYZ(Element *el, double dx, double dy, double dz) {
79 if (el->hasAttribute("x") && el->hasAttribute("y") && el->hasAttribute("z")) {
80 shiftAttribute(el, "x", dx);
81 shiftAttribute(el, "y", dy);
82 shiftAttribute(el, "z", dz);
83 }
84}
85
86// If user has defined a bounding box, this needs to be shifted accordingly
87void shiftBoundingBox(Element *root, double dx, double dy, double dz) {
88
89 // find <bounding-box> anywhere under root
90 Element *bb = firstElem(root, "bounding-box");
91 if (!bb)
92 return;
93
94 shiftAttribute(firstElem(bb, "x-min"), "val", dx);
95 shiftAttribute(firstElem(bb, "x-max"), "val", dx);
96 shiftAttribute(firstElem(bb, "y-min"), "val", dy);
97 shiftAttribute(firstElem(bb, "y-max"), "val", dy);
98 shiftAttribute(firstElem(bb, "z-min"), "val", dz);
99 shiftAttribute(firstElem(bb, "z-max"), "val", dz);
100}
101
102// Go through all the children of the provided root node and write those corresponding to elements (the only ones we are
103// interested in)
104std::string writeOnlyElementChildNodes(Poco::XML::Element *root) {
105 if (!root)
106 return std::string();
107
108 std::ostringstream out;
109 DOMWriter writer;
110
111 // iterate over the child nodes in linked list style loop
112 for (Node *n = root->firstChild(); n; n = n->nextSibling()) {
113 if (n->nodeType() == Node::ELEMENT_NODE) {
114 writer.writeNode(out, n);
115 }
116 }
117 return out.str();
118}
119
120// If XML is user defined and created out the ShapeFactory, it will be wrapped in a <type> tag
121// this needs to be removed for the translated xml string to be re-parsed by the Factory
122std::string removeTypeTagWrapper(const std::string &xml) {
123 DOMParser parser;
124 Poco::AutoPtr<Document> doc;
125 try {
126 doc = parser.parseString(xml);
127 } catch (...) {
128 throw std::runtime_error("Invalid CSG XML encountered");
129 }
130 Element *root = doc ? doc->documentElement() : nullptr;
131 if (!root)
132 return xml;
133
134 const std::string tag = root->tagName();
135 if (tag == "type") {
136 return writeOnlyElementChildNodes(root);
137 }
138
139 // No type tag, return original xml
140 return xml;
141}
142
143std::string translateCSG(const std::string &xml, double dx, double dy, double dz) {
144 DOMParser parser;
145 Poco::AutoPtr<Document> doc = parser.parseString(xml);
146
147 // Access root element
148 Element *root = doc ? doc->documentElement() : nullptr;
149 if (!root)
150 return xml; // fallback on malformed input
151
152 // Iterate over all nodes, selecting only elements, check if they should be translated, and if so update their
153 // definition
154 NodeIterator it(doc, NodeFilter::SHOW_ELEMENT);
155 for (Node *n = it.nextNode(); n; n = it.nextNode()) {
156 auto *el = dynamic_cast<Element *>(n);
157 if (!el)
158 continue;
159 const std::string tag = el->tagName();
160 // check if element tag is one that has x,y,z attributes that should be translated
161 if (isPointLike(tag)) {
162 addXYZ(el, dx, dy, dz);
163 }
164 }
165
166 // If there is a bounding-box, this should also be shifted
167 shiftBoundingBox(root, dx, dy, dz);
168
169 std::ostringstream out;
170 DOMWriter writer;
171 writer.writeNode(out, doc);
172 return out.str();
173}
174
175} // anonymous namespace
176
177namespace Mantid::DataHandling {
178
179// Register the algorithm into the AlgorithmFactory
180DECLARE_ALGORITHM(TranslateSampleShape)
181
182using namespace Mantid::Geometry;
183using namespace Mantid::Kernel;
184using namespace Mantid::API;
185
189 declareProperty(std::make_unique<WorkspaceProperty<Workspace>>("InputWorkspace", "", Direction::InOut),
190 "The workspace containing the sample whose shape is to be translated");
191
192 // Vector to translate shape
193 declareProperty(std::make_unique<ArrayProperty<double>>("TranslationVector", "0.0,0.0,0.0",
194 std::make_shared<ArrayLengthValidator<double>>(3)),
195 "Vector by which to translate the loaded sample shape (metres)");
196}
197
201 Workspace_sptr ws = getProperty("InputWorkspace");
202 auto ei = std::dynamic_pointer_cast<ExperimentInfo>(ws);
203 const std::vector<double> translationVector = getProperty("TranslationVector");
204
205 if (!ei) {
206 // We're dealing with an MD workspace which has multiple experiment infos
207 auto infos = std::dynamic_pointer_cast<MultipleExperimentInfos>(ws);
208 if (!infos) {
209 throw std::invalid_argument("Input workspace does not support TranslateSampleShape");
210 }
211 if (infos->getNumExperimentInfo() < 1) {
213 infos->addExperimentInfo(info);
214 }
215 ei = infos->getExperimentInfo(0);
216 }
217
218 std::string shapeXML;
219 bool isMeshShape = false;
220 if (!checkIsValidShape(ei, shapeXML, isMeshShape)) {
221 throw std::runtime_error("Input sample does not have a valid shape!");
222 }
223
224 const auto dx = translationVector[0];
225 const auto dy = translationVector[1];
226 const auto dz = translationVector[2];
227
228 if (isMeshShape) {
229 // Mesh shapes can be translated directly
230 auto meshShape = std::dynamic_pointer_cast<MeshObject>(ei->sample().getShapePtr());
231 meshShape->translate(V3D(dx, dy, dz));
232 } else {
233 // CSG shapes need to translate XML definition, then use this to set a new shape
234 auto csgShape = std::dynamic_pointer_cast<CSGObject>(ei->sample().getShapePtr());
235
236 // get shape xml
237 const std::string &origXML = csgShape->getShapeXML();
238
239 // translate xml def
240 std::string translatedXML = translateCSG(origXML, dx, dy, dz);
241
242 // remove type tag, if there is one
243 translatedXML = removeTypeTagWrapper(translatedXML);
244
245 // use new csg xml string to replace the shape
246 setSampleShape(ws, translatedXML);
247 }
248}
249
251 bool &isMeshShape) {
252 if (ei->sample().hasShape()) {
253 const auto csgShape = std::dynamic_pointer_cast<CSGObject>(ei->sample().getShapePtr());
254 if (csgShape && csgShape->hasValidShape()) {
255 shapeXML = csgShape->getShapeXML();
256 if (!shapeXML.empty()) {
257 return true;
258 }
259 } else {
260 const auto meshShape = std::dynamic_pointer_cast<MeshObject>(ei->sample().getShapePtr());
261 if (meshShape && meshShape->hasValidShape()) {
262 isMeshShape = true;
263 return true;
264 }
265 }
266 }
267 return false;
268}
269
270void TranslateSampleShape::setSampleShape(const Workspace_sptr &ws, std::string &translatedXML) {
271 auto creator = AlgorithmManager::Instance().create("CreateSampleShape");
272 creator->initialize();
273 creator->setChild(true);
274 creator->setProperty("InputWorkspace", ws);
275 creator->setPropertyValue("ShapeXML", translatedXML);
276 creator->execute();
277}
278
279} // namespace Mantid::DataHandling
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
This class is shared by a few Workspace types and holds information related to a particular experimen...
A property class for workspaces.
bool checkIsValidShape(const API::ExperimentInfo_sptr &ei, std::string &shapeXML, bool &isMeshShape)
void setSampleShape(const API::Workspace_sptr &ws, std::string &translatedXML)
void init() override
Initialise the properties.
ArrayLenghtValidator : Validate length of an array property.
Support for a property that holds an array of values.
Class for 3D vectors.
Definition V3D.h:34
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
std::shared_ptr< ExperimentInfo > ExperimentInfo_sptr
Shared pointer to ExperimentInfo.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ InOut
Both an input & output workspace.
Definition Property.h:55