se2ez
yaml.cpp
Go to the documentation of this file.
1 /* Author: Zachary Kingston */
2 
3 #include <se2ez/core/io.h>
4 #include <se2ez/core/math.h>
5 #include <se2ez/core/yaml.h>
6 #include <se2ez/core/geometry.h>
7 #include <se2ez/core/frame.h>
8 #include <se2ez/core/state.h>
9 #include <se2ez/core/robot.h>
10 #include <se2ez/core/collision.h>
12 
13 #include <yaml-cpp/yaml.h>
14 
15 using namespace se2ez;
16 
17 namespace
18 {
19  /** \brief Parses a YAML node into a geometry object.
20  * \param[in] node Node to parse.
21  * \return A geometry object from the node.
22  */
23  GeometryPtr YAMLToGeometry(const YAML::Node &node)
24  {
25  // Extract type
26  if (!io::isNode(node["type"]))
27  io::throwParsingError(node, "No \"type\" entry in geometry in YAML");
28 
29  Geometry::Type type;
30  try
31  {
32  type = Geometry::stringToType(node["type"].as<std::string>());
33  }
34  catch (...)
35  {
36  io::throwParsingError(node["type"], "\"type\" is not a valid joint type.");
37  }
38 
39  // Extract offset
40  Eigen::Vector3d offset{0., 0., 0.};
41  if (io::isNode(node["offset"]))
42  {
43  if (node["offset"].size() != 3)
44  io::throwParsingError(node["offset"], "\"offset\" must be three-dimensional");
45 
46  offset = io::toVector(node["offset"]);
47  }
48 
49  // Extract color
50  Eigen::Vector4d color{0., 0., 0., 1.};
51  if (io::isNode(node["color"]))
52  {
53  if (node["color"].size() != 4)
54  io::throwParsingError(node["color"], "\"color\" must be four-dimensional");
55 
56  color = io::toVector(node["color"]);
57  }
58 
59  // Extract dimensions / or points
60  if (type == Geometry::BOX || type == Geometry::CIRCLE)
61  {
62  Eigen::VectorXd dimensions{};
63  if (!io::isNode(node["dimensions"]))
64  io::throwParsingError(node, "No \"dimensions\" entry in geometry in YAML");
65 
66  if (node["dimensions"].IsSequence())
67  dimensions = io::toVector(node["dimensions"]);
68  else
69  dimensions = math::toVector(node["dimensions"].as<double>());
70 
71  return std::make_shared<Geometry>(type, dimensions, offset, color);
72  }
73 
74  if (type == Geometry::CONVEX || type == Geometry::SIMPLE)
75  {
76  if (!io::isNode(node["points"]))
77  io::throwParsingError(node, "No \"points\" entry in geometry in YAML");
78 
79  if (!node["points"].IsSequence())
80  io::throwParsingError(node["points"], "Points must be a sequence");
81 
82  tf::EigenVector<Eigen::Vector2d> points(node["points"].size());
83  for (unsigned int i = 0; i < node["points"].size(); ++i)
84  points[i] = io::toVector(node["points"][i]);
85 
86  return std::make_shared<Geometry>(type, points, offset, color);
87  }
88 
89  io::throwParsingError(node["type"], "Unhandled geometry type in YAML!");
90  }
91 
92  /** \brief Parses a YAML node into frame information.
93  * \param[in] node Node to parse.
94  * \return A tuple of the parent, the created frame, and a list of names of frames that are allowable
95  * collisions.
96  */
97  std::tuple<std::string, FramePtr, std::vector<std::string>> YAMLToFrame(const YAML::Node &node)
98  {
99  // Extract name
100  if (!io::isNode(node["name"]))
101  io::throwParsingError(node, "No \"name\" entry in frame in YAML");
102 
103  std::string name = node["name"].as<std::string>();
104 
105  // Extract joint
106  Joint::Type type = Joint::FIXED;
107  if (io::isNode(node["joint"]))
108  {
109  try
110  {
111  type = Joint::stringToType(node["joint"].as<std::string>());
112  }
113  catch (...)
114  {
115  io::throwParsingError(node["joint"], "Invalid joint type!");
116  }
117  }
118 
119  // Extract tip frame
120  Eigen::Vector3d tip{0., 0., 0.};
121  if (io::isNode(node["tip"]))
122  {
123  if (node["tip"].size() != 3)
124  io::throwParsingError(node["tip"], "\"tip\" must be three-dimensional");
125 
126  tip = io::toVector(node["tip"]);
127  }
128 
129  // Extract limits
130  Eigen::VectorXd lower{};
131  Eigen::VectorXd upper{};
132  if (io::isNode(node["limits"]))
133  {
134  if (!io::isNode(node["limits"]["upper"]) || !io::isNode(node["limits"]["lower"]))
135  io::throwParsingError(node["limits"], "No \"upper\" or \"lower\" entry in frame in YAML");
136 
137  if (node["limits"]["upper"].IsSequence())
138  {
139  upper = io::toVector(node["limits"]["upper"]);
140  lower = io::toVector(node["limits"]["lower"]);
141  }
142  else
143  {
144  upper = math::toVector(node["limits"]["upper"].as<double>());
145  lower = math::toVector(node["limits"]["lower"].as<double>());
146  }
147  }
148 
149  // Extract geometry
150  std::vector<GeometryPtr> geometry;
151  if (io::isNode(node["geometry"]))
152  {
153  if (node["geometry"].IsSequence())
154  for (const auto &geo : node["geometry"])
155  geometry.emplace_back(YAMLToGeometry(geo));
156 
157  else
158  io::throwParsingError(node["geometry"], "\"geometry\" must be a sequence");
159  }
160 
161  // Extract parent
162  std::string parent = "root";
163  if (io::isNode(node["parent"]))
164  parent = node["parent"].as<std::string>();
165 
166  std::vector<std::string> allowed;
167  if (io::isNode(node["allowed"]))
168  {
169  if (!node["allowed"].IsSequence())
170  io::throwParsingError(node["allowed"], "\"allowed\" entry in YAML not a sequence");
171 
172  for (const auto &allow : node["allowed"])
173  allowed.emplace_back(allow.as<std::string>());
174  }
175 
176  return std::make_tuple(parent, std::make_shared<Frame>(name, tip, type, lower, upper, geometry),
177  allowed);
178  }
179 
180  /** \brief Parses a YAML node into a Robot.
181  * \param[in] node Node to parse.
182  * \return An allocated robot.
183  */
184  RobotPtr YAMLToRobot(const YAML::Node &node)
185  {
186  RobotPtr robot = std::make_shared<Robot>();
187 
188  if (!io::isNode(node["robot"]))
189  io::throwParsingError(node, "No \"robot\" entry in YAML");
190 
191  if (!node["robot"].IsSequence())
192  io::throwParsingError(node["robot"], "\"robot\" entry in YAML not a sequence");
193 
194  for (const auto &frame : node["robot"])
195  {
196  const auto &result = YAMLToFrame(frame);
197  robot->attachFrame(std::get<1>(result), std::get<0>(result));
198 
199  for (const auto &allow : std::get<2>(result))
200  robot->getACM()->disable(allow, std::get<1>(result)->getName());
201  }
202 
203  robot->compileTree();
204  return robot;
205  }
206 
207  /** \brief Parses a YAML node into a named state for a robot.
208  * \param[in,out] robot Robot to add state to.
209  * \param[in] node Node to parse.
210  */
211  void YAMLToNamedState(RobotPtr robot, const YAML::Node &node)
212  {
213  if (!io::isNode(node["name"]))
214  io::throwParsingError(node, "No \"name\" entry in YAML");
215 
216  if (!io::isNode(node["configuration"]))
217  io::throwParsingError(node, "No \"configuration\" entry in YAML");
218 
219  if (!node["configuration"].IsSequence())
220  io::throwParsingError(node["configuration"], "\"configuration\" entry in YAML not a sequence");
221 
222  StatePtr state = std::make_shared<State>(robot);
223  std::string name = node["name"].as<std::string>();
224 
225  for (const auto &joint : node["configuration"])
226  {
227  std::string frame = joint["frame"].as<std::string>();
228  auto x = robot->getFrameValues(frame, state->data);
229  auto v = io::toVector(joint["value"]);
230 
231  if (x.size() != v.size())
233  joint, "Wrong number of values in frame \"%2%\" for named state \"%1%\"", name, frame);
234 
235  x = v;
236  }
237 
238  robot->setNamedState(name, state);
239  }
240 
241  /** \brief Parses a YAML node into a list of named states for a robot.
242  * \param[in,out] robot Robot to add states to.
243  * \param[in] node Node to parse.
244  */
245  void YAMLToStates(RobotPtr robot, const YAML::Node &node)
246  {
247  if (!io::isNode(node["states"]))
248  return;
249 
250  if (!node["states"].IsSequence())
251  io::throwParsingError(node["states"], "\"states\" entry in YAML not a sequence");
252 
253  for (const auto &state : node["states"])
254  YAMLToNamedState(robot, state);
255  }
256 } // namespace
257 
258 bool io::isNode(const YAML::Node &node)
259 {
260  bool r = true;
261  try
262  {
263  r = !node.IsNull();
264 
265  if (r)
266  try
267  {
268  r = node.as<std::string>() != "~";
269  }
270  catch (std::exception &e)
271  {
272  }
273  }
274  catch (YAML::InvalidNode &e)
275  {
276  return false;
277  }
278 
279  return r;
280 }
281 
282 Eigen::VectorXd io::toVector(const YAML::Node &node)
283 {
284  if (!node.IsSequence())
285  io::throwParsingError(node, "Node is not a sequence");
286 
287  Eigen::VectorXd x(node.size());
288  for (unsigned int i = 0; i < node.size(); ++i)
289  x[i] = node[i].as<double>();
290 
291  return x;
292 }
293 
294 YAML::Node io::toVector(const Eigen::Ref<const Eigen::VectorXd> &vec)
295 {
296  YAML::Node node;
297  for (unsigned int i = 0; i < vec.size(); ++i)
298  node.push_back(vec[i]);
299 
300  return node;
301 }
302 
304 {
305  const auto &result = io::loadFileToYAML(filename);
306  if (!result.first)
307  {
308  SE2EZ_ERROR("Failed to load file \"%1%\"", filename);
309  return nullptr;
310  }
311 
312  try
313  {
314  auto robot = YAMLToRobot(result.second);
315  YAMLToStates(robot, result.second);
316 
317  return robot;
318  }
319  catch (std::exception &e)
320  {
321  SE2EZ_ERROR("In YAML File %1%: %2%", io::resolvePath(filename), e.what());
322  return nullptr;
323  }
324 }
325 
326 bool io::loadStates(RobotPtr robot, const std::string &filename)
327 {
328  const auto &result = io::loadFileToYAML(filename);
329  if (!result.first)
330  {
331  SE2EZ_ERROR("Failed to load file \"%1%\"", filename);
332  return false;
333  }
334 
335  try
336  {
337  YAMLToStates(robot, result.second);
338  return true;
339  }
340  catch (std::exception &e)
341  {
342  SE2EZ_ERROR("In YAML File %1%: %2%", io::resolvePath(filename), e.what());
343  return false;
344  }
345 }
Eigen::VectorXd toVector(const YAML::Node &node)
Convert a sequence of doubles in YAML to an Eigen vector.
Definition: yaml.cpp:282
A shared pointer wrapper for se2ez::State.
RobotPtr loadRobot(const std::string &filename)
Loads a robot from a YAML file.
Definition: yaml.cpp:303
A rectangular prism.
Definition: geometry.h:33
void throwParsingError(const YAML::Node &node, const std::string &fmt, Args &&... args)
Convenience function to print out a YAML error message with line and column information.
Definition: utility.h:42
A filled circle.
Definition: geometry.h:34
A convex polygon.
Definition: geometry.h:35
Type
Type of joint.
Definition: joint.h:38
T make_tuple(T... args)
const std::pair< bool, YAML::Node > loadFileToYAML(const std::string &path)
Loads a file to a YAML node.
Definition: io.cpp:123
static Type stringToType(const std::string &geometry)
Try to convert a string into a geometry type. Not case-sensitive.
Definition: geometry.cpp:13
Fixed joint, just a rigid transformation.
Definition: joint.h:40
Type
Type of geometry.
Definition: geometry.h:31
T what(T... args)
A shared pointer wrapper for se2ez::Geometry.
Eigen::VectorXd toVector(double x)
Converts a single double value into a 1 x 1 matrix.
Definition: math.cpp:134
A simple polygon (no holes).
Definition: geometry.h:36
static Type stringToType(const std::string &joint)
Try to convert a string into a joint type. Not case-sensitive.
Definition: joint.cpp:14
T size(T... args)
A shared pointer wrapper for se2ez::Robot.
bool loadStates(RobotPtr robot, const std::string &filename)
Loads named robot states from a YAML file.
Definition: yaml.cpp:326
Main namespace.
Definition: collision.h:11
#define SE2EZ_ERROR(fmt,...)
Definition: log.h:39
bool isNode(const YAML::Node &node)
Returns true if node is a valid YAML node.
Definition: yaml.cpp:258
const std::string resolvePath(const std::string &path)
Resolves file paths to their canonical form.
Definition: io.cpp:79
T emplace_back(T... args)