diff --git a/CMakeLists.txt b/CMakeLists.txt index db26cae0d..93444c4c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -584,6 +584,9 @@ if(ENABLE_GUI) endif() set(CHOREONOID_QT_MAJOR_VERSION ${default_qt_major_version} CACHE STRING "Specify the major version of Qt; either 5 or 6.") + if(CHOREONOID_QT_MAJOR_VERSION GREATER_EQUAL 6 AND CMAKE_CXX_STANDARD LESS_EQUAL 14) + message(FATAL_ERROR "Qt6 requires C++17 or higher.") + endif() choreonoid_find_qt_package() set(CMAKE_AUTOMOC OFF) diff --git a/cmake/ChoreonoidConfig.cmake.in b/cmake/ChoreonoidConfig.cmake.in index 230a88e13..17932ae82 100644 --- a/cmake/ChoreonoidConfig.cmake.in +++ b/cmake/ChoreonoidConfig.cmake.in @@ -49,6 +49,7 @@ set(CHOREONOID_COMPILE_DEFINITIONS "@compile_definitions@") set(CHOREONOID_DEFAULT_FVISIBILITY_HIDDEN @CHOREONOID_DEFAULT_FVISIBILITY_HIDDEN@) set(CHOREONOID_INCLUDE_DIRS "@include_dirs@") set(CHOREONOID_LIBRARY_DIRS "@library_dirs@") +set(CHOREONOID_USE_PYTHON2 @CNOID_USE_PYTHON2@) if(@has_boost_libs_for_util_libs@) find_package(Boost @boost_version@ EXACT COMPONENTS @boost_components_for_util_libs@) diff --git a/include/cnoid/CameraConfigDialog b/include/cnoid/CameraConfigDialog new file mode 100644 index 000000000..d435473d8 --- /dev/null +++ b/include/cnoid/CameraConfigDialog @@ -0,0 +1 @@ +#include "src/Base/CameraConfigDialog.h" diff --git a/include/cnoid/CameraItem b/include/cnoid/CameraItem new file mode 100644 index 000000000..33cde2de6 --- /dev/null +++ b/include/cnoid/CameraItem @@ -0,0 +1 @@ +#include "src/Base/CameraItem.h" diff --git a/include/cnoid/CompilerWarningControlMacros b/include/cnoid/CompilerWarningControlMacros new file mode 100644 index 000000000..954a60415 --- /dev/null +++ b/include/cnoid/CompilerWarningControlMacros @@ -0,0 +1 @@ +#include "src/Util/CompilerWarningControlMacros.h" diff --git a/include/cnoid/GeneralSceneFileLoadDialog b/include/cnoid/GeneralSceneFileLoadDialog new file mode 100644 index 000000000..505d29bf1 --- /dev/null +++ b/include/cnoid/GeneralSceneFileLoadDialog @@ -0,0 +1 @@ +#include "src/Base/GeneralSceneFileLoadDialog.h" diff --git a/include/cnoid/MultiSeqItemCreationPanel b/include/cnoid/MultiSeqItemCreationPanel deleted file mode 100644 index d37719646..000000000 --- a/include/cnoid/MultiSeqItemCreationPanel +++ /dev/null @@ -1 +0,0 @@ -#include "src/Base/MultiSeqItemCreationPanel.h" diff --git a/include/cnoid/ParticleSystem b/include/cnoid/ParticleSystem new file mode 100644 index 000000000..829192efe --- /dev/null +++ b/include/cnoid/ParticleSystem @@ -0,0 +1 @@ +#include "src/SceneEffectsPlugin/ParticleSystem.h" diff --git a/include/cnoid/ProjectBackupManager b/include/cnoid/ProjectBackupManager new file mode 100644 index 000000000..b139bada0 --- /dev/null +++ b/include/cnoid/ProjectBackupManager @@ -0,0 +1 @@ +#include "src/Base/ProjectBackupManager.h" diff --git a/include/cnoid/RainSnowDevice b/include/cnoid/RainSnowDevice new file mode 100644 index 000000000..b62fb78ca --- /dev/null +++ b/include/cnoid/RainSnowDevice @@ -0,0 +1 @@ +#include "src/SceneEffectsPlugin/RainSnowDevice.h" diff --git a/include/cnoid/RenderableItemSceneExporter b/include/cnoid/RenderableItemSceneExporter new file mode 100644 index 000000000..a0aec882a --- /dev/null +++ b/include/cnoid/RenderableItemSceneExporter @@ -0,0 +1 @@ +#include "src/Base/RenderableItemSceneExporter.h" diff --git a/include/cnoid/SceneWidgetEvent b/include/cnoid/SceneWidgetEvent new file mode 100644 index 000000000..54543d385 --- /dev/null +++ b/include/cnoid/SceneWidgetEvent @@ -0,0 +1 @@ +#include "../choreonoid-nx/src/Base/SceneWidgetEvent.h" diff --git a/include/cnoid/SmokeDevice b/include/cnoid/SmokeDevice new file mode 100644 index 000000000..a197ed5e4 --- /dev/null +++ b/include/cnoid/SmokeDevice @@ -0,0 +1 @@ +#include "src/SceneEffectsPlugin/SmokeDevice.h" diff --git a/include/cnoid/WorldLogFileItem b/include/cnoid/WorldLogFileItem new file mode 100644 index 000000000..a5ead74b2 --- /dev/null +++ b/include/cnoid/WorldLogFileItem @@ -0,0 +1 @@ +#include "src/BodyPlugin/WorldLogFileItem.h" diff --git a/include/cnoid/ZmpDevice b/include/cnoid/ZmpDevice new file mode 100644 index 000000000..9d753bab7 --- /dev/null +++ b/include/cnoid/ZmpDevice @@ -0,0 +1 @@ +#include "src/Body/ZmpDevice.h" diff --git a/misc/script/CMakeLists.txt b/misc/script/CMakeLists.txt index aa78a38e1..e800122f0 100644 --- a/misc/script/CMakeLists.txt +++ b/misc/script/CMakeLists.txt @@ -7,3 +7,11 @@ configure_file(make_src_archive.sh.in make_src_archive.sh @ONLY) if(WIN32) configure_file(install_win32_deps.rb.template install_win32_deps.rb) endif() + +if(UNIX) + option(INSTALL_SYNC_LOGFILE_COMMAND "Installing the choreonoid-sync-logfile command" OFF) + mark_as_advanced(INSTALL_SYNC_LOGFILE_COMMAND) + if(INSTALL_SYNC_LOGFILE_COMMAND) + configure_file(sync-logfile.sh ${PROJECT_BINARY_DIR}/bin/choreonoid-sync-logfile COPYONLY) + endif() +endif() diff --git a/misc/script/install-requisites-ubuntu-24.04.sh b/misc/script/install-requisites-ubuntu-24.04.sh index 74f1f792c..ad77cf0c6 100755 --- a/misc/script/install-requisites-ubuntu-24.04.sh +++ b/misc/script/install-requisites-ubuntu-24.04.sh @@ -1,7 +1,7 @@ #!/bin/sh sudo --preserve-env=DEBIAN_FRONTEND,TZ \ -apt-get install \ +apt-get -y install \ build-essential \ cmake-curses-gui \ libeigen3-dev \ diff --git a/misc/script/sync-logfile.sh b/misc/script/sync-logfile.sh new file mode 100755 index 000000000..753ac28f7 --- /dev/null +++ b/misc/script/sync-logfile.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Function to display usage information +usage() { + echo "Usage: $0 SOURCE_FILE REMOTE_HOST [REMOTE_FILE]" + echo " SOURCE_FILE: Path to the local source file" + echo " REMOTE_HOST: Remote host in the format user@hostname" + echo " REMOTE_FILE: (Optional) Path to the remote destination file. If omitted, same as SOURCE_FILE" + exit 1 +} + +# Check command-line arguments +if [ $# -lt 2 ]; then + usage +fi + +# Set parameters +SOURCE_FILE="$1" +REMOTE_HOST="$2" +REMOTE_FILE="${3:-$SOURCE_FILE}" + +# Establish SSH connection and run in the background +ssh -N -f -M -S /tmp/ssh_socket "$REMOTE_HOST" + +cleanup() { + echo "Cleaning up and closing SSH connection..." + ssh -S /tmp/ssh_socket -O exit "$REMOTE_HOST" + exit 0 +} + +# Execute cleanup on script termination +trap cleanup EXIT + +# Initial sync: Replace the remote file with the source file at script start +initial_sync() { + echo "Performing initial sync..." + scp -o "ControlPath=/tmp/ssh_socket" "$SOURCE_FILE" "$REMOTE_HOST:$REMOTE_FILE" + if [ $? -eq 0 ]; then + echo "Initial sync completed successfully." + else + echo "Error during initial sync. Exiting." + cleanup + fi +} + +# Function to sync specified byte range +sync_range() { + local start=$1 + local end=$2 + local length=$((end - start)) + + if [ $length -gt 0 ]; then + dd if="$SOURCE_FILE" bs=1 skip=$start count=$length 2>/dev/null | + ssh -S /tmp/ssh_socket "$REMOTE_HOST" "cat >> $REMOTE_FILE" + + return $length + fi + return 0 +} + +# Perform initial sync +initial_sync + +# Record the last synced file size +last_size=$(stat -c %s "$SOURCE_FILE") + +# Get current time in seconds with nanosecond precision +get_time() { + date +%s.%N +} + +start_time=$(get_time) +last_output_time=$start_time +total_synced_bytes=0 +sync_count=0 + +while true; do + current_time=$(get_time) + + # Get current file size + current_size=$(stat -c %s "$SOURCE_FILE") + + # Sync if file size has changed + if [ "$current_size" != "$last_size" ]; then + if [ "$current_size" -lt "$last_size" ]; then + # If file size decreased, replace the entire file + scp -o "ControlPath=/tmp/ssh_socket" "$SOURCE_FILE" "$REMOTE_HOST:$REMOTE_FILE" + echo "File size decreased. Replaced entire file. New size: $current_size bytes" + synced_bytes=$current_size + else + # If file size increased, send only the newly added data + sync_range "$last_size" "$current_size" + synced_bytes=$? + fi + + # Update statistics + total_synced_bytes=$((total_synced_bytes + synced_bytes)) + sync_count=$((sync_count + 1)) + + # Update the last synced size + last_size=$current_size + fi + + # Output statistics approximately once per second, only if there were syncs + if (( $(echo "$current_time - $last_output_time >= 1" | bc -l) )); then + if [ $sync_count -gt 0 ]; then + echo "Sync count: $sync_count, Total bytes synced: $total_synced_bytes" + fi + last_output_time=$current_time + sync_count=0 + total_synced_bytes=0 + fi + + # Minimal sleep to reduce CPU usage + sleep 0.001 +done diff --git a/share/model/misc/fencedfloor.body b/share/model/misc/fencedfloor.body new file mode 100644 index 000000000..e12168cc9 --- /dev/null +++ b/share/model/misc/fencedfloor.body @@ -0,0 +1,45 @@ +format: ChoreonoidBody +formatVersion: 2.0 +name: FencedFloor +links: + - + name: Floor + jointType: fixed + material: Ground + elements: + Shape: + translation: [ 0, 0, -0.1 ] + geometry: { type: Box, size: [ 10.0, 10.0, 0.2 ] } + appearance: { material: { diffuseColor: [ 0, 0, 1 ] }} + - + name: Fence1 + parent: Floor + translation: [ 4.95, 0, 0.2 ] + jointType: fixed + elements: + Shape: &Fence1 + geometry: { type: Box, size: [ 0.1, 10.0, 0.4 ] } + appearance: &App1 { material: { diffuseColor: [ 0.7, 0.7, 0.7 ] }} + - + name: Fence2 + parent: Floor + translation: [ -4.95, 0, 0.2 ] + jointType: fixed + elements: + Shape: *Fence1 + - + name: Fence3 + parent: Floor + translation: [ 0, 4.95, 0.2 ] + jointType: fixed + elements: + Shape: &Fence2 + geometry: { type: Box, size: [ 9.8, 0.1, 0.4 ] } + appearance: *App1 + - + name: Fence4 + parent: Floor + translation: [ 0, -4.95, 0.2 ] + jointType: fixed + elements: + Shape: *Fence2 diff --git a/share/model/misc/smoke.body b/share/model/misc/smoke.body index aecd3b137..bc579cb12 100644 --- a/share/model/misc/smoke.body +++ b/share/model/misc/smoke.body @@ -8,4 +8,4 @@ links: name: Base jointType: fixed elements: - SmokeDevice: { } + SmokeDevice: { tint_color: [ 1.0, 1.0, 1.0 ] } diff --git a/src/AGXDynamicsPlugin/AGXObjectFactory.cpp b/src/AGXDynamicsPlugin/AGXObjectFactory.cpp index 58f555695..22bf5bd85 100644 --- a/src/AGXDynamicsPlugin/AGXObjectFactory.cpp +++ b/src/AGXDynamicsPlugin/AGXObjectFactory.cpp @@ -49,7 +49,11 @@ agxSDK::SimulationRef AGXObjectFactory::createSimulation(const AGXSimulationDesc sim->getSpace()->setContactReductionThreshold(desc.contactReductionThreshhold); sim->getDynamicsSystem()->setEnableContactWarmstarting(desc.enableContactWarmstarting); sim->getMergeSplitHandler()->setEnable(desc.enableAMOR); + +#if AGX_MAJOR_VERSION < 38 sim->getDynamicsSystem()->getAutoSleep()->setEnable(desc.enableAutoSleep); +#endif + return sim; } @@ -107,7 +111,11 @@ LinkRigidBodyRef AGXObjectFactory::createLinkRigidBody(const AGXRigidBodyDesc& d rigid->setPosition(desc.p); rigid->setRotation(desc.R); rigid->setName(desc.name); + +#if AGX_MAJOR_VERSION < 38 rigid->getAutoSleepProperties().setEnable(desc.enableAutoSleep); +#endif + return rigid; } diff --git a/src/AGXDynamicsPlugin/AGXScene.cpp b/src/AGXDynamicsPlugin/AGXScene.cpp index cf213fd6c..72f981442 100644 --- a/src/AGXDynamicsPlugin/AGXScene.cpp +++ b/src/AGXDynamicsPlugin/AGXScene.cpp @@ -132,12 +132,18 @@ void AGXScene::setGravity(const agx::Vec3 & g) bool AGXScene::getEnableAutoSleep() const { +#if AGX_MAJOR_VERSION >= 38 + return false; +#else return getSimulation()->getDynamicsSystem()->getAutoSleep()->getEnable(); +#endif } void AGXScene::setEnableAutoSleep(const bool & bOn) { +#if AGX_MAJOR_VERSION < 38 getSimulation()->getDynamicsSystem()->getAutoSleep()->setEnable(bOn); +#endif } bool AGXScene::saveSceneToAGXFile() @@ -151,4 +157,4 @@ agxSDK::SimulationRef AGXScene::getSimulation() const return _agxSimulation; } -} \ No newline at end of file +} diff --git a/src/AISTCollisionDetector/AISTCollisionDetector.cpp b/src/AISTCollisionDetector/AISTCollisionDetector.cpp index 66d85ac30..88554677d 100644 --- a/src/AISTCollisionDetector/AISTCollisionDetector.cpp +++ b/src/AISTCollisionDetector/AISTCollisionDetector.cpp @@ -627,8 +627,9 @@ void AISTCollisionDetector::Impl::detectCollisionsInParallel(const std::function if(size == 0){ break; } - threadPool->start([this, i, index, size](){ - extractCollisionsOfAssignedPairs(index, index + size, collisionPairArrays[i]); }); + threadPool->post([this, i, index, size](){ + extractCollisionsOfAssignedPairs(index, index + size, collisionPairArrays[i]); + }); index += size; } threadPool->waitLoop(); diff --git a/src/Base/App.cpp b/src/Base/App.cpp index ded9e50e8..3ccd30cbf 100644 --- a/src/Base/App.cpp +++ b/src/Base/App.cpp @@ -10,6 +10,7 @@ #include "MessageView.h" #include "RootItem.h" #include "ProjectManager.h" +#include "ProjectBackupManager.h" #include "UnifiedEditHistory.h" #include "UnifiedEditHistoryView.h" #include "ItemEditRecordManager.h" @@ -20,10 +21,11 @@ #include "ExtCommandItem.h" #include "SceneItem.h" #include "SceneGeometryMeasurementTracker.h" +#include "CameraItem.h" +#include "LightingItem.h" #include "PointSetItem.h" #include "PointSetGeometryMeasurementTracker.h" #include "MultiPointSetItem.h" -#include "LightingItem.h" #include "AbstractTextItem.h" #include "ScriptItem.h" #include "MessageLogItem.h" @@ -261,7 +263,7 @@ App::Impl::Impl(App* self, int& argc, char** argv, const std::string& appName, c #ifdef Q_OS_WIN32 // Make a bundled Python available if it exists in the Choreonoid top directory. std::smatch match; - for(auto& dir : filesystem::directory_iterator(executableTopDirPath())){ + for(auto& dir : stdx::filesystem::directory_iterator(executableTopDirPath())){ static std::regex re("^Python\\d+$"); string dirString = dir.path().filename().string(); if(regex_match(dirString, match, re)){ @@ -386,6 +388,7 @@ void App::Impl::initialize() ItemManager::initializeClass(ext); ProjectManager::initializeClass(ext); + ProjectBackupManager::initializeClass(); RootItem::initializeClass(ext); UnifiedEditHistory::initializeClass(ext); UnifiedEditHistoryView::initializeClass(ext); @@ -434,13 +437,14 @@ void App::Impl::initialize() ReferencedObjectSeqItem::initializeClass(ext); SceneItem::initializeClass(ext); SceneGeometryMeasurementTracker::initializeClass(); + CameraItem::initializeClass(ext); + LightingItem::initializeClass(ext); PointSetItem::initializeClass(ext); PointSetGeometryMeasurementTracker::initializeClass(); MultiPointSetItem::initializeClass(ext); AbstractTextItem::initializeClass(ext); ScriptItem::initializeClass(ext); MessageLogItem::initializeClass(ext); - LightingItem::initializeClass(ext); CoordinateFrameListItem::initializeClass(ext); CoordinateFrameItem::initializeClass(ext); PositionTagGroupItem::initializeClass(ext); @@ -495,9 +499,9 @@ App::~App() App::Impl::~Impl() { - AppConfig::flush(); delete qapplication; delete pluginManager; + AppConfig::flush(); } @@ -586,6 +590,7 @@ int App::Impl::exec() RootItem::instance()->clearChildren(); pluginManager->finalizePlugins(); + ext->deleteManagedObjects(); delete mainWindow; mainWindow = nullptr; diff --git a/src/Base/AppCustomizationUtil.h b/src/Base/AppCustomizationUtil.h index 0908603b0..3e9193f7a 100644 --- a/src/Base/AppCustomizationUtil.h +++ b/src/Base/AppCustomizationUtil.h @@ -10,6 +10,7 @@ #include "FileDialog.h" #include "TimeBar.h" #include "DisplayValueFormat.h" +#include "GeneralSceneFileLoadDialog.h" #include namespace cnoid { @@ -116,6 +117,11 @@ class AppCustomizationUtil TimeBar::setNegativeTimeEnabled(on); } + static void setGeneralSceneFileLoadDialogDefaultLengthUnitHint(SceneLoader::LengthUnitType hint) + { + GeneralSceneFileLoadDialog::setDefaultLengthUnitHint(hint); + } + void setLengthUnit(DisplayValueFormat::LengthUnit unit) { displayValueFormat->setLengthUnit(unit); diff --git a/src/Base/Archive.cpp b/src/Base/Archive.cpp index 1087bbf1b..e6d05231f 100644 --- a/src/Base/Archive.cpp +++ b/src/Base/Archive.cpp @@ -1,11 +1,15 @@ #include "Archive.h" #include "Item.h" -#include "MessageView.h" +#include "ProjectBackupManager.h" #include +#include +#include #include #include #include +#include #include +#include #include "gettext.h" using namespace std; @@ -51,6 +55,9 @@ class ArchiveSharedData : public Referenced vector postProcesses; vector nextPostProcesses; bool isDoingPostProcesses; + bool isSavingProjectAsBackup; + + MessageOut* mout; ArchiveSharedData(){ currentItem = nullptr; @@ -89,7 +96,8 @@ Archive::~Archive() } -void Archive::initSharedInfo(const std::string& projectFile, bool isSubProject) +void Archive::initSharedInfo +(const std::string& projectFile, bool isSubProject, MessageOut* mout, bool isSavingProjectAsBackup) { shared = new ArchiveSharedData; @@ -104,6 +112,9 @@ void Archive::initSharedInfo(const std::string& projectFile, bool isSubProject) auto projectDir = toUTF8(filesystem::absolute(fromUTF8(projectFile)).parent_path().generic_string()); shared->pathVariableProcessor->setBaseDirectory(projectDir); shared->pathVariableProcessor->setProjectDirectory(projectDir); + + shared->mout = mout; + shared->isSavingProjectAsBackup = isSavingProjectAsBackup; } @@ -260,8 +271,7 @@ std::string Archive::resolveRelocatablePath(const std::string& relocatable, bool { auto expanded = shared->pathVariableProcessor->expand(relocatable, doAbsolutize); if(expanded.empty()){ - MessageView::instance()->putln( - shared->pathVariableProcessor->errorMessage(), MessageView::Warning); + mout()->putErrorln(shared->pathVariableProcessor->errorMessage()); } return expanded; } @@ -288,20 +298,60 @@ std::string Archive::readItemFilePath() const } return filepath; } - -bool Archive::loadFileTo(Item* item) const + +bool Archive::loadFileTo(Item* item, bool& out_hasFileInformation) const { + bool loaded = false; + auto mout_ = mout(); + out_hasFileInformation = false; + string file; + string fileFormat; + string backupFile; + string backupFileFormat; + if(read({ "file", "filename" }, file)){ + out_hasFileInformation = true; file = resolveRelocatablePath(file); + } + read("format", fileFormat); + read("backup_file", backupFile); + read("backup_file_format", backupFileFormat); + + if(backupFile.empty()){ if(!file.empty()){ - string format; - read("format", format); - return item->load(file, currentParentItem(), format, this); + loaded = item->load(file, currentParentItem(), fileFormat, this, mout_); + } + } else { + backupFile = resolveRelocatablePath(backupFile); + if(!backupFile.empty()){ + loaded = item->load(backupFile, currentParentItem(), backupFileFormat, this, mout_); + if(loaded){ + item->updateFileInformation(file, fileFormat, item->fileOptions(), false); + if(!file.empty()){ + filesystem::path filePath(fromUTF8(file)); + filesystem::path backupPath(fromUTF8(backupFile)); + stdx::error_code ec; + if(!filesystem::equivalent(filePath, backupPath, ec)){ + item->setConsistentWithFile(false); + } + mout_->putln( + formatR(_("Note that item \"{0}\" will be saved to the original file \"{1}\"."), + item->displayName(), file)); + } + } } } - return false; + + return loaded; +} + + +bool Archive::loadFileTo(Item* item) const +{ + bool hasFileInformation; + return loadFileTo(item, hasFileInformation); } @@ -358,6 +408,71 @@ bool Archive::writeFileInformation(Item* item) } +bool Archive::saveItemToFile(Item* item) +{ + if(!shared){ + return false; + } + + bool saved = false; + + if(!shared->isSavingProjectAsBackup){ + if(item->overwriteOrSaveWithDialog()){ + writeFileInformation(item); + saved = true; + } + } else { + auto backupManager = ProjectBackupManager::instance(); + filesystem::path backupFilePath; + filesystem::path hardLinkFilePath; + string fileFormat; + + if(backupManager->getItemBackupFileInformation(item, backupFilePath, hardLinkFilePath, fileFormat)){ + + string backupFilename = toUTF8(backupFilePath.string()); + + if(!hardLinkFilePath.empty()){ + stdx::error_code ec; + filesystem::create_hard_link(hardLinkFilePath, backupFilePath, ec); + if(ec){ + mout()->putErrorln(toUTF8(ec.message())); + } else { + saved = true; + } + } else { + string orgFilename = item->filePath(); + string orgFileFormat = item->fileFormat(); + time_t orgModificationTime = item->fileModificationTime(); + bool orgProjectConsistency = item->isConsistentWithProjectArchive(); + bool orgFileConsistency = item->isConsistentWithFile(); + + item->updateFileInformation(backupFilename, item->fileFormat(), item->fileOptions(), false); + item->setConsistentWithFile(false); + saved = item->overwrite(false, std::string(), orgModificationTime, mout()); + + fileFormat = item->fileFormat(); + backupManager->setItemBackupFileFormat(item, fileFormat); + + item->updateFileInformation(orgFilename, orgFileFormat, item->fileOptions(), false); + item->setConsistentWithFile(orgFileConsistency); + item->setConsistentWithProjectArchive(orgProjectConsistency); + } + + if(saved){ + writeFileInformation(item); + if(!writeRelocatablePath("backup_file", backupFilename)){ + saved = false; + } else if(!fileFormat.empty()){ + write("backup_file_format", fileFormat); + } + } + } + } + + return saved; +} + + void Archive::clearIds() { if(shared){ @@ -534,3 +649,18 @@ FilePathVariableProcessor* Archive::filePathVariableProcessor() const } return nullptr; } + + +bool Archive::isSavingProjectAsBackup() const +{ + return shared ? shared->isSavingProjectAsBackup : false; +} + + +MessageOut* Archive::mout() const +{ + if(shared){ + return shared->mout; + } + return MessageOut::nullout(); +} diff --git a/src/Base/Archive.h b/src/Base/Archive.h index 04a71a3a6..98fb30db1 100644 --- a/src/Base/Archive.h +++ b/src/Base/Archive.h @@ -14,6 +14,7 @@ class ViewManager; class ProjectManager; class FilePathVariableProcessor; class ArchiveSharedData; +class MessageOut; class CNOID_EXPORT Archive : public Mapping { @@ -22,7 +23,8 @@ class CNOID_EXPORT Archive : public Mapping Archive(int line, int column); virtual ~Archive(); - void initSharedInfo(const std::string& projectFile, bool isSubProject); + void initSharedInfo( + const std::string& projectFile, bool isSubProject, MessageOut* mout, bool isSavingProjectAsBackup); void inheritSharedInfoFrom(Archive& archive); /** @@ -65,34 +67,33 @@ class CNOID_EXPORT Archive : public Mapping std::string resolveRelocatablePath(const std::string& relocatable, bool doAbsolutize = true) const; bool readRelocatablePath(const std::string& key, std::string& out_value) const; std::string readItemFilePath() const; - - //! \deprecated - [[deprecated("Use resolveRelocatablePath(path, false).")]] - std::string expandPathVariables(const std::string& path) const { - return resolveRelocatablePath(path, false); - } + bool loadFileTo(Item* item, bool& out_hasFileInformation) const; bool loadFileTo(Item* item) const; bool loadFileTo(Item* item, const std::string& filepath) const; std::string getRelocatablePath(const std::string& path) const; bool writeRelocatablePath(const std::string& key, const std::string& path); bool writeFileInformation(Item* item); + bool saveItemToFile(Item* item); + + Item* currentParentItem() const; + std::string projectDirectory() const; + FilePathVariableProcessor* filePathVariableProcessor() const; + bool isSavingProjectAsBackup() const; + MessageOut* mout() const; - //! \deprecated + [[deprecated("Use resolveRelocatablePath(path, false).")]] + std::string expandPathVariables(const std::string& path) const { + return resolveRelocatablePath(path, false); + } [[deprecated("Use loadFileTo(Item* item, const std::string& filepath)")]] bool loadFileTo(const std::string& filepath, Item* item) const { return loadFileTo(item, filepath); } - //! \deprecated [[deprecated]] bool loadItemFile(Item* item, const std::string& fileNameKey, const std::string& fileFormatKey = std::string()) const; - Item* currentParentItem() const; - - std::string projectDirectory() const; - - FilePathVariableProcessor* filePathVariableProcessor() const; private: ref_ptr shared; diff --git a/src/Base/Base.qrc b/src/Base/Base.qrc index fd73447c2..b07be9cc3 100644 --- a/src/Base/Base.qrc +++ b/src/Base/Base.qrc @@ -36,6 +36,7 @@ icon/rightview.svg icon/topview.svg icon/bottomview.svg + icon/isometricview.svg icon/global.svg diff --git a/src/Base/CMakeLists.txt b/src/Base/CMakeLists.txt index 14f7ad72e..fafaa90ca 100644 --- a/src/Base/CMakeLists.txt +++ b/src/Base/CMakeLists.txt @@ -5,6 +5,7 @@ set(sources MainMenu.cpp ProjectManager.cpp ProjectPacker.cpp + ProjectBackupManager.cpp PathVariableEditor.cpp PluginManager.cpp MainWindow.cpp @@ -25,6 +26,7 @@ set(sources RenderableItem.cpp RenderableItemUtil.cpp RenderableItemSceneStatistics.cpp + RenderableItemSceneExporter.cpp PolymorphicItemFunctionSet.cpp PutPropertyFunction.cpp RootItem.cpp @@ -44,7 +46,6 @@ set(sources DisplayValueFormat.cpp PositionWidget.cpp LocationView.cpp - MultiSeqItemCreationPanel.cpp ToolBar.cpp TimeBar.cpp ScriptBar.cpp @@ -130,6 +131,10 @@ set(sources SceneItemFileIO.cpp SceneGeometryMeasurementTracker.cpp GeneralSceneFileImporterBase.cpp + GeneralSceneFileLoadDialog.cpp + CameraItem.cpp + CameraConfigDialog.cpp + LightingItem.cpp PointSetItem.cpp PointSetGeometryMeasurementTracker.cpp MultiPointSetItem.cpp @@ -149,7 +154,6 @@ set(sources TaskView.cpp VirtualJoystickView.cpp MessageLogItem.cpp - LightingItem.cpp QtSvgUtil.cpp ) @@ -213,6 +217,7 @@ set(headers OptionManager.h ProjectManager.h ProjectPacker.h + ProjectBackupManager.h PluginManager.h Plugin.h MessageView.h @@ -226,6 +231,7 @@ set(headers LocatableItem.h RenderableItem.h RenderableItemUtil.h + RenderableItemSceneExporter.h ImageableItem.h TargetItemPicker.h UnifiedEditHistory.h @@ -235,7 +241,6 @@ set(headers DisplayValueFormat.h PositionWidget.h LocationView.h - MultiSeqItemCreationPanel.h PutPropertyFunction.h RootItem.h FolderItem.h @@ -285,6 +290,10 @@ set(headers SceneItem.h SceneItemFileIO.h GeneralSceneFileImporterBase.h + GeneralSceneFileLoadDialog.h + CameraItem.h + CameraConfigDialog.h + LightingItem.h PointSetItem.h MultiPointSetItem.h PositionTagGroupItem.h @@ -302,7 +311,6 @@ set(headers TextEdit.h TaskView.h MessageLogItem.h - LightingItem.h QtEventUtil.h QVariantUtil.h QtSvgUtil.h diff --git a/src/Base/CameraConfigDialog.cpp b/src/Base/CameraConfigDialog.cpp new file mode 100644 index 000000000..bf4dec89e --- /dev/null +++ b/src/Base/CameraConfigDialog.cpp @@ -0,0 +1,476 @@ +#include "CameraConfigDialog.h" +#include "CameraItem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gettext.h" + +using namespace std; +using namespace cnoid; + +namespace cnoid { + +class CameraConfigDialog::Impl +{ +public: + CameraConfigDialog* self; + CameraItemPtr cameraItem; + LineEdit nameEdit; + RadioButton perspectiveRadio; + RadioButton orthographicRadio; + LengthSpinBox positionSpins[3]; + DoubleSpinBox directionSpins[3]; + DoubleSpinBox upSpins[3]; + DoubleSpinBox nearClipSpin; + DoubleSpinBox farClipSpin; + DoubleSpinBox fovSpin; + CheckBox interactiveViewpointChangeCheck; + QVBoxLayout* alignVBox; + QVBoxLayout* optionVBox1; + QVBoxLayout* optionVBox2; + ConnectionSet widgetConnections; + + CheckBox activationInSceneViewCheck; + Connection activationCheckConnection; + + ScopedConnection cameraConnection; + + Impl(CameraConfigDialog* self); + void showToConfigureCameraItem(CameraItem* cameraItem); + void setCameraItem(CameraItem* cameraItem); + void updateWidgetsWithCurrentCameraStates(); + void setVectorElementSpins(const Vector3& v, LengthSpinBox spins[]); + void setVectorElementSpins(const Vector3& v, DoubleSpinBox spins[]); + void onNameEditingFinished(const std::string& name); + void alignWithBuiltinCamera(); + void onCameraTypeRadioChanged(int type); + void onCameraPositionSpinValueChanged(); + void onFieldOfViewSpinValueChanged(double fov); + void onClipDistanceSpinValueChanged(); + void onInteractiveViewpointChangeToggled(bool on); +}; + +} + + +CameraConfigDialog* CameraConfigDialog::instance() +{ + static CameraConfigDialog* instance_ = new CameraConfigDialog; + return instance_; +} + + +CameraConfigDialog::CameraConfigDialog() +{ + impl = new Impl(this); +} + + +CameraConfigDialog::Impl::Impl(CameraConfigDialog* self_) + : self(self_) +{ + auto vbox = new QVBoxLayout; + self->setLayout(vbox); + + auto hbox = new QHBoxLayout; + hbox->addWidget(new QLabel(_("Name:"))); + widgetConnections.add( + nameEdit.sigEditingFinished().connect( + [this](){ onNameEditingFinished(nameEdit.text().toStdString()); })); + hbox->addWidget(&nameEdit); + vbox->addLayout(hbox); + + hbox = new QHBoxLayout; + alignVBox = new QVBoxLayout; + auto alignButton = new PushButton(_("Align with the builtin camera")); + alignButton->sigClicked().connect( + [this](){ alignWithBuiltinCamera(); }); + alignVBox->addWidget(alignButton); + hbox->addStretch(); + hbox->addLayout(alignVBox); + hbox->addStretch(); + vbox->addLayout(hbox); + + optionVBox1 = new QVBoxLayout; + vbox->addLayout(optionVBox1); + + hbox = new QHBoxLayout; + hbox->addWidget(new QLabel(_("Camera type:"))); + auto cameraTypeGroup = new ButtonGroup; + perspectiveRadio.setText(_("Perspective")); + perspectiveRadio.setChecked(true); + cameraTypeGroup->addButton(&perspectiveRadio, CameraItem::Perspective); + hbox->addWidget(&perspectiveRadio); + orthographicRadio.setText(_("Orthographic")); + cameraTypeGroup->addButton(&orthographicRadio, CameraItem::Orthographic); + hbox->addWidget(&orthographicRadio); + widgetConnections.add( + cameraTypeGroup->sigButtonToggled().connect( + [this](int type, bool on){ + if(on){ + onCameraTypeRadioChanged(type); + } + })); + hbox->addStretch(); + vbox->addLayout(hbox); + + auto grid = new QGridLayout; + grid->addWidget(new QLabel(_("Position:")), 0, 0); + grid->addWidget(new QLabel(_("Look-at:")), 1, 0); + grid->addWidget(new QLabel(_("Up vector:")), 2, 0); + + DoubleSpinBox* spins[3][3]; + for(int i=0; i < 3; ++i){ + auto spin = &positionSpins[i]; + spin->setMeterRange(-9.999, 9.999); + spin->setMeterSingleStep(0.001); + spins[0][i] = spin; + spins[1][i] = &directionSpins[i]; + spins[2][i] = &upSpins[i]; + } + for(int i=1; i < 3; ++i){ + for(int j=0; j < 3; ++j){ + auto spin = spins[i][j]; + spin->setDecimals(3); + spin->setRange(-9.999, 9.999); + spin->setSingleStep(0.001); + } + } + const char* xyzLabels[] = { "X", "Y", "Z" }; + for(int i=0; i < 3; ++i){ + for(int j=0; j < 3; ++j){ + grid->addWidget(new QLabel(xyzLabels[j]), i, j * 2 + 1); + auto spin = spins[i][j]; + widgetConnections.add( + spin->sigValueChanged().connect( + [this](double){ onCameraPositionSpinValueChanged(); })); + grid->addWidget(spin, i, j * 2 + 2); + } + } + vbox->addLayout(grid); + + hbox = new QHBoxLayout; + hbox->addWidget(new QLabel(_("Field of View:"))); + fovSpin.setDecimals(0); + fovSpin.setRange(1.0, 179.0); + widgetConnections.add( + fovSpin.sigValueChanged().connect( + [this](double value){ onFieldOfViewSpinValueChanged(value); })); + hbox->addWidget(&fovSpin); + hbox->addWidget(new QLabel(_("[deg]"))); + hbox->addStretch(); + vbox->addLayout(hbox); + + hbox = new QHBoxLayout; + hbox->addWidget(new QLabel(_("Near Clip:"))); + nearClipSpin.setDecimals(3); + nearClipSpin.setRange(0.001, 9.999); + nearClipSpin.setSingleStep(0.001); + widgetConnections.add( + nearClipSpin.sigValueChanged().connect( + [this](double){ onClipDistanceSpinValueChanged(); })); + hbox->addWidget(&nearClipSpin); + + hbox->addWidget(new QLabel(_("Far Clip:"))); + farClipSpin.setDecimals(1); + farClipSpin.setRange(0.1, 999.9); + farClipSpin.setSingleStep(1.0); + widgetConnections.add( + farClipSpin.sigValueChanged().connect( + [this](double){ onClipDistanceSpinValueChanged(); })); + hbox->addWidget(&farClipSpin); + hbox->addStretch(); + vbox->addLayout(hbox); + + optionVBox2 = new QVBoxLayout; + vbox->addLayout(optionVBox2); + + interactiveViewpointChangeCheck.setText(_("Interactive viewpoint change")); + widgetConnections.add( + interactiveViewpointChangeCheck.sigToggled().connect( + [this](bool on){ onInteractiveViewpointChangeToggled(on); })); + vbox->addWidget(&interactiveViewpointChangeCheck); + + activationInSceneViewCheck.setText(_("Activate in the scene view")); + activationCheckConnection = + activationInSceneViewCheck.sigToggled().connect( + [this](bool on){ cameraItem->activateCameraInSceneView(SceneView::lastFocusSceneView(), on); }); + vbox->addWidget(&activationInSceneViewCheck); + + auto buttonBox = new QDialogButtonBox(self); + auto okButton = new PushButton(_("&OK")); + buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole); + connect(buttonBox, &QDialogButtonBox::accepted, self, &QDialog::accept); + vbox->addWidget(buttonBox); + + self->setWindowPositionKeepingMode(true); +} + + +CameraConfigDialog::~CameraConfigDialog() +{ + delete impl; +} + + +CameraItem* CameraConfigDialog::showToCreateCameraItem(Item* parentItem) +{ + setWindowTitle(_("Camera Creation")); + + CameraItemPtr cameraItem = new CameraItem; + cameraItem->setName(_("Camera")); + cameraItem->setChecked(true); + cameraItem->setInteractiveViewpointChangeEnabled(false); + + cameraItem->cameraTransform()->setPosition( + Isometry3::Identity() * AngleAxis(PI / 2.0, Vector3::UnitZ()) * AngleAxis(PI, Vector3::UnitX())); + + parentItem->addChildItem(cameraItem); + + impl->showToConfigureCameraItem(cameraItem); + + return cameraItem; +} + + +void CameraConfigDialog::showToConfigureCameraItem(CameraItem* cameraItem) +{ + setWindowTitle(_("Camera Configuration")); + impl->showToConfigureCameraItem(cameraItem); +} + + +void CameraConfigDialog::Impl::showToConfigureCameraItem(CameraItem* cameraItem) +{ + setCameraItem(cameraItem); + self->show(); +} + + +QVBoxLayout* CameraConfigDialog::alignVBox() +{ + return impl->alignVBox; +} + + +QVBoxLayout* CameraConfigDialog::optionVBox1() +{ + return impl->optionVBox1; +} + + +QVBoxLayout* CameraConfigDialog::optionVBox2() +{ + return impl->optionVBox2; +} + + +CameraItem* CameraConfigDialog::cameraItem() +{ + return impl->cameraItem; +} + + +void CameraConfigDialog::Impl::setCameraItem(CameraItem* cameraItem) +{ + cameraConnection.disconnect(); + this->cameraItem = cameraItem; + + activationCheckConnection.block(); + + if(!cameraItem){ + activationInSceneViewCheck.setChecked(false); + + } else { + auto currentCamera = SceneView::instance()->sceneWidget()->renderer()->currentCamera(); + activationInSceneViewCheck.setChecked(cameraItem->perspectiveCamera() == currentCamera); + + cameraConnection = + cameraItem->cameraTransform()->sigUpdated().connect( + [this](const SgUpdate&){ self->updateWidgetsWithCurrentCameraStates(); }); + + self->updateWidgetsWithCurrentCameraStates(); + } + + activationCheckConnection.unblock(); +} + + +void CameraConfigDialog::updateWidgetsWithCurrentCameraStates() +{ + impl->updateWidgetsWithCurrentCameraStates(); +} + + +void CameraConfigDialog::Impl::updateWidgetsWithCurrentCameraStates() +{ + widgetConnections.block(); + + nameEdit.setText(cameraItem->name().c_str()); + + if(cameraItem->cameraType() == CameraItem::Perspective){ + perspectiveRadio.setChecked(true); + } else { + orthographicRadio.setChecked(true); + } + + Isometry3 T = self->getCurrentCameraPositionToDisplay(); + + setVectorElementSpins(T.translation(), positionSpins); + setVectorElementSpins(Vector3(SgCamera::direction(T)), directionSpins); + setVectorElementSpins(Vector3(SgCamera::up(T)), upSpins); + + auto camera = cameraItem->currentCamera(); + nearClipSpin.setValue(camera->nearClipDistance()); + farClipSpin.setValue(camera->farClipDistance()); + + fovSpin.setValue(degree(cameraItem->perspectiveCamera()->fieldOfView())); + + interactiveViewpointChangeCheck.setChecked( + cameraItem->isInteractiveViewpointChangeEnabled()); + + widgetConnections.unblock(); +} + + +Isometry3 CameraConfigDialog::getCurrentCameraPositionToDisplay() +{ + return impl->cameraItem->cameraTransform()->T(); +} + + +void CameraConfigDialog::setCameraPositionToDisplayToCameraTransform(const Isometry3& T) +{ + auto transform = impl->cameraItem->cameraTransform(); + transform->setPosition(T); + transform->notifyUpdate(); +} + + +void CameraConfigDialog::Impl::setVectorElementSpins(const Vector3& v, LengthSpinBox spins[]) +{ + for(int i=0; i < 3; ++i){ + spins[i].setMeterValue(v[i]); + } +} + + +void CameraConfigDialog::Impl::setVectorElementSpins(const Vector3& v, DoubleSpinBox spins[]) +{ + for(int i=0; i < 3; ++i){ + spins[i].setValue(v[i]); + } +} + + +void CameraConfigDialog::Impl::onNameEditingFinished(const std::string& name) +{ + cameraItem->setName(name); +} + + +void CameraConfigDialog::Impl::alignWithBuiltinCamera() +{ + auto sceneWidget = SceneView::lastFocusSceneView()->sceneWidget(); + + SgCamera* camera; + SgCamera* builtinCamera; + if(perspectiveRadio.isChecked()){ + auto persCamera = cameraItem->perspectiveCamera(); + auto builtinPersCamera = sceneWidget->builtinPerspectiveCamera(); + persCamera->setFieldOfView(builtinPersCamera->fieldOfView()); + camera = persCamera; + builtinCamera = builtinPersCamera; + } else { + auto orthoCamera = cameraItem->orthographicCamera(); + auto builtinOrthoCamera = sceneWidget->builtinOrthographicCamera(); + orthoCamera->setHeight(builtinOrthoCamera->height()); + camera = orthoCamera; + builtinCamera = builtinOrthoCamera; + } + camera->setNearClipDistance(builtinCamera->nearClipDistance()); + camera->setFarClipDistance(builtinCamera->farClipDistance()); + + auto transform = cameraItem->cameraTransform(); + transform->setPosition(sceneWidget->builtinCameraTransform()->position()); + + camera->notifyUpdate(); +} + + +void CameraConfigDialog::Impl::onCameraTypeRadioChanged(int type) +{ + cameraItem->setCameraType(static_cast(type)); + updateWidgetsWithCurrentCameraStates(); +} + + +void CameraConfigDialog::Impl::onCameraPositionSpinValueChanged() +{ + cameraConnection.block(); + + Vector3 eye(positionSpins[0].meterValue(), positionSpins[1].meterValue(), positionSpins[2].meterValue()); + Vector3 dir(directionSpins[0].value(), directionSpins[1].value(), directionSpins[2].value()); + Vector3 up(upSpins[0].value(), upSpins[1].value(), upSpins[2].value()); + + if(dir.norm() > 0.0 && up.norm() > 0.0){ + Isometry3 T = SgCamera::positionLookingFor(eye, dir, up); + self->setCameraPositionToDisplayToCameraTransform(T); + } + + cameraConnection.unblock(); + + cameraItem->notifyUpdate(); +} + + +void CameraConfigDialog::Impl::onFieldOfViewSpinValueChanged(double fov) +{ + auto camera = cameraItem->perspectiveCamera(); + camera->setFieldOfView(radian(fov)); + camera->notifyUpdate(); + cameraItem->notifyUpdate(); +} + + +void CameraConfigDialog::Impl::onClipDistanceSpinValueChanged() +{ + auto persCamera = cameraItem->perspectiveCamera(); + persCamera->setNearClipDistance(nearClipSpin.value()); + persCamera->setFarClipDistance(farClipSpin.value()); + persCamera->notifyUpdate(); + + auto orthoCamera = cameraItem->orthographicCamera(); + orthoCamera->setNearClipDistance(nearClipSpin.value()); + orthoCamera->setFarClipDistance(farClipSpin.value()); + orthoCamera->notifyUpdate(); + + cameraItem->notifyUpdate(); +} + + +void CameraConfigDialog::Impl::onInteractiveViewpointChangeToggled(bool on) +{ + cameraItem->setInteractiveViewpointChangeEnabled(on); + cameraItem->notifyUpdate(); +} + + +void CameraConfigDialog::hideEvent(QHideEvent* event) +{ + impl->setCameraItem(nullptr); + Dialog::hideEvent(event); +} diff --git a/src/Base/CameraConfigDialog.h b/src/Base/CameraConfigDialog.h new file mode 100644 index 000000000..3f871fefa --- /dev/null +++ b/src/Base/CameraConfigDialog.h @@ -0,0 +1,45 @@ +#ifndef CNOID_BASE_CAMERA_CONFIG_DIALOG_H +#define CNOID_BASE_CAMERA_CONFIG_DIALOG_H + +#include +#include +#include "exportdecl.h" + +class QVBoxLayout; + +namespace cnoid { + +class Item; +class CameraItem; + +class CNOID_EXPORT CameraConfigDialog : public Dialog +{ +public: + CameraConfigDialog(); + virtual ~CameraConfigDialog(); + + static CameraConfigDialog* instance(); + + CameraItem* showToCreateCameraItem(Item* parentItem); + virtual void showToConfigureCameraItem(CameraItem* cameraItem); + +protected: + QVBoxLayout* alignVBox(); + QVBoxLayout* optionVBox1(); + QVBoxLayout* optionVBox2(); + CameraItem* cameraItem(); + virtual void updateWidgetsWithCurrentCameraStates(); + virtual Isometry3 getCurrentCameraPositionToDisplay(); + virtual void setCameraPositionToDisplayToCameraTransform(const Isometry3& T); + +protected: + virtual void hideEvent(QHideEvent* event) override; + +private: + class Impl; + Impl* impl; +}; + +} + +#endif diff --git a/src/Base/CameraItem.cpp b/src/Base/CameraItem.cpp new file mode 100644 index 000000000..47b400eeb --- /dev/null +++ b/src/Base/CameraItem.cpp @@ -0,0 +1,479 @@ +#include "CameraItem.h" +#include "CameraConfigDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gettext.h" + +using namespace std; +using namespace cnoid; + + +namespace cnoid { + +class CameraItem::Impl +{ +public: + CameraItem* self; + InteractiveCameraTransformPtr cameraTransform; + SgPerspectiveCameraPtr persCamera; + SgOrthographicCameraPtr orthoCamera; + SgCameraPtr currentCamera; + SgUpdate update; + Selection cameraType; + + Impl(CameraItem* self, InteractiveCameraTransform* cameraTransform, bool initCameraPosition); + Impl(CameraItem* self, const Impl& org, InteractiveCameraTransform* newCameraTransform); + void doPutProperties(PutPropertyFunction& putProperty); + bool setClipDistances(double nearDistance, double farDistance); + bool setFieldOfView(double fov); + bool setCameraType(int index); +}; + +} + + +void CameraItem::initializeClass(ExtensionManager* ext) +{ + ext->itemManager() + .registerClass(N_("CameraItem")) + .addCreationPanel(); + + ItemTreeView::customizeContextMenu( + [](CameraItem* item, MenuManager& menuManager, ItemFunctionDispatcher menuFunction){ + menuManager.addItem(_("Activate camera"))->sigTriggered().connect( + [item](){ item->activateCameraInSceneView(SceneView::lastFocusSceneView()); }); + menuManager.addItem(_("Apply to built-in camera"))->sigTriggered().connect( + [item](){ + item->activateSceneViewBuiltinCameraWithCameraItemConfiguration( + SceneView::lastFocusSceneView()); + }); + menuManager.addItem(_("Camera configuration"))->sigTriggered().connect( + [item](){ item->showDialogToConfigureCamera(); }); + menuManager.addSeparator(); + menuFunction.dispatchAs(item); + }); +} + + +CameraItem* CameraItem::showDialogToCreateCameraItem(Item* parentItem) +{ + return CameraConfigDialog::instance()->showToCreateCameraItem(parentItem); +} + + +void CameraItem::showDialogToConfigureCamera() +{ + CameraConfigDialog::instance()->showToConfigureCameraItem(this); +} + + +CameraItem::CameraItem() + : Item("Camera") +{ + impl = new Impl(this, new InteractiveCameraTransform, true); +} + + +CameraItem::CameraItem(const char* name, InteractiveCameraTransform* cameraTransform) + : Item(name) +{ + impl = new Impl(this, cameraTransform, true); +} + + +CameraItem::Impl::Impl(CameraItem* self, InteractiveCameraTransform* cameraTransform, bool initCameraPosition) + : self(self), + cameraType(NumCameraTypes, CNOID_GETTEXT_DOMAIN_NAME), + cameraTransform(cameraTransform) +{ + cameraType.setSymbol(Perspective, N_("Perspective")); + cameraType.setSymbol(Orthographic, N_("Orthographic")); + cameraType.select(Perspective); + + if(initCameraPosition){ + cameraTransform->setPosition( + SceneView::instance()->sceneWidget()->builtinCameraTransform()->position()); + } else { + cameraTransform->setInteractiveViewpointChangeLocked(true); + } + + persCamera = new SgPerspectiveCamera; + persCamera->setName(self->name()); + cameraTransform->addChild(persCamera); + currentCamera = persCamera; + + orthoCamera = new SgOrthographicCamera; + orthoCamera->setName(self->name()); +} + + +CameraItem::CameraItem(const CameraItem& org) + : Item(org) +{ + impl = new Impl(this, *org.impl, new InteractiveCameraTransform); +} + + +CameraItem::CameraItem(const CameraItem& org, InteractiveCameraTransform* newCameraTransform) + : Item(org) +{ + impl = new Impl(this, *org.impl, newCameraTransform); +} + + +CameraItem::Impl::Impl(CameraItem* self, const Impl& org, InteractiveCameraTransform* newCameraTransform) + : Impl(self, newCameraTransform, false) +{ + cameraType = org.cameraType; + + cameraTransform->setPosition(org.cameraTransform->position()); + cameraTransform->setInteractiveViewpointChangeLocked( + org.cameraTransform->isInteractiveViewpointChangeLocked()); +} + + +CameraItem::~CameraItem() +{ + delete impl; +} + + +bool CameraItem::setName(const std::string& name_) +{ + if(name_ != name()){ + impl->persCamera->setName(name_); + impl->orthoCamera->setName(name_); + Item::setName(name_); + impl->persCamera->notifyUpdate(impl->update); + impl->orthoCamera->notifyUpdate(impl->update); + } + return true; +} + + +Item* CameraItem::doCloneItem(CloneMap* /* cloneMap */) const +{ + return new CameraItem(*this); +} + + +void CameraItem::setInteractiveViewpointChangeEnabled(bool on) +{ + impl->cameraTransform->setInteractiveViewpointChangeLocked(!on); +} + + +bool CameraItem::isInteractiveViewpointChangeEnabled() const +{ + return !impl->cameraTransform->isInteractiveViewpointChangeLocked(); +} + + +void CameraItem::setCameraType(CameraType type) +{ + impl->setCameraType(type); +} + + +/** + \todo Improve the scene widget so that the current camera path described in a string list + can be kept even if the actual camera node is changed, and simplify the following implementation. +*/ +bool CameraItem::Impl::setCameraType(int index) +{ + if(cameraType.selectedIndex() == index){ + return true; + } + + cameraType.select(index); + + SgCamera* cameraToRemove; + if(cameraType.is(CameraItem::Perspective)){ + currentCamera = persCamera; + cameraToRemove = orthoCamera; + }else if(cameraType.is(CameraItem::Orthographic)){ + currentCamera = orthoCamera; + cameraToRemove = persCamera; + } + + vector renderers; + for(auto sceneView : SceneView::instances()){ + renderers.push_back(sceneView->sceneWidget()->renderer()); + } + + cameraTransform->addChild(currentCamera, update); + for(auto renderer : renderers){ + if(renderer->currentCamera() == cameraToRemove){ + renderer->extractPreprocessedNodes(); + renderer->setCurrentCamera(currentCamera); + } + } + cameraTransform->removeChild(cameraToRemove, update); + for(auto renderer : renderers){ + renderer->extractPreprocessedNodes(); + } + + return true; +} + + +CameraItem::CameraType CameraItem::cameraType() const +{ + return static_cast(impl->cameraType.which()); +} + + +SgPerspectiveCamera* CameraItem::perspectiveCamera() +{ + return impl->persCamera; +} + + +SgOrthographicCamera* CameraItem::orthographicCamera() +{ + return impl->orthoCamera; +} + + +SgCamera* CameraItem::currentCamera() +{ + return impl->currentCamera; +} + + +SgPosTransform* CameraItem::cameraTransform() +{ + return impl->cameraTransform; +} + + +SgNode* CameraItem::getScene() +{ + return impl->cameraTransform; +} + + +double CameraItem::fieldOfView() const +{ + return impl->persCamera->fieldOfView(); +} + + +bool CameraItem::setFieldOfView(double fov) +{ + return impl->setFieldOfView(fov); +} + + +bool CameraItem::Impl::setFieldOfView(double fov) +{ + if(fov > 0.0 && fov < PI){ + persCamera->setFieldOfView(fov); + persCamera->notifyUpdate(update); + return true; + } + return false; +} + + +double CameraItem::nearClipDistance() const +{ + return impl->persCamera->nearClipDistance(); +} + + +bool CameraItem::setNearClipDistance(double nearDistance) +{ + if(nearDistance > 0.0){ + impl->setClipDistances(nearDistance, farClipDistance()); + return true; + } + return false; +} + + +double CameraItem::farClipDistance() const +{ + return impl->persCamera->farClipDistance(); +} + + +bool CameraItem::setFarClipDistance(double farDistance) +{ + if(farDistance > 0.0){ + impl->setClipDistances(nearClipDistance(), farDistance); + return true; + } + return false; +} + + +bool CameraItem::setClipDistances(double nearDistance, double farDistance) +{ + if(nearDistance > 0.0 && farDistance > 0.0){ + impl->setClipDistances(nearDistance, farDistance); + return true; + } + return false; +} + + +bool CameraItem::Impl::setClipDistances(double nearDistance, double farDistance) +{ + if(persCamera->nearClipDistance() != nearDistance || persCamera->farClipDistance() != farDistance){ + persCamera->setNearClipDistance(nearDistance); + persCamera->setFarClipDistance(farDistance); + orthoCamera->setNearClipDistance(nearDistance); + orthoCamera->setFarClipDistance(farDistance); + persCamera->notifyUpdate(update); + orthoCamera->notifyUpdate(update); + } + return true; +} + + +void CameraItem::activateCameraInSceneView(SceneView* sceneView, bool on) +{ + auto sceneWidget = sceneView->sceneWidget(); + auto renderer = sceneWidget->renderer(); + + if(on && !isChecked()){ + setChecked(true); + renderer->extractPreprocessedNodes(); + } + + auto sceneViewCamera = renderer->currentCamera(); + if(on){ + if(impl->currentCamera != sceneViewCamera){ + renderer->setCurrentCamera(impl->currentCamera); + } + } else { + if(impl->currentCamera == sceneViewCamera){ + if(impl->currentCamera == impl->persCamera){ + renderer->setCurrentCamera(sceneWidget->builtinPerspectiveCamera()); + } else { + renderer->setCurrentCamera(sceneWidget->builtinOrthographicCamera()); + } + } + } +} + + +void CameraItem::activateSceneViewBuiltinCameraWithCameraItemConfiguration(SceneView* sceneView) +{ + auto sceneWidget = SceneView::instance()->sceneWidget(); + auto builtinCameraTransform = sceneWidget->builtinCameraTransform(); + builtinCameraTransform->setPosition(impl->cameraTransform->position()); + + SgCamera* builtinCamera = nullptr; + if(impl->currentCamera == impl->persCamera){ + auto builtinPersCamera = sceneWidget->builtinPerspectiveCamera(); + builtinPersCamera->setFieldOfView(impl->persCamera->fieldOfView()); + builtinCamera = builtinPersCamera; + } else if(impl->currentCamera == impl->orthoCamera){ + auto builtinOrthoCamera = sceneWidget->builtinOrthographicCamera(); + builtinOrthoCamera->setHeight(impl->orthoCamera->height()); + builtinCamera = builtinOrthoCamera; + } + + if(builtinCamera){ + builtinCamera->setNearClipDistance(impl->currentCamera->nearClipDistance()); + builtinCamera->setFarClipDistance(impl->currentCamera->farClipDistance()); + sceneWidget->renderer()->setCurrentCamera(builtinCamera); + builtinCamera->notifyUpdate(); + } +} + + +void CameraItem::onDoubleClicked() +{ + activateSceneViewBuiltinCameraWithCameraItemConfiguration(SceneView::lastFocusSceneView()); +} + + +void CameraItem::doPutProperties(PutPropertyFunction& putProperty) +{ + impl->doPutProperties(putProperty); +} + + +void CameraItem::Impl::doPutProperties(PutPropertyFunction& putProperty) +{ + putProperty(_("Camera type"), cameraType, + [&](int index){ return setCameraType(index); }); + putProperty(_("Field Of View"), degree(self->fieldOfView()), + [&](double fov){ return setFieldOfView(radian(fov)); } ); + putProperty(_("Near clip distance"), self->nearClipDistance(), + [&](double distance){ return self->setNearClipDistance(distance); }); + putProperty(_("Far clip distance"), self->farClipDistance(), + [&](double distance){ return self->setFarClipDistance(distance); } ); + putProperty(_("Interactive viewpoint change"), self->isInteractiveViewpointChangeEnabled(), + [&](bool on){ self->setInteractiveViewpointChangeEnabled(on); return true; }); +} + + +bool CameraItem::store(Archive& archive) +{ + archive.write("camera_type", impl->cameraType.selectedSymbol()); + + auto transform = impl->cameraTransform; + Isometry3 T = transform->T(); + write(archive, "translation", T.translation()); + writeDegreeAngleAxis(archive, "rotation", AngleAxis(T.linear())); + + archive.write("field_of_view", impl->persCamera->fieldOfView()); + archive.write("near_clip_distance", impl->persCamera->nearClipDistance()); + archive.write("far_clip_distance", impl->persCamera->farClipDistance()); + archive.write("interactive_viewpoint_change", isInteractiveViewpointChangeEnabled()); + + return true; +} + + +bool CameraItem::restore(const Archive& archive) +{ + string symbol; + if(archive.read({ "camera_type", "cameraType" }, symbol)){ + int index = impl->cameraType.index(symbol); + impl->setCameraType(index); + } + + auto transform = impl->cameraTransform; + Isometry3 T = Isometry3::Identity(); + Vector3 p; + if(read(archive, "translation", p)){ + T.translation() = p; + } + AngleAxis aa; + if(readDegreeAngleAxis(archive, "rotation", aa)){ + T.linear() = aa.toRotationMatrix(); + } + transform->setPosition(T); + + impl->setFieldOfView(archive.get({ "field_of_view", "fieldOfView" }, impl->persCamera->fieldOfView())); + + double nearDistance = + archive.get({ "near_clip_distance", "nearClipDistance" }, impl->persCamera->nearClipDistance()); + double farDistance = + archive.get({ "far_clip_distance", "farClipDistance" }, impl->persCamera->farClipDistance()); + impl->setClipDistances(nearDistance, farDistance); + + bool on; + if(archive.read("interactive_viewpoint_change", on)){ + setInteractiveViewpointChangeEnabled(on); + } + + transform->notifyUpdate(); + + return true; +} diff --git a/src/Base/CameraItem.h b/src/Base/CameraItem.h new file mode 100644 index 000000000..a6f7b27d1 --- /dev/null +++ b/src/Base/CameraItem.h @@ -0,0 +1,80 @@ +#ifndef CNOID_BASE_CAMERA_ITEM_H +#define CNOID_BASE_CAMERA_ITEM_H + +#include +#include +#include "exportdecl.h" + +namespace cnoid { + +class SgCamera; +class SgPerspectiveCamera; +class SgOrthographicCamera; +class SgPosTransform; +class InteractiveCameraTransform; +class SceneView; + +class CNOID_EXPORT CameraItem : public Item, public RenderableItem +{ +public: + static void initializeClass(ExtensionManager* ext); + static CameraItem* showDialogToCreateCameraItem(Item* parentItem); + + enum CameraType { + Perspective, + Orthographic, + NumCameraTypes, + }; + + CameraItem(); + virtual ~CameraItem(); + + virtual bool setName(const std::string& name) override; + + void setInteractiveViewpointChangeEnabled(bool on); + bool isInteractiveViewpointChangeEnabled() const; + void setCameraType(CameraType type); + CameraType cameraType() const; + SgPerspectiveCamera* perspectiveCamera(); + SgOrthographicCamera* orthographicCamera(); + SgCamera* currentCamera(); + SgPosTransform* cameraTransform(); + + double fieldOfView() const; + bool setFieldOfView(double fov); + + double nearClipDistance() const; + bool setNearClipDistance(double distance); + double farClipDistance() const; + bool setFarClipDistance(double distance); + bool setClipDistances(double nearDistance, double farDistance); + + virtual void showDialogToConfigureCamera(); + + void activateCameraInSceneView(SceneView* sceneView, bool on = true); + void activateSceneViewBuiltinCameraWithCameraItemConfiguration(SceneView* sceneView); + + // RenderableItem + virtual SgNode* getScene() override; + +protected: + CameraItem(const char* name, InteractiveCameraTransform* cameraTransform); + CameraItem(const CameraItem& org); + CameraItem(const CameraItem& org, InteractiveCameraTransform* newCameraTransform); + + virtual Item* doCloneItem(CloneMap* cloneMap) const override; + virtual void onDoubleClicked() override; + virtual void doPutProperties(PutPropertyFunction& putProperty) override; + virtual bool store(Archive& archive) override; + virtual bool restore(const Archive& archive) override; + +private: + class Impl; + Impl* impl; +}; + +typedef ref_ptr CameraItemPtr; + +} + +#endif diff --git a/src/Base/CaptureBar.cpp b/src/Base/CaptureBar.cpp index 849439c0d..19b0f7ff2 100644 --- a/src/Base/CaptureBar.cpp +++ b/src/Base/CaptureBar.cpp @@ -219,7 +219,7 @@ void CaptureBar::save(QWidget* widget, std::functionget("directory", QDir::currentPath().toStdString())); diff --git a/src/Base/CoordinateFrameItem.cpp b/src/Base/CoordinateFrameItem.cpp index 0c6f9d2f7..32e0fae8b 100644 --- a/src/Base/CoordinateFrameItem.cpp +++ b/src/Base/CoordinateFrameItem.cpp @@ -21,8 +21,7 @@ class FrameLocation : public LocationProxy FrameLocation(CoordinateFrameItem::Impl* impl); void updateLocationType(); - virtual Item* getCorrespondingItem() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; virtual std::string getName() const override; virtual Isometry3 getLocation() const override; virtual bool isLocked() const override; @@ -49,6 +48,7 @@ class CoordinateFrameItem::Impl bool isChangingCheckStatePassively; Impl(CoordinateFrameItem* self, CoordinateFrame* frame); + bool setName(const std::string& name); void onFrameUpdated(int flags); bool resetFrameId(const GeneralId& id); void onCheckToggled(bool on); @@ -91,8 +91,8 @@ CoordinateFrameItem::Impl::Impl(CoordinateFrameItem* self, CoordinateFrame* fram : self(self), frame(frame) { - self->setName(frame->id().label()); - + setName(frame->id().label()); + frameListItem = nullptr; frameList = nullptr; @@ -119,6 +119,47 @@ Item* CoordinateFrameItem::doCloneItem(CloneMap* cloneMap) const } +bool CoordinateFrameItem::setName(const std::string& name) +{ + return impl->setName(name); +} + + +bool CoordinateFrameItem::Impl::setName(const std::string& name) +{ + bool doSetName = false; + auto& id = frame->id(); + if(id.isInt()){ + try { + int newId = std::stoi(name); + if(newId >= 0){ + if(newId == id.toInt()){ + doSetName = true; + } else { + auto block = frameConnection.scopedBlock(); + doSetName = frame->resetId(newId); + } + } + } catch(...){ + + } + } else if(id.isString()){ + if(!name.empty()){ + if(name == id.toString()){ + doSetName = true; + } else { + auto block = frameConnection.scopedBlock(); + doSetName = frame->resetId(name); + } + } + } + if(doSetName){ + return self->Item::setName(name); + } + return false; +} + + std::string CoordinateFrameItem::displayName() const { if(impl->frameListItem){ @@ -378,11 +419,10 @@ void CoordinateFrameItem::setLocationLocked(bool on) FrameLocation::FrameLocation(CoordinateFrameItem::Impl* impl) - : LocationProxy(InvalidLocation), + : LocationProxy(impl->self, InvalidLocation), impl(impl) { - impl->self->sigNameChanged().connect( - [&](const std::string& /* oldName */){ notifyAttributeChange(); }); + setNameDependencyOnItemName(); } @@ -410,13 +450,7 @@ void FrameLocation::updateLocationType() } -Item* FrameLocation::getCorrespondingItem() -{ - return impl->self; -} - - -LocationProxyPtr FrameLocation::getParentLocationProxy() const +LocationProxyPtr FrameLocation::getParentLocationProxy() { if(impl->frame->isLocal()){ if(impl->frameListItem){ diff --git a/src/Base/CoordinateFrameItem.h b/src/Base/CoordinateFrameItem.h index 489ea6c89..1fd1fede1 100644 --- a/src/Base/CoordinateFrameItem.h +++ b/src/Base/CoordinateFrameItem.h @@ -24,6 +24,7 @@ class CNOID_EXPORT CoordinateFrameItem : public Item, public LocatableItem CoordinateFrameItem(CoordinateFrame* frame); virtual ~CoordinateFrameItem(); + virtual bool setName(const std::string& name) override; virtual std::string displayName() const override; CoordinateFrameListItem* frameListItem(); diff --git a/src/Base/DistanceMeasurementItem.cpp b/src/Base/DistanceMeasurementItem.cpp index 07325c7e1..2bc7bfc79 100644 --- a/src/Base/DistanceMeasurementItem.cpp +++ b/src/Base/DistanceMeasurementItem.cpp @@ -568,7 +568,7 @@ void DistanceMeasurementItem::Impl::calcShortestDistance() bool detected = false; for(auto& handlePair : handlePairs){ - threadPool->start([&](){ + threadPool->post([&](){ Vector3 p1, p2; auto distance = collisionDetectorDistanceAPI->detectDistance( handlePair.first, handlePair.second, p1, p2); diff --git a/src/Base/ExtensionManager.cpp b/src/Base/ExtensionManager.cpp index e751dfbae..2531362ea 100644 --- a/src/Base/ExtensionManager.cpp +++ b/src/Base/ExtensionManager.cpp @@ -235,6 +235,12 @@ void ExtensionManager::Impl::deleteManagedObjects() } +void ExtensionManager::deleteManagedObjects() +{ + impl->deleteManagedObjects(); +} + + void ExtensionManager::setProjectArchiver( const std::string& name, std::function storeFunction, diff --git a/src/Base/ExtensionManager.h b/src/Base/ExtensionManager.h index dc7d6f82b..766c3afbe 100644 --- a/src/Base/ExtensionManager.h +++ b/src/Base/ExtensionManager.h @@ -56,6 +56,8 @@ class CNOID_EXPORT ExtensionManager return pointer; } + void deleteManagedObjects(); + void setProjectArchiver( const std::string& name, std::function storeFunction, diff --git a/src/Base/FileDialog.cpp b/src/Base/FileDialog.cpp index a6d933b6c..517a8fedd 100644 --- a/src/Base/FileDialog.cpp +++ b/src/Base/FileDialog.cpp @@ -45,7 +45,7 @@ class FileDialog::Impl : public QFileDialog Signal sigAboutToFinish; Impl(FileDialog* self); - void updatePresetDirectories(); + void updatePresetDirectories(bool doSetCurrentDirectory); void onFilterSelected(const QString& selected); void onFinished(int result); void storeRecentDirectories(); @@ -110,13 +110,13 @@ FileDialog::~FileDialog() } -void FileDialog::updatePresetDirectories() +void FileDialog::updatePresetDirectories(bool doSetCurrentDirectory) { - impl->updatePresetDirectories(); + impl->updatePresetDirectories(doSetCurrentDirectory); } -void FileDialog::Impl::updatePresetDirectories() +void FileDialog::Impl::updatePresetDirectories(bool doSetCurrentDirectory) { QList urls; @@ -160,26 +160,28 @@ void FileDialog::Impl::updatePresetDirectories() setHistory(qhistory); } - bool directoryDetermined = false; - if(!isBeforeChoosingAnyFile){ - auto qhistory = history(); - if(!qhistory.empty()){ - setDirectory(qhistory.last()); - directoryDetermined = true; + if(doSetCurrentDirectory){ + bool directoryDetermined = false; + if(!isBeforeChoosingAnyFile){ + auto qhistory = history(); + if(!qhistory.empty()){ + setDirectory(qhistory.last()); + directoryDetermined = true; + } } - } - if(!directoryDetermined){ - auto projectDir = ProjectManager::instance()->currentProjectDirectory(); - if(!projectDir.empty()){ - setDirectory(projectDir.c_str()); - } else { + if(!directoryDetermined){ + auto projectDir = ProjectManager::instance()->currentProjectDirectory(); + if(!projectDir.empty()){ + setDirectory(projectDir.c_str()); + } else { #ifdef Q_OS_WIN32 - setDirectory(QDir::homePath() + "/Documents"); + setDirectory(QDir::homePath() + "/Documents"); #else - setDirectory(QDir::current()); + setDirectory(QDir::current()); #endif + } } - } + } } @@ -239,6 +241,27 @@ void FileDialog::selectNameFilter(int index) } +QString FileDialog::makeNameFilter(const std::string& caption, const std::vector& extensions) +{ + QString filter(caption.c_str()); + + if(extensions.empty()){ + filter += " (*)"; + } else { + QString prefix = " ("; + for(auto& ext : extensions){ + filter += prefix; + filter += "*."; + filter += ext.c_str(); + prefix = " "; + } + filter += ")"; + } + + return filter; +} + + SignalProxy FileDialog::sigAboutToFinish() { return impl->sigAboutToFinish; diff --git a/src/Base/FileDialog.h b/src/Base/FileDialog.h index 91f29e11e..cf644a58c 100644 --- a/src/Base/FileDialog.h +++ b/src/Base/FileDialog.h @@ -27,7 +27,7 @@ class CNOID_EXPORT FileDialog : public QDialog QDialog::setWindowTitle(title); } - void updatePresetDirectories(); + void updatePresetDirectories(bool doSetCurrentDirectory = true); bool selectFilePath(const std::string& filePath); void insertOptionPanel(QWidget* panel); @@ -62,6 +62,8 @@ class CNOID_EXPORT FileDialog : public QDialog // Util functions void selectNameFilter(int index); + static QString makeNameFilter(const std::string& caption, const std::vector& extensions); + private: class Impl; Impl* impl; diff --git a/src/Base/GeneralSceneFileImporterBase.cpp b/src/Base/GeneralSceneFileImporterBase.cpp index b86dfaebf..5cea605bb 100644 --- a/src/Base/GeneralSceneFileImporterBase.cpp +++ b/src/Base/GeneralSceneFileImporterBase.cpp @@ -1,13 +1,9 @@ #include "GeneralSceneFileImporterBase.h" +#include "GeneralSceneFileLoadDialog.h" #include -#include -#include -#include #include #include #include -#include -#include #include "gettext.h" using namespace std; @@ -23,18 +19,9 @@ class GeneralSceneFileImporterBase::Impl static int sharedImplCounter; unique_ptr sceneLoader; - QWidget* optionPanel; + GeneralSceneFileLoadDialog::OptionSet optionSet; - Selection lengthUnitHint; - ComboBox* unitCombo; - - Selection upperAxisHint; - ComboBox* axisCombo; - - Impl(); - ~Impl(); SgNode* loadScene(GeneralSceneFileImporterBase* self, const std::string& filename); - void createOptionPanel(); }; GeneralSceneFileImporterBase::Impl* GeneralSceneFileImporterBase::Impl::sharedImpl = nullptr; @@ -68,23 +55,6 @@ GeneralSceneFileImporterBase::GeneralSceneFileImporterBase() } -GeneralSceneFileImporterBase::Impl::Impl() - : lengthUnitHint(SceneLoader::NumLengthUnitTypes), - upperAxisHint(SceneLoader::NumUpperAxisTypes, CNOID_GETTEXT_DOMAIN_NAME) -{ - lengthUnitHint.setSymbol(SceneLoader::Meter, "meter"); - lengthUnitHint.setSymbol(SceneLoader::Millimeter, "millimeter"); - lengthUnitHint.setSymbol(SceneLoader::Inch, "inch"); - - upperAxisHint.setSymbol(SceneLoader::Z_Upper, "Z"); - upperAxisHint.setSymbol(SceneLoader::Y_Upper, "Y"); - - optionPanel = nullptr; - unitCombo = nullptr; - axisCombo = nullptr; -} - - GeneralSceneFileImporterBase::~GeneralSceneFileImporterBase() { if(--Impl::sharedImplCounter == 0){ @@ -94,14 +64,6 @@ GeneralSceneFileImporterBase::~GeneralSceneFileImporterBase() } -GeneralSceneFileImporterBase::Impl::~Impl() -{ - if(optionPanel){ - delete optionPanel; - } -} - - SgNode* GeneralSceneFileImporterBase::loadScene(const std::string& filename) { return impl->loadScene(this, filename); @@ -115,8 +77,8 @@ SgNode* GeneralSceneFileImporterBase::Impl::loadScene(GeneralSceneFileImporterBa sceneLoader->setMessageSink(self->os()); } - sceneLoader->setLengthUnitHint(static_cast(lengthUnitHint.which())); - sceneLoader->setUpperAxisHint(static_cast(upperAxisHint.which())); + sceneLoader->setLengthUnitHint(optionSet.lengthUnitHint()); + sceneLoader->setUpperAxisHint(optionSet.upperAxisHint()); bool isSupported; SgNode* scene = sceneLoader->load(filename, isSupported); @@ -147,41 +109,28 @@ bool GeneralSceneFileImporterBase::saveScene(SgNode* /* scene */, const std::str void GeneralSceneFileImporterBase::resetOptions() { - impl->lengthUnitHint.select(SceneLoader::Meter); - impl->upperAxisHint.select(SceneLoader::Z_Upper); + impl->optionSet.resetOptions(); } void GeneralSceneFileImporterBase::storeOptions(Mapping* archive) { - if(!impl->lengthUnitHint.is(SceneLoader::Meter)){ - archive->write("meshLengthUnitHint", impl->lengthUnitHint.selectedSymbol()); - } - if(!impl->upperAxisHint.is(SceneLoader::Z_Upper)){ - archive->write("meshUpperAxisHint", impl->upperAxisHint.selectedSymbol()); - } + impl->optionSet.storeOptions(archive); } bool GeneralSceneFileImporterBase::restoreOptions(const Mapping* archive) { - string value; - if(archive->read("meshLengthUnitHint", value)){ - impl->lengthUnitHint.select(value); - } - if(archive->read("meshUpperAxisHint", value)){ - impl->upperAxisHint.select(value); - } - return true; + return impl->optionSet.restoreOptions(archive); } void GeneralSceneFileImporterBase::setCurrentLengthUnitHint(LengthUnitType unitType) { switch(unitType){ - case Meter: impl->lengthUnitHint.select(SceneLoader::Meter); break; - case Millimeter: impl->lengthUnitHint.select(SceneLoader::Millimeter); break; - case Inch: impl->lengthUnitHint.select(SceneLoader::Inch); break; + case Meter: impl->optionSet.setLengthUnitHint(SceneLoader::Meter); break; + case Millimeter: impl->optionSet.setLengthUnitHint(SceneLoader::Millimeter); break; + case Inch: impl->optionSet.setLengthUnitHint(SceneLoader::Inch); break; default: break; } } @@ -189,53 +138,5 @@ void GeneralSceneFileImporterBase::setCurrentLengthUnitHint(LengthUnitType unitT QWidget* GeneralSceneFileImporterBase::getOptionPanelForLoading() { - if(!impl->optionPanel){ - impl->createOptionPanel(); - } - - impl->unitCombo->blockSignals(true); - impl->unitCombo->setCurrentIndex(impl->lengthUnitHint.which()); - impl->unitCombo->blockSignals(false); - - impl->axisCombo->blockSignals(true); - impl->axisCombo->setCurrentIndex(impl->upperAxisHint.which()); - impl->axisCombo->blockSignals(false); - - return impl->optionPanel; -} - - -void GeneralSceneFileImporterBase::Impl::createOptionPanel() -{ - optionPanel = new QWidget; - auto hbox = new QHBoxLayout; - hbox->setContentsMargins(0, 0, 0, 0); - optionPanel->setLayout(hbox); - - hbox->addWidget(new QLabel(_("[ Mesh import hints ]"))); - - hbox->addWidget(new QLabel(_("Unit:"))); - unitCombo = new ComboBox; - unitCombo->addItem(_("Meter")); - unitCombo->addItem(_("Millimeter")); - unitCombo->addItem(_("Inch")); - unitCombo->sigCurrentIndexChanged().connect( - [this](int index){ lengthUnitHint.select(index); }); - hbox->addWidget(unitCombo); - - hbox->addWidget(new QLabel(_("Upper axis:"))); - axisCombo = new ComboBox; - for(int i=0; i < SceneLoader::NumUpperAxisTypes; ++i){ - axisCombo->addItem(upperAxisHint.label(i)); - } - axisCombo->sigCurrentIndexChanged().connect( - [this](int index){ upperAxisHint.select(index); }); - hbox->addWidget(axisCombo); -} - - -void GeneralSceneFileImporterBase::fetchOptionPanelForLoading() -{ - impl->lengthUnitHint.select(impl->unitCombo->currentIndex()); - impl->upperAxisHint.select(impl->axisCombo->currentIndex()); + return impl->optionSet.panel(); } diff --git a/src/Base/GeneralSceneFileImporterBase.h b/src/Base/GeneralSceneFileImporterBase.h index d0f0a8ce1..04fd5d85b 100644 --- a/src/Base/GeneralSceneFileImporterBase.h +++ b/src/Base/GeneralSceneFileImporterBase.h @@ -31,7 +31,6 @@ class CNOID_EXPORT GeneralSceneFileImporterBase : public ItemFileIO virtual void storeOptions(Mapping* archive) override; virtual bool restoreOptions(const Mapping* archive) override; virtual QWidget* getOptionPanelForLoading() override; - virtual void fetchOptionPanelForLoading() override; private: class Impl; diff --git a/src/Base/GeneralSceneFileLoadDialog.cpp b/src/Base/GeneralSceneFileLoadDialog.cpp new file mode 100644 index 000000000..3e36a9d2d --- /dev/null +++ b/src/Base/GeneralSceneFileLoadDialog.cpp @@ -0,0 +1,230 @@ +#include "GeneralSceneFileLoadDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include "gettext.h" + +using namespace std; +using namespace cnoid; + +namespace { + +SceneLoader::LengthUnitType defaultLengthUnitHint = SceneLoader::Meter; + +} + +namespace cnoid { + +class GeneralSceneFileLoadDialog::OptionSet::Panel : public QWidget +{ +public: + ComboBox unitCombo; + ComboBox axisCombo; + + Panel(OptionSet* optionSet); +}; + +} + + +void GeneralSceneFileLoadDialog::setDefaultLengthUnitHint(SceneLoader::LengthUnitType hint) +{ + defaultLengthUnitHint = hint; +} + + +GeneralSceneFileLoadDialog::GeneralSceneFileLoadDialog() +{ + initialize(); +} + + +GeneralSceneFileLoadDialog::GeneralSceneFileLoadDialog(QWidget* parent, Qt::WindowFlags f) + : FileDialog(parent, f) +{ + initialize(); +} + + +void GeneralSceneFileLoadDialog::initialize() +{ + setWindowTitle(_("Load a scene file")); + fileTypeCaption_ = _("Scene file"); + setViewMode(QFileDialog::List); + setFileMode(QFileDialog::ExistingFile); + setLabelText(QFileDialog::Accept, _("Load")); + setLabelText(QFileDialog::Reject, _("Cancel")); + + insertOptionPanel(optionSet.panel()); +} + + +void GeneralSceneFileLoadDialog::setFileTypeCaption(const std::string& caption) +{ + fileTypeCaption_ = caption; +} + + +int GeneralSceneFileLoadDialog::exec() +{ + loadedScene_.reset(); + setNameFilter(makeNameFilter(fileTypeCaption_, SceneLoader::availableFileExtensions())); + auto mout = MessageOut::master(); + + int result = FileDialog::exec(); + if(result == QDialog::Accepted){ + if(!sceneLoader){ + sceneLoader.reset(new SceneLoader); + sceneLoader->setMessageSink(mout->cout()); + } + string filename = selectedFiles().at(0).toStdString(); + + sceneLoader->setLengthUnitHint(optionSet.lengthUnitHint()); + sceneLoader->setUpperAxisHint(optionSet.upperAxisHint()); + + mout->put(formatR(_("Loading the file \"{0}\" ..."), filename)); + loadedScene_ = sceneLoader->load(filename); + if(loadedScene_){ + mout->putln(_(" OK!")); + } else { + mout->putErrorln(_(" Failed.")); + } + } + + return result; +} + + +void GeneralSceneFileLoadDialog::clearLoadedScene() +{ + loadedScene_.reset(); +} + + +GeneralSceneFileLoadDialog::OptionSet::OptionSet() +{ + resetOptions(); + panel_ = nullptr; +} + + +GeneralSceneFileLoadDialog::OptionSet::~OptionSet() +{ + if(panel_){ + delete panel_; + panel_ = nullptr; + } +} + + +void GeneralSceneFileLoadDialog::OptionSet::resetOptions() +{ + lengthUnitHint_ = defaultLengthUnitHint; + upperAxisHint_ = SceneLoader::Z_Upper; +} + + +void GeneralSceneFileLoadDialog::OptionSet::setLengthUnitHint(AbstractSceneLoader::LengthUnitType hint) +{ + if(panel_){ + auto& combo = panel_->unitCombo; + combo.setCurrentIndex(combo.findData(hint)); + } else { + lengthUnitHint_ = hint; + } +} + + +void GeneralSceneFileLoadDialog::OptionSet::setUpperAxisHint(SceneLoader::UpperAxisType hint) +{ + if(panel_){ + auto& combo = panel_->axisCombo; + combo.setCurrentIndex(combo.findData(hint)); + } else { + upperAxisHint_ = hint; + } +} + + +void GeneralSceneFileLoadDialog::OptionSet::storeOptions(Mapping* archive) +{ + if(lengthUnitHint_ != SceneLoader::Meter){ + archive->write( + "length_unit_hint", + (lengthUnitHint_ == SceneLoader::Millimeter) ? "millimeter" : "inch"); + } + + if(upperAxisHint_ != SceneLoader::Z_Upper){ + archive->write("upper_axis_hint", "Y"); + } +} + + +bool GeneralSceneFileLoadDialog::OptionSet::restoreOptions(const Mapping* archive) +{ + string symbol; + + lengthUnitHint_ = SceneLoader::Meter; + if(archive->read({ "length_unit_hint", "meshLengthUnitHint" }, symbol)){ + if(symbol == "millimeter"){ + setLengthUnitHint(SceneLoader::Millimeter); + } else if(symbol == "inch"){ + setLengthUnitHint(SceneLoader::Inch); + } + } + + upperAxisHint_ = SceneLoader::Z_Upper; + if(archive->read({ "upper_axis_hint", "meshUpperAxisHint" }, symbol)){ + if(symbol == "Y"){ + setUpperAxisHint(SceneLoader::Y_Upper); + } + } + + return true; +} + + +QWidget* GeneralSceneFileLoadDialog::OptionSet::panel() +{ + if(!panel_){ + panel_ = new Panel(this); + } + return panel_; +} + + +GeneralSceneFileLoadDialog::OptionSet::Panel::Panel(OptionSet* optionSet) +{ + auto hbox = new QHBoxLayout; + hbox->setContentsMargins(0, 0, 0, 0); + setLayout(hbox); + + hbox->addWidget(new QLabel(_("[ import hints ]"))); + + hbox->addWidget(new QLabel(_("Unit:"))); + unitCombo.addItem(_("Meter"), SceneLoader::Meter); + unitCombo.addItem(_("Millimeter"), SceneLoader::Millimeter); + unitCombo.addItem(_("Inch"), SceneLoader::Inch); + unitCombo.setCurrentIndex(unitCombo.findData(optionSet->lengthUnitHint())); + unitCombo.sigCurrentIndexChanged().connect( + [this, optionSet](int index){ + optionSet->lengthUnitHint_ = + static_cast(unitCombo.itemData(index).toInt()); + }); + hbox->addWidget(&unitCombo); + + hbox->addWidget(new QLabel(_("Upper axis:"))); + axisCombo.addItem("Z", SceneLoader::Z_Upper); + axisCombo.addItem("Y", SceneLoader::Y_Upper); + axisCombo.setCurrentIndex(axisCombo.findData(optionSet->upperAxisHint())); + axisCombo.sigCurrentIndexChanged().connect( + [this, optionSet](int index){ + optionSet->upperAxisHint_ = + static_cast(unitCombo.itemData(index).toInt()); + }); + hbox->addWidget(&axisCombo); +} diff --git a/src/Base/GeneralSceneFileLoadDialog.h b/src/Base/GeneralSceneFileLoadDialog.h new file mode 100644 index 000000000..c675766fb --- /dev/null +++ b/src/Base/GeneralSceneFileLoadDialog.h @@ -0,0 +1,64 @@ +#ifndef CNOID_BASE_GENERAL_SCENE_FILE_LOAD_DIALOG_H +#define CNOID_BASE_GENERAL_SCENE_FILE_LOAD_DIALOG_H + +#include "FileDialog.h" +#include +#include +#include +#include "exportdecl.h" + +namespace cnoid { + +class Mapping; + +class CNOID_EXPORT GeneralSceneFileLoadDialog : public FileDialog +{ +public: + // For the application customization + static void setDefaultLengthUnitHint(SceneLoader::LengthUnitType hint); + + GeneralSceneFileLoadDialog(); + GeneralSceneFileLoadDialog(QWidget* parent, Qt::WindowFlags f = Qt::WindowFlags()); + void setFileTypeCaption(const std::string& caption); + virtual int exec() override; + SgNode* loadedScene() const { return loadedScene_; } + void clearLoadedScene(); + + class OptionSet + { + public: + OptionSet(); + virtual ~OptionSet(); + void resetOptions(); + + void setLengthUnitHint(SceneLoader::LengthUnitType hint); + SceneLoader::LengthUnitType lengthUnitHint() const { return lengthUnitHint_; } + + void setUpperAxisHint(SceneLoader::UpperAxisType hint); + SceneLoader::UpperAxisType upperAxisHint() const { return upperAxisHint_; } + + void storeOptions(Mapping* archive); + bool restoreOptions(const Mapping* archive); + + QWidget* panel(); + + private: + SceneLoader::LengthUnitType lengthUnitHint_; + SceneLoader::UpperAxisType upperAxisHint_; + + class Panel; + Panel* panel_; + }; + +private: + std::string fileTypeCaption_; + SgNodePtr loadedScene_; + std::unique_ptr sceneLoader; + OptionSet optionSet; + + void initialize(); +}; + +} + +#endif diff --git a/src/Base/Item.cpp b/src/Base/Item.cpp index 03d15b3b7..b0703dbab 100644 --- a/src/Base/Item.cpp +++ b/src/Base/Item.cpp @@ -86,6 +86,7 @@ class Item::Impl Signal sigAnyCheckToggled; Signal sigLogicalSumOfAllChecksToggled; map> checkIdToSignalMap; + Signal sigContinuousUpdateStateChanged; // for file overwriting management, mainly accessed by ItemManager::Impl std::string filePath; @@ -93,6 +94,7 @@ class Item::Impl MappingPtr fileOptions; std::time_t fileModificationTime; bool isConsistentWithFile; + int fileConsistencyId; bool isConsistentWithProjectArchive; Impl(Item* self); @@ -121,7 +123,7 @@ class Item::Impl Item* newParentItem, Item* newNextItem, bool isManualOperation, vector>& callbacksWhenAdded); bool checkNewTreePositionAcceptanceIter(bool isManualOperation, vector>& callbacksWhenAdded); void collectSubTreeItems(vector& items, Item* item); - void callFuncOnConnectedToRoot(); + void callFuncOnConnectedToRoot(RootItem* rootItem); void justRemoveSelfFromParent(); void doRemoveFromParentItem(bool isMoving, bool isParentBeingDeleted); void notifySubTreeItemsOfTreePositionChange(Item* prevParentItem, Item* prevNextSibling); @@ -129,10 +131,11 @@ class Item::Impl Item* topItem, Item* prevTopParentItem, Item* topPathChangedItem, bool isPathChanged = false); void addToItemsToEmitSigSubTreeChanged(); static void emitSigSubTreeChanged(); - void emitSigDisconnectedFromRootForSubTree(); + void emitSigDisconnectedFromRootForSubTree(RootItem* rootItem); void traverse(Item* item, const std::function& callback); void removeAddon(ItemAddon* addon, bool isMoving); ItemAddon* createAddon(const std::type_info& type); + void notifyContinuousUpdateStateChangeRecursively(bool on); static void clearItemReplacementMaps(); }; @@ -168,6 +171,7 @@ Item::Item(const Item& org) Item::Impl::Impl(Item* self, const Impl& org) : self(self), + checkStates(org.checkStates), displayNameModifier(org.displayNameModifier) { initialize(); @@ -184,6 +188,7 @@ Item::Impl::Impl(Item* self, const Impl& org) } fileModificationTime = org.fileModificationTime; isConsistentWithFile = org.isConsistentWithFile; + fileConsistencyId = org.fileConsistencyId; } } @@ -197,10 +202,12 @@ void Item::Impl::initialize() self->lastChild_ = nullptr; self->numChildren_ = 0; self->attributes_ = 0; + self->continuousUpdateCounter = 0; self->isSelected_ = false; fileModificationTime = 0; - isConsistentWithFile = false; + isConsistentWithFile = true; + fileConsistencyId = 0; isConsistentWithProjectArchive = false; } @@ -655,7 +662,11 @@ Item* Item::localRootItem() const bool Item::addChildItem(Item* item, bool isManualOperation) { - return impl->doInsertChildItem(item, nullptr, isManualOperation); + if(item->parentItem() == this){ + return true; // Already inserted + } else { + return impl->doInsertChildItem(item, nullptr, isManualOperation); + } } @@ -674,7 +685,12 @@ bool Item::insertChildItem(Item* item, Item* nextItem, bool isManualOperation) bool Item::addSubItem(Item* item) { item->setAttribute(SubItem); - return impl->doInsertChildItem(item, nullptr, false); + + if(item->parentItem() == this){ + return true; // Already inserted + } else { + return impl->doInsertChildItem(item, nullptr, false); + } } @@ -769,7 +785,7 @@ bool Item::Impl::doInsertChildItem(ItemPtr item, Item* newNextItem, bool isManua committed on October 31, 2018, and the reason why the order was changed at that time is not clear. */ if(!isMoving){ - item->impl->callFuncOnConnectedToRoot(); + item->impl->callFuncOnConnectedToRoot(rootItem); } item->impl->notifySubTreeItemsOfTreePositionChange(prevParentItem, prevNextSibling); @@ -910,11 +926,14 @@ void Item::Impl::collectSubTreeItems(vector& items, Item* item) } -void Item::Impl::callFuncOnConnectedToRoot() +void Item::Impl::callFuncOnConnectedToRoot(RootItem* rootItem) { self->onConnectedToRoot(); + if(self->isContinuousUpdateState()){ + rootItem->incrementContinuousUpdateStateItemRef(); + } for(Item* child = self->childItem(); child; child = child->nextItem()){ - child->impl->callFuncOnConnectedToRoot(); + child->impl->callFuncOnConnectedToRoot(rootItem); } } @@ -982,7 +1001,7 @@ void Item::Impl::doRemoveFromParentItem(bool isMoving, bool isParentBeingDeleted rootItem->notifyEventOnSubTreeRemoved(self, isMoving); if(!isMoving){ notifySubTreeItemsOfTreePositionChange(prevParent, prevNextSibling); - emitSigDisconnectedFromRootForSubTree(); + emitSigDisconnectedFromRootForSubTree(rootItem); } } @@ -1152,11 +1171,16 @@ void Item::Impl::emitSigSubTreeChanged() } -void Item::Impl::emitSigDisconnectedFromRootForSubTree() +void Item::Impl::emitSigDisconnectedFromRootForSubTree(RootItem* rootItem) { for(Item* child = self->childItem(); child; child = child->nextItem()){ - child->impl->emitSigDisconnectedFromRootForSubTree(); + child->impl->emitSigDisconnectedFromRootForSubTree(rootItem); + } + + if(self->isContinuousUpdateState()){ + rootItem->decrementContinuousUpdateStateItemRef(); } + sigDisconnectedFromRoot(); self->onDisconnectedFromRoot(); @@ -1520,15 +1544,87 @@ std::vector Item::addons() const } -bool Item::load(const std::string& filename, const std::string& format, const Mapping* options) +Item::ContinuousUpdateEntry Item::startContinuousUpdate() +{ + return new ContinuousUpdateRef(this); +} + + +bool Item::isContinuousUpdateStateSubTree() const +{ + if(isContinuousUpdateState()){ + return true; + } + if(parent_){ + return parent_->isContinuousUpdateStateSubTree(); + } + return false; +} + + +SignalProxy Item::sigContinuousUpdateStateChanged() +{ + return impl->sigContinuousUpdateStateChanged; +} + + +void Item::Impl::notifyContinuousUpdateStateChangeRecursively(bool on) +{ + for(auto child = self->childItem(); child; child = child->nextItem()){ + if(!child->isContinuousUpdateState()){ + child->impl->sigContinuousUpdateStateChanged(on); + child->impl->notifyContinuousUpdateStateChangeRecursively(on); + } + } +} + + +Item::ContinuousUpdateRef::ContinuousUpdateRef(Item* item) + : itemRef(item) +{ + if(item->continuousUpdateCounter == 0){ + bool hasOnInUpperNodes = item->isContinuousUpdateStateSubTree(); + ++item->continuousUpdateCounter; + item->impl->sigContinuousUpdateStateChanged(true); + if(!hasOnInUpperNodes){ + item->impl->notifyContinuousUpdateStateChangeRecursively(true); + } + if(auto rootItem = item->findRootItem()){ + rootItem->incrementContinuousUpdateStateItemRef(); + } + } +} + + +Item::ContinuousUpdateRef::~ContinuousUpdateRef() +{ + if(auto item = itemRef.lock()){ + if(item->continuousUpdateCounter == 1){ + bool hasOnInUpperNodes = item->isContinuousUpdateStateSubTree(); + --item->continuousUpdateCounter; + item->impl->sigContinuousUpdateStateChanged(false); + if(!hasOnInUpperNodes){ + item->impl->notifyContinuousUpdateStateChangeRecursively(false); + } + if(auto rootItem = item->findRootItem()){ + rootItem->decrementContinuousUpdateStateItemRef(); + } + } + } +} + + +bool Item::load +(const std::string& filename, const std::string& format, const Mapping* options, MessageOut* mout) { - return ItemManager::loadItem(this, filename, parentItem(), format, options); + return ItemManager::loadItem(this, filename, parentItem(), format, options, mout); } -bool Item::load(const std::string& filename, Item* parent, const std::string& format, const Mapping* options) +bool Item::load +(const std::string& filename, Item* parent, const std::string& format, const Mapping* options, MessageOut* mout) { - return ItemManager::loadItem(this, filename, parent, format, options); + return ItemManager::loadItem(this, filename, parent, format, options, mout); } @@ -1544,9 +1640,9 @@ bool Item::isFileSavable() const } -bool Item::save(const std::string& filename, const std::string& format, const Mapping* options) +bool Item::save(const std::string& filename, const std::string& format, const Mapping* options, MessageOut* mout) { - return ItemManager::saveItem(this, filename, format, options); + return ItemManager::saveItem(this, filename, format, options, mout); } @@ -1556,16 +1652,15 @@ bool Item::saveWithFileDialog() } -bool Item::overwriteOrSaveWithDialog(bool forceOverwrite, const std::string& format) +bool Item::overwrite(bool forceOverwrite, const std::string& format, time_t cutoffTime, MessageOut* mout) { - return ItemManager::overwriteItemOrSaveItemWithDialog(this, forceOverwrite, format); + return ItemManager::overwriteItem(this, forceOverwrite, format, false, cutoffTime, mout); } -bool Item::overwrite(bool forceOverwrite, const std::string& format) +bool Item::overwriteOrSaveWithDialog(bool forceOverwrite, const std::string& format) { - // The fourth argument is currently true for the backward compatibility. - return ItemManager::overwriteItem(this, forceOverwrite, format, true); + return ItemManager::overwriteItemOrSaveItemWithDialog(this, forceOverwrite, format); } @@ -1587,6 +1682,12 @@ const std::string& Item::fileFormat() const } +Mapping* Item::fileOptions() +{ + return impl->fileOptions; +} + + const Mapping* Item::fileOptions() const { return impl->fileOptions; @@ -1619,6 +1720,12 @@ bool Item::isConsistentWithFile() const } +int Item::fileConsistencyId() const +{ + return impl->fileConsistencyId; +} + + void Item::setConsistentWithFile(bool isConsistent) { impl->isConsistentWithFile = isConsistent; @@ -1631,11 +1738,13 @@ void Item::setConsistentWithFile(bool isConsistent) void Item::suggestFileUpdate() { impl->isConsistentWithFile = false; + impl->fileConsistencyId++; impl->isConsistentWithProjectArchive = false; } -void Item::updateFileInformation(const std::string& filename, const std::string& format, Mapping* options) +void Item::updateFileInformation +(const std::string& filename, const std::string& format, Mapping* options, bool doNotify) { filesystem::path fpath(fromUTF8(filename)); if(filesystem::exists(fpath)){ @@ -1650,7 +1759,9 @@ void Item::updateFileInformation(const std::string& filename, const std::string& impl->fileFormat = format; impl->fileOptions = options; - notifyUpdate(); + if(doNotify){ + notifyUpdate(); + } } diff --git a/src/Base/Item.h b/src/Base/Item.h index 28b4db3e1..23b319936 100644 --- a/src/Base/Item.h +++ b/src/Base/Item.h @@ -21,6 +21,7 @@ class ExtensionManager; class PutPropertyFunction; class EditRecord; class ItemTreeWidget; +class MessageOut; class CNOID_EXPORT Item : public ClonableReferenced { @@ -465,6 +466,26 @@ class CNOID_EXPORT Item : public ClonableReferenced std::vector addons(); std::vector addons() const; + class ContinuousUpdateRef : public Referenced + { + private: + ContinuousUpdateRef(Item* item); + ~ContinuousUpdateRef(); + weak_ref_ptr itemRef; + friend class Item; + }; + typedef ref_ptr ContinuousUpdateEntry; + + ContinuousUpdateEntry startContinuousUpdate(); + bool isContinuousUpdateState() const { return continuousUpdateCounter > 0; } + bool isContinuousUpdateStateSubTree() const; + + /** + \note The sigUpdated signal is not emitted when the continuous update state is changed + becasue the state is not a permenent one. + */ + SignalProxy sigContinuousUpdateStateChanged(); + /** This function loads the data of the item from a file by using a registered FileIO object. To make this function available, a FileIO object must be registered to an ItemManager @@ -472,7 +493,7 @@ class CNOID_EXPORT Item : public ClonableReferenced */ bool load( const std::string& filename, const std::string& format = std::string(), - const Mapping* options = nullptr); + const Mapping* options = nullptr, MessageOut* mout = nullptr); /** An overload version of the load function. @@ -481,7 +502,7 @@ class CNOID_EXPORT Item : public ClonableReferenced */ bool load( const std::string& filename, Item* parent, const std::string& format = std::string(), - const Mapping* options = nullptr); + const Mapping* options = nullptr, MessageOut* mout = nullptr); bool isFileSavable() const; @@ -490,35 +511,39 @@ class CNOID_EXPORT Item : public ClonableReferenced To make this function available, a FileIO object must be registered to an ItemManager in advance with its registerFileIO function. */ - bool save(const std::string& filename, const std::string& format = std::string(), - const Mapping* options = nullptr); + bool save( + const std::string& filename, const std::string& format = std::string(), const Mapping* options = nullptr, + MessageOut* mout = nullptr); bool saveWithFileDialog(); /** - This function tries to save the data of the item to the file from which the data of the item has - been loaded. If the data has not been loaded from a file, a file save dialog for the item is shown. + This function tries to overwrite the external data file of the item. + If the data has not been loaded from a file, the functions do nothing and returns false. */ - bool overwriteOrSaveWithDialog(bool forceOverwrite = false, const std::string& format = std::string()); + bool overwrite( + bool forceOverwrite = false, const std::string& format = std::string(), time_t cutoffTime = 0, + MessageOut* mout = nullptr); /** - What this funtions does is currently same as the overwriteOrSaveWithDialog function for the backward - compatibility. This function will be removed in a future version, and will be redefined as an - overwrite-only function. + This function tries to save the data of the item to the file from which the data of the item has + been loaded. If the data has not been loaded from a file, a file save dialog for the item is shown. */ - [[deprecated("Use the overwriteOrSaveWithDialog function")]] - bool overwrite(bool forceOverwrite = false, const std::string& format = std::string()); + bool overwriteOrSaveWithDialog(bool forceOverwrite = false, const std::string& format = std::string()); //! Full path file name const std::string& filePath() const; //! File name without the directory std::string fileName() const; const std::string& fileFormat() const; + Mapping* fileOptions(); const Mapping* fileOptions() const; std::time_t fileModificationTime() const; bool isConsistentWithFile() const; + int fileConsistencyId() const; - void updateFileInformation(const std::string& filename, const std::string& format, Mapping* options = nullptr); + void updateFileInformation( + const std::string& filename, const std::string& format, Mapping* options = nullptr, bool doNotify = true); void setConsistentWithFile(bool isConsistent); void suggestFileUpdate(); @@ -617,6 +642,7 @@ class CNOID_EXPORT Item : public ClonableReferenced Item* lastChild_; int numChildren_; unsigned int attributes_; + int continuousUpdateCounter; std::string name_; bool isSelected_; diff --git a/src/Base/ItemFileDialog.cpp b/src/Base/ItemFileDialog.cpp index 28b2de1ef..52208e096 100644 --- a/src/Base/ItemFileDialog.cpp +++ b/src/Base/ItemFileDialog.cpp @@ -149,7 +149,7 @@ ItemList ItemFileDialog::Impl::loadItems(Item* parentItem, bool doAddition self->setLabelText(QFileDialog::Accept, _("Import")); } - self->updatePresetDirectories(); + self->updatePresetDirectories(true); if(self->exec() == QDialog::Accepted){ @@ -247,7 +247,7 @@ bool ItemFileDialog::Impl::saveItem(Item* item) self->setLabelText(QFileDialog::Accept, _("Save")); self->setFileMode(QFileDialog::AnyFile); - self->updatePresetDirectories(); + self->updatePresetDirectories(true); bool selected = false; filesystem::path filePath(fromUTF8(item->filePath())); @@ -462,25 +462,3 @@ bool ItemFileDialog::Impl::onFileDialogAboutToFinish(int result) return finished; } - - -QString ItemFileDialog::makeNameFilter -(const std::string& caption, const std::vector& extensions) -{ - QString filter(caption.c_str()); - - if(extensions.empty()){ - filter += " (*)"; - } else { - QString prefix = " ("; - for(auto& ext : extensions){ - filter += prefix; - filter += "*."; - filter += ext.c_str(); - prefix = " "; - } - filter += ")"; - } - - return filter; -} diff --git a/src/Base/ItemFileDialog.h b/src/Base/ItemFileDialog.h index cdc41c4d7..b8074c201 100644 --- a/src/Base/ItemFileDialog.h +++ b/src/Base/ItemFileDialog.h @@ -31,9 +31,6 @@ class CNOID_EXPORT ItemFileDialog : public FileDialog void setExportMode(bool on = true); bool saveItem(Item* item); - static QString makeNameFilter( - const std::string& caption, const std::vector& extensions); - private: void setRegisteredFileIOsFor_(const std::type_info& type); diff --git a/src/Base/ItemFileIO.cpp b/src/Base/ItemFileIO.cpp index 7ab7ba471..7d622b49e 100644 --- a/src/Base/ItemFileIO.cpp +++ b/src/Base/ItemFileIO.cpp @@ -1,9 +1,9 @@ #include "ItemFileIO.h" #include "ItemManager.h" -#include "MessageView.h" #include #include #include +#include #include #include #include @@ -32,8 +32,7 @@ class ItemFileIO::Impl int currentInvocationType; Item* parentItem; Item* actuallyLoadedItem; - std::ostream* os; - MessageView* mv; + MessageOutPtr mout; std::string errorMessage; std::time_t lastSelectedTimeInLoadDialog; std::time_t lastSelectedTimeInSaveDialog; @@ -68,8 +67,7 @@ ItemFileIO::Impl::Impl(ItemFileIO* self, const std::string& format, int api) isItemNameUpdateInSavingEnabled = true; currentInvocationType = Direct; parentItem = nullptr; - mv = MessageView::instance(); - os = &mv->cout(true); + mout = MessageOut::master(); lastSelectedTimeInLoadDialog = 0; lastSelectedTimeInSaveDialog = 0; } @@ -107,8 +105,7 @@ ItemFileIO::Impl::Impl(ItemFileIO* self, const Impl& org) { currentInvocationType = Direct; parentItem = nullptr; - mv = MessageView::instance(); - os = &mv->cout(true); + mout = org.mout; } @@ -388,15 +385,14 @@ bool ItemFileIO::Impl::loadItem } this->parentItem = parentItem; - mv->notify(formatR(_("Loading {0} \"{1}\""), caption, filename)); - mv->flush(); + mout->notify(formatR(_("Loading {0} \"{1}\""), caption, filename)); actuallyLoadedItem = item; bool loaded = self->load(item, filename); - mv->flush(); + mout->flush(); if(!loaded){ - mv->put(_(" -> failed.\n"), MessageView::Highlight); + mout->putErrorln(_(" -> failed.")); } else { if(item->name().empty()){ item->setName(toUTF8(filesystem::path(fromUTF8(filename)).stem().string())); @@ -419,9 +415,8 @@ bool ItemFileIO::Impl::loadItem parentItem->insertChild(nextItem, item, true); } - mv->put(_(" -> ok!\n")); + mout->putln(_(" -> ok!")); } - mv->flush(); this->parentItem = nullptr; actuallyLoadedItem = nullptr; @@ -481,19 +476,18 @@ bool ItemFileIO::Impl::saveItem bool isExport = (interfaceLevel == Conversion); if(!isExport){ - mv->notify( + mout->notify( formatR(_("Saving {0} \"{1}\" to \"{2}\""), caption, item->displayName(), filename)); } else { - mv->notify( + mout->notify( formatR(_("Exporting {0} \"{1}\" into \"{2}\""), caption, item->displayName(), filename)); } - mv->flush(); bool saved = self->save(item, filename); - mv->flush(); + mout->flush(); if(!saved){ - mv->put(_(" -> failed.\n"), MessageView::Highlight); + mout->putErrorln(_(" -> failed.")); } else { MappingPtr optionArchive; @@ -514,9 +508,8 @@ bool ItemFileIO::Impl::saveItem item->setName(newName); } } - mv->put(_(" -> ok!\n")); + mout->putln(_(" -> ok!")); } - mv->flush(); this->parentItem = nullptr; @@ -592,21 +585,33 @@ int ItemFileIO::currentInvocationType() const } +void ItemFileIO::setMessageOut(MessageOut* mout) +{ + impl->mout = mout; +} + + +MessageOut* ItemFileIO::mout() +{ + return impl->mout; +} + + std::ostream& ItemFileIO::os() { - return *impl->os; + return impl->mout->cout(); } void ItemFileIO::putWarning(const std::string& message) { - impl->mv->putln(message, MessageView::Warning); + impl->mout->putWarningln(message); } void ItemFileIO::putError(const std::string& message) { - impl->mv->putln(message, MessageView::Error); + impl->mout->putErrorln(message); } diff --git a/src/Base/ItemFileIO.h b/src/Base/ItemFileIO.h index 9ebd67671..7e1039333 100644 --- a/src/Base/ItemFileIO.h +++ b/src/Base/ItemFileIO.h @@ -16,6 +16,7 @@ class Item; class ItemManager; class ItemFileDialog; class Mapping; +class MessageOut; class CNOID_EXPORT ItemFileIO : public Referenced { @@ -142,6 +143,8 @@ class CNOID_EXPORT ItemFileIO : public Referenced // Save API virtual bool save(Item* item, const std::string& filename); + void setMessageOut(MessageOut* mout); + MessageOut* mout(); std::ostream& os(); void putWarning(const std::string& message); void putError(const std::string& message); diff --git a/src/Base/ItemManager.cpp b/src/Base/ItemManager.cpp index 6cd6feb04..14cf497dc 100644 --- a/src/Base/ItemManager.cpp +++ b/src/Base/ItemManager.cpp @@ -12,6 +12,8 @@ #include "MainMenu.h" #include "Action.h" #include "CheckBox.h" +#include +#include #include #include #include @@ -119,8 +121,8 @@ class ItemManager::Impl void registerClass( function& factory, Item* singletonInstance, int classId, const string& className); - void addCreationPanel(const std::type_info& type, ItemCreationPanel* panel); - CreationDialog* createCreationDialog(const std::type_info& type); + void addCreationPanel(const std::type_info& type, ItemCreationPanel* panel, bool isVisibleInMainMenu); + CreationDialog* createCreationDialog(const std::type_info& type, bool isVisibleInMainMenu); static void onNewItemActivated(CreationDialog* dialog); ClassInfoPtr registerFileIO(const type_info& typeId, ItemFileIO* fileIO); @@ -144,6 +146,7 @@ class CreationDialog : public QDialog ClassInfo* classInfo; ItemCreationPanel* creationPanel; QVBoxLayout* panelLayout; + QAction* mainMenuItemAction; ItemPtr defaultProtoItem; bool isSingleton; }; @@ -459,15 +462,15 @@ Item* ItemManager::createItemWithDialog_ } -void ItemManager::addCreationPanel_(const std::type_info& type, ItemCreationPanel* panel) +void ItemManager::addCreationPanel_(const std::type_info& type, ItemCreationPanel* panel, bool isVisibleInMainMenu) { - impl->addCreationPanel(type, panel); + impl->addCreationPanel(type, panel, isVisibleInMainMenu); } -void ItemManager::Impl::addCreationPanel(const std::type_info& type, ItemCreationPanel* panel) +void ItemManager::Impl::addCreationPanel(const std::type_info& type, ItemCreationPanel* panel, bool isVisibleInMainMenu) { - CreationDialog* dialog = createCreationDialog(type); + CreationDialog* dialog = createCreationDialog(type, isVisibleInMainMenu); if(dialog){ if(panel){ dialog->addPanel(panel); @@ -480,7 +483,7 @@ void ItemManager::Impl::addCreationPanel(const std::type_info& type, ItemCreatio } -CreationDialog* ItemManager::Impl::createCreationDialog(const std::type_info& type) +CreationDialog* ItemManager::Impl::createCreationDialog(const std::type_info& type, bool isVisibleInMainMenu) { CreationDialog* dialog = nullptr; @@ -502,10 +505,18 @@ CreationDialog* ItemManager::Impl::createCreationDialog(const std::type_info& ty dialog->hide(); info->creationDialogs.push_back(dialog); - mainMenu->add_File_New_Item( - translatedName, - [=](){ onNewItemActivated(dialog); }, - registeredCreationPanels.empty()); + auto action = + mainMenu->add_File_New_Item( + translatedName, + [=](){ onNewItemActivated(dialog); }, + registeredCreationPanels.empty()); + + if(action){ + dialog->mainMenuItemAction = action; + if(!isVisibleInMainMenu){ + action->setVisible(false); + } + } } return dialog; @@ -535,6 +546,7 @@ CreationDialog::CreationDialog (const QString& title, ClassInfo* classInfo, Item* singletonInstance) : QDialog(MainWindow::instance()), classInfo(classInfo), + mainMenuItemAction(nullptr), defaultProtoItem(singletonInstance), isSingleton((bool)singletonInstance) { @@ -577,7 +589,7 @@ Item* CreationDialog::createItem(Item* parentItem, Item* protoItem) protoItem->removeFromParentItem(); protoItem->clearNonSubItemChildren(); } else { - showWarningDialog( + showErrorDialog( formatR(_("{0} is a singleton item type and its instance exists in the project item tree."), classInfo->className)); return nullptr; @@ -586,15 +598,24 @@ Item* CreationDialog::createItem(Item* parentItem, Item* protoItem) } if(!protoItem){ defaultProtoItem = classInfo->factory(); - defaultProtoItem->setName(classInfo->name); + if(defaultProtoItem->name().empty()){ + defaultProtoItem->setName(classInfo->name); + } protoItem = defaultProtoItem; } + ItemPtr newInstance; if(creationPanel->initializeCreation(protoItem, parentItem)){ if(exec() == QDialog::Accepted){ if(creationPanel->updateItem(protoItem, parentItem)){ if((protoItem == defaultProtoItem) && !isSingleton){ - newInstance = protoItem->clone(); + CloneMap cloneMap; + newInstance = protoItem->cloneSubTree(cloneMap); + if(!newInstance){ + showErrorDialog( + formatR(_("New {0} cannot be created from its prototype item."), + classInfo->className)); + } } else { newInstance = protoItem; } @@ -624,11 +645,11 @@ Item* CreationDialog::getOrCreateDefaultProtoItem() ItemCreationPanel::ItemCreationPanel() { - + nameEntry = nullptr; } -DefaultItemCreationPanel::DefaultItemCreationPanel() +void ItemCreationPanel::initializePanelWithNameEntry() { QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(new QLabel(_("Name:"))); @@ -636,19 +657,45 @@ DefaultItemCreationPanel::DefaultItemCreationPanel() layout->addWidget(nameEntry); setLayout(layout); } + + +void ItemCreationPanel::initializeNameEntryForCreation(Item* protoItem) +{ + if(nameEntry){ + static_cast(nameEntry)->setText(protoItem->name().c_str()); + } +} + + +bool ItemCreationPanel::updateItemWithNameEntry(Item* protoItem) +{ + if(nameEntry){ + auto nameEntry_ = static_cast(nameEntry); + if(!nameEntry_->text().isEmpty()){ + protoItem->setName(nameEntry_->text().toStdString()); + return true; + } + } + return false; +} + + +DefaultItemCreationPanel::DefaultItemCreationPanel() +{ + initializePanelWithNameEntry(); +} bool DefaultItemCreationPanel::initializeCreation(Item* protoItem, Item* /* parentItem */) { - static_cast(nameEntry)->setText(protoItem->name().c_str()); + initializeNameEntryForCreation(protoItem); return true; } bool DefaultItemCreationPanel::updateItem(Item* protoItem, Item* /* parentItem */) { - protoItem->setName(static_cast(nameEntry)->text().toStdString()); - return true; + return updateItemWithNameEntry(protoItem); } @@ -837,9 +884,17 @@ ItemFileIO* ItemManager::Impl::findMatchedFileIO if(!targetFileIO){ if(format.empty()){ - messageView->putln( - formatR(_("The file format for accessing \"{0}\" cannot be determined."), filename), - MessageView::Error); + for(auto& fileIO : fileIOs){ + if(fileIO->hasApi(ioTypeFlag)){ + targetFileIO = fileIO; + break; + } + } + if(!targetFileIO){ + messageView->putln( + formatR(_("The file format for accessing \"{0}\" cannot be determined."), filename), + MessageView::Error); + } } else { messageView->putln( formatR(_("Unknown file format \"{0}\" is specified in accessing \"{1}\"."), format, filename), @@ -932,12 +987,22 @@ void ItemManager::addSaver_ bool ItemManager::loadItem -(Item* item, const std::string& filename, Item* parentItem, const std::string& format, const Mapping* options) +(Item* item, const std::string& filename, Item* parentItem, const std::string& format, const Mapping* options, + MessageOut* mout) { + bool loaded = false; if(auto fileIO = Impl::findMatchedFileIO(typeid(*item), filename, format, ItemFileIO::Load)){ - return fileIO->loadItem(item, filename, parentItem, false, nullptr, options); + MessageOutPtr orgMout; + if(mout){ + orgMout = fileIO->mout(); + fileIO->setMessageOut(mout); + } + loaded = fileIO->loadItem(item, filename, parentItem, false, nullptr, options); + if(mout){ + fileIO->setMessageOut(orgMout); + } } - return false; + return loaded; } @@ -980,12 +1045,21 @@ Item* ItemManager::findOriginalItemForReloadedItem(Item* item) bool ItemManager::saveItem -(Item* item, const std::string& filename, const std::string& format, const Mapping* options) +(Item* item, const std::string& filename, const std::string& format, const Mapping* options, MessageOut* mout) { + bool saved = false; if(auto fileIO = Impl::findMatchedFileIO(typeid(*item), filename, format, ItemFileIO::Save)){ - return fileIO->saveItem(item, filename, options); + MessageOutPtr orgMout; + if(mout){ + orgMout = fileIO->mout(); + fileIO->setMessageOut(mout); + } + saved = fileIO->saveItem(item, filename, options); + if(mout){ + fileIO->setMessageOut(orgMout); + } } - return false; + return saved; } @@ -1035,7 +1109,8 @@ bool ItemManager::saveItemWithDialog(Item* item, const std::string& format, bool bool ItemManager::overwriteItem -(Item* item, bool forceOverwrite, const std::string& format, bool doSaveItemWithDialog) +(Item* item, bool forceOverwrite, const std::string& format, bool doSaveItemWithDialog, time_t cutoffTime, + MessageOut* mout) { if(doSaveItemWithDialog){ if(!checkFileImmutable(item)){ @@ -1053,21 +1128,36 @@ bool ItemManager::overwriteItem } else { if(!filename.empty()){ filesystem::path fpath(fromUTF8(filename)); - if(!filesystem::exists(fpath) || - filesystem::last_write_time_to_time_t(fpath) > item->fileModificationTime()){ + if(!filesystem::exists(fpath)){ needToOverwrite = true; - filename.clear(); + if(doSaveItemWithDialog){ + filename.clear(); + } + } else if(cutoffTime == 0){ + if(filesystem::last_write_time_to_time_t(fpath) != item->fileModificationTime()){ + // The actual file was replaced with another file + needToOverwrite = true; + if(doSaveItemWithDialog){ + filename.clear(); + } + } + } else { // The cutoff-time is specified + if(filesystem::last_write_time_to_time_t(fpath) < cutoffTime){ + needToOverwrite = true; + } } } } - if(!needToOverwrite && !item->isConsistentWithFile()){ - needToOverwrite = true; + if(!needToOverwrite){ + if(!item->isConsistentWithFile() || (filename.empty() && doSaveItemWithDialog)){ + needToOverwrite = true; + } } bool synchronized = !needToOverwrite; if(!synchronized){ if(!filename.empty() && format.empty()){ - synchronized = saveItem(item, filename, lastFormat, item->fileOptions()); + synchronized = saveItem(item, filename, lastFormat, item->fileOptions(), mout); } if(!synchronized && doSaveItemWithDialog){ synchronized = saveItemWithDialog(item, format, false); @@ -1157,7 +1247,7 @@ string getOpenFileName(const string& caption, const string& extensions) dialog.setWindowTitle(caption.c_str()); dialog.setNameFilter(makeNameFilterString(caption, extensions)); dialog.setFileMode(QFileDialog::ExistingFile); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); if(dialog.exec() == QDialog::Accepted){ filename = dialog.selectedFiles().value(0).toStdString(); } @@ -1172,7 +1262,7 @@ vector getOpenFileNames(const string& caption, const string& extensions) dialog.setWindowTitle(caption.c_str()); dialog.setNameFilter(makeNameFilterString(caption, extensions)); dialog.setFileMode(QFileDialog::ExistingFiles); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); if(dialog.exec() == QDialog::Accepted){ for(auto& file : dialog.selectedFiles()){ filenames.push_back(file.toStdString()); diff --git a/src/Base/ItemManager.h b/src/Base/ItemManager.h index 2706819be..99fef768e 100644 --- a/src/Base/ItemManager.h +++ b/src/Base/ItemManager.h @@ -23,6 +23,15 @@ class CNOID_EXPORT ItemCreationPanel : public QWidget ItemCreationPanel(); virtual bool initializeCreation(Item* protoItem, Item* parentItem) = 0; virtual bool updateItem(Item* protoItem, Item* parentItem) = 0; + +protected: + void initializePanelWithNameEntry(); + void initializeNameEntryForCreation(Item* protoItem); + bool updateItemWithNameEntry(Item* protoItem); + +private: + // This is actually a QLineEdit widget and is created by initializePanelWithNameEntry. + QWidget* nameEntry; }; template @@ -44,7 +53,6 @@ class ItemCreationPanelBase : public ItemCreationPanel class CNOID_EXPORT DefaultItemCreationPanel : public ItemCreationPanel { - QWidget* nameEntry; public: DefaultItemCreationPanel(); virtual bool initializeCreation(Item* protoItem, Item* parentItem) override; @@ -139,8 +147,12 @@ class CNOID_EXPORT ItemManager return static_cast(getPrototypeInstance_(typeid(ItemType))); } - template ItemManager& addCreationPanel(ItemCreationPanel* panel = nullptr) { - addCreationPanel_(typeid(ItemType), panel); + template ItemManager& addCreationPanel(ItemCreationPanel* panel, bool isVisibleInMainMenu = true) { + addCreationPanel_(typeid(ItemType), panel, isVisibleInMainMenu); + return *this; + } + template ItemManager& addCreationPanel(bool isVisibleInMainMenu = true) { + addCreationPanel_(typeid(ItemType), nullptr, isVisibleInMainMenu); return *this; } @@ -248,7 +260,7 @@ class CNOID_EXPORT ItemManager const std::string& className, const std::type_info& type, const std::type_info& superType, std::function factory, Item* singletonInstance); void addAlias_(const std::type_info& type, const std::string& className, const std::string& moduleName); - void addCreationPanel_(const std::type_info& type, ItemCreationPanel* panel); + void addCreationPanel_(const std::type_info& type, ItemCreationPanel* panel, bool isVisibleInMainMenu); static Item* getPrototypeInstance_(const std::type_info& type); void registerFileIO_(const std::type_info& type, ItemFileIO* fileIO); @@ -269,12 +281,15 @@ class CNOID_EXPORT ItemManager // The following static functions are called from functions in the Item class static bool loadItem( Item* item, const std::string& filename, Item* parentItem, const std::string& format, - const Mapping* options = nullptr); + const Mapping* options = nullptr, MessageOut* mout = nullptr); static bool saveItem( - Item* item, const std::string& filename, const std::string& format, const Mapping* options = nullptr); + Item* item, const std::string& filename, const std::string& format, + const Mapping* options = nullptr, MessageOut* mout = nullptr); static bool saveItemWithDialog(Item* item, const std::string& format = std::string(), bool doCheckFileImmutable = true); - static bool overwriteItem(Item* item, bool forceOverwrite, const std::string& format, bool doSaveItemWithDialog = false); + static bool overwriteItem( + Item* item, bool forceOverwrite, const std::string& format, bool doSaveItemWithDialog = false, + time_t cutoffTime = 0, MessageOut* mout = nullptr); static bool overwriteItemOrSaveItemWithDialog(Item* item, bool forceOverwrite, const std::string& format){ return overwriteItem(item, forceOverwrite, format, true); } diff --git a/src/Base/ItemPropertyWidget.cpp b/src/Base/ItemPropertyWidget.cpp index 772dff3d5..bc3fe990b 100644 --- a/src/Base/ItemPropertyWidget.cpp +++ b/src/Base/ItemPropertyWidget.cpp @@ -134,6 +134,7 @@ class ItemPropertyWidget::Impl : public PutPropertyFunction ItemPtr currentItem; ScopedConnectionSet itemConnections; PolymorphicItemFunctionSet propertyFunctions; + bool isEditable; double minValue; double maxValue; @@ -152,6 +153,7 @@ class ItemPropertyWidget::Impl : public PutPropertyFunction void updateProperties(bool isItemChanged = false); void addProperty(const std::string& name, PropertyItem* propertyItem); void onTargetItemSpecified(Item* item); + void setEditable(bool on); void zoomFontSize(int pointSizeDiff); // PutPropertyFunction's virtual functions @@ -209,7 +211,11 @@ PropertyItem::PropertyItem(ItemPropertyWidget::Impl* view, ValueVariant value, F value(value), func(func) { - setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + int flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if(view->isEditable){ + flags |= Qt::ItemIsEditable; + } + setFlags(static_cast(flags)); hasValidFunction = true; } @@ -565,7 +571,7 @@ void CustomizedItemDelegate::openFileDialog(FilePathProperty value, FilePathEdit } dialog.setLabelText(QFileDialog::Reject, _("Cancel")); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); filesystem::path directory; if(!value.baseDirectory().empty()){ @@ -711,17 +717,26 @@ void ItemPropertyWidget::Impl::setCurrentItem(Item* item) { if(item != currentItem){ itemConnections.disconnect(); + isEditable = false; + if(item){ itemConnections.add( item->sigUpdated().connect( - [this](){ updateProperties(); })); + [this]{ updateProperties(); })); itemConnections.add( item->sigNameChanged().connect( [this](const std::string& /* oldName */){ updateProperties(); })); itemConnections.add( item->sigDisconnectedFromRoot().connect( - [this](){ setCurrentItem(nullptr); })); + [this]{ setCurrentItem(nullptr); })); + itemConnections.add( + item->sigContinuousUpdateStateChanged().connect( + [this](bool on){ setEditable(!on); })); + + isEditable = !item->isContinuousUpdateState(); + } + currentItem = item; updateProperties(true); } @@ -904,6 +919,13 @@ void ItemPropertyWidget::Impl::operator() } +void ItemPropertyWidget::Impl::setEditable(bool on) +{ + isEditable = on; + updateProperties(false); +} + + void ItemPropertyWidget::keyPressEvent(QKeyEvent* event) { if(event->modifiers() & Qt::ControlModifier){ diff --git a/src/Base/ItemTreeArchiver.cpp b/src/Base/ItemTreeArchiver.cpp index e8449ea8d..e430a44be 100644 --- a/src/Base/ItemTreeArchiver.cpp +++ b/src/Base/ItemTreeArchiver.cpp @@ -3,8 +3,8 @@ #include "RootItem.h" #include "SubProjectItem.h" #include "ItemManager.h" -#include "MessageView.h" #include "Archive.h" +#include #include #include #include @@ -20,12 +20,12 @@ namespace cnoid { class ItemTreeArchiver::Impl { public: - MessageView* mv; int itemIdCounter; int numArchivedItems; int numRestoredItems; const std::set* pOptionalPlugins; bool isTemporaryItemSaveEnabled; + MessageOut* mout; Impl(); ArchivePtr store(Archive& parentArchive, Item* item); @@ -54,9 +54,8 @@ ItemTreeArchiver::ItemTreeArchiver() ItemTreeArchiver::Impl::Impl() - : mv(MessageView::instance()) { - + mout = MessageOut::master(); } @@ -66,6 +65,12 @@ ItemTreeArchiver::~ItemTreeArchiver() } +void ItemTreeArchiver::setMessageOut(MessageOut* mout) +{ + impl->mout = mout; +} + + void ItemTreeArchiver::reset() { impl->itemIdCounter = 0; @@ -113,7 +118,7 @@ ArchivePtr ItemTreeArchiver::Impl::store(Archive& parentArchive, Item* item) bool isComplete = true; ArchivePtr archive = storeIter(parentArchive, item, isComplete); if(!isComplete){ - mv->putln(_("Not all items were stored correctly."), MessageView::Warning); + mout->putWarningln(_("Not all items were stored correctly.")); } return archive; @@ -137,9 +142,8 @@ ArchivePtr ItemTreeArchiver::Impl::storeIter(Archive& parentArchive, Item* item, string className; if(!ItemManager::getClassIdentifier(item, pluginName, className)){ - mv->putln( - formatR(_("\"{}\" cannot be stored. Its type is not registered."), item->displayName()), - MessageView::Error); + mout->putErrorln( + formatR(_("\"{}\" cannot be stored. Its type is not registered."), item->displayName())); isComplete = false; return nullptr; } @@ -150,14 +154,13 @@ ArchivePtr ItemTreeArchiver::Impl::storeIter(Archive& parentArchive, Item* item, ArchivePtr dataArchive; if(!item->isSubItem()){ - mv->putln(formatR(_("Storing {0} \"{1}\""), className, item->displayName())); - mv->flush(); + mout->putln(formatR(_("Storing {0} \"{1}\""), className, item->displayName())); dataArchive = new Archive; dataArchive->inheritSharedInfoFrom(parentArchive); if(!item->store(*dataArchive)){ - mv->putln(formatR(_("\"{}\" cannot be stored."), item->displayName()), MessageView::Error); + mout->putErrorln(formatR(_("\"{}\" cannot be stored."), item->displayName())); isComplete = false; return nullptr; } @@ -198,7 +201,9 @@ ArchivePtr ItemTreeArchiver::Impl::storeIter(Archive& parentArchive, Item* item, storeAddons(*archive, item); } - item->setConsistentWithProjectArchive(true); + if(!archive->isSavingProjectAsBackup()){ + item->setConsistentWithProjectArchive(true); + } if(subProjectItem && !subProjectItem->isSavingSubProject()){ return archive; @@ -268,10 +273,9 @@ void ItemTreeArchiver::Impl::storeAddons(Archive& archive, Item* item) for(auto& addon : addons){ string name, moduleName; if(!ItemManager::getAddonIdentifier(addon, moduleName, name)){ - mv->putln( + mout->putErrorln( formatR(_("Addon \"{0}\" of item \"{1}\" cannot be stored. Its type is not registered."), - typeid(*addon).name(), item->displayName()), - MessageView::Error); + typeid(*addon).name(), item->displayName())); } else { ArchivePtr addonArchive = new Archive; addonArchive->inheritSharedInfoFrom(archive); @@ -282,9 +286,8 @@ void ItemTreeArchiver::Impl::storeAddons(Archive& archive, Item* item) } else { //! \note Storing the addon data is just skipped when the store function returns false. /* - mv->putln(formatR(_("Addon \"{0}\" of item \"{1}\" cannot be stored."), - name, item->name()), - MessageView::Error); + mout->putErrorln(formatR(_("Addon \"{0}\" of item \"{1}\" cannot be stored."), + name, item->name())); */ } } @@ -314,7 +317,7 @@ ItemList<> ItemTreeArchiver::Impl::restore try { restoreItemIter(archive, parentItem, topLevelItems, 0); } catch (const ValueNode::Exception& ex){ - mv->putln(ex.message(), MessageView::Error); + mout->putErrorln(ex.message()); } archive.setCurrentParentItem(nullptr); @@ -334,22 +337,22 @@ void ItemTreeArchiver::Impl::restoreItemIter try { item = restoreItem(archive, parentItem, itemName, className, isRootItem, isOptional); } catch (const ValueNode::Exception& ex){ - mv->putln(ex.message(), MessageView::Error); + mout->putErrorln(ex.message()); } if(!item){ if(!isOptional){ if(!itemName.empty()){ if(!className.empty()){ - mv->putln(formatR(_("{0} \"{1}\" cannot be restored."), className, itemName), MessageView::Error); + mout->putErrorln(formatR(_("{0} \"{1}\" cannot be restored."), className, itemName)); } else { - mv->putln(formatR(_("\"{0}\" cannot be restored."), itemName), MessageView::Error); + mout->putErrorln(formatR(_("\"{0}\" cannot be restored."), itemName)); } } else { if(!className.empty()){ - mv->putln(formatR(_("An instance of {0} cannot be restored."), className), MessageView::Error); + mout->putErrorln(formatR(_("An instance of {0} cannot be restored."), className)); } else { - mv->putln(_("An instance of unkown item type cannot be restored."), MessageView::Error); + mout->putErrorln(_("An instance of unkown item type cannot be restored.")); } } } @@ -387,14 +390,13 @@ ItemPtr ItemTreeArchiver::Impl::restoreItem const bool isSubItem = archive.get({ "is_sub_item", "isSubItem" }, false); if(isSubItem){ if(itemName.empty()){ - mv->putln(_("The archive has an empty-name sub item, which cannot be processed."), MessageView::Error); + mout->putErrorln(_("The archive has an empty-name sub item, which cannot be processed.")); return nullptr; } ItemPtr subItem = parentItem->findChildItem(itemName, [](Item* item){ return item->isSubItem(); }); if(!subItem){ - mv->putln( - formatR(_("Sub item \"{}\" is not found. Its children cannot be restored."), itemName), - MessageView::Error); + mout->putErrorln( + formatR(_("Sub item \"{}\" is not found. Its children cannot be restored."), itemName)); } restoreItemStates(archive, subItem); return subItem; @@ -402,7 +404,7 @@ ItemPtr ItemTreeArchiver::Impl::restoreItem string pluginName; if(!(archive.read("plugin", pluginName) && archive.read("class", className))){ - mv->putln(_("Archive is broken."), MessageView::Error); + mout->putErrorln(_("Archive is broken.")); return nullptr; } @@ -410,9 +412,8 @@ ItemPtr ItemTreeArchiver::Impl::restoreItem if(!item){ io_isOptional = (pOptionalPlugins->find(pluginName) != pOptionalPlugins->end()); if(!io_isOptional){ - mv->putln( - formatR(_("{0} of {1}Plugin is not a registered item type."), className, pluginName), - MessageView::Error); + mout->putErrorln( + formatR(_("{0} of {1}Plugin is not a registered item type."), className, pluginName)); ++numArchivedItems; } return nullptr; @@ -437,13 +438,12 @@ ItemPtr ItemTreeArchiver::Impl::restoreItem item->setAttribute(Item::Attached); } - mv->putln(formatR(_("Restoring {0} \"{1}\""), className, itemName)); - mv->flush(); + mout->putln(formatR(_("Restoring {0} \"{1}\""), className, itemName)); ValueNodePtr dataNode = archive.find("data"); if(dataNode->isValid()){ if(!dataNode->isMapping()){ - mv->putln(_("The 'data' key does not have mapping-type data."), MessageView::Error); + mout->putErrorln(_("The 'data' key does not have mapping-type data.")); item.reset(); } else { Archive* dataArchive = static_cast(dataNode->toMapping()); @@ -458,10 +458,9 @@ ItemPtr ItemTreeArchiver::Impl::restoreItem } if(item){ if(!parentItem->addChildItem(item)){ - mv->putln( + mout->putErrorln( formatR(_("{0} \"{1}\" cannot be added to \"{2}\" as a child item."), - className, itemName, parentItem->displayName()), - MessageView::Error); + className, itemName, parentItem->displayName())); item.reset(); } else { restoreItemStates(archive, item); @@ -479,7 +478,7 @@ void ItemTreeArchiver::Impl::restoreAddons(Archive& archive, Item* item) auto addonsNode = archive.find("addons"); if(addonsNode->isValid()){ if(!addonsNode->isListing()){ - mv->putln(_("The 'addons' value must be a listing."), MessageView::Error); + mout->putErrorln(_("The 'addons' value must be a listing.")); } else { string name; string moduleName; @@ -488,21 +487,24 @@ void ItemTreeArchiver::Impl::restoreAddons(Archive& archive, Item* item) auto addonArchive = dynamic_cast(addonList->at(i)->toMapping()); addonArchive->inheritSharedInfoFrom(archive); if(!(addonArchive->read("name", name) && addonArchive->read("plugin", moduleName))){ - mv->putln(formatR(_("The name and plugin are not specified at addon {0}."), i), - MessageView::Error); + mout->putErrorln( + formatR(_("The name and plugin are not specified at addon {0}."), i)); } else { ItemAddonPtr addon = ItemManager::createAddon(moduleName, name); if(!addon){ - mv->putln(formatR(_("Addon \"{0}\" of plugin \"{1}\" cannot be created."), - name, moduleName), MessageView::Error); + mout->putErrorln( + formatR(_("Addon \"{0}\" of plugin \"{1}\" cannot be created."), + name, moduleName)); } else { if(!item->setAddon(addon)){ - mv->putln(formatR(_("Addon \"{0}\" cannot be added to item \"{1}\"."), - name, item->displayName()), MessageView::Error); + mout->putErrorln( + formatR(_("Addon \"{0}\" cannot be added to item \"{1}\"."), + name, item->displayName())); } else { if(!addon->restore(*addonArchive)){ - mv->putln(formatR(_("Addon \"{0}\" of plugin \"{1}\" cannot be restored."), - name, moduleName), MessageView::Error); + mout->putErrorln( + formatR(_("Addon \"{0}\" of plugin \"{1}\" cannot be restored."), + name, moduleName)); item->removeAddon(addon); } } diff --git a/src/Base/ItemTreeArchiver.h b/src/Base/ItemTreeArchiver.h index 54ae3363e..797210bad 100644 --- a/src/Base/ItemTreeArchiver.h +++ b/src/Base/ItemTreeArchiver.h @@ -9,12 +9,14 @@ namespace cnoid { class Item; +class MessageOut; class CNOID_EXPORT ItemTreeArchiver { public: ItemTreeArchiver(); ~ItemTreeArchiver(); + void setMessageOut(MessageOut* mout); void reset(); void setTemporaryItemSaveEnabled(bool on); bool isTemporaryItemSaveEnabled() const; diff --git a/src/Base/ItemTreeView.cpp b/src/Base/ItemTreeView.cpp index 15e1650c7..a014a2f2f 100644 --- a/src/Base/ItemTreeView.cpp +++ b/src/Base/ItemTreeView.cpp @@ -129,20 +129,29 @@ void ItemTreeView::Impl::onContextMenuRequested(Item* item, MenuManager& menu) auto selectAll = menu.addItem(_("Select all")); auto clearSelection = menu.addItem(_("Clear selection")); - if(!item){ + bool isContinuousUpdateStateSubTree = + item ? item->isContinuousUpdateStateSubTree() : false; + + if(!item || isContinuousUpdateStateSubTree){ rename->setEnabled(false); cut->setEnabled(false); copy1->setEnabled(false); copy2->setEnabled(false); + paste->setEnabled(false); check->setEnabled(false); uncheck->setEnabled(false); toggleCheck->setEnabled(false); reload->setEnabled(false); + saveAs->setEnabled(false); clearSelection->setEnabled(false); } else { - rename->sigTriggered().connect( - [this, item](){ itemTreeWidget->editItemName(item); }); + if(item->isSubItem() || item->hasAttribute(Item::Attached)){ + rename->setEnabled(false); + } else { + rename->sigTriggered().connect( + [this, item](){ itemTreeWidget->editItemName(item); }); + } if(itemTreeWidget->checkCuttable(item)){ cut->sigTriggered().connect( @@ -159,12 +168,6 @@ void ItemTreeView::Impl::onContextMenuRequested(Item* item, MenuManager& menu) copy1->setEnabled(false); copy2->setEnabled(false); } - check->sigTriggered().connect( - [this](){ itemTreeWidget->setSelectedItemsChecked(true); }); - uncheck->sigTriggered().connect( - [this](){ itemTreeWidget->setSelectedItemsChecked(false); }); - toggleCheck->sigTriggered().connect( - [this](){ itemTreeWidget->toggleSelectedItemChecks(); }); if(!item->hasAttribute(Item::Reloadable)){ reload->setEnabled(false); @@ -187,7 +190,18 @@ void ItemTreeView::Impl::onContextMenuRequested(Item* item, MenuManager& menu) [this](){ itemTreeWidget->clearSelection(); }); } - if(itemTreeWidget->checkPastable(item)){ + if(item){ + clearSelection->setEnabled(true); + check->sigTriggered().connect( + [this](){ itemTreeWidget->setSelectedItemsChecked(true); }); + uncheck->sigTriggered().connect( + [this](){ itemTreeWidget->setSelectedItemsChecked(false); }); + toggleCheck->sigTriggered().connect( + [this](){ itemTreeWidget->toggleSelectedItemChecks(); }); + } + + if(itemTreeWidget->checkPastable(item) && !isContinuousUpdateStateSubTree){ + paste->setEnabled(true); paste->sigTriggered().connect( [this](){ itemTreeWidget->pasteItems(); }); } else { diff --git a/src/Base/ItemTreeWidget.cpp b/src/Base/ItemTreeWidget.cpp index 236127a93..9b3dcc2d3 100644 --- a/src/Base/ItemTreeWidget.cpp +++ b/src/Base/ItemTreeWidget.cpp @@ -115,7 +115,7 @@ class ItemTreeWidget::Impl : public TreeWidget ItwItem* findNextItwItemInSubTree(Item* item, bool doTraverse); bool isItemUnderTreeWidgetInternalOperation(Item* item); void onSubTreeAddedOrMoved(Item* item); - void onSubTreeRemoved(Item* item); + void onSubTreeRemoved(Item* item, bool isMoving); void onItemAssigned(Item* assigned, const Item* srcItem); void getItemsIter(ItwItem* itwItem, ItemList<>& itemList); @@ -169,6 +169,8 @@ class ItemTreeWidget::ItwItem : public QTreeWidgetItem ScopedConnection itemSelectionConnection; ScopedConnection itemCheckConnection; ScopedConnection displayUpdateConnection; + ScopedConnection continuousUpdateStateConnection; + bool isNameEditable; bool isExpandedBeforeRemoving; bool isTemporaryAttributeDisplay; @@ -180,6 +182,7 @@ class ItemTreeWidget::ItwItem : public QTreeWidgetItem ItwItem(Item* item, ItemTreeWidget::Impl* widgetImpl); virtual ~ItwItem(); + void updateEditFlags(); virtual void setData(int column, int role, const QVariant& value) override; }; @@ -192,15 +195,21 @@ ItemTreeWidget::ItwItem::ItwItem(Item* item, ItemTreeWidget::Impl* widgetImpl) { widgetImpl->itemToItwItemMap[item] = this; - Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; + isNameEditable = true; + bool isContinuousUpdateStateSubTree = item->isContinuousUpdateStateSubTree(); + + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if(widgetImpl->isCheckColumnShown){ flags |= Qt::ItemIsUserCheckable; } - if(!item->hasAttribute(Item::Attached)){ - flags |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled; - } setFlags(flags); + updateEditFlags(); + + continuousUpdateStateConnection = + item->sigContinuousUpdateStateChanged().connect( + [this](bool){ updateEditFlags(); }); + setToolTip(0, QString()); setText(0, item->displayName().c_str()); @@ -250,6 +259,24 @@ ItemTreeWidget::ItwItem::~ItwItem() } +void ItemTreeWidget::ItwItem::updateEditFlags() +{ + Qt::ItemFlags flags_ = flags() & ~(Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); + bool isContinuousUpdateStateSubTree = item->isContinuousUpdateStateSubTree(); + + if(!isContinuousUpdateStateSubTree){ + flags_ |= Qt::ItemIsDropEnabled; + } + if(!item->isSubItem() && !item->hasAttribute(Item::Attached) && !isContinuousUpdateStateSubTree){ + if(isNameEditable){ + flags_ |= Qt::ItemIsEditable; + } + flags_ |= Qt::ItemIsDragEnabled; + } + setFlags(flags_); +} + + void ItemTreeWidget::ItwItem::setData(int column, int role, const QVariant& value) { if(column == 0){ @@ -341,10 +368,9 @@ void ItemTreeWidget::Display::setStatusTip(const std::string& statusTip) void ItemTreeWidget::Display::setNameEditable(bool on) { - if(on){ - itwItem->setFlags(itwItem->flags() | Qt::ItemIsEditable); - } else { - itwItem->setFlags(itwItem->flags() & ~Qt::ItemIsEditable); + if(on != itwItem->isNameEditable){ + itwItem->isNameEditable = on; + itwItem->updateEditFlags(); } } @@ -438,7 +464,7 @@ void ItemTreeWidget::Impl::initialize() projectRootItemConnections.add( projectRootItem->sigSubTreeRemoved().connect( - [this](Item* item, bool){ onSubTreeRemoved(item); })); + [this](Item* item, bool isMoving){ onSubTreeRemoved(item, isMoving); })); projectRootItemConnections.add( projectRootItem->sigItemAssigned().connect( @@ -937,10 +963,19 @@ void ItemTreeWidget::Impl::insertItem(QTreeWidgetItem* parentTwItem, Item* item, { auto itwItem = findItwItem(item); + bool doExpand = false; + if(itwItem){ - parentTwItem = itwItem; + if(itwItem->parent()){ + parentTwItem = itwItem; + } else { // Item is moving + doExpand = itwItem->isExpandedBeforeRemoving; + delete itwItem; + itwItem = nullptr; + } + } - } else { + if(!itwItem){ if(!findOrCreateLocalRootItem(false)){ return; } @@ -985,6 +1020,10 @@ void ItemTreeWidget::Impl::insertItem(QTreeWidgetItem* parentTwItem, Item* item, insertItem(parentTwItem, child, isTopLevelItemCandidate); } } + + if(itwItem && doExpand){ + itwItem->setExpanded(true); + } } @@ -1074,7 +1113,7 @@ void ItemTreeWidget::Impl::onSubTreeAddedOrMoved(Item* item) } -void ItemTreeWidget::Impl::onSubTreeRemoved(Item* item) +void ItemTreeWidget::Impl::onSubTreeRemoved(Item* item, bool isMoving) { if(isItemUnderTreeWidgetInternalOperation(item) || !localRootItem){ return; @@ -1083,15 +1122,18 @@ void ItemTreeWidget::Impl::onSubTreeRemoved(Item* item) isChangingTreeWidgetTreeStructure++; if(auto itwItem = findItwItem(item)){ + itwItem->isExpandedBeforeRemoving = itwItem->isExpanded(); if(auto parentTwItem = itwItem->parent()){ parentTwItem->removeChild(itwItem); } else { takeTopLevelItem(indexOfTopLevelItem(itwItem)); } - delete itwItem; + if(!isMoving){ + delete itwItem; + } } else { for(auto child = item->childItem(); child; child = child->nextItem()){ - onSubTreeRemoved(child); + onSubTreeRemoved(child, isMoving); } } @@ -1863,7 +1905,19 @@ void ItemTreeWidget::Impl::keyPressEvent(QKeyEvent* event) case Qt::Key_R: unifiedEditHistory->flushNewRecordBuffer(); for(auto& item : getSelectedItems()){ - item->reload(); + if(!item->isContinuousUpdateStateSubTree()){ + item->reload(); + } else { + if(item->isContinuousUpdateState()){ + showWarningDialog( + formatR(_("Item \"{0}\" cannot be reloaded currently, as it is being continuously updated."), + item->displayName())); + } else { + showWarningDialog( + formatR(_("Item \"{0}\" cannot be reloaded currently, as it is a part of a sub-tree being continuously updated."), + item->displayName())); + } + } } break; diff --git a/src/Base/LightingItem.cpp b/src/Base/LightingItem.cpp index 9f3114369..b50a7d916 100644 --- a/src/Base/LightingItem.cpp +++ b/src/Base/LightingItem.cpp @@ -12,19 +12,41 @@ using namespace std; using namespace cnoid; +namespace { + +SgMaterialPtr lightMaterial; +SgShapePtr lightSphere; +SgShapePtr lightCapsule; +SgGroupPtr directionalLightShape; +SgGroupPtr pointLightShape; +SgGroupPtr spotLightShape; + +SgMaterial* getOrCreateLightMaterial(); +SgShape* getOrCreateLightSphere(); +SgShape* getOrCreateLightCapsule(); +SgGroup* getOrCreateDirectionalLightShape(); +SgGroup* getOrCreatePointLightShape(); +SgGroup* getOrCreateSpotLightShape(); + +} + namespace cnoid { class LightingItem::Impl { public: LightingItem* self; - SgPosTransformPtr lightPosTransform; Selection lightType; + + SgPosTransformPtr lightPosTransform; SgLightPtr light; + SgGroupPtr lightShape; + SgUpdate sgUpdate; + bool isMarkerEnabled; + Vector3f color; float intensity; float ambientIntensity; - bool on; // For the spot light and the directional light Vector3 direction; // For the point light @@ -35,16 +57,12 @@ class LightingItem::Impl float beamWidth; float cutOffAngle; float cutOffExponent; - bool isMarkerEnabled; - SgGroupPtr lightShape; - SgGroupPtr directionalLightShape; - SgGroupPtr pointLightShape; - SgGroupPtr spotLightShape; + bool on; Impl(LightingItem* self); Impl(LightingItem* self, const Impl& org); void setLightType(LightType type); - void genarateLightShape(); + void updateLight(); void doPutProperties(PutPropertyFunction& putProperty); bool store(Archive& archive); bool restore(const Archive& archive); @@ -84,16 +102,12 @@ LightingItem::~LightingItem() delete impl; } + Item* LightingItem::doCloneItem(CloneMap* /* cloneMap */) const { return new LightingItem(*this); } -SgNode* LightingItem::getScene() -{ - return impl->lightPosTransform; -} - LightingItem::Impl::Impl(LightingItem* self) : self(self), @@ -103,121 +117,35 @@ LightingItem::Impl::Impl(LightingItem* self) lightType.setSymbol(PointLight, N_("Point light")); lightType.setSymbol(SpotLight, N_("Spot light")); - on = true; - - // Get the default values - SgSpotLight light; - color = light.color(); - intensity = light.intensity(); - direction = light.direction(); - ambientIntensity = light.ambientIntensity(); - constantAttenuation = light.constantAttenuation(); - linearAttenuation = light.linearAttenuation(); - quadraticAttenuation = light.quadraticAttenuation(); - beamWidth = light.beamWidth(); - cutOffAngle = light.cutOffAngle(); - cutOffExponent = light.cutOffExponent(); - - isMarkerEnabled = false; lightPosTransform = new SgPosTransform; lightPosTransform->setTranslation(Vector3(0.0, 0.0, 3.0)); - genarateLightShape(); - setLightType(SpotLight); -} - + isMarkerEnabled = false; + + // Get the default values + static SgSpotLightPtr refLight = new SgSpotLight; + color = refLight->color(); + intensity = refLight->intensity(); + direction = refLight->direction(); + ambientIntensity = refLight->ambientIntensity(); + constantAttenuation = refLight->constantAttenuation(); + linearAttenuation = refLight->linearAttenuation(); + quadraticAttenuation = refLight->quadraticAttenuation(); + beamWidth = refLight->beamWidth(); + cutOffAngle = refLight->cutOffAngle(); + cutOffExponent = refLight->cutOffExponent(); + on = true; -void LightingItem::Impl::genarateLightShape() -{ - SgMaterial* material = new SgMaterial; - material->setDiffuseColor(Vector3f(1.0f, 1.0f, 0.0f)); - material->setAmbientIntensity(0.2f); - directionalLightShape = new SgGroup; - MeshGenerator meshGenerator; - auto sphere = new SgShape; - sphere->setMesh(meshGenerator.generateSphere(0.05, false)); - sphere->setMaterial(material); - directionalLightShape->addChild(sphere); - auto capsule = new SgShape; - capsule->setMesh(meshGenerator.generateCapsule(0.005, 0.03)); - capsule->setMaterial(material); - - static const std::vector d_pos = { - Vector3(0.0, 0.0, -0.07), Vector3(0, 0.04, -0.06), - Vector3(0.0, -0.04, -0.06), Vector3(0.04, 0, -0.06), - Vector3(-0.04, 0, -0.06) - }; - for(size_t i=0; i < d_pos.size(); i++){ - auto cT = new SgPosTransform; - cT->setRotation(AngleAxis(radian(90), Vector3::UnitX())); - cT->setTranslation(d_pos[i]); - cT->addChild(capsule); - directionalLightShape->addChild(cT); - } - - pointLightShape = new SgGroup; - pointLightShape->addChild(sphere); - - static const std::vector p_pos = { - Vector3(0.0, 0.0, 0.07), Vector3(0.0, 0.0, -0.07), - Vector3(0.07, 0.0, 0.0), Vector3(-0.07, 0.0, 0.0), - Vector3(0.0, 0.07, 0.0), Vector3(0.0, -0.07, 0.0) - }; - static const std::vector p_att = { - AngleAxis(radian(90.0), Vector3::UnitX()), AngleAxis(radian(90.0), Vector3::UnitX()), - AngleAxis(radian(90.0), Vector3::UnitZ()), AngleAxis(radian(90.0), Vector3::UnitZ()), - AngleAxis(radian(0.0), Vector3::UnitZ()), AngleAxis(radian(0.0), Vector3::UnitZ()) - }; - for(size_t i=0; i < p_pos.size(); i++){ - auto cT = new SgPosTransform; - cT->setRotation(p_att[i]); - cT->setTranslation(p_pos[i]); - cT->addChild(capsule); - pointLightShape->addChild(cT); - } - - spotLightShape = new SgGroup; - auto box = new SgShape; - box->setMesh(meshGenerator.generateBox(Vector3(0.07, 0.07, 0.07))); - box->setMaterial(material); - auto cone = new SgShape; - cone->setMesh(meshGenerator.generateCone(0.07, 0.07)); - cone->setMaterial(material); - auto coneT = new SgPosTransform; - coneT->setRotation(AngleAxis(radian(90), Vector3::UnitX())); - coneT->addChild(cone); - spotLightShape->addChild(box); - spotLightShape->addChild(coneT); - - static const std::vector s_pos = { - Vector3(0, 0, -0.055), Vector3(0.0, 0.05, -0.055) - }; - static const std::vector s_att = { - AngleAxis(radian(90.0), Vector3::UnitX()), AngleAxis(radian(125), Vector3::UnitX()) - }; - auto cT0 = new SgPosTransform; - cT0->setRotation(s_att[0]); - cT0->setTranslation(s_pos[0]); - cT0->addChild(capsule); - spotLightShape->addChild(cT0); - for(int i=0; i<4; i++){ - auto cT1 = new SgPosTransform; - cT1->setRotation(s_att[1]); - cT1->setTranslation(s_pos[1]); - cT1->addChild(capsule); - auto cT2 = new SgPosTransform; - cT2->setRotation(AngleAxis(radian(90.0 * i), Vector3::UnitZ())); - cT2->addChild(cT1); - spotLightShape->addChild(cT2); - } + setLightType(SpotLight); } LightingItem::Impl::Impl(LightingItem* self, const Impl& org) : self(self), - lightType(org.lightType), - on(org.on) + lightType(org.lightType) { - light = org.light; + lightPosTransform = new SgPosTransform(*org.lightPosTransform); + isMarkerEnabled = org.isMarkerEnabled; + color = org.color; intensity = org.intensity; ambientIntensity = org.ambientIntensity; @@ -228,12 +156,9 @@ LightingItem::Impl::Impl(LightingItem* self, const Impl& org) beamWidth = org.beamWidth; cutOffAngle = org.cutOffAngle; cutOffExponent = org.cutOffExponent; - directionalLightShape = org.directionalLightShape; - pointLightShape = org.pointLightShape; - spotLightShape = org.spotLightShape; - isMarkerEnabled = org.isMarkerEnabled; - lightShape = org.lightShape; - lightPosTransform = new SgPosTransform(*org.lightPosTransform); + on = org.on; + + setLightType(static_cast(lightType.which())); } @@ -246,28 +171,32 @@ void LightingItem::setLightType(LightType type) void LightingItem::Impl::setLightType(LightType type) { lightType.select(type); - lightPosTransform->clearChildren(); - - switch(lightType.which()){ + if(!lightPosTransform->empty()){ + updateLight(); + } +} + + +void LightingItem::Impl::updateLight() +{ + switch(lightType.which()){ case DirectionalLight: { auto directionalLight = new SgDirectionalLight; directionalLight->setDirection(direction); - lightShape = directionalLightShape; light = directionalLight; + lightShape = getOrCreateDirectionalLightShape(); break; } - case PointLight: { auto pointLight = new SgPointLight; pointLight->setConstantAttenuation(constantAttenuation); pointLight->setLinearAttenuation(linearAttenuation); pointLight->setQuadraticAttenuation(quadraticAttenuation); - lightShape = pointLightShape; light = pointLight; + lightShape = getOrCreatePointLightShape(); break; } - case SpotLight: { auto spotLight = new SgSpotLight; spotLight->setConstantAttenuation(constantAttenuation); @@ -277,11 +206,10 @@ void LightingItem::Impl::setLightType(LightType type) spotLight->setBeamWidth(beamWidth); spotLight->setCutOffAngle(cutOffAngle); spotLight->setCutOffExponent(cutOffExponent); - lightShape = spotLightShape; light = spotLight; + lightShape = getOrCreateSpotLightShape(); break; } - default: break; } @@ -290,11 +218,13 @@ void LightingItem::Impl::setLightType(LightType type) light->setIntensity(intensity); light->setAmbientIntensity(ambientIntensity); light->on(on); + + lightPosTransform->clearChildren(); + lightPosTransform->addChild(light); if(isMarkerEnabled){ lightPosTransform->addChild(lightShape); } - SgTmpUpdate update; - lightPosTransform->addChild(light, update); + lightPosTransform->notifyUpdate(sgUpdate.withAction(SgUpdate::Added | SgUpdate::Removed)); } @@ -307,120 +237,169 @@ void LightingItem::setTranslation(const Vector3& translation) void LightingItem::setDirection(const Vector3& direction) { - impl->direction = direction; - if(auto directionalLight = dynamic_pointer_cast(impl->light)){ - directionalLight->setDirection(direction); - directionalLight->notifyUpdate(); - } else if(auto spotLight = dynamic_pointer_cast(impl->light)){ - spotLight->setDirection(direction); - spotLight->notifyUpdate(); + if(direction != impl->direction){ + impl->direction = direction; + if(impl->light){ + if(auto directionalLight = dynamic_pointer_cast(impl->light)){ + directionalLight->setDirection(direction); + directionalLight->notifyUpdate( + impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } else if(auto spotLight = dynamic_pointer_cast(impl->light)){ + spotLight->setDirection(direction); + spotLight->notifyUpdate( + impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } + } } } void LightingItem::setLightEnabled(bool on) { - impl->on = on; - impl->light->on(on); - impl->light->notifyUpdate(); + if(on != impl->on){ + impl->on = on; + if(impl->light){ + impl->light->on(on); + impl->light->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } + } } void LightingItem::setIntensity(float intensity) { - impl->intensity = intensity; - impl->light->setIntensity(intensity); - impl->light->notifyUpdate(); + if(intensity != impl->intensity){ + impl->intensity = intensity; + if(impl->light){ + impl->light->setIntensity(intensity); + impl->light->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } + } } void LightingItem::setAmbientIntensity(float intensity) { - impl->ambientIntensity = intensity; - impl->light->setAmbientIntensity(intensity); - impl->light->notifyUpdate(); + if(intensity != impl->ambientIntensity){ + impl->ambientIntensity = intensity; + if(impl->light){ + impl->light->setAmbientIntensity(intensity); + impl->light->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } + } } void LightingItem::setColor(const Vector3f& color) { - impl->color = color; - impl->light->setColor(color); - impl->light->notifyUpdate(); + if(color != impl->color){ + impl->color = color; + if(impl->light){ + impl->light->setColor(color); + impl->light->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } + } } void LightingItem::setConstantAttenuation(float a0) { - impl->constantAttenuation = a0; - if(auto pointLight = dynamic_pointer_cast(impl->light)){ - pointLight->setConstantAttenuation(a0); - pointLight->notifyUpdate(); + if(a0 != impl->constantAttenuation){ + impl->constantAttenuation = a0; + if(auto pointLight = dynamic_pointer_cast(impl->light)){ + pointLight->setConstantAttenuation(a0); + pointLight->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } } } void LightingItem::setLinearAttenuation(float a1) { - impl->linearAttenuation = a1; - if(auto pointLight = dynamic_pointer_cast(impl->light)){ - pointLight->setLinearAttenuation(a1); - pointLight->notifyUpdate(); + if(a1 != impl->linearAttenuation){ + impl->linearAttenuation = a1; + if(auto pointLight = dynamic_pointer_cast(impl->light)){ + pointLight->setLinearAttenuation(a1); + pointLight->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } } } void LightingItem::setQuadraticAttenuation(float a2) { - impl->quadraticAttenuation = a2; - if(auto pointLight = dynamic_pointer_cast(impl->light)){ - pointLight->setQuadraticAttenuation(a2); - pointLight->notifyUpdate(); + if(a2 != impl->quadraticAttenuation){ + impl->quadraticAttenuation = a2; + if(auto pointLight = dynamic_pointer_cast(impl->light)){ + pointLight->setQuadraticAttenuation(a2); + pointLight->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } } } void LightingItem::setBeamWidth(float w) { - impl->beamWidth = w; - if(auto spotLight = dynamic_pointer_cast(impl->light)){ - spotLight->setBeamWidth(w); - spotLight->notifyUpdate(); + if(w != impl->beamWidth){ + impl->beamWidth = w; + if(auto spotLight = dynamic_pointer_cast(impl->light)){ + spotLight->setBeamWidth(w); + spotLight->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } } } void LightingItem::setCutOffAngle(float a) { - impl->cutOffAngle = a; - if(auto spotLight = dynamic_pointer_cast(impl->light)){ - spotLight->setCutOffAngle(a); - spotLight->notifyUpdate(); + if(a != impl->cutOffAngle){ + impl->cutOffAngle = a; + if(auto spotLight = dynamic_pointer_cast(impl->light)){ + spotLight->setCutOffAngle(a); + spotLight->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } } } void LightingItem::setCutOffExponent(float e) { - impl->cutOffExponent = e; - if(auto spotLight = dynamic_pointer_cast(impl->light)){ - spotLight->setCutOffExponent(e); - spotLight->notifyUpdate(); + if(e != impl->cutOffExponent){ + impl->cutOffExponent = e; + if(auto spotLight = dynamic_pointer_cast(impl->light)){ + spotLight->setCutOffExponent(e); + spotLight->notifyUpdate(impl->sgUpdate.withAction(SgUpdate::AppearanceModified)); + } } } void LightingItem::setLightMarkerEnabled(bool on) { - impl->isMarkerEnabled = on; - if(on){ - impl->lightPosTransform->addChildOnce(impl->lightShape, true); - }else{ - impl->lightPosTransform->removeChild(impl->lightShape, true); + if(on != impl->isMarkerEnabled){ + impl->isMarkerEnabled = on; + if(!impl->lightPosTransform->empty()){ + if(on){ + impl->lightPosTransform->addChildOnce( + impl->lightShape, impl->sgUpdate.withAction(SgUpdate::Added)); + } else { + impl->lightPosTransform->removeChild( + impl->lightShape, impl->sgUpdate.withAction(SgUpdate::Removed)); + } + } } } +SgNode* LightingItem::getScene() +{ + if(impl->lightPosTransform->empty()){ + impl->updateLight(); + } + return impl->lightPosTransform; +} + + void LightingItem::doPutProperties(PutPropertyFunction& putProperty) { return impl->doPutProperties(putProperty); @@ -559,3 +538,144 @@ bool LightingItem::Impl::restore(const Archive& archive) return true; } + + +namespace { + +SgMaterial* getOrCreateLightMaterial() +{ + if(!lightMaterial){ + lightMaterial = new SgMaterial; + lightMaterial->setDiffuseColor(Vector3f(1.0f, 1.0f, 0.0f)); + lightMaterial->setAmbientIntensity(0.2f); + } + return lightMaterial; +} + + +SgShape* getOrCreateLightSphere() +{ + if(!lightSphere){ + lightSphere = new SgShape; + lightSphere->setMesh( + MeshGenerator::mainThreadInstance()->generateSphere(0.05, false)); + lightSphere->setMaterial(getOrCreateLightMaterial()); + } + return lightSphere; +} + + +SgShape* getOrCreateLightCapsule() +{ + if(lightCapsule){ + lightCapsule = new SgShape; + lightCapsule->setMesh( + MeshGenerator::mainThreadInstance()->generateCapsule(0.005, 0.03)); + lightCapsule->setMaterial(getOrCreateLightMaterial()); + } + return lightCapsule; +} + + +SgGroup* getOrCreateDirectionalLightShape() +{ + if(!directionalLightShape){ + directionalLightShape = new SgGroup; + directionalLightShape->addChild(getOrCreateLightSphere()); + + static const std::vector d_pos = { + Vector3(0.0, 0.0, -0.07), Vector3(0, 0.04, -0.06), + Vector3(0.0, -0.04, -0.06), Vector3(0.04, 0, -0.06), + Vector3(-0.04, 0, -0.06) + }; + + auto capsule = getOrCreateLightCapsule(); + for(size_t i=0; i < d_pos.size(); ++i){ + auto cT = new SgPosTransform; + cT->setRotation(AngleAxis(radian(90), Vector3::UnitX())); + cT->setTranslation(d_pos[i]); + cT->addChild(capsule); + directionalLightShape->addChild(cT); + } + } + return directionalLightShape; +} + + +SgGroup* getOrCreatePointLightShape() +{ + if(!pointLightShape){ + pointLightShape = new SgGroup; + pointLightShape->addChild(getOrCreateLightSphere()); + + static const std::vector p_pos = { + Vector3(0.0, 0.0, 0.07), Vector3(0.0, 0.0, -0.07), + Vector3(0.07, 0.0, 0.0), Vector3(-0.07, 0.0, 0.0), + Vector3(0.0, 0.07, 0.0), Vector3(0.0, -0.07, 0.0) + }; + static const std::vector p_att = { + AngleAxis(radian(90.0), Vector3::UnitX()), AngleAxis(radian(90.0), Vector3::UnitX()), + AngleAxis(radian(90.0), Vector3::UnitZ()), AngleAxis(radian(90.0), Vector3::UnitZ()), + AngleAxis(radian(0.0), Vector3::UnitZ()), AngleAxis(radian(0.0), Vector3::UnitZ()) + }; + + auto capsule = getOrCreateLightCapsule(); + for(size_t i=0; i < p_pos.size(); ++i){ + auto cT = new SgPosTransform; + cT->setRotation(p_att[i]); + cT->setTranslation(p_pos[i]); + cT->addChild(capsule); + pointLightShape->addChild(cT); + } + } + return pointLightShape; +} + + +SgGroup* getOrCreateSpotLightShape() +{ + if(!spotLightShape){ + auto meshGenerator = MeshGenerator::mainThreadInstance(); + auto material = getOrCreateLightMaterial(); + spotLightShape = new SgGroup; + auto box = new SgShape; + box->setMesh(meshGenerator->generateBox(Vector3(0.07, 0.07, 0.07))); + box->setMaterial(material); + auto cone = new SgShape; + cone->setMesh(meshGenerator->generateCone(0.07, 0.07)); + cone->setMaterial(material); + auto coneT = new SgPosTransform; + coneT->setRotation(AngleAxis(radian(90), Vector3::UnitX())); + coneT->addChild(cone); + spotLightShape->addChild(box); + spotLightShape->addChild(coneT); + + static const std::vector s_pos = { + Vector3(0, 0, -0.055), Vector3(0.0, 0.05, -0.055) + }; + static const std::vector s_att = { + AngleAxis(radian(90.0), Vector3::UnitX()), AngleAxis(radian(125), Vector3::UnitX()) + }; + auto capsule = getOrCreateLightCapsule(); + + auto cT0 = new SgPosTransform; + cT0->setRotation(s_att[0]); + cT0->setTranslation(s_pos[0]); + cT0->addChild(capsule); + spotLightShape->addChild(cT0); + + for(int i=0; i < 4; ++i){ + auto cT1 = new SgPosTransform; + cT1->setRotation(s_att[1]); + cT1->setTranslation(s_pos[1]); + cT1->addChild(capsule); + auto cT2 = new SgPosTransform; + cT2->setRotation(AngleAxis(radian(90.0 * i), Vector3::UnitZ())); + cT2->addChild(cT1); + spotLightShape->addChild(cT2); + } + } + return spotLightShape; +} + +} diff --git a/src/Base/LocatableItem.cpp b/src/Base/LocatableItem.cpp index 2d87f1dab..ec8ff0577 100644 --- a/src/Base/LocatableItem.cpp +++ b/src/Base/LocatableItem.cpp @@ -11,9 +11,14 @@ Signal sigEditRequest; } -LocationProxy::LocationProxy(LocationType type) - : locationType_(type) +LocationProxy::LocationProxy(Item* locatableItem, LocationType type) + : locatableItem_(locatableItem), + locationType_(type) { + if(locatableItem){ + itemConnection = locatableItem->sigDisconnectedFromRoot().connect( + [this]{ locatableItem_ = nullptr; }); + } isLocked_ = false; } @@ -24,15 +29,25 @@ LocationProxy::~LocationProxy() } +void LocationProxy::setNameDependencyOnItemName() +{ + if(locatableItem_){ + if(!itemNameConnection.connected()){ + itemNameConnection = locatableItem_->sigNameChanged().connect( + [this](const std::string&){ notifyAttributeChange(); }); + } + } +} + + std::string LocationProxy::getName() const { auto self = const_cast(this); - if(auto item = self->getCorrespondingItem()){ - if(!itemNameConnection_.connected()){ - self->itemNameConnection_ = item->sigNameChanged().connect( - [self](const std::string&){ self->notifyAttributeChange(); }); + if(locatableItem_){ + if(!itemNameConnection.connected()){ + self->setNameDependencyOnItemName(); } - return item->displayName(); + return locatableItem_->displayName(); } return std::string(); } @@ -59,8 +74,17 @@ void LocationProxy::setLocked(bool on) } -bool LocationProxy::isDoingContinuousUpdate() const +bool LocationProxy::isContinuousUpdateState() const { + auto self = const_cast(this); + if(locatableItem_){ + if(!self->continuousUpdateStateConnection.connected()){ + self->continuousUpdateStateConnection = + locatableItem_->sigContinuousUpdateStateChanged().connect( + [self](bool){ self->notifyAttributeChange(); }); + } + return locatableItem_->isContinuousUpdateState(); + } return false; } @@ -77,16 +101,10 @@ void LocationProxy::finishLocationEditing() } -Item* LocationProxy::getCorrespondingItem() -{ - return nullptr; -} - - -LocationProxyPtr LocationProxy::getParentLocationProxy() const +LocationProxyPtr LocationProxy::getParentLocationProxy() { - if(auto item = const_cast(this)->getCorrespondingItem()){ - if(auto parentLocatableItem = item->findOwnerItem()){ + if(locatableItem_){ + if(auto parentLocatableItem = locatableItem_->findOwnerItem()){ return parentLocatableItem->getLocationProxy(); } } @@ -107,7 +125,7 @@ Isometry3 LocationProxy::getGlobalLocationOf(const Isometry3 T) const return T; case ParentRelativeLocation: case OffsetLocation: - if(auto parent = getParentLocationProxy()){ + if(auto parent = const_cast(this)->getParentLocationProxy()){ return parent->getGlobalLocation() * T; } else { return T; diff --git a/src/Base/LocatableItem.h b/src/Base/LocatableItem.h index 8c3ef6043..40d4d96ed 100644 --- a/src/Base/LocatableItem.h +++ b/src/Base/LocatableItem.h @@ -15,6 +15,10 @@ typedef ref_ptr LocationProxyPtr; class CNOID_EXPORT LocationProxy : public Referenced { public: + virtual ~LocationProxy(); + + Item* locatableItem() { return locatableItem_; } + enum LocationType { InvalidLocation, GlobalLocation, @@ -23,11 +27,9 @@ class CNOID_EXPORT LocationProxy : public Referenced // this maeks the global coordinate unavailable in the user interface OffsetLocation }; - - virtual ~LocationProxy(); - LocationType locationType() const { return locationType_; } void setLocationType(LocationType type) { locationType_ = type; } + virtual std::string getName() const; /** @@ -40,11 +42,10 @@ class CNOID_EXPORT LocationProxy : public Referenced virtual Isometry3 getLocation() const = 0; virtual bool isLocked() const; virtual void setLocked(bool on); - virtual bool isDoingContinuousUpdate() const; + virtual bool isContinuousUpdateState() const; virtual bool setLocation(const Isometry3& T); virtual void finishLocationEditing(); - virtual Item* getCorrespondingItem(); - virtual LocationProxyPtr getParentLocationProxy() const; + virtual LocationProxyPtr getParentLocationProxy(); virtual void expire(); virtual SignalProxy sigLocationChanged() = 0; virtual SignalProxy sigAttributeChanged(); @@ -57,15 +58,25 @@ class CNOID_EXPORT LocationProxy : public Referenced static SignalProxy sigEditRequest(); + [[deprecated("Use isContinuousUpdateState.")]] + bool isDoingContinuousUpdate() const { return isContinuousUpdateState(); } + + [[deprecated("Use locatableItem.")]] + Item* getCorrespondingItem() { return locatableItem(); } + protected: - LocationProxy(LocationType type); + LocationProxy(Item* locatableItem, LocationType type); + void setNameDependencyOnItemName(); private: + Item* locatableItem_; LocationType locationType_; bool isLocked_; Signal sigAttributeChanged_; Signal sigExpired_; - ScopedConnection itemNameConnection_; + ScopedConnection itemConnection; + ScopedConnection itemNameConnection; + ScopedConnection continuousUpdateStateConnection; }; class CNOID_EXPORT LocatableItem diff --git a/src/Base/LocationView.cpp b/src/Base/LocationView.cpp index dd2cec714..1dca198b5 100644 --- a/src/Base/LocationView.cpp +++ b/src/Base/LocationView.cpp @@ -610,7 +610,7 @@ void LocationView::Impl::setCurrentLocationCategory(int categoryIndex) bool blocked = locked; if(!blocked){ for(auto& info : locationInfos){ - if(info->location->isDoingContinuousUpdate()){ + if(info->location->isContinuousUpdateState()){ blocked = true; break; } @@ -762,7 +762,7 @@ void LocationView::Impl::updateBaseCoordinateSystems() Item* targetItem = locationInfo->item; if(!targetItem){ - targetItem = location->getCorrespondingItem(); + targetItem = location->locatableItem(); } for(int i=0; i < n; ++i){ diff --git a/src/Base/MainMenu.cpp b/src/Base/MainMenu.cpp index b584394e3..15372271a 100644 --- a/src/Base/MainMenu.cpp +++ b/src/Base/MainMenu.cpp @@ -4,6 +4,7 @@ #include "ItemManager.h" #include "ItemFileDialog.h" #include "ProjectManager.h" +#include "ProjectBackupManager.h" #include "PluginManager.h" #include "ViewManager.h" #include "RootItem.h" @@ -16,6 +17,7 @@ #include "PathVariableEditor.h" #include "DistanceMeasurementDialog.h" #include "RenderableItemSceneStatistics.h" +#include "RenderableItemSceneExporter.h" #include "MovieRecorderDialog.h" #include "SceneWidget.h" #include "DescriptionDialog.h" @@ -25,6 +27,7 @@ #include #include #include +#include #include "gettext.h" using namespace std; @@ -101,6 +104,13 @@ void MainMenu::setMenuItems() mm.setPath(N_("Project File Options")); setActionAsProjectLayoutToggle(mm.addCheckItem(_("Layout"))); + setActionAsShowProjectRecoveryDialog(mm.addItem(_("Recovery from Backup"))); + setActionAsShowProjectBackupConfigDialog(mm.addItem(_("Backup Configuration"))); + + // For test + //setActionAsProjectBackupTest(mm.addItem("Backup Test")); + //setActionAsCrashTest(mm.addItem("Crash Test")); + setActionAsShowPathVariableEditor(mm.addItem(_("Edit Path Variables"))); mm.goBackToUpperMenu().addSeparator(); @@ -110,7 +120,7 @@ void MainMenu::setMenuItems() // Add a menu item to show a dialog to load a plugin if the startup plugin loading is disabled // This is for the debug use mm.setPath(N_("Plugin")).addItem(_("Load Plugin")) - ->sigTriggered().connect([this, pluginManager](){ pluginManager->showDialogToLoadPlugin(); }); + ->sigTriggered().connect([this, pluginManager]{ pluginManager->showDialogToLoadPlugin(); }); mm.addSeparator(); } @@ -153,6 +163,7 @@ void MainMenu::setMenuItems() set_Tools_Menu(mm.currentMenu()); setActionAsShowDistanceMeasurementDialog(mm.addItem(_("Distance Measurement"))); setActionAsPutSceneStatistics(mm.addItem(_("Put Scene Statistics"))); + setActionAsExportSelectedRenderableItemScene(mm.addItem(_("Export Scene"))); setActionAsShowMovieRecorderDialog(mm.addItem(_("Movie Recorder"))); //------------------------ Filters ------------------------ @@ -172,13 +183,13 @@ void MainMenu::setMenuItems() auto vsyncItem = mm.addCheckItem(_("Vertical Sync")); glMenu->sigAboutToShow().connect( - [vsyncItem](){ vsyncItem->setChecked(SceneWidget::isVerticalSyncMode()); }); + [vsyncItem]{ vsyncItem->setChecked(SceneWidget::isVerticalSyncMode()); }); vsyncItem->sigToggled().connect( [](bool on){ SceneWidget::setVerticalSyncMode(on); }); auto lowMemoryItem = mm.addCheckItem(_("Low GPU Memory Consumption Mode")); glMenu->sigAboutToShow().connect( - [lowMemoryItem](){ lowMemoryItem->setChecked(SceneWidget::isLowMemoryConsumptionMode()); }); + [lowMemoryItem]{ lowMemoryItem->setChecked(SceneWidget::isLowMemoryConsumptionMode()); }); lowMemoryItem->sigToggled().connect( [](bool on){ SceneWidget::setLowMemoryConsumptionMode(on); }); @@ -252,7 +263,7 @@ Action* MainMenu::addMenuItem void MainMenu::setActionAsReloadSelectedItems(Action* action) { action->sigTriggered().connect( - [](){ + []{ for(auto& item : RootItem::instance()->selectedItems()){ item->reload(); } @@ -263,7 +274,7 @@ void MainMenu::setActionAsReloadSelectedItems(Action* action) void MainMenu::setActionAsSaveSelectedItems(Action* action) { action->sigTriggered().connect( - [](){ + []{ for(auto& item : RootItem::instance()->selectedItems()){ item->overwriteOrSaveWithDialog(true, ""); } @@ -274,7 +285,7 @@ void MainMenu::setActionAsSaveSelectedItems(Action* action) void MainMenu::setActionAsSaveSelectedItemsAs(Action* action) { action->sigTriggered().connect( - [](){ + []{ for(auto& item : RootItem::instance()->selectedItems()){ item->saveWithFileDialog(); } @@ -285,7 +296,7 @@ void MainMenu::setActionAsSaveSelectedItemsAs(Action* action) void MainMenu::setActionAsExportSelectedItems(Action* action) { action->sigTriggered().connect( - [](){ + []{ ItemFileDialog dialog; dialog.setExportMode(); for(auto& item : RootItem::instance()->selectedItems()){ @@ -306,82 +317,100 @@ void MainMenu::setActionAsExportSelectedItems(Action* action) void MainMenu::setActionAsOpenProject(Action* action) { action->sigTriggered().connect( - [](){ ProjectManager::instance()->showDialogToLoadProject(); }); + []{ ProjectManager::instance()->showDialogToLoadProject(); }); } void MainMenu::setActionAsSaveProject(Action* action) { action->sigTriggered().connect( - [](){ ProjectManager::instance()->overwriteCurrentProject(); }); + []{ ProjectManager::instance()->overwriteCurrentProject(); }); } void MainMenu::setActionAsSaveProjectAs(Action* action) { action->sigTriggered().connect( - [](){ ProjectManager::instance()->showDialogToSaveProject(); }); + []{ ProjectManager::instance()->showDialogToSaveProject(); }); } void MainMenu::setActionAsProjectLayoutToggle(Action* action) { qobject_cast(action->parent())->sigAboutToShow().connect( - [action](){ action->setChecked(ProjectManager::instance()->isLayoutInclusionMode()); }); + [action]{ action->setChecked(ProjectManager::instance()->isLayoutInclusionMode()); }); action->sigToggled().connect([](bool on){ ProjectManager::instance()->setLayoutInclusionMode(on); }); } +void MainMenu::setActionAsShowProjectRecoveryDialog(Action* action) +{ + action->sigTriggered().connect([]{ ProjectBackupManager::instance()->showRecoveryDialog(); }); +} + + +void MainMenu::setActionAsShowProjectBackupConfigDialog(Action* action) +{ + action->sigTriggered().connect([]{ ProjectBackupManager::instance()->showConfigDialog(); }); +} + + +void MainMenu::setActionAsProjectBackupTest(Action* action) +{ + action->sigTriggered().connect([]{ ProjectBackupManager::instance()->saveProjectAsBackup(); }); +} + + void MainMenu::setActionAsShowPathVariableEditor(Action* action) { - action->sigTriggered().connect([](){ PathVariableEditor::instance()->show(); }); + action->sigTriggered().connect([]{ PathVariableEditor::instance()->show(); }); } void MainMenu::setActionAsExitApplication(Action* action) { - action->sigTriggered().connect([](){ MainWindow::instance()->close(); }); + action->sigTriggered().connect([]{ MainWindow::instance()->close(); }); } void MainMenu::setActionAsUndo(Action* action) { qobject_cast(action->parent())->sigAboutToShow().connect( - [action](){ action->setEnabled(UnifiedEditHistory::instance()->isUndoable()); }); - action->sigTriggered().connect([](){ UnifiedEditHistory::instance()->undo(); }); + [action]{ action->setEnabled(UnifiedEditHistory::instance()->isUndoable()); }); + action->sigTriggered().connect([]{ UnifiedEditHistory::instance()->undo(); }); } void MainMenu::setActionAsRedo(Action* action) { qobject_cast(action->parent())->sigAboutToShow().connect( - [action](){ action->setEnabled(UnifiedEditHistory::instance()->isRedoable()); }); - action->sigTriggered().connect([](){ UnifiedEditHistory::instance()->redo(); }); + [action]{ action->setEnabled(UnifiedEditHistory::instance()->isRedoable()); }); + action->sigTriggered().connect([]{ UnifiedEditHistory::instance()->redo(); }); } void MainMenu::setMenuAsToolBarVisibilityMenu(Menu* menu) { menu->sigAboutToShow().connect( - [menu](){ MainWindow::instance()->toolBarArea()->setVisibilityMenuItems(menu); }); + [menu]{ MainWindow::instance()->toolBarArea()->setVisibilityMenuItems(menu); }); } void MainMenu::setMenuAsViewVisibilityMenu(Menu* menu) { - menu->sigAboutToShow().connect([this, menu](){ onViewOperationMenuAboutToShow(menu, ViewVisibilityMenu); }); + menu->sigAboutToShow().connect([this, menu]{ onViewOperationMenuAboutToShow(menu, ViewVisibilityMenu); }); } void MainMenu::setMenuAsViewCreationMenu(Menu* menu) { - menu->sigAboutToShow().connect([this, menu](){ onViewOperationMenuAboutToShow(menu, ViewCreationMenu); }); + menu->sigAboutToShow().connect([this, menu]{ onViewOperationMenuAboutToShow(menu, ViewCreationMenu); }); } void MainMenu::setMenuAsViewDeletionMenu(Menu* menu, bool isItemToDeleteAllHiddenViewsEnabled) { - menu->sigAboutToShow().connect([this, menu](){ onViewOperationMenuAboutToShow(menu, ViewDeletionMenu); }); + menu->sigAboutToShow().connect([this, menu]{ onViewOperationMenuAboutToShow(menu, ViewDeletionMenu); }); this->isItemToDeleteAllHiddenViewsEnabled = isItemToDeleteAllHiddenViewsEnabled; } @@ -439,7 +468,7 @@ void MainMenu::onViewOperationMenuAboutToShow(Menu* menu, int viewMenuType) auto action = new Action(menu); action->setText(viewClass->translatedDefaultInstanceName()); action->sigTriggered().connect( - [viewClass](){ + [viewClass]{ if(auto view = viewClass->createViewWithDialog()){ view->mountOnMainWindow(true); } @@ -456,7 +485,7 @@ void MainMenu::onViewOperationMenuAboutToShow(Menu* menu, int viewMenuType) auto action = new Action(menu); action->setText(view->windowTitle()); action->sigTriggered().connect( - [view](){ ViewManager::deleteView(view); }); + [view]{ ViewManager::deleteView(view); }); menu->addAction(action); } } @@ -469,7 +498,7 @@ void MainMenu::onViewOperationMenuAboutToShow(Menu* menu, int viewMenuType) auto action = new Action(menu); action->setText(_("Delete All Unmounted Views")); action->sigTriggered().connect( - [](){ViewManager::deleteUnmountedViews(); }); + []{ViewManager::deleteUnmountedViews(); }); menu->addAction(action); } } @@ -478,7 +507,7 @@ void MainMenu::onViewOperationMenuAboutToShow(Menu* menu, int viewMenuType) void MainMenu::setActionAsViewTabToggle(Action* action) { qobject_cast(action->parent())->sigAboutToShow().connect( - [action](){ action->setChecked(MainWindow::instance()->viewArea()->viewTabsVisible()); }); + [action]{ action->setChecked(MainWindow::instance()->viewArea()->viewTabsVisible()); }); action->sigToggled().connect( [](bool on){ MainWindow::instance()->viewArea()->setViewTabsVisible(on); }); } @@ -487,7 +516,7 @@ void MainMenu::setActionAsViewTabToggle(Action* action) void MainMenu::setActionAsStatusBarToggle(Action* action) { qobject_cast(action->parent())->sigAboutToShow().connect( - [action](){ action->setChecked(InfoBar::instance()->isVisible()); }); + [action]{ action->setChecked(InfoBar::instance()->isVisible()); }); action->sigToggled().connect([](bool on){ InfoBar::instance()->setVisible(on); }); } @@ -509,31 +538,43 @@ void MainMenu::setActionAsFullScreenToggle(Action* action) void MainMenu::setActionAsResetMainWindowLayout(Action* action) { - action->sigTriggered().connect([](){ MainWindow::instance()->resetLayout(); }); + action->sigTriggered().connect([]{ MainWindow::instance()->resetLayout(); }); } void MainMenu::setActionAsShowDistanceMeasurementDialog(Action* action) { - action->sigTriggered().connect([](){ DistanceMeasurementDialog::instance()->show(); }); + action->sigTriggered().connect([]{ DistanceMeasurementDialog::instance()->show(); }); } void MainMenu::setActionAsPutSceneStatistics(Action* action) { - action->sigTriggered().connect([](){ putRenderableItemSceneStatistics(); }); + action->sigTriggered().connect([]{ putRenderableItemSceneStatistics(); }); +} + + +void MainMenu::setActionAsExportSelectedRenderableItemScene(Action* action) +{ + action->sigTriggered().connect([]{ showDialogToExportSelectedRenderableItemScene(); }); } void MainMenu::setActionAsShowMovieRecorderDialog(Action* action) { - action->sigTriggered().connect([](){ MovieRecorderDialog::instance()->show(); }); + action->sigTriggered().connect([]{ MovieRecorderDialog::instance()->show(); }); } void MainMenu::setActionAsShowDialogAboutChoreonoid(Action* action) { - action->sigTriggered().connect([](){ showDialogAboutChoreonoid(); }); + action->sigTriggered().connect([]{ showDialogAboutChoreonoid(); }); +} + + +void MainMenu::setActionAsCrashTest(Action* action) +{ + action->sigTriggered().connect([]{ throw std::runtime_error("crash test"); }); } diff --git a/src/Base/MainMenu.h b/src/Base/MainMenu.h index ec8824970..8fed3209f 100644 --- a/src/Base/MainMenu.h +++ b/src/Base/MainMenu.h @@ -65,6 +65,9 @@ class CNOID_EXPORT MainMenu void setActionAsSaveProject(Action* action); void setActionAsSaveProjectAs(Action* action); void setActionAsProjectLayoutToggle(Action* action); + void setActionAsShowProjectRecoveryDialog(Action* action); + void setActionAsShowProjectBackupConfigDialog(Action* action); + void setActionAsProjectBackupTest(Action* action); void setActionAsShowPathVariableEditor(Action* action); void setActionAsExitApplication(Action* action); void setActionAsUndo(Action* action); @@ -79,8 +82,10 @@ class CNOID_EXPORT MainMenu void setActionAsResetMainWindowLayout(Action* action); void setActionAsShowDistanceMeasurementDialog(Action* action); void setActionAsPutSceneStatistics(Action* action); + void setActionAsExportSelectedRenderableItemScene(Action* action); void setActionAsShowMovieRecorderDialog(Action* action); void setActionAsShowDialogAboutChoreonoid(Action* action); + void setActionAsCrashTest(Action* action); private: static void setCustomClassFactory(std::function factory); diff --git a/src/Base/MessageView.cpp b/src/Base/MessageView.cpp index 5a81939ff..6f6894ace 100644 --- a/src/Base/MessageView.cpp +++ b/src/Base/MessageView.cpp @@ -286,15 +286,19 @@ MessageView::Impl::Impl(MessageView* self) : MessageOut::master()->addSink( [this](const std::string& message, int type){ - put(message, type, false, false, true, false); - }); + put(message, type, false, false, false, false); + }, + [](const std::string& message, int /* type */){ + InfoBar::instance()->notify(message); + }, + [this]{ flush(); }); MessageOut::interactive()->addSink( [this](const std::string& message, int type){ switch(type){ case MessageOut::Normal: case MessageOut::Highlighted: - put(message, type, false, false, true, false); + put(message, type, false, false, false, false); break; case MessageOut::Warning: showWarningDialog(message); @@ -305,7 +309,11 @@ MessageView::Impl::Impl(MessageView* self) : default: break; } - }); + }, + [](const std::string& message, int /* type */){ + InfoBar::instance()->notify(message); + }, + [this]{ flush(); }); } diff --git a/src/Base/MovieRecorderDialog.cpp b/src/Base/MovieRecorderDialog.cpp index 74b0dd201..7e346f8e2 100644 --- a/src/Base/MovieRecorderDialog.cpp +++ b/src/Base/MovieRecorderDialog.cpp @@ -6,11 +6,9 @@ #include "LineEdit.h" #include "CheckBox.h" #include "ComboBox.h" -#include "Dialog.h" #include "FileDialog.h" #include "Separator.h" #include -#include #include "gettext.h" using namespace std; @@ -555,7 +553,7 @@ void MovieRecorderDialog::showDirectorySelectionDialog() dialog.setViewMode(QFileDialog::List); dialog.setFileMode(QFileDialog::Directory); dialog.setOption(QFileDialog::ShowDirsOnly); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); dialog.setDirectory(directoryEntry->text()); if(dialog.exec()){ diff --git a/src/Base/MultiPointSetItem.cpp b/src/Base/MultiPointSetItem.cpp index 45cc8df7b..ef0e799d6 100644 --- a/src/Base/MultiPointSetItem.cpp +++ b/src/Base/MultiPointSetItem.cpp @@ -127,7 +127,6 @@ void MultiPointSetItem::initializeClass(ExtensionManager* ext) if(!initialized){ ItemManager& im = ext->itemManager(); im.registerClass(N_("MultiPointSetItem")); - im.addCreationPanel(); im.addLoaderAndSaver( _("Multi Point Clouds"), "MULTI-PCD-SET", "yaml", [](MultiPointSetItem* item, const std::string& filename, std::ostream& os, Item*){ diff --git a/src/Base/MultiSE3MatrixSeqItem.cpp b/src/Base/MultiSE3MatrixSeqItem.cpp index e5ccaf0ab..8a941df09 100644 --- a/src/Base/MultiSE3MatrixSeqItem.cpp +++ b/src/Base/MultiSE3MatrixSeqItem.cpp @@ -1,5 +1,4 @@ #include "MultiSE3MatrixSeqItem.h" -#include "MultiSeqItemCreationPanel.h" #include "ItemManager.h" #include "gettext.h" @@ -10,9 +9,6 @@ void MultiSE3MatrixSeqItem::initializeClass(ExtensionManager* ext) { ext->itemManager().registerClass( N_("MultiSE3MatrixSeqItem")); - - ext->itemManager().addCreationPanel( - new MultiSeqItemCreationPanel(_("Number of SE3 values in a frame"))); } diff --git a/src/Base/MultiSE3SeqItem.cpp b/src/Base/MultiSE3SeqItem.cpp index b908caab3..b83895f09 100644 --- a/src/Base/MultiSE3SeqItem.cpp +++ b/src/Base/MultiSE3SeqItem.cpp @@ -1,5 +1,4 @@ #include "MultiSE3SeqItem.h" -#include "MultiSeqItemCreationPanel.h" #include "ItemManager.h" #include "gettext.h" @@ -10,9 +9,6 @@ void MultiSE3SeqItem::initializeClass(ExtensionManager* ext) { ext->itemManager().registerClass( N_("MultiSE3SeqItem")); - - ext->itemManager().addCreationPanel( - new MultiSeqItemCreationPanel(_("Number of SE3 values in a frame"))); } diff --git a/src/Base/MultiSeqItemCreationPanel.cpp b/src/Base/MultiSeqItemCreationPanel.cpp deleted file mode 100644 index a697738e9..000000000 --- a/src/Base/MultiSeqItemCreationPanel.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "MultiSeqItemCreationPanel.h" -#include "MultiSeqItem.h" -#include -#include -#include "gettext.h" - -using namespace std; -using namespace cnoid; - - -MultiSeqItemCreationPanel::MultiSeqItemCreationPanel(const char* numSeqsCaption) - : nameEntry(nullptr), - numSeqsCaption(numSeqsCaption) -{ - -} - - -void MultiSeqItemCreationPanel::createPanel() -{ - QVBoxLayout* vbox = new QVBoxLayout; - setLayout(vbox); - - QHBoxLayout* hbox = new QHBoxLayout; - hbox->addWidget(new QLabel(_("Name"))); - nameEntry = new QLineEdit; - hbox->addWidget(nameEntry); - vbox->addLayout(hbox); - - hbox = new QHBoxLayout; - - hbox->addWidget(new QLabel(numSeqsCaption)); - numSeqsSpin = new SpinBox; - numSeqsSpin->setRange(1, 999); - hbox->addWidget(numSeqsSpin); - - hbox->addWidget(new QLabel(_("Time length"))); - timeLengthSpin = new DoubleSpinBox; - timeLengthSpin->setDecimals(2); - timeLengthSpin->setRange(0.0, 9999.99); - timeLengthSpin->setSingleStep(0.01); - hbox->addWidget(timeLengthSpin); - - hbox->addWidget(new QLabel(_("Frame rate"))); - frameRateSpin = new DoubleSpinBox; - frameRateSpin->setDecimals(0); - frameRateSpin->setRange(1.0, 9999.0); - hbox->addWidget(frameRateSpin); - - vbox->addLayout(hbox); -} - - -bool MultiSeqItemCreationPanel::initializeCreation(AbstractSeqItem* protoItem, Item* parentItem) -{ - if(!nameEntry){ - createPanel(); - } - - nameEntry->setText(protoItem->name().c_str()); - - auto seq = protoItem->abstractSeq(); - double frameRate = seq->getFrameRate(); - timeLengthSpin->setValue(seq->getNumFrames() / frameRate); - frameRateSpin->setValue(frameRate); - - if(auto multiSeqItem = dynamic_cast(protoItem)){ - numSeqsSpin->setValue(multiSeqItem->abstractMultiSeq()->getNumParts()); - } - - doExtraInitialization(protoItem, parentItem); - - return true; -} - - -void MultiSeqItemCreationPanel::doExtraInitialization(AbstractSeqItem* /* protoItem */, Item* /* parentItem */) -{ - -} - - -bool MultiSeqItemCreationPanel::updateItem(AbstractSeqItem* protoItem, Item* parentItem) -{ - protoItem->setName(nameEntry->text().toStdString()); - - auto seq = protoItem->abstractSeq(); - double frameRate = frameRateSpin->value(); - seq->setFrameRate(frameRate); - - if(auto multiSeqItem = dynamic_cast(protoItem)){ - multiSeqItem->abstractMultiSeq()->setNumParts(numSeqsSpin->value()); - } - - doExtraItemUpdate(protoItem, parentItem); - - seq->setNumFrames(static_cast(timeLengthSpin->value() * frameRate)); - - return true; -} - - -void MultiSeqItemCreationPanel::doExtraItemUpdate(AbstractSeqItem* /* protoItem */, Item* /* parentItem */) -{ - -} diff --git a/src/Base/MultiSeqItemCreationPanel.h b/src/Base/MultiSeqItemCreationPanel.h deleted file mode 100644 index 005687c80..000000000 --- a/src/Base/MultiSeqItemCreationPanel.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CNOID_BASE_MULTI_SEQ_ITEM_CREATION_PANEL_H -#define CNOID_BASE_MULTI_SEQ_ITEM_CREATION_PANEL_H - -#include "AbstractSeqItem.h" -#include "SpinBox.h" -#include -#include -#include "exportdecl.h" - -namespace cnoid { - -class CNOID_EXPORT MultiSeqItemCreationPanel : public ItemCreationPanelBase -{ -public: - MultiSeqItemCreationPanel(const char* numSeqsCaption); - - virtual bool initializeCreation(AbstractSeqItem* protoItem, Item* parentItem) override; - virtual bool updateItem(AbstractSeqItem* protoItem, Item* parentItem) override; - - virtual void doExtraInitialization(AbstractSeqItem* protoItem, Item* parentItem); - virtual void doExtraItemUpdate(AbstractSeqItem* protoItem, Item* parentItem); - -private: - QLineEdit* nameEntry; - const char* numSeqsCaption; - SpinBox* numSeqsSpin; - DoubleSpinBox* timeLengthSpin; - DoubleSpinBox* frameRateSpin; - - void createPanel(); -}; - -} - -#endif diff --git a/src/Base/MultiValueSeqItem.cpp b/src/Base/MultiValueSeqItem.cpp index 849a97ca6..14c1c4700 100644 --- a/src/Base/MultiValueSeqItem.cpp +++ b/src/Base/MultiValueSeqItem.cpp @@ -1,5 +1,4 @@ #include "MultiValueSeqItem.h" -#include "MultiSeqItemCreationPanel.h" #include "ItemManager.h" #include #include "gettext.h" @@ -37,9 +36,6 @@ void MultiValueSeqItem::initializeClass(ExtensionManager* ext) { ext->itemManager().registerClass(N_("MultiValueSeqItem")); - ext->itemManager().addCreationPanel( - new MultiSeqItemCreationPanel(_("Number of values in a frame"))); - ext->itemManager().addLoaderAndSaver( _("Plain Format of a Multi Value Sequence"), "PLAIN-MULTI-VALUE-SEQ", "*", [](MultiValueSeqItem* item, const string& filename, ostream& os, Item* /* parentItem */){ diff --git a/src/Base/PluginManager.cpp b/src/Base/PluginManager.cpp index 9c28b7485..303786e09 100644 --- a/src/Base/PluginManager.cpp +++ b/src/Base/PluginManager.cpp @@ -381,9 +381,11 @@ void PluginManager::Impl::loadScannedPluginFiles(bool doActivation) if(numNotLoaded > 0){ for(auto& plugin : allPluginInfos){ if(plugin->status == PluginManager::NOT_LOADED){ - mout->putErrorln(formatR(_("Loading {0} failed.\n"), plugin->name)); + mout->putError(formatR(_("Loading {0} failed.\n"), plugin->name)); if(!plugin->lastErrorMessage.empty()){ mout->putErrorln(plugin->lastErrorMessage); + } else { + mout->flush(); } plugin->status = PluginManager::INVALID; } @@ -428,8 +430,8 @@ void PluginManager::Impl::loadScannedPluginFiles(bool doActivation) lacks += info->requisites[j]; } } - mout->putError( - formatR(_("{0}-plugin cannot be initialized because required plugin(s) {1} are not found.\n"), + mout->putErrorln( + formatR(_("{0}-plugin cannot be initialized because required plugin(s) {1} are not found."), info->name, lacks)); } } @@ -452,13 +454,16 @@ bool PluginManager::loadPlugin(int index) bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) { PluginInfoPtr& info = allPluginInfos[index]; + bool hasMessages = false; if(info->status == PluginManager::ACTIVE){ mout->put(formatR(_("Plugin file \"{}\" has already been activated.\n"), info->pathString)); + hasMessages = true; } else if(info->status == PluginManager::NOT_LOADED){ if(false){ mout->put(formatR(_("Detecting plugin file \"{}\".\n"), info->pathString)); + hasMessages = true; } info->dll.setFileName(info->pathString.c_str()); @@ -494,8 +499,9 @@ bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) if(!symbol){ info->status = PluginManager::INVALID; info->lastErrorMessage = _("The plugin entry function \"getChoreonoidPlugin\" is not found.\n"); - info->lastErrorMessage += info->dll.errorString().toStdString(); - mout->putErrorln(info->lastErrorMessage); + info->lastErrorMessage += info->dll.errorString().toStdString() + "\n"; + mout->putError(info->lastErrorMessage); + hasMessages = true; } else { Plugin::PluginEntry getCnoidPluginFunc = (Plugin::PluginEntry)(symbol); @@ -505,6 +511,7 @@ bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) if(!plugin){ info->status = PluginManager::INVALID; mout->putError(_("The plugin object cannot be created.\n")); + hasMessages = true; } else { info->status = PluginManager::LOADED; @@ -517,6 +524,7 @@ bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) _("The internal version of the {0} plugin is different from the system internal version.\n" "The plugin file \"{1}\" should be removed or updated to avoid a problem.\n"), info->name, info->pathString)); + hasMessages = true; } const int numRequisites = plugin->numRequisites(); @@ -547,9 +555,10 @@ bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) PluginInfoPtr& another = p->second; another->status = PluginManager::CONFLICT; info->lastErrorMessage = - formatR(_("Plugin file \"{0}\" conflicts with \"{1}\"."), + formatR(_("Plugin file \"{0}\" conflicts with \"{1}\".\n"), info->pathString, another->pathString); - mout->putErrorln(info->lastErrorMessage); + mout->putError(info->lastErrorMessage); + hasMessages = true; } } @@ -557,6 +566,11 @@ bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) info->lastErrorMessage.clear(); } else { mout->putError(_("Loading the plugin failed.\n")); + hasMessages = true; + } + + if(hasMessages){ + mout->flush(); } return (info->status == PluginManager::LOADED); @@ -566,11 +580,13 @@ bool PluginManager::Impl::loadPlugin(int index, bool isLoadingMultiplePlugins) bool PluginManager::Impl::activatePlugin(int index) { string errorMessage; + bool hasMessages = false; PluginInfoPtr info = allPluginInfos[index]; if(info->status == PluginManager::ACTIVE){ mout->putWarning(formatR(_("Plugin file \"{}\" has already been activated.\n"), info->pathString)); + hasMessages = true; } else if(info->status == PluginManager::LOADED){ @@ -648,12 +664,18 @@ bool PluginManager::Impl::activatePlugin(int index) } mout->put(formatR(_("{}-plugin has been activated.\n"), info->name)); + hasMessages = true; } } } if(!errorMessage.empty()){ mout->putErrorln(formatR(_("Loading the plugin failed.\n{0}"), errorMessage)); + hasMessages = true; + } + + if(hasMessages){ + mout->flush(); } return (info->status == PluginManager::ACTIVE); @@ -727,8 +749,8 @@ bool PluginManager::Impl::finalizePlugin(PluginInfoPtr info) if(allDependentsFinalized){ info->plugin->isActive_ = false; if(!info->plugin->finalize()){ - MessageOut::master()->putError( - formatR(_("{0}-plugin cannot be finalized.\n"), info->name)); + mout->putErrorln( + formatR(_("{0}-plugin cannot be finalized."), info->name)); } else { bool isUnloadable = info->plugin->isUnloadable(); info->status = PluginManager::FINALIZED; @@ -765,7 +787,7 @@ void PluginManager::Impl::unloadPluginsActually() if(info->dll.unload()){ info->status = PluginManager::UNLOADED; nameToPluginInfoMap.erase(info->name); - mout->put(formatR(_("Plugin DLL \"{}\" has been unloaded.\n"), info->pathString)); + mout->putln(formatR(_("Plugin DLL \"{}\" has been unloaded."), info->pathString)); if(info->doReloading){ info->status = PluginManager::NOT_LOADED; info->doReloading = false; diff --git a/src/Base/PointSetItem.cpp b/src/Base/PointSetItem.cpp index 87d49fdf8..5317da058 100644 --- a/src/Base/PointSetItem.cpp +++ b/src/Base/PointSetItem.cpp @@ -163,7 +163,6 @@ void PointSetItem::initializeClass(ExtensionManager* ext) if(!initialized){ ItemManager& im = ext->itemManager(); im.registerClass(N_("PointSetItem")); - im.addCreationPanel(); im.addFileIO(new PointSetItemPcdFileIo); initialized = true; } @@ -999,10 +998,10 @@ LocationProxyPtr PointSetItem::getLocationProxy() PointSetLocation::PointSetLocation(PointSetItem* item) - : LocationProxy(GlobalLocation), + : LocationProxy(item, GlobalLocation), item(item) { - + setNameDependencyOnItemName(); } diff --git a/src/Base/PositionTagGroupItem.cpp b/src/Base/PositionTagGroupItem.cpp index 754b8b7ed..5894c8033 100644 --- a/src/Base/PositionTagGroupItem.cpp +++ b/src/Base/PositionTagGroupItem.cpp @@ -114,12 +114,11 @@ class TagGroupLocationProxy : public LocationProxy TagGroupLocationProxy(PositionTagGroupItem::Impl* impl); virtual std::string getName() const override; - virtual Item* getCorrespondingItem() override; virtual Isometry3 getLocation() const override; virtual bool setLocation(const Isometry3& T) override; virtual void finishLocationEditing() override; virtual SignalProxy sigLocationChanged() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; }; typedef ref_ptr TagGroupLocationProxyPtr; @@ -141,7 +140,7 @@ class TagLocationProxy : public LocationProxy virtual bool setLocation(const Isometry3& T) override; virtual void finishLocationEditing() override; virtual SignalProxy sigLocationChanged() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; }; typedef ref_ptr TagLocationProxyPtr; @@ -156,7 +155,7 @@ class TagParentLocationProxy : public LocationProxy virtual std::string getName() const override; virtual Isometry3 getLocation() const override; virtual SignalProxy sigLocationChanged() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; }; typedef ref_ptr TagParentLocationProxyPtr; @@ -285,7 +284,7 @@ void PositionTagGroupItem::initializeClass(ExtensionManager* ext) if(!initialized){ auto& im = ext->itemManager(); im.registerClass(N_("PositionTagGroupItem")); - im.addCreationPanel(); + im.addCreationPanel(false); initialized = true; } } @@ -311,8 +310,6 @@ PositionTagGroupItem::PositionTagGroupItem(const PositionTagGroupItem& org, Clon : Item(org) { impl = new Impl(this, org.impl, cloneMap); - - setChecked(org.isChecked()); } @@ -1461,10 +1458,10 @@ bool SceneTagGroup::onContextMenuRequest(SceneWidgetEvent* event) TagGroupLocationProxy::TagGroupLocationProxy(PositionTagGroupItem::Impl* impl) - : LocationProxy(ParentRelativeLocation), + : LocationProxy(impl->self, ParentRelativeLocation), impl(impl) { - + setNameDependencyOnItemName(); } @@ -1474,12 +1471,6 @@ std::string TagGroupLocationProxy::getName() const } -Item* TagGroupLocationProxy::getCorrespondingItem() -{ - return impl->self; -} - - Isometry3 TagGroupLocationProxy::getLocation() const { return impl->T_offset; @@ -1505,17 +1496,19 @@ SignalProxy TagGroupLocationProxy::sigLocationChanged() } -LocationProxyPtr TagGroupLocationProxy::getParentLocationProxy() const +LocationProxyPtr TagGroupLocationProxy::getParentLocationProxy() { return impl->groupParentLocation; } TagLocationProxy::TagLocationProxy(PositionTagGroupItem::Impl* impl, int tagIndex) - : LocationProxy(ParentRelativeLocation), + : LocationProxy(impl->self, ParentRelativeLocation), impl(impl), tagIndex(tagIndex) { + setNameDependencyOnItemName(); + connection.reset( impl->tagGroup->sigTagPositionUpdated().connect( [&](int index){ @@ -1579,23 +1572,23 @@ SignalProxy TagLocationProxy::sigLocationChanged() } -LocationProxyPtr TagLocationProxy::getParentLocationProxy() const +LocationProxyPtr TagLocationProxy::getParentLocationProxy() { return impl->tagParentLocation; } TagParentLocationProxy::TagParentLocationProxy(PositionTagGroupItem::Impl* impl) - : LocationProxy(ParentRelativeLocation), + : LocationProxy(impl->self, ParentRelativeLocation), impl(impl) { - + setNameDependencyOnItemName(); } std::string TagParentLocationProxy::getName() const { - return formatR(_("{0}: Origin"), impl->self->name()); + return formatR(_("{0}: Origin"), impl->self->displayName()); } @@ -1611,7 +1604,7 @@ SignalProxy TagParentLocationProxy::sigLocationChanged() } -LocationProxyPtr TagParentLocationProxy::getParentLocationProxy() const +LocationProxyPtr TagParentLocationProxy::getParentLocationProxy() { if(impl->groupParentLocation){ return impl->groupParentLocation; diff --git a/src/Base/ProjectBackupManager.cpp b/src/Base/ProjectBackupManager.cpp new file mode 100644 index 000000000..59332f5c9 --- /dev/null +++ b/src/Base/ProjectBackupManager.cpp @@ -0,0 +1,873 @@ +#include "ProjectBackupManager.h" +#include "App.h" +#include "AppConfig.h" +#include "ProjectManager.h" +#include "RootItem.h" +#include "TimeBar.h" +#include "InfoBar.h" +#include "Timer.h" +#include "Dialog.h" +#include +#include "Buttons.h" +#include "CheckBox.h" +#include "SpinBox.h" +#include "Separator.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gettext.h" + +using namespace std; +using namespace cnoid; +namespace filesystem = cnoid::stdx::filesystem; + +namespace { + +static ProjectBackupManager* instance_ = nullptr; +MappingPtr config; + +class ItemFileInfo +{ +public: + filesystem::path filePath; + string fileFormat; + int fileConsistencyId; + ScopedConnection itemConnection; +}; + +class ConfigDialog : public Dialog +{ +public: + ProjectBackupManager::Impl* manager; + CheckBox enableAutoBackupCheck; + SpinBox intervalSpin; + SpinBox maxNumBackupsSpin; + CheckBox pauseAutoBackupDuringPlaybackCheck; + CheckBox pauseAutoBackupDuringContinuousUpdateCheck; + CheckBox doBackupBeforeStartingContinuousUpdateCheck; + + ConfigDialog(ProjectBackupManager::Impl* manager); + void updateWidgets(); + void show(); + virtual void accept() override; + virtual void done(int r) override; +}; + +class RecoveryDialog : public Dialog +{ +public: + ProjectBackupManager::Impl* manager; + QTableWidget* table; + + RecoveryDialog(ProjectBackupManager::Impl* manager); + void updateProjectsInTableWidget(); + void show(); + void restoreProject(int projectIndex); + virtual void reject() override; +}; + +} + +namespace cnoid { + +class ProjectBackupManager::Impl +{ +public: + ProjectBackupManager* self; + ProjectManager* projectManager; + RootItem* rootItem; + Timer* timer; + int interval; // sec + bool isAutoBackupEnabled; + bool isAutoBackupPausedDuringPlayback; + bool isAutoBackupPausedDuringContinuousUpdate; + bool doBackupBeforeStartingContinuousUpdate; + int maxNumBackups; + int autoBackupPauseCounter; + + filesystem::path backupTopDirPath; + filesystem::path crashMarkerFilePath; + filesystem::path currentBackupDirPath; + deque backupDirPaths; + bool isBackupDirectoryEnsured; + bool isCrashMarkerCreated; + + typedef unordered_map ItemFileInfoMap; + ItemFileInfoMap itemFileInfoMap; + + vector backupProjectFilePaths; + + ScopedConnection rootItemConnection; + + ConfigDialog* configDialog; + RecoveryDialog* recoveryDialog; + + Impl(ProjectBackupManager* self); + ~Impl(); + void writeConfigurations(); + void setAutoBackupInterval(int sec); + void enableAutoBackup(bool on); + void pauseAutoBackup(); + void unpauseAutoBackup(); + void onAutoBackupRequest(); + void onTreeContinuousUpdateStateExistenceChanged(bool on); + bool ensureBackupDirectory(); + bool saveProjectAsBackup(); + void removeOldBackups(); + bool getItemBackupFileInformation( + Item* item, + stdx::filesystem::path& out_backupFilePath, + stdx::filesystem::path& out_hardLinkFilePath, + std::string& out_fileFormat); + void updateBackupProjectFilePaths(); + bool restoreProject(int backupProjectIndex); +}; + +} + + +void ProjectBackupManager::initializeClass() +{ + config = AppConfig::archive()->openMapping("ProjectBackupManager"); + if(config->get("enable_auto_backup", false)){ + App::sigExecutionStarted().connect( + []{ instance()->enableAutoBackupAtStartup(); }); + } +} + + +ProjectBackupManager* ProjectBackupManager::instance() +{ + static bool isAboutToQuit = false; + + if(!instance_ && !isAboutToQuit){ + instance_ = new ProjectBackupManager; + App::sigAboutToQuit().connect( + []{ + delete instance_; + instance_ = nullptr; + isAboutToQuit = true; + }); + } + + return instance_; +} + + +bool ProjectBackupManager::isAutoBackupEnabled() +{ + if(instance_){ + return instance_->impl->isAutoBackupEnabled; + } + return false; +} + + +ProjectBackupManager::ProjectBackupManager() +{ + impl = new Impl(this); +} + + +ProjectBackupManager::Impl::Impl(ProjectBackupManager* self) + : self(self) +{ + projectManager = ProjectManager::instance(); + rootItem = RootItem::instance(); + timer = nullptr; + interval = config->get("backup_interval", 60); // Default is 1 min. + isAutoBackupEnabled = false; + isAutoBackupPausedDuringPlayback = config->get("pause_backup_in_playback", true); + isAutoBackupPausedDuringContinuousUpdate = config->get("pause_backup_in_continuous_update", true); + doBackupBeforeStartingContinuousUpdate = config->get("backup_before_continuous_update", true); + maxNumBackups = config->get("max_backups", 10); + autoBackupPauseCounter = 0; + backupTopDirPath = AppConfig::configDataDirPath() / "backup"; + crashMarkerFilePath = backupTopDirPath / "crash_marker.tmp"; + isBackupDirectoryEnsured = false; + isCrashMarkerCreated = false; + configDialog = nullptr; + recoveryDialog = nullptr; +} + + +ProjectBackupManager::~ProjectBackupManager() +{ + delete impl; +} + + +ProjectBackupManager::Impl::~Impl() +{ + if(configDialog){ + delete configDialog; + configDialog = nullptr; + } + if(recoveryDialog){ + delete recoveryDialog; + recoveryDialog = nullptr; + } + if(timer){ + delete timer; + timer = nullptr; + } + if(isCrashMarkerCreated){ + filesystem::remove(crashMarkerFilePath); + } +} + + +void ProjectBackupManager::Impl::writeConfigurations() +{ + config->write("enable_auto_backup", isAutoBackupEnabled); + config->write("backup_interval", interval); + config->write("max_backups", maxNumBackups); + config->write("pause_backup_in_playback", isAutoBackupPausedDuringPlayback); + config->write("pause_backup_in_continuous_update", isAutoBackupPausedDuringContinuousUpdate); + config->write("backup_before_continuous_update", doBackupBeforeStartingContinuousUpdate); + AppConfig::flush(); +} + + +void ProjectBackupManager::enableAutoBackupAtStartup() +{ + if(checkForPreviousCrash()){ + bool ok = showWarningDialog( + _("The application has crashed, and auto-backup project files are available for project recovery. " + "Do you want to show the dialog for recovery?"), + true); + if(ok){ + impl->isAutoBackupEnabled = true; + showRecoveryDialog(); + } + } + enableAutoBackup(); +} + + +void ProjectBackupManager::setAutoBackupInterval(int sec) +{ + impl->setAutoBackupInterval(sec); +} + + +void ProjectBackupManager::Impl::setAutoBackupInterval(int sec) +{ + interval = sec; + + if(timer && timer->isActive()){ + enableAutoBackup(true); + } +} + + +void ProjectBackupManager::setMaxNumBackups(int n) +{ + impl->maxNumBackups = n; + impl->removeOldBackups(); +} + + +bool ProjectBackupManager::checkForPreviousCrash() +{ + if(!impl->isCrashMarkerCreated){ + if(filesystem::exists(impl->crashMarkerFilePath)){ + return true; + } + } + return false; +} + + +void ProjectBackupManager::enableAutoBackup(bool on) +{ + impl->enableAutoBackup(on); +} + + +void ProjectBackupManager::Impl::enableAutoBackup(bool on) +{ + if(on){ + ensureBackupDirectory(); + + if(!timer){ + timer = new Timer; + timer->sigTimeout().connect( + [this]{ onAutoBackupRequest(); }); + } + timer->setInterval(interval * 1000); // to msec + isAutoBackupEnabled = true; + + if(!rootItemConnection.connected()){ + rootItemConnection = + rootItem->sigTreeContinuousUpdateStateExistenceChanged().connect( + [this](bool on){ onTreeContinuousUpdateStateExistenceChanged(on); }); + } + + if(autoBackupPauseCounter == 0){ + timer->start(); + } + + } else { + rootItemConnection.disconnect(); + + if(timer){ + timer->stop(); + } + + isAutoBackupEnabled = false; + } +} + + +void ProjectBackupManager::disableAutoBackup() +{ + impl->enableAutoBackup(false); +} + + +void ProjectBackupManager::Impl::pauseAutoBackup() +{ + ++autoBackupPauseCounter; + if(timer){ + timer->stop(); + } +} + + +void ProjectBackupManager::Impl::unpauseAutoBackup() +{ + if(autoBackupPauseCounter > 0){ + --autoBackupPauseCounter; + if(isAutoBackupEnabled && autoBackupPauseCounter == 0){ + if(timer && !timer->isActive()){ + enableAutoBackup(true); + } + } + } +} + + +void ProjectBackupManager::Impl::onAutoBackupRequest() +{ + if(isAutoBackupPausedDuringPlayback){ + if(TimeBar::instance()->isPlaybackProcessing()){ + return; // Skip auto backup + } + } + if(isAutoBackupPausedDuringContinuousUpdate){ + if(rootItem->hasAnyItemsInContinuousUpdateState()){ + return; // Skip auto backup + } + } + + saveProjectAsBackup(); +} + + +void ProjectBackupManager::Impl::onTreeContinuousUpdateStateExistenceChanged(bool on) +{ + if(on){ + if(doBackupBeforeStartingContinuousUpdate){ + saveProjectAsBackup(); + } + } +} + + +bool ProjectBackupManager::Impl::ensureBackupDirectory() +{ + if(isBackupDirectoryEnsured){ + return true; + } + + bool isDirectoryReady = false; + stdx::error_code ec; + if(filesystem::exists(backupTopDirPath)){ + if(filesystem::is_directory(backupTopDirPath)){ + isDirectoryReady = true; + } else { + filesystem::remove(backupTopDirPath, ec); + } + } + + if(!isDirectoryReady && !ec){ + isDirectoryReady = filesystem::create_directories(backupTopDirPath, ec); + } + + if(!isDirectoryReady || ec){ + MessageOut::master()->putErrorln( + formatR(_("The project backup directory \"{0}\" cannot be created: {1}"), + toUTF8(backupTopDirPath.string()), toUTF8(ec.message()))); + return false; + } + + // Create a crash marker file + std::ofstream(crashMarkerFilePath.string()).close(); + isCrashMarkerCreated = true; + + backupDirPaths.clear(); + static std::regex dateTimePattern("^\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-.+$"); + for(const auto& entry : filesystem::directory_iterator(backupTopDirPath, ec)){ + if(filesystem::is_directory(entry)){ + auto& path = entry.path(); + std::string dirName = path.filename().string(); + if(std::regex_match(dirName, dateTimePattern)){ + backupDirPaths.push_back(path); + } + } + } + if(!backupDirPaths.empty()){ + std::sort(backupDirPaths.begin(), backupDirPaths.end(), + [](const filesystem::path& a, const filesystem::path& b){ + return a.string() > b.string(); + }); + } + + isBackupDirectoryEnsured = true; + + return true; +} + + +bool ProjectBackupManager::saveProjectAsBackup() +{ + return impl->saveProjectAsBackup(); +} + + +bool ProjectBackupManager::Impl::saveProjectAsBackup() +{ + if(!ensureBackupDirectory()){ + return false; + } + + auto projectName = projectManager->currentProjectName(); + if(projectName.empty()){ + projectName = "NewProject"; + } + + std::time_t now = std::time(nullptr); + std::tm* localTime = std::localtime(&now); + char datetime[20]; + std::strftime(datetime, sizeof(datetime), "%Y-%m-%d-%H-%M-%S", localTime); + string dirName = formatC("{0}-{1}", datetime, projectName); + + currentBackupDirPath = backupTopDirPath / fromUTF8(dirName); + + if(filesystem::exists(currentBackupDirPath)){ + currentBackupDirPath.clear(); + return false; // Skip this time. Another Choreonoid instance may be doing backup. + } + + stdx::error_code ec; + bool isDirectoryReady = filesystem::create_directories(currentBackupDirPath, ec); + + if(!isDirectoryReady || ec){ + MessageOut::master()->putErrorln( + formatR(_("The project backup directory \"{0}\" cannot be created: {1}"), + toUTF8(currentBackupDirPath.string()), toUTF8(ec.message()))); + currentBackupDirPath.clear(); + return false; + } + + auto backupProjectFilePath = currentBackupDirPath / (fromUTF8(projectName) + ".cnoid"); + auto backupProjectFile = toUTF8(backupProjectFilePath.string()); + + auto infoBar = InfoBar::instance(); + infoBar->notify( + formatR(_("Automatically saving project \"{0}\" to \"{1}\" as backup ..."), + projectName, backupProjectFile)); + + projectManager->saveProjectAsBackup(backupProjectFile); + + infoBar->notify( + formatR(_("Automatically saving project \"{0}\" to \"{1}\" as backup ... done."), + projectName, backupProjectFile)); + + backupDirPaths.push_front(currentBackupDirPath); + currentBackupDirPath.clear(); + + removeOldBackups(); + + return true; +} + + +void ProjectBackupManager::Impl::removeOldBackups() +{ + while(backupDirPaths.size() > maxNumBackups){ + stdx::error_code ec; + filesystem::remove_all(backupDirPaths.back(), ec); + backupDirPaths.pop_back(); + } +} + + +bool ProjectBackupManager::getItemBackupFileInformation +(Item* item, + stdx::filesystem::path& out_backupFilePath, + stdx::filesystem::path& out_hardLinkFilePath, + std::string& out_fileFormat) +{ + return impl->getItemBackupFileInformation( + item, out_backupFilePath, out_hardLinkFilePath, out_fileFormat); +} + + +bool ProjectBackupManager::Impl::getItemBackupFileInformation +(Item* item, + stdx::filesystem::path& out_backupFilePath, + stdx::filesystem::path& out_hardLinkFilePath, + std::string& out_fileFormat) +{ + if(currentBackupDirPath.empty()){ + return false; + } + + // Note: filename is in native character encoding + auto filename = fromUTF8(item->fileName()); + if(filename.empty()){ + filename = fromUTF8(item->name()); + } + if(filename.empty()){ + filename = "item"; + } + + out_fileFormat = item->fileFormat(); + + out_backupFilePath = currentBackupDirPath / filename; + + static std::regex pattern("^([^-.]*)(-([0-9]+))?(\\.(.*?))?$"); + std::smatch matches; + while(filesystem::exists(out_backupFilePath)){ + if(!std::regex_match(filename, matches, pattern)){ + return false; + } + int number = 2; + if(matches[3].matched){ + number = std::stoi(matches[3].str()) + 1; + } + if(matches[5].matched){ // extension + filename = formatR("{0}-{1}.{2}", matches[1].str(), number, matches[5].str()); + } else { + filename = formatR("{0}-{1}", matches[1].str(), number); + } + out_backupFilePath = currentBackupDirPath / filename; + } + + int lastFileConsistencyId; + auto& info = itemFileInfoMap[item]; + if(info.itemConnection.connected()){ + lastFileConsistencyId = info.fileConsistencyId; + info.fileConsistencyId = item->fileConsistencyId(); + } else { + info.itemConnection = + item->sigDisconnectedFromRoot().connect( + [this, item]{ itemFileInfoMap.erase(item); }); + info.fileConsistencyId = item->fileConsistencyId(); + lastFileConsistencyId = info.fileConsistencyId - 1; + } + + out_hardLinkFilePath.clear(); + if(item->isConsistentWithFile()){ + const auto& itemFilePath = item->filePath(); + if(!itemFilePath.empty()){ + out_hardLinkFilePath = fromUTF8(itemFilePath); + if(!filesystem::exists(out_hardLinkFilePath)){ + out_hardLinkFilePath.clear(); + } + } + } + if(out_hardLinkFilePath.empty()){ + if(item->fileConsistencyId() == lastFileConsistencyId){ + if(filesystem::exists(info.filePath)){ + out_hardLinkFilePath = info.filePath; + out_fileFormat = info.fileFormat; + } + } + } + + info.filePath = out_backupFilePath; + + return true; +} + + +void ProjectBackupManager::setItemBackupFileFormat(Item* item, const std::string& fileFormat) +{ + auto it = impl->itemFileInfoMap.find(item); + if(it != impl->itemFileInfoMap.end()){ + it->second.fileFormat = fileFormat; + } +} + + +const std::vector& ProjectBackupManager::getBackupProjectFilePaths() +{ + impl->updateBackupProjectFilePaths(); + return impl->backupProjectFilePaths; +} + + +void ProjectBackupManager::Impl::updateBackupProjectFilePaths() +{ + ensureBackupDirectory(); + + backupProjectFilePaths.clear(); + + static std::regex dirNamePattern("^(\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2})-(.+)$"); + std::smatch matches; + for(auto& dirPath : backupDirPaths){ + string dirName = dirPath.filename().string(); + if(std::regex_match(dirName, matches, dirNamePattern)){ + string projectName = matches[2].str(); + auto projectFilePath = dirPath / (projectName + ".cnoid"); + backupProjectFilePaths.push_back(projectFilePath); + } + } +} + + +bool ProjectBackupManager::restoreProject(int backupProjectIndex) +{ + return impl->restoreProject(backupProjectIndex); +} + + +bool ProjectBackupManager::Impl::restoreProject(int backupProjectIndex) +{ + bool restored = false; + + if(backupProjectIndex < static_cast(backupProjectFilePaths.size())){ + projectManager->clearProject(); + auto& path = backupProjectFilePaths[backupProjectIndex]; + auto items = projectManager->loadProject(toUTF8(path.string())); + if(!items.empty()){ + restored = true; + } + } + + return restored; +} + + +void ProjectBackupManager::showConfigDialog() +{ + if(!impl->configDialog){ + impl->configDialog = new ConfigDialog(impl); + } + impl->configDialog->show(); +} + + +void ProjectBackupManager::showRecoveryDialog() +{ + if(!impl->recoveryDialog){ + impl->recoveryDialog = new RecoveryDialog(impl); + } + impl->pauseAutoBackup(); + impl->projectManager->tryToCloseProject(); + impl->recoveryDialog->show(); + impl->unpauseAutoBackup(); +} + + +ConfigDialog::ConfigDialog(ProjectBackupManager::Impl* manager_) + : manager(manager_) +{ + setWindowTitle(_("Project Auto-Backup Configuration")); + + auto vbox = new QVBoxLayout; + setLayout(vbox); + + enableAutoBackupCheck.setText(_("Enable auto backup")); + vbox->addWidget(&enableAutoBackupCheck); + + auto hbox = new QHBoxLayout; + hbox->addWidget(new QLabel(_("Time interval"))); + intervalSpin.setRange(1, 9999); + hbox->addWidget(&intervalSpin); + hbox->addWidget(new QLabel(_("sec."))); + hbox->addStretch(); + vbox->addLayout(hbox); + + hbox = new QHBoxLayout; + hbox->addWidget(new QLabel(_("Max number of backups"))); + maxNumBackupsSpin.setRange(1, 1000); + hbox->addWidget(&maxNumBackupsSpin); + hbox->addStretch(); + vbox->addLayout(hbox); + + pauseAutoBackupDuringPlaybackCheck.setText(_("Pause auto backup during playback")); + vbox->addWidget(&pauseAutoBackupDuringPlaybackCheck); + + pauseAutoBackupDuringContinuousUpdateCheck.setText(_("Pause auto backup during continuous update")); + vbox->addWidget(&pauseAutoBackupDuringContinuousUpdateCheck); + + doBackupBeforeStartingContinuousUpdateCheck.setText(_("Backup before starting continuous update")); + vbox->addWidget(&doBackupBeforeStartingContinuousUpdateCheck); + + int vspace = layoutVerticalSpacing(); + vbox->addSpacing(vspace); + vbox->addWidget(new HSeparator); + vbox->addSpacing(vspace); + + auto buttonBox = new QDialogButtonBox(this); + buttonBox->addButton(QDialogButtonBox::Ok); + buttonBox->addButton(QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + vbox->addWidget(buttonBox); +} + + +void ConfigDialog::updateWidgets() +{ + enableAutoBackupCheck.setChecked(manager->isAutoBackupEnabled); + intervalSpin.setValue(manager->interval); + maxNumBackupsSpin.setValue(manager->maxNumBackups); + pauseAutoBackupDuringPlaybackCheck.setChecked(manager->isAutoBackupPausedDuringPlayback); + pauseAutoBackupDuringContinuousUpdateCheck.setChecked(manager->isAutoBackupPausedDuringContinuousUpdate); + doBackupBeforeStartingContinuousUpdateCheck.setChecked(manager->doBackupBeforeStartingContinuousUpdate); +} + + +void ConfigDialog::show() +{ + manager->pauseAutoBackup(); + updateWidgets(); + Dialog::show(); +} + + +void ConfigDialog::accept() +{ + manager->setAutoBackupInterval(intervalSpin.value()); + manager->self->setMaxNumBackups(maxNumBackupsSpin.value()); + manager->isAutoBackupPausedDuringPlayback = pauseAutoBackupDuringPlaybackCheck.isChecked(); + manager->isAutoBackupPausedDuringContinuousUpdate = pauseAutoBackupDuringContinuousUpdateCheck.isChecked(); + manager->doBackupBeforeStartingContinuousUpdate = doBackupBeforeStartingContinuousUpdateCheck.isChecked(); + manager->enableAutoBackup(enableAutoBackupCheck.isChecked()); + manager->writeConfigurations(); + + Dialog::accept(); +} + + +void ConfigDialog::done(int r) +{ + manager->unpauseAutoBackup(); + Dialog::done(r); +} + + + +RecoveryDialog::RecoveryDialog(ProjectBackupManager::Impl* manager) + : manager(manager) +{ + setWindowTitle(_("Backup Project Recovery")); + + auto vbox = new QVBoxLayout; + setLayout(vbox); + + table = new QTableWidget(this); + table->setColumnCount(2); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setSelectionMode(QAbstractItemView::SingleSelection); + table->verticalHeader()->hide(); + table->setHorizontalHeaderItem(0, new QTableWidgetItem(_("Date and Time"))); + table->setHorizontalHeaderItem(1, new QTableWidgetItem(_("Project"))); + auto header = table->horizontalHeader(); + header->setSectionResizeMode(QHeaderView::ResizeToContents); + header->setStretchLastSection(true); + + vbox->addWidget(table); + + connect(table, &QTableWidget::cellDoubleClicked, + [this](int row, int column){ restoreProject(row); }); + + auto buttonBox = new QDialogButtonBox(this); + auto recoveryButton = new PushButton(_("&Recovery")); + recoveryButton->setDefault(true); + + recoveryButton->sigClicked().connect( + [this]{ + auto items = table->selectedItems(); + if(!items.empty()){ + restoreProject(items[0]->row()); + } + }); + + buttonBox->addButton(recoveryButton, QDialogButtonBox::ActionRole); + + buttonBox->addButton(QDialogButtonBox::Close); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + vbox->addWidget(buttonBox); +} + + +void RecoveryDialog::updateProjectsInTableWidget() +{ + static std::regex dirNamePattern("^(\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2})-(.+)$"); + std::smatch matches; + auto& projectFilePaths = manager->self->getBackupProjectFilePaths(); + table->setRowCount(projectFilePaths.size()); + for(size_t i=0; i < projectFilePaths.size(); ++i){ + auto& path = projectFilePaths[i]; + auto directory = path.parent_path().filename().string(); + if(std::regex_match(directory, matches, dirNamePattern)){ + string date = matches[1].str(); + string projectName = toUTF8(matches[2].str()); + auto dateItem = new QTableWidgetItem(date.c_str()); + dateItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + table->setItem(i, 0, dateItem); + auto nameItem = new QTableWidgetItem(projectName.c_str()); + nameItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + table->setItem(i, 1, nameItem); + } + } +} + + +void RecoveryDialog::show() +{ + manager->pauseAutoBackup(); + + updateProjectsInTableWidget(); + + int h = table->rowHeight(0) * 14; + int w = table->columnWidth(0) * 3; + resize(w, h); + + Dialog::show(); +} + + +void RecoveryDialog::restoreProject(int projectIndex) +{ + if(!manager->restoreProject(projectIndex)){ + showErrorDialog(_("Selected backup is not found.")); + updateProjectsInTableWidget(); + } +} + + +void RecoveryDialog::reject() +{ + manager->unpauseAutoBackup(); + Dialog::reject(); +} diff --git a/src/Base/ProjectBackupManager.h b/src/Base/ProjectBackupManager.h new file mode 100644 index 000000000..0d51f2b2e --- /dev/null +++ b/src/Base/ProjectBackupManager.h @@ -0,0 +1,48 @@ +#ifndef CNOID_BASE_PROJECT_BACKUP_MANAGER_H +#define CNOID_BASE_PROJECT_BACKUP_MANAGER_H + +#include +#include +#include "exportdecl.h" + +namespace cnoid { + +class Item; + +class CNOID_EXPORT ProjectBackupManager +{ +public: + static void initializeClass(); + static ProjectBackupManager* instance(); + static bool isAutoBackupEnabled(); + + void enableAutoBackupAtStartup(); + void setAutoBackupInterval(int sec); + void setMaxNumBackups(int n); + bool checkForPreviousCrash(); + void enableAutoBackup(bool on = true); + void disableAutoBackup(); + bool saveProjectAsBackup(); + bool getItemBackupFileInformation( + Item* item, + stdx::filesystem::path& out_backupFilePath, + stdx::filesystem::path& out_hardLinkFilePath, + std::string& out_fileFormat); + void setItemBackupFileFormat(Item* item, const std::string& fileFormat); + const std::vector& getBackupProjectFilePaths(); + bool restoreProject(int backupProjectIndex); + void showConfigDialog(); + void showRecoveryDialog(); + + class Impl; + +private: + ProjectBackupManager(); + ~ProjectBackupManager(); + + Impl* impl; +}; + +} + +#endif diff --git a/src/Base/ProjectManager.cpp b/src/Base/ProjectManager.cpp index 89d630dd5..43d46412d 100644 --- a/src/Base/ProjectManager.cpp +++ b/src/Base/ProjectManager.cpp @@ -1,8 +1,8 @@ #include "ProjectManager.h" #include "RootItem.h" +#include "SubProjectItem.h" #include "ItemManager.h" #include "ViewManager.h" -#include "MessageView.h" #include "ToolBar.h" #include "Archive.h" #include "ItemTreeArchiver.h" @@ -13,6 +13,7 @@ #include "FileDialog.h" #include "MainWindow.h" #include "CheckBox.h" +#include #include #include #include @@ -37,7 +38,6 @@ bool isLayoutInclusionMode = true; bool isTemporaryItemSaveCheckAvailable = true; int projectBeingLoadedCounter = 0; MainWindow* mainWindow = nullptr; -MessageView* mv = nullptr; vector projectFilesToLoad; class SaveDialog : public FileDialog @@ -68,7 +68,7 @@ class ProjectManager::Impl template bool restoreObjectStates( - Archive* projectArchive, Archive* states, const vector& objects, const char* nameSuffix); + Archive* projectArchive, Archive* states, const vector& objects, const char* nameSuffix, MessageOut* mout); ItemList<> loadProject( const std::string& filename, Item* parentItem, @@ -79,14 +79,14 @@ class ProjectManager::Impl template bool storeObjects(Archive& parentArchive, const char* key, vector objects); - bool saveProject(const string& filename, Item* item, bool doSaveTemporaryItems); + bool saveProject(const string& filename, Item* item, bool doSaveTemporaryItems, bool isBackupMode); void onProjectOptionsParsed(); void onInputFileOptionsParsed(std::vector& inputFiles); bool onSaveDialogAboutToFinish(int result); bool confirmToCloseProject(bool isAboutToLoadNewProject); - bool checkValidItemExistence(Item* item) const; - bool checkIfItemsConsistentWithProjectArchive(Item* item) const; + static bool checkValidItemExistence(Item* item); + static bool checkIfItemsConsistentWithProjectArchive(Item* item, bool isTopItem = true); void connectArchiver( const std::string& name, @@ -153,7 +153,6 @@ void ProjectManager::initializeClass(ExtensionManager* ext) if(!instance_){ instance_ = ext->manage(new ProjectManager(ext)); mainWindow = MainWindow::instance(); - mv = MessageView::instance(); } } @@ -233,16 +232,23 @@ void ProjectManager::setCurrentProjectName(const std::string& name) void ProjectManager::Impl::setCurrentProjectFile(const string& filename) { - filesystem::path path(fromUTF8(filename)); - auto name = toUTF8(path.stem().string()); - currentProjectName = name; - mainWindow->setProjectTitle(name); + if(filename.empty()){ + currentProjectName.clear(); + currentProjectFile.clear(); + currentProjectDirectory.clear(); + } else { + filesystem::path path(fromUTF8(filename)); + auto name = toUTF8(path.stem().string()); + currentProjectName = name; - // filesystem::canonical can only be used with C++17 - path = filesystem::lexically_normal(filesystem::absolute(path)); + // filesystem::canonical can only be used with C++17 + path = filesystem::lexically_normal(filesystem::absolute(path)); + + currentProjectFile = toUTF8(path.string()); + currentProjectDirectory = toUTF8(path.parent_path().string()); + } - currentProjectFile = toUTF8(path.string()); - currentProjectDirectory = toUTF8(path.parent_path().string()); + mainWindow->setProjectTitle(currentProjectName); } @@ -319,7 +325,7 @@ void ProjectManager::Impl::clearProject() template bool ProjectManager::Impl::restoreObjectStates -(Archive* projectArchive, Archive* states, const vector& objects, const char* nameSuffix) +(Archive* projectArchive, Archive* states, const vector& objects, const char* nameSuffix, MessageOut* mout) { bool restored = false; for(size_t i=0; i < objects.size(); ++i){ @@ -333,10 +339,9 @@ bool ProjectManager::Impl::restoreObjectStates restored = true; } } catch(const ValueNode::Exception& ex){ - mv->putln( - formatR(_("The state of the \"{0}\" {1} was not completely restored.\n{2}"), - name, nameSuffix, ex.message()), - MessageView::Warning); + mout->putWarningln( + formatR(_("The state of the \"{0}\" {1} was not completely restored.\n"), + name, nameSuffix, ex.message())); } } } @@ -360,14 +365,17 @@ ItemList<> ProjectManager::Impl::loadProject (const std::string& filename, Item* parentItem, bool isInvokingApplication, bool isBuiltinProject, bool doClearExistingProject) { + auto mout = MessageOut::master(); + ItemList<> topLevelItems; if(doClearExistingProject){ clearProject(); - mv->flush(); + mout->flush(); sigProjectCleared(); } + bool isBackup = false; bool isTopProject = (projectBeingLoadedCounter == 0); if(isTopProject && parentItem){ projectBeingLoadedCounter = 1; @@ -383,9 +391,9 @@ ItemList<> ProjectManager::Impl::loadProject try { if(!isBuiltinProject){ - mv->notify(formatR(_("Loading project file \"{}\" ..."), filename)); + mout->notify(formatR(_("Loading project file \"{}\" ..."), filename)); if(!isInvokingApplication){ - mv->flush(); + mout->flush(); } } @@ -396,12 +404,12 @@ ItemList<> ProjectManager::Impl::loadProject if(!isBuiltinProject){ parsed = reader.load(filename); if(!parsed){ - mv->putln(reader.errorMessage(), MessageView::Error); + mout->putErrorln(reader.errorMessage()); } } else { QResource resource(filename.c_str()); if(!resource.isValid()){ - mv->putln(formatR(_("Resource \"{0}\" is not found."), filename), MessageView::Error); + mout->putErrorln(formatR(_("Resource \"{0}\" is not found."), filename)); } else { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) auto data = resource.uncompressedData(); @@ -410,13 +418,13 @@ ItemList<> ProjectManager::Impl::loadProject #endif parsed = reader.parse(data.constData(), data.size()); if(!parsed){ - mv->putln(reader.errorMessage(), MessageView::Error); + mout->putErrorln(reader.errorMessage()); } } } if(parsed && reader.numDocuments() == 0){ - mv->putln(_("The project file is empty."), MessageView::Warning); + mout->putWarningln(_("The project file is empty.")); } else if(parsed){ @@ -426,7 +434,17 @@ ItemList<> ProjectManager::Impl::loadProject } Archive* archive = static_cast(reader.document()->toMapping()); - archive->initSharedInfo(filename, isSubProject); + archive->initSharedInfo(filename, isSubProject, mout, false); + + string backupSourceFile; + isBackup = archive->read("backup_source", backupSourceFile); + if(isBackup){ + if(backupSourceFile.empty()){ + mout->putln(_("This is a backup of an unsaved project.")); + } else { + mout->putln(formatR(_("This is a backup of \"{0}\"."), backupSourceFile)); + } + } std::set optionalPlugins; Listing& optionalPluginsNode = *archive->findListing("optionalPlugins"); @@ -486,7 +504,7 @@ ItemList<> ProjectManager::Impl::loadProject Archive* barStates = archive->findSubArchive("toolbars"); if(barStates->isValid()){ - if(restoreObjectStates(archive, barStates, mainWindow->toolBars(), "bar")){ + if(restoreObjectStates(archive, barStates, mainWindow->toolBars(), "bar", mout)){ loaded = true; } } @@ -494,7 +512,8 @@ ItemList<> ProjectManager::Impl::loadProject if(isInvokingApplication && !isBuiltinProject){ mainWindow->waitForWindowSystemToActivate(); } - + + itemTreeArchiver.setMessageOut(mout); itemTreeArchiver.reset(); Archive* items = archive->findSubArchive("items"); if(items->isValid()){ @@ -505,16 +524,18 @@ ItemList<> ProjectManager::Impl::loadProject numArchivedItems = itemTreeArchiver.numArchivedItems(); numRestoredItems = itemTreeArchiver.numRestoredItems(); if(!isBuiltinProject){ - mv->putln(formatR(_("{0} / {1} item(s) have been loaded."), numRestoredItems, numArchivedItems)); + mout->putln(formatR(_("{0} / {1} item(s) have been loaded."), numRestoredItems, numArchivedItems)); } if(numRestoredItems < numArchivedItems){ int numUnloaded = numArchivedItems - numRestoredItems; if(!isBuiltinProject){ - mv->putln(formatR(_("{0} item(s) were not loaded."), numUnloaded), MessageView::Warning); + mout->putWarningln( + formatR(_("{0} item(s) were not loaded."), numUnloaded)); } else { - mv->putln(formatR(_("{0} item(s) were not loaded in the builtin project \"{1}\"."), - numUnloaded, filename), MessageView::Warning); + mout->putWarningln( + formatR(_("{0} item(s) were not loaded in the builtin project \"{1}\"."), + numUnloaded, filename)); } } @@ -528,31 +549,42 @@ ItemList<> ProjectManager::Impl::loadProject if(archive->get("isNewProjectTemplate", false)){ clearCurrentProjectFile(); } else { - setCurrentProjectFile(filename); + if(!isBackup){ + setCurrentProjectFile(filename); + } else { + setCurrentProjectFile(backupSourceFile); + } } } - mv->flush(); + mout->flush(); archive->callPostProcesses(); if(!isBuiltinProject){ if(numRestoredItems == numArchivedItems){ - mv->notify(formatR(_("Project \"{}\" has been completely loaded."), filename)); + mout->notify(formatR(_("Project \"{}\" has been completely loaded."), filename)); + } else { + mout->notify(formatR(_("Project \"{}\" has been partially loaded."), filename)); + } + } + if(!isSubProject && isBackup){ + if(backupSourceFile.empty()){ + mout->putln(_("This project backup has been loaded as an unsaved project.")); } else { - mv->notify(formatR(_("Project \"{}\" has been partially loaded."), filename)); + mout->putln( + formatR(_("This project backup has been loaded as project \"{0}\"."), backupSourceFile)); } } } } } catch (const ValueNode::Exception& ex){ - mv->put(ex.message()); + mout->putErrorln(ex.message()); } if(!loaded){ - mv->notify( - formatR(_("Loading project \"{}\" failed. Any valid objects were not loaded."), filename), - MessageView::Error); + mout->notifyError( + formatR(_("Loading project \"{}\" failed. Any valid objects were not loaded."), filename)); clearCurrentProjectFile(); } @@ -564,8 +596,10 @@ ItemList<> ProjectManager::Impl::loadProject vp->clearBaseDirectory(); vp->clearProjectDirectory(); - for(auto& item : topLevelItems){ - setItemsConsistentWithProjectArchive(item); + if(!isBackup){ + for(auto& item : topLevelItems){ + setItemsConsistentWithProjectArchive(item); + } } } @@ -581,7 +615,17 @@ ItemList<> ProjectManager::Impl::loadProject void ProjectManager::Impl::setItemsConsistentWithProjectArchive(Item* item) { - item->setConsistentWithProjectArchive(true); + // The following check is necessary in the backup mode. + // In this mode, each item that is saved to a file is loaded from a backup file, + // but the file to save is replaced with the original file. In this case, the file consistency + // flag must be set to false when the backup file is not equivalent to the original file. + // This check maintains the flags for those items. + bool doUpdate = item->isConsistentWithFile(); + + if(doUpdate){ + item->setConsistentWithProjectArchive(true); + } + for(Item* child = item->childItem(); child; child = child->nextItem()){ setItemsConsistentWithProjectArchive(child); } @@ -591,7 +635,7 @@ void ProjectManager::Impl::setItemsConsistentWithProjectArchive(Item* item) void ProjectManager::restoreLayout(Mapping* layout) { ArchivePtr archive = new Archive; - archive->initSharedInfo("dummy", false); + archive->initSharedInfo("dummy", false, MessageOut::master(), false); archive->insert(layout, false); ViewManager::ViewStateInfo viewStateInfo; @@ -600,7 +644,7 @@ void ProjectManager::restoreLayout(Mapping* layout) if(ViewManager::restoreViewStates(viewStateInfo)){ Archive* barStates = archive->findSubArchive("toolbars"); if(barStates->isValid()){ - impl->restoreObjectStates(archive, barStates, mainWindow->toolBars(), "bar"); + impl->restoreObjectStates(archive, barStates, mainWindow->toolBars(), "bar", MessageOut::master()); } } } @@ -639,17 +683,32 @@ template bool ProjectManager::Impl::storeObjects bool ProjectManager::saveProject(const string& filename, Item* item) { - return impl->saveProject(filename, item, false); + return impl->saveProject(filename, item, false, false); } -bool ProjectManager::Impl::saveProject(const string& filename, Item* item, bool doSaveTemporaryItems) +bool ProjectManager::saveProjectAsBackup(const string& filename, Item* item) { + return impl->saveProject(filename, item, false, true); +} + + +bool ProjectManager::Impl::saveProject +(const string& filename, Item* item, bool doSaveTemporaryItems, bool isBackupMode) +{ + MessageOutPtr mout; + ofstream ofs; + + if(!isBackupMode){ + mout = MessageOut::master(); + } else { + ofs.open(fromUTF8(filename + ".log")); + mout = new MessageOut(ofs); + } + YAMLWriter writer(filename); if(!writer.isFileOpen()){ - mv->put( - formatR(_("Can't open file \"{}\" for writing.\n"), filename), - MessageView::Error); + mout->putErrorln(formatR(_("Can't open file \"{}\" for writing."), filename)); return false; } @@ -663,21 +722,23 @@ bool ProjectManager::Impl::saveProject(const string& filename, Item* item, bool item = rootItem; } - mv->putln(); - if(isSubProject){ - mv->notify(formatR(_("Saving sub project {0} as \"{1}\" ..."), item->displayName(), filename)); - } else { - mv->notify(formatR(_("Saving the project as \"{}\" ..."), filename)); + if(!isSubProject){ + mout->put("\n"); + mout->notify(formatR(_("Saving the project as \"{}\" ..."), filename)); } - mv->flush(); bool saved = false; + itemTreeArchiver.setMessageOut(mout); itemTreeArchiver.reset(); itemTreeArchiver.setTemporaryItemSaveEnabled(doSaveTemporaryItems); ArchivePtr archive = new Archive; - archive->initSharedInfo(filename, isSubProject); + archive->initSharedInfo(filename, isSubProject, mout, isBackupMode); + + if(isBackupMode){ + archive->write("backup_source", currentProjectFile, DOUBLE_QUOTED); + } ArchivePtr itemArchive = itemTreeArchiver.store(archive, item); @@ -730,12 +791,20 @@ bool ProjectManager::Impl::saveProject(const string& filename, Item* item, bool if(saved){ writer.setKeyOrderPreservationMode(true); writer.putNode(archive); - mv->notify(_("Saving the project file has been finished.")); if(!isSubProject){ + mout->notify(_("Saving the project file has been finished.")); + } else { + mout->notify(_("Saving the sub-project file has been finished.")); + } + if(!isBackupMode && !isSubProject){ setCurrentProjectFile(filename); } } else { - mv->notify(_("Saving the project file failed."), MessageView::Error); + if(!isSubProject){ + mout->notifyError(_("Saving the project file failed.")); + } else { + mout->notifyError(_("Saving the sub-project file failed.")); + } clearCurrentProjectFile(); } @@ -746,7 +815,7 @@ bool ProjectManager::Impl::saveProject(const string& filename, Item* item, bool ref_ptr ProjectManager::storeCurrentLayout() { ArchivePtr archive = new Archive; - archive->initSharedInfo("dummy", false); + archive->initSharedInfo("dummy", false, MessageOut::master(), false); ViewManager::storeViewStates(archive, "views", true); mainWindow->storeLayout(archive); return archive; @@ -759,7 +828,7 @@ bool ProjectManager::overwriteCurrentProject() if(impl->currentProjectFile.empty()){ saved = showDialogToSaveProject(); } else { - saved = impl->saveProject(impl->currentProjectFile, nullptr, false); + saved = impl->saveProject(impl->currentProjectFile, nullptr, false, false); } return saved; } @@ -805,7 +874,7 @@ bool ProjectManager::showDialogToLoadProject() filters << _("Any files (*)"); dialog.setNameFilters(filters); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); bool loaded = false; if(dialog.exec()){ @@ -827,7 +896,7 @@ bool ProjectManager::showDialogToSaveProject() dialog = new SaveDialog(impl); } - dialog->updatePresetDirectories(); + dialog->updatePresetDirectories(true); if(!dialog->selectFilePath(impl->currentProjectFile)){ dialog->selectFile(impl->currentProjectName); @@ -836,7 +905,7 @@ bool ProjectManager::showDialogToSaveProject() bool saved = false; if(dialog->exec() == QDialog::Accepted){ saved = impl->saveProject( - dialog->getSaveFilename(), nullptr, dialog->temporaryItemSaveCheck.isChecked()); + dialog->getSaveFilename(), nullptr, dialog->temporaryItemSaveCheck.isChecked(), false); } return saved; @@ -920,13 +989,13 @@ bool ProjectManager::Impl::confirmToCloseProject(bool isAboutToLoadNewProject) } -bool ProjectManager::checkValidItemExistence() const +bool ProjectManager::checkValidItemExistence() { - return impl->checkValidItemExistence(RootItem::instance()); + return Impl::checkValidItemExistence(RootItem::instance()); } -bool ProjectManager::Impl::checkValidItemExistence(Item* item) const +bool ProjectManager::Impl::checkValidItemExistence(Item* item) { if(!item->hasAttribute(Item::Builtin) && !item->isTemporary()){ return true; @@ -940,19 +1009,26 @@ bool ProjectManager::Impl::checkValidItemExistence(Item* item) const } -bool ProjectManager::checkIfItemsConsistentWithProjectArchive() const +bool ProjectManager::checkIfItemsConsistentWithProjectArchive(Item* topItem) { - return impl->checkIfItemsConsistentWithProjectArchive(RootItem::instance()); + return Impl::checkIfItemsConsistentWithProjectArchive(topItem ? topItem : RootItem::instance(), true); } -bool ProjectManager::Impl::checkIfItemsConsistentWithProjectArchive(Item* item) const +bool ProjectManager::Impl::checkIfItemsConsistentWithProjectArchive(Item* item, bool isTopItem) { - if(!item->isConsistentWithProjectArchive()){ - return false; + if(!isTopItem){ + if(!item->isConsistentWithProjectArchive()){ + return false; + } + if(auto subProjectItem = dynamic_cast(item)){ + if(subProjectItem->saveMode() == SubProjectItem::MANUAL_SAVE){ + return true; // Do not check sub-project items if the sub-project is the manusal save mode. + } + } } for(auto child = item->childItem(); child; child = child->nextItem()){ - if(!checkIfItemsConsistentWithProjectArchive(child)){ + if(!checkIfItemsConsistentWithProjectArchive(child, false)){ return false; } } diff --git a/src/Base/ProjectManager.h b/src/Base/ProjectManager.h index c1f0899eb..7eefaf1fe 100644 --- a/src/Base/ProjectManager.h +++ b/src/Base/ProjectManager.h @@ -42,6 +42,7 @@ class CNOID_EXPORT ProjectManager void loadBuiltinProject(const std::string& resourceFile, Item* parentItem = nullptr); bool isLoadingProject() const; bool saveProject(const std::string& filename, Item* item = nullptr); + bool saveProjectAsBackup(const std::string& filename, Item* item = nullptr); bool overwriteCurrentProject(); bool tryToCloseProject(); @@ -75,8 +76,8 @@ class CNOID_EXPORT ProjectManager ref_ptr storeCurrentLayout(); void restoreLayout(Mapping* layout); - bool checkValidItemExistence() const; - bool checkIfItemsConsistentWithProjectArchive() const; + static bool checkValidItemExistence(); + static bool checkIfItemsConsistentWithProjectArchive(Item* topItem = nullptr /* RootItem */); class Impl; diff --git a/src/Base/RectRegionMarker.cpp b/src/Base/RectRegionMarker.cpp index be0290332..3f76961b1 100644 --- a/src/Base/RectRegionMarker.cpp +++ b/src/Base/RectRegionMarker.cpp @@ -1,8 +1,3 @@ -/** - @file - @author Shin'ichiro Nakaoka -*/ - #include "RectRegionMarker.h" #include "SceneWidget.h" #include "MenuManager.h" @@ -147,9 +142,7 @@ void RectRegionMarker::startEditing(SceneWidget* sceneWidget) impl->sceneWidgetConnection = sceneWidget->sigAboutToBeDestroyed().connect( - [&](){ - finishEditing(); - }); + [this]{ finishEditing(); }); } @@ -164,6 +157,7 @@ void RectRegionMarker::finishEditing() if(impl->sceneWidget){ impl->sceneWidget->deactivateCustomMode(this); impl->sceneWidget->sceneRoot()->removeChild(this); + impl->sceneWidgetConnection.disconnect(); impl->sceneWidget = nullptr; } } diff --git a/src/Base/RectRegionMarker.h b/src/Base/RectRegionMarker.h index ad3e18a46..4d2bde644 100644 --- a/src/Base/RectRegionMarker.h +++ b/src/Base/RectRegionMarker.h @@ -1,7 +1,3 @@ -/** - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_BASE_RECT_REGION_MARKER_H #define CNOID_BASE_RECT_REGION_MARKER_H diff --git a/src/Base/RenderableItemSceneExporter.cpp b/src/Base/RenderableItemSceneExporter.cpp new file mode 100644 index 000000000..0be3980f1 --- /dev/null +++ b/src/Base/RenderableItemSceneExporter.cpp @@ -0,0 +1,75 @@ +#include "RenderableItemSceneExporter.h" +#include "MainWindow.h" +#include "RootItem.h" +#include "RenderableItem.h" +#include "FileDialog.h" +#include +#include +#include +#include +#include "gettext.h" + +using namespace std; +using namespace cnoid; + + +void cnoid::showDialogToExportSelectedRenderableItemScene() +{ + FileDialog dialog(MainWindow::instance()); + dialog.setWindowTitle(_("Export the scene of selected items")); + dialog.setViewMode(QFileDialog::List); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setLabelText(QFileDialog::Accept, _("Export")); + dialog.setLabelText(QFileDialog::Reject, _("Cancel")); + dialog.setNameFilter("OBJ file (*.obj)"); + dialog.updatePresetDirectories(true); + if(dialog.exec() == QDialog::Accepted){ + auto mout = MessageOut::master(); + string filename = dialog.selectedFiles().value(0).toStdString(); + auto items = RootItem::instance()->selectedItems(); + if(items.empty()){ + mout->putErrorln(_("No selected items.")); + } else { + exportRenderableItemSceneAsObjFile(items, filename, mout); + } + } +} + + +bool cnoid::exportRenderableItemSceneAsObjFile +(const ItemList<>& items, const std::string& filename, MessageOut* mout) +{ + SgGroupPtr scene = new SgGroup; + vector sceneItems; + for(auto& item : items){ + if(auto renderableItem = dynamic_cast(item.get())){ + scene->addChild(renderableItem->getScene()); + sceneItems.push_back(item.get()); + } + } + if(scene->empty()){ + mout->putWarningln(_("Scene to export is empty.")); + return false; + } + + mout->put(formatR(_("Export the scene of the following items to \"{0}\":\n "), filename)); + for(int i=0; i < sceneItems.size(); ++i){ + mout->put(sceneItems[i]->displayName()); + if(i < sceneItems.size() - 1){ + mout->put(", "); + } + } + mout->putln(""); + + ObjSceneWriter sceneWriter; + sceneWriter.setMessageSink(mout->cout()); + bool result = sceneWriter.writeScene(filename, scene); + if(result){ + mout->putln(_("Completed!")); + } else { + mout->putErrorln(_("Failed.")); + } + + return result; +} diff --git a/src/Base/RenderableItemSceneExporter.h b/src/Base/RenderableItemSceneExporter.h new file mode 100644 index 000000000..c2ddbe4f3 --- /dev/null +++ b/src/Base/RenderableItemSceneExporter.h @@ -0,0 +1,17 @@ +#ifndef CNOID_BASE_RENDERABLE_ITEM_SCENE_EXPORTER_H +#define CNOID_BASE_RENDERABLE_ITEM_SCENE_EXPORTER_H + +#include "ItemList.h" +#include "exportdecl.h" + +namespace cnoid { + +class MessageOut; + +CNOID_EXPORT void showDialogToExportSelectedRenderableItemScene(); +CNOID_EXPORT bool exportRenderableItemSceneAsObjFile( + const ItemList<>& items, const std::string& filename, MessageOut* mout); + +} + +#endif diff --git a/src/Base/RootItem.cpp b/src/Base/RootItem.cpp index b8fc8bb16..93ebb6736 100644 --- a/src/Base/RootItem.cpp +++ b/src/Base/RootItem.cpp @@ -67,6 +67,9 @@ class RootItem::Impl Signal sigItemAssigned; Signal sigItemNameChanged; + Signal sigTreeContinuousUpdateStateExistenceChanged; + int continuousUpdateStateItemRef; + Impl(RootItem* self); void selectItemIter(Item* item, Item* itemToSelect); bool updateSelectedItemsIter(Item* item); @@ -166,6 +169,8 @@ RootItem::Impl::Impl(RootItem* self) isProjectBeingLoaded = false; } }); + + continuousUpdateStateItemRef = 0; } @@ -325,6 +330,24 @@ void RootItem::notifyEventOnSubTreeRemoved(Item* item, bool isMoving) } +void RootItem::incrementContinuousUpdateStateItemRef() +{ + ++impl->continuousUpdateStateItemRef; + if(impl->continuousUpdateStateItemRef == 1){ + impl->sigTreeContinuousUpdateStateExistenceChanged(true); + } +} + + +void RootItem::decrementContinuousUpdateStateItemRef() +{ + --impl->continuousUpdateStateItemRef; + if(impl->continuousUpdateStateItemRef == 0){ + impl->sigTreeContinuousUpdateStateExistenceChanged(false); + } +} + + void RootItem::emitSigItemAssinged(Item* assigned, const Item* srcItem) { impl->sigItemAssigned(assigned, srcItem); @@ -607,6 +630,18 @@ SignalProxy RootItem::sigCheckToggled(int checkId) } +bool RootItem::hasAnyItemsInContinuousUpdateState() const +{ + return impl->continuousUpdateStateItemRef > 0; +} + + +SignalProxy RootItem::sigTreeContinuousUpdateStateExistenceChanged() +{ + return impl->sigTreeContinuousUpdateStateExistenceChanged; +} + + bool RootItem::store(Archive& archive) { return true; diff --git a/src/Base/RootItem.h b/src/Base/RootItem.h index 7d9a7327d..c5c06c155 100644 --- a/src/Base/RootItem.h +++ b/src/Base/RootItem.h @@ -84,6 +84,9 @@ class CNOID_EXPORT RootItem : public Item SignalProxy sigCheckToggled(int checkId = PrimaryCheck); + bool hasAnyItemsInContinuousUpdateState() const; + SignalProxy sigTreeContinuousUpdateStateExistenceChanged(); + virtual bool store(Archive& archive) override; virtual bool restore(const Archive& archive) override; @@ -100,6 +103,8 @@ class CNOID_EXPORT RootItem : public Item void notifyEventOnSubTreeMoved(Item* item, std::vector& orgSubTreeItems); void notifyEventOnSubTreeRemoving(Item* item, bool isMoving); void notifyEventOnSubTreeRemoved(Item* item, bool isMoving); + void incrementContinuousUpdateStateItemRef(); + void decrementContinuousUpdateStateItemRef(); void emitSigItemAssinged(Item* assigned, const Item* srcItem); void emitSigItemNameChanged(Item* item, const std::string& oldName); void emitSigSelectionChanged(Item* item, bool on, bool isCurrent); diff --git a/src/Base/SceneBar.cpp b/src/Base/SceneBar.cpp index 4d9374ada..3b76dfc79 100644 --- a/src/Base/SceneBar.cpp +++ b/src/Base/SceneBar.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "gettext.h" using namespace std; @@ -41,7 +42,8 @@ enum ElementId { TopViewButton = 72, BottomViewButton = 73, RightViewButton = 74, - LeftViewButton = 75 + LeftViewButton = 75, + IsometricViewButton = 76 }; } @@ -140,15 +142,15 @@ void SceneBar::Impl::initialize() editModeToggle = self->addToggleButton(":/Base/icon/sceneedit.svg", EditModeToggle); editModeToggle->setToolTip(_("Switch to the edit mode")); - editModeToggle->sigToggled().connect([&](bool on){ onEditModeButtonToggled(on); }); + editModeToggle->sigToggled().connect([this](bool on){ onEditModeButtonToggled(on); }); customModeButtonGroup.setExclusive(false); customModeButtonGroup.sigButtonToggled().connect( - [&](int mode, bool on){ onCustomModeButtonToggled(mode, on); }); + [this](int mode, bool on){ onCustomModeButtonToggled(mode, on); }); firstPersonModeToggle = self->addToggleButton(":/Base/icon/walkthrough.svg", FirstPersonModeToggle); firstPersonModeToggle->setToolTip(_("First-person viewpoint control mode")); - firstPersonModeToggle->sigToggled().connect([&](bool on){ onFirstPersonModeButtonToggled(on); }); + firstPersonModeToggle->sigToggled().connect([this](bool on){ onFirstPersonModeButtonToggled(on); }); cameraCombo = new ComboBox; @@ -167,13 +169,13 @@ void SceneBar::Impl::initialize() cameraCombo->setToolTip(_("Projection method / camera selection")); cameraCombo->sigCurrentIndexChanged().connect( - [&](int index){ onCameraComboCurrentIndexChanged(index); }); + [this](int index){ onCameraComboCurrentIndexChanged(index); }); self->addWidget(cameraCombo, CameraCombo); auto fittingButton = self->addButton(":/Base/icon/viewfitting.svg", FittingButton); fittingButton->setToolTip(_("Move the camera to look at the objects")); fittingButton->sigClicked().connect( - [&](){ + [this]{ auto sceneWidget = currentSceneView->sceneWidget(); sceneWidget->viewAll(); sceneWidget->setViewpointOperationMode(SceneWidget::ThirdPersonMode); @@ -181,7 +183,7 @@ void SceneBar::Impl::initialize() vertexToggle = self->addToggleButton(":/Base/icon/vertex.svg", VertexToggle); vertexToggle->setToolTip(_("Vertex rendering")); - vertexToggle->sigToggled().connect([&](bool){ onPolygonModeButtonToggled(); }); + vertexToggle->sigToggled().connect([this](bool){ onPolygonModeButtonToggled(); }); auto wireframeToggle = self->addToggleButton(":/Base/icon/wireframe.svg", WireframeToggle); wireframeToggle->setToolTip(_("Wireframe rendering")); @@ -197,38 +199,38 @@ void SceneBar::Impl::initialize() polygonModeGroup.addButton(solidPolygonToggle, 2); polygonModeGroup.sigButtonToggled().connect( - [&](int, bool on){ + [this](int, bool on){ if(on){ onPolygonModeButtonToggled(); } }); highlightToggle = self->addToggleButton(":/Base/icon/highlight.svg", HighlightToggle); highlightToggle->setToolTip(_("Highlight selected objects")); - highlightToggle->sigToggled().connect([&](bool on){ onHighlightingToggled(on); }); + highlightToggle->sigToggled().connect([this](bool on){ onHighlightingToggled(on); }); visualModelToggle = self->addToggleButton(":/Base/icon/visualshape.svg", VisualModelToggle); visualModelToggle->setToolTip(_("Show visual models")); visualModelToggle->setChecked(true); - visualModelToggle->sigToggled().connect([&](bool){ updateCollisionModelVisibility(); }); + visualModelToggle->sigToggled().connect([this](bool){ updateCollisionModelVisibility(); }); modelTypeFlipButton = self->addButton(":/Base/icon/shapeflip.svg", ModelTypeFlipButton); modelTypeFlipButton->setToolTip(_("Flip active model types")); - modelTypeFlipButton->sigClicked().connect([&](){ flipVisibleModels(); }); + modelTypeFlipButton->sigClicked().connect([this](){ flipVisibleModels(); }); collisionModelToggle = self->addToggleButton(":/Base/icon/collisionshape.svg", CollisionModelToggle); collisionModelToggle->setToolTip(_("Show the collision detection models")); - collisionModelToggle->sigToggled().connect([&](bool){ updateCollisionModelVisibility();}); + collisionModelToggle->sigToggled().connect([this](bool){ updateCollisionModelVisibility();}); collisionLineToggle = self->addToggleButton(":/Base/icon/collisionlines.svg", CollisionLineToggle); collisionLineToggle->setToolTip(_("Toggle the collision line visibility")); - collisionLineToggle->sigToggled().connect([&](bool on){ onCollisionLineButtonToggled(on); }); + collisionLineToggle->sigToggled().connect([this](bool on){ onCollisionLineButtonToggled(on); }); auto configButton = self->addButton(":/Base/icon/setup.svg", ConfigButton); configButton->setToolTip(_("Show the config dialog")); - configButton->sigClicked().connect([&](){ currentSceneView->sceneViewConfig()->showConfigDialog(); }); + configButton->sigClicked().connect([this]{ currentSceneView->sceneViewConfig()->showConfigDialog(); }); sceneViewFocusConnection = SceneView::sigLastFocusSceneViewChanged().connect( - [&](SceneView* view){ setCurrentSceneView(view); }); + [this](SceneView* view){ setCurrentSceneView(view); }); setCurrentSceneView(SceneView::instance()); @@ -543,28 +545,32 @@ void SceneBar::Impl::enableViewButtonSet() button = self->addButton(":/Base/icon/frontview.svg", FrontViewButton); button->setToolTip(_("Front view")); - button->sigClicked().connect([&](){ onViewButtonClicked(FrontViewButton); }); + button->sigClicked().connect([this]{ onViewButtonClicked(FrontViewButton); }); button = self->addButton(":/Base/icon/backview.svg", BackViewButton); button->setToolTip(_("Back view")); - button->sigClicked().connect([&](){ onViewButtonClicked(BackViewButton); }); + button->sigClicked().connect([this]{ onViewButtonClicked(BackViewButton); }); button = self->addButton(":/Base/icon/topview.svg", TopViewButton); button->setToolTip(_("Top view")); - button->sigClicked().connect([&](){ onViewButtonClicked(TopViewButton); }); + button->sigClicked().connect([this]{ onViewButtonClicked(TopViewButton); }); button = self->addButton(":/Base/icon/bottomview.svg", BottomViewButton); button->setToolTip(_("Bottom view")); - button->sigClicked().connect([&](){ onViewButtonClicked(BottomViewButton); }); + button->sigClicked().connect([this]{ onViewButtonClicked(BottomViewButton); }); button = self->addButton(":/Base/icon/rightview.svg", RightViewButton); button->setToolTip(_("Right view")); - button->sigClicked().connect([&](){ onViewButtonClicked(RightViewButton); }); + button->sigClicked().connect([this]{ onViewButtonClicked(RightViewButton); }); button = self->addButton(":/Base/icon/leftview.svg", LeftViewButton); button->setToolTip(_("Left view")); - button->sigClicked().connect([&](){ onViewButtonClicked(LeftViewButton); }); + button->sigClicked().connect([this]{ onViewButtonClicked(LeftViewButton); }); + button = self->addButton(":/Base/icon/isometricview.svg", IsometricViewButton); + button->setToolTip(_("Isometric view")); + button->sigClicked().connect([this]{ onViewButtonClicked(IsometricViewButton); }); + isViewButtonSetEnabled = true; } @@ -572,32 +578,42 @@ void SceneBar::Impl::enableViewButtonSet() void SceneBar::Impl::onViewButtonClicked(ElementId button) { if(currentSceneView){ + AngleAxis aa; + switch(button){ + case FrontViewButton: + aa = AngleAxis(M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX()); + break; + case BackViewButton: + aa = AngleAxis(-M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX()); + break; + case TopViewButton: + aa = AngleAxis(-M_PI_2, Vector3::UnitZ()); + break; + case BottomViewButton: + aa = AngleAxis(M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI, Vector3::UnitX()); + break; + case RightViewButton: + aa = AngleAxis(M_PI, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX()); + break; + case LeftViewButton: + aa = AngleAxis(M_PI_2, Vector3::UnitX()); + break; + case IsometricViewButton: + aa = + AngleAxis(M_PI_4, Vector3::UnitZ()) * AngleAxis(radian(-35.264), Vector3::UnitY()) * + AngleAxis(M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX()); + break; + default: + break; + } auto sceneWidget = currentSceneView->sceneWidget(); if(auto transform = sceneWidget->activeInteractiveCameraTransform()){ - switch(button){ - case FrontViewButton: - transform->setRotation(AngleAxis(M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX())); - break; - case BackViewButton: - transform->setRotation(AngleAxis(-M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX())); - break; - case TopViewButton: - transform->setRotation(AngleAxis(-M_PI_2, Vector3::UnitZ())); - break; - case BottomViewButton: - transform->setRotation(AngleAxis(M_PI_2, Vector3::UnitZ()) * AngleAxis(M_PI, Vector3::UnitX())); - break; - case RightViewButton: - transform->setRotation(AngleAxis(M_PI, Vector3::UnitZ()) * AngleAxis(M_PI_2, Vector3::UnitX())); - break; - case LeftViewButton: - transform->setRotation(AngleAxis(M_PI_2, Vector3::UnitX())); - break; - default: - break; - } - sceneWidget->viewAll(); + transform->setRotation(aa); + } else { + sceneWidget->renderer()->setCurrentCamera(sceneWidget->builtinOrthographicCamera()); + sceneWidget->builtinCameraTransform()->setRotation(aa); } + sceneWidget->viewAll(); } } diff --git a/src/Base/SceneItem.cpp b/src/Base/SceneItem.cpp index f8735b0f7..30b6fa2fd 100644 --- a/src/Base/SceneItem.cpp +++ b/src/Base/SceneItem.cpp @@ -99,6 +99,7 @@ SceneItem::SceneItem(const SceneItem& org, CloneMap* cloneMap) // shallow copy auto orgTopNode = org.topNode(); topNode_ = new SgPosTransform(*orgTopNode); + orgTopNode->copyChildrenTo(topNode_); if(cloneMap){ cloneMap->setClone(orgTopNode, topNode_); } @@ -298,10 +299,10 @@ LocationProxyPtr SceneItem::getLocationProxy() SceneItem::Location::Location(SceneItem* item) - : LocationProxy(GlobalLocation), + : LocationProxy(item, GlobalLocation), item(item) { - + setNameDependencyOnItemName(); } diff --git a/src/Base/SceneItemFileIO.cpp b/src/Base/SceneItemFileIO.cpp index 1e2c0f459..f35be38a4 100644 --- a/src/Base/SceneItemFileIO.cpp +++ b/src/Base/SceneItemFileIO.cpp @@ -3,6 +3,7 @@ #include "ItemManager.h" #include #include +#include #include "gettext.h" using namespace std; @@ -69,6 +70,7 @@ StdSceneWriter* SceneItemStdSceneFileExporter::ensureSceneWriter() if(!sceneWriter_){ sceneWriter_.reset(new StdSceneWriter); sceneWriter_->setMessageSink(os()); + sceneWriter_->setFilePathVariableProcessor(FilePathVariableProcessor::systemInstance()); } return sceneWriter_.get(); } diff --git a/src/Base/SceneWidget.cpp b/src/Base/SceneWidget.cpp index 7011b19ba..3fb68e15a 100644 --- a/src/Base/SceneWidget.cpp +++ b/src/Base/SceneWidget.cpp @@ -106,7 +106,35 @@ struct EditableNodeInfo void clear(){ node.reset(); handler = nullptr; } }; - + +template +class Interpolator +{ +public: + double x0; + VectorType y0; + VectorType a; + VectorType b; + + Interpolator(double x0, VectorType y0, double x1, VectorType y1) + : x0(x0), y0(y0) + { + double h = x1 - x0; + double h2 = h * h; + double h3 = h2 * h; + a = 2.0 * (y0 - y1) / h3; + b = 3.0 * (y1 - y0) / h2; + } + + VectorType interpolate(double x) const + { + double h = x - x0; + double h2 = h * h; + double h3 = h2 * h; + return a * h3 + b * h2 + y0; + } +}; + } namespace cnoid { @@ -268,9 +296,13 @@ class SceneWidget::Impl : public QOpenGLWidget void renderFps(); void onCurrentCameraChanged(); - void setVisiblePolygonElements(int elementFlags); int visiblePolygonElements() const; void setCollisionLineVisibility(bool on); + void setVisiblePolygonElements(int elementFlags); + void setCameraPositionLookingFor( + const Vector3& eye, const Vector3& direction, const Vector3& up, double transitionTime); + void setCameraPositionLookingAt( + const Vector3& eye, const Vector3& center, const Vector3& up, double transitionTime); void resetCursor(); void setEditMode(bool on, bool doAdvertise); @@ -1038,6 +1070,12 @@ void SceneWidget::setCursor(const QCursor cursor) } +void SceneWidget::setDefaultCursor() +{ + impl->setCursor(impl->defaultCursor); +} + + void SceneWidget::Impl::resetCursor() { self->setCursor(isEditMode ? editModeCursor : defaultCursor); @@ -1506,20 +1544,25 @@ void SceneWidget::Impl::keyPressEvent(QKeyEvent* event) bool handled = false; if(isEditMode){ - auto info = applyEditableFunction( - focusedEditablePath, - [&](SgNode* node, SceneWidgetEventHandler* handler){ - bool handled = false; - if(customNodeEventHandler){ - handled = customNodeEventHandler(node, handler, &latestEvent); - } - if(!handled){ - handled = handler->onKeyPressEvent(&latestEvent); - } - return handled; - }); - if(info){ - handled = true; + if(activeCustomModeHandler){ + handled = activeCustomModeHandler->onKeyPressEvent(&latestEvent); + } + if(!handled){ + auto info = applyEditableFunction( + focusedEditablePath, + [&](SgNode* node, SceneWidgetEventHandler* handler){ + bool handled = false; + if(customNodeEventHandler){ + handled = customNodeEventHandler(node, handler, &latestEvent); + } + if(!handled){ + handled = handler->onKeyPressEvent(&latestEvent); + } + return handled; + }); + if(info){ + handled = true; + } } } @@ -1586,13 +1629,18 @@ void SceneWidget::Impl::keyReleaseEvent(QKeyEvent* event) handled = true; } else if(isEditMode){ - if(focusedEditable){ - if(customNodeEventHandler){ - handled = customNodeEventHandler( - focusedEditable.node, focusedEditable.handler, &latestEvent); - } - if(!handled){ - handled = focusedEditable.handler->onKeyReleaseEvent(&latestEvent); + if(activeCustomModeHandler){ + handled = activeCustomModeHandler->onKeyReleaseEvent(&latestEvent); + } + if(!handled){ + if(focusedEditable){ + if(customNodeEventHandler){ + handled = customNodeEventHandler( + focusedEditable.node, focusedEditable.handler, &latestEvent); + } + if(!handled){ + handled = focusedEditable.handler->onKeyReleaseEvent(&latestEvent); + } } } } @@ -1636,6 +1684,12 @@ void SceneWidget::Impl::mousePressEvent(QMouseEvent* event) if(!handled){ if(event->button() == Qt::RightButton){ + if(dragMode != NO_DRAGGING){ + if(focusedEditable){ + focusedEditable.handler->onPointerLeaveEvent(&latestEvent); + } + dragMode = NO_DRAGGING; + } if(isEditMode){ showEditModePopupMenu(getGlobalPosition(event)); } else { @@ -2724,9 +2778,131 @@ void SceneWidget::setShowFPS(bool on) } -void SceneWidget::setCameraPosition(const Vector3& eye, const Vector3& direction, const Vector3& up) +void SceneWidget::setCameraPosition +(const Vector3& eye, const Vector3& direction, const Vector3& up) +{ + impl->setCameraPositionLookingFor(eye, direction, up, 0.0); +} + + +void SceneWidget::setCameraPositionLookingFor +(const Vector3& eye, const Vector3& direction, const Vector3& up, double transitionTime) { - impl->builtinCameraTransform->setPosition(SgCamera::positionLookingFor(eye, direction, up)); + impl->setCameraPositionLookingFor(eye, direction, up, transitionTime); +} + + +void SceneWidget::Impl::setCameraPositionLookingFor +(const Vector3& eye, const Vector3& direction, const Vector3& up, double transitionTime) +{ + if(transitionTime == 0.0){ + builtinCameraTransform->setPosition(SgCamera::positionLookingFor(eye, direction, up)); + return; + } + + typedef Eigen::Matrix Vector6; + + Vector6 pos0; + auto& T0 = builtinCameraTransform->T(); + pos0.head<3>() = T0.translation(); // eye + pos0.tail<3>() = rpyFromRot(T0.linear()); // rpy + + Vector6 pos1; + Vector3 rpy1 = rpyFromRot(SgCamera::positionLookingFor(eye, direction, up).linear()); + pos1 << eye, rpy1; + + Interpolator interp(0.0, pos0, transitionTime, pos1); + + QElapsedTimer timer; + timer.start(); + + while(true){ + double time = timer.elapsed() / 1000.0; + if(time >= transitionTime){ + break; + } + Vector6 pos = interp.interpolate(time); + Isometry3 T; + T.translation() = pos.head<3>(); // eye + T.linear() = rotFromRpy(pos.tail<3>()); + builtinCameraTransform->setPosition(T); + repaint(); + + QCoreApplication::processEvents(); + } + + builtinCameraTransform->setPosition(SgCamera::positionLookingFor(eye, direction, up)); + update(); +} + + +void SceneWidget::setCameraPositionLookingAt +(const Vector3& eye, const Vector3& center, const Vector3& up, double transitionTime) +{ + impl->setCameraPositionLookingAt(eye, center, up, transitionTime); +} + + +void SceneWidget::Impl::setCameraPositionLookingAt +(const Vector3& eye, const Vector3& center, const Vector3& up, double transitionTime) +{ + if(transitionTime == 0.0){ + builtinCameraTransform->setPosition(SgCamera::positionLookingAt(eye, center, up)); + } + + auto& T0 = builtinCameraTransform->T(); + Vector3 d = SgCamera::direction(T0); + Vector3 eye0 = T0.translation(); + double t = -(eye0 - center).dot(d); + Vector3 center0 = eye0 + t * d; + Vector3 up0 = SgCamera::up(T0); + Vector3 upRotationAxis = up0.cross(up).normalized(); + if(upRotationAxis.isZero()){ + for(int i=0; i < 2; ++i){ + upRotationAxis = Vector3::Unit(i).cross(up).normalized(); + if(!upRotationAxis.isZero()){ + break; + } + } + } + double theta = acos(up0.dot(up)); + + typedef Eigen::Matrix Vector7; + Vector7 pos0; + pos0.head<3>() = eye0; + pos0.segment<3>(3) = center0; + pos0(6) = 0.0; + + Vector7 pos1; + pos1 << eye, center, theta; + + Interpolator interp(0.0, pos0, transitionTime, pos1); + + QElapsedTimer timer; + timer.start(); + + auto setCameraPosition = + [&](double time){ + }; + + while(true){ + double time = timer.elapsed() / 1000.0; + if(time >= transitionTime){ + break; + } + Vector7 pos = interp.interpolate(time); + Vector3 eye = pos.head<3>(); + Vector3 center = pos.segment<3>(3); + double theta = pos(6); + Vector3 up = Vector3(AngleAxis(theta, upRotationAxis) * up0).normalized(); + builtinCameraTransform->setPosition(SgCamera::positionLookingAt(eye, center, up)); + repaint(); + + QCoreApplication::processEvents(); + } + + builtinCameraTransform->setPosition(SgCamera::positionLookingAt(eye, center, up)); + update(); } diff --git a/src/Base/SceneWidget.h b/src/Base/SceneWidget.h index 701c64845..86d26a098 100644 --- a/src/Base/SceneWidget.h +++ b/src/Base/SceneWidget.h @@ -137,6 +137,8 @@ class CNOID_EXPORT SceneWidget : public Widget void setColor(const Vector4& color); void setCameraPosition(const Vector3& eye, const Vector3& direction, const Vector3& up); + void setCameraPositionLookingFor(const Vector3& eye, const Vector3& direction, const Vector3& up, double transitionTime = 0.0); + void setCameraPositionLookingAt(const Vector3& eye, const Vector3& center, const Vector3& up, double transitionTime = 0.0); void setFieldOfView(double value); void setClipDistances(double nearDistance, double farDistance); void setInteractiveCameraRollRestricted(bool on); @@ -146,13 +148,8 @@ class CNOID_EXPORT SceneWidget : public Widget bool setSceneFocus(const SgNodePath& path); - /** - @return cursor id which is passed to releaseCursor() - */ - //int setCursor(const QCursor cursor); - //void releaseCursor(int cursorId); - void setCursor(const QCursor cursor); + void setDefaultCursor(); Menu* contextMenu(); void showContextMenuAtPointerPosition(); diff --git a/src/Base/SceneWidgetEvent.h b/src/Base/SceneWidgetEvent.h index 522ff0ced..361a1bdf9 100644 --- a/src/Base/SceneWidgetEvent.h +++ b/src/Base/SceneWidgetEvent.h @@ -6,6 +6,7 @@ #define CNOID_BASE_SCENE_WIDGET_EVENT_H #include +#include #include "exportdecl.h" namespace cnoid { @@ -44,6 +45,8 @@ class CNOID_EXPORT SceneWidgetEvent @return a Qt::MouseButton value */ int button() const { return button_; } + bool isLeftButtonPressed() const { return button_ == Qt::LeftButton; } + bool isRightButtonPressed() const { return button_ == Qt::RightButton; } /** @return a Qt::KeyboardModifiers value diff --git a/src/Base/SubProjectItem.cpp b/src/Base/SubProjectItem.cpp index 1e8302e28..d5a0b35d2 100644 --- a/src/Base/SubProjectItem.cpp +++ b/src/Base/SubProjectItem.cpp @@ -28,7 +28,6 @@ class SubProjectItem::Impl std::string projectFileToLoad; Selection saveMode; bool isSavingSubProject; - ScopedConnectionSet updateConnections; unique_ptr projectManager_; Impl(SubProjectItem* self); @@ -36,8 +35,6 @@ class SubProjectItem::Impl bool loadSubProject(const std::string& filename); ProjectManager* projectManager(); void doLoadSubProject(const std::string& filename); - void enableSubProjectUpdateDetection(); - void onSubProjectUpdated(); bool saveSubProject(const std::string& filename); }; @@ -160,39 +157,9 @@ void SubProjectItem::Impl::doLoadSubProject(const std::string& filename) itemTreeView->itemTreeWidget()->setExpanded(self); } } - - if(saveMode.is(SubProjectItem::AUTOMATIC_SAVE)){ - enableSubProjectUpdateDetection(); - } -} - - -void SubProjectItem::Impl::enableSubProjectUpdateDetection() -{ - updateConnections.disconnect(); - - for(auto& item : self->descendantItems()){ - updateConnections.add( - item->sigNameChanged().connect( - [&](const std::string&){ onSubProjectUpdated(); })); - updateConnections.add( - item->sigUpdated().connect( - [&](){ onSubProjectUpdated(); })); - } - - updateConnections.add( - self->sigSubTreeChanged().connect( - [&](){ onSubProjectUpdated(); })); } -void SubProjectItem::Impl::onSubProjectUpdated() -{ - self->suggestFileUpdate(); - self->notifyUpdate(); -} - - bool SubProjectItem::Impl::saveSubProject(const std::string& filename) { isSavingSubProject = true; @@ -218,10 +185,7 @@ void SubProjectItem::setSaveMode(int mode) { if(impl->saveMode.select(mode)){ if(mode == MANUAL_SAVE){ - impl->updateConnections.disconnect(); setConsistentWithFile(true); - } else { - impl->enableSubProjectUpdateDetection(); } } } @@ -241,10 +205,16 @@ void SubProjectItem::doPutProperties(PutPropertyFunction& putProperty) bool SubProjectItem::store(Archive& archive) { if(!impl->isSavingSubProject){ - if(overwriteOrSaveWithDialog()){ - archive.writeFileInformation(this); - archive.write("save_mode", impl->saveMode.selectedSymbol(), DOUBLE_QUOTED); + if(impl->saveMode.is(SubProjectItem::AUTOMATIC_SAVE)){ + if(!ProjectManager::checkIfItemsConsistentWithProjectArchive(this)){ + // To save the sub-tree as a sub-project file + suggestFileUpdate(); + } + } + if(!archive.saveItemToFile(this)){ + return false; } + archive.write("save_mode", impl->saveMode.selectedSymbol(), DOUBLE_QUOTED); } return true; } diff --git a/src/Base/TimeBar.cpp b/src/Base/TimeBar.cpp index 32cb1b2ac..f5ddda14c 100644 --- a/src/Base/TimeBar.cpp +++ b/src/Base/TimeBar.cpp @@ -98,6 +98,7 @@ class TimeBar::Impl : public QObject bool isRepeatMode; bool isAutoExpansionMode; bool isDoingPlayback; + bool isPlaybackProcessing; bool hasOngoingTime; bool isOngoingTimeSyncEnabled; map ongoingTimeMap; @@ -197,6 +198,7 @@ TimeBar::Impl::Impl(TimeBar* self) isRepeatMode = false; isAutoExpansionMode = true; isDoingPlayback = false; + isPlaybackProcessing = false; hasOngoingTime = false; isOngoingTimeSyncEnabled = true; timerId = 0; @@ -663,6 +665,8 @@ void TimeBar::Impl::startPlayback(double time) { stopPlayback(false); + isPlaybackProcessing = true; + bool isOngoingTimeValid = hasOngoingTime && isOngoingTimeSyncEnabled; if(isOngoingTimeValid){ time = ongoingTime; @@ -705,6 +709,10 @@ void TimeBar::Impl::startPlayback(double time) elapsedTimer.start(); } } + + if(!isDoingPlayback){ + isPlaybackProcessing = false; + } } @@ -750,16 +758,24 @@ double TimeBar::Impl::stopPlayback(bool isStoppedManually) } } + isPlaybackProcessing = false; + return lastValidTime; } -bool TimeBar::isDoingPlayback() +bool TimeBar::isDoingPlayback() const { return impl->isDoingPlayback; } +bool TimeBar::isPlaybackProcessing() const +{ + return impl->isPlaybackProcessing; +} + + void TimeBar::Impl::timerEvent(QTimerEvent*) { double time = animationTimeOffset + playbackSpeedRatio * (elapsedTimer.elapsed() / 1000.0); diff --git a/src/Base/TimeBar.h b/src/Base/TimeBar.h index 888d57bde..a88ff9399 100644 --- a/src/Base/TimeBar.h +++ b/src/Base/TimeBar.h @@ -68,7 +68,10 @@ class CNOID_EXPORT TimeBar : public ToolBar void startPlayback(); void startPlayback(double time); void stopPlayback(bool isStoppedManually = false); - bool isDoingPlayback(); + bool isDoingPlayback() const; + + //! In contrast to isDoingPlayback(), this function returns true even when initializing or finalizing the playback. + bool isPlaybackProcessing() const; bool isOngoingTimeSyncEnabled() const; void setOngoingTimeSyncEnabled(bool on); diff --git a/src/Base/icon/isometricview.svg b/src/Base/icon/isometricview.svg new file mode 100644 index 000000000..5ed6b23a5 --- /dev/null +++ b/src/Base/icon/isometricview.svg @@ -0,0 +1,60 @@ + +image/svg+xml + + + + + diff --git a/src/Base/po/ja.po b/src/Base/po/ja.po index 67fb7a794..9e03cb19d 100644 --- a/src/Base/po/ja.po +++ b/src/Base/po/ja.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Choreonoid\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-14 22:18+0900\n" +"POT-Creation-Date: 2024-10-25 16:33+0900\n" "PO-Revision-Date: 2021-10-25 12:00+0900\n" "Language: ja\n" "MIME-Version: 1.0\n" @@ -45,6 +45,87 @@ msgstr "" "アプリケーション設定ファイル \"{0}\" を読み込むことができません.\n" "{1}" +msgid "Note that item \"{0}\" will be saved to the original file \"{1}\"." +msgstr "アイテム \"{0}\" は元ファイルである \"{1}\" に保存されることになります." + +msgid "Name:" +msgstr "名前:" + +msgid "Align with the builtin camera" +msgstr "組み込みカメラに合わせる" + +msgid "Camera type:" +msgstr "カメラタイプ:" + +msgid "Perspective" +msgstr "透視投影" + +msgid "Orthographic" +msgstr "平行投影" + +msgid "Position:" +msgstr "位置:" + +msgid "Look-at:" +msgstr "視線方向:" + +msgid "Up vector:" +msgstr "上方向:" + +msgid "Field of View:" +msgstr "視野角:" + +msgid "[deg]" +msgstr "" + +msgid "Near Clip:" +msgstr "近方クリップ:" + +msgid "Far Clip:" +msgstr "遠方クリップ:" + +msgid "Interactive viewpoint change" +msgstr "インタラクティブな視点変更" + +msgid "Activate in the scene view" +msgstr "シーンビューで有効化" + +msgid "&OK" +msgstr "&OK" + +msgid "Camera Creation" +msgstr "カメラの作成" + +msgid "Camera" +msgstr "カメラ" + +msgid "Camera Configuration" +msgstr "カメラの設定" + +msgid "CameraItem" +msgstr "カメラアイテム" + +msgid "Activate camera" +msgstr "このカメラを使用" + +msgid "Apply to built-in camera" +msgstr "組み込みカメラに適用" + +msgid "Camera configuration" +msgstr "カメラの設定" + +msgid "Camera type" +msgstr "カメラタイプ" + +msgid "Field Of View" +msgstr "視野角" + +msgid "Near clip distance" +msgstr "近方クリップ距離" + +msgid "Far clip distance" +msgstr "近方クリップ距離" + msgid "CaptureBar" msgstr "キャプチャバー" @@ -144,9 +225,6 @@ msgstr "距離測定" msgid "Object %1" msgstr "対象%1" -msgid "Position:" -msgstr "位置:" - msgid "Pick" msgstr "ピック" @@ -186,9 +264,6 @@ msgstr "オーバーレイ" msgid "Create measurement item" msgstr "計測アイテムの作成" -msgid "Name:" -msgstr "名前" - msgid "&Measure" msgstr "測定" @@ -208,6 +283,12 @@ msgstr "距離測定アイテム \"{0}\" はプロジェクトから削除され msgid "The distance measurement cannot be started since the target items are not specified." msgstr "対象アイテムが指定されていないので距離測定を開始できません." +msgid "[m]" +msgstr "" + +msgid "[mm]" +msgstr "" + msgid "Distance Marker Color" msgstr "距離マーカの色" @@ -277,8 +358,26 @@ msgstr "フォルダアイテム" msgid "The file format of \"{}\" is not supported.\n" msgstr "ファイルフォーマット \"{}\" はサポートされていません.\n" -msgid "[ Mesh import hints ]" -msgstr "[ メッシュインポートヒント ]" +msgid "Load a scene file" +msgstr "シーンファイルの読み込み" + +msgid "Scene file" +msgstr "シーンファイル" + +msgid "Load" +msgstr "読み込み" + +msgid "Loading the file \"{0}\" ..." +msgstr "ファイル \"{0}\" を読み込み中…" + +msgid " OK!" +msgstr "" + +msgid " Failed." +msgstr " 失敗." + +msgid "[ import hints ]" +msgstr "[ インポートヒント ]" msgid "Unit:" msgstr "単位:" @@ -453,9 +552,6 @@ msgstr "{0}の読み込み" msgid "Import {0}" msgstr "{0}のインポート" -msgid "Load" -msgstr "読み込み" - msgid "Import" msgstr "インポート" @@ -489,11 +585,11 @@ msgstr "{0}を読み込むことができません: {1}" msgid "Loading {0} \"{1}\"" msgstr "{0} \"{1}\" を読み込み中" -msgid " -> failed.\n" -msgstr " -> 失敗.\n" +msgid " -> failed." +msgstr " -> 失敗." -msgid " -> ok!\n" -msgstr " -> 完了!\n" +msgid " -> ok!" +msgstr " -> 完了!" msgid "{0} cannot be saved with empty filename." msgstr "ファイル名が指定されていないので{0}を保存できません." @@ -531,6 +627,9 @@ msgstr "生成(&C)" msgid "&Cancel" msgstr "キャンセル(&C)" +msgid "New {0} cannot be created from its prototype item." +msgstr "新しい{0}をそのプロトタイプアイテムから生成することができません." + msgid "There is no file I/O processor registered for the \"{0}\" type." msgstr "アイテム型 \"{0}\" に対してI/Oプロセッサが登録されていません." @@ -705,6 +804,15 @@ msgstr "コピーされたアイテムを \"{0}\" にペーストできません msgid "Paste items in {0}" msgstr "複数のアイテムを{0}上で貼り付け" +msgid "Item \"{0}\" cannot be reloaded currently, as it is being continuously updated." +msgstr "アイテム \"{0}\" は継続的に更新中のため、今は再読込することができません." + +msgid "" +"Item \"{0}\" cannot be reloaded currently, as it is a part of a sub-tree being continuously " +"updated." +msgstr "" +"アイテム \"{0}\" は継続的に更新中のサブツリーの一部であるため、今は再読込することができません." + msgid "Drop items in {0}" msgstr "複数のアイテムを{0}上でドロップ" @@ -876,6 +984,12 @@ msgstr "プロジェクトに名前を付けて保存" msgid "Project File Options" msgstr "プロジェクトファイルオプション" +msgid "Recovery from Backup" +msgstr "バックアップからの回復" + +msgid "Backup Configuration" +msgstr "バックアップの設定" + msgid "Edit Path Variables" msgstr "パス変数の設定" @@ -930,6 +1044,9 @@ msgstr "ツール" msgid "Put Scene Statistics" msgstr "シーン統計情報の表示" +msgid "Export Scene" +msgstr "シーンのエクスポート" + msgid "Movie Recorder" msgstr "動画レコーダ" @@ -1174,9 +1291,6 @@ msgstr "PointSet: 消去モード終了" msgid "MultiSE3MatrixSeqItem" msgstr "複数SE3行列時系列アイテム" -msgid "Number of SE3 values in a frame" -msgstr "フレームあたりSE3要素数" - msgid "Multi SE3 Seq" msgstr "複数SE3時系列" @@ -1195,9 +1309,6 @@ msgstr "複数値時系列グラフビュー" msgid "MultiValueSeqItem" msgstr "複数値時系列アイテム" -msgid "Number of values in a frame" -msgstr "フレームあたり要素数" - msgid "Plain Format of a Multi Value Sequence" msgstr "複数値時系列のプレインフォーマット" @@ -1219,8 +1330,11 @@ msgstr "パス" msgid "&Apply" msgstr "適用(&A)" -msgid "{0}-plugin cannot be initialized because required plugin(s) {1} are not found.\n" -msgstr "必要なプラグイン{1}が見つからないため,{0}プラグインの初期化ができません.\n" +msgid "Loading {0} failed.\n" +msgstr "{0}の読み込みに失敗しました.\n" + +msgid "{0}-plugin cannot be initialized because required plugin(s) {1} are not found." +msgstr "必要なプラグイン{1}が見つからないため,{0}プラグインの初期化ができません." msgid "Plugin file \"{}\" has already been activated.\n" msgstr "プラグインファイル \"{}\" は既に読み込まれています.\n" @@ -1244,8 +1358,8 @@ msgstr "" "{0}プラグインの内部バージョンがシステムの内部バージョンと一致しません.\n" "問題を避けるため,プラグインファイル \"{1}\" は削除か更新を行なってください.\n" -msgid "Plugin file \"{0}\" conflicts with \"{1}\"." -msgstr "{0}プラグインは \"{1}\" と競合しています." +msgid "Plugin file \"{0}\" conflicts with \"{1}\".\n" +msgstr "{0}プラグインは \"{1}\" と競合しています.\n" msgid "Loading the plugin failed.\n" msgstr "プラグインの読み込みができませんでした.\n" @@ -1266,11 +1380,11 @@ msgstr "" "プラグインの読み込みができませんでした.\n" "{0}" -msgid "{0}-plugin cannot be finalized.\n" -msgstr "{}プラグインを終了できません.\n" +msgid "{0}-plugin cannot be finalized." +msgstr "{}プラグインを終了できません." -msgid "Plugin DLL \"{}\" has been unloaded.\n" -msgstr "プラグインDLL \"{}\" が解放されました.\n" +msgid "Plugin DLL \"{}\" has been unloaded." +msgstr "プラグインDLL \"{}\" が解放されました." msgid "Load plugins" msgstr "プラグインの読み込み" @@ -1378,11 +1492,62 @@ msgid "Additional precision" msgstr "追加精度" msgid "" -"The state of the \"{0}\" {1} was not completely restored.\n" -"{2}" +"The application has crashed, and auto-backup project files are available for project recovery. Do " +"you want to show the dialog for recovery?" msgstr "" -"\"{0}\" {1} の状態は完全には復帰できませんでした.\n" -"{2}" +"アプリケーションがクラッシュしましたが、プロジェクト回復のための自動バックアップされたプロジェクト" +"ファイルが利用可能です。回復用のダイアログを表示しますか?" + +msgid "The project backup directory \"{0}\" cannot be created: {1}" +msgstr "プロジェクトパックディレクトリ \"{0}\" を作成することができません: {1}" + +msgid "Automatically saving project \"{0}\" to \"{1}\" as backup ..." +msgstr "プロジェクト \"{0}\" を \"{1}\" にバックアップとして自動保存中 …" + +msgid "Automatically saving project \"{0}\" to \"{1}\" as backup ... done." +msgstr "プロジェクト \"{0}\" を \"{1}\" にバックアップとして自動保存中 … 完了." + +msgid "Project Auto-Backup Configuration" +msgstr "プロジェクト自動バックアップの設定" + +msgid "Enable auto backup" +msgstr "自動バックアップの有効化" + +msgid "Time interval" +msgstr "時間間隔" + +msgid "sec." +msgstr "秒" + +msgid "Max number of backups" +msgstr "最大バックアップ数" + +msgid "Pause auto backup during playback" +msgstr "プレイバック中は自動バックアップを停止" + +msgid "Pause auto backup during continuous update" +msgstr "継続更新中は自動バックアップを停止" + +msgid "Backup before starting continuous update" +msgstr "継続更新開始前にバックアップ" + +msgid "Backup Project Recovery" +msgstr "バックアッププロジェクトの回復" + +msgid "Date and Time" +msgstr "日時" + +msgid "Project" +msgstr "プロジェクト" + +msgid "&Recovery" +msgstr "回復(&R)" + +msgid "Selected backup is not found." +msgstr "選択されたバックアップがみつかりません." + +msgid "The state of the \"{0}\" {1} was not completely restored.\n" +msgstr "\"{0}\" {1} の状態は完全には復帰できませんでした.\n" msgid "Loading project file \"{}\" ..." msgstr "プロジェクトファイル \"{}\" を読み込み中…" @@ -1393,6 +1558,12 @@ msgstr "リソース \"{}\" がみつかりません." msgid "The project file is empty." msgstr "プロジェクトファイルは空です." +msgid "This is a backup of an unsaved project." +msgstr "これは未保存プロジェクトのバックアップです." + +msgid "This is a backup of \"{0}\"." +msgstr "これは \"{0}\" のバックアップです." + msgid "{0} / {1} item(s) have been loaded." msgstr "{0} / {1} のアイテムが読みこまれました." @@ -1408,14 +1579,17 @@ msgstr "プロジェクト \"{}\" を完全に読み込みました." msgid "Project \"{}\" has been partially loaded." msgstr "プロジェクト \"{}\" を部分的に読み込みました." +msgid "This project backup has been loaded as an unsaved project." +msgstr "このプロジェクトバックアップは未保存プロジェクトとして読み込まれました." + +msgid "This project backup has been loaded as project \"{0}\"." +msgstr "このプロジェクトバックアップはプロジェクト \"{0}\" として読み込まれました." + msgid "Loading project \"{}\" failed. Any valid objects were not loaded." msgstr "プロジェクト \"{}\" の読み込みに失敗しました.いかなるオブジェクトもロードされていません." -msgid "Can't open file \"{}\" for writing.\n" -msgstr "\"{}\" は書き込み用ファイルとして開けません.\n" - -msgid "Saving sub project {0} as \"{1}\" ..." -msgstr "サブプロジェクト \"{0}\" を \"{1}\" に保存中…" +msgid "Can't open file \"{}\" for writing." +msgstr "\"{}\" は書き込み用ファイルとして開けません." msgid "Saving the project as \"{}\" ..." msgstr "プロジェクトを \"{}\" に保存中…" @@ -1423,9 +1597,15 @@ msgstr "プロジェクトを \"{}\" に保存中…" msgid "Saving the project file has been finished." msgstr "プロジェクトファイルの保存が完了しました." +msgid "Saving the sub-project file has been finished." +msgstr "サブプロジェクトファイルの保存が完了しました." + msgid "Saving the project file failed." msgstr "プロジェクトファイルの保存に失敗しました." +msgid "Saving the sub-project file failed." +msgstr "サブプロジェクトファイルの保存に失敗しました." + msgid "Open a project" msgstr "プロジェクトを開く" @@ -1518,6 +1698,31 @@ msgstr "プロジェクトパック \"{0}\" はプロジェクトファイルを msgid "ReferencedObjectSeqItem" msgstr "参照オブジェクト時系列アイテム" +msgid "Export the scene of selected items" +msgstr "選択アイテムのシーンのエクスポート" + +msgid "Export" +msgstr "エクスポート" + +msgid "No selected items." +msgstr "アイテムが選択されていません." + +msgid "Scene to export is empty." +msgstr "エクスポートするシーンが空です." + +msgid "" +"Export the scene of the following items to \"{0}\":\n" +" " +msgstr "" +"以下のアイテムのシーンを \"{0}\" にエクスポートします:\n" +" " + +msgid "Completed!" +msgstr "完了!" + +msgid "Failed." +msgstr "失敗." + msgid "Scene statistics:" msgstr "シーン統計情報:" @@ -1620,6 +1825,9 @@ msgstr "右面ビュー" msgid "Left view" msgstr "左面ビュー" +msgid "Isometric view" +msgstr "等角投影ビュー" + msgid "Scene" msgstr "シーン" @@ -1689,15 +1897,9 @@ msgstr "テクスチャ" msgid "Fog" msgstr "フォグ" -msgid "Background color" -msgstr "背景色" - msgid "Background Color" msgstr "背景色" -msgid "Default color" -msgstr "デフォルト色" - msgid "Default Color" msgstr "デフォルト色" @@ -1728,9 +1930,6 @@ msgstr "シーンビュー" msgid "Configuration of {0}" msgstr "{0}の設定" -msgid "Camera" -msgstr "カメラ" - msgid "Lighting" msgstr "ライティング" @@ -1746,12 +1945,6 @@ msgstr "デバッグ" msgid "OpengGL image buffer for picking" msgstr "ピッキング用OpenGL画像バッファ" -msgid "Perspective" -msgstr "透視投影" - -msgid "Orthographic" -msgstr "並行投影" - msgid "OpenGL initialization failed." msgstr "OpenGLの初期化に失敗しました." @@ -1836,12 +2029,18 @@ msgstr "遠方" msgid "Vertical axis" msgstr "垂直軸" +msgid "Background color" +msgstr "背景色" + msgid "Span" msgstr "長さ" msgid "Interval" msgstr "間隔" +msgid "Default color" +msgstr "デフォルト色" + msgid "ScriptBar" msgstr "スクリプトバー" @@ -1988,9 +2187,6 @@ msgstr "進行中の更新に同期" msgid "Automatically expand the time range" msgstr "時間範囲の自動拡張" -msgid "&OK" -msgstr "&OK" - msgid "Stop animation" msgstr "アニメーションの停止" diff --git a/src/Base/pybind11/PyToolBars.cpp b/src/Base/pybind11/PyToolBars.cpp index 3392d928c..361c2329e 100644 --- a/src/Base/pybind11/PyToolBars.cpp +++ b/src/Base/pybind11/PyToolBars.cpp @@ -135,8 +135,5 @@ void exportPyToolBars(py::module m) .def("getPlaybackSpeedScale", &TimeBar::playbackSpeedRatio) .def("getPlaybackFrameRate", &TimeBar::playbackFrameRate) ; - - PySignal(m, "SigPlaybackInitialized"); - PySignal(m, "SigTimeChanged"); } } diff --git a/src/Body/AttachmentDevice.cpp b/src/Body/AttachmentDevice.cpp index 01777e756..28a56b16c 100644 --- a/src/Body/AttachmentDevice.cpp +++ b/src/Body/AttachmentDevice.cpp @@ -134,8 +134,9 @@ std::string AttachmentDevice::category() const void AttachmentDevice::setCategory(const std::string& category) { - clearCategory(); - category_ = new string; + if(!category_){ + category_ = new string; + } *category_ = category; } @@ -149,6 +150,21 @@ void AttachmentDevice::clearCategory() } +bool AttachmentDevice::isAttachableTo(HolderDevice* holder) const +{ + if(body() == holder->body()){ + return false; + } + if(!holder->hasCategories()){ + return true; + } + if(!category_){ + return false; + } + return holder->hasCategory(*category_); +} + + int AttachmentDevice::stateSize() const { return 1; diff --git a/src/Body/AttachmentDevice.h b/src/Body/AttachmentDevice.h index 0d077ad97..8e25a13aa 100644 --- a/src/Body/AttachmentDevice.h +++ b/src/Body/AttachmentDevice.h @@ -35,6 +35,7 @@ class CNOID_EXPORT AttachmentDevice : public Device std::string category() const; void setCategory(const std::string& category); void clearCategory(); + bool isAttachableTo(HolderDevice* holder) const; bool readSpecifications(const Mapping* info); bool writeSpecifications(Mapping* info) const; diff --git a/src/Body/Body.h b/src/Body/Body.h index 468d84900..24caeee6e 100644 --- a/src/Body/Body.h +++ b/src/Body/Body.h @@ -286,6 +286,42 @@ class CNOID_EXPORT Body : public ClonableReferenced void exchangePositionWithMultiplexBody(Body* multiplexBody); SignalProxy sigMultiplexBodyAddedOrRemoved(); + class MultiplexBodyIterator { + Body* current; + public: + explicit MultiplexBodyIterator(Body* body = nullptr) : current(body) { } + Body*& operator*() { return current; } + Body** operator->() { return ¤t; } + MultiplexBodyIterator& operator++() { + if(current){ + current = current->nextMultiplexBody_; + } + return *this; + } + MultiplexBodyIterator operator++(int) { + MultiplexBodyIterator tmp = *this; + ++(*this); + return tmp; + } + bool operator==(const MultiplexBodyIterator& other) const { + return current == other.current; + } + bool operator!=(const MultiplexBodyIterator& other) const { + return current != other.current; + } + }; + + class MultiplexBodyList + { + Body* mainBody; + public: + MultiplexBodyList(Body* mainBody) : mainBody(mainBody) { } + MultiplexBodyIterator begin() { return MultiplexBodyIterator(mainBody); } + MultiplexBodyIterator end() { return MultiplexBodyIterator(nullptr); } + }; + + MultiplexBodyList multiplexBodies() { return MultiplexBodyList(this); } + const Mapping* info() const; Mapping* info(); diff --git a/src/Body/CMakeLists.txt b/src/Body/CMakeLists.txt index 45199f0a2..ab2d4fbd8 100644 --- a/src/Body/CMakeLists.txt +++ b/src/Body/CMakeLists.txt @@ -56,6 +56,7 @@ set(sources BodyKinematicsKit.cpp KinematicBodySet.cpp LeggedBodyHelper.cpp + ZmpDevice.cpp BodyCollisionLinkFilter.cpp BodyCollisionDetector.cpp BodyCollisionDetectorUtil.cpp @@ -144,6 +145,7 @@ set(headers BodyKinematicsKit.h KinematicBodySet.h LeggedBodyHelper.h + ZmpDevice.h PenetrationBlocker.h ForwardDynamics.h ForwardDynamicsABM.h diff --git a/src/Body/HolderDevice.cpp b/src/Body/HolderDevice.cpp index 772304090..df15ab3f4 100644 --- a/src/Body/HolderDevice.cpp +++ b/src/Body/HolderDevice.cpp @@ -13,7 +13,7 @@ namespace cnoid { class HolderDevice::NonState { public: - std::string category; + vector categories; int holdCondition; double maxHoldDistance; std::string holdTargetName; @@ -53,7 +53,7 @@ HolderDevice::HolderDevice(const HolderDevice& org, bool copyStateOnly, CloneMap HolderDevice::NonState::NonState(const NonState& org, CloneMap* cloneMap) - : category(org.category), + : categories(org.categories), holdTargetName(org.holdTargetName) { holdCondition = org.holdCondition; @@ -114,7 +114,7 @@ Referenced* HolderDevice::doClone(CloneMap* cloneMap) const void HolderDevice::copyHolderDeviceFrom(const HolderDevice* other) { - ns->category = other->ns->category; + ns->categories = other->ns->categories; ns->holdCondition = other->ns->holdCondition; ns->maxHoldDistance = other->ns->maxHoldDistance; ns->holdTargetName = other->ns->holdTargetName; @@ -152,10 +152,60 @@ void HolderDevice::on(bool on) } -std::string HolderDevice::category() const +std::vector HolderDevice::categories() const +{ + if(ns){ + return ns->categories; + } + return vector(); +} + + +bool HolderDevice::hasCategories() const +{ + return ns && !ns->categories.empty(); +} + + +bool HolderDevice::hasCategory(const char* category) const +{ + if(ns){ + for(auto& c : ns->categories){ + if(c == category){ + return true; + } + } + } + return false; +} + + +bool HolderDevice::hasCategory(const std::string& category) const +{ + return hasCategory(category.c_str()); +} + + +void HolderDevice::addCategory(const std::string& category) +{ + if(!category.empty() && ns){ + ns->categories.push_back(category); + } +} + + +void HolderDevice::clearCategories() { if(ns){ - return ns->category; + ns->categories.clear(); + } +} + + +std::string HolderDevice::category() const +{ + if(ns && !ns->categories.empty()){ + return ns->categories.front(); } return string(); } @@ -164,7 +214,8 @@ std::string HolderDevice::category() const void HolderDevice::setCategory(const std::string& category) { if(ns){ - ns->category = category; + ns->categories.clear(); + ns->categories.push_back(category); } } @@ -299,29 +350,45 @@ double* HolderDevice::writeState(double* out_buf) const bool HolderDevice::readDescription(const Mapping* info) { - if(!info->read("category", ns->category)){ - ns->category.clear(); + string symbol; + + clearCategories(); + if(auto& categories = *info->findListing("categories")){ + for(auto& category : categories){ + addCategory(category->toString()); + } + } else if(info->read("category", symbol)){ + addCategory(symbol); } - string condition; - if(info->read("hold_condition", condition)){ - if(condition == "distance"){ + + if(info->read("hold_condition", symbol)){ + if(symbol == "distance"){ ns->holdCondition = Distance; - } else if(condition == "collision"){ + } else if(symbol == "collision"){ ns->holdCondition = Collision; - } else if(condition == "name"){ + } else if(symbol == "name"){ ns->holdCondition = Name; } } info->read("max_hold_distance", ns->maxHoldDistance); info->read("hold_target_name", ns->holdTargetName); + return true; } bool HolderDevice::writeDescription(Mapping* info) const { - if(!ns->category.empty()){ - info->write("category", ns->category); + if(!ns->categories.empty()){ + if(ns->categories.size() == 1){ + // For backward compatibility + info->write("category", ns->categories.front()); + } else { + auto categories = info->openListing("categories"); + for(auto& category : ns->categories){ + categories->append(category); + } + } } string condition; switch(ns->holdCondition){ diff --git a/src/Body/HolderDevice.h b/src/Body/HolderDevice.h index 0c4fb675e..ab3954732 100644 --- a/src/Body/HolderDevice.h +++ b/src/Body/HolderDevice.h @@ -30,7 +30,16 @@ class CNOID_EXPORT HolderDevice : public Device virtual bool on() const override; virtual void on(bool on) override; + std::vector categories() const; + bool hasCategories() const; + bool hasCategory(const char* category) const; + bool hasCategory(const std::string& category) const; + void addCategory(const std::string& category); + void clearCategories(); + + [[deprecated("Use hasCategory or categories.")]] std::string category() const; + [[deprecated("Use addCategory.")]] void setCategory(const std::string& category); enum HoldCondition { Distance, Collision, Name }; diff --git a/src/Body/KinematicBodySet.cpp b/src/Body/KinematicBodySet.cpp index 2bc0cae16..88ae97cb6 100644 --- a/src/Body/KinematicBodySet.cpp +++ b/src/Body/KinematicBodySet.cpp @@ -53,7 +53,7 @@ void KinematicBodySet::setBodyPart(int index, BodyKinematicsKit* kinematicsKit) } -void KinematicBodySet::clearBodyPart(int index) +void KinematicBodySet::removeBodyPart(int index) { if(index < static_cast(bodyParts_.size())){ auto& part = bodyParts_[index]; @@ -80,6 +80,11 @@ void KinematicBodySet::clearBodyPart(int index) void KinematicBodySet::clear() { + for(size_t i=0; i < bodyParts_.size(); ++i){ + if(bodyParts_[i]){ + removeBodyPart(i); + } + } bodyParts_.clear(); numValidBodyParts_ = 0; mainBodyPartIndex_ = -1; diff --git a/src/Body/KinematicBodySet.h b/src/Body/KinematicBodySet.h index e0e42cdd6..26760dfdf 100644 --- a/src/Body/KinematicBodySet.h +++ b/src/Body/KinematicBodySet.h @@ -17,7 +17,9 @@ class CNOID_EXPORT KinematicBodySet : public ClonableReferenced } virtual void setBodyPart(int index, BodyKinematicsKit* kinematicsKit); - void clearBodyPart(int index); + virtual void removeBodyPart(int index); + [[deprecated("Use removeBodyPart.")]] + void clearBodyPart(int index) { removeBodyPart(index); } void clear(); void setMainBodyPartIndex(int index) { mainBodyPartIndex_ = index; } int mainBodyPartIndex() const { return mainBodyPartIndex_; } diff --git a/src/Body/LeggedBodyHelper.cpp b/src/Body/LeggedBodyHelper.cpp index 3ec96a785..b4900d5fd 100644 --- a/src/Body/LeggedBodyHelper.cpp +++ b/src/Body/LeggedBodyHelper.cpp @@ -263,3 +263,38 @@ Vector3 LeggedBodyHelper::homeCopOfSoles() const } return p / n; } + + +ZmpDevice* LeggedBodyHelper::getOrCreateZmpDevice() +{ + if(!zmpDevice_){ + if(isValid_){ + zmpDevice_ = body_->findDevice(); + if(!zmpDevice_){ + zmpDevice_ = new ZmpDevice; + body_->addDevice(zmpDevice_, body_->rootLink()); + } + } + } + return zmpDevice_; +} + + +Vector3 LeggedBodyHelper::zmp() const +{ + if(zmpDevice_){ + return zmpDevice_->zmp(); + } + return Vector3::Zero(); +} + + +void LeggedBodyHelper::setZmp(const Vector3& zmp, bool doNotify) +{ + if(getOrCreateZmpDevice()){ + zmpDevice_->setZmp(zmp); + if(doNotify){ + zmpDevice_->notifyStateChange(); + } + } +} diff --git a/src/Body/LeggedBodyHelper.h b/src/Body/LeggedBodyHelper.h index c5ff6c06f..2096422d4 100644 --- a/src/Body/LeggedBodyHelper.h +++ b/src/Body/LeggedBodyHelper.h @@ -3,6 +3,7 @@ #include "Body.h" #include "InverseKinematics.h" +#include "ZmpDevice.h" #include #include "exportdecl.h" @@ -48,7 +49,11 @@ class CNOID_EXPORT LeggedBodyHelper : public Referenced Vector3 homeCopOfSoles() const; const Isometry3& toeOffset(int footIndex) const { return footInfos[footIndex].toeOffset; }; - + + ZmpDevice* getOrCreateZmpDevice(); + Vector3 zmp() const; + void setZmp(const Vector3& zmp, bool doNotify = false); + private: BodyPtr body_; bool isValid_; @@ -65,6 +70,8 @@ class CNOID_EXPORT LeggedBodyHelper : public Referenced }; std::vector> footInfos; + + ZmpDevicePtr zmpDevice_; }; typedef ref_ptr LeggedBodyHelperPtr; diff --git a/src/Body/Link.cpp b/src/Body/Link.cpp index 992813295..e15d5f698 100644 --- a/src/Body/Link.cpp +++ b/src/Body/Link.cpp @@ -450,7 +450,7 @@ void Link::setMaterial(const std::string& name) bool Link::hasShape() const { - return !visualShape_->empty() || !collisionShape_->empty();\ + return !visualShape_->empty() || !collisionShape_->empty(); } diff --git a/src/Body/MarkerDevice.cpp b/src/Body/MarkerDevice.cpp index 1501d4414..dfcf8ad46 100644 --- a/src/Body/MarkerDevice.cpp +++ b/src/Body/MarkerDevice.cpp @@ -301,5 +301,3 @@ registerMarkerDevice( return marker->writeSpecifications(info); }); } - - diff --git a/src/Body/SceneBody.cpp b/src/Body/SceneBody.cpp index b8a8efd71..d8b22dce8 100644 --- a/src/Body/SceneBody.cpp +++ b/src/Body/SceneBody.cpp @@ -80,11 +80,11 @@ class SceneBody::Impl ScopedConnection existenceConnection; Impl(SceneBody* self); - void setBody(Body* body); - void updateLinkPositions(Body* body, vector& sceneLinks, SgUpdateRef& update); - void updateMultiplexBodyPositions(SgUpdateRef& update); - SceneBody* addMultiplexSceneBody(Body* multiplexBody, SgUpdateRef& update); - void removeSubsequentMultiplexSceneBodies(int index, SgUpdateRef& update, bool doCache); + bool setBody(Body* body); + void updateLinkPositions(Body* body, vector& sceneLinks); + void updateMultiplexBodyPositions(); + SceneBody* addMultiplexSceneBody(Body* multiplexBody); + void removeSubsequentMultiplexSceneBodies(int index, bool doCache); void clearSceneDevices(); void onBodyExistenceChanged(bool on); }; @@ -421,14 +421,17 @@ void SceneBody::setBody(Body* body, std::function sceneLinkFa } -void SceneBody::Impl::setBody(Body* body) +bool SceneBody::Impl::setBody(Body* body) { + bool updatedActually = false; if(body != self->body_){ self->body_ = body; self->updateSceneModel(); existenceConnection = body->sigExistenceChanged().connect([this](bool on){ onBodyExistenceChanged(on); }); + updatedActually = true; } + return updatedActually; } @@ -441,11 +444,9 @@ void SceneBody::updateSceneModel() sceneLinks_.clear(); } - SgUpdateRef noUpdate; - bool isMainBody = body_->isMultiplexMainBody(); if(isMainBody){ - impl->removeSubsequentMultiplexSceneBodies(0, noUpdate, false); + impl->removeSubsequentMultiplexSceneBodies(0, false); } const int n = body_->numLinks(); @@ -459,9 +460,9 @@ void SceneBody::updateSceneModel() updateSceneDeviceModels(false); if(isMainBody){ - updateLinkPositions(noUpdate); + updateLinkPositions(nullptr); } else { - impl->updateLinkPositions(body_, sceneLinks_, noUpdate); + impl->updateLinkPositions(body_, sceneLinks_); } notifyUpdate(SgUpdate::REMOVED | SgUpdate::ADDED | SgUpdate::MODIFIED); @@ -496,48 +497,52 @@ void SceneBody::cloneShapes(CloneMap& cloneMap) void SceneBody::updateLinkPositions(SgUpdateRef update) { // Main body - impl->updateLinkPositions(body_, sceneLinks_, update); + impl->updateLinkPositions(body_, sceneLinks_); + + impl->updateMultiplexBodyPositions(); - impl->updateMultiplexBodyPositions(update); + if(update){ + notifyUpdate(update); + } } -void SceneBody::Impl::updateLinkPositions(Body* body, vector& sceneLinks, SgUpdateRef& update) +void SceneBody::Impl::updateLinkPositions(Body* body, vector& sceneLinks) { int n = std::min(body->numLinks(), static_cast(sceneLinks.size())); for(int i=0; i < n; ++i){ SceneLink* sceneLink = sceneLinks[i]; Link* link = body->link(i); sceneLink->setPosition(link->position()); - if(update){ - sceneLink->notifyUpdate(*update); - } } } -void SceneBody::Impl::updateMultiplexBodyPositions(SgUpdateRef& update) +void SceneBody::Impl::updateMultiplexBodyPositions() { auto multiplexBody = self->body_->nextMultiplexBody(); int multiplexBodyIndex = 0; while(multiplexBody){ SceneBody* sceneBody; + bool linkPositionsUpdated = false; if(multiplexBodyIndex < multiplexSceneBodies.size()){ sceneBody = multiplexSceneBodies[multiplexBodyIndex]; - sceneBody->impl->setBody(multiplexBody); + linkPositionsUpdated = sceneBody->impl->setBody(multiplexBody); } else { - sceneBody = addMultiplexSceneBody(multiplexBody, update); + sceneBody = addMultiplexSceneBody(multiplexBody); + } + if(!linkPositionsUpdated){ + auto& sceneLinks = sceneBody->sceneLinks_; + updateLinkPositions(multiplexBody, sceneLinks); } - auto& sceneLinks = sceneBody->sceneLinks_; - updateLinkPositions(multiplexBody, sceneLinks, update); multiplexBody = multiplexBody->nextMultiplexBody(); ++multiplexBodyIndex; } - removeSubsequentMultiplexSceneBodies(multiplexBodyIndex, update, true); + removeSubsequentMultiplexSceneBodies(multiplexBodyIndex, true); } -SceneBody* SceneBody::Impl::addMultiplexSceneBody(Body* multiplexBody, SgUpdateRef& update) +SceneBody* SceneBody::Impl::addMultiplexSceneBody(Body* multiplexBody) { SceneBodyPtr sceneBody; @@ -554,19 +559,19 @@ SceneBody* SceneBody::Impl::addMultiplexSceneBody(Body* multiplexBody, SgUpdateR multiplexSceneBodyGroup = new SgGroup; self->addChild(multiplexSceneBodyGroup); } - multiplexSceneBodyGroup->addChild(sceneBody, update); + multiplexSceneBodyGroup->addChild(sceneBody); return sceneBody; } -void SceneBody::Impl::removeSubsequentMultiplexSceneBodies(int index, SgUpdateRef& update, bool doCache) +void SceneBody::Impl::removeSubsequentMultiplexSceneBodies(int index, bool doCache) { int n = multiplexSceneBodies.size(); if(index < n){ int lastIndex = n - 1; for(size_t i = index; i < n; ++i){ - multiplexSceneBodyGroup->removeChildAt(lastIndex--, update); + multiplexSceneBodyGroup->removeChildAt(lastIndex--); if(doCache){ multiplexSceneBodyCache.push_back(multiplexSceneBodies[i]); } diff --git a/src/Body/ZmpDevice.cpp b/src/Body/ZmpDevice.cpp new file mode 100644 index 000000000..b4547c221 --- /dev/null +++ b/src/Body/ZmpDevice.cpp @@ -0,0 +1,124 @@ +#include "ZmpDevice.h" +#include "StdBodyFileUtil.h" +#include + +using namespace std; +using namespace cnoid; + + +ZmpDevice::ZmpDevice() +{ + zmp_.setZero(); + on_ = true; +} + + +ZmpDevice::ZmpDevice(const ZmpDevice& org, bool copyStateOnly, CloneMap* cloneMap) + : Device(org, copyStateOnly) +{ + copyZmpDeviceStateFrom(org); +} + + +ZmpDevice::~ZmpDevice() +{ + +} + + +const char* ZmpDevice::typeName() const +{ + return "ZmpDevice"; +} + + +void ZmpDevice::copyZmpDeviceStateFrom(const ZmpDevice& other) +{ + zmp_ = other.zmp_; + on_ = other.on_; +} + + +void ZmpDevice::copyStateFrom(const DeviceState& other) +{ + if(typeid(other) != typeid(ZmpDevice)){ + throw std::invalid_argument("Type mismatch in the Device::copyStateFrom function"); + } + copyZmpDeviceStateFrom(static_cast(other)); +} + + +DeviceState* ZmpDevice::cloneState() const +{ + return new ZmpDevice(*this, true, nullptr); + +} + + +Referenced* ZmpDevice::doClone(CloneMap* cloneMap) const +{ + return new ZmpDevice(*this, false, cloneMap); +} + + +void ZmpDevice::forEachActualType(std::function func) +{ + if(!func(typeid(ZmpDevice))){ + Device::forEachActualType(func); + } +} + + +bool ZmpDevice::on() const +{ + return on_; +} + + +void ZmpDevice::on(bool on) +{ + on_ = on; +} + + +int ZmpDevice::stateSize() const +{ + return 2; +} + + +const double* ZmpDevice::readState(const double* buf) +{ + int i = 0; + on_ = buf[i++]; + zmp_[0] = buf[i++]; + zmp_[1] = buf[i++]; + zmp_[2] = buf[i++]; + return buf + i; +} + + +double* ZmpDevice::writeState(double* out_buf) const +{ + int i = 0; + out_buf[i++] = on_ ? 1.0 : 0.0; + out_buf[i++] = zmp_[0]; + out_buf[i++] = zmp_[1]; + out_buf[i++] = zmp_[2]; + return out_buf + i; +} + + +namespace { + +StdBodyFileDeviceTypeRegistration +registerZmpDevice( + "ZmpDevice", + [](StdBodyLoader* loader, const Mapping* info){ + ZmpDevicePtr device = new ZmpDevice; + return loader->readDevice(device, info); + }, + [](StdBodyWriter* /* writer */, Mapping* /* info */, const ZmpDevice* /* zmpDevice */){ + return true; + }); +} diff --git a/src/Body/ZmpDevice.h b/src/Body/ZmpDevice.h new file mode 100644 index 000000000..c1767bce1 --- /dev/null +++ b/src/Body/ZmpDevice.h @@ -0,0 +1,43 @@ +#ifndef CNOID_BODY_ZMP_DEVICE_H +#define CNOID_BODY_ZMP_DEVICE_H + +#include "Device.h" +#include "exportdecl.h" + +namespace cnoid { + +class CNOID_EXPORT ZmpDevice : public Device +{ +public: + ZmpDevice(); + virtual ~ZmpDevice(); + + virtual const char* typeName() const override; + void copyZmpDeviceStateFrom(const ZmpDevice& other); + virtual void copyStateFrom(const DeviceState& other) override; + virtual DeviceState* cloneState() const override; + virtual void forEachActualType(std::function func) override; + virtual int stateSize() const override; + virtual const double* readState(const double* buf) override; + virtual double* writeState(double* out_buf) const override; + + virtual bool on() const override; + virtual void on(bool on) override; + + const Vector3& zmp() const { return zmp_; }; + void setZmp(const Vector3& zmp) { zmp_ = zmp; } + +protected: + ZmpDevice(const ZmpDevice& org, bool copyStateOnly, CloneMap* cloneMap); + virtual Referenced* doClone(CloneMap* cloneMap) const override; + +private: + Vector3 zmp_; + bool on_; +}; + +typedef ref_ptr ZmpDevicePtr; + +} + +#endif diff --git a/src/BodyPlugin/BodyItem.cpp b/src/BodyPlugin/BodyItem.cpp index d60be7de0..e57031e90 100644 --- a/src/BodyPlugin/BodyItem.cpp +++ b/src/BodyPlugin/BodyItem.cpp @@ -45,12 +45,6 @@ const bool TRACE_FUNCTIONS = false; vector bodyFilesToLoad; -class BodyStateEx : public BodyState -{ -public: - Vector3 zmp; -}; - class BodyLocation : public LocationProxy { public: @@ -61,28 +55,22 @@ class BodyLocation : public LocationProxy virtual Isometry3 getLocation() const override; virtual bool isLocked() const override; virtual void setLocked(bool on) override; - virtual bool isDoingContinuousUpdate() const override; virtual bool setLocation(const Isometry3& T) override; virtual void finishLocationEditing() override; - virtual Item* getCorrespondingItem() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; virtual SignalProxy sigLocationChanged() override; }; class LinkLocation : public LocationProxy { public: - weak_ref_ptr refBodyItem; weak_ref_ptr refLink; - LinkLocation(); LinkLocation(BodyItem* bodyItem, Link* link); - void setTarget(BodyItem* bodyItem, Link* link); virtual std::string getName() const override; virtual Isometry3 getLocation() const override; virtual bool isLocked() const override; - virtual Item* getCorrespondingItem() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; virtual SignalProxy sigLocationChanged() override; }; @@ -103,11 +91,11 @@ class KinematicStateRecord : public EditRecord public: BodyItemPtr bodyItem; BodyItem::Impl* bodyItemImpl; - BodyStateEx newState; - BodyStateEx oldState; + BodyState newState; + BodyState oldState; KinematicStateRecord(BodyItem::Impl* bodyItemImpl); - KinematicStateRecord(BodyItem::Impl* bodyItemImpl, const BodyStateEx& oldState); + KinematicStateRecord(BodyItem::Impl* bodyItemImpl, const BodyState& oldState); KinematicStateRecord(const KinematicStateRecord& org); virtual EditRecord* clone() const override; @@ -161,8 +149,8 @@ class BodyItem::Impl unique_ptr kinematicsKitManager; shared_ptr pinDragIK; - BodyStateEx initialState; - BodyStateEx lastEditState; + BodyState initialState; + BodyState lastEditState; BodyPtr exchangedMultiplexBody; @@ -170,10 +158,7 @@ class BodyItem::Impl float transparency; Signal sigModelUpdated; - Signal sigContinuousKinematicUpdateStateChanged; - LeggedBodyHelperPtr legged; - Vector3 zmp; static unique_ptr renderableItemUtil; @@ -188,8 +173,6 @@ class BodyItem::Impl void setBody(Body* body); void notifyModelUpdate(int flags); void setCurrentBaseLink(Link* link, bool forceUpdate, bool doNotify); - void storeKinematicStateEx(BodyStateEx& state); - void restoreKinematicStateEx(const BodyStateEx& state); bool makeRootFixed(); bool makeRootFree(); BodyItemKinematicsKitManager* getOrCreateKinematicsKitManager(); @@ -258,7 +241,6 @@ BodyItem::BodyItem() impl = new Impl(this); impl->init(false); - continuousKinematicUpdateCounter = 0; isAttachedToParentBody_ = false; isVisibleLinkSelectionMode_ = false; } @@ -299,11 +281,8 @@ BodyItem::BodyItem(const BodyItem& org, CloneMap* cloneMap) impl = new Impl(this, *org.impl, cloneMap); impl->init(true); - continuousKinematicUpdateCounter = 0; isAttachedToParentBody_ = false; isVisibleLinkSelectionMode_ = org.isVisibleLinkSelectionMode_; - - setChecked(org.isChecked()); } @@ -312,7 +291,6 @@ BodyItem::Impl::Impl(BodyItem* self, const Impl& org, CloneMap* cloneMap) { isAttachmentEnabled = org.isAttachmentEnabled; transparency = org.transparency; - zmp = org.zmp; isCollisionDetectionEnabled = org.isCollisionDetectionEnabled; isSelfCollisionDetectionEnabled = org.isSelfCollisionDetectionEnabled; @@ -359,7 +337,6 @@ void BodyItem::Impl::initBody(bool calledFromCopyConstructor) if(!calledFromCopyConstructor){ setCurrentBaseLink(nullptr, true, false); - zmp.setZero(); self->storeInitialState(); } @@ -402,7 +379,6 @@ bool BodyItem::Impl::doAssign(const Item* srcItem) isAttachmentEnabled = srcImpl->isAttachmentEnabled; isLocationLocked = srcImpl->isLocationLocked; transparency = srcImpl->transparency; - zmp = srcImpl->zmp; isCollisionDetectionEnabled = srcImpl->isCollisionDetectionEnabled; isSelfCollisionDetectionEnabled = srcImpl->isSelfCollisionDetectionEnabled; @@ -415,15 +391,7 @@ bool BodyItem::Impl::doAssign(const Item* srcItem) setCurrentBaseLink(baseLink, false, false); } } - // copy the current kinematic state Body* srcBody = srcBodyItem->body(); - for(int i=0; i < srcBody->numLinks(); ++i){ - Link* srcLink = srcBody->link(i); - Link* link = body->link(srcLink->name()); - if(link){ - link->q() = srcLink->q(); - } - } if(baseLink){ baseLink->p() = srcBaseLink->p(); baseLink->R() = srcBaseLink->R(); @@ -432,9 +400,20 @@ bool BodyItem::Impl::doAssign(const Item* srcItem) body->rootLink()->R() = srcBody->rootLink()->R(); } - initialState = srcImpl->initialState; + // copy the current kinematic state + int numSrcLinks = srcBody->numLinks(); + for(int i=0; i < numSrcLinks; ++i){ + Link* srcLink = srcBody->link(i); + Link* link = body->link(srcLink->name()); + if(link){ + link->q() = srcLink->q(); + } + } + + self->calcForwardKinematics(); + self->storeKinematicState(initialState); - self->notifyKinematicStateChange(true); + self->notifyKinematicStateChange(); return true; } @@ -462,7 +441,7 @@ void BodyItem::onTreePathChanged() void BodyItem::onConnectedToRoot() { - impl->storeKinematicStateEx(impl->lastEditState); + storeKinematicState(impl->lastEditState); } @@ -649,36 +628,22 @@ void BodyItem::storeKinematicState(BodyState& state) } -void BodyItem::Impl::storeKinematicStateEx(BodyStateEx& state) -{ - state.storeMultiplexStateOfBody(body); - state.zmp = zmp; -} - - void BodyItem::restoreKinematicState(const BodyState& state) { state.restoreMultiplexStateToBody(impl->body); } -void BodyItem::Impl::restoreKinematicStateEx(const BodyStateEx& state) -{ - state.restoreMultiplexStateToBody(body); - zmp = state.zmp; -} - - void BodyItem::storeInitialState() { Item::setConsistentWithProjectArchive(false); - impl->storeKinematicStateEx(impl->initialState); + storeKinematicState(impl->initialState); } void BodyItem::restoreInitialState(bool doNotify) { - impl->restoreKinematicStateEx(impl->initialState); + restoreKinematicState(impl->initialState); if(doNotify){ notifyKinematicStateUpdate(); } @@ -948,8 +913,9 @@ stdx::optional BodyItem::getParticularPosition(PositionType position) void BodyItem::Impl::getParticularPosition(BodyItem::PositionType position, stdx::optional& pos) { if(position == BodyItem::ZERO_MOMENT_POINT){ - pos = zmp; - + if(self->isLeggedBody()){ + pos = legged->zmp(); + } } else { if(position == BodyItem::CM_PROJECTION){ pos = self->centerOfMass(); @@ -976,25 +942,6 @@ void BodyItem::Impl::getParticularPosition(BodyItem::PositionType position, stdx } -const Vector3& BodyItem::zmp() const -{ - return impl->zmp; -} - - -void BodyItem::setZmp(const Vector3& zmp) -{ - impl->zmp = zmp; -} - - -void BodyItem::editZmp(const Vector3& zmp) -{ - setZmp(zmp); - notifyKinematicStateUpdate(); -} - - void BodyItem::Impl::notifyKinematicStateChange(bool requestFK, bool requestVelFK, bool requestAccFK, bool isDirect) { updateElements.reset(); @@ -1080,7 +1027,7 @@ void BodyItem::notifyKinematicStateUpdate(bool doNotifyStateChange) auto record = new KinematicStateRecord(impl, impl->lastEditState); UnifiedEditHistory::instance()->addRecord(record); - impl->storeKinematicStateEx(impl->lastEditState); + storeKinematicState(impl->lastEditState); } @@ -1209,7 +1156,7 @@ LocationProxyPtr BodyItem::createLinkLocationProxy(Link* link) BodyLocation::BodyLocation(BodyItem::Impl* impl) - : LocationProxy(impl->attachmentToParent ? OffsetLocation : GlobalLocation), + : LocationProxy(impl->self, impl->attachmentToParent ? OffsetLocation : GlobalLocation), impl(impl) { @@ -1255,12 +1202,6 @@ void BodyLocation::setLocked(bool on) } -bool BodyLocation::isDoingContinuousUpdate() const -{ - return impl->self->isDoingContinuousKinematicUpdate(); -} - - bool BodyLocation::setLocation(const Isometry3& T) { auto rootLink = impl->body->rootLink(); @@ -1287,24 +1228,22 @@ void BodyLocation::finishLocationEditing() } -Item* BodyLocation::getCorrespondingItem() -{ - return impl->self; -} - - -LocationProxyPtr BodyLocation::getParentLocationProxy() const +LocationProxyPtr BodyLocation::getParentLocationProxy() { if(impl->parentBodyItem){ - if(impl->attachmentToParent){ + if(!impl->attachmentToParent){ + return impl->parentBodyItem->getLocationProxy(); + } else { + auto parentLink = impl->body->parentBodyLink(); + if(impl->parentLinkLocation){ + if(impl->parentLinkLocation->refLink.lock() != parentLink){ + impl->parentLinkLocation.reset(); + } + } if(!impl->parentLinkLocation){ - impl->parentLinkLocation = new LinkLocation; + impl->parentLinkLocation = new LinkLocation(impl->parentBodyItem, parentLink); } - auto parentLink = impl->body->parentBodyLink(); - impl->parentLinkLocation->setTarget(impl->parentBodyItem, parentLink); return impl->parentLinkLocation; - } else { - return impl->parentBodyItem->getLocationProxy(); } } return nullptr; @@ -1317,26 +1256,11 @@ SignalProxy BodyLocation::sigLocationChanged() } -LinkLocation::LinkLocation() - : LocationProxy(GlobalLocation) -{ - -} - - LinkLocation::LinkLocation(BodyItem* bodyItem, Link* link) - : LocationProxy(GlobalLocation), - refBodyItem(bodyItem), + : LocationProxy(bodyItem, GlobalLocation), refLink(link) { - -} - - -void LinkLocation::setTarget(BodyItem* bodyItem, Link* link) -{ - refBodyItem = bodyItem; - refLink = link; + setNameDependencyOnItemName(); } @@ -1364,16 +1288,10 @@ bool LinkLocation::isLocked() const } -Item* LinkLocation::getCorrespondingItem() -{ - return refBodyItem.lock(); -} - - -LocationProxyPtr LinkLocation::getParentLocationProxy() const +LocationProxyPtr LinkLocation::getParentLocationProxy() { - if(auto body = refBodyItem.lock()){ - body->getLocationProxy(); + if(auto bodyItem = static_cast(locatableItem())){ + bodyItem->getLocationProxy(); } return nullptr; } @@ -1381,7 +1299,7 @@ LocationProxyPtr LinkLocation::getParentLocationProxy() const SignalProxy LinkLocation::sigLocationChanged() { - if(auto bodyItem = refBodyItem.lock()){ + if(auto bodyItem = static_cast(locatableItem())){ return bodyItem->sigKinematicStateChanged(); } else { static Signal dummySignal; @@ -1567,7 +1485,7 @@ Link* BodyItem::Impl::attachToBodyItem(BodyItem* bodyItem) for(auto& attachment : body->devices()){ if(attachment->link()->isRoot()){ for(auto& holder : bodyItem->body()->devices()){ - if(attachment->category() == holder->category()){ + if(attachment->isAttachableTo(holder)){ holder->addAttachment(attachment); holder->on(true); attachment->on(true); @@ -1616,43 +1534,6 @@ void BodyItem::Impl::onParentBodyKinematicStateChanged() } -BodyItem::ContinuousKinematicUpdateEntry BodyItem::startContinuousKinematicUpdate() -{ - return new ContinuousKinematicUpdateRef(this); -} - - -SignalProxy BodyItem::sigContinuousKinematicUpdateStateChanged() -{ - return impl->sigContinuousKinematicUpdateStateChanged; -} - - -BodyItem::ContinuousKinematicUpdateRef::ContinuousKinematicUpdateRef(BodyItem* item) - : bodyItemRef(item) -{ - if(++item->continuousKinematicUpdateCounter == 1){ - item->impl->sigContinuousKinematicUpdateStateChanged(true); - if(auto& bodyLocation = item->impl->bodyLocation){ - bodyLocation->notifyAttributeChange(); - } - } -} - - -BodyItem::ContinuousKinematicUpdateRef::~ContinuousKinematicUpdateRef() -{ - if(auto item = bodyItemRef.lock()){ - if(--item->continuousKinematicUpdateCounter == 0){ - item->impl->sigContinuousKinematicUpdateStateChanged(false); - if(auto& bodyLocation = item->impl->bodyLocation){ - bodyLocation->notifyAttributeChange(); - } - } - } -} - - MyCompositeBodyIK::MyCompositeBodyIK(BodyItem::Impl* bodyItemImpl) : bodyItemImpl(bodyItemImpl), attachment(bodyItemImpl->attachmentToParent) @@ -1813,8 +1694,6 @@ bool BodyItem::Impl::store(Archive& archive) archive.write("transparency", transparency); } - write(archive, "zmp", zmp); - return true; } @@ -1896,8 +1775,6 @@ bool BodyItem::Impl::restore(const Archive& archive) setTransparency(t); } - read(archive, "zmp", zmp); - isUpdateNotificationOnSubTreeRestoredRequested = false; isNonRootLinkStateRestorationOnSubTreeRestoredRequested = false; @@ -2080,18 +1957,18 @@ KinematicStateRecord::KinematicStateRecord(BodyItem::Impl* bodyItemImpl) bodyItem(bodyItemImpl->self), bodyItemImpl(bodyItemImpl) { - bodyItemImpl->storeKinematicStateEx(newState); + bodyItem->storeKinematicState(newState); oldState = newState; } -KinematicStateRecord::KinematicStateRecord(BodyItem::Impl* bodyItemImpl, const BodyStateEx& oldState) +KinematicStateRecord::KinematicStateRecord(BodyItem::Impl* bodyItemImpl, const BodyState& oldState) : EditRecord(bodyItemImpl->self), bodyItem(bodyItemImpl->self), bodyItemImpl(bodyItemImpl), oldState(oldState) { - bodyItemImpl->storeKinematicStateEx(newState); + bodyItem->storeKinematicState(newState); } @@ -2124,8 +2001,8 @@ std::string KinematicStateRecord::label() const bool KinematicStateRecord::undo() { - bodyItemImpl->restoreKinematicStateEx(oldState); - bodyItemImpl->storeKinematicStateEx(bodyItemImpl->lastEditState); + bodyItem->restoreKinematicState(oldState); + bodyItem->storeKinematicState(bodyItemImpl->lastEditState); bodyItemImpl->notifyKinematicStateChange(false, false, false, true); return true; } @@ -2133,8 +2010,8 @@ bool KinematicStateRecord::undo() bool KinematicStateRecord::redo() { - bodyItemImpl->restoreKinematicStateEx(newState); - bodyItemImpl->storeKinematicStateEx(bodyItemImpl->lastEditState); + bodyItem->restoreKinematicState(newState); + bodyItem->storeKinematicState(bodyItemImpl->lastEditState); bodyItemImpl->notifyKinematicStateChange(false, false, false, true); return true; } diff --git a/src/BodyPlugin/BodyItem.h b/src/BodyPlugin/BodyItem.h index 6a6c66670..fce2091dc 100644 --- a/src/BodyPlugin/BodyItem.h +++ b/src/BodyPlugin/BodyItem.h @@ -175,10 +175,6 @@ class CNOID_EXPORT BodyItem : public Item, public LocatableItem, public Renderab bool isLeggedBody() const; bool doLegIkToMoveCm(const Vector3& c, bool onlyProjectionToFloor = false); - const Vector3& zmp() const; - void setZmp(const Vector3& zmp); - void editZmp(const Vector3& zmp); - enum PositionType { CM_PROJECTION, HOME_COP, LEFT_HOME_COP, RIGHT_HOME_COP, ZERO_MOMENT_POINT }; stdx::optional getParticularPosition(PositionType posType); @@ -192,23 +188,27 @@ class CNOID_EXPORT BodyItem : public Item, public LocatableItem, public Renderab void setLocationLocked(bool on); LocationProxyPtr createLinkLocationProxy(Link* link); - class ContinuousKinematicUpdateRef : public Referenced - { - private: - ContinuousKinematicUpdateRef(BodyItem* item); - ~ContinuousKinematicUpdateRef(); - weak_ref_ptr bodyItemRef; - friend class BodyItem; - }; - typedef ref_ptr ContinuousKinematicUpdateEntry; + [[deprecated]] + typedef ContinuousUpdateEntry ContinuousKinematicUpdateEntry; + + [[deprecated]] + ContinuousUpdateEntry startContinuousKinematicUpdate() { + return Item::startContinuousUpdate(); + } - ContinuousKinematicUpdateEntry startContinuousKinematicUpdate(); - bool isDoingContinuousKinematicUpdate() const { return continuousKinematicUpdateCounter > 0; } + [[deprecated]] + bool isDoingContinuousKinematicUpdate() const { + return Item::isContinuousUpdateState(); + } + /** \note The sigUpdated signal is not emitted when the corresponding state changed becasue this is not a permenent state. */ - SignalProxy sigContinuousKinematicUpdateStateChanged(); + [[deprecated]] + SignalProxy sigContinuousKinematicUpdateStateChanged(){ + return Item::sigContinuousUpdateStateChanged(); + } // RenderableItem function virtual SgNode* getScene() override; @@ -256,7 +256,6 @@ class CNOID_EXPORT BodyItem : public Item, public LocatableItem, public Renderab private: Impl* impl; - int continuousKinematicUpdateCounter; bool isAttachedToParentBody_; bool isVisibleLinkSelectionMode_; std::vector collisions_; diff --git a/src/BodyPlugin/BodyItemFileIO.cpp b/src/BodyPlugin/BodyItemFileIO.cpp index 548276808..e5700c2c9 100644 --- a/src/BodyPlugin/BodyItemFileIO.cpp +++ b/src/BodyPlugin/BodyItemFileIO.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -215,6 +216,8 @@ StdBodyWriter* BodyItemBodyFileIO::ensureBodyWriter() bodyWriter_ = new StdBodyWriter; bodyWriter_->setMessageSink(os()); bodyWriter_->setOriginalShapeExtModelFileUriRewritingEnabled(true); + bodyWriter_->sceneWriter()->setFilePathVariableProcessor( + FilePathVariableProcessor::systemInstance()); } return bodyWriter_; } @@ -408,6 +411,8 @@ StdSceneWriter* StdSceneFileExporter::ensureSceneWriter() sceneWriter.reset(new StdSceneWriter); sceneWriter->setMessageSink(os()); sceneWriter->setIndentWidth(1); + sceneWriter->setFilePathVariableProcessor( + FilePathVariableProcessor::systemInstance()); } return sceneWriter.get(); } diff --git a/src/BodyPlugin/BodyLibraryView.cpp b/src/BodyPlugin/BodyLibraryView.cpp index cdbd50e65..1dfe8b1ee 100644 --- a/src/BodyPlugin/BodyLibraryView.cpp +++ b/src/BodyPlugin/BodyLibraryView.cpp @@ -1257,7 +1257,7 @@ bool BodyLibraryView::Impl::setBodyThumbnailWithDialog(LibraryItem* item) dialog.setViewMode(QFileDialog::List); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setLabelText(QFileDialog::Accept, _("Select")); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); fs::path filePath(fromUTF8(item->file)); bool isBodyCopiedInLibraryDirectory = checkIfInternalFilePath(filePath); diff --git a/src/BodyPlugin/BodyLinkView.cpp b/src/BodyPlugin/BodyLinkView.cpp index 2fa285d66..53ef740a6 100644 --- a/src/BodyPlugin/BodyLinkView.cpp +++ b/src/BodyPlugin/BodyLinkView.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -726,11 +727,10 @@ void BodyLinkView::Impl::updateKinematicState(bool blockSignals) } } - if(currentBodyItem->isLeggedBody()){ - const Vector3& zmp = currentBodyItem->zmp(); - for(int i=0; i < 3; ++i){ - zmpXyzSpin[i].setValue(zmp[i]); - } + auto legged = getLeggedBodyHelper(currentBodyItem->body()); + Vector3 zmp = legged->zmp(); + for(int i=0; i < 3; ++i){ + zmpXyzSpin[i].setValue(zmp[i]); } if(blockSignals){ @@ -963,12 +963,14 @@ void BodyLinkView::Impl::doInverseKinematics(Vector3 p, Matrix3 R) void BodyLinkView::Impl::onZmpXyzChanged() { if(currentBodyItem){ - Vector3 zmp; - for(int i=0; i < 3; ++i){ - zmp[i] = zmpXyzSpin[i].value(); + auto legged = getLeggedBodyHelper(currentBodyItem->body()); + if(legged->isValid()){ + Vector3 zmp; + for(int i=0; i < 3; ++i){ + zmp[i] = zmpXyzSpin[i].value(); + } + legged->setZmp(zmp, true); } - currentBodyItem->setZmp(zmp); - currentBodyItem->notifyKinematicStateChange(false); } } diff --git a/src/BodyPlugin/BodyMotionEngine.cpp b/src/BodyPlugin/BodyMotionEngine.cpp index 09bbbea79..c38d6849b 100644 --- a/src/BodyPlugin/BodyMotionEngine.cpp +++ b/src/BodyPlugin/BodyMotionEngine.cpp @@ -38,8 +38,11 @@ BodyMotionEngineCore::BodyMotionEngineCore(BodyItem* bodyItem) void BodyMotionEngineCore::updateBodyState(double time, const BodyState& state) { if(auto bodyItem_ = bodyItemRef.lock()){ - bool needFk = updateBodyState_(time, bodyItem_->body(), state); - bodyItem_->notifyKinematicStateChange(needFk); + auto body = bodyItem_->body(); + if(updateBodyState_(time, body, state)){ + calcForwardKinematics(body, false); + } + bodyItem_->notifyKinematicStateChange(); } } @@ -50,7 +53,17 @@ bool BodyMotionEngineCore::updateBodyState_(double time, Body* body, const BodyS // Main body auto stateBlock = state.firstBlock(); - if(updateSingleBodyState(time, body, stateBlock, true)){ + if(stateBlock.empty()){ + if(body->existence()){ + body->setExistence(false); + } + return false; + } + + if(!body->existence()){ + body->setExistence(true); + } + if(updateSingleBodyState(time, body, stateBlock)){ needFk = true; } @@ -62,7 +75,7 @@ bool BodyMotionEngineCore::updateBodyState_(double time, Body* body, const BodyS Body* multiplexBody = body; while(stateBlock){ multiplexBody = multiplexBody->getOrCreateNextMultiplexBody(); - updateSingleBodyState(time, multiplexBody, stateBlock, false); + updateSingleBodyState(time, multiplexBody, stateBlock); stateBlock = state.nextBlockOf(stateBlock); } multiplexBody->clearMultiplexBodies(); @@ -72,7 +85,7 @@ bool BodyMotionEngineCore::updateBodyState_(double time, Body* body, const BodyS } -bool BodyMotionEngineCore::updateSingleBodyState(double time, Body* body, BodyStateBlock bodyStateBlock, bool isMainBody) +bool BodyMotionEngineCore::updateSingleBodyState(double time, Body* body, BodyStateBlock bodyStateBlock) { bool needFk = false; @@ -80,24 +93,15 @@ bool BodyMotionEngineCore::updateSingleBodyState(double time, Body* body, BodySt int numLinkPositions = bodyStateBlock.numLinkPositions(); int numDeviceStates = bodyStateBlock.numDeviceStates(); - if(numLinkPositions == 0 && numDeviceStates == 0){ - if(body->existence() && isMainBody){ - body->setExistence(false); - } - } else { - if(!body->existence() && isMainBody){ - body->setExistence(true); - } - int numLinks = std::min(numAllLinks, numLinkPositions); - for(int i=0; i < numLinks; ++i){ - auto link = body->link(i); - auto linkPosition = bodyStateBlock.linkPosition(i); - link->setTranslation(linkPosition.translation()); - link->setRotation(linkPosition.rotation()); - } - if(numLinks < numAllLinks){ - needFk = true; - } + int numLinks = std::min(numAllLinks, numLinkPositions); + for(int i=0; i < numLinks; ++i){ + auto link = body->link(i); + auto linkPosition = bodyStateBlock.linkPosition(i); + link->setTranslation(linkPosition.translation()); + link->setRotation(linkPosition.rotation()); + } + if(numLinks < numAllLinks){ + needFk = true; } int numAllJoints = body->numAllJoints(); @@ -159,6 +163,14 @@ void BodyMotionEngineCore::updateBodyVelocity(Body* body, const BodyState& prevS } +void BodyMotionEngineCore::calcForwardKinematics(Body* mainBody, bool doUpdateVelocities) +{ + for(auto& body : mainBody->multiplexBodies()){ + body->calcForwardKinematics(doUpdateVelocities); + } +} + + static TimeSyncItemEngine* createBodyMotionEngine(BodyMotionItem* motionItem, BodyMotionEngine* engine0) { if(auto bodyItem = motionItem->findOwnerItem()){ @@ -263,7 +275,7 @@ bool BodyMotionEngine::onTimeChanged(double time) } if(needFk){ - body->calcForwardKinematics(doUpdateVelocities); + core.calcForwardKinematics(body, doUpdateVelocities); } if(body->numMultiplexBodies() != prevNumMultiplexBodies){ diff --git a/src/BodyPlugin/BodyMotionEngine.h b/src/BodyPlugin/BodyMotionEngine.h index e90d62491..7139ba4f0 100644 --- a/src/BodyPlugin/BodyMotionEngine.h +++ b/src/BodyPlugin/BodyMotionEngine.h @@ -33,8 +33,9 @@ class BodyMotionEngineCore std::vector deviceInfos; bool updateBodyState_(double time, Body* body, const BodyState& state); - bool updateSingleBodyState(double time, Body* body, BodyStateBlock stateBlock, bool isMainBody); + bool updateSingleBodyState(double time, Body* body, BodyStateBlock stateBlock); void updateBodyVelocity(Body* body, const BodyState& prevState, double timeStep); + void calcForwardKinematics(Body* mainBody, bool doUpdateVelocities); friend class BodyMotionEngine; }; diff --git a/src/BodyPlugin/BodyMotionItem.cpp b/src/BodyPlugin/BodyMotionItem.cpp index ab25738d7..472953b1a 100644 --- a/src/BodyPlugin/BodyMotionItem.cpp +++ b/src/BodyPlugin/BodyMotionItem.cpp @@ -1,6 +1,5 @@ #include "BodyMotionItem.h" #include "BodyItem.h" -#include #include #include #include @@ -40,14 +39,6 @@ typedef ref_ptr ExtraSeqItemInfoPtr; typedef std::map ExtraSeqItemInfoMap; -class BodyMotionItemCreationPanel : public MultiSeqItemCreationPanel -{ -public: - BodyMotionItemCreationPanel(); - virtual void doExtraInitialization(AbstractSeqItem* protoItem, Item* parentItem) override; - virtual void doExtraItemUpdate(AbstractSeqItem* protoItem, Item* parentItem) override; -}; - } namespace cnoid { @@ -70,52 +61,6 @@ class BodyMotionItem::Impl } -BodyMotionItemCreationPanel::BodyMotionItemCreationPanel() - : MultiSeqItemCreationPanel(_("Number of joints")) -{ - -} - - -void BodyMotionItemCreationPanel::doExtraInitialization(AbstractSeqItem* protoItem, Item* parentItem) -{ - BodyItemPtr bodyItem = dynamic_cast(parentItem); - if(!bodyItem){ - bodyItem = parentItem->findOwnerItem(); - } - if(bodyItem){ - auto motionItem = static_cast(protoItem); - auto jointPosSeq = motionItem->motion()->jointPosSeq(); - int numJoints = bodyItem->body()->numJoints(); - if(numJoints != jointPosSeq->numParts()){ - jointPosSeq->setNumParts(numJoints, true); - } - } -} - - -void BodyMotionItemCreationPanel::doExtraItemUpdate(AbstractSeqItem* protoItem, Item* parentItem) -{ - BodyItemPtr bodyItem = dynamic_cast(parentItem); - if(!bodyItem){ - bodyItem = parentItem->findOwnerItem(); - } - if(bodyItem){ - auto motionItem = static_cast(protoItem); - auto body = bodyItem->body(); - auto qseq = motionItem->motion()->jointPosSeq(); - int n = std::min(body->numJoints(), qseq->numParts()); - for(int i=0; i < n; ++i){ - auto joint = body->joint(i); - if(joint->q_initial() != 0.0){ - auto part = qseq->part(i); - std::fill(part.begin(), part.end(), joint->q_initial()); - } - } - } -} - - void BodyMotionItem::initializeClass(ExtensionManager* ext) { static bool initialized = false; @@ -128,8 +73,6 @@ void BodyMotionItem::initializeClass(ExtensionManager* ext) im.registerClass(N_("BodyMotionItem")); - im.addCreationPanel(new BodyMotionItemCreationPanel); - im.addLoaderAndSaver( _("Body Motion"), "BODY-MOTION-YAML", "seq;yaml", [](BodyMotionItem* item, const std::string& filename, std::ostream& os, Item* /* parentItem */){ @@ -184,10 +127,17 @@ void BodyMotionItem::initializeClass(ExtensionManager* ext) ItemTreeView::customizeContextMenu( [](BodyMotionItem* item, MenuManager& menuManager, ItemFunctionDispatcher menuFunction){ menuManager.setPath("/").setPath(_("Data conversion")); - menuManager.addItem(_("Generate old-format position data items"))->sigTriggered().connect( - [item](){ item->motion()->updateLinkPosSeqAndJointPosSeqWithBodyStateSeq(); }); - menuManager.addItem(_("Restore position data from old-format data items"))->sigTriggered().connect( - [item](){ item->motion()->updateBodyStateSeqWithLinkPosSeqAndJointPosSeq(); }); + auto generate = menuManager.addItem(_("Generate old-format position data items")); + auto restore = menuManager.addItem(_("Restore position data from old-format data items")); + if(item->isContinuousUpdateStateSubTree()){ + generate->setEnabled(false); + restore->setEnabled(false); + } else { + generate->sigTriggered().connect( + [item]{ item->motion()->updateLinkPosSeqAndJointPosSeqWithBodyStateSeq(); }); + restore->sigTriggered().connect( + [item](){ item->motion()->updateBodyStateSeqWithLinkPosSeqAndJointPosSeq(); }); + } menuManager.setPath("/"); menuManager.addSeparator(); menuFunction.dispatchAs(item); diff --git a/src/BodyPlugin/BodySuperimposerAddon.cpp b/src/BodyPlugin/BodySuperimposerAddon.cpp index a1aed84a4..ba8e32a61 100644 --- a/src/BodyPlugin/BodySuperimposerAddon.cpp +++ b/src/BodyPlugin/BodySuperimposerAddon.cpp @@ -39,7 +39,6 @@ class BodySuperimposerAddon::Impl BodyItem* bodyItem; ScopedConnectionSet bodyItemConnections; vector bodyInfos; - typedef vector> PositionArray; bool needToCheckSuperimposedBodies; SgGroupPtr topGroup; float transparency; diff --git a/src/BodyPlugin/BodySyncCameraConfigDialog.cpp b/src/BodyPlugin/BodySyncCameraConfigDialog.cpp index b39790872..8e589aea5 100644 --- a/src/BodyPlugin/BodySyncCameraConfigDialog.cpp +++ b/src/BodyPlugin/BodySyncCameraConfigDialog.cpp @@ -30,40 +30,15 @@ class BodySyncCameraConfigDialog::Impl public: BodySyncCameraConfigDialog* self; BodySyncCameraItemPtr cameraItem; - LineEdit nameEdit; ButtonGroup coordinateRadioGroup; RadioButton globalRadio; RadioButton localRadio; - LengthSpinBox positionSpins[3]; - DoubleSpinBox directionSpins[3]; - DoubleSpinBox upSpins[3]; - DoubleSpinBox nearClipSpin; - DoubleSpinBox farClipSpin; - DoubleSpinBox fovSpin; CheckBox parallelTrackingCheck; - CheckBox interactiveViewpointChangeCheck; ConnectionSet widgetConnections; - CheckBox activationInSceneViewCheck; - Connection activationCheckConnection; - - ScopedConnection cameraConnection; - Impl(BodySyncCameraConfigDialog* self); - void showToConfigureCameraItem(BodySyncCameraItem* cameraItem); - void setCameraItem(BodySyncCameraItem* cameraItem); - void updateWidgetsWithCurrentCameraStates(); - void setVectorElementSpins(const Vector3& v, LengthSpinBox spins[]); - void setVectorElementSpins(const Vector3& v, DoubleSpinBox spins[]); - void onNameEditingFinished(const std::string& name); - void alignWithBuiltinCamera(); - void alignWithTargetOrigin(); - void onCameraPositionSpinValueChanged(); - void onFieldOfViewSpinValueChanged(double fov); - void onClipDistanceSpinValueChanged(); + void alignWithTargetOrigin(); void onParallelTrackingModeToggled(bool on); - void onInteractiveViewpointChangeToggled(bool on); - void onSceneViewActivationChecked(bool on); }; } @@ -80,42 +55,24 @@ BodySyncCameraConfigDialog* BodySyncCameraConfigDialog::instance() } + + + BodySyncCameraConfigDialog::BodySyncCameraConfigDialog() { impl = new Impl(this); } -BodySyncCameraConfigDialog::Impl::Impl(BodySyncCameraConfigDialog* self) - : self(self) +BodySyncCameraConfigDialog::Impl::Impl(BodySyncCameraConfigDialog* self_) + : self(self_) { - auto vbox = new QVBoxLayout; - self->setLayout(vbox); - - auto hbox = new QHBoxLayout; - hbox->addWidget(new QLabel(_("Name:"))); - widgetConnections.add( - nameEdit.sigEditingFinished().connect( - [&](){ onNameEditingFinished(nameEdit.text().toStdString()); })); - hbox->addWidget(&nameEdit); - vbox->addLayout(hbox); - - hbox = new QHBoxLayout; - auto alignVBox = new QVBoxLayout; - auto alignButton1 = new PushButton(_("Align with the builtin camera")); - alignButton1->sigClicked().connect( - [&](){ alignWithBuiltinCamera(); }); - alignVBox->addWidget(alignButton1); - auto alignButton2 = new PushButton(_("Align with the target origin")); - alignButton2->sigClicked().connect( + auto alignButton = new PushButton(_("Align with the target origin")); + alignButton->sigClicked().connect( [&](){ alignWithTargetOrigin(); }); - alignVBox->addWidget(alignButton2); - hbox->addStretch(); - hbox->addLayout(alignVBox); - hbox->addStretch(); - vbox->addLayout(hbox); + self->alignVBox()->addWidget(alignButton); - hbox = new QHBoxLayout; + auto hbox = new QHBoxLayout; hbox->addWidget(new QLabel(_("Coordinate System:"))); globalRadio.setText(_("Global")); hbox->addWidget(&globalRadio); @@ -123,108 +80,20 @@ BodySyncCameraConfigDialog::Impl::Impl(BodySyncCameraConfigDialog* self) localRadio.setChecked(true); hbox->addWidget(&localRadio); hbox->addStretch(); - vbox->addLayout(hbox); + self->optionVBox1()->addLayout(hbox); coordinateRadioGroup.addButton(&globalRadio, 0); coordinateRadioGroup.addButton(&localRadio, 1); coordinateRadioGroup.sigButtonToggled().connect( [this](int id, bool checked){ - if(checked) updateWidgetsWithCurrentCameraStates(); + if(checked) self->updateWidgetsWithCurrentCameraStates(); }); - auto grid = new QGridLayout; - grid->addWidget(new QLabel(_("Position:")), 0, 0); - grid->addWidget(new QLabel(_("Direction:")), 1, 0); - grid->addWidget(new QLabel(_("Up:")), 2, 0); - - DoubleSpinBox* spins[3][3]; - for(int i=0; i < 3; ++i){ - auto spin = &positionSpins[i]; - spin->setMeterRange(-9.999, 9.999); - spin->setMeterSingleStep(0.001); - spins[0][i] = spin; - spins[1][i] = &directionSpins[i]; - spins[2][i] = &upSpins[i]; - } - for(int i=1; i < 3; ++i){ - for(int j=0; j < 3; ++j){ - auto spin = spins[i][j]; - spin->setDecimals(3); - spin->setRange(-9.999, 9.999); - spin->setSingleStep(0.001); - } - } - const char* xyzLabels[] = { "X", "Y", "Z" }; - for(int i=0; i < 3; ++i){ - for(int j=0; j < 3; ++j){ - grid->addWidget(new QLabel(xyzLabels[j]), i, j * 2 + 1); - auto spin = spins[i][j]; - widgetConnections.add( - spin->sigValueChanged().connect( - [this](double){ onCameraPositionSpinValueChanged(); })); - grid->addWidget(spin, i, j * 2 + 2); - } - } - vbox->addLayout(grid); - - hbox = new QHBoxLayout; - hbox->addWidget(new QLabel(_("Field of View:"))); - fovSpin.setDecimals(0); - fovSpin.setRange(1.0, 179.0); - widgetConnections.add( - fovSpin.sigValueChanged().connect( - [this](double value){ onFieldOfViewSpinValueChanged(value); })); - hbox->addWidget(&fovSpin); - hbox->addWidget(new QLabel(_("[deg]"))); - hbox->addStretch(); - vbox->addLayout(hbox); - - hbox = new QHBoxLayout; - hbox->addWidget(new QLabel(_("Near Clip:"))); - nearClipSpin.setDecimals(3); - nearClipSpin.setRange(0.001, 9.999); - nearClipSpin.setSingleStep(0.001); - widgetConnections.add( - nearClipSpin.sigValueChanged().connect( - [this](double){ onClipDistanceSpinValueChanged(); })); - hbox->addWidget(&nearClipSpin); - - hbox->addWidget(new QLabel(_("Far Clip:"))); - farClipSpin.setDecimals(1); - farClipSpin.setRange(0.1, 999.9); - farClipSpin.setSingleStep(1.0); - widgetConnections.add( - farClipSpin.sigValueChanged().connect( - [this](double){ onClipDistanceSpinValueChanged(); })); - hbox->addWidget(&farClipSpin); - hbox->addStretch(); - vbox->addLayout(hbox); - parallelTrackingCheck.setText(_("Parallel tracking")); widgetConnections.add( parallelTrackingCheck.sigToggled().connect( [this](bool on){ onParallelTrackingModeToggled(on); })); - vbox->addWidget(¶llelTrackingCheck); - - interactiveViewpointChangeCheck.setText(_("Interactive viewpoint change")); - widgetConnections.add( - interactiveViewpointChangeCheck.sigToggled().connect( - [this](bool on){ onInteractiveViewpointChangeToggled(on); })); - vbox->addWidget(&interactiveViewpointChangeCheck); - - activationInSceneViewCheck.setText(_("Activate in the scene view")); - activationCheckConnection = - activationInSceneViewCheck.sigToggled().connect( - [this](bool on){ onSceneViewActivationChecked(on); }); - vbox->addWidget(&activationInSceneViewCheck); - - auto buttonBox = new QDialogButtonBox(self); - auto okButton = new PushButton(_("&OK")); - buttonBox->addButton(okButton, QDialogButtonBox::AcceptRole); - connect(buttonBox, &QDialogButtonBox::accepted, [this](){ this->self->accept(); }); - vbox->addWidget(buttonBox); - - self->setWindowPositionKeepingMode(true); + self->optionVBox2()->addWidget(¶llelTrackingCheck); } @@ -255,118 +124,57 @@ BodySyncCameraItem* BodySyncCameraConfigDialog::showToCreateCameraItem(BodyItem* bodyItem->addChildItem(cameraItem); - impl->showToConfigureCameraItem(cameraItem); + showToConfigureCameraItem(cameraItem); return cameraItem; } -void BodySyncCameraConfigDialog::showToConfigureCameraItem(BodySyncCameraItem* cameraItem) -{ - setWindowTitle(_("Camera Configuration")); - impl->showToConfigureCameraItem(cameraItem); -} - - -void BodySyncCameraConfigDialog::Impl::showToConfigureCameraItem(BodySyncCameraItem* cameraItem) -{ - setCameraItem(cameraItem); - self->show(); -} - - -void BodySyncCameraConfigDialog::Impl::setCameraItem(BodySyncCameraItem* cameraItem) +void BodySyncCameraConfigDialog::showToConfigureCameraItem(CameraItem* cameraItem) { - cameraConnection.disconnect(); - this->cameraItem = cameraItem; - - activationCheckConnection.block(); - - if(!cameraItem){ - activationInSceneViewCheck.setChecked(false); - - } else { - auto currentCamera = SceneView::instance()->sceneWidget()->renderer()->currentCamera(); - activationInSceneViewCheck.setChecked(cameraItem->perspectiveCamera() == currentCamera); - - cameraConnection = - cameraItem->cameraTransform()->sigUpdated().connect( - [this](const SgUpdate&){ updateWidgetsWithCurrentCameraStates(); }); - - updateWidgetsWithCurrentCameraStates(); + impl->cameraItem = dynamic_cast(cameraItem); + if(impl->cameraItem){ + CameraConfigDialog::showToConfigureCameraItem(cameraItem); } - - activationCheckConnection.unblock(); } -void BodySyncCameraConfigDialog::Impl::updateWidgetsWithCurrentCameraStates() +void BodySyncCameraConfigDialog::updateWidgetsWithCurrentCameraStates() { - widgetConnections.block(); - - nameEdit.setText(cameraItem->name().c_str()); - - Isometry3 T; - if(globalRadio.isChecked()){ - T = cameraItem->cameraTransform()->T(); - } else { - T = cameraItem->relativeCameraPosition(); - } - setVectorElementSpins(T.translation(), positionSpins); - setVectorElementSpins(Vector3(SgCamera::direction(T)), directionSpins); - setVectorElementSpins(Vector3(SgCamera::up(T)), upSpins); + impl->cameraItem->updateRelativeCameraPosition(); - auto camera = cameraItem->perspectiveCamera(); - nearClipSpin.setValue(camera->nearClipDistance()); - farClipSpin.setValue(camera->farClipDistance()); - fovSpin.setValue(degree(camera->fieldOfView())); - - parallelTrackingCheck.setChecked(cameraItem->isParallelTrackingMode()); - - interactiveViewpointChangeCheck.setChecked( - cameraItem->isInteractiveViewpointChangeEnabled()); + impl->widgetConnections.block(); + impl->parallelTrackingCheck.setChecked(impl->cameraItem->isParallelTrackingMode()); + impl->widgetConnections.unblock(); - widgetConnections.unblock(); + CameraConfigDialog::updateWidgetsWithCurrentCameraStates(); + } -void BodySyncCameraConfigDialog::Impl::setVectorElementSpins(const Vector3& v, LengthSpinBox spins[]) +Isometry3 BodySyncCameraConfigDialog::getCurrentCameraPositionToDisplay() { - for(int i=0; i < 3; ++i){ - spins[i].setMeterValue(v[i]); + Isometry3 T; + if(impl->globalRadio.isChecked()){ + T = impl->cameraItem->cameraTransform()->T(); + } else { + T = impl->cameraItem->relativeCameraPosition(); } + return T; } -void BodySyncCameraConfigDialog::Impl::setVectorElementSpins(const Vector3& v, DoubleSpinBox spins[]) +void BodySyncCameraConfigDialog::setCameraPositionToDisplayToCameraTransform(const Isometry3& T) { - for(int i=0; i < 3; ++i){ - spins[i].setValue(v[i]); + Isometry3 Tg; + if(impl->localRadio.isChecked()){ + Tg = impl->cameraItem->targetLinkPosition() * T; + } else { + Tg = T; } -} - - -void BodySyncCameraConfigDialog::Impl::onNameEditingFinished(const std::string& name) -{ - cameraItem->setName(name); -} - - -void BodySyncCameraConfigDialog::Impl::alignWithBuiltinCamera() -{ - auto sceneWidget = SceneView::instance()->sceneWidget(); - - auto builtin = sceneWidget->builtinPerspectiveCamera(); - auto camera = cameraItem->perspectiveCamera(); - camera->setNearClipDistance(builtin->nearClipDistance()); - camera->setFarClipDistance(builtin->farClipDistance()); - camera->setFieldOfView(builtin->fieldOfView()); - - auto transform = cameraItem->cameraTransform(); - transform->setPosition(sceneWidget->builtinCameraTransform()->position()); - - camera->notifyUpdate(); - globalRadio.setChecked(true); + auto transform = impl->cameraItem->cameraTransform(); + transform->setPosition(Tg); + transform->notifyUpdate(); } @@ -375,93 +183,11 @@ void BodySyncCameraConfigDialog::Impl::alignWithTargetOrigin() auto transform = cameraItem->cameraTransform(); transform->setPosition(cameraItem->targetLinkPosition()); transform->notifyUpdate(); - localRadio.setChecked(true); } - - -void BodySyncCameraConfigDialog::Impl::onCameraPositionSpinValueChanged() -{ - cameraConnection.block(); - Vector3 eye(positionSpins[0].meterValue(), positionSpins[1].meterValue(), positionSpins[2].meterValue()); - Vector3 dir(directionSpins[0].value(), directionSpins[1].value(), directionSpins[2].value()); - Vector3 up(upSpins[0].value(), upSpins[1].value(), upSpins[2].value()); - - if(dir.norm() > 0.0 && up.norm() > 0.0){ - Isometry3 T = SgCamera::positionLookingFor(eye, dir, up); - if(localRadio.isChecked()){ - T = cameraItem->targetLinkPosition() * T; - } - auto transform = cameraItem->cameraTransform(); - transform->setPosition(T); - transform->notifyUpdate(); - } - - cameraConnection.unblock(); - - cameraItem->notifyUpdate(); -} - - -void BodySyncCameraConfigDialog::Impl::onFieldOfViewSpinValueChanged(double fov) -{ - auto camera = cameraItem->perspectiveCamera(); - camera->setFieldOfView(radian(fov)); - camera->notifyUpdate(); - cameraItem->notifyUpdate(); -} - - -void BodySyncCameraConfigDialog::Impl::onClipDistanceSpinValueChanged() -{ - auto camera = cameraItem->perspectiveCamera(); - camera->setNearClipDistance(nearClipSpin.value()); - camera->setFarClipDistance(farClipSpin.value()); - camera->notifyUpdate(); - cameraItem->notifyUpdate(); -} - void BodySyncCameraConfigDialog::Impl::onParallelTrackingModeToggled(bool on) { cameraItem->setParallelTrackingMode(on); cameraItem->notifyUpdate(); } - - -void BodySyncCameraConfigDialog::Impl::onInteractiveViewpointChangeToggled(bool on) -{ - cameraItem->setInteractiveViewpointChangeEnabled(on); - cameraItem->notifyUpdate(); -} - - -void BodySyncCameraConfigDialog::Impl::onSceneViewActivationChecked(bool on) -{ - auto renderer = SceneView::instance()->sceneWidget()->renderer(); - - if(on && !cameraItem->isChecked()){ - cameraItem->setChecked(true); - renderer->extractPreprocessedNodes(); - } - - auto camera = cameraItem->perspectiveCamera(); - auto currentCamera = renderer->currentCamera(); - - if(on){ - if(camera != currentCamera){ - renderer->setCurrentCamera(camera); - } - } else { - if(camera == currentCamera){ - renderer->setCurrentCamera(0); - } - } -} - - -void BodySyncCameraConfigDialog::hideEvent(QHideEvent* event) -{ - impl->setCameraItem(nullptr); - Dialog::hideEvent(event); -} diff --git a/src/BodyPlugin/BodySyncCameraConfigDialog.h b/src/BodyPlugin/BodySyncCameraConfigDialog.h index 70516559f..ae9bf467e 100644 --- a/src/BodyPlugin/BodySyncCameraConfigDialog.h +++ b/src/BodyPlugin/BodySyncCameraConfigDialog.h @@ -1,7 +1,7 @@ #ifndef CNOID_BODY_PLUGIN_BODY_SYNC_CAMERA_CONFIG_DIALOG_H #define CNOID_BODY_PLUGIN_BODY_SYNC_CAMERA_CONFIG_DIALOG_H -#include +#include namespace cnoid { @@ -9,19 +9,21 @@ class BodyItem; class BodySyncCameraItem; class Link; -class BodySyncCameraConfigDialog : public Dialog +class BodySyncCameraConfigDialog : public CameraConfigDialog { public: BodySyncCameraConfigDialog(); - ~BodySyncCameraConfigDialog(); + virtual ~BodySyncCameraConfigDialog(); static BodySyncCameraConfigDialog* instance(); BodySyncCameraItem* showToCreateCameraItem(BodyItem* bodyItem, Link* link); - void showToConfigureCameraItem(BodySyncCameraItem* cameraItem); + virtual void showToConfigureCameraItem(CameraItem* cameraItem) override; protected: - virtual void hideEvent(QHideEvent* event) override; + virtual void updateWidgetsWithCurrentCameraStates() override; + virtual Isometry3 getCurrentCameraPositionToDisplay() override; + virtual void setCameraPositionToDisplayToCameraTransform(const Isometry3& T) override; private: class Impl; diff --git a/src/BodyPlugin/BodySyncCameraItem.cpp b/src/BodyPlugin/BodySyncCameraItem.cpp index 2fa09a2e8..9a8315615 100644 --- a/src/BodyPlugin/BodySyncCameraItem.cpp +++ b/src/BodyPlugin/BodySyncCameraItem.cpp @@ -1,22 +1,12 @@ -/** - \file - \author Shin'ichiro Nakaoka -*/ - #include "BodySyncCameraItem.h" #include "BodySyncCameraConfigDialog.h" #include #include #include #include -#include -#include #include -#include -#include #include #include -#include #include #include #include "gettext.h" @@ -156,13 +146,8 @@ class BodySyncCameraItem::Impl public: BodySyncCameraItem* self; BodySyncCameraTransformPtr cameraTransform; - SgPerspectiveCameraPtr persCamera; - SgOrthographicCameraPtr orthoCamera; - SgCameraPtr currentCamera; - SgUpdate update; - Selection cameraType; - Impl(BodySyncCameraItem* self, bool initCameraPosition); + Impl(BodySyncCameraItem* self); Impl(BodySyncCameraItem* self, const Impl& org); void doPutProperties(PutPropertyFunction& putProperty); bool setClipDistances(double nearDistance, double farDistance); @@ -176,17 +161,9 @@ class BodySyncCameraItem::Impl void BodySyncCameraItem::initializeClass(ExtensionManager* ext) { ext->itemManager() - .registerClass(N_("BodySyncCameraItem")) + .registerClass(N_("BodySyncCameraItem")) .addAlias("BodyTrackingCameraItem", "Body") .addCreationPanel(); - - ItemTreeView::customizeContextMenu( - [](BodySyncCameraItem* item, MenuManager& menuManager, ItemFunctionDispatcher menuFunction){ - menuManager.addItem(_("Camera configuration"))->sigTriggered().connect( - [item](){ item->showDialogToConfigureCamera(); }); - menuManager.addSeparator(); - menuFunction.dispatchAs(item); - }); } @@ -203,53 +180,29 @@ void BodySyncCameraItem::showDialogToConfigureCamera() BodySyncCameraItem::BodySyncCameraItem() - : Item("BodySyncCamera") + : CameraItem("BodySyncCamera", new BodySyncCameraTransform) { - impl = new Impl(this, true); + impl = new Impl(this); } BodySyncCameraItem::BodySyncCameraItem(const BodySyncCameraItem& org) - : Item(org) + : CameraItem(org, new BodySyncCameraTransform) { impl = new Impl(this, *org.impl); } -BodySyncCameraItem::Impl::Impl(BodySyncCameraItem* self, bool initCameraPosition) - : self(self), - cameraType(NumCameraTypes, CNOID_GETTEXT_DOMAIN_NAME) +BodySyncCameraItem::Impl::Impl(BodySyncCameraItem* self_) + : self(self_) { - cameraType.setSymbol(Perspective, N_("Perspective")); - cameraType.setSymbol(Orthographic, N_("Orthographic")); - cameraType.select(Perspective); - - cameraTransform = new BodySyncCameraTransform; - if(initCameraPosition){ - cameraTransform->setPosition( - SceneView::instance()->sceneWidget()->builtinCameraTransform()->position()); - } else { - cameraTransform->setInteractiveViewpointChangeLocked(true); - } - - persCamera = new SgPerspectiveCamera; - persCamera->setName(self->name()); - cameraTransform->addChild(persCamera); - currentCamera = persCamera; - - orthoCamera = new SgOrthographicCamera; - orthoCamera->setName(self->name()); + cameraTransform = static_cast(self->cameraTransform()); } BodySyncCameraItem::Impl::Impl(BodySyncCameraItem* self, const Impl& org) - : Impl(self, false) + : Impl(self) { - cameraType = org.cameraType; - - cameraTransform->setPosition(org.cameraTransform->position()); - cameraTransform->setInteractiveViewpointChangeLocked( - org.cameraTransform->isInteractiveViewpointChangeLocked()); cameraTransform->setParallelTrackingMode(org.cameraTransform->isParallelTrackingMode()); cameraTransform->targetLinkName = org.cameraTransform->targetLinkName; } @@ -261,19 +214,6 @@ BodySyncCameraItem::~BodySyncCameraItem() } -bool BodySyncCameraItem::setName(const std::string& name_) -{ - if(name_ != name()){ - impl->persCamera->setName(name_); - impl->orthoCamera->setName(name_); - Item::setName(name_); - impl->persCamera->notifyUpdate(impl->update); - impl->orthoCamera->notifyUpdate(impl->update); - } - return true; -} - - Item* BodySyncCameraItem::doCloneItem(CloneMap* /* cloneMap */) const { return new BodySyncCameraItem(*this); @@ -313,93 +253,9 @@ bool BodySyncCameraItem::isParallelTrackingMode() const } -void BodySyncCameraItem::setInteractiveViewpointChangeEnabled(bool on) -{ - impl->cameraTransform->setInteractiveViewpointChangeLocked(!on); -} - - -bool BodySyncCameraItem::isInteractiveViewpointChangeEnabled() const -{ - return !impl->cameraTransform->isInteractiveViewpointChangeLocked(); -} - - -void BodySyncCameraItem::setCameraType(CameraType type) +void BodySyncCameraItem::updateRelativeCameraPosition() { - impl->setCameraType(type); -} - - -/** - \todo Improve the scene widget so that the current camera path described in a string list - can be kept even if the actual camera node is changed, and simplify the following implementation. -*/ -bool BodySyncCameraItem::Impl::setCameraType(int index) -{ - if(cameraType.selectedIndex() == index){ - return true; - } - - cameraType.select(index); - - SgCamera* cameraToRemove; - if(cameraType.is(BodySyncCameraItem::Perspective)){ - currentCamera = persCamera; - cameraToRemove = orthoCamera; - }else if(cameraType.is(BodySyncCameraItem::Orthographic)){ - currentCamera = orthoCamera; - cameraToRemove = persCamera; - } - - vector renderers; - for(auto sceneView : SceneView::instances()){ - renderers.push_back(sceneView->sceneWidget()->renderer()); - } - - cameraTransform->addChild(currentCamera, update); - for(auto renderer : renderers){ - if(renderer->currentCamera() == cameraToRemove){ - renderer->extractPreprocessedNodes(); - renderer->setCurrentCamera(currentCamera); - } - } - cameraTransform->removeChild(cameraToRemove, update); - for(auto renderer : renderers){ - renderer->extractPreprocessedNodes(); - } - - return true; -} - - -BodySyncCameraItem::CameraType BodySyncCameraItem::cameraType() const -{ - return static_cast(impl->cameraType.which()); -} - - -SgPerspectiveCamera* BodySyncCameraItem::perspectiveCamera() -{ - return impl->persCamera; -} - - -SgOrthographicCamera* BodySyncCameraItem::orthographicCamera() -{ - return impl->orthoCamera; -} - - -SgCamera* BodySyncCameraItem::currentCamera() -{ - return impl->currentCamera; -} - - -SgPosTransform* BodySyncCameraItem::cameraTransform() -{ - return impl->cameraTransform; + return impl->cameraTransform->updateRelativePosition(); } @@ -409,99 +265,15 @@ Isometry3 BodySyncCameraItem::relativeCameraPosition() const } -SgNode* BodySyncCameraItem::getScene() -{ - return impl->cameraTransform; -} - - void BodySyncCameraItem::onTreePathChanged() { impl->cameraTransform->setBodyItem(findOwnerItem()); } -double BodySyncCameraItem::fieldOfView() const -{ - return impl->persCamera->fieldOfView(); -} - - -bool BodySyncCameraItem::setFieldOfView(double fov) -{ - return impl->setFieldOfView(fov); -} - - -bool BodySyncCameraItem::Impl::setFieldOfView(double fov) -{ - if(fov > 0.0 && fov < PI){ - persCamera->setFieldOfView(fov); - persCamera->notifyUpdate(update); - return true; - } - return false; -} - - -double BodySyncCameraItem::nearClipDistance() const -{ - return impl->persCamera->nearClipDistance(); -} - - -bool BodySyncCameraItem::setNearClipDistance(double nearDistance) -{ - if(nearDistance > 0.0){ - impl->setClipDistances(nearDistance, farClipDistance()); - return true; - } - return false; -} - - -double BodySyncCameraItem::farClipDistance() const -{ - return impl->persCamera->farClipDistance(); -} - - -bool BodySyncCameraItem::setFarClipDistance(double farDistance) -{ - if(farDistance > 0.0){ - impl->setClipDistances(nearClipDistance(), farDistance); - return true; - } - return false; -} - - -bool BodySyncCameraItem::setClipDistances(double nearDistance, double farDistance) -{ - if(nearDistance > 0.0 && farDistance > 0.0){ - impl->setClipDistances(nearDistance, farDistance); - return true; - } - return false; -} - - -bool BodySyncCameraItem::Impl::setClipDistances(double nearDistance, double farDistance) -{ - if(persCamera->nearClipDistance() != nearDistance || persCamera->farClipDistance() != farDistance){ - persCamera->setNearClipDistance(nearDistance); - persCamera->setFarClipDistance(farDistance); - orthoCamera->setNearClipDistance(nearDistance); - orthoCamera->setFarClipDistance(farDistance); - persCamera->notifyUpdate(update); - orthoCamera->notifyUpdate(update); - } - return true; -} - - void BodySyncCameraItem::doPutProperties(PutPropertyFunction& putProperty) { + CameraItem::doPutProperties(putProperty); impl->doPutProperties(putProperty); } @@ -510,38 +282,23 @@ void BodySyncCameraItem::Impl::doPutProperties(PutPropertyFunction& putProperty) { putProperty(_("Target link"), cameraTransform->targetLinkName, [&](const string& name){ cameraTransform->setTargetLink(name); return true; }); - putProperty(_("Camera type"), cameraType, - [&](int index){ return setCameraType(index); }); - putProperty(_("Field Of View"), degree(self->fieldOfView()), - [&](double fov){ return setFieldOfView(radian(fov)); } ); - putProperty(_("Near clip distance"), self->nearClipDistance(), - [&](double distance){ return self->setNearClipDistance(distance); }); - putProperty(_("Far clip distance"), self->farClipDistance(), - [&](double distance){ return self->setFarClipDistance(distance); } ); putProperty(_("Parallel tracking"), cameraTransform->isParallelTrackingMode(), [&](bool on){ self->setParallelTrackingMode(on); return true; }); - putProperty(_("Interactive viewpoint change"), self->isInteractiveViewpointChangeEnabled(), - [&](bool on){ self->setInteractiveViewpointChangeEnabled(on); return true; }); } bool BodySyncCameraItem::store(Archive& archive) { + if(!CameraItem::store(archive)){ + return false; + } + archive.write("target_link", impl->cameraTransform->targetLinkName, DOUBLE_QUOTED); archive.write("parallel_tracking", isParallelTrackingMode()); - archive.write("interactive_viewpoint_change", isInteractiveViewpointChangeEnabled()); - archive.write("camera_type", impl->cameraType.selectedSymbol()); - archive.write("near_clip_distance", impl->persCamera->nearClipDistance()); - archive.write("far_clip_distance", impl->persCamera->farClipDistance()); - archive.write("field_of_view", impl->persCamera->fieldOfView()); auto transform = impl->cameraTransform; - Isometry3 T = transform->T(); - write(archive, "translation", T.translation()); - writeDegreeAngleAxis(archive, "rotation", AngleAxis(T.linear())); - if(auto link = transform->targetLink){ - T = link->T().inverse() * T; + Isometry3 T = link->T().inverse() * transform->T(); write(archive, "local_translation", T.translation()); writeDegreeAngleAxis(archive, "local_rotation", AngleAxis(T.linear())); } @@ -552,6 +309,10 @@ bool BodySyncCameraItem::store(Archive& archive) bool BodySyncCameraItem::restore(const Archive& archive) { + if(!CameraItem::restore(archive)){ + return false; + } + auto transform = impl->cameraTransform; string symbol; @@ -574,48 +335,26 @@ bool BodySyncCameraItem::restore(const Archive& archive) if(doUpdate){ setParallelTrackingMode(on); } - if(archive.read("interactive_viewpoint_change", on)){ - setInteractiveViewpointChangeEnabled(on); - } - if(archive.read({ "camera_type", "cameraType" }, symbol)){ - int index = impl->cameraType.index(symbol); - impl->setCameraType(index); - } - double nearDistance = - archive.get({ "near_clip_distance", "nearClipDistance" }, impl->persCamera->nearClipDistance()); - double farDistance = - archive.get({ "far_clip_distance", "farClipDistance" }, impl->persCamera->farClipDistance()); - impl->setClipDistances(nearDistance, farDistance); - - impl->setFieldOfView(archive.get({ "field_of_view", "fieldOfView" }, impl->persCamera->fieldOfView())); - - bool isPositionReady = false; + + bool hasLocalPosition = false; Vector3 p; AngleAxis aa; Isometry3 T = Isometry3::Identity(); if(auto link = transform->targetLink){ if(read(archive, "local_translation", p)){ T.translation() = p; - isPositionReady = true; + hasLocalPosition = true; } if(readDegreeAngleAxis(archive, "local_rotation", aa)){ T.linear() = aa.toRotationMatrix(); - isPositionReady = true; + hasLocalPosition = true; } - if(isPositionReady){ + if(hasLocalPosition){ T = link->T() * T; + transform->setPosition(T); + transform->notifyUpdate(); } } - if(!isPositionReady){ - if(read(archive, "translation", p)){ - T.translation() = p; - } - if(readDegreeAngleAxis(archive, "rotation", aa)){ - T.linear() = aa.toRotationMatrix(); - } - } - transform->setPosition(T); - transform->notifyUpdate(); - + return true; } diff --git a/src/BodyPlugin/BodySyncCameraItem.h b/src/BodyPlugin/BodySyncCameraItem.h index 195e2fa7a..91d566ca6 100644 --- a/src/BodyPlugin/BodySyncCameraItem.h +++ b/src/BodyPlugin/BodySyncCameraItem.h @@ -1,83 +1,33 @@ -/*! - @file - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_BODY_PLUGIN_BODY_SYNC_CAMERA_ITEM_H #define CNOID_BODY_PLUGIN_BODY_SYNC_CAMERA_ITEM_H -#include -#include +#include #include #include "exportdecl.h" namespace cnoid { -class SgCamera; -class SgPerspectiveCamera; -class SgOrthographicCamera; -class SgPosTransform; class BodyItem; class Link; -class CNOID_EXPORT BodySyncCameraItem : public Item, public RenderableItem +class CNOID_EXPORT BodySyncCameraItem : public CameraItem { public: static void initializeClass(ExtensionManager* ext); static BodySyncCameraItem* showDialogToCreateBodySyncCameraItem(BodyItem* bodyItem, Link* link); - enum CameraType { - Perspective, - Orthographic, - NumCameraTypes, - // deprecated - PERSPECTIVE = Perspective, - ORTHOGRAPHIC = Orthographic, - N_CAMERA_TYPE - }; - BodySyncCameraItem(); - ~BodySyncCameraItem(); - - virtual bool setName(const std::string& name) override; + virtual ~BodySyncCameraItem(); void setTargetLink(const std::string& name); const std::string& targetLinkName() const; Isometry3 targetLinkPosition() const; void setParallelTrackingMode(bool on); bool isParallelTrackingMode() const; - void setInteractiveViewpointChangeEnabled(bool on); - bool isInteractiveViewpointChangeEnabled() const; - void setCameraType(CameraType type); - CameraType cameraType() const; - SgPerspectiveCamera* perspectiveCamera(); - SgOrthographicCamera* orthographicCamera(); - SgCamera* currentCamera(); - SgPosTransform* cameraTransform(); + void updateRelativeCameraPosition(); Isometry3 relativeCameraPosition() const; - - double fieldOfView() const; - bool setFieldOfView(double fov); - - double nearClipDistance() const; - bool setNearClipDistance(double distance); - double farClipDistance() const; - bool setFarClipDistance(double distance); - bool setClipDistances(double nearDistance, double farDistance); - - void showDialogToConfigureCamera(); - - // RenderableItem - virtual SgNode* getScene() override; - - [[deprecated("Use setParallelTrackingMode.")]] - void setRotationSyncEnabled(bool on) { - setParallelTrackingMode(!on); - } - [[deprecated("Use isParallelTrackingMode.")]] - bool isRotationSyncEnabled() const { - return !isParallelTrackingMode(); - } + + virtual void showDialogToConfigureCamera() override; protected: BodySyncCameraItem(const BodySyncCameraItem& org); @@ -94,12 +44,6 @@ class CNOID_EXPORT BodySyncCameraItem : public Item, public RenderableItem typedef ref_ptr BodySyncCameraItemPtr; -// for the backward compatibility -[[deprecated]] -typedef BodySyncCameraItem BodyTrackingCameraItem; -[[deprecated]] -typedef ref_ptr BodyTrackingCameraItemPtr; - } #endif diff --git a/src/BodyPlugin/DeviceOverwriteItem.cpp b/src/BodyPlugin/DeviceOverwriteItem.cpp index 05be57beb..02767f095 100644 --- a/src/BodyPlugin/DeviceOverwriteItem.cpp +++ b/src/BodyPlugin/DeviceOverwriteItem.cpp @@ -40,8 +40,7 @@ class DeviceLocation : public LocationProxy DeviceLocation(DeviceOverwriteItem::Impl* impl); virtual Isometry3 getLocation() const override; virtual bool setLocation(const Isometry3& T) override; - virtual Item* getCorrespondingItem() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; virtual SignalProxy sigLocationChanged() override; }; @@ -462,7 +461,7 @@ LocationProxyPtr DeviceOverwriteItem::getLocationProxy() DeviceLocation::DeviceLocation(DeviceOverwriteItem::Impl* impl) - : LocationProxy(OffsetLocation), + : LocationProxy(impl->self, OffsetLocation), impl(impl) { setLocked(true); @@ -489,13 +488,7 @@ bool DeviceLocation::setLocation(const Isometry3& T) } -Item* DeviceLocation::getCorrespondingItem() -{ - return impl->self; -} - - -LocationProxyPtr DeviceLocation::getParentLocationProxy() const +LocationProxyPtr DeviceLocation::getParentLocationProxy() { if(!impl->linkLocation){ if(impl->device){ diff --git a/src/BodyPlugin/GLVisionSimulatorItem.cpp b/src/BodyPlugin/GLVisionSimulatorItem.cpp index ba6c0728e..739353764 100644 --- a/src/BodyPlugin/GLVisionSimulatorItem.cpp +++ b/src/BodyPlugin/GLVisionSimulatorItem.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -263,9 +264,11 @@ class GLVisionSimulatorItem::Impl vector sensorNames; string sensorNameListString; Selection threadMode; + Vector3f backgroundColor; bool isBestEffortModeProperty; bool shootAllSceneObjects; bool isHeadLightEnabled; + bool isWorldLightEnabled; bool areAdditionalLightsEnabled; double maxFrameRate; double maxLatency; @@ -317,16 +320,20 @@ GLVisionSimulatorItem::Impl::Impl(GLVisionSimulatorItem* self) threadMode(GLVisionSimulatorItem::N_THREAD_MODES, CNOID_GETTEXT_DOMAIN_NAME) { simulatorItem = nullptr; - maxFrameRate = 1000.0; - maxLatency = 1.0; + + isVisionDataRecordingEnabled = false; + rangeSensorPrecisionRatio = 2.0; depthError = 0.0; - isVisionDataRecordingEnabled = false; + backgroundColor << 0.0f, 0.0f, 0.0f; isBestEffortModeProperty = true; + shootAllSceneObjects = true; isHeadLightEnabled = true; + isWorldLightEnabled = true; areAdditionalLightsEnabled = true; - shootAllSceneObjects = true; + maxFrameRate = 1000.0; + maxLatency = 1.0; threadMode.setSymbol(GLVisionSimulatorItem::SINGLE_THREAD_MODE, N_("Single")); threadMode.setSymbol(GLVisionSimulatorItem::SENSOR_THREAD_MODE, N_("Sensor")); @@ -353,14 +360,18 @@ GLVisionSimulatorItem::Impl::Impl(GLVisionSimulatorItem* self, const Impl& org) simulatorItem = nullptr; isVisionDataRecordingEnabled = org.isVisionDataRecordingEnabled; + rangeSensorPrecisionRatio = org.rangeSensorPrecisionRatio; depthError = org.depthError; + bodyNameListString = getNameListString(bodyNames); sensorNameListString = getNameListString(sensorNames); threadMode = org.threadMode; + backgroundColor = org.backgroundColor; isBestEffortModeProperty = org.isBestEffortModeProperty; shootAllSceneObjects = org.shootAllSceneObjects; isHeadLightEnabled = org.isHeadLightEnabled; + isWorldLightEnabled = org.isWorldLightEnabled; areAdditionalLightsEnabled = org.areAdditionalLightsEnabled; maxFrameRate = org.maxFrameRate; maxLatency = org.maxLatency; @@ -445,6 +456,12 @@ void GLVisionSimulatorItem::setRangeSensorPrecisionRatio(double r) } +void GLVisionSimulatorItem::setBackgroundColor(const Vector3f& c) +{ + impl->backgroundColor = c; +} + + void GLVisionSimulatorItem::setAllSceneObjectsEnabled(bool on) { impl->setProperty(impl->shootAllSceneObjects, on); @@ -457,6 +474,12 @@ void GLVisionSimulatorItem::setHeadLightEnabled(bool on) } +void GLVisionSimulatorItem::setWorldLightEnabled(bool on) +{ + impl->setProperty(impl->isWorldLightEnabled, on); +} + + void GLVisionSimulatorItem::setAdditionalLightsEnabled(bool on) { impl->setProperty(impl->areAdditionalLightsEnabled, on); @@ -1002,11 +1025,13 @@ bool SensorScreenRenderer::initializeGL(SgCamera* sceneCamera) flagToUpdatePreprocessedNodeTree = true; renderer->extractPreprocessedNodes(); renderer->setCurrentCamera(sceneCamera); + renderer->setBackgroundColor(simImpl->backgroundColor); if(rangeSensorForRendering){ renderer->setLightingMode(GLSceneRenderer::NoLighting); } else { - if(screenId != FRONT_SCREEN){ + renderer->headLight()->on(simImpl->isHeadLightEnabled); + if(simImpl->isHeadLightEnabled && screenId != FRONT_SCREEN){ SgDirectionalLight* headLight = dynamic_cast(renderer->headLight()); if(headLight){ switch(screenId){ @@ -1028,7 +1053,8 @@ bool SensorScreenRenderer::initializeGL(SgCamera* sceneCamera) } } } - renderer->headLight()->on(simImpl->isHeadLightEnabled); + + renderer->worldLight()->on(simImpl->isWorldLightEnabled); renderer->enableAdditionalLights(simImpl->areAdditionalLightsEnabled); } @@ -1898,7 +1924,19 @@ void GLVisionSimulatorItem::Impl::doPutProperties(PutPropertyFunction& putProper putProperty(_("Precision ratio of range sensors"), rangeSensorPrecisionRatio, changeProperty(rangeSensorPrecisionRatio)); putProperty.reset()(_("Depth error"), depthError, changeProperty(depthError)); + + putProperty(_("Background color"), str(backgroundColor), + [this](const string& value){ + Vector3f c; + if(toVector3(value, c)){ + backgroundColor = c; + return true; + } + return false; + }); + putProperty(_("Head light"), isHeadLightEnabled, changeProperty(isHeadLightEnabled)); + putProperty(_("World light"), isWorldLightEnabled, changeProperty(isWorldLightEnabled)); putProperty(_("Additional lights"), areAdditionalLightsEnabled, changeProperty(areAdditionalLightsEnabled)); putProperty(_("Anti-aliasing"), isAntiAliasingEnabled, changeProperty(isAntiAliasingEnabled)); } @@ -1923,7 +1961,9 @@ bool GLVisionSimulatorItem::Impl::store(Archive& archive) archive.write("all_scene_objects", shootAllSceneObjects); archive.write("range_sensor_precision_ratio", rangeSensorPrecisionRatio); archive.write("depth_error", depthError); + write(archive, "background_color", backgroundColor); archive.write("enable_head_light", isHeadLightEnabled); + archive.write("enable_world_light", isWorldLightEnabled); archive.write("enable_additional_lights", areAdditionalLightsEnabled); archive.write("antialiasing", isAntiAliasingEnabled); return true; @@ -1951,7 +1991,9 @@ bool GLVisionSimulatorItem::Impl::restore(const Archive& archive) archive.read({ "all_scene_objects", "allSceneObjects" }, shootAllSceneObjects); archive.read({ "range_sensor_precision_ratio", "rangeSensorPrecisionRatio" }, rangeSensorPrecisionRatio); archive.read({ "depth_error", "depthError" }, depthError); + read(archive, "background_color", backgroundColor); archive.read({ "enable_head_light", "enableHeadLight" }, isHeadLightEnabled); + archive.read("enable_world_light", isWorldLightEnabled); archive.read({ "enable_additional_lights", "enableAdditionalLights" }, areAdditionalLightsEnabled); archive.read({ "antialiasing", "antiAliasing" }, isAntiAliasingEnabled); diff --git a/src/BodyPlugin/GLVisionSimulatorItem.h b/src/BodyPlugin/GLVisionSimulatorItem.h index 52e781194..7bf18c2a0 100644 --- a/src/BodyPlugin/GLVisionSimulatorItem.h +++ b/src/BodyPlugin/GLVisionSimulatorItem.h @@ -2,6 +2,7 @@ #define CNOID_BODY_PLUGIN_GL_VISION_SIMULATOR_ITEM_H #include "SubSimulatorItem.h" +#include #include "exportdecl.h" namespace cnoid { @@ -25,8 +26,10 @@ class CNOID_EXPORT GLVisionSimulatorItem : public SubSimulatorItem void setThreadMode(int mode); void setBestEffortMode(bool on); void setRangeSensorPrecisionRatio(double r); + void setBackgroundColor(const Vector3f& c); void setAllSceneObjectsEnabled(bool on); void setHeadLightEnabled(bool on); + void setWorldLightEnabled(bool on); void setAdditionalLightsEnabled(bool on); virtual bool initializeSimulation(SimulatorItem* simulatorItem) override; diff --git a/src/BodyPlugin/JointDisplacementWidgetSet.cpp b/src/BodyPlugin/JointDisplacementWidgetSet.cpp index a72b70ec3..fdf60e393 100644 --- a/src/BodyPlugin/JointDisplacementWidgetSet.cpp +++ b/src/BodyPlugin/JointDisplacementWidgetSet.cpp @@ -100,7 +100,7 @@ class JointDisplacementWidgetSet::Impl : public QObject ScopedConnection linkSelectionChangeConnection; ScopedConnection kinematicStateChangeConnection; - ScopedConnection continuousKinematicUpdateStateChangeConnection; + ScopedConnection continuousUpdateStateChangeConnection; ScopedConnection modelUpdateConnection; DisplayValueFormat* dvFormat; @@ -383,7 +383,7 @@ void JointDisplacementWidgetSet::Impl::setBodyItem(BodyItem* bodyItem) linkedJointHandler.reset(); kinematicStateChangeConnection.disconnect(); - continuousKinematicUpdateStateChangeConnection.disconnect(); + continuousUpdateStateChangeConnection.disconnect(); modelUpdateConnection.disconnect(); if(bodyItem){ @@ -392,11 +392,11 @@ void JointDisplacementWidgetSet::Impl::setBodyItem(BodyItem* bodyItem) kinematicStateChangeConnection = bodyItem->sigKinematicStateChanged().connect(updateJointDisplacementsLater); - continuousKinematicUpdateStateChangeConnection = - bodyItem->sigContinuousKinematicUpdateStateChanged().connect( + continuousUpdateStateChangeConnection = + bodyItem->sigContinuousUpdateStateChanged().connect( [this](bool on){ setUserInputEnabled(!on); }); - setUserInputEnabled(!bodyItem->isDoingContinuousKinematicUpdate()); + setUserInputEnabled(!bodyItem->isContinuousUpdateState()); modelUpdateConnection = bodyItem->sigModelUpdated().connect( @@ -784,6 +784,7 @@ void JointIndicator::initialize(Link* joint) spin.setDecimals(0); spin.setRange(0.0, 0.0); spin.setEnabled(false); + dial.setValue(0); dial.setRange(0, 0); dial.setWrapping(false); dial.setNotchesVisible(false); diff --git a/src/BodyPlugin/KinematicBodyItemSet.cpp b/src/BodyPlugin/KinematicBodyItemSet.cpp index 9bf4a1aeb..761d417e6 100644 --- a/src/BodyPlugin/KinematicBodyItemSet.cpp +++ b/src/BodyPlugin/KinematicBodyItemSet.cpp @@ -27,13 +27,27 @@ Referenced* KinematicBodyItemSet::doClone(CloneMap* cloneMap) const void KinematicBodyItemSet::setBodyPart(int index, BodyKinematicsKit* kinematicsKit) { if(auto kit = dynamic_cast(kinematicsKit)){ + bodyItemConnectionMap[index] = + kit->bodyItem()->sigDisconnectedFromRoot().connect( + [this, index]{ + removeBodyPart(index); + notifyBodySetChange(); + }); KinematicBodySet::setBodyPart(index, kit); + } else { throw std::invalid_argument("Type mismatch in the KinematicBodyItemSet::setBodyPart function"); } } +void KinematicBodyItemSet::removeBodyPart(int index) +{ + bodyItemConnectionMap.erase(index); + KinematicBodySet::removeBodyPart(index); +} + + int KinematicBodyItemSet::indexOf(const BodyItem* item) const { int index = -1; diff --git a/src/BodyPlugin/KinematicBodyItemSet.h b/src/BodyPlugin/KinematicBodyItemSet.h index e0f954ebc..b6fa64dfa 100644 --- a/src/BodyPlugin/KinematicBodyItemSet.h +++ b/src/BodyPlugin/KinematicBodyItemSet.h @@ -3,6 +3,7 @@ #include "BodyItemKinematicsKit.h" #include +#include #include "exportdecl.h" namespace cnoid { @@ -15,9 +16,10 @@ class CNOID_EXPORT KinematicBodyItemSet : public KinematicBodySet KinematicBodyItemSet(); void setBodyItemPart(int index, BodyItemKinematicsKit* kinematicsKit) { - KinematicBodySet::setBodyPart(index, kinematicsKit); + setBodyPart(index, kinematicsKit); } virtual void setBodyPart(int index, BodyKinematicsKit* kinematicsKit) override; + virtual void removeBodyPart(int index) override; BodyItemKinematicsKit* bodyItemPart(int index){ return static_cast(bodyPart(index)); @@ -53,6 +55,9 @@ class CNOID_EXPORT KinematicBodyItemSet : public KinematicBodySet protected: KinematicBodyItemSet(const KinematicBodyItemSet& org, CloneMap* cloneMap); virtual Referenced* doClone(CloneMap* cloneMap) const override; + +private: + std::map bodyItemConnectionMap; }; typedef ref_ptr KinematicBodyItemSetPtr; diff --git a/src/BodyPlugin/KinematicSimulatorItem.cpp b/src/BodyPlugin/KinematicSimulatorItem.cpp index afa18d3ac..499df9398 100644 --- a/src/BodyPlugin/KinematicSimulatorItem.cpp +++ b/src/BodyPlugin/KinematicSimulatorItem.cpp @@ -274,18 +274,19 @@ bool KinematicSimulatorItem::Impl::stepSimulation(const std::vector(activeSimBodies[i]); - auto body = simBody->body(); - for(auto& link : body->links()){ - if(link->actuationMode() == Link::JointDisplacement){ - link->q() = link->q_target(); + for(auto& body : simBody->body()->multiplexBodies()){ + for(auto& link : body->links()){ + if(link->actuationMode() == Link::JointDisplacement){ + link->q() = link->q_target(); + } } - } - if(auto& linkedJointHandler = simBody->linkedJointHandler){ - if(linkedJointHandler->updateLinkedJointDisplacements()){ - linkedJointHandler->limitLinkedJointDisplacementsWithinMovableRanges(); + if(auto& linkedJointHandler = simBody->linkedJointHandler){ + if(linkedJointHandler->updateLinkedJointDisplacements()){ + linkedJointHandler->limitLinkedJointDisplacementsWithinMovableRanges(); + } } + body->calcForwardKinematics(); } - body->calcForwardKinematics(); } for(auto& info : activeHolderInfos){ @@ -341,7 +342,7 @@ void KinematicSimulatorItem::Impl::activateHolder(HolderInfo* info) auto rootLink = body->rootLink(); AttachmentDevice* attachment = nullptr; for(auto& device : body->devices()){ - if(device->link() == rootLink && device->category() == holder->category()){ + if(device->link() == rootLink && device->isAttachableTo(holder)){ attachment = device; break; } @@ -422,10 +423,11 @@ void KinematicSimulatorItem::Impl::onHolderCollisionDetected void KinematicSimulatorItem::Impl::findAttachableBodiesByDistance (HolderDevice* holder, const Isometry3& T_holder, vector& out_bodies) { + auto holderBody = holder->body(); const double maxDistance = holder->maxHoldDistance(); for(auto& simBody : self->simulationBodies()){ - if(simBody->isActive()){ - Body* body = simBody->body(); + Body* body = simBody->body(); + if(body != holderBody && simBody->isActive()){ while(body){ auto rootLink = body->rootLink(); if(rootLink->isFreeJoint()){ diff --git a/src/BodyPlugin/LeggedBodyBar.cpp b/src/BodyPlugin/LeggedBodyBar.cpp index 046c6a3c0..affbc9cba 100644 --- a/src/BodyPlugin/LeggedBodyBar.cpp +++ b/src/BodyPlugin/LeggedBodyBar.cpp @@ -1,6 +1,7 @@ #include "LeggedBodyBar.h" #include "BodySelectionManager.h" #include "BodyItem.h" +#include #include #include #include @@ -135,7 +136,10 @@ void LeggedBodyBar::Impl::onZmpButtonClicked(BodyItem::PositionType position) applyBodyItemOperation( [this, position](BodyItem* bodyItem){ if(auto p = bodyItem->getParticularPosition(position)){ - bodyItem->editZmp(*p); + auto legged = getLeggedBodyHelper(bodyItem->body()); + if(legged->isValid()){ + legged->setZmp(*p, true); + } } }); } diff --git a/src/BodyPlugin/LinkOverwriteItem.cpp b/src/BodyPlugin/LinkOverwriteItem.cpp index 090aa9f64..22997a33c 100644 --- a/src/BodyPlugin/LinkOverwriteItem.cpp +++ b/src/BodyPlugin/LinkOverwriteItem.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "gettext.h" using namespace std; @@ -42,8 +44,7 @@ class OffsetLocation : public LocationProxy virtual std::string getName() const override; virtual Isometry3 getLocation() const override; virtual bool setLocation(const Isometry3& T) override; - virtual Item* getCorrespondingItem() override; - virtual LocationProxyPtr getParentLocationProxy() const override; + virtual LocationProxyPtr getParentLocationProxy() override; virtual SignalProxy sigLocationChanged() override; }; @@ -57,7 +58,7 @@ class LinkOverwriteItem::Impl { public: LinkOverwriteItem* self; - int targetElementSet; + int overwriteElementSet; LinkPtr targetLink; LinkPtr referenceLink; LinkPtr additionalLink; @@ -65,6 +66,24 @@ class LinkOverwriteItem::Impl LinkPtr originalLinkClone; bool isRootLink; + Isometry3 shapeOffset; + SgPosTransformPtr shapeOffsetTransform; + SgPosTransformPtr collisionShapeOffsetTransform; + Vector3f shapeColor; + + struct MaterialInfo + { + SgMaterialPtr orgMaterialClone; + bool isUpdated; + }; + unordered_map materialMap; + unordered_set noMaterialShapes; + SgMaterialPtr materialForNoMaterialShapes; + + SgNodePtr visualShape; + SgNodePtr collisionShape; + SgUpdate sgUpdate; + PositionDraggerPtr originMarker; OffsetLocationPtr offsetLocation; int locationTargetType; @@ -77,11 +96,19 @@ class LinkOverwriteItem::Impl Impl(LinkOverwriteItem* self, const Impl& org, CloneMap* cloneMap); bool updateOverwriting(BodyItem* bodyItem); void overwriteExistingLink(Link* existingLink); - void copyTargetElements(Link* srcLink, Link* destLink, int elementSet); + void copyOverwriteLinkElements(Link* srcLink, Link* destLink); + void overwriteShapeElements(Link* link); + void cancelOverwritingShapeElements(Link* link); + void overwriteShapeOffset(Link* link, bool doNotify); + void cancelOverwritingShapeOffset(Link* link, bool doNotify); + void overwriteShapeColor(Link* link, bool doNotify); + void cancelOverwritingShapeColor(Link* link, bool doNotify); + void restoreOriginalShapeMaterials(Link* link, bool doNotify); + void restoreOverwritingShapeMaterials(Link* link, bool doNotify); + void notifyMaterialUpdates(int noMaterialShapeAction); bool addNewLink(Body* body); void releaseOverwriteTarget(); void cancelOverwriting(); - SgPosTransform* getShapeOffsetTransform(Link* link); bool restoreShapeWrittenInOldFormat(const Archive& archive, ValueNode* shapeArchive); void setReferenceLinkToRestoreShapeWritteinInOldFormat(Link* orgLink, SgNode* newShape); SgPosTransform* extractOrInsertOffsetTransform(vector& nodePaths); @@ -119,8 +146,10 @@ LinkOverwriteItem::LinkOverwriteItem() LinkOverwriteItem::Impl::Impl(LinkOverwriteItem* self) : self(self) { - targetElementSet = NoElement; + overwriteElementSet = NoElement; isRootLink = false; + shapeOffset.setIdentity(); + shapeColor.setOnes(); locationTargetType = LinkOffsetLocation; } @@ -134,7 +163,7 @@ LinkOverwriteItem::LinkOverwriteItem(const LinkOverwriteItem& org, CloneMap* clo LinkOverwriteItem::Impl::Impl(LinkOverwriteItem* self, const Impl& org, CloneMap* cloneMap) : self(self), - targetElementSet(org.targetElementSet), + overwriteElementSet(org.overwriteElementSet), additionalLinkParentName(org.additionalLinkParentName) { if(org.targetLink && cloneMap && self->bodyItem()){ @@ -146,11 +175,18 @@ LinkOverwriteItem::Impl::Impl(LinkOverwriteItem* self, const Impl& org, CloneMap if(org.additionalLink){ additionalLink = CloneMap::getClone(org.additionalLink, cloneMap); } - if(org.originalLinkClone){ - originalLinkClone = CloneMap::getClone(org.originalLinkClone, cloneMap); - } isRootLink = org.isRootLink; locationTargetType = org.locationTargetType; + + shapeOffset = org.shapeOffset; + shapeColor = org.shapeColor; + + if(org.visualShape){ + visualShape = CloneMap::getClone(org.visualShape, cloneMap); + } + if(org.collisionShape){ + collisionShape = CloneMap::getClone(org.collisionShape, cloneMap); + } } @@ -210,21 +246,33 @@ bool LinkOverwriteItem::setName(const std::string& name) } -void LinkOverwriteItem::setTargetElementSet(int elementSet) +void LinkOverwriteItem::setOverwriteElementSet(int elementSet) { - impl->targetElementSet = elementSet; + impl->overwriteElementSet = elementSet; } -void LinkOverwriteItem::addTargetElement(int element) +void LinkOverwriteItem::addOverwriteElement(int element) +{ + impl->overwriteElementSet |= element; +} + + +void LinkOverwriteItem::addOverwriteElementSet(int elementSet) +{ + impl->overwriteElementSet |= elementSet; +} + + +int LinkOverwriteItem::overwriteElementSet() const { - impl->targetElementSet |= element; + return impl->overwriteElementSet; } -int LinkOverwriteItem::targetElementSet() const +bool LinkOverwriteItem::hasOverwriteElement(int element) const { - return impl->targetElementSet; + return impl->overwriteElementSet & element; } @@ -280,6 +328,110 @@ Link* LinkOverwriteItem::additionalLink() } +void LinkOverwriteItem::setShapeOffset(const Isometry3& T, bool doOverwrite) +{ + impl->shapeOffset = T; + impl->overwriteElementSet |= ShapeOffset; + if(doOverwrite && impl->targetLink){ + impl->overwriteShapeOffset(impl->targetLink, true); + if(auto bodyItem_ = bodyItem()){ + bodyItem_->notifyModelUpdate(BodyItem::ShapeUpdate); + } + } +} + + +const Isometry3& LinkOverwriteItem::shapeOffset() const +{ + return impl->shapeOffset; +} + + +void LinkOverwriteItem::setShapeColor(const Vector3f& color, bool doOverwrite) +{ + impl->shapeColor = color; + impl->overwriteElementSet |= ShapeColor; + if(doOverwrite && impl->targetLink){ + impl->overwriteShapeColor(impl->targetLink, true); + if(auto bodyItem_ = bodyItem()){ + bodyItem_->notifyModelUpdate(BodyItem::ShapeUpdate); + } + } +} + + +void LinkOverwriteItem::resetShapeColor(bool doNotify) +{ + impl->cancelOverwritingShapeColor(impl->targetLink, doNotify); + if(doNotify){ + if(auto bodyItem_ = bodyItem()){ + bodyItem_->notifyModelUpdate(BodyItem::ShapeUpdate); + } + } +} + + +const Vector3f& LinkOverwriteItem::shapeColor() const +{ + return impl->shapeColor; +} + + +void LinkOverwriteItem::setShape(SgNode* shape) +{ + setVisualShape(shape); + setCollisionShape(shape); +} + + +void LinkOverwriteItem::setVisualShape(SgNode* shape) +{ + impl->overwriteElementSet |= Shape; + impl->visualShape = shape; +} + + +void LinkOverwriteItem::setCollisionShape(SgNode* shape) +{ + impl->overwriteElementSet |= Shape; + impl->collisionShape = shape; +} + + +SgNode* LinkOverwriteItem::visualShape() +{ + return impl->visualShape; +} + + +SgNode* LinkOverwriteItem::collisionShape() +{ + return impl->collisionShape; +} + + +std::string LinkOverwriteItem::findOriginalShapeFile() const +{ + string filename; + + if(impl->targetLink){ + auto pred = [](SgObject* object){ return object->hasUri(); }; + SgObject* uriObject = impl->targetLink->visualShape()->findObject(pred); + if(!uriObject){ + uriObject = impl->targetLink->collisionShape()->findObject(pred); + } + if(uriObject){ + filename = uriObject->localFileAbsolutePath(); + if(filename.empty()){ + filename = uriObject->localFilePath(); + } + } + } + + return filename; +} + + Link* LinkOverwriteItem::sourceLink() { return impl->referenceLink ? impl->referenceLink : impl->additionalLink; @@ -328,7 +480,15 @@ bool LinkOverwriteItem::Impl::updateOverwriting(BodyItem* bodyItem) auto body = bodyItem->body(); Link* newTargetLink = nullptr; - if(!targetLink){ + if(targetLink){ + if(referenceLink){ + overwriteExistingLink(targetLink); + updated = true; + } else if(additionalLink){ + overwriteShapeElements(targetLink); + updated = true; + } + } else { if(referenceLink){ if(isRootLink){ newTargetLink = body->rootLink(); @@ -349,6 +509,7 @@ bool LinkOverwriteItem::Impl::updateOverwriting(BodyItem* bodyItem) updated = true; } else if(additionalLink){ updated = addNewLink(body); + overwriteShapeElements(newTargetLink); } if(updated){ targetLink = newTargetLink; @@ -357,20 +518,13 @@ bool LinkOverwriteItem::Impl::updateOverwriting(BodyItem* bodyItem) } } } - } else { - if(referenceLink){ - overwriteExistingLink(targetLink); - updated = true; - } else if(additionalLink){ - updated = true; - } } if(updated){ bool isKinematicStateChangeNotificationRequested = false; if((additionalLink && newTargetLink) || - (targetElementSet & (JointType | JointId | JointName))){ + (overwriteElementSet & (JointType | JointId | JointName))){ body->updateLinkTree(); if(bodyItem->isBeingRestored()){ bodyItem->requestNonRootLinkStatesRestorationOnSubTreeRestored(); @@ -383,7 +537,7 @@ bool LinkOverwriteItem::Impl::updateOverwriting(BodyItem* bodyItem) BodyItem::DeviceSetUpdate | BodyItem::ShapeUpdate); if(!isKinematicStateChangeNotificationRequested){ - if(targetElementSet & (OffsetPosition | JointType | JointAxis)){ + if(overwriteElementSet & (OffsetPosition | JointType | JointAxis)){ bodyItem->notifyKinematicStateChange(true); } } @@ -398,8 +552,8 @@ bool LinkOverwriteItem::Impl::updateOverwriting(BodyItem* bodyItem) } if(offsetLocation){ - if(((targetElementSet & OffsetPosition) && (locationTargetType == LinkOffsetLocation)) || - ((targetElementSet & Shape) && (locationTargetType == ShapeOffsetLocation))){ + if(((overwriteElementSet & OffsetPosition) && (locationTargetType == LinkOffsetLocation)) || + ((overwriteElementSet & ShapeOffset) && (locationTargetType == ShapeOffsetLocation))){ offsetLocation->sigLocationChanged_(); } } @@ -416,59 +570,277 @@ void LinkOverwriteItem::Impl::overwriteExistingLink(Link* existingLink) if(!originalLinkClone){ originalLinkClone = existingLink->clone(); } - copyTargetElements(referenceLink, existingLink, targetElementSet); + copyOverwriteLinkElements(referenceLink, existingLink); + overwriteShapeElements(existingLink); } -void LinkOverwriteItem::Impl::copyTargetElements(Link* srcLink, Link* destLink, int elementSet) +void LinkOverwriteItem::Impl::copyOverwriteLinkElements(Link* srcLink, Link* destLink) { if(isRootLink){ destLink->setName(srcLink->name()); } - if(elementSet & OffsetPosition){ + if(overwriteElementSet & OffsetPosition){ destLink->setOffsetPosition(srcLink->offsetPosition()); } - if(elementSet & JointType){ + if(overwriteElementSet & JointType){ destLink->setJointType(srcLink->jointType()); } - if(elementSet & JointAxis){ + if(overwriteElementSet & JointAxis){ destLink->setJointAxis(srcLink->jointAxis()); } - if(elementSet & JointId){ + if(overwriteElementSet & JointId){ destLink->setJointId(srcLink->jointId()); } - if(elementSet & JointName){ + if(overwriteElementSet & JointName){ destLink->setJointName(srcLink->jointName()); } - if(elementSet & JointRange){ + if(overwriteElementSet & JointRange){ destLink->setJointRange(srcLink->q_lower(), srcLink->q_upper()); } - if(elementSet & JointVelocityRange){ + if(overwriteElementSet & JointVelocityRange){ destLink->setJointVelocityRange(srcLink->dq_lower(), srcLink->dq_upper()); } - if(elementSet & JointEffortRange){ + if(overwriteElementSet & JointEffortRange){ destLink->setJointEffortRange(srcLink->u_lower(), srcLink->u_upper()); } - if(elementSet & Mass){ + if(overwriteElementSet & Mass){ destLink->setMass(srcLink->mass()); } - if(elementSet & Inertia){ + if(overwriteElementSet & Inertia){ destLink->setInertia(srcLink->I()); } - if(elementSet & CenterOfMass){ + if(overwriteElementSet & CenterOfMass){ destLink->setCenterOfMass(srcLink->centerOfMass()); } - if(elementSet & Material){ + if(overwriteElementSet & Material){ destLink->setMaterial(srcLink->materialId()); } - if(elementSet & Shape){ - SgUpdate update; - destLink->clearShapeNodes(update); - for(auto& node : *srcLink->visualShape()){ - destLink->addVisualShapeNode(node, update); +} + + +void LinkOverwriteItem::Impl::overwriteShapeElements(Link* link) +{ + if(overwriteElementSet & Shape){ + link->clearShapeNodes(sgUpdate); + link->addVisualShapeNode(visualShape, sgUpdate); + link->addCollisionShapeNode(collisionShape, sgUpdate); + shapeOffsetTransform.reset(); + collisionShapeOffsetTransform.reset(); + } + overwriteShapeOffset(link, true); + overwriteShapeColor(link, true); +} + + +void LinkOverwriteItem::Impl::cancelOverwritingShapeElements(Link* link) +{ + cancelOverwritingShapeOffset(link, true); + cancelOverwritingShapeColor(link, true); + + if(overwriteElementSet & Shape){ + if(originalLinkClone){ + link->clearShapeNodes(sgUpdate); + for(auto& node : *originalLinkClone->visualShape()){ + link->addVisualShapeNode(node, sgUpdate); + } + for(auto& node : *originalLinkClone->collisionShape()){ + link->addCollisionShapeNode(node, sgUpdate); + } + } + } +} + + +void LinkOverwriteItem::Impl::overwriteShapeOffset(Link* link, bool doNotify) +{ + if(overwriteElementSet & ShapeOffset){ + if(shapeOffsetTransform || collisionShapeOffsetTransform){ + if(shapeOffsetTransform){ + shapeOffsetTransform->setPosition(shapeOffset); + if(doNotify){ + shapeOffsetTransform->notifyUpdate(sgUpdate.withAction(SgUpdate::Modified)); + } + } + if(collisionShapeOffsetTransform && collisionShapeOffsetTransform != shapeOffsetTransform){ + collisionShapeOffsetTransform->setPosition(shapeOffset); + if(doNotify){ + collisionShapeOffsetTransform->notifyUpdate(sgUpdate.withAction(SgUpdate::Modified)); + } + } + } else { + bool hasDedicatedCollisionShape = link->hasDedicatedCollisionShape(); + auto visualShape = link->visualShape(); + if(!visualShape->empty()){ + shapeOffsetTransform = new SgPosTransform(shapeOffset); + visualShape->moveChildrenTo(shapeOffsetTransform); + visualShape->addChild(shapeOffsetTransform); + if(doNotify){ + visualShape->notifyUpdate(sgUpdate.withAction(SgUpdate::Added | SgUpdate::Removed)); + } + } + auto collisionShape = link->collisionShape(); + if(!hasDedicatedCollisionShape){ + if(shapeOffsetTransform){ + collisionShape->clearChildren(); + collisionShape->addChild(shapeOffsetTransform); + collisionShapeOffsetTransform = shapeOffsetTransform; + if(doNotify){ + collisionShape->notifyUpdate(sgUpdate.withAction(SgUpdate::Added | SgUpdate::Removed)); + } + } + } else if(!collisionShape->empty()){ + collisionShapeOffsetTransform = new SgPosTransform(shapeOffset); + collisionShape->moveChildrenTo(collisionShapeOffsetTransform); + collisionShape->addChild(collisionShapeOffsetTransform); + if(doNotify){ + collisionShape->notifyUpdate(sgUpdate.withAction(SgUpdate::Added | SgUpdate::Removed)); + } + } + } + } else if(shapeOffsetTransform || collisionShapeOffsetTransform){ + cancelOverwritingShapeOffset(link, doNotify); + } +} + + +void LinkOverwriteItem::Impl::cancelOverwritingShapeOffset(Link* link, bool doNotify) +{ + if(shapeOffsetTransform){ + auto shape = link->visualShape(); + shape->contains(shapeOffsetTransform); + shape->removeChild(shapeOffsetTransform); + shapeOffsetTransform->copyChildrenTo(shape); + shapeOffsetTransform.reset(); + if(doNotify){ + shape->notifyUpdate(sgUpdate.withAction(SgUpdate::Added | SgUpdate::Removed)); + } + } + if(collisionShapeOffsetTransform){ + auto shape = link->collisionShape(); + shape->contains(collisionShapeOffsetTransform); + shape->removeChild(collisionShapeOffsetTransform); + collisionShapeOffsetTransform->moveChildrenTo(shape); + collisionShapeOffsetTransform.reset(); + if(doNotify){ + shape->notifyUpdate(sgUpdate.withAction(SgUpdate::Added | SgUpdate::Removed)); + } + } +} + + +void LinkOverwriteItem::Impl::overwriteShapeColor(Link* link, bool doNotify) +{ + if(overwriteElementSet & ShapeColor){ + for(auto& kv : materialMap){ + kv.second.isUpdated = false; } - for(auto& node : *srcLink->collisionShape()){ - destLink->addCollisionShapeNode(node, update); + if(!materialForNoMaterialShapes){ + materialForNoMaterialShapes = new SgMaterial; + } + materialForNoMaterialShapes->setDiffuseColor(shapeColor); + bool materialForNoMaterialShapesAdded = false; + + link->shape()->traverseNodes( + [this, &materialForNoMaterialShapesAdded](SgNode* node) -> SgObject::TraverseStatus { + if(auto shape = dynamic_cast(node)){ + if(noMaterialShapes.find(shape) == noMaterialShapes.end()){ + if(auto material = shape->material()){ + auto& info = materialMap[material]; + if(!info.isUpdated){ + if(!info.orgMaterialClone){ + info.orgMaterialClone = static_cast(material->clone()); + } + material->setDiffuseColor(shapeColor); + if(!material->emissiveColor().isZero()){ + material->setEmissiveColor(shapeColor); + } + info.isUpdated = true; + } + } else { + shape->setMaterial(materialForNoMaterialShapes); + noMaterialShapes.insert(shape); + materialForNoMaterialShapesAdded = true; + } + } + } + return SgObject::Continue; + }); + + if(doNotify){ + sgUpdate.setAction(SgUpdate::Modified); + for(auto& kv : materialMap){ + kv.first->notifyUpdate(sgUpdate); + } + if(materialForNoMaterialShapesAdded){ + sgUpdate.addAction(SgUpdate::Added); + materialForNoMaterialShapes->notifyUpdate(sgUpdate); + } + } + + } else if(!materialMap.empty() || materialForNoMaterialShapes){ + cancelOverwritingShapeColor(link, doNotify); + } +} + + +void LinkOverwriteItem::Impl::cancelOverwritingShapeColor(Link* link, bool doNotify) +{ + restoreOriginalShapeMaterials(link, doNotify); + materialMap.clear(); + noMaterialShapes.clear(); + materialForNoMaterialShapes.reset(); + overwriteElementSet &= ~ShapeColor; +} + + +void LinkOverwriteItem::Impl::restoreOriginalShapeMaterials(Link* link, bool doNotify) +{ + for(auto& kv : materialMap){ + auto& material = kv.first; + auto& info = kv.second; + if(info.orgMaterialClone){ + material->copyMaterialPropertiesFrom(info.orgMaterialClone); + } + } + for(auto& shape : noMaterialShapes){ + shape->setMaterial(nullptr); + } + if(doNotify){ + notifyMaterialUpdates(SgUpdate::Removed); + } +} + + +void LinkOverwriteItem::Impl::restoreOverwritingShapeMaterials(Link* link, bool doNotify) +{ + for(auto& kv : materialMap){ + auto& material = kv.first; + material->setDiffuseColor(shapeColor); + if(!material->emissiveColor().isZero()){ + material->setEmissiveColor(shapeColor); + } + } + for(auto& shape : noMaterialShapes){ + shape->setMaterial(materialForNoMaterialShapes); + } + if(doNotify){ + notifyMaterialUpdates(SgUpdate::Added); + } +} + + +void LinkOverwriteItem::Impl::notifyMaterialUpdates(int noMaterialShapeAction) +{ + sgUpdate.setAction(SgUpdate::Modified); + for(auto& kv : materialMap){ + auto& material = kv.first; + material->notifyUpdate(sgUpdate); + } + if(!noMaterialShapes.empty()){ + sgUpdate.addAction(noMaterialShapeAction); + for(auto& shape : noMaterialShapes){ + shape->notifyUpdate(sgUpdate); } } } @@ -522,6 +894,11 @@ void LinkOverwriteItem::Impl::releaseOverwriteTarget() additionalLink.reset(); originalLinkClone.reset(); isRootLink = false; + shapeOffsetTransform.reset(); + collisionShapeOffsetTransform.reset(); + materialMap.clear(); + noMaterialShapes.clear(); + materialForNoMaterialShapes.reset(); } @@ -531,12 +908,17 @@ void LinkOverwriteItem::Impl::cancelOverwriting() if(targetLink){ if(auto body = targetLink->body()){ - if(originalLinkClone){ - copyTargetElements(originalLinkClone, targetLink, targetElementSet); - updated = true; - } else if(auto parent = targetLink->parent()){ - parent->removeChild(targetLink); + if(referenceLink){ + if(originalLinkClone){ + copyOverwriteLinkElements(originalLinkClone, targetLink); + } + cancelOverwritingShapeElements(targetLink); updated = true; + } else if(additionalLink){ + if(auto parent = targetLink->parent()){ + parent->removeChild(targetLink); + updated = true; + } } if(updated){ body->updateLinkTree(); @@ -555,26 +937,6 @@ void LinkOverwriteItem::Impl::cancelOverwriting() } -SgPosTransform* LinkOverwriteItem::Impl::getShapeOffsetTransform(Link* link) -{ - if(link){ - auto shape = link->shape(); - if(shape->numChildren() == 1){ - if(auto transform = dynamic_cast(shape->child(0))){ - return transform; - } - } - auto collisionShape = link->collisionShape(); - if(collisionShape->numChildren() == 1){ - if(auto transform = dynamic_cast(collisionShape->child(0))){ - return transform; - } - } - } - return nullptr; -} - - LocationProxyPtr LinkOverwriteItem::getLocationProxy() { if(!impl->offsetLocation){ @@ -651,7 +1013,8 @@ bool LinkOverwriteItem::Impl::store(Archive& archive) archive.write("is_root", true); } - if(self->isAddingLink()){ + bool isAddingLink = self->isAddingLink(); + if(isAddingLink){ archive.write("is_additional", true); if(auto parentLink = link->parent()){ archive.write("parent", parentLink->name(), DOUBLE_QUOTED); @@ -662,7 +1025,7 @@ bool LinkOverwriteItem::Impl::store(Archive& archive) archive.setFloatingNumberFormat("%.9g"); - if(targetElementSet & OffsetPosition){ + if(overwriteElementSet & OffsetPosition){ // The "translation" value is always written so that the restore function can // know that the OffsetPosition is an element to overwrite. write(archive, "translation", link->offsetTranslation()); @@ -671,29 +1034,41 @@ bool LinkOverwriteItem::Impl::store(Archive& archive) writeDegreeAngleAxis(archive, "rotation", aa); } } - if(targetElementSet & JointType){ + if(overwriteElementSet & JointType){ archive.write("joint_type", link->jointTypeSymbol()); } - if(targetElementSet & JointAxis){ + if(overwriteElementSet & JointAxis){ write(archive, "joint_axis", link->jointAxis()); } - if(targetElementSet & JointId){ + if(overwriteElementSet & JointId){ archive.write("joint_id", link->jointId()); } - if(targetElementSet & JointName){ + if(overwriteElementSet & JointName){ archive.write("joint_name", link->jointName(), DOUBLE_QUOTED); } - if(targetElementSet & JointRange){ + if(overwriteElementSet & JointRange){ StdBodyWriter::writeJointDisplacementRange(&archive, link, true); } - if(targetElementSet & JointVelocityRange){ + if(overwriteElementSet & JointVelocityRange){ StdBodyWriter::writeJointVelocityRange(&archive, link, true); } - if(targetElementSet & JointEffortRange){ + if(overwriteElementSet & JointEffortRange){ StdBodyWriter::writeJointEffortRange(&archive, link, true); } - if(targetElementSet & Shape){ + if(overwriteElementSet & ShapeOffset){ + write(archive, "shape_translation", shapeOffset.translation()); + AngleAxis aa(shapeOffset.linear()); + if(aa.angle() != 0.0){ + writeDegreeAngleAxis(archive, "shape_rotation", aa); + } + } + + if(overwriteElementSet & ShapeColor){ + write(archive, "shape_color", shapeColor); + } + + if(overwriteElementSet & Shape){ if(!sceneWriter){ sceneWriter = sharedSceneWriter.lock(); if(!sceneWriter){ @@ -705,11 +1080,11 @@ bool LinkOverwriteItem::Impl::store(Archive& archive) } sceneWriter->setFilePathVariableProcessor(archive.filePathVariableProcessor()); - auto visualShape = link->visualShape(); - auto collisionShape = link->collisionShape(); - bool hasDedicatedCollisionShape = link->hasDedicatedCollisionShape(); - - if(!visualShape->empty()){ + if(overwriteElementSet & ShapeColor){ + restoreOriginalShapeMaterials(link, false); + } + bool hasDedicatedCollisionShape = (visualShape != collisionShape); + if(visualShape){ auto visualShapeArchive = sceneWriter->writeScene(visualShape); if(!hasDedicatedCollisionShape){ archive.insert("shape", visualShapeArchive); @@ -717,13 +1092,16 @@ bool LinkOverwriteItem::Impl::store(Archive& archive) archive.insert("visual_shape", visualShapeArchive); } } - if(!collisionShape->empty() && hasDedicatedCollisionShape){ + if(collisionShape && hasDedicatedCollisionShape){ if(auto collisionShapeArchive = sceneWriter->writeScene(collisionShape)){ archive.insert("collision_shape", collisionShapeArchive); } } + if(overwriteElementSet & ShapeColor){ + restoreOverwritingShapeMaterials(link, false); + } } - + return true; } @@ -765,7 +1143,7 @@ bool LinkOverwriteItem::Impl::restore(const Archive& archive) LinkPtr link = new Link; link->setName(self->name()); - int elementSet = NoElement; + overwriteElementSet = NoElement; if(!archive.get("is_additional", false)){ self->setReferenceLink(link, archive.get("is_root", false)); @@ -781,7 +1159,7 @@ bool LinkOverwriteItem::Impl::restore(const Archive& archive) if(readDegreeAngleAxis(archive, "rotation", aa)){ link->setOffsetRotation(aa); } - elementSet |= OffsetPosition; + overwriteElementSet |= OffsetPosition; } string symbol; if(archive.read("joint_type", symbol)){ @@ -796,17 +1174,17 @@ bool LinkOverwriteItem::Impl::restore(const Archive& archive) } else { archive.throwException(formatR(_("Illegal jointType value \"{0}\""), symbol)); } - elementSet |= JointType; + overwriteElementSet |= JointType; } Vector3 a; if(read(archive, "joint_axis", a)){ link->setJointAxis(a); - elementSet |= JointAxis; + overwriteElementSet |= JointAxis; } int id; if(archive.read("joint_id", id)){ link->setJointId(id); - elementSet |= JointId; + overwriteElementSet |= JointId; } if(archive.read("joint_name", symbol)){ if(symbol.empty()){ @@ -814,45 +1192,56 @@ bool LinkOverwriteItem::Impl::restore(const Archive& archive) } else { link->setJointName(symbol); } - elementSet |= JointName; + overwriteElementSet |= JointName; } if(StdBodyLoader::readJointDisplacementRange(&archive, link)){ - elementSet |= JointRange; + overwriteElementSet |= JointRange; } if(StdBodyLoader::readJointVelocityRange(&archive, link)){ - elementSet |= JointVelocityRange; + overwriteElementSet |= JointVelocityRange; } if(StdBodyLoader::readJointEffortRange(&archive, link)){ - elementSet |= JointEffortRange; + overwriteElementSet |= JointEffortRange; + } + + if(read(archive, "shape_translation", p)){ + Isometry3 T; + T.translation() = p; + AngleAxis aa; + if(readDegreeAngleAxis(archive, "shape_rotation", aa)){ + T.linear() = aa.toRotationMatrix(); + } else { + T.linear().setIdentity(); + } + self->setShapeOffset(T); + } + + Vector3f c; + if(read(archive, "shape_color", c)){ + shapeColor = c; + overwriteElementSet |= ShapeColor; } auto shapeArchive = archive.find("shape"); if(shapeArchive->isValid()){ if(auto shape = ensureSceneReader(archive)->readScene(shapeArchive)){ - link->addShapeNode(shape); - elementSet |= Shape; + self->setShape(shape); } } auto visualShapeArchive = archive.find("visual_shape"); if(visualShapeArchive->isValid()){ if(auto visualShape = ensureSceneReader(archive)->readScene(visualShapeArchive)){ - link->addVisualShapeNode(visualShape); - elementSet |= Shape; + self->setVisualShape(visualShape); } } auto collisionShapeArchive = archive.find("collision_shape"); if(collisionShapeArchive->isValid()){ if(auto collisionShape = ensureSceneReader(archive)->readScene(collisionShapeArchive)){ - link->addCollisionShapeNode(collisionShape); - elementSet |= Shape; + self->setCollisionShape(collisionShape); } } - if(elementSet){ - self->setTargetElementSet(elementSet); - } - - return elementSet != NoElement; + return true; } @@ -876,17 +1265,38 @@ bool LinkOverwriteItem::Impl::restoreShapeWrittenInOldFormat(const Archive& arch } +bool checkHintsInMetadata(SgObject* object, bool& out_isNotMeter, bool& out_isYUpperAxis) +{ + auto metadata = object->uriMetadata(); + if(!metadata){ + return false; + } + string symbol; + if(metadata->read("length_unit", symbol)){ + if(symbol != "meter"){ + out_isNotMeter = true; + } + } + if(metadata->read("upper_axis", symbol)){ + if(symbol == "Y"){ + out_isYUpperAxis = true; + } + } + return true; +} + + void LinkOverwriteItem::Impl::setReferenceLinkToRestoreShapeWritteinInOldFormat(Link* orgLink, SgNode* newShape) { - SgPosTransformPtr shapeOffset = nullptr; + SgPosTransformPtr shapeOffsetNode = nullptr; vector shapeNodePaths; SceneNodeExtractor nodeExtractor; if(newShape){ - shapeOffset = dynamic_cast(newShape); - if(!shapeOffset){ - shapeOffset = new SgPosTransform; - shapeOffset->addChild(newShape); + shapeOffsetNode = dynamic_cast(newShape); + if(!shapeOffsetNode){ + shapeOffsetNode = new SgPosTransform; + shapeOffsetNode->addChild(newShape); } shapeNodePaths = nodeExtractor.extractNodes(newShape, true); } @@ -895,21 +1305,30 @@ void LinkOverwriteItem::Impl::setReferenceLinkToRestoreShapeWritteinInOldFormat( SgObject::setNonNodeCloning(cloneMap, false); SgNodePtr shapeClone = cloneMap.getClone(orgLink->shape()); vector existingShapeNodePaths = nodeExtractor.extractNodes(shapeClone, false); + bool doRemoveScalingForLengthUnitConversion = false; + bool doRemoveRotationForUpperAxisConversion = false; if(existingShapeNodePaths.empty()){ if(!newShape){ - shapeOffset = new SgPosTransform; + shapeOffsetNode = new SgPosTransform; } } else { if(!newShape){ - shapeOffset = extractOrInsertOffsetTransform(existingShapeNodePaths); + shapeOffsetNode = extractOrInsertOffsetTransform(existingShapeNodePaths); } else { int n = std::min(shapeNodePaths.size(), existingShapeNodePaths.size()); for(int i=0; i < n; ++i){ auto shapeNode = static_cast(shapeNodePaths[i].back().get()); auto existingShapeNode = static_cast(existingShapeNodePaths[i].back().get()); if(!shapeNode->mesh()){ - shapeNode->setMesh(existingShapeNode->mesh()); + auto existingMesh = existingShapeNode->mesh(); + shapeNode->setMesh(existingMesh); + + if(!checkHintsInMetadata( + existingShapeNode, doRemoveScalingForLengthUnitConversion, doRemoveRotationForUpperAxisConversion)){ + checkHintsInMetadata( + existingMesh, doRemoveScalingForLengthUnitConversion, doRemoveRotationForUpperAxisConversion); + } } if(!shapeNode->material()){ shapeNode->setMaterial(existingShapeNode->material()); @@ -918,12 +1337,30 @@ void LinkOverwriteItem::Impl::setReferenceLinkToRestoreShapeWritteinInOldFormat( } } + Isometry3 T = shapeOffsetNode->position(); + if(doRemoveRotationForUpperAxisConversion){ + Matrix3 R; + R << 0, 0, 1, + 1, 0, 0, + 0, 1, 0; + T.linear() = T.linear() * R.transpose(); + } + if(!T.matrix().isIdentity()){ + self->setShapeOffset(T); + } + if(!shapeOffsetNode->empty()){ + SgNode* node = shapeOffsetNode->child(0); + if(doRemoveScalingForLengthUnitConversion){ + if(auto scale = dynamic_cast(node)){ + node = scale->child(0); + } + } + self->setShape(node); + } + LinkPtr link = new Link; link->setName(orgLink->name()); - link->addShapeNode(shapeOffset, true); - self->setReferenceLink(link); - self->setTargetElementSet(Shape); } @@ -952,10 +1389,11 @@ SgPosTransform* LinkOverwriteItem::Impl::extractOrInsertOffsetTransform(vectorself, LocationProxy::OffsetLocation), impl(impl) { setLocked(true); + setNameDependencyOnItemName(); } @@ -987,9 +1425,7 @@ Isometry3 OffsetLocation::getLocation() const if(isLinkOffsetLocation()){ return impl->targetLink->offsetPosition(); } else if(isShapeOffsetLocation()){ - if(auto transform = impl->getShapeOffsetTransform(impl->targetLink)){ - return transform->T(); - } + return impl->shapeOffset; } } return Isometry3::Identity(); @@ -1004,11 +1440,8 @@ bool OffsetLocation::setLocation(const Isometry3& T) sourceLink->setOffsetPosition(T); updated = true; } else if(isShapeOffsetLocation()){ - if(auto transform = impl->getShapeOffsetTransform(sourceLink)){ - transform->setPosition(T); - transform->notifyUpdate(); - updated = true; - } + impl->self->setShapeOffset(T, true); + updated = true; } } if(updated){ @@ -1018,13 +1451,7 @@ bool OffsetLocation::setLocation(const Isometry3& T) } -Item* OffsetLocation::getCorrespondingItem() -{ - return impl->self; -} - - -LocationProxyPtr OffsetLocation::getParentLocationProxy() const +LocationProxyPtr OffsetLocation::getParentLocationProxy() { if(impl->targetLink){ if(isLinkOffsetLocation()){ diff --git a/src/BodyPlugin/LinkOverwriteItem.h b/src/BodyPlugin/LinkOverwriteItem.h index a731be997..a33f3bab5 100644 --- a/src/BodyPlugin/LinkOverwriteItem.h +++ b/src/BodyPlugin/LinkOverwriteItem.h @@ -39,27 +39,64 @@ class CNOID_EXPORT LinkOverwriteItem : public BodyElementOverwriteItem, Inertia = 1 << 9, CenterOfMass = 1 << 10, Material = 1 << 11, - Shape = 1 << 12, - AllElements = + ShapeOffset = 1 << 12, + ShapeColor = 1 << 13, + Shape = 1 << 14, + AllNonShapeElements = OffsetPosition | JointType | JointAxis | JointId | JointName | JointRange | - JointVelocityRange | JointEffortRange | Mass | Inertia | CenterOfMass | - Material | Shape, + JointVelocityRange | JointEffortRange | Mass | Inertia | CenterOfMass | Material }; - void setTargetElementSet(int elementSet); - void addTargetElement(int element); - int targetElementSet() const; - + void setOverwriteElementSet(int elementSet); + void addOverwriteElement(int element); + void addOverwriteElementSet(int elementSet); + int overwriteElementSet() const; + bool hasOverwriteElement(int element) const; + + [[deprecated("Use setOverwriteElementSet.")]] + void setTargetElementSet(int elementSet) { setOverwriteElementSet(elementSet); } + [[deprecated("Use addOverwriteElement.")]] + void addTargetElement(int element) { addOverwriteElement(element); } + [[deprecated("Use overwriteElementSet.")]] + int targetElementSet() const { return overwriteElementSet(); } + + /** + When a LinkOverwriteItem overwrites an existing link, the information of the link elements to be overwritten is + stored in a reference link, which is a separate object from the existing link. + Note that if the overwritten elements are only shape elements, the reference link is not necessary. + */ bool setReferenceLink(Link* referenceLink, bool isRootLink = false); Link* referenceLink(); bool isRootLink() const; Link* originalLinkClone(); + /** + When a LinkOverwriteItem creates a new link, the new link is stored as an "additional link". + In this case, the LinkOverwriteItem does not have the reference link. + */ bool setAdditionalLink(Link* additionalLink, const std::string& parentLinkName = std::string()); Link* additionalLink(); + void setShapeOffset(const Isometry3& T, bool doOverwrite = false); + const Isometry3& shapeOffset() const; + void setShapeColor(const Vector3f& color, bool doOverwrite = false); + const Vector3f& shapeColor() const; + void resetShapeColor(bool doNotify = false); + + void setShape(SgNode* shape); + void setVisualShape(SgNode* shape); + void setCollisionShape(SgNode* shape); + SgNode* visualShape(); + SgNode* collisionShape(); + + std::string findOriginalShapeFile() const; + + /** + The source link is either the reference link or additional link. + */ Link* sourceLink(); + Link* targetLink(); bool isOverwriting() const; @@ -69,8 +106,6 @@ class CNOID_EXPORT LinkOverwriteItem : public BodyElementOverwriteItem, bool updateOverwriting(); virtual void releaseOverwriteTarget() override; - SgPosTransform* shapeOffsetTransform(); - // LocatableItem function virtual LocationProxyPtr getLocationProxy() override; diff --git a/src/BodyPlugin/LinkPositionWidget.cpp b/src/BodyPlugin/LinkPositionWidget.cpp index a43748c5e..c5bc08af9 100644 --- a/src/BodyPlugin/LinkPositionWidget.cpp +++ b/src/BodyPlugin/LinkPositionWidget.cpp @@ -526,10 +526,10 @@ void LinkPositionWidget::Impl::setTargetBodyAndLink(BodyItem* bodyItem, Link* li [this](){ updateTargetLink(targetLink); })); targetConnections.add( - bodyItem->sigContinuousKinematicUpdateStateChanged().connect( + bodyItem->sigContinuousUpdateStateChanged().connect( [this](bool on){ setUserInputEnabled(!on); })); - setUserInputEnabled(!bodyItem->isDoingContinuousKinematicUpdate()); + setUserInputEnabled(!bodyItem->isContinuousUpdateState()); } } @@ -893,7 +893,7 @@ void LinkPositionWidget::Impl::showConfigurationDialog() if(!configurationDialog){ configurationDialog = new JointSpaceConfigurationDialog(this); if(targetBodyItem){ - configurationDialog->setUserInputEnabled(!targetBodyItem->isDoingContinuousKinematicUpdate()); + configurationDialog->setUserInputEnabled(!targetBodyItem->isContinuousUpdateState()); } } diff --git a/src/BodyPlugin/OperableSceneBody.cpp b/src/BodyPlugin/OperableSceneBody.cpp index eecc1ace8..2f46cf1a9 100644 --- a/src/BodyPlugin/OperableSceneBody.cpp +++ b/src/BodyPlugin/OperableSceneBody.cpp @@ -35,6 +35,33 @@ namespace { enum LinkOperationType { None, FK, IK, SimInterference }; +class ZmpMarker : public SphereMarker +{ + ZmpDevicePtr zmpDevice; + ScopedConnection zmpDeviceConnection; + SgUpdate update; + +public: + ZmpMarker(LeggedBodyHelper* legged); + void setActive(bool on); + void setZmp(const Vector3& zmp); +}; + +typedef ref_ptr ZmpMarkerPtr; + +double calcMarkerRadius(Link* link) +{ + if(auto shape = link->visualShape()){ + const BoundingBox& bb = shape->boundingBox(); + if(bb.empty()){ + return 1.0; // Is this OK? + } + double V = ((bb.max().x() - bb.min().x()) * (bb.max().y() - bb.min().y()) * (bb.max().z() - bb.min().z())); + return pow(V, 1.0 / 3.0) * 0.6; + } + return 1.0; +} + } namespace cnoid { @@ -54,7 +81,6 @@ class OperableSceneLink::Impl Impl(OperableSceneBody* sceneBody, OperableSceneLink* self); OperableSceneBody* operableSceneBody(); - double calcMarkerRadius() const; void showOrigin(bool on); void showCenterOfMass(bool on); }; @@ -120,8 +146,7 @@ class OperableSceneBody::Impl SgGroupPtr markerGroup; CrossMarkerPtr cmMarker; CrossMarkerPtr cmProjectionMarker; - SphereMarkerPtr zmpMarker; - Vector3 orgZmpPos; + ZmpMarkerPtr zmpMarker; double bodyMarkerRadius; bool isCmVisible; bool isCmProjectionVisible; @@ -156,10 +181,9 @@ class OperableSceneBody::Impl double calcLinkMarkerRadius(SceneLink* sceneLink) const; void ensureCmMarker(); void ensureCmProjectionMarker(); - LeggedBodyHelper* checkLeggedBody(); - bool ensureZmpMarker(); void showCenterOfMass(bool on); void showCmProjection(bool on); + LeggedBodyHelper* checkLeggedBody(); void showZmp(bool on); void makeLinkFree(OperableSceneLink* sceneLink); void setBaseLink(OperableSceneLink* sceneLink); @@ -288,20 +312,6 @@ void OperableSceneLink::setVisible(bool on) } -double OperableSceneLink::Impl::calcMarkerRadius() const -{ - if(auto shape = self->visualShape()){ - const BoundingBox& bb = shape->boundingBox(); - if(bb.empty()){ - return 1.0; // Is this OK? - } - double V = ((bb.max().x() - bb.min().x()) * (bb.max().y() - bb.min().y()) * (bb.max().z() - bb.min().z())); - return pow(V, 1.0 / 3.0) * 0.6; - } - return 1.0; -} - - void OperableSceneLink::showOrigin(bool on) { impl->showOrigin(on); @@ -366,7 +376,7 @@ void OperableSceneLink::Impl::showCenterOfMass(bool on) if(on != isCenterOfMassShown){ if(on){ if(!cmMarker){ - auto radius = calcMarkerRadius(); + auto radius = calcMarkerRadius(self->link()); cmMarker = new CrossMarker(radius, Vector3f(0.0f, 1.0f, 0.0f), 2.0); cmMarker->setName("CenterOfMass"); } @@ -531,7 +541,7 @@ void OperableSceneBody::Impl::onSceneGraphConnection(bool on) [this](){ onBodyItemUpdated(); })); connections.add( - bodyItem->sigContinuousKinematicUpdateStateChanged().connect( + bodyItem->sigContinuousUpdateStateChanged().connect( [this](bool){ onBodyItemUpdated(); })); connections.add( @@ -553,7 +563,7 @@ void OperableSceneBody::Impl::onSceneGraphConnection(bool on) void OperableSceneBody::Impl::onBodyItemUpdated() { - bool isUserInputBlocked = bodyItem->isDoingContinuousKinematicUpdate() || bodyItem->isLocationLocked(); + bool isUserInputBlocked = bodyItem->isContinuousUpdateState() || bodyItem->isLocationLocked(); if(isUserInputBlocked){ if(sceneLinkForPositionDragger){ detachPositionDragger(); @@ -620,9 +630,6 @@ void OperableSceneBody::Impl::onKinematicStateChanged() com(2) = 0.0; cmProjectionMarker->setTranslation(com); } - if(isZmpVisible){ - zmpMarker->setTranslation(bodyItem->zmp()); - } if(activeSimulatorItem){ if(dragMode == LINK_VIRTUAL_ELASTIC_STRING){ @@ -828,32 +835,6 @@ void OperableSceneBody::Impl::ensureCmProjectionMarker() } -LeggedBodyHelper* OperableSceneBody::Impl::checkLeggedBody() -{ - auto legged = getLeggedBodyHelper(self->body()); - if(!legged->isValid() || legged->numFeet() == 0){ - legged = nullptr; - } - return legged; -} - - -bool OperableSceneBody::Impl::ensureZmpMarker() -{ - if(!zmpMarker){ - if(auto legged = checkLeggedBody()){ - Link* footLink = legged->footLink(0); - auto sceneLink = self->operableSceneLink(footLink->index()); - double radius = sceneLink->impl->calcMarkerRadius(); - zmpMarker = new SphereMarker(radius, Vector3f(0.0f, 1.0f, 0.0f), 0.3f); - zmpMarker->addChild(new CrossMarker(radius * 2.5, Vector3f(0.0f, 1.0f, 0.0f), 2.0f)); - zmpMarker->setName("ZMP"); - } - } - return (zmpMarker != nullptr); -} - - void OperableSceneBody::Impl::showCenterOfMass(bool on) { isCmVisible = on; @@ -890,24 +871,79 @@ void OperableSceneBody::Impl::showCmProjection(bool on) } +LeggedBodyHelper* OperableSceneBody::Impl::checkLeggedBody() +{ + auto legged = getLeggedBodyHelper(self->body()); + if(!legged->isValid() || legged->numFeet() == 0){ + legged = nullptr; + } + return legged; +} + + void OperableSceneBody::Impl::showZmp(bool on) { if(on){ - if(ensureZmpMarker()){ - zmpMarker->setTranslation(bodyItem->zmp()); + if(auto legged = checkLeggedBody()){ + if(!zmpMarker){ + zmpMarker = new ZmpMarker(legged); + } markerGroup->addChildOnce(zmpMarker, update); isZmpVisible = true; } } else { if(zmpMarker){ markerGroup->removeChild(zmpMarker, update); - zmpMarker.reset(); } isZmpVisible = false; } } +ZmpMarker::ZmpMarker(LeggedBodyHelper* legged) +{ + setName("ZMP"); + + Link* footLink = legged->footLink(0); + double radius = calcMarkerRadius(footLink); + setRadius(radius); + setColor(Vector3f(0.0f, 1.0f, 0.0f)); + setTransparency(0.3f); + + addChild(new CrossMarker(radius * 2.5, Vector3f(0.0f, 1.0f, 0.0f), 2.0f)); + + zmpDevice = legged->getOrCreateZmpDevice(); + + update.setAction(SgUpdate::GeometryModified); + + sigGraphConnection().connect( + [this](bool on){ setActive(on); }); +} + + +void ZmpMarker::setActive(bool on) +{ + if(on){ + setTranslation(zmpDevice->zmp()); + zmpDeviceConnection = + zmpDevice->sigStateChanged().connect( + [this](){ + setTranslation(zmpDevice->zmp()); + notifyUpdate(update); + }); + } else { + zmpDeviceConnection.disconnect(); + } +} + + +void ZmpMarker::setZmp(const Vector3& zmp) +{ + zmpDevice->setZmp(zmp); + zmpDevice->notifyStateChange(); +} + + void OperableSceneBody::Impl::makeLinkFree(OperableSceneLink* sceneLink) { if(bodyItem->currentBaseLink() == sceneLink->link()){ @@ -992,15 +1028,21 @@ OperableSceneBody::Impl::PointedType OperableSceneBody::Impl::findPointedObject( { PointedType pointedType = PT_NONE; pointedSceneLink = nullptr; + + if(path.empty()){ + return pointedType; + } + for(size_t i = path.size() - 1; i >= 1; --i){ if(auto sceneLink = dynamic_cast(path[i].get())){ auto sceneBody = sceneLink->sceneBody(); - if(sceneBody == self){ - pointedSceneLink = dynamic_cast(sceneLink); - } else { // multiplex body + auto body = sceneBody->body(); + if(body->isMultiplexBody() && !body->isMultiplexMainBody()){ // multiplex body pointedSceneLink = operableSceneLink(sceneLink->link()->index()); bodyItem->exchangeWithMultiplexBody(sceneBody->body()); onKinematicStateChanged(); + } else if(sceneBody == self){ + pointedSceneLink = dynamic_cast(sceneLink); } } if(pointedSceneLink){ @@ -1323,7 +1365,7 @@ bool OperableSceneBody::Impl::onButtonPressEvent(SceneWidgetEvent* event) PointedType pointedType = findPointedObject(event->nodePath()); if(pointedType == PT_ZMP && event->button() == Qt::LeftButton){ - if(!bodyItem->isDoingContinuousKinematicUpdate()){ + if(!bodyItem->isContinuousUpdateState()){ startZmpTranslation(event); return true; } @@ -1372,7 +1414,7 @@ bool OperableSceneBody::Impl::onButtonPressEvent(SceneWidgetEvent* event) if(event->button() == Qt::LeftButton){ updateMarkersAndManipulators(true); - if(!bodyItem->isDoingContinuousKinematicUpdate()){ + if(!bodyItem->isContinuousUpdateState()){ if(operationType == LinkOperationType::FK){ startFK(event); } else if(operationType == LinkOperationType::IK){ @@ -1515,6 +1557,8 @@ void OperableSceneBody::onPointerLeaveEvent(SceneWidgetEvent* event) void OperableSceneBody::Impl::onPointerLeaveEvent(SceneWidgetEvent* event) { + finishEditing(); + if(highlightedLink){ highlightedLink->enableHighlight(false); highlightedLink = nullptr; @@ -1600,6 +1644,8 @@ bool OperableSceneBody::onContextMenuRequest(SceneWidgetEvent* event) bool OperableSceneBody::Impl::onContextMenuRequest(SceneWidgetEvent* event) { + finishEditing(); + PointedType pointedType = findPointedObject(event->nodePath()); if(pointedType != PT_SCENE_LINK){ @@ -2002,7 +2048,7 @@ void OperableSceneBody::Impl::finishForcedPosition() void OperableSceneBody::Impl::startZmpTranslation(SceneWidgetEvent* event) { - dragProjector.setInitialTranslation(bodyItem->zmp()); + dragProjector.setInitialTranslation(zmpMarker->translation()); dragProjector.setTranslationPlaneNormal(Vector3::UnitZ()); if(dragProjector.startTranslation(event)){ dragMode = ZMP_TRANSLATION; @@ -2015,8 +2061,7 @@ void OperableSceneBody::Impl::dragZmpTranslation(SceneWidgetEvent* event) if(dragProjector.dragTranslation(event)){ Vector3 p = dragProjector.position().translation(); p.z() = dragProjector.initialPosition().translation().z(); - bodyItem->setZmp(p); - bodyItem->notifyKinematicStateChange(true); + zmpMarker->setZmp(p); dragged = true; } } diff --git a/src/BodyPlugin/RegionIntrusionDetectorItem.cpp b/src/BodyPlugin/RegionIntrusionDetectorItem.cpp index 475df4d63..fe14c5761 100644 --- a/src/BodyPlugin/RegionIntrusionDetectorItem.cpp +++ b/src/BodyPlugin/RegionIntrusionDetectorItem.cpp @@ -24,7 +24,6 @@ class RegionLocation : public LocationProxy Signal sigLocationChanged_; RegionLocation(RegionIntrusionDetectorItem::Impl* impl); - virtual Item* getCorrespondingItem() override; virtual Isometry3 getLocation() const override; virtual bool setLocation(const Isometry3& T) override; virtual SignalProxy sigLocationChanged() override; @@ -261,19 +260,13 @@ LocationProxyPtr RegionIntrusionDetectorItem::getLocationProxy() RegionLocation::RegionLocation(RegionIntrusionDetectorItem::Impl* impl) - : LocationProxy(GlobalLocation), + : LocationProxy(impl->self, GlobalLocation), impl(impl) { } -Item* RegionLocation::getCorrespondingItem() -{ - return impl->self; -} - - Isometry3 RegionLocation::getLocation() const { return impl->regionOffset; diff --git a/src/BodyPlugin/SensorVisualizerItem.cpp b/src/BodyPlugin/SensorVisualizerItem.cpp index bbdd60c01..990b4243c 100644 --- a/src/BodyPlugin/SensorVisualizerItem.cpp +++ b/src/BodyPlugin/SensorVisualizerItem.cpp @@ -1,8 +1,3 @@ -/*! - @file - @author Shin'ichiro Nakaoka -*/ - #include "SensorVisualizerItem.h" #include "BodyItem.h" #include @@ -408,7 +403,9 @@ void SensorVisualizerItem::Impl::addSensorVisualizerItem(Body* body) } if(item->bodyItem != bodyItem){ item->setBodyItem(bodyItem); - self->addSubItem(item); + if(item->parentItem() != self){ + self->addSubItem(item); + } } } } @@ -432,7 +429,9 @@ void SensorVisualizerItem::Impl::addVisionSensorVisualizerItem(Body* body) } if(item->bodyItem != bodyItem){ item->setBodyItem(bodyItem, sensor); - self->addSubItem(item); + if(item->parentItem() != self){ + self->addSubItem(item); + } } } } @@ -771,13 +770,11 @@ SignalProxy CameraImageVisualizerItem::sigImageUpdated() void CameraImageVisualizerItem::setBodyItem(BodyItem* bodyItem, Camera* camera) { - if(name().empty()){ - string name = camera->name(); - if(dynamic_cast(camera)){ - name += "-Image"; - } - setName(name); + string cameraName = camera->name(); + if(dynamic_cast(camera)){ + cameraName += "-Image"; } + setName(cameraName); this->camera = camera; @@ -825,11 +822,8 @@ Item* PointCloudVisualizerItem::doCloneItem(CloneMap* /* cloneMap */) const void PointCloudVisualizerItem::setBodyItem(BodyItem* bodyItem, RangeCamera* rangeCamera) { - if(name().empty()){ - setName(rangeCamera->name()); - } + setName(rangeCamera->name()); this->rangeCamera = rangeCamera; - SubSensorVisualizerItem::setBodyItem(bodyItem); } @@ -917,11 +911,8 @@ RangeSensorVisualizerItem::RangeSensorVisualizerItem() void RangeSensorVisualizerItem::setBodyItem(BodyItem* bodyItem, RangeSensor* rangeSensor) { - if(name().empty()){ - setName(rangeSensor->name()); - } + setName(rangeSensor->name()); this->rangeSensor = rangeSensor; - SubSensorVisualizerItem::setBodyItem(bodyItem); } diff --git a/src/BodyPlugin/SensorVisualizerItem.h b/src/BodyPlugin/SensorVisualizerItem.h index 030e46235..a118ca65d 100644 --- a/src/BodyPlugin/SensorVisualizerItem.h +++ b/src/BodyPlugin/SensorVisualizerItem.h @@ -1,8 +1,3 @@ -/*! - @file - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_BODY_PLUGIN_SENSOR_VISUALIZER_ITEM_H #define CNOID_BODY_PLUGIN_SENSOR_VISUALIZER_ITEM_H diff --git a/src/BodyPlugin/SimulatorItem.cpp b/src/BodyPlugin/SimulatorItem.cpp index c5c11f2bc..d50ed533a 100644 --- a/src/BodyPlugin/SimulatorItem.cpp +++ b/src/BodyPlugin/SimulatorItem.cpp @@ -98,7 +98,8 @@ struct FunctionSet class ControllerInfo : public Referenced, public ControllerIO { public: - ControllerItemPtr controller; + ControllerItemPtr controllerItem; + vector continuousUpdateEntries; SimulationBody::Impl* simBodyImpl; Body* body_; SimulatorItem::Impl* simImpl; @@ -155,7 +156,7 @@ class SimulationLogEngine : public TimeSyncItemEngine TimeSyncItemEngineManager* manager; vector subEngines;; CollisionSeqEnginePtr collisionSeqEngine; - vector bodyItemEntries; + vector bodyItemEntries; bool doKeepPlayback; SimulationLogEngine(SimulatorItem::Impl* itemImpl); @@ -181,7 +182,7 @@ class SimulationBody::Impl SimulationBody* self; BodyPtr body_; BodyItemPtr bodyItem; - BodyItem::ContinuousKinematicUpdateEntry continuousKinematicUpdateEntry; + vector continuousUpdateEntries; vector controllerInfos; SimulatorItem::Impl* simImpl; @@ -242,6 +243,7 @@ class SimulatorItem::Impl : public QThread vector simBodiesWithBody; vector activeSimBodies; vector loggedControllerInfos; + vector continuousUpdateEntries; BodyItemToSimBodyMap simBodyMap; @@ -477,14 +479,27 @@ void SimulatorItem::initializeClass(ExtensionManager* ext) ItemTreeView::customizeContextMenu( [](SimulatorItem* item, MenuManager& menuManager, ItemFunctionDispatcher menuFunction){ menuManager.setPath("/").setPath(_("Simulation")); - menuManager.addItem(_("Start"))->sigTriggered().connect( - [item](){ item->startSimulation(); }); - menuManager.addItem(_("Pause"))->sigTriggered().connect( - [item](){ item->pauseSimulation(); }); - menuManager.addItem(_("Resume"))->sigTriggered().connect( - [item](){ item->restartSimulation(); }); - menuManager.addItem(_("Finish"))->sigTriggered().connect( - [item](){ item->stopSimulation(true); }); + auto start = menuManager.addItem(_("Start")); + auto pause = menuManager.addItem(_("Pause")); + auto resume = menuManager.addItem(_("Resume")); + auto finish = menuManager.addItem(_("Finish")); + + if(item->isRunning()){ + start->setEnabled(false); + pause->sigTriggered().connect([item]{ item->pauseSimulation(); }); + finish->sigTriggered().connect([item]{ item->stopSimulation(true); }); + } else { + start->sigTriggered().connect([item]{ item->startSimulation(); }); + pause->setEnabled(false); + finish->setEnabled(false); + } + if(item->isPausing()){ + pause->setEnabled(false); + resume->sigTriggered().connect([item]{ item->restartSimulation(); }); + } else { + resume->setEnabled(false); + } + menuManager.setPath("/"); menuManager.addSeparator(); menuFunction.dispatchAs(item); @@ -505,35 +520,35 @@ SimulatorItem* SimulatorItem::findActiveSimulatorItemFor(Item* item) } -ControllerInfo::ControllerInfo(ControllerItem* controller, SimulationBody::Impl* simBodyImpl) - : controller(controller), +ControllerInfo::ControllerInfo(ControllerItem* controllerItem, SimulationBody::Impl* simBodyImpl) + : controllerItem(controllerItem), simBodyImpl(simBodyImpl), body_(simBodyImpl->body_), simImpl(simBodyImpl->simImpl), isLogEnabled_(false), isSimulationFromInitialState_(simImpl->isSimulationFromInitialState) { - if(controller){ + if(controllerItem){ // ControllerInfo cannot directly set a simulator item to the controller item // because ControllerItem::setSimulatorItem is a private function. - simImpl->setSimulatorItemToControllerItem(controller); + simImpl->setSimulatorItemToControllerItem(controllerItem); } } ControllerInfo::~ControllerInfo() { - if(controller){ + if(controllerItem){ // ControllerInfo cannot directly reset a simulator item for the controller item // because ControllerItem::setSimulatorItem is a private function. - simImpl->resetSimulatorItemForControllerItem(controller); + simImpl->resetSimulatorItemForControllerItem(controllerItem); } } std::string ControllerInfo::controllerName() const { - return controller ? controller->name() : string(); + return controllerItem ? controllerItem->name() : string(); } @@ -585,8 +600,8 @@ bool ControllerInfo::enableLog() logBuf->setFrameRate(simImpl->worldFrameRate); logBufFrameOffset = 0; - string logName = simImpl->self->name() + "-" + controller->name(); - logItem = controller->findChildItem(logName); + string logName = simImpl->self->name() + "-" + controllerItem->name(); + logItem = controllerItem->findChildItem(logName); if(logItem){ logItem->resetSeq(); if(!logItem->isTemporary()){ @@ -594,11 +609,12 @@ bool ControllerInfo::enableLog() logItem->notifyUpdate(); } } else { - logItem = controller->createLogItem(); + logItem = controllerItem->createLogItem(); logItem->setTemporary(); logItem->setName(logName); - controller->addChildItem(logItem); + controllerItem->addChildItem(logItem); } + continuousUpdateEntries.push_back(logItem->startContinuousUpdate()); log = logItem->seq(); log->setNumFrames(0); log->setFrameRate(simImpl->worldFrameRate); @@ -661,13 +677,13 @@ void ControllerInfo::flushLog() bool ControllerInfo::isNoDelayMode() const { - return controller->isNoDelayMode(); + return controllerItem->isNoDelayMode(); } bool ControllerInfo::setNoDelayMode(bool on) { - controller->setNoDelayMode(on); + controllerItem->setNoDelayMode(on); return on; } @@ -723,7 +739,7 @@ int SimulationBody::numControllers() const ControllerItem* SimulationBody::controller(int index) const { if(index < static_cast(impl->controllerInfos.size())){ - return impl->controllerInfos[index]->controller; + return impl->controllerInfos[index]->controllerItem; } return nullptr; } @@ -739,6 +755,7 @@ bool SimulationBody::Impl::initialize(SimulatorItem* simulatorItem, BodyItem* bo { simImpl = simulatorItem->impl; this->bodyItem = bodyItem; + continuousUpdateEntries.push_back(bodyItem->startContinuousUpdate()); deviceStateConnections.disconnect(); controllerInfos.clear(); recordItemPrefix = simImpl->self->name() + "-" + bodyItem->name(); @@ -765,6 +782,7 @@ bool SimulationBody::Impl::initialize(SimulatorItem::Impl* simImpl, ControllerIt ControllerInfoPtr info = new ControllerInfo(controllerItem, this); if(controllerItem->initialize(info)){ + info->continuousUpdateEntries.push_back(controllerItem->startContinuousUpdate()); controllerInfos.push_back(info); initialized = true; } @@ -781,13 +799,13 @@ void SimulationBody::Impl::extractAssociatedItems() parentOfRecordItems = bodyItem; } else if(controllerInfos.size() == 1){ - parentOfRecordItems = controllerInfos.front()->controller; + parentOfRecordItems = controllerInfos.front()->controllerItem; } else { // find the common owner of all the controllers int minDepth = std::numeric_limits::max(); for(size_t i=0; i < controllerInfos.size(); ++i){ - Item* owner = controllerInfos[i]->controller->parentItem(); + Item* owner = controllerInfos[i]->controllerItem->parentItem(); int depth = 0; Item* item = owner; while(item && item != bodyItem){ @@ -869,15 +887,12 @@ void SimulationBody::Impl::initializeRecordBuffers() lastStateBuf.clear(); hasLastState = false; - if(!isDynamic){ - numLinksToRecord = 0; - numJointsToRecord = 0; - } else { + if(isDynamic){ numLinksToRecord = simImpl->isAllLinkPositionOutputMode ? body_->numLinks() : 1; numJointsToRecord = body_->numAllJoints(); - if(!simImpl->isRecordingEnabled){ - bodyMotionEngine = make_unique(bodyItem); - } + } else { + numLinksToRecord = 0; + numJointsToRecord = 0; } numDevicesToRecord = 0; @@ -899,17 +914,22 @@ void SimulationBody::Impl::initializeRecordBuffers() } } - if(numLinksToRecord || numJointsToRecord || numDevicesToRecord){ + if(numLinksToRecord == 0 && numJointsToRecord == 0 && numDevicesToRecord == 0){ + doRecord = false; + } else { doRecord = true; bodyStateBuf.setNumLinkPositionsHint(numLinksToRecord); bodyStateBuf.setNumJointDisplacementsHint(numJointsToRecord); bodyStateBuf.setNumDeviceStatesHint(numDevicesToRecord); bodyStateBuf.setFrameRate(simImpl->worldFrameRate); + // This buf always has the first element to keep unchanged device states bodyStateBuf.appendAllocatedFrame(); currentBodyStateBufIndex = 1; - } else { - doRecord = false; + + if(!simImpl->isRecordingEnabled){ + bodyMotionEngine = make_unique(bodyItem); + } } } @@ -948,6 +968,7 @@ void SimulationBody::Impl::initializeRecordItems() doAddMotionItem = true; } + continuousUpdateEntries.push_back(motionItem->startContinuousUpdate()); motion = motionItem->motion(); motion->setNumFrames(0); motion->setOffsetTime(0.0); @@ -1038,9 +1059,9 @@ void SimulationBody::Impl::bufferRecords() multiplexBody = multiplexBody->nextMultiplexBody(); } } - - ++currentBodyStateBufIndex; } + + ++currentBodyStateBufIndex; } } @@ -1235,7 +1256,7 @@ SimulatorItem::Impl::Impl(SimulatorItem* self) recordingMode.setSymbol(FullRecording, N_("full")); recordingMode.setSymbol(TailRecording, N_("tail")); recordingMode.setSymbol(NoRecording, N_("off")); - recordingMode.select(FullRecording); + recordingMode.select(TailRecording); timeRangeMode.setSymbol(UnlimitedTime, N_("Unlimited")); timeRangeMode.setSymbol(SpecifiedTime, N_("Specified time")); @@ -1247,7 +1268,7 @@ SimulatorItem::Impl::Impl(SimulatorItem* self) realtimeSyncMode.setSymbol(ConservativeRealtimeSync, N_("On (Conservative)")); realtimeSyncMode.select(CompensatoryRealtimeSync); - timeLength = 180.0; // 3 min. + timeLength = 300.0; // 5 min. useControllerThreadsProperty = true; isActiveControlTimeRangeMode = false; isAllLinkPositionOutputMode = true; @@ -1589,6 +1610,7 @@ void SimulatorItem::Impl::clearSimulation() simBodiesWithBody.clear(); activeSimBodies.clear(); loggedControllerInfos.clear(); + continuousUpdateEntries.clear(); simBodyMap.clear(); preDynamicsFunctions.clear(); @@ -1773,10 +1795,10 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) } bodyItem->notifyKinematicStateChange(); - } else if(auto controller = dynamic_cast(targetItem.get())){ + } else if(auto controllerItem = dynamic_cast(targetItem.get())){ // ControllerItem which is not associated with a body SimulationBodyPtr simBody = new SimulationBody(nullptr); - if(simBody->impl->initialize(this, controller)){ + if(simBody->impl->initialize(this, controllerItem)){ allSimBodies.push_back(simBody); } } else if(auto script = dynamic_cast(targetItem.get())){ @@ -1815,6 +1837,7 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) mv->putln(formatR(_("SubSimulatorItem \"{}\" is disabled."), item->displayName())); } if(initialized){ + continuousUpdateEntries.push_back(item->startContinuousUpdate()); ++p; } else { p = subSimulatorItems.erase(p); @@ -1871,6 +1894,8 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) collisionSeq->setNumFrames(1); CollisionSeq::Frame frame0 = collisionSeq->frame(0); frame0[0] = std::make_shared(); + + continuousUpdateEntries.push_back(collisionSeqItem->startContinuousUpdate()); } for(auto& simBody : allSimBodies){ @@ -1880,23 +1905,24 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) auto iter = controllerInfos.begin(); while(iter != controllerInfos.end()){ auto& info = *iter; - ControllerItem* controller = info->controller; + ControllerItem* controllerItem = info->controllerItem; bool ready = false; if(body){ - ready = controller->start(); + ready = controllerItem->start(); if(!ready){ mv->putln(formatR(_("{0} for {1} failed to start."), - controller->displayName(), simBodyImpl->bodyItem->displayName()), + controllerItem->displayName(), simBodyImpl->bodyItem->displayName()), MessageView::Warning); } } else { - ready = controller->start(); + ready = controllerItem->start(); if(!ready){ - mv->putln(formatR(_("{} failed to start."), controller->displayName()), + mv->putln(formatR(_("{} failed to start."), controllerItem->displayName()), MessageView::Warning); } } if(ready){ + info->continuousUpdateEntries.push_back(controllerItem->startContinuousUpdate()); activeControllerInfos.push_back(info); ++iter; } else { @@ -1954,7 +1980,7 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) worldLogFileItem->outputBodyHeader(activeSimBodies[i]->impl->body_->name()); } worldLogFileItem->endHeaderOutput(); - worldLogFileItem->notifyUpdate(); + worldLogFileItem->notifyUpdateWithProjectFileConsistency(); nextLogFrame = 0; nextLogTime = 0.0; double r = worldLogFileItem->recordingFrameRate(); @@ -1963,8 +1989,12 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) } logTimeStep = 1.0 / r; } + continuousUpdateEntries.push_back(worldLogFileItem->startContinuousUpdate()); } + continuousUpdateEntries.push_back(worldItem->startContinuousUpdate()); + continuousUpdateEntries.push_back(self->startContinuousUpdate()); + logEngine->startOngoingTimeUpdate(0.0); flushRecords(); start(); @@ -1974,11 +2004,6 @@ bool SimulatorItem::Impl::initializeSimulation(bool doReset) sigSimulationStarted(); - // For blocking manual user operations for modifying body kinematic state using the builtin GUIs - for(auto& simBody : simBodiesWithBody){ - auto simpl = simBody->impl; - simpl->continuousKinematicUpdateEntry = simpl->bodyItem->startContinuousKinematicUpdate(); - } if(isSceneViewEditModeBlockedDuringSimulation){ SceneView::blockEditModeForAllViews(self); } @@ -2172,21 +2197,21 @@ bool SimulatorItem::Impl::stepSimulationMain() if(!useControllerThreads){ for(auto& info : activeControllerInfos){ - auto& controller = info->controller; - controller->input(); - doContinue |= controller->control(); - if(controller->isNoDelayMode()){ - controller->output(); + auto& controllerItem = info->controllerItem; + controllerItem->input(); + doContinue |= controllerItem->control(); + if(controllerItem->isNoDelayMode()){ + controllerItem->output(); } } } else { bool hasNoDelayModeControllers = false; for(auto& info : activeControllerInfos){ - auto& controller = info->controller; - if(controller->isNoDelayMode()){ + auto& controllerItem = info->controllerItem; + if(controllerItem->isNoDelayMode()){ hasNoDelayModeControllers = true; } - info->controller->input(); + info->controllerItem->input(); { std::lock_guard lock(info->controlMutex); info->isControlRequested = true; @@ -2197,11 +2222,11 @@ bool SimulatorItem::Impl::stepSimulationMain() // Todo: Process the controller that finishes control earlier first to // reduce the total elapsed time before finishing all the output functions. for(auto& info : activeControllerInfos){ - if(info->controller->isNoDelayMode()){ + if(info->controllerItem->isNoDelayMode()){ if(info->waitForControlInThreadToFinish()){ doContinue = true; } - info->controller->output(); + info->controllerItem->output(); } } } @@ -2217,7 +2242,7 @@ bool SimulatorItem::Impl::stepSimulationMain() if(useControllerThreads){ for(auto& info : activeControllerInfos){ - if(!info->controller->isNoDelayMode()){ + if(!info->controllerItem->isNoDelayMode()){ if(info->waitForControlInThreadToFinish()){ doContinue = true; } @@ -2228,8 +2253,8 @@ bool SimulatorItem::Impl::stepSimulationMain() postDynamicsFunctions.call(); for(auto& info : activeControllerInfos){ - if(!info->controller->isNoDelayMode()){ - info->controller->output(); + if(!info->controllerItem->isNoDelayMode()){ + info->controllerItem->output(); } } @@ -2269,7 +2294,7 @@ void ControllerInfo::concurrentControlLoop() } } - bool doContinue = controller->control(); + bool doContinue = controllerItem->control(); { std::lock_guard lock(controlMutex); @@ -2434,6 +2459,7 @@ void SimulatorItem::Impl::stopSimulation(bool isForced, bool doSync) } isForcedToStopSimulation = isForced; stopRequested = true; + pauseRequested = false; if(doSync){ wait(); @@ -2457,8 +2483,7 @@ void SimulatorItem::Impl::onSimulationLoopStopped(bool isForced) for(auto& simBody : allSimBodies){ for(auto& info : simBody->impl->controllerInfos){ - auto& controller = info->controller; - controller->stop(); + info->controllerItem->stop(); } } self->finalizeSimulation(); @@ -3014,7 +3039,7 @@ void SimulationLogEngine::setupBodyItems(bool doStartPlayback) bodyItem->notifyKinematicStateUpdate(false); } if(doStartPlayback){ - bodyItemEntries.push_back(bodyItem->startContinuousKinematicUpdate()); + bodyItemEntries.push_back(bodyItem->startContinuousUpdate()); } } } diff --git a/src/BodyPlugin/WorldLogFileItem.cpp b/src/BodyPlugin/WorldLogFileItem.cpp index 94ab40af0..daee07266 100644 --- a/src/BodyPlugin/WorldLogFileItem.cpp +++ b/src/BodyPlugin/WorldLogFileItem.cpp @@ -17,13 +17,16 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -372,15 +375,11 @@ class WorldLogFileEngine : public TimeSyncItemEngine { public: WorldLogFileItem* logItem; - WorldLogFileEngine(WorldLogFileItem* item) - : TimeSyncItemEngine(item) - { - logItem = item; - } - virtual bool onTimeChanged(double time) { - return logItem->recallStateAtTime(time); - } + WorldLogFileEngine(WorldLogFileItem* item); + virtual bool onTimeChanged(double time) override; + virtual double onPlaybackStopped(double time, bool isStoppedManually) override; }; +typedef ref_ptr WorldLogFileEnginePtr; typedef map ItemToItemMap; @@ -426,6 +425,7 @@ class WorldLogFileItem::Impl int currentDeviceStateCacheArrayIndex; vector doubleWriteBuf; + filesystem::path readFilePath; ifstream ifs; ReadBuf readBuf; ReadBuf readBuf2; @@ -435,10 +435,21 @@ class WorldLogFileItem::Impl double currentReadFrameTime; bool isCurrentFrameDataLoaded; bool isOverRange; - + vector bodyInfos; ScopedConnection worldSubTreeChangedConnection; bool isBodyInfoUpdateNeeded; + + WorldLogFileEnginePtr logEngine; + Timer* livePlaybackTimer; + int livePlaybackLastFramePos; + std::uintmax_t livePlaybackLogFileSize; + int livePlaybackReadInterval; // msec + double livePlaybackReadTimeout; // sec + QElapsedTimer livePlaybackReadTimeoutTimer; + bool doCheckLivePlaybackReadTimeout; + + MessageOut* mout; Impl(WorldLogFileItem* self); Impl(WorldLogFileItem* self, Impl& org); @@ -450,6 +461,7 @@ class WorldLogFileItem::Impl bool readTopHeader(); bool readFrameHeader(int pos); bool seek(double time); + bool seekToLivePlaybackLastFrame(); bool loadCurrentFrameData(); bool recallStateAtTime(double time); void readBodyStates(double time); @@ -471,6 +483,12 @@ class WorldLogFileItem::Impl int createArchiveItemMap(Item* item, ArchiveInfo& info); ItemPtr createArchiveModelItem(Item* modelItem, ArchiveInfo& info, bool isBodyItem); int replaceWithArchiveItems(Item* item, ItemToItemMap& orgItemToArchiveItemMap); + WorldLogFileEngine* getOrCreateLogEngine(); + bool setLivePlaybackReadInterval(int interval); + bool setLivePlaybackReadTimeout(double timeout); + void startLivePlayback(); + void stopLivePlayback(); + void onLivePlaybackTimerTimeOut(); }; } @@ -490,6 +508,8 @@ void WorldLogFileItem::initializeClass(ExtensionManager* ext) ItemTreeView::customizeContextMenu( [](WorldLogFileItem* item, MenuManager& menuManager, ItemFunctionDispatcher menuFunction){ menuManager.setPath("/"); + menuManager.addItem(_("Start Live Playback"))->sigTriggered().connect( + [item]{ item->impl->startLivePlayback(); }); menuManager.addItem(_("Save project as log playback archive"))->sigTriggered().connect( [item](){ item->impl->openDialogToSelectDirectoryToSavePlaybackArchive(); }); menuManager.setPath("/"); @@ -499,8 +519,8 @@ void WorldLogFileItem::initializeClass(ExtensionManager* ext) TimeSyncItemEngineManager::instance() ->registerFactory( - [](WorldLogFileItem* item, WorldLogFileEngine* engine0){ - return engine0 ? engine0 : new WorldLogFileEngine(item); + [](WorldLogFileItem* item, WorldLogFileEngine* /* engine0 */){ + return item->impl->getOrCreateLogEngine(); }); } @@ -521,6 +541,11 @@ WorldLogFileItem::Impl::Impl(WorldLogFileItem* self) isTimeStampSuffixEnabled = false; recordingFrameRate = 0.0; isBodyInfoUpdateNeeded = true; + livePlaybackTimer = nullptr; + livePlaybackLogFileSize = 0; + livePlaybackReadInterval = 10; + livePlaybackReadTimeout = 0.0; + mout = MessageOut::master(); } @@ -539,7 +564,13 @@ WorldLogFileItem::Impl::Impl(WorldLogFileItem* self, Impl& org) { isTimeStampSuffixEnabled = org.isTimeStampSuffixEnabled; recordingFrameRate = org.recordingFrameRate; + currentReadFramePos = 0; isBodyInfoUpdateNeeded = true; + livePlaybackTimer = nullptr; + livePlaybackLogFileSize = 0; + livePlaybackReadInterval = org.livePlaybackReadInterval; + livePlaybackReadTimeout = org.livePlaybackReadTimeout; + mout = MessageOut::master(); } @@ -551,7 +582,10 @@ WorldLogFileItem::~WorldLogFileItem() WorldLogFileItem::Impl::~Impl() { - + if(livePlaybackTimer){ + delete livePlaybackTimer; + livePlaybackTimer = nullptr; + } } @@ -686,9 +720,9 @@ bool WorldLogFileItem::Impl::readTopHeader() if(ifs.is_open()){ ifs.close(); } - string fname = fromUTF8(getActualFilename()); - if(filesystem::exists(fname)){ - ifs.open(fname.c_str(), ios::in | ios::binary); + readFilePath = filesystem::path(fromUTF8(getActualFilename())); + if(filesystem::exists(readFilePath)){ + ifs.open(readFilePath.string(), ios::in | ios::binary); if(ifs.is_open()){ readBuf.clear(); try { @@ -702,9 +736,8 @@ bool WorldLogFileItem::Impl::readTopHeader() } } catch(CorruptLogException&){ bodyNames.clear(); - MessageView::instance()->putln( - formatR(_("Log file of {0} is corrupt."), self->displayName()), - MessageView::Error); + MessageOut::master()->putErrorln( + formatR(_("Log file of {0} is corrupt."), self->displayName())); } } } @@ -735,11 +768,30 @@ bool WorldLogFileItem::Impl::readFrameHeader(int pos) ifs.seekg(currentReadFramePos); return false; } + + int prevFrameOffset; + double frameTime; + int dataSize; + try { + prevFrameOffset = readBuf.readSeekOffset(); + frameTime = readBuf.readFloat(); + dataSize = readBuf.readSeekOffset(); + } catch(CorruptLogException&){ + bodyNames.clear(); + mout->putErrorln(formatR(_("Log file of {0} is corrupt."), self->displayName())); + ifs.seekg(currentReadFramePos); + return false; + } + + if(!readBuf.checkSize(currentReadFrameDataSize)){ + ifs.seekg(currentReadFramePos); + return false; + } currentReadFramePos = pos; - prevReadFrameOffset = readBuf.readSeekOffset(); - currentReadFrameTime = readBuf.readFloat(); - currentReadFrameDataSize = readBuf.readSeekOffset(); + prevReadFrameOffset = prevFrameOffset; + currentReadFrameTime = frameTime; + currentReadFrameDataSize = dataSize; return true; } @@ -824,9 +876,7 @@ bool WorldLogFileItem::Impl::recallStateAtTime(double time) } } catch(CorruptLogException&){ - MessageView::instance()->putln( - formatR(_("Corrupt log at time {0} in {1}."), time, self->displayName()), - MessageView::Error); + mout->putErrorln(formatR(_("Corrupt log at time {0} in {1}."), time, self->displayName())); } return isValid; @@ -1232,28 +1282,36 @@ void WorldLogFileItem::doPutProperties(PutPropertyFunction& putProperty) FilePathProperty logFileProperty(filePath(), { string(_("World Log File (*.log)")) }); logFileProperty.setExistingFileMode(false); putProperty(_("Log file"), logFileProperty, - [&](const string& file){ return impl->setLogFile(file); }); + [this](const string& file){ return impl->setLogFile(file); }); putProperty(_("Actual log file"), FilePathProperty(impl->getActualFilename())); putProperty(_("Time-stamp suffix"), impl->isTimeStampSuffixEnabled, changeProperty(impl->isTimeStampSuffixEnabled)); putProperty(_("Recording frame rate"), impl->recordingFrameRate, changeProperty(impl->recordingFrameRate)); + putProperty.min(1)(_("Live playback read interval (ms)"), impl->livePlaybackReadInterval, + [this](int value){ return setLivePlaybackReadInterval(value); }); + putProperty.min(0.0)(_("Live playback read timeout"), impl->livePlaybackReadTimeout, + [this](double value){ return setLivePlaybackReadTimeout(value); }); } bool WorldLogFileItem::store(Archive& archive) { archive.writeFileInformation(this); - archive.write("timeStampSuffix", impl->isTimeStampSuffixEnabled); - archive.write("recordingFrameRate", impl->recordingFrameRate); + archive.write("time_stamp_suffix", impl->isTimeStampSuffixEnabled); + archive.write("recording_frame_rate", impl->recordingFrameRate); + archive.write("live_playback_read_interval_ms", impl->livePlaybackReadInterval); + archive.write("live_playback_read_timeout", impl->livePlaybackReadTimeout); return true; } bool WorldLogFileItem::restore(const Archive& archive) { - archive.read("timeStampSuffix", impl->isTimeStampSuffixEnabled); - archive.read("recordingFrameRate", impl->recordingFrameRate); + archive.read({"time_stamp_suffix", "timeStampSuffix" }, impl->isTimeStampSuffixEnabled); + archive.read({"recording_frame_rate", "recordingFrameRate" }, impl->recordingFrameRate); + setLivePlaybackReadInterval(archive.get("live_playback_read_interval_ms", impl->livePlaybackReadInterval)); + setLivePlaybackReadTimeout(archive.get("live_playback_read_timeout", impl->livePlaybackReadTimeout)); std::string filename; if(archive.read({ "file", "filename" }, filename)){ @@ -1271,7 +1329,7 @@ void WorldLogFileItem::Impl::openDialogToSelectDirectoryToSavePlaybackArchive() dialog.setFileMode(QFileDialog::AnyFile); dialog.setLabelText(QFileDialog::Accept, _("Save")); dialog.setLabelText(QFileDialog::Reject, _("Cancel")); - dialog.updatePresetDirectories(); + dialog.updatePresetDirectories(true); QStringList filters; filters << _("Project files (*.cnoid)"); @@ -1328,9 +1386,7 @@ void WorldLogFileItem::Impl::saveProjectAsPlaybackArchive(const string& projectF return; } - auto mv = MessageView::instance(); - mv->putln(_("Creating the project for the log playback ...")); - mv->flush(); + mout->putln(_("Creating the project for the log playback ...")); auto projectFilePath = filesystem::absolute(fromUTF8(projectFile), ec); if(ec){ @@ -1449,10 +1505,9 @@ ItemPtr WorldLogFileItem::Impl::createArchiveModelItem(Item* modelItem, ArchiveI filesystem::create_directories(modelDirPath, ec); if(ec){ - MessageView::instance()->putln( + mout->putErrorln( formatR(_("Directory \"{0}\" cannot be created: {1}."), - toUTF8(modelDirPath.filename().string()), ec.message()), - MessageView::Error); + toUTF8(modelDirPath.filename().string()), ec.message())); } else { archiveModelItem = modelItem->clone(); auto filePath = modelDirPath / nativeBaseName; @@ -1526,3 +1581,172 @@ int WorldLogFileItem::Impl::replaceWithArchiveItems(Item* item, ItemToItemMap& o return numValidItems; } + + +WorldLogFileEngine* WorldLogFileItem::Impl::getOrCreateLogEngine() +{ + if(!logEngine){ + logEngine = new WorldLogFileEngine(self); + } + return logEngine; +} + + +bool WorldLogFileItem::setLivePlaybackReadInterval(int interval) +{ + return impl->setLivePlaybackReadInterval(interval); +} + + +bool WorldLogFileItem::Impl::setLivePlaybackReadInterval(int interval) +{ + if(interval >= 1){ + if(interval != livePlaybackReadInterval){ + livePlaybackReadInterval = interval; + if(livePlaybackTimer){ + bool isActive = livePlaybackTimer->isActive(); + if(isActive){ + livePlaybackTimer->stop(); + } + livePlaybackTimer->setInterval(interval); + if(isActive){ + livePlaybackTimer->start(); + } + } + } + return true; + } + return false; +} + + +bool WorldLogFileItem::setLivePlaybackReadTimeout(double timeout) +{ + if(timeout >= 0.0){ + impl->livePlaybackReadTimeout = timeout; + return true; + } + return false; +} + + +void WorldLogFileItem::startLivePlayback() +{ + impl->startLivePlayback(); +} + + +void WorldLogFileItem::Impl::startLivePlayback() +{ + livePlaybackLastFramePos = 0; + seekToLivePlaybackLastFrame(); + double time = currentReadFrameTime; + if(time < 0.0){ + time = 0.0; + } + + getOrCreateLogEngine()->startOngoingTimeUpdate(time); + + if(!livePlaybackTimer){ + livePlaybackTimer = new Timer; + livePlaybackTimer->sigTimeout().connect( + [this]{ onLivePlaybackTimerTimeOut(); }); + livePlaybackTimer->setInterval(10); + } + + doCheckLivePlaybackReadTimeout = false; + + livePlaybackTimer->start(); +} + + +void WorldLogFileItem::stopLivePlayback() +{ + impl->stopLivePlayback(); +} + + +void WorldLogFileItem::Impl::stopLivePlayback() +{ + if(livePlaybackTimer){ + livePlaybackTimer->stop(); + } + if(logEngine){ + logEngine->stopOngoingTimeUpdate(); + } +} + + +void WorldLogFileItem::Impl::onLivePlaybackTimerTimeOut() +{ + if(seekToLivePlaybackLastFrame()){ + logEngine->updateOngoingTime(currentReadFrameTime); + + if(livePlaybackReadTimeout > 0.0){ + doCheckLivePlaybackReadTimeout = true; + livePlaybackReadTimeoutTimer.start(); + } + } else { + if(doCheckLivePlaybackReadTimeout){ + if(livePlaybackReadTimeoutTimer.elapsed() >= livePlaybackReadTimeout * 1000.0){ + stopLivePlayback(); + } + } + } +} + + +//! \return true if a new frame is found +bool WorldLogFileItem::Impl::seekToLivePlaybackLastFrame() +{ + if(currentReadFramePos == 0){ + if(!readTopHeader()){ + return false; + } + } + int framePos = currentReadFramePos; + while(true){ + if(!readFrameHeader(framePos)){ + break; + } + framePos += frameHeaderSize + currentReadFrameDataSize; + } + + bool hasNewFrames = currentReadFramePos > livePlaybackLastFramePos; + livePlaybackLastFramePos = currentReadFramePos; + + stdx::error_code ec; + std::uintmax_t prevFileSize = livePlaybackLogFileSize; + livePlaybackLogFileSize = filesystem::file_size(readFilePath, ec); + + if(!hasNewFrames){ + if(livePlaybackLogFileSize < prevFileSize || prevFileSize < 0){ + readTopHeader(); + return seekToLivePlaybackLastFrame(); + } + } + + return hasNewFrames; +} + + +WorldLogFileEngine::WorldLogFileEngine(WorldLogFileItem* item) + : TimeSyncItemEngine(item) +{ + logItem = item; +} + + +bool WorldLogFileEngine::onTimeChanged(double time) +{ + return logItem->recallStateAtTime(time); +} + + +double WorldLogFileEngine::onPlaybackStopped(double time, bool /* isStoppedManually */) +{ + if(isUpdatingOngoingTime()){ + logItem->stopLivePlayback(); + } + return time; +} diff --git a/src/BodyPlugin/WorldLogFileItem.h b/src/BodyPlugin/WorldLogFileItem.h index a51e86ebe..2c6c30c67 100644 --- a/src/BodyPlugin/WorldLogFileItem.h +++ b/src/BodyPlugin/WorldLogFileItem.h @@ -47,6 +47,11 @@ class CNOID_EXPORT WorldLogFileItem : public Item bool recallStateAtTime(double time); void invalidateLastStateConsistency(); + bool setLivePlaybackReadInterval(int interval); + bool setLivePlaybackReadTimeout(double timeout); + void startLivePlayback(); + void stopLivePlayback(); + virtual void notifyUpdate() override; protected: diff --git a/src/BodyPlugin/ZMPSeqItem.cpp b/src/BodyPlugin/ZMPSeqItem.cpp index b2a9b601e..c59435add 100644 --- a/src/BodyPlugin/ZMPSeqItem.cpp +++ b/src/BodyPlugin/ZMPSeqItem.cpp @@ -2,6 +2,7 @@ #include "BodyItem.h" #include "BodyMotionItem.h" #include "BodyMotionEngine.h" +#include #include #include #include @@ -15,28 +16,28 @@ namespace { class ZMPSeqEngine : public TimeSyncItemEngine { shared_ptr seq; - weak_ref_ptr bodyItemRef; + LeggedBodyHelperPtr legged; ScopedConnection connection; public: ZMPSeqEngine(ZMPSeqItem* seqItem, BodyItem* bodyItem) : TimeSyncItemEngine(seqItem), - seq(seqItem->zmpseq()), - bodyItemRef(bodyItem) + seq(seqItem->zmpseq()) { + legged = getLeggedBodyHelper(bodyItem->body()); connection = seqItem->sigUpdated().connect([this](){ refresh(); }); } virtual bool onTimeChanged(double time) override { bool isValidTime = false; - if(auto bodyItem = bodyItemRef.lock()){ + if(legged->isValid()){ if(!seq->empty()){ const Vector3& zmp = seq->at(seq->clampFrameIndex(seq->frameOfTime(time), isValidTime)); if(seq->isRootRelative()){ - bodyItem->setZmp(bodyItem->body()->rootLink()->T() * zmp); + legged->setZmp(legged->body()->rootLink()->T() * zmp, true); } else { - bodyItem->setZmp(zmp); + legged->setZmp(zmp, true); } } } diff --git a/src/BodyPlugin/po/ja.po b/src/BodyPlugin/po/ja.po index b2940c91f..0f4576824 100644 --- a/src/BodyPlugin/po/ja.po +++ b/src/BodyPlugin/po/ja.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Choreonoid\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-28 14:22+0900\n" +"POT-Creation-Date: 2024-10-05 00:01+0900\n" "PO-Revision-Date: 2021-10-25 12:00+0900\n" "Language: ja\n" "MIME-Version: 1.0\n" @@ -702,87 +702,27 @@ msgstr "オン" msgid "BodySuperimposer" msgstr "ボディスーパーインポーザ" -msgid "Name:" -msgstr "名前:" - -msgid "Align with the builtin camera" -msgstr "組み込みカメラの位置に設定" - msgid "Align with the target origin" msgstr "ターゲット原点に設定" msgid "Coordinate System:" msgstr "座標系:" -msgid "Position:" -msgstr "位置:" - -msgid "Direction:" -msgstr "視線方向:" - -msgid "Up:" -msgstr "上方向:" - -msgid "Field of View:" -msgstr "視野角:" - -msgid "[deg]" -msgstr "" - -msgid "Near Clip:" -msgstr "近方クリップ:" - -msgid "Far Clip:" -msgstr "遠方クリップ:" - msgid "Parallel tracking" msgstr "平行トラッキング" -msgid "Interactive viewpoint change" -msgstr "インタラクティブな視点変更" - -msgid "Activate in the scene view" -msgstr "シーンビューで有効化" - -msgid "&OK" -msgstr "" - msgid "Camera Creation" msgstr "カメラの作成" msgid "{0} Camera" msgstr "{0}カメラ" -msgid "Camera Configuration" -msgstr "カメラの設定" - msgid "BodySyncCameraItem" msgstr "ボディ同期カメラ" -msgid "Camera configuration" -msgstr "カメラの設定" - -msgid "Perspective" -msgstr "透視投影" - -msgid "Orthographic" -msgstr "並行投影" - msgid "Target link" msgstr "対象リンク" -msgid "Camera type" -msgstr "カメラ タイプ" - -msgid "Field Of View" -msgstr "視野角" - -msgid "Near clip distance" -msgstr "近方クリップ距離" - -msgid "Far clip distance" -msgstr "遠方クリップ距離" - msgid "CollisionDetectionController" msgstr "干渉検出コントローラ" @@ -885,9 +825,15 @@ msgstr "レンジセンサ精度係数" msgid "Depth error" msgstr "深度エラー" +msgid "Background color" +msgstr "背景色" + msgid "Head light" msgstr "ヘッドライト" +msgid "World light" +msgstr "ワールドライト" + msgid "Additional lights" msgstr "追加のライト" @@ -1126,6 +1072,9 @@ msgstr "" msgid "angle" msgstr "角度" +msgid "[deg]" +msgstr "" + msgid "Penetration block depth" msgstr "貫通防止における許容深度" @@ -1800,6 +1749,9 @@ msgstr "ワールドログファイルアイテム" msgid "World Log" msgstr "ワールドログ" +msgid "Start Live Playback" +msgstr "ライブ再生の開始" + msgid "Save project as log playback archive" msgstr "ログ再生用アーカイブとしてプロジェクトを保存" @@ -1824,6 +1776,12 @@ msgstr "タイムスタンプ接尾辞" msgid "Recording frame rate" msgstr "記録フレームレート" +msgid "Live playback read interval (ms)" +msgstr "ライブ再生読込時間間隔(ミリ秒)" + +msgid "Live playback read timeout" +msgstr "ライブ再生読込タイムアウト" + msgid "Save" msgstr "保存" diff --git a/src/BodyPlugin/pybind11/PyBodyItem.cpp b/src/BodyPlugin/pybind11/PyBodyItem.cpp index eaa8599ab..5fc3dd66c 100644 --- a/src/BodyPlugin/pybind11/PyBodyItem.cpp +++ b/src/BodyPlugin/pybind11/PyBodyItem.cpp @@ -51,8 +51,6 @@ void exportBodyItem(py::module m) .def("clearCollisions", &BodyItem::clearCollisions) .def_property_readonly("centerOfMass", &BodyItem::centerOfMass) .def("doLegIkToMoveCm", &BodyItem::doLegIkToMoveCm) - .def_property_readonly("zmp", &BodyItem::zmp) - .def("setZmp", &BodyItem::setZmp) .def("setStance", &BodyItem::setStance) // deprecated @@ -62,7 +60,6 @@ void exportBodyItem(py::module m) .def("getCurrentBaseLink", &BodyItem::currentBaseLink) .def("getSigKinematicStateChanged", &BodyItem::sigKinematicStateChanged) .def("getCenterOfMass", &BodyItem::centerOfMass) - .def("getZmp", &BodyItem::zmp) // This function has been removed. The following is an incomplete wrapper. .def("makeBodyStatic", [](BodyItem& self){ self.body()->setRootLinkFixed(true); }) ; diff --git a/src/GLSceneRenderer/GLSceneRenderer.cpp b/src/GLSceneRenderer/GLSceneRenderer.cpp index 0eebc7f73..0b1c23171 100644 --- a/src/GLSceneRenderer/GLSceneRenderer.cpp +++ b/src/GLSceneRenderer/GLSceneRenderer.cpp @@ -86,7 +86,7 @@ GLSceneRenderer::Impl::Impl(GLSceneRenderer* self, SgGroup* sceneRoot) vp.h = invaid; self->aspectRatio_ = 1.0f; self->devicePixelRatio_ = 1.0f; - backgroundColor << 0.1f, 0.1f, 0.3f; // dark blue + backgroundColor << 0.0f, 0.0f, 0.0f; // black defaultColor << 1.0f, 1.0f, 1.0f; os_ = &nullout(); diff --git a/src/ManipulatorPlugin/MprPosition.cpp b/src/ManipulatorPlugin/MprPosition.cpp index 3316c9c48..03c9fa24d 100644 --- a/src/ManipulatorPlugin/MprPosition.cpp +++ b/src/ManipulatorPlugin/MprPosition.cpp @@ -30,7 +30,7 @@ bool checkJointDisplacementRanges(JointTraverse& traverse, MessageOut* mout) if(joint->hasActualJoint()){ if(joint->q() < joint->q_lower() || joint->q() > joint->q_upper()){ if(mout){ - mout->putError( + mout->putErrorln( formatR(_("The joint displacement of {0} is out of its movable range."), joint->jointName())); } @@ -418,7 +418,7 @@ bool MprIkPosition::fetch(BodyKinematicsKit* kinematicsKit, MessageOut* mout) int state = configuration->getCurrentNearSingularPointState(); if(state > 0){ if(mout){ - mout->putError( + mout->putErrorln( formatR(_("The current manipulator position is not valid: {0}."), configuration->getNearSingularPointFactorString(state))); } @@ -683,9 +683,9 @@ bool MprCompositePosition::fetch(KinematicBodySet* bodySet, MessageOut* mout) if(numFetchedElements != numPositionElements){ if(id().isValid()){ - mout->putWarning(formatR(_("Could not fetch all the elements of position {0}."), id().label())); + mout->putWarningln(formatR(_("Could not fetch all the elements of position {0}."), id().label())); } else { - mout->putWarning(_("Could not fetch all the elements of the position.")); + mout->putWarningln(_("Could not fetch all the elements of the position.")); } } } diff --git a/src/ManipulatorPlugin/MprProgram.cpp b/src/ManipulatorPlugin/MprProgram.cpp index 9db8d4f9e..6ec0f51a1 100644 --- a/src/ManipulatorPlugin/MprProgram.cpp +++ b/src/ManipulatorPlugin/MprProgram.cpp @@ -27,9 +27,10 @@ class MprProgram::Impl MprProgram* self; weak_ref_ptr holderStatement; MprPositionListPtr positionList; - Signal sigStatementUpdated; Signal sigStatementInserted; Signal sigStatementRemoved; + Signal sigStatementUpdated; + Signal sigStatementReferenceUpdated; std::string name; std::function positionTagGroupFinder; @@ -222,6 +223,18 @@ void MprProgram::notifyStatementUpdate(MprStatement* statement) const } +void MprProgram::notifyStatementReferenceUpdate(MprStatement* statement) const +{ + impl->sigStatementReferenceUpdated(statement); + + if(auto hs = holderStatement()){ + if(auto hp = hs->holderProgram()){ + hp->notifyStatementReferenceUpdate(statement); + } + } +} + + SignalProxy MprProgram::sigStatementInserted() { return impl->sigStatementInserted; @@ -241,6 +254,12 @@ SignalProxy MprProgram::sigStatementUpdated() } +SignalProxy MprProgram::sigStatementReferenceUpdated() +{ + return impl->sigStatementReferenceUpdated; +} + + MprStructuredStatement* MprProgram::holderStatement() const { return impl->holderStatement.lock(); diff --git a/src/ManipulatorPlugin/MprProgram.h b/src/ManipulatorPlugin/MprProgram.h index db92db886..145b38d2f 100644 --- a/src/ManipulatorPlugin/MprProgram.h +++ b/src/ManipulatorPlugin/MprProgram.h @@ -63,8 +63,10 @@ class CNOID_EXPORT MprProgram : public ClonableReferenced SignalProxy sigStatementInserted(); SignalProxy sigStatementRemoved(); SignalProxy sigStatementUpdated(); + SignalProxy sigStatementReferenceUpdated(); void notifyStatementUpdate(MprStatement* statement) const; + void notifyStatementReferenceUpdate(MprStatement* statement) const; MprStructuredStatement* holderStatement() const; bool isTopLevelProgram() const; diff --git a/src/ManipulatorPlugin/MprProgramItemBase.cpp b/src/ManipulatorPlugin/MprProgramItemBase.cpp index fe75a62d8..b1011f694 100644 --- a/src/ManipulatorPlugin/MprProgramItemBase.cpp +++ b/src/ManipulatorPlugin/MprProgramItemBase.cpp @@ -47,7 +47,7 @@ class MprProgramItemBase::Impl MprPosition* findPositionOrShowWarning(MprPositionStatement* statement, MessageOut* mout); BodyItemKinematicsKit* findKinematicsKit(); bool moveTo(MprPosition* position, bool doUpdateAll, MessageOut* mout); - bool superimposePosition(MprPosition* position, MessageOut* mout); + bool superimposePosition(MprPosition* position); bool touchupPosition(MprPosition* position, MessageOut* mout); }; @@ -107,11 +107,13 @@ void MprProgramItemBase::Impl::initialize() topLevelProgram->sigStatementRemoved().connect( [&](MprStatement*, MprProgram*){ - self->suggestFileUpdate(); }); + self->suggestFileUpdate(); + }); topLevelProgram->sigStatementUpdated().connect( [&](MprStatement*){ - self->suggestFileUpdate(); }); + self->suggestFileUpdate(); + }); } @@ -245,7 +247,7 @@ MprPosition* MprProgramItemBase::Impl::findPositionOrShowWarning(MprPositionStat { MprPosition* position = statement->position(); if(!position && mout){ - mout->putError( + mout->putErrorln( formatR(_("Position {0} is not found in {1}."), statement->positionLabel(), self->name()).c_str()); } @@ -343,7 +345,7 @@ bool MprProgramItemBase::Impl::moveTo(MprPosition* position, bool doUpdateAll, M } if(!errorMessage.empty() && mout){ - mout->putError(errorMessage); + mout->putErrorln(errorMessage); } return updated; @@ -353,15 +355,15 @@ bool MprProgramItemBase::Impl::moveTo(MprPosition* position, bool doUpdateAll, M bool MprProgramItemBase::superimposePosition(MprPositionStatement* statement, MessageOut* mout) { if(auto position = impl->findPositionOrShowWarning(statement, mout)){ - return impl->superimposePosition(position, mout); + return impl->superimposePosition(position); } return false; } -bool MprProgramItemBase::superimposePosition(MprPosition* position, MessageOut* mout) +bool MprProgramItemBase::superimposePosition(MprPosition* position, MessageOut* /* mout */) { - return impl->superimposePosition(position, mout); + return impl->superimposePosition(position); } @@ -369,7 +371,7 @@ bool MprProgramItemBase::superimposePosition(MprPosition* position, MessageOut* \todo Simplify the following implementation by using an independent BodySuperimposerAddons for each body item and updating the positions of all the boides simultaneously. */ -bool MprProgramItemBase::Impl::superimposePosition(MprPosition* position, MessageOut* mout) +bool MprProgramItemBase::Impl::superimposePosition(MprPosition* position) { if(!targetBodyItemSet){ return false; @@ -540,14 +542,13 @@ void MprProgramItemBase::doPutProperties(PutPropertyFunction& putProperty) bool MprProgramItemBase::store(Archive& archive) { - if(overwriteOrSaveWithDialog()){ - archive.writeFileInformation(this); + if(archive.saveItemToFile(this)){ if(impl->isStartupProgram){ archive.write("is_startup_program", true); } return true; } - return true; + return false; } diff --git a/src/ManipulatorPlugin/MprProgramViewBase.cpp b/src/ManipulatorPlugin/MprProgramViewBase.cpp index 9804a15aa..091439317 100644 --- a/src/ManipulatorPlugin/MprProgramViewBase.cpp +++ b/src/ManipulatorPlugin/MprProgramViewBase.cpp @@ -912,6 +912,11 @@ void MprProgramViewBase::Impl::setProgramItem(MprProgramItemBase* item) program->sigStatementUpdated().connect( [this](MprStatement* statement){ onStatementUpdated(statement); })); + + programConnections.add( + program->sigStatementReferenceUpdated().connect( + [this](MprStatement* statement){ + onStatementUpdated(statement); })); programNameLabel.setStyleSheet("font-weight: bold"); diff --git a/src/ManipulatorPlugin/MprStatementPanel.cpp b/src/ManipulatorPlugin/MprStatementPanel.cpp index bf6c7c064..bce2fb8f9 100644 --- a/src/ManipulatorPlugin/MprStatementPanel.cpp +++ b/src/ManipulatorPlugin/MprStatementPanel.cpp @@ -1,6 +1,7 @@ #include "MprStatementPanel.h" #include "MprProgramItemBase.h" #include +#include using namespace std; using namespace cnoid; @@ -14,7 +15,7 @@ class MprStatementPanel::Impl MprProgramItemBasePtr programItem; MprStatementPtr statement; function setCaption; - ScopedConnection statementUpdateConnection; + ScopedConnectionSet statementUpdateConnections; Impl(MprStatementPanel* self); }; @@ -56,14 +57,22 @@ void MprStatementPanel::activate impl->statement = statement; impl->setCaption = setCaption; - impl->statementUpdateConnection = + impl->statementUpdateConnections.add( programItem->program()->sigStatementUpdated().connect( [this](MprStatement* updated){ if(updated == impl->statement){ onStatementUpdated(); } - }); + })); + impl->statementUpdateConnections.add( + programItem->program()->sigStatementReferenceUpdated().connect( + [this](MprStatement* updated){ + if(updated == impl->statement){ + onStatementUpdated(); + } + })); + setEditable(statement->holderProgram()->isEditable()); onActivated(); @@ -77,7 +86,7 @@ void MprStatementPanel::deactivate() impl->programItem.reset(); impl->statement.reset(); impl->setCaption = nullptr; - impl->statementUpdateConnection.disconnect(); + impl->statementUpdateConnections.disconnect(); } diff --git a/src/ManipulatorPlugin/MprTagTraceStatement.cpp b/src/ManipulatorPlugin/MprTagTraceStatement.cpp index f517294d4..bcf4fc0a4 100644 --- a/src/ManipulatorPlugin/MprTagTraceStatement.cpp +++ b/src/ManipulatorPlugin/MprTagTraceStatement.cpp @@ -65,7 +65,7 @@ std::string MprTagTraceStatement::label(int index) const void MprTagTraceStatement::setTagGroup -(PositionTagGroup* tags, bool doUpdateTagGroupName, bool doUpdateTagTraceProgram, bool doNotifyUpdate) +(PositionTagGroup* tags, bool doUpdateTagGroupName, bool doUpdateTagTraceProgram, bool doNotifyReferenceUpdate) { if(tags != tagGroup_){ tagGroupConnections.disconnect(); @@ -83,8 +83,10 @@ void MprTagTraceStatement::setTagGroup if(doUpdateTagTraceProgram){ updateTagTraceProgram(); } - if(doNotifyUpdate){ - notifyUpdate(); + if(doNotifyReferenceUpdate){ + if(auto holder = holderProgram()){ + holder->notifyStatementReferenceUpdate(this); + } } } } diff --git a/src/ManipulatorPlugin/MprTagTraceStatement.h b/src/ManipulatorPlugin/MprTagTraceStatement.h index 35fda1376..8518d66c7 100644 --- a/src/ManipulatorPlugin/MprTagTraceStatement.h +++ b/src/ManipulatorPlugin/MprTagTraceStatement.h @@ -29,7 +29,8 @@ class CNOID_EXPORT MprTagTraceStatement : public MprStructuredStatement const std::string& originalTagGroupName() const { return tagGroupName(); } void setTagGroup( - PositionTagGroup* tags, bool doUpdateTagGroupName, bool doUpdateTagTraceProgram, bool doNotifyUpdate); + PositionTagGroup* tags, bool doUpdateTagGroupName, bool doUpdateTagTraceProgram, + bool doNotifyReferenceUpdate); PositionTagGroup* tagGroup() { return tagGroup_; } //! The position of the tag group on the base coordinate frame diff --git a/src/MocapPlugin/SkeletonMotionItem.cpp b/src/MocapPlugin/SkeletonMotionItem.cpp index 45490ddca..c0ad81ef2 100644 --- a/src/MocapPlugin/SkeletonMotionItem.cpp +++ b/src/MocapPlugin/SkeletonMotionItem.cpp @@ -492,10 +492,10 @@ void SceneBone::setPosition(const Vector3f& p0, const Vector3f& p1) SkeletonMotionItem::Location::Location(SkeletonMotionItem* item) - : LocationProxy(GlobalLocation), + : LocationProxy(item, GlobalLocation), item(item) { - + setNameDependencyOnItemName(); } std::string SkeletonMotionItem::Location::getName() const diff --git a/src/PoseSeqPlugin/HumanoidPoseFetchView.cpp b/src/PoseSeqPlugin/HumanoidPoseFetchView.cpp index eb8b8c8c9..4e38b0caa 100644 --- a/src/PoseSeqPlugin/HumanoidPoseFetchView.cpp +++ b/src/PoseSeqPlugin/HumanoidPoseFetchView.cpp @@ -1021,7 +1021,7 @@ void HumanoidPoseFetchView::Impl::setBodyPartLinks if(group->checkIfLink(i)){ if(auto link = body->link(group->linkIndex(i))){ if(!prefix.empty()){ - if(link->name().find_first_of(prefix) != 0){ + if(link->name().find(prefix) != 0){ continue; } } diff --git a/src/PoseSeqPlugin/PoseSeq.cpp b/src/PoseSeqPlugin/PoseSeq.cpp index 02ffa8766..e06dec1a9 100644 --- a/src/PoseSeqPlugin/PoseSeq.cpp +++ b/src/PoseSeqPlugin/PoseSeq.cpp @@ -61,6 +61,15 @@ void PoseSeq::setName(const std::string& name) } +void PoseSeq::clear() +{ + auto it = poses.begin(); + while(it != poses.end()){ + it = erase(it); + } +} + + bool PoseSeq::load(const std::string& filename, const Body* body) { errorMessage_.clear(); diff --git a/src/PoseSeqPlugin/PoseSeq.h b/src/PoseSeqPlugin/PoseSeq.h index dea23407d..cf42e3bf6 100644 --- a/src/PoseSeqPlugin/PoseSeq.h +++ b/src/PoseSeqPlugin/PoseSeq.h @@ -51,6 +51,8 @@ class CNOID_EXPORT PoseSeq : public ClonableReferenced return poses.size(); } + void clear(); + iterator begin(){ return poses.begin(); } diff --git a/src/PoseSeqPlugin/PoseSeqEngine.cpp b/src/PoseSeqPlugin/PoseSeqEngine.cpp index 47bbc6c68..e52fc11bd 100644 --- a/src/PoseSeqPlugin/PoseSeqEngine.cpp +++ b/src/PoseSeqPlugin/PoseSeqEngine.cpp @@ -3,6 +3,7 @@ #include "BodyMotionGenerationBar.h" #include #include +#include #include using namespace cnoid; @@ -16,6 +17,7 @@ class PoseSeqEngine : public TimeSyncItemEngine PoseSeqInterpolatorPtr interpolator; BodyMotionGenerationBar* bodyMotionGenerationBar; LinkTraverse fkTraverse; + LeggedBodyHelperPtr legged; ScopedConnectionSet connections; PoseSeqEngine(PoseSeqItem* poseSeqItem, BodyItem* bodyItem) @@ -24,6 +26,7 @@ class PoseSeqEngine : public TimeSyncItemEngine { interpolator = poseSeqItem->interpolator(); bodyMotionGenerationBar = BodyMotionGenerationBar::instance(); + legged = getLeggedBodyHelper(bodyItem->body()); connections.add( poseSeqItem->sigUpdated().connect([this](){ refresh(); })); @@ -58,7 +61,7 @@ class PoseSeqEngine : public TimeSyncItemEngine auto zmp = interpolator->ZMP(); if(zmp){ - bodyItem->setZmp(*zmp); + legged->setZmp(*zmp, true); } bodyItem->notifyKinematicStateChange(true); diff --git a/src/PoseSeqPlugin/PoseSeqItem.cpp b/src/PoseSeqPlugin/PoseSeqItem.cpp index 8b79c7b98..bce67855f 100644 --- a/src/PoseSeqPlugin/PoseSeqItem.cpp +++ b/src/PoseSeqPlugin/PoseSeqItem.cpp @@ -1006,8 +1006,13 @@ bool PoseSeqItem::store(Archive& archive) bool PoseSeqItem::Impl::store(Archive& archive) { - if(self->overwriteOrSaveWithDialog()){ - archive.writeFileInformation(self); + bool stored = false; + if(seq->empty() && self->filePath().empty()){ + stored = true; + } else { + stored = archive.saveItemToFile(self); + } + if(stored){ archive.write("bar_length", barLength); if(bodyMotionItem->isSelected()){ archive.write("is_body_motion_selected", true); @@ -1015,9 +1020,8 @@ bool PoseSeqItem::Impl::store(Archive& archive) if(bodyMotionItem->isChecked()){ archive.write("is_body_motion_checked", true); } - return true; } - return false; + return stored; } @@ -1029,7 +1033,13 @@ bool PoseSeqItem::restore(const Archive& archive) bool PoseSeqItem::Impl::restore(const Archive& archive) { - if(archive.loadFileTo(self)){ + bool hasFileInformation; + bool loaded = archive.loadFileTo(self, hasFileInformation); + if(loaded || !hasFileInformation){ + if(!loaded){ + seq->clear(); + self->setConsistentWithFile(true); + } archive.read({ "bar_length", "barLength" }, barLength); if(archive.get("is_body_motion_selected", false)){ bodyMotionItem->setSelected(true); diff --git a/src/PoseSeqPlugin/PoseSeqViewBase.cpp b/src/PoseSeqPlugin/PoseSeqViewBase.cpp index b76125d9c..32758ab0d 100644 --- a/src/PoseSeqPlugin/PoseSeqViewBase.cpp +++ b/src/PoseSeqPlugin/PoseSeqViewBase.cpp @@ -465,9 +465,9 @@ bool PoseSeqViewBase::toggleZmp(BodyKeyPose* pose, bool on) { bool modified = false; if(on){ - const Vector3& zmp = currentBodyItem->zmp(); + auto zmp = legged->zmp(); if(!pose->isZmpValid() || zmp != pose->zmp()){ - pose->setZmp(currentBodyItem->zmp()); + pose->setZmp(zmp); modified = true; } } else { @@ -684,6 +684,7 @@ void PoseSeqViewBase::setCurrentPoseSeqItem(PoseSeqItem* poseSeqItem) seq.reset(); currentBodyItem.reset(); body.reset(); + legged.reset(); if(!poseSeqItem){ if(linkTreeWidget->bodyItem()){ @@ -703,6 +704,7 @@ void PoseSeqViewBase::setCurrentPoseSeqItem(PoseSeqItem* poseSeqItem) currentBodyItem = poseSeqItem->findOwnerItem(); if(currentBodyItem){ body = currentBodyItem->body(); + legged = getLeggedBodyHelper(body); } linkTreeWidget->setBodyItem(currentBodyItem); if(currentBodyItem){ @@ -1316,7 +1318,7 @@ PoseSeq::iterator PoseSeqViewBase::insertBodyKeyPose() } if(isChecked(zmpRow, validPartColumn)){ - pose->setZmp(currentBodyItem->zmp()); + pose->setZmp(legged->zmp()); hasValidPart = true; if(isChecked(zmpRow, stationaryPointColumn)){ pose->setZmpStationaryPoint(); @@ -1435,7 +1437,7 @@ bool PoseSeqViewBase::setCurrentBodyStateToPose(BodyKeyPose* pose, bool onlySele } if(pose->isZmpValid()){ - const Vector3& zmp = currentBodyItem->zmp(); + auto zmp = legged->zmp(); if(zmp != pose->zmp()){ pose->setZmp(zmp); updated = true; diff --git a/src/PoseSeqPlugin/PoseSeqViewBase.h b/src/PoseSeqPlugin/PoseSeqViewBase.h index ad30d2a1c..f2c6c3a83 100644 --- a/src/PoseSeqPlugin/PoseSeqViewBase.h +++ b/src/PoseSeqPlugin/PoseSeqViewBase.h @@ -6,16 +6,17 @@ #include #include #include -#include #include #include #include #include +#include +#include +#include #include #include #include #include -#include #include #include #include @@ -43,6 +44,7 @@ class PoseSeqViewBase bool isSelectedPoseMoving; BodyItemPtr currentBodyItem; BodyPtr body; + LeggedBodyHelperPtr legged; double currentTime; double timeScale; Signal sigCurrentTimeChanged; diff --git a/src/SceneEffectsPlugin/ParticleSystem.cpp b/src/SceneEffectsPlugin/ParticleSystem.cpp index 69dcedf35..dfce13de0 100644 --- a/src/SceneEffectsPlugin/ParticleSystem.cpp +++ b/src/SceneEffectsPlugin/ParticleSystem.cpp @@ -16,6 +16,7 @@ ParticleSystem::ParticleSystem() initialSpeedVariation_ = 0.1f; emissionRange_ = static_cast(PI / 3.0); acceleration_.setZero(); + tintColor_.setOnes(); } @@ -30,6 +31,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem& org) initialSpeedVariation_ = org.initialSpeedVariation_; emissionRange_ = org.emissionRange_; acceleration_ = org.acceleration_; + tintColor_ = org.tintColor_; } @@ -49,6 +51,9 @@ void ParticleSystem::readParameters(const Mapping* info) info->read({ "initial_speed_variation", "initialSpeedVariation" }, initialSpeedVariation_); info->readAngle({ "emission_range", "emissionRange" }, emissionRange_); read(info, "acceleration", acceleration_); + if(!read(info, "tint_color", tintColor_)){ + tintColor_.setOnes(); + } } @@ -62,5 +67,8 @@ void ParticleSystem::writeParameters(Mapping* info) const info->write("initial_speed_variation", initialSpeedVariation_); info->write("emission_range", degree(emissionRange_)); write(info, "acceleration", acceleration_); + if(!tintColor_.isApprox(Vector3f::Ones())){ + write(info, "tint_color", tintColor_); + } } diff --git a/src/SceneEffectsPlugin/ParticleSystem.h b/src/SceneEffectsPlugin/ParticleSystem.h index 698a482c1..ff6844e2f 100644 --- a/src/SceneEffectsPlugin/ParticleSystem.h +++ b/src/SceneEffectsPlugin/ParticleSystem.h @@ -2,12 +2,13 @@ #define CNOID_SCENE_EFFECTS_PLUGIN_PARTICLE_SYSTEMS_H #include +#include "exportdecl.h" namespace cnoid { class Mapping; -class ParticleSystem +class CNOID_EXPORT ParticleSystem { public: ParticleSystem(); @@ -41,6 +42,9 @@ class ParticleSystem const Vector3f& acceleration() const { return acceleration_; } void setAcceleration(const Vector3f& a){ acceleration_ = a; } + const Vector3f& tintColor() const { return tintColor_; } + void setTintColor(const Vector3f& c){ tintColor_ = c; } + void readParameters(const Mapping* info); void writeParameters(Mapping* info) const; @@ -54,6 +58,7 @@ class ParticleSystem float initialSpeedVariation_; float emissionRange_; Vector3f acceleration_; + Vector3f tintColor_; }; } diff --git a/src/SceneEffectsPlugin/RainSnowDevice.cpp b/src/SceneEffectsPlugin/RainSnowDevice.cpp index 55e9006f7..b2d8fc048 100644 --- a/src/SceneEffectsPlugin/RainSnowDevice.cpp +++ b/src/SceneEffectsPlugin/RainSnowDevice.cpp @@ -1,8 +1,3 @@ -/** - @file - @author Shin'ichiro Nakaoka -*/ - #include "RainSnowDevice.h" #include "SceneRainSnow.h" #include "SceneEffectDeviceTypeRegistration.h" diff --git a/src/SceneEffectsPlugin/RainSnowDevice.h b/src/SceneEffectsPlugin/RainSnowDevice.h index 7a2925cc3..c6cac0c51 100644 --- a/src/SceneEffectsPlugin/RainSnowDevice.h +++ b/src/SceneEffectsPlugin/RainSnowDevice.h @@ -1,17 +1,13 @@ -/** - @file - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_SCENE_EFFECTS_PLUGIN_RAIN_SNOW_DEVICE_H #define CNOID_SCENE_EFFECTS_PLUGIN_RAIN_SNOW_DEVICE_H #include "ParticleSystem.h" #include +#include "exportdecl.h" namespace cnoid { -class RainSnowDevice : public Device +class CNOID_EXPORT RainSnowDevice : public Device { public: void copyStateFrom(const RainSnowDevice& other); diff --git a/src/SceneEffectsPlugin/SceneSmoke.cpp b/src/SceneEffectsPlugin/SceneSmoke.cpp index 9639161c7..632f41335 100644 --- a/src/SceneEffectsPlugin/SceneSmoke.cpp +++ b/src/SceneEffectsPlugin/SceneSmoke.cpp @@ -1,8 +1,3 @@ -/** - @file - @author Shin'ichiro Nakaoka -*/ - #include "SceneEffectsPlugin.h" #include "SceneSmoke.h" #include "ParticlesProgram.h" @@ -25,11 +20,13 @@ class SmokeProgram : public ParticlesProgram GLint lifeTimeLocation; GLint accelLocation; + GLint tintColorLocation; // Variables to detect changes causing the buffer update int numParticles; float lifeTime; float emissionRange; + Vector3f tintColor; GLuint initVelBuffer; GLuint offsetTimeBuffer; @@ -79,7 +76,7 @@ SmokeProgram::SmokeProgram(GLSLSceneRenderer* renderer) ":/SceneEffectsPlugin/shader/Smoke.vert", ":/SceneEffectsPlugin/shader/Particles.frag") { - + tintColor.setOnes(); } @@ -92,6 +89,7 @@ bool SmokeProgram::initializeRendering(SceneParticles* particles) auto& glsl = glslProgram(); lifeTimeLocation = glsl.getUniformLocation("lifeTime"); accelLocation = glsl.getUniformLocation("accel"); + tintColorLocation = glsl.getUniformLocation("tintColor"); glGenBuffers(1, &initVelBuffer); glGenBuffers(1, &offsetTimeBuffer); @@ -177,6 +175,11 @@ void SmokeProgram::render(SceneSmoke* smoke) Vector3f accel = globalAttitude().transpose() * ps.acceleration(); glUniform3fv(accelLocation, 1, accel.data()); + if(!tintColor.isApprox(ps.tintColor())){ + tintColor = ps.tintColor(); + glUniform3fv(tintColorLocation, 1, tintColor.data()); + } + /* GLint blendSrc, blendDst; glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrc); diff --git a/src/SceneEffectsPlugin/SceneSmoke.h b/src/SceneEffectsPlugin/SceneSmoke.h index abe690a82..8010bccf2 100644 --- a/src/SceneEffectsPlugin/SceneSmoke.h +++ b/src/SceneEffectsPlugin/SceneSmoke.h @@ -1,7 +1,3 @@ -/** - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_SCENE_EFFECTS_PLUGIN_SCENE_SMOKE_H #define CNOID_SCENE_EFFECTS_PLUGIN_SCENE_SMOKE_H diff --git a/src/SceneEffectsPlugin/SmokeDevice.cpp b/src/SceneEffectsPlugin/SmokeDevice.cpp index e495dcb9b..5087c745d 100644 --- a/src/SceneEffectsPlugin/SmokeDevice.cpp +++ b/src/SceneEffectsPlugin/SmokeDevice.cpp @@ -1,8 +1,3 @@ -/** - @file - @author Shin'ichiro Nakaoka -*/ - #include "SmokeDevice.h" #include "SceneSmoke.h" #include "SceneEffectDeviceTypeRegistration.h" @@ -15,7 +10,7 @@ using namespace cnoid; namespace { -SceneEffectDeviceTypeRegistration snowDeviceRegistration("SmokeDevice");; +SceneEffectDeviceTypeRegistration smokeDeviceRegistration("SmokeDevice");; } @@ -100,7 +95,7 @@ void SmokeDevice::on(bool on) int SmokeDevice::stateSize() const { - return 11; + return 14; } @@ -119,6 +114,8 @@ const double* SmokeDevice::readState(const double* buf) ps.setEmissionRange(buf[i++]); ps.setAcceleration(Vector3f(buf[i], buf[i+1], buf[i+2])); i += 3; + ps.setTintColor(Vector3f(buf[i], buf[i+1], buf[i+2])); + i += 3; return buf + i; } @@ -140,6 +137,9 @@ double* SmokeDevice::writeState(double* out_buf) const out_buf[i++] = ps.acceleration()[0]; out_buf[i++] = ps.acceleration()[1]; out_buf[i++] = ps.acceleration()[2]; + out_buf[i++] = ps.tintColor()[0]; + out_buf[i++] = ps.tintColor()[1]; + out_buf[i++] = ps.tintColor()[2]; return out_buf + i; } diff --git a/src/SceneEffectsPlugin/SmokeDevice.h b/src/SceneEffectsPlugin/SmokeDevice.h index 304b76d42..96dec8342 100644 --- a/src/SceneEffectsPlugin/SmokeDevice.h +++ b/src/SceneEffectsPlugin/SmokeDevice.h @@ -1,17 +1,13 @@ -/** - @file - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_SCENE_EFFECTS_PLUGIN_SMOKE_DEVICE_H #define CNOID_SCENE_EFFECTS_PLUGIN_SMOKE_DEVICE_H #include "ParticleSystem.h" #include +#include "exportdecl.h" namespace cnoid { -class SmokeDevice : public Device +class CNOID_EXPORT SmokeDevice : public Device { public: SmokeDevice(); diff --git a/src/SceneEffectsPlugin/shader/Particles.frag b/src/SceneEffectsPlugin/shader/Particles.frag index 262e63d54..c7e21f27d 100644 --- a/src/SceneEffectsPlugin/shader/Particles.frag +++ b/src/SceneEffectsPlugin/shader/Particles.frag @@ -26,6 +26,7 @@ struct LightInfo { uniform LightInfo lights[10]; +uniform vec3 tintColor = vec3(1.0, 1.0, 1.0); uniform vec3 fogColor; uniform float maxFogDist; uniform float minFogDist; @@ -72,7 +73,7 @@ void main() { vec4 texColor = texture(particleTex, gl_PointCoord); - vec3 color = texColor.xyz; + vec3 color = texColor.xyz * tintColor; vec3 c = vec3(0.0); for(int i=0; i < numLights; ++i){ c += calcLightingColor(color, lights[i]); diff --git a/src/URDFBodyLoader/URDFBodyLoader.cpp b/src/URDFBodyLoader/URDFBodyLoader.cpp index f7ac58301..f42fc3096 100644 --- a/src/URDFBodyLoader/URDFBodyLoader.cpp +++ b/src/URDFBodyLoader/URDFBodyLoader.cpp @@ -681,6 +681,7 @@ bool URDFBodyLoader::Impl::readGeometryTag(const xml_node& geometryNode, ShapeDe description.length = geometryNode.child(CYLINDER).attribute(LENGTH).as_double(); } else if (!geometryNode.child(SPHERE).empty()) { + description.shapeType = ShapeDescription::Sphere; if (geometryNode.child(SPHERE).attribute(RADIUS).empty()) { os() << "Error: sphere radius is not defined"; return false; diff --git a/src/Util/AbstractSceneLoader.cpp b/src/Util/AbstractSceneLoader.cpp index eb0ebafbd..c9a53138a 100644 --- a/src/Util/AbstractSceneLoader.cpp +++ b/src/Util/AbstractSceneLoader.cpp @@ -1,16 +1,13 @@ -/** - \author Shin'ichiro Nakaoka -*/ - #include "AbstractSceneLoader.h" +#include "ValueTree.h" +using namespace std; using namespace cnoid; AbstractSceneLoader::AbstractSceneLoader() { - lengthUnitHint_ = Meter; - upperAxisHint_ = Z_Upper; + clearHintsForLoading(); } @@ -50,6 +47,33 @@ void AbstractSceneLoader::setUpperAxisHint(UpperAxisType hint) } +void AbstractSceneLoader::clearHintsForLoading() +{ + lengthUnitHint_ = Meter; + upperAxisHint_ = Z_Upper; +} + + +void AbstractSceneLoader::restoreLengthUnitAndUpperAxisHints(Mapping* metadata) +{ + if(metadata){ + string symbol; + if(metadata->read("length_unit", symbol)){ + if(symbol == "millimeter"){ + lengthUnitHint_ = Millimeter; + } else if(symbol == "inch"){ + lengthUnitHint_ = Inch; + } + } + if(metadata->read("upper_axis", symbol)){ + if(symbol == "Y"){ + upperAxisHint_ = Y_Upper; + } + } + } +} + + /** This function inserts a SgScaleTransform node and a SgPosTransform node to adjust the length unit and the upper direction axis. Each loader can use this function when all @@ -102,3 +126,20 @@ SgNode* AbstractSceneLoader::insertTransformNodeToAdjustUpperAxis(SgNode* node) } return node; } + + +void AbstractSceneLoader::storeLengthUnitAndUpperAxisHintsAsMetadata(SgObject* object) +{ + MappingPtr metadata = new Mapping; + if(lengthUnitHint_ == Millimeter){ + metadata->write("length_unit", "millimeter"); + } else if(lengthUnitHint_ == Inch){ + metadata->write("length_unit", "inch"); + } + if(upperAxisHint_ == Y_Upper){ + metadata->write("upper_axis", "Y"); + } + if(!metadata->empty()){ + object->setUriMetadata(metadata); + } +} diff --git a/src/Util/AbstractSceneLoader.h b/src/Util/AbstractSceneLoader.h index 5497efa83..bd0bf2477 100644 --- a/src/Util/AbstractSceneLoader.h +++ b/src/Util/AbstractSceneLoader.h @@ -1,6 +1,3 @@ -/** - @author Shin'ichiro Nakaoka -*/ #ifndef CNOID_UTIL_ABSTRACT_SCENE_LOADER_H #define CNOID_UTIL_ABSTRACT_SCENE_LOADER_H @@ -28,11 +25,15 @@ class CNOID_EXPORT AbstractSceneLoader virtual void setUpperAxisHint(UpperAxisType hint); UpperAxisType upperAxisHint() const { return upperAxisHint_; } + void clearHintsForLoading(); + void restoreLengthUnitAndUpperAxisHints(Mapping* metadata); + virtual SgNode* load(const std::string& filename) = 0; protected: SgNode* insertTransformNodesToAdjustLengthUnitAndUpperAxis(SgNode* node); SgNode* insertTransformNodeToAdjustUpperAxis(SgNode* node); + void storeLengthUnitAndUpperAxisHintsAsMetadata(SgObject* object); private: LengthUnitType lengthUnitHint_; diff --git a/src/Util/AbstractSceneWriter.cpp b/src/Util/AbstractSceneWriter.cpp index aee3c2ff5..b5287424f 100644 --- a/src/Util/AbstractSceneWriter.cpp +++ b/src/Util/AbstractSceneWriter.cpp @@ -3,16 +3,41 @@ #include "UTF8.h" #include "Format.h" #include +#include +#include #include "gettext.h" using namespace std; using namespace cnoid; namespace filesystem = stdx::filesystem; +namespace cnoid { -AbstractSceneWriter::~AbstractSceneWriter() +class AbstractSceneWriter::Impl +{ +public: + struct CopiedFileInfo { + string filename; + bool copied; + }; + + // Original file to copied file + unordered_map imageFileMap; + unordered_set copiedImageFiles; +}; + +} + + +AbstractSceneWriter::AbstractSceneWriter() { + impl = new Impl; +} + +AbstractSceneWriter::~AbstractSceneWriter() +{ + delete impl; } @@ -22,60 +47,93 @@ void AbstractSceneWriter::setMessageSink(std::ostream& os) } -bool AbstractSceneWriter::findOrCopyImageFile(SgImage* image, const std::string& outputBaseDir) +void AbstractSceneWriter::clearImageFileInformation() +{ + impl->imageFileMap.clear(); + impl->copiedImageFiles.clear(); +} + + +bool AbstractSceneWriter::findOrCopyImageFile(SgImage* image, const std::string& outputBaseDir, std::string& out_copiedFile) { bool foundOrCopied = false; bool orgImageFileFound = false; stdx::error_code ec; auto uri = image->uri(); - if(uri.find_first_of("file://") == 0){ + if(uri.find("file://") == 0){ uri = uri.substr(7); } filesystem::path filePath(fromUTF8(uri)); if(filePath.is_absolute()){ orgImageFileFound = filesystem::exists(filePath, ec); - + if(orgImageFileFound){ + out_copiedFile = uri; + foundOrCopied = true; + } } else if(image->hasAbsoluteUri()){ auto& absUri = image->absoluteUri(); - if(absUri.find_first_of("file://") == 0){ + if(absUri.find("file://") == 0){ filesystem::path orgFilePath(fromUTF8(absUri.substr(7))); - if(filesystem::exists(orgFilePath, ec)){ + auto found = impl->imageFileMap.find(orgFilePath.string()); + if(found != impl->imageFileMap.end()){ + auto& info = found->second; + out_copiedFile = toUTF8(info.filename); orgImageFileFound = true; + foundOrCopied = info.copied; + + } else if(filesystem::exists(orgFilePath, ec)){ + orgImageFileFound = true; + filesystem::path absPath; if(filePath.is_relative()){ - filePath = filesystem::path(fromUTF8(outputBaseDir)) / filePath; + absPath = filesystem::path(fromUTF8(outputBaseDir)) / filePath; + } else { + absPath = filePath; } - if(filesystem::equivalent(orgFilePath, filePath, ec)){ + auto stem = filePath.stem().string(); + auto ext = filePath.extension().string(); + int counter = 2; + while(true){ + auto inserted = impl->copiedImageFiles.insert(absPath.string()); + if(inserted.second){ + break; + } + filePath = filePath.parent_path() / formatC("{0}-{1}{2}", stem, counter, ext); + ++counter; + if(filePath.is_relative()){ + absPath = filesystem::path(fromUTF8(outputBaseDir)) / filePath; + } else { + absPath = filePath; + } + } + if(filesystem::equivalent(orgFilePath, absPath, ec)){ foundOrCopied = true; + out_copiedFile = toUTF8(filePath.string()); } else { - ec.clear(); - filesystem::create_directories(filePath.parent_path(), ec); + filesystem::create_directories(absPath.parent_path(), ec); if(!ec){ #if __cplusplus > 201402L filesystem::copy_file( - orgFilePath, filePath, filesystem::copy_options::update_existing, ec); + orgFilePath, absPath, filesystem::copy_options::overwrite_existing, ec); #else - bool doCopy = true; - if(filesystem::exists(filePath, ec)){ - if(filesystem::last_write_time(filePath, ec) >= filesystem::last_write_time(orgFilePath, ec)){ - doCopy = false; - } - } - if(doCopy){ - filesystem::copy_file( - orgFilePath, filePath, filesystem::copy_option::overwrite_if_exists, ec); - } + filesystem::copy_file( + orgFilePath, absPath, filesystem::copy_option::overwrite_if_exists, ec); #endif - if(!ec){ - foundOrCopied = true; - } } - if(ec){ - os() << formatR(_("Warning: Texture image file \"{0}\" cannot be copied: {1}"), - uri, ec.message()) << endl; + if(!ec){ + foundOrCopied = true; + out_copiedFile = toUTF8(filePath.string()); } } + if(ec){ + os() << formatR(_("Warning: Texture image file \"{0}\" cannot be copied: {1}"), + uri, ec.message()) << endl; + } + + auto& info = impl->imageFileMap[orgFilePath.string()]; + info.filename = filePath.string(); + info.copied = foundOrCopied; } } } diff --git a/src/Util/AbstractSceneWriter.h b/src/Util/AbstractSceneWriter.h index b8b22c85d..4402b961d 100644 --- a/src/Util/AbstractSceneWriter.h +++ b/src/Util/AbstractSceneWriter.h @@ -13,16 +13,21 @@ class SgImage; class CNOID_EXPORT AbstractSceneWriter { public: + AbstractSceneWriter(); virtual ~AbstractSceneWriter(); virtual void setMessageSink(std::ostream& os); virtual bool writeScene(const std::string& filename, SgNode* node) = 0; protected: - bool findOrCopyImageFile(SgImage* image, const std::string& outputBaseDir); + void clearImageFileInformation(); + bool findOrCopyImageFile(SgImage* image, const std::string& outputBaseDir, std::string& out_copiedFile); std::ostream& os(){ return *os_; } private: std::ostream* os_; + + class Impl; + Impl* impl; }; } diff --git a/src/Util/CMakeLists.txt b/src/Util/CMakeLists.txt index e356024c5..4409ac989 100644 --- a/src/Util/CMakeLists.txt +++ b/src/Util/CMakeLists.txt @@ -97,6 +97,7 @@ endif() set(headers ${CMAKE_CURRENT_BINARY_DIR}/Config.h + CompilerWarningControlMacros.h Referenced.h CloneMap.h ClonableReferenced.h diff --git a/src/Util/CompilerWarningControlMacros.h b/src/Util/CompilerWarningControlMacros.h new file mode 100644 index 000000000..584d611ff --- /dev/null +++ b/src/Util/CompilerWarningControlMacros.h @@ -0,0 +1,21 @@ +#ifndef CNOID_UTIL_COMPILER_WARNING_CONTROL_MACROS_H +#define CNOID_UTIL_COMPILER_WARNING_CONTROL_MACROS_H + +#ifdef __GNUC__ + #define CNOID_DISABLE_DEPRECATED_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-enum-enum-conversion\"") + #define CNOID_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +#elif defined(_MSC_VER) + #define CNOID_DISABLE_DEPRECATED_WARNINGS \ + __pragma(warning(push)) \ + __pragma(warning(disable: 4996)) \ + __pragma(warning(disable: 5054)) + #define CNOID_RESTORE_WARNINGS __pragma(warning(pop)) +#else + #define CNOID_DISABLE_DEPRECATED_WARNINGS + #define CNOID_RESTORE_WARNINGS +#endif + +#endif diff --git a/src/Util/FilePathVariableProcessor.cpp b/src/Util/FilePathVariableProcessor.cpp index bbb30ec6c..80e719efd 100644 --- a/src/Util/FilePathVariableProcessor.cpp +++ b/src/Util/FilePathVariableProcessor.cpp @@ -73,7 +73,6 @@ FilePathVariableProcessor* FilePathVariableProcessor::systemInstance() if(!instance){ instance = new FilePathVariableProcessor; - instance->setSubstitutionWithSystemPathVariableEnabled(true); } return instance; @@ -89,7 +88,7 @@ FilePathVariableProcessor::FilePathVariableProcessor() FilePathVariableProcessor::Impl::Impl() { isProjectDirDifferentFromBaseDir = false; - isSubstitutionWithSystemPathVariableEnabled = false; + isSubstitutionWithSystemPathVariableEnabled = true; topDirPath = executableTopDirPath(); topDirString = executableTopDir(); diff --git a/src/Util/FileUtil.cpp b/src/Util/FileUtil.cpp index faeaf334d..d84913551 100644 --- a/src/Util/FileUtil.cpp +++ b/src/Util/FileUtil.cpp @@ -62,22 +62,48 @@ int findPathInDirectory(const filesystem::path& directory, const filesystem::pat if(directory.is_absolute() && path.is_absolute()){ filesystem::path compactPath = filesystem::lexically_normal(path); - filesystem::path::const_iterator p = directory.begin(); - filesystem::path::const_iterator q = compactPath.begin(); + filesystem::path::const_iterator it1 = directory.begin(); + filesystem::path::const_iterator it2 = compactPath.begin(); - while(p != directory.end() && q != compactPath.end()){ - if(!(*p == *q)){ + while(it1 != directory.end() && it2 != compactPath.end()){ + bool matched = false; +#ifdef _WIN32 + auto p1 = *it1; + auto s1 = p1.make_preferred().string(); + std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); + auto p2 = *it2; + auto s2 = p2.make_preferred().string(); + std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); + + // In Windows, directory and file names cannot end with a period(.). + // Any trailing period should be removed for comarison purpose. + if(!s1.empty() && s1.back() == '.'){ + s1.pop_back(); + } + if(!s2.empty() && s2.back() == '.'){ + s2.pop_back(); + } + + if(s1 == s2){ + matched = true; + } +#else + if(*it1 == *it2){ + matched = true; + } +#endif + if(!matched){ break; } ++numMatchedDepth; - ++p; - ++q; + ++it1; + ++it2; } - if(p == directory.end()){ + if(it1 == directory.end()){ out_relativePath.clear(); - while(q != compactPath.end()){ - out_relativePath /= *q++; + while(it2 != compactPath.end()){ + out_relativePath /= *it2++; } return numMatchedDepth; } @@ -126,6 +152,16 @@ stdx::optional getRelativePath(const filesystem::path& p auto p2 = *it2; auto s2 = p2.make_preferred().string(); std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); + + // In Windows, directory and file names cannot end with a period(.). + // Any trailing period should be removed for comarison purpose. + if(!s1.empty() && s1.back() == '.'){ + s1.pop_back(); + } + if(!s2.empty() && s2.back() == '.'){ + s2.pop_back(); + } + if(s1 == s2){ matched = true; } diff --git a/src/Util/HierarchicalClassRegistry.cpp b/src/Util/HierarchicalClassRegistry.cpp index 5e9d1df18..3fa8ec4c4 100644 --- a/src/Util/HierarchicalClassRegistry.cpp +++ b/src/Util/HierarchicalClassRegistry.cpp @@ -1,8 +1,11 @@ #include "HierarchicalClassRegistry.h" +#include "Format.h" #include #include #include #include +#include +#include using namespace std; using namespace cnoid; @@ -17,16 +20,32 @@ class HierarchicalClassRegistryBase::Impl PolymorphicIdMap polymorphicIdMap; std::vector superClassPolymorphicIdMap; std::vector classNameMap; + std::type_index baseTypeIndex; + Impl(const std::type_info& baseType, const char* baseTypeName); int registerClassAsTypeInfo(const std::type_info& type, const std::type_info& superType, const char* name); }; } -HierarchicalClassRegistryBase::HierarchicalClassRegistryBase() +HierarchicalClassRegistryBase::HierarchicalClassRegistryBase(const std::type_info& baseType, const char* baseTypeName) +{ + impl = new Impl(baseType, baseTypeName); +} + + +HierarchicalClassRegistryBase::Impl::Impl(const std::type_info& baseType, const char* baseTypeName) + : baseTypeIndex(baseType) { - impl = new Impl; + std::lock_guard lock(polymorphicIdMutex); + + int baseClassId = 0; + polymorphicIdMap[baseType] = baseClassId; + superClassPolymorphicIdMap.push_back(-1); + if(baseTypeName){ + classNameMap.push_back(baseTypeName); + } } @@ -56,29 +75,44 @@ int HierarchicalClassRegistryBase::Impl::registerClassAsTypeInfo { std::lock_guard lock(polymorphicIdMutex); - int superClassId; - auto iter = polymorphicIdMap.find(superType); - if(iter != polymorphicIdMap.end()){ - superClassId = iter->second; - } else { - superClassId = polymorphicIdMap.size(); - polymorphicIdMap[superType] = superClassId; + if(type == superType){ + if(std::type_index(type) == baseTypeIndex){ + if(name){ + if(classNameMap.empty()){ + classNameMap.resize(1); + } + classNameMap[0] = name; + } + return 0; + } + if(name){ + throw std::invalid_argument( + formatC("Cannot register type {0} as its own supertype in HierarchicalClassRegistry.", name)); + } else { + throw std::invalid_argument( + "Cannot register a type as its own supertype in HierarchicalClassRegistry."); + } } - const bool hasSuperClass = (type != superType); - int id; - - if(!hasSuperClass){ - id = superClassId; - } else { - id = polymorphicIdMap.size(); - polymorphicIdMap[type] = id; + + auto it = polymorphicIdMap.find(superType); + if(it == polymorphicIdMap.end()){ + if(name){ + throw std::invalid_argument( + formatC("Specified supertype of {0} is not registered in HierarchicalClassRegistry.", name)); + } else { + throw std::invalid_argument( + formatC("Specified supertype of {0} is not registered in HierarchicalClassRegistry.", type.name())); + } } + int superClassId = it->second; + + int id = polymorphicIdMap.size(); + polymorphicIdMap[type] = id; + if(id >= static_cast(superClassPolymorphicIdMap.size())){ superClassPolymorphicIdMap.resize(id + 1, -1); } - if(hasSuperClass){ - superClassPolymorphicIdMap[id] = superClassId; - } + superClassPolymorphicIdMap[id] = superClassId; if(name){ if(id >= static_cast(classNameMap.size())){ diff --git a/src/Util/HierarchicalClassRegistry.h b/src/Util/HierarchicalClassRegistry.h index 5e4ede841..f80f35749 100644 --- a/src/Util/HierarchicalClassRegistry.h +++ b/src/Util/HierarchicalClassRegistry.h @@ -10,7 +10,6 @@ namespace cnoid { class CNOID_EXPORT HierarchicalClassRegistryBase { public: - HierarchicalClassRegistryBase(); ~HierarchicalClassRegistryBase(); void reserve(int n); @@ -25,6 +24,7 @@ class CNOID_EXPORT HierarchicalClassRegistryBase int numRegisteredClasses() const; protected: + HierarchicalClassRegistryBase(const std::type_info& baseType, const char* baseTypeName); int getClassId(const std::type_info& type, int unknownClassId = -1) const; private: @@ -36,8 +36,8 @@ template class HierarchicalClassRegistry : public HierarchicalClassRegistryBase { public: - HierarchicalClassRegistry() { - registerClassAsTypeInfo(typeid(BaseClass), typeid(BaseClass)); + HierarchicalClassRegistry(const char* baseClassName = nullptr) + : HierarchicalClassRegistryBase(typeid(BaseClass), baseClassName) { } HierarchicalClassRegistry(const HierarchicalClassRegistry& org) = delete; diff --git a/src/Util/MeshGenerator.cpp b/src/Util/MeshGenerator.cpp index d1bf28794..67f8d820b 100644 --- a/src/Util/MeshGenerator.cpp +++ b/src/Util/MeshGenerator.cpp @@ -1,13 +1,9 @@ -/*! - @file - @author Shin'ichiro Nakaoka -*/ - #include "MeshGenerator.h" #include "MeshFilter.h" #include "MeshExtractor.h" #include "EigenUtil.h" #include "Triangulator.h" +#include using namespace std; using namespace cnoid; @@ -18,6 +14,14 @@ constexpr int defaultDivisionNumber = 20; } + +MeshGenerator* MeshGenerator::mainThreadInstance() +{ + static auto instance = std::make_unique(); + return instance.get(); +} + + MeshGenerator::MeshGenerator() { divisionNumber_ = ::defaultDivisionNumber; diff --git a/src/Util/MeshGenerator.h b/src/Util/MeshGenerator.h index 7acb52b89..dfff0548d 100644 --- a/src/Util/MeshGenerator.h +++ b/src/Util/MeshGenerator.h @@ -1,8 +1,3 @@ -/*! - @file - @author Shin'ichiro Nakaoka -*/ - #ifndef CNOID_UTIL_MESH_GENERATOR_H #define CNOID_UTIL_MESH_GENERATOR_H @@ -17,6 +12,8 @@ class MeshFilter; class CNOID_EXPORT MeshGenerator { public: + static MeshGenerator* mainThreadInstance(); + MeshGenerator(); MeshGenerator(const MeshGenerator& org); ~MeshGenerator(); diff --git a/src/Util/MessageOut.cpp b/src/Util/MessageOut.cpp index 5292b3197..f028990e9 100644 --- a/src/Util/MessageOut.cpp +++ b/src/Util/MessageOut.cpp @@ -1,5 +1,6 @@ #include "MessageOut.h" -#include +#include +#include #include #include #include @@ -11,37 +12,30 @@ namespace { mutex sinkMutex; -class MessageOutStreamBuf : public std::stringbuf +class MessageOutStreamBuf : public std::basic_streambuf { public: - MessageOut& mout; - MessageOutStreamBuf(MessageOut& mout) - : mout(mout) { } - virtual int sync() override { - mout.put(str()); - return 0; - } -}; + MessageOutStreamBuf(MessageOut& mout, int messageType); + + virtual int_type overflow(int_type c) override; + virtual int sync() override; -class MessageOutErrorStreamBuf : public std::stringbuf -{ -public: MessageOut& mout; - MessageOutErrorStreamBuf(MessageOut& mout) - : mout(mout) { } - virtual int sync() override { - mout.putError(str()); - return 0; - } + int messageType; + vector buf; }; class Sink : public Referenced { public: - std::function function; - - Sink(const std::function& func) - : function(func) { } + std::function messageFunc; + std::function notifyFunc; + std::function flushFunc; + + Sink(const std::function& messageFunc, + const std::function& notifyFunc, + const std::function& flushFunc) + : messageFunc(messageFunc), notifyFunc(notifyFunc), flushFunc(flushFunc) { } }; typedef ref_ptr SinkPtr; @@ -66,10 +60,12 @@ class MessageOut::Impl bool isPendingMode; bool hasErrors; - unique_ptr streamBuf; + ostream* direct_cout; unique_ptr cout; - unique_ptr errorStreamBuf; + unique_ptr streamBuf; + ostream* direct_cerr; unique_ptr cerr; + unique_ptr errorStreamBuf; Impl(MessageOut* self); }; @@ -77,6 +73,40 @@ class MessageOut::Impl } +MessageOutStreamBuf::MessageOutStreamBuf(MessageOut& mout, int messageType) + : mout(mout), + messageType(messageType) +{ + buf.resize(4096); + auto p = &buf.front(); + setp(p, p + buf.size()); +} + + +MessageOutStreamBuf::int_type MessageOutStreamBuf::overflow(int_type c) +{ + sync(); + + if(c != traits_type::eof()){ + buf[0] = c; + pbump(1); + return traits_type::not_eof(c); + } else { + return traits_type::eof(); + } +} + + +int MessageOutStreamBuf::sync() +{ + auto p = &buf.front(); + mout.put(string(p, pptr() - p), messageType); + mout.flush(); + setp(p, p + buf.size()); + return 0; +} + + MessageOut* MessageOut::master() { static MessageOutPtr instance = new MessageOut; @@ -91,17 +121,50 @@ MessageOut* MessageOut::interactive() } +MessageOut* MessageOut::nullout() +{ + static MessageOutPtr instance = new MessageOut; + return instance; +} + + MessageOut::MessageOut() { impl = new Impl(this); } +MessageOut::MessageOut +(std::function messageFunc, + std::function notifyFunc, + std::function flushFunc) +{ + impl = new Impl(this); + addSink(messageFunc, notifyFunc, flushFunc); +} + + +MessageOut::MessageOut(std::ostream& sink) +{ + impl = new Impl(this); + + addSink( + [&sink](const std::string& message, int /* type */){ sink << message; }, + [](const std::string& /* message */, int /* type */){ }, + [&sink]{ sink.flush(); }); + + impl->direct_cout = &sink; + impl->direct_cerr = &sink; +} + + MessageOut::Impl::Impl(MessageOut* self) : self(self) { isPendingMode = false; hasErrors = false; + direct_cout = nullptr; + direct_cerr = nullptr; } @@ -118,10 +181,13 @@ void MessageOut::clearSinks() } -MessageOut::SinkHandle MessageOut::addSink(std::function func) +MessageOut::SinkHandle MessageOut::addSink +(std::function messageFunc, + std::function notifyFunc, + std::function flushFunc) { lock_guard lock(sinkMutex); - impl->sinks.push_back(new Sink(func)); + impl->sinks.push_back(new Sink(messageFunc, notifyFunc, flushFunc)); return impl->sinks.back(); } @@ -146,7 +212,7 @@ void MessageOut::put(const std::string& message, int type) } if(!impl->isPendingMode){ for(auto& sink : impl->sinks){ - sink->function(message, type); + sink->messageFunc(message, type); } } else { impl->pendingMessages.emplace_back(message, type); @@ -157,14 +223,37 @@ void MessageOut::put(const std::string& message, int type) void MessageOut::putln(const std::string& message, int type) { put(message + "\n", type); + flush(); +} + + +void MessageOut::notify(const std::string& message, int type) +{ + putln(message, type); + for(auto& sink : impl->sinks){ + sink->notifyFunc(message, type); + } +} + + +void MessageOut::flush() +{ + if(!impl->isPendingMode){ + for(auto& sink : impl->sinks){ + sink->flushFunc(); + } + } } std::ostream& MessageOut::cout() { lock_guard lock(sinkMutex); + if(impl->direct_cout){ + return *impl->direct_cout; + } if(!impl->cout){ - impl->streamBuf = make_unique(*this); + impl->streamBuf = make_unique(*this, Normal); impl->cout = make_unique(impl->streamBuf.get()); } return *impl->cout; @@ -174,8 +263,11 @@ std::ostream& MessageOut::cout() std::ostream& MessageOut::cerr() { lock_guard lock(sinkMutex); + if(impl->direct_cerr){ + return *impl->direct_cerr; + } if(!impl->cout){ - impl->errorStreamBuf = make_unique(*this); + impl->errorStreamBuf = make_unique(*this, Error); impl->cerr = make_unique(impl->errorStreamBuf.get()); } return *impl->cerr; @@ -200,7 +292,8 @@ void MessageOut::flushPendingMessages() lock_guard lock(sinkMutex); for(auto& message : impl->pendingMessages){ for(auto& sink : impl->sinks){ - sink->function(message.text, message.type); + sink->messageFunc(message.text, message.type); + sink->flushFunc(); } } impl->pendingMessages.clear(); diff --git a/src/Util/MessageOut.h b/src/Util/MessageOut.h index 269fb0214..19e392e49 100644 --- a/src/Util/MessageOut.h +++ b/src/Util/MessageOut.h @@ -12,9 +12,14 @@ class CNOID_EXPORT MessageOut : public Referenced public: static MessageOut* master(); static MessageOut* interactive(); + static MessageOut* nullout(); MessageOut(); - MessageOut(std::function sink); + MessageOut( + std::function messageFunc, + std::function notifyFunc, + std::function flushFunc); + MessageOut(std::ostream& sink); MessageOut(MessageOut* parent); ~MessageOut(); @@ -23,42 +28,51 @@ class CNOID_EXPORT MessageOut : public Referenced typedef ReferencedPtr SinkHandle; void clearSinks(); - SinkHandle addSink(std::function sink); + SinkHandle addSink( + std::function messageFunc, + std::function notifyFunc, + std::function flushFunc); int numSinks() const; SinkHandle sinkHandle(int index); void removeSink(SinkHandle sink); - void put(const std::string& message, int type); - void putln(const std::string& message, int type); + void put(const std::string& message, int type = Normal); + void putln(const std::string& message, int type = Normal); + void notify(const std::string& message, int type = Normal); - void put(const std::string& message){ - put(message, Normal); - } - void putln(const std::string& message){ - putln(message, Normal); - } void putHighlighted(const std::string& message){ put(message, Highlighted); } void putHighlightedln(const std::string& message){ putln(message, Highlighted); } + void notifyHighlighted(const std::string& message){ + notify(message, Highlighted); + } void putWarning(const std::string& message){ put(message, Warning); } void putWarningln(const std::string& message){ putln(message, Warning); } + void notifyWarning(const std::string& message){ + notify(message, Warning); + } void putError(const std::string& message){ put(message, Error); } void putErrorln(const std::string& message){ putln(message, Error); } + void notifyError(const std::string& message){ + notify(message, Error); + } std::ostream& cout(); std::ostream& cerr(); + void flush(); + void setPendingMode(bool on); void flushPendingMessages(); diff --git a/src/Util/ObjSceneLoader.cpp b/src/Util/ObjSceneLoader.cpp index da06eaa43..dbf1e2242 100644 --- a/src/Util/ObjSceneLoader.cpp +++ b/src/Util/ObjSceneLoader.cpp @@ -4,6 +4,7 @@ #include "SceneLoader.h" #include "Triangulator.h" #include "ImageIO.h" +#include "ValueTree.h" #include "NullOut.h" #include "Format.h" #include @@ -217,26 +218,17 @@ SgNode* ObjSceneLoader::Impl::load(const string& filename) doCoordinateConversion = false; scale = 1.0f; - string metadata; auto lengthUnit = self->lengthUnitHint(); if(lengthUnit == AbstractSceneLoader::Millimeter){ scale = 1.0e-3f; doCoordinateConversion = true; - metadata = "millimeter"; } else if(lengthUnit == AbstractSceneLoader::Inch){ scale = 0.0254f; doCoordinateConversion = true; - metadata = "inch"; } upperAxis = self->upperAxisHint(); - if(upperAxis != Z_Upper){ + if(upperAxis == Y_Upper){ doCoordinateConversion = true; - if(upperAxis == Y_Upper){ - if(!metadata.empty()){ - metadata += " "; - } - metadata += "y_upper"; - } } try { @@ -248,9 +240,7 @@ SgNode* ObjSceneLoader::Impl::load(const string& filename) if(scene){ scene->setUriWithFilePathAndCurrentDirectory(filename); - if(!metadata.empty()){ - scene->setUriMetadataString(metadata); - } + self->storeLengthUnitAndUpperAxisHintsAsMetadata(scene); } scanner.close(); @@ -648,7 +638,7 @@ bool ObjSceneLoader::Impl::loadMaterialTemplateLibrary(std::string filename) case 'm': if(subScanner.readStringAtCurrentPosition(token)){ - if(token.find_first_of("map_") == 0){ + if(token.find("map_") == 0){ readTexture(token.substr(4)); } else { isUnknownDirective = true; @@ -786,11 +776,14 @@ void ObjSceneLoader::Impl::updateAmbientIntensities() auto& info = kv.second; auto& material = info.material; if(material){ - float a = info.ambientColor.norm(); + float intensity = 1.0f; float d = material->diffuseColor().norm(); - float intensity = a / d; - if(intensity >= (1.0f - 1.0e-3)){ - intensity = 1.0f; + if(d > 0.0f){ + float a = info.ambientColor.norm(); + intensity = a / d; + if(intensity >= (1.0f - 1.0e-3)){ + intensity = 1.0f; + } } material->setAmbientIntensity(intensity); } diff --git a/src/Util/ObjSceneWriter.cpp b/src/Util/ObjSceneWriter.cpp index c98acaf6a..4a01af5cc 100644 --- a/src/Util/ObjSceneWriter.cpp +++ b/src/Util/ObjSceneWriter.cpp @@ -131,6 +131,8 @@ void ObjSceneWriter::Impl::clear() materialLabelSet.clear(); lastMaterialPair = MaterialPair(nullptr, nullptr); materialLabelIdCounter = 0; + + self->clearImageFileInformation(); } @@ -317,8 +319,9 @@ void ObjSceneWriter::Impl::writeMaterial(SgMaterial* material, SgTexture* textur if(texture){ auto image = texture->image(); if(image && image->hasUri()){ - if(self->findOrCopyImageFile(image, toUTF8(baseDirPath.generic_string()))){ - mfs << "map_Kd " << image->uri() << "\n"; + string copiedFile; + if(self->findOrCopyImageFile(image, toUTF8(baseDirPath.generic_string()), copiedFile)){ + mfs << "map_Kd " << copiedFile << "\n"; } } } diff --git a/src/Util/STLSceneLoader.cpp b/src/Util/STLSceneLoader.cpp index b70340880..e5db1c810 100644 --- a/src/Util/STLSceneLoader.cpp +++ b/src/Util/STLSceneLoader.cpp @@ -46,6 +46,10 @@ class MeshLoader SgNormalArrayPtr normals; SgIndexArray* normalIndices; + bool isUpperAxisY; + bool doScaling; + float scale; + /** The following two variables are only used in BinaryMeshLoader. Defining them here instead of BinaryMeshLoader can improve the loading speed. @@ -70,12 +74,12 @@ class MeshLoader string errorMessage; - MeshLoader(size_t numTriangles); + MeshLoader(STLSceneLoader::Impl* loaderImpl, size_t numTriangles); MeshLoader(const MeshLoader& mainLoader); template - void addNormal(const Vector3f& normal, AddIndexFunction addIndex); + void addNormal(Vector3f& normal, AddIndexFunction addIndex); template - void addVertex(const Vector3f& vertex, AddIndexFunction addIndex); + void addVertex(Vector3f& vertex, AddIndexFunction addIndex); bool join(); int findElement( const Vector3f& element, const SgVectorArray& prevElements, int searchLength); @@ -96,11 +100,11 @@ class MeshLoader class BinaryMeshLoader : public MeshLoader { public: - BinaryMeshLoader(size_t numTriangles) : MeshLoader(numTriangles) { } + BinaryMeshLoader(STLSceneLoader::Impl* loaderImpl, size_t numTriangles) : MeshLoader(loaderImpl, numTriangles) { } BinaryMeshLoader(const BinaryMeshLoader& mainLoader) : MeshLoader(mainLoader) { } void initializeArrays(size_t triangleOffset, size_t numTriangles); - void addNormal(const Vector3f& normal); - void addVertex(const Vector3f& vertex); + void addNormal(Vector3f& normal); + void addVertex(Vector3f& vertex); void load(ifstream& ifs, size_t triangleOffset, size_t numTriangles); void loadConcurrently(const string& filename, size_t triangleOffset, size_t numTriangles); }; @@ -116,10 +120,10 @@ class AsciiMeshLoader : public MeshLoader string filename; bool isSuccessfullyLoaded; - AsciiMeshLoader(const string& filename, bool doOpen); + AsciiMeshLoader(STLSceneLoader::Impl* loaderImpl, const string& filename, bool doOpen); AsciiMeshLoader(const AsciiMeshLoader& mainLoader); - void addNormal(const Vector3f& normal); - void addVertex(const Vector3f& vertex); + void addNormal(Vector3f& normal); + void addVertex(Vector3f& vertex); bool seekToTriangleBorderPosition(pos_type position); bool load(); void loadConcurrently(); @@ -144,11 +148,15 @@ namespace cnoid { class STLSceneLoader::Impl { public: + STLSceneLoader* self; + bool isUpperAxisY; + bool doScaling; + float scale; size_t maxNumThreads; ostream* os_; ostream& os() { return *os_; } - Impl(); + Impl(STLSceneLoader* self); SgNode* load(const string& filename); SgMeshPtr loadBinaryFormat(const string& filename, ifstream& ifs, size_t numTriangles); SgMeshPtr loadBinaryFormatConcurrently( @@ -162,9 +170,7 @@ class STLSceneLoader::Impl } -namespace { - -MeshLoader::MeshLoader(size_t numTriangles) +MeshLoader::MeshLoader(STLSceneLoader::Impl* loaderImpl, size_t numTriangles) : sharedMesh(new SgMesh), numTriangles(numTriangles) { @@ -178,6 +184,10 @@ MeshLoader::MeshLoader(size_t numTriangles) sharedMesh->setNumTriangles(numTriangles); normalIndices->resize(numTriangles * 3); + + isUpperAxisY = loaderImpl->isUpperAxisY; + doScaling = loaderImpl->doScaling; + scale = loaderImpl->scale; } @@ -190,6 +200,10 @@ MeshLoader::MeshLoader(const MeshLoader& mainLoader) triangleVertices = nullptr; normals = new SgNormalArray; normalIndices = nullptr; + + isUpperAxisY = mainLoader.isUpperAxisY; + doScaling = mainLoader.doScaling; + scale = mainLoader.scale; } @@ -214,8 +228,18 @@ static inline bool isApprox(const Vector3f& v1, const Vector3f& v2) template -void MeshLoader::addNormal(const Vector3f& normal, AddIndexFunction addIndex) +void MeshLoader::addNormal(Vector3f& normal, AddIndexFunction addIndex) { + if(isUpperAxisY){ + float y = normal.y(); + normal.y() = normal.x(); + normal.x() = normal.z(); + normal.z() = y; + } + if(doScaling){ + normal *= scale; + } + bool found = false; int index = normals->size() - 1; @@ -232,15 +256,25 @@ void MeshLoader::addNormal(const Vector3f& normal, AddIndexFunction addIndex) if(!found){ index = normals->size(); - normals->push_back(normal); + normals->emplace_back(normal); } addIndex(index); } template -void MeshLoader::addVertex(const Vector3f& vertex, AddIndexFunction addIndex) +void MeshLoader::addVertex(Vector3f& vertex, AddIndexFunction addIndex) { + if(isUpperAxisY){ + float y = vertex.y(); + vertex.y() = vertex.x(); + vertex.x() = vertex.z(); + vertex.z() = y; + } + if(doScaling){ + vertex *= scale; + } + bool found = false; int index = vertices->size() - 1; @@ -259,7 +293,7 @@ void MeshLoader::addVertex(const Vector3f& vertex, AddIndexFunction addIndex) addIndex(index); } else { addIndex(vertices->size()); - vertices->push_back(vertex); + vertices->emplace_back(vertex); bbox.expandBy(vertex); } } @@ -433,16 +467,15 @@ SgMeshPtr MeshLoader::completeMesh(bool doShrink) return sharedMesh; } -} - STLSceneLoader::STLSceneLoader() { - impl = new Impl; + impl = new Impl(this); } -STLSceneLoader::Impl::Impl() +STLSceneLoader::Impl::Impl(STLSceneLoader* self) + : self(self) { maxNumThreads = std::max((unsigned)1, thread::hardware_concurrency()); @@ -464,7 +497,7 @@ void STLSceneLoader::setMessageSink(std::ostream& os) SgNode* STLSceneLoader::load(const std::string& filename) { - return insertTransformNodesToAdjustLengthUnitAndUpperAxis(impl->load(filename)); + return impl->load(filename); } @@ -492,6 +525,21 @@ SgNode* STLSceneLoader::Impl::load(const string& filename) } } + isUpperAxisY = false; + doScaling = false; + scale = 1.0f; + + if(self->upperAxisHint() == Y_Upper){ + isUpperAxisY = true; + } + if(self->lengthUnitHint() == Millimeter){ + doScaling = true; + scale = 1.0e-3f; + } else if(self->lengthUnitHint() == Inch){ + doScaling = true; + scale = 0.0254f; + } + SgMeshPtr mesh; if(isBinary){ mesh = loadBinaryFormat(filename, ifs, numTriangles); @@ -506,6 +554,7 @@ SgNode* STLSceneLoader::Impl::load(const string& filename) } mesh->setUriWithFilePathAndCurrentDirectory(filename); + self->storeLengthUnitAndUpperAxisHintsAsMetadata(mesh); auto shape = new SgShape; shape->setMesh(mesh); @@ -524,7 +573,7 @@ SgMeshPtr STLSceneLoader::Impl::loadBinaryFormat(const string& filename, ifstrea return nullptr; } - BinaryMeshLoader mainLoader(numTriangles); + BinaryMeshLoader mainLoader(this, numTriangles); size_t numThreads = std::min(maxNumThreads, std::max(size_t(1), numTriangles / NumTrianglesPerThread)); @@ -569,8 +618,6 @@ SgMeshPtr STLSceneLoader::Impl::loadBinaryFormatConcurrently } -namespace { - void BinaryMeshLoader::initializeArrays(size_t triangleOffset, size_t numTriangles) { this->triangleOffset = triangleOffset; @@ -586,7 +633,7 @@ void BinaryMeshLoader::initializeArrays(size_t triangleOffset, size_t numTriangl } -void BinaryMeshLoader::addNormal(const Vector3f& normal) +void BinaryMeshLoader::addNormal(Vector3f& normal) { MeshLoader::addNormal( normal, @@ -598,7 +645,7 @@ void BinaryMeshLoader::addNormal(const Vector3f& normal) } -void BinaryMeshLoader::addVertex(const Vector3f& vertex) +void BinaryMeshLoader::addVertex(Vector3f& vertex) { MeshLoader::addVertex( vertex, @@ -628,10 +675,14 @@ void BinaryMeshLoader::load(ifstream& ifs, size_t triangleOffset, size_t numTria char data[datasize]; for(size_t i = 0; i < numTriangles; ++i){ ifs.read(data, datasize); - addNormal(Vector3f(reinterpret_cast(data))); - addVertex(Vector3f(reinterpret_cast(&data[12]))); - addVertex(Vector3f(reinterpret_cast(&data[24]))); - addVertex(Vector3f(reinterpret_cast(&data[36]))); + Vector3f normal(reinterpret_cast(data)); + addNormal(normal); + Vector3f v0(reinterpret_cast(&data[12])); + addVertex(v0); + Vector3f v1(reinterpret_cast(&data[24])); + addVertex(v1); + Vector3f v2(reinterpret_cast(&data[36])); + addVertex(v2); } } @@ -645,8 +696,6 @@ void BinaryMeshLoader::loadConcurrently(const string& filename, size_t triangleO }); } -} - SgMeshPtr STLSceneLoader::Impl::loadAsciiFormat(const string& filename, pos_type fileSize) { @@ -661,14 +710,14 @@ SgMeshPtr STLSceneLoader::Impl::loadAsciiFormat(const string& filename, pos_type SgMeshPtr mesh; bool doOpen = (numThreads == 1); - AsciiMeshLoader mainLoader(filename, doOpen); + AsciiMeshLoader mainLoader(this, filename, doOpen); if(numThreads == 1){ if(mainLoader.load()){ mesh = mainLoader.completeMesh(true); } } else { - mesh =loadAsciiFormatConcurrently(filename, mainLoader, fileSize, numThreads); + mesh = loadAsciiFormatConcurrently(filename, mainLoader, fileSize, numThreads); } return mesh; @@ -723,10 +772,8 @@ SgMeshPtr STLSceneLoader::Impl::loadAsciiFormatConcurrently } -namespace { - -AsciiMeshLoader::AsciiMeshLoader(const string& filename, bool doOpen) - : MeshLoader(0), +AsciiMeshLoader::AsciiMeshLoader(STLSceneLoader::Impl* loaderImpl, const string& filename, bool doOpen) + : MeshLoader(loaderImpl, 0), filename(filename) { if(doOpen){ @@ -756,7 +803,7 @@ bool AsciiMeshLoader::seekToTriangleBorderPosition(pos_type position) } -void AsciiMeshLoader::addNormal(const Vector3f& normal) +void AsciiMeshLoader::addNormal(Vector3f& normal) { MeshLoader::addNormal( normal, @@ -768,7 +815,7 @@ void AsciiMeshLoader::addNormal(const Vector3f& normal) } -void AsciiMeshLoader::addVertex(const Vector3f& vertex) +void AsciiMeshLoader::addVertex(Vector3f& vertex) { MeshLoader::addVertex( vertex, @@ -870,8 +917,6 @@ void AsciiMeshLoader::initializeIntegration(MeshLoader* prevLoader) } } -} - SgMeshPtr STLSceneLoader::Impl::integrateSubLoaderMeshes (MeshLoader& mainLoader, vector loaders) diff --git a/src/Util/STLSceneLoader.h b/src/Util/STLSceneLoader.h index a96f18a18..6477dddb9 100644 --- a/src/Util/STLSceneLoader.h +++ b/src/Util/STLSceneLoader.h @@ -14,8 +14,9 @@ class CNOID_EXPORT STLSceneLoader : public AbstractSceneLoader virtual void setMessageSink(std::ostream& os) override; virtual SgNode* load(const std::string& filename) override; -private: class Impl; + +private: Impl* impl; }; diff --git a/src/Util/SceneDrawables.cpp b/src/Util/SceneDrawables.cpp index c9a34d1f7..ab626b186 100644 --- a/src/Util/SceneDrawables.cpp +++ b/src/Util/SceneDrawables.cpp @@ -40,12 +40,18 @@ SgMaterial::SgMaterial() SgMaterial::SgMaterial(const SgMaterial& org) : SgObject(org) { - ambientIntensity_ = org.ambientIntensity_; - diffuseColor_ = org.diffuseColor_; - emissiveColor_ = org.emissiveColor_; - specularColor_ = org.specularColor_; - specularExponent_ = org.specularExponent_; - transparency_ = org.transparency_; + copyMaterialPropertiesFrom(&org); +} + + +void SgMaterial::copyMaterialPropertiesFrom(const SgMaterial* other) +{ + ambientIntensity_ = other->ambientIntensity_; + diffuseColor_ = other->diffuseColor_; + emissiveColor_ = other->emissiveColor_; + specularColor_ = other->specularColor_; + specularExponent_ = other->specularExponent_; + transparency_ = other->transparency_; } diff --git a/src/Util/SceneDrawables.h b/src/Util/SceneDrawables.h index ab9aeaf89..45d221ab3 100644 --- a/src/Util/SceneDrawables.h +++ b/src/Util/SceneDrawables.h @@ -16,6 +16,8 @@ class CNOID_EXPORT SgMaterial : public SgObject SgMaterial(); SgMaterial(const SgMaterial& org); + void copyMaterialPropertiesFrom(const SgMaterial* other); + float ambientIntensity() const { return ambientIntensity_; } void setAmbientIntensity(float intensity) { ambientIntensity_ = intensity; } const Vector3f& diffuseColor() const { return diffuseColor_; } diff --git a/src/Util/SceneGraph.cpp b/src/Util/SceneGraph.cpp index c40da25fa..4525a1744 100644 --- a/src/Util/SceneGraph.cpp +++ b/src/Util/SceneGraph.cpp @@ -1,6 +1,7 @@ #include "SceneGraph.h" #include "SceneNodeClassRegistry.h" #include "CloneMap.h" +#include "ValueTree.h" #include "UTF8.h" #include "Format.h" #include @@ -99,6 +100,25 @@ SgObject* SgObject::findObject_(std::function& pred) } +bool SgObject::traverseObjects_(std::function& pred) +{ + auto status = pred(this); + if(status == Stop){ + return false; + } + if(status == Next){ + return true; + } + int n = numChildObjects(); + for(int i=0; i < n; ++i){ + if(!childObject(i)->traverseObjects_(pred)){ + return false; + } + } + return true; +} + + void SgObject::notifyUpperNodesOfUpdate(SgUpdate& update) { notifyUpperNodesOfUpdate(update, update.hasAction(SgUpdate::GeometryModified)); @@ -221,12 +241,12 @@ const std::string& SgObject::uriFragment() const } -const std::string& SgObject::uriMetadataString() const +Mapping* SgObject::uriMetadata() const { - if(!uriInfo){ - uriInfo.reset(new UriInfo); + if(uriInfo){ + return static_cast(uriInfo->metadata.get()); } - return uriInfo->metadata; + return nullptr; } @@ -300,7 +320,7 @@ void SgObject::setUriFragment(const std::string& fragment) } -void SgObject::setUriMetadataString(const std::string& data) +void SgObject::setUriMetadata(Mapping* data) { if(!uriInfo){ uriInfo.reset(new UriInfo); @@ -425,6 +445,26 @@ SgNodePath SgNode::findNode(const std::string& name, Affine3& out_T) } +bool SgNode::traverseNodes_(std::function& pred) +{ + auto status = pred(this); + if(status == Stop){ + return false; + } + if(status == Next){ + return true; + } + if(auto group = toGroupNode()){ + for(auto& child : *group){ + if(!child->traverseNodes_(pred)){ + return false; + } + } + } + return true; +} + + SgGroup::SgGroup() : SgNode(findClassId()) { @@ -457,15 +497,6 @@ SgGroup::SgGroup(const SgGroup& org, CloneMap* cloneMap) } } } - } else { - // shallow copy - /** - \todo Stop the shallow copy of the child nodes. - Only the attributes of this node should be copied when the clone map is not used. - */ - for(auto& child : org){ - addChild(child); - } } if(org.hasValidBoundingBoxCache()){ @@ -657,10 +688,11 @@ void SgGroup::clearChildren(SgUpdateRef update) } -void SgGroup::copyChildrenTo(SgGroup* group, SgUpdateRef update) +void SgGroup::copyChildrenTo(SgGroup* group, SgUpdateRef update) const { for(size_t i=0; i < children.size(); ++i){ - group->addChild(child(i), update); + auto node = const_cast(child(i)); + group->addChild(node, update); } } @@ -1147,7 +1179,6 @@ namespace { struct NodeClassRegistration { NodeClassRegistration() { SceneNodeClassRegistry::instance() - .registerClass("SgNode") .registerClass("SgGroup") .registerClass("SgInvariantGroup") .registerClass("SgTransform") diff --git a/src/Util/SceneGraph.h b/src/Util/SceneGraph.h index 52fc610c9..fcc298604 100644 --- a/src/Util/SceneGraph.h +++ b/src/Util/SceneGraph.h @@ -19,6 +19,7 @@ class SgNode; typedef ref_ptr SgNodePtr; class SgGroup; class SgTransform; +class Mapping; typedef std::vector SgNodePath; @@ -79,6 +80,12 @@ class CNOID_EXPORT SgObject : public ClonableReferenced return findObject_(pred); } + enum TraverseStatus { Continue, Next, Stop }; + + bool traverseObjects(std::function pred) { + return traverseObjects_(pred); + } + SignalProxy sigUpdated() { return sigUpdated_; } @@ -131,8 +138,8 @@ class CNOID_EXPORT SgObject : public ClonableReferenced const std::string& uriObjectName() const; bool hasUriFragment() const { return uriInfo && !uriInfo->fragment.empty(); } const std::string& uriFragment() const; - bool hasUriMetadataString() const { return uriInfo && !uriInfo->metadata.empty(); } - const std::string& uriMetadataString() const; + + Mapping* uriMetadata() const; void setUriWithFilePathAndBaseDirectory(const std::string& filePath, const std::string& baseDirectory); [[deprecated("Use setUriWithFilePathAndBaseDirectory.")]] void setUriByFilePathAndBaseDirectory(const std::string& filePath, const std::string& baseDirectory); @@ -142,7 +149,7 @@ class CNOID_EXPORT SgObject : public ClonableReferenced void setUri(const std::string& uri, const std::string& absoluteUri); void setUriObjectName(const std::string& name); void setUriFragment(const std::string& fragment); - void setUriMetadataString(const std::string& data); + void setUriMetadata(Mapping* data); void clearUri() { uriInfo.reset(); } bool isNode() const { return hasAttribute(Node); } @@ -172,12 +179,13 @@ class CNOID_EXPORT SgObject : public ClonableReferenced std::string absoluteUri; std::string objectName; std::string fragment; - std::string metadata; + ReferencedPtr metadata; // Actual type is MappingPtr }; mutable std::unique_ptr uriInfo; SgObject* findObject_(std::function& pred); + bool traverseObjects_(std::function& pred); }; typedef ref_ptr SgObjectPtr; @@ -229,6 +237,10 @@ class CNOID_EXPORT SgNode : public SgObject void releaseDecorationReference() { --decorationRefCounter; } bool isDecoratedSomewhere() const { return decorationRefCounter > 0; } + bool traverseNodes(std::function pred) { + return traverseNodes_(pred); + } + protected: SgNode(int classId); virtual Referenced* doClone(CloneMap* cloneMap) const override; @@ -237,6 +249,8 @@ class CNOID_EXPORT SgNode : public SgObject int classId_; int decorationRefCounter; + bool traverseNodes_(std::function& pred); + //! \deprecated static int registerNodeType(const std::type_info& nodeType, const std::type_info& superType); }; @@ -303,7 +317,7 @@ class CNOID_EXPORT SgGroup : public SgNode bool removeChild(SgNode* node, SgUpdateRef update = nullptr); void removeChildAt(int index, SgUpdateRef update = nullptr); void clearChildren(SgUpdateRef update = nullptr); - void copyChildrenTo(SgGroup* group, SgUpdateRef update = nullptr); + void copyChildrenTo(SgGroup* group, SgUpdateRef update = nullptr) const; void moveChildrenTo(SgGroup* group, SgUpdateRef update = nullptr); [[deprecated("Use insertChild(int index, SgNode* node, SgUpdateRef update)")]] diff --git a/src/Util/SceneMarkers.cpp b/src/Util/SceneMarkers.cpp index 8470e17d0..e08ebf624 100644 --- a/src/Util/SceneMarkers.cpp +++ b/src/Util/SceneMarkers.cpp @@ -235,7 +235,6 @@ void SphereMarker::initialize(double radius, const Vector3f& color, float transp void SphereMarker::setRadius(double r) { scale->setScale(r); - scale->notifyUpdate(); } @@ -243,10 +242,15 @@ void SphereMarker::setColor(const Vector3f& c) { material->setDiffuseColor(c); material->setEmissiveColor(c); - material->notifyUpdate(); } +void SphereMarker::setTransparency(float transparency) +{ + material->setTransparency(transparency); +} + + BoundingBoxMarker::BoundingBoxMarker(const BoundingBox& bbox, const Vector3f& color, float transparency, double width) { setAttribute(Marker); diff --git a/src/Util/SceneMarkers.h b/src/Util/SceneMarkers.h index 6ae458f06..9ca2b2438 100644 --- a/src/Util/SceneMarkers.h +++ b/src/Util/SceneMarkers.h @@ -67,6 +67,8 @@ class CNOID_EXPORT SphereMarker : public SgPosTransform SphereMarker(double radius, const Vector3f& color, float transparency = 0.0f); void setRadius(double r); void setColor(const Vector3f& c); + void setTransparency(float transparency); + private: void initialize(double radius, const Vector3f& color, float transparency); SgScaleTransformPtr scale; diff --git a/src/Util/SceneNodeClassRegistry.cpp b/src/Util/SceneNodeClassRegistry.cpp index 267b0feab..63b46e224 100644 --- a/src/Util/SceneNodeClassRegistry.cpp +++ b/src/Util/SceneNodeClassRegistry.cpp @@ -10,6 +10,7 @@ SceneNodeClassRegistry SceneNodeClassRegistry::instance_; SceneNodeClassRegistry::SceneNodeClassRegistry() + : HierarchicalClassRegistry("SgNode") { reserve(50); } diff --git a/src/Util/StdSceneReader.cpp b/src/Util/StdSceneReader.cpp index 1fcc973d9..16fdbabb1 100644 --- a/src/Util/StdSceneReader.cpp +++ b/src/Util/StdSceneReader.cpp @@ -69,17 +69,16 @@ class StdSceneReader::Impl unique_ptr yamlReader; string directory; string file; - string metadata; + MappingPtr metadata; }; typedef ref_ptr ResourceInfoPtr; - map resourceInfoMap; + unordered_map resourceInfoMap; string baseDirectory; SceneLoader sceneLoader; - bool sceneLoaderConfigurationChanged; unique_ptr uriSchemeProcessor; - typedef map ImagePathToSgImageMap; + typedef unordered_map ImagePathToSgImageMap; ImagePathToSgImageMap imagePathToSgImageMap; typedef SgNode* (Impl::*NodeFunction)(Mapping* info); @@ -137,8 +136,9 @@ class StdSceneReader::Impl SgNode* readText(Mapping* info); SgNode* readResourceAsScene(Mapping* info); Resource readResourceNode(Mapping* info); + MappingPtr readOldFormatMetaDataString(const std::string& data); void extractNamedSceneNodes(Mapping* resourceNode, ResourceInfo* info, Resource& resource); - ResourceInfo* getOrCreateResourceInfo(Mapping* resourceNode, const string& uri, const string& metadata); + ResourceInfo* getOrCreateResourceInfo(Mapping* resourceNode, const string& uri, Mapping* metadata); stdx::filesystem::path findFileInPackage(const string& file); void adjustNodeCoordinate(SceneNodeInfo& info); void makeSceneNodeMap(ResourceInfo* info); @@ -213,7 +213,6 @@ StdSceneReader::Impl::Impl(StdSceneReader* self) } os_ = &nullout(); - sceneLoaderConfigurationChanged = false; imageIO.setUpsideDown(true); } @@ -1672,7 +1671,14 @@ StdSceneReader::Resource StdSceneReader::Impl::readResourceNode(Mapping* info) if(fragmentNode->isValid()){ resource.fragment = fragmentNode->toString(); } - info->read("metadata", resource.metadata); + auto metadataNode = info->find("metadata"); + if(metadataNode->isValid()){ + if(metadataNode->isMapping()){ + resource.metadata = metadataNode->toMapping(); + } else if(metadataNode->isString()){ + resource.metadata = readOldFormatMetaDataString(metadataNode->toString()); + } + } ResourceInfo* resourceInfo = getOrCreateResourceInfo(info, resource.uri, resource.metadata); if(resourceInfo){ @@ -1700,11 +1706,30 @@ StdSceneReader::Resource StdSceneReader::Impl::readResourceNode(Mapping* info) if(resource.scene){ resource.scene = readTransformParameters(info, resource.scene); - if(auto uriObject = resource.scene->findObject([](SgObject* object){ return object->hasUri(); })){ - uriObject->setUri(resource.uri, resource.file); - if(!resource.fragment.empty()){ - uriObject->setUriFragment(resource.fragment); - } + + SgObject* uriObject = resource.scene->findObject( + [&resource](SgObject* object){ + if(object->hasUri()){ + size_t pos = object->uri().rfind(resource.uri); + if(pos != string::npos){ + if(object->uri().size() - pos == resource.uri.size()){ + return true; + } + } + } + return false; + }); + + if(!uriObject){ + uriObject = resource.scene; + } + + uriObject->setUri(resource.uri, resource.file); + if(!resource.fragment.empty()){ + uriObject->setUriFragment(resource.fragment); + } + if(resource.metadata){ + uriObject->setUriMetadata(resource.metadata); } } } @@ -1713,6 +1738,33 @@ StdSceneReader::Resource StdSceneReader::Impl::readResourceNode(Mapping* info) } +MappingPtr StdSceneReader::Impl::readOldFormatMetaDataString(const std::string& data) +{ + MappingPtr metadata; + if(!data.empty()){ + metadata = new Mapping; + size_t start; + size_t end = 0; + string symbol; + while((start = data.find_first_not_of(' ', end)) != std::string::npos) { + end = data.find(' ', start); + symbol = data.substr(start, end - start); + if(symbol == "millimeter"){ + metadata->write("length_unit", "millimeter"); + } else if(symbol == "inch"){ + metadata->write("length_unit", "inch"); + } else if(symbol == "y_upper"){ + metadata->write("upper_axis", "Y"); + } + } + if(metadata->empty()){ + metadata.reset(); + } + } + return metadata; +} + + void StdSceneReader::Impl::extractNamedSceneNodes (Mapping* resourceNode, ResourceInfo* info, Resource& resource) { @@ -1737,9 +1789,16 @@ void StdSceneReader::Impl::extractNamedSceneNodes StdSceneReader::Impl::ResourceInfo* -StdSceneReader::Impl::getOrCreateResourceInfo(Mapping* resourceNode, const string& uri, const string& metadata) +StdSceneReader::Impl::getOrCreateResourceInfo(Mapping* resourceNode, const string& uri, Mapping* metadata) { - auto iter = resourceInfoMap.find(uri); + string uriWithMetadata; + if(!metadata){ + uriWithMetadata = uri; + } else { + uriWithMetadata = formatC("{0}?metadata={1:0x}", uri, metadata->getContentHash()); + } + + auto iter = resourceInfoMap.find(uriWithMetadata); if(iter != resourceInfoMap.end()){ return iter->second; @@ -1768,29 +1827,10 @@ StdSceneReader::Impl::getOrCreateResourceInfo(Mapping* resourceNode, const strin info->yamlReader = std::move(reader); } else { - if(sceneLoaderConfigurationChanged){ - sceneLoader.setLengthUnitHint(AbstractSceneLoader::Meter); - sceneLoader.setUpperAxisHint(AbstractSceneLoader::Z_Upper); - sceneLoaderConfigurationChanged = false; - } - if(!metadata.empty()){ - size_t start; - size_t end = 0; - string symbol; - while((start = metadata.find_first_not_of(' ', end)) != std::string::npos) { - end = metadata.find(' ', start); - symbol = metadata.substr(start, end - start); - if(symbol == "millimeter"){ - sceneLoader.setLengthUnitHint(AbstractSceneLoader::Millimeter); - sceneLoaderConfigurationChanged = true; - } else if(symbol == "inch"){ - sceneLoader.setLengthUnitHint(AbstractSceneLoader::Inch); - sceneLoaderConfigurationChanged = true; - } else if(symbol == "y_upper"){ - sceneLoader.setUpperAxisHint(AbstractSceneLoader::Y_Upper); - sceneLoaderConfigurationChanged = true; - } - } + sceneLoader.clearHintsForLoading(); + if(metadata){ + sceneLoader.restoreLengthUnitAndUpperAxisHints(metadata); + info->metadata = metadata; } SgNodePtr scene = sceneLoader.load(info->file); if(!scene){ @@ -1802,7 +1842,7 @@ StdSceneReader::Impl::getOrCreateResourceInfo(Mapping* resourceNode, const strin info->directory = toUTF8(filePath.parent_path().string()); - resourceInfoMap[uri] = info; + resourceInfoMap[uriWithMetadata] = info; return info; } diff --git a/src/Util/StdSceneReader.h b/src/Util/StdSceneReader.h index f51fa1740..8baaae71a 100644 --- a/src/Util/StdSceneReader.h +++ b/src/Util/StdSceneReader.h @@ -71,7 +71,7 @@ class CNOID_EXPORT StdSceneReader std::string file; std::string directory; std::string fragment; - std::string metadata; + MappingPtr metadata; }; Resource readResourceNode(Mapping* info); diff --git a/src/Util/StdSceneWriter.cpp b/src/Util/StdSceneWriter.cpp index a873baeb0..6eb148ecc 100644 --- a/src/Util/StdSceneWriter.cpp +++ b/src/Util/StdSceneWriter.cpp @@ -18,7 +18,6 @@ #include "Format.h" #include #include -#include #include "gettext.h" using namespace std; @@ -43,6 +42,7 @@ class StdSceneWriter::Impl bool isMeshEnabled; SgMaterialPtr defaultMaterial; unique_ptr uriSchemeProcessor; + FilePathVariableProcessorPtr filePathVariableProcessor; unique_ptr yamlWriter; unique_ptr subSceneWriter; unique_ptr objSceneWriter; @@ -58,8 +58,6 @@ class StdSceneWriter::Impl bool isOriginalSceneExtModelFileUriRewritingEnabled; unordered_map uriRewritingMap; set extModelFiles; - regex uriSchemeRegex; - bool isUriSchemeRegexReady; ostream* os_; ostream& os() { return *os_; } @@ -70,7 +68,7 @@ class StdSceneWriter::Impl StdSceneWriter* getOrCreateSubSceneWriter(); ObjSceneWriter* getOrCreateObjSceneWriter(); void setOutputBaseDirectory(const std::string& directory); - void ensureUriSchemeProcessor(FilePathVariableProcessor* fpvp = nullptr); + void ensureUriSchemeProcessor(); bool writeScene(const std::string& filename, SgNode* node, const std::vector* pnodes); void rewriteOriginalSceneExtModelFileUris(); pair findOrCreateMapping(SgObject* object); @@ -158,7 +156,6 @@ StdSceneWriter::Impl::Impl(StdSceneWriter* self) isMeshEnabled = true; isOriginalSceneExtModelFileUriRewritingEnabled = false; extModelFileMode = EmbedModels; - isUriSchemeRegexReady = false; os_ = &nullout(); } @@ -183,6 +180,7 @@ void StdSceneWriter::Impl::copyConfigurations(const Impl* org) outputBaseDirPath = org->outputBaseDirPath; originalBaseDirPath = org->originalBaseDirPath; os_ = org->os_; + filePathVariableProcessor = org->filePathVariableProcessor; if(org->yamlWriter){ getOrCreateYamlWriter()->setIndentWidth(org->yamlWriter->indentWidth()); } @@ -259,32 +257,33 @@ void StdSceneWriter::Impl::setOutputBaseDirectory(const std::string& directory) { outputBaseDirectory = directory; outputBaseDirPath = fromUTF8(directory); - if(uriSchemeProcessor){ - uriSchemeProcessor->filePathVariableProcessor()->setBaseDirectory(directory); + if(filePathVariableProcessor){ + filePathVariableProcessor->setBaseDirectory(directory); } } -void StdSceneWriter::Impl::ensureUriSchemeProcessor(FilePathVariableProcessor* fpvp) +void StdSceneWriter::Impl::ensureUriSchemeProcessor() { if(!uriSchemeProcessor){ uriSchemeProcessor = make_unique(); - if(!fpvp){ - fpvp = new FilePathVariableProcessor; - fpvp->setBaseDirPath(outputBaseDirPath); + if(!filePathVariableProcessor){ + filePathVariableProcessor = new FilePathVariableProcessor; + filePathVariableProcessor->setBaseDirPath(outputBaseDirPath); } - } - if(fpvp){ - uriSchemeProcessor->setFilePathVariableProcessor(fpvp); + uriSchemeProcessor->setFilePathVariableProcessor(filePathVariableProcessor); } } void StdSceneWriter::setFilePathVariableProcessor(FilePathVariableProcessor* fpvp) { - impl->ensureUriSchemeProcessor(fpvp); + impl->filePathVariableProcessor = fpvp; impl->outputBaseDirectory = fpvp->baseDirectory(); impl->outputBaseDirPath = fpvp->baseDirPath(); + if(impl->uriSchemeProcessor){ + impl->uriSchemeProcessor->setFilePathVariableProcessor(fpvp); + } } @@ -378,6 +377,7 @@ void StdSceneWriter::clear() impl->sceneToYamlNodeMap.clear(); impl->uriRewritingMap.clear(); impl->extModelFiles.clear(); + clearImageFileInformation(); } @@ -591,9 +591,8 @@ void StdSceneWriter::Impl::makeLinkToOriginalModelFile(Mapping* archive, SgObjec } archive->write("uri", uri, DOUBLE_QUOTED); - auto& metadata = sceneObject->uriMetadataString(); - if(!metadata.empty()){ - archive->write("metadata", metadata, DOUBLE_QUOTED); + if(auto metadata = sceneObject->uriMetadata()){ + archive->insert("metadata", metadata); } } @@ -609,9 +608,8 @@ void StdSceneWriter::Impl::copyModelFilesAndLinkToCopiedFile(Mapping* archive, S os() << formatR(_("Warning: Model file \"{0}\" cannot be copied."), sceneObject->uri()) << endl; } else { archive->write("uri", relativeFilePathToCopiedFile, DOUBLE_QUOTED); - auto& metadata = sceneObject->uriMetadataString(); - if(!metadata.empty()){ - archive->write("metadata", metadata, DOUBLE_QUOTED); + if(auto metadata = sceneObject->uriMetadata()){ + archive->insert("metadata", metadata); } } } @@ -634,8 +632,7 @@ string StdSceneWriter::Impl::copyModelFiles(SgObject* sceneObject) findPathInDirectory(originalBaseDirPath, srcFilePath, relativeFilePath); } if(relativeFilePath.empty()){ - uriSchemeProcessor->detectScheme(sceneObject->uri()); - filesystem::path uriPath = fromUTF8(uriSchemeProcessor->path()); + filesystem::path uriPath = fromUTF8(uriSchemeProcessor->getFilePath(sceneObject->uri())); uriPath = uriPath.lexically_normal(); if(uriPath.has_root_path()){ uriPath = uriPath.relative_path(); @@ -688,11 +685,9 @@ string StdSceneWriter::Impl::copyModelFiles(SgObject* sceneObject) } } - if(!relativeFilePathToCopiedFile.empty()){ - int n = sceneObject->numChildObjects(); - for(int i=0; i < n; ++i){ - copyModelFiles(sceneObject->childObject(i)); - } + int n = sceneObject->numChildObjects(); + for(int i=0; i < n; ++i){ + copyModelFiles(sceneObject->childObject(i)); } return relativeFilePathToCopiedFile; @@ -738,7 +733,7 @@ bool StdSceneWriter::Impl::replaceOriginalModelFile } else { // New reference if(objectOfUri->hasAbsoluteUri()){ auto& absUri = objectOfUri->absoluteUri(); - if(absUri.find_first_of("file://") == 0){ + if(absUri.find("file://") == 0){ filesystem::path orgFilePath(absUri.substr(7)); if(filesystem::equivalent(fullPath, orgFilePath, ec)){ os() << formatR(_("Model file \"{0}\" cannot be replaced with the same format file in the same directory"), @@ -1224,18 +1219,18 @@ MappingPtr StdSceneWriter::Impl::writeTexture(SgTexture* texture) if(auto image = texture->image()){ if(image->hasUri()){ filesystem::path imageDirPath = outputBaseDirPath; + filesystem::path mainSceneNamePath; if(!mainSceneName.empty()){ - imageDirPath /= filesystem::path(fromUTF8(mainSceneName)); + mainSceneNamePath = filesystem::path(fromUTF8(mainSceneName)); + imageDirPath /= mainSceneNamePath; } - if(self->findOrCopyImageFile(image, toUTF8(imageDirPath.string()))){ - ensureUriSchemeProcessor(); - uriSchemeProcessor->detectScheme(image->uri()); - filesystem::path path(fromUTF8(uriSchemeProcessor->path())); - path = imageDirPath / path.filename(); - if(auto relPath = getRelativePath(path, outputBaseDirPath)){ - archive->write("uri", toUTF8(relPath->generic_string()), DOUBLE_QUOTED); + string copiedFile; + if(self->findOrCopyImageFile(image, toUTF8(imageDirPath.string()), copiedFile)){ + filesystem::path path(fromUTF8(copiedFile)); + if(path.is_relative() && !mainSceneName.empty()){ + path = mainSceneNamePath / path; } - isValid = true; + archive->write("uri", toUTF8(path.generic_string()), DOUBLE_QUOTED); if(texture->repeatS() == texture->repeatT()){ archive->write("repeat", texture->repeatS()); } else { @@ -1243,6 +1238,7 @@ MappingPtr StdSceneWriter::Impl::writeTexture(SgTexture* texture) repeat.append(texture->repeatS()); repeat.append(texture->repeatT()); } + isValid = true; } } } diff --git a/src/Util/ThreadPool.h b/src/Util/ThreadPool.h index 82cd6cd24..259940f7c 100644 --- a/src/Util/ThreadPool.h +++ b/src/Util/ThreadPool.h @@ -44,12 +44,26 @@ class ThreadPool } int size() const { return threads.size(); } - - void start(std::function f) { + + void dispatch(std::function f) { + std::unique_lock lock(mutex); + while(numActiveThreads == static_cast(threads.size())){ + finishCondition.wait(lock); + } + queue.push(f); + condition.notify_one(); + } + + void post(std::function f) { std::lock_guard guard(mutex); queue.push(f); condition.notify_one(); } + + [[deprecated("Use dispatch or post.")]] + void start(std::function f) { + post(f); + } void wait(){ std::unique_lock lock(mutex); diff --git a/src/Util/VRMLSceneLoader.cpp b/src/Util/VRMLSceneLoader.cpp index fab1d907f..2f4b00bac 100644 --- a/src/Util/VRMLSceneLoader.cpp +++ b/src/Util/VRMLSceneLoader.cpp @@ -105,5 +105,7 @@ SgNode* VRMLSceneLoader::Impl::load(const std::string& filename) } group.reset(); + node->setUriWithFilePathAndCurrentDirectory(filename); + return node.retn(); } diff --git a/src/Util/ValueTree.cpp b/src/Util/ValueTree.cpp index d387fcbbd..63079d512 100644 --- a/src/Util/ValueTree.cpp +++ b/src/Util/ValueTree.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "gettext.h" #ifdef _WIN32 @@ -42,6 +43,10 @@ ListingPtr invalidListing; constexpr double PI = 3.141592653589793238462643383279502884; constexpr double TO_RADIAN = PI / 180.0; +void hash_combine(std::size_t& seed, std::size_t hash) { + seed ^= hash + 0x9e3779b9 + (seed<<6) + (seed>>2); +} + } ValueNode::Initializer ValueNode::initializer; @@ -320,7 +325,7 @@ bool ValueNode::read(std::string& out_value) const { if(isScalar()){ out_value = static_cast(this)->stringValue_; - return !out_value.empty(); + return true; } return false; } @@ -1081,6 +1086,24 @@ void Mapping::writePath(const std::string &key, const std::string& value) } +size_t Mapping::getContentHash() const +{ + size_t seed = 0; + std::hash hasher; + for(auto& kv : *this){ + auto& node = kv.second; + if(node->isScalar()){ + hash_combine(seed, hasher(node->toString())); + } else if(node->isMapping()){ + hash_combine(seed, node->toMapping()->getContentHash()); + } else if(node->isListing()){ + hash_combine(seed, node->toListing()->getContentHash()); + } + } + return seed; +} + + Listing::Listing() { typeBits = LISTING; @@ -1270,3 +1293,20 @@ void Listing::write(int i, const std::string& value, StringStyle stringStyle) { values[i] = new ScalarNode(value, stringStyle); } + + +size_t Listing::getContentHash() const +{ + size_t seed = 0; + std::hash hasher; + for(auto& node : *this){ + if(node->isScalar()){ + hash_combine(seed, hasher(node->toString())); + } else if(node->isMapping()){ + hash_combine(seed, node->toMapping()->getContentHash()); + } else if(node->isListing()){ + hash_combine(seed, node->toListing()->getContentHash()); + } + } + return seed; +} diff --git a/src/Util/ValueTree.h b/src/Util/ValueTree.h index 596a7b3c7..575aec2a3 100644 --- a/src/Util/ValueTree.h +++ b/src/Util/ValueTree.h @@ -444,6 +444,8 @@ class CNOID_EXPORT Mapping : public ValueNode StringStyle keyStringStyle() const { return keyStringStyle_; } + size_t getContentHash() const; + #ifdef CNOID_BACKWARD_COMPATIBILITY Listing* findSequence(const std::string& key) const { return findListing(key); } Listing* openSequence(const std::string& key) { return openListing(key); } @@ -600,6 +602,8 @@ class CNOID_EXPORT Listing : public ValueNode const_iterator begin() const { return values.begin(); } const_iterator end() const { return values.end(); }; + size_t getContentHash() const; + private: Listing(int line, int column); diff --git a/src/Util/YAMLReader.cpp b/src/Util/YAMLReader.cpp index a6367efed..efb12ef64 100644 --- a/src/Util/YAMLReader.cpp +++ b/src/Util/YAMLReader.cpp @@ -409,6 +409,12 @@ void YAMLReaderImpl::onMappingEnd(yaml_event_t& event) cout << "YAMLReaderImpl::onMappingEnd()" << endl; } + if(auto mapping = nodeStack.top().node->toMapping()){ + if(mapping->empty()){ + mapping->setFlowStyle(false); + } + } + popNode(event); } diff --git a/src/Util/pybind11/PySignal.cpp b/src/Util/pybind11/PySignal.cpp index 07eb49b19..fee5cab22 100644 --- a/src/Util/pybind11/PySignal.cpp +++ b/src/Util/pybind11/PySignal.cpp @@ -13,11 +13,14 @@ namespace cnoid { void exportPySignalTypes(py::module& m) { - PySignal(m, "VoidSignal"); - PySignal(m,"BoolSignal"); - PySignal(m, "IntSignal"); - PySignal(m, "DoubleSignal"); - PySignal(m, "StringSignal"); + PySignal(m, "VoidVoidSignal"); + PySignal(m,"VoidBoolSignal"); + PySignal(m, "VoidIntSignal"); + PySignal(m, "VoidDoubleSignal"); + PySignal(m, "BoolDoubleSignal"); + PySignal(m, "VoidDoubleBoolSignal"); + PySignal(m, "DoubleDoubleBoolSignal"); + PySignal(m, "VoidStringSignal"); py::class_(m, "Connection") .def(py::init<>())