VisualizationRobot.cpp
Go to the documentation of this file.
2
3#include <VirtualRobot/SceneObject.h>
4#include <VirtualRobot/Visualization/CoinVisualization/CoinVisualization.h>
5#include <VirtualRobot/XML/RobotIO.h>
6
12
13#include <Inventor/SbColor.h>
14#include <Inventor/nodes/SoMaterial.h>
15
16namespace armarx::viz::coin
17{
18 namespace
19 {
21 loadRobot(std::string const& project, std::string const& filename)
22 {
24
25 if (filename.empty())
26 {
27 ARMARX_INFO << deactivateSpam() << "No filename provided for robot.";
28 return nullptr;
29 }
30
31 std::string fullFilename;
32 // Suppress warnings when full path is specified (is discouraged, but may happen).
33 if (project.empty() && std::filesystem::path(filename).is_absolute())
34 {
36 << "You specified the absolute path to the robot file:"
37 << "\n\t'" << filename << "'"
38 << "\nConsider specifying the containing ArmarX package and relative "
39 "data path instead to "
40 << "improve portability to other systems.";
41 }
42 // We need to always check that the file is readable otherwise, VirtualRobot::RobotIO::loadRobot crashes
44 if (!ArmarXDataPath::SearchReadableFile(filename, fullFilename))
45 {
47 << "Unable to find readable file for name: " << filename;
48 return nullptr;
49 }
50
51 try
52 {
53 // Always load full model. Visualization can choose between full and collision model
54 VirtualRobot::RobotIO::RobotDescription loadMode = VirtualRobot::RobotIO::eFull;
55 // This check is always true since it does not compare the flag to anything ==> if (true)
56 // Deactivated for now
57 // if (data::ModelDrawStyle::COLLISION)
58 // {
59 // loadMode = VirtualRobot::RobotIO::eCollisionModel;
60 // }
61 ARMARX_INFO << "Loading robot from " << fullFilename;
62 result = VirtualRobot::RobotIO::loadRobot(fullFilename, loadMode);
63 if (result)
64 {
65 result->setThreadsafe(false);
66 result->setPropagatingJointValuesEnabled(true);
67 }
68 else
69 {
70 ARMARX_WARNING << "Could not load robot from file: " << fullFilename
71 << "\nReason: loadRobot() returned nullptr";
72 }
73 }
74 catch (std::exception const& ex)
75 {
76 ARMARX_WARNING << "Could not load robot from file: " << fullFilename
77 << "\nReason: " << ex.what();
78 }
79
80 return result;
81 }
82
83 struct RobotInstancePool
84 {
85 std::string project;
86 std::string filename;
87 std::size_t usedInstances = 0;
88 std::vector<VirtualRobot::RobotPtr> robots;
89 };
90
91 static std::vector<RobotInstancePool> robotCache;
92
94 getRobotFromCache(std::string const& project, std::string const& filename)
95 {
96 // We can use a global variable, since this code is only executed in the GUI thread
97
98 LoadedRobot result;
99 result.project = project;
100 result.filename = filename;
101
102 for (RobotInstancePool& instancePool : robotCache)
103 {
104 if (instancePool.project == project && instancePool.filename == filename)
105 {
106 // There are two possibilities here:
107 if (instancePool.usedInstances < instancePool.robots.size())
108 {
109 // 1) We have still unused instances in the pool ==> Just return one
110 ARMARX_DEBUG << "Reusing robot instance from cache " << VAROUT(project)
111 << ", " << VAROUT(filename);
112 result.robot = instancePool.robots[instancePool.usedInstances];
113 instancePool.usedInstances += 1;
114 }
115
116 else
117 {
118 // 2) We do not have unused instances in the pool ==> Clone one
119 ARMARX_INFO << "Cloning robot from cache " << VAROUT(project) << ", "
120 << VAROUT(filename);
121
122 if (!instancePool.robots.empty())
123 {
124 VirtualRobot::RobotPtr const& robotToClone =
125 instancePool.robots.front();
126 float scaling = 1.0f;
127 bool preventCloningMeshesIfScalingIs1 = true;
128
129 {
130 armarx::core::time::ScopedStopWatch sw(
131 [](const armarx::Duration& duration) {
132 ARMARX_DEBUG << "Cloning took "
133 << duration.toSecondsDouble() << " seconds.";
134 });
135 result.robot = robotToClone->clone(
136 nullptr, scaling, preventCloningMeshesIfScalingIs1);
137 }
138
139 // Insert the cloned robot into the instance pool
140 instancePool.robots.push_back(result.robot);
141 instancePool.usedInstances += 1;
142 }
143 else
144 {
145 ARMARX_WARNING << "Encountered empty robot instance pool while trying "
146 "to clone new instance"
147 << "\nRobot: " << VAROUT(project) << ", "
148 << VAROUT(filename)
149 << "\nUsed instances: " << instancePool.usedInstances
150 << "\nRobots: " << instancePool.robots.size();
151 }
152 }
153 return result;
154 }
155 }
156
157 ARMARX_DEBUG << "Loading robot from file " << VAROUT(project) << ", "
158 << VAROUT(filename);
159 result.robot = loadRobot(project, filename);
160 if (result.robot)
161 {
162 RobotInstancePool& instancePool = robotCache.emplace_back();
163 instancePool.project = project;
164 instancePool.filename = filename;
165 instancePool.robots.push_back(result.robot);
166 instancePool.usedInstances = 1;
167 }
168 else
169 {
170 ARMARX_WARNING << deactivateSpam(5) << "Robot " << VAROUT(project) << ", "
171 << VAROUT(filename) << "could not be loaded!";
172 }
173 return result;
174 }
175 } // namespace
176
178 {
179 for (RobotInstancePool& instancePool : robotCache)
180 {
181 if (instancePool.project == loaded.project && instancePool.filename == loaded.filename)
182 {
183 ARMARX_DEBUG << "Removing robot from chace " << VAROUT(loaded.project) << ", "
184 << VAROUT(loaded.filename);
185 std::vector<VirtualRobot::RobotPtr>& robots = instancePool.robots;
186 auto robotIter = std::find(robots.begin(), robots.end(), loaded.robot);
187 if (robotIter != robots.end())
188 {
189 // Do not erase the robot, but rather move it to the back
190 // We can later reuse the unused instances at the back of the vector
191 std::swap(*robotIter, robots.back());
192 if (instancePool.usedInstances > 0)
193 {
194 instancePool.usedInstances -= 1;
195 }
196 else
197 {
198 ARMARX_WARNING << "Expected there to be at least one used instance "
199 << "while trying to put robot instance back into the pool"
200 << "\nRobot: " << VAROUT(loaded.project) << ", "
201 << VAROUT(loaded.filename)
202 << "\nUsed instances: " << instancePool.usedInstances;
203 }
204 }
205 }
206 }
207 }
208
209 bool
211 {
212 IceUtil::Time time_start = IceUtil::Time::now();
213 (void)time_start;
214
215 bool robotChanged =
216 loaded.project != element.project || loaded.filename != element.filename;
217 if (robotChanged)
218 {
219 // The robot file changed, so reload the robot
220 loaded = getRobotFromCache(element.project, element.filename);
221 }
222 if (!loaded.robot)
223 {
225 << "Robot will not visualized since it could not be loaded."
226 << "\nID: " << element.id << "\nProject: " << element.project
227 << "\nFilename: " << element.filename;
228 return true;
229 }
230 //IceUtil::Time time_load = IceUtil::Time::now();
231
232 bool drawStyleChanged = loadedDrawStyle != element.drawStyle;
233 if (robotChanged || drawStyleChanged)
234 {
235 recreateVisualizationNodes(element.drawStyle);
236 loadedDrawStyle = element.drawStyle;
237 }
238 //IceUtil::Time time_style = IceUtil::Time::now();
239
240 // Set pose, configuration and so on
241
242 VirtualRobot::Robot& robot = *loaded.robot;
243
244 // We do not need to update the global pose in the robot
245 // Since we only visualize the results, we can simply use the
246 // pose from the Element base class.
247
248 // Eigen::Matrix4f pose = defrost(element.pose);
249 // robot.setGlobalPose(pose, false);
250
251 // Check joint values for changes
252
253 for (const auto& pair : element.jointValues)
254 {
255 std::string const& nodeName = pair.first;
256 float newJointValue = pair.second;
257 VirtualRobot::RobotNodePtr robotNode = robot.getRobotNode(nodeName);
258
259 if (robotNode == nullptr)
260 {
261 continue;
262 }
263
264 const float oldJointValue = robotNode->getJointValue();
265 const float diff = std::abs(newJointValue - oldJointValue);
266 const bool jointValuesChanged = diff > 0.001f;
267 if (jointValuesChanged)
268 {
269 // Only set the joint values if they changed
270 // This call causes internal updates to the visualization even if joint angles are the same!
271 robot.setJointValues(element.jointValues);
272 break;
273 }
274 }
275 //IceUtil::Time time_set = IceUtil::Time::now();
276
277 if (loadedDrawStyle & data::ModelDrawStyle::OVERRIDE_COLOR)
278 {
279 if (loadedColor.r != element.color.r || loadedColor.g != element.color.g ||
280 loadedColor.b != element.color.b || loadedColor.a != element.color.a)
281 {
282 int numChildren = node->getNumChildren();
283 for (int i = 0; i < numChildren; i++)
284 {
285 SoSeparator* nodeSep = static_cast<SoSeparator*>(node->getChild(i));
286 // The first entry must be a SoMaterial (see recreateVisualizationNodes)
287 SoMaterial* m = dynamic_cast<SoMaterial*>(nodeSep->getChild(0));
288 if (!m)
289 {
290 ARMARX_WARNING << "Error at node with index: " << i;
291 continue;
292 }
293
294 auto color = element.color;
295 const float conv = 1.0f / 255.0f;
296 float a = color.a * conv;
297 SbColor coinColor(conv * color.r, conv * color.g, conv * color.b);
298 m->diffuseColor = coinColor;
299 m->ambientColor = coinColor;
300 m->transparency = 1.0f - a;
301 m->setOverride(true);
302 }
303 loadedColor = element.color;
304 }
305 }
306 //IceUtil::Time time_color = IceUtil::Time::now();
307
308 // Setting the joint angles takes > 0.5 ms! This is unexpected...
309 // ARMARX_INFO << "Total: " << (time_color - time_start).toMilliSecondsDouble()
310 // << "\nLoad: " << (time_load - time_start).toMilliSecondsDouble()
311 // << "\nStyle: " << (time_style - time_load).toMilliSecondsDouble()
312 // << "\nSet: " << (time_set - time_style).toMilliSecondsDouble()
313 // << "\nColor: " << (time_color - time_set).toMilliSecondsDouble();
314
315 return true;
316 }
317
318 void
320 {
321 VirtualRobot::SceneObject::VisualizationType visuType = VirtualRobot::SceneObject::Full;
322 if (drawStyle & data::ModelDrawStyle::COLLISION)
323 {
324 visuType = VirtualRobot::SceneObject::Collision;
325 }
326
327 node->removeAllChildren();
328
329 auto& robot = *loaded.robot;
330 for (size_t i = 0; i < robot.getRobotNodes().size(); ++i)
331 {
332 VirtualRobot::RobotNodePtr robNode = robot.getRobotNodes()[i];
333 SoSeparator* nodeSep = new SoSeparator;
334
335 // This material is used to color the nodes individually
336 // We require it to be the first node in the separator for updates
337 SoMaterial* nodeMat = new SoMaterial;
338 nodeMat->setOverride(false);
339 nodeSep->addChild(nodeMat);
340
341 auto robNodeVisu = robNode->getVisualization<VirtualRobot::CoinVisualization>(visuType);
342 if (robNodeVisu)
343 {
344 SoNode* sepRobNode = robNodeVisu->getCoinVisualization();
345
346 if (sepRobNode)
347 {
348 nodeSep->addChild(sepRobNode);
349 }
350 }
351
352 node->addChild(nodeSep);
353 }
354 }
355
356 void
358 {
359 robotCache.clear();
360 }
361} // namespace armarx::viz::coin
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define VAROUT(x)
static bool SearchReadableFile(const std::string &querryFileName, std::string &resultFileName, bool verbose=true)
static bool FindPackageAndAddDataPath(const std::string &packageName)
Search for the package and add its data path if it was found.
double toSecondsDouble() const
Returns the amount of seconds.
Definition Duration.cpp:90
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
void project(pcl::PointCloud< pcl::PointXYZ > &cloud, wykobi::Polygon polygon)
bool update(ElementType const &element)