Component.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package navigation::ArmarXObjects::room_editor
17 * @author Fabian Reister ( fabian dot reister at kit dot edu )
18 * @date 2026
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "Component.h"
24
25#include <algorithm>
26#include <cstdint>
27#include <memory>
28#include <mutex>
29#include <string>
30#include <tuple>
31#include <utility>
32#include <vector>
33
44
46
47#include <armarx/navigation/components/navigation_memory/ComponentInterface.h>
50
52{
53
54 const std::string Component::defaultName = "room_editor";
55
57 {
58 addPlugin(roomsReaderPlugin);
59 addPlugin(roomsWriterPlugin);
60 }
61
64 {
67
68 def->optional(properties.providerSegmentName,
69 "p.providerSegmentName",
70 "If set, only the rooms of this provider segment (i.e. navigation graph) "
71 "are edited. If empty, all rooms are edited.");
72
73 def->optional(
74 properties.updateRate, "p.updateRate", "Rate of the ArViz interaction loop [Hz].");
75
76 def->optional(properties.layerPrefix,
77 "p.visu.layerPrefix",
78 "Prefix of the ArViz layers created by this component.");
79
80 def->optional(properties.vertexHandleRadius,
81 "p.visu.vertexHandleRadius",
82 "Radius of the draggable polygon vertex handles [mm].");
83
84 def->optional(properties.edgeHandleSize,
85 "p.visu.edgeHandleSize",
86 "Edge length of the draggable edge handles [mm].");
87
88 def->optional(properties.exportPackageName,
89 "p.export.packageName",
90 "ArmarX package the rooms are exported to.");
91
92 def->optional(properties.exportDirectory,
93 "p.export.directory",
94 "Directory relative to the package's data directory the rooms are "
95 "exported to. The `rooms.json` files are written to "
96 "`<directory>/<navigation graph>/rooms.json`, so this usually has to "
97 "include the dataset, e.g. `navigation-graphs/50_19`.");
98
99 return def;
100 }
101
102 void
106
107 void
109 {
110 RoomEditor::Parameters editorParameters;
111 editorParameters.layerPrefix = properties.layerPrefix;
112 editorParameters.vertexHandleRadius = properties.vertexHandleRadius;
113 editorParameters.edgeHandleSize = properties.edgeHandleSize;
114
115 editor = std::make_unique<RoomEditor>(arviz, editorParameters);
116 editor->setRoomChangedCallback([this](const EditableRoom& room) { storeRoom(room); });
117
120
121 task = new RunningTask<Component>(this, &Component::run, "RoomEditorTask");
122 task->start();
123 }
124
125 void
127 {
128 if (task)
129 {
130 const bool join = true;
131 task->stop(join);
132 task = nullptr;
133 }
134
135 editor.reset();
136 }
137
138 void
142
143 std::string
145 {
146 return Component::defaultName;
147 }
148
149 std::string
151 {
152 return Component::defaultName;
153 }
154
155 void
156 Component::run()
157 {
158 ARMARX_CHECK_NOT_NULL(editor);
159
160 viz::StagedCommit stage;
161
162 reload();
163 editor->redrawAll(stage);
164
165 // The ArViz handles are manipulated with Coin draggers. Those only receive mouse
166 // events while the 3D viewer is NOT in viewing (camera) mode, which is the default.
167 // Selecting an element and using its context menu works either way, so "the context
168 // menu works but nothing can be dragged" usually means viewing mode is still on.
169 ARMARX_IMPORTANT << "Room editor is ready. Drag the handles in ArViz to reshape a room. "
170 "If the handles cannot be grabbed, disable the 3D viewer's viewing "
171 "mode (the toolbar toggle above the 3D view, shortcut `V`).";
172
173 const float rate = std::max(properties.updateRate, 1.F);
174 armarx::Metronome metronome(
175 armarx::Duration::MilliSeconds(static_cast<std::int64_t>(1000.F / rate)));
176
177 while (not task->isStopped())
178 {
179 const viz::CommitResult result = arviz.commit(stage);
180
181 // The stage is rebuilt from scratch: only layers that actually changed are
182 // re-committed by the editor.
183 stage.reset();
184
185 editor->update(result, stage);
186
187 // Never pull the rug from underneath an ongoing interaction.
188 if (not editor->isInteracting() and reloadRequested.exchange(false))
189 {
190 reload();
191 editor->redrawAll(stage);
192 }
193
194 metronome.waitForNextTick();
195 }
196 }
197
198 bool
199 Component::reload()
200 {
201 ARMARX_CHECK_NOT_NULL(editor);
202
203 memory::client::rooms::Reader::Query query;
204 query.timestamp = armarx::Clock::Now();
205 if (not properties.providerSegmentName.empty())
206 {
207 query.providerName = properties.providerSegmentName;
208 }
209
210 const auto result = roomsReaderPlugin->get().query(query);
211
212 if (not result)
213 {
214 ARMARX_WARNING << "Could not read the rooms from the navigation memory: "
215 << result.errorMessage;
216 editor->setRooms({});
217 numRooms = 0;
218 return false;
219 }
220
221 std::vector<EditableRoom> rooms;
222 rooms.reserve(result.entries.size());
223
224 for (const auto& entry : result.entries)
225 {
226 EditableRoom editable{.providerSegmentName = entry.instanceID.providerSegmentName,
227 .room = entry.room};
228
229 if (editable.room.name.empty())
230 {
231 // The entity name is the authoritative room name.
232 editable.room.name = entry.instanceID.entityName;
233 }
234
235 rooms.push_back(std::move(editable));
236 }
237
238 // Stable ordering, so that the colors of the rooms do not jump around on reload.
239 std::sort(rooms.begin(),
240 rooms.end(),
241 [](const EditableRoom& lhs, const EditableRoom& rhs)
242 {
243 return std::tie(lhs.providerSegmentName, lhs.room.name) <
244 std::tie(rhs.providerSegmentName, rhs.room.name);
245 });
246
247 ARMARX_INFO << "Read " << rooms.size() << " room(s) from the navigation memory.";
248
249 numRooms = static_cast<int>(rooms.size());
250 editor->setRooms(rooms);
251
252 return true;
253 }
254
255 void
256 Component::storeRoom(const EditableRoom& editable)
257 {
258 if (editable.providerSegmentName.empty())
259 {
260 ARMARX_ERROR << "Cannot write room `" << editable.room.name
261 << "` back into the memory: the provider segment is unknown.";
262 return;
263 }
264
265 if (not roomsWriterPlugin->get().store(
266 editable.room, editable.providerSegmentName, armarx::Clock::Now()))
267 {
268 ARMARX_ERROR << "Failed to write room `" << editable.room.name
269 << "` back into the navigation memory.";
270 return;
271 }
272
273 ARMARX_INFO << "Updated room `" << editable.room.name << "` in the navigation memory.";
274 }
275
276 bool
277 Component::exportRooms()
278 {
279 std::string packageName;
280 std::string directory;
281 {
282 std::scoped_lock lock(exportPathMutex);
283 packageName = properties.exportPackageName;
284 directory = properties.exportDirectory;
285 }
286
287 const auto navigationMemoryPrx = memory::NavigationMemoryInterfacePrx::uncheckedCast(
288 roomsReaderPlugin->get().readingPrx());
289
290 if (not navigationMemoryPrx)
291 {
292 ARMARX_ERROR << "Export failed: not connected to a navigation memory.";
293 return false;
294 }
295
296 const armarx::PackagePath packagePath(packageName, directory);
297
298 try
299 {
300 if (not navigationMemoryPrx->storeRooms(packagePath.serialize()))
301 {
302 ARMARX_ERROR << "Export failed: the navigation memory could not store the rooms.";
303 return false;
304 }
305 }
306 catch (const Ice::Exception& e)
307 {
308 ARMARX_ERROR << "Export failed: " << e.what();
309 return false;
310 }
311
312 ARMARX_IMPORTANT << "Exported the rooms to package `" << packageName << "`, directory `"
313 << directory << "`.";
314 return true;
315 }
316
317 void
318 Component::reloadFromMemory(const Ice::Current& /*current*/)
319 {
320 reloadRequested = true;
321 }
322
323 bool
324 Component::exportToJson(const Ice::Current& /*current*/)
325 {
326 return exportRooms();
327 }
328
329 void
331 {
332 using namespace armarx::RemoteGui::Client;
333
334 tab.statusLabel.setText("Editing " + std::to_string(numRooms.load()) + " room(s).");
335
336 tab.reloadButton.setLabel("Reload from memory");
337
338 tab.exportPackageName.setValue(properties.exportPackageName);
339 tab.exportDirectory.setValue(properties.exportDirectory);
340 tab.exportButton.setLabel("Export rooms to JSON");
341 tab.exportLabel.setText("");
342
343 GridLayout exportGrid;
344 int row = 0;
345 {
346 exportGrid.add(Label("Package:"), {row, 0}).add(tab.exportPackageName, {row, 1});
347 ++row;
348
349 exportGrid.add(Label("Data path:"), {row, 0}).add(tab.exportDirectory, {row, 1});
350 ++row;
351
352 exportGrid.add(tab.exportButton, {row, 0}).add(tab.exportLabel, {row, 1});
353 ++row;
354 }
355
356 GroupBox exportGroup;
357 exportGroup.setLabel("Export");
358 exportGroup.addChild(exportGrid);
359
360 VBoxLayout root = {tab.statusLabel, tab.reloadButton, exportGroup, VSpacer()};
361 RemoteGui_createTab(getName(), root, &tab);
362 }
363
364 void
366 {
367 tab.statusLabel.setText("Editing " + std::to_string(numRooms.load()) + " room(s).");
368
369 if (tab.reloadButton.wasClicked())
370 {
371 reloadRequested = true;
372 }
373
374 if (tab.exportButton.wasClicked())
375 {
376 {
377 std::scoped_lock lock(exportPathMutex);
378 properties.exportPackageName = tab.exportPackageName.getValue();
379 properties.exportDirectory = tab.exportDirectory.getValue();
380 }
381
382 tab.exportLabel.setText(exportRooms() ? "Exported the rooms."
383 : "Export failed. See the log for details.");
384 }
385 }
386
388
389} // namespace armarx::navigation::components::room_editor
int Label(int n[], int size, int *curLabel, MiscLib::Vector< std::pair< int, size_t > > *labels)
Definition Bitmap.cpp:801
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:88
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
std::string getName() const
Retrieve name of object.
Simple rate limiter for use in loops to maintain a certain frequency given a clock.
Definition Metronome.h:57
Interactive ArViz-based editor for room polygons.
Definition Component.h:76
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition Component.cpp:63
static std::string GetDefaultName()
Get the component's default name.
CommitResult commit(StagedCommit const &commit)
Definition Client.cpp:89
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
void reloadFromMemory()
Re-reads the rooms from the navigation memory.
bool exportToJson()
Dumps the rooms of the navigation memory to disk (rooms.json), using the package and directory config...
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void RemoteGui_createTab(std::string const &name, RemoteGui::Client::Widget const &rootWidget, RemoteGui::Client::Tab *tab)
void addChild(Widget const &child)
Definition Widgets.cpp:95
GridLayout & add(Widget const &child, Pos pos, Span span=Span{1, 1})
Definition Widgets.cpp:438
void setLabel(std::string const &text)
Definition Widgets.cpp:420
A room together with the provider segment (i.e. the navigation graph) it belongs to.
Definition RoomEditor.h:45
float edgeHandleSize
Edge length of the (cubic) edge handles [mm].
Definition RoomEditor.h:74
float vertexHandleRadius
Radius of the (spherical) vertex handles [mm].
Definition RoomEditor.h:71
std::string layerPrefix
Prefix of the ArViz layers created by the editor.
Definition RoomEditor.h:68
A staged commit prepares multiple layers to be committed.
Definition Client.h:30
void reset()
Reset all staged layers and interaction requests.
Definition Client.h:66