diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b0b3c0eef..ca57ca5531 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,9 +106,9 @@ if (MINGW) set_target_properties(PrusaSlicer PROPERTIES PREFIX "") endif (MINGW) -if (NOT WIN32) - # Binary name on unix like systems (OSX, Linux) - set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") +if (NOT WIN32 AND NOT APPLE) + # Binary name on unix like systems (Linux, Unix) + set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () target_link_libraries(PrusaSlicer libslic3r cereal) @@ -209,20 +209,34 @@ if (WIN32) add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer) prusaslicer_copy_dlls(PrusaSlicerDllsCopy) -elseif (XCODE) - # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level - add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources" - COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) else () + if (APPLE) + # On OSX, the name of the binary matches the name of the Application. + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf PrusaSlicer prusa-slicer + COMMAND ln -sf PrusaSlicer prusa-gcodeviewer + COMMAND ln -sf PrusaSlicer PrusaGCodeViewer + WORKING_DIRECTORY "$" + COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer" + VERBATIM) + else () + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf prusa-slicer prusa-gcodeviewer + WORKING_DIRECTORY "$" + COMMENT "Symlinking the G-code viewer to PrusaSlicer" + VERBATIM) + endif () + if (XCODE) + # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") + else () + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources") + endif () add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources" + COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}" COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) -endif() + VERBATIM) +endif () # Slic3r binary install target if (WIN32) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2962f0cdfe..94996dc928 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -101,8 +102,14 @@ int CLI::run(int argc, char **argv) std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); - bool start_as_gcodeviewer = false; - + bool start_as_gcodeviewer = +#ifdef _WIN32 + false; +#else + // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. + boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); +#endif // _WIN32 + const std::vector &load_configs = m_config.option("load", true)->values; // load config files supplied via --load diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5681ed66db..1c30078102 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -195,6 +195,8 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Process.cpp + Utils/Process.hpp Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f6fd939e25..f4d7f03eca 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -9,7 +9,6 @@ #include //#include #include -#include #include #include @@ -31,6 +30,7 @@ #include "I18N.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "../Utils/Process.hpp" #include #include "GUI_App.hpp" @@ -40,12 +40,6 @@ #include #endif // _WIN32 -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. -#ifdef __APPLE__ - #include -#endif - namespace Slic3r { namespace GUI { @@ -1054,8 +1048,8 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); -#if ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); +#if ENABLE_GCODE_VIEWER append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { if (m_plater->model().objects.empty() || @@ -1064,6 +1058,8 @@ void MainFrame::init_menubar() set_mode(EMode::GCodeViewer); }, "", nullptr); #endif // ENABLE_GCODE_VIEWER + append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview") + dots, _L("Open G-code viewer"), + [this](wxCommandEvent&) { start_new_gcodeviewer_open_file(this); }, "", nullptr); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1180,20 +1176,11 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), - [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")), - [this](wxCommandEvent&) { - wxString path = wxStandardPaths::Get().GetExecutablePath(); -#ifdef __APPLE__ - boost::process::spawn((const char*)path.c_str()); -#else - wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); -#endif - }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr); } // View menu diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp new file mode 100644 index 0000000000..96f5062070 --- /dev/null +++ b/src/slic3r/Utils/Process.cpp @@ -0,0 +1,132 @@ +#include "Process.hpp" + +#include + +#include "../GUI/GUI.hpp" +// for file_wildcards() +#include "../GUI/GUI_App.hpp" +// localization +#include "../GUI/I18N.hpp" + +#include +#include + +#include +#include + +// For starting another PrusaSlicer instance on OSX. +// Fails to compile on Windows on the build server. +#ifdef __APPLE__ + #include +<<<<<<< HEAD +======= + #include +>>>>>>> vb_gcodeviewer_menu +#endif + +#include + +namespace Slic3r { +namespace GUI { + +enum class NewSlicerInstanceType { + Slicer, + GCodeViewer +}; + +// Start a new Slicer process instance either in a Slicer mode or in a G-code mode. +// Optionally load a 3MF, STL or a G-code on start. +static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const wxString *path_to_open) +{ +#ifdef _WIN32 + wxString path; + wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE); + path += "\\"; + path += (instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer.exe" : "prusa-gcodeviewer.exe"; + std::vector args; + args.reserve(3); + args.emplace_back(path.wc_str()); + if (path_to_open != nullptr) + args.emplace_back(path_to_open->wc_str()); + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << to_u8(path) << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << to_u8(path); +#else + // Own executable path. + boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); + #if defined(__APPLE__) + { + bin_path = bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); + // On Apple the wxExecute fails, thus we use boost::process instead. + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; + try { + std::vector args; + if (path_to_open) + args.emplace_back(into_u8(*path_to_open)); + boost::process::spawn(bin_path, args); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); + } + } + #else // Linux or Unix + { + std::vector args; + args.reserve(3); + #ifdef __linux + static const char *gcodeviewer_param = "--gcodeviewer"; + { + // If executed by an AppImage, start the AppImage, not the main process. + // see https://docs.appimage.org/packaging-guide/environment-variables.html#id2 + const char *appimage_binary = std::getenv("APPIMAGE"); + if (appimage_binary) { + args.emplace_back(appimage_binary); + if (instance_type == NewSlicerInstanceType::GCodeViewer) + args.emplace_back(gcodeviewer_param); + } + } + #endif // __linux + std::string my_path; + if (args.empty()) { + // Binary path was not set to the AppImage in the Linux specific block above, call the application directly. + my_path = (bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer" : "prusa-gcodeviewer")).string(); + args.emplace_back(my_path.c_str()); + } + std::string to_open; + if (path_to_open) { + to_open = into_u8(*path_to_open); + args.emplace_back(to_open.c_str()); + } + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << args[0] << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << args[0]; + } + #endif // Linux or Unix +#endif // Win32 +} + +void start_new_slicer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, path_to_open); +} + +void start_new_gcodeviewer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::GCodeViewer, path_to_open); +} + +void start_new_gcodeviewer_open_file(wxWindow *parent) +{ + wxFileDialog dialog(parent ? parent : wxGetApp().GetTopWindow(), + _L("Open G-code file:"), + from_u8(wxGetApp().app_config->get_last_dir()), wxString(), + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) { + wxString path = dialog.GetPath(); + start_new_gcodeviewer(&path); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/Utils/Process.hpp b/src/slic3r/Utils/Process.hpp new file mode 100644 index 0000000000..c6acaa643e --- /dev/null +++ b/src/slic3r/Utils/Process.hpp @@ -0,0 +1,19 @@ +#ifndef GUI_PROCESS_HPP +#define GUI_PROCESS_HPP + +class wxWindow; + +namespace Slic3r { +namespace GUI { + +// Start a new slicer instance, optionally with a file to open. +void start_new_slicer(const wxString *path_to_open = nullptr); +// Start a new G-code viewer instance, optionally with a file to open. +void start_new_gcodeviewer(const wxString *path_to_open = nullptr); +// Open a file dialog, ask the user to select a new G-code to open, start a new G-code viewer. +void start_new_gcodeviewer_open_file(wxWindow *parent = nullptr); + +} // namespace GUI +} // namespace Slic3r + +#endif // GUI_PROCESS_HPP diff --git a/src/slic3r/Utils/Thread.hpp b/src/slic3r/Utils/Thread.hpp index e9c76d2aba..194971c9eb 100644 --- a/src/slic3r/Utils/Thread.hpp +++ b/src/slic3r/Utils/Thread.hpp @@ -1,5 +1,5 @@ -#ifndef THREAD_HPP -#define THREAD_HPP +#ifndef GUI_THREAD_HPP +#define GUI_THREAD_HPP #include #include @@ -25,4 +25,4 @@ template inline boost::thread create_thread(Fn &&fn) } -#endif // THREAD_HPP +#endif // GUI_THREAD_HPP