diff --git a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp index cb2ba76bf3a1a2bf3f739c52692ef5ebf0f8cb4a..6c89282c5bc007f80915d56654d8d18706bf6609 100644 --- a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp +++ b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp @@ -27,7 +27,9 @@ #include <SimoxUtility/algorithm/string.h> #include <RobotAPI/libraries/armem/core/error.h> +#include <RobotAPI/libraries/armem/core/ice_conversions.h> #include <RobotAPI/libraries/armem/server/MemoryRemoteGui.h> +#include <RobotAPI/libraries/armem/core/ice_conversions_templates.h> #include <RobotAPI/components/armem/server/ExampleMemory/aron/ExampleData.aron.generated.h> @@ -116,6 +118,88 @@ namespace armarx + // ACTIONS + armem::actions::GetActionsOutputSeq + ExampleMemory::getActions( + const armem::actions::GetActionsInputSeq& input) + { + using namespace armem::actions; + Action greeting{"hi", "Say hello to " + input[0].id.entityName}; + Action failure{"fail", "Fail dramatically"}; + Action nothing{"null", "Do nothing, but deeply nested"}; + + SubMenu one{"one", "One", { nothing } }; + SubMenu two{"two", "Two", { one } }; + SubMenu three{"three", "Three", { two } }; + SubMenu four{"four", "Four", { three } }; + + Menu menu { + greeting, + failure, + four, + SubMenu{"mut", "Mutate", { + Action{"copy", "Copy latest instance"} + }} + }; + + return {{ menu.toIce() }}; + } + + armem::actions::ExecuteActionOutputSeq ExampleMemory::executeActions( + const armem::actions::ExecuteActionInputSeq& input) + { + using namespace armem::actions; + + ExecuteActionOutputSeq output; + for (const auto& [id, path] : input) + { + armem::MemoryID memoryID = armem::fromIce<armem::MemoryID>(id); + if (path == ActionPath{"hi"}) + { + ARMARX_INFO << "Hello, " << memoryID.str() << "!"; + output.emplace_back(true, ""); + } + else if (path == ActionPath{"fail"}) + { + ARMARX_WARNING << "Alas, I am gravely wounded!"; + output.emplace_back(false, "Why would you do that to him?"); + } + else if (not path.empty() and path.front() == "four" and path.back() == "null") + { + // Do nothing. + ARMARX_INFO << "Nested action (path: " << path << ")"; + output.emplace_back(true, ""); + } + else if (path == ActionPath{"mut", "copy"}) + { + auto* instance = workingMemory().findLatestInstance(memoryID); + if (instance != nullptr) + { + armem::EntityUpdate update; + armem::MemoryID newID = memoryID.getCoreSegmentID() + .withProviderSegmentName(memoryID.providerSegmentName) + .withEntityName(memoryID.entityName); + update.entityID = newID; + update.timeCreated = armem::Time::now(); + update.instancesData = { instance->data() }; + + armem::Commit newCommit; + this->commit(armem::toIce<armem::data::Commit>(newCommit)); + + tab.rebuild = true; + ARMARX_INFO << "Duplicated " << memoryID; + output.emplace_back(true, ""); + } + else + { + output.emplace_back(false, "Couldn't duplicate " + memoryID.str()); + } + } + } + + return output; + } + // REMOTE GUI void ExampleMemory::createRemoteGuiTab() diff --git a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h index a7dda191aa96c14567899a4f9064edf5f69d2bcf..66a500eeb43043ceb660d9ad9c720d2dc946fb66 100644 --- a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h +++ b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h @@ -58,7 +58,7 @@ namespace armarx // WritingInterface interface public: armem::data::AddSegmentsResult addSegments(const armem::data::AddSegmentsInput& input, const Ice::Current&) override; - armem::data::CommitResult commit(const armem::data::Commit& commit, const Ice::Current&) override; + armem::data::CommitResult commit(const armem::data::Commit& commit, const Ice::Current& = Ice::emptyCurrent) override; // LightweightRemoteGuiComponentPluginUser interface @@ -66,6 +66,11 @@ namespace armarx void createRemoteGuiTab(); void RemoteGui_update() override; + // ActionsInterface interface + public: + armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq& input) override; + armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq& input) override; + protected: diff --git a/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.cpp b/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.cpp index 144f9edcd76acbbe536084229dfe440804add7e2..accfc00fec22ef5b1f1d454b4476d3306b198747 100644 --- a/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.cpp +++ b/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.cpp @@ -9,8 +9,11 @@ #include <RobotAPI/libraries/armem/server/MemoryRemoteGui.h> #include <RobotAPI/libraries/GraspingUtility/aron/GraspCandidate.aron.generated.h> +#include <RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.h> +#include <RobotAPI/libraries/GraspingUtility/aron_conversions.h> #include <RobotAPI/libraries/armem_grasping/aron/KnownGraspCandidate.aron.generated.h> + namespace armarx::armem::server::grasp { armarx::PropertyDefinitionsPtr GraspMemory::createPropertyDefinitions() @@ -73,7 +76,7 @@ namespace armarx::armem::server::grasp { // This function is overloaded to trigger the remote gui rebuild. armem::data::CommitResult result = ReadWritePluginUser::commit(commit); - tab.rebuild = true; + gui.tab.rebuild = true; return result; } @@ -88,24 +91,422 @@ namespace armarx::armem::server::grasp void GraspMemory::createRemoteGuiTab() { - using namespace armarx::RemoteGui::Client; + //add all instances to map + workingMemory().getCoreSegment("GraspCandidate").forEachInstance([this](const auto & instance) + { + if (gui.candidatesToDraw.find(instance.id().str()) == gui.candidatesToDraw.end()){ + gui.candidatesToDraw[instance.id().str()] = false; + } + }); -// { -// -// tab.memoryGroup = armem::server::MemoryRemoteGui().makeGroupBox(workingMemory()); -// } -// -// VBoxLayout root = {tab.memoryGroup, VSpacer()}; -// RemoteGui_createTab(getName(), root, &tab); + using namespace armarx::RemoteGui::Client; + GridLayout root; + { + gui.tab.selectAll.setLabel("Select All"); + gui.tab.deselectAll.setLabel("Deselect All"); + int row = 0; + + // bimanual candidates are not available for visualization for now +// { +// root.add(Label("CoreSegment"), Pos{row, 0}); +// row++; +// std::vector<std::string> options = workingMemory().getCoreSegmentNames(); +// if (options.empty()) +// { +// options.push_back("<None>"); +// } +// gui.tab.selectCoreSegment.setOptions(options); +// if (gui.coreSegIndex >= 0 && gui.coreSegIndex < static_cast<int>(options.size())) +// { +// gui.tab.selectCoreSegment.setIndex(gui.coreSegIndex); +// gui.coreSegment = options[gui.coreSegIndex]; +// } +// root.add(gui.tab.selectCoreSegment, Pos{row,0}); +// row++; +// } + if(workingMemory().hasCoreSegment(gui.coreSegment)) + { + { + root.add(Label("Provider"), Pos{row, 0}); + row++; + std::vector<std::string> options = workingMemory().getCoreSegment(gui.coreSegment).getProviderSegmentNames(); + if (options.empty()) + { + options.push_back("<None>"); + } + else + { + options.insert(options.begin(), "<All>"); + } + + gui.tab.selectProvider.setOptions(options); + + if (gui.providerIndex >= 0 && gui.providerIndex < static_cast<int>(options.size())) + { + gui.tab.selectProvider.setIndex(gui.providerIndex); + gui.provider = options[gui.providerIndex]; + } + root.add(gui.tab.selectProvider, Pos{row, 0}); + row++; + } + + if(gui.provider == "<All>" + || workingMemory().getCoreSegment(gui.coreSegment).hasProviderSegment(gui.provider)) + { + std::vector<MemoryID> providers; + { + root.add(Label("Entity"), Pos{row,0}); + row++; + std::vector<std::string> options; + + if (gui.provider == "<All>") + { + workingMemory().getCoreSegment(gui.coreSegment).forEachProviderSegment([&providers](const auto & provider) + { + providers.push_back(provider.id()); + }); + } + else + { + providers.push_back(MemoryID(memoryName, gui.coreSegment, gui.provider)); + + } + for (MemoryID & provider : providers) + { + for(std::string & entity : workingMemory().getProviderSegment(provider).getEntityNames()) + { + options.push_back(entity); + } + } + if (options.empty()) + { + options.push_back("<None>"); + } + else + { + options.insert(options.begin(), "<All>"); + } + gui.tab.selectEntity.setOptions(options); + if (gui.entityIndex >= 0 && gui.entityIndex < static_cast<int>(options.size())) + { + gui.tab.selectEntity.setIndex(gui.entityIndex); + gui.entity = options[gui.entityIndex]; + } + root.add(gui.tab.selectEntity, Pos{row, 0}); + row++; + } + + if( gui.entity == "<All>" + || (gui.provider == "<All>" && workingMemory().getCoreSegment(gui.coreSegment).hasEntity(gui.entity)) + || workingMemory().getCoreSegment(gui.coreSegment).getProviderSegment(gui.provider).hasEntity(gui.entity)) + { + std::vector<MemoryID> entities; + { + root.add(Label("Snapshot"), Pos{row, 0}); + row++; + std::vector<std::string> options; + for (MemoryID & provider : providers) + { + if (gui.entity == "<All>") + { + workingMemory().getProviderSegment(provider).forEachEntity([&entities](auto & entity) + { + entities.push_back(entity.id()); + }); + } + else + { + if (workingMemory().getProviderSegment(provider).hasEntity(gui.entity)) + { + entities.push_back(MemoryID(memoryName, gui.coreSegment, provider.providerSegmentName, gui.entity)); + } + } + } + + for (MemoryID & entity : entities) + { + workingMemory().getEntity(entity).forEachSnapshot([this, &options](const auto & snapshot) + { + std::string time = armem::toDateTimeMilliSeconds(snapshot.time()); + gui.timeMap[time] = snapshot.time(); + options.push_back(time); + }); + } + + if (options.empty()) + { + options.push_back("<None>"); + } + else + { + options.insert(options.begin(), "<All>"); + } + gui.tab.selectSnapshot.setOptions(options); + if (gui.snapshotIndex >= 0 && gui.snapshotIndex < static_cast<int>(options.size())) + { + gui.tab.selectSnapshot.setIndex(gui.snapshotIndex); + if(options[gui.snapshotIndex] != "<None>") + { + gui.snapshot = options[gui.snapshotIndex]; + } + } + root.add(gui.tab.selectSnapshot, Pos{row, 0}); + row++; + + + } + bool comboExists; + if (gui.snapshot == "<All>") + { + comboExists = true; + } + else + { + for (MemoryID & entity : entities) + { + if (workingMemory().getEntity(entity).hasSnapshot(gui.timeMap.at(gui.snapshot))) + { + comboExists = true; + } + } + } + if(comboExists) + { + root.add(Label("Instances"), Pos{row,0}); + row++; + root.add(gui.tab.selectAll, Pos{row, 0}); + row++; + root.add(gui.tab.deselectAll, Pos{row, 0}); + row++; + gui.tab.checkInstances.clear(); + std::vector<MemoryID> snapshots; + for (MemoryID & entity : entities) + { + if(gui.snapshot == "<All>") + { + workingMemory().getEntity(entity).forEachSnapshot([&snapshots](auto & snapshot) + { + snapshots.push_back(snapshot.id()); + }); + } + else + { + if (workingMemory().getEntity(entity).hasSnapshot(gui.timeMap.at(gui.snapshot))) + { + snapshots.push_back(workingMemory().getEntity(entity).getSnapshot(gui.timeMap.at(gui.snapshot)).id()); + } + } + } + + for (MemoryID & snapshot : snapshots) + { + workingMemory().getSnapshot(snapshot).forEachInstance([this, &row, &root](const auto & instance) + { + root.add(Label(instance.id().str()) , Pos{row, 0}); + gui.tab.checkInstances[instance.id().str()] = CheckBox(); + if(gui.candidatesToDraw.at(instance.id().str())) + { + gui.tab.checkInstances.at(instance.id().str()).setValue(true); + } + root.add(gui.tab.checkInstances[instance.id().str()], Pos{row, 1}); + row++; + }); + } + } + else + { + root.add(Label("Instances"), Pos{row,0}); + row++; + root.add(gui.tab.selectAll, Pos{row, 0}); + row++; + root.add(gui.tab.deselectAll, Pos{row, 0}); + row++; + gui.tab.checkInstances.clear(); + } + } + else + { + gui.tab.checkInstances.clear(); + + std::vector<std::string> options = {"<None>"}; + + gui.tab.selectSnapshot.setOptions(options); + + root.add(Label("Snapshot"), Pos{row, 0}); + row++; + root.add(gui.tab.selectSnapshot, Pos{row, 0}); + row++; + root.add(gui.tab.selectAll, Pos{row, 0}); + row++; + root.add(gui.tab.deselectAll, Pos{row, 0}); + row++; + } + } + else + { + gui.tab.checkInstances.clear(); + + std::vector<std::string> options = {"<None>"}; + + gui.tab.selectEntity.setOptions(options); + + root.add(Label("Entity"), Pos{row,0}); + row++; + root.add(gui.tab.selectEntity, Pos{row, 0}); + row++; + + gui.tab.selectSnapshot.setOptions(options); + + root.add(Label("Snapshot"), Pos{row, 0}); + row++; + root.add(gui.tab.selectSnapshot, Pos{row, 0}); + row++; + root.add(gui.tab.selectAll, Pos{row, 0}); + row++; + root.add(gui.tab.deselectAll, Pos{row, 0}); + row++; + } + } + else { + gui.tab.checkInstances.clear(); + + std::vector<std::string> options = {"<None>"}; + gui.tab.selectProvider.setOptions(options); + + root.add(Label("Provider"), Pos{row, 0}); + row++; + root.add(gui.tab.selectProvider, Pos{row, 0}); + row++; + + gui.tab.selectEntity.setOptions(options); + + root.add(Label("Entity"), Pos{row,0}); + row++; + root.add(gui.tab.selectEntity, Pos{row, 0}); + row++; + + gui.tab.selectSnapshot.setOptions(options); + + root.add(Label("Snapshot"), Pos{row, 0}); + row++; + root.add(gui.tab.selectSnapshot, Pos{row, 0}); + row++; + root.add(gui.tab.selectAll, Pos{row, 0}); + row++; + root.add(gui.tab.deselectAll, Pos{row, 0}); + row++; + + } + } + + + RemoteGui_createTab(getName(), root, &gui.tab); + gui.tab.rebuild = false; } void GraspMemory::RemoteGui_update() { -// if (tab.rebuild.exchange(false)) + armarx::grasping::GraspCandidateVisu visu; + +// if (gui.tab.selectCoreSegment.hasValueChanged()) // { -//// createRemoteGuiTab(); +// gui.coreSegment = gui.tab.selectCoreSegment.getValue(); +// gui.coreSegIndex = gui.tab.selectCoreSegment.getIndex(); +// gui.providerIndex = 0; +// gui.entityIndex = 0; +// gui.snapshotIndex = 0; +// gui.tab.rebuild = true; // } + + if (gui.tab.selectProvider.hasValueChanged()) + { + gui.provider = gui.tab.selectProvider.getValue(); + gui.providerIndex = gui.tab.selectProvider.getIndex(); + gui.entityIndex = 0; + gui.snapshotIndex = 0; + gui.tab.rebuild = true; + } + + if (gui.tab.selectEntity.hasValueChanged()) + { + gui.entity = gui.tab.selectEntity.getValue(); + gui.entityIndex = gui.tab.selectEntity.getIndex(); + gui.snapshotIndex = 0; + gui.tab.rebuild = true; + } + + if (gui.tab.selectSnapshot.hasValueChanged()) + { + gui.snapshot = gui.tab.selectSnapshot.getValue(); + gui.snapshotIndex = gui.tab.selectSnapshot.getIndex(); + gui.tab.rebuild = true; + } + + + if(gui.tab.selectAll.wasClicked()) + { + for(auto & element : gui.tab.checkInstances) + { + element.second.setValue(true); + } + } + + if(gui.tab.deselectAll.wasClicked()) + { + for(auto & element : gui.tab.checkInstances) + { + element.second.setValue(false); + } + } + + for(auto & element : gui.tab.checkInstances) + { + gui.candidatesToDraw.at(element.first) = element.second.getValue(); + } + + std::map<std::string, viz::Layer> reachableLayers; + std::map<std::string, viz::Layer> unreachableLayers; + + for(auto & element : gui.candidatesToDraw ) + { + std::string entityName = armem::MemoryID::fromString(element.first).entityName; + + if (reachableLayers.find(entityName) == reachableLayers.end()) + { + reachableLayers[entityName] = arviz.layer(entityName + "_reachable"); + unreachableLayers[entityName] = arviz.layer(entityName + "_unreachable"); + } + + //draw candidates + if (element.second) + { + armarx::grasping::GraspCandidate candidate; + + armarx::grasping::arondto::GraspCandidate aronTransform; + + armem::wm::EntityInstance instance = workingMemory().getInstance(armem::MemoryID::fromString(element.first)); + + aronTransform.fromAron(workingMemory().getInstance(armem::MemoryID::fromString(element.first)).data()); + + fromAron(aronTransform, candidate); + + visu.visualize(element.first, candidate, reachableLayers.at(entityName), unreachableLayers.at(entityName)); + } + } + + std::vector<viz::Layer> layers; + for(auto & [entityName, layer] : reachableLayers) + { + layers.push_back(layer); + layers.push_back(unreachableLayers.at(entityName)); + } + arviz.commit(layers); + + if (gui.tab.rebuild) + { + createRemoteGuiTab(); + } } } diff --git a/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.h b/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.h index ea7dceaddd3af6671ac35b151e3bc803222d65ca..867bf0a62575f2affd51e1842d6631ceb0204e8e 100644 --- a/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.h +++ b/source/RobotAPI/components/armem/server/GraspMemory/GraspMemory.h @@ -70,9 +70,34 @@ namespace armarx::armem::server::grasp { std::atomic_bool rebuild = false; - RemoteGui::Client::GroupBox memoryGroup; + // RemoteGui::Client::ComboBox selectCoreSegment; + RemoteGui::Client::ComboBox selectProvider; + RemoteGui::Client::ComboBox selectEntity; + RemoteGui::Client::ComboBox selectSnapshot; + RemoteGui::Client::Button selectAll; + RemoteGui::Client::Button deselectAll; + std::map<std::string, RemoteGui::Client::CheckBox> checkInstances; }; - RemoteGuiTab tab; + + + struct GuiInfo + { + // int coreSegIndex = 0; + int providerIndex = 0; + int entityIndex = 0; + int snapshotIndex = 0; + + RemoteGuiTab tab; + + std::string coreSegment = "GraspCandidate"; + std::string provider = ""; + std::string entity = ""; + std::string snapshot = ""; + + std::map<std::string, bool> candidatesToDraw; + std::map<std::string, Time> timeMap; + }; + GuiInfo gui; }; } diff --git a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp index c605df2efece207f0a0b11337dd96c61ca534470..1d9c4ac147ddc2407952ed6db08c8fd4cfcc279b 100644 --- a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp +++ b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp @@ -156,6 +156,62 @@ namespace armarx::armem::server::obj { } + armem::actions::GetActionsOutputSeq ObjectMemory::getActions( + const armem::actions::GetActionsInputSeq& inputs) + { + using namespace armem::actions; + GetActionsOutputSeq outputs; + for (const auto& input : inputs) + { + auto memoryID = fromIce<armem::MemoryID>(input.id); + if (armem::contains(armem::MemoryID("Object", "Class"), memoryID) + and memoryID.hasEntityName() and not memoryID.hasGap()) + { + Menu menu { + Action{"vis", "Visualize object"}, + }; + // For strange C++ reasons, this works, but emplace_back doesn't. + outputs.push_back({ menu.toIce() }); + } + } + + return outputs; + } + + armem::actions::ExecuteActionOutputSeq ObjectMemory::executeActions( + const armem::actions::ExecuteActionInputSeq& inputs) + { + using namespace armem::actions; + ExecuteActionOutputSeq outputs; + + for (const auto& input : inputs) + { + auto memoryID = fromIce<armem::MemoryID>(input.id); + if (input.actionPath == ActionPath{"vis"}) + { + if (armem::contains(armem::MemoryID("Object", "Class"), memoryID) + and memoryID.hasEntityName() and not memoryID.hasGap()) + { + classSegment.visualizeClass(memoryID); + outputs.emplace_back(true, ""); + } + else + { + std::stringstream sstream; + sstream << "MemoryID " << memoryID + << " does not refer to a valid object class."; + outputs.emplace_back(false, sstream.str()); + } + } + else + { + std::stringstream sstream; + sstream << "Action path " << input.actionPath << " is not defined."; + outputs.emplace_back(false, sstream.str()); + } + } + return outputs; + } void ObjectMemory::createRemoteGuiTab() { diff --git a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h index 91217c90cc0dcb5be302663a0b472cac3327f3c0..82f39db591bd56537fa280e0dd10a56fa9806ca3 100644 --- a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h +++ b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h @@ -93,6 +93,14 @@ namespace armarx::armem::server::obj void onDisconnectComponent() override; void onExitComponent() override; + // Without this, ObjectMemory draws the original + // methods from ObjectMemoryInterface and complains + // that an overload is being hidden. + using ReadWritePluginUser::getActions; + using ReadWritePluginUser::executeActions; + + armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq& inputs) override; + armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq& inputs) override; // Remote GUI void createRemoteGuiTab(); diff --git a/source/RobotAPI/interface/CMakeLists.txt b/source/RobotAPI/interface/CMakeLists.txt index 1d5bd4be73c9215365c746f875731042e4a54f48..36442cf0bdaa4238a8a832fe39f6c244569fe830 100644 --- a/source/RobotAPI/interface/CMakeLists.txt +++ b/source/RobotAPI/interface/CMakeLists.txt @@ -107,6 +107,7 @@ set(SLICE_FILES armem.ice + armem/actions.ice armem/io.ice armem/commit.ice armem/memory.ice @@ -116,6 +117,7 @@ set(SLICE_FILES armem/client/MemoryListenerInterface.ice armem/server.ice + armem/server/ActionsInterface.ice armem/server/StoringMemoryInterface.ice armem/server/LoadingMemoryInterface.ice armem/server/MemoryInterface.ice diff --git a/source/RobotAPI/interface/armem.ice b/source/RobotAPI/interface/armem.ice index 79c54360ec8cdea2189d6f748cbadc4544379130..f7621099de52b5a2f15b3f0f6367ec5c19471fb3 100644 --- a/source/RobotAPI/interface/armem.ice +++ b/source/RobotAPI/interface/armem.ice @@ -1,5 +1,6 @@ #pragma once +#include <RobotAPI/interface/armem/actions.ice> #include <RobotAPI/interface/armem/client.ice> #include <RobotAPI/interface/armem/commit.ice> #include <RobotAPI/interface/armem/memory.ice> diff --git a/source/RobotAPI/interface/armem/actions.ice b/source/RobotAPI/interface/armem/actions.ice new file mode 100644 index 0000000000000000000000000000000000000000..5bb06fc4c9ec815dba87fef49a31bc13498b66fc --- /dev/null +++ b/source/RobotAPI/interface/armem/actions.ice @@ -0,0 +1,74 @@ +#pragma once + +#include <RobotAPI/interface/armem/memory.ice> + +module armarx +{ + module armem + { + module actions + { + module data + { + + class MenuEntry + { + /// Item in the created action path. + string id; + /// Human-readable text displayed to the user. + string text; + }; + sequence<MenuEntry> MenuEntrySeq; + + /** + * Clickable / executable action. + */ + class Action extends MenuEntry + { + }; + + /** + * Expandable sub-menu, but cannot be executed itself. + */ + class SubMenu extends MenuEntry + { + MenuEntrySeq entries; + }; + + + class Menu + { + MenuEntrySeq entries; + }; + + sequence<string> ActionPath; + + struct GetActionsInput + { + armem::data::MemoryID id; + }; + sequence<GetActionsInput> GetActionsInputSeq; + + struct GetActionsOutput + { + Menu menu; + }; + sequence<GetActionsOutput> GetActionsOutputSeq; + + struct ExecuteActionInput + { + armem::data::MemoryID id; + ActionPath actionPath; + }; + sequence<ExecuteActionInput> ExecuteActionInputSeq; + + struct ExecuteActionOutput + { + bool success = false; + string errorMessage; + }; + sequence<ExecuteActionOutput> ExecuteActionOutputSeq; + } + } + } +} diff --git a/source/RobotAPI/interface/armem/mns/MemoryNameSystemInterface.ice b/source/RobotAPI/interface/armem/mns/MemoryNameSystemInterface.ice index 95b6a9fa6b03d8f7d4644d2538ecfd64d5258229..2d6b220a2050001960fcc4b564d54b3526f4ed44 100644 --- a/source/RobotAPI/interface/armem/mns/MemoryNameSystemInterface.ice +++ b/source/RobotAPI/interface/armem/mns/MemoryNameSystemInterface.ice @@ -1,5 +1,6 @@ #pragma once +#include <RobotAPI/interface/armem/server/ActionsInterface.ice> #include <RobotAPI/interface/armem/server/ReadingMemoryInterface.ice> #include <RobotAPI/interface/armem/server/WritingMemoryInterface.ice> @@ -15,11 +16,14 @@ module armarx /** * A memory server can implement the reading and/or writing * memory interface. They should be handled individually. + * Additionally, it can implement an actions interface, + * which is currently the case for all reading and writing memories. */ struct MemoryServerInterfaces { server::ReadingMemoryInterface* reading; server::WritingMemoryInterface* writing; + server::actions::ActionsInterface* actions; }; dictionary<string, MemoryServerInterfaces> MemoryServerInterfacesMap; diff --git a/source/RobotAPI/interface/armem/server/ActionsInterface.ice b/source/RobotAPI/interface/armem/server/ActionsInterface.ice new file mode 100644 index 0000000000000000000000000000000000000000..8fa6754f7e1e0c961c4a5ff9c9bb317dc9fc3aef --- /dev/null +++ b/source/RobotAPI/interface/armem/server/ActionsInterface.ice @@ -0,0 +1,21 @@ +#pragma once + +#include <RobotAPI/interface/armem/actions.ice> + +module armarx +{ + module armem + { + module server + { + module actions + { + interface ActionsInterface + { + actions::data::GetActionsOutputSeq getActions(actions::data::GetActionsInputSeq inputs); + actions::data::ExecuteActionOutputSeq executeActions(actions::data::ExecuteActionInputSeq inputs); + }; + } + } + } +} diff --git a/source/RobotAPI/interface/armem/server/MemoryInterface.ice b/source/RobotAPI/interface/armem/server/MemoryInterface.ice index ec83f439d8a91e8e57fe4c1767a8ba27bfab7c5d..05b2ea8c54207f6bcfa7a2d708f8fd7e59df416b 100644 --- a/source/RobotAPI/interface/armem/server/MemoryInterface.ice +++ b/source/RobotAPI/interface/armem/server/MemoryInterface.ice @@ -5,6 +5,7 @@ #include <RobotAPI/interface/armem/server/ReadingMemoryInterface.ice> #include <RobotAPI/interface/armem/server/WritingMemoryInterface.ice> +#include <RobotAPI/interface/armem/server/ActionsInterface.ice> #include <RobotAPI/interface/armem/client/MemoryListenerInterface.ice> @@ -26,6 +27,7 @@ module armarx interface MemoryInterface extends WorkingMemoryInterface, LongTermMemoryInterface, + actions::ActionsInterface, client::MemoryListenerInterface { }; diff --git a/source/RobotAPI/interface/units/GraspCandidateProviderInterface.ice b/source/RobotAPI/interface/units/GraspCandidateProviderInterface.ice index 702e9667ab047b4144008175e9bd4a02020d34f6..6ae5638174ec0293a4a3ab73fea68e869a397cf9 100644 --- a/source/RobotAPI/interface/units/GraspCandidateProviderInterface.ice +++ b/source/RobotAPI/interface/units/GraspCandidateProviderInterface.ice @@ -144,7 +144,6 @@ module armarx StringVariantBaseMap currentConfig; }; - dictionary<string, ProviderInfo> InfoMap; diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp index a534e7df3a8aec94c251a0dc7600d87c8ef76b44..11e583902b8fbb0963e8f4c1b00746de01356f1b 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp +++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp @@ -347,6 +347,31 @@ namespace armarx } return nullptr; } + + objpose::data::ObjectPose* objpose::findObjectPoseByID(data::ObjectPoseSeq& objectPoses, const armarx::data::ObjectID& id) + { + for (objpose::data::ObjectPose& pose : objectPoses) + { + if (pose.objectID == id) + { + return &pose; + } + } + return nullptr; + } + + const objpose::data::ObjectPose* objpose::findObjectPoseByID(const data::ObjectPoseSeq& objectPoses, const armarx::data::ObjectID& id) + { + for (const objpose::data::ObjectPose& pose : objectPoses) + { + if (pose.objectID == id) + { + return &pose; + } + } + return nullptr; + } + } diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h index 74b1b3e6dd6632ca7c11dcc060f5934a24725262..66c87f22f5f369c154043e83a72146f0d5bbea54 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h +++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h @@ -150,5 +150,7 @@ namespace armarx::objpose ObjectPose* findObjectPoseByID(ObjectPoseSeq& objectPoses, const ObjectID& id); const ObjectPose* findObjectPoseByID(const ObjectPoseSeq& objectPoses, const ObjectID& id); + data::ObjectPose* findObjectPoseByID(data::ObjectPoseSeq& objectPoses, const armarx::data::ObjectID& id); + const data::ObjectPose* findObjectPoseByID(const data::ObjectPoseSeq& objectPoses, const armarx::data::ObjectID& id); } diff --git a/source/RobotAPI/libraries/GraspingUtility/CMakeLists.txt b/source/RobotAPI/libraries/GraspingUtility/CMakeLists.txt index 7ec81af63d843443f25008f486a87880aeb05c65..1ae8862c277d13836d015473953c605db92c9349 100644 --- a/source/RobotAPI/libraries/GraspingUtility/CMakeLists.txt +++ b/source/RobotAPI/libraries/GraspingUtility/CMakeLists.txt @@ -10,6 +10,7 @@ armarx_add_library( RobotAPI::ArmarXObjects RobotAPI::ArViz RobotAPI::armem_objects + SOURCES box_to_grasp_candidates.cpp grasp_candidate_drawer.cpp GraspCandidateHelper.cpp @@ -17,14 +18,18 @@ armarx_add_library( aron_conversions.cpp GraspCandidateWriter.cpp GraspCandidateReader.cpp + GraspCandidateVisu.cpp + HEADERS box_to_grasp_candidates.h box_to_grasp_candidates.ipp grasp_candidate_drawer.h GraspCandidateHelper.h BimanualGraspCandidateHelper.h + aron_conversions.h GraspCandidateWriter.h GraspCandidateReader.h + GraspCandidateVisu.h ) armarx_enable_aron_file_generation_for_target( TARGET_NAME diff --git a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.cpp b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.cpp index 4e742c96f69db5d5abeabe58f7cbc69684e740bb..8f875542bce9f7a5ed3fe91bc697eb3cd76e1edf 100644 --- a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.cpp +++ b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.cpp @@ -14,14 +14,16 @@ namespace armarx::armem { } - void GraspCandidateReader::connect() + + void GraspCandidateReader::connect(bool use) { // Wait for the memory to become available and add it as dependency. ARMARX_IMPORTANT << "GraspCandidateReader: Waiting for memory '" << properties.memoryName << "' ..."; try { - memoryReader = memoryNameSystem.useReader(properties.memoryName); + memoryReader = use ? memoryNameSystem.useReader(properties.memoryName) + : memoryNameSystem.getReader(MemoryID(properties.memoryName)); ARMARX_IMPORTANT << "GraspCandidateReader: Connected to memory '" << properties.memoryName; } @@ -30,13 +32,9 @@ namespace armarx::armem ARMARX_ERROR << e.what(); return; } - - } - - armarx::grasping::GraspCandidate asGraspCandidate(const armem::wm::EntityInstance& instance) { armarx::grasping::GraspCandidate candidate; @@ -64,12 +62,26 @@ namespace armarx::armem grasping::GraspCandidatePtr GraspCandidateReader::queryGraspCandidateInstanceByID(const armem::MemoryID& id) const + { + auto dict = queryGraspCandidateInstancesByID({id}); + if (auto it = dict.find(id.str()); it != dict.end()) + { + return it->second; + } + else + { + return nullptr; + } + } + + + grasping::GraspCandidateDict GraspCandidateReader::queryGraspCandidateInstancesByID(const std::vector<MemoryID>& ids) const { armem::client::query::Builder qb; ARMARX_DEBUG << "Query for memory name: " << properties.memoryName; - qb.singleEntitySnapshot(id.getEntitySnapshotID()); + qb.multipleEntitySnapshots(ids); const armem::client::QueryResult qResult = memoryReader.query(qb.buildQueryInput()); @@ -80,24 +92,19 @@ namespace armarx::armem throw armem::error::QueryFailed(properties.memoryName, qResult.errorMessage); } - - armarx::grasping::GraspCandidatePtr candidate; - - armem::wm::FunctionalVisitor visitor; - visitor.instanceConstFn = [id, &candidate](armem::wm::EntityInstance const & instance) + armarx::grasping::GraspCandidateDict candidates; + for (const MemoryID& id : ids) { - if (instance.id() == id) + if (const armem::wm::EntityInstance* instance = qResult.memory.findInstance(id)) { - candidate = new grasping::GraspCandidate(asGraspCandidate(instance)); + candidates[id.str()] = new grasping::GraspCandidate(asGraspCandidate(*instance)); } - return true; - }; - - visitor.applyTo(qResult.memory); + } - return candidate; + return candidates; } + grasping::BimanualGraspCandidatePtr GraspCandidateReader::queryBimanualGraspCandidateInstanceByID( const armem::MemoryID& id) const { @@ -134,7 +141,7 @@ namespace armarx::armem return candidate; } - std::map<std::string, grasping::GraspCandidatePtr> GraspCandidateReader::queryLatestGraspCandidateEntity( + grasping::GraspCandidateDict GraspCandidateReader::queryLatestGraspCandidateEntity( const std::string& provider, const std::string& entity) const { armem::client::query::Builder qb; @@ -172,7 +179,7 @@ namespace armarx::armem return getBimanualGraspCandidatesFromResultSet(qResult); } - std::map<std::string, grasping::GraspCandidatePtr> GraspCandidateReader::queryLatestGraspCandidates( + grasping::GraspCandidateDict GraspCandidateReader::queryLatestGraspCandidates( const std::string& provider) const { @@ -249,7 +256,7 @@ namespace armarx::armem def->optional(properties.memoryName, prefix + "MemoryName"); } - std::map<std::string, grasping::GraspCandidatePtr> GraspCandidateReader::getGraspCandidatesFromResultSet( + grasping::GraspCandidateDict GraspCandidateReader::getGraspCandidatesFromResultSet( const armem::client::QueryResult& qResult) const { if (!qResult.success) diff --git a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.h b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.h index 0a1604afdb9ca58c93ce52628cee61e7ee647dcc..5de5c25e335e5544170f18f838df2e5e23d213fa 100644 --- a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.h +++ b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateReader.h @@ -1,9 +1,12 @@ #pragma once +#include <vector> + #include <RobotAPI/libraries/armem/client/Reader.h> #include <RobotAPI/libraries/armem/client.h> #include <RobotAPI/interface/units/GraspCandidateProviderInterface.h> + namespace armarx::armem { @@ -12,19 +15,20 @@ namespace armarx::armem public: GraspCandidateReader(armem::client::MemoryNameSystem& memoryNameSystem); - void connect(); + void connect(bool use = true); grasping::GraspCandidatePtr queryGraspCandidateInstanceByID(armem::MemoryID const& id) const; + grasping::GraspCandidateDict queryGraspCandidateInstancesByID(std::vector<armem::MemoryID> const& ids) const; grasping::BimanualGraspCandidatePtr queryBimanualGraspCandidateInstanceByID(armem::MemoryID const& id) const; - std::map<std::string, grasping::GraspCandidatePtr> queryLatestGraspCandidateEntity( + grasping::GraspCandidateDict queryLatestGraspCandidateEntity( std::string const& provider, std::string const& entity) const; std::map<std::string, grasping::BimanualGraspCandidatePtr> queryLatestBimanualGraspCandidateEntity( std::string const& provider, std::string const& entity) const; - std::map<std::string, grasping::GraspCandidatePtr> queryLatestGraspCandidates( + grasping::GraspCandidateDict queryLatestGraspCandidates( std::string const& provider = "") const; std::map<std::string, grasping::BimanualGraspCandidatePtr> queryLatestBimanualGraspCandidates( @@ -37,7 +41,7 @@ namespace armarx::armem private: - std::map<std::string, grasping::GraspCandidatePtr> getGraspCandidatesFromResultSet( + grasping::GraspCandidateDict getGraspCandidatesFromResultSet( armem::client::QueryResult const& qResult) const; std::map<std::string, grasping::BimanualGraspCandidatePtr> getBimanualGraspCandidatesFromResultSet( diff --git a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.cpp b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1c5e3bc4781d504f8f467ce2b8584d5358b09a06 --- /dev/null +++ b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.cpp @@ -0,0 +1,89 @@ +#include "GraspCandidateVisu.h" + +#include <RobotAPI/libraries/core/Pose.h> +#include <RobotAPI/components/ArViz/Client/elements/RobotHand.h> + + +namespace armarx::grasping +{ + GraspCandidateVisu::GraspCandidateVisu() + { + } + + + void GraspCandidateVisu::visualize( + const grasping::GraspCandidateDict& candidates, + viz::Client& arviz) + { + viz::Layer graspsLayer = arviz.layer("Grasps"); + viz::Layer graspsNonReachableLayer = arviz.layer("Grasps Non-Reachable"); + + visualize(candidates, graspsLayer, graspsNonReachableLayer); + arviz.commit({graspsLayer, graspsNonReachableLayer}); + } + + + void GraspCandidateVisu::visualize(const std::string &id, const GraspCandidate &candidate, + viz::Layer& layerReachable, + viz::Layer& layerNonReachable) + { + int alpha = alpha_default; + if (auto it = alphasByKey.find(id); it != alphasByKey.end()) + { + alpha = it->second; + } + + viz::Robot hand = visualize("Grasp_" + id, candidate, alpha); + + if (candidate.reachabilityInfo->reachable) + { + layerReachable.add(hand); + } + else + { + layerNonReachable.add(hand); + } + } + + void GraspCandidateVisu::visualize(const grasping::GraspCandidateDict& candidates, + viz::Layer& layerReachable, + viz::Layer& layerNonReachable) + { + for (auto& [id, candidate] : candidates) + { + visualize(id, *candidate, layerReachable, layerNonReachable); + } + } + + + viz::Robot GraspCandidateVisu::visualize( + const std::string& name, + const GraspCandidate& candidate) + { + return visualize(name, candidate, alpha_default); + } + + + viz::Robot + GraspCandidateVisu::visualize( + const std::string& name, + const grasping::GraspCandidate& candidate, + int alpha) + { + bool isReachable = candidate.reachabilityInfo->reachable; + viz::Color color = isReachable ? viz::Color::green() : viz::Color::red(); + color.a = alpha; + + Eigen::Matrix4f tcp2handRoot = fromIce(candidate.tcpPoseInHandRoot).inverse(); + Eigen::Matrix4f graspPose = PosePtr::dynamicCast(candidate.graspPose)->toEigen(); + std::string modelFile = "rt/robotmodel/Armar6-SH/Armar6-" + candidate.side + "Hand-v3.xml"; + + viz::Robot hand = viz::RobotHand(name) + .file("Armar6RT", modelFile) + .pose(fromIce(candidate.robotPose) * graspPose * tcp2handRoot) + .overrideColor(color); + + return hand; + } +} + diff --git a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.h b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.h new file mode 100644 index 0000000000000000000000000000000000000000..8eb298e4183d0daef8d004d4d3fc3cacc8cd30e7 --- /dev/null +++ b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateVisu.h @@ -0,0 +1,48 @@ +#pragma once + +#include <RobotAPI/components/ArViz/Client/Layer.h> +#include <RobotAPI/components/ArViz/Client/Client.h> + +#include <RobotAPI/interface/units/GraspCandidateProviderInterface.h> + + +namespace armarx::grasping +{ + class GraspCandidateVisu + { + public: + GraspCandidateVisu(); + + + void visualize( + const grasping::GraspCandidateDict& candidates, + viz::Client& arviz); + + void visualize( + const grasping::GraspCandidateDict& candidates, + viz::Layer& layerReachable, + viz::Layer& layerNonReachable); + + void visualize( + const std::string& id, + const grasping::GraspCandidate& candidate, + viz::Layer& layerReachable, + viz::Layer& layerNonReachable); + + viz::Robot visualize( + const std::string& name, + const grasping::GraspCandidate& candidate); + viz::Robot visualize( + const std::string& name, + const grasping::GraspCandidate& candidate, + int alpha); + + + public: + + int alpha_default = 255; + std::map<std::string, int> alphasByKey = {}; + + + }; +} diff --git a/source/RobotAPI/libraries/armem/CMakeLists.txt b/source/RobotAPI/libraries/armem/CMakeLists.txt index ba231f670b3342011fd228da611d2e9d1b8d87cc..5feda749b6fc3eb3e1f601812b778e71b20c099b 100644 --- a/source/RobotAPI/libraries/armem/CMakeLists.txt +++ b/source/RobotAPI/libraries/armem/CMakeLists.txt @@ -12,6 +12,7 @@ set(LIBS ) set(LIB_FILES + core/actions.cpp core/Commit.cpp core/MemoryID.cpp core/MemoryID_operators.cpp @@ -77,6 +78,7 @@ set(LIB_HEADERS core.h core/forward_declarations.h + core/actions.h core/Commit.h core/DataMode.h core/MemoryID.h diff --git a/source/RobotAPI/libraries/armem/client/MemoryNameSystem.h b/source/RobotAPI/libraries/armem/client/MemoryNameSystem.h index 01bf3aecfdc821d41f2902efd8329fa9e509152a..9422d575cd9b32d58c64110d6d3ef83a8deabe56 100644 --- a/source/RobotAPI/libraries/armem/client/MemoryNameSystem.h +++ b/source/RobotAPI/libraries/armem/client/MemoryNameSystem.h @@ -181,7 +181,15 @@ namespace armarx::armem::client std::map<MemoryID, wm::EntityInstance> resolveEntityInstances(const std::vector<MemoryID>& ids); - + /** + * @brief Resolve the given memory server for the given memory ID. + * + * @param memoryID The memory ID. + * @return The memory server proxy. + * + * @throw `error::CouldNotResolveMemoryServer` If the memory name could not be resolved. + */ + mns::dto::MemoryServerInterfaces resolveServer(const MemoryID& memoryID); // Registration - only for memory servers @@ -217,16 +225,6 @@ namespace armarx::armem::client private: - /** - * @brief Resolve the given memory server for the given memory ID. - * - * @param memoryID The memory ID. - * @return The memory server proxy. - * - * @throw `error::CouldNotResolveMemoryServer` If the memory name could not be resolved. - */ - mns::dto::MemoryServerInterfaces resolveServer(const MemoryID& memoryID); - /** * @brief Wait for the given memory server. * diff --git a/source/RobotAPI/libraries/armem/client/query/Builder.cpp b/source/RobotAPI/libraries/armem/client/query/Builder.cpp index 8637055e5b850494f15c10255b29de94785cec08..2a31828717f34906875bdbb8a17c62719b675331 100644 --- a/source/RobotAPI/libraries/armem/client/query/Builder.cpp +++ b/source/RobotAPI/libraries/armem/client/query/Builder.cpp @@ -120,4 +120,12 @@ namespace armarx::armem::client::query .snapshots().atTime(snapshotID.timestamp); } + void Builder::multipleEntitySnapshots(const std::vector<MemoryID>& snapshotIDs) + { + for (const MemoryID& snapshotID : snapshotIDs) + { + singleEntitySnapshot(snapshotID); + } + } + } diff --git a/source/RobotAPI/libraries/armem/client/query/Builder.h b/source/RobotAPI/libraries/armem/client/query/Builder.h index 3b52aa4b1945d6a2bef62932403013670843f94a..90d3e211ac9e776f061695420392b765bac1aac2 100644 --- a/source/RobotAPI/libraries/armem/client/query/Builder.h +++ b/source/RobotAPI/libraries/armem/client/query/Builder.h @@ -61,6 +61,7 @@ namespace armarx::armem::client::query void latestEntitySnapshot(const MemoryID& entityID); void singleEntitySnapshot(const MemoryID& snapshotID); + void multipleEntitySnapshots(const std::vector<MemoryID>& snapshotIDs); QueryInput buildQueryInput() const; diff --git a/source/RobotAPI/libraries/armem/core/actions.cpp b/source/RobotAPI/libraries/armem/core/actions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9eb28f7187d9ac51295dfa3be351f14fc97ef2cd --- /dev/null +++ b/source/RobotAPI/libraries/armem/core/actions.cpp @@ -0,0 +1,112 @@ +#include "actions.h" + +#include <RobotAPI/interface/armem/actions.h> + +namespace armarx::armem::actions +{ + + MenuEntry::MenuEntry(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries) : + id(id), text(text), entries(entries) + { + } + + MenuEntry& + MenuEntry::add(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries) + { + return this->entries.emplace_back(id, text, entries); + } + + data::MenuEntryPtr + MenuEntry::toIce() const // NOLINT (recursive call chain) + { + if (entries.empty()) + { + return new data::Action{this->id, this->text}; + } + + data::SubMenuPtr ice = new data::SubMenu{this->id, this->text, {}}; + for (const MenuEntry& entry : entries) + { + ice->entries.push_back(entry.toIce()); + } + return ice; + } + + MenuEntry + MenuEntry::fromIce(const data::MenuEntryPtr& ice) // NOLINT (recursive call chain) + { + if (ice->ice_isA(data::SubMenu::ice_staticId())) + { + auto ptr = IceUtil::Handle<data::SubMenu>::dynamicCast(ice); + MenuEntry subMenu{ptr->id, ptr->text, {}}; + for (const auto& entry : ptr->entries) + { + subMenu.entries.push_back(MenuEntry::fromIce(entry)); + } + return subMenu; + } + + return MenuEntry{ice->id, ice->text, {}}; + } + + Action::Action(const std::string& id, const std::string& text) : MenuEntry(id, text) + { + } + + SubMenu::SubMenu(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries) : + MenuEntry(id, text, entries) + { + } + + MenuEntry& + SubMenu::add(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries) + { + return this->entries.emplace_back(id, text, entries); + } + + + Menu::Menu(std::initializer_list<MenuEntry> entries) : entries(entries) + { + } + + Menu::Menu(const std::vector<MenuEntry>& entries) : entries(entries) + { + } + + + MenuEntry& + Menu::add(const std::string& id, const std::string& text, const std::vector<MenuEntry>& entries) + { + return this->entries.emplace_back(id, text, entries); + } + + data::MenuPtr + Menu::toIce() const + { + data::MenuPtr ice{new data::Menu}; + for (const MenuEntry& entry : entries) + { + ice->entries.push_back(entry.toIce()); + } + return ice; + } + + Menu + Menu::fromIce(const data::MenuPtr& ice) + { + Menu menu; + for (const auto& entry : ice->entries) + { + menu.entries.push_back(MenuEntry::fromIce(entry)); + } + return menu; + } +} // namespace armarx::armem::actions diff --git a/source/RobotAPI/libraries/armem/core/actions.h b/source/RobotAPI/libraries/armem/core/actions.h new file mode 100644 index 0000000000000000000000000000000000000000..2d96dbbac3f5b8f614772a881b504793d78b40a9 --- /dev/null +++ b/source/RobotAPI/libraries/armem/core/actions.h @@ -0,0 +1,77 @@ +#pragma once + +#include <vector> + +#include <RobotAPI/interface/armem/actions.h> + +namespace armarx::armem::actions +{ + // Business objects to make working with the Armem + // action interface easier. To see an example usage, + // check the ExampleMemory. + + struct MenuEntry + { + MenuEntry(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries = {}); + + MenuEntry& add(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries = {}); + + data::MenuEntryPtr toIce() const; + + static MenuEntry fromIce(const data::MenuEntryPtr& ice); + + public: + std::string id; + std::string text; + std::vector<MenuEntry> entries; + }; + + + struct Action : public MenuEntry + { + Action(const std::string& id, const std::string& text); + }; + + struct SubMenu : public MenuEntry + { + SubMenu(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries = {}); + + MenuEntry& add(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries = {}); + }; + + + class Menu + { + + public: + Menu(std::initializer_list<MenuEntry> entries); + + Menu(const std::vector<MenuEntry>& entries = {}); + + MenuEntry& add(const std::string& id, + const std::string& text, + const std::vector<MenuEntry>& entries = {}); + + data::MenuPtr toIce() const; + + static Menu fromIce(const data::MenuPtr& ice); + + public: + std::vector<MenuEntry> entries; + }; + + using data::ExecuteActionInputSeq; + using data::ExecuteActionOutputSeq; + using data::GetActionsInputSeq; + using data::GetActionsOutputSeq; + using data::ActionPath; + +} // namespace armarx::armem::actions diff --git a/source/RobotAPI/libraries/armem/core/ice_conversions.cpp b/source/RobotAPI/libraries/armem/core/ice_conversions.cpp index 29f57ca33e82e865e52c49f08241581a632e0fca..20df9176bb562ab9025c001451d6fef23793d504 100644 --- a/source/RobotAPI/libraries/armem/core/ice_conversions.cpp +++ b/source/RobotAPI/libraries/armem/core/ice_conversions.cpp @@ -138,5 +138,14 @@ namespace armarx update.timeArrived = timeArrived; } + void armem::fromIce(const actions::data::MenuPtr& ice, actions::Menu& menu) + { + menu = actions::Menu::fromIce(ice); + } + + void armem::toIce(actions::data::MenuPtr& ice, const actions::Menu& menu) + { + ice = menu.toIce(); + } } diff --git a/source/RobotAPI/libraries/armem/core/ice_conversions.h b/source/RobotAPI/libraries/armem/core/ice_conversions.h index e8f104fe04ebcb721b0ffbf4b70b18ada0af234c..a8ef35e7938f07f14a81b8ecb731e2470b395c97 100644 --- a/source/RobotAPI/libraries/armem/core/ice_conversions.h +++ b/source/RobotAPI/libraries/armem/core/ice_conversions.h @@ -1,10 +1,12 @@ #pragma once +#include <RobotAPI/interface/armem/actions.h> #include <RobotAPI/interface/armem/commit.h> #include <RobotAPI/interface/armem/memory.h> #include "ice_conversions_templates.h" +#include "actions.h" #include "Commit.h" #include "MemoryID.h" #include "Time.h" @@ -43,5 +45,8 @@ namespace armarx::armem void fromIce(const data::Commit& ice, Commit& commit, Time timeArrived); void fromIce(const data::EntityUpdate& ice, EntityUpdate& update, Time timeArrived); + + void fromIce(const actions::data::MenuPtr& ice, actions::Menu& menu); + void toIce(actions::data::MenuPtr& ice, const actions::Menu& menu); } diff --git a/source/RobotAPI/libraries/armem/mns/MemoryNameSystem.cpp b/source/RobotAPI/libraries/armem/mns/MemoryNameSystem.cpp index a0c606a990fd4635a5fa2cded1c0e5be8f7566a8..566cf7e0bcbdba70e8e16ca5c527120628c89f32 100644 --- a/source/RobotAPI/libraries/armem/mns/MemoryNameSystem.cpp +++ b/source/RobotAPI/libraries/armem/mns/MemoryNameSystem.cpp @@ -59,7 +59,7 @@ namespace armarx::armem::mns int row = 0; grid.add(Label("Memory Name"), {row, 0}) .add(Label("Component Name"), {row, 1}) - .add(Label("R/W"), {row, 2}) + .add(Label("R/W/A"), {row, 2}) .add(Label("Registration Time"), {row, 3}) ; row++; @@ -79,6 +79,11 @@ namespace armarx::armem::mns componentName = info.server.writing->ice_getIdentity().name; mode += "W"; } + if (info.server.actions) + { + componentName = info.server.actions->ice_getIdentity().name; + mode += "A"; + } int col = 0; grid.add(Label(name), {row, col}); diff --git a/source/RobotAPI/libraries/armem/mns/Registry.cpp b/source/RobotAPI/libraries/armem/mns/Registry.cpp index 260cfcd818a426ae7b4f18c6cde5a4180a459f0b..b8b2d02c807750aad2cb36c28cd55013b2a0b496 100644 --- a/source/RobotAPI/libraries/armem/mns/Registry.cpp +++ b/source/RobotAPI/libraries/armem/mns/Registry.cpp @@ -1,6 +1,7 @@ #include "Registry.h" #include <ArmarXCore/core/logging/Logging.h> +#include <RobotAPI/interface/armem/server/ActionsInterface.h> namespace armarx::armem::mns @@ -158,5 +159,11 @@ namespace armarx::armem::mns return server.writing; } + + server::actions::ActionsInterfacePrx getActionsInterface(const dto::MemoryServerInterfaces& server) + { + return server.actions; + } + } diff --git a/source/RobotAPI/libraries/armem/mns/Registry.h b/source/RobotAPI/libraries/armem/mns/Registry.h index c1965bdac24fcdf943ad2b7bc945698358dd3f0c..7fcd1c04b95b5b0fe938ae4fdc383ada5bd147d5 100644 --- a/source/RobotAPI/libraries/armem/mns/Registry.h +++ b/source/RobotAPI/libraries/armem/mns/Registry.h @@ -3,6 +3,7 @@ #include <RobotAPI/libraries/armem/core/Time.h> #include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h> +#include <RobotAPI/interface/armem/server/ActionsInterface.h> #include <RobotAPI/interface/armem/server/ReadingMemoryInterface.h> #include <RobotAPI/interface/armem/server/WritingMemoryInterface.h> @@ -68,4 +69,5 @@ namespace armarx::armem::mns server::ReadingMemoryInterfacePrx getReadingInterface(const dto::MemoryServerInterfaces& server); server::WritingMemoryInterfacePrx getWritingInterface(const dto::MemoryServerInterfaces& server); + server::actions::ActionsInterfacePrx getActionsInterface(const dto::MemoryServerInterfaces& server); } diff --git a/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp b/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp index 5c8387726e6eb65307e66173bcb7e7a0984cdd6b..7ae0bc4bac3f4e3166f47fd4777d44d68f41a86f 100644 --- a/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp +++ b/source/RobotAPI/libraries/armem/server/plugins/Plugin.cpp @@ -77,6 +77,7 @@ namespace armarx::armem::server::plugins mns::dto::MemoryServerInterfaces server; server.reading = ReadingMemoryInterfacePrx::uncheckedCast(parent.getProxy()); server.writing = WritingMemoryInterfacePrx::uncheckedCast(parent.getProxy()); + server.actions = actions::ActionsInterfacePrx::uncheckedCast(parent.getProxy()); mns::dto::RegisterServerResult result; try diff --git a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp index 285c65e0b02895bbb59663a461db1249330daf13..41e09c13be1cbc68f0281023596be9c275f16dc5 100644 --- a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp +++ b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp @@ -91,4 +91,30 @@ namespace armarx::armem::server::plugins return plugin->longtermMemory; } -} + // ACTIONS + armem::actions::GetActionsOutputSeq ReadWritePluginUser::getActions( + const armem::actions::GetActionsInputSeq& inputs, const ::Ice::Current& /*unused*/) + { + return getActions(inputs); + } + + armem::actions::GetActionsOutputSeq ReadWritePluginUser::getActions( + const armem::actions::GetActionsInputSeq& inputs) + { + (void) inputs; + return {}; + } + + armem::actions::ExecuteActionOutputSeq ReadWritePluginUser::executeActions( + const armem::actions::ExecuteActionInputSeq& inputs, const ::Ice::Current& /*unused*/) + { + return executeActions(inputs); + } + + armem::actions::ExecuteActionOutputSeq ReadWritePluginUser::executeActions( + const armem::actions::ExecuteActionInputSeq& inputs) + { + return {}; + } + +} // namespace armarx::armem::server::plugins diff --git a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h index 8d3dad4c98d3f11592b250ce11a10fc96dc8cd81..83e761281bf064a111a90efe15a9148689daa2ff 100644 --- a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h +++ b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h @@ -3,6 +3,7 @@ #include <RobotAPI/libraries/armem/server/forward_declarations.h> #include <RobotAPI/libraries/armem/client/plugins/ListeningPluginUser.h> +#include <RobotAPI/libraries/armem/core/actions.h> #include <RobotAPI/interface/armem/server/MemoryInterface.h> #include <ArmarXCore/core/ManagedIceObject.h> @@ -47,6 +48,12 @@ namespace armarx::armem::server::plugins // StoringInterface interface virtual data::StoreResult store(const data::StoreInput&, const Ice::Current& = Ice::emptyCurrent) override; + // ActionsInterface interface + virtual armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq& inputs); + virtual armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq& inputs); + + virtual armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq& inputs, const ::Ice::Current&) override; + virtual armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq& inputs, const ::Ice::Current&) override; public: diff --git a/source/RobotAPI/libraries/armem_gui/ActionsMenuBuilder.cpp b/source/RobotAPI/libraries/armem_gui/ActionsMenuBuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8bf2121617d084537b456fb741fad34fcbf8e26 --- /dev/null +++ b/source/RobotAPI/libraries/armem_gui/ActionsMenuBuilder.cpp @@ -0,0 +1,52 @@ +#include "ActionsMenuBuilder.h" + +#include <RobotAPI/libraries/armem/core/wm/ice_conversions.h> + + +namespace armarx::armem::gui +{ + ActionsMenuBuilder::ActionsMenuBuilder( + MemoryID memoryID, + QWidget* parent, + std::function<void(const MemoryID&, const actions::ActionPath&)> func) : + memoryID(std::move(memoryID)), parent(parent), func(std::move(func)) + { + } + + QMenu* + ActionsMenuBuilder::buildActionsMenu(actions::data::GetActionsOutput& actionsOutput) + { + auto* menu = new QMenu("Actions", parent); + + for (const auto& entry : actions::Menu::fromIce(actionsOutput.menu).entries) + { + addMenuEntry(menu, {}, entry); + } + + return menu; + } + + void + ActionsMenuBuilder::addMenuEntry( // NOLINT (clangd complains about the recursion here) + QMenu* menu, + actions::ActionPath path, + const actions::MenuEntry& entry) + { + path.push_back(entry.id); + if (entry.entries.empty()) + { + menu->addAction(QString::fromStdString(entry.text), + parent, + [this, path]() { func(memoryID, path); }); + } + else + { + QMenu* qSubmenu = menu->addMenu(QString::fromStdString(entry.text)); + for (const auto& subEntry : entry.entries) + { + addMenuEntry(qSubmenu, path, subEntry); + } + } + } + +} // namespace armarx::armem::gui diff --git a/source/RobotAPI/libraries/armem_gui/ActionsMenuBuilder.h b/source/RobotAPI/libraries/armem_gui/ActionsMenuBuilder.h new file mode 100644 index 0000000000000000000000000000000000000000..4d1261f65cecf4cfd4afd77867ad4e2b9af7ebac --- /dev/null +++ b/source/RobotAPI/libraries/armem_gui/ActionsMenuBuilder.h @@ -0,0 +1,28 @@ +#pragma once + +#include <QMenu> + +#include <RobotAPI/interface/armem/actions.h> +#include <RobotAPI/libraries/armem/core/actions.h> +#include <RobotAPI/libraries/armem/core/MemoryID.h> + +namespace armarx::armem::gui +{ + class ActionsMenuBuilder + { + public: + ActionsMenuBuilder(MemoryID memoryID, + QWidget* parent, + std::function<void(const MemoryID&, const actions::ActionPath&)> func); + + QMenu* buildActionsMenu(actions::data::GetActionsOutput& actionsOutput); + + private: + void + addMenuEntry(QMenu* menu, actions::ActionPath path, const actions::MenuEntry& entry); + + MemoryID memoryID; + QWidget* parent; + std::function<void(const MemoryID&, const actions::ActionPath&)> func; + }; +} // namespace armarx::armem::gui diff --git a/source/RobotAPI/libraries/armem_gui/CMakeLists.txt b/source/RobotAPI/libraries/armem_gui/CMakeLists.txt index 5a774f498c13b372fa2b188bba2d6b1508fc3094..b92bcbfbce86f7bc65bee7a9f186c73d14d0c9b5 100644 --- a/source/RobotAPI/libraries/armem_gui/CMakeLists.txt +++ b/source/RobotAPI/libraries/armem_gui/CMakeLists.txt @@ -21,6 +21,7 @@ set(SOURCES gui_utils.cpp lifecycle.cpp + ActionsMenuBuilder.cpp MemoryViewer.cpp PeriodicUpdateWidget.cpp @@ -60,6 +61,7 @@ set(HEADERS gui_utils.h lifecycle.h + ActionsMenuBuilder.h MemoryViewer.h PeriodicUpdateWidget.h TreeWidgetBuilder.h diff --git a/source/RobotAPI/libraries/armem_gui/MemoryViewer.cpp b/source/RobotAPI/libraries/armem_gui/MemoryViewer.cpp index ddb264ec45b8b36ea0867e453c68a93a136e88bb..318329d8eb16a348338132ad276e819e631b1cdb 100644 --- a/source/RobotAPI/libraries/armem_gui/MemoryViewer.cpp +++ b/source/RobotAPI/libraries/armem_gui/MemoryViewer.cpp @@ -1,45 +1,47 @@ #include "MemoryViewer.h" -#include <RobotAPI/libraries/aron/converter/json/NLohmannJSONConverter.h> - -#include <RobotAPI/libraries/armem/core/wm/ice_conversions.h> -#include <RobotAPI/libraries/armem_gui/gui_utils.h> - -#include <RobotAPI/libraries/armem/server/query_proc/wm/wm.h> -#include <RobotAPI/libraries/armem/server/query_proc/ltm/disk/ltm.h> - -#include <ArmarXGui/libraries/SimpleConfigDialog/SimpleConfigDialog.h> - -#include <ArmarXCore/core/ManagedIceObject.h> -#include <ArmarXCore/core/time/TimeUtil.h> -#include <ArmarXCore/observers/variant/Variant.h> - -#include <SimoxUtility/algorithm/get_map_keys_values.h> - #include <QApplication> #include <QBoxLayout> #include <QCheckBox> #include <QClipboard> #include <QDialog> #include <QGroupBox> -#include <QMenu> #include <QLabel> #include <QLayout> +#include <QMenu> #include <QSettings> #include <QTimer> -#include <iomanip> +#include <Ice/Exception.h> + +#include <SimoxUtility/algorithm/get_map_keys_values.h> + +#include <ArmarXCore/core/ManagedIceObject.h> +#include <ArmarXCore/core/time/TimeUtil.h> +#include <ArmarXCore/observers/variant/Variant.h> + +#include <ArmarXGui/libraries/SimpleConfigDialog/SimpleConfigDialog.h> + +#include <RobotAPI/libraries/aron/converter/json/NLohmannJSONConverter.h> +#include <RobotAPI/interface/armem/actions.h> +#include <RobotAPI/interface/armem/memory.h> +#include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h> +#include <RobotAPI/libraries/armem/core/wm/ice_conversions.h> +#include <RobotAPI/libraries/armem/server/query_proc/ltm/disk/ltm.h> +#include <RobotAPI/libraries/armem/server/query_proc/wm/wm.h> +#include <RobotAPI/libraries/armem_gui/ActionsMenuBuilder.h> +#include <RobotAPI/libraries/armem_gui/gui_utils.h> namespace armarx::armem::gui { - MemoryViewer::MemoryViewer( - QBoxLayout* updateWidgetLayout, - QGroupBox* memoryGroupBox, QLayout* memoryGroupBoxParentLayout, - QGroupBox* instanceGroupBox, QLayout* instanceGroupBoxParentLayout, - QBoxLayout* diskControlWidgetLayout, - QLabel* statusLabel - ) + MemoryViewer::MemoryViewer(QBoxLayout* updateWidgetLayout, + QGroupBox* memoryGroupBox, + QLayout* memoryGroupBoxParentLayout, + QGroupBox* instanceGroupBox, + QLayout* instanceGroupBoxParentLayout, + QBoxLayout* diskControlWidgetLayout, + QLabel* statusLabel) { Logging::setTag("MemoryViewer"); @@ -47,19 +49,17 @@ namespace armarx::armem::gui this->statusLabel->clear(); statusLabel->setContextMenuPolicy(Qt::CustomContextMenu); - connect(statusLabel, &QLabel::customContextMenuRequested, [statusLabel](const QPoint & pos) - { - QMenu menu(statusLabel); - menu.addAction("Copy to clipboard", [statusLabel]() - { - QApplication::clipboard()->setText(statusLabel->text()); - }); - menu.addAction("Clear status", [statusLabel]() - { - statusLabel->clear(); - }); - menu.exec(statusLabel->mapToGlobal(pos)); - }); + connect(statusLabel, + &QLabel::customContextMenuRequested, + [statusLabel](const QPoint& pos) + { + QMenu menu(statusLabel); + menu.addAction("Copy to clipboard", + [statusLabel]() + { QApplication::clipboard()->setText(statusLabel->text()); }); + menu.addAction("Clear status", [statusLabel]() { statusLabel->clear(); }); + menu.exec(statusLabel->mapToGlobal(pos)); + }); // Update timer @@ -68,7 +68,7 @@ namespace armarx::armem::gui updateWidgetLayout->insertWidget(0, updateWidget); processQueryResultTimer = new QTimer(this); - processQueryResultTimer->setInterval(1000 / 60); // Keep this stable. + processQueryResultTimer->setInterval(1000 / 60); // Keep this stable. processQueryResultTimer->start(); // Memory View @@ -91,8 +91,14 @@ namespace armarx::armem::gui } // Connections - connect(diskControl, &armem::gui::disk::ControlWidget::requestedStoreOnDisk, this, &This::storeOnDisk); - connect(diskControl, &armem::gui::disk::ControlWidget::requestedLoadFromDisk, this, &This::loadFromDisk); + connect(diskControl, + &armem::gui::disk::ControlWidget::requestedStoreOnDisk, + this, + &This::storeOnDisk); + connect(diskControl, + &armem::gui::disk::ControlWidget::requestedLoadFromDisk, + this, + &This::loadFromDisk); connect(this, &This::connected, this, &This::startQueries); connect(updateWidget, &armem::gui::PeriodicUpdateWidget::update, this, &This::startQueries); @@ -102,21 +108,43 @@ namespace armarx::armem::gui connect(memoryGroup->commitWidget(), &armem::gui::CommitWidget::commit, this, &This::commit); connect(this, &This::memoryDataChanged, this, &This::updateMemoryTree); - connect(memoryGroup->tree(), &armem::gui::MemoryTreeWidget::selectedItemChanged, this, &This::updateInstanceTree); - - connect(memoryGroup->tree(), &armem::gui::MemoryTreeWidget::updated, this, &This::memoryTreeUpdated); - connect(instanceGroup, &armem::gui::InstanceGroupBox::viewUpdated, this, &This::instanceTreeUpdated); - connect(instanceGroup->view, &armem::gui::InstanceView::memoryIdResolutionRequested, this, &This::resolveMemoryID); + connect(memoryGroup->tree(), + &armem::gui::MemoryTreeWidget::selectedItemChanged, + this, + &This::updateInstanceTree); + + connect(memoryGroup->tree(), + &armem::gui::MemoryTreeWidget::updated, + this, + &This::memoryTreeUpdated); + connect(memoryGroup->tree(), + &armem::gui::MemoryTreeWidget::actionsMenuRequested, + this, + &This::showActionsMenu); + connect(instanceGroup, + &armem::gui::InstanceGroupBox::viewUpdated, + this, + &This::instanceTreeUpdated); + connect(instanceGroup->view, + &armem::gui::InstanceView::memoryIdResolutionRequested, + this, + &This::resolveMemoryID); + connect(instanceGroup->view, + &armem::gui::InstanceView::actionsMenuRequested, + this, + &This::showActionsMenu); } - void MemoryViewer::setLogTag(const std::string& _tag) // Leading _ silences a warning + void + MemoryViewer::setLogTag(const std::string& _tag) // Leading _ silences a warning { Logging::setTag(_tag); } - void MemoryViewer::onInit(ManagedIceObject& component) + void + MemoryViewer::onInit(ManagedIceObject& component) { if (mnsName.size() > 0) { @@ -131,7 +159,8 @@ namespace armarx::armem::gui } - void MemoryViewer::onConnect(ManagedIceObject& component) + void + MemoryViewer::onConnect(ManagedIceObject& component) { if (not mnsName.empty()) { @@ -154,7 +183,8 @@ namespace armarx::armem::gui } - void MemoryViewer::onDisconnect(ManagedIceObject&) + void + MemoryViewer::onDisconnect(ManagedIceObject&) { updateWidget->stopTimer(); @@ -162,14 +192,15 @@ namespace armarx::armem::gui } - const armem::wm::Memory* MemoryViewer::getSingleMemoryData(const std::string& memoryName) + const armem::wm::Memory* + MemoryViewer::getSingleMemoryData(const std::string& memoryName) { auto it = memoryData.find(memoryName); if (it == memoryData.end()) { std::stringstream ss; - ss << "Memory name '" << memoryName << "' is unknown. Known are: " - << simox::alg::get_keys(memoryData); + ss << "Memory name '" << memoryName + << "' is unknown. Known are: " << simox::alg::get_keys(memoryData); statusLabel->setText(QString::fromStdString(ss.str())); return nullptr; } @@ -185,7 +216,8 @@ namespace armarx::armem::gui } - void MemoryViewer::storeInLTM() + void + MemoryViewer::storeInLTM() { TIMING_START(MemoryStore); @@ -244,7 +276,8 @@ namespace armarx::armem::gui } - void MemoryViewer::storeOnDisk(QString directory) + void + MemoryViewer::storeOnDisk(QString directory) { TIMING_START(MemoryExport) @@ -256,10 +289,12 @@ namespace armarx::armem::gui } - void MemoryViewer::loadFromDisk(QString directory) + void + MemoryViewer::loadFromDisk(QString directory) { std::string status; - std::map<std::string, wm::Memory> data = diskControl->loadFromDisk(directory, memoryGroup->queryWidget()->queryInput(), &status); + std::map<std::string, wm::Memory> data = + diskControl->loadFromDisk(directory, memoryGroup->queryWidget()->queryInput(), &status); memoryWriters = mns.getAllWriters(true); for (auto& [name, memory] : data) @@ -281,16 +316,19 @@ namespace armarx::armem::gui } - void MemoryViewer::startQueries() + void + MemoryViewer::startQueries() { memoryReaders = mns.getAllReaders(true); startDueQueries(runningQueries, memoryReaders); } - void MemoryViewer::processQueryResults() + void + MemoryViewer::processQueryResults() { - const std::map<std::string, client::QueryResult> results = collectQueryResults(runningQueries, memoryReaders); + const std::map<std::string, client::QueryResult> results = + collectQueryResults(runningQueries, memoryReaders); int errorCount = 0; applyQueryResults(results, &errorCount); @@ -300,7 +338,8 @@ namespace armarx::armem::gui } - void MemoryViewer::updateStatusLabel(int errorCount) + void + MemoryViewer::updateStatusLabel(int errorCount) { // Code to output status label information if (statusLabel and errorCount > 0) @@ -317,9 +356,9 @@ namespace armarx::armem::gui } - void MemoryViewer::startDueQueries( - std::map<std::string, Ice::AsyncResultPtr>& queries, - const std::map<std::string, armem::client::Reader>& readers) + void + MemoryViewer::startDueQueries(std::map<std::string, Ice::AsyncResultPtr>& queries, + const std::map<std::string, armem::client::Reader>& readers) { armem::client::QueryInput input = memoryGroup->queryWidget()->queryInput(); @@ -334,9 +373,8 @@ namespace armarx::armem::gui std::map<std::string, client::QueryResult> - MemoryViewer::collectQueryResults( - std::map<std::string, Ice::AsyncResultPtr>& queries, - const std::map<std::string, client::Reader>& readers) + MemoryViewer::collectQueryResults(std::map<std::string, Ice::AsyncResultPtr>& queries, + const std::map<std::string, client::Reader>& readers) { TIMING_START(tCollectQueryResults) @@ -352,7 +390,8 @@ namespace armarx::armem::gui { try { - results[name] = client::QueryResult::fromIce(jt->second.memoryPrx->end_query(queryPromise)); + results[name] = client::QueryResult::fromIce( + jt->second.memoryPrx->end_query(queryPromise)); } catch (const Ice::ConnectionRefusedException&) { @@ -366,27 +405,29 @@ namespace armarx::armem::gui } else { - ++it; // Uncompleted => Keep. + ++it; // Uncompleted => Keep. } } TIMING_END_STREAM(tCollectQueryResults, ARMARX_VERBOSE) if (debugObserver) { - debugObserver->begin_setDebugChannel(Logging::tag.tagName, - { - { "t Collect Query Results [ms]", new Variant(tCollectQueryResults.toMilliSecondsDouble()) }, - { "# Collected Query Results", new Variant(static_cast<int>(results.size())) }, - }); + debugObserver->begin_setDebugChannel( + Logging::tag.tagName, + { + {"t Collect Query Results [ms]", + new Variant(tCollectQueryResults.toMilliSecondsDouble())}, + {"# Collected Query Results", new Variant(static_cast<int>(results.size()))}, + }); } return results; } - void MemoryViewer::applyQueryResults( - const std::map<std::string, client::QueryResult>& results, - int* outErrorCount) + void + MemoryViewer::applyQueryResults(const std::map<std::string, client::QueryResult>& results, + int* outErrorCount) { TIMING_START(tProcessQueryResults) for (const auto& [name, result] : results) @@ -397,7 +438,8 @@ namespace armarx::armem::gui } else { - ARMARX_WARNING << "Querying memory server '" << name << "' produced an error: \n" << result.errorMessage; + ARMARX_WARNING << "Querying memory server '" << name << "' produced an error: \n" + << result.errorMessage; if (outErrorCount) { outErrorCount++; @@ -422,16 +464,19 @@ namespace armarx::armem::gui TIMING_END_STREAM(tProcessQueryResults, ARMARX_VERBOSE) if (debugObserver) { - debugObserver->begin_setDebugChannel(Logging::tag.tagName, - { - { "t Process Query Results [ms]", new Variant(tProcessQueryResults.toMilliSecondsDouble()) }, - { "# Processed Query Results", new Variant(static_cast<int>(results.size())) }, - }); + debugObserver->begin_setDebugChannel( + Logging::tag.tagName, + { + {"t Process Query Results [ms]", + new Variant(tProcessQueryResults.toMilliSecondsDouble())}, + {"# Processed Query Results", new Variant(static_cast<int>(results.size()))}, + }); } } - void MemoryViewer::updateInstanceTree(const armem::MemoryID& selectedID) + void + MemoryViewer::updateInstanceTree(const armem::MemoryID& selectedID) { const armem::wm::Memory* data = getSingleMemoryData(selectedID.memoryName); if (data) @@ -481,11 +526,12 @@ namespace armarx::armem::gui } - void MemoryViewer::resolveMemoryID(const MemoryID& id) + void + MemoryViewer::resolveMemoryID(const MemoryID& id) { // ARMARX_IMPORTANT << "Resolving memory ID: " << id; - auto handleError = [this](const std::string & msg) + auto handleError = [this](const std::string& msg) { statusLabel->setText(QString::fromStdString(msg)); ARMARX_WARNING << msg; @@ -548,7 +594,8 @@ namespace armarx::armem::gui } - void MemoryViewer::updateMemoryTree() + void + MemoryViewer::updateMemoryTree() { std::map<std::string, const armem::wm::Memory*> convMap; for (auto& [name, data] : memoryData) @@ -564,7 +611,9 @@ namespace armarx::armem::gui { try { - debugObserver->setDebugDatafield(Logging::tag.tagName, "GUI Update [ms]", new Variant(GuiUpdate.toMilliSecondsDouble())); + debugObserver->setDebugDatafield(Logging::tag.tagName, + "GUI Update [ms]", + new Variant(GuiUpdate.toMilliSecondsDouble())); } catch (const Ice::Exception&) { @@ -574,28 +623,140 @@ namespace armarx::armem::gui } + void + MemoryViewer::showActionsMenu(const MemoryID& memoryID, + QWidget* parent, + const QPoint& pos, + QMenu* menu) + { + // Called if we have to stop because of an error. + auto showMenu = [menu, pos]() + { + if (menu) + menu->exec(pos); + }; + + mns::dto::MemoryServerInterfaces prx; + try + { + prx = mns.resolveServer(memoryID); + } + catch (const error::CouldNotResolveMemoryServer& e) + { + statusLabel->setText( + QString::fromStdString( + e.makeMsg(memoryID, "Could not resolve memory server."))); + showMenu(); + return; + } + + if (!prx.actions) + { + std::stringstream ss; + ss << "Memory server " << memoryID + << " does not support actions or is offline."; + statusLabel->setText(QString::fromStdString(ss.str())); + showMenu(); + return; + } + + actions::GetActionsOutputSeq result; + try + { + result = prx.actions->getActions({{toIce<data::MemoryID>(memoryID)}}); + } + catch (const Ice::LocalException& e) + { + std::stringstream ss; + ss << "Could not get actions for " << memoryID << "."; + statusLabel->setText(QString::fromStdString(ss.str())); + showMenu(); + return; + } + + if (result.size() == 0) + { + showMenu(); + return; + } + auto builder = ActionsMenuBuilder( + memoryID, + parent, + [this, prx](const MemoryID& memoryID, const actions::ActionPath& path) + { + actions::data::ExecuteActionOutputSeq result; + try + { + result = prx.actions->executeActions( + {{toIce<armem::data::MemoryID>(memoryID), path}}); + } + catch (const Ice::LocalException& e) + { + std::stringstream ss; + ss << "Failed to execute action: " << e.what(); + statusLabel->setText(QString::fromStdString(ss.str())); + } + + for (const auto& [success, errorMessage] : result) + { + if (not success) + { + std::stringstream ss; + ss << "Failed to execute action: " << errorMessage; + statusLabel->setText(QString::fromStdString(ss.str())); + ARMARX_WARNING << ss.str(); + } + } + }); + + QMenu* actionsMenu = builder.buildActionsMenu(result[0]); + if (menu == nullptr) + { + actionsMenu->exec(pos); + } + else + { + menu->addMenu(actionsMenu); + menu->exec(pos); + } + } + + const static std::string CONFIG_KEY_MEMORY = "MemoryViewer.MemoryNameSystem"; const static std::string CONFIG_KEY_DEBUG_OBSERVER = "MemoryViewer.DebugObserverName"; - void MemoryViewer::loadSettings(QSettings* settings) + void + MemoryViewer::loadSettings(QSettings* settings) { - mnsName = settings->value(QString::fromStdString(CONFIG_KEY_MEMORY), "MemoryNameSystem").toString().toStdString(); - debugObserverName = settings->value(QString::fromStdString(CONFIG_KEY_DEBUG_OBSERVER), "DebugObserver").toString().toStdString(); + mnsName = settings->value(QString::fromStdString(CONFIG_KEY_MEMORY), "MemoryNameSystem") + .toString() + .toStdString(); + debugObserverName = + settings->value(QString::fromStdString(CONFIG_KEY_DEBUG_OBSERVER), "DebugObserver") + .toString() + .toStdString(); } - void MemoryViewer::saveSettings(QSettings* settings) + void + MemoryViewer::saveSettings(QSettings* settings) { - settings->setValue(QString::fromStdString(CONFIG_KEY_MEMORY), QString::fromStdString(mnsName)); - settings->setValue(QString::fromStdString(CONFIG_KEY_DEBUG_OBSERVER), QString::fromStdString(debugObserverName)); + settings->setValue(QString::fromStdString(CONFIG_KEY_MEMORY), + QString::fromStdString(mnsName)); + settings->setValue(QString::fromStdString(CONFIG_KEY_DEBUG_OBSERVER), + QString::fromStdString(debugObserverName)); } - void MemoryViewer::writeConfigDialog(SimpleConfigDialog* dialog) + void + MemoryViewer::writeConfigDialog(SimpleConfigDialog* dialog) { - dialog->addProxyFinder<armarx::armem::mns::MemoryNameSystemInterfacePrx>({CONFIG_KEY_MEMORY, "MemoryNameSystem", "MemoryNameSystem"}); - dialog->addProxyFinder<armarx::DebugObserverInterfacePrx>({CONFIG_KEY_DEBUG_OBSERVER, "Debug Observer", "DebugObserver"}); + dialog->addProxyFinder<armarx::armem::mns::MemoryNameSystemInterfacePrx>( + {CONFIG_KEY_MEMORY, "MemoryNameSystem", "MemoryNameSystem"}); + dialog->addProxyFinder<armarx::DebugObserverInterfacePrx>( + {CONFIG_KEY_DEBUG_OBSERVER, "Debug Observer", "DebugObserver"}); } - void MemoryViewer::readConfigDialog(SimpleConfigDialog* dialog) + void + MemoryViewer::readConfigDialog(SimpleConfigDialog* dialog) { mnsName = dialog->getProxyName(CONFIG_KEY_MEMORY); if (mnsName.empty()) @@ -605,4 +766,4 @@ namespace armarx::armem::gui debugObserverName = dialog->getProxyName(CONFIG_KEY_DEBUG_OBSERVER); } -} +} // namespace armarx::armem::gui diff --git a/source/RobotAPI/libraries/armem_gui/MemoryViewer.h b/source/RobotAPI/libraries/armem_gui/MemoryViewer.h index b4ea2897516a60e01ff13709ab41d7e5df413964..51cda4cb797911c3afcd1afaf43657b145b9a140 100644 --- a/source/RobotAPI/libraries/armem_gui/MemoryViewer.h +++ b/source/RobotAPI/libraries/armem_gui/MemoryViewer.h @@ -7,6 +7,7 @@ #include <ArmarXCore/interface/observers/ObserverInterface.h> #include <ArmarXCore/core/logging/Logging.h> +#include <RobotAPI/interface/armem/actions.h> #include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h> #include <RobotAPI/libraries/armem/client/MemoryNameSystem.h> #include <RobotAPI/libraries/armem/client/Reader.h> @@ -71,6 +72,9 @@ namespace armarx::armem::gui void resolveMemoryID(const MemoryID& id); + void showActionsMenu(const MemoryID& memoryID, QWidget* parent, + const QPoint& pos, QMenu* menu); + // Disk Control void storeOnDisk(QString directory); void loadFromDisk(QString directory); @@ -98,7 +102,6 @@ namespace armarx::armem::gui void updateMemoryTree(); - signals: void memoryDataChanged(); @@ -130,7 +133,6 @@ namespace armarx::armem::gui int* outErrorCount = nullptr); - public: std::string mnsName; diff --git a/source/RobotAPI/libraries/armem_gui/instance/InstanceView.cpp b/source/RobotAPI/libraries/armem_gui/instance/InstanceView.cpp index 6cdbcab8351b7597bd5dc0dab8e1ed07408f5ad9..3bbb2ee55a87be3216386a0f847b40829b4bb785 100644 --- a/source/RobotAPI/libraries/armem_gui/instance/InstanceView.cpp +++ b/source/RobotAPI/libraries/armem_gui/instance/InstanceView.cpp @@ -253,13 +253,13 @@ namespace armarx::armem::gui::instance return; // No item => no context menu. } - QMenu menu(this); + QMenu* menu = new QMenu(this); if (item == this->treeItemInstanceID && currentInstance.has_value()) { if (QAction* action = makeActionCopyMemoryID(currentInstance->id())) { - menu.addAction(action); + menu->addAction(action); } } else if (item == this->treeItemData && currentInstance.has_value()) @@ -267,11 +267,10 @@ namespace armarx::armem::gui::instance auto actions = makeActionsCopyDataToClipboard(); for (const auto& action : actions) { - if (!action) + if (action) { - continue; + menu->addAction(action); } - menu.addAction(action); } } else if (item->parent() == nullptr) @@ -288,7 +287,7 @@ namespace armarx::armem::gui::instance if (const std::optional<aron::Path> path = getElementPath(item)) { QAction* viewAction = new QAction("Show image"); - menu.addAction(viewAction); + menu->addAction(viewAction); connect(viewAction, &QAction::triggered, [this, path]() { this->showImageView(path.value()); @@ -330,11 +329,11 @@ namespace armarx::armem::gui::instance { if (QAction* action = makeActionCopyMemoryID(id.value())) { - menu.addAction(action); + menu->addAction(action); } if (QAction* action = makeActionResolveMemoryID(id.value())) { - menu.addAction(action); + menu->addAction(action); } } } @@ -346,17 +345,20 @@ namespace armarx::armem::gui::instance auto actions = makeActionsCopyDataToClipboard(elementPath.value()); for (const auto& action : actions) { - if (!action) + if (action) { - continue; + menu->addAction(action); } - menu.addAction(action); } } - if (menu.actions().size() > 0) + if (menu->actions().size() > 0) { - menu.exec(tree->mapToGlobal(pos)); + emit actionsMenuRequested(currentInstance->id(), this, tree->mapToGlobal(pos), menu); + } + else + { + emit actionsMenuRequested(currentInstance->id(), this, tree->mapToGlobal(pos), nullptr); } } diff --git a/source/RobotAPI/libraries/armem_gui/instance/InstanceView.h b/source/RobotAPI/libraries/armem_gui/instance/InstanceView.h index 289e67caff9749d8a3e8012ea2c2798507b255aa..982d277db1febb68070290d3c86a3f06c602b990 100644 --- a/source/RobotAPI/libraries/armem_gui/instance/InstanceView.h +++ b/source/RobotAPI/libraries/armem_gui/instance/InstanceView.h @@ -3,6 +3,7 @@ #include <optional> #include <deque> +#include <QMenu> #include <QWidget> #include <QGroupBox> @@ -55,6 +56,8 @@ namespace armarx::armem::gui::instance void updated(); void instanceSelected(const MemoryID& id); void memoryIdResolutionRequested(const MemoryID& id); + void actionsMenuRequested(const MemoryID& memoryID, QWidget* parent, + const QPoint& pos, QMenu* menu); private slots: diff --git a/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.cpp b/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.cpp index 0ece75cfb419df323a3269a7baacb8d3e4fbba6e..858c932bdba6825696a99de538bbdf02284c159c 100644 --- a/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.cpp +++ b/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.cpp @@ -3,10 +3,12 @@ #include <RobotAPI/libraries/armem_gui/instance/sanitize_typename.h> #include <RobotAPI/libraries/aron/core/type/variant/container/Object.h> +#include <RobotAPI/interface/armem/actions.h> #include <SimoxUtility/algorithm/string.h> #include <QHeaderView> +#include <QMenu> namespace armarx::armem::gui::memory @@ -45,6 +47,9 @@ namespace armarx::armem::gui::memory connect(this, &This::entitySelected, this, &This::itemSelected); connect(this, &This::snapshotSelected, this, &This::itemSelected); connect(this, &This::instanceSelected, this, &This::itemSelected); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &QTreeWidget::customContextMenuRequested, this, &This::prepareTreeContextMenu); } void TreeWidget::initBuilders() @@ -269,6 +274,18 @@ namespace armarx::armem::gui::memory (void) data, (void) dataItem; } + void TreeWidget::prepareTreeContextMenu(const QPoint& pos) + { + const QTreeWidgetItem* item = this->itemAt(pos); + if (item == nullptr) + { + return; + } + + MemoryID memoryID(item->data(int(Columns::ID), Qt::UserRole).toString().toStdString()); + emit actionsMenuRequested(memoryID, this, mapToGlobal(pos), nullptr); + } + template <class MemoryItemT> QTreeWidgetItem* TreeWidget::makeItem(const std::string& key, const MemoryItemT& memoryItem) { diff --git a/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.h b/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.h index 75f56c9ace248e58daf3f7ee3ac69baa12b0edfe..1ad975e5f29e741df801c55906d0ff364e607996 100644 --- a/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.h +++ b/source/RobotAPI/libraries/armem_gui/memory/TreeWidget.h @@ -44,6 +44,9 @@ namespace armarx::armem::gui::memory void snapshotSelected(const MemoryID& id); void instanceSelected(const MemoryID& id); + void actionsMenuRequested(const MemoryID& memoryID, QWidget* parent, + const QPoint& pos, QMenu* menu); + private slots: @@ -65,6 +68,7 @@ namespace armarx::armem::gui::memory void updateChildren(const armem::wm::EntitySnapshot& snapshot, QTreeWidgetItem* snapshotItem); void updateChildren(const armem::wm::EntityInstance& data, QTreeWidgetItem* parent); + void prepareTreeContextMenu(const QPoint& pos); template <class MemoryItemT> QTreeWidgetItem* makeItem(const std::string& key, const MemoryItemT& memoryItem); diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp b/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp index 96369565e92c7747a33f627e6b8f98ab20ff52c7..91a9196beec405f386854d9f45f02ef5cd5bf1e7 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp +++ b/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp @@ -630,7 +630,8 @@ namespace armarx::armem::server::obj::instance // Store attachment in new entity snapshot. { - armem::EntityUpdate update; + armem::Commit commit; + armem::EntityUpdate & update = commit.add(); update.entityID = objectEntity->id(); update.timeCreated = now; { @@ -639,7 +640,7 @@ namespace armarx::armem::server::obj::instance updated.pose.attachmentValid = true; update.instancesData = { updated.toAron() }; } - objectEntity->update(update); + iceMemory.commit(commit); } ARMARX_INFO << "Attached object " << objectID << " by provider '" << data.pose.providerName << "' " @@ -737,7 +738,8 @@ namespace armarx::armem::server::obj::instance Time now, bool commitAttachedPose) { - armem::EntityUpdate update; + armem::Commit commit; + armem::EntityUpdate & update = commit.add(); update.entityID = entity.id(); update.timeCreated = now; { @@ -762,7 +764,7 @@ namespace armarx::armem::server::obj::instance update.instancesData = { updated.toAron() }; } - entity.update(update); + iceMemory.commit(commit); }