Browse Source

Also relative path if possible for texture & model paths.
Allow for script exe that are in path.
supermerill/SuperSlicer#1875

supermerill 2 years ago
parent
commit
77eb806114

+ 26 - 41
src/libslic3r/GCode/PostProcessor.cpp

@@ -7,7 +7,6 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/log/trivial.hpp>
 #include <boost/format.hpp>
-#include <boost/filesystem.hpp>
 #include <boost/nowide/convert.hpp>
 #include <boost/nowide/cenv.hpp>
 #include <boost/nowide/fstream.hpp>
@@ -26,44 +25,6 @@
 #include <unistd.h>     //readlink
 #endif
 
-// Try to find where the file can be.
-// First try without any modification, for absolute apth and relative path fromt he current directory
-// Then try from the slic3r.exe directory
-// Then from the configuration directory
-// Then from the USER directory
-static boost::filesystem::path find_file(boost::filesystem::path filename) {
-    boost::filesystem::path ret = filename;
-    if (!boost::filesystem::exists(filename)) {
-        // try from our install directory 
-#ifdef WIN32
-        wchar_t wpath_exe[_MAX_PATH + 1];
-        ::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
-        boost::filesystem::path local_dir = boost::filesystem::path(wpath_exe).parent_path();
-#else
-        char result[PATH_MAX + 1];
-        size_t count = readlink("/proc/self/exe", result, sizeof(result)-1);
-        boost::filesystem::path local_dir = boost::filesystem::path(std::string(result, (count > 0) ? count : 0)).parent_path();
-#endif
-        if (!boost::filesystem::exists(local_dir / filename)) {
-            //try with configuration directory
-            local_dir = boost::filesystem::path(Slic3r::data_dir());
-        }
-        if (!boost::filesystem::exists(local_dir / filename)) {
-            //try with configuration directory
-#ifdef WIN32
-            local_dir = boost::filesystem::path(::getenv("USERPROFILE"));
-#else
-            local_dir = boost::filesystem::path(::getenv("HOME"));
-#endif
-        }
-        if (!boost::filesystem::exists(local_dir / filename)) {
-            throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + filename.string());
-        } else {
-            ret = local_dir / filename;
-        }
-    }
-    return ret;
-}
 
 #ifdef WIN32
 
