mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 13:16:00 +08:00
Fixed conflicts after merge with master
This commit is contained in:
commit
9660101951
2
.gitignore
vendored
2
.gitignore
vendored
@ -18,4 +18,4 @@ local-lib
|
||||
build-linux/*
|
||||
deps/build-linux/*
|
||||
**/.DS_Store
|
||||
/.idea/
|
||||
**/.idea/
|
||||
|
@ -43,14 +43,6 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
|
||||
|
||||
set(IS_CROSS_COMPILE FALSE)
|
||||
|
||||
if (SLIC3R_STATIC)
|
||||
# Prefer config scripts over find modules. This is helpful when building with
|
||||
# the static dependencies. Many libraries have their own export scripts
|
||||
# while having a Find<PkgName> module in standard cmake installation.
|
||||
# (e.g. CURL)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||
set(CMAKE_FIND_APPBUNDLE LAST)
|
||||
@ -458,19 +450,10 @@ if (NOT EIGEN3_FOUND)
|
||||
endif ()
|
||||
include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR})
|
||||
|
||||
# Find expat or use bundled version
|
||||
# Always use the system libexpat on Linux.
|
||||
|
||||
# Find expat. We have our overriden FindEXPAT which exports libexpat target
|
||||
# no matter what.
|
||||
find_package(EXPAT REQUIRED)
|
||||
|
||||
add_library(libexpat INTERFACE)
|
||||
|
||||
if (TARGET EXPAT::EXPAT )
|
||||
target_link_libraries(libexpat INTERFACE EXPAT::EXPAT)
|
||||
elseif(TARGET expat::expat)
|
||||
target_link_libraries(libexpat INTERFACE expat::expat)
|
||||
endif ()
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
|
||||
set(OpenGL_GL_PREFERENCE "LEGACY")
|
||||
|
@ -30,82 +30,101 @@
|
||||
# ``CURL_VERSION_STRING``
|
||||
# The version of curl found.
|
||||
|
||||
# Look for the header file.
|
||||
find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
|
||||
mark_as_advanced(CURL_INCLUDE_DIR)
|
||||
# First, prefer config scripts
|
||||
set(_q "")
|
||||
if(CURL_FIND_QUIETLY)
|
||||
set(_q QUIET)
|
||||
endif()
|
||||
find_package(CURL ${CURL_FIND_VERSION} CONFIG ${_q})
|
||||
|
||||
if(NOT CURL_LIBRARY)
|
||||
# Look for the library (sorted from most current/relevant entry to least).
|
||||
find_library(CURL_LIBRARY_RELEASE NAMES
|
||||
curl
|
||||
# Windows MSVC prebuilts:
|
||||
curllib
|
||||
libcurl_imp
|
||||
curllib_static
|
||||
# Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip):
|
||||
libcurl
|
||||
# Static library on Windows
|
||||
libcurl_a
|
||||
)
|
||||
mark_as_advanced(CURL_LIBRARY_RELEASE)
|
||||
|
||||
find_library(CURL_LIBRARY_DEBUG NAMES
|
||||
# Windows MSVC CMake builds in debug configuration on vcpkg:
|
||||
libcurl-d_imp
|
||||
libcurl-d
|
||||
# Static library on Windows, compiled in debug mode
|
||||
libcurl_a_debug
|
||||
)
|
||||
mark_as_advanced(CURL_LIBRARY_DEBUG)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake)
|
||||
select_library_configurations_SLIC3R(CURL)
|
||||
if(NOT CURL_FIND_QUIETLY)
|
||||
if (NOT CURL_FOUND)
|
||||
message(STATUS "Falling back to MODULE search for CURL...")
|
||||
else()
|
||||
message(STATUS "CURL found in ${CURL_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CURL_INCLUDE_DIR)
|
||||
foreach(_curl_version_header curlver.h curl.h)
|
||||
if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}")
|
||||
file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"")
|
||||
if (NOT CURL_FOUND)
|
||||
|
||||
string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}")
|
||||
unset(curl_version_str)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
# Look for the header file.
|
||||
find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
|
||||
mark_as_advanced(CURL_INCLUDE_DIR)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL
|
||||
REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR
|
||||
VERSION_VAR CURL_VERSION_STRING)
|
||||
if(NOT CURL_LIBRARY)
|
||||
# Look for the library (sorted from most current/relevant entry to least).
|
||||
find_library(CURL_LIBRARY_RELEASE NAMES
|
||||
curl
|
||||
# Windows MSVC prebuilts:
|
||||
curllib
|
||||
libcurl_imp
|
||||
curllib_static
|
||||
# Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip):
|
||||
libcurl
|
||||
# Static library on Windows
|
||||
libcurl_a
|
||||
)
|
||||
mark_as_advanced(CURL_LIBRARY_RELEASE)
|
||||
|
||||
if(CURL_FOUND)
|
||||
set(CURL_LIBRARIES ${CURL_LIBRARY})
|
||||
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
|
||||
find_library(CURL_LIBRARY_DEBUG NAMES
|
||||
# Windows MSVC CMake builds in debug configuration on vcpkg:
|
||||
libcurl-d_imp
|
||||
libcurl-d
|
||||
# Static library on Windows, compiled in debug mode
|
||||
libcurl_a_debug
|
||||
)
|
||||
mark_as_advanced(CURL_LIBRARY_DEBUG)
|
||||
|
||||
if(NOT TARGET CURL::libcurl)
|
||||
add_library(CURL::libcurl UNKNOWN IMPORTED)
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}")
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake)
|
||||
select_library_configurations_SLIC3R(CURL)
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CURL_LIBRARY}")
|
||||
if(CURL_INCLUDE_DIR)
|
||||
foreach(_curl_version_header curlver.h curl.h)
|
||||
if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}")
|
||||
file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"")
|
||||
|
||||
string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}")
|
||||
unset(curl_version_str)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL
|
||||
REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR
|
||||
VERSION_VAR CURL_VERSION_STRING)
|
||||
|
||||
if(CURL_FOUND)
|
||||
set(CURL_LIBRARIES ${CURL_LIBRARY})
|
||||
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
|
||||
|
||||
if(NOT TARGET CURL::libcurl)
|
||||
add_library(CURL::libcurl UNKNOWN IMPORTED)
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${CURL_LIBRARY}")
|
||||
endif()
|
||||
if(CURL_LIBRARY_RELEASE)
|
||||
set_property(TARGET CURL::libcurl APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
if(CURL_LIBRARY_DEBUG)
|
||||
set_property(TARGET CURL::libcurl APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}")
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}")
|
||||
|
||||
if(EXISTS "${CURL_LIBRARY}")
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${CURL_LIBRARY}")
|
||||
endif()
|
||||
if(CURL_LIBRARY_RELEASE)
|
||||
set_property(TARGET CURL::libcurl APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
if(CURL_LIBRARY_DEBUG)
|
||||
set_property(TARGET CURL::libcurl APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(CURL::libcurl PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endif (NOT CURL_FOUND)
|
||||
|
@ -66,16 +66,25 @@ public:
|
||||
return this->success;
|
||||
}
|
||||
|
||||
void unload_opengl_dll()
|
||||
bool unload_opengl_dll()
|
||||
{
|
||||
if (this->hOpenGL) {
|
||||
BOOL released = FreeLibrary(this->hOpenGL);
|
||||
if (released)
|
||||
printf("System OpenGL library released\n");
|
||||
if (this->hOpenGL != nullptr) {
|
||||
if (::FreeLibrary(this->hOpenGL) != FALSE) {
|
||||
if (::GetModuleHandle(L"opengl32.dll") == nullptr) {
|
||||
printf("System OpenGL library successfully released\n");
|
||||
this->hOpenGL = nullptr;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
printf("System OpenGL library released but not removed\n");
|
||||
}
|
||||
else
|
||||
printf("System OpenGL library NOT released\n");
|
||||
this->hOpenGL = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
|
||||
@ -270,20 +279,26 @@ int wmain(int argc, wchar_t **argv)
|
||||
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
|
||||
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
|
||||
if (load_mesa) {
|
||||
opengl_version_check.unload_opengl_dll();
|
||||
wchar_t path_to_mesa[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_mesa, path_to_exe);
|
||||
wcscat(path_to_mesa, L"mesa\\opengl32.dll");
|
||||
printf("Loading MESA OpenGL library: %S\n", path_to_mesa);
|
||||
HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0);
|
||||
if (hInstance_OpenGL == nullptr) {
|
||||
printf("MESA OpenGL library was not loaded\n");
|
||||
} else
|
||||
printf("MESA OpenGL library was loaded sucessfully\n");
|
||||
bool res = opengl_version_check.unload_opengl_dll();
|
||||
if (!res) {
|
||||
MessageBox(nullptr, L"PrusaSlicer was unable to automatically switch to MESA OpenGL library\nPlease, try to run the application using the '--sw-renderer' option.\n",
|
||||
L"PrusaSlicer Warning", MB_OK);
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
wchar_t path_to_mesa[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_mesa, path_to_exe);
|
||||
wcscat(path_to_mesa, L"mesa\\opengl32.dll");
|
||||
printf("Loading MESA OpenGL library: %S\n", path_to_mesa);
|
||||
HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0);
|
||||
if (hInstance_OpenGL == nullptr)
|
||||
printf("MESA OpenGL library was not loaded\n");
|
||||
else
|
||||
printf("MESA OpenGL library was loaded sucessfully\n");
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
|
||||
wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 };
|
||||
wcscpy(path_to_slic3r, path_to_exe);
|
||||
wcscat(path_to_slic3r, L"PrusaSlicer.dll");
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "../FillRectilinear.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
@ -18,33 +20,42 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl
|
||||
m_supporting_radius2 = Slic3r::sqr(int64_t(radius));
|
||||
// Sample source polygons with a regular grid sampling pattern.
|
||||
for (const ExPolygon &expoly : union_ex(current_overhang)) {
|
||||
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
|
||||
// Find a squared distance to the source expolygon boundary.
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) {
|
||||
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
|
||||
prev = p2;
|
||||
const Points sampled_points = sample_grid_pattern(expoly, m_cell_size);
|
||||
const size_t unsupported_points_prev_size = m_unsupported_points.size();
|
||||
m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size());
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, sampled_points.size()), [&self = *this, &expoly = std::as_const(expoly), &sampled_points = std::as_const(sampled_points), &unsupported_points_prev_size = std::as_const(unsupported_points_prev_size)](const tbb::blocked_range<size_t> &range) -> void {
|
||||
for (size_t sp_idx = range.begin(); sp_idx < range.end(); ++sp_idx) {
|
||||
const Point &sp = sampled_points[sp_idx];
|
||||
// Find a squared distance to the source expolygon boundary.
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) {
|
||||
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2));
|
||||
prev = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))};
|
||||
assert(self.m_unsupported_points_bbox.contains(sp));
|
||||
}
|
||||
m_unsupported_points.emplace_back(p, sqrt(d2));
|
||||
assert(m_unsupported_points_bbox.contains(p));
|
||||
}
|
||||
}); // end of parallel_for
|
||||
}
|
||||
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||
std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||
constexpr coord_t prime_for_hash = 191;
|
||||
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
|
||||
a.dist_to_boundary < b.dist_to_boundary :
|
||||
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
|
||||
});
|
||||
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
|
||||
UnsupportedCell& cell = *it;
|
||||
m_unsupported_points_grid.emplace(this->to_grid_point(cell.loc), it);
|
||||
}
|
||||
|
||||
m_unsupported_points_erased.resize(m_unsupported_points.size());
|
||||
std::fill(m_unsupported_points_erased.begin(), m_unsupported_points_erased.end(), false);
|
||||
|
||||
m_unsupported_points_grid.initialize(m_unsupported_points, [&self = std::as_const(*this)](const Point &p) -> Point { return self.to_grid_point(p); });
|
||||
|
||||
// Because the distance between two points is at least one axis equal to m_cell_size, every cell
|
||||
// in m_unsupported_points_grid contains exactly one point.
|
||||
assert(m_unsupported_points.size() == m_unsupported_points_grid.size());
|
||||
@ -96,12 +107,11 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
||||
}
|
||||
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
|
||||
// Remove unsupported leafs at this grid location.
|
||||
if (auto it = m_unsupported_points_grid.find(grid_addr); it != m_unsupported_points_grid.end()) {
|
||||
std::list<UnsupportedCell>::iterator& list_it = it->second;
|
||||
UnsupportedCell& cell = *list_it;
|
||||
if (const size_t cell_idx = m_unsupported_points_grid.find_cell_idx(grid_addr); cell_idx != std::numeric_limits<size_t>::max()) {
|
||||
const UnsupportedCell &cell = m_unsupported_points[cell_idx];
|
||||
if ((cell.loc - added_leaf).cast<int64_t>().squaredNorm() <= m_supporting_radius2) {
|
||||
m_unsupported_points.erase(list_it);
|
||||
m_unsupported_points_grid.erase(it);
|
||||
m_unsupported_points_erased[cell_idx] = true;
|
||||
m_unsupported_points_grid.mark_erased(grid_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,11 +38,17 @@ public:
|
||||
* \return ``true`` if successful, or ``false`` if there are no more points
|
||||
* to consider.
|
||||
*/
|
||||
bool tryGetNextPoint(Point* p) const {
|
||||
if (m_unsupported_points.empty())
|
||||
return false;
|
||||
*p = m_unsupported_points.front().loc;
|
||||
return true;
|
||||
bool tryGetNextPoint(Point *out_unsupported_location, size_t *out_unsupported_cell_idx, const size_t start_idx = 0) const
|
||||
{
|
||||
for (size_t point_idx = start_idx; point_idx < m_unsupported_points.size(); ++point_idx) {
|
||||
if (!m_unsupported_points_erased[point_idx]) {
|
||||
*out_unsupported_cell_idx = point_idx;
|
||||
*out_unsupported_location = m_unsupported_points[point_idx].loc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -77,7 +83,6 @@ protected:
|
||||
*/
|
||||
struct UnsupportedCell
|
||||
{
|
||||
UnsupportedCell(const Point &loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
|
||||
// The position of the center of this cell.
|
||||
Point loc;
|
||||
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
|
||||
@ -87,7 +92,8 @@ protected:
|
||||
/*!
|
||||
* Cells which still need to be supported at some point.
|
||||
*/
|
||||
std::list<UnsupportedCell> m_unsupported_points;
|
||||
std::vector<UnsupportedCell> m_unsupported_points;
|
||||
std::vector<bool> m_unsupported_points_erased;
|
||||
|
||||
/*!
|
||||
* BoundingBox of all points in m_unsupported_points. Used for mapping of sign integer numbers to positive integer numbers.
|
||||
@ -98,7 +104,84 @@ protected:
|
||||
* Links the unsupported points to a grid point, so that we can quickly look
|
||||
* up the cell belonging to a certain position in the grid.
|
||||
*/
|
||||
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
|
||||
|
||||
class UnsupportedPointsGrid
|
||||
{
|
||||
public:
|
||||
UnsupportedPointsGrid() = default;
|
||||
void initialize(const std::vector<UnsupportedCell> &unsupported_points, const std::function<Point(const Point &)> &map_cell_to_grid)
|
||||
{
|
||||
if (unsupported_points.empty())
|
||||
return;
|
||||
|
||||
BoundingBox unsupported_points_bbox;
|
||||
for (const UnsupportedCell &cell : unsupported_points)
|
||||
unsupported_points_bbox.merge(cell.loc);
|
||||
|
||||
m_size = unsupported_points.size();
|
||||
m_grid_range = BoundingBox(map_cell_to_grid(unsupported_points_bbox.min), map_cell_to_grid(unsupported_points_bbox.max));
|
||||
m_grid_size = m_grid_range.size() + Point::Ones();
|
||||
|
||||
m_data.assign(m_grid_size.y() * m_grid_size.x(), std::numeric_limits<size_t>::max());
|
||||
m_data_erased.assign(m_grid_size.y() * m_grid_size.x(), true);
|
||||
|
||||
for (size_t cell_idx = 0; cell_idx < unsupported_points.size(); ++cell_idx) {
|
||||
const size_t flat_idx = map_to_flat_array(map_cell_to_grid(unsupported_points[cell_idx].loc));
|
||||
assert(m_data[flat_idx] == std::numeric_limits<size_t>::max());
|
||||
m_data[flat_idx] = cell_idx;
|
||||
m_data_erased[flat_idx] = false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
size_t find_cell_idx(const Point &grid_addr)
|
||||
{
|
||||
if (!m_grid_range.contains(grid_addr))
|
||||
return std::numeric_limits<size_t>::max();
|
||||
|
||||
if (const size_t flat_idx = map_to_flat_array(grid_addr); !m_data_erased[flat_idx]) {
|
||||
assert(m_data[flat_idx] != std::numeric_limits<size_t>::max());
|
||||
return m_data[flat_idx];
|
||||
}
|
||||
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
void mark_erased(const Point &grid_addr)
|
||||
{
|
||||
assert(m_grid_range.contains(grid_addr));
|
||||
if (!m_grid_range.contains(grid_addr))
|
||||
return;
|
||||
|
||||
const size_t flat_idx = map_to_flat_array(grid_addr);
|
||||
assert(!m_data_erased[flat_idx] && m_data[flat_idx] != std::numeric_limits<size_t>::max());
|
||||
assert(m_size != 0);
|
||||
|
||||
m_data_erased[flat_idx] = true;
|
||||
--m_size;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_size = 0;
|
||||
|
||||
BoundingBox m_grid_range;
|
||||
Point m_grid_size;
|
||||
|
||||
std::vector<size_t> m_data;
|
||||
std::vector<bool> m_data_erased;
|
||||
|
||||
inline size_t map_to_flat_array(const Point &loc) const
|
||||
{
|
||||
const Point offset_loc = loc - m_grid_range.min;
|
||||
const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x();
|
||||
assert(offset_loc.x() >= 0 && offset_loc.y() >= 0);
|
||||
assert(flat_idx < m_grid_size.y() * m_grid_size.x());
|
||||
return flat_idx;
|
||||
}
|
||||
};
|
||||
|
||||
UnsupportedPointsGrid m_unsupported_points_grid;
|
||||
|
||||
/*!
|
||||
* Maps the point to the grid coordinates.
|
||||
|
@ -125,6 +125,8 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi
|
||||
if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined)
|
||||
below_outlines_bbox.merge(outlines_locator_bbox);
|
||||
|
||||
below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON));
|
||||
|
||||
outlines_locator.set_bbox(below_outlines_bbox);
|
||||
outlines_locator.create(below_outlines, locator_cell_size);
|
||||
|
||||
|
@ -10,6 +10,10 @@
|
||||
#include "../../Geometry.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/blocked_range2d.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location)
|
||||
@ -56,8 +60,9 @@ void Layer::generateNewTrees
|
||||
|
||||
// Until no more points need to be added to support all:
|
||||
// Determine next point from tree/outline areas via distance-field
|
||||
Point unsupported_location;
|
||||
while (distance_field.tryGetNextPoint(&unsupported_location)) {
|
||||
size_t unsupported_cell_idx = 0;
|
||||
Point unsupported_location;
|
||||
while (distance_field.tryGetNextPoint(&unsupported_location, &unsupported_cell_idx, unsupported_cell_idx)) {
|
||||
throw_on_cancel_callback();
|
||||
GroundingLocation grounding_loc = getBestGroundingLocation(
|
||||
unsupported_location, current_outlines, current_outlines_bbox, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
|
||||
@ -141,30 +146,52 @@ GroundingLocation Layer::getBestGroundingLocation
|
||||
|
||||
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
|
||||
|
||||
NodeSPtr sub_tree{ nullptr };
|
||||
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||
NodeSPtr sub_tree{nullptr};
|
||||
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
|
||||
const coord_t search_radius = std::min(current_dist, within_dist);
|
||||
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
|
||||
region.min = to_grid_point(region.min, current_outlines_bbox);
|
||||
region.max = to_grid_point(region.max, current_outlines_bbox);
|
||||
Point grid_addr;
|
||||
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
|
||||
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
|
||||
auto it_range = tree_node_locator.equal_range(grid_addr);
|
||||
for (auto it = it_range.first; it != it_range.second; ++ it) {
|
||||
auto candidate_sub_tree = it->second.lock();
|
||||
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
|
||||
if (candidate_dist < current_dist) {
|
||||
current_dist = candidate_dist;
|
||||
sub_tree = candidate_sub_tree;
|
||||
|
||||
Point current_dist_grid_addr{std::numeric_limits<coord_t>::lowest(), std::numeric_limits<coord_t>::lowest()};
|
||||
std::mutex current_dist_mutex;
|
||||
tbb::parallel_for(tbb::blocked_range2d<coord_t>(region.min.y(), region.max.y(), region.min.x(), region.max.x()), [¤t_dist, current_dist_copy = current_dist, ¤t_dist_mutex, &sub_tree, ¤t_dist_grid_addr, &exclude_tree = std::as_const(exclude_tree), &outline_locator = std::as_const(outline_locator), &supporting_radius = std::as_const(supporting_radius), &tree_node_locator = std::as_const(tree_node_locator), &unsupported_location = std::as_const(unsupported_location)](const tbb::blocked_range2d<coord_t> &range) -> void {
|
||||
for (coord_t grid_addr_y = range.rows().begin(); grid_addr_y < range.rows().end(); ++grid_addr_y)
|
||||
for (coord_t grid_addr_x = range.cols().begin(); grid_addr_x < range.cols().end(); ++grid_addr_x) {
|
||||
const Point local_grid_addr{grid_addr_x, grid_addr_y};
|
||||
NodeSPtr local_sub_tree{nullptr};
|
||||
coord_t local_current_dist = current_dist_copy;
|
||||
const auto it_range = tree_node_locator.equal_range(local_grid_addr);
|
||||
for (auto it = it_range.first; it != it_range.second; ++it) {
|
||||
const NodeSPtr candidate_sub_tree = it->second.lock();
|
||||
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||
if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < local_current_dist) {
|
||||
local_current_dist = candidate_dist;
|
||||
local_sub_tree = candidate_sub_tree;
|
||||
}
|
||||
}
|
||||
}
|
||||
// To always get the same result in a parallel version as in a non-parallel version,
|
||||
// we need to preserve that for the same current_dist, we select the same sub_tree
|
||||
// as in the non-parallel version. For this purpose, inside the variable
|
||||
// current_dist_grid_addr is stored from with 2D grid position assigned sub_tree comes.
|
||||
// And when there are two sub_tree with the same current_dist, one which will be found
|
||||
// the first in the non-parallel version is selected.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(current_dist_mutex);
|
||||
if (local_current_dist < current_dist ||
|
||||
(local_current_dist == current_dist && (grid_addr_y < current_dist_grid_addr.y() ||
|
||||
(grid_addr_y == current_dist_grid_addr.y() && grid_addr_x < current_dist_grid_addr.x())))) {
|
||||
current_dist = local_current_dist;
|
||||
sub_tree = local_sub_tree;
|
||||
current_dist_grid_addr = local_grid_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}); // end of parallel_for
|
||||
}
|
||||
|
||||
return ! sub_tree ?
|
||||
|
@ -269,6 +269,9 @@ protected:
|
||||
|
||||
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
|
||||
|
||||
friend BoundingBox get_extents(const NodeSPtr &root_node);
|
||||
friend BoundingBox get_extents(const std::vector<NodeSPtr> &tree_roots);
|
||||
|
||||
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
friend void export_to_svg(const NodeSPtr &root_node, Slic3r::SVG &svg);
|
||||
friend void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes);
|
||||
@ -278,6 +281,23 @@ protected:
|
||||
bool inside(const Polygons &polygons, const Point &p);
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, coord_t within_max_dist);
|
||||
|
||||
inline BoundingBox get_extents(const NodeSPtr &root_node)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const NodeSPtr &children : root_node->m_children)
|
||||
bbox.merge(get_extents(children));
|
||||
bbox.merge(root_node->getLocation());
|
||||
return bbox;
|
||||
}
|
||||
|
||||
inline BoundingBox get_extents(const std::vector<NodeSPtr> &tree_roots)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const NodeSPtr &root_node : tree_roots)
|
||||
bbox.merge(get_extents(root_node));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
void export_to_svg(const NodeSPtr &root_node, SVG &svg);
|
||||
void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes);
|
||||
|
@ -819,6 +819,29 @@ namespace Slic3r {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If instances contain a single volume, the volume offset should be 0,0,0
|
||||
// This equals to say that instance world position and volume world position should match
|
||||
// Correct all instances/volumes for which this does not hold
|
||||
for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) {
|
||||
ModelObject* o = model.objects[obj_id];
|
||||
if (o->volumes.size() == 1) {
|
||||
ModelVolume* v = o->volumes.front();
|
||||
const Slic3r::Geometry::Transformation& first_inst_trafo = o->instances.front()->get_transformation();
|
||||
const Vec3d world_vol_offset = (first_inst_trafo * v->get_transformation()).get_offset();
|
||||
const Vec3d world_inst_offset = first_inst_trafo.get_offset();
|
||||
|
||||
if (!world_vol_offset.isApprox(world_inst_offset)) {
|
||||
const Slic3r::Geometry::Transformation& vol_trafo = v->get_transformation();
|
||||
for (int inst_id = 0; inst_id < int(o->instances.size()); ++inst_id) {
|
||||
ModelInstance* i = o->instances[inst_id];
|
||||
const Slic3r::Geometry::Transformation& inst_trafo = i->get_transformation();
|
||||
i->set_offset((inst_trafo * vol_trafo).get_offset());
|
||||
}
|
||||
v->set_offset(Vec3d::Zero());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) {
|
||||
ModelObject* o = model.objects[obj_id];
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "format.hpp"
|
||||
@ -453,6 +453,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
|
||||
presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options);
|
||||
}
|
||||
|
||||
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
if (type == Preset::TYPE_PRINTER)
|
||||
copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config);
|
||||
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
|
||||
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
|
||||
presets.save_current_preset(new_name);
|
||||
// Mark the print & filament enabled if they are compatible with the currently selected preset.
|
||||
@ -1860,4 +1865,32 @@ void PresetBundle::set_default_suppressed(bool default_suppressed)
|
||||
printers.set_default_suppressed(default_suppressed);
|
||||
}
|
||||
|
||||
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config)
|
||||
{
|
||||
const boost::filesystem::path user_dir = boost::filesystem::absolute(boost::filesystem::path(data_dir()) / "printer").make_preferred();
|
||||
const boost::filesystem::path res_dir = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||
|
||||
auto do_copy = [&user_dir, &res_dir](ConfigOptionString* cfg, const std::string& type) {
|
||||
if (cfg == nullptr || cfg->value.empty())
|
||||
return;
|
||||
|
||||
const boost::filesystem::path src_dir = boost::filesystem::absolute(boost::filesystem::path(cfg->value)).make_preferred().parent_path();
|
||||
if (src_dir != user_dir && src_dir.parent_path() != res_dir) {
|
||||
const std::string dst_value = (user_dir / boost::filesystem::path(cfg->value).filename()).string();
|
||||
std::string error;
|
||||
if (copy_file_inner(cfg->value, dst_value, error) == SUCCESS)
|
||||
cfg->value = dst_value;
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Copying from " << cfg->value << " to " << dst_value << " failed. Unable to set custom bed " << type << ". [" << error << "]";
|
||||
cfg->value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
do_copy(config.option<ConfigOptionString>("bed_custom_texture"), "texture");
|
||||
do_copy(config.option<ConfigOptionString>("bed_custom_model"), "model");
|
||||
}
|
||||
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -178,6 +178,12 @@ private:
|
||||
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
|
||||
|
||||
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
// Copies bed texture and model files to 'data_dir()\printer' folder, if needed
|
||||
// and updates the config accordingly
|
||||
extern void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config);
|
||||
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetBundle_hpp_ */
|
||||
|
@ -288,7 +288,7 @@ template<unsigned MAX_ITER>
|
||||
struct RotfinderBoilerplate {
|
||||
static constexpr unsigned MAX_TRIES = MAX_ITER;
|
||||
|
||||
int status = 0;
|
||||
int status = 0, prev_status = 0;
|
||||
TriangleMesh mesh;
|
||||
unsigned max_tries;
|
||||
const RotOptimizeParams ¶ms;
|
||||
@ -314,13 +314,20 @@ struct RotfinderBoilerplate {
|
||||
|
||||
RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p)
|
||||
: mesh{get_mesh_to_rotate(mo)}
|
||||
, params{p}
|
||||
, max_tries(p.accuracy() * MAX_TRIES)
|
||||
{
|
||||
, params{p}
|
||||
{}
|
||||
|
||||
void statusfn() {
|
||||
int s = status * 100 / max_tries;
|
||||
if (s != prev_status) {
|
||||
params.statuscb()(s);
|
||||
prev_status = s;
|
||||
}
|
||||
|
||||
++status;
|
||||
}
|
||||
|
||||
void statusfn() { params.statuscb()(++status * 100.0 / max_tries); }
|
||||
bool stopcond() { return ! params.statuscb()(-1); }
|
||||
};
|
||||
|
||||
|
@ -87,8 +87,7 @@
|
||||
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
|
||||
// Enable gizmo grabbers to share common models
|
||||
#define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1)
|
||||
// Disable association to 3mf and stl files if the application is run on Windows 8 or later
|
||||
#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1)
|
||||
|
||||
// Enable copy of custom bed model and texture
|
||||
#define ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE (1 && ENABLE_2_5_0_ALPHA1)
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
@ -176,6 +176,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Jobs/Worker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.hpp
|
||||
GUI/Jobs/BoostThreadWorker.cpp
|
||||
GUI/Jobs/UIThreadWorker.hpp
|
||||
GUI/Jobs/BusyCursorJob.hpp
|
||||
GUI/Jobs/PlaterWorker.hpp
|
||||
GUI/Jobs/ArrangeJob.hpp
|
||||
@ -250,6 +251,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/TCPConsole.hpp
|
||||
Utils/MKS.cpp
|
||||
Utils/MKS.hpp
|
||||
Utils/WinRegistry.cpp
|
||||
Utils/WinRegistry.hpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
@ -1934,10 +1934,7 @@ void ConfigWizard::priv::load_pages()
|
||||
index->add_page(page_update);
|
||||
index->add_page(page_reload_from_disk);
|
||||
#ifdef _WIN32
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (page_files_association != nullptr)
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
index->add_page(page_files_association);
|
||||
index->add_page(page_files_association);
|
||||
#endif // _WIN32
|
||||
index->add_page(page_mode);
|
||||
|
||||
@ -2750,32 +2747,20 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
|
||||
|
||||
#ifdef _WIN32
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (page_files_association != nullptr) {
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
|
||||
app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
|
||||
// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
|
||||
app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
|
||||
app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
|
||||
// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
if (page_files_association->associate_3mf())
|
||||
wxGetApp().associate_3mf_files();
|
||||
if (page_files_association->associate_stl())
|
||||
wxGetApp().associate_stl_files();
|
||||
}
|
||||
// else {
|
||||
// if (page_files_association->associate_gcode())
|
||||
// wxGetApp().associate_gcode_files();
|
||||
// }
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (wxGetApp().is_editor()) {
|
||||
if (page_files_association->associate_3mf())
|
||||
wxGetApp().associate_3mf_files();
|
||||
if (page_files_association->associate_stl())
|
||||
wxGetApp().associate_stl_files();
|
||||
}
|
||||
else {
|
||||
app_config->set("associate_3mf", "0");
|
||||
app_config->set("associate_stl", "0");
|
||||
// app_config->set("associate_gcode", "0");
|
||||
}
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
|
||||
// else {
|
||||
// if (page_files_association->associate_gcode())
|
||||
// wxGetApp().associate_gcode_files();
|
||||
// }
|
||||
#endif // _WIN32
|
||||
|
||||
page_mode->serialize_mode(app_config);
|
||||
@ -2795,6 +2780,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
page_diams->apply_custom_config(*custom_config);
|
||||
page_temps->apply_custom_config(*custom_config);
|
||||
|
||||
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
copy_bed_model_and_texture_if_needed(*custom_config);
|
||||
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
|
||||
|
||||
const std::string profile_name = page_custom->profile_name();
|
||||
preset_bundle->load_config_from_wizard(profile_name, *custom_config);
|
||||
}
|
||||
@ -2935,11 +2924,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
p->add_page(p->page_update = new PageUpdate(this));
|
||||
p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
|
||||
#ifdef _WIN32
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
// file association is not possible anymore starting with Win 8
|
||||
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8)
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
p->add_page(p->page_files_association = new PageFilesAssociation(this));
|
||||
p->add_page(p->page_files_association = new PageFilesAssociation(this));
|
||||
#endif // _WIN32
|
||||
p->add_page(p->page_mode = new PageMode(this));
|
||||
p->add_page(p->page_firmware = new PageFirmware(this));
|
||||
|
@ -991,11 +991,13 @@ void GCodeViewer::render()
|
||||
render_toolpaths();
|
||||
render_shells();
|
||||
float legend_height = 0.0f;
|
||||
render_legend(legend_height);
|
||||
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
|
||||
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
|
||||
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
|
||||
m_sequential_view.render(legend_height);
|
||||
if (!m_layers.empty()) {
|
||||
render_legend(legend_height);
|
||||
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
|
||||
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
|
||||
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
|
||||
m_sequential_view.render(legend_height);
|
||||
}
|
||||
}
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
render_statistics();
|
||||
|
@ -59,6 +59,7 @@
|
||||
#include "../Utils/Process.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "../Utils/AppUpdater.hpp"
|
||||
#include "../Utils/WinRegistry.hpp"
|
||||
#include "slic3r/Config/Snapshot.hpp"
|
||||
#include "ConfigSnapshotDialog.hpp"
|
||||
#include "FirmwareDialog.hpp"
|
||||
@ -1203,17 +1204,10 @@ bool GUI_App::on_init_inner()
|
||||
|
||||
if (is_editor()) {
|
||||
#ifdef __WXMSW__
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
// file association is not possible anymore starting with Win 8
|
||||
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (app_config->get("associate_3mf") == "1")
|
||||
associate_3mf_files();
|
||||
if (app_config->get("associate_stl") == "1")
|
||||
associate_stl_files();
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
}
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (app_config->get("associate_3mf") == "1")
|
||||
associate_3mf_files();
|
||||
if (app_config->get("associate_stl") == "1")
|
||||
associate_stl_files();
|
||||
#endif // __WXMSW__
|
||||
|
||||
preset_updater = new PresetUpdater();
|
||||
@ -1251,15 +1245,8 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
else {
|
||||
#ifdef __WXMSW__
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
// file association is not possible anymore starting with Win 8
|
||||
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (app_config->get("associate_gcode") == "1")
|
||||
associate_gcode_files();
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
}
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (app_config->get("associate_gcode") == "1")
|
||||
associate_gcode_files();
|
||||
#endif // __WXMSW__
|
||||
}
|
||||
|
||||
@ -2432,23 +2419,16 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri
|
||||
this->plater_->refresh_print();
|
||||
|
||||
#ifdef _WIN32
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
// file association is not possible anymore starting with Win 8
|
||||
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (is_editor()) {
|
||||
if (app_config->get("associate_3mf") == "1")
|
||||
associate_3mf_files();
|
||||
if (app_config->get("associate_stl") == "1")
|
||||
associate_stl_files();
|
||||
}
|
||||
else {
|
||||
if (app_config->get("associate_gcode") == "1")
|
||||
associate_gcode_files();
|
||||
}
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
if (is_editor()) {
|
||||
if (app_config->get("associate_3mf") == "1")
|
||||
associate_3mf_files();
|
||||
if (app_config->get("associate_stl") == "1")
|
||||
associate_stl_files();
|
||||
}
|
||||
else {
|
||||
if (app_config->get("associate_gcode") == "1")
|
||||
associate_gcode_files();
|
||||
}
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
#endif // _WIN32
|
||||
|
||||
if (mainframe->preferences_dialog->settings_layout_changed()) {
|
||||
@ -3156,119 +3136,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa
|
||||
|
||||
|
||||
#ifdef __WXMSW__
|
||||
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
|
||||
{
|
||||
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
|
||||
wchar_t szValueCurrent[1000];
|
||||
DWORD dwType;
|
||||
DWORD dwSize = sizeof(szValueCurrent);
|
||||
|
||||
int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
|
||||
|
||||
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
|
||||
|
||||
if ((iRC != ERROR_SUCCESS) && !bDidntExist)
|
||||
// an error occurred
|
||||
return false;
|
||||
|
||||
if (!bDidntExist) {
|
||||
if (dwType != REG_SZ)
|
||||
// invalid type
|
||||
return false;
|
||||
|
||||
if (::wcscmp(szValueCurrent, pszValue) == 0)
|
||||
// value already set
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD dwDisposition;
|
||||
HKEY hkey;
|
||||
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
|
||||
bool ret = false;
|
||||
if (iRC == ERROR_SUCCESS) {
|
||||
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
|
||||
if (iRC == ERROR_SUCCESS)
|
||||
ret = true;
|
||||
}
|
||||
|
||||
RegCloseKey(hkey);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GUI_App::associate_3mf_files()
|
||||
{
|
||||
wchar_t app_path[MAX_PATH];
|
||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
||||
|
||||
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
|
||||
std::wstring prog_id = L"Prusa.Slicer.1";
|
||||
std::wstring prog_desc = L"PrusaSlicer";
|
||||
std::wstring prog_command = prog_path + L" \"%1\"";
|
||||
std::wstring reg_base = L"Software\\Classes";
|
||||
std::wstring reg_extension = reg_base + L"\\.3mf";
|
||||
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
|
||||
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
|
||||
|
||||
bool is_new = false;
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
||||
|
||||
if (is_new)
|
||||
// notify Windows only when any of the values gets changed
|
||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true);
|
||||
}
|
||||
|
||||
void GUI_App::associate_stl_files()
|
||||
{
|
||||
wchar_t app_path[MAX_PATH];
|
||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
||||
|
||||
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
|
||||
std::wstring prog_id = L"Prusa.Slicer.1";
|
||||
std::wstring prog_desc = L"PrusaSlicer";
|
||||
std::wstring prog_command = prog_path + L" \"%1\"";
|
||||
std::wstring reg_base = L"Software\\Classes";
|
||||
std::wstring reg_extension = reg_base + L"\\.stl";
|
||||
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
|
||||
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
|
||||
|
||||
bool is_new = false;
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
||||
|
||||
if (is_new)
|
||||
// notify Windows only when any of the values gets changed
|
||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true);
|
||||
}
|
||||
|
||||
void GUI_App::associate_gcode_files()
|
||||
{
|
||||
wchar_t app_path[MAX_PATH];
|
||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
||||
|
||||
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
|
||||
std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1";
|
||||
std::wstring prog_desc = L"PrusaSlicerGCodeViewer";
|
||||
std::wstring prog_command = prog_path + L" \"%1\"";
|
||||
std::wstring reg_base = L"Software\\Classes";
|
||||
std::wstring reg_extension = reg_base + L"\\.gcode";
|
||||
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
|
||||
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
|
||||
|
||||
bool is_new = false;
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
||||
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
||||
|
||||
if (is_new)
|
||||
// notify Windows only when any of the values gets changed
|
||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true);
|
||||
}
|
||||
#endif // __WXMSW__
|
||||
|
||||
|
||||
void GUI_App::on_version_read(wxCommandEvent& evt)
|
||||
{
|
||||
app_config->set("version_online", into_u8(evt.GetString()));
|
||||
|
@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range)
|
||||
if (gcode_preview_data_valid) {
|
||||
// Load the real G-code preview.
|
||||
m_canvas->load_gcode_preview(*m_gcode_result, colors);
|
||||
m_left_sizer->Show(m_bottom_toolbar_panel);
|
||||
m_left_sizer->Layout();
|
||||
Refresh();
|
||||
zs = m_canvas->get_gcode_layers_zs();
|
||||
if (!zs.empty())
|
||||
m_left_sizer->Show(m_bottom_toolbar_panel);
|
||||
m_loaded = true;
|
||||
}
|
||||
else if (wxGetApp().is_editor()) {
|
||||
|
@ -879,7 +879,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil
|
||||
GLGizmoRotate(parent, GLGizmoRotate::X),
|
||||
GLGizmoRotate(parent, GLGizmoRotate::Y),
|
||||
GLGizmoRotate(parent, GLGizmoRotate::Z) })
|
||||
{}
|
||||
{
|
||||
load_rotoptimize_state();
|
||||
}
|
||||
|
||||
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
|
@ -40,6 +40,12 @@ class PlaterWorker: public Worker {
|
||||
{
|
||||
wxWakeUpIdle();
|
||||
ctl.update_status(st, msg);
|
||||
|
||||
// If the worker is not using additional threads, the UI
|
||||
// is refreshed with this call. If the worker is running
|
||||
// in it's own thread, the yield should not have any
|
||||
// visible effects.
|
||||
wxYieldIfNeeded();
|
||||
}
|
||||
|
||||
bool was_canceled() const override { return ctl.was_canceled(); }
|
||||
|
125
src/slic3r/GUI/Jobs/UIThreadWorker.hpp
Normal file
125
src/slic3r/GUI/Jobs/UIThreadWorker.hpp
Normal file
@ -0,0 +1,125 @@
|
||||
#ifndef UITHREADWORKER_HPP
|
||||
#define UITHREADWORKER_HPP
|
||||
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
|
||||
#include "Worker.hpp"
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Implementation of a worker which does not create any additional threads.
|
||||
class UIThreadWorker : public Worker, private Job::Ctl {
|
||||
std::queue<std::unique_ptr<Job>, std::deque<std::unique_ptr<Job>>> m_jobqueue;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
bool m_running = false;
|
||||
bool m_canceled = false;
|
||||
|
||||
void process_front()
|
||||
{
|
||||
std::unique_ptr<Job> job;
|
||||
|
||||
if (!m_jobqueue.empty()) {
|
||||
job = std::move(m_jobqueue.front());
|
||||
m_jobqueue.pop();
|
||||
}
|
||||
|
||||
if (job) {
|
||||
std::exception_ptr eptr;
|
||||
m_running = true;
|
||||
|
||||
try {
|
||||
job->process(*this);
|
||||
} catch (...) {
|
||||
eptr= std::current_exception();
|
||||
}
|
||||
|
||||
job->finalize(m_canceled, eptr);
|
||||
|
||||
// Unhandled exceptions are rethrown without mercy.
|
||||
if (eptr)
|
||||
std::rethrow_exception(eptr);
|
||||
|
||||
m_running = false;
|
||||
|
||||
m_canceled = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// Implement Job::Ctl interface:
|
||||
|
||||
void update_status(int st, const std::string &msg = "") override
|
||||
{
|
||||
if (m_progress) {
|
||||
m_progress->set_progress(st);
|
||||
m_progress->set_status_text(msg.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool was_canceled() const override { return m_canceled; }
|
||||
|
||||
std::future<void> call_on_main_thread(std::function<void()> fn) override
|
||||
{
|
||||
return std::async(std::launch::deferred, [fn]{ fn(); });
|
||||
}
|
||||
|
||||
public:
|
||||
explicit UIThreadWorker(std::shared_ptr<ProgressIndicator> pri,
|
||||
const std::string & /*name*/ = "")
|
||||
: m_progress{pri}
|
||||
{
|
||||
if (m_progress)
|
||||
m_progress->set_cancel_callback([this]() { cancel(); });
|
||||
}
|
||||
|
||||
UIThreadWorker() = default;
|
||||
|
||||
bool push(std::unique_ptr<Job> job) override
|
||||
{
|
||||
m_canceled = false;
|
||||
|
||||
if (job)
|
||||
m_jobqueue.push(std::move(job));
|
||||
|
||||
return bool(job);
|
||||
}
|
||||
|
||||
bool is_idle() const override { return !m_running && m_jobqueue.empty(); }
|
||||
|
||||
void cancel() override { m_canceled = true; }
|
||||
|
||||
void cancel_all() override
|
||||
{
|
||||
m_canceled = true;
|
||||
process_front();
|
||||
|
||||
while (!m_jobqueue.empty())
|
||||
m_jobqueue.pop();
|
||||
}
|
||||
|
||||
void process_events() override {
|
||||
while (!m_jobqueue.empty())
|
||||
process_front();
|
||||
}
|
||||
|
||||
bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override {
|
||||
process_front();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override {
|
||||
process_events();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ProgressIndicator * get_pri() { return m_progress.get(); }
|
||||
const ProgressIndicator * get_pri() const { return m_progress.get(); }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // UITHREADWORKER_HPP
|
@ -1656,6 +1656,9 @@ struct Plater::priv
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
//
|
||||
// UIThreadWorker can be used as a replacement for BoostThreadWorker if
|
||||
// no additional worker threads are desired (useful for debugging or profiling)
|
||||
PlaterWorker<BoostThreadWorker> m_worker;
|
||||
SLAImportDialog * m_sla_import_dlg;
|
||||
|
||||
|
@ -79,9 +79,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin
|
||||
m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str());
|
||||
m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1";
|
||||
|
||||
// update colors for color pickers
|
||||
update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
|
||||
update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
|
||||
if (wxGetApp().is_editor()) {
|
||||
// update colors for color pickers
|
||||
update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
|
||||
update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
|
||||
}
|
||||
|
||||
this->ShowModal();
|
||||
}
|
||||
@ -230,23 +232,16 @@ void PreferencesDialog::build()
|
||||
app_config->get("export_sources_full_pathnames") == "1");
|
||||
|
||||
#ifdef _WIN32
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
// file association is not possible anymore starting with Win 8
|
||||
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
// Please keep in sync with ConfigWizard
|
||||
append_bool_option(m_optgroup_general, "associate_3mf",
|
||||
L("Associate .3mf files to PrusaSlicer"),
|
||||
L("If enabled, sets PrusaSlicer as default application to open .3mf files."),
|
||||
app_config->get("associate_3mf") == "1");
|
||||
// Please keep in sync with ConfigWizard
|
||||
append_bool_option(m_optgroup_general, "associate_3mf",
|
||||
L("Associate .3mf files to PrusaSlicer"),
|
||||
L("If enabled, sets PrusaSlicer as default application to open .3mf files."),
|
||||
app_config->get("associate_3mf") == "1");
|
||||
|
||||
append_bool_option(m_optgroup_general, "associate_stl",
|
||||
L("Associate .stl files to PrusaSlicer"),
|
||||
L("If enabled, sets PrusaSlicer as default application to open .stl files."),
|
||||
app_config->get("associate_stl") == "1");
|
||||
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
}
|
||||
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
|
||||
append_bool_option(m_optgroup_general, "associate_stl",
|
||||
L("Associate .stl files to PrusaSlicer"),
|
||||
L("If enabled, sets PrusaSlicer as default application to open .stl files."),
|
||||
app_config->get("associate_stl") == "1");
|
||||
#endif // _WIN32
|
||||
|
||||
m_optgroup_general->append_separator();
|
||||
|
@ -1925,12 +1925,10 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
|
||||
|
||||
#if ENABLE_GL_SHADERS_ATTRIBUTES
|
||||
const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center());
|
||||
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
|
||||
#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE
|
||||
Transform3d orient_matrix = Transform3d::Identity();
|
||||
#else
|
||||
glsafe(::glPushMatrix());
|
||||
#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE
|
||||
#endif // ENABLE_GL_SHADERS_ATTRIBUTES
|
||||
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
const Vec3d center = get_bounding_box().center();
|
||||
|
490
src/slic3r/Utils/WinRegistry.cpp
Normal file
490
src/slic3r/Utils/WinRegistry.cpp
Normal file
@ -0,0 +1,490 @@
|
||||
#include "libslic3r/Technologies.hpp"
|
||||
#include "WinRegistry.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h>
|
||||
#include <wincrypt.h>
|
||||
#include <winternl.h>
|
||||
#include <sddl.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Helper class which automatically closes the handle when
|
||||
// going out of scope
|
||||
class AutoHandle
|
||||
{
|
||||
HANDLE m_handle{ nullptr };
|
||||
|
||||
public:
|
||||
explicit AutoHandle(HANDLE handle) : m_handle(handle) {}
|
||||
~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); }
|
||||
HANDLE get() { return m_handle; }
|
||||
};
|
||||
|
||||
// Helper class which automatically closes the key when
|
||||
// going out of scope
|
||||
class AutoRegKey
|
||||
{
|
||||
HKEY m_key{ nullptr };
|
||||
|
||||
public:
|
||||
explicit AutoRegKey(HKEY key) : m_key(key) {}
|
||||
~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); }
|
||||
HKEY get() { return m_key; }
|
||||
};
|
||||
|
||||
// returns true if the given value is set/modified into Windows registry
|
||||
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
|
||||
{
|
||||
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
|
||||
wchar_t szValueCurrent[1000];
|
||||
DWORD dwType;
|
||||
DWORD dwSize = sizeof(szValueCurrent);
|
||||
|
||||
LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
|
||||
|
||||
const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
|
||||
|
||||
if (iRC != ERROR_SUCCESS && !bDidntExist)
|
||||
// an error occurred
|
||||
return false;
|
||||
|
||||
if (!bDidntExist) {
|
||||
if (dwType != REG_SZ)
|
||||
// invalid type
|
||||
return false;
|
||||
|
||||
if (::wcscmp(szValueCurrent, pszValue) == 0)
|
||||
// value already set
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD dwDisposition;
|
||||
HKEY hkey;
|
||||
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
|
||||
bool ret = false;
|
||||
if (iRC == ERROR_SUCCESS) {
|
||||
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
|
||||
if (iRC == ERROR_SUCCESS)
|
||||
ret = true;
|
||||
}
|
||||
|
||||
RegCloseKey(hkey);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::wstring get_current_user_string_sid()
|
||||
{
|
||||
HANDLE rawProcessToken;
|
||||
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY,
|
||||
&rawProcessToken))
|
||||
return L"";
|
||||
|
||||
AutoHandle processToken(rawProcessToken);
|
||||
|
||||
DWORD userSize = 0;
|
||||
if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) &&
|
||||
GetLastError() == ERROR_INSUFFICIENT_BUFFER))
|
||||
return L"";
|
||||
|
||||
std::vector<unsigned char> userBytes(userSize);
|
||||
if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize))
|
||||
return L"";
|
||||
|
||||
wchar_t* rawSid = nullptr;
|
||||
if (!::ConvertSidToStringSidW(reinterpret_cast<PTOKEN_USER>(userBytes.data())->User.Sid, &rawSid))
|
||||
return L"";
|
||||
|
||||
return rawSid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the string which becomes the input to the UserChoice hash.
|
||||
*
|
||||
* @see generate_user_choice_hash() for parameters.
|
||||
*
|
||||
* @return The formatted string, empty string on failure.
|
||||
*
|
||||
* NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing),
|
||||
* used at least since 1803.
|
||||
* There was at least one older version, not currently supported: On Win10 RTM
|
||||
* (build 10240, aka 1507) the hash function is the same, but the timestamp and
|
||||
* User Experience string aren't included; instead (for protocols) the string
|
||||
* ends with the exe path. The changelog of SetUserFTA suggests the algorithm
|
||||
* changed in 1703, so there may be two versions: before 1703, and 1703 to now.
|
||||
*/
|
||||
static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
|
||||
{
|
||||
aTimestamp.wSecond = 0;
|
||||
aTimestamp.wMilliseconds = 0;
|
||||
|
||||
FILETIME fileTime = { 0 };
|
||||
if (!::SystemTimeToFileTime(&aTimestamp, &fileTime))
|
||||
return L"";
|
||||
|
||||
// This string is built into Windows as part of the UserChoice hash algorithm.
|
||||
// It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or
|
||||
// across builds of the same SKU, but this is the only currently known
|
||||
// version. There isn't any known way of deriving it, so we assume this
|
||||
// constant value. If we are wrong, we will not be able to generate correct
|
||||
// UserChoice hashes.
|
||||
const wchar_t* userExperience =
|
||||
L"User Choice set via Windows User Experience "
|
||||
L"{D18B6DD5-6124-4341-9318-804003BAFA0B}";
|
||||
|
||||
const wchar_t* userChoiceFmt =
|
||||
L"%s%s%s"
|
||||
L"%08lx"
|
||||
L"%08lx"
|
||||
L"%s";
|
||||
|
||||
int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId,
|
||||
fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
|
||||
userChoiceLen += 1; // _scwprintf does not include the terminator
|
||||
|
||||
std::wstring userChoice(userChoiceLen, L'\0');
|
||||
_snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt,
|
||||
aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
|
||||
|
||||
::CharLowerW(userChoice.data());
|
||||
return userChoice;
|
||||
}
|
||||
|
||||
// @return The MD5 hash of the input, nullptr on failure.
|
||||
static std::vector<DWORD> cng_md5(const unsigned char* bytes, ULONG bytesLen) {
|
||||
constexpr ULONG MD5_BYTES = 16;
|
||||
constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD);
|
||||
std::vector<DWORD> hash;
|
||||
|
||||
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||
if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) {
|
||||
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||
// As of Windows 7 the hash handle will manage its own object buffer when
|
||||
// pbHashObject is nullptr and cbHashObject is 0.
|
||||
if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) {
|
||||
// BCryptHashData promises not to modify pbInput.
|
||||
if (NT_SUCCESS(::BCryptHashData(hHash, const_cast<unsigned char*>(bytes), bytesLen, 0))) {
|
||||
hash.resize(MD5_DWORDS);
|
||||
if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast<unsigned char*>(hash.data()),
|
||||
MD5_DWORDS * sizeof(DWORD), 0)))
|
||||
hash.clear();
|
||||
}
|
||||
::BCryptDestroyHash(hHash);
|
||||
}
|
||||
::BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline DWORD word_swap(DWORD v)
|
||||
{
|
||||
return (v >> 16) | (v << 16);
|
||||
}
|
||||
|
||||
// @return The input bytes encoded as base64, nullptr on failure.
|
||||
static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) {
|
||||
DWORD base64Len = 0;
|
||||
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len))
|
||||
return L"";
|
||||
|
||||
std::wstring base64(base64Len, L'\0');
|
||||
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len))
|
||||
return L"";
|
||||
|
||||
return base64;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate the UserChoice Hash.
|
||||
*
|
||||
* This implementation is based on the references listed above.
|
||||
* It is organized to show the logic as clearly as possible, but at some
|
||||
* point the reasoning is just "this is how it works".
|
||||
*
|
||||
* @param inputString A null-terminated string to hash.
|
||||
*
|
||||
* @return The base64-encoded hash, or empty string on failure.
|
||||
*/
|
||||
static std::wstring hash_string(const wchar_t* inputString)
|
||||
{
|
||||
auto inputBytes = reinterpret_cast<const unsigned char*>(inputString);
|
||||
int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t);
|
||||
|
||||
constexpr size_t DWORDS_PER_BLOCK = 2;
|
||||
constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK;
|
||||
// Incomplete blocks are ignored.
|
||||
int blockCount = inputByteCount / BLOCK_SIZE;
|
||||
|
||||
if (blockCount == 0)
|
||||
return L"";
|
||||
|
||||
// Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers
|
||||
// in the scramble below.
|
||||
auto md5 = cng_md5(inputBytes, inputByteCount);
|
||||
if (md5.empty())
|
||||
return L"";
|
||||
|
||||
// The following loop effectively computes two checksums, scrambled like a
|
||||
// hash after every DWORD is added.
|
||||
|
||||
// Constant multipliers for the scramble, one set for each DWORD in a block.
|
||||
const DWORD C0s[DWORDS_PER_BLOCK][5] = {
|
||||
{md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL},
|
||||
{md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} };
|
||||
const DWORD C1s[DWORDS_PER_BLOCK][5] = {
|
||||
{md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL},
|
||||
{md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} };
|
||||
|
||||
// The checksums.
|
||||
DWORD h0 = 0;
|
||||
DWORD h1 = 0;
|
||||
// Accumulated total of the checksum after each DWORD.
|
||||
DWORD h0Acc = 0;
|
||||
DWORD h1Acc = 0;
|
||||
|
||||
for (int i = 0; i < blockCount; ++i) {
|
||||
for (int j = 0; j < DWORDS_PER_BLOCK; ++j) {
|
||||
const DWORD* C0 = C0s[j];
|
||||
const DWORD* C1 = C1s[j];
|
||||
|
||||
DWORD input;
|
||||
memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD));
|
||||
|
||||
h0 += input;
|
||||
// Scramble 0
|
||||
h0 *= C0[0];
|
||||
h0 = word_swap(h0) * C0[1];
|
||||
h0 = word_swap(h0) * C0[2];
|
||||
h0 = word_swap(h0) * C0[3];
|
||||
h0 = word_swap(h0) * C0[4];
|
||||
h0Acc += h0;
|
||||
|
||||
h1 += input;
|
||||
// Scramble 1
|
||||
h1 = word_swap(h1) * C1[1] + h1 * C1[0];
|
||||
h1 = (h1 >> 16) * C1[2] + h1 * C1[3];
|
||||
h1 = word_swap(h1) * C1[4] + h1;
|
||||
h1Acc += h1;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc };
|
||||
return crypto_api_base64_encode(reinterpret_cast<const unsigned char*>(hash), sizeof(hash));
|
||||
}
|
||||
|
||||
static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
|
||||
{
|
||||
const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp);
|
||||
if (userChoice.empty())
|
||||
return L"";
|
||||
|
||||
return hash_string(userChoice.c_str());
|
||||
}
|
||||
|
||||
static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS)
|
||||
{
|
||||
FILETIME fileTime;
|
||||
ULARGE_INTEGER fileTimeInt;
|
||||
if (!::SystemTimeToFileTime(&aSystemTime, &fileTime))
|
||||
return false;
|
||||
|
||||
fileTimeInt.LowPart = fileTime.dwLowDateTime;
|
||||
fileTimeInt.HighPart = fileTime.dwHighDateTime;
|
||||
|
||||
// FILETIME is in units of 100ns.
|
||||
fileTimeInt.QuadPart += aIncrementMS * 1000 * 10;
|
||||
|
||||
fileTime.dwLowDateTime = fileTimeInt.LowPart;
|
||||
fileTime.dwHighDateTime = fileTimeInt.HighPart;
|
||||
SYSTEMTIME tmpSystemTime;
|
||||
if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime))
|
||||
return false;
|
||||
|
||||
aSystemTime = tmpSystemTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
|
||||
// below minutes.
|
||||
static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2)
|
||||
{
|
||||
aSystemTime1.wSecond = 0;
|
||||
aSystemTime1.wMilliseconds = 0;
|
||||
|
||||
aSystemTime2.wSecond = 0;
|
||||
aSystemTime2.wMilliseconds = 0;
|
||||
|
||||
FILETIME fileTime1;
|
||||
FILETIME fileTime2;
|
||||
if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2))
|
||||
return false;
|
||||
|
||||
return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
|
||||
}
|
||||
|
||||
static std::wstring get_association_key_path(const wchar_t* aExt)
|
||||
{
|
||||
const wchar_t* keyPathFmt;
|
||||
if (aExt[0] == L'.')
|
||||
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s";
|
||||
else
|
||||
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s";
|
||||
|
||||
int keyPathLen = _scwprintf(keyPathFmt, aExt);
|
||||
keyPathLen += 1; // _scwprintf does not include the terminator
|
||||
|
||||
std::wstring keyPath(keyPathLen, '\0');
|
||||
_snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt);
|
||||
return keyPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set an association with a UserChoice key
|
||||
*
|
||||
* Removes the old key, creates a new one with ProgID and Hash set to
|
||||
* enable a new asociation.
|
||||
*
|
||||
* @param aExt File type or protocol to associate
|
||||
* @param aProgID ProgID to use for the asociation
|
||||
*
|
||||
* @return true if successful, false on error.
|
||||
*/
|
||||
static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) {
|
||||
|
||||
const std::wstring aSid = get_current_user_string_sid();
|
||||
if (aSid.empty())
|
||||
return false;
|
||||
|
||||
SYSTEMTIME hashTimestamp;
|
||||
::GetSystemTime(&hashTimestamp);
|
||||
std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
|
||||
if (hash.empty())
|
||||
return false;
|
||||
|
||||
// The hash changes at the end of each minute, so check that the hash should
|
||||
// be the same by the time we're done writing.
|
||||
const ULONGLONG kWriteTimingThresholdMilliseconds = 100;
|
||||
// Generating the hash could have taken some time, so start from now.
|
||||
SYSTEMTIME writeEndTimestamp;
|
||||
::GetSystemTime(&writeEndTimestamp);
|
||||
if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds))
|
||||
return false;
|
||||
|
||||
if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) {
|
||||
::Sleep(kWriteTimingThresholdMilliseconds * 2);
|
||||
|
||||
// For consistency, use the current time.
|
||||
::GetSystemTime(&hashTimestamp);
|
||||
hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
|
||||
if (hash.empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring assocKeyPath = get_association_key_path(aExt);
|
||||
if (assocKeyPath.empty())
|
||||
return false;
|
||||
|
||||
LSTATUS ls;
|
||||
HKEY rawAssocKey;
|
||||
ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey);
|
||||
if (ls != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
AutoRegKey assocKey(rawAssocKey);
|
||||
|
||||
HKEY currUserChoiceKey;
|
||||
ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey);
|
||||
if (ls == ERROR_SUCCESS) {
|
||||
::RegCloseKey(currUserChoiceKey);
|
||||
// When Windows creates this key, it is read-only (Deny Set Value), so we need
|
||||
// to delete it first.
|
||||
// We don't set any similar special permissions.
|
||||
ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice");
|
||||
if (ls != ERROR_SUCCESS)
|
||||
return false;
|
||||
}
|
||||
|
||||
HKEY rawUserChoiceKey;
|
||||
ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr,
|
||||
0 /* options */, KEY_READ | KEY_WRITE,
|
||||
0 /* security attributes */, &rawUserChoiceKey,
|
||||
nullptr);
|
||||
if (ls != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
AutoRegKey userChoiceKey(rawUserChoiceKey);
|
||||
DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t);
|
||||
ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast<const unsigned char*>(aProgID), progIdByteCount);
|
||||
if (ls != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t);
|
||||
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast<const unsigned char*>(hash.data()), hashByteCount);
|
||||
if (ls != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id)
|
||||
{
|
||||
const std::wstring reg_extension = get_association_key_path(extension.c_str());
|
||||
if (reg_extension.empty())
|
||||
return false;
|
||||
|
||||
bool needs_update = true;
|
||||
bool modified = false;
|
||||
HKEY rawAssocKey = nullptr;
|
||||
LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey);
|
||||
AutoRegKey assoc_key(rawAssocKey);
|
||||
if (res == ERROR_SUCCESS) {
|
||||
DWORD data_size_bytes = 0;
|
||||
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes);
|
||||
if (res == ERROR_SUCCESS) {
|
||||
// +1 in case dataSizeBytes was odd, +1 to ensure termination
|
||||
DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2;
|
||||
std::wstring curr_prog_id(data_size_chars, L'\0');
|
||||
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes);
|
||||
if (res == ERROR_SUCCESS) {
|
||||
const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0');
|
||||
if (pos != std::wstring::npos)
|
||||
curr_prog_id = curr_prog_id.substr(0, pos);
|
||||
needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_update)
|
||||
modified = set_user_choice(extension.c_str(), prog_id.c_str());
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default)
|
||||
{
|
||||
assert(!extension.empty() && extension.front() == L'.');
|
||||
|
||||
const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension;
|
||||
const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id;
|
||||
const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command";
|
||||
|
||||
wchar_t app_path[1040];
|
||||
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
|
||||
const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\"";
|
||||
|
||||
bool modified = false;
|
||||
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
|
||||
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
|
||||
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
|
||||
if (set_as_default)
|
||||
modified |= set_as_default_per_file_type(extension, prog_id);
|
||||
|
||||
// notify Windows only when any of the values gets changed
|
||||
if (modified)
|
||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _WIN32
|
23
src/slic3r/Utils/WinRegistry.hpp
Normal file
23
src/slic3r/Utils/WinRegistry.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef slic3r_Utils_WinRegistry_hpp_
|
||||
#define slic3r_Utils_WinRegistry_hpp_
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'.
|
||||
// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'.
|
||||
// The file type registration implementation is based on code taken from:
|
||||
// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
|
||||
// The set as default implementation is based on code taken from:
|
||||
// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874
|
||||
// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp
|
||||
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
#endif // slic3r_Utils_WinRegistry_hpp_
|
@ -4,10 +4,9 @@
|
||||
#include <thread>
|
||||
|
||||
#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
|
||||
#include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
|
||||
#include "slic3r/GUI/Jobs/ProgressIndicator.hpp"
|
||||
|
||||
//#include <boost/thread/thread.hpp>
|
||||
|
||||
struct Progress: Slic3r::ProgressIndicator {
|
||||
int range = 100;
|
||||
int pr = 0;
|
||||
@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator {
|
||||
int get_range() const override { return range; }
|
||||
};
|
||||
|
||||
TEST_CASE("nullptr job should be ignored", "[Jobs]") {
|
||||
Slic3r::GUI::BoostThreadWorker worker{std::make_unique<Progress>()};
|
||||
using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >;
|
||||
|
||||
TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) {
|
||||
TestType worker{std::make_unique<Progress>()};
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
|
||||
worker.wait_for_current_job();
|
||||
worker.process_events();
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
||||
|
||||
TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) {
|
||||
TestType worker{std::make_unique<Progress>()};
|
||||
worker.push(nullptr);
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
||||
|
||||
TEST_CASE("State should not be idle while running a job", "[Jobs]") {
|
||||
TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
BoostThreadWorker worker{std::make_unique<Progress>(), "worker_thread"};
|
||||
TestType worker{std::make_unique<Progress>(), "worker_thread"};
|
||||
|
||||
queue_job(worker, [&worker](Job::Ctl &ctl) {
|
||||
ctl.call_on_main_thread([&worker] {
|
||||
@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") {
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
||||
|
||||
TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") {
|
||||
TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
TestType worker{pri};
|
||||
|
||||
queue_job(worker, [](Job::Ctl &ctl){
|
||||
for (int s = 0; s <= 100; ++s) {
|
||||
@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec
|
||||
REQUIRE(pri->statustxt == "Running");
|
||||
}
|
||||
|
||||
TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") {
|
||||
TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
TestType worker{pri};
|
||||
|
||||
queue_job(
|
||||
worker,
|
||||
@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") {
|
||||
REQUIRE(pri->pr != 100);
|
||||
}
|
||||
|
||||
TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
|
||||
TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
TestType worker{pri};
|
||||
|
||||
std::array<bool, 4> jobres = {false, false, false, false};
|
||||
|
||||
@ -125,12 +137,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
|
||||
REQUIRE(jobres[3] == false);
|
||||
}
|
||||
|
||||
TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") {
|
||||
TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) {
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
auto pri = std::make_shared<Progress>();
|
||||
BoostThreadWorker worker{pri};
|
||||
TestType worker{pri};
|
||||
|
||||
queue_job(
|
||||
worker, [](Job::Ctl &) { throw std::runtime_error("test"); },
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
set(SLIC3R_APP_NAME "PrusaSlicer")
|
||||
set(SLIC3R_APP_KEY "PrusaSlicer")
|
||||
set(SLIC3R_VERSION "2.5.0-alpha0")
|
||||
set(SLIC3R_VERSION "2.6.0-alpha0")
|
||||
set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
|
||||
set(SLIC3R_RC_VERSION "2,5,0,0")
|
||||
set(SLIC3R_RC_VERSION_DOTS "2.5.0.0")
|
||||
set(SLIC3R_RC_VERSION "2,6,0,0")
|
||||
set(SLIC3R_RC_VERSION_DOTS "2.6.0.0")
|
||||
|
Loading…
x
Reference in New Issue
Block a user