diff --git a/Installation/cmake/modules/FindLibSSH.cmake b/Installation/cmake/modules/FindLibSSH.cmake new file mode 100644 index 00000000000..e2c49ee2fce --- /dev/null +++ b/Installation/cmake/modules/FindLibSSH.cmake @@ -0,0 +1,39 @@ +# - Try to find the LibSSH libraries +# This module defines: +# LIBSSH_FOUND - system has LibSSH lib +# LIBSSH_INCLUDE_DIR - the LibSSH include directory +# LIBSSH_LIBRARIES_DIR - directory where the LibSSH libraries are located +# LIBSSH_LIBRARIES - Link these to use LibSSH + + +include(FindPackageHandleStandardArgs) +include(${CMAKE_CURRENT_LIST_DIR}/CGAL_GeneratorSpecificSettings.cmake) + +if(LIBSSH_INCLUDE_DIR) + set(LIBSSH_in_cache TRUE) +else() + set(LIBSSH_in_cache FALSE) +endif() +if(NOT LIBSSH_LIBRARIES) + set(LIBSSH_in_cache FALSE) +endif() + +# Is it already configured? +if( NOT LIBSSH_in_cache ) + + find_path(LIBSSH_INCLUDE_DIR + NAMES "libssh/libssh.h" + ) + + find_library(LIBSSH_LIBRARIES NAMES ssh libssh + HINTS "/usr/lib" + "usr/lib/x86_64-linux-gnu" + PATH_SUFFIXES lib + DOC "Path to the LIBSSH library" + ) +endif() + +SET(LIBSSH_FOUND TRUE) +if( NOT LIBSSH_LIBRARIES OR NOT LIBSSH_INCLUDE_DIR) + SET(LIBSSH_FOUND FALSE) +endif() diff --git a/Polyhedron/demo/Polyhedron/CMakeLists.txt b/Polyhedron/demo/Polyhedron/CMakeLists.txt index 1b1cd817152..7b17019b1d0 100644 --- a/Polyhedron/demo/Polyhedron/CMakeLists.txt +++ b/Polyhedron/demo/Polyhedron/CMakeLists.txt @@ -64,7 +64,7 @@ include(${CGAL_USE_FILE}) find_package(Qt5 QUIET COMPONENTS OpenGL Script - OPTIONAL_COMPONENTS ScriptTools) + OPTIONAL_COMPONENTS ScriptTools WebSockets) if(Qt5_FOUND) @@ -95,6 +95,11 @@ if( POLYHEDRON_DEMO_ACTIVATE_CONCURRENCY ) endif() endif() +#find libssh for scene sharing +find_package(LibSSH) +if( NOT LIBSSH_FOUND ) + message("NOTICE : The SSH features will be disabled.") +endif() # Activate concurrency ? (turned OFF by default) option(CGAL_ACTIVATE_CONCURRENT_MESH_3 @@ -131,7 +136,7 @@ if(CGAL_Qt5_FOUND AND Qt5_FOUND) qt5_wrap_ui( statisticsUI_FILES Statistics_on_item_dialog.ui) qt5_wrap_ui( FileLoaderDialogUI_files FileLoaderDialog.ui ) qt5_wrap_ui( Show_point_dialogUI_FILES Show_point_dialog.ui ) - qt5_wrap_ui( PreferencesUI_FILES Preferences.ui Details.ui) + qt5_wrap_ui( PreferencesUI_FILES Preferences.ui Details.ui SSH_dialog.ui) qt5_wrap_ui( Show_point_dialogUI_FILES Show_point_dialog.ui ) qt5_wrap_ui( ViewerUI_FILES LightingDialog.ui) qt5_generate_moc( "File_loader_dialog.h" "${CMAKE_CURRENT_BINARY_DIR}/File_loader_dialog_moc.cpp" ) @@ -195,6 +200,11 @@ if(CGAL_Qt5_FOUND AND Qt5_FOUND) target_link_libraries(demo_framework PUBLIC Qt5::OpenGL Qt5::Widgets Qt5::Gui Qt5::Script ) + if(TARGET Qt5::WebSockets) + target_link_libraries(demo_framework PUBLIC Qt5::WebSockets) + message(STATUS "Qt5WebSockets was found. Using WebSockets is therefore possible.") + endif() + cgal_add_compilation_test(demo_framework) # Let's define `three_EXPORT` during the compilation of `demo_framework`, # in addition of `demo_framework_EXPORT` (defined automatically by @@ -329,11 +339,20 @@ if(CGAL_Qt5_FOUND AND Qt5_FOUND) MainWindow.cpp Polyhedron_demo.cpp File_loader_dialog_moc.cpp + Use_ssh.cpp ${CGAL_Qt5_RESOURCE_FILES} ${CGAL_Qt5_MOC_FILES} ${FileLoaderDialogUI_files} ${MainWindowUI_files} ${PreferencesUI_FILES} ${statisticsUI_FILES} ${SubViewerUI_files}) target_link_libraries(polyhedron_demo PUBLIC demo_framework point_dialog Qt5::Gui Qt5::OpenGL Qt5::Widgets Qt5::Script) + if(LIBSSH_FOUND) + add_definitions(-DCGAL_USE_SSH) + target_link_libraries(polyhedron_demo PUBLIC ${LIBSSH_LIBRARIES}) + endif() #libssh + if(TARGET Qt5::WebSockets) + add_definitions(-DCGAL_USE_WEBSOCKETS) + target_link_libraries(polyhedron_demo PUBLIC Qt5::WebSockets) + endif() add_executable ( Polyhedron_3 Polyhedron_3.cpp ) target_link_libraries( Polyhedron_3 PRIVATE polyhedron_demo ) add_to_cached_list( CGAL_EXECUTABLE_TARGETS Polyhedron_3 ) diff --git a/Polyhedron/demo/Polyhedron/MainWindow.cpp b/Polyhedron/demo/Polyhedron/MainWindow.cpp index 2230abc1f1a..d8872fd851f 100644 --- a/Polyhedron/demo/Polyhedron/MainWindow.cpp +++ b/Polyhedron/demo/Polyhedron/MainWindow.cpp @@ -1,3 +1,6 @@ +#ifdef CGAL_USE_SSH +# include "CGAL/Use_ssh.h" +#endif #include #include "config.h" @@ -43,6 +46,7 @@ #include #include #include +#include #ifdef QT_SCRIPT_LIB # include # ifdef QT_SCRIPTTOOLS_LIB @@ -59,6 +63,7 @@ #include "ui_Preferences.h" #include "ui_Details.h" #include "ui_Statistics_on_item_dialog.h" +#include "ui_SSH_dialog.h" #include "Show_point_dialog.h" #include "File_loader_dialog.h" #include "Viewer.h" @@ -70,6 +75,14 @@ # include # include #include "Color_map.h" + + +#ifdef CGAL_USE_WEBSOCKETS +#include +#include +#include +#endif + using namespace CGAL::Three; QScriptValue myScene_itemToScriptValue(QScriptEngine *engine, @@ -151,6 +164,7 @@ MainWindow::MainWindow(const QStringList &keywords, bool verbose, QWidget* paren // remove the Load Script menu entry, when the demo has not been compiled with QT_SCRIPT_LIB #if !defined(QT_SCRIPT_LIB) ui->menuBar->removeAction(ui->actionLoadScript); + ui->menuBar->removeAction(ui->on_actionLoad_a_Scene_from_a_Script_File); #endif // Save some pointers from ui, for latter use. sceneView = ui->sceneView; @@ -159,9 +173,9 @@ MainWindow::MainWindow(const QStringList &keywords, bool verbose, QWidget* paren CGAL::Three::Three::s_mainviewer = viewer; viewer->setObjectName("mainViewer"); viewer_window->showMaximized(); - viewer_window->setWindowFlags( + viewer_window->setWindowFlags( Qt::SubWindow - | Qt::CustomizeWindowHint + | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowTitleHint @@ -354,7 +368,7 @@ MainWindow::MainWindow(const QStringList &keywords, bool verbose, QWidget* paren // Load plugins, and re-enable actions that need it. operationSearchBar.setPlaceholderText("Filter..."); - searchAction->setDefaultWidget(&operationSearchBar); + searchAction->setDefaultWidget(&operationSearchBar); connect(&operationSearchBar, &QLineEdit::textChanged, this, [=](){filterOperations(true);}); loadPlugins(); @@ -690,7 +704,7 @@ bool MainWindow::load_plugin(QString fileName, bool blacklisted) else{ //qdebug << "error: " << qPrintable(loader.errorString()); pluginsStatus_map[name] = loader.errorString(); - + } PathNames_map[name].push_back(fileinfo.absoluteDir().absolutePath()); return true; @@ -934,7 +948,7 @@ void MainWindow::viewerShow(float xmin, } void MainWindow::viewerShow(Viewer_interface* vi, float x, float y, float z) { - + CGAL::qglviewer::ManipulatedCameraFrame backup_frame(*vi->camera()->frame()); vi->camera()->fitSphere(CGAL::qglviewer::Vec(x, y, z), vi->camera()->sceneRadius()/100); @@ -1001,20 +1015,21 @@ void MainWindow::computeViewerBBox(CGAL::qglviewer::Vec& min, CGAL::qglviewer::V const double xmax = bbox.xmax(); const double ymax = bbox.ymax(); const double zmax = bbox.zmax(); - - - + + + min = CGAL::qglviewer::Vec(xmin, ymin, zmin); max= CGAL::qglviewer::Vec(xmax, ymax, zmax); - + CGAL::qglviewer::Vec bbox_center((xmin+xmax)/2, (ymin+ymax)/2, (zmin+zmax)/2); + double bbox_diag = CGAL::approximate_sqrt( CGAL::square(xmax - xmin) + CGAL::square(ymax - ymin) + CGAL::square(zmax - zmin)); CGAL::qglviewer::Vec offset(0,0,0); - + double l_dist = (std::max)((std::abs)(bbox_center.x - viewer->offset().x), (std::max)((std::abs)(bbox_center.y - viewer->offset().y), (std::abs)(bbox_center.z - viewer->offset().z))); @@ -1240,7 +1255,7 @@ void MainWindow::open(QString filename) settings.setValue("OFF open directory", fileinfo.absoluteDir().absolutePath()); loadItem(fileinfo, findLoader(load_pair.first), ok); - + if(!ok) return; this->addToRecentFiles(fileinfo.absoluteFilePath()); @@ -1427,7 +1442,7 @@ void MainWindow::selectionChanged() { if(vi == NULL) continue; - + if(item != NULL && item->manipulatable()) { vi->setManipulatedFrame(item->manipulatedFrame()); } else { @@ -1562,7 +1577,7 @@ void MainWindow::showSceneContextMenu(const QPoint& p) { menu_actions["line width"] = action->menu()->actions().last(); } } - + } Q_FOREACH(Scene::Item_id index, scene->selectionIndices()) { @@ -1595,7 +1610,7 @@ void MainWindow::showSceneContextMenu(const QPoint& p) { )->value()); slider->setOrientation(Qt::Horizontal); sliderAction->setDefaultWidget(slider); - + connect(slider, &QSlider::valueChanged, [this, slider]() { Q_FOREACH(Scene::Item_id id, scene->selectionIndices()) @@ -1630,7 +1645,7 @@ void MainWindow::showSceneContextMenu(const QPoint& p) { )->value()); slider->setOrientation(Qt::Horizontal); sliderAction->setDefaultWidget(slider); - + connect(slider, &QSlider::valueChanged, [this, slider]() { Q_FOREACH(Scene::Item_id id, scene->selectionIndices()) @@ -1665,7 +1680,7 @@ void MainWindow::showSceneContextMenu(const QPoint& p) { )->value()); slider->setOrientation(Qt::Horizontal); sliderAction->setDefaultWidget(slider); - + connect(slider, &QSlider::valueChanged, [this, slider]() { Q_FOREACH(Scene::Item_id id, scene->selectionIndices()) @@ -1708,7 +1723,7 @@ void MainWindow::showSceneContextMenu(const QPoint& p) { )->value()); slider->setOrientation(Qt::Horizontal); sliderAction->setDefaultWidget(slider); - + connect(slider, &QSlider::valueChanged, [this, slider]() { Q_FOREACH(Scene::Item_id id, scene->selectionIndices()) @@ -1832,6 +1847,7 @@ void MainWindow::readSettings() this->default_point_size = settings.value("points_size").toInt(); this->default_normal_length = settings.value("normals_length").toInt(); this->default_lines_width = settings.value("lines_width").toInt(); + setProperty("ws_url", settings.value("ws_server_url").toString()); } void MainWindow::writeSettings() @@ -1906,14 +1922,7 @@ void MainWindow::throw_exception() { void MainWindow::on_actionLoadScript_triggered() { #if defined(QT_SCRIPT_LIB) - QString filename = QFileDialog::getOpenFileName( - this, - tr("Select a script to run..."), - ".", - "QTScripts (*.js);;All Files (*)"); - if(filename.isEmpty()) - return; - loadScript(QFileInfo(filename)); + #endif } @@ -1943,7 +1952,7 @@ void MainWindow::on_actionLoad_triggered() filters << filter; } } - + QString directory = settings.value("OFF open directory", QDir::current().dirName()).toString(); @@ -1971,7 +1980,7 @@ void MainWindow::on_actionLoad_triggered() static_cast(nb_files), std::back_inserter(colors_)); std::size_t nb_item = -1; - + Q_FOREACH(const QString& filename, dialog.selectedFiles()) { CGAL::Three::Scene_item* item = NULL; @@ -2216,7 +2225,6 @@ void MainWindow::on_actionPreferences_triggered() QDialog dialog(this); Ui::PreferencesDialog prefdiag; prefdiag.setupUi(&dialog); - float lineWidth[2]; if(!viewer->isOpenGL_4_3()) viewer->glGetFloatv(GL_LINE_WIDTH_RANGE, lineWidth); @@ -2227,22 +2235,22 @@ void MainWindow::on_actionPreferences_triggered() } prefdiag.linesHorizontalSlider->setMinimum(lineWidth[0]); prefdiag.linesHorizontalSlider->setMaximum(lineWidth[1]); - + prefdiag.offset_updateCheckBox->setChecked( settings.value("offset_update", false).toBool()); connect(prefdiag.offset_updateCheckBox, SIGNAL(toggled(bool)), scene, SLOT(enableVisibilityRecentering(bool))); - + prefdiag.antialiasingCheckBox->setChecked(settings.value("antialiasing", false).toBool()); connect(prefdiag.antialiasingCheckBox, SIGNAL(toggled(bool)), viewer, SLOT(setAntiAliasing(bool))); - + prefdiag.quick_cameraCheckBox->setChecked( settings.value("quick_camera_mode", true).toBool()); connect(prefdiag.quick_cameraCheckBox, SIGNAL(toggled(bool)), viewer, SLOT(setFastDrawing(bool))); prefdiag.max_itemsSpinBox->setValue(viewer->textRenderer()->getMax_textItems()); - + connect(prefdiag.max_itemsSpinBox,static_cast(&QSpinBox::valueChanged), this, [this](int i){ setMaxTextItemsDisplayed(i); @@ -2273,20 +2281,20 @@ void MainWindow::on_actionPreferences_triggered() }); connect(prefdiag.background_colorPushButton, &QPushButton::clicked, this, &MainWindow::setBackgroundColor); - + connect(prefdiag.default_save_asPushButton, &QPushButton::clicked, this, &MainWindow::setDefaultSaveDir); - + connect(prefdiag.lightingPushButton, &QPushButton::clicked, this, &MainWindow::setLighting_triggered); - + prefdiag.surface_meshComboBox->setCurrentText(CGAL::Three::Three::modeName( CGAL::Three::Three::s_defaultSMRM)); connect(prefdiag.surface_meshComboBox, &QComboBox::currentTextChanged, this, [this](const QString& text){ this->s_defaultSMRM = CGAL::Three::Three::modeFromName(text); }); - + prefdiag.point_setComboBox->setCurrentText(CGAL::Three::Three::modeName( CGAL::Three::Three::s_defaultPSRM)); connect(prefdiag.point_setComboBox, &QComboBox::currentTextChanged, @@ -2360,6 +2368,70 @@ void MainWindow::on_actionPreferences_triggered() }); dialog.exec(); }); + connect(prefdiag.sshButton, &QPushButton::clicked, + this, [this](){ + QDialog dialog(this); + Ui::SSHDialog sshdiag; + sshdiag.setupUi(&dialog); + +#ifdef CGAL_USE_SSH + sshdiag.userBox->setEnabled(true); + sshdiag.serverBox->setEnabled(true); + sshdiag.pkBox->setEnabled(true); + sshdiag.privkBox->setEnabled(true); + sshdiag.userEdit->setText(settings.value("ssh_user", QString()).toString()); + sshdiag.serverEdit->setText(settings.value("ssh_server", QString()).toString()); + sshdiag.publicEdit->setText(settings.value("ssh_public_key", QString()).toString()); + sshdiag.privkEdit->setText(settings.value("ssh_priv_key", QString()).toString()); + connect(sshdiag.pubButton, &QPushButton::clicked, + this, [this, sshdiag](){ + QFileDialog diag(this, + "Public Key", + "", + "All Files (*)"); + diag.setFilter(QDir::Hidden|QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); + if(!diag.exec()) + return; + sshdiag.publicEdit->setText(diag.selectedFiles().front()); + }); + connect(sshdiag.privButton, &QPushButton::clicked, + this, [this, sshdiag](){ + QFileDialog diag(this, + "Private Key", + "", + "All Files (*)"); + diag.setFilter(QDir::Hidden|QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); + if(!diag.exec()) + return; + sshdiag.privkEdit->setText(diag.selectedFiles().front()); + }); +#else + sshdiag.userBox->setEnabled(false); + sshdiag.serverBox->setEnabled(false); + sshdiag.pkBox->setEnabled(false); + sshdiag.privkBox->setEnabled(false); +#endif + sshdiag.wsEdit->setText(settings.value("ws_server_url", QString()).toString()); + + dialog.exec(); + if ( dialog.result() ) + { +#ifdef CGAL_USE_SSH + settings.setValue("ssh_user", + sshdiag.userEdit->text()); + settings.setValue("ssh_server", + sshdiag.serverEdit->text()); + settings.setValue("ssh_public_key", + sshdiag.publicEdit->text()); + settings.setValue("ssh_priv_key", + sshdiag.privkEdit->text()); +#endif + settings.setValue("ws_server_url", + sshdiag.wsEdit->text()); + setProperty("ws_url", sshdiag.wsEdit->text()); + } + }); + dialog.exec(); if ( dialog.result() ) @@ -2372,7 +2444,7 @@ void MainWindow::on_actionPreferences_triggered() if (item->checkState(0)==Qt::Unchecked) plugin_blacklist.insert(item->text(1)); } - + //write settings settings.setValue("antialiasing", prefdiag.antialiasingCheckBox->isChecked()); @@ -2392,7 +2464,7 @@ void MainWindow::on_actionPreferences_triggered() settings.setValue("points_size", this->default_point_size); settings.setValue("normals_length", this->default_normal_length); settings.setValue("lines_width", this->default_lines_width); - + } else { @@ -2412,7 +2484,7 @@ void MainWindow::setBackgroundColor() v->update(); } } - + } void MainWindow::setLighting_triggered() @@ -2574,7 +2646,7 @@ QString MainWindow::get_item_stats() Q_FOREACH(int id, scene->selectionIndices()) { Scene_item* item = scene->item(id); - QString classname = item->property("classname").toString(); + QString classname = item->property("classname").toString(); if(classname.isEmpty()) classname = item->metaObject()->className(); if(!classnames.contains(classname)) @@ -2801,53 +2873,183 @@ void MainWindow::propagate_action() } } +QString make_fullpath(const QString& filename, bool duplicate = false) +{ + QString fullpath = QString("%1/%2").arg(QDir::tempPath()).arg(filename); + QString tmp_fullpath = fullpath; + if(duplicate) + { + int i=0; + while(QFileInfo(tmp_fullpath).exists()) + { + QString basename = QFileInfo(tmp_fullpath).baseName(); + QString dir = QFileInfo(tmp_fullpath).dir().path(); + QString suffix= QFileInfo(fullpath).completeSuffix(); + tmp_fullpath=QString("%1/%2%3.%4").arg(dir).arg(basename).arg(++i).arg(suffix); + } + } + return tmp_fullpath; +} +/* + The two following functions allow to create files from string and strings from files. + This is used as a workaround of the absence of stream management in our IO system. + The whole to/from Base64 is used to avoid problems with binary formats. Everything is written + as a base64 binary string, and converted back to what it was. +*/ +QByteArray file_to_string(const char* filename) +{ + std::ifstream f(filename, std::ifstream::binary); + // get size of file + f.seekg (0,f.end); + long size = f.tellg(); + f.seekg (0); + std::ostringstream ss; + // allocate memory for file content + char* buffer = new char[size]; + + // read content of infile + f.read(buffer,size); + + // write to outfile + ss.write(buffer,size); + // release dynamically-allocated memory + delete[] buffer; + //ss.write( << f.rdbuf(); // reading data + f.close(); + std::string st = ss.str(); + QByteArray ba(st.c_str(), static_cast(st.size())); + return ba; +} + +QString MainWindow::write_string_to_file(const QString& str, const QString &filename) +{ + QString fullpath = make_fullpath(filename); + std::ofstream f(fullpath.toStdString().c_str(), std::ofstream::binary); + QByteArray compressed_item(str.toStdString().c_str()); + QByteArray item = qUncompress(QByteArray::fromBase64(compressed_item)); + QByteArray bb = item; + f.write(bb.constData(),bb.toStdString().size()); + f.close(); + return fullpath; +} + void MainWindow::on_actionSa_ve_Scene_as_Script_triggered() { - QString filename = - QFileDialog::getSaveFileName(this, - "Save the Scene as a Script File", - last_saved_dir, - "Qt Script files (*.js)"); - std::ofstream os(filename.toUtf8()); + if(scene->numberOfEntries() == 0) + return; + bool do_upload = false; +#ifdef CGAL_USE_SSH + QString user = settings.value("ssh_user", QString()).toString(); + QString pass; + if(!user.isEmpty()) + { + QMessageBox::StandardButton doyou = + QMessageBox::question(this, tr("Upload ?"), tr("Do you wish to upload the scene" + " using the SSH preferences ?")); + bool ok; + do_upload = (doyou == QMessageBox::Yes); + if(do_upload) + { + pass = QInputDialog::getText(this, "SSH Password", + "Enter ssh key password:", + QLineEdit::Password, + tr(""), + &ok); + if(!ok) + return; + pass = pass.trimmed(); + } + } +#endif + + QString filename; + + if(do_upload){ + filename = QString("%1/save_scene.js").arg(QDir::tempPath()); + }else{ + filename = QFileDialog::getSaveFileName(this, + "Save the Scene as a Script File", + last_saved_dir, + "Qt Script files (*.js)"); + } + std::ofstream os(filename.toUtf8(), std::ofstream::binary); if(!os) return; - std::vector names; - std::vector loaders; + CGAL::Three::Three::CursorScopeGuard cs(Qt::WaitCursor); + std::vector > names; + std::vector > loaders; std::vector colors; std::vector rendering_modes; QStringList not_saved; for(int i = 0; i < scene->numberOfEntries(); ++i) { Scene_item* item = scene->item(i); - QString loader = item->property("loader_name").toString(); - QString source = item->property("source filename").toString(); + QString loader;// = item->property("loader_name").toString(); + QString ext; + for(Polyhedron_demo_io_plugin_interface* iop : io_plugins) + { + if(iop->isDefaultLoader(item)) + { + QString sf = iop->saveNameFilters().split(";;").first(); + //OFF Files (*.off) + QRegularExpression re("\\(\\*\\.(.*)\\)"); + QRegularExpressionMatch rem = re.match(sf); + if(!rem.hasMatch()) + continue; + ext = rem.captured(1); + QListto_save; + to_save.append(item); + QString savename(tr("%1.%2").arg(item->name()).arg(ext)); + QString fullpath = make_fullpath(savename, true); + savename = QFileInfo(fullpath).fileName(); + iop->save(QFileInfo(fullpath), to_save); + names.push_back(std::make_pair(savename, item->name())); + loader=iop->name(); + break; + } + } if(loader.isEmpty()) { - not_saved.push_back(item->name()); + QMessageBox::warning(this, "", tr("No plugin found for %1. Not saved.").arg(item->name())); continue; } - names.push_back(source); - loaders.push_back(loader); + loaders.push_back(std::make_pair(loader, ext)); colors.push_back(item->color()); rendering_modes.push_back(item->renderingMode()); } + if(loaders.empty()) + return; //path os << "var camera = \""<dumpCameraCoordinates().toStdString()<<"\";\n"; os << "var items = ["; for(std::size_t i = 0; i< names.size() -1; ++i) { - os << "\'" << names[i].toStdString() << "\', "; + QString fullpath = make_fullpath(names[i].first); + + QByteArray item = file_to_string(fullpath.toStdString().c_str()); + os<<"[\'"; + os<show(); } visibleDockWidgets.clear(); - + } } @@ -2930,7 +3189,7 @@ void MainWindow::setupViewer(Viewer* viewer, SubViewer* subviewer) viewer, SLOT(update())); connect(scene, SIGNAL(updated()), viewer, SLOT(update())); - + QAction* action = subviewer->findChild("actionRecenter"); connect(action, SIGNAL(triggered()), viewer, SLOT(update())); @@ -2992,16 +3251,43 @@ void MainWindow::setupViewer(Viewer* viewer, SubViewer* subviewer) this, SLOT(selectSceneItem(int))); connect(viewer, SIGNAL(selectedPoint(double, double, double)), this, SLOT(showSelectedPoint(double, double, double))); - + connect(viewer, SIGNAL(selectionRay(double, double, double, double, double, double)), scene, SIGNAL(selectionRay(double, double, double, double, double, double))); - + connect(viewer, &Viewer::sendMessage, this, [](QString s){ information(s); }); + +#ifdef CGAL_USE_WEBSOCKETS + action= subviewer->viewer->findChild("actionShareCamera"); + connect(action, &QAction::toggled, + this, [this, viewer](bool b) + { + if(!viewer){ + return; + } + QString session; + if(b){ + bool ok; + session = QInputDialog::getText( + this,"Session", + "Please enter the session name.\n" + "Only the machines that enter the same session name will be connected.\n" + "Several sessions can run simultaneously on a same server. ", + QLineEdit::Normal, QString(), &ok); + if(session.isEmpty() || !ok) + { + viewer->setShareCam(false, session); + return; + } + } + viewer->setShareCam(b, session); + }); +#endif } @@ -3021,7 +3307,7 @@ void MainWindow::on_actionAdd_Viewer_triggered() scene->removeViewer(viewer2); viewerDestroyed(viewer2); }); - + setupViewer(viewer2, subviewer); viewer2->camera()->interpolateToFitScene(); subviewer->show(); @@ -3154,11 +3440,20 @@ SubViewer::SubViewer(QWidget *parent, MainWindow* mw, Viewer* mainviewer) QAction* actionTotalPass = new QAction("Set Transparency Pass &Number...",this); actionTotalPass->setObjectName("actionTotalPass"); viewMenu->addAction(actionTotalPass); +#ifdef CGAL_USE_WEBSOCKETS + QAction* actionShareCamera= new QAction("Join &WS Server",viewer); + actionShareCamera->setObjectName("actionShareCamera"); + actionShareCamera->setCheckable(true); + actionShareCamera->setChecked(false); + viewMenu->addAction(actionShareCamera); +#endif + QAction* actionBackFrontShading = new QAction("Activate Back/Front shading.",this); actionBackFrontShading->setObjectName("actionBackFrontShading"); actionBackFrontShading->setCheckable(true); actionBackFrontShading->setChecked(false); viewMenu->addAction(actionBackFrontShading); + if(mainviewer) setAttribute(Qt::WA_DeleteOnClose); setWindowIcon(QIcon(":/cgal/icons/resources/menu.png")); @@ -3206,7 +3501,7 @@ void SubViewer::color() void SubViewer::closeEvent(QCloseEvent *closeEvent) { - + if(is_main) { QMessageBox::information(mw, "", "This is the main viewer. It cannot be closed."); @@ -3227,9 +3522,9 @@ void SubViewer::changeEvent(QEvent *event) { menu->addAction(action); } - setWindowFlags( + setWindowFlags( Qt::SubWindow - | Qt::CustomizeWindowHint + | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint //| Qt::WindowSystemMenuHint | Qt::WindowTitleHint @@ -3245,9 +3540,9 @@ void SubViewer::changeEvent(QEvent *event) { menu->removeAction(action); } - setWindowFlags( + setWindowFlags( Qt::SubWindow - | Qt::CustomizeWindowHint + | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowTitleHint @@ -3269,7 +3564,7 @@ void MainWindow::invalidate_bbox(bool do_recenter) void MainWindow::on_action_Save_triggered() { - if(QMessageBox::question(this, "Save", "Are you sure you want to override these files ?") + if(QMessageBox::question(this, "Save", "Are you sure you want to override these files ?") == QMessageBox::No) return; QList to_save; @@ -3285,3 +3580,187 @@ void MainWindow::on_action_Save_triggered() } } } + +void MainWindow::on_actionLoad_a_Scene_from_a_Script_File_triggered() +{ + bool do_download = false; + QString filename; + +#ifdef CGAL_USE_SSH + QString user = settings.value("ssh_user", QString()).toString(); + QString pass; + if(!user.isEmpty()) + { + QMessageBox::StandardButton doyou = + QMessageBox::question(this, tr("Download ?"), tr("Do you wish to download the scene" + " using the SSH preferences ?")); + bool ok; + do_download= (doyou == QMessageBox::Yes); + if(do_download) + { + pass = QInputDialog::getText(this, "SSH Password", + "Enter ssh key password:", + QLineEdit::Password, + tr(""), + &ok); + if(!ok) + return; + pass = pass.trimmed(); + } + } +#endif + + if(do_download) + { + #ifdef CGAL_USE_SSH + using namespace CGAL::ssh_internal; + QString server = settings.value("ssh_server", QString()).toString(); + QString pk = settings.value("ssh_public_key", QString()).toString(); + QString privK = settings.value("ssh_priv_key", QString()).toString(); + user = user.trimmed(); + server = server.trimmed(); + pk = pk.trimmed(); + privK=privK.trimmed(); + QString path; + path = QInputDialog::getText(this, + "", + tr("Enter the remote path for your file.")); + if(path.isEmpty()) + return; + try{ + ssh_session session; + bool res = establish_ssh_session(session, + user.toStdString().c_str(), + server.toStdString().c_str(), + pk.toStdString().c_str(), + privK.toStdString().c_str(), + pass.toStdString().c_str()); + if(!res) + { + QMessageBox::warning(this, + "Error", + "The SSH session could not be started."); + return; + } + filename = QString("%1/load_scene.js").arg(QDir::tempPath()); + path = tr("/tmp/%2").arg(path); + res = pull_file(session,path.toStdString().c_str(), filename.toStdString().c_str()); + if(!res) + { + QMessageBox::warning(this, + "Error", + "The file could not be fetched. Check your console for more info."); + close_connection(session); + return; + } + close_connection(session); + } catch( ssh::SshException e ) + { + std::cout << "Error during connection : "; + std::cout << e.getError() << std::endl; + } + #endif + } + else + { + filename = QFileDialog::getOpenFileName( + this, + tr("Select a Whole Scene file..."), + ".", + "Whole Scene files (*.js)"); + if(filename.isEmpty()) + return; + } + loadScript(QFileInfo(filename)); + if(do_download){ + QFile tmp_file(filename); + tmp_file.remove(); + } +} + +#ifdef CGAL_USE_WEBSOCKETS +void MainWindow::on_action_Start_a_Session_triggered() +{ + QAction * action= findChild("action_Start_a_Session"); + static EchoServer *server =nullptr; + if(action->isChecked()){ + server = new EchoServer(1234); + QObject::connect(server, &EchoServer::closed, server,&EchoServer::deleteLater); + } + else + { + server->deleteLater(); + } +} + +EchoServer::EchoServer(quint16 port) : + QObject(CGAL::Three::Three::mainWindow()), + m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Echo Server"), + QWebSocketServer::NonSecureMode, this)) +{ + if (m_pWebSocketServer->listen(QHostAddress::Any, port)) { + connect(m_pWebSocketServer, &QWebSocketServer::newConnection, + this, &EchoServer::onNewConnection); + connect(m_pWebSocketServer, &QWebSocketServer::closed, this, &EchoServer::closed); + } + QHostAddress local_host("0.0.0.0"); + + //to avoid printing 127.0.0.1. Not realy sure it won't ever print the external ipv4 though. + const QHostAddress &localhost = QHostAddress(QHostAddress::LocalHost); + for (const QHostAddress &address: QNetworkInterface::allAddresses()) { + if (address.protocol() == QAbstractSocket::IPv4Protocol && address != localhost) + { + local_host= address; + break; + } + } + QMessageBox mb(QMessageBox::NoIcon, "WS Server", + tr("WebSockets Server started.\nEnter the following address in\nyour Network Preferences to be able to join it :\n" + "ws://%1:%2").arg(local_host.toString()).arg(port), QMessageBox::Ok, CGAL::Three::Three::mainWindow()); + mb.setTextInteractionFlags(Qt::TextSelectableByMouse); + mb.exec(); +} + +EchoServer::~EchoServer() +{ + m_pWebSocketServer->close(); + qDeleteAll(m_clients.begin(), m_clients.end()); +} + +void EchoServer::onNewConnection() +{ + QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection(); + + connect(pSocket, &QWebSocket::textMessageReceived, this, &EchoServer::processTextMessage); + connect(pSocket, &QWebSocket::binaryMessageReceived, this, &EchoServer::processBinaryMessage); + connect(pSocket, &QWebSocket::disconnected, this, &EchoServer::socketDisconnected); + + m_clients << pSocket; +} + +void EchoServer::processTextMessage(QString message) +{ + QWebSocket *pClient = qobject_cast(sender()); + for(auto *client : m_clients) { + if(client != pClient) + client->sendTextMessage(message); + } +} + +void EchoServer::processBinaryMessage(QByteArray message) +{ + QWebSocket *pClient = qobject_cast(sender()); + if (pClient) { + pClient->sendBinaryMessage(message); + } +} + +void EchoServer::socketDisconnected() +{ + QWebSocket *pClient = qobject_cast(sender()); + if (pClient) { + m_clients.removeAll(pClient); + pClient->deleteLater(); + } +} +#endif diff --git a/Polyhedron/demo/Polyhedron/MainWindow.h b/Polyhedron/demo/Polyhedron/MainWindow.h index e4bd6d5b3b1..b7650b52b34 100644 --- a/Polyhedron/demo/Polyhedron/MainWindow.h +++ b/Polyhedron/demo/Polyhedron/MainWindow.h @@ -138,6 +138,8 @@ public Q_SLOTS: This slot is for use by scripts.*/ bool open(QString filename, QString loader_name); + QString write_string_to_file(const QString &str, const QString& filename); + /*! Reloads an item. Expects to be called by a QAction with the index of the item to be reloaded as data attached to the action. The index must identify a valid `Scene_item`.*/ @@ -352,6 +354,10 @@ protected Q_SLOTS: void save(QString filename, QList& to_save); //!Calls the function saveSnapShot of the viewer. void on_actionSaveSnapshot_triggered(); +#ifdef CGAL_USE_WEBSOCKETS + //!Starts a new WS server if none is already exist. Else, does nothing. + void on_action_Start_a_Session_triggered(); +#endif //!Opens a Dialog to choose a color and make it the background color. void setBackgroundColor(); //!Opens a Dialog to change the lighting settings @@ -447,6 +453,7 @@ public: #endif public Q_SLOTS: void on_actionSa_ve_Scene_as_Script_triggered(); + void on_actionLoad_a_Scene_from_a_Script_File_triggered(); void toggleFullScreen(); void setDefaultSaveDir(); void invalidate_bbox(bool do_recenter); @@ -494,4 +501,30 @@ protected: private: bool is_main; }; +#ifdef CGAL_USE_WEBSOCKETS +QT_FORWARD_DECLARE_CLASS(QWebSocketServer) +QT_FORWARD_DECLARE_CLASS(QWebSocket) + +class EchoServer : public QObject +{ + Q_OBJECT +public: + explicit EchoServer(quint16 port); + ~EchoServer(); + + +Q_SIGNALS: + void closed(); + +private Q_SLOTS: + void onNewConnection(); + void processTextMessage(QString message); + void processBinaryMessage(QByteArray message); + void socketDisconnected(); + +private: + QWebSocketServer *m_pWebSocketServer; + QList m_clients; +}; +#endif #endif // ifndef MAINWINDOW_H diff --git a/Polyhedron/demo/Polyhedron/MainWindow.ui b/Polyhedron/demo/Polyhedron/MainWindow.ui index a011dedab64..6273a37bd16 100644 --- a/Polyhedron/demo/Polyhedron/MainWindow.ui +++ b/Polyhedron/demo/Polyhedron/MainWindow.ui @@ -58,6 +58,7 @@ + @@ -96,6 +97,7 @@ + @@ -463,6 +465,22 @@ &Save + + + Load a Scene &from a Script File... + + + + + true + + + &Start a Session + + + Start a WebSocket Server to Share your Camera with Others on your Network. + + diff --git a/Polyhedron/demo/Polyhedron/Plugins/AABB_tree/Cut_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/AABB_tree/Cut_plugin.cpp index 0cf39f89ccb..d23ff947d9a 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/AABB_tree/Cut_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/AABB_tree/Cut_plugin.cpp @@ -860,6 +860,12 @@ public: return ok; } + bool isDefaultLoader(const Scene_item* item) const Q_DECL_OVERRIDE{ + if(qobject_cast(item)) + return true; + return false; + } + using Polyhedron_demo_io_plugin_interface::init; void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface* m) override; diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/3mf_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/3mf_io_plugin.cpp index d2e34e4e00e..90f036e8cea 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/3mf_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/3mf_io_plugin.cpp @@ -78,61 +78,6 @@ class Io_3mf_plugin: std::vector names; QList result; std::vector > all_colors; - int nb_polylines = - CGAL::read_polylines_from_3mf(fileinfo.filePath().toUtf8().toStdString(), - all_points, all_colors, names); - if(nb_polylines < 0 ) - { - ok = false; - std::cerr << "Error in reading of meshes."<polylines; - polylines.push_back(all_points[i]); - pol_item->setName(names[i].data()); - pol_item->invalidateOpenGLBuffers(); - CGAL::Color c = all_colors[i].front(); - pol_item->setColor(QColor(c.red(), c.green(), c.blue())); - pol_item->setProperty("already_colord", true); - result << pol_item; - if(add_to_scene) - CGAL::Three::Three::scene()->addItem(pol_item); - } - all_points.clear(); - all_colors.clear(); - names.clear(); - int nb_point_sets = - CGAL::read_point_clouds_from_3mf(fileinfo.filePath().toUtf8().toStdString(), - all_points, all_colors, names); - if(nb_point_sets < 0 ) - { - ok = false; - std::cerr << "Error in reading of meshes."<point_set()->insert(all_points[i][j]); - } - pts_item->setName(names[i].data()); - pts_item->invalidateOpenGLBuffers(); - CGAL::Color c = all_colors[i].front(); - pts_item->setColor(QColor(c.red(), c.green(), c.blue())); - pts_item->setProperty("already_colord", true); - result << pts_item; - if(add_to_scene) - CGAL::Three::Three::scene()->addItem(pts_item); - } - all_points.clear(); - names.clear(); - all_colors.clear(); int nb_meshes = CGAL::read_triangle_soups_from_3mf(fileinfo.filePath().toUtf8().toStdString(), all_points, all_polygons, all_colors, names); diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/Nef_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/Nef_io_plugin.cpp index 4aab33b4e0a..22b2ed695d4 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/Nef_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/Nef_io_plugin.cpp @@ -15,13 +15,18 @@ class Polyhedron_demo_io_nef_plugin : Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0" FILE "nef_io_plugin.json") public: - QString nameFilters() const; - QString name() const { return "io_nef_plugin"; } - bool canLoad(QFileInfo) const; - QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true); + QString nameFilters() const override; + QString name() const override { return "io_nef_plugin"; } + bool canLoad(QFileInfo) const override; + QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true) override; - bool canSave(const CGAL::Three::Scene_item*); - bool save(QFileInfo fileinfo,QList& items); + bool canSave(const CGAL::Three::Scene_item*) override; + bool save(QFileInfo fileinfo,QList& items) override; + bool isDefaultLoader(const Scene_item* item) const override{ + if(qobject_cast(item)) + return true; + return false; + } }; QString Polyhedron_demo_io_nef_plugin::nameFilters() const { diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/OFF_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/OFF_io_plugin.cpp index a94d0ef2d69..6b195e276c4 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/OFF_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/OFF_io_plugin.cpp @@ -29,28 +29,28 @@ class Polyhedron_demo_off_plugin : Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "off_io_plugin.json") public: - bool isDefaultLoader(const Scene_item *item) const + bool isDefaultLoader(const Scene_item *item) const override { if(qobject_cast(item) || qobject_cast(item)) return true; return false; } - bool isDefaultLoader(const QString& name) const + bool isDefaultLoader(const QString& name) const override { if(name == QString("off")) return true; return false; } - QString name() const { return "off_plugin"; } - QString nameFilters() const { return "OFF files (*.off);;Wavefront OBJ (*.obj)"; } - bool canLoad(QFileInfo fileinfo) const; - QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true); + QString name() const override{ return "off_plugin"; } + QString nameFilters() const override { return "OFF files (*.off);;Wavefront OBJ (*.obj)"; } + bool canLoad(QFileInfo fileinfo) const override; + QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true) override; CGAL::Three::Scene_item* load_off(QFileInfo fileinfo); CGAL::Three::Scene_item* load_obj(QFileInfo fileinfo); - bool canSave(const CGAL::Three::Scene_item*); - bool save(QFileInfo fileinfo,QList& ); + bool canSave(const CGAL::Three::Scene_item*) override; + bool save(QFileInfo fileinfo,QList& ) override; }; bool Polyhedron_demo_off_plugin::canLoad(QFileInfo) const { diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/PLY_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/PLY_io_plugin.cpp index 6ce2fdeb793..75c2e1d9845 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/PLY_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/PLY_io_plugin.cpp @@ -23,19 +23,19 @@ class Polyhedron_demo_ply_plugin : Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "ply_io_plugin.json") public: - bool isDefaultLoader(const CGAL::Three::Scene_item *item) const + bool isDefaultLoader(const CGAL::Three::Scene_item *item) const override { if(qobject_cast(item)) return true; return false; } - QString name() const { return "ply_plugin"; } - QString nameFilters() const { return "PLY files (*.ply)"; } - bool canLoad(QFileInfo fileinfo) const; - QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true); + QString name() const override{ return "ply_plugin"; } + QString nameFilters() const override{ return "PLY files (*.ply)"; } + bool canLoad(QFileInfo fileinfo) const override; + QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true)override; - bool canSave(const CGAL::Three::Scene_item*); - bool save(QFileInfo fileinfo,QList&); + bool canSave(const CGAL::Three::Scene_item*)override; + bool save(QFileInfo fileinfo,QList&)override; }; diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/Polylines_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/Polylines_io_plugin.cpp index d86309749f8..d9fe0b44e0d 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/Polylines_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/Polylines_io_plugin.cpp @@ -75,6 +75,12 @@ public: return QList()<(item)) + return true; + return false; + } protected Q_SLOTS: //!Splits the selected Scene_polylines_item in multiple items all containing a single polyline. void split(); diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/VTK_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/VTK_io_plugin.cpp index 1c340b8f2ad..362709853c6 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/VTK_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/VTK_io_plugin.cpp @@ -290,10 +290,11 @@ public: QString nameFilters() const { - return "VTK PolyData files (*.vtk);; VTK XML PolyData (*.vtp);; VTK XML UnstructuredGrid (*.vtu)"; } + return "VTK XML UnstructuredGrid (*.vtu);;VTK PolyData files (*.vtk);; VTK XML PolyData (*.vtp)"; } QString name() const { return "vtk_plugin"; } bool canSave(const CGAL::Three::Scene_item* item) { + return (qobject_cast(item) || qobject_cast(item)); } diff --git a/Polyhedron/demo/Polyhedron/Plugins/IO/lcc_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/IO/lcc_io_plugin.cpp index 71c47452676..55a3a7e1b14 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/IO/lcc_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/IO/lcc_io_plugin.cpp @@ -17,24 +17,24 @@ class LCC_io_plugin : Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "lcc_io_plugin.json") public: - bool isDefaultLoader(const CGAL::Three::Scene_item *item) const + bool isDefaultLoader(const CGAL::Three::Scene_item *item) const override { if(qobject_cast(item)) return true; return false; } - QString name() const { return "lcc_plugin"; } - QString nameFilters() const { return + QString name() const override{ return "lcc_plugin"; } + QString nameFilters() const override{ return "OFF files (*.off);;" "3-map files (*.3map)"; } - QString saveNameFilters() const { + QString saveNameFilters() const override{ return "3-map files (*.3map)"; } - bool canLoad(QFileInfo) const { return true; } - QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true){ + bool canLoad(QFileInfo) const override{ return true; } + QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true) override{ // Open file std::ifstream ifs(fileinfo.filePath().toUtf8()); if(!ifs) { @@ -67,8 +67,8 @@ public: } - bool canSave(const CGAL::Three::Scene_item*){return false;} - bool save(QFileInfo, QList& ){ + bool canSave(const CGAL::Three::Scene_item*)override{return false;} + bool save(QFileInfo, QList& )override{ return false; } diff --git a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/C3t3_io_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/C3t3_io_plugin.cpp index 4a7c004be2b..21a0d5c6207 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/C3t3_io_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/C3t3_io_plugin.cpp @@ -18,15 +18,20 @@ class Polyhedron_demo_c3t3_binary_io_plugin : Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.IOPluginInterface/1.90" FILE "c3t3_io_plugin.json") public: - QString name() const { return "C3t3_io_plugin"; } - QString nameFilters() const { return "binary files (*.cgal);;ascii (*.mesh);;maya (*.ma)"; } - QString saveNameFilters() const { return "binary files (*.cgal);;ascii (*.mesh);;maya (*.ma);;avizo (*.am);;OFF files (*.off)"; } - QString loadNameFilters() const { return "binary files (*.cgal);;ascii (*.mesh)"; } - bool canLoad(QFileInfo) const; - QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true); + QString name() const override { return "C3t3_io_plugin"; } + QString nameFilters() const override { return "binary files (*.cgal);;ascii (*.mesh);;maya (*.ma)"; } + QString saveNameFilters() const override { return "binary files (*.cgal);;ascii (*.mesh);;maya (*.ma);;avizo (*.am);;OFF files (*.off)"; } + QString loadNameFilters() const override { return "binary files (*.cgal);;ascii (*.mesh)"; } + bool canLoad(QFileInfo) const override; + QList load(QFileInfo fileinfo, bool& ok, bool add_to_scene=true) override; - bool canSave(const CGAL::Three::Scene_item*); - bool save(QFileInfo fileinfo,QList& ); + bool canSave(const CGAL::Three::Scene_item*) override; + bool save(QFileInfo fileinfo,QList& ) override; + bool isDefaultLoader(const Scene_item* item) const override{ + if(qobject_cast(item)) + return true; + return false; + } private: bool try_load_other_binary_format(std::istream& in, C3t3& c3t3); diff --git a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp index 286642446a0..d9b4fff0ef8 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/Mesh_3/Io_image_plugin.cpp @@ -296,6 +296,11 @@ public: items.pop_front(); return ok; } + bool isDefaultLoader(const Scene_item* item) const override{ + if(qobject_cast(item)) + return true; + return false; + } QString name() const override{ return "segmented images"; } diff --git a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp index b4864357dd3..be80490894e 100644 --- a/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp +++ b/Polyhedron/demo/Polyhedron/Plugins/PMP/Selection_plugin.cpp @@ -127,6 +127,12 @@ public: return res; } + bool isDefaultLoader(const Scene_item* item) const override{ + if(qobject_cast(item)) + return true; + return false; + } + bool applicable(QAction* action) const override { if(action == actionSelfIntersection) return qobject_cast(scene->item(scene->mainSelectionIndex())); diff --git a/Polyhedron/demo/Polyhedron/Preferences.ui b/Polyhedron/demo/Polyhedron/Preferences.ui index 10a7f773f85..358bf1ffbb8 100644 --- a/Polyhedron/demo/Polyhedron/Preferences.ui +++ b/Polyhedron/demo/Polyhedron/Preferences.ui @@ -7,7 +7,7 @@ 0 0 631 - 526 + 628 @@ -58,9 +58,9 @@ 0 - -96 + 0 275 - 528 + 534 @@ -285,6 +285,16 @@ + + + + true + + + Network Settings + + + diff --git a/Polyhedron/demo/Polyhedron/SSH_dialog.ui b/Polyhedron/demo/Polyhedron/SSH_dialog.ui new file mode 100644 index 00000000000..0f18f927cb8 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/SSH_dialog.ui @@ -0,0 +1,151 @@ + + + SSHDialog + + + + 0 + 0 + 415 + 441 + + + + SSH Settings + + + + + + + + SSH Preferences + + + + + + User + + + + + + + + + + + + Server + + + + + + + + + + + + Public Key + + + + + + + + + Browse... + + + + + + + + + + Private Key + + + + + + + + + Browse... + + + + + + + + + + + + + Camera Synchronization Server + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SSHDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SSHDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Polyhedron/demo/Polyhedron/Use_ssh.cpp b/Polyhedron/demo/Polyhedron/Use_ssh.cpp new file mode 100644 index 00000000000..a1b342eda69 --- /dev/null +++ b/Polyhedron/demo/Polyhedron/Use_ssh.cpp @@ -0,0 +1,286 @@ +// Copyright (c) 2020 GeometryFactory (France). All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// +// Author(s) : Maxime Gimeno + +#ifdef CGAL_USE_SSH + +#include + +#include "CGAL/Use_ssh.h" +#include +#include +#include +#include +#include +#include + +#include + +bool test_result(int res) +{ + switch(res){ + + case SSH_AUTH_ERROR: + { + std::cerr<<"Auth failed with error: "< buffer(size); + if (!file.read(buffer.data(), size)) + { + std::cerr<<"error while reading file."< timespan(size); + std::this_thread::sleep_for(timespan); + if (res != SSH_OK) + { + std::cerr<< "Can't write to remote file: %s\n" + << ssh_get_error(session)< buffer; + + ssh_scp scp = ssh_scp_new( + session, SSH_SCP_READ | SSH_SCP_RECURSIVE, from_path); + if (scp == NULL) + { + std::cerr<<"Error allocating scp session: %s\n" + << ssh_get_error(session)< #include #include +#include +#include +#ifdef CGAL_USE_WEBSOCKETS +#include +#endif #include @@ -40,6 +45,7 @@ public: bool inDrawWithNames; bool clipping; bool projection_is_ortho; + bool cam_sharing; GLfloat gl_point_size; QVector4D clipbox[6]; QPainter *painter; @@ -111,6 +117,12 @@ public: { return shader_programs; } +#ifdef CGAL_USE_WEBSOCKETS + QWebSocket m_webSocket; +#endif + bool is_connected; + QString session; + QUrl m_url; }; class LightingDialog : @@ -268,6 +280,7 @@ void Viewer::doBindings() d->spec_power = viewer_settings.value("spec_power", 51.8).toFloat(); d->scene = 0; d->projection_is_ortho = false; + d->cam_sharing = false; d->twosides = false; this->setProperty("draw_two_sides", false); this->setProperty("back_front_shading", false); @@ -278,6 +291,7 @@ void Viewer::doBindings() d->shader_programs.resize(NB_OF_PROGRAMS); d->textRenderer = new TextRenderer(); d->is_2d_selection_mode = false; + d->is_connected = false; connect( d->textRenderer, SIGNAL(sendMessage(QString,int)), this, SLOT(printMessage(QString,int)) ); @@ -623,7 +637,7 @@ void Viewer::mousePressEvent(QMouseEvent* event) d->showDistance(event->pos()); event->accept(); } - else { + else{ makeCurrent(); CGAL::QGLViewer::mousePressEvent(event); } @@ -1931,6 +1945,79 @@ bool Viewer::isClipping() const { return d->clipping; } +#ifdef CGAL_USE_WEBSOCKETS +void Viewer::setShareCam(bool b, QString session) +{ + static bool init = false; + if(b) + { + d->cam_sharing = b; + d->session = session; + QString ws_url + = CGAL::Three::Three::mainWindow()->property("ws_url").toString(); + if(ws_url.isEmpty()) + { + QMessageBox::warning(this, "Error", "No Server configured. Please go to Edit->Preferences->Network Settings and fill the \"Camera Synchronization Server\" Field."); + } + else{ + if(!init) + { + connect(&d->m_webSocket, &QWebSocket::connected, this, &Viewer::onSocketConnected); + connect(&d->m_webSocket, &QWebSocket::disconnected, this,[this]() + { + d->is_connected = false; + Viewer::socketClosed(); + }); + init = true; + } + d->m_webSocket.open(QUrl(ws_url)); + QApplication::setOverrideCursor(Qt::WaitCursor); + QTimer::singleShot(1000, this, [this](){ + QApplication::restoreOverrideCursor(); + if(!d->is_connected){ + QMessageBox::warning(CGAL::Three::Three::mainWindow(), + "Connection failure", + "The requested server was not found."); + setShareCam(false, ""); + } + }); + } + } + else + { + QAction* action = findChild("actionShareCamera"); + action->setChecked(false); + d->m_webSocket.close(); + } +} +void Viewer::onSocketConnected() +{ + connect(&d->m_webSocket, &QWebSocket::textMessageReceived, + this, &Viewer::onTextMessageSocketReceived); + connect(camera()->frame(), &CGAL::qglviewer::ManipulatedCameraFrame::manipulated, + this, [this](){ + if(d->cam_sharing){ + QString cam_state = QString("[%1] %2").arg(d->session).arg(dumpCameraCoordinates()); + //send to server + d->m_webSocket.sendTextMessage(cam_state); + } + }); + d->is_connected = true; +} + +void Viewer::onTextMessageSocketReceived(QString message) +{ + QString session; + QString position; + QRegularExpression re("\\[(.*)\\] (.*)"); + QRegularExpressionMatch match = re.match(message); + session = match.captured(1); + position = match.captured(2); + if(session != d->session){ + return; + } + moveCameraToCoordinates(position, 0.05f); +} +#endif #include "Viewer.moc" - diff --git a/Polyhedron/demo/Polyhedron/Viewer.h b/Polyhedron/demo/Polyhedron/Viewer.h index 600b9a6f041..f4fdf522abe 100644 --- a/Polyhedron/demo/Polyhedron/Viewer.h +++ b/Polyhedron/demo/Polyhedron/Viewer.h @@ -95,6 +95,7 @@ public: Q_SIGNALS: void sendMessage(QString); void doneInitGL(CGAL::Three::Viewer_interface*); + void socketClosed(); public Q_SLOTS: //! Sets the antialiasing to true or false. void setAntiAliasing(bool b) Q_DECL_OVERRIDE; @@ -131,6 +132,11 @@ public Q_SLOTS: void setBackFrontColors(); void messageLogged(QOpenGLDebugMessage); +#ifdef CGAL_USE_WEBSOCKETS + void setShareCam(bool, QString); + void onSocketConnected(); + void onTextMessageSocketReceived(QString message); +#endif protected: void paintEvent(QPaintEvent *)Q_DECL_OVERRIDE; diff --git a/Polyhedron/demo/Polyhedron/include/CGAL/Use_ssh.h b/Polyhedron/demo/Polyhedron/include/CGAL/Use_ssh.h new file mode 100644 index 00000000000..2cdca27452b --- /dev/null +++ b/Polyhedron/demo/Polyhedron/include/CGAL/Use_ssh.h @@ -0,0 +1,25 @@ +#ifndef USE_SSH_H +#define USE_SSH_H +#include +namespace CGAL{ +namespace ssh_internal{ +//should be used inside a try/catch(ssh::SshException e) + +//give an unitialized session. +bool establish_ssh_session(ssh_session& session, + const char *user, + const char *server, + const char *pub_key_path, + const char *priv_key_path, + const char *priv_key_password); +void close_connection(ssh_session& session); + +bool push_file(ssh_session& session, + const char* dest_path, + const char* filepath); +bool pull_file(ssh_session &session, + const char *from_path, + const char *to_path); + +}} +#endif diff --git a/Stream_support/include/CGAL/IO/read_3mf.h b/Stream_support/include/CGAL/IO/read_3mf.h index 8be3a8c1446..7a6def1f2fe 100644 --- a/Stream_support/include/CGAL/IO/read_3mf.h +++ b/Stream_support/include/CGAL/IO/read_3mf.h @@ -65,12 +65,6 @@ bool extract_soups (NMR::PLib3MFModelMeshObject *pMeshObject, pBuffer.resize(nNeededChars + 1); hResult = NMR::lib3mf_object_getnameutf8(pMeshObject, &pBuffer[0], nNeededChars + 1, NULL); pBuffer[nNeededChars] = 0; - std::string temp(&pBuffer[0]); - if(temp.find("_cgal_pc") != std::string::npos - || temp.find("_cgal_pl")!= std::string::npos) //ignore point clouds and polylines - { - return false; - } name = std::string(&pBuffer[0]); } else @@ -120,151 +114,6 @@ bool extract_soups (NMR::PLib3MFModelMeshObject *pMeshObject, return true; } -template -bool extract_polylines (NMR::PLib3MFModelMeshObject *pMeshObject, - const NMR::MODELTRANSFORM& , - PointRange& points, - PolygonRange&, - ColorRange& colors, - std::string& name) { - typedef typename PointRange::value_type Point_3; - - HRESULT hResult; - DWORD nNeededChars; - std::vector pBuffer; - // Retrieve Mesh Name Length - hResult = NMR::lib3mf_object_getnameutf8(pMeshObject, NULL, 0, &nNeededChars); - if (hResult != LIB3MF_OK) - { - points.resize(0); - std::cerr<<"Error during name extraction."; - return false; - } - - // Retrieve Mesh Name - if (nNeededChars > 0) { - pBuffer.resize(nNeededChars + 1); - hResult = NMR::lib3mf_object_getnameutf8(pMeshObject, &pBuffer[0], nNeededChars + 1, NULL); - pBuffer[nNeededChars] = 0; - std::string temp(&pBuffer[0]); - if(temp.find("_cgal_pl")== std::string::npos) //ignore not polylines - { - points.resize(0); - return false; - } - name = std::string(&pBuffer[0]); - } - else - { - points.resize(0); - return false; - } - - NMR::PLib3MFPropertyHandler * pPropertyHandler; - hResult = NMR::lib3mf_meshobject_createpropertyhandler(pMeshObject, &pPropertyHandler); - if (hResult != LIB3MF_OK) { - DWORD nErrorMessage; - LPCSTR pszErrorMessage; - std::cerr << "could not create property handler: " << std::hex << hResult << std::endl; - NMR::lib3mf_getlasterror(pMeshObject, &nErrorMessage, &pszErrorMessage); - std::cerr << "error #" << std::hex << nErrorMessage << ": " << pszErrorMessage << std::endl; - NMR::lib3mf_release(pMeshObject); - return false; - } - - points.resize(points.size()-3);//ignore dummy_vertices - for(DWORD vid = 0; vid < points.size(); ++vid) - { - NMR::MODELMESHVERTEX pVertex; - NMR::lib3mf_meshobject_getvertex(pMeshObject, vid+3, &pVertex); - points[vid] = - Point_3(pVertex.m_fPosition[0], - pVertex.m_fPosition[1], - pVertex.m_fPosition[2]); - } - - NMR::MODELMESH_TRIANGLECOLOR_SRGB pColor; - NMR::lib3mf_propertyhandler_getcolor(pPropertyHandler, 0, &pColor);//get color of the dummy triangle - NMR::MODELMESHCOLOR_SRGB mColor = pColor.m_Colors[0]; - colors[0]=CGAL::Color(mColor.m_Red, mColor.m_Green, - mColor.m_Blue, mColor.m_Alpha); - return true; -} - -template -bool extract_point_clouds (NMR::PLib3MFModelMeshObject *pMeshObject, - const NMR::MODELTRANSFORM&, - PointRange& points, - PolygonRange&, - ColorRange& colors, - std::string& name) { - typedef typename PointRange::value_type Point_3; - - HRESULT hResult; - DWORD nNeededChars; - std::vector pBuffer; - // Retrieve Mesh Name Length - hResult = NMR::lib3mf_object_getnameutf8(pMeshObject, NULL, 0, &nNeededChars); - if (hResult != LIB3MF_OK) - { - std::cerr<<"Error during name extraction."; - points.resize(0); - return false; - } - - // Retrieve Mesh Name - if (nNeededChars > 0) { - pBuffer.resize(nNeededChars + 1); - hResult = NMR::lib3mf_object_getnameutf8(pMeshObject, &pBuffer[0], nNeededChars + 1, NULL); - pBuffer[nNeededChars] = 0; - std::string temp(&pBuffer[0]); - if(temp.find("_cgal_pc")== std::string::npos) //ignore not point_cloud - { - points.resize(0); - return false; - } - name = std::string(&pBuffer[0]); - } - else{ - points.resize(0); - return false; - } - - NMR::PLib3MFPropertyHandler * pPropertyHandler; - hResult = NMR::lib3mf_meshobject_createpropertyhandler(pMeshObject, &pPropertyHandler); - if (hResult != LIB3MF_OK) { - DWORD nErrorMessage; - LPCSTR pszErrorMessage; - std::cerr << "could not create property handler: " << std::hex << hResult << std::endl; - NMR::lib3mf_getlasterror(pMeshObject, &nErrorMessage, &pszErrorMessage); - std::cerr << "error #" << std::hex << nErrorMessage << ": " << pszErrorMessage << std::endl; - NMR::lib3mf_release(pMeshObject); - return -1; - } - - points.resize(points.size()-3);//ignore dummy_vertices - for(DWORD vid = 0; vid < points.size(); ++vid) - { - NMR::MODELMESHVERTEX pVertex; - NMR::lib3mf_meshobject_getvertex(pMeshObject, vid+3, &pVertex); - points[vid] = - Point_3(pVertex.m_fPosition[0], - pVertex.m_fPosition[1], - pVertex.m_fPosition[2]); - } - - NMR::MODELMESH_TRIANGLECOLOR_SRGB pColor; - NMR::lib3mf_propertyhandler_getcolor(pPropertyHandler, 0, &pColor);//get color of the dummy triangle - NMR::MODELMESHCOLOR_SRGB mColor = pColor.m_Colors[0]; - colors[0]=CGAL::Color(mColor.m_Red, mColor.m_Green, - mColor.m_Blue, mColor.m_Alpha); - return true; -} - template @@ -607,43 +456,6 @@ int read_triangle_soups_from_3mf(const std::string& file_name, PointRanges& all_ all_colors, names, extract_soups); } - -template -int read_polylines_from_3mf(const std::string& file_name, - PointRanges& all_points, - ColorRanges& all_colors, - std::vector& names - ) -{ - typedef typename PointRanges::value_type PointRange; - typedef std::vector Polygon; - typedef std::vector PolygonRange; - typedef std::vector ColorRange; - std::vector all_polygons; - return read_from_3mf, - std::vector, PointRange, PolygonRange, ColorRange> - (file_name, all_points, all_polygons, all_colors, names, - extract_polylines); -} - - -template -int read_point_clouds_from_3mf(const std::string& file_name, - PointRanges& all_points, - ColorRanges& all_colors, - std::vector& names - ) -{ - typedef typename PointRanges::value_type PointRange; - typedef std::vector Polygon; - typedef std::vector PolygonRange; - typedef std::vector ColorRange; - std::vector all_polygons; - return read_from_3mf, - std::vector, PointRange, PolygonRange, ColorRange> - (file_name, all_points, all_polygons, all_colors, names, - extract_point_clouds); -} }//end CGAL #endif // CGAL_IO_READ_3MF_H diff --git a/Stream_support/test/Stream_support/test_3mf_to_sm.cpp b/Stream_support/test/Stream_support/test_3mf_to_sm.cpp index 654cae33bb0..4f4e94feb7e 100644 --- a/Stream_support/test/Stream_support/test_3mf_to_sm.cpp +++ b/Stream_support/test/Stream_support/test_3mf_to_sm.cpp @@ -51,33 +51,6 @@ int main(int argc, char** argv) ofs << mesh; ofs.close(); } - int nb_polylines = - CGAL::read_polylines_from_3mf(file_name, all_points, all_colors, names); - - if(nb_polylines == 0) - std::cout<<"No polyline found."<