@@ -156,25 +117,37 @@ static int run_script(const std::string &script, const std::string &gcode, std::
 
     std::wstring command_line;
     const std::wstring command = szArglist[0];
+    bool need_absolute_path = false;
     // for perl and python, hope that the os can find it in the path.
     if (boost::iends_with(command, L".pl")) {
         BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script : detecting perl script");
         // This is a perl script. Run it through the perl interpreter.
         quote_argv_winapi(boost::nowide::widen("perl.exe"), command_line);
         command_line += L" ";
+        need_absolute_path = true;
     } else if (boost::iends_with(command, L".py")) {
         BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script : detecting python script");
         // This is a python script. Run it through the python interpreter.
         quote_argv_winapi(boost::nowide::widen("python.exe"), command_line);
         command_line += L" ";
+        need_absolute_path = true;
     } else if (boost::iends_with(command, ".bat")) {
         BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script : detecting bat script");
         // Run a batch file through the command line interpreter.
         command_line = L"cmd.exe /C ";
+        need_absolute_path = true;
     }
     
     //try to find the file, in different directories
-    quote_argv_winapi(find_file(boost::filesystem::path(command)).wstring(), command_line);
+    std::wstring absolute_command_path = Slic3r::find_full_path(boost::filesystem::path(command)).generic_wstring();
+    if (absolute_command_path.empty()) {
+        if (need_absolute_path) {
+            throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
+        } else {
+            absolute_command_path = command;
+        }
+    }
+    quote_argv_winapi(absolute_command_path, command_line);
     command_line += L" ";
 
     for (int i = 1; i < nArgs; ++ i) {
@@ -200,18 +173,30 @@ static int run_script(const std::string &script, const std::string &gcode, std::
     // Quote and escape the gcode path argument
     std::string command_line;
     size_t first_space = script.find(' ');
+    bool need_absolute_path = false;
     const std::string command = (std::string::npos != first_space) ? script.substr(0, first_space) : script;
     const std::string args = (std::string::npos != first_space) ? script.substr(first_space) : "";
     if (boost::iends_with(command, L".pl")) {
         BOOST_LOG_TRIVIAL(trace) << boost::format("Executing script : detecting perl script");
         // This is a perl script. Run it through the perl interpreter.
         command_line = "perl ";
+        need_absolute_path = true;
     } else if (boost::iends_with(command, L".py")) {
         BOOST_LOG_TRIVIAL(trace) << boost::format("Executing script : detecting python script");
         // This is a python script. Run it through the python interpreter.
         command_line = "python ";
+        need_absolute_path = true;
+    }
+    //try to find the file, in different directories
+    std::string absolute_command_path = Slic3r::find_full_path(boost::filesystem::path(command)).generic_string();
+    if (absolute_command_path.empty()) {
+        if (need_absolute_path) {
+            throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + command);
+        } else {
+            absolute_command_path = command;
+        }
     }
-    command_line += find_file(boost::filesystem::path(command)).string();
+    command_line += absolute_command_path;
     command_line += args;
 
     command_line.append(" '");

+ 2 - 0
src/libslic3r/GCode/PostProcessor.hpp

@@ -3,6 +3,8 @@
 
 #include <string>
 
+#include <boost/filesystem.hpp>
+
 #include "../libslic3r.h"
 #include "../PrintConfig.hpp"
 

+ 9 - 3
src/libslic3r/PrintConfig.cpp

@@ -2,6 +2,7 @@
 #include "Config.hpp"
 #include "Flow.hpp"
 #include "I18N.hpp"
+#include "Utils.hpp"
 
 #include <set>
 #include <unordered_set>
@@ -3955,7 +3956,9 @@ void PrintConfigDef::init_fff_params()
     def->tooltip = L("If you want to process the output G-code through custom scripts, "
                    "just list their absolute paths here. Separate multiple scripts with a semicolon. "
                    "Scripts will be passed the absolute path to the G-code file as the first argument, "
-                   "and they can access the Slic3r config settings by reading environment variables.");
+                   "and they can access the Slic3r config settings by reading environment variables."
+                   "\nThe script, if passed as a relative path, will also be searched from the slic3r directory, "
+                   "the slic3r configuration directory and the user directory.");
     def->gui_flags = "serialized";
     def->multiline = true;
     def->full_width = true;
@@ -7443,10 +7446,13 @@ std::map<std::string, std::string> PrintConfigDef::to_prusa(t_config_option_key&
     if ("host_type" == opt_key) {
         if ("klipper" == value || "mpmdv2" == value || "monoprice" == value) value = "octoprint";
     }
-    if ("fan_below_layer_time" == opt_key)
+    if ("fan_below_layer_time" == opt_key) {
         if (value.find('.') != std::string::npos)
             value = value.substr(0, value.find('.'));
-
+    }
+    if ("bed_custom_texture" == opt_key || "Bed custom texture" == opt_key) {
+        value = Slic3r::find_full_path(value, value).generic_string();
+    }
     return new_entries;
 }
 

+ 7 - 0
src/libslic3r/Utils.hpp

@@ -79,6 +79,13 @@ extern std::string normalize_utf8_nfc(const char *src);
 extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0);
 extern size_t get_utf8_sequence_length(const char *seq, size_t size);
 
+// If the file has a relative path, it tries to find it in the exe directory, the configuration directory and the user directory.
+// If it can't find it, it returns an empty path
+extern boost::filesystem::path find_full_path(const boost::filesystem::path filename, const boost::filesystem::path return_fail = "");
+
+// If the filename is an absolute path, it remove the exe directory path, the configuration directory path or the user directory path to create a relative path.
+extern boost::filesystem::path shorten_path(const boost::filesystem::path filename);
+
 // Safely rename a file even if the target exists.
 // On Windows, the file explorer (or anti-virus or whatever else) often locks the file
 // for a short while, so the file may not be movable. Retry while we see recoverable errors.

+ 86 - 0
src/libslic3r/utils.cpp

@@ -71,6 +71,19 @@
     #define strcasecmp _stricmp
 #endif
 
+#include <cstdlib>   // getenv()
+#ifdef WIN32
+	// The standard Windows includes.
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <shellapi.h>
+#else
+	// POSIX
+#include <sstream>
+#include <boost/process.hpp>
+#include <unistd.h>     //readlink
+#endif
+
 namespace Slic3r {
 
 static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;
@@ -469,6 +482,79 @@ namespace WindowsSupport
 } // namespace WindowsSupport
 #endif /* _WIN32 */
 
+
+// Try to find where the file can be.
+// First try without any modification, for absolute apth and relative path fromt he current directory
+// Then try from the slic3r.exe directory
+// Then from the configuration directory
+// Then from the USER directory
+boost::filesystem::path find_full_path(const boost::filesystem::path filename, const boost::filesystem::path return_fail) {
+	boost::filesystem::path ret = filename;
+	if (!boost::filesystem::exists(filename)) {
+		// try from our install directory 
+#ifdef WIN32
+		wchar_t wpath_exe[_MAX_PATH + 1];
+		::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
+		boost::filesystem::path local_dir = boost::filesystem::path(wpath_exe).parent_path();
+#else
+		char result[PATH_MAX + 1];
+		size_t count = readlink("/proc/self/exe", result, sizeof(result) - 1);
+		boost::filesystem::path local_dir = boost::filesystem::path(std::string(result, (count > 0) ? count : 0)).parent_path();
+#endif
+		if (!boost::filesystem::exists(local_dir / filename)) {
+			//try with configuration directory
+			local_dir = boost::filesystem::path(Slic3r::data_dir());
+		}
+		if (!boost::filesystem::exists(local_dir / filename)) {
+			//try with configuration directory
+#ifdef WIN32
+			local_dir = boost::filesystem::path(::getenv("USERPROFILE"));
+#else
+			local_dir = boost::filesystem::path(::getenv("HOME"));
+#endif
+		}
+		if (!boost::filesystem::exists(local_dir / filename)) {
+			return return_fail;
+		} else {
+			ret = local_dir / filename;
+		}
+	}
+	return ret;
+}
+
+boost::filesystem::path shorten_path(const boost::filesystem::path filename) {
+	std::string current_filename = filename.generic_string();
+	// try from our install directory 
+#ifdef WIN32
+	wchar_t wpath_exe[_MAX_PATH + 1];
+	::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
+	std::string local_dir = boost::filesystem::path(wpath_exe).parent_path().generic_string();
+#else
+	char result[PATH_MAX + 1];
+	size_t count = readlink("/proc/self/exe", result, sizeof(result) - 1);
+	std::string local_dir = boost::filesystem::path(std::string(result, (count > 0) ? count : 0)).parent_path().generic_string();
+#endif
+	if (boost::starts_with(current_filename, local_dir)) {
+		return boost::filesystem::path(current_filename.substr(local_dir.size() + 1));
+	}
+	//try with configuration directory
+	local_dir = Slic3r::data_dir();
+	if (boost::starts_with(current_filename, local_dir)) {
+		return boost::filesystem::path(current_filename.substr(local_dir.size() + 1));
+	}
+	//try with configuration directory
+#ifdef WIN32
+	local_dir = boost::filesystem::path(::getenv("USERPROFILE")).generic_string();
+#else
+	local_dir = boost::filesystem::path(::getenv("HOME")).generic_string();
+#endif
+	if (boost::starts_with(current_filename, local_dir)) {
+		return boost::filesystem::path(current_filename.substr(local_dir.size() + 1));
+	}
+	return filename;
+}
+
+
 // borrowed from LVVM lib/Support/Windows/Path.inc
 std::error_code rename_file(const std::string &from, const std::string &to)
 {

+ 20 - 13
src/slic3r/GUI/3DBed.cpp

@@ -5,6 +5,7 @@
 #include "libslic3r/Polygon.hpp"
 #include "libslic3r/ClipperUtils.hpp"
 #include "libslic3r/BoundingBox.hpp"
+#include "libslic3r/GCode/PostProcessor.hpp"
 #include "libslic3r/Geometry/Circle.hpp"
 #include "libslic3r/Tesselate.hpp"
 #include "libslic3r/PresetBundle.hpp"
@@ -188,12 +189,12 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c
 {
     auto check_texture = [](const std::string& texture) {
         boost::system::error_code ec; // so the exists call does not throw (e.g. after a permission problem)
-        return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture, ec);
+        return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(Slic3r::find_full_path(texture), ec);
     };
 
     auto check_model = [](const std::string& model) {
         boost::system::error_code ec;
-        return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model, ec);
+        return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(Slic3r::find_full_path(model), ec);
     };
 
     Type type;
@@ -208,11 +209,12 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c
         texture = system_texture;
         m_texture_with_grid = system_with_grid;
     }
-
     std::string texture_filename = custom_texture.empty() ? texture : custom_texture;
     if (! texture_filename.empty() && ! check_texture(texture_filename)) {
         BOOST_LOG_TRIVIAL(error) << "Unable to load bed texture: " << texture_filename;
         texture_filename.clear();
+    } else {
+
     }
 
     std::string model_filename = custom_model.empty() ? model : custom_model;
@@ -229,7 +231,9 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c
     m_type = type;
     m_build_volume = BuildVolume { bed_shape, max_print_height };
     m_texture_filename = texture_filename;
+    m_texture_path = Slic3r::find_full_path(m_texture_filename, m_texture_filename);
     m_model_filename = model_filename;
+    m_model_path = Slic3r::find_full_path(m_model_filename, m_model_filename);
     m_extended_bounding_box = this->calc_extended_bounding_box();
 
     ExPolygon poly{ Polygon::new_scale(bed_shape) };
@@ -423,16 +427,17 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
         render_default(bottom, false);
         return;
     }
+    std::string texture_absolute_filename = m_texture_path.generic_string();
 
-    if (texture->get_id() == 0 || texture->get_source() != m_texture_filename) {
+    if (texture->get_id() == 0 || texture->get_source() != texture_absolute_filename) {
         texture->reset();
 
-        if (boost::algorithm::iends_with(m_texture_filename, ".svg")) {
+        if (boost::algorithm::iends_with(texture_absolute_filename, ".svg")) {
             // use higher resolution images if graphic card and opengl version allow
             GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
-            if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
+            if (temp_texture->get_id() == 0 || temp_texture->get_source() != texture_absolute_filename) {
                 // generate a temporary lower resolution texture to show while no main texture levels have been compressed
-                if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
+                if (!temp_texture->load_from_svg_file(texture_absolute_filename, false, false, false, max_tex_size / 8)) {
                     render_default(bottom, false);
                     return;
                 }
@@ -440,15 +445,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
             }
 
             // starts generating the main texture, compression will run asynchronously
-            if (!texture->load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) {
+            if (!texture->load_from_svg_file(texture_absolute_filename, true, true, true, max_tex_size)) {
                 render_default(bottom, false);
                 return;
             }
         } 
-        else if (boost::algorithm::iends_with(m_texture_filename, ".png")) {
+        else if (boost::algorithm::iends_with(texture_absolute_filename, ".png")) {
             // generate a temporary lower resolution texture to show while no main texture levels have been compressed
-            if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
-                if (!temp_texture->load_from_file(m_texture_filename, false, GLTexture::None, false)) {
+            if (temp_texture->get_id() == 0 || temp_texture->get_source() != texture_absolute_filename) {
+                if (!temp_texture->load_from_file(texture_absolute_filename, false, GLTexture::None, false)) {
                     render_default(bottom, false);
                     return;
                 }
@@ -456,7 +461,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
             }
 
             // starts generating the main texture, compression will run asynchronously
-            if (!texture->load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) {
+            if (!texture->load_from_file(texture_absolute_filename, true, GLTexture::MultiThreaded, true)) {
                 render_default(bottom, false);
                 return;
             }
@@ -575,9 +580,11 @@ void Bed3D::render_model() const
     if (m_model_filename.empty())
         return;
 
+    std::string model_absolute_filename = m_model_path.generic_string();
+
     GLModel* model = const_cast<GLModel*>(&m_model);
 
-    if (model->get_filename() != m_model_filename && model->init_from_file(m_model_filename)) {
+    if (model->get_filename() != model_absolute_filename && model->init_from_file(model_absolute_filename)) {
         model->set_color(-1, DEFAULT_MODEL_COLOR);
 
         // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad

+ 6 - 0
src/slic3r/GUI/3DBed.hpp

@@ -75,9 +75,15 @@ public:
 private:
     BuildVolume m_build_volume;
     Type m_type{ Type::Custom };
+    // m_texture_filename can be relative or absolute
     std::string m_texture_filename;
+    // absolute path for m_texture_filename
+    boost::filesystem::path m_texture_path;
     bool m_texture_with_grid = false;
+    // m_model_filename can be relative or absolute
     std::string m_model_filename;
+    // absolute path for m_model_filename
+    boost::filesystem::path m_model_path;
     // Print volume bounding box exteded with axes and model.
     BoundingBoxf3 m_extended_bounding_box;
     // Slightly expanded print bed polygon, for collision detection.

+ 28 - 4
src/slic3r/GUI/BedShapeDialog.cpp

@@ -9,6 +9,7 @@
 #include <wx/tooltip.h>
 
 #include "libslic3r/BoundingBox.hpp"
+#include "libslic3r/Utils.hpp"
 #include "libslic3r/Model.hpp"
 #include "libslic3r/Polygon.hpp"
 
@@ -299,16 +300,20 @@ wxPanel* BedShapePanel::init_texture_panel()
         sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2);
 
         load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_texture(); }));
+        load_btn->SetToolTip(_L("Load a png/svg file to be used as a texture. "
+            "\nIf it can be found via the executable, configuration or user directory then a relative path will be kept instead of the full one."));
+
         remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) {
                 m_custom_texture = NONE;
                 update_shape();
             }));
 
         filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) {
+                boost::filesystem::path absolute_texture_path = Slic3r::find_full_path(m_custom_texture);
                 e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string()));
                 wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
                 if (lbl != nullptr) {
-                    bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture);
+                    bool exists = (m_custom_texture == NONE) || !absolute_texture_path.empty();
                     lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
 
                     wxString tooltip_text = "";
@@ -316,7 +321,11 @@ wxPanel* BedShapePanel::init_texture_panel()
                         if (!exists)
                             tooltip_text += _L("Not found:") + " ";
 
-                        tooltip_text += _(m_custom_texture);
+                        if (absolute_texture_path == m_custom_texture || absolute_texture_path.empty()) {
+                            tooltip_text += _(m_custom_texture);
+                        } else {
+                            tooltip_text += _(m_custom_texture + "\n (" + absolute_texture_path.generic_string() + ")");
+                        }
                     }
 
                     wxToolTip* tooltip = lbl->GetToolTip();
@@ -369,6 +378,8 @@ wxPanel* BedShapePanel::init_model_panel()
         sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2);
 
         load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_model(); }));
+        load_btn->SetToolTip(_L("Load a stl file to be used as a model. "
+            "\nIf it can be found via the executable, configuration or user directory then a relative path will be kept instead of the full one."));
 
         remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) {
                 m_custom_model = NONE;
@@ -376,10 +387,11 @@ wxPanel* BedShapePanel::init_model_panel()
             }));
 
         filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) {
+                boost::filesystem::path absolute_model_path = Slic3r::find_full_path(m_custom_model);
                 e.SetText(_(boost::filesystem::path(m_custom_model).filename().string()));
                 wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
                 if (lbl != nullptr) {
-                    bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model);
+                    bool exists = (m_custom_model == NONE) || !absolute_model_path.empty();
                     lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
 
                     wxString tooltip_text = "";
@@ -387,7 +399,11 @@ wxPanel* BedShapePanel::init_model_panel()
                         if (!exists)
                             tooltip_text += _L("Not found:") + " ";
 
-                        tooltip_text += _(m_custom_model);
+                        if (absolute_model_path == m_custom_texture || absolute_model_path.empty()) {
+                            tooltip_text += _(m_custom_model);
+                        } else {
+                            tooltip_text += _(m_custom_model + "\n (" + absolute_model_path.generic_string() + ")");
+                        }
                     }
 
                     wxToolTip* tooltip = lbl->GetToolTip();
@@ -565,6 +581,10 @@ void BedShapePanel::load_texture()
 
     wxBusyCursor wait;
 
+    //remove unnecessary parts
+    if (!file_name.empty()) {
+        file_name = Slic3r::shorten_path(file_name).generic_string();
+    }
     m_custom_texture = file_name;
     update_shape();
 }
@@ -587,6 +607,10 @@ void BedShapePanel::load_model()
 
     wxBusyCursor wait;
 
+    //remove unnecessary parts
+    if (!file_name.empty()) {
+        file_name = Slic3r::shorten_path(file_name).generic_string();
+    }
     m_custom_model = file_name;
     update_shape();
 }