Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLukas Matena <lukasmatena@seznam.cz>2019-12-12 13:37:33 +0300
committerLukas Matena <lukasmatena@seznam.cz>2019-12-12 13:37:33 +0300
commit537260494df5736b5a36677b86cf04c364fbf831 (patch)
tree838db423c8bc7f4d3594267398c6ad2b2c591d89 /src
parent135660decfd2cdb6d8b26059c05fa084e399bff2 (diff)
parentc284a65caaa6d859164c33f858512d7d8d3d0f5c (diff)
Merge branch 'master' into lm_tm_hollowing
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt14
-rw-r--r--src/admesh/connect.cpp9
-rw-r--r--src/hidapi/CMakeLists.txt19
-rw-r--r--src/hidapi/include/hidapi.h395
-rw-r--r--src/hidapi/linux/hid.c918
-rw-r--r--src/hidapi/mac/hid.c1121
-rw-r--r--src/hidapi/win/hid.c956
-rw-r--r--src/libslic3r/Fill/Fill3DHoneycomb.cpp2
-rw-r--r--src/libslic3r/Fill/FillBase.cpp158
-rw-r--r--src/libslic3r/Fill/FillBase.hpp4
-rw-r--r--src/libslic3r/Fill/FillGyroid.cpp3
-rw-r--r--src/libslic3r/Format/3mf.cpp107
-rw-r--r--src/libslic3r/Format/AMF.cpp72
-rw-r--r--src/libslic3r/GCode.cpp356
-rw-r--r--src/libslic3r/GCode.hpp19
-rw-r--r--src/libslic3r/GCode/Analyzer.cpp73
-rw-r--r--src/libslic3r/GCode/Analyzer.hpp24
-rw-r--r--src/libslic3r/GCode/PreviewData.cpp39
-rw-r--r--src/libslic3r/GCode/PreviewData.hpp2
-rw-r--r--src/libslic3r/GCode/ThumbnailData.hpp4
-rw-r--r--src/libslic3r/GCode/WipeTower.cpp7
-rw-r--r--src/libslic3r/GCode/WipeTower.hpp5
-rw-r--r--src/libslic3r/GCodeReader.hpp1
-rw-r--r--src/libslic3r/GCodeTimeEstimator.hpp2
-rw-r--r--src/libslic3r/Geometry.hpp73
-rw-r--r--src/libslic3r/Line.cpp11
-rw-r--r--src/libslic3r/Line.hpp3
-rw-r--r--src/libslic3r/Model.cpp22
-rw-r--r--src/libslic3r/Model.hpp34
-rw-r--r--src/libslic3r/MotionPlanner.cpp2
-rw-r--r--src/libslic3r/MutablePriorityQueue.hpp68
-rw-r--r--src/libslic3r/Print.cpp95
-rw-r--r--src/libslic3r/Print.hpp14
-rw-r--r--src/libslic3r/PrintConfig.hpp6
-rw-r--r--src/libslic3r/PrintObject.cpp6
-rw-r--r--src/libslic3r/Semver.hpp1
-rw-r--r--src/libslic3r/ShortestPath.cpp1291
-rw-r--r--src/libslic3r/Slicing.cpp141
-rw-r--r--src/libslic3r/Slicing.hpp24
-rw-r--r--src/libslic3r/SlicingAdaptive.cpp89
-rw-r--r--src/libslic3r/SlicingAdaptive.hpp36
-rw-r--r--src/libslic3r/Technologies.hpp17
-rw-r--r--src/libslic3r/TriangleMesh.hpp23
-rw-r--r--src/slic3r/CMakeLists.txt8
-rw-r--r--src/slic3r/Config/Version.cpp9
-rw-r--r--src/slic3r/Config/Version.hpp2
-rw-r--r--src/slic3r/GUI/3DBed.cpp10
-rw-r--r--src/slic3r/GUI/3DBed.hpp2
-rw-r--r--src/slic3r/GUI/3DScene.hpp2
-rw-r--r--src/slic3r/GUI/AppConfig.cpp76
-rw-r--r--src/slic3r/GUI/AppConfig.hpp7
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.cpp32
-rw-r--r--src/slic3r/GUI/BackgroundSlicingProcess.hpp14
-rw-r--r--src/slic3r/GUI/Camera.cpp89
-rw-r--r--src/slic3r/GUI/Camera.hpp8
-rw-r--r--src/slic3r/GUI/ConfigManipulation.cpp2
-rw-r--r--src/slic3r/GUI/ConfigWizard.cpp310
-rw-r--r--src/slic3r/GUI/ConfigWizard_private.hpp37
-rw-r--r--src/slic3r/GUI/ExtruderSequenceDialog.cpp235
-rw-r--r--src/slic3r/GUI/ExtruderSequenceDialog.hpp45
-rw-r--r--src/slic3r/GUI/GLCanvas3D.cpp698
-rw-r--r--src/slic3r/GUI/GLCanvas3D.hpp86
-rw-r--r--src/slic3r/GUI/GLShader.cpp4
-rw-r--r--src/slic3r/GUI/GLToolbar.cpp17
-rw-r--r--src/slic3r/GUI/GLToolbar.hpp3
-rw-r--r--src/slic3r/GUI/GUI_App.cpp64
-rw-r--r--src/slic3r/GUI/GUI_App.hpp4
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.cpp159
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.hpp3
-rw-r--r--src/slic3r/GUI/GUI_ObjectManipulation.cpp2
-rw-r--r--src/slic3r/GUI/GUI_Preview.cpp114
-rw-r--r--src/slic3r/GUI/GUI_Preview.hpp12
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoCut.cpp13
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.cpp26
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoMove.hpp6
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp23
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp8
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.cpp20
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoScale.hpp3
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp19
-rw-r--r--src/slic3r/GUI/Gizmos/GLGizmosManager.cpp22
-rw-r--r--src/slic3r/GUI/ImGuiWrapper.cpp30
-rw-r--r--src/slic3r/GUI/ImGuiWrapper.hpp3
-rw-r--r--src/slic3r/GUI/KBShortcutsDialog.cpp1
-rw-r--r--src/slic3r/GUI/MainFrame.cpp3
-rw-r--r--src/slic3r/GUI/MeshUtils.cpp1
-rw-r--r--src/slic3r/GUI/MeshUtils.hpp52
-rw-r--r--src/slic3r/GUI/Mouse3DController.cpp816
-rw-r--r--src/slic3r/GUI/Mouse3DController.hpp175
-rw-r--r--src/slic3r/GUI/Plater.cpp325
-rw-r--r--src/slic3r/GUI/Plater.hpp8
-rw-r--r--src/slic3r/GUI/Preset.cpp145
-rw-r--r--src/slic3r/GUI/Preset.hpp39
-rw-r--r--src/slic3r/GUI/PresetBundle.cpp155
-rw-r--r--src/slic3r/GUI/PresetBundle.hpp2
-rw-r--r--src/slic3r/GUI/PresetHints.cpp4
-rw-r--r--src/slic3r/GUI/Tab.cpp23
-rw-r--r--src/slic3r/GUI/UpdateDialogs.cpp4
-rw-r--r--src/slic3r/GUI/wxExtensions.cpp720
-rw-r--r--src/slic3r/GUI/wxExtensions.hpp131
-rw-r--r--src/slic3r/Utils/FixModelByWin10.cpp2
-rw-r--r--src/slic3r/Utils/PresetUpdater.cpp159
-rw-r--r--src/slic3r/Utils/PresetUpdater.hpp5
103 files changed, 9887 insertions, 1376 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1bf278722..54ebf33b9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,4 @@
+cmake_minimum_required(VERSION 3.8)
project(PrusaSlicer-native)
add_subdirectory(build-utils)
@@ -22,6 +23,8 @@ add_subdirectory(libslic3r)
if (SLIC3R_GUI)
add_subdirectory(imgui)
+ add_subdirectory(hidapi)
+ include_directories(hidapi/include)
if(WIN32)
message(STATUS "WXWIN environment set to: $ENV{WXWIN}")
@@ -131,13 +134,13 @@ target_link_libraries(PrusaSlicer libslic3r_gui ${wxWidgets_LIBRARIES})
if (MSVC)
# Generate debug symbols even in release mode.
target_link_options(PrusaSlicer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
- target_link_libraries(PrusaSlicer user32.lib Setupapi.lib OpenGL32.Lib GlU32.Lib)
+ target_link_libraries(PrusaSlicer user32.lib Setupapi.lib)
elseif (MINGW)
- target_link_libraries(PrusaSlicer opengl32 ws2_32 uxtheme setupapi)
+ target_link_libraries(PrusaSlicer ws2_32 uxtheme setupapi)
elseif (APPLE)
target_link_libraries(PrusaSlicer "-framework OpenGL")
else ()
- target_link_libraries(PrusaSlicer -ldl -lGL -lGLU)
+ target_link_libraries(PrusaSlicer -ldl)
endif ()
endif ()
@@ -193,6 +196,11 @@ if (WIN32)
VERBATIM
)
endif ()
+
+ # This has to be a separate target due to the windows command line lenght limits
+ add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer)
+ prusaslicer_copy_dlls(PrusaSlicerDllsCopy)
+
elseif (XCODE)
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
add_custom_command(TARGET PrusaSlicer POST_BUILD
diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp
index d6de6ce6a..c266c724c 100644
--- a/src/admesh/connect.cpp
+++ b/src/admesh/connect.cpp
@@ -601,11 +601,12 @@ void stl_remove_unconnected_facets(stl_file *stl)
stl->neighbors_start[facet].which_vertex_not[edge[1]],
stl->neighbors_start[facet].which_vertex_not[edge[2]]
};
+
// Update statistics on edge connectivity.
- if (neighbor[0] == -1)
- stl_update_connects_remove_1(neighbor[1]);
- if (neighbor[1] == -1)
- stl_update_connects_remove_1(neighbor[0]);
+ if ((neighbor[0] == -1) && (neighbor[1] != -1))
+ stl_update_connects_remove_1(neighbor[1]);
+ if ((neighbor[1] == -1) && (neighbor[0] != -1))
+ stl_update_connects_remove_1(neighbor[0]);
if (neighbor[0] >= 0) {
if (neighbor[1] >= 0) {
diff --git a/src/hidapi/CMakeLists.txt b/src/hidapi/CMakeLists.txt
new file mode 100644
index 000000000..1f53c9b69
--- /dev/null
+++ b/src/hidapi/CMakeLists.txt
@@ -0,0 +1,19 @@
+
+if (WIN32)
+ set(HIDAPI_IMPL win/hid.c)
+elseif (APPLE)
+ set(HIDAPI_IMPL mac/hid.c)
+else ()
+ # Assume Linux or Unix other than Mac OS
+ set(HIDAPI_IMPL linux/hid.c)
+endif()
+
+include_directories(include)
+
+add_library(hidapi STATIC ${HIDAPI_IMPL})
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ # Don't link the udev library, as there are two versions out there (libudev.so.0, libudev.so.1), so they are linked explicitely.
+# target_link_libraries(hidapi udev)
+ target_link_libraries(hidapi)
+endif()
diff --git a/src/hidapi/include/hidapi.h b/src/hidapi/include/hidapi.h
new file mode 100644
index 000000000..1819f8de0
--- /dev/null
+++ b/src/hidapi/include/hidapi.h
@@ -0,0 +1,395 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 8/22/2009
+
+ Copyright 2009, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ http://github.com/signal11/hidapi .
+********************************************************/
+
+/** @file
+ * @defgroup API hidapi API
+ */
+
+#ifndef HIDAPI_H__
+#define HIDAPI_H__
+
+#include <wchar.h>
+
+#ifdef _WIN32
+ #define HID_API_EXPORT __declspec(dllexport)
+ #define HID_API_CALL
+#else
+ #define HID_API_EXPORT /**< API export macro */
+ #define HID_API_CALL /**< API call macro */
+#endif
+
+#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ struct hid_device_;
+ typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
+
+ /** hidapi info structure */
+ struct hid_device_info {
+ /** Platform-specific device path */
+ char *path;
+ /** Device Vendor ID */
+ unsigned short vendor_id;
+ /** Device Product ID */
+ unsigned short product_id;
+ /** Serial Number */
+ wchar_t *serial_number;
+ /** Device Release Number in binary-coded decimal,
+ also known as Device Version Number */
+ unsigned short release_number;
+ /** Manufacturer String */
+ wchar_t *manufacturer_string;
+ /** Product string */
+ wchar_t *product_string;
+ /** Usage Page for this Device/Interface
+ (Windows/Mac only). */
+ unsigned short usage_page;
+ /** Usage for this Device/Interface
+ (Windows/Mac only).*/
+ unsigned short usage;
+ /** The USB interface which this logical device
+ represents.
+
+ * Valid on both Linux implementations in all cases.
+ * Valid on the Windows implementation only if the device
+ contains more than one interface.
+ * Valid on the Mac implementation if and only if the device
+ is a USB HID device. */
+ int interface_number;
+
+ /** Pointer to the next device */
+ struct hid_device_info *next;
+ };
+
+
+ /** @brief Initialize the HIDAPI library.
+
+ This function initializes the HIDAPI library. Calling it is not
+ strictly necessary, as it will be called automatically by
+ hid_enumerate() and any of the hid_open_*() functions if it is
+ needed. This function should be called at the beginning of
+ execution however, if there is a chance of HIDAPI handles
+ being opened by different threads simultaneously.
+
+ @ingroup API
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_init(void);
+
+ /** @brief Finalize the HIDAPI library.
+
+ This function frees all of the static data associated with
+ HIDAPI. It should be called at the end of execution to avoid
+ memory leaks.
+
+ @ingroup API
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_exit(void);
+
+ /** @brief Enumerate the HID Devices.
+
+ This function returns a linked list of all the HID devices
+ attached to the system which match vendor_id and product_id.
+ If @p vendor_id is set to 0 then any vendor matches.
+ If @p product_id is set to 0 then any product matches.
+ If @p vendor_id and @p product_id are both set to 0, then
+ all HID devices will be returned.
+
+ @ingroup API
+ @param vendor_id The Vendor ID (VID) of the types of device
+ to open.
+ @param product_id The Product ID (PID) of the types of
+ device to open.
+
+ @returns
+ This function returns a pointer to a linked list of type
+ struct #hid_device_info, containing information about the HID devices
+ attached to the system, or NULL in the case of failure. Free
+ this linked list by calling hid_free_enumeration().
+ */
+ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
+
+ /** @brief Free an enumeration Linked List
+
+ This function frees a linked list created by hid_enumerate().
+
+ @ingroup API
+ @param devs Pointer to a list of struct_device returned from
+ hid_enumerate().
+ */
+ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
+
+ /** @brief Open a HID device using a Vendor ID (VID), Product ID
+ (PID) and optionally a serial number.
+
+ If @p serial_number is NULL, the first device with the
+ specified VID and PID is opened.
+
+ @ingroup API
+ @param vendor_id The Vendor ID (VID) of the device to open.
+ @param product_id The Product ID (PID) of the device to open.
+ @param serial_number The Serial Number of the device to open
+ (Optionally NULL).
+
+ @returns
+ This function returns a pointer to a #hid_device object on
+ success or NULL on failure.
+ */
+ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
+
+ /** @brief Open a HID device by its path name.
+
+ The path name be determined by calling hid_enumerate(), or a
+ platform-specific path name can be used (eg: /dev/hidraw0 on
+ Linux).
+
+ @ingroup API
+ @param path The path name of the device to open
+
+ @returns
+ This function returns a pointer to a #hid_device object on
+ success or NULL on failure.
+ */
+ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
+
+ /** @brief Write an Output report to a HID device.
+
+ The first byte of @p data[] must contain the Report ID. For
+ devices which only support a single report, this must be set
+ to 0x0. The remaining bytes contain the report data. Since
+ the Report ID is mandatory, calls to hid_write() will always
+ contain one more byte than the report contains. For example,
+ if a hid report is 16 bytes long, 17 bytes must be passed to
+ hid_write(), the Report ID (or 0x0, for devices with a
+ single report), followed by the report data (16 bytes). In
+ this example, the length passed in would be 17.
+
+ hid_write() will send the data on the first OUT endpoint, if
+ one exists. If it does not, it will send the data through
+ the Control Endpoint (Endpoint 0).
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data The data to send, including the report number as
+ the first byte.
+ @param length The length in bytes of the data to send.
+
+ @returns
+ This function returns the actual number of bytes written and
+ -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
+
+ /** @brief Read an Input report from a HID device with timeout.
+
+ Input reports are returned
+ to the host through the INTERRUPT IN endpoint. The first byte will
+ contain the Report number if the device uses numbered reports.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data A buffer to put the read data into.
+ @param length The number of bytes to read. For devices with
+ multiple reports, make sure to read an extra byte for
+ the report number.
+ @param milliseconds timeout in milliseconds or -1 for blocking wait.
+
+ @returns
+ This function returns the actual number of bytes read and
+ -1 on error. If no packet was available to be read within
+ the timeout period, this function returns 0.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
+
+ /** @brief Read an Input report from a HID device.
+
+ Input reports are returned
+ to the host through the INTERRUPT IN endpoint. The first byte will
+ contain the Report number if the device uses numbered reports.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data A buffer to put the read data into.
+ @param length The number of bytes to read. For devices with
+ multiple reports, make sure to read an extra byte for
+ the report number.
+
+ @returns
+ This function returns the actual number of bytes read and
+ -1 on error. If no packet was available to be read and
+ the handle is in non-blocking mode, this function returns 0.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length);
+
+ /** @brief Set the device handle to be non-blocking.
+
+ In non-blocking mode calls to hid_read() will return
+ immediately with a value of 0 if there is no data to be
+ read. In blocking mode, hid_read() will wait (block) until
+ there is data to read before returning.
+
+ Nonblocking can be turned on and off at any time.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param nonblock enable or not the nonblocking reads
+ - 1 to enable nonblocking
+ - 0 to disable nonblocking.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock);
+
+ /** @brief Send a Feature report to the device.
+
+ Feature reports are sent over the Control endpoint as a
+ Set_Report transfer. The first byte of @p data[] must
+ contain the Report ID. For devices which only support a
+ single report, this must be set to 0x0. The remaining bytes
+ contain the report data. Since the Report ID is mandatory,
+ calls to hid_send_feature_report() will always contain one
+ more byte than the report contains. For example, if a hid
+ report is 16 bytes long, 17 bytes must be passed to
+ hid_send_feature_report(): the Report ID (or 0x0, for
+ devices which do not use numbered reports), followed by the
+ report data (16 bytes). In this example, the length passed
+ in would be 17.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data The data to send, including the report number as
+ the first byte.
+ @param length The length in bytes of the data to send, including
+ the report number.
+
+ @returns
+ This function returns the actual number of bytes written and
+ -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
+
+ /** @brief Get a feature report from a HID device.
+
+ Set the first byte of @p data[] to the Report ID of the
+ report to be read. Make sure to allow space for this
+ extra byte in @p data[]. Upon return, the first byte will
+ still contain the Report ID, and the report data will
+ start in data[1].
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data A buffer to put the read data into, including
+ the Report ID. Set the first byte of @p data[] to the
+ Report ID of the report to be read, or set it to zero
+ if your device does not use numbered reports.
+ @param length The number of bytes to read, including an
+ extra byte for the report ID. The buffer can be longer
+ than the actual report.
+
+ @returns
+ This function returns the number of bytes read plus
+ one for the report ID (which is still in the first
+ byte), or -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
+
+ /** @brief Close a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ */
+ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev);
+
+ /** @brief Get The Manufacturer String from a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen);
+
+ /** @brief Get The Product String from a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen);
+
+ /** @brief Get The Serial Number String from a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen);
+
+ /** @brief Get a string from a HID device, based on its string index.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string_index The index of the string to get.
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen);
+
+ /** @brief Get a string describing the last error which occurred.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+
+ @returns
+ This function returns a string containing the last error
+ which occurred or NULL if none has occurred.
+ */
+ HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/src/hidapi/linux/hid.c b/src/hidapi/linux/hid.c
new file mode 100644
index 000000000..a62bcffb6
--- /dev/null
+++ b/src/hidapi/linux/hid.c
@@ -0,0 +1,918 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 8/22/2009
+ Linux Version - 6/2/2009
+
+ Copyright 2009, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ http://github.com/signal11/hidapi .
+********************************************************/
+
+/* C */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <errno.h>
+
+/* Unix */
+#include <dlfcn.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/utsname.h>
+#include <fcntl.h>
+#include <poll.h>
+
+/* Linux */
+#include <linux/hidraw.h>
+#include <linux/version.h>
+#include <linux/input.h>
+#include <libudev.h>
+
+#include "hidapi.h"
+
+typedef const char* (*hid_wrapper_udev_device_get_devnode_type)(struct udev_device *udev_device);
+typedef struct udev_device* (*hid_wrapper_udev_device_get_parent_with_subsystem_devtype_type)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
+typedef const char* (*hid_wrapper_udev_device_get_sysattr_value_type)(struct udev_device *udev_device, const char *sysattr);
+typedef struct udev_device* (*hid_wrapper_udev_device_new_from_devnum_type)(struct udev *udev, char type, dev_t devnum);
+typedef struct udev_device* (*hid_wrapper_udev_device_new_from_syspath_type)(struct udev *udev, const char *syspath);
+typedef struct udev_device* (*hid_wrapper_udev_device_unref_type)(struct udev_device *udev_device);
+typedef int (*hid_wrapper_udev_enumerate_add_match_subsystem_type)(struct udev_enumerate *udev_enumerate, const char *subsystem);
+typedef struct udev_list_entry* (*hid_wrapper_udev_enumerate_get_list_entry_type)(struct udev_enumerate *udev_enumerate);
+typedef struct udev_enumerate* (*hid_wrapper_udev_enumerate_new_type)(struct udev *udev);
+typedef int (*hid_wrapper_udev_enumerate_scan_devices_type)(struct udev_enumerate *udev_enumerate);
+typedef struct udev_enumerate* (*hid_wrapper_udev_enumerate_unref_type)(struct udev_enumerate *udev_enumerate);
+typedef const char* (*hid_wrapper_udev_list_entry_get_name_type)(struct udev_list_entry *list_entry);
+typedef struct udev_list_entry* (*hid_wrapper_udev_list_entry_get_next_type)(struct udev_list_entry *list_entry);
+typedef struct udev* (*hid_wrapper_udev_new_type)(void);
+typedef struct udev* (*hid_wrapper_udev_unref_type)(struct udev *udev);
+
+void *hid_wrapper_handle = NULL;
+static hid_wrapper_udev_device_get_devnode_type hid_wrapper_udev_device_get_devnode = NULL;
+static hid_wrapper_udev_device_get_parent_with_subsystem_devtype_type hid_wrapper_udev_device_get_parent_with_subsystem_devtype = NULL;
+static hid_wrapper_udev_device_get_sysattr_value_type hid_wrapper_udev_device_get_sysattr_value = NULL;
+static hid_wrapper_udev_device_new_from_devnum_type hid_wrapper_udev_device_new_from_devnum = NULL;
+static hid_wrapper_udev_device_new_from_syspath_type hid_wrapper_udev_device_new_from_syspath = NULL;
+static hid_wrapper_udev_device_unref_type hid_wrapper_udev_device_unref = NULL;
+static hid_wrapper_udev_enumerate_add_match_subsystem_type hid_wrapper_udev_enumerate_add_match_subsystem = NULL;
+static hid_wrapper_udev_enumerate_get_list_entry_type hid_wrapper_udev_enumerate_get_list_entry = NULL;
+static hid_wrapper_udev_enumerate_new_type hid_wrapper_udev_enumerate_new = NULL;
+static hid_wrapper_udev_enumerate_scan_devices_type hid_wrapper_udev_enumerate_scan_devices = NULL;
+static hid_wrapper_udev_enumerate_unref_type hid_wrapper_udev_enumerate_unref = NULL;
+static hid_wrapper_udev_list_entry_get_name_type hid_wrapper_udev_list_entry_get_name = NULL;
+static hid_wrapper_udev_list_entry_get_next_type hid_wrapper_udev_list_entry_get_next = NULL;
+static hid_wrapper_udev_new_type hid_wrapper_udev_new = NULL;
+static hid_wrapper_udev_unref_type hid_wrapper_udev_unref = NULL;
+
+static void hid_wrapper_udev_close()
+{
+ if (hid_wrapper_handle)
+ dlclose(hid_wrapper_handle);
+ hid_wrapper_handle = NULL;
+ hid_wrapper_udev_device_get_devnode = NULL;
+ hid_wrapper_udev_device_get_parent_with_subsystem_devtype = NULL;
+ hid_wrapper_udev_device_get_sysattr_value = NULL;
+ hid_wrapper_udev_device_new_from_devnum = NULL;
+ hid_wrapper_udev_device_new_from_syspath = NULL;
+ hid_wrapper_udev_device_unref = NULL;
+ hid_wrapper_udev_enumerate_add_match_subsystem = NULL;
+ hid_wrapper_udev_enumerate_get_list_entry = NULL;
+ hid_wrapper_udev_enumerate_new = NULL;
+ hid_wrapper_udev_enumerate_scan_devices = NULL;
+ hid_wrapper_udev_enumerate_unref = NULL;
+ hid_wrapper_udev_list_entry_get_name = NULL;
+ hid_wrapper_udev_list_entry_get_next = NULL;
+ hid_wrapper_udev_new = NULL;
+ hid_wrapper_udev_unref = NULL;
+}
+
+static const char *hid_wrapper_libudev_paths[] = {
+ "libudev.so.1", "libudev.so.0"
+};
+
+static int hid_wrapper_udev_init()
+{
+ int i;
+
+ hid_wrapper_udev_close();
+
+ // Search for the libudev0 or libudev1 library.
+ for (i = 0; i < sizeof(hid_wrapper_libudev_paths) / sizeof(hid_wrapper_libudev_paths[0]); ++ i)
+ if ((hid_wrapper_handle = dlopen(hid_wrapper_libudev_paths[i], RTLD_NOW | RTLD_GLOBAL)) != NULL)
+ break;
+
+ if (hid_wrapper_handle == NULL) {
+ // Error, close the shared library handle and finish.
+ hid_wrapper_udev_close();
+ return -1;
+ }
+
+ // Resolve the functions.
+ hid_wrapper_udev_device_get_devnode = (hid_wrapper_udev_device_get_devnode_type) dlsym(hid_wrapper_handle, "udev_device_get_devnode");
+ hid_wrapper_udev_device_get_parent_with_subsystem_devtype = (hid_wrapper_udev_device_get_parent_with_subsystem_devtype_type) dlsym(hid_wrapper_handle, "udev_device_get_parent_with_subsystem_devtype");
+ hid_wrapper_udev_device_get_sysattr_value = (hid_wrapper_udev_device_get_sysattr_value_type) dlsym(hid_wrapper_handle, "udev_device_get_sysattr_value");
+ hid_wrapper_udev_device_new_from_devnum = (hid_wrapper_udev_device_new_from_devnum_type) dlsym(hid_wrapper_handle, "udev_device_new_from_devnum");
+ hid_wrapper_udev_device_new_from_syspath = (hid_wrapper_udev_device_new_from_syspath_type) dlsym(hid_wrapper_handle, "udev_device_new_from_syspath");
+ hid_wrapper_udev_device_unref = (hid_wrapper_udev_device_unref_type) dlsym(hid_wrapper_handle, "udev_device_unref");
+ hid_wrapper_udev_enumerate_add_match_subsystem = (hid_wrapper_udev_enumerate_add_match_subsystem_type) dlsym(hid_wrapper_handle, "udev_enumerate_add_match_subsystem");
+ hid_wrapper_udev_enumerate_get_list_entry = (hid_wrapper_udev_enumerate_get_list_entry_type) dlsym(hid_wrapper_handle, "udev_enumerate_get_list_entry");
+ hid_wrapper_udev_enumerate_new = (hid_wrapper_udev_enumerate_new_type) dlsym(hid_wrapper_handle, "udev_enumerate_new");
+ hid_wrapper_udev_enumerate_scan_devices = (hid_wrapper_udev_enumerate_scan_devices_type) dlsym(hid_wrapper_handle, "udev_enumerate_scan_devices");
+ hid_wrapper_udev_enumerate_unref = (hid_wrapper_udev_enumerate_unref_type) dlsym(hid_wrapper_handle, "udev_enumerate_unref");
+ hid_wrapper_udev_list_entry_get_name = (hid_wrapper_udev_list_entry_get_name_type) dlsym(hid_wrapper_handle, "udev_list_entry_get_name");
+ hid_wrapper_udev_list_entry_get_next = (hid_wrapper_udev_list_entry_get_next_type) dlsym(hid_wrapper_handle, "udev_list_entry_get_next");
+ hid_wrapper_udev_new = (hid_wrapper_udev_new_type) dlsym(hid_wrapper_handle, "udev_new");
+ hid_wrapper_udev_unref = (hid_wrapper_udev_unref_type) dlsym(hid_wrapper_handle, "udev_unref");
+
+ // Were all the funcions resolved?
+ if (hid_wrapper_handle == NULL ||
+ hid_wrapper_udev_device_get_devnode == NULL ||
+ hid_wrapper_udev_device_get_parent_with_subsystem_devtype == NULL ||
+ hid_wrapper_udev_device_get_sysattr_value == NULL ||
+ hid_wrapper_udev_device_new_from_devnum == NULL ||
+ hid_wrapper_udev_device_new_from_syspath == NULL ||
+ hid_wrapper_udev_device_unref == NULL ||
+ hid_wrapper_udev_enumerate_add_match_subsystem == NULL ||
+ hid_wrapper_udev_enumerate_get_list_entry == NULL ||
+ hid_wrapper_udev_enumerate_new == NULL ||
+ hid_wrapper_udev_enumerate_scan_devices == NULL ||
+ hid_wrapper_udev_enumerate_unref == NULL ||
+ hid_wrapper_udev_list_entry_get_name == NULL ||
+ hid_wrapper_udev_list_entry_get_next == NULL ||
+ hid_wrapper_udev_new == NULL ||
+ hid_wrapper_udev_unref == NULL)
+ {
+ // Error, close the shared library handle and finish.
+ hid_wrapper_udev_close();
+ return -1;
+ }
+
+ // Success.
+ return 0;
+}
+
+/* Definitions from linux/hidraw.h. Since these are new, some distros
+ may not have header files which contain them. */
+#ifndef HIDIOCSFEATURE
+#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
+#endif
+#ifndef HIDIOCGFEATURE
+#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
+#endif
+
+
+/* USB HID device property names */
+const char *device_string_names[] = {
+ "manufacturer",
+ "product",
+ "serial",
+};
+
+/* Symbolic names for the properties above */
+enum device_string_id {
+ DEVICE_STRING_MANUFACTURER,
+ DEVICE_STRING_PRODUCT,
+ DEVICE_STRING_SERIAL,
+
+ DEVICE_STRING_COUNT,
+};
+
+struct hid_device_ {
+ int device_handle;
+ int blocking;
+ int uses_numbered_reports;
+};
+
+
+static __u32 kernel_version = 0;
+
+static __u32 detect_kernel_version(void)
+{
+ struct utsname name;
+ int major, minor, release;
+ int ret;
+
+ uname(&name);
+ ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release);
+ if (ret == 3) {
+ return KERNEL_VERSION(major, minor, release);
+ }
+
+ ret = sscanf(name.release, "%d.%d", &major, &minor);
+ if (ret == 2) {
+ return KERNEL_VERSION(major, minor, 0);
+ }
+
+ printf("Couldn't determine kernel version from version string \"%s\"\n", name.release);
+ return 0;
+}
+
+static hid_device *new_hid_device(void)
+{
+ hid_device *dev = calloc(1, sizeof(hid_device));
+ dev->device_handle = -1;
+ dev->blocking = 1;
+ dev->uses_numbered_reports = 0;
+
+ return dev;
+}
+
+
+/* The caller must free the returned string with free(). */
+static wchar_t *utf8_to_wchar_t(const char *utf8)
+{
+ wchar_t *ret = NULL;
+
+ if (utf8) {
+ size_t wlen = mbstowcs(NULL, utf8, 0);
+ if ((size_t) -1 == wlen) {
+ return wcsdup(L"");
+ }
+ ret = calloc(wlen+1, sizeof(wchar_t));
+ mbstowcs(ret, utf8, wlen+1);
+ ret[wlen] = 0x0000;
+ }
+
+ return ret;
+}
+
+/* Get an attribute value from a udev_device and return it as a whar_t
+ string. The returned string must be freed with free() when done.*/
+static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
+{
+ return utf8_to_wchar_t(hid_wrapper_udev_device_get_sysattr_value(dev, udev_name));
+}
+
+/* uses_numbered_reports() returns 1 if report_descriptor describes a device
+ which contains numbered reports. */
+static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
+ unsigned int i = 0;
+ int size_code;
+ int data_len, key_size;
+
+ while (i < size) {
+ int key = report_descriptor[i];
+
+ /* Check for the Report ID key */
+ if (key == 0x85/*Report ID*/) {
+ /* This device has a Report ID, which means it uses
+ numbered reports. */
+ return 1;
+ }
+
+ //printf("key: %02hhx\n", key);
+
+ if ((key & 0xf0) == 0xf0) {
+ /* This is a Long Item. The next byte contains the
+ length of the data section (value) for this key.
+ See the HID specification, version 1.11, section
+ 6.2.2.3, titled "Long Items." */
+ if (i+1 < size)
+ data_len = report_descriptor[i+1];
+ else
+ data_len = 0; /* malformed report */
+ key_size = 3;
+ }
+ else {
+ /* This is a Short Item. The bottom two bits of the
+ key contain the size code for the data section
+ (value) for this key. Refer to the HID
+ specification, version 1.11, section 6.2.2.2,
+ titled "Short Items." */
+ size_code = key & 0x3;
+ switch (size_code) {
+ case 0:
+ case 1:
+ case 2:
+ data_len = size_code;
+ break;
+ case 3:
+ data_len = 4;
+ break;
+ default:
+ /* Can't ever happen since size_code is & 0x3 */
+ data_len = 0;
+ break;
+ };
+ key_size = 1;
+ }
+
+ /* Skip over this key and it's associated data */
+ i += data_len + key_size;
+ }
+
+ /* Didn't find a Report ID key. Device doesn't use numbered reports. */
+ return 0;
+}
+
+/*
+ * The caller is responsible for free()ing the (newly-allocated) character
+ * strings pointed to by serial_number_utf8 and product_name_utf8 after use.
+ */
+static int
+parse_uevent_info(const char *uevent, int *bus_type,
+ unsigned short *vendor_id, unsigned short *product_id,
+ char **serial_number_utf8, char **product_name_utf8)
+{
+ char *tmp = strdup(uevent);
+ char *saveptr = NULL;
+ char *line;
+ char *key;
+ char *value;
+
+ int found_id = 0;
+ int found_serial = 0;
+ int found_name = 0;
+
+ line = strtok_r(tmp, "\n", &saveptr);
+ while (line != NULL) {
+ /* line: "KEY=value" */
+ key = line;
+ value = strchr(line, '=');
+ if (!value) {
+ goto next_line;
+ }
+ *value = '\0';
+ value++;
+
+ if (strcmp(key, "HID_ID") == 0) {
+ /**
+ * type vendor product
+ * HID_ID=0003:000005AC:00008242
+ **/
+ int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id);
+ if (ret == 3) {
+ found_id = 1;
+ }
+ } else if (strcmp(key, "HID_NAME") == 0) {
+ /* The caller has to free the product name */
+ *product_name_utf8 = strdup(value);
+ found_name = 1;
+ } else if (strcmp(key, "HID_UNIQ") == 0) {
+ /* The caller has to free the serial number */
+ *serial_number_utf8 = strdup(value);
+ found_serial = 1;
+ }
+
+next_line:
+ line = strtok_r(NULL, "\n", &saveptr);
+ }
+
+ free(tmp);
+ return (found_id && found_name && found_serial);
+}
+
+
+static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen)
+{
+ struct udev *udev;
+ struct udev_device *udev_dev, *parent, *hid_dev;
+ struct stat s;
+ int ret = -1;
+ char *serial_number_utf8 = NULL;
+ char *product_name_utf8 = NULL;
+
+ /* Create the udev object */
+ udev = hid_wrapper_udev_new();
+ if (!udev) {
+ printf("Can't create udev\n");
+ return -1;
+ }
+
+ /* Get the dev_t (major/minor numbers) from the file handle. */
+ ret = fstat(dev->device_handle, &s);
+ if (-1 == ret)
+ return ret;
+ /* Open a udev device from the dev_t. 'c' means character device. */
+ udev_dev = hid_wrapper_udev_device_new_from_devnum(udev, 'c', s.st_rdev);
+ if (udev_dev) {
+ hid_dev = hid_wrapper_udev_device_get_parent_with_subsystem_devtype(
+ udev_dev,
+ "hid",
+ NULL);
+ if (hid_dev) {
+ unsigned short dev_vid;
+ unsigned short dev_pid;
+ int bus_type;
+ size_t retm;
+
+ ret = parse_uevent_info(
+ hid_wrapper_udev_device_get_sysattr_value(hid_dev, "uevent"),
+ &bus_type,
+ &dev_vid,
+ &dev_pid,
+ &serial_number_utf8,
+ &product_name_utf8);
+
+ if (bus_type == BUS_BLUETOOTH) {
+ switch (key) {
+ case DEVICE_STRING_MANUFACTURER:
+ wcsncpy(string, L"", maxlen);
+ ret = 0;
+ break;
+ case DEVICE_STRING_PRODUCT:
+ retm = mbstowcs(string, product_name_utf8, maxlen);
+ ret = (retm == (size_t)-1)? -1: 0;
+ break;
+ case DEVICE_STRING_SERIAL:
+ retm = mbstowcs(string, serial_number_utf8, maxlen);
+ ret = (retm == (size_t)-1)? -1: 0;
+ break;
+ case DEVICE_STRING_COUNT:
+ default:
+ ret = -1;
+ break;
+ }
+ }
+ else {
+ /* This is a USB device. Find its parent USB Device node. */
+ parent = hid_wrapper_udev_device_get_parent_with_subsystem_devtype(
+ udev_dev,
+ "usb",
+ "usb_device");
+ if (parent) {
+ const char *str;
+ const char *key_str = NULL;
+
+ if (key >= 0 && key < DEVICE_STRING_COUNT) {
+ key_str = device_string_names[key];
+ } else {
+ ret = -1;
+ goto end;
+ }
+
+ str = hid_wrapper_udev_device_get_sysattr_value(parent, key_str);
+ if (str) {
+ /* Convert the string from UTF-8 to wchar_t */
+ retm = mbstowcs(string, str, maxlen);
+ ret = (retm == (size_t)-1)? -1: 0;
+ goto end;
+ }
+ }
+ }
+ }
+ }
+
+end:
+ free(serial_number_utf8);
+ free(product_name_utf8);
+
+ hid_wrapper_udev_device_unref(udev_dev);
+ /* parent and hid_dev don't need to be (and can't be) unref'd.
+ I'm not sure why, but they'll throw double-free() errors. */
+ hid_wrapper_udev_unref(udev);
+
+ return ret;
+}
+
+int HID_API_EXPORT hid_init(void)
+{
+ const char *locale;
+
+ /* Set the locale if it's not set. */
+ locale = setlocale(LC_CTYPE, NULL);
+ if (!locale)
+ setlocale(LC_CTYPE, "");
+
+ kernel_version = detect_kernel_version();
+
+ return hid_wrapper_udev_init();
+}
+
+int HID_API_EXPORT hid_exit(void)
+{
+ /* Nothing to do for this in the Linux/hidraw implementation. */
+ hid_wrapper_udev_close();
+ return 0;
+}
+
+struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+ struct udev *udev;
+ struct udev_enumerate *enumerate;
+ struct udev_list_entry *devices, *dev_list_entry;
+
+ struct hid_device_info *root = NULL; /* return object */
+ struct hid_device_info *cur_dev = NULL;
+ struct hid_device_info *prev_dev = NULL; /* previous device */
+
+ hid_init();
+
+ /* Create the udev object */
+ udev = hid_wrapper_udev_new();
+ if (!udev) {
+ printf("Can't create udev\n");
+ return NULL;
+ }
+
+ /* Create a list of the devices in the 'hidraw' subsystem. */
+ enumerate = hid_wrapper_udev_enumerate_new(udev);
+ hid_wrapper_udev_enumerate_add_match_subsystem(enumerate, "hidraw");
+ hid_wrapper_udev_enumerate_scan_devices(enumerate);
+ devices = hid_wrapper_udev_enumerate_get_list_entry(enumerate);
+ /* For each item, see if it matches the vid/pid, and if so
+ create a udev_device record for it */
+ for (dev_list_entry = devices; dev_list_entry; dev_list_entry = hid_wrapper_udev_list_entry_get_next(dev_list_entry)) {
+ const char *sysfs_path;
+ const char *dev_path;
+ const char *str;
+ struct udev_device *raw_dev; /* The device's hidraw udev node. */
+ struct udev_device *hid_dev; /* The device's HID udev node. */
+ struct udev_device *usb_dev; /* The device's USB udev node. */
+ struct udev_device *intf_dev; /* The device's interface (in the USB sense). */
+ unsigned short dev_vid;
+ unsigned short dev_pid;
+ char *serial_number_utf8 = NULL;
+ char *product_name_utf8 = NULL;
+ int bus_type;
+ int result;
+
+ /* Get the filename of the /sys entry for the device
+ and create a udev_device object (dev) representing it */
+ sysfs_path = hid_wrapper_udev_list_entry_get_name(dev_list_entry);
+ raw_dev = hid_wrapper_udev_device_new_from_syspath(udev, sysfs_path);
+ dev_path = hid_wrapper_udev_device_get_devnode(raw_dev);
+
+ hid_dev = hid_wrapper_udev_device_get_parent_with_subsystem_devtype(
+ raw_dev,
+ "hid",
+ NULL);
+
+ if (!hid_dev) {
+ /* Unable to find parent hid device. */
+ goto next;
+ }
+
+ result = parse_uevent_info(
+ hid_wrapper_udev_device_get_sysattr_value(hid_dev, "uevent"),
+ &bus_type,
+ &dev_vid,
+ &dev_pid,
+ &serial_number_utf8,
+ &product_name_utf8);
+
+ if (!result) {
+ /* parse_uevent_info() failed for at least one field. */
+ goto next;
+ }
+
+ if (bus_type != BUS_USB && bus_type != BUS_BLUETOOTH) {
+ /* We only know how to handle USB and BT devices. */
+ goto next;
+ }
+
+ /* Check the VID/PID against the arguments */
+ if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
+ (product_id == 0x0 || product_id == dev_pid)) {
+ struct hid_device_info *tmp;
+
+ /* VID/PID match. Create the record. */
+ tmp = malloc(sizeof(struct hid_device_info));
+ if (cur_dev) {
+ cur_dev->next = tmp;
+ }
+ else {
+ root = tmp;
+ }
+ prev_dev = cur_dev;
+ cur_dev = tmp;
+
+ /* Fill out the record */
+ cur_dev->next = NULL;
+ cur_dev->path = dev_path? strdup(dev_path): NULL;
+
+ /* VID/PID */
+ cur_dev->vendor_id = dev_vid;
+ cur_dev->product_id = dev_pid;
+
+ /* Serial Number */
+ cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8);
+
+ /* Release Number */
+ cur_dev->release_number = 0x0;
+
+ /* Interface Number */
+ cur_dev->interface_number = -1;
+
+ switch (bus_type) {
+ case BUS_USB:
+ /* The device pointed to by raw_dev contains information about
+ the hidraw device. In order to get information about the
+ USB device, get the parent device with the
+ subsystem/devtype pair of "usb"/"usb_device". This will
+ be several levels up the tree, but the function will find
+ it. */
+ usb_dev = hid_wrapper_udev_device_get_parent_with_subsystem_devtype(
+ raw_dev,
+ "usb",
+ "usb_device");
+
+ if (!usb_dev) {
+ /* Free this device */
+ free(cur_dev->serial_number);
+ free(cur_dev->path);
+ free(cur_dev);
+
+ /* Take it off the device list. */
+ if (prev_dev) {
+ prev_dev->next = NULL;
+ cur_dev = prev_dev;
+ }
+ else {
+ cur_dev = root = NULL;
+ }
+
+ goto next;
+ }
+
+ /* Manufacturer and Product strings */
+ cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]);
+ cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]);
+
+ /* Release Number */
+ str = hid_wrapper_udev_device_get_sysattr_value(usb_dev, "bcdDevice");
+ cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0;
+
+ /* Get a handle to the interface's udev node. */
+ intf_dev = hid_wrapper_udev_device_get_parent_with_subsystem_devtype(
+ raw_dev,
+ "usb",
+ "usb_interface");
+ if (intf_dev) {
+ str = hid_wrapper_udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber");
+ cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1;
+ }
+
+ break;
+
+ case BUS_BLUETOOTH:
+ /* Manufacturer and Product strings */
+ cur_dev->manufacturer_string = wcsdup(L"");
+ cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
+
+ break;
+
+ default:
+ /* Unknown device type - this should never happen, as we
+ * check for USB and Bluetooth devices above */
+ break;
+ }
+ }
+
+ next:
+ free(serial_number_utf8);
+ free(product_name_utf8);
+ hid_wrapper_udev_device_unref(raw_dev);
+ /* hid_dev, usb_dev and intf_dev don't need to be (and can't be)
+ unref()d. It will cause a double-free() error. I'm not
+ sure why. */
+ }
+ /* Free the enumerator and udev objects. */
+ hid_wrapper_udev_enumerate_unref(enumerate);
+ hid_wrapper_udev_unref(udev);
+
+ return root;
+}
+
+void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
+{
+ struct hid_device_info *d = devs;
+ while (d) {
+ struct hid_device_info *next = d->next;
+ free(d->path);
+ free(d->serial_number);
+ free(d->manufacturer_string);
+ free(d->product_string);
+ free(d);
+ d = next;
+ }
+}
+
+hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
+{
+ struct hid_device_info *devs, *cur_dev;
+ const char *path_to_open = NULL;
+ hid_device *handle = NULL;
+
+ devs = hid_enumerate(vendor_id, product_id);
+ cur_dev = devs;
+ while (cur_dev) {
+ if (cur_dev->vendor_id == vendor_id &&
+ cur_dev->product_id == product_id) {
+ if (serial_number) {
+ if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ else {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ cur_dev = cur_dev->next;
+ }
+
+ if (path_to_open) {
+ /* Open the device */
+ handle = hid_open_path(path_to_open);
+ }
+
+ hid_free_enumeration(devs);
+
+ return handle;
+}
+
+hid_device * HID_API_EXPORT hid_open_path(const char *path)
+{
+ hid_device *dev = NULL;
+
+ hid_init();
+
+ dev = new_hid_device();
+
+ /* OPEN HERE */
+ dev->device_handle = open(path, O_RDWR);
+
+ /* If we have a good handle, return it. */
+ if (dev->device_handle > 0) {
+
+ /* Get the report descriptor */
+ int res, desc_size = 0;
+ struct hidraw_report_descriptor rpt_desc;
+
+ memset(&rpt_desc, 0x0, sizeof(rpt_desc));
+
+ /* Get Report Descriptor Size */
+ res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size);
+ if (res < 0)
+ perror("HIDIOCGRDESCSIZE");
+
+
+ /* Get Report Descriptor */
+ rpt_desc.size = desc_size;
+ res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc);
+ if (res < 0) {
+ perror("HIDIOCGRDESC");
+ } else {
+ /* Determine if this device uses numbered reports. */
+ dev->uses_numbered_reports =
+ uses_numbered_reports(rpt_desc.value,
+ rpt_desc.size);
+ }
+
+ return dev;
+ }
+ else {
+ /* Unable to open any devices. */
+ free(dev);
+ return NULL;
+ }
+}
+
+
+int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
+{
+ int bytes_written;
+
+ bytes_written = write(dev->device_handle, data, length);
+
+ return bytes_written;
+}
+
+
+int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
+{
+ int bytes_read;
+
+ if (milliseconds >= 0) {
+ /* Milliseconds is either 0 (non-blocking) or > 0 (contains
+ a valid timeout). In both cases we want to call poll()
+ and wait for data to arrive. Don't rely on non-blocking
+ operation (O_NONBLOCK) since some kernels don't seem to
+ properly report device disconnection through read() when
+ in non-blocking mode. */
+ int ret;
+ struct pollfd fds;
+
+ fds.fd = dev->device_handle;
+ fds.events = POLLIN;
+ fds.revents = 0;
+ ret = poll(&fds, 1, milliseconds);
+ if (ret == -1 || ret == 0) {
+ /* Error or timeout */
+ return ret;
+ }
+ else {
+ /* Check for errors on the file descriptor. This will
+ indicate a device disconnection. */
+ if (fds.revents & (POLLERR | POLLHUP | POLLNVAL))
+ return -1;
+ }
+ }
+
+ bytes_read = read(dev->device_handle, data, length);
+ if (bytes_read < 0 && (errno == EAGAIN || errno == EINPROGRESS))
+ bytes_read = 0;
+
+ if (bytes_read >= 0 &&
+ kernel_version != 0 &&
+ kernel_version < KERNEL_VERSION(2,6,34) &&
+ dev->uses_numbered_reports) {
+ /* Work around a kernel bug. Chop off the first byte. */
+ memmove(data, data+1, bytes_read);
+ bytes_read--;
+ }
+
+ return bytes_read;
+}
+
+int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
+{
+ return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
+}
+
+int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
+{
+ /* Do all non-blocking in userspace using poll(), since it looks
+ like there's a bug in the kernel in some versions where
+ read() will not return -1 on disconnection of the USB device */
+
+ dev->blocking = !nonblock;
+ return 0; /* Success */
+}
+
+
+int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+ int res;
+
+ res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data);
+ if (res < 0)
+ perror("ioctl (SFEATURE)");
+
+ return res;
+}
+
+int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+ int res;
+
+ res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data);
+ if (res < 0)
+ perror("ioctl (GFEATURE)");
+
+
+ return res;
+}
+
+
+void HID_API_EXPORT hid_close(hid_device *dev)
+{
+ if (!dev)
+ return;
+ close(dev->device_handle);
+ free(dev);
+}
+
+
+int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
+{
+ return -1;
+}
+
+
+HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
+{
+ return NULL;
+}
diff --git a/src/hidapi/mac/hid.c b/src/hidapi/mac/hid.c
new file mode 100644
index 000000000..ca10a9cca
--- /dev/null
+++ b/src/hidapi/mac/hid.c
@@ -0,0 +1,1121 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 2010-07-03
+
+ Copyright 2010, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ http://github.com/signal11/hidapi .
+********************************************************/
+
+/* See Apple Technical Note TN2187 for details on IOHidManager. */
+
+#include <IOKit/hid/IOHIDManager.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/usb/USBSpec.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <wchar.h>
+#include <locale.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+#include "hidapi.h"
+
+/* Barrier implementation because Mac OSX doesn't have pthread_barrier.
+ It also doesn't have clock_gettime(). So much for POSIX and SUSv2.
+ This implementation came from Brent Priddy and was posted on
+ StackOverflow. It is used with his permission. */
+typedef int pthread_barrierattr_t;
+typedef struct pthread_barrier {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ int count;
+ int trip_count;
+} pthread_barrier_t;
+
+static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
+{
+ if(count == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if(pthread_mutex_init(&barrier->mutex, 0) < 0) {
+ return -1;
+ }
+ if(pthread_cond_init(&barrier->cond, 0) < 0) {
+ pthread_mutex_destroy(&barrier->mutex);
+ return -1;
+ }
+ barrier->trip_count = count;
+ barrier->count = 0;
+
+ return 0;
+}
+
+static int pthread_barrier_destroy(pthread_barrier_t *barrier)
+{
+ pthread_cond_destroy(&barrier->cond);
+ pthread_mutex_destroy(&barrier->mutex);
+ return 0;
+}
+
+static int pthread_barrier_wait(pthread_barrier_t *barrier)
+{
+ pthread_mutex_lock(&barrier->mutex);
+ ++(barrier->count);
+ if(barrier->count >= barrier->trip_count)
+ {
+ barrier->count = 0;
+ pthread_cond_broadcast(&barrier->cond);
+ pthread_mutex_unlock(&barrier->mutex);
+ return 1;
+ }
+ else
+ {
+ pthread_cond_wait(&barrier->cond, &(barrier->mutex));
+ pthread_mutex_unlock(&barrier->mutex);
+ return 0;
+ }
+}
+
+static int return_data(hid_device *dev, unsigned char *data, size_t length);
+
+/* Linked List of input reports received from the device. */
+struct input_report {
+ uint8_t *data;
+ size_t len;
+ struct input_report *next;
+};
+
+struct hid_device_ {
+ IOHIDDeviceRef device_handle;
+ int blocking;
+ int uses_numbered_reports;
+ int disconnected;
+ CFStringRef run_loop_mode;
+ CFRunLoopRef run_loop;
+ CFRunLoopSourceRef source;
+ uint8_t *input_report_buf;
+ CFIndex max_input_report_len;
+ struct input_report *input_reports;
+
+ pthread_t thread;
+ pthread_mutex_t mutex; /* Protects input_reports */
+ pthread_cond_t condition;
+ pthread_barrier_t barrier; /* Ensures correct startup sequence */
+ pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */
+ int shutdown_thread;
+};
+
+static hid_device *new_hid_device(void)
+{
+ hid_device *dev = calloc(1, sizeof(hid_device));
+ dev->device_handle = NULL;
+ dev->blocking = 1;
+ dev->uses_numbered_reports = 0;
+ dev->disconnected = 0;
+ dev->run_loop_mode = NULL;
+ dev->run_loop = NULL;
+ dev->source = NULL;
+ dev->input_report_buf = NULL;
+ dev->input_reports = NULL;
+ dev->shutdown_thread = 0;
+
+ /* Thread objects */
+ pthread_mutex_init(&dev->mutex, NULL);
+ pthread_cond_init(&dev->condition, NULL);
+ pthread_barrier_init(&dev->barrier, NULL, 2);
+ pthread_barrier_init(&dev->shutdown_barrier, NULL, 2);
+
+ return dev;
+}
+
+static void free_hid_device(hid_device *dev)
+{
+ if (!dev)
+ return;
+
+ /* Delete any input reports still left over. */
+ struct input_report *rpt = dev->input_reports;
+ while (rpt) {
+ struct input_report *next = rpt->next;
+ free(rpt->data);
+ free(rpt);
+ rpt = next;
+ }
+
+ /* Free the string and the report buffer. The check for NULL
+ is necessary here as CFRelease() doesn't handle NULL like
+ free() and others do. */
+ if (dev->run_loop_mode)
+ CFRelease(dev->run_loop_mode);
+ if (dev->source)
+ CFRelease(dev->source);
+ free(dev->input_report_buf);
+
+ /* Clean up the thread objects */
+ pthread_barrier_destroy(&dev->shutdown_barrier);
+ pthread_barrier_destroy(&dev->barrier);
+ pthread_cond_destroy(&dev->condition);
+ pthread_mutex_destroy(&dev->mutex);
+
+ /* Free the structure itself. */
+ free(dev);
+}
+
+static IOHIDManagerRef hid_mgr = 0x0;
+
+
+#if 0
+static void register_error(hid_device *dev, const char *op)
+{
+
+}
+#endif
+
+
+static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key)
+{
+ CFTypeRef ref;
+ int32_t value;
+
+ ref = IOHIDDeviceGetProperty(device, key);
+ if (ref) {
+ if (CFGetTypeID(ref) == CFNumberGetTypeID()) {
+ CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value);
+ return value;
+ }
+ }
+ return 0;
+}
+
+static unsigned short get_vendor_id(IOHIDDeviceRef device)
+{
+ return get_int_property(device, CFSTR(kIOHIDVendorIDKey));
+}
+
+static unsigned short get_product_id(IOHIDDeviceRef device)
+{
+ return get_int_property(device, CFSTR(kIOHIDProductIDKey));
+}
+
+static int32_t get_max_report_length(IOHIDDeviceRef device)
+{
+ return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey));
+}
+
+static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len)
+{
+ CFStringRef str;
+
+ if (!len)
+ return 0;
+
+ str = IOHIDDeviceGetProperty(device, prop);
+
+ buf[0] = 0;
+
+ if (str) {
+ CFIndex str_len = CFStringGetLength(str);
+ CFRange range;
+ CFIndex used_buf_len;
+ CFIndex chars_copied;
+
+ len --;
+
+ range.location = 0;
+ range.length = ((size_t)str_len > len)? len: (size_t)str_len;
+ chars_copied = CFStringGetBytes(str,
+ range,
+ kCFStringEncodingUTF32LE,
+ (char)'?',
+ FALSE,
+ (UInt8*)buf,
+ len * sizeof(wchar_t),
+ &used_buf_len);
+
+ if (chars_copied == len)
+ buf[len] = 0; /* len is decremented above */
+ else
+ buf[chars_copied] = 0;
+
+ return 0;
+ }
+ else
+ return -1;
+
+}
+
+static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+ return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len);
+}
+
+static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+ return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len);
+}
+
+static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+ return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len);
+}
+
+
+/* Implementation of wcsdup() for Mac. */
+static wchar_t *dup_wcs(const wchar_t *s)
+{
+ size_t len = wcslen(s);
+ wchar_t *ret = malloc((len+1)*sizeof(wchar_t));
+ wcscpy(ret, s);
+
+ return ret;
+}
+
+/* hidapi_IOHIDDeviceGetService()
+ *
+ * Return the io_service_t corresponding to a given IOHIDDeviceRef, either by:
+ * - on OS X 10.6 and above, calling IOHIDDeviceGetService()
+ * - on OS X 10.5, extract it from the IOHIDDevice struct
+ */
+static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device)
+{
+ static void *iokit_framework = NULL;
+ static io_service_t (*dynamic_IOHIDDeviceGetService)(IOHIDDeviceRef device) = NULL;
+
+ /* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists.
+ * If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL
+ * and the fallback method will be used.
+ */
+ if (iokit_framework == NULL) {
+ iokit_framework = dlopen("/System/Library/IOKit.framework/IOKit", RTLD_LAZY);
+
+ if (iokit_framework != NULL)
+ dynamic_IOHIDDeviceGetService = dlsym(iokit_framework, "IOHIDDeviceGetService");
+ }
+
+ if (dynamic_IOHIDDeviceGetService != NULL) {
+ /* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */
+ return dynamic_IOHIDDeviceGetService(device);
+ }
+ else
+ {
+ /* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist.
+ *
+ * Be naughty and pull the service out of the IOHIDDevice.
+ * IOHIDDevice is an opaque struct not exposed to applications, but its
+ * layout is stable through all available versions of OS X.
+ * Tested and working on OS X 10.5.8 i386, x86_64, and ppc.
+ */
+ struct IOHIDDevice_internal {
+ /* The first field of the IOHIDDevice struct is a
+ * CFRuntimeBase (which is a private CF struct).
+ *
+ * a, b, and c are the 3 fields that make up a CFRuntimeBase.
+ * See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h
+ *
+ * The second field of the IOHIDDevice is the io_service_t we're looking for.
+ */
+ uintptr_t a;
+ uint8_t b[4];
+#if __LP64__
+ uint32_t c;
+#endif
+ io_service_t service;
+ };
+ struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *)device;
+
+ return tmp->service;
+ }
+}
+
+/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */
+static int init_hid_manager(void)
+{
+ /* Initialize all the HID Manager Objects */
+ hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+ if (hid_mgr) {
+ IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
+ IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ return 0;
+ }
+
+ return -1;
+}
+
+/* Initialize the IOHIDManager if necessary. This is the public function, and
+ it is safe to call this function repeatedly. Return 0 for success and -1
+ for failure. */
+int HID_API_EXPORT hid_init(void)
+{
+ if (!hid_mgr) {
+ return init_hid_manager();
+ }
+
+ /* Already initialized. */
+ return 0;
+}
+
+int HID_API_EXPORT hid_exit(void)
+{
+ if (hid_mgr) {
+ /* Close the HID manager. */
+ IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone);
+ CFRelease(hid_mgr);
+ hid_mgr = NULL;
+ }
+
+ return 0;
+}
+
+static void process_pending_events(void) {
+ SInt32 res;
+ do {
+ res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE);
+ } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut);
+}
+
+struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+ struct hid_device_info *root = NULL; /* return object */
+ struct hid_device_info *cur_dev = NULL;
+ CFIndex num_devices;
+ int i;
+
+ /* Set up the HID Manager if it hasn't been done */
+ if (hid_init() < 0)
+ return NULL;
+
+ /* give the IOHIDManager a chance to update itself */
+ process_pending_events();
+
+ /* Get a list of the Devices */
+ IOHIDManagerSetDeviceMatching(hid_mgr, NULL);
+ CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr);
+
+ /* Convert the list into a C array so we can iterate easily. */
+ num_devices = CFSetGetCount(device_set);
+ IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
+ CFSetGetValues(device_set, (const void **) device_array);
+
+ /* Iterate over each device, making an entry for it. */
+ for (i = 0; i < num_devices; i++) {
+ unsigned short dev_vid;
+ unsigned short dev_pid;
+ #define BUF_LEN 256
+ wchar_t buf[BUF_LEN];
+
+ IOHIDDeviceRef dev = device_array[i];
+
+ if (!dev) {
+ continue;
+ }
+ dev_vid = get_vendor_id(dev);
+ dev_pid = get_product_id(dev);
+
+ /* Check the VID/PID against the arguments */
+ if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
+ (product_id == 0x0 || product_id == dev_pid)) {
+ struct hid_device_info *tmp;
+ bool is_usb_hid; /* Is this an actual HID usb device */
+ io_object_t iokit_dev;
+ kern_return_t res;
+ io_string_t path;
+
+ /* VID/PID match. Create the record. */
+ tmp = malloc(sizeof(struct hid_device_info));
+ if (cur_dev) {
+ cur_dev->next = tmp;
+ }
+ else {
+ root = tmp;
+ }
+ cur_dev = tmp;
+
+ is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass;
+
+ /* Get the Usage Page and Usage for this device. */
+ cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey));
+ cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey));
+
+ /* Fill out the record */
+ cur_dev->next = NULL;
+
+ /* Fill in the path (IOService plane) */
+ iokit_dev = hidapi_IOHIDDeviceGetService(dev);
+ res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path);
+ if (res == KERN_SUCCESS)
+ cur_dev->path = strdup(path);
+ else
+ cur_dev->path = strdup("");
+
+ /* Serial Number */
+ get_serial_number(dev, buf, BUF_LEN);
+ cur_dev->serial_number = dup_wcs(buf);
+
+ /* Manufacturer and Product strings */
+ get_manufacturer_string(dev, buf, BUF_LEN);
+ cur_dev->manufacturer_string = dup_wcs(buf);
+ get_product_string(dev, buf, BUF_LEN);
+ cur_dev->product_string = dup_wcs(buf);
+
+ /* VID/PID */
+ cur_dev->vendor_id = dev_vid;
+ cur_dev->product_id = dev_pid;
+
+ /* Release Number */
+ cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey));
+
+ /* We can only retrieve the interface number for USB HID devices.
+ * IOKit always seems to return 0 when querying a standard USB device
+ * for its interface. */
+ if (is_usb_hid) {
+ /* Get the interface number */
+ cur_dev->interface_number = get_int_property(dev, CFSTR(kUSBInterfaceNumber));
+ } else {
+ cur_dev->interface_number = -1;
+ }
+ }
+ }
+
+ free(device_array);
+ CFRelease(device_set);
+
+ return root;
+}
+
+void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
+{
+ /* This function is identical to the Linux version. Platform independent. */
+ struct hid_device_info *d = devs;
+ while (d) {
+ struct hid_device_info *next = d->next;
+ free(d->path);
+ free(d->serial_number);
+ free(d->manufacturer_string);
+ free(d->product_string);
+ free(d);
+ d = next;
+ }
+}
+
+hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
+{
+ /* This function is identical to the Linux version. Platform independent. */
+ struct hid_device_info *devs, *cur_dev;
+ const char *path_to_open = NULL;
+ hid_device * handle = NULL;
+
+ devs = hid_enumerate(vendor_id, product_id);
+ cur_dev = devs;
+ while (cur_dev) {
+ if (cur_dev->vendor_id == vendor_id &&
+ cur_dev->product_id == product_id) {
+ if (serial_number) {
+ if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ else {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ cur_dev = cur_dev->next;
+ }
+
+ if (path_to_open) {
+ /* Open the device */
+ handle = hid_open_path(path_to_open);
+ }
+
+ hid_free_enumeration(devs);
+
+ return handle;
+}
+
+static void hid_device_removal_callback(void *context, IOReturn result,
+ void *sender)
+{
+ /* Stop the Run Loop for this device. */
+ hid_device *d = context;
+
+ d->disconnected = 1;
+ CFRunLoopStop(d->run_loop);
+}
+
+/* The Run Loop calls this function for each input report received.
+ This function puts the data into a linked list to be picked up by
+ hid_read(). */
+static void hid_report_callback(void *context, IOReturn result, void *sender,
+ IOHIDReportType report_type, uint32_t report_id,
+ uint8_t *report, CFIndex report_length)
+{
+ struct input_report *rpt;
+ hid_device *dev = context;
+
+ /* Make a new Input Report object */
+ rpt = calloc(1, sizeof(struct input_report));
+ rpt->data = calloc(1, report_length);
+ memcpy(rpt->data, report, report_length);
+ rpt->len = report_length;
+ rpt->next = NULL;
+
+ /* Lock this section */
+ pthread_mutex_lock(&dev->mutex);
+
+ /* Attach the new report object to the end of the list. */
+ if (dev->input_reports == NULL) {
+ /* The list is empty. Put it at the root. */
+ dev->input_reports = rpt;
+ }
+ else {
+ /* Find the end of the list and attach. */
+ struct input_report *cur = dev->input_reports;
+ int num_queued = 0;
+ while (cur->next != NULL) {
+ cur = cur->next;
+ num_queued++;
+ }
+ cur->next = rpt;
+
+ /* Pop one off if we've reached 30 in the queue. This
+ way we don't grow forever if the user never reads
+ anything from the device. */
+ if (num_queued > 30) {
+ return_data(dev, NULL, 0);
+ }
+ }
+
+ /* Signal a waiting thread that there is data. */
+ pthread_cond_signal(&dev->condition);
+
+ /* Unlock */
+ pthread_mutex_unlock(&dev->mutex);
+
+}
+
+/* This gets called when the read_thread's run loop gets signaled by
+ hid_close(), and serves to stop the read_thread's run loop. */
+static void perform_signal_callback(void *context)
+{
+ hid_device *dev = context;
+ CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/
+}
+
+static void *read_thread(void *param)
+{
+ hid_device *dev = param;
+ SInt32 code;
+
+ /* Move the device's run loop to this thread. */
+ IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode);
+
+ /* Create the RunLoopSource which is used to signal the
+ event loop to stop when hid_close() is called. */
+ CFRunLoopSourceContext ctx;
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.version = 0;
+ ctx.info = dev;
+ ctx.perform = &perform_signal_callback;
+ dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode);
+
+ /* Store off the Run Loop so it can be stopped from hid_close()
+ and on device disconnection. */
+ dev->run_loop = CFRunLoopGetCurrent();
+
+ /* Notify the main thread that the read thread is up and running. */
+ pthread_barrier_wait(&dev->barrier);
+
+ /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input
+ reports into the hid_report_callback(). */
+ while (!dev->shutdown_thread && !dev->disconnected) {
+ code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE);
+ /* Return if the device has been disconnected */
+ if (code == kCFRunLoopRunFinished) {
+ dev->disconnected = 1;
+ break;
+ }
+
+
+ /* Break if The Run Loop returns Finished or Stopped. */
+ if (code != kCFRunLoopRunTimedOut &&
+ code != kCFRunLoopRunHandledSource) {
+ /* There was some kind of error. Setting
+ shutdown seems to make sense, but
+ there may be something else more appropriate */
+ dev->shutdown_thread = 1;
+ break;
+ }
+ }
+
+ /* Now that the read thread is stopping, Wake any threads which are
+ waiting on data (in hid_read_timeout()). Do this under a mutex to
+ make sure that a thread which is about to go to sleep waiting on
+ the condition actually will go to sleep before the condition is
+ signaled. */
+ pthread_mutex_lock(&dev->mutex);
+ pthread_cond_broadcast(&dev->condition);
+ pthread_mutex_unlock(&dev->mutex);
+
+ /* Wait here until hid_close() is called and makes it past
+ the call to CFRunLoopWakeUp(). This thread still needs to
+ be valid when that function is called on the other thread. */
+ pthread_barrier_wait(&dev->shutdown_barrier);
+
+ return NULL;
+}
+
+/* hid_open_path()
+ *
+ * path must be a valid path to an IOHIDDevice in the IOService plane
+ * Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"
+ */
+hid_device * HID_API_EXPORT hid_open_path(const char *path)
+{
+ hid_device *dev = NULL;
+ io_registry_entry_t entry = MACH_PORT_NULL;
+
+ dev = new_hid_device();
+
+ /* Set up the HID Manager if it hasn't been done */
+ if (hid_init() < 0)
+ return NULL;
+
+ /* Get the IORegistry entry for the given path */
+ entry = IORegistryEntryFromPath(kIOMasterPortDefault, path);
+ if (entry == MACH_PORT_NULL) {
+ /* Path wasn't valid (maybe device was removed?) */
+ goto return_error;
+ }
+
+ /* Create an IOHIDDevice for the entry */
+ dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry);
+ if (dev->device_handle == NULL) {
+ /* Error creating the HID device */
+ goto return_error;
+ }
+
+ /* Open the IOHIDDevice */
+ IOReturn ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice);
+ if (ret == kIOReturnSuccess) {
+ char str[32];
+
+ /* Create the buffers for receiving data */
+ dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle);
+ dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t));
+
+ /* Create the Run Loop Mode for this device.
+ printing the reference seems to work. */
+ sprintf(str, "HIDAPI_%p", dev->device_handle);
+ dev->run_loop_mode =
+ CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
+
+ /* Attach the device to a Run Loop */
+ IOHIDDeviceRegisterInputReportCallback(
+ dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
+ &hid_report_callback, dev);
+ IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev);
+
+ /* Start the read thread */
+ pthread_create(&dev->thread, NULL, read_thread, dev);
+
+ /* Wait here for the read thread to be initialized. */
+ pthread_barrier_wait(&dev->barrier);
+
+ IOObjectRelease(entry);
+ return dev;
+ }
+ else {
+ goto return_error;
+ }
+
+return_error:
+ if (dev->device_handle != NULL)
+ CFRelease(dev->device_handle);
+
+ if (entry != MACH_PORT_NULL)
+ IOObjectRelease(entry);
+
+ free_hid_device(dev);
+ return NULL;
+}
+
+static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length)
+{
+ const unsigned char *data_to_send;
+ size_t length_to_send;
+ IOReturn res;
+
+ /* Return if the device has been disconnected. */
+ if (dev->disconnected)
+ return -1;
+
+ if (data[0] == 0x0) {
+ /* Not using numbered Reports.
+ Don't send the report number. */
+ data_to_send = data+1;
+ length_to_send = length-1;
+ }
+ else {
+ /* Using numbered Reports.
+ Send the Report Number */
+ data_to_send = data;
+ length_to_send = length;
+ }
+
+ if (!dev->disconnected) {
+ res = IOHIDDeviceSetReport(dev->device_handle,
+ type,
+ data[0], /* Report ID*/
+ data_to_send, length_to_send);
+
+ if (res == kIOReturnSuccess) {
+ return length;
+ }
+ else
+ return -1;
+ }
+
+ return -1;
+}
+
+int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
+{
+ return set_report(dev, kIOHIDReportTypeOutput, data, length);
+}
+
+/* Helper function, so that this isn't duplicated in hid_read(). */
+static int return_data(hid_device *dev, unsigned char *data, size_t length)
+{
+ /* Copy the data out of the linked list item (rpt) into the
+ return buffer (data), and delete the liked list item. */
+ struct input_report *rpt = dev->input_reports;
+ size_t len = (length < rpt->len)? length: rpt->len;
+ memcpy(data, rpt->data, len);
+ dev->input_reports = rpt->next;
+ free(rpt->data);
+ free(rpt);
+ return len;
+}
+
+static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+ while (!dev->input_reports) {
+ int res = pthread_cond_wait(cond, mutex);
+ if (res != 0)
+ return res;
+
+ /* A res of 0 means we may have been signaled or it may
+ be a spurious wakeup. Check to see that there's acutally
+ data in the queue before returning, and if not, go back
+ to sleep. See the pthread_cond_timedwait() man page for
+ details. */
+
+ if (dev->shutdown_thread || dev->disconnected)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
+{
+ while (!dev->input_reports) {
+ int res = pthread_cond_timedwait(cond, mutex, abstime);
+ if (res != 0)
+ return res;
+
+ /* A res of 0 means we may have been signaled or it may
+ be a spurious wakeup. Check to see that there's acutally
+ data in the queue before returning, and if not, go back
+ to sleep. See the pthread_cond_timedwait() man page for
+ details. */
+
+ if (dev->shutdown_thread || dev->disconnected)
+ return -1;
+ }
+
+ return 0;
+
+}
+
+int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
+{
+ int bytes_read = -1;
+
+ /* Lock the access to the report list. */
+ pthread_mutex_lock(&dev->mutex);
+
+ /* There's an input report queued up. Return it. */
+ if (dev->input_reports) {
+ /* Return the first one */
+ bytes_read = return_data(dev, data, length);
+ goto ret;
+ }
+
+ /* Return if the device has been disconnected. */
+ if (dev->disconnected) {
+ bytes_read = -1;
+ goto ret;
+ }
+
+ if (dev->shutdown_thread) {
+ /* This means the device has been closed (or there
+ has been an error. An error code of -1 should
+ be returned. */
+ bytes_read = -1;
+ goto ret;
+ }
+
+ /* There is no data. Go to sleep and wait for data. */
+
+ if (milliseconds == -1) {
+ /* Blocking */
+ int res;
+ res = cond_wait(dev, &dev->condition, &dev->mutex);
+ if (res == 0)
+ bytes_read = return_data(dev, data, length);
+ else {
+ /* There was an error, or a device disconnection. */
+ bytes_read = -1;
+ }
+ }
+ else if (milliseconds > 0) {
+ /* Non-blocking, but called with timeout. */
+ int res;
+ struct timespec ts;
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ TIMEVAL_TO_TIMESPEC(&tv, &ts);
+ ts.tv_sec += milliseconds / 1000;
+ ts.tv_nsec += (milliseconds % 1000) * 1000000;
+ if (ts.tv_nsec >= 1000000000L) {
+ ts.tv_sec++;
+ ts.tv_nsec -= 1000000000L;
+ }
+
+ res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts);
+ if (res == 0)
+ bytes_read = return_data(dev, data, length);
+ else if (res == ETIMEDOUT)
+ bytes_read = 0;
+ else
+ bytes_read = -1;
+ }
+ else {
+ /* Purely non-blocking */
+ bytes_read = 0;
+ }
+
+ret:
+ /* Unlock */
+ pthread_mutex_unlock(&dev->mutex);
+ return bytes_read;
+}
+
+int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
+{
+ return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
+}
+
+int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
+{
+ /* All Nonblocking operation is handled by the library. */
+ dev->blocking = !nonblock;
+
+ return 0;
+}
+
+int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+ return set_report(dev, kIOHIDReportTypeFeature, data, length);
+}
+
+int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+ CFIndex len = length - 1;
+ IOReturn res;
+
+ /* Return if the device has been unplugged. */
+ if (dev->disconnected)
+ return -1;
+
+ res = IOHIDDeviceGetReport(dev->device_handle,
+ kIOHIDReportTypeFeature,
+ data[0], /* Report ID */
+ data + 1, &len);
+ if (res == kIOReturnSuccess)
+ return len + 1;
+ else
+ return -1;
+}
+
+
+void HID_API_EXPORT hid_close(hid_device *dev)
+{
+ if (!dev)
+ return;
+
+ /* Disconnect the report callback before close. */
+ if (!dev->disconnected) {
+ IOHIDDeviceRegisterInputReportCallback(
+ dev->device_handle, dev->input_report_buf, dev->max_input_report_len,
+ NULL, dev);
+ IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev);
+ IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode);
+ IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
+ }
+
+ /* Cause read_thread() to stop. */
+ dev->shutdown_thread = 1;
+
+ /* Wake up the run thread's event loop so that the thread can exit. */
+ CFRunLoopSourceSignal(dev->source);
+ CFRunLoopWakeUp(dev->run_loop);
+
+ /* Notify the read thread that it can shut down now. */
+ pthread_barrier_wait(&dev->shutdown_barrier);
+
+ /* Wait for read_thread() to end. */
+ pthread_join(dev->thread, NULL);
+
+ /* Close the OS handle to the device, but only if it's not
+ been unplugged. If it's been unplugged, then calling
+ IOHIDDeviceClose() will crash. */
+ if (!dev->disconnected) {
+ IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice);
+ }
+
+ /* Clear out the queue of received reports. */
+ pthread_mutex_lock(&dev->mutex);
+ while (dev->input_reports) {
+ return_data(dev, NULL, 0);
+ }
+ pthread_mutex_unlock(&dev->mutex);
+ CFRelease(dev->device_handle);
+
+ free_hid_device(dev);
+}
+
+int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ return get_manufacturer_string(dev->device_handle, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ return get_product_string(dev->device_handle, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ return get_serial_number(dev->device_handle, string, maxlen);
+}
+
+int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
+{
+ /* TODO: */
+
+ return 0;
+}
+
+
+HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
+{
+ /* TODO: */
+
+ return NULL;
+}
+
+
+
+
+
+
+
+#if 0
+static int32_t get_location_id(IOHIDDeviceRef device)
+{
+ return get_int_property(device, CFSTR(kIOHIDLocationIDKey));
+}
+
+static int32_t get_usage(IOHIDDeviceRef device)
+{
+ int32_t res;
+ res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey));
+ if (!res)
+ res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey));
+ return res;
+}
+
+static int32_t get_usage_page(IOHIDDeviceRef device)
+{
+ int32_t res;
+ res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey));
+ if (!res)
+ res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey));
+ return res;
+}
+
+static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len)
+{
+ return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len);
+}
+
+
+int main(void)
+{
+ IOHIDManagerRef mgr;
+ int i;
+
+ mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+ IOHIDManagerSetDeviceMatching(mgr, NULL);
+ IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone);
+
+ CFSetRef device_set = IOHIDManagerCopyDevices(mgr);
+
+ CFIndex num_devices = CFSetGetCount(device_set);
+ IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef));
+ CFSetGetValues(device_set, (const void **) device_array);
+
+ for (i = 0; i < num_devices; i++) {
+ IOHIDDeviceRef dev = device_array[i];
+ printf("Device: %p\n", dev);
+ printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev));
+
+ wchar_t serial[256], buf[256];
+ char cbuf[256];
+ get_serial_number(dev, serial, 256);
+
+
+ printf(" Serial: %ls\n", serial);
+ printf(" Loc: %ld\n", get_location_id(dev));
+ get_transport(dev, buf, 256);
+ printf(" Trans: %ls\n", buf);
+ make_path(dev, cbuf, 256);
+ printf(" Path: %s\n", cbuf);
+
+ }
+
+ return 0;
+}
+#endif
diff --git a/src/hidapi/win/hid.c b/src/hidapi/win/hid.c
new file mode 100644
index 000000000..4a71e2552
--- /dev/null
+++ b/src/hidapi/win/hid.c
@@ -0,0 +1,956 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 8/22/2009
+
+ Copyright 2009, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ http://github.com/signal11/hidapi .
+********************************************************/
+
+#include <windows.h>
+
+#ifndef _NTDEF_
+typedef LONG NTSTATUS;
+#endif
+
+#ifdef __MINGW32__
+#include <ntdef.h>
+#include <winbase.h>
+#endif
+
+#ifdef __CYGWIN__
+#include <ntdef.h>
+#define _wcsdup wcsdup
+#endif
+
+/* The maximum number of characters that can be passed into the
+ HidD_Get*String() functions without it failing.*/
+#define MAX_STRING_WCHARS 0xFFF
+
+/*#define HIDAPI_USE_DDK*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ #include <setupapi.h>
+ #include <winioctl.h>
+ #ifdef HIDAPI_USE_DDK
+ #include <hidsdi.h>
+ #endif
+
+ /* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */
+ #define HID_OUT_CTL_CODE(id) \
+ CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
+ #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+#include "hidapi.h"
+
+#undef MIN
+#define MIN(x,y) ((x) < (y)? (x): (y))
+
+#ifdef _MSC_VER
+ /* Thanks Microsoft, but I know how to use strncpy(). */
+ #pragma warning(disable:4996)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HIDAPI_USE_DDK
+ /* Since we're not building with the DDK, and the HID header
+ files aren't part of the SDK, we have to define all this
+ stuff here. In lookup_functions(), the function pointers
+ defined below are set. */
+ typedef struct _HIDD_ATTRIBUTES{
+ ULONG Size;
+ USHORT VendorID;
+ USHORT ProductID;
+ USHORT VersionNumber;
+ } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
+
+ typedef USHORT USAGE;
+ typedef struct _HIDP_CAPS {
+ USAGE Usage;
+ USAGE UsagePage;
+ USHORT InputReportByteLength;
+ USHORT OutputReportByteLength;
+ USHORT FeatureReportByteLength;
+ USHORT Reserved[17];
+ USHORT fields_not_used_by_hidapi[10];
+ } HIDP_CAPS, *PHIDP_CAPS;
+ typedef void* PHIDP_PREPARSED_DATA;
+ #define HIDP_STATUS_SUCCESS 0x110000
+
+ typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib);
+ typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length);
+ typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length);
+ typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
+ typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
+ typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps);
+ typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
+
+ static HidD_GetAttributes_ HidD_GetAttributes;
+ static HidD_GetSerialNumberString_ HidD_GetSerialNumberString;
+ static HidD_GetManufacturerString_ HidD_GetManufacturerString;
+ static HidD_GetProductString_ HidD_GetProductString;
+ static HidD_SetFeature_ HidD_SetFeature;
+ static HidD_GetFeature_ HidD_GetFeature;
+ static HidD_GetIndexedString_ HidD_GetIndexedString;
+ static HidD_GetPreparsedData_ HidD_GetPreparsedData;
+ static HidD_FreePreparsedData_ HidD_FreePreparsedData;
+ static HidP_GetCaps_ HidP_GetCaps;
+ static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
+
+ static HMODULE lib_handle = NULL;
+ static BOOLEAN initialized = FALSE;
+#endif /* HIDAPI_USE_DDK */
+
+struct hid_device_ {
+ HANDLE device_handle;
+ BOOL blocking;
+ USHORT output_report_length;
+ size_t input_report_length;
+ void *last_error_str;
+ DWORD last_error_num;
+ BOOL read_pending;
+ char *read_buf;
+ OVERLAPPED ol;
+};
+
+static hid_device *new_hid_device()
+{
+ hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
+ dev->device_handle = INVALID_HANDLE_VALUE;
+ dev->blocking = TRUE;
+ dev->output_report_length = 0;
+ dev->input_report_length = 0;
+ dev->last_error_str = NULL;
+ dev->last_error_num = 0;
+ dev->read_pending = FALSE;
+ dev->read_buf = NULL;
+ memset(&dev->ol, 0, sizeof(dev->ol));
+ dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
+
+ return dev;
+}
+
+static void free_hid_device(hid_device *dev)
+{
+ CloseHandle(dev->ol.hEvent);
+ CloseHandle(dev->device_handle);
+ LocalFree(dev->last_error_str);
+ free(dev->read_buf);
+ free(dev);
+}
+
+static void register_error(hid_device *dev, const char *op)
+{
+ WCHAR *ptr, *msg;
+
+ FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPVOID)&msg, 0/*sz*/,
+ NULL);
+
+ /* Get rid of the CR and LF that FormatMessage() sticks at the
+ end of the message. Thanks Microsoft! */
+ ptr = msg;
+ while (*ptr) {
+ if (*ptr == '\r') {
+ *ptr = 0x0000;
+ break;
+ }
+ ptr++;
+ }
+
+ /* Store the message off in the Device entry so that
+ the hid_error() function can pick it up. */
+ LocalFree(dev->last_error_str);
+ dev->last_error_str = msg;
+}
+
+#ifndef HIDAPI_USE_DDK
+static int lookup_functions()
+{
+ lib_handle = LoadLibraryA("hid.dll");
+ if (lib_handle) {
+#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1;
+ RESOLVE(HidD_GetAttributes);
+ RESOLVE(HidD_GetSerialNumberString);
+ RESOLVE(HidD_GetManufacturerString);
+ RESOLVE(HidD_GetProductString);
+ RESOLVE(HidD_SetFeature);
+ RESOLVE(HidD_GetFeature);
+ RESOLVE(HidD_GetIndexedString);
+ RESOLVE(HidD_GetPreparsedData);
+ RESOLVE(HidD_FreePreparsedData);
+ RESOLVE(HidP_GetCaps);
+ RESOLVE(HidD_SetNumInputBuffers);
+#undef RESOLVE
+ }
+ else
+ return -1;
+
+ return 0;
+}
+#endif
+
+static HANDLE open_device(const char *path, BOOL open_rw)
+{
+ HANDLE handle;
+ DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0;
+ DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+
+ handle = CreateFileA(path,
+ desired_access,
+ share_mode,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
+ 0);
+
+ return handle;
+}
+
+int HID_API_EXPORT hid_init(void)
+{
+#ifndef HIDAPI_USE_DDK
+ if (!initialized) {
+ if (lookup_functions() < 0) {
+ hid_exit();
+ return -1;
+ }
+ initialized = TRUE;
+ }
+#endif
+ return 0;
+}
+
+int HID_API_EXPORT hid_exit(void)
+{
+#ifndef HIDAPI_USE_DDK
+ if (lib_handle)
+ FreeLibrary(lib_handle);
+ lib_handle = NULL;
+ initialized = FALSE;
+#endif
+ return 0;
+}
+
+struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+ BOOL res;
+ struct hid_device_info *root = NULL; /* return object */
+ struct hid_device_info *cur_dev = NULL;
+
+ /* Windows objects for interacting with the driver. */
+ GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
+ SP_DEVINFO_DATA devinfo_data;
+ SP_DEVICE_INTERFACE_DATA device_interface_data;
+ SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
+ HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
+ int device_index = 0;
+ int i;
+
+ if (hid_init() < 0)
+ return NULL;
+
+ /* Initialize the Windows objects. */
+ memset(&devinfo_data, 0x0, sizeof(devinfo_data));
+ devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
+ device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+
+ /* Get information for all the devices belonging to the HID class. */
+ device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+ /* Iterate over each device in the HID class, looking for the right one. */
+
+ for (;;) {
+ HANDLE write_handle = INVALID_HANDLE_VALUE;
+ DWORD required_size = 0;
+ HIDD_ATTRIBUTES attrib;
+
+ res = SetupDiEnumDeviceInterfaces(device_info_set,
+ NULL,
+ &InterfaceClassGuid,
+ device_index,
+ &device_interface_data);
+
+ if (!res) {
+ /* A return of FALSE from this function means that
+ there are no more devices. */
+ break;
+ }
+
+ /* Call with 0-sized detail size, and let the function
+ tell us how long the detail struct needs to be. The
+ size is put in &required_size. */
+ res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
+ &device_interface_data,
+ NULL,
+ 0,
+ &required_size,
+ NULL);
+
+ /* Allocate a long enough structure for device_interface_detail_data. */
+ device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size);
+ device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
+
+ /* Get the detailed data for this device. The detail data gives us
+ the device path for this device, which is then passed into
+ CreateFile() to get a handle to the device. */
+ res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
+ &device_interface_data,
+ device_interface_detail_data,
+ required_size,
+ NULL,
+ NULL);
+
+ if (!res) {
+ /* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail");
+ Continue to the next device. */
+ goto cont;
+ }
+
+ /* Make sure this device is of Setup Class "HIDClass" and has a
+ driver bound to it. */
+ for (i = 0; ; i++) {
+ char driver_name[256];
+
+ /* Populate devinfo_data. This function will return failure
+ when there are no more interfaces left. */
+ res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);
+ if (!res)
+ goto cont;
+
+ res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
+ SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
+ if (!res)
+ goto cont;
+
+ if ((strcmp(driver_name, "HIDClass") == 0) ||
+ (strcmp(driver_name, "Mouse") == 0) ||
+ (strcmp(driver_name, "Keyboard") == 0)) {
+ /* See if there's a driver bound. */
+ res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
+ SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
+ if (res)
+ break;
+ }
+ }
+
+ //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath);
+
+ /* Open a handle to the device */
+ write_handle = open_device(device_interface_detail_data->DevicePath, FALSE);
+
+ /* Check validity of write_handle. */
+ if (write_handle == INVALID_HANDLE_VALUE) {
+ /* Unable to open the device. */
+ //register_error(dev, "CreateFile");
+ goto cont_close;
+ }
+
+
+ /* Get the Vendor ID and Product ID for this device. */
+ attrib.Size = sizeof(HIDD_ATTRIBUTES);
+ HidD_GetAttributes(write_handle, &attrib);
+ //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
+
+ /* Check the VID/PID to see if we should add this
+ device to the enumeration list. */
+ if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) &&
+ (product_id == 0x0 || attrib.ProductID == product_id)) {
+
+ #define WSTR_LEN 512
+ const char *str;
+ struct hid_device_info *tmp;
+ PHIDP_PREPARSED_DATA pp_data = NULL;
+ HIDP_CAPS caps;
+ BOOLEAN res;
+ NTSTATUS nt_res;
+ wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */
+ size_t len;
+
+ /* VID/PID match. Create the record. */
+ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
+ if (cur_dev) {
+ cur_dev->next = tmp;
+ }
+ else {
+ root = tmp;
+ }
+ cur_dev = tmp;
+
+ /* Get the Usage Page and Usage for this device. */
+ res = HidD_GetPreparsedData(write_handle, &pp_data);
+ if (res) {
+ nt_res = HidP_GetCaps(pp_data, &caps);
+ if (nt_res == HIDP_STATUS_SUCCESS) {
+ cur_dev->usage_page = caps.UsagePage;
+ cur_dev->usage = caps.Usage;
+ }
+
+ HidD_FreePreparsedData(pp_data);
+ }
+
+ /* Fill out the record */
+ cur_dev->next = NULL;
+ str = device_interface_detail_data->DevicePath;
+ if (str) {
+ len = strlen(str);
+ cur_dev->path = (char*) calloc(len+1, sizeof(char));
+ strncpy(cur_dev->path, str, len+1);
+ cur_dev->path[len] = '\0';
+ }
+ else
+ cur_dev->path = NULL;
+
+ /* Serial Number */
+ res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr));
+ wstr[WSTR_LEN-1] = 0x0000;
+ if (res) {
+ cur_dev->serial_number = _wcsdup(wstr);
+ }
+
+ /* Manufacturer String */
+ res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr));
+ wstr[WSTR_LEN-1] = 0x0000;
+ if (res) {
+ cur_dev->manufacturer_string = _wcsdup(wstr);
+ }
+
+ /* Product String */
+ res = HidD_GetProductString(write_handle, wstr, sizeof(wstr));
+ wstr[WSTR_LEN-1] = 0x0000;
+ if (res) {
+ cur_dev->product_string = _wcsdup(wstr);
+ }
+
+ /* VID/PID */
+ cur_dev->vendor_id = attrib.VendorID;
+ cur_dev->product_id = attrib.ProductID;
+
+ /* Release Number */
+ cur_dev->release_number = attrib.VersionNumber;
+
+ /* Interface Number. It can sometimes be parsed out of the path
+ on Windows if a device has multiple interfaces. See
+ http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or
+ search for "Hardware IDs for HID Devices" at MSDN. If it's not
+ in the path, it's set to -1. */
+ cur_dev->interface_number = -1;
+ if (cur_dev->path) {
+ char *interface_component = strstr(cur_dev->path, "&mi_");
+ if (interface_component) {
+ char *hex_str = interface_component + 4;
+ char *endptr = NULL;
+ cur_dev->interface_number = strtol(hex_str, &endptr, 16);
+ if (endptr == hex_str) {
+ /* The parsing failed. Set interface_number to -1. */
+ cur_dev->interface_number = -1;
+ }
+ }
+ }
+ }
+
+cont_close:
+ CloseHandle(write_handle);
+cont:
+ /* We no longer need the detail data. It can be freed */
+ free(device_interface_detail_data);
+
+ device_index++;
+
+ }
+
+ /* Close the device information handle. */
+ SetupDiDestroyDeviceInfoList(device_info_set);
+
+ return root;
+
+}
+
+void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
+{
+ /* TODO: Merge this with the Linux version. This function is platform-independent. */
+ struct hid_device_info *d = devs;
+ while (d) {
+ struct hid_device_info *next = d->next;
+ free(d->path);
+ free(d->serial_number);
+ free(d->manufacturer_string);
+ free(d->product_string);
+ free(d);
+ d = next;
+ }
+}
+
+
+HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
+{
+ /* TODO: Merge this functions with the Linux version. This function should be platform independent. */
+ struct hid_device_info *devs, *cur_dev;
+ const char *path_to_open = NULL;
+ hid_device *handle = NULL;
+
+ devs = hid_enumerate(vendor_id, product_id);
+ cur_dev = devs;
+ while (cur_dev) {
+ if (cur_dev->vendor_id == vendor_id &&
+ cur_dev->product_id == product_id) {
+ if (serial_number) {
+ if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ else {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ cur_dev = cur_dev->next;
+ }
+
+ if (path_to_open) {
+ /* Open the device */
+ handle = hid_open_path(path_to_open);
+ }
+
+ hid_free_enumeration(devs);
+
+ return handle;
+}
+
+HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
+{
+ hid_device *dev;
+ HIDP_CAPS caps;
+ PHIDP_PREPARSED_DATA pp_data = NULL;
+ BOOLEAN res;
+ NTSTATUS nt_res;
+
+ if (hid_init() < 0) {
+ return NULL;
+ }
+
+ dev = new_hid_device();
+
+ /* Open a handle to the device */
+ dev->device_handle = open_device(path, TRUE);
+
+ /* Check validity of write_handle. */
+ if (dev->device_handle == INVALID_HANDLE_VALUE) {
+ /* System devices, such as keyboards and mice, cannot be opened in
+ read-write mode, because the system takes exclusive control over
+ them. This is to prevent keyloggers. However, feature reports
+ can still be sent and received. Retry opening the device, but
+ without read/write access. */
+ dev->device_handle = open_device(path, FALSE);
+
+ /* Check the validity of the limited device_handle. */
+ if (dev->device_handle == INVALID_HANDLE_VALUE) {
+ /* Unable to open the device, even without read-write mode. */
+ register_error(dev, "CreateFile");
+ goto err;
+ }
+ }
+
+ /* Set the Input Report buffer size to 64 reports. */
+ res = HidD_SetNumInputBuffers(dev->device_handle, 64);
+ if (!res) {
+ register_error(dev, "HidD_SetNumInputBuffers");
+ goto err;
+ }
+
+ /* Get the Input Report length for the device. */
+ res = HidD_GetPreparsedData(dev->device_handle, &pp_data);
+ if (!res) {
+ register_error(dev, "HidD_GetPreparsedData");
+ goto err;
+ }
+ nt_res = HidP_GetCaps(pp_data, &caps);
+ if (nt_res != HIDP_STATUS_SUCCESS) {
+ register_error(dev, "HidP_GetCaps");
+ goto err_pp_data;
+ }
+ dev->output_report_length = caps.OutputReportByteLength;
+ dev->input_report_length = caps.InputReportByteLength;
+ HidD_FreePreparsedData(pp_data);
+
+ dev->read_buf = (char*) malloc(dev->input_report_length);
+
+ return dev;
+
+err_pp_data:
+ HidD_FreePreparsedData(pp_data);
+err:
+ free_hid_device(dev);
+ return NULL;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
+{
+ DWORD bytes_written;
+ BOOL res;
+
+ OVERLAPPED ol;
+ unsigned char *buf;
+ memset(&ol, 0, sizeof(ol));
+
+ /* Make sure the right number of bytes are passed to WriteFile. Windows
+ expects the number of bytes which are in the _longest_ report (plus
+ one for the report number) bytes even if the data is a report
+ which is shorter than that. Windows gives us this value in
+ caps.OutputReportByteLength. If a user passes in fewer bytes than this,
+ create a temporary buffer which is the proper size. */
+ if (length >= dev->output_report_length) {
+ /* The user passed the right number of bytes. Use the buffer as-is. */
+ buf = (unsigned char *) data;
+ } else {
+ /* Create a temporary buffer and copy the user's data
+ into it, padding the rest with zeros. */
+ buf = (unsigned char *) malloc(dev->output_report_length);
+ memcpy(buf, data, length);
+ memset(buf + length, 0, dev->output_report_length - length);
+ length = dev->output_report_length;
+ }
+
+ res = WriteFile(dev->device_handle, buf, length, NULL, &ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* WriteFile() failed. Return error. */
+ register_error(dev, "WriteFile");
+ bytes_written = -1;
+ goto end_of_function;
+ }
+ }
+
+ /* Wait here until the write is done. This makes
+ hid_write() synchronous. */
+ res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/);
+ if (!res) {
+ /* The Write operation failed. */
+ register_error(dev, "WriteFile");
+ bytes_written = -1;
+ goto end_of_function;
+ }
+
+end_of_function:
+ if (buf != data)
+ free(buf);
+
+ return bytes_written;
+}
+
+
+int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
+{
+ DWORD bytes_read = 0;
+ size_t copy_len = 0;
+ BOOL res;
+
+ /* Copy the handle for convenience. */
+ HANDLE ev = dev->ol.hEvent;
+
+ if (!dev->read_pending) {
+ /* Start an Overlapped I/O read. */
+ dev->read_pending = TRUE;
+ memset(dev->read_buf, 0, dev->input_report_length);
+ ResetEvent(ev);
+ res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* ReadFile() has failed.
+ Clean up and return error. */
+ CancelIo(dev->device_handle);
+ dev->read_pending = FALSE;
+ goto end_of_function;
+ }
+ }
+ }
+
+ if (milliseconds >= 0) {
+ /* See if there is any data yet. */
+ res = WaitForSingleObject(ev, milliseconds);
+ if (res != WAIT_OBJECT_0) {
+ /* There was no data this time. Return zero bytes available,
+ but leave the Overlapped I/O running. */
+ return 0;
+ }
+ }
+
+ /* Either WaitForSingleObject() told us that ReadFile has completed, or
+ we are in non-blocking mode. Get the number of bytes read. The actual
+ data has been copied to the data[] array which was passed to ReadFile(). */
+ res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
+
+ /* Set pending back to false, even if GetOverlappedResult() returned error. */
+ dev->read_pending = FALSE;
+
+ if (res && bytes_read > 0) {
+ if (dev->read_buf[0] == 0x0) {
+ /* If report numbers aren't being used, but Windows sticks a report
+ number (0x0) on the beginning of the report anyway. To make this
+ work like the other platforms, and to make it work more like the
+ HID spec, we'll skip over this byte. */
+ bytes_read--;
+ copy_len = length > bytes_read ? bytes_read : length;
+ memcpy(data, dev->read_buf+1, copy_len);
+ }
+ else {
+ /* Copy the whole buffer, report number and all. */
+ copy_len = length > bytes_read ? bytes_read : length;
+ memcpy(data, dev->read_buf, copy_len);
+ }
+ }
+
+end_of_function:
+ if (!res) {
+ register_error(dev, "GetOverlappedResult");
+ return -1;
+ }
+
+ return copy_len;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
+{
+ return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock)
+{
+ dev->blocking = !nonblock;
+ return 0; /* Success */
+}
+
+int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+ BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length);
+ if (!res) {
+ register_error(dev, "HidD_SetFeature");
+ return -1;
+ }
+
+ return length;
+}
+
+
+int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+ BOOL res;
+#if 0
+ res = HidD_GetFeature(dev->device_handle, data, length);
+ if (!res) {
+ register_error(dev, "HidD_GetFeature");
+ return -1;
+ }
+ return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */
+#else
+ DWORD bytes_returned;
+
+ OVERLAPPED ol;
+ memset(&ol, 0, sizeof(ol));
+
+ res = DeviceIoControl(dev->device_handle,
+ IOCTL_HID_GET_FEATURE,
+ data, length,
+ data, length,
+ &bytes_returned, &ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* DeviceIoControl() failed. Return error. */
+ register_error(dev, "Send Feature Report DeviceIoControl");
+ return -1;
+ }
+ }
+
+ /* Wait here until the write is done. This makes
+ hid_get_feature_report() synchronous. */
+ res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
+ if (!res) {
+ /* The operation failed. */
+ register_error(dev, "Send Feature Report GetOverLappedResult");
+ return -1;
+ }
+
+ /* bytes_returned does not include the first byte which contains the
+ report ID. The data buffer actually contains one more byte than
+ bytes_returned. */
+ bytes_returned++;
+
+ return bytes_returned;
+#endif
+}
+
+void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
+{
+ if (!dev)
+ return;
+ CancelIo(dev->device_handle);
+ free_hid_device(dev);
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetManufacturerString");
+ return -1;
+ }
+
+ return 0;
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetProductString");
+ return -1;
+ }
+
+ return 0;
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetSerialNumberString");
+ return -1;
+ }
+
+ return 0;
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetIndexedString");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
+{
+ return (wchar_t*)dev->last_error_str;
+}
+
+
+/*#define PICPGM*/
+/*#define S11*/
+#define P32
+#ifdef S11
+ unsigned short VendorID = 0xa0a0;
+ unsigned short ProductID = 0x0001;
+#endif
+
+#ifdef P32
+ unsigned short VendorID = 0x04d8;
+ unsigned short ProductID = 0x3f;
+#endif
+
+
+#ifdef PICPGM
+ unsigned short VendorID = 0x04d8;
+ unsigned short ProductID = 0x0033;
+#endif
+
+
+#if 0
+int __cdecl main(int argc, char* argv[])
+{
+ int res;
+ unsigned char buf[65];
+
+ UNREFERENCED_PARAMETER(argc);
+ UNREFERENCED_PARAMETER(argv);
+
+ /* Set up the command buffer. */
+ memset(buf,0x00,sizeof(buf));
+ buf[0] = 0;
+ buf[1] = 0x81;
+
+
+ /* Open the device. */
+ int handle = open(VendorID, ProductID, L"12345");
+ if (handle < 0)
+ printf("unable to open device\n");
+
+
+ /* Toggle LED (cmd 0x80) */
+ buf[1] = 0x80;
+ res = write(handle, buf, 65);
+ if (res < 0)
+ printf("Unable to write()\n");
+
+ /* Request state (cmd 0x81) */
+ buf[1] = 0x81;
+ write(handle, buf, 65);
+ if (res < 0)
+ printf("Unable to write() (2)\n");
+
+ /* Read requested state */
+ read(handle, buf, 65);
+ if (res < 0)
+ printf("Unable to read()\n");
+
+ /* Print out the returned buffer. */
+ for (int i = 0; i < 4; i++)
+ printf("buf[%d]: %d\n", i, buf[i]);
+
+ return 0;
+}
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp
index 6c4b4d903..8aac6e49c 100644
--- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp
+++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp
@@ -169,7 +169,7 @@ void Fill3DHoneycomb::_fill_surface_single(
if (params.dont_connect)
append(polylines_out, std::move(polylines_chained));
else
- this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params);
+ this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, this->spacing, params);
}
}
diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp
index 88eba9a51..39679e3d6 100644
--- a/src/libslic3r/Fill/FillBase.cpp
+++ b/src/libslic3r/Fill/FillBase.cpp
@@ -2,6 +2,7 @@
#include "../ClipperUtils.hpp"
#include "../EdgeGrid.hpp"
+#include "../Geometry.hpp"
#include "../Surface.hpp"
#include "../PrintConfig.hpp"
#include "../libslic3r.h"
@@ -609,16 +610,15 @@ static inline SegmentPoint clip_start_segment_and_point(const Points &polyline,
// Initialized to "invalid".
SegmentPoint out;
if (polyline.size() >= 2) {
- const double d2 = distance * distance;
Vec2d pt_prev = polyline.front().cast<double>();
for (int i = 1; i < polyline.size(); ++ i) {
Vec2d pt = polyline[i].cast<double>();
Vec2d v = pt - pt_prev;
double l2 = v.squaredNorm();
- if (l2 > d2) {
+ if (l2 > distance * distance) {
out.idx_segment = i;
out.t = distance / sqrt(l2);
- out.point = pt + out.t * v;
+ out.point = pt_prev + out.t * v;
break;
}
distance -= sqrt(l2);
@@ -635,16 +635,17 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do
// Initialized to "invalid".
SegmentPoint out;
if (polyline.size() >= 2) {
- const double d2 = distance * distance;
Vec2d pt_next = polyline.back().cast<double>();
for (int i = int(polyline.size()) - 2; i >= 0; -- i) {
Vec2d pt = polyline[i].cast<double>();
Vec2d v = pt - pt_next;
double l2 = v.squaredNorm();
- if (l2 > d2) {
+ if (l2 > distance * distance) {
out.idx_segment = i;
out.t = distance / sqrt(l2);
- out.point = pt + out.t * v;
+ out.point = pt_next + out.t * v;
+ // Store the parameter referenced to the starting point of a segment.
+ out.t = 1. - out.t;
break;
}
distance -= sqrt(l2);
@@ -654,21 +655,26 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do
return out;
}
+// Optimized version with the precalculated v1 = p1b - p1a and l1_2 = v1.squaredNorm().
+// Assumption: l1_2 < EPSILON.
+static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &v1, const double l1_2, const Vec2d &p2)
+{
+ assert(l1_2 > EPSILON);
+ Vec2d v12 = p2 - p1a;
+ double t = v12.dot(v1);
+ return (t <= 0. ) ? v12.squaredNorm() :
+ (t >= l1_2) ? (p2 - p1a).squaredNorm() :
+ ((t / l1_2) * v1 - v12).squaredNorm();
+}
+
static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2)
{
- const Vec2d v = p1b - p1a;
- const Vec2d va = p2 - p1a;
- const double l2 = v.squaredNorm();
+ const Vec2d v = p1b - p1a;
+ const double l2 = v.squaredNorm();
if (l2 < EPSILON)
// p1a == p1b
- return va.squaredNorm();
- // Project p2 onto the (p1a, p1b) segment.
- const double t = va.dot(v);
- if (t < 0.)
- return va.squaredNorm();
- else if (t > l2)
- return (p2 - p1b).squaredNorm();
- return ((t / l2) * v - va).squaredNorm();
+ return (p2 - p1a).squaredNorm();
+ return segment_point_distance_squared(p1a, p1b, v, v.squaredNorm(), p2);
}
// Distance to the closest point of line.
@@ -684,43 +690,11 @@ static inline double min_distance_of_segments(const Vec2d &p1a, const Vec2d &p1b
double l2_2 = v2.squaredNorm();
if (l2_2 < EPSILON)
// p2a == p2b: Return distance of p2a from the (p1a, p1b) segment.
- return segment_point_distance_squared(p1a, p1b, p2a);
-
- // Project p2a, p2b onto the (p1a, p1b) segment.
- auto project_p2a_p2b_onto_seg_p1a_p1b = [](const Vec2d& p1a, const Vec2d& p1b, const Vec2d& p2a, const Vec2d& p2b, const Vec2d& v1, const double l1_2) {
- Vec2d v1a2a = p2a - p1a;
- Vec2d v1a2b = p2b - p1a;
- double t1 = v1a2a.dot(v1);
- double t2 = v1a2b.dot(v1);
- if (t1 <= 0.) {
- if (t2 <= 0.)
- // Both p2a and p2b are left of v1.
- return (((t1 < t2) ? p2b : p2a) - p1a).squaredNorm();
- else if (t2 < l1_2)
- // Project p2b onto the (p1a, p1b) segment.
- return ((t2 / l1_2) * v1 - v1a2b).squaredNorm();
- }
- else if (t1 >= l1_2) {
- if (t2 >= l1_2)
- // Both p2a and p2b are right of v1.
- return (((t1 < t2) ? p2a : p2b) - p1b).squaredNorm();
- else if (t2 < l1_2)
- // Project p2b onto the (p1a, p1b) segment.
- return ((t2 / l1_2) * v1 - v1a2b).squaredNorm();
- }
- else {
- // Project p1b onto the (p1a, p1b) segment.
- double dist_min = ((t2 / l1_2) * v1 - v1a2a).squaredNorm();
- if (t2 > 0. && t2 < l1_2)
- dist_min = std::min(dist_min, ((t2 / l1_2) * v1 - v1a2b).squaredNorm());
- return dist_min;
- }
- return std::numeric_limits<double>::max();
- };
+ return segment_point_distance_squared(p1a, p1b, v1, l1_2, p2a);
return std::min(
- project_p2a_p2b_onto_seg_p1a_p1b(p1a, p1b, p2a, p2b, v1, l1_2),
- project_p2a_p2b_onto_seg_p1a_p1b(p2a, p2b, p1a, p1b, v2, l2_2));
+ std::min(segment_point_distance_squared(p1a, p1b, v1, l1_2, p2a), segment_point_distance_squared(p1a, p1b, v1, l1_2, p2b)),
+ std::min(segment_point_distance_squared(p2a, p2b, v2, l2_2, p1a), segment_point_distance_squared(p2a, p2b, v2, l2_2, p1b)));
}
// Mark the segments of split boundary as consumed if they are very close to some of the infill line.
@@ -756,11 +730,26 @@ void mark_boundary_segments_touching_infill(
const Vec2d seg_pt2 = segment.second.cast<double>();
if (min_distance_of_segments(seg_pt1, seg_pt2, *this->pt1, *this->pt2) < this->dist2_max) {
// Mark this boundary segment as touching the infill line.
- ContourPointData&bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second];
+ ContourPointData &bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second];
bdp.segment_consumed = true;
// There is no need for checking seg_pt2 as it will be checked the next time.
- if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max)
+ bool point_touching = false;
+ if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max) {
+ point_touching = true;
bdp.point_consumed = true;
+ }
+#if 0
+ {
+ static size_t iRun = 0;
+ ExPolygon expoly(Polygon(*grid.contours().front()));
+ for (size_t i = 1; i < grid.contours().size(); ++i)
+ expoly.holes.emplace_back(Polygon(*grid.contours()[i]));
+ SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill", iRun ++).c_str(), get_extents(expoly));
+ svg.draw(expoly, "green");
+ svg.draw(Line(segment.first, segment.second), "red");
+ svg.draw(Line(this->pt1->cast<coord_t>(), this->pt2->cast<coord_t>()), "magenta");
+ }
+#endif
}
}
// Continue traversing the grid along the edge.
@@ -777,6 +766,9 @@ void mark_boundary_segments_touching_infill(
const Vec2d *pt2;
} visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding);
+ BoundingBoxf bboxf(boundary_bbox.min.cast<double>(), boundary_bbox.max.cast<double>());
+ bboxf.offset(- SCALED_EPSILON);
+
for (const Polyline &polyline : infill) {
// Clip the infill polyline by the Eucledian distance along the polyline.
SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance);
@@ -806,25 +798,39 @@ void mark_boundary_segments_touching_infill(
visitor.init(pt1d, pt2d);
grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor);
#else
- Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx].cast<double>();
- Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx].cast<double>();
+ Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx ].cast<double>();
+ Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx + 1].cast<double>();
+#if 0
+ {
+ static size_t iRun = 0;
+ ExPolygon expoly(Polygon(*grid.contours().front()));
+ for (size_t i = 1; i < grid.contours().size(); ++i)
+ expoly.holes.emplace_back(Polygon(*grid.contours()[i]));
+ SVG svg(debug_out_path("%s-%d.svg", "FillBase-mark_boundary_segments_touching_infill0", iRun ++).c_str(), get_extents(expoly));
+ svg.draw(expoly, "green");
+ svg.draw(polyline, "blue");
+ svg.draw(Line(pt1.cast<coord_t>(), pt2.cast<coord_t>()), "magenta", scale_(0.1));
+ }
+#endif
visitor.init(pt1, pt2);
// Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size.
Vec2d v = (pt2 - pt1).normalized() * distance_colliding;
Vec2d vperp(-v.y(), v.x());
Vec2d a = pt1 - v - vperp;
Vec2d b = pt1 + v - vperp;
- grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
+ if (Geometry::liang_barsky_line_clipping(a, b, bboxf))
+ grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
a = pt1 - v + vperp;
b = pt1 + v + vperp;
- grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
+ if (Geometry::liang_barsky_line_clipping(a, b, bboxf))
+ grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
#endif
}
}
}
}
-void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const FillParams &params)
+void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams &params)
{
assert(! infill_ordered.empty());
assert(! boundary_src.contour.points.empty());
@@ -900,16 +906,16 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
// Mark the points and segments of split boundary as consumed if they are very close to some of the infill line.
{
- //const double clip_distance = scale_(this->spacing);
- const double clip_distance = 3. * scale_(this->spacing);
- const double distance_colliding = scale_(this->spacing);
+ // @supermerill used 2. * scale_(spacing)
+ const double clip_distance = 3. * scale_(spacing);
+ const double distance_colliding = 1.1 * scale_(spacing);
mark_boundary_segments_touching_infill(boundary, boundary_data, bbox, infill_ordered, clip_distance, distance_colliding);
}
// Connection from end of one infill line to the start of another infill line.
- //const float length_max = scale_(this->spacing);
-// const float length_max = scale_((2. / params.density) * this->spacing);
- const float length_max = scale_((1000. / params.density) * this->spacing);
+ //const float length_max = scale_(spacing);
+// const float length_max = scale_((2. / params.density) * spacing);
+ const float length_max = scale_((1000. / params.density) * spacing);
std::vector<size_t> merged_with(infill_ordered.size());
for (size_t i = 0; i < merged_with.size(); ++ i)
merged_with[i] = i;
@@ -951,12 +957,26 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
size_t idx_chain_last = 0;
for (ConnectionCost &connection_cost : connections_sorted) {
- const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1];
- const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
+ const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1];
+ const std::pair<size_t, size_t> *cp1prev = cp1 - 1;
+ const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
+ const std::pair<size_t, size_t> *cp2next = cp2 + 1;
assert(cp1->first == cp2->first);
std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
if (connection_cost.reversed)
std::swap(cp1, cp2);
+ // Mark the the other end points of the segments to be taken as consumed temporarily, so they will not be crossed
+ // by the new connection line.
+ bool prev_marked = false;
+ bool next_marked = false;
+ if (cp1prev->first == cp1->first && ! contour_data[cp1prev->second].point_consumed) {
+ contour_data[cp1prev->second].point_consumed = true;
+ prev_marked = true;
+ }
+ if (cp2next->first == cp1->first && ! contour_data[cp2next->second].point_consumed) {
+ contour_data[cp2next->second].point_consumed = true;
+ next_marked = true;
+ }
if (could_take(contour_data, cp1->second, cp2->second)) {
// Indices of the polygons to be connected.
size_t idx_first = connection_cost.idx_first;
@@ -975,6 +995,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
// Mark the second polygon as merged with the first one.
merged_with[idx_second] = merged_with[idx_first];
}
+ if (prev_marked)
+ contour_data[cp1prev->second].point_consumed = false;
+ if (next_marked)
+ contour_data[cp2next->second].point_consumed = false;
}
polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
for (Polyline &pl : infill_ordered)
diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp
index d005f6e4a..517ce8383 100644
--- a/src/libslic3r/Fill/FillBase.hpp
+++ b/src/libslic3r/Fill/FillBase.hpp
@@ -111,9 +111,9 @@ protected:
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
- void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams &params);
-
public:
+ static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, double spacing, const FillParams &params);
+
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);
// Align a coordinate to a grid. The coordinate may be negative,
diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp
index ed0586edf..913b0b0c0 100644
--- a/src/libslic3r/Fill/FillGyroid.cpp
+++ b/src/libslic3r/Fill/FillGyroid.cpp
@@ -185,6 +185,7 @@ void FillGyroid::_fill_surface_single(
if (! polylines.empty())
// remove too small bits (larger than longer)
polylines.erase(
+ //FIXME what is the small size? Removing tiny extrusions disconnects walls!
std::remove_if(polylines.begin(), polylines.end(), [this](const Polyline &pl) { return pl.length() < scale_(this->spacing * 3); }),
polylines.end());
@@ -195,7 +196,7 @@ void FillGyroid::_fill_surface_single(
if (params.dont_connect)
append(polylines_out, std::move(polylines));
else
- this->connect_infill(std::move(polylines), expolygon, polylines_out, params);
+ this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
// new paths must be rotated back
if (abs(infill_angle) >= EPSILON) {
for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it)
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index 516dc2919..3cb89bedf 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -52,6 +52,7 @@ const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
+const std::string CUSTOM_GCODE_PER_HEIGHT_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_height.xml";
const char* MODEL_TAG = "model";
const char* RESOURCES_TAG = "resources";
@@ -421,6 +422,8 @@ namespace Slic3r {
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
+ void _extract_custom_gcode_per_height_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
+
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
@@ -635,6 +638,11 @@ namespace Slic3r {
// extract slic3r print config file
_extract_print_config_from_archive(archive, stat, config, filename);
}
+ if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_HEIGHT_FILE))
+ {
+ // extract slic3r layer config ranges file
+ _extract_custom_gcode_per_height_from_archive(archive, stat);
+ }
else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE))
{
// extract slic3r model config file
@@ -1155,6 +1163,43 @@ namespace Slic3r {
return true;
}
+ void _3MF_Importer::_extract_custom_gcode_per_height_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
+ {
+ if (stat.m_uncomp_size > 0)
+ {
+ std::string buffer((size_t)stat.m_uncomp_size, 0);
+ mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
+ if (res == 0) {
+ add_error("Error while reading custom Gcodes per height data to buffer");
+ return;
+ }
+
+ std::istringstream iss(buffer); // wrap returned xml to istringstream
+ pt::ptree main_tree;
+ pt::read_xml(iss, main_tree);
+
+ if (main_tree.front().first != "custom_gcodes_per_height")
+ return;
+ pt::ptree code_tree = main_tree.front().second;
+
+ if (!m_model->custom_gcode_per_height.empty())
+ m_model->custom_gcode_per_height.clear();
+
+ for (const auto& code : code_tree)
+ {
+ if (code.first != "code")
+ continue;
+ pt::ptree tree = code.second;
+ double height = tree.get<double>("<xmlattr>.height");
+ std::string gcode = tree.get<std::string>("<xmlattr>.gcode");
+ int extruder = tree.get<int>("<xmlattr>.extruder");
+ std::string color = tree.get<std::string>("<xmlattr>.color");
+
+ m_model->custom_gcode_per_height.push_back(Model::CustomGCode(height, gcode, extruder, color)) ;
+ }
+ }
+ }
+
void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
{
if (m_xml_parser == nullptr)
@@ -1568,8 +1613,10 @@ namespace Slic3r {
if (m_check_version && (m_version > VERSION_3MF))
{
- std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
- throw version_error(msg.c_str());
+ // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
+ // throw version_error(msg.c_str());
+ const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str();
+ throw version_error(msg);
}
}
@@ -1938,6 +1985,7 @@ namespace Slic3r {
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
+ bool _add_custom_gcode_per_height_file_to_archive(mz_zip_archive& archive, Model& model);
};
#if ENABLE_THUMBNAIL_GENERATOR
@@ -2048,6 +2096,15 @@ namespace Slic3r {
}
+ // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_height.xml").
+ // All custom gcode per height of whole Model are stored here
+ if (!_add_custom_gcode_per_height_file_to_archive(archive, model))
+ {
+ close_zip_writer(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
// Adds slic3r print config file ("Metadata/Slic3r_PE.config").
// This file contains the content of FullPrintConfing / SLAFullPrintConfig.
if (config != nullptr)
@@ -2432,7 +2489,7 @@ namespace Slic3r {
if (!tree.empty())
{
std::ostringstream oss;
- boost::property_tree::write_xml(oss, tree);
+ pt::write_xml(oss, tree);
out = oss.str();
// Post processing("beautification") of the output string for a better preview
@@ -2662,7 +2719,49 @@ namespace Slic3r {
return true;
}
- bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
+bool _3MF_Exporter::_add_custom_gcode_per_height_file_to_archive( mz_zip_archive& archive, Model& model)
+{
+ std::string out = "";
+
+ if (!model.custom_gcode_per_height.empty())
+ {
+ pt::ptree tree;
+ pt::ptree& main_tree = tree.add("custom_gcodes_per_height", "");
+
+ for (const Model::CustomGCode& code : model.custom_gcode_per_height)
+ {
+ pt::ptree& code_tree = main_tree.add("code", "");
+ // store minX and maxZ
+ code_tree.put("<xmlattr>.height" , code.height );
+ code_tree.put("<xmlattr>.gcode" , code.gcode );
+ code_tree.put("<xmlattr>.extruder" , code.extruder );
+ code_tree.put("<xmlattr>.color" , code.color );
+ }
+
+ if (!tree.empty())
+ {
+ std::ostringstream oss;
+ boost::property_tree::write_xml(oss, tree);
+ out = oss.str();
+
+ // Post processing("beautification") of the output string
+ boost::replace_all(out, "><", ">\n<");
+ }
+ }
+
+ if (!out.empty())
+ {
+ if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_HEIGHT_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add custom Gcodes per height file to archive");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
{
if ((path == nullptr) || (config == nullptr) || (model == nullptr))
return false;
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
index 181d6cb99..d50f6e395 100644
--- a/src/libslic3r/Format/AMF.cpp
+++ b/src/libslic3r/Format/AMF.cpp
@@ -16,6 +16,10 @@
#include "AMF.hpp"
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/xml_parser.hpp>
+namespace pt = boost::property_tree;
+
#include <boost/filesystem/operations.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/nowide/fstream.hpp>
@@ -147,6 +151,8 @@ struct AMFParserContext
NODE_TYPE_MIRRORY, // amf/constellation/instance/mirrory
NODE_TYPE_MIRRORZ, // amf/constellation/instance/mirrorz
NODE_TYPE_PRINTABLE, // amf/constellation/instance/mirrorz
+ NODE_TYPE_CUSTOM_GCODE, // amf/custom_code_per_height
+ NODE_TYPE_GCODE_PER_HEIGHT, // amf/custom_code_per_height/code
NODE_TYPE_METADATA, // anywhere under amf/*/metadata
};
@@ -227,7 +233,7 @@ struct AMFParserContext
// Current instance allocated for an amf/constellation/instance subtree.
Instance *m_instance;
// Generic string buffer for vertices, face indices, metadata etc.
- std::string m_value[3];
+ std::string m_value[4];
// Pointer to config to update if config data are stored inside the amf file
DynamicPrintConfig *m_config;
@@ -268,6 +274,8 @@ void AMFParserContext::startElement(const char *name, const char **atts)
}
} else if (strcmp(name, "constellation") == 0) {
node_type_new = NODE_TYPE_CONSTELLATION;
+ } else if (strcmp(name, "custom_gcodes_per_height") == 0) {
+ node_type_new = NODE_TYPE_CUSTOM_GCODE;
}
break;
case 2:
@@ -294,6 +302,13 @@ void AMFParserContext::startElement(const char *name, const char **atts)
}
else
this->stop();
+ }
+ else if (strcmp(name, "code") == 0 && m_path[1] == NODE_TYPE_CUSTOM_GCODE) {
+ node_type_new = NODE_TYPE_GCODE_PER_HEIGHT;
+ m_value[0] = get_attribute(atts, "height");
+ m_value[1] = get_attribute(atts, "gcode");
+ m_value[2] = get_attribute(atts, "extruder");
+ m_value[3] = get_attribute(atts, "color");
}
break;
case 3:
@@ -616,6 +631,19 @@ void AMFParserContext::endElement(const char * /* name */)
m_instance = nullptr;
break;
+ case NODE_TYPE_GCODE_PER_HEIGHT: {
+ double height = double(atof(m_value[0].c_str()));
+ const std::string& gcode = m_value[1];
+ int extruder = atoi(m_value[2].c_str());
+ const std::string& color = m_value[3];
+
+ m_model.custom_gcode_per_height.push_back(Model::CustomGCode(height, gcode, extruder, color));
+
+ for (std::string& val: m_value)
+ val.clear();
+ break;
+ }
+
case NODE_TYPE_METADATA:
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0)
m_config->load_from_gcode_string(m_value[1].c_str());
@@ -884,8 +912,10 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
if (check_version && (ctx.m_version > VERSION_AMF))
{
- std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
- throw std::runtime_error(msg.c_str());
+ // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
+ // throw std::runtime_error(msg.c_str());
+ const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str();
+ throw std::runtime_error(msg);
}
return true;
@@ -1190,6 +1220,42 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
stream << instances;
stream << " </constellation>\n";
}
+
+ if (!model->custom_gcode_per_height.empty())
+ {
+ std::string out = "";
+ pt::ptree tree;
+
+ pt::ptree& main_tree = tree.add("custom_gcodes_per_height", "");
+
+ for (const Model::CustomGCode& code : model->custom_gcode_per_height)
+ {
+ pt::ptree& code_tree = main_tree.add("code", "");
+ // store minX and maxZ
+ code_tree.put("<xmlattr>.height", code.height);
+ code_tree.put("<xmlattr>.gcode", code.gcode);
+ code_tree.put("<xmlattr>.extruder", code.extruder);
+ code_tree.put("<xmlattr>.color", code.color);
+ }
+
+ if (!tree.empty())
+ {
+ std::ostringstream oss;
+ pt::write_xml(oss, tree);
+ out = oss.str();
+
+ int del_header_pos = out.find("<custom_gcodes_per_height");
+ if (del_header_pos != std::string::npos)
+ out.erase(out.begin(), out.begin() + del_header_pos);
+
+ // Post processing("beautification") of the output string
+ boost::replace_all(out, "><code", ">\n <code");
+ boost::replace_all(out, "><", ">\n<");
+
+ stream << out << "\n";
+ }
+ }
+
stream << "</amf>\n";
std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf");
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 56a94b28c..91627631f 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -6,9 +6,6 @@
#include "Geometry.hpp"
#include "GCode/PrintExtents.hpp"
#include "GCode/WipeTower.hpp"
-#if ENABLE_THUMBNAIL_GENERATOR
-#include "GCode/ThumbnailData.hpp"
-#endif // ENABLE_THUMBNAIL_GENERATOR
#include "ShortestPath.hpp"
#include "Utils.hpp"
@@ -35,9 +32,7 @@
#include <Shiny/Shiny.h>
-#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
#include "miniz_extension.hpp"
-#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
#if 0
// Enable debugging and asserts, even in the release build.
@@ -432,39 +427,44 @@ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower:
Vec2f pos = tcr.start_pos;
Vec2f transformed_pos = pos;
Vec2f old_pos(-1000.1f, -1000.1f);
+ std::string never_skip_tag = WipeTower::never_skip_tag();
while (gcode_str) {
std::getline(gcode_str, line); // we read the gcode line by line
- // All G1 commands should be translated and rotated
+ // All G1 commands should be translated and rotated. X and Y coords are
+ // only pushed to the output when they differ from last time.
+ // WT generator can override this by appending the never_skip_tag
if (line.find("G1 ") == 0) {
+ bool never_skip = false;
+ auto it = line.find(never_skip_tag);
+ if (it != std::string::npos) {
+ // remove the tag and remember we saw it
+ never_skip = true;
+ line.erase(it, it+never_skip_tag.size());
+ }
std::ostringstream line_out;
std::istringstream line_str(line);
line_str >> std::noskipws; // don't skip whitespace
char ch = 0;
while (line_str >> ch) {
- if (ch == 'X')
- line_str >> pos.x();
+ if (ch == 'X' || ch =='Y')
+ line_str >> (ch == 'X' ? pos.x() : pos.y());
else
- if (ch == 'Y')
- line_str >> pos.y();
- else
- line_out << ch;
+ line_out << ch;
}
- transformed_pos = pos;
- transformed_pos = Eigen::Rotation2Df(angle) * transformed_pos;
- transformed_pos += translation;
+ transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
- if (transformed_pos != old_pos) {
+ if (transformed_pos != old_pos || never_skip) {
line = line_out.str();
std::ostringstream oss;
oss << std::fixed << std::setprecision(3) << "G1 ";
- if (transformed_pos.x() != old_pos.x())
+ if (transformed_pos.x() != old_pos.x() || never_skip)
oss << " X" << transformed_pos.x() - extruder_offset.x();
- if (transformed_pos.y() != old_pos.y())
+ if (transformed_pos.y() != old_pos.y() || never_skip)
oss << " Y" << transformed_pos.y() - extruder_offset.y();
-
+ oss << " ";
line.replace(line.find("G1 "), 3, oss.str());
old_pos = transformed_pos;
}
@@ -496,37 +496,37 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
assert(m_layer_idx == 0);
std::string gcode;
- if (&m_priming != nullptr) {
- // Disable linear advance for the wipe tower operations.
- //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
- for (const WipeTower::ToolChangeResult& tcr : m_priming) {
- if (!tcr.extrusions.empty())
- gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
+ // Disable linear advance for the wipe tower operations.
+ //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
+ for (const WipeTower::ToolChangeResult& tcr : m_priming) {
+ if (!tcr.extrusions.empty())
+ gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
- // Let the tool change be executed by the wipe tower class.
- // Inform the G-code writer about the changes done behind its back.
- //gcode += tcr.gcode;
- // Let the m_writer know the current extruder_id, but ignore the generated G-code.
- // unsigned int current_extruder_id = tcr.extrusions.back().tool;
- // gcodegen.writer().toolchange(current_extruder_id);
- // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
- }
+ // Let the tool change be executed by the wipe tower class.
+ // Inform the G-code writer about the changes done behind its back.
+ //gcode += tcr.gcode;
+ // Let the m_writer know the current extruder_id, but ignore the generated G-code.
+ // unsigned int current_extruder_id = tcr.extrusions.back().tool;
+ // gcodegen.writer().toolchange(current_extruder_id);
+ // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
- // A phony move to the end position at the wipe tower.
- /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Prepare a future wipe.
- gcodegen.m_wipe.path.points.clear();
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
- m_priming.back().end_pos.y)));*/
}
+
+ // A phony move to the end position at the wipe tower.
+ /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
+ // Prepare a future wipe.
+ gcodegen.m_wipe.path.points.clear();
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
+ m_priming.back().end_pos.y)));*/
+
return gcode;
}
@@ -631,7 +631,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
if (layer_to_print.print_z() > maximal_print_z + 2. * EPSILON)
throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" +
- _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) +
+ _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
"usually caused by negligibly small extrusions or by a faulty model. Try to repair "
" the model or change its orientation on the bed.")));
@@ -695,7 +695,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
}
#if ENABLE_THUMBNAIL_GENERATOR
-void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
+void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
#else
void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data)
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -725,7 +725,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
try {
m_placeholder_parser_failed_templates.clear();
#if ENABLE_THUMBNAIL_GENERATOR
- this->_do_export(*print, file, thumbnail_data);
+ this->_do_export(*print, file, thumbnail_cb);
#else
this->_do_export(*print, file);
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -793,9 +793,9 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
}
#if ENABLE_THUMBNAIL_GENERATOR
-void GCode::_do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data)
+void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb)
#else
-void GCode::_do_export(Print &print, FILE *file)
+void GCode::_do_export(Print& print, FILE* file)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
PROFILE_FUNC();
@@ -803,6 +803,7 @@ void GCode::_do_export(Print &print, FILE *file)
// resets time estimators
m_normal_time_estimator.reset();
m_normal_time_estimator.set_dialect(print.config().gcode_flavor);
+ m_normal_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]);
m_silent_time_estimator_enabled = (print.config().gcode_flavor == gcfMarlin) && print.config().silent_mode;
// Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
@@ -812,46 +813,47 @@ void GCode::_do_export(Print &print, FILE *file)
// shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
if (print.config().gcode_flavor.value == gcfMarlin) {
m_normal_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[0]);
- m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]);
- m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]);
- m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]);
- m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]);
- m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]);
- m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]);
- m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]);
- m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]);
- m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]);
- m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]);
- m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]);
- m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]);
- m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]);
- m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]);
- m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]);
+ m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]);
+ m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]);
+ m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]);
if (m_silent_time_estimator_enabled)
{
m_silent_time_estimator.reset();
m_silent_time_estimator.set_dialect(print.config().gcode_flavor);
- /* "Stealth mode" values can be just a copy of "normal mode" values
+ m_silent_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]);
+ /* "Stealth mode" values can be just a copy of "normal mode" values
* (when they aren't input for a printer preset).
* Thus, use back value from values, instead of second one, which could be absent
*/
- m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back());
- m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back());
- m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back());
- m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back());
- m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back());
- m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back());
- m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back());
- m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back());
- m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back());
- m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back());
- m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back());
- m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back());
- m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back());
- m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back());
- m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back());
- m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back());
+ m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back());
+ m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back());
+ m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back());
+ m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back());
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back());
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back());
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back());
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back());
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back());
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back());
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back());
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back());
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back());
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back());
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back());
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back());
if (print.config().single_extruder_multi_material) {
// As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
// are considered to be active for the single extruder multi-material printers only.
@@ -881,6 +883,9 @@ void GCode::_do_export(Print &print, FILE *file)
}
m_analyzer.set_extruder_offsets(extruder_offsets);
+ // tell analyzer about the extrusion axis
+ m_analyzer.set_extrusion_axis(print.config().get_extrusion_axis()[0]);
+
// send extruders count to analyzer to allow it to detect invalid extruder idxs
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(print.config().option("extruder_colour"));
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(print.config().option("filament_colour"));
@@ -909,7 +914,8 @@ void GCode::_do_export(Print &print, FILE *file)
std::sort(zs.begin(), zs.end());
m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
}
- } else {
+ }
+ else {
// Print all objects with the same print_z together.
std::vector<coordf_t> zs;
for (auto object : print.objects()) {
@@ -927,40 +933,41 @@ void GCode::_do_export(Print &print, FILE *file)
m_enable_cooling_markers = true;
this->apply_print_config(print.config());
this->set_extruders(print.extruders());
-
- // Initialize colorprint.
- m_colorprint_heights = cast<float>(print.config().colorprint_heights.values);
+
+ // Initialize custom gcode
+ Model* model = print.get_object(0)->model_object()->get_model();
+ m_custom_g_code_heights = model->custom_gcode_per_height;
// Initialize autospeed.
{
// get the minimum cross-section used in the print
std::vector<double> mm3_per_mm;
for (auto object : print.objects()) {
- for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
+ for (size_t region_id = 0; region_id < object->region_volumes.size(); ++region_id) {
const PrintRegion* region = print.regions()[region_id];
for (auto layer : object->layers()) {
const LayerRegion* layerm = layer->regions()[region_id];
- if (region->config().get_abs_value("perimeter_speed" ) == 0 ||
- region->config().get_abs_value("small_perimeter_speed" ) == 0 ||
- region->config().get_abs_value("external_perimeter_speed" ) == 0 ||
- region->config().get_abs_value("bridge_speed" ) == 0)
+ if (region->config().get_abs_value("perimeter_speed") == 0 ||
+ region->config().get_abs_value("small_perimeter_speed") == 0 ||
+ region->config().get_abs_value("external_perimeter_speed") == 0 ||
+ region->config().get_abs_value("bridge_speed") == 0)
mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
- if (region->config().get_abs_value("infill_speed" ) == 0 ||
- region->config().get_abs_value("solid_infill_speed" ) == 0 ||
- region->config().get_abs_value("top_solid_infill_speed" ) == 0 ||
- region->config().get_abs_value("bridge_speed" ) == 0)
+ if (region->config().get_abs_value("infill_speed") == 0 ||
+ region->config().get_abs_value("solid_infill_speed") == 0 ||
+ region->config().get_abs_value("top_solid_infill_speed") == 0 ||
+ region->config().get_abs_value("bridge_speed") == 0)
mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
}
}
- if (object->config().get_abs_value("support_material_speed" ) == 0 ||
- object->config().get_abs_value("support_material_interface_speed" ) == 0)
+ if (object->config().get_abs_value("support_material_speed") == 0 ||
+ object->config().get_abs_value("support_material_interface_speed") == 0)
for (auto layer : object->support_layers())
mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
}
print.throw_if_canceled();
// filter out 0-width segments
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
- if (! mm3_per_mm.empty()) {
+ if (!mm3_per_mm.empty()) {
// In order to honor max_print_speed we need to find a target volumetric
// speed that we can use throughout the print. So we define this target
// volumetric speed as the volumetric speed produced by printing the
@@ -973,7 +980,7 @@ void GCode::_do_export(Print &print, FILE *file)
}
}
print.throw_if_canceled();
-
+
m_cooling_buffer = make_unique<CoolingBuffer>(*this);
if (print.config().spiral_vase.value)
m_spiral_vase = make_unique<SpiralVase>(print.config());
@@ -991,15 +998,15 @@ void GCode::_do_export(Print &print, FILE *file)
#if ENABLE_THUMBNAIL_GENERATOR
// Write thumbnails using base64 encoding
- if (thumbnail_data != nullptr)
+ if (thumbnail_cb != nullptr)
{
const size_t max_row_length = 78;
-
- for (const ThumbnailData& data : *thumbnail_data)
+ ThumbnailsList thumbnails;
+ thumbnail_cb(thumbnails, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
+ for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
{
-#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
@@ -1025,39 +1032,6 @@ void GCode::_do_export(Print &print, FILE *file)
mz_free(png_data);
}
-#else
- _write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height);
-
- size_t row_size = 4 * data.width;
- for (int r = (int)data.height - 1; r >= 0; --r)
- {
- std::string encoded;
- encoded.resize(boost::beast::detail::base64::encoded_size(row_size));
- encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)(data.pixels.data() + r * row_size), row_size));
-
- unsigned int row_count = 0;
- while (encoded.size() > max_row_length)
- {
- if (row_count == 0)
- _write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
- else
- _write_format(file, ";>%s\n", encoded.substr(0, max_row_length).c_str());
-
- encoded = encoded.substr(max_row_length);
- ++row_count;
- }
-
- if (encoded.size() > 0)
- {
- if (row_count == 0)
- _write_format(file, "; %s\n", encoded.c_str());
- else
- _write_format(file, ";>%s\n", encoded.c_str());
- }
- }
-
- _write(file, "; thumbnail end\n;\n");
-#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
}
print.throw_if_canceled();
}
@@ -1150,6 +1124,47 @@ void GCode::_do_export(Print &print, FILE *file)
}
print.throw_if_canceled();
+ /* To avoid change filament for non-used extruder for Multi-material,
+ * check model->custom_gcode_per_height using tool_ordering values
+ * */
+ if (!m_custom_g_code_heights. empty())
+ {
+ bool delete_executed = false;
+ auto it = m_custom_g_code_heights.end();
+ while (it != m_custom_g_code_heights.begin())
+ {
+ --it;
+ if (it->gcode != ColorChangeCode)
+ continue;
+
+ auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(it->height));
+
+ bool used_extruder = false;
+ for (; it_layer_tools != tool_ordering.end(); it_layer_tools++)
+ {
+ const std::vector<unsigned>& extruders = it_layer_tools->extruders;
+ if (std::find(extruders.begin(), extruders.end(), (unsigned)(it->extruder-1)) != extruders.end())
+ {
+ used_extruder = true;
+ break;
+ }
+ }
+ if (used_extruder)
+ continue;
+
+ /* If we are there, current extruder wouldn't be used,
+ * so this color change is a redundant move.
+ * Delete this item from m_custom_g_code_heights
+ * */
+ it = m_custom_g_code_heights.erase(it);
+ delete_executed = true;
+ }
+
+ if (delete_executed)
+ model->custom_gcode_per_height = m_custom_g_code_heights;
+ }
+
+
m_cooling_buffer->set_current_extruder(initial_extruder_id);
// Emit machine envelope limits for the Marlin firmware.
@@ -1816,19 +1831,66 @@ void GCode::process_layer(
// In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all.
// (Layers can be close to each other, model could have been resliced with bigger layer height, ...).
bool colorprint_change = false;
- while (!m_colorprint_heights.empty() && m_colorprint_heights.front()-EPSILON < layer.print_z) {
- m_colorprint_heights.erase(m_colorprint_heights.begin());
+
+ std::string custom_code = "";
+ std::string pause_print_msg = "";
+ int m600_before_extruder = -1;
+ while (!m_custom_g_code_heights.empty() && m_custom_g_code_heights.front().height-EPSILON < layer.print_z) {
+ custom_code = m_custom_g_code_heights.front().gcode;
+
+ if (custom_code == ColorChangeCode && m_custom_g_code_heights.front().extruder > 0)
+ m600_before_extruder = m_custom_g_code_heights.front().extruder - 1;
+ if (custom_code == PausePrintCode)
+ pause_print_msg = m_custom_g_code_heights.front().color;
+
+ m_custom_g_code_heights.erase(m_custom_g_code_heights.begin());
colorprint_change = true;
}
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
- if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1)
- {
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Color_Change_Tag + "\n";
- // add tag for time estimator
- gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
- gcode += "M600\n";
+
+ // don't save "tool_change"(ExtruderChangeCode) code to GCode
+ if (colorprint_change && custom_code != ExtruderChangeCode) {
+ const bool single_material_print = print.config().nozzle_diameter.size() == 1;
+
+ if (custom_code == ColorChangeCode) // color change
+ {
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_before_extruder) + "\n";
+ // add tag for time estimator
+ gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
+
+ if (!single_material_print && m600_before_extruder >= 0 && first_extruder_id != m600_before_extruder
+ // && !MMU1
+ ) {
+ //! FIXME_in_fw show message during print pause
+ gcode += "M601\n"; // pause print
+ gcode += "M117 Change filament for Extruder " + std::to_string(m600_before_extruder) + "\n";
+ }
+ else
+ gcode += custom_code + "\n";
+ }
+ else
+ {
+ if (custom_code == PausePrintCode) // Pause print
+ {
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
+ //! FIXME_in_fw show message during print pause
+ if (!pause_print_msg.empty())
+ gcode += "M117 " + pause_print_msg + "\n";
+ // add tag for time estimator
+ //gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
+ }
+ else // custom Gcode
+ {
+ // add tag for analyzer
+ gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
+ // add tag for time estimator
+ //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
+ }
+ gcode += custom_code + "\n";
+ }
}
@@ -2138,6 +2200,12 @@ void GCode::process_layer(
if (m_cooling_buffer)
gcode = m_cooling_buffer->process_layer(gcode, layer.id());
+ // add tag for analyzer
+ if (gcode.find(GCodeAnalyzer::Pause_Print_Tag) != gcode.npos)
+ gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
+ else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos)
+ gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
+
#ifdef HAS_PRESSURE_EQUALIZER
// Apply pressure equalization if enabled;
// printf("G-code before filter:\n%s\n", gcode.c_str());
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 19ec5be3c..7f009b814 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -17,6 +17,9 @@
#include "GCodeTimeEstimator.hpp"
#include "EdgeGrid.hpp"
#include "GCode/Analyzer.hpp"
+#if ENABLE_THUMBNAIL_GENERATOR
+#include "GCode/ThumbnailData.hpp"
+#endif // ENABLE_THUMBNAIL_GENERATOR
#include <memory>
#include <string>
@@ -30,9 +33,6 @@ namespace Slic3r {
// Forward declarations.
class GCode;
class GCodePreviewData;
-#if ENABLE_THUMBNAIL_GENERATOR
-struct ThumbnailData;
-#endif // ENABLE_THUMBNAIL_GENERATOR
class AvoidCrossingPerimeters {
public:
@@ -167,7 +167,7 @@ public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
#if ENABLE_THUMBNAIL_GENERATOR
- void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
+ void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
#else
void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -199,7 +199,7 @@ public:
protected:
#if ENABLE_THUMBNAIL_GENERATOR
- void _do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data);
+ void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb);
#else
void _do_export(Print &print, FILE *file);
#endif //ENABLE_THUMBNAIL_GENERATOR
@@ -362,9 +362,12 @@ protected:
bool m_second_layer_things_done;
// Index of a last object copy extruded.
std::pair<const PrintObject*, Point> m_last_obj_copy;
- // Layer heights for colorprint - updated before the export and erased during the process
- // so no toolchange occurs twice.
- std::vector<float> m_colorprint_heights;
+ /* Extensions for colorprint - now it's not a just color_print_heights,
+ * there can be some custom gcode.
+ * Updated before the export and erased during the process,
+ * so no toolchange occurs twice.
+ * */
+ std::vector<Model::CustomGCode> m_custom_g_code_heights;
// Time estimators
GCodeTimeEstimator m_normal_time_estimator;
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
index 3f0b8735f..7b8004ab0 100644
--- a/src/libslic3r/GCode/Analyzer.cpp
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -29,6 +29,9 @@ const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:";
const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:";
const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:";
const std::string GCodeAnalyzer::Color_Change_Tag = "_ANALYZER_COLOR_CHANGE";
+const std::string GCodeAnalyzer::Pause_Print_Tag = "_ANALYZER_PAUSE_PRINT";
+const std::string GCodeAnalyzer::Custom_Code_Tag = "_ANALYZER_CUSTOM_CODE";
+const std::string GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag = "_ANALYZER_END_PAUSE_PRINT_OR_CUSTOM_CODE";
const double GCodeAnalyzer::Default_mm3_per_mm = 0.0;
const float GCodeAnalyzer::Default_Width = 0.0f;
@@ -105,24 +108,11 @@ GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::
{
}
-GCodeAnalyzer::GCodeAnalyzer()
-{
- reset();
-}
-
-void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap& extruder_offsets)
-{
- m_extruder_offsets = extruder_offsets;
-}
-
void GCodeAnalyzer::set_extruders_count(unsigned int count)
{
m_extruders_count = count;
-}
-
-void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor)
-{
- m_gcode_flavor = flavor;
+ for (unsigned int i=0; i<m_extruders_count; i++)
+ m_extruder_color[i] = i;
}
void GCodeAnalyzer::reset()
@@ -147,6 +137,7 @@ void GCodeAnalyzer::reset()
m_moves_map.clear();
m_extruder_offsets.clear();
m_extruders_count = 1;
+ m_extruder_color.clear();
}
const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode)
@@ -595,7 +586,11 @@ void GCodeAnalyzer::_processT(const std::string& cmd)
BOOST_LOG_TRIVIAL(error) << "GCodeAnalyzer encountered an invalid toolchange, maybe from a custom gcode.";
}
else
+ {
_set_extruder_id(id);
+ if (_get_cp_color_id() != INT_MAX)
+ _set_cp_color_id(m_extruder_color[id]);
+ }
// stores tool change move
_store_move(GCodeMove::Tool_change);
@@ -648,7 +643,33 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
pos = comment.find(Color_Change_Tag);
if (pos != comment.npos)
{
- _process_color_change_tag();
+ pos = comment.find_last_of(",T");
+ int extruder = pos == comment.npos ? 0 : std::atoi(comment.substr(pos + 1, comment.npos).c_str());
+ _process_color_change_tag(extruder);
+ return true;
+ }
+
+ // color change tag
+ pos = comment.find(Pause_Print_Tag);
+ if (pos != comment.npos)
+ {
+ _process_pause_print_or_custom_code_tag();
+ return true;
+ }
+
+ // color change tag
+ pos = comment.find(Custom_Code_Tag);
+ if (pos != comment.npos)
+ {
+ _process_pause_print_or_custom_code_tag();
+ return true;
+ }
+
+ // color change tag
+ pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag);
+ if (pos != comment.npos)
+ {
+ _process_end_pause_print_or_custom_code_tag();
return true;
}
@@ -681,10 +702,24 @@ void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos)
_set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr));
}
-void GCodeAnalyzer::_process_color_change_tag()
+void GCodeAnalyzer::_process_color_change_tag(int extruder)
+{
+ m_extruder_color[extruder] = m_extruders_count + m_state.cp_color_counter; // color_change position in list of color for preview
+ m_state.cp_color_counter++;
+
+ if (_get_extruder_id() == extruder)
+ _set_cp_color_id(m_extruder_color[extruder]);
+}
+
+void GCodeAnalyzer::_process_pause_print_or_custom_code_tag()
+{
+ _set_cp_color_id(INT_MAX);
+}
+
+void GCodeAnalyzer::_process_end_pause_print_or_custom_code_tag()
{
- m_state.cur_cp_color_id++;
- _set_cp_color_id(m_state.cur_cp_color_id);
+ if (_get_cp_color_id() == INT_MAX)
+ _set_cp_color_id(m_extruder_color[_get_extruder_id()]);
}
void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units)
diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp
index df4f6f652..781c3f265 100644
--- a/src/libslic3r/GCode/Analyzer.hpp
+++ b/src/libslic3r/GCode/Analyzer.hpp
@@ -20,6 +20,9 @@ public:
static const std::string Width_Tag;
static const std::string Height_Tag;
static const std::string Color_Change_Tag;
+ static const std::string Pause_Print_Tag;
+ static const std::string Custom_Code_Tag;
+ static const std::string End_Pause_Print_Or_Custom_Code_Tag;
static const double Default_mm3_per_mm;
static const float Default_Width;
@@ -89,6 +92,7 @@ public:
typedef std::vector<GCodeMove> GCodeMovesList;
typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap;
typedef std::map<unsigned int, Vec2d> ExtruderOffsetsMap;
+ typedef std::map<unsigned int, unsigned int> ExtruderToColorMap;
private:
struct State
@@ -102,7 +106,7 @@ private:
float start_extrusion;
float position[Num_Axis];
float origin[Num_Axis];
- unsigned int cur_cp_color_id = 0;
+ unsigned int cp_color_counter = 0;
};
private:
@@ -113,16 +117,20 @@ private:
unsigned int m_extruders_count;
GCodeFlavor m_gcode_flavor;
+ ExtruderToColorMap m_extruder_color;
+
// The output of process_layer()
std::string m_process_output;
public:
- GCodeAnalyzer();
+ GCodeAnalyzer() { reset(); }
- void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets);
+ void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets) { m_extruder_offsets = extruder_offsets; }
void set_extruders_count(unsigned int count);
- void set_gcode_flavor(const GCodeFlavor& flavor);
+ void set_extrusion_axis(char axis) { m_parser.set_extrusion_axis(axis); }
+
+ void set_gcode_flavor(const GCodeFlavor& flavor) { m_gcode_flavor = flavor; }
// Reinitialize the analyzer
void reset();
@@ -212,7 +220,13 @@ private:
void _process_height_tag(const std::string& comment, size_t pos);
// Processes color change tag
- void _process_color_change_tag();
+ void _process_color_change_tag(int extruder);
+
+ // Processes pause print and custom gcode tag
+ void _process_pause_print_or_custom_code_tag();
+
+ // Processes new layer tag
+ void _process_end_pause_print_or_custom_code_tag();
void _set_units(EUnits units);
EUnits _get_units() const;
diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp
index 53c13a2f2..e7475089a 100644
--- a/src/libslic3r/GCode/PreviewData.cpp
+++ b/src/libslic3r/GCode/PreviewData.cpp
@@ -379,7 +379,8 @@ std::string GCodePreviewData::get_legend_title() const
return "";
}
-GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector<float>& tool_colors, const std::vector</*double*/std::pair<double, double>>& cp_values) const
+GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector<float>& tool_colors,
+ const std::vector<std::string>& cp_items) const
{
struct Helper
{
@@ -455,31 +456,25 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
case Extrusion::ColorPrint:
{
const int color_cnt = (int)tool_colors.size()/4;
-
- const auto color_print_cnt = (int)cp_values.size();
- for (int i = color_print_cnt; i >= 0 ; --i)
+ const auto color_print_cnt = (int)cp_items.size();
+ if (color_print_cnt == 1) // means "Default print color"
{
- GCodePreviewData::Color color;
- ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (i % color_cnt) * 4), 4 * sizeof(float));
-
- if (color_print_cnt == 0) {
- items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color);
- break;
- }
+ Color color;
+ ::memcpy((void*)color.rgba, (const void*)(tool_colors.data()), 4 * sizeof(float));
- std::string id_str = std::to_string(i + 1) + ": ";
+ items.emplace_back(cp_items[0], color);
+ break;
+ }
- if (i == 0) {
- items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color);
- break;
- }
- if (i == color_print_cnt) {
- items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color);
- continue;
- }
+ if (color_cnt != color_print_cnt)
+ break;
-// items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1] % cp_values[i]).str(), color);
- items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color);
+ for (int i = 0 ; i < color_print_cnt; ++i)
+ {
+ Color color;
+ ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float));
+
+ items.emplace_back(cp_items[i], color);
}
break;
}
diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp
index 70b6edffd..725c0258d 100644
--- a/src/libslic3r/GCode/PreviewData.hpp
+++ b/src/libslic3r/GCode/PreviewData.hpp
@@ -237,7 +237,7 @@ public:
void set_extrusion_paths_colors(const std::vector<std::string>& colors);
std::string get_legend_title() const;
- LegendItemsList get_legend_items(const std::vector<float>& tool_colors, const std::vector</*double*/std::pair<double, double>>& cp_values) const;
+ LegendItemsList get_legend_items(const std::vector<float>& tool_colors, const std::vector<std::string>& cp_items) const;
// Return an estimate of the memory consumed by the time estimator.
size_t memory_used() const;
diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp
index 9823ffd31..4acfe4374 100644
--- a/src/libslic3r/GCode/ThumbnailData.hpp
+++ b/src/libslic3r/GCode/ThumbnailData.hpp
@@ -4,6 +4,7 @@
#if ENABLE_THUMBNAIL_GENERATOR
#include <vector>
+#include "libslic3r/Point.hpp"
namespace Slic3r {
@@ -20,6 +21,9 @@ struct ThumbnailData
bool is_valid() const;
};
+typedef std::vector<ThumbnailData> ThumbnailsList;
+typedef std::function<void(ThumbnailsList & thumbnails, const Vec2ds & sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)> ThumbnailsGeneratorCallback;
+
} // namespace Slic3r
#endif // ENABLE_THUMBNAIL_GENERATOR
diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp
index cd326b9a0..5ee577482 100644
--- a/src/libslic3r/GCode/WipeTower.cpp
+++ b/src/libslic3r/GCode/WipeTower.cpp
@@ -1004,9 +1004,10 @@ void WipeTower::toolchange_Change(
writer.append("[toolchange_gcode]\n");
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
- // gcode could have left the extruder somewhere, we cannot just start extruding.
- Vec2f current_pos = writer.pos_rotated();
- writer.append(std::string("G1 X") + std::to_string(current_pos.x()) + " Y" + std::to_string(current_pos.y()) + "\n");
+ // gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
+ // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
+ Vec2f current_pos = writer.pos_rotated();
+ writer.append(std::string("G1 X") + std::to_string(current_pos.x()) + " Y" + std::to_string(current_pos.y()) + never_skip_tag() + "\n");
// The toolchange Tn command will be inserted later, only in case that the user does
// not provide a custom toolchange gcode.
diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp
index 311490055..e6832958e 100644
--- a/src/libslic3r/GCode/WipeTower.hpp
+++ b/src/libslic3r/GCode/WipeTower.hpp
@@ -17,9 +17,12 @@ class PrintConfig;
enum GCodeFlavor : unsigned char;
+
class WipeTower
{
public:
+ static char const* never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
+
struct Extrusion
{
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
@@ -96,6 +99,8 @@ public:
+
+
// Switch to a next layer.
void set_layer(
// Print height of this layer.
diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp
index 24f979267..fea581e20 100644
--- a/src/libslic3r/GCodeReader.hpp
+++ b/src/libslic3r/GCodeReader.hpp
@@ -119,6 +119,7 @@ public:
float f() const { return m_position[F]; }
char extrusion_axis() const { return m_extrusion_axis; }
+ void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
private:
const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command);
diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp
index 0219c87d1..bd997a047 100644
--- a/src/libslic3r/GCodeTimeEstimator.hpp
+++ b/src/libslic3r/GCodeTimeEstimator.hpp
@@ -342,6 +342,8 @@ namespace Slic3r {
void increment_g1_line_id();
void reset_g1_line_id();
+ void set_extrusion_axis(char axis) { m_parser.set_extrusion_axis(axis); }
+
void set_extruder_id(unsigned int id);
unsigned int get_extruder_id() const;
void reset_extruder_id();
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index d996658f2..283d1edb0 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -137,6 +137,79 @@ inline bool segments_intersect(
segments_could_intersect(jp1, jp2, ip1, ip2) <= 0;
}
+// Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html
+template<typename T>
+bool liang_barsky_line_clipping(
+ // Start and end points of the source line, result will be stored there as well.
+ Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0,
+ Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1,
+ // Bounding box to clip with.
+ const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox)
+{
+ Eigen::Matrix<T, 2, 1, Eigen::DontAlign> v = x1 - x0;
+ double t0 = 0.0;
+ double t1 = 1.0;
+
+ // Traverse through left, right, bottom, top edges.
+ for (int edge = 0; edge < 4; ++ edge)
+ {
+ double p, q;
+ switch (edge) {
+ case 0: p = - v.x(); q = - bbox.min.x() + x0.x(); break;
+ case 1: p = v.x(); q = bbox.max.x() - x0.x(); break;
+ case 2: p = - v.y(); q = - bbox.min.y() + x0.y(); break;
+ default: p = v.y(); q = bbox.max.y() - x0.y(); break;
+ }
+
+ if (p == 0) {
+ if (q < 0)
+ // Line parallel to the bounding box edge is fully outside of the bounding box.
+ return false;
+ // else don't clip
+ } else {
+ double r = q / p;
+ if (p < 0) {
+ if (r > t1)
+ // Fully clipped.
+ return false;
+ if (r > t0)
+ // Partially clipped.
+ t0 = r;
+ } else {
+ assert(p > 0);
+ if (r < t0)
+ // Fully clipped.
+ return false;
+ if (r < t1)
+ // Partially clipped.
+ t1 = r;
+ }
+ }
+ }
+
+ // Clipped successfully.
+ x1 = x0 + t1 * v;
+ x0 += t0 * v;
+ return true;
+}
+
+// Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html
+template<typename T>
+bool liang_barsky_line_clipping(
+ // Start and end points of the source line.
+ const Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0src,
+ const Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1src,
+ // Bounding box to clip with.
+ const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox,
+ // Start and end points of the clipped line.
+ Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0clip,
+ Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1clip)
+{
+ x0clip = x0src;
+ x1clip = x1src;
+ return liang_barsky_line_clipping(x0clip, x1clip, bbox);
+}
+
Pointf3s convex_hull(Pointf3s points);
Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons);
diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp
index e5f7b8fa9..05cbfee4e 100644
--- a/src/libslic3r/Line.cpp
+++ b/src/libslic3r/Line.cpp
@@ -107,6 +107,17 @@ bool Line::intersection(const Line &l2, Point *intersection) const
return false; // not intersecting
}
+bool Line::clip_with_bbox(const BoundingBox &bbox)
+{
+ Vec2d x0clip, x1clip;
+ bool result = Geometry::liang_barsky_line_clipping<double>(this->a.cast<double>(), this->b.cast<double>(), BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>()), x0clip, x1clip);
+ if (result) {
+ this->a = x0clip.cast<coord_t>();
+ this->b = x1clip.cast<coord_t>();
+ }
+ return result;
+}
+
Vec3d Linef3::intersect_plane(double z) const
{
auto v = (this->b - this->a).cast<double>();
diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp
index 559ca946a..06809c02a 100644
--- a/src/libslic3r/Line.hpp
+++ b/src/libslic3r/Line.hpp
@@ -6,6 +6,7 @@
namespace Slic3r {
+class BoundingBox;
class Line;
class Line3;
class Linef3;
@@ -43,6 +44,8 @@ public:
Vector normal() const { return Vector((this->b(1) - this->a(1)), -(this->b(0) - this->a(0))); }
bool intersection(const Line& line, Point* intersection) const;
double ccw(const Point& point) const { return point.ccw(*this); }
+ // Clip a line with a bounding box. Returns false if the line is completely outside of the bounding box.
+ bool clip_with_bbox(const BoundingBox &bbox);
static double distance_to_squared(const Point &point, const Point &a, const Point &b);
static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); }
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 9a26fbaaf..f218bfa6c 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -41,6 +41,9 @@ Model& Model::assign_copy(const Model &rhs)
mo->set_model(this);
this->objects.emplace_back(mo);
}
+
+ // copy custom code per height
+ this->custom_gcode_per_height = rhs.custom_gcode_per_height;
return *this;
}
@@ -59,6 +62,9 @@ Model& Model::assign_copy(Model &&rhs)
for (ModelObject *model_object : this->objects)
model_object->set_model(this);
rhs.objects.clear();
+
+ // copy custom code per height
+ this->custom_gcode_per_height = rhs.custom_gcode_per_height;
return *this;
}
@@ -586,6 +592,22 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte
return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string();
}
+std::vector<std::pair<double, DynamicPrintConfig>> Model::get_custom_tool_changes(double default_layer_height, size_t num_extruders) const
+{
+ std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes;
+ if (!custom_gcode_per_height.empty()) {
+ for (const CustomGCode& custom_gcode : custom_gcode_per_height)
+ if (custom_gcode.gcode == ExtruderChangeCode) {
+ DynamicPrintConfig config;
+ // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
+ config.set_key_value("extruder", new ConfigOptionInt(custom_gcode.extruder > num_extruders ? 0 : custom_gcode.extruder));
+ // For correct extruders(tools) changing, we should decrease custom_gcode.height value by one default layer height
+ custom_tool_changes.push_back({ custom_gcode.height - default_layer_height, config });
+ }
+ }
+ return custom_tool_changes;
+}
+
ModelObject::~ModelObject()
{
this->clear_volumes();
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index ccc48d52b..a9adbe0dc 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -749,6 +749,37 @@ public:
ModelObjectPtrs objects;
// Wipe tower object.
ModelWipeTower wipe_tower;
+
+ // Extensions for color print
+ struct CustomGCode
+ {
+ CustomGCode(double height, const std::string& code, int extruder, const std::string& color) :
+ height(height), gcode(code), extruder(extruder), color(color) {}
+
+ bool operator<(const CustomGCode& other) const { return other.height > this->height; }
+ bool operator==(const CustomGCode& other) const
+ {
+ return (other.height == this->height) &&
+ (other.gcode == this->gcode) &&
+ (other.extruder == this->extruder )&&
+ (other.color == this->color );
+ }
+ bool operator!=(const CustomGCode& other) const
+ {
+ return (other.height != this->height) ||
+ (other.gcode != this->gcode) ||
+ (other.extruder != this->extruder )||
+ (other.color != this->color );
+ }
+
+ double height;
+ std::string gcode;
+ int extruder; // 0 - "gcode" will be applied for whole print
+ // else - "gcode" will be applied only for "extruder" print
+ std::string color; // if gcode is equal to PausePrintCode,
+ // this field is used for save a short message shown on Printer display
+ };
+ std::vector<CustomGCode> custom_gcode_per_height;
// Default constructor assigns a new ID to the model.
Model() { assert(this->id().valid()); }
@@ -814,6 +845,9 @@ public:
// Propose an output path, replace extension. The new_extension shall contain the initial dot.
std::string propose_export_file_name_and_path(const std::string &new_extension) const;
+ // from custom_gcode_per_height get just tool_change codes
+ std::vector<std::pair<double, DynamicPrintConfig>> get_custom_tool_changes(double default_layer_height, size_t num_extruders) const;
+
private:
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
void assign_new_unique_ids_recursive();
diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp
index 42bc6c2f1..45a80671c 100644
--- a/src/libslic3r/MotionPlanner.cpp
+++ b/src/libslic3r/MotionPlanner.cpp
@@ -319,7 +319,7 @@ Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) c
std::vector<size_t> map_node_to_queue_id(m_adjacency_list.size(), size_t(-1));
distance[node_start] = 0.;
- auto queue = make_mutable_priority_queue<node_t>(
+ auto queue = make_mutable_priority_queue<node_t, false>(
[&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; },
[&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; });
queue.reserve(m_adjacency_list.size());
diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp
index da469b7ba..b20bf60ea 100644
--- a/src/libslic3r/MutablePriorityQueue.hpp
+++ b/src/libslic3r/MutablePriorityQueue.hpp
@@ -3,7 +3,7 @@
#include <assert.h>
-template<typename T, typename IndexSetter, typename LessPredicate>
+template<typename T, typename IndexSetter, typename LessPredicate, const bool ResetIndexWhenRemoved = false>
class MutablePriorityQueue
{
public:
@@ -42,26 +42,30 @@ private:
LessPredicate m_less_predicate;
};
-template<typename T, typename IndexSetter, typename LessPredicate>
-MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(IndexSetter &&index_setter, LessPredicate &&less_predicate)
+template<typename T, const bool ResetIndexWhenRemoved, typename IndexSetter, typename LessPredicate>
+MutablePriorityQueue<T, IndexSetter, LessPredicate, ResetIndexWhenRemoved> make_mutable_priority_queue(IndexSetter &&index_setter, LessPredicate &&less_predicate)
{
- return MutablePriorityQueue<T, IndexSetter, LessPredicate>(
+ return MutablePriorityQueue<T, IndexSetter, LessPredicate, ResetIndexWhenRemoved>(
std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::clear()
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::clear()
{
-#ifndef NDEBUG
- for (size_t idx = 0; idx < m_heap.size(); ++ idx)
- // Mark as removed from the queue.
- m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
+#ifdef NDEBUG
+ // Only mark as removed from the queue in release mode, if configured so.
+ if (ResetIndexWhenRemoved)
#endif /* NDEBUG */
+ {
+ for (size_t idx = 0; idx < m_heap.size(); ++ idx)
+ // Mark as removed from the queue.
+ m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
+ }
m_heap.clear();
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::push(const T &item)
{
size_t idx = m_heap.size();
m_heap.emplace_back(item);
@@ -69,8 +73,8 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &i
update_heap_up(0, idx);
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(T &&item)
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::push(T &&item)
{
size_t idx = m_heap.size();
m_heap.emplace_back(std::move(item));
@@ -78,14 +82,18 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(T &&item)
update_heap_up(0, idx);
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::pop()
{
assert(! m_heap.empty());
-#ifndef NDEBUG
- // Mark as removed from the queue.
- m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
+#ifdef NDEBUG
+ // Only mark as removed from the queue in release mode, if configured so.
+ if (ResetIndexWhenRemoved)
#endif /* NDEBUG */
+ {
+ // Mark as removed from the queue.
+ m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
+ }
if (m_heap.size() > 1) {
m_heap.front() = m_heap.back();
m_heap.pop_back();
@@ -95,14 +103,18 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
m_heap.clear();
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::remove(size_t idx)
{
assert(idx < m_heap.size());
-#ifndef NDEBUG
- // Mark as removed from the queue.
- m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
+#ifdef NDEBUG
+ // Only mark as removed from the queue in release mode, if configured so.
+ if (ResetIndexWhenRemoved)
#endif /* NDEBUG */
+ {
+ // Mark as removed from the queue.
+ m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
+ }
if (idx + 1 == m_heap.size()) {
m_heap.pop_back();
return;
@@ -114,8 +126,8 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t i
update_heap_up(0, idx);
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_up(size_t top, size_t bottom)
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::update_heap_up(size_t top, size_t bottom)
{
size_t childIdx = bottom;
T *child = &m_heap[childIdx];
@@ -138,8 +150,8 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_up(
}
}
-template<class T, class LessPredicate, class IndexSetter>
-inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_down(size_t top, size_t bottom)
+template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::update_heap_down(size_t top, size_t bottom)
{
size_t parentIdx = top;
T *parent = &m_heap[parentIdx];
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 78a09c511..ae32bd2d3 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -637,11 +637,59 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
else
m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
}
+
+ // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs,
+ // considering custom_tool_change values
+ void assign(const t_layer_config_ranges &in, const std::vector<std::pair<double, DynamicPrintConfig>> &custom_tool_changes) {
+ m_ranges.clear();
+ m_ranges.reserve(in.size());
+ // Input ranges are sorted lexicographically. First range trims the other ranges.
+ coordf_t last_z = 0;
+ for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in)
+ if (range.first.second > last_z) {
+ coordf_t min_z = std::max(range.first.first, 0.);
+ if (min_z > last_z + EPSILON) {
+ m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr);
+ last_z = min_z;
+ }
+ if (range.first.second > last_z + EPSILON) {
+ const DynamicPrintConfig* cfg = &range.second;
+ m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg);
+ last_z = range.first.second;
+ }
+ }
+
+ // add ranges for extruder changes from custom_tool_changes
+ for (size_t i = 0; i < custom_tool_changes.size(); i++) {
+ const DynamicPrintConfig* cfg = &custom_tool_changes[i].second;
+ coordf_t cur_Z = custom_tool_changes[i].first;
+ coordf_t next_Z = i == custom_tool_changes.size()-1 ? DBL_MAX : custom_tool_changes[i+1].first;
+ if (cur_Z > last_z + EPSILON) {
+ if (i==0)
+ m_ranges.emplace_back(t_layer_height_range(last_z, cur_Z), nullptr);
+ m_ranges.emplace_back(t_layer_height_range(cur_Z, next_Z), cfg);
+ }
+ else if (next_Z > last_z + EPSILON)
+ m_ranges.emplace_back(t_layer_height_range(last_z, next_Z), cfg);
+ }
+
+ if (m_ranges.empty())
+ m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr);
+ else if (m_ranges.back().second == nullptr)
+ m_ranges.back().first.second = DBL_MAX;
+ else if (m_ranges.back().first.second != DBL_MAX)
+ m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
+ }
const DynamicPrintConfig* config(const t_layer_height_range &range) const {
auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr));
- assert(it != m_ranges.end());
- assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON);
- assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON);
+ // #ys_FIXME_COLOR
+ // assert(it != m_ranges.end());
+ // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON);
+ // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON);
+ if (it == m_ranges.end() ||
+ std::abs(it->first.first - range.first) > EPSILON ||
+ std::abs(it->first.second - range.second) > EPSILON )
+ return nullptr; // desired range doesn't found
return (it == m_ranges.end()) ? nullptr : it->second;
}
std::vector<std::pair<t_layer_height_range, const DynamicPrintConfig*>>::const_iterator begin() const { return m_ranges.cbegin(); }
@@ -689,6 +737,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// The object list did not change.
for (const ModelObject *model_object : m_model.objects)
model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
+
+ // But if custom gcode per layer height was changed
+ if (m_model.custom_gcode_per_height != model.custom_gcode_per_height) {
+ // we should stop background processing
+ update_apply_status(this->invalidate_step(psGCodeExport));
+ m_model.custom_gcode_per_height = model.custom_gcode_per_height;
+ }
} else if (model_object_list_extended(m_model, model)) {
// Add new objects. Their volumes and configs will be synchronized later.
update_apply_status(this->invalidate_step(psGCodeExport));
@@ -780,6 +835,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
for (PrintObject *print_object : m_objects)
print_object_status.emplace(PrintObjectStatus(print_object));
+ std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes =
+ m_model.get_custom_tool_changes(m_default_object_config.layer_height, num_extruders);
+
// 3) Synchronize ModelObjects & PrintObjects.
for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) {
ModelObject &model_object = *m_model.objects[idx_model_object];
@@ -787,7 +845,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
assert(it_status != model_object_status.end());
assert(it_status->status != ModelObjectStatus::Deleted);
const ModelObject& model_object_new = *model.objects[idx_model_object];
- const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
+ // ys_FIXME_COLOR
+ // const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
+ const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges, custom_tool_changes);
if (it_status->status == ModelObjectStatus::New)
// PrintObject instances will be added in the next loop.
continue;
@@ -955,6 +1015,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
PrintRegionConfig this_region_config;
bool this_region_config_set = false;
for (PrintObject *print_object : m_objects) {
+ if(m_force_update_print_regions && !custom_tool_changes.empty())
+ goto print_object_end;
const LayerRanges *layer_ranges;
{
auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id()));
@@ -1190,6 +1252,8 @@ std::string Print::validate() const
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
if (m_config.use_volumetric_e)
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
+ if (m_config.complete_objects && extruders().size() > 1)
+ return L("The Wipe Tower is currently not supported for multimaterial sequential prints.");
if (m_objects.size() > 1) {
bool has_custom_layering = false;
@@ -1258,7 +1322,7 @@ std::string Print::validate() const
} while (ref_z == next_ref_z);
}
if (std::abs(this_height - ref_height) > EPSILON)
- return L("The Wipe tower is only supported if all objects have the same layer height profile");
+ return L("The Wipe tower is only supported if all objects have the same variable layer height");
i += 2;
}
}
@@ -1538,7 +1602,7 @@ void Print::process()
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
#if ENABLE_THUMBNAIL_GENERATOR
-std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
+std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
#else
std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -1559,7 +1623,7 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa
// The following line may die for multiple reasons.
GCode gcode;
#if ENABLE_THUMBNAIL_GENERATOR
- gcode.do_export(this, path.c_str(), preview_data, thumbnail_data);
+ gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb);
#else
gcode.do_export(this, path.c_str(), preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -1618,11 +1682,18 @@ void Print::_make_skirt()
if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) {
double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width;
double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width;
- Vec2d pt = Vec2d(m_config.wipe_tower_x-m_wipe_tower_data.brim_width, m_config.wipe_tower_y-m_wipe_tower_data.brim_width);
- points.push_back(Point(scale_(pt.x()), scale_(pt.y())));
- points.push_back(Point(scale_(pt.x()+width), scale_(pt.y())));
- points.push_back(Point(scale_(pt.x()+width), scale_(pt.y()+depth)));
- points.push_back(Point(scale_(pt.x()), scale_(pt.y()+depth)));
+ Vec2d pt = Vec2d(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width);
+
+ std::vector<Vec2d> pts;
+ pts.push_back(Vec2d(pt.x(), pt.y()));
+ pts.push_back(Vec2d(pt.x()+width, pt.y()));
+ pts.push_back(Vec2d(pt.x()+width, pt.y()+depth));
+ pts.push_back(Vec2d(pt.x(), pt.y()+depth));
+ for (Vec2d& pt : pts) {
+ pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt;
+ pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value);
+ points.push_back(Point(scale_(pt.x()), scale_(pt.y())));
+ }
}
if (points.size() < 3)
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 4fcd67166..098049f1d 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -11,6 +11,9 @@
#include "Slicing.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
+#if ENABLE_THUMBNAIL_GENERATOR
+#include "GCode/ThumbnailData.hpp"
+#endif // ENABLE_THUMBNAIL_GENERATOR
namespace Slic3r {
@@ -19,9 +22,6 @@ class PrintObject;
class ModelObject;
class GCode;
class GCodePreviewData;
-#if ENABLE_THUMBNAIL_GENERATOR
-struct ThumbnailData;
-#endif // ENABLE_THUMBNAIL_GENERATOR
// Print step IDs for keeping track of the print state.
enum PrintStep {
@@ -311,7 +311,7 @@ public:
// Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
// If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
#if ENABLE_THUMBNAIL_GENERATOR
- std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
+ std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
#else
std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -370,6 +370,9 @@ public:
// Accessed by SupportMaterial
const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; }
+ // force update of PrintRegions, when custom_tool_change is not empty and (Re)Slicing is started
+ void set_force_update_print_regions(bool force_update_print_regions) { m_force_update_print_regions = force_update_print_regions; }
+
protected:
// methods for handling regions
PrintRegion* get_region(size_t idx) { return m_regions[idx]; }
@@ -412,6 +415,9 @@ private:
// Estimated print time, filament consumed.
PrintStatistics m_print_statistics;
+ // flag used
+ bool m_force_update_print_regions = false;
+
// To allow GCode to set the Print's GCodeExport step status.
friend class GCode;
// Allow PrintObject to access m_mutex and m_cancel_callback.
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index ac822b525..20fb5d08a 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -71,6 +71,12 @@ enum SLAPillarConnectionMode {
slapcmDynamic
};
+// ys_FIXME ! may be, it's not a best place
+// Additional Codes which can be set by user using DoubleSlider
+static const std::string ColorChangeCode = "M600";
+static const std::string PausePrintCode = "M601";
+static const std::string ExtruderChangeCode = "tool_change";
+
template<> inline const t_config_enum_values& ConfigOptionEnum<PrinterTechnology>::get_enum_values() {
static t_config_enum_values keys_map;
if (keys_map.empty()) {
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index c4ca46a8c..bf97baaf6 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -1522,9 +1522,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c
layer_height_profile.clear();
if (layer_height_profile.empty()) {
- //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes);
- layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges);
- updated = true;
+ //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes);
+ layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges);
+ updated = true;
}
return updated;
}
diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp
index a755becaa..172391077 100644
--- a/src/libslic3r/Semver.hpp
+++ b/src/libslic3r/Semver.hpp
@@ -114,6 +114,7 @@ public:
bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver) != 0; }
bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver) != 0; }
bool in_range(const Semver &low, const Semver &high) const { return low <= *this && *this <= high; }
+ bool valid() const { return *this != zero() && *this != inf() && *this != invalid(); }
// Conversion
std::string to_string() const {
diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp
index b38655e68..8b5cbf483 100644
--- a/src/libslic3r/ShortestPath.cpp
+++ b/src/libslic3r/ShortestPath.cpp
@@ -191,7 +191,7 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
}
// Initialize a heap of end points sorted by the lowest distance to the next valid point of a path.
- auto queue = make_mutable_priority_queue<EndPoint*>(
+ auto queue = make_mutable_priority_queue<EndPoint*, false>(
[](EndPoint *ep, size_t idx){ ep->heap_idx = idx; },
[](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; });
queue.reserve(end_points.size() * 2 - 1);
@@ -389,6 +389,585 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
return out;
}
+template<typename QueueType, typename KDTreeType, typename ChainsType, typename EndPointType>
+void update_end_point_in_queue(QueueType &queue, const KDTreeType &kdtree, ChainsType &chains, std::vector<EndPointType> &end_points, EndPointType &end_point, size_t first_point_idx, const EndPointType *first_point)
+{
+ // Updating an end point or a 2nd from an end point.
+ size_t this_idx = end_point.index(end_points);
+ // If this segment is not the starting segment, then this end point or the opposite is unconnected.
+ assert(first_point_idx == this_idx || first_point_idx == (this_idx ^ 1) || end_point.chain_id == 0 || end_point.opposite(end_points).chain_id == 0);
+ end_point.edge_candidate = nullptr;
+ if (first_point_idx == this_idx || (end_point.chain_id > 0 && first_point_idx == (this_idx ^ 1)))
+ {
+ // One may never flip the 1st edge, don't try it again.
+ if (! end_point.heap_idx_invalid())
+ queue.remove(end_point.heap_idx);
+ }
+ else
+ {
+ // Update edge_candidate and distance.
+ size_t chain1a = end_point.chain_id;
+ size_t chain1b = end_points[this_idx ^ 1].chain_id;
+ size_t this_chain = chains.equivalent(std::max(chain1a, chain1b));
+ // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda).
+ size_t next_idx = find_closest_point(kdtree, end_point.pos, [&end_points, &chains, this_idx, first_point_idx, first_point, this_chain](size_t idx) {
+ assert(end_points[this_idx].edge_candidate == nullptr);
+ // Either this end of the edge or the other end of the edge is not yet connected.
+ assert((end_points[this_idx ].chain_id == 0 && end_points[this_idx ].edge_out == nullptr) ||
+ (end_points[this_idx ^ 1].chain_id == 0 && end_points[this_idx ^ 1].edge_out == nullptr));
+ if ((idx ^ this_idx) <= 1 || idx == first_point_idx)
+ // Points of the same segment shall not be connected.
+ // Don't connect to the first point, we must not flip the 1st edge.
+ return false;
+ size_t chain2a = end_points[idx].chain_id;
+ size_t chain2b = end_points[idx ^ 1].chain_id;
+ if (chain2a > 0 && chain2b > 0)
+ // Only unconnected end point or a point next to an unconnected end point may be connected to.
+ // Ideally those would be removed from the KD tree, but the update is difficult.
+ return false;
+ assert(chain2a == 0 || chain2b == 0);
+ size_t chain2 = chains.equivalent(std::max(chain2a, chain2b));
+ if (this_chain == chain2)
+ // Don't connect back to the same chain, don't create a loop.
+ return this_chain == 0;
+ // Don't connect to a segment requiring flipping if the segment starts or ends with the first point.
+ if (chain2a > 0) {
+ // Chain requires flipping.
+ assert(chain2b == 0);
+ auto &chain = chains.chain(chain2);
+ if (chain.begin == first_point || chain.end == first_point)
+ return false;
+ }
+ // Everything is all right, try to connect.
+ return true;
+ });
+ assert(next_idx < end_points.size());
+ assert(chains.equivalent(end_points[next_idx].chain_id) != chains.equivalent(end_points[next_idx ^ 1].chain_id) || end_points[next_idx].chain_id == 0);
+ end_point.edge_candidate = &end_points[next_idx];
+ end_point.distance_out = (end_points[next_idx].pos - end_point.pos).norm();
+ if (end_point.chain_id > 0)
+ end_point.distance_out += chains.chain_flip_penalty(this_chain);
+ if (end_points[next_idx].chain_id > 0)
+ // The candidate chain is flipped.
+ end_point.distance_out += chains.chain_flip_penalty(end_points[next_idx].chain_id);
+ // Update position of this end point in the queue based on the distance calculated at the line above.
+ if (end_point.heap_idx_invalid())
+ queue.push(&end_point);
+ else
+ queue.update(end_point.heap_idx);
+ }
+}
+
+template<typename PointType, typename SegmentEndPointFunc, bool REVERSE_COULD_FAIL, typename CouldReverseFunc>
+std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals2_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
+{
+ std::vector<std::pair<size_t, bool>> out;
+
+ if (num_segments == 0) {
+ // Nothing to do.
+ }
+ else if (num_segments == 1)
+ {
+ // Just sort the end points so that the first point visited is closest to start_near.
+ out.emplace_back(0, start_near != nullptr &&
+ (end_point_func(0, true) - *start_near).template cast<double>().squaredNorm() < (end_point_func(0, false) - *start_near).template cast<double>().squaredNorm());
+ }
+ else
+ {
+ // End points of segments for the KD tree closest point search.
+ // A single end point is inserted into the search structure for loops, two end points are entered for open paths.
+ struct EndPoint {
+ EndPoint(const Vec2d &pos) : pos(pos) {}
+ Vec2d pos;
+
+ // Candidate for a new connection link.
+ EndPoint *edge_candidate = nullptr;
+ // Distance to the next end point following the link.
+ // Zero value -> start of the final path.
+ double distance_out = std::numeric_limits<double>::max();
+
+ size_t heap_idx = std::numeric_limits<size_t>::max();
+ bool heap_idx_invalid() const { return this->heap_idx == std::numeric_limits<size_t>::max(); }
+
+ // Identifier of the chain, to which this end point belongs. Zero means unassigned.
+ size_t chain_id = 0;
+ // Double linked chain of segment end points in current path.
+ EndPoint *edge_out = nullptr;
+
+ size_t index(std::vector<EndPoint> &endpoints) const { return this - endpoints.data(); }
+ // Opposite end point of the same segment.
+ EndPoint& opposite(std::vector<EndPoint> &endpoints) { return endpoints[(this - endpoints.data()) ^ 1]; }
+ const EndPoint& opposite(const std::vector<EndPoint> &endpoints) const { return endpoints[(this - endpoints.data()) ^ 1]; }
+ };
+
+ std::vector<EndPoint> end_points;
+ end_points.reserve(num_segments * 2);
+ for (size_t i = 0; i < num_segments; ++ i) {
+ end_points.emplace_back(end_point_func(i, true ).template cast<double>());
+ end_points.emplace_back(end_point_func(i, false).template cast<double>());
+ }
+
+ // Construct the closest point KD tree over end points of segments.
+ auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; };
+ KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size());
+
+ // Chained segments with their sum of connection lengths.
+ // The chain supports flipping all the segments, connecting the segments at the opposite ends.
+ // (this is a very useful path optimization for infill lines).
+ struct Chain {
+ size_t num_segments = 0;
+ double cost = 0.;
+ double cost_flipped = 0.;
+ EndPoint *begin = nullptr;
+ EndPoint *end = nullptr;
+ size_t equivalent_with = 0;
+
+ // Flipping the chain has a time complexity of O(n).
+ void flip(std::vector<EndPoint> &endpoints)
+ {
+ assert(this->num_segments > 1);
+ assert(this->begin->edge_out == nullptr);
+ assert(this->end ->edge_out == nullptr);
+ assert(this->begin->opposite(endpoints).edge_out != nullptr);
+ assert(this->end ->opposite(endpoints).edge_out != nullptr);
+ // Start of the current segment processed.
+ EndPoint *ept = this->begin;
+ // Previous end point to connect the other side of ept to.
+ EndPoint *ept_prev = nullptr;
+ do {
+ EndPoint *ept_end = &ept->opposite(endpoints);
+ EndPoint *ept_next = ept_end->edge_out;
+ assert(ept_next == nullptr || ept_next->edge_out == ept_end);
+ // Connect to the preceding segment.
+ ept_end->edge_out = ept_prev;
+ if (ept_prev != nullptr)
+ ept_prev->edge_out = ept_end;
+ ept_prev = ept;
+ ept = ept_next;
+ } while (ept != nullptr);
+ ept_prev->edge_out = nullptr;
+ // Swap the costs.
+ std::swap(this->cost, this->cost_flipped);
+ // Swap the ends.
+ EndPoint *new_begin = &this->begin->opposite(endpoints);
+ EndPoint *new_end = &this->end->opposite(endpoints);
+ std::swap(this->begin->chain_id, new_begin->chain_id);
+ std::swap(this->end ->chain_id, new_end ->chain_id);
+ this->begin = new_begin;
+ this->end = new_end;
+ assert(this->begin->edge_out == nullptr);
+ assert(this->end ->edge_out == nullptr);
+ assert(this->begin->opposite(endpoints).edge_out != nullptr);
+ assert(this->end ->opposite(endpoints).edge_out != nullptr);
+ }
+
+ double flip_penalty() const { return this->cost_flipped - this->cost; }
+ };
+
+ // Helper to detect loops in already connected paths and to accomodate flipping of chains.
+ //
+ // Unique chain IDs are assigned to paths. If paths are connected, end points will not have their chain IDs updated, but the chain IDs
+ // will remember an "equivalent" chain ID, which is the lowest ID of all the IDs in the path, and the lowest ID is equivalent to itself.
+ // Chain IDs are indexed starting with 1.
+ //
+ // Chains remember their lengths and their lengths when each segment of the chain is flipped.
+ class Chains {
+ public:
+ // Zero'th chain ID is invalid.
+ Chains(size_t reserve) {
+ m_chains.reserve(reserve / 2);
+ // Indexing starts with 1.
+ m_chains.emplace_back();
+ }
+
+ // Generate next equivalence class.
+ size_t next_id() {
+ m_chains.emplace_back();
+ m_chains.back().equivalent_with = ++ m_last_chain_id;
+ return m_last_chain_id;
+ }
+
+ // Get equivalence class for chain ID, update the "equivalent_with" along the equivalence path.
+ size_t equivalent(size_t chain_id) {
+ if (chain_id != 0) {
+ for (size_t last = chain_id;;) {
+ size_t lower = m_chains[last].equivalent_with;
+ if (lower == last) {
+ m_chains[chain_id].equivalent_with = lower;
+ chain_id = lower;
+ break;
+ }
+ last = lower;
+ }
+ }
+ return chain_id;
+ }
+
+ // Return a lowest chain ID of the two input chains.
+ // Produce a new chain ID of both chain IDs are zero.
+ size_t merge(size_t chain_id1, size_t chain_id2) {
+ if (chain_id1 == 0)
+ return (chain_id2 == 0) ? this->next_id() : chain_id2;
+ if (chain_id2 == 0)
+ return chain_id1;
+ assert(m_chains[chain_id1].equivalent_with == chain_id1);
+ assert(m_chains[chain_id2].equivalent_with == chain_id2);
+ size_t chain_id = std::min(chain_id1, chain_id2);
+ m_chains[chain_id1].equivalent_with = chain_id;
+ m_chains[chain_id2].equivalent_with = chain_id;
+ return chain_id;
+ }
+
+ Chain& chain(size_t chain_id) { return m_chains[chain_id]; }
+ const Chain& chain(size_t chain_id) const { return m_chains[chain_id]; }
+
+ double chain_flip_penalty(size_t chain_id) {
+ chain_id = this->equivalent(chain_id);
+ return m_chains[chain_id].flip_penalty();
+ }
+
+#ifndef NDEBUG
+ bool validate()
+ {
+ // Validate that the segments merged chain IDs make up a directed acyclic graph
+ // with edges oriented towards the lower chain ID, therefore all ending up
+ // in the lowest chain ID of all of them.
+ assert(m_last_chain_id >= 0);
+ assert(m_last_chain_id + 1 == m_chains.size());
+ for (size_t i = 0; i < m_chains.size(); ++ i) {
+ for (size_t last = i;;) {
+ size_t lower = m_chains[last].equivalent_with;
+ assert(lower <= last);
+ if (lower == last)
+ break;
+ last = lower;
+ }
+ }
+ return true;
+ }
+#endif /* NDEBUG */
+
+ private:
+ std::vector<Chain> m_chains;
+ // Unique chain ID assigned to chains of end points of segments.
+ size_t m_last_chain_id = 0;
+ } chains(num_segments);
+
+ // Find the first end point closest to start_near.
+ EndPoint *first_point = nullptr;
+ size_t first_point_idx = std::numeric_limits<size_t>::max();
+ if (start_near != nullptr) {
+ size_t idx = find_closest_point(kdtree, start_near->template cast<double>());
+ assert(idx < end_points.size());
+ first_point = &end_points[idx];
+ first_point->distance_out = 0.;
+ first_point->chain_id = chains.next_id();
+ Chain &chain = chains.chain(first_point->chain_id);
+ chain.begin = first_point;
+ chain.end = &first_point->opposite(end_points);
+ first_point_idx = idx;
+ }
+ EndPoint *initial_point = first_point;
+ EndPoint *last_point = nullptr;
+
+ // Assign the closest point and distance to the end points.
+ for (EndPoint &end_point : end_points) {
+ assert(end_point.edge_candidate == nullptr);
+ if (&end_point != first_point) {
+ size_t this_idx = end_point.index(end_points);
+ // Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda).
+ // Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it.
+ size_t next_idx = find_closest_point(kdtree, end_point.pos,
+ [this_idx, first_point_idx](size_t idx){ return idx != first_point_idx && (idx ^ this_idx) > 1; });
+ assert(next_idx < end_points.size());
+ EndPoint &end_point2 = end_points[next_idx];
+ end_point.edge_candidate = &end_point2;
+ end_point.distance_out = (end_point2.pos - end_point.pos).norm();
+ }
+ }
+
+ // Initialize a heap of end points sorted by the lowest distance to the next valid point of a path.
+ auto queue = make_mutable_priority_queue<EndPoint*, true>(
+ [](EndPoint *ep, size_t idx){ ep->heap_idx = idx; },
+ [](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; });
+ queue.reserve(end_points.size() * 2);
+ for (EndPoint &ep : end_points)
+ if (first_point != &ep)
+ queue.push(&ep);
+
+#ifndef NDEBUG
+ auto validate_graph_and_queue = [&chains, &end_points, &queue, first_point]() -> bool {
+ assert(chains.validate());
+ for (EndPoint &ep : end_points) {
+ if (ep.heap_idx < queue.size()) {
+ // End point is on the heap.
+ assert(*(queue.cbegin() + ep.heap_idx) == &ep);
+ // One side or the other of the segment is not yet connected.
+ assert(ep.chain_id == 0 || ep.opposite(end_points).chain_id == 0);
+ } else {
+ // End point is NOT on the heap, therefore it must part of the output path.
+ assert(ep.heap_idx_invalid());
+ assert(ep.chain_id != 0);
+ if (&ep == first_point) {
+ assert(ep.edge_out == nullptr);
+ } else {
+ assert(ep.edge_out != nullptr);
+ // Detect loops.
+ for (EndPoint *pt = &ep; pt != nullptr;) {
+ // Out of queue. It is a final point.
+ EndPoint *pt_other = &pt->opposite(end_points);
+ if (pt_other->heap_idx < queue.size()) {
+ // The other side of this segment is undecided yet.
+ // assert(pt_other->edge_out == nullptr);
+ break;
+ }
+ pt = pt_other->edge_out;
+ }
+ }
+ }
+ }
+ for (EndPoint *ep : queue)
+ // Points in the queue or the opposites of the same segment are not connected yet.
+ assert(ep->chain_id == 0 || ep->opposite(end_points).chain_id == 0);
+ return true;
+ };
+#endif /* NDEBUG */
+
+ // Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops.
+ assert(num_segments >= 2);
+#ifndef NDEBUG
+ double distance_taken_last = 0.;
+#endif /* NDEBUG */
+ // Some links stored onto the priority queue are being invalidated during the calculation and they are not
+ // updated immediately. If such a situation is detected for an end point pulled from the priority queue,
+ // the end point is being updated and re-inserted into the priority queue. Therefore the number of iterations
+ // required is higher than expected (it would be the number of links, num_segments - 1).
+ // The limit here may not be necessary, but it guards us against an endless loop if something goes wrong.
+ size_t num_iter = num_segments * 16;
+ for (size_t num_connections_to_end = num_segments - 1; num_iter > 0; -- num_iter) {
+ assert(validate_graph_and_queue());
+ // Take the first end point, for which the link points to the currently closest valid neighbor.
+ EndPoint *end_point1 = queue.top();
+ assert(end_point1 != first_point);
+ EndPoint *end_point1_other = &end_point1->opposite(end_points);
+ // true if end_point1 is not the end of its chain, but the 2nd point. When connecting to the 2nd point, this chain needs
+ // to be flipped first.
+ bool chain1_flip = end_point1->chain_id > 0;
+ // Either this point at the queue is not connected, or it is the 2nd point of a chain.
+ // If connecting to a 2nd point of a chain, the 1st point shall not yet be connected and this chain will need
+ // to be flipped.
+ assert( chain1_flip || (end_point1->chain_id == 0 && end_point1->edge_out == nullptr));
+ assert(! chain1_flip || (end_point1_other->chain_id == 0 && end_point1_other->edge_out == nullptr));
+ assert(end_point1->edge_candidate != nullptr);
+#ifndef NDEBUG
+ // Each edge added shall be longer than the previous one taken.
+ //assert(end_point1->distance_out > distance_taken_last - SCALED_EPSILON);
+ if (end_point1->distance_out < distance_taken_last - SCALED_EPSILON) {
+// printf("Warning: taking shorter length than previously is suspicious\n");
+ }
+ distance_taken_last = end_point1->distance_out;
+#endif /* NDEBUG */
+ // Take the closest end point to the first end point,
+ EndPoint *end_point2 = end_point1->edge_candidate;
+ EndPoint *end_point2_other = &end_point2->opposite(end_points);
+ bool chain2_flip = end_point2->chain_id > 0;
+ // Is the link from end_point1 to end_point2 still valid? If yes, the link may be taken. Otherwise the link needs to be refreshed.
+ bool valid = true;
+ size_t end_point1_chain_id = 0;
+ size_t end_point2_chain_id = 0;
+ if (end_point2->chain_id > 0 && end_point2_other->chain_id > 0) {
+ // The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one.
+ valid = false;
+ } else {
+ // End points of the opposite ends of the segments.
+ end_point1_chain_id = chains.equivalent((chain1_flip ? end_point1 : end_point1_other)->chain_id);
+ end_point2_chain_id = chains.equivalent((chain2_flip ? end_point2 : end_point2_other)->chain_id);
+ if (end_point1_chain_id == end_point2_chain_id && end_point1_chain_id != 0)
+ // This edge forms a loop. Update end_point1 and try another one.
+ valid = false;
+ else {
+ // Verify whether end_point1.distance_out still matches the current state of the two end points to be connected and their chains.
+ // Namely, the other chain may have been flipped in the meantime.
+ double dist = (end_point2->pos - end_point1->pos).norm();
+ if (chain1_flip)
+ dist += chains.chain_flip_penalty(end_point1_chain_id);
+ if (chain2_flip)
+ dist += chains.chain_flip_penalty(end_point2_chain_id);
+ if (std::abs(dist - end_point1->distance_out) > SCALED_EPSILON)
+ // The distance changed due to flipping of one of the chains. Refresh this end point in the queue.
+ valid = false;
+ }
+ if (valid && first_point != nullptr) {
+ // Verify that a chain starting or ending with the first_point does not get flipped.
+ if (chain1_flip) {
+ Chain &chain = chains.chain(end_point1_chain_id);
+ if (chain.begin == first_point || chain.end == first_point)
+ valid = false;
+ }
+ if (valid && chain2_flip) {
+ Chain &chain = chains.chain(end_point2_chain_id);
+ if (chain.begin == first_point || chain.end == first_point)
+ valid = false;
+ }
+ }
+ }
+ if (valid) {
+ // Remove the first and second point from the queue.
+ queue.pop();
+ queue.remove(end_point2->heap_idx);
+ assert(end_point1->edge_candidate == end_point2);
+ end_point1->edge_candidate = nullptr;
+ Chain *chain1 = (end_point1_chain_id == 0) ? nullptr : &chains.chain(end_point1_chain_id);
+ Chain *chain2 = (end_point2_chain_id == 0) ? nullptr : &chains.chain(end_point2_chain_id);
+ assert(chain1 == nullptr || (chain1_flip ? (chain1->begin == end_point1_other || chain1->end == end_point1_other) : (chain1->begin == end_point1 || chain1->end == end_point1)));
+ assert(chain2 == nullptr || (chain2_flip ? (chain2->begin == end_point2_other || chain2->end == end_point2_other) : (chain2->begin == end_point2 || chain2->end == end_point2)));
+ if (chain1_flip)
+ chain1->flip(end_points);
+ if (chain2_flip)
+ chain2->flip(end_points);
+ assert(chain1 == nullptr || chain1->begin == end_point1 || chain1->end == end_point1);
+ assert(chain2 == nullptr || chain2->begin == end_point2 || chain2->end == end_point2);
+ size_t chain_id = chains.merge(end_point1_chain_id, end_point2_chain_id);
+ Chain &chain = chains.chain(chain_id);
+ {
+ Chain chain_dst;
+ chain_dst.begin = (chain1 == nullptr) ? end_point1_other : (chain1->begin == end_point1) ? chain1->end : chain1->begin;
+ chain_dst.end = (chain2 == nullptr) ? end_point2_other : (chain2->begin == end_point2) ? chain2->end : chain2->begin;
+ chain_dst.cost = (chain1 == 0 ? 0. : chain1->cost) + (chain2 == 0 ? 0. : chain2->cost) + (end_point2->pos - end_point1->pos).norm();
+ chain_dst.cost_flipped = (chain1 == 0 ? 0. : chain1->cost_flipped) + (chain2 == 0 ? 0. : chain2->cost_flipped) + (end_point2_other->pos - end_point1_other->pos).norm();
+ chain_dst.num_segments = (chain1 == 0 ? 1 : chain1->num_segments) + (chain2 == 0 ? 1 : chain2->num_segments);
+ chain_dst.equivalent_with = chain_id;
+ chain = chain_dst;
+ }
+ if (chain.begin != end_point1_other && ! end_point1_other->heap_idx_invalid())
+ queue.remove(end_point1_other->heap_idx);
+ if (chain.end != end_point2_other && ! end_point2_other->heap_idx_invalid())
+ queue.remove(end_point2_other->heap_idx);
+ end_point1->edge_out = end_point2;
+ end_point2->edge_out = end_point1;
+ end_point1->chain_id = chain_id;
+ end_point2->chain_id = chain_id;
+ end_point1_other->chain_id = chain_id;
+ end_point2_other->chain_id = chain_id;
+ if (chain.begin != first_point)
+ chain.begin->chain_id = 0;
+ if (chain.end != first_point)
+ chain.end->chain_id = 0;
+ if (-- num_connections_to_end == 0) {
+ assert(validate_graph_and_queue());
+ // Last iteration. There shall be exactly one or two end points waiting to be connected.
+ assert(queue.size() <= ((first_point == nullptr) ? 4 : 2));
+ if (first_point == nullptr) {
+ // Find the first remaining end point.
+ do {
+ first_point = queue.top();
+ queue.pop();
+ } while (first_point->edge_out != nullptr);
+ assert(first_point->edge_out == nullptr);
+ }
+ // Find the first remaining end point.
+ do {
+ last_point = queue.top();
+ queue.pop();
+ } while (last_point->edge_out != nullptr);
+ assert(last_point->edge_out == nullptr);
+#ifndef NDEBUG
+ while (! queue.empty()) {
+ assert(queue.top()->edge_out != nullptr && queue.top()->chain_id > 0);
+ queue.pop();
+ }
+#endif /* NDEBUG */
+ break;
+ } else {
+ //FIXME update the 2nd end points on the queue.
+ // Update end points of the flipped segments.
+ update_end_point_in_queue(queue, kdtree, chains, end_points, chain.begin->opposite(end_points), first_point_idx, first_point);
+ update_end_point_in_queue(queue, kdtree, chains, end_points, chain.end->opposite(end_points), first_point_idx, first_point);
+ if (chain1_flip)
+ update_end_point_in_queue(queue, kdtree, chains, end_points, *chain.begin, first_point_idx, first_point);
+ if (chain2_flip)
+ update_end_point_in_queue(queue, kdtree, chains, end_points, *chain.end, first_point_idx, first_point);
+ // End points of chains shall certainly stay in the queue.
+ assert(chain.begin == first_point || chain.begin->heap_idx < queue.size());
+ assert(chain.end == first_point || chain.end ->heap_idx < queue.size());
+ assert(&chain.begin->opposite(end_points) != first_point &&
+ (chain.begin == first_point ? chain.begin->opposite(end_points).heap_idx_invalid() : chain.begin->opposite(end_points).heap_idx < queue.size()));
+ assert(&chain.end ->opposite(end_points) != first_point &&
+ (chain.end == first_point ? chain.end ->opposite(end_points).heap_idx_invalid() : chain.end ->opposite(end_points).heap_idx < queue.size()));
+
+ }
+ } else {
+ // This edge forms a loop. Update end_point1 and try another one.
+ update_end_point_in_queue(queue, kdtree, chains, end_points, *end_point1, first_point_idx, first_point);
+#ifndef NDEBUG
+ // Each edge shall be longer than the last one removed from the queue.
+ //assert(end_point1->distance_out > distance_taken_last - SCALED_EPSILON);
+ if (end_point1->distance_out < distance_taken_last - SCALED_EPSILON) {
+// printf("Warning: taking shorter length than previously is suspicious\n");
+ }
+#endif /* NDEBUG */
+ //FIXME Remove the other end point from the KD tree.
+ // As the KD tree update is expensive, do it only after some larger number of points is removed from the queue.
+ }
+ assert(validate_graph_and_queue());
+ }
+ assert(queue.empty());
+
+ // Now interconnect pairs of segments into a chain.
+ assert(first_point != nullptr);
+ out.reserve(num_segments);
+ bool failed = false;
+ do {
+ assert(out.size() < num_segments);
+ size_t first_point_id = first_point - &end_points.front();
+ size_t segment_id = first_point_id >> 1;
+ bool reverse = (first_point_id & 1) != 0;
+ EndPoint *second_point = &end_points[first_point_id ^ 1];
+ if (REVERSE_COULD_FAIL) {
+ if (reverse && ! could_reverse_func(segment_id)) {
+ failed = true;
+ break;
+ }
+ } else {
+ assert(! reverse || could_reverse_func(segment_id));
+ }
+ out.emplace_back(segment_id, reverse);
+ first_point = second_point->edge_out;
+ } while (first_point != nullptr);
+ if (REVERSE_COULD_FAIL) {
+ if (failed) {
+ if (start_near == nullptr) {
+ // We may try the reverse order.
+ out.clear();
+ first_point = last_point;
+ failed = false;
+ do {
+ assert(out.size() < num_segments);
+ size_t first_point_id = first_point - &end_points.front();
+ size_t segment_id = first_point_id >> 1;
+ bool reverse = (first_point_id & 1) != 0;
+ EndPoint *second_point = &end_points[first_point_id ^ 1];
+ if (reverse && ! could_reverse_func(segment_id)) {
+ failed = true;
+ break;
+ }
+ out.emplace_back(segment_id, reverse);
+ first_point = second_point->edge_out;
+ } while (first_point != nullptr);
+ }
+ }
+ if (failed)
+ // As a last resort, try a dumb algorithm, which is not sensitive to edge reversal constraints.
+ out = chain_segments_closest_point<EndPoint, decltype(kdtree), CouldReverseFunc>(end_points, kdtree, could_reverse_func, (initial_point != nullptr) ? *initial_point : end_points.front());
+ } else {
+ assert(! failed);
+ }
+ }
+
+ assert(out.size() == num_segments);
+ return out;
+}
+
template<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
{
@@ -402,6 +981,19 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy(SegmentEndPointFunc e
return chain_segments_greedy_constrained_reversals_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
}
+template<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
+std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals2(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
+{
+ return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, true, CouldReverseFunc>(end_point_func, could_reverse_func, num_segments, start_near);
+}
+
+template<typename PointType, typename SegmentEndPointFunc>
+std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near)
+{
+ auto could_reverse_func = [](size_t /* idx */) -> bool { return true; };
+ return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
+}
+
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
{
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
@@ -472,8 +1064,22 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
return out;
}
+#ifndef NDEBUG
+ // #define DEBUG_SVG_OUTPUT
+#endif /* NDEBUG */
+
+#ifdef DEBUG_SVG_OUTPUT
+void svg_draw_polyline_chain(const char *name, size_t idx, const Polylines &polylines)
+{
+ BoundingBox bbox = get_extents(polylines);
+ SVG svg(debug_out_path("%s-%d.svg", name, idx).c_str(), bbox);
+ svg.draw(polylines);
+ for (size_t i = 1; i < polylines.size(); ++i)
+ svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
+}
+#endif /* DEBUG_SVG_OUTPUT */
+
// Flip the sequences of polylines to lower the total length of connecting lines.
-// #define DEBUG_SVG_OUTPUT
static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bool fixed_start)
{
#ifndef NDEBUG
@@ -487,14 +1093,8 @@ static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bo
static int iRun = 0;
++ iRun;
- BoundingBox bbox = get_extents(polylines);
#ifdef DEBUG_SVG_OUTPUT
- {
- SVG svg(debug_out_path("improve_ordering_by_segment_flipping-initial-%d.svg", iRun).c_str(), bbox);
- svg.draw(polylines);
- for (size_t i = 1; i < polylines.size(); ++ i)
- svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
- }
+ svg_draw_polyline_chain("improve_ordering_by_segment_flipping-initial", iRun, polylines);
#endif /* DEBUG_SVG_OUTPUT */
#endif /* NDEBUG */
@@ -550,7 +1150,7 @@ static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bo
#endif /* NDEBUG */
// Initialize a MutablePriorityHeap of connections between polylines.
- auto queue = make_mutable_priority_queue<Connection*>(
+ auto queue = make_mutable_priority_queue<Connection*, false>(
[](Connection *connection, size_t idx){ connection->heap_idx = idx; },
// Sort by decreasing connection distance.
[&polylines, &connections](Connection *l, Connection *r){ return l->squaredNorm(polylines, connections) > r->squaredNorm(polylines, connections); });
@@ -643,34 +1243,689 @@ static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bo
#ifndef NDEBUG
double cost_final = cost();
#ifdef DEBUG_SVG_OUTPUT
+ svg_draw_polyline_chain("improve_ordering_by_segment_flipping-final", iRun, polylines);
+#endif /* DEBUG_SVG_OUTPUT */
+ assert(cost_final <= cost_prev);
+ assert(cost_final <= cost_initial);
+#endif /* NDEBUG */
+}
+
+struct FlipEdge {
+ FlipEdge(const Vec2d &p1, const Vec2d &p2, size_t source_index) : p1(p1), p2(p2), source_index(source_index) {}
+ void flip() { std::swap(this->p1, this->p2); }
+ Vec2d p1;
+ Vec2d p2;
+ size_t source_index;
+};
+
+struct ConnectionCost {
+ ConnectionCost(double cost, double cost_flipped) : cost(cost), cost_flipped(cost_flipped) {}
+ ConnectionCost() : cost(0.), cost_flipped(0.) {}
+ void flip() { std::swap(this->cost, this->cost_flipped); }
+ double cost = 0;
+ double cost_flipped = 0;
+};
+static inline ConnectionCost operator-(const ConnectionCost &lhs, const ConnectionCost& rhs) { return ConnectionCost(lhs.cost - rhs.cost, lhs.cost_flipped - rhs.cost_flipped); }
+
+static inline std::pair<double, size_t> minimum_crossover_cost(
+ const std::vector<FlipEdge> &edges,
+ const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1,
+ const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2,
+ const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3,
+ const double cost_current)
+{
+ auto connection_cost = [&edges](
+ const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1, bool reversed1, bool flipped1,
+ const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2, bool reversed2, bool flipped2,
+ const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3, bool reversed3, bool flipped3) {
+ auto first_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.first].p2 : edges[span.first].p1; };
+ auto last_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.second - 1].p1 : edges[span.second - 1].p2; };
+ auto point = [first_point, last_point](const std::pair<size_t, size_t> &span, bool start, bool flipped) { return start ? first_point(span, flipped) : last_point(span, flipped); };
+ auto cost = [](const ConnectionCost &acost, bool flipped) {
+ assert(acost.cost >= 0. && acost.cost_flipped >= 0.);
+ return flipped ? acost.cost_flipped : acost.cost;
+ };
+ // Ignore reversed single segment spans.
+ auto simple_span_ignore = [](const std::pair<size_t, size_t>& span, bool reversed) {
+ return span.first + 1 == span.second && reversed;
+ };
+ assert(span1.first < span1.second);
+ assert(span2.first < span2.second);
+ assert(span3.first < span3.second);
+ return
+ simple_span_ignore(span1, reversed1) || simple_span_ignore(span2, reversed2) || simple_span_ignore(span3, reversed3) ?
+ // Don't perform unnecessary calculations simulating reversion of single segment spans.
+ std::numeric_limits<double>::max() :
+ // Calculate the cost of reverting chains and / or flipping segment orientations.
+ cost(cost1, flipped1) + cost(cost2, flipped2) + cost(cost3, flipped3) +
+ (point(span2, ! reversed2, flipped2) - point(span1, reversed1, flipped1)).norm() +
+ (point(span3, ! reversed3, flipped3) - point(span2, reversed2, flipped2)).norm();
+ };
+
+#ifndef NDEBUG
{
- SVG svg(debug_out_path("improve_ordering_by_segment_flipping-final-%d.svg", iRun).c_str(), bbox);
- svg.draw(polylines);
- for (size_t i = 1; i < polylines.size(); ++ i)
- svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
+ double c = connection_cost(span1, cost1, false, false, span2, cost2, false, false, span3, cost3, false, false);
+ assert(std::abs(c - cost_current) < SCALED_EPSILON);
+ }
+#endif /* NDEBUG */
+
+ double cost_min = cost_current;
+ size_t flip_min = 0; // no flip, no improvement
+ for (size_t i = 0; i < (1 << 6); ++ i) {
+ // From the three combinations of 1,2,3 ordering, the other three are reversals of the first three.
+ double c1 = (i == 0) ? cost_current :
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
+ double c2 = connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
+ double c3 = connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
+ if (c1 < cost_min) {
+ cost_min = c1;
+ flip_min = i;
+ }
+ if (c2 < cost_min) {
+ cost_min = c2;
+ flip_min = i + (1 << 6);
+ }
+ if (c3 < cost_min) {
+ cost_min = c3;
+ flip_min = i + (2 << 6);
+ }
+ }
+ return std::make_pair(cost_min, flip_min);
+}
+
+static inline std::pair<double, size_t> minimum_crossover_cost(
+ const std::vector<FlipEdge> &edges,
+ const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1,
+ const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2,
+ const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3,
+ const std::pair<size_t, size_t> &span4, const ConnectionCost &cost4,
+ const double cost_current)
+{
+ auto connection_cost = [&edges](
+ const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1, bool reversed1, bool flipped1,
+ const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2, bool reversed2, bool flipped2,
+ const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3, bool reversed3, bool flipped3,
+ const std::pair<size_t, size_t> &span4, const ConnectionCost &cost4, bool reversed4, bool flipped4) {
+ auto first_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.first].p2 : edges[span.first].p1; };
+ auto last_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.second - 1].p1 : edges[span.second - 1].p2; };
+ auto point = [first_point, last_point](const std::pair<size_t, size_t> &span, bool start, bool flipped) { return start ? first_point(span, flipped) : last_point(span, flipped); };
+ auto cost = [](const ConnectionCost &acost, bool flipped) {
+ assert(acost.cost >= 0. && acost.cost_flipped >= 0.);
+ return flipped ? acost.cost_flipped : acost.cost;
+ };
+ // Ignore reversed single segment spans.
+ auto simple_span_ignore = [](const std::pair<size_t, size_t>& span, bool reversed) {
+ return span.first + 1 == span.second && reversed;
+ };
+ assert(span1.first < span1.second);
+ assert(span2.first < span2.second);
+ assert(span3.first < span3.second);
+ assert(span4.first < span4.second);
+ return
+ simple_span_ignore(span1, reversed1) || simple_span_ignore(span2, reversed2) || simple_span_ignore(span3, reversed3) || simple_span_ignore(span4, reversed4) ?
+ // Don't perform unnecessary calculations simulating reversion of single segment spans.
+ std::numeric_limits<double>::max() :
+ // Calculate the cost of reverting chains and / or flipping segment orientations.
+ cost(cost1, flipped1) + cost(cost2, flipped2) + cost(cost3, flipped3) + cost(cost4, flipped4) +
+ (point(span2, ! reversed2, flipped2) - point(span1, reversed1, flipped1)).norm() +
+ (point(span3, ! reversed3, flipped3) - point(span2, reversed2, flipped2)).norm() +
+ (point(span4, ! reversed4, flipped4) - point(span3, reversed3, flipped3)).norm();
+ };
+
+#ifndef NDEBUG
+ {
+ double c = connection_cost(span1, cost1, false, false, span2, cost2, false, false, span3, cost3, false, false, span4, cost4, false, false);
+ assert(std::abs(c - cost_current) < SCALED_EPSILON);
+ }
+#endif /* NDEBUG */
+
+ double cost_min = cost_current;
+ size_t flip_min = 0; // no flip, no improvement
+ for (size_t i = 0; i < (1 << 8); ++ i) {
+ // From the three combinations of 1,2,3 ordering, the other three are reversals of the first three.
+ size_t permutation = 0;
+ for (double c : {
+ (i == 0) ? cost_current :
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, cost4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, cost4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, cost2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, cost4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, cost4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, cost2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, cost4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, cost1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span4, cost4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, cost1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, cost3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span3, cost3, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(span3, cost3, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, cost1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, cost4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0)
+ }) {
+ if (c < cost_min) {
+ cost_min = c;
+ flip_min = i + (permutation << 8);
+ }
+ ++ permutation;
+ }
}
+ return std::make_pair(cost_min, flip_min);
+}
+
+static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vector<FlipEdge> &edges_out,
+ const std::pair<size_t, size_t> &span1, const std::pair<size_t, size_t> &span2, const std::pair<size_t, size_t> &span3,
+ size_t i)
+{
+ assert(edges_in.size() == edges_out.size());
+ auto do_it = [&edges_in, &edges_out](
+ const std::pair<size_t, size_t> &span1, bool reversed1, bool flipped1,
+ const std::pair<size_t, size_t> &span2, bool reversed2, bool flipped2,
+ const std::pair<size_t, size_t> &span3, bool reversed3, bool flipped3) {
+ auto it_edges_out = edges_out.begin();
+ auto copy_span = [&edges_in, &edges_out, &it_edges_out](std::pair<size_t, size_t> span, bool reversed, bool flipped) {
+ assert(span.first < span.second);
+ auto it = it_edges_out;
+ if (reversed)
+ std::reverse_copy(edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out);
+ else
+ std::copy (edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out);
+ it_edges_out += span.second - span.first;
+ if (reversed != flipped) {
+ for (; it != it_edges_out; ++ it)
+ it->flip();
+ }
+ };
+ copy_span(span1, reversed1, flipped1);
+ copy_span(span2, reversed2, flipped2);
+ copy_span(span3, reversed3, flipped3);
+ };
+ switch (i >> 6) {
+ case 0:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
+ break;
+ case 1:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
+ break;
+ default:
+ assert((i >> 6) == 2);
+ do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
+ }
+ assert(edges_in.size() == edges_out.size());
+}
+
+
+static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vector<FlipEdge> &edges_out,
+ const std::pair<size_t, size_t> &span1, const std::pair<size_t, size_t> &span2, const std::pair<size_t, size_t> &span3, const std::pair<size_t, size_t> &span4,
+ size_t i)
+{
+ assert(edges_in.size() == edges_out.size());
+ auto do_it = [&edges_in, &edges_out](
+ const std::pair<size_t, size_t> &span1, bool reversed1, bool flipped1,
+ const std::pair<size_t, size_t> &span2, bool reversed2, bool flipped2,
+ const std::pair<size_t, size_t> &span3, bool reversed3, bool flipped3,
+ const std::pair<size_t, size_t> &span4, bool reversed4, bool flipped4) {
+ auto it_edges_out = edges_out.begin();
+ auto copy_span = [&edges_in, &edges_out, &it_edges_out](std::pair<size_t, size_t> span, bool reversed, bool flipped) {
+ assert(span.first < span.second);
+ auto it = it_edges_out;
+ if (reversed)
+ std::reverse_copy(edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out);
+ else
+ std::copy (edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out);
+ it_edges_out += span.second - span.first;
+ if (reversed != flipped) {
+ for (; it != it_edges_out; ++ it)
+ it->flip();
+ }
+ };
+ copy_span(span1, reversed1, flipped1);
+ copy_span(span2, reversed2, flipped2);
+ copy_span(span3, reversed3, flipped3);
+ copy_span(span4, reversed4, flipped4);
+ };
+ switch (i >> 8) {
+ case 0:
+ assert(i != 0); // otherwise it would be a no-op
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 1:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 2:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 3:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 4:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 5:
+ do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 6:
+ do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 7:
+ do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span4, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 8:
+ do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 9:
+ do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span4, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ case 10:
+ do_it(span3, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ default:
+ assert((i >> 8) == 11);
+ do_it(span3, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, span4, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0);
+ break;
+ }
+ assert(edges_in.size() == edges_out.size());
+}
+
+static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
+{
+ if (edges.size() < 2)
+ return;
+
+ std::vector<ConnectionCost> connections(edges.size());
+ std::vector<FlipEdge> edges_tmp(edges);
+ std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0));
+ std::vector<char> connection_tried(edges.size(), false);
+ for (size_t iter = 0; iter < edges.size(); ++ iter) {
+ // Initialize connection costs and connection lengths.
+ for (size_t i = 1; i < edges.size(); ++ i) {
+ const FlipEdge &e1 = edges[i - 1];
+ const FlipEdge &e2 = edges[i];
+ ConnectionCost &c = connections[i];
+ c = connections[i - 1];
+ double l = (e2.p1 - e1.p2).norm();
+ c.cost += l;
+ c.cost_flipped += (e2.p2 - e1.p1).norm();
+ connection_lengths[i - 1] = std::make_pair(l, i);
+ }
+ std::sort(connection_lengths.begin(), connection_lengths.end(), [](const std::pair<double, size_t> &l, const std::pair<double, size_t> &r) { return l.first > r.first; });
+ std::fill(connection_tried.begin(), connection_tried.end(), false);
+ size_t crossover1_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover2_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover_flip_final = 0;
+ for (const std::pair<double, size_t> &first_crossover_candidate : connection_lengths) {
+ double longest_connection_length = first_crossover_candidate.first;
+ size_t longest_connection_idx = first_crossover_candidate.second;
+ connection_tried[longest_connection_idx] = true;
+ // Find the second crossover connection with the lowest total chain cost.
+ size_t crossover_pos_min = std::numeric_limits<size_t>::max();
+ double crossover_cost_min = connections.back().cost;
+ size_t crossover_flip_min = 0;
+ for (size_t j = 1; j < connections.size(); ++ j)
+ if (! connection_tried[j]) {
+ size_t a = j;
+ size_t b = longest_connection_idx;
+ if (a > b)
+ std::swap(a, b);
+ std::pair<double, size_t> cost_and_flip = minimum_crossover_cost(edges,
+ std::make_pair(size_t(0), a), connections[a - 1], std::make_pair(a, b), connections[b - 1] - connections[a], std::make_pair(b, edges.size()), connections.back() - connections[b],
+ connections.back().cost);
+ if (cost_and_flip.second > 0 && cost_and_flip.first < crossover_cost_min) {
+ crossover_pos_min = j;
+ crossover_cost_min = cost_and_flip.first;
+ crossover_flip_min = cost_and_flip.second;
+ assert(crossover_cost_min < connections.back().cost + EPSILON);
+ }
+ }
+ if (crossover_cost_min < connections.back().cost) {
+ // The cost of the chain with the proposed two crossovers has a lower total cost than the current chain. Apply the crossover.
+ crossover1_pos_final = longest_connection_idx;
+ crossover2_pos_final = crossover_pos_min;
+ crossover_flip_final = crossover_flip_min;
+ break;
+ } else {
+ // Continue with another long candidate edge.
+ }
+ }
+ if (crossover_flip_final > 0) {
+ // Pair of cross over positions and flip / reverse constellation has been found, which improves the total cost of the connection.
+ // Perform a crossover.
+ if (crossover1_pos_final > crossover2_pos_final)
+ std::swap(crossover1_pos_final, crossover2_pos_final);
+ do_crossover(edges, edges_tmp, std::make_pair(size_t(0), crossover1_pos_final), std::make_pair(crossover1_pos_final, crossover2_pos_final), std::make_pair(crossover2_pos_final, edges.size()), crossover_flip_final);
+ edges.swap(edges_tmp);
+ } else {
+ // No valid pair of cross over positions was found improving the total cost. Giving up.
+ break;
+ }
+ }
+}
+
+static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
+{
+ if (edges.size() < 3) {
+ reorder_by_two_exchanges_with_segment_flipping(edges);
+ return;
+ }
+
+ std::vector<ConnectionCost> connections(edges.size());
+ std::vector<FlipEdge> edges_tmp(edges);
+ std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0));
+ std::vector<char> connection_tried(edges.size(), false);
+ for (size_t iter = 0; iter < edges.size(); ++ iter) {
+ // Initialize connection costs and connection lengths.
+ for (size_t i = 1; i < edges.size(); ++ i) {
+ const FlipEdge &e1 = edges[i - 1];
+ const FlipEdge &e2 = edges[i];
+ ConnectionCost &c = connections[i];
+ c = connections[i - 1];
+ double l = (e2.p1 - e1.p2).norm();
+ c.cost += l;
+ c.cost_flipped += (e2.p2 - e1.p1).norm();
+ connection_lengths[i - 1] = std::make_pair(l, i);
+ }
+ std::sort(connection_lengths.begin(), connection_lengths.end(), [](const std::pair<double, size_t> &l, const std::pair<double, size_t> &r) { return l.first > r.first; });
+ std::fill(connection_tried.begin(), connection_tried.end(), false);
+ size_t crossover1_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover2_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover3_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover_flip_final = 0;
+ for (const std::pair<double, size_t> &first_crossover_candidate : connection_lengths) {
+ double longest_connection_length = first_crossover_candidate.first;
+ size_t longest_connection_idx = first_crossover_candidate.second;
+ connection_tried[longest_connection_idx] = true;
+ // Find the second crossover connection with the lowest total chain cost.
+ size_t crossover_pos_min = std::numeric_limits<size_t>::max();
+ double crossover_cost_min = connections.back().cost;
+ for (size_t j = 1; j < connections.size(); ++ j)
+ if (! connection_tried[j]) {
+ for (size_t k = j + 1; k < connections.size(); ++ k)
+ if (! connection_tried[k]) {
+ size_t a = longest_connection_idx;
+ size_t b = j;
+ size_t c = k;
+ if (a > c)
+ std::swap(a, c);
+ if (a > b)
+ std::swap(a, b);
+ if (b > c)
+ std::swap(b, c);
+ std::pair<double, size_t> cost_and_flip = minimum_crossover_cost(edges,
+ std::make_pair(size_t(0), a), connections[a - 1], std::make_pair(a, b), connections[b - 1] - connections[a],
+ std::make_pair(b, c), connections[c - 1] - connections[b], std::make_pair(c, edges.size()), connections.back() - connections[c],
+ connections.back().cost);
+ if (cost_and_flip.second > 0 && cost_and_flip.first < crossover_cost_min) {
+ crossover_cost_min = cost_and_flip.first;
+ crossover1_pos_final = a;
+ crossover2_pos_final = b;
+ crossover3_pos_final = c;
+ crossover_flip_final = cost_and_flip.second;
+ assert(crossover_cost_min < connections.back().cost + EPSILON);
+ }
+ }
+ }
+ if (crossover_flip_final > 0) {
+ // The cost of the chain with the proposed two crossovers has a lower total cost than the current chain. Apply the crossover.
+ break;
+ } else {
+ // Continue with another long candidate edge.
+ }
+ }
+ if (crossover_flip_final > 0) {
+ // Pair of cross over positions and flip / reverse constellation has been found, which improves the total cost of the connection.
+ // Perform a crossover.
+ do_crossover(edges, edges_tmp, std::make_pair(size_t(0), crossover1_pos_final), std::make_pair(crossover1_pos_final, crossover2_pos_final),
+ std::make_pair(crossover2_pos_final, crossover3_pos_final), std::make_pair(crossover3_pos_final, edges.size()), crossover_flip_final);
+ edges.swap(edges_tmp);
+ } else {
+ // No valid pair of cross over positions was found improving the total cost. Giving up.
+ break;
+ }
+ }
+}
+
+typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign> Matrixd;
+
+class FourOptCosts {
+public:
+ FourOptCosts(const ConnectionCost &c1, const ConnectionCost &c2, const ConnectionCost &c3, const ConnectionCost &c4) : costs { &c1, &c2, &c3, &c4 } {}
+
+ double operator()(size_t piece_idx, bool flipped) const { return flipped ? costs[piece_idx]->cost_flipped : costs[piece_idx]->cost; }
+
+private:
+ const ConnectionCost* costs[4];
+};
+
+static inline std::pair<double, size_t> minimum_crossover_cost(
+ const FourOptCosts &segment_costs,
+ const Matrixd &segment_end_point_distance_matrix,
+ const double cost_current)
+{
+ // Distance from the end of span1 to the start of span2.
+ auto end_point_distance = [&segment_end_point_distance_matrix](size_t span1, bool reversed1, bool flipped1, size_t span2, bool reversed2, bool flipped2) {
+ return segment_end_point_distance_matrix(span1 * 4 + (! reversed1) * 2 + flipped1, span2 * 4 + reversed2 * 2 + flipped2);
+ };
+ auto connection_cost = [&segment_costs, end_point_distance](
+ const size_t span1, bool reversed1, bool flipped1,
+ const size_t span2, bool reversed2, bool flipped2,
+ const size_t span3, bool reversed3, bool flipped3,
+ const size_t span4, bool reversed4, bool flipped4) {
+ // Calculate the cost of reverting chains and / or flipping segment orientations.
+ return segment_costs(span1, flipped1) + segment_costs(span2, flipped2) + segment_costs(span3, flipped3) + segment_costs(span4, flipped4) +
+ end_point_distance(span1, reversed1, flipped1, span2, reversed2, flipped2) +
+ end_point_distance(span2, reversed2, flipped2, span3, reversed3, flipped3) +
+ end_point_distance(span3, reversed3, flipped3, span4, reversed4, flipped4);
+ };
+
+#ifndef NDEBUG
+ {
+ double c = connection_cost(0, false, false, 1, false, false, 2, false, false, 3, false, false);
+ assert(std::abs(c - cost_current) < SCALED_EPSILON);
+ }
+#endif /* NDEBUG */
+
+ double cost_min = cost_current;
+ size_t flip_min = 0; // no flip, no improvement
+ for (size_t i = 0; i < (1 << 8); ++ i) {
+ // From the three combinations of 1,2,3 ordering, the other three are reversals of the first three.
+ size_t permutation = 0;
+ for (double c : {
+ (i == 0) ? cost_current :
+ connection_cost(0, (i & 1) != 0, (i & (1 << 1)) != 0, 1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(0, (i & 1) != 0, (i & (1 << 1)) != 0, 1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(0, (i & 1) != 0, (i & (1 << 1)) != 0, 2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(0, (i & 1) != 0, (i & (1 << 1)) != 0, 2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 1, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(0, (i & 1) != 0, (i & (1 << 1)) != 0, 3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(0, (i & 1) != 0, (i & (1 << 1)) != 0, 3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 1, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(1, (i & 1) != 0, (i & (1 << 1)) != 0, 0, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(1, (i & 1) != 0, (i & (1 << 1)) != 0, 0, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(1, (i & 1) != 0, (i & (1 << 1)) != 0, 2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 0, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(1, (i & 1) != 0, (i & (1 << 1)) != 0, 3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 0, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 2, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(2, (i & 1) != 0, (i & (1 << 1)) != 0, 0, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 1, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0),
+ connection_cost(2, (i & 1) != 0, (i & (1 << 1)) != 0, 1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, 0, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0, 3, (i & (1 << 6)) != 0, (i & (1 << 7)) != 0)
+ }) {
+ if (c < cost_min) {
+ cost_min = c;
+ flip_min = i + (permutation << 8);
+ }
+ ++ permutation;
+ }
+ }
+ return std::make_pair(cost_min, flip_min);
+}
+
+static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector<FlipEdge> &edges)
+{
+ if (edges.size() < 3) {
+ reorder_by_two_exchanges_with_segment_flipping(edges);
+ return;
+ }
+
+ std::vector<ConnectionCost> connections(edges.size());
+ std::vector<FlipEdge> edges_tmp(edges);
+ std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0));
+ std::vector<char> connection_tried(edges.size(), false);
+ for (size_t iter = 0; iter < edges.size(); ++ iter) {
+ // Initialize connection costs and connection lengths.
+ for (size_t i = 1; i < edges.size(); ++ i) {
+ const FlipEdge &e1 = edges[i - 1];
+ const FlipEdge &e2 = edges[i];
+ ConnectionCost &c = connections[i];
+ c = connections[i - 1];
+ double l = (e2.p1 - e1.p2).norm();
+ c.cost += l;
+ c.cost_flipped += (e2.p2 - e1.p1).norm();
+ connection_lengths[i - 1] = std::make_pair(l, i);
+ }
+ std::sort(connection_lengths.begin(), connection_lengths.end(), [](const std::pair<double, size_t> &l, const std::pair<double, size_t> &r) { return l.first > r.first; });
+ std::fill(connection_tried.begin(), connection_tried.end(), false);
+ size_t crossover1_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover2_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover3_pos_final = std::numeric_limits<size_t>::max();
+ size_t crossover_flip_final = 0;
+ // Distances between the end points of the four pieces of the current segment sequence.
+#ifdef NDEBUG
+ Matrixd segment_end_point_distance_matrix(4 * 4, 4 * 4);
+#else /* NDEBUG */
+ Matrixd segment_end_point_distance_matrix = Matrixd::Constant(4 * 4, 4 * 4, std::numeric_limits<double>::max());
+#endif /* NDEBUG */
+ for (const std::pair<double, size_t> &first_crossover_candidate : connection_lengths) {
+ double longest_connection_length = first_crossover_candidate.first;
+ size_t longest_connection_idx = first_crossover_candidate.second;
+ connection_tried[longest_connection_idx] = true;
+ // Find the second crossover connection with the lowest total chain cost.
+ size_t crossover_pos_min = std::numeric_limits<size_t>::max();
+ double crossover_cost_min = connections.back().cost;
+ for (size_t j = 1; j < connections.size(); ++ j)
+ if (! connection_tried[j]) {
+ for (size_t k = j + 1; k < connections.size(); ++ k)
+ if (! connection_tried[k]) {
+ size_t a = longest_connection_idx;
+ size_t b = j;
+ size_t c = k;
+ if (a > c)
+ std::swap(a, c);
+ if (a > b)
+ std::swap(a, b);
+ if (b > c)
+ std::swap(b, c);
+ const Vec2d* endpts[16] = {
+ &edges[0].p1, &edges[0].p2, &edges[a - 1].p2, &edges[a - 1].p1,
+ &edges[a].p1, &edges[a].p2, &edges[b - 1].p2, &edges[b - 1].p1,
+ &edges[b].p1, &edges[b].p2, &edges[c - 1].p2, &edges[c - 1].p1,
+ &edges[c].p1, &edges[c].p2, &edges.back().p2, &edges.back().p1 };
+ for (size_t v = 0; v < 16; ++ v) {
+ const Vec2d &p1 = *endpts[v];
+ for (size_t u = (v & (~3)) + 4; u < 16; ++ u)
+ segment_end_point_distance_matrix(u, v) = segment_end_point_distance_matrix(v, u) = (*endpts[u] - p1).norm();
+ }
+ FourOptCosts segment_costs(connections[a - 1], connections[b - 1] - connections[a], connections[c - 1] - connections[b], connections.back() - connections[c]);
+ std::pair<double, size_t> cost_and_flip = minimum_crossover_cost(segment_costs, segment_end_point_distance_matrix, connections.back().cost);
+ if (cost_and_flip.second > 0 && cost_and_flip.first < crossover_cost_min) {
+ crossover_cost_min = cost_and_flip.first;
+ crossover1_pos_final = a;
+ crossover2_pos_final = b;
+ crossover3_pos_final = c;
+ crossover_flip_final = cost_and_flip.second;
+ assert(crossover_cost_min < connections.back().cost + EPSILON);
+ }
+ }
+ }
+ if (crossover_flip_final > 0) {
+ // The cost of the chain with the proposed two crossovers has a lower total cost than the current chain. Apply the crossover.
+ break;
+ } else {
+ // Continue with another long candidate edge.
+ }
+ }
+ if (crossover_flip_final > 0) {
+ // Pair of cross over positions and flip / reverse constellation has been found, which improves the total cost of the connection.
+ // Perform a crossover.
+ do_crossover(edges, edges_tmp, std::make_pair(size_t(0), crossover1_pos_final), std::make_pair(crossover1_pos_final, crossover2_pos_final),
+ std::make_pair(crossover2_pos_final, crossover3_pos_final), std::make_pair(crossover3_pos_final, edges.size()), crossover_flip_final);
+ edges.swap(edges_tmp);
+ } else {
+ // No valid pair of cross over positions was found improving the total cost. Giving up.
+ break;
+ }
+ }
+}
+
+// Flip the sequences of polylines to lower the total length of connecting lines.
+static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polylines &polylines, bool fixed_start)
+{
+#ifndef NDEBUG
+ auto cost = [&polylines]() {
+ double sum = 0.;
+ for (size_t i = 1; i < polylines.size(); ++i)
+ sum += (polylines[i].first_point() - polylines[i - 1].last_point()).cast<double>().norm();
+ return sum;
+ };
+ double cost_initial = cost();
+
+ static int iRun = 0;
+ ++ iRun;
+#ifdef DEBUG_SVG_OUTPUT
+ svg_draw_polyline_chain("improve_ordering_by_two_exchanges_with_segment_flipping-initial", iRun, polylines);
#endif /* DEBUG_SVG_OUTPUT */
#endif /* NDEBUG */
- assert(cost_final <= cost_prev);
+ std::vector<FlipEdge> edges;
+ edges.reserve(polylines.size());
+ std::transform(polylines.begin(), polylines.end(), std::back_inserter(edges),
+ [&polylines](const Polyline &pl){ return FlipEdge(pl.first_point().cast<double>(), pl.last_point().cast<double>(), &pl - polylines.data()); });
+#if 1
+ reorder_by_two_exchanges_with_segment_flipping(edges);
+#else
+ // reorder_by_three_exchanges_with_segment_flipping(edges);
+ reorder_by_three_exchanges_with_segment_flipping2(edges);
+#endif
+ Polylines out;
+ out.reserve(polylines.size());
+ for (const FlipEdge &edge : edges) {
+ Polyline &pl = polylines[edge.source_index];
+ out.emplace_back(std::move(pl));
+ if (edge.p2 == pl.first_point().cast<double>()) {
+ // Polyline is flipped.
+ out.back().reverse();
+ } else {
+ // Polyline is not flipped.
+ assert(edge.p1 == pl.first_point().cast<double>());
+ }
+ }
+
+#ifndef NDEBUG
+ double cost_final = cost();
+#ifdef DEBUG_SVG_OUTPUT
+ svg_draw_polyline_chain("improve_ordering_by_two_exchanges_with_segment_flipping-final", iRun, out);
+#endif /* DEBUG_SVG_OUTPUT */
assert(cost_final <= cost_initial);
+#endif /* NDEBUG */
}
Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
{
+#ifdef DEBUG_SVG_OUTPUT
+ static int iRun = 0;
+ ++ iRun;
+ svg_draw_polyline_chain("chain_polylines-initial", iRun, polylines);
+#endif /* DEBUG_SVG_OUTPUT */
+
Polylines out;
if (! polylines.empty()) {
auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); };
- std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
+ std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy2<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
out.reserve(polylines.size());
for (auto &segment_and_reversal : ordered) {
out.emplace_back(std::move(polylines[segment_and_reversal.first]));
if (segment_and_reversal.second)
out.back().reverse();
}
- if (out.size() > 1)
- improve_ordering_by_segment_flipping(out, start_near != nullptr);
+ if (out.size() > 1 && start_near == nullptr) {
+ improve_ordering_by_two_exchanges_with_segment_flipping(out, start_near != nullptr);
+ //improve_ordering_by_segment_flipping(out, start_near != nullptr);
+ }
}
+
+#ifdef DEBUG_SVG_OUTPUT
+ svg_draw_polyline_chain("chain_polylines-final", iRun, out);
+#endif /* DEBUG_SVG_OUTPUT */
return out;
}
diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp
index c62736ffe..9af6048ab 100644
--- a/src/libslic3r/Slicing.cpp
+++ b/src/libslic3r/Slicing.cpp
@@ -224,40 +224,59 @@ std::vector<coordf_t> layer_height_profile_from_ranges(
// Based on the work of @platsch
// Fill layer_height_profile by heights ensuring a prescribed maximum cusp height.
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+std::vector<double> layer_height_profile_adaptive(const SlicingParameters& slicing_params,
+ const ModelObject& object, float cusp_value)
+#else
std::vector<coordf_t> layer_height_profile_adaptive(
const SlicingParameters &slicing_params,
const t_layer_config_ranges & /* layer_config_ranges */,
const ModelVolumePtrs &volumes)
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
{
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// 1) Initialize the SlicingAdaptive class with the object meshes.
SlicingAdaptive as;
as.set_slicing_parameters(slicing_params);
- for (const ModelVolume *volume : volumes)
+ as.set_object(object);
+#else
+ // 1) Initialize the SlicingAdaptive class with the object meshes.
+ SlicingAdaptive as;
+ as.set_slicing_parameters(slicing_params);
+ for (const ModelVolume* volume : volumes)
if (volume->is_model_part())
as.add_mesh(&volume->mesh());
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+
as.prepare();
// 2) Generate layers using the algorithm of @platsch
// loop until we have at least one layer and the max slice_z reaches the object height
- //FIXME make it configurable
- // Cusp value: A maximum allowed distance from a corner of a rectangular extrusion to a chrodal line, in mm.
- const coordf_t cusp_value = 0.2; // $self->config->get_value('cusp_value');
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ double cusp_value = 0.2;
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
- std::vector<coordf_t> layer_height_profile;
- layer_height_profile.push_back(0.);
+ std::vector<double> layer_height_profile;
+ layer_height_profile.push_back(0.0);
layer_height_profile.push_back(slicing_params.first_object_layer_height);
if (slicing_params.first_object_layer_height_fixed()) {
layer_height_profile.push_back(slicing_params.first_object_layer_height);
layer_height_profile.push_back(slicing_params.first_object_layer_height);
}
- coordf_t slice_z = slicing_params.first_object_layer_height;
- coordf_t height = slicing_params.first_object_layer_height;
+ double slice_z = slicing_params.first_object_layer_height;
int current_facet = 0;
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ while (slice_z <= slicing_params.object_print_z_height()) {
+ double height = slicing_params.max_layer_height;
+#else
+ double height = slicing_params.first_object_layer_height;
while ((slice_z - height) <= slicing_params.object_print_z_height()) {
- height = 999;
+ height = 999.0;
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// Slic3r::debugf "\n Slice layer: %d\n", $id;
// determine next layer height
- coordf_t cusp_height = as.cusp_height(slice_z, cusp_value, current_facet);
+ double cusp_height = as.cusp_height((float)slice_z, cusp_value, current_facet);
+
// check for horizontal features and object size
/*
if($self->config->get_value('match_horizontal_surfaces')) {
@@ -303,19 +322,113 @@ std::vector<coordf_t> layer_height_profile_adaptive(
layer_height_profile.push_back(slice_z);
layer_height_profile.push_back(height);
slice_z += height;
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
layer_height_profile.push_back(slice_z);
layer_height_profile.push_back(height);
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
}
- coordf_t last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]);
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ double z_gap = slicing_params.object_print_z_height() - layer_height_profile[layer_height_profile.size() - 2];
+ if (z_gap > 0.0)
+ {
+ layer_height_profile.push_back(slicing_params.object_print_z_height());
+ layer_height_profile.push_back(clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, z_gap));
+ }
+#else
+ double last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]);
layer_height_profile.push_back(last);
layer_height_profile.push_back(slicing_params.first_object_layer_height);
layer_height_profile.push_back(slicing_params.object_print_z_height());
layer_height_profile.push_back(slicing_params.first_object_layer_height);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
return layer_height_profile;
}
+std::vector<double> smooth_height_profile(const std::vector<double>& profile, const SlicingParameters& slicing_params, const HeightProfileSmoothingParams& smoothing_params)
+{
+ auto gauss_blur = [&slicing_params](const std::vector<double>& profile, const HeightProfileSmoothingParams& smoothing_params) -> std::vector<double> {
+ auto gauss_kernel = [] (unsigned int radius) -> std::vector<double> {
+ unsigned int size = 2 * radius + 1;
+ std::vector<double> ret;
+ ret.reserve(size);
+
+ // Reworked from static inline int getGaussianKernelSize(float sigma) taken from opencv-4.1.2\modules\features2d\src\kaze\AKAZEFeatures.cpp
+ double sigma = 0.3 * (double)(radius - 1) + 0.8;
+ double two_sq_sigma = 2.0 * sigma * sigma;
+ double inv_root_two_pi_sq_sigma = 1.0 / ::sqrt(M_PI * two_sq_sigma);
+
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ double x = (double)i - (double)radius;
+ ret.push_back(inv_root_two_pi_sq_sigma * ::exp(-x * x / two_sq_sigma));
+ }
+
+ return ret;
+ };
+
+ // skip first layer ?
+ size_t skip_count = slicing_params.first_object_layer_height_fixed() ? 4 : 0;
+
+ // not enough data to smmoth
+ if ((int)profile.size() - (int)skip_count < 6)
+ return profile;
+
+ unsigned int radius = std::max(smoothing_params.radius, (unsigned int)1);
+ std::vector<double> kernel = gauss_kernel(radius);
+ int two_radius = 2 * (int)radius;
+
+ std::vector<double> ret;
+ size_t size = profile.size();
+ ret.reserve(size);
+
+ // leave first layer untouched
+ for (size_t i = 0; i < skip_count; ++i)
+ {
+ ret.push_back(profile[i]);
+ }
+
+ // smooth the rest of the profile by biasing a gaussian blur
+ // the bias moves the smoothed profile closer to the min_layer_height
+ double delta_h = slicing_params.max_layer_height - slicing_params.min_layer_height;
+ double inv_delta_h = (delta_h != 0.0) ? 1.0 / delta_h : 1.0;
+
+ double max_dz_band = (double)radius * slicing_params.layer_height;
+ for (size_t i = skip_count; i < size; i += 2)
+ {
+ double zi = profile[i];
+ double hi = profile[i + 1];
+ ret.push_back(zi);
+ ret.push_back(0.0);
+ double& height = ret.back();
+ int begin = std::max((int)i - two_radius, (int)skip_count);
+ int end = std::min((int)i + two_radius, (int)size - 2);
+ double weight_total = 0.0;
+ for (int j = begin; j <= end; j += 2)
+ {
+ int kernel_id = radius + (j - (int)i) / 2;
+ double dz = std::abs(zi - profile[j]);
+ if (dz * slicing_params.layer_height <= max_dz_band)
+ {
+ double dh = std::abs(slicing_params.max_layer_height - profile[j + 1]);
+ double weight = kernel[kernel_id] * sqrt(dh * inv_delta_h);
+ height += weight * profile[j + 1];
+ weight_total += weight;
+ }
+ }
+
+ height = clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, (weight_total != 0.0) ? height /= weight_total : hi);
+ if (smoothing_params.keep_min)
+ height = std::min(height, hi);
+ }
+
+ return ret;
+ };
+
+ return gauss_blur(profile, smoothing_params);
+}
+
void adjust_layer_height_profile(
const SlicingParameters &slicing_params,
std::vector<coordf_t> &layer_height_profile,
@@ -609,7 +722,11 @@ int generate_layer_height_texture(
const Vec3crd &color1 = palette_raw[idx1];
const Vec3crd &color2 = palette_raw[idx2];
coordf_t z = cell_to_z * coordf_t(cell);
- assert(z >= lo && z <= hi);
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ assert((lo - EPSILON <= z) && (z <= hi + EPSILON));
+#else
+ assert(z >= lo && z <= hi);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// Intensity profile to visualize the layers.
coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h);
// Color mapping from layer height to RGB.
diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp
index 064363ec2..03ef7e67d 100644
--- a/src/libslic3r/Slicing.hpp
+++ b/src/libslic3r/Slicing.hpp
@@ -18,8 +18,12 @@ namespace Slic3r
class PrintConfig;
class PrintObjectConfig;
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+class ModelObject;
+#else
class ModelVolume;
typedef std::vector<ModelVolume*> ModelVolumePtrs;
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// Parameters to guide object slicing and support generation.
// The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow
@@ -138,11 +142,29 @@ extern std::vector<coordf_t> layer_height_profile_from_ranges(
const SlicingParameters &slicing_params,
const t_layer_config_ranges &layer_config_ranges);
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+extern std::vector<double> layer_height_profile_adaptive(
+ const SlicingParameters& slicing_params,
+ const ModelObject& object, float cusp_value);
+
+struct HeightProfileSmoothingParams
+{
+ unsigned int radius;
+ bool keep_min;
+
+ HeightProfileSmoothingParams() : radius(5), keep_min(false) {}
+ HeightProfileSmoothingParams(unsigned int radius, bool keep_min) : radius(radius), keep_min(keep_min) {}
+};
+
+extern std::vector<double> smooth_height_profile(
+ const std::vector<double>& profile, const SlicingParameters& slicing_params,
+ const HeightProfileSmoothingParams& smoothing_params);
+#else
extern std::vector<coordf_t> layer_height_profile_adaptive(
const SlicingParameters &slicing_params,
const t_layer_config_ranges &layer_config_ranges,
const ModelVolumePtrs &volumes);
-
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
enum LayerHeightEditActionType : unsigned int {
LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0,
diff --git a/src/libslic3r/SlicingAdaptive.cpp b/src/libslic3r/SlicingAdaptive.cpp
index ad03b550b..b6ebf1ac0 100644
--- a/src/libslic3r/SlicingAdaptive.cpp
+++ b/src/libslic3r/SlicingAdaptive.cpp
@@ -1,16 +1,22 @@
#include "libslic3r.h"
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+#include "Model.hpp"
+#else
#include "TriangleMesh.hpp"
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
#include "SlicingAdaptive.hpp"
namespace Slic3r
{
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
void SlicingAdaptive::clear()
{
- m_meshes.clear();
+ m_meshes.clear();
m_faces.clear();
m_face_normal_z.clear();
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
std::pair<float, float> face_z_span(const stl_facet *f)
{
@@ -21,38 +27,54 @@ std::pair<float, float> face_z_span(const stl_facet *f)
void SlicingAdaptive::prepare()
{
- // 1) Collect faces of all meshes.
- int nfaces_total = 0;
- for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ if (m_object == nullptr)
+ return;
+
+ m_faces.clear();
+ m_face_normal_z.clear();
+
+ m_mesh = m_object->raw_mesh();
+ const ModelInstance* first_instance = m_object->instances.front();
+ m_mesh.transform(first_instance->get_matrix(), first_instance->is_left_handed());
+
+ // 1) Collect faces from mesh.
+ m_faces.reserve(m_mesh.stl.stats.number_of_facets);
+ for (stl_facet& face : m_mesh.stl.facet_start)
+ {
+ face.normal.normalize();
+ m_faces.emplace_back(&face);
+ }
+#else
+ // 1) Collect faces of all meshes.
+ int nfaces_total = 0;
+ for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
nfaces_total += (*it_mesh)->stl.stats.number_of_facets;
- m_faces.reserve(nfaces_total);
- for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
- for (const stl_facet &face : (*it_mesh)->stl.facet_start)
- m_faces.emplace_back(&face);
+ m_faces.reserve(nfaces_total);
+ for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
+ for (const stl_facet& face : (*it_mesh)->stl.facet_start)
+ m_faces.emplace_back(&face);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// 2) Sort faces lexicographically by their Z span.
- std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) {
- std::pair<float, float> span1 = face_z_span(f1);
- std::pair<float, float> span2 = face_z_span(f2);
- return span1 < span2;
- });
+ std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { return face_z_span(f1) < face_z_span(f2); });
// 3) Generate Z components of the facet normals.
- m_face_normal_z.assign(m_faces.size(), 0.f);
+ m_face_normal_z.assign(m_faces.size(), 0.0f);
for (size_t iface = 0; iface < m_faces.size(); ++ iface)
m_face_normal_z[iface] = m_faces[iface]->normal(2);
}
float SlicingAdaptive::cusp_height(float z, float cusp_value, int &current_facet)
{
- float height = m_slicing_params.max_layer_height;
+ float height = (float)m_slicing_params.max_layer_height;
bool first_hit = false;
// find all facets intersecting the slice-layer
int ordered_id = current_facet;
for (; ordered_id < int(m_faces.size()); ++ ordered_id) {
- std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]);
- // facet's minimum is higher than slice_z -> end loop
+ std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]);
+ // facet's minimum is higher than slice_z -> end loop
if (zspan.first >= z)
break;
// facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point
@@ -61,14 +83,14 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int &current_facet
if (! first_hit) {
first_hit = true;
current_facet = ordered_id;
- }
+ }
// skip touching facets which could otherwise cause small cusp values
if (zspan.second <= z + EPSILON)
continue;
// compute cusp-height for this facet and store minimum of all heights
float normal_z = m_face_normal_z[ordered_id];
- height = std::min(height, (normal_z == 0.f) ? 9999.f : std::abs(cusp_value / normal_z));
- }
+ height = std::min(height, (normal_z == 0.0f) ? (float)m_slicing_params.max_layer_height : std::abs(cusp_value / normal_z));
+ }
}
// lower height limit due to printer capabilities
@@ -77,8 +99,8 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int &current_facet
// check for sloped facets inside the determined layer and correct height if necessary
if (height > m_slicing_params.min_layer_height) {
for (; ordered_id < int(m_faces.size()); ++ ordered_id) {
- std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]);
- // facet's minimum is higher than slice_z + height -> end loop
+ std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]);
+ // facet's minimum is higher than slice_z + height -> end loop
if (zspan.first >= z + height)
break;
@@ -88,13 +110,13 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int &current_facet
// Compute cusp-height for this facet and check against height.
float normal_z = m_face_normal_z[ordered_id];
- float cusp = (normal_z == 0) ? 9999 : abs(cusp_value / normal_z);
-
+ float cusp = (normal_z == 0.0f) ? (float)m_slicing_params.max_layer_height : std::abs(cusp_value / normal_z);
+
float z_diff = zspan.first - z;
// handle horizontal facets
- if (m_face_normal_z[ordered_id] > 0.999) {
- // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
+ if (normal_z > 0.999f) {
+ // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
height = z_diff;
// Slic3r::debugf "to %f due to near horizontal facet\n", $height;
} else if (cusp > z_diff) {
@@ -112,29 +134,30 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int &current_facet
// lower height limit due to printer capabilities again
height = std::max(height, float(m_slicing_params.min_layer_height));
}
-
+
// Slic3r::debugf "cusp computation, layer-bottom at z:%f, cusp_value:%f, resulting layer height:%f\n", unscale $z, $cusp_value, $height;
return height;
}
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// Returns the distance to the next horizontal facet in Z-dir
// to consider horizontal object features in slice thickness
float SlicingAdaptive::horizontal_facet_distance(float z)
{
for (size_t i = 0; i < m_faces.size(); ++ i) {
- std::pair<float, float> zspan = face_z_span(m_faces[i]);
- // facet's minimum is higher than max forward distance -> end loop
+ std::pair<float, float> zspan = face_z_span(m_faces[i]);
+ // facet's minimum is higher than max forward distance -> end loop
if (zspan.first > z + m_slicing_params.max_layer_height)
break;
// min_z == max_z -> horizontal facet
- if (zspan.first > z && zspan.first == zspan.second)
+ if ((zspan.first > z) && (zspan.first == zspan.second))
return zspan.first - z;
}
// objects maximum?
- return (z + m_slicing_params.max_layer_height > m_slicing_params.object_print_z_height()) ?
- std::max<float>(m_slicing_params.object_print_z_height() - z, 0.f) :
- m_slicing_params.max_layer_height;
+ return (z + (float)m_slicing_params.max_layer_height > (float)m_slicing_params.object_print_z_height()) ?
+ std::max((float)m_slicing_params.object_print_z_height() - z, 0.f) : (float)m_slicing_params.max_layer_height;
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
}; // namespace Slic3r
diff --git a/src/libslic3r/SlicingAdaptive.hpp b/src/libslic3r/SlicingAdaptive.hpp
index bfd081d81..1d2996986 100644
--- a/src/libslic3r/SlicingAdaptive.hpp
+++ b/src/libslic3r/SlicingAdaptive.hpp
@@ -5,29 +5,49 @@
#include "Slicing.hpp"
#include "admesh/stl.h"
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+#include "TriangleMesh.hpp"
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
namespace Slic3r
{
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+class ModelVolume;
+#else
class TriangleMesh;
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
class SlicingAdaptive
{
public:
- void clear();
- void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; }
- void add_mesh(const TriangleMesh *mesh) { m_meshes.push_back(mesh); }
- void prepare();
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void clear();
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; }
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void set_object(const ModelObject& object) { m_object = &object; }
+#else
+ void add_mesh(const TriangleMesh* mesh) { m_meshes.push_back(mesh); }
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void prepare();
float cusp_height(float z, float cusp_value, int &current_facet);
- float horizontal_facet_distance(float z);
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ float horizontal_facet_distance(float z);
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
protected:
SlicingParameters m_slicing_params;
- std::vector<const TriangleMesh*> m_meshes;
- // Collected faces of all meshes, sorted by raising Z of the bottom most face.
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ const ModelObject* m_object;
+ TriangleMesh m_mesh;
+#else
+ std::vector<const TriangleMesh*> m_meshes;
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ // Collected faces of all meshes, sorted by raising Z of the bottom most face.
std::vector<const stl_facet*> m_faces;
- // Z component of face normals, normalized.
+ // Z component of face normals, normalized.
std::vector<float> m_face_normal_z;
};
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 5d0a7592c..0ec8b36ee 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -26,8 +26,6 @@
// Disable synchronization of unselected instances
#define DISABLE_INSTANCES_SYNCH (0 && ENABLE_1_42_0_ALPHA1)
-// Disable imgui dialog for move, rotate and scale gizmos
-#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_1_42_0_ALPHA1)
// Use wxDataViewRender instead of wxDataViewCustomRenderer
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1)
@@ -38,8 +36,21 @@
#define ENABLE_2_2_0_ALPHA1 1
// Enable thumbnail generator
+// When removing this technology, remove it also from stable branch,
+// where it has been partially copied for patch 2.1.1
#define ENABLE_THUMBNAIL_GENERATOR (1 && ENABLE_2_2_0_ALPHA1)
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG (0 && ENABLE_THUMBNAIL_GENERATOR)
-#define ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE (1 && ENABLE_THUMBNAIL_GENERATOR)
+
+// Enable adaptive layer height profile
+#define ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE (1 && ENABLE_2_2_0_ALPHA1)
+
+// Enable grayed variant for gizmos icons in non activable state
+#define ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE (1 && ENABLE_2_2_0_ALPHA1)
+
+// Enable fix for view toolbar background not showing up on Mac with dark mode
+#define ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX (1 && ENABLE_2_2_0_ALPHA1)
+
+// Enable selection for missing files in reload from disk command
+#define ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION (1 && ENABLE_2_2_0_ALPHA1)
#endif // _technologies_h_
diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp
index 9c9f82040..ef935455e 100644
--- a/src/libslic3r/TriangleMesh.hpp
+++ b/src/libslic3r/TriangleMesh.hpp
@@ -198,6 +198,29 @@ private:
void make_expolygons(std::vector<IntersectionLine> &lines, const float closing_radius, ExPolygons* slices) const;
};
+inline void slice_mesh(
+ const TriangleMesh & mesh,
+ const std::vector<float> & z,
+ std::vector<Polygons> & layers,
+ TriangleMeshSlicer::throw_on_cancel_callback_type thr = nullptr)
+{
+ if (mesh.empty()) return;
+ TriangleMeshSlicer slicer(&mesh);
+ slicer.slice(z, &layers, thr);
+}
+
+inline void slice_mesh(
+ const TriangleMesh & mesh,
+ const std::vector<float> & z,
+ std::vector<ExPolygons> & layers,
+ float closing_radius,
+ TriangleMeshSlicer::throw_on_cancel_callback_type thr = nullptr)
+{
+ if (mesh.empty()) return;
+ TriangleMeshSlicer slicer(&mesh);
+ slicer.slice(z, closing_radius, &layers, thr);
+}
+
TriangleMesh make_cube(double x, double y, double z);
// Generate a TriangleMesh of a cylinder
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 6368388c4..859c17e89 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -1,5 +1,5 @@
+cmake_minimum_required(VERSION 3.8)
project(libslic3r_gui)
-cmake_minimum_required(VERSION 2.6)
include(PrecompiledHeader)
@@ -107,6 +107,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Camera.hpp
GUI/wxExtensions.cpp
GUI/wxExtensions.hpp
+ GUI/ExtruderSequenceDialog.cpp
+ GUI/ExtruderSequenceDialog.hpp
GUI/WipeTowerDialog.cpp
GUI/WipeTowerDialog.hpp
GUI/RammingChart.cpp
@@ -138,6 +140,8 @@ set(SLIC3R_GUI_SOURCES
GUI/ProgressStatusBar.cpp
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
+ GUI/Mouse3DController.cpp
+ GUI/Mouse3DController.hpp
Utils/Http.cpp
Utils/Http.hpp
Utils/FixModelByWin10.cpp
@@ -172,7 +176,7 @@ add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
encoding_check(libslic3r_gui)
-target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES})
+target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi)
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
endif ()
diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp
index da522dd5e..fcebf88d1 100644
--- a/src/slic3r/Config/Version.cpp
+++ b/src/slic3r/Config/Version.cpp
@@ -286,16 +286,21 @@ Index::const_iterator Index::find(const Semver &ver) const
return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end();
}
-Index::const_iterator Index::recommended() const
+Index::const_iterator Index::recommended(const Semver &slic3r_version) const
{
const_iterator highest = this->end();
for (const_iterator it = this->begin(); it != this->end(); ++ it)
- if (it->is_current_slic3r_supported() &&
+ if (it->is_slic3r_supported(slic3r_version) &&
(highest == this->end() || highest->config_version < it->config_version))
highest = it;
return highest;
}
+Index::const_iterator Index::recommended() const
+{
+ return this->recommended(Slic3r::SEMVER);
+}
+
std::vector<Index> Index::load_db()
{
boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache";
diff --git a/src/slic3r/Config/Version.hpp b/src/slic3r/Config/Version.hpp
index 19c565ffb..e5f1a2e21 100644
--- a/src/slic3r/Config/Version.hpp
+++ b/src/slic3r/Config/Version.hpp
@@ -71,6 +71,8 @@ public:
// Returns configs().end() if such version does not exist in the index. This shall never happen
// if the index is valid.
const_iterator recommended() const;
+ // Recommended config for a provided slic3r version. Used when checking for slic3r update (slic3r_version is the old one read out from PrusaSlicer.ini)
+ const_iterator recommended(const Semver &slic3r_version) const;
// Returns the filesystem path from which this index has originally been loaded
const boost::filesystem::path& path() const { return m_path; }
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp
index d0bab50c6..dce7c4c99 100644
--- a/src/slic3r/GUI/3DBed.cpp
+++ b/src/slic3r/GUI/3DBed.cpp
@@ -171,6 +171,7 @@ void Bed3D::Axes::render() const
glsafe(::glPopMatrix());
glsafe(::glDisable(GL_LIGHTING));
+ glsafe(::glDisable(GL_DEPTH_TEST));
}
void Bed3D::Axes::render_axis(double length) const
@@ -264,11 +265,14 @@ Point Bed3D::point_projection(const Point& point) const
return m_polygon.point_projection(point);
}
-void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const
+void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const
{
m_scale_factor = scale_factor;
- render_axes();
+ if (show_axes)
+ render_axes();
+
+ glsafe(::glEnable(GL_DEPTH_TEST));
switch (m_type)
{
@@ -280,6 +284,8 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const
default:
case Custom: { render_custom(canvas, theta > 90.0f); break; }
}
+
+ glsafe(::glDisable(GL_DEPTH_TEST));
}
void Bed3D::calc_bounding_boxes() const
diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp
index 132836711..19ecddc0a 100644
--- a/src/slic3r/GUI/3DBed.hpp
+++ b/src/slic3r/GUI/3DBed.hpp
@@ -112,7 +112,7 @@ public:
bool contains(const Point& point) const;
Point point_projection(const Point& point) const;
- void render(GLCanvas3D& canvas, float theta, float scale_factor) const;
+ void render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const;
private:
void calc_bounding_boxes() const;
diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp
index 8c5040eee..5d559c246 100644
--- a/src/slic3r/GUI/3DScene.hpp
+++ b/src/slic3r/GUI/3DScene.hpp
@@ -303,6 +303,8 @@ public:
int instance_id;
bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; }
bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); }
+ bool operator< (const CompositeID &rhs) const
+ { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); }
};
CompositeID composite_id;
// Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID,
diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp
index 6b3f54f3a..d33d945ef 100644
--- a/src/slic3r/GUI/AppConfig.cpp
+++ b/src/slic3r/GUI/AppConfig.cpp
@@ -103,7 +103,7 @@ void AppConfig::load()
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
throw std::runtime_error(
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
- "Try to manualy delete the file to recover from the error. Your user profiles will not be affected.")) +
+ "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
}
@@ -271,6 +271,80 @@ void AppConfig::set_recent_projects(const std::vector<std::string>& recent_proje
}
}
+void AppConfig::set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone)
+{
+ std::string key = std::string("mouse_device:") + name;
+ auto it = m_storage.find(key);
+ if (it == m_storage.end())
+ it = m_storage.insert(std::map<std::string, std::map<std::string, std::string>>::value_type(key, std::map<std::string, std::string>())).first;
+
+ it->second.clear();
+ it->second["translation_speed"] = std::to_string(translation_speed);
+ it->second["translation_deadzone"] = std::to_string(translation_deadzone);
+ it->second["rotation_speed"] = std::to_string(rotation_speed);
+ it->second["rotation_deadzone"] = std::to_string(rotation_deadzone);
+}
+
+bool AppConfig::get_mouse_device_translation_speed(const std::string& name, double& speed)
+{
+ std::string key = std::string("mouse_device:") + name;
+ auto it = m_storage.find(key);
+ if (it == m_storage.end())
+ return false;
+
+ auto it_val = it->second.find("translation_speed");
+ if (it_val == it->second.end())
+ return false;
+
+ speed = ::atof(it_val->second.c_str());
+ return true;
+}
+
+bool AppConfig::get_mouse_device_translation_deadzone(const std::string& name, double& deadzone)
+{
+ std::string key = std::string("mouse_device:") + name;
+ auto it = m_storage.find(key);
+ if (it == m_storage.end())
+ return false;
+
+ auto it_val = it->second.find("translation_deadzone");
+ if (it_val == it->second.end())
+ return false;
+
+ deadzone = ::atof(it_val->second.c_str());
+ return true;
+}
+
+bool AppConfig::get_mouse_device_rotation_speed(const std::string& name, float& speed)
+{
+ std::string key = std::string("mouse_device:") + name;
+ auto it = m_storage.find(key);
+ if (it == m_storage.end())
+ return false;
+
+ auto it_val = it->second.find("rotation_speed");
+ if (it_val == it->second.end())
+ return false;
+
+ speed = (float)::atof(it_val->second.c_str());
+ return true;
+}
+
+bool AppConfig::get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone)
+{
+ std::string key = std::string("mouse_device:") + name;
+ auto it = m_storage.find(key);
+ if (it == m_storage.end())
+ return false;
+
+ auto it_val = it->second.find("rotation_deadzone");
+ if (it_val == it->second.end())
+ return false;
+
+ deadzone = (float)::atof(it_val->second.c_str());
+ return true;
+}
+
void AppConfig::update_config_dir(const std::string &dir)
{
this->set("recent", "config_directory", dir);
diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp
index 97c369ab6..355370450 100644
--- a/src/slic3r/GUI/AppConfig.hpp
+++ b/src/slic3r/GUI/AppConfig.hpp
@@ -131,8 +131,15 @@ public:
std::vector<std::string> get_recent_projects() const;
void set_recent_projects(const std::vector<std::string>& recent_projects);
+ void set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone);
+ bool get_mouse_device_translation_speed(const std::string& name, double& speed);
+ bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone);
+ bool get_mouse_device_rotation_speed(const std::string& name, float& speed);
+ bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone);
+
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
+
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 5ab65f340..e587509ac 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -20,9 +20,6 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
-#if ENABLE_THUMBNAIL_GENERATOR
-#include "libslic3r/GCode/ThumbnailData.hpp"
-#endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/libslic3r.h"
#include <cassert>
@@ -36,6 +33,7 @@
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include "I18N.hpp"
+#include "GUI.hpp"
namespace Slic3r {
@@ -91,11 +89,19 @@ void BackgroundSlicingProcess::process_fff()
m_print->process();
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
#if ENABLE_THUMBNAIL_GENERATOR
- m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_data);
+ m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
#else
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
- if (this->set_step_started(bspsGCodeFinalize)) {
+
+ if (m_fff_print->model().custom_gcode_per_height != GUI::wxGetApp().model().custom_gcode_per_height) {
+ GUI::wxGetApp().model().custom_gcode_per_height = m_fff_print->model().custom_gcode_per_height;
+ // #ys_FIXME : controll text
+ GUI::show_info(nullptr, _(L("To except of redundant tool manipulation, \n"
+ "Color change(s) for unused extruder(s) was(were) deleted")), _(L("Info")));
+ }
+
+ if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
//FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name.
@@ -111,7 +117,7 @@ void BackgroundSlicingProcess::process_fff()
m_print->set_status(100, _utf8(L("Slicing complete")));
}
this->set_step_done(bspsGCodeFinalize);
- }
+ }
}
#if ENABLE_THUMBNAIL_GENERATOR
@@ -139,9 +145,12 @@ void BackgroundSlicingProcess::process_sla()
m_sla_print->export_raster(zipper);
#if ENABLE_THUMBNAIL_GENERATOR
- if (m_thumbnail_data != nullptr)
+ if (m_thumbnail_cb != nullptr)
{
- for (const ThumbnailData& data : *m_thumbnail_data)
+ ThumbnailsList thumbnails;
+ m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
+// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true, true); // renders also supports and pad
+ for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
write_thumbnail(zipper, data);
@@ -461,9 +470,12 @@ void BackgroundSlicingProcess::prepare_upload()
Zipper zipper{source_path.string()};
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
#if ENABLE_THUMBNAIL_GENERATOR
- if (m_thumbnail_data != nullptr)
+ if (m_thumbnail_cb != nullptr)
{
- for (const ThumbnailData& data : *m_thumbnail_data)
+ ThumbnailsList thumbnails;
+ m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true);
+// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, true, true); // renders also supports and pad
+ for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
write_thumbnail(zipper, data);
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
index a603d52ac..a66dcf39c 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp
@@ -17,9 +17,6 @@ namespace Slic3r {
class DynamicPrintConfig;
class GCodePreviewData;
-#if ENABLE_THUMBNAIL_GENERATOR
-struct ThumbnailData;
-#endif // ENABLE_THUMBNAIL_GENERATOR
class Model;
class SLAPrint;
@@ -53,7 +50,7 @@ public:
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
#if ENABLE_THUMBNAIL_GENERATOR
- void set_thumbnail_data(const std::vector<ThumbnailData>* data) { m_thumbnail_data = data; }
+ void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
#endif // ENABLE_THUMBNAIL_GENERATOR
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
@@ -135,6 +132,11 @@ public:
// This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
// and it does not account for the OctoPrint scheduling.
bool finished() const { return m_print->finished(); }
+
+ void set_force_update_print_regions(bool force_update_print_regions) {
+ if (m_fff_print)
+ m_fff_print->set_force_update_print_regions(force_update_print_regions);
+ }
private:
void thread_proc();
@@ -159,8 +161,8 @@ private:
// Data structure, to which the G-code export writes its annotations.
GCodePreviewData *m_gcode_preview_data = nullptr;
#if ENABLE_THUMBNAIL_GENERATOR
- // Data structures, used to write thumbnails into gcode.
- const std::vector<ThumbnailData>* m_thumbnail_data = nullptr;
+ // Callback function, used to write thumbnails into gcode.
+ ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
#endif // ENABLE_THUMBNAIL_GENERATOR
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path;
diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp
index 9fbabe930..bb514e888 100644
--- a/src/slic3r/GUI/Camera.cpp
+++ b/src/slic3r/GUI/Camera.cpp
@@ -91,10 +91,16 @@ void Camera::select_next_type()
void Camera::set_target(const Vec3d& target)
{
- m_target = target;
- m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0));
- m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1));
- m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2));
+ BoundingBoxf3 test_box = m_scene_box;
+ test_box.translate(-m_scene_box.center());
+ // We may let this factor be customizable
+ static const double ScaleFactor = 1.5;
+ test_box.scale(ScaleFactor);
+ test_box.translate(m_scene_box.center());
+
+ m_target(0) = clamp(test_box.min(0), test_box.max(0), target(0));
+ m_target(1) = clamp(test_box.min(1), test_box.max(1), target(1));
+ m_target(2) = clamp(test_box.min(2), test_box.max(2), target(2));
}
void Camera::set_theta(float theta, bool apply_limit)
@@ -109,20 +115,20 @@ void Camera::set_theta(float theta, bool apply_limit)
}
}
-void Camera::set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h)
+void Camera::update_zoom(double delta_zoom)
{
- zoom = std::max(std::min(zoom, 4.0), -4.0) / 10.0;
- zoom = m_zoom / (1.0 - zoom);
+ set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1));
+}
+void Camera::set_zoom(double zoom)
+{
// Don't allow to zoom too far outside the scene.
- double zoom_min = calc_zoom_to_bounding_box_factor(max_box, canvas_w, canvas_h);
+ double zoom_min = calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]);
if (zoom_min > 0.0)
zoom = std::max(zoom, zoom_min * 0.7);
// Don't allow to zoom too close to the scene.
- zoom = std::min(zoom, 100.0);
-
- m_zoom = zoom;
+ m_zoom = std::min(zoom, 100.0);
}
bool Camera::select_view(const std::string& direction)
@@ -190,7 +196,7 @@ void Camera::apply_view_matrix() const
glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data()));
}
-void Camera::apply_projection(const BoundingBoxf3& box) const
+void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const
{
set_distance(DefaultDistance);
@@ -201,6 +207,12 @@ void Camera::apply_projection(const BoundingBoxf3& box) const
{
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
+ if (near_z > 0.0)
+ m_frustrum_zs.first = std::min(m_frustrum_zs.first, near_z);
+
+ if (far_z > 0.0)
+ m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z);
+
w = 0.5 * (double)m_viewport[2];
h = 0.5 * (double)m_viewport[3];
@@ -310,7 +322,6 @@ void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canv
void Camera::debug_render() const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
- imgui.set_next_window_bg_alpha(0.5f);
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
std::string type = get_type_as_string();
@@ -355,27 +366,10 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
while (true)
{
- ret = std::make_pair(DBL_MAX, -DBL_MAX);
-
- // box vertices in world space
- std::vector<Vec3d> vertices;
- vertices.reserve(8);
- vertices.push_back(box.min);
- vertices.emplace_back(box.max(0), box.min(1), box.min(2));
- vertices.emplace_back(box.max(0), box.max(1), box.min(2));
- vertices.emplace_back(box.min(0), box.max(1), box.min(2));
- vertices.emplace_back(box.min(0), box.min(1), box.max(2));
- vertices.emplace_back(box.max(0), box.min(1), box.max(2));
- vertices.push_back(box.max);
- vertices.emplace_back(box.min(0), box.max(1), box.max(2));
-
- // set the Z range in eye coordinates (negative Zs are in front of the camera)
- for (const Vec3d& v : vertices)
- {
- double z = -(m_view_matrix * v)(2);
- ret.first = std::min(ret.first, z);
- ret.second = std::max(ret.second, z);
- }
+ // box in eye space
+ BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
+ ret.first = -eye_box.max(2);
+ ret.second = -eye_box.min(2);
// apply margin
ret.first -= FrustrumZMargin;
@@ -434,8 +428,10 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
vertices.push_back(box.max);
vertices.emplace_back(box.min(0), box.max(1), box.max(2));
- double max_x = 0.0;
- double max_y = 0.0;
+ double min_x = DBL_MAX;
+ double min_y = DBL_MAX;
+ double max_x = -DBL_MAX;
+ double max_y = -DBL_MAX;
#if !ENABLE_THUMBNAIL_GENERATOR
// margin factor to give some empty space around the box
@@ -452,17 +448,24 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
double x_on_plane = proj_on_plane.dot(right);
double y_on_plane = proj_on_plane.dot(up);
- max_x = std::max(max_x, std::abs(x_on_plane));
- max_y = std::max(max_y, std::abs(y_on_plane));
+ min_x = std::min(min_x, x_on_plane);
+ min_y = std::min(min_y, y_on_plane);
+ max_x = std::max(max_x, x_on_plane);
+ max_y = std::max(max_y, y_on_plane);
}
- if ((max_x == 0.0) || (max_y == 0.0))
+ double dx = max_x - min_x;
+ double dy = max_y - min_y;
+ if ((dx <= 0.0) || (dy <= 0.0))
return -1.0f;
- max_x *= margin_factor;
- max_y *= margin_factor;
+ double med_x = 0.5 * (max_x + min_x);
+ double med_y = 0.5 * (max_y + min_y);
+
+ dx *= margin_factor;
+ dy *= margin_factor;
- return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y));
+ return std::min((double)canvas_w / dx, (double)canvas_h / dy);
}
#if ENABLE_THUMBNAIL_GENERATOR
@@ -524,7 +527,7 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv
double dx = margin_factor * (max_x - min_x);
double dy = margin_factor * (max_y - min_y);
- if ((dx == 0.0) || (dy == 0.0))
+ if ((dx <= 0.0) || (dy <= 0.0))
return -1.0f;
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp
index cb634138f..6cd1b75a5 100644
--- a/src/slic3r/GUI/Camera.hpp
+++ b/src/slic3r/GUI/Camera.hpp
@@ -70,8 +70,8 @@ public:
void set_theta(float theta, bool apply_limit);
double get_zoom() const { return m_zoom; }
- void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h);
- void set_zoom(double zoom) { m_zoom = zoom; }
+ void update_zoom(double delta_zoom);
+ void set_zoom(double zoom);
const BoundingBoxf3& get_scene_box() const { return m_scene_box; }
void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; }
@@ -95,7 +95,9 @@ public:
void apply_viewport(int x, int y, unsigned int w, unsigned int h) const;
void apply_view_matrix() const;
- void apply_projection(const BoundingBoxf3& box) const;
+ // Calculates and applies the projection matrix tighting the frustrum z range around the given box.
+ // If larger z span is needed, pass the desired values of near and far z (negative values are ignored)
+ void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const;
#if ENABLE_THUMBNAIL_GENERATOR
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor);
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index f76f752f0..966f34761 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -77,7 +77,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
"- no top solid layers\n"
"- 0% fill density\n"
"- no support material\n"
- "- no ensure_vertical_shell_thickness"));
+ "- inactive Ensure vertical shell thickness"));
if (is_global_config)
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable Spiral Vase?"));
wxMessageDialog dialog(nullptr, msg_text, _(L("Spiral Vase")),
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index 393d973b7..971bd1f1d 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -374,7 +374,7 @@ ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxStrin
sizer->AddSpacer(10);
content = new wxBoxSizer(wxVERTICAL);
- sizer->Add(content, 1);
+ sizer->Add(content, 1, wxEXPAND);
SetSizer(sizer);
@@ -504,7 +504,8 @@ void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
{
if (technology == T_FFF
&& (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
- && printer_pickers.size() > 0) {
+ && printer_pickers.size() > 0
+ && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
printer_pickers[0]->select_one(0, true);
}
}
@@ -528,15 +529,17 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
list_l2->SetMinSize(wxSize(13*em, list_h));
list_l3->SetMinSize(wxSize(25*em, list_h));
- auto *grid = new wxFlexGridSizer(3, 0, em);
+ auto *grid = new wxFlexGridSizer(3, em/2, em);
+ grid->AddGrowableCol(2, 1);
+ grid->AddGrowableRow(1, 1);
grid->Add(new wxStaticText(this, wxID_ANY, list1name));
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Vendor:"))));
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Profile:"))));
- grid->Add(list_l1);
- grid->Add(list_l2);
- grid->Add(list_l3);
+ grid->Add(list_l1, 0, wxEXPAND);
+ grid->Add(list_l2, 0, wxEXPAND);
+ grid->Add(list_l3, 1, wxEXPAND);
auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *sel_all = new wxButton(this, wxID_ANY, _(L("All")));
@@ -548,7 +551,7 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
grid->Add(new wxBoxSizer(wxHORIZONTAL));
grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
- append(grid);
+ append(grid, 1, wxEXPAND);
list_l1->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
update_lists(list_l1->GetSelection(), list_l2->GetSelection());
@@ -626,10 +629,28 @@ void PageMaterials::update_lists(int sel1, int sel2)
const std::string &vendor = list_l2->get_data(sel2);
materials->filter_presets(type, vendor, [this](const Preset *p) {
- const int i = list_l3->append(p->name, p);
- const bool checked = wizard_p()->appconfig_new.has(materials->appconfig_section(), p->name);
- list_l3->Check(i, checked);
- });
+ bool was_checked = false;
+
+ int cur_i = list_l3->find(p->alias);
+ if (cur_i == wxNOT_FOUND)
+ cur_i = list_l3->append(p->alias, &p->alias);
+ else
+ was_checked = list_l3->IsChecked(cur_i);
+
+ const std::string& section = materials->appconfig_section();
+
+ const bool checked = wizard_p()->appconfig_new.has(section, p->name);
+ list_l3->Check(cur_i, checked | was_checked);
+
+ /* Update preset selection in config.
+ * If one preset from aliases bundle is selected,
+ * than mark all presets with this aliases as selected
+ * */
+ if (checked && !was_checked)
+ wizard_p()->update_presets_in_config(section, p->alias, true);
+ else if (!checked && was_checked)
+ wizard_p()->appconfig_new.set(section, p->name, "1");
+ } );
}
sel2_prev = sel2;
@@ -639,13 +660,9 @@ void PageMaterials::update_lists(int sel1, int sel2)
void PageMaterials::select_material(int i)
{
const bool checked = list_l3->IsChecked(i);
- const Preset &preset = list_l3->get_data(i);
- if (checked) {
- wizard_p()->appconfig_new.set(materials->appconfig_section(), preset.name, "1");
- } else {
- wizard_p()->appconfig_new.erase(materials->appconfig_section(), preset.name);
- }
+ const std::string& alias_key = list_l3->get_data(i);
+ wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
}
void PageMaterials::select_all(bool select)
@@ -753,8 +770,8 @@ PageMode::PageMode(ConfigWizard *parent)
{
append_text(_(L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n"
"The Simple mode shows only the most frequently used settings relevant for regular 3D printing. "
- "The other two offer progressivly more sophisticated fine-tuning, "
- "they are suitable for advanced and expert usiser, respectively.")));
+ "The other two offer progressively more sophisticated fine-tuning, "
+ "they are suitable for advanced and expert users, respectively.")));
radio_simple = new wxRadioButton(this, wxID_ANY, _(L("Simple mode")));
radio_advanced = new wxRadioButton(this, wxID_ANY, _(L("Advanced mode")));
@@ -777,11 +794,17 @@ void PageMode::on_activate()
void PageMode::serialize_mode(AppConfig *app_config) const
{
- const char *mode = "simple";
+ std::string mode = "";
+ if (radio_simple->GetValue()) { mode = "simple"; }
if (radio_advanced->GetValue()) { mode = "advanced"; }
if (radio_expert->GetValue()) { mode = "expert"; }
+ // If "Mode" page wasn't selected (no one radiobutton is checked),
+ // we shouldn't to update a view_mode value in app_config
+ if (mode.empty())
+ return;
+
app_config->set("view_mode", mode);
}
@@ -790,7 +813,7 @@ PageVendors::PageVendors(ConfigWizard *parent)
{
const AppConfig &appconfig = this->wizard_p()->appconfig_new;
- append_text(wxString::Format(_(L("Pick another vendor supported by %s: (FIXME: this text)")), SLIC3R_APP_NAME));
+ append_text(wxString::Format(_(L("Pick another vendor supported by %s")), SLIC3R_APP_NAME) + ":");
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
@@ -809,9 +832,11 @@ PageVendors::PageVendors(ConfigWizard *parent)
if (enabled) {
cbox->SetValue(true);
- auto pair = wizard_p()->pages_3rdparty.find(vendor->id);
- wxCHECK_RET(pair != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
- pair->second->install = true;
+ auto pages = wizard_p()->pages_3rdparty.find(vendor->id);
+ wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created");
+
+ for (PagePrinters* page : { pages->second.first, pages->second.second })
+ if (page) page->install = true;
}
append(cbox);
@@ -1221,7 +1246,7 @@ const std::string Materials::UNKNOWN = "(Unknown)";
void Materials::push(const Preset *preset)
{
- presets.insert(preset);
+ presets.push_back(preset);
types.insert(technology & T_FFF
? Materials::get_filament_type(preset)
: Materials::get_material_type(preset));
@@ -1309,9 +1334,10 @@ void ConfigWizard::priv::load_pages()
index->add_page(page_fff);
index->add_page(page_msla);
index->add_page(page_vendors);
- for (const auto &pair : pages_3rdparty) {
- PagePrinters *page = pair.second;
- if (page->install) { index->add_page(page); }
+ for (const auto &pages : pages_3rdparty) {
+ for ( PagePrinters* page : { pages.second.first, pages.second.second })
+ if (page && page->install)
+ index->add_page(page);
}
index->add_page(page_custom);
@@ -1326,6 +1352,9 @@ void ConfigWizard::priv::load_pages()
if (any_fff_selected) { index->add_page(page_filaments); }
if (any_sla_selected) { index->add_page(page_sla_materials); }
+ // there should to be selected at least one printer
+ btn_finish->Enable(any_fff_selected || any_sla_selected);
+
index->add_page(page_update);
index->add_page(page_mode);
@@ -1384,8 +1413,6 @@ void ConfigWizard::priv::load_vendors()
pair.second.preset_bundle->load_installed_printers(appconfig_new);
}
- update_materials(T_ANY);
-
if (app_config->has_section(AppConfig::SECTION_FILAMENTS)) {
appconfig_new.set_section(AppConfig::SECTION_FILAMENTS, app_config->get_section(AppConfig::SECTION_FILAMENTS));
}
@@ -1396,7 +1423,8 @@ void ConfigWizard::priv::load_vendors()
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
{
- hscroll_sizer->Add(page, 0, wxEXPAND);
+ const int proportion = (page->shortname == _(L("Filaments"))) || (page->shortname == _(L("SLA Materials"))) ? 1 : 0;
+ hscroll_sizer->Add(page, proportion, wxEXPAND);
all_pages.push_back(page);
}
@@ -1409,10 +1437,22 @@ void ConfigWizard::priv::enable_next(bool enable)
void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page)
{
switch (start_page) {
- case ConfigWizard::SP_PRINTERS: index->go_to(page_fff); break;
- case ConfigWizard::SP_FILAMENTS: index->go_to(page_filaments); break;
- case ConfigWizard::SP_MATERIALS: index->go_to(page_sla_materials); break;
- default: index->go_to(page_welcome); break;
+ case ConfigWizard::SP_PRINTERS:
+ index->go_to(page_fff);
+ btn_next->SetFocus();
+ break;
+ case ConfigWizard::SP_FILAMENTS:
+ index->go_to(page_filaments);
+ btn_finish->SetFocus();
+ break;
+ case ConfigWizard::SP_MATERIALS:
+ index->go_to(page_sla_materials);
+ btn_finish->SetFocus();
+ break;
+ default:
+ index->go_to(page_welcome);
+ btn_next->SetFocus();
+ break;
}
}
@@ -1422,10 +1462,31 @@ void ConfigWizard::priv::create_3rdparty_pages()
const VendorProfile *vendor = pair.second.vendor_profile;
if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; }
- auto *page = new PagePrinters(q, vendor->name, vendor->name, *vendor, 1, T_ANY);
- add_page(page);
+ bool is_fff_technology = false;
+ bool is_sla_technology = false;
+
+ for (auto& model: vendor->models)
+ {
+ if (!is_fff_technology && model.technology == ptFFF)
+ is_fff_technology = true;
+ if (!is_sla_technology && model.technology == ptSLA)
+ is_sla_technology = true;
+ }
+
+ PagePrinters* pageFFF = nullptr;
+ PagePrinters* pageSLA = nullptr;
+
+ if (is_fff_technology) {
+ pageFFF = new PagePrinters(q, vendor->name + " " +_(L("FFF Technology Printers")), vendor->name+" FFF", *vendor, 1, T_FFF);
+ add_page(pageFFF);
+ }
+
+ if (is_sla_technology) {
+ pageSLA = new PagePrinters(q, vendor->name + " " + _(L("SLA Technology Printers")), vendor->name+" MSLA", *vendor, 1, T_SLA);
+ add_page(pageSLA);
+ }
- pages_3rdparty.insert({vendor->id, page});
+ pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}});
}
}
@@ -1441,52 +1502,54 @@ void ConfigWizard::priv::update_materials(Technology technology)
{
if (any_fff_selected && (technology & T_FFF)) {
filaments.clear();
+ aliases_fff.clear();
// Iterate filaments in all bundles
for (const auto &pair : bundles) {
for (const auto &filament : pair.second.preset_bundle->filaments) {
// Check if filament is already added
- if (filaments.containts(&filament)) { continue; }
-
+ if (filaments.containts(&filament))
+ continue;
// Iterate printers in all bundles
- for (const auto &pair : bundles) {
- for (const auto &printer : pair.second.preset_bundle->printers) {
+ // For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
+// for (const auto &pair : bundles)
+ for (const auto &printer : pair.second.preset_bundle->printers)
// Filter out inapplicable printers
- if (!printer.is_visible || printer.printer_technology() != ptFFF) {
- continue;
- }
-
- if (filament.is_compatible_with_printer(printer)) {
+ if (printer.is_visible && printer.printer_technology() == ptFFF &&
+ is_compatible_with_printer(PresetWithVendorProfile(filament, nullptr), PresetWithVendorProfile(printer, nullptr)) &&
+ // Check if filament is already added
+ ! filaments.containts(&filament)) {
filaments.push(&filament);
+ if (!filament.alias.empty())
+ aliases_fff[filament.alias].insert(filament.name);
}
- }
- }
}
}
}
if (any_sla_selected && (technology & T_SLA)) {
sla_materials.clear();
+ aliases_sla.clear();
// Iterate SLA materials in all bundles
for (const auto &pair : bundles) {
for (const auto &material : pair.second.preset_bundle->sla_materials) {
// Check if material is already added
- if (sla_materials.containts(&material)) { continue; }
-
+ if (sla_materials.containts(&material))
+ continue;
// Iterate printers in all bundles
- for (const auto &pair : bundles) {
- for (const auto &printer : pair.second.preset_bundle->printers) {
+ // For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
+// for (const auto &pair : bundles)
+ for (const auto &printer : pair.second.preset_bundle->printers)
// Filter out inapplicable printers
- if (!printer.is_visible || printer.printer_technology() != ptSLA) {
- continue;
- }
-
- if (material.is_compatible_with_printer(printer)) {
+ if (printer.is_visible && printer.printer_technology() == ptSLA &&
+ is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr)) &&
+ // Check if material is already added
+ ! sla_materials.containts(&material)) {
sla_materials.push(&material);
+ if (!material.alias.empty())
+ aliases_sla[material.alias].insert(material.name);
}
- }
- }
}
}
}
@@ -1499,10 +1562,10 @@ void ConfigWizard::priv::on_custom_setup()
void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt)
{
- if (page_msla->any_selected() != any_sla_selected ||
- page_fff->any_selected() != any_fff_selected) {
- any_fff_selected = page_fff->any_selected();
- any_sla_selected = page_msla->any_selected();
+ if (check_sla_selected() != any_sla_selected ||
+ check_fff_selected() != any_fff_selected) {
+ any_fff_selected = check_fff_selected();
+ any_sla_selected = check_sla_selected();
load_pages();
}
@@ -1519,9 +1582,9 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
}
}
- if (page == page_fff) {
+ if (page->technology & T_FFF) {
page_filaments->clear();
- } else if (page == page_msla) {
+ } else if (page->technology & T_SLA) {
page_sla_materials->clear();
}
}
@@ -1530,17 +1593,48 @@ void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool i
{
auto it = pages_3rdparty.find(vendor->id);
wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile");
- PagePrinters *page = it->second;
- if (page->install && !install) {
- page->select_all(false);
- }
- page->install = install;
- page->Layout();
+ for (PagePrinters* page : { it->second.first, it->second.second })
+ if (page) {
+ if (page->install && !install)
+ page->select_all(false);
+ page->install = install;
+ page->Layout();
+ }
load_pages();
}
+bool ConfigWizard::priv::check_material_config(Technology technology)
+{
+ const auto exist_preset = [this](const std::string& section, const Materials& materials)
+ {
+ if (appconfig_new.has_section(section) &&
+ !appconfig_new.get_section(section).empty())
+ {
+ const std::map<std::string, std::string>& appconfig_presets = appconfig_new.get_section(section);
+ for (const auto& preset : appconfig_presets)
+ if (materials.exist_preset(preset.first))
+ return true;
+ }
+ return false;
+ };
+
+ if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments))
+ {
+ show_info(q, _(L("You have to select at least one filament for selected printers")), "");
+ return false;
+ }
+
+ if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials))
+ {
+ show_info(q, _(L("You have to select at least one material for selected printers")), "");
+ return false;
+ }
+
+ return true;
+}
+
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const auto enabled_vendors = appconfig_new.vendors();
@@ -1666,11 +1760,48 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
preset_bundle->export_selections(*app_config);
}
+void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
+{
+ const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla;
+
+ auto update = [this, add](const std::string& s, const std::string& key) {
+ if (add)
+ appconfig_new.set(s, key, "1");
+ else
+ appconfig_new.erase(s, key);
+ };
+
+ // add or delete presets had a same alias
+ auto it = aliases.find(alias_key);
+ if (it != aliases.end())
+ for (const std::string& name : it->second)
+ update(section, name);
+}
+
+bool ConfigWizard::priv::check_fff_selected()
+{
+ bool ret = page_fff->any_selected();
+ for (const auto& printer: pages_3rdparty)
+ if (printer.second.first) // FFF page
+ ret |= printer.second.first->any_selected();
+
+ return ret;
+}
+
+bool ConfigWizard::priv::check_sla_selected()
+{
+ bool ret = page_msla->any_selected();
+ for (const auto& printer: pages_3rdparty)
+ if (printer.second.second) // SLA page
+ ret |= printer.second.second->any_selected();
+ return ret;
+}
+
// Public
ConfigWizard::ConfigWizard(wxWindow *parent)
- : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name().ToStdString()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, p(new priv(this))
{
this->SetFont(wxGetApp().normal_font());
@@ -1722,10 +1853,15 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->page_msla = new PagePrinters(this, _(L("Prusa MSLA Technology Printers")), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
p->add_page(p->page_msla);
+ p->any_sla_selected = p->check_sla_selected();
+ p->any_fff_selected = p->check_fff_selected();
+
+ p->update_materials(T_ANY);
+
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
_(L("Filament Profiles Selection")), _(L("Filaments")), _(L("Type:")) ));
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
- _(L("SLA Material Profiles Selection")), _(L("SLA Materials")), _(L("Layer height:")) ));
+ _(L("SLA Material Profiles Selection")) + " ", _(L("SLA Materials")), _(L("Layer height:")) ));
p->add_page(p->page_custom = new PageCustom(this));
p->add_page(p->page_update = new PageUpdate(this));
@@ -1739,9 +1875,6 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->create_3rdparty_pages(); // Needs to ne done _before_ creating PageVendors
p->add_page(p->page_vendors = new PageVendors(this));
- p->any_sla_selected = p->page_msla->any_selected();
- p->any_fff_selected = p->page_fff->any_selected();
-
p->load_pages();
p->index->go_to(size_t{0});
@@ -1760,9 +1893,23 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
});
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); });
- p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_next(); });
- p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->EndModal(wxID_OK); });
- p->btn_finish->Hide();
+
+ p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
+ {
+ // check, that there is selected at least one filament/material
+ ConfigWizardPage* active_page = this->p->index->active_page();
+ if ( (active_page == p->page_filaments || active_page == p->page_sla_materials)
+ && !p->check_material_config(dynamic_cast<PageMaterials*>(active_page)->materials->technology))
+ return;
+ this->p->index->go_next();
+ });
+
+ p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &)
+ {
+ if (!p->check_material_config(T_ANY))
+ return;
+ this->EndModal(wxID_OK);
+ });
p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) {
p->any_sla_selected = true;
@@ -1775,7 +1922,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) {
const bool is_last = p->index->active_is_last();
p->btn_next->Show(! is_last);
- p->btn_finish->Show(is_last);
+ if (is_last)
+ p->btn_finish->SetFocus();
Layout();
});
diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp
index 995957816..fc91909e7 100644
--- a/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -58,15 +58,16 @@ enum Technology {
struct Materials
{
Technology technology;
- std::set<const Preset*> presets;
+ // use vector for the presets to purpose of save of presets sorting in the bundle
+ std::vector<const Preset*> presets;
std::set<std::string> types;
Materials(Technology technology) : technology(technology) {}
void push(const Preset *preset);
void clear();
- bool containts(const Preset *preset) {
- return presets.find(preset) != presets.end();
+ bool containts(const Preset *preset) const {
+ return std::find(presets.begin(), presets.end(), preset) != presets.end();
}
const std::string& appconfig_section() const;
@@ -81,6 +82,14 @@ struct Materials
}
}
+ bool exist_preset(const std::string& preset_name) const
+ {
+ for (const Preset* preset : presets)
+ if (preset->name == preset_name)
+ return true;
+ return false;
+ }
+
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
@@ -240,10 +249,12 @@ template<class T, class D> struct DataList : public T
return wxNOT_FOUND;
}
+
+ int size() { return this->GetCount(); }
};
typedef DataList<wxListBox, std::string> StringList;
-typedef DataList<wxCheckListBox, Preset> PresetList;
+typedef DataList<wxCheckListBox, std::string> PresetList;
struct PageMaterials: ConfigWizardPage
{
@@ -343,7 +354,10 @@ struct PageTemperatures: ConfigWizardPage
virtual void apply_custom_config(DynamicPrintConfig &config);
};
-typedef std::map<std::string /* = vendor ID */, PagePrinters*> Pages3rdparty;
+// hypothetically, each vendor can has printers both of technologies (FFF and SLA)
+typedef std::map<std::string /* = vendor ID */,
+ std::pair<PagePrinters* /* = FFF page */,
+ PagePrinters* /* = SLA page */>> Pages3rdparty;
class ConfigWizardIndex: public wxPanel
@@ -404,6 +418,8 @@ wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
// ConfigWizard private data
+typedef std::map<std::string, std::set<std::string>> PresetAliases;
+
struct ConfigWizard::priv
{
ConfigWizard *q;
@@ -415,6 +431,8 @@ struct ConfigWizard::priv
// PrinterPickers state.
Materials filaments; // Holds available filament presets and their types & vendors
Materials sla_materials; // Ditto for SLA materials
+ PresetAliases aliases_fff; // Map of aliase to preset names
+ PresetAliases aliases_sla; // Map of aliase to preset names
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page
@@ -454,7 +472,6 @@ struct ConfigWizard::priv
: q(q)
, filaments(T_FFF)
, sla_materials(T_SLA)
- , any_sla_selected(false)
{}
void load_pages();
@@ -472,13 +489,17 @@ struct ConfigWizard::priv
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
+ bool check_material_config(Technology technology);
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
+ // #ys_FIXME_alise
+ void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
+
+ bool check_fff_selected(); // Used to decide whether to display Filaments page
+ bool check_sla_selected(); // Used to decide whether to display SLA Materials page
int em() const { return index->em(); }
};
-
-
}
}
diff --git a/src/slic3r/GUI/ExtruderSequenceDialog.cpp b/src/slic3r/GUI/ExtruderSequenceDialog.cpp
new file mode 100644
index 000000000..a850ac192
--- /dev/null
+++ b/src/slic3r/GUI/ExtruderSequenceDialog.cpp
@@ -0,0 +1,235 @@
+#include "ExtruderSequenceDialog.hpp"
+
+#include <wx/wx.h>
+#include <wx/stattext.h>
+#include <wx/dialog.h>
+#include <wx/sizer.h>
+#include <wx/bmpcbox.h>
+
+#include <vector>
+#include <set>
+#include <functional>
+
+#include "GUI.hpp"
+#include "GUI_App.hpp"
+#include "I18N.hpp"
+#include "OptionsGroup.hpp"
+
+
+namespace Slic3r {
+namespace GUI {
+
+ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence)
+ : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Set extruder sequence")),
+ wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
+ m_sequence(sequence)
+{
+ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ SetDoubleBuffered(true);
+ SetFont(wxGetApp().normal_font());
+
+ auto main_sizer = new wxBoxSizer(wxVERTICAL);
+ const int em = wxGetApp().em_unit();
+
+ m_bmp_del = ScalableBitmap(this, "remove_copies");
+ m_bmp_add = ScalableBitmap(this, "add_copies");
+
+ auto option_sizer = new wxBoxSizer(wxVERTICAL);
+
+ auto intervals_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder change for every"))+ " : ");
+ auto intervals_box_sizer = new wxStaticBoxSizer(intervals_box, wxVERTICAL);
+
+ m_intervals_grid_sizer = new wxFlexGridSizer(3, 5, em);
+
+ auto editor_sz = wxSize(4*em, wxDefaultCoord);
+
+ auto ID_RADIO_BUTTON = wxWindow::NewControlId(1);
+
+ wxRadioButton* rb_by_layers = new wxRadioButton(this, ID_RADIO_BUTTON, "", wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
+ rb_by_layers->Bind(wxEVT_RADIOBUTTON, [this](wxCommandEvent& event) { m_sequence.is_mm_intervals = false; });
+ rb_by_layers->SetValue(!m_sequence.is_mm_intervals);
+
+ wxStaticText* st_by_layers = new wxStaticText(this, wxID_ANY, _(L("layers")));
+ m_interval_by_layers = new wxTextCtrl(this, wxID_ANY,
+ wxString::Format("%d", m_sequence.interval_by_layers),
+ wxDefaultPosition, editor_sz);
+ m_interval_by_layers->Bind(wxEVT_TEXT, [this, rb_by_layers](wxEvent&)
+ {
+ wxString str = m_interval_by_layers->GetValue();
+ if (str.IsEmpty()) {
+ m_interval_by_layers->SetValue(wxString::Format("%d", m_sequence.interval_by_layers));
+ return;
+ }
+
+ int val = wxAtoi(str);
+ if (val < 1) {
+ m_interval_by_layers->SetValue("1");
+ val = 1;
+ }
+
+ if (m_sequence.interval_by_layers == val)
+ return;
+
+ m_sequence.interval_by_layers = val;
+
+ m_sequence.is_mm_intervals = false;
+ rb_by_layers->SetValue(true);
+ });
+
+ m_intervals_grid_sizer->Add(rb_by_layers, 0, wxALIGN_CENTER_VERTICAL);
+ m_intervals_grid_sizer->Add(m_interval_by_layers,0, wxALIGN_CENTER_VERTICAL);
+ m_intervals_grid_sizer->Add(st_by_layers,0, wxALIGN_CENTER_VERTICAL);
+
+ wxRadioButton* rb_by_mm = new wxRadioButton(this, ID_RADIO_BUTTON, "");
+ rb_by_mm->Bind(wxEVT_RADIOBUTTON, [this](wxEvent&) { m_sequence.is_mm_intervals = true; });
+ rb_by_mm->SetValue(m_sequence.is_mm_intervals);
+
+ wxStaticText* st_by_mm = new wxStaticText(this, wxID_ANY, _(L("mm")));
+ m_interval_by_mm = new wxTextCtrl(this, wxID_ANY,
+ double_to_string(sequence.interval_by_mm),
+ wxDefaultPosition, editor_sz, wxTE_PROCESS_ENTER);
+
+ auto change_value = [this]()
+ {
+ wxString str = m_interval_by_mm->GetValue();
+ if (str.IsEmpty()) {
+ m_interval_by_mm->SetValue(wxString::Format("%d", m_sequence.interval_by_mm));
+ return;
+ }
+
+ str.Replace(",", ".", false);
+ double val;
+ if (str == "." || !str.ToCDouble(&val) || val <= 0.0)
+ val = 3.0; // default value
+
+ if (fabs(m_sequence.interval_by_layers - val) < 0.001)
+ return;
+
+ m_sequence.interval_by_mm = val;
+ };
+
+ m_interval_by_mm->Bind(wxEVT_TEXT, [this, rb_by_mm](wxEvent&)
+ {
+ m_sequence.is_mm_intervals = true;
+ rb_by_mm->SetValue(true);
+ });
+
+ m_interval_by_mm->Bind(wxEVT_KILL_FOCUS, [this, change_value](wxFocusEvent& event)
+ {
+ change_value();
+ event.Skip();
+ });
+
+ m_interval_by_mm->Bind(wxEVT_TEXT_ENTER, [this, change_value](wxEvent&)
+ {
+ change_value();
+ });
+
+ m_intervals_grid_sizer->Add(rb_by_mm, 0, wxALIGN_CENTER_VERTICAL);
+ m_intervals_grid_sizer->Add(m_interval_by_mm,0, wxALIGN_CENTER_VERTICAL);
+ m_intervals_grid_sizer->Add(st_by_mm,0, wxALIGN_CENTER_VERTICAL);
+
+ intervals_box_sizer->Add(m_intervals_grid_sizer, 0, wxLEFT, em);
+ option_sizer->Add(intervals_box_sizer, 0, wxEXPAND);
+
+
+ auto extruders_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder(tool) sequence"))+ " : ");
+ auto extruders_box_sizer = new wxStaticBoxSizer(extruders_box, wxVERTICAL);
+
+ m_extruders_grid_sizer = new wxFlexGridSizer(3, 5, em);
+
+ apply_extruder_sequence();
+
+ extruders_box_sizer->Add(m_extruders_grid_sizer, 0, wxALL, em);
+ option_sizer->Add(extruders_box_sizer, 0, wxEXPAND | wxTOP, em);
+
+ main_sizer->Add(option_sizer, 0, wxEXPAND | wxALL, em);
+
+ wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
+ main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, em);
+
+ SetSizer(main_sizer);
+ main_sizer->SetSizeHints(this);
+
+ /* For this moment min sizes for dialog and its sizer are calculated.
+ * If we left them, it can cause a problem with layouts during deleting of extruders
+ */
+ if (m_sequence.extruders.size()>1)
+ {
+ wxSize sz = wxSize(-1, 10 * em);
+ SetMinSize(sz);
+ GetSizer()->SetMinSize(sz);
+ }
+}
+
+void ExtruderSequenceDialog::apply_extruder_sequence()
+{
+ m_extruders_grid_sizer->Clear(true);
+
+ for (size_t extruder=0; extruder < m_sequence.extruders.size(); ++extruder)
+ {
+ wxBitmapComboBox* extruder_selector = nullptr;
+ apply_extruder_selector(&extruder_selector, this, "", wxDefaultPosition, wxSize(15*wxGetApp().em_unit(), -1));
+ extruder_selector->SetSelection(m_sequence.extruders[extruder]);
+
+ extruder_selector->Bind(wxEVT_COMBOBOX, [this, extruder_selector, extruder](wxCommandEvent& evt)
+ {
+ m_sequence.extruders[extruder] = extruder_selector->GetSelection();
+ evt.StopPropagation();
+ });
+
+ auto del_btn = new ScalableButton(this, wxID_ANY, m_bmp_del);
+ del_btn->SetToolTip(_(L("Remove extruder from sequence")));
+ if (m_sequence.extruders.size()==1)
+ del_btn->Disable();
+
+ del_btn->Bind(wxEVT_BUTTON, [this, extruder](wxEvent&) {
+ m_sequence.delete_extruder(extruder);
+ apply_extruder_sequence();
+ });
+
+ auto add_btn = new ScalableButton(this, wxID_ANY, m_bmp_add);
+ add_btn->SetToolTip(_(L("Add extruder to sequence")));
+
+ add_btn->Bind(wxEVT_BUTTON, [this, extruder](wxEvent&) {
+ m_sequence.add_extruder(extruder);
+ apply_extruder_sequence();
+ });
+
+ m_extruders_grid_sizer->Add(extruder_selector, 0, wxALIGN_CENTER_VERTICAL);
+ m_extruders_grid_sizer->Add(del_btn, 0, wxALIGN_CENTER_VERTICAL);
+ m_extruders_grid_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL);
+ }
+ m_extruders_grid_sizer->ShowItems(true); // show items hidden in apply_extruder_selector()
+
+ Fit();
+ Refresh();
+}
+
+void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect)
+{
+ SetFont(wxGetApp().normal_font());
+
+ m_bmp_add.msw_rescale();
+ m_bmp_del.msw_rescale();
+
+ const int em = em_unit();
+
+ m_intervals_grid_sizer->SetHGap(em);
+ m_intervals_grid_sizer->SetVGap(em);
+ m_extruders_grid_sizer->SetHGap(em);
+ m_extruders_grid_sizer->SetVGap(em);
+
+ msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
+
+ // wxSize size = get_size();
+ // SetMinSize(size);
+
+ Fit();
+ Refresh();
+}
+
+}
+}
+
+
diff --git a/src/slic3r/GUI/ExtruderSequenceDialog.hpp b/src/slic3r/GUI/ExtruderSequenceDialog.hpp
new file mode 100644
index 000000000..3efd9e3a2
--- /dev/null
+++ b/src/slic3r/GUI/ExtruderSequenceDialog.hpp
@@ -0,0 +1,45 @@
+#ifndef slic3r_GUI_ExtruderSequenceDialog_hpp_
+#define slic3r_GUI_ExtruderSequenceDialog_hpp_
+
+#include "GUI_Utils.hpp"
+#include "wxExtensions.hpp"
+
+class wxTextCtrl;
+class wxFlexGridSizer;
+
+namespace Slic3r {
+namespace GUI {
+
+// ----------------------------------------------------------------------------
+// ExtruderSequenceDialog: a node inside ObjectDataViewModel
+// ----------------------------------------------------------------------------
+
+class ExtruderSequenceDialog: public DPIDialog
+{
+ ScalableBitmap m_bmp_del;
+ ScalableBitmap m_bmp_add;
+ DoubleSlider::ExtrudersSequence m_sequence;
+
+ wxTextCtrl* m_interval_by_layers {nullptr};
+ wxTextCtrl* m_interval_by_mm {nullptr};
+
+ wxFlexGridSizer* m_intervals_grid_sizer {nullptr};
+ wxFlexGridSizer* m_extruders_grid_sizer {nullptr};
+public:
+ ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence);
+
+ ~ExtruderSequenceDialog() {}
+
+ DoubleSlider::ExtrudersSequence GetValue() { return m_sequence; }
+
+protected:
+ void apply_extruder_sequence();
+ void on_dpi_changed(const wxRect& suggested_rect) override;
+
+};
+
+}
+}
+
+
+#endif // slic3r_GUI_ExtruderSequenceDialog_hpp_
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index c5ade144e..c8f660e14 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -22,9 +22,11 @@
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/Tab.hpp"
#include "slic3r/GUI/GUI_Preview.hpp"
+
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
+#include "Mouse3DController.hpp"
#include "I18N.hpp"
#if ENABLE_RETINA_GL
@@ -130,6 +132,9 @@ GLCanvas3D::LayersEditing::LayersEditing()
, m_object_max_z(0.f)
, m_slicing_parameters(nullptr)
, m_layer_height_profile_modified(false)
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ , m_adaptive_cusp(0.0f)
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
, state(Unknown)
, band_width(2.0f)
, strength(0.005f)
@@ -150,7 +155,9 @@ GLCanvas3D::LayersEditing::~LayersEditing()
}
const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f;
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
const float GLCanvas3D::LayersEditing::THICKNESS_RESET_BUTTON_HEIGHT = 22.0f;
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
{
@@ -217,13 +224,110 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
if (!m_enabled)
return;
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
+
+ const Size& cnv_size = canvas.get_canvas_size();
+ float canvas_w = (float)cnv_size.get_width();
+ float canvas_h = (float)cnv_size.get_height();
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+ imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f);
+
+ imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
+ imgui.text(_(L("Left mouse button:")));
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ imgui.text(_(L("Add detail")));
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
+ imgui.text(_(L("Right mouse button:")));
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ imgui.text(_(L("Remove detail")));
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
+ imgui.text(_(L("Shift + Left mouse button:")));
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ imgui.text(_(L("Reset to base")));
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
+ imgui.text(_(L("Shift + Right mouse button:")));
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ imgui.text(_(L("Smoothing")));
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
+ imgui.text(_(L("Mouse wheel:")));
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ imgui.text(_(L("Increase/decrease edit area")));
+
+ ImGui::Separator();
+ if (imgui.button(_(L("Adaptive"))))
+ wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_cusp));
+
+ ImGui::SameLine();
+ float text_align = ImGui::GetCursorPosX();
+ ImGui::AlignTextToFramePadding();
+ imgui.text(_(L("Cusp (mm)")));
+ if (ImGui::IsItemHovered())
+ {
+ ImGui::BeginTooltip();
+ ImGui::TextUnformatted(_(L("I am a tooltip")));
+ ImGui::EndTooltip();
+ }
+
+ ImGui::SameLine();
+ float widget_align = ImGui::GetCursorPosX();
+ ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
+ m_adaptive_cusp = clamp(0.0f, 0.5f * (float)m_slicing_parameters->layer_height, m_adaptive_cusp);
+ ImGui::SliderFloat("", &m_adaptive_cusp, 0.0f, 0.5f * (float)m_slicing_parameters->layer_height, "%.3f");
+
+ ImGui::Separator();
+ if (imgui.button(_(L("Smooth"))))
+ wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
+
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(text_align);
+ ImGui::AlignTextToFramePadding();
+ imgui.text(_(L("Radius")));
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(widget_align);
+ ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
+ int radius = (int)m_smooth_params.radius;
+ if (ImGui::SliderInt("##1", &radius, 1, 10))
+ m_smooth_params.radius = (unsigned int)radius;
+
+ ImGui::SetCursorPosX(text_align);
+ ImGui::AlignTextToFramePadding();
+ imgui.text(_(L("Keep min")));
+ ImGui::SameLine();
+ if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization
+ ImGui::SetCursorPosX(widget_align);
+
+ ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
+ imgui.checkbox("##2", m_smooth_params.keep_min);
+
+ ImGui::Separator();
+ if (imgui.button(_(L("Reset"))))
+ wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
+
+ imgui.end();
+
+ const Rect& bar_rect = get_bar_rect_viewport(canvas);
+#else
const Rect& bar_rect = get_bar_rect_viewport(canvas);
const Rect& reset_rect = get_reset_rect_viewport(canvas);
_render_tooltip_texture(canvas, bar_rect, reset_rect);
_render_reset_texture(reset_rect);
- _render_active_object_annotations(canvas, bar_rect);
- _render_profile(bar_rect);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ render_active_object_annotations(canvas, bar_rect);
+ render_profile(bar_rect);
}
float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
@@ -248,11 +352,13 @@ bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, floa
return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
}
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y)
{
const Rect& rect = get_reset_rect_screen(canvas);
return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
{
@@ -260,9 +366,14 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
float w = (float)cnv_size.get_width();
float h = (float)cnv_size.get_height();
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ return Rect(w - thickness_bar_width(canvas), 0.0f, w, h);
+#else
return Rect(w - thickness_bar_width(canvas), 0.0f, w, h - reset_button_height(canvas));
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
}
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas)
{
const Size& cnv_size = canvas.get_canvas_size();
@@ -271,6 +382,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas)
return Rect(w - thickness_bar_width(canvas), h - reset_button_height(canvas), w, h);
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
{
@@ -281,9 +393,14 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
+#else
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
}
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas)
{
const Size& cnv_size = canvas.get_canvas_size();
@@ -295,13 +412,43 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
-
-bool GLCanvas3D::LayersEditing::_is_initialized() const
+bool GLCanvas3D::LayersEditing::is_initialized() const
{
return m_shader.is_initialized();
}
+std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const
+{
+ std::string ret;
+ if (m_enabled && (m_layer_height_profile.size() >= 4))
+ {
+ float z = get_cursor_z_relative(canvas);
+ if (z != -1000.0f)
+ {
+ z *= m_object_max_z;
+
+ float h = 0.0f;
+ for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2)
+ {
+ float zi = m_layer_height_profile[i];
+ float zi_1 = m_layer_height_profile[i - 2];
+ if ((zi_1 <= z) && (z <= zi))
+ {
+ float dz = zi - zi_1;
+ h = (dz != 0.0f) ? lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz) : m_layer_height_profile[i + 1];
+ break;
+ }
+ }
+ if (h > 0.0f)
+ ret = std::to_string(h);
+ }
+ }
+ return ret;
+}
+
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const
{
// TODO: do this with ImGui
@@ -347,8 +494,9 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co
GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top());
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
-void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const
+void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const
{
m_shader.start_using();
@@ -379,7 +527,7 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas
m_shader.stop_using();
}
-void GLCanvas3D::LayersEditing::_render_profile(const Rect& bar_rect) const
+void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const
{
//FIXME show some kind of legend.
@@ -496,6 +644,26 @@ void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas)
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp)
+{
+ this->update_slicing_parameters();
+ m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, cusp);
+ const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile;
+ m_layers_texture.valid = false;
+ canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
+}
+
+void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params)
+{
+ this->update_slicing_parameters();
+ m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params);
+ const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile;
+ m_layers_texture.valid = false;
+ canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
+}
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+
void GLCanvas3D::LayersEditing::generate_layer_height_texture()
{
this->update_slicing_parameters();
@@ -530,7 +698,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas)
{
if (last_object_id >= 0) {
if (m_layer_height_profile_modified) {
- wxGetApp().plater()->take_snapshot(_(L("Layers heights")));
+ wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit")));
const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile;
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
@@ -544,6 +712,11 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters()
m_slicing_parameters = new SlicingParameters();
*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z);
}
+
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ if (m_adaptive_cusp == 0.0f)
+ m_adaptive_cusp = 0.25f * m_slicing_parameters->layer_height;
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
}
float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas)
@@ -557,6 +730,7 @@ float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas)
* THICKNESS_BAR_WIDTH;
}
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas)
{
return
@@ -567,6 +741,7 @@ float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas)
#endif
* THICKNESS_RESET_BUTTON_HEIGHT;
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
@@ -836,47 +1011,125 @@ GLCanvas3D::LegendTexture::LegendTexture()
{
}
-void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas,
- std::vector<std::pair<double, double>>& cp_legend_values)
+void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D& canvas,
+ const std::vector<float>& colors_in,
+ std::vector<float>& colors,
+ std::vector<std::string>& cp_legend_items)
{
- if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint &&
- wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets
+ std::vector<Model::CustomGCode> custom_gcode_per_height = wxGetApp().plater()->model().custom_gcode_per_height;
+
+ const int extruders_cnt = wxGetApp().extruders_edited_cnt();
+ if (extruders_cnt == 1)
{
- auto& config = wxGetApp().preset_bundle->project_config;
- const std::vector<double>& color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
+ if (custom_gcode_per_height.empty()) {
+ cp_legend_items.push_back(I18N::translate_utf8(L("Default print color")));
+ colors = colors_in;
+ return;
+ }
+ std::vector<std::pair<double, double>> cp_values;
- if (!color_print_values.empty()) {
- std::vector<double> print_zs = canvas.get_current_print_zs(true);
- for (auto cp_value : color_print_values)
- {
- auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), cp_value - DoubleSlider::epsilon());
+ std::vector<double> print_zs = canvas.get_current_print_zs(true);
+ for (auto custom_code : custom_gcode_per_height)
+ {
+ if (custom_code.gcode != ColorChangeCode)
+ continue;
+ auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.height - DoubleSlider::epsilon());
- if (lower_b == print_zs.end())
- continue;
+ if (lower_b == print_zs.end())
+ continue;
+
+ double current_z = *lower_b;
+ double previous_z = lower_b == print_zs.begin() ? 0.0 : *(--lower_b);
+
+ // to avoid duplicate values, check adding values
+ if (cp_values.empty() ||
+ !(cp_values.back().first == previous_z && cp_values.back().second == current_z))
+ cp_values.push_back(std::pair<double, double>(previous_z, current_z));
+ }
+
+ const auto items_cnt = (int)cp_values.size();
+ if (items_cnt == 0) // There is no one color change, but there is/are some pause print or custom Gcode
+ {
+ cp_legend_items.push_back(I18N::translate_utf8(L("Default print color")));
+ cp_legend_items.push_back(I18N::translate_utf8(L("Pause print or custom G-code")));
+ colors = colors_in;
+ return;
+ }
- double current_z = *lower_b;
- double previous_z = lower_b == print_zs.begin() ? 0.0 : *(--lower_b);
+ const int color_cnt = (int)colors_in.size() / 4;
+ colors.resize(colors_in.size(), 0.0);
+
+ ::memcpy((void*)(colors.data()), (const void*)(colors_in.data() + (color_cnt - 1) * 4), 4 * sizeof(float));
+ cp_legend_items.push_back(I18N::translate_utf8(L("Pause print or custom G-code")));
+ size_t color_pos = 4;
+
+ for (int i = items_cnt; i >= 0; --i, color_pos+=4)
+ {
+ // update colors for color print item
+ ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + i * 4), 4 * sizeof(float));
- // to avoid duplicate values, check adding values
- if (cp_legend_values.empty() ||
- !(cp_legend_values.back().first == previous_z && cp_legend_values.back().second == current_z) )
- cp_legend_values.push_back(std::pair<double, double>(previous_z, current_z));
+ // create label for color print item
+ std::string id_str = std::to_string(i + 1) + ": ";
+
+ if (i == 0) {
+ cp_legend_items.push_back(id_str + (boost::format(I18N::translate_utf8(L("up to %.2f mm"))) % cp_values[0].first).str());
+ break;
}
+ if (i == items_cnt) {
+ cp_legend_items.push_back(id_str + (boost::format(I18N::translate_utf8(L("above %.2f mm"))) % cp_values[i - 1].second).str());
+ continue;
+ }
+
+ cp_legend_items.push_back(id_str + (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str());
}
}
+ else
+ {
+ // colors = colors_in;
+ const int color_cnt = (int)colors_in.size() / 4;
+ colors.resize(colors_in.size(), 0.0);
+
+ ::memcpy((void*)(colors.data()), (const void*)(colors_in.data()), 4 * extruders_cnt * sizeof(float));
+ size_t color_pos = 4 * extruders_cnt;
+ size_t color_in_pos = 4 * (color_cnt - 1);
+
+ for (unsigned int i = 0; i < (unsigned int)extruders_cnt; ++i)
+ cp_legend_items.push_back((boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str());
+
+ ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float));
+ color_pos += 4;
+ color_in_pos -= 4;
+ cp_legend_items.push_back(I18N::translate_utf8(L("Pause print or custom G-code")));
+
+ int cnt = custom_gcode_per_height.size();
+ for (int i = cnt-1; i >= 0; --i)
+ if (custom_gcode_per_height[i].gcode == ColorChangeCode) {
+ ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float));
+ color_pos += 4;
+ color_in_pos -= 4;
+ cp_legend_items.push_back((boost::format(I18N::translate_utf8(L("Color change for Extruder %d at %.2f mm"))) % custom_gcode_per_height[i].extruder % custom_gcode_per_height[i].height).str());
+ }
+ }
}
-bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress)
+bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors_in, const GLCanvas3D& canvas, bool compress)
{
reset();
// collects items to render
auto title = _(preview_data.get_legend_title());
- std::vector<std::pair<double, double>> cp_legend_values;
- fill_color_print_legend_values(preview_data, canvas, cp_legend_values);
+ std::vector<std::string> cp_legend_items;
+ std::vector<float> cp_colors;
- const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors, cp_legend_values);
+ if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint)
+ {
+ cp_legend_items.reserve(cp_colors.size());
+ fill_color_print_legend_items(canvas, tool_colors_in, cp_colors, cp_legend_items);
+ }
+
+ const std::vector<float>& tool_colors = preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint ? cp_colors : tool_colors_in;
+ const GCodePreviewData::LegendItemsList& items = preview_data.get_legend_items(tool_colors, cp_legend_items);
unsigned int items_count = (unsigned int)items.size();
if (items_count == 0)
@@ -1094,7 +1347,9 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
}
}
+#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent);
+#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
@@ -1118,6 +1373,11 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
+wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
+wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
#if ENABLE_THUMBNAIL_GENERATOR
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
@@ -1130,7 +1390,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
, m_retina_helper(nullptr)
#endif
, m_in_render(false)
- , m_render_enabled(true)
, m_bed(bed)
, m_camera(camera)
, m_view_toolbar(view_toolbar)
@@ -1260,7 +1519,9 @@ bool GLCanvas3D::init()
if (m_selection.is_enabled() && !m_selection.init())
return false;
+#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
post_event(SimpleEvent(EVT_GLCANVAS_INIT));
+#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
m_initialized = true;
@@ -1376,7 +1637,7 @@ void GLCanvas3D::set_model(Model* model)
void GLCanvas3D::bed_shape_changed()
{
- m_camera.set_scene_box(scene_bounding_box());
+ refresh_camera_scene_box();
m_camera.requires_zoom_to_bed = true;
m_dirty = true;
if (m_bed.is_prusa())
@@ -1402,7 +1663,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
{
BoundingBoxf3 bb = volumes_bounding_box();
- bb.merge(m_bed.get_bounding_box(false));
+ bb.merge(m_bed.get_bounding_box(true));
if (m_config != nullptr)
{
@@ -1424,6 +1685,32 @@ bool GLCanvas3D::is_layers_editing_allowed() const
return m_layers_editing.is_allowed();
}
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+void GLCanvas3D::reset_layer_height_profile()
+{
+ wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Reset")));
+ m_layers_editing.reset_layer_height_profile(*this);
+ m_layers_editing.state = LayersEditing::Completed;
+ m_dirty = true;
+}
+
+void GLCanvas3D::adaptive_layer_height_profile(float cusp)
+{
+ wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Adaptive")));
+ m_layers_editing.adaptive_layer_height_profile(*this, cusp);
+ m_layers_editing.state = LayersEditing::Completed;
+ m_dirty = true;
+}
+
+void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params)
+{
+ wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Smooth all")));
+ m_layers_editing.smooth_layer_height_profile(*this, smoothing_params);
+ m_layers_editing.state = LayersEditing::Completed;
+ m_dirty = true;
+}
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+
bool GLCanvas3D::is_reload_delayed() const
{
return m_reload_delayed;
@@ -1521,7 +1808,7 @@ void GLCanvas3D::update_volumes_colors_by_extruder()
void GLCanvas3D::render()
{
- if (!m_render_enabled || m_in_render)
+ if (m_in_render)
{
// if called recursively, return
m_dirty = true;
@@ -1550,10 +1837,11 @@ void GLCanvas3D::render()
return;
}
+ const Size& cnv_size = get_canvas_size();
+
if (m_camera.requires_zoom_to_bed)
{
zoom_to_bed();
- const Size& cnv_size = get_canvas_size();
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
m_camera.requires_zoom_to_bed = false;
}
@@ -1594,7 +1882,7 @@ void GLCanvas3D::render()
_render_objects();
_render_sla_slices();
_render_selection();
- _render_bed(theta);
+ _render_bed(theta, true);
#if ENABLE_RENDER_SELECTION_CENTER
_render_selection_center();
@@ -1623,7 +1911,6 @@ void GLCanvas3D::render()
#if ENABLE_RENDER_STATISTICS
ImGuiWrapper& imgui = *wxGetApp().imgui();
- imgui.set_next_window_bg_alpha(0.5f);
imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
imgui.text("Last frame: ");
ImGui::SameLine();
@@ -1644,6 +1931,8 @@ void GLCanvas3D::render()
m_camera.debug_render();
#endif // ENABLE_CAMERA_STATISTICS
+ wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
+
wxGetApp().imgui()->render();
m_canvas->SwapBuffers();
@@ -1655,13 +1944,13 @@ void GLCanvas3D::render()
}
#if ENABLE_THUMBNAIL_GENERATOR
-void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
+void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const
{
switch (GLCanvas3DManager::get_framebuffers_type())
{
- case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
- case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
- default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
+ case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
+ case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
+ default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -1790,9 +2079,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
struct GLVolumeState {
GLVolumeState() :
- volume_idx(-1) {}
+ volume_idx(size_t(-1)) {}
GLVolumeState(const GLVolume* volume, unsigned int volume_idx) :
composite_id(volume->composite_id), volume_idx(volume_idx) {}
+ GLVolumeState(const GLVolume::CompositeID &composite_id) :
+ composite_id(composite_id), volume_idx(size_t(-1)) {}
GLVolume::CompositeID composite_id;
// Volume index in the old GLVolume vector.
@@ -1918,22 +2209,13 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
}
}
sort_remove_duplicates(instance_ids_selected);
+ auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; };
+ std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower);
if (m_reload_delayed)
return;
bool update_object_list = false;
-
- auto find_old_volume_id = [&deleted_volumes](const GLVolume::CompositeID& id) -> unsigned int {
- for (unsigned int i = 0; i < (unsigned int)deleted_volumes.size(); ++i)
- {
- const GLVolumeState& v = deleted_volumes[i];
- if (v.composite_id == id)
- return v.volume_idx;
- }
- return (unsigned int)-1;
- };
-
if (m_volumes.volumes != glvolumes_new)
update_object_list = true;
m_volumes.volumes = std::move(glvolumes_new);
@@ -1948,9 +2230,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
if (it->new_geometry()) {
// New volume.
- unsigned int old_id = find_old_volume_id(it->composite_id);
- if (old_id != (unsigned int)-1)
- map_glvolume_old_to_new[old_id] = m_volumes.volumes.size();
+ auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower);
+ if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id)
+ // If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection.
+ map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size();
m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized);
m_volumes.volumes.back()->geometry_id = key.geometry_id;
update_object_list = true;
@@ -2102,7 +2385,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
}
- m_camera.set_scene_box(scene_bounding_box());
+ refresh_camera_scene_box();
if (m_selection.is_empty())
{
@@ -2138,12 +2421,12 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume&
static void load_gcode_retractions(const GCodePreviewData::Retraction& retractions, GLCanvas3D::GCodePreviewVolumeIndex::EType extrusion_type, GLVolumeCollection &volumes, GLCanvas3D::GCodePreviewVolumeIndex &volume_index, bool gl_initialized)
{
- volume_index.first_volumes.emplace_back(extrusion_type, 0, (unsigned int)volumes.volumes.size());
-
// nothing to render, return
if (retractions.positions.empty())
return;
+ volume_index.first_volumes.emplace_back(extrusion_type, 0, (unsigned int)volumes.volumes.size());
+
GLVolume *volume = volumes.new_nontoolpath_volume(retractions.color.rgba, VERTEX_BUFFER_RESERVE_SIZE);
GCodePreviewData::Retraction::PositionsList copy(retractions.positions);
@@ -2209,6 +2492,9 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const
++ idx_volume_index_src;
idx_volume_of_this_type_last = (idx_volume_index_src + 1 == m_gcode_preview_volume_index.first_volumes.size()) ? m_volumes.volumes.size() : m_gcode_preview_volume_index.first_volumes[idx_volume_index_src + 1].id;
idx_volume_of_this_type_first_new = idx_volume_dst;
+ if (idx_volume_src == idx_volume_of_this_type_last)
+ // Empty sequence of volumes for the current index item.
+ continue;
}
if (! m_volumes.volumes[idx_volume_src]->print_zs.empty())
m_volumes.volumes[idx_volume_dst ++] = m_volumes.volumes[idx_volume_src];
@@ -2247,7 +2533,7 @@ void GLCanvas3D::load_sla_preview()
}
}
-void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values)
+void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values)
{
const Print *print = this->fff_print();
if (print == nullptr)
@@ -2342,14 +2628,21 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_dirty |= m_main_toolbar.update_items_state();
m_dirty |= m_undoredo_toolbar.update_items_state();
m_dirty |= m_view_toolbar.update_items_state();
+ bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(m_camera);
+ m_dirty |= mouse3d_controller_applied;
if (!m_dirty)
return;
_refresh_if_shown_on_screen();
- if (m_keep_dirty)
+ if (m_keep_dirty || mouse3d_controller_applied)
+ {
m_dirty = true;
+ evt.RequestMore();
+ }
+ else
+ m_dirty = false;
}
void GLCanvas3D::on_char(wxKeyEvent& evt)
@@ -2394,6 +2687,20 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
break;
+
+#ifdef __APPLE__
+ case 'm':
+ case 'M':
+#else /* __APPLE__ */
+ case WXK_CONTROL_M:
+#endif /* __APPLE__ */
+ {
+ Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
+ controller.show_settings_dialog(!controller.is_settings_dialog_shown());
+ m_dirty = true;
+ break;
+ }
+
#ifdef __APPLE__
case 'v':
case 'V':
@@ -2461,11 +2768,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
case 'B':
case 'b': { zoom_to_bed(); break; }
case 'I':
- case 'i': { set_camera_zoom(1.0); break; }
+ case 'i': { _update_camera_zoom(1.0); break; }
case 'K':
case 'k': { m_camera.select_next_type(); m_dirty = true; break; }
case 'O':
- case 'o': { set_camera_zoom(-1.0); break; }
+ case 'o': { _update_camera_zoom(-1.0); break; }
#if ENABLE_RENDER_PICKING_PASS
case 'T':
case 't': {
@@ -2568,6 +2875,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
{
+ // try to filter out events coming from mouse 3d
+ Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
+ if (controller.process_mouse_wheel())
+ return;
+
if (!m_initialized)
return;
@@ -2612,7 +2924,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return;
// Calculate the zoom delta and apply it to the current zoom factor
- set_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
+ _update_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
}
void GLCanvas3D::on_timer(wxTimerEvent& evt)
@@ -2804,6 +3116,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_layers_editing.state = LayersEditing::Editing;
_perform_layer_editing_action(&evt);
}
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos(0), pos(1)))
{
if (evt.LeftDown())
@@ -2816,6 +3129,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
}
}
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled)
{
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)
@@ -2947,6 +3261,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if ((m_layers_editing.state != LayersEditing::Unknown) && (layer_editing_object_idx != -1))
{
+ set_tooltip("");
if (m_layers_editing.state == LayersEditing::Editing)
_perform_layer_editing_action(&evt);
}
@@ -3057,6 +3372,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
std::string tooltip = "";
if (tooltip.empty())
+ tooltip = m_layers_editing.get_tooltip(*this);
+
+ if (tooltip.empty())
tooltip = m_gizmos.get_tooltip();
if (tooltip.empty())
@@ -3395,13 +3713,6 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
m_dirty = true;
}
-void GLCanvas3D::set_camera_zoom(double zoom)
-{
- const Size& cnv_size = get_canvas_size();
- m_camera.set_zoom(zoom, _max_bounding_box(false, true), cnv_size.get_width(), cnv_size.get_height());
- m_dirty = true;
-}
-
void GLCanvas3D::update_gizmos_on_off_state()
{
set_as_dirty();
@@ -3533,14 +3844,13 @@ static bool string_getter(const bool is_undo, int idx, const char** out_text)
return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text);
}
-void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
+void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) const
{
ImGuiWrapper* imgui = wxGetApp().imgui();
const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
- imgui->set_next_window_bg_alpha(0.5f);
- std::string title = is_undo ? L("Undo History") : L("Redo History");
+ std::string title = is_undo ? L("Undo History") : L("Redo History");
imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
int hovered = m_imgui_undo_redo_hovered_pos;
@@ -3587,8 +3897,7 @@ static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
}
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
-
-static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volumes, ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool transparent_background)
+void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const
{
auto is_visible = [](const GLVolume& v) -> bool
{
@@ -3602,7 +3911,7 @@ static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volu
GLVolumePtrs visible_volumes;
- for (GLVolume* vol : volumes)
+ for (GLVolume* vol : m_volumes.volumes)
{
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0)))
{
@@ -3625,17 +3934,31 @@ static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volu
camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height);
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
camera.apply_view_matrix();
- camera.apply_projection(box);
+
+ double near_z = -1.0;
+ double far_z = -1.0;
+
+ if (show_bed)
+ {
+ // extends the near and far z of the frustrum to avoid the bed being clipped
+
+ // box in eye space
+ BoundingBoxf3 t_bed_box = m_bed.get_bounding_box(true).transformed(camera.get_view_matrix());
+ near_z = -t_bed_box.max(2);
+ far_z = -t_bed_box.min(2);
+ }
+
+ camera.apply_projection(box, near_z, far_z);
if (transparent_background)
- glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 0.0f));
+ glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
- shader.start_using();
+ m_shader.start_using();
- GLint shader_id = shader.get_shader_program_id();
+ GLint shader_id = m_shader.get_shader_program_id();
GLint color_id = ::glGetUniformLocation(shader_id, "uniform_color");
GLint print_box_detection_id = ::glGetUniformLocation(shader_id, "print_box.volume_detection");
glcheck();
@@ -3653,15 +3976,18 @@ static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volu
vol->render();
}
- shader.stop_using();
+ m_shader.stop_using();
glsafe(::glDisable(GL_DEPTH_TEST));
+ if (show_bed)
+ _render_bed(camera.get_theta(), false);
+
if (transparent_background)
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
}
-void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
+void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const
{
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
@@ -3714,7 +4040,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
- render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
+ _render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
if (multisample)
{
@@ -3765,7 +4091,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un
glsafe(::glDisable(GL_MULTISAMPLE));
}
-void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
+void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData & thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const
{
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
@@ -3818,7 +4144,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
{
- render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
+ _render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
if (multisample)
{
@@ -3869,7 +4195,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data
glsafe(::glDisable(GL_MULTISAMPLE));
}
-void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
+void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const
{
// check that thumbnail size does not exceed the default framebuffer size
const Size& cnv_size = get_canvas_size();
@@ -3886,7 +4212,7 @@ void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigne
if (!thumbnail_data.is_valid())
return;
- render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
+ _render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
@@ -3906,6 +4232,11 @@ bool GLCanvas3D::_init_toolbars()
if (!_init_undoredo_toolbar())
return false;
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ if (!_init_view_toolbar())
+ return false;
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+
return true;
}
@@ -4046,7 +4377,7 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "layersediting";
item.icon_filename = "layers_white.svg";
- item.tooltip = _utf8(L("Height ranges"));
+ item.tooltip = _utf8(L("Variable layer height"));
item.sprite_id = 10;
item.left.toggable = true;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
@@ -4163,6 +4494,13 @@ bool GLCanvas3D::_init_undoredo_toolbar()
return true;
}
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+bool GLCanvas3D::_init_view_toolbar()
+{
+ return wxGetApp().plater()->init_view_toolbar();
+}
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+
bool GLCanvas3D::_set_current()
{
return m_context != nullptr && m_canvas->SetCurrent(*m_context);
@@ -4187,8 +4525,6 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
// updates camera
m_camera.apply_viewport(0, 0, w, h);
-
- m_dirty = false;
}
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const
@@ -4225,6 +4561,12 @@ void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box)
}
#endif // ENABLE_THUMBNAIL_GENERATOR
+void GLCanvas3D::_update_camera_zoom(double zoom)
+{
+ m_camera.update_zoom(zoom);
+ m_dirty = true;
+}
+
void GLCanvas3D::_refresh_if_shown_on_screen()
{
if (_is_shown_on_screen())
@@ -4406,13 +4748,13 @@ void GLCanvas3D::_render_background() const
glsafe(::glPopMatrix());
}
-void GLCanvas3D::_render_bed(float theta) const
+void GLCanvas3D::_render_bed(float theta, bool show_axes) const
{
float scale_factor = 1.0;
#if ENABLE_RETINA_GL
scale_factor = m_retina_helper->get_scale_factor();
#endif // ENABLE_RETINA_GL
- m_bed.render(const_cast<GLCanvas3D&>(*this), theta, scale_factor);
+ m_bed.render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
}
void GLCanvas3D::_render_objects() const
@@ -5024,7 +5366,7 @@ void GLCanvas3D::_load_print_toolpaths()
volume->indexed_vertex_array.finalize_geometry(m_initialized);
}
-void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values)
+void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values)
{
std::vector<float> tool_colors = _parse_colors(str_tool_colors);
@@ -5036,11 +5378,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
bool has_infill;
bool has_support;
const std::vector<float>* tool_colors;
- const std::vector<double>* color_print_values;
+ bool is_single_material_print;
+ int extruders_cnt;
+ const std::vector<Model::CustomGCode>* color_print_values;
static const float* color_perimeters() { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow
static const float* color_infill() { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish
static const float* color_support() { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
+ static const float* color_pause_or_custom_code() { static float color[4] = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // gray
// For cloring by a tool, return a parsed color.
bool color_by_tool() const { return tool_colors != nullptr; }
@@ -5050,9 +5395,106 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
// For coloring by a color_print(M600), return a parsed color.
bool color_by_color_print() const { return color_print_values!=nullptr; }
const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const {
- auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), layers[layer_idx]->print_z + EPSILON);
+ const Model::CustomGCode value(layers[layer_idx]->print_z + EPSILON, "", 0, "");
+ auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value);
return (it - color_print_values->begin()) % number_tools();
}
+
+ const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const
+ {
+ const coordf_t print_z = layers[layer_idx]->print_z;
+
+ auto it = std::find_if(color_print_values->begin(), color_print_values->end(),
+ [print_z](const Model::CustomGCode& code)
+ { return fabs(code.height - print_z) < EPSILON; });
+ if (it != color_print_values->end())
+ {
+ const std::string& code = it->gcode;
+ // pause print or custom Gcode
+ if (code == PausePrintCode ||
+ (code != ColorChangeCode && code != ExtruderChangeCode))
+ return number_tools()-1; // last color item is a gray color for pause print or custom G-code
+
+ // change tool (extruder)
+ if (code == ExtruderChangeCode)
+ return get_color_idx_for_tool_change(it, extruder);
+ // change color for current extruder
+ if (code == ColorChangeCode) {
+ int color_idx = get_color_idx_for_color_change(it, extruder);
+ if (color_idx >= 0)
+ return color_idx;
+ }
+ }
+
+ const Model::CustomGCode value(print_z + EPSILON, "", 0, "");
+ it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value);
+ while (it != color_print_values->begin())
+ {
+ --it;
+ // change color for current extruder
+ if (it->gcode == ColorChangeCode) {
+ int color_idx = get_color_idx_for_color_change(it, extruder);
+ if (color_idx >= 0)
+ return color_idx;
+ }
+ // change tool (extruder)
+ if (it->gcode == ExtruderChangeCode)
+ return get_color_idx_for_tool_change(it, extruder);
+ }
+
+ return std::min<int>(extruders_cnt - 1, std::max<int>(extruder - 1, 0));;
+ }
+
+ private:
+ int get_m600_color_idx(std::vector<Model::CustomGCode>::const_iterator it) const
+ {
+ int shift = 0;
+ while (it != color_print_values->begin()) {
+ --it;
+ if (it->gcode == ColorChangeCode)
+ shift++;
+ }
+ return extruders_cnt + shift;
+ }
+
+ int get_color_idx_for_tool_change(std::vector<Model::CustomGCode>::const_iterator it, const int extruder) const
+ {
+ const int current_extruder = it->extruder == 0 ? extruder : it->extruder;
+ if (number_tools() == extruders_cnt + 1) // there is no one "M600"
+ return std::min<int>(extruders_cnt - 1, std::max<int>(current_extruder - 1, 0));
+
+ auto it_n = it;
+ while (it_n != color_print_values->begin()) {
+ --it_n;
+ if (it_n->gcode == ColorChangeCode && it_n->extruder == current_extruder)
+ return get_m600_color_idx(it_n);
+ }
+
+ return std::min<int>(extruders_cnt - 1, std::max<int>(current_extruder - 1, 0));
+ }
+
+ int get_color_idx_for_color_change(std::vector<Model::CustomGCode>::const_iterator it, const int extruder) const
+ {
+ if (extruders_cnt == 1)
+ return get_m600_color_idx(it);
+
+ auto it_n = it;
+ bool is_tool_change = false;
+ while (it_n != color_print_values->begin()) {
+ --it_n;
+ if (it_n->gcode == ExtruderChangeCode) {
+ is_tool_change = true;
+ if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder))
+ return get_m600_color_idx(it);
+ break;
+ }
+ }
+ if (!is_tool_change && it->extruder == extruder)
+ return get_m600_color_idx(it);
+
+ return -1;
+ }
+
} ctxt;
ctxt.has_perimeters = print_object.is_step_done(posPerimeters);
@@ -5060,6 +5502,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
ctxt.has_support = print_object.is_step_done(posSupportMaterial);
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values;
+ ctxt.is_single_material_print = this->fff_print()->extruders().size()==1;
+ ctxt.extruders_cnt = wxGetApp().extruders_edited_cnt();
ctxt.shifted_copies = &print_object.copies();
@@ -5083,6 +5527,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
// Maximum size of an allocation block: 32MB / sizeof(float)
BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info();
+ const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print();
+
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1));
tbb::spin_mutex new_volume_mutex;
@@ -5100,32 +5546,18 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
const size_t volumes_cnt_initial = m_volumes.volumes.size();
tbb::parallel_for(
tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
- [&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
+ [&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range<size_t>& range) {
GLVolumePtrs vols;
std::vector<size_t> color_print_layer_to_glvolume;
- auto volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& {
- return *vols[ctxt.color_by_color_print() ?
- color_print_layer_to_glvolume[layer_idx - range.begin()] :
+ auto volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& {
+ return *vols[ctxt.color_by_color_print()?
+ ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) :
ctxt.color_by_tool() ?
std::min<int>(ctxt.number_tools() - 1, std::max<int>(extruder - 1, 0)) :
feature
];
};
- if (ctxt.color_by_color_print()) {
- // Create a map from the layer index to a GLVolume, which is initialized with the correct layer span color.
- std::vector<int> color_print_tool_to_glvolume(ctxt.number_tools(), -1);
- color_print_layer_to_glvolume.reserve(range.end() - range.begin());
- vols.reserve(ctxt.number_tools());
- for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
- int idx_tool = (int)ctxt.color_print_color_idx_by_layer_idx(idx_layer);
- if (color_print_tool_to_glvolume[idx_tool] == -1) {
- color_print_tool_to_glvolume[idx_tool] = (int)vols.size();
- vols.emplace_back(new_volume(ctxt.color_tool(idx_tool)));
- }
- color_print_layer_to_glvolume.emplace_back(color_print_tool_to_glvolume[idx_tool]);
- }
- }
- else if (ctxt.color_by_tool()) {
+ if (ctxt.color_by_color_print() || ctxt.color_by_tool()) {
for (size_t i = 0; i < ctxt.number_tools(); ++i)
vols.emplace_back(new_volume(ctxt.color_tool(i)));
}
@@ -5136,6 +5568,26 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
vol->indexed_vertex_array.reserve(VERTEX_BUFFER_RESERVE_SIZE / 6);
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
const Layer *layer = ctxt.layers[idx_layer];
+
+ if (is_selected_separate_extruder)
+ {
+ bool at_least_one_has_correct_extruder = false;
+ for (const LayerRegion* layerm : layer->regions())
+ {
+ if (layerm->slices.surfaces.empty())
+ continue;
+ const PrintRegionConfig& cfg = layerm->region()->config();
+ if (cfg.perimeter_extruder.value == m_selected_extruder ||
+ cfg.infill_extruder.value == m_selected_extruder ||
+ cfg.solid_infill_extruder.value == m_selected_extruder ) {
+ at_least_one_has_correct_extruder = true;
+ break;
+ }
+ }
+ if (!at_least_one_has_correct_extruder)
+ continue;
+ }
+
for (GLVolume *vol : vols)
if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) {
vol->print_zs.push_back(layer->print_z);
@@ -5144,6 +5596,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
}
for (const Point &copy : *ctxt.shifted_copies) {
for (const LayerRegion *layerm : layer->regions()) {
+ if (is_selected_separate_extruder)
+ {
+ const PrintRegionConfig& cfg = layerm->region()->config();
+ if (cfg.perimeter_extruder.value != m_selected_extruder ||
+ cfg.infill_extruder.value != m_selected_extruder ||
+ cfg.solid_infill_extruder.value != m_selected_extruder)
+ continue;
+ }
if (ctxt.has_perimeters)
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0));
@@ -5427,11 +5887,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
case GCodePreviewData::Extrusion::ColorPrint:
{
int color_cnt = (int)tool_colors.size() / 4;
+ int val = value > color_cnt ? color_cnt - 1 : value;
- int val = int(value);
- while (val >= color_cnt)
- val -= color_cnt;
-
GCodePreviewData::Color color;
::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + val * 4), 4 * sizeof(float));
@@ -5492,10 +5949,13 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - populate volumes" << m_volumes.log_memory_info() << log_memory_info();
// populates volumes
+ const bool is_selected_separate_extruder = m_selected_extruder > 0 && preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint;
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
{
for (const GCodePreviewData::Extrusion::Path& path : layer.paths)
{
+ if (is_selected_separate_extruder && path.extruder_id != m_selected_extruder - 1)
+ continue;
std::vector<std::pair<float, GLVolume*>> &filters = roles_filters[size_t(path.extrusion_role)];
auto key = std::make_pair<float, GLVolume*>(Helper::path_filter(preview_data.extrusion.view_type, path), nullptr);
auto it_filter = std::lower_bound(filters.begin(), filters.end(), key);
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 32974a8df..8df5fb03b 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -81,7 +81,11 @@ template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
using Vec3dEvent = Event<Vec3d>;
template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
+using HeightProfileSmoothEvent = Event<HeightProfileSmoothingParams>;
+
+#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent);
+#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
@@ -104,6 +108,11 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
+wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
+wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
class GLCanvas3D
{
@@ -153,13 +162,17 @@ private:
private:
static const float THICKNESS_BAR_WIDTH;
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static const float THICKNESS_RESET_BUTTON_HEIGHT;
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
bool m_enabled;
Shader m_shader;
unsigned int m_z_texture_id;
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
mutable GLTexture m_tooltip_texture;
mutable GLTexture m_reset_texture;
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// Not owned by LayersEditing.
const DynamicPrintConfig *m_config;
// ModelObject for the currently selected object (Model::objects[last_object_id]).
@@ -168,9 +181,14 @@ private:
float m_object_max_z;
// Owned by LayersEditing.
SlicingParameters *m_slicing_parameters;
- std::vector<coordf_t> m_layer_height_profile;
+ std::vector<double> m_layer_height_profile;
bool m_layer_height_profile_modified;
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ mutable float m_adaptive_cusp;
+ mutable HeightProfileSmoothingParams m_smooth_params;
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+
class LayersTexture
{
public:
@@ -217,28 +235,44 @@ private:
void adjust_layer_height_profile();
void accept_changes(GLCanvas3D& canvas);
void reset_layer_height_profile(GLCanvas3D& canvas);
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp);
+ void smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_paramsn);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static float get_cursor_z_relative(const GLCanvas3D& canvas);
static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y);
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static Rect get_reset_rect_screen(const GLCanvas3D& canvas);
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static Rect get_reset_rect_viewport(const GLCanvas3D& canvas);
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
float object_max_z() const { return m_object_max_z; }
+ std::string get_tooltip(const GLCanvas3D& canvas) const;
+
private:
- bool _is_initialized() const;
+ bool is_initialized() const;
void generate_layer_height_texture();
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const;
void _render_reset_texture(const Rect& reset_rect) const;
- void _render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const;
- void _render_profile(const Rect& bar_rect) const;
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const;
+ void render_profile(const Rect& bar_rect) const;
void update_slicing_parameters();
static float thickness_bar_width(const GLCanvas3D &canvas);
+#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
static float reset_button_height(const GLCanvas3D &canvas);
+#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
};
struct Mouse
@@ -352,8 +386,10 @@ private:
public:
LegendTexture();
- void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas,
- std::vector<std::pair<double, double>>& cp_legend_values);
+ void fill_color_print_legend_items(const GLCanvas3D& canvas,
+ const std::vector<float>& colors_in,
+ std::vector<float>& colors,
+ std::vector<std::string>& cp_legend_items);
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress);
@@ -383,7 +419,6 @@ private:
std::unique_ptr<RetinaHelper> m_retina_helper;
#endif
bool m_in_render;
- bool m_render_enabled;
LegendTexture m_legend_texture;
WarningTexture m_warning_texture;
wxTimer m_timer;
@@ -442,7 +477,8 @@ private:
RenderStats m_render_stats;
#endif // ENABLE_RENDER_STATISTICS
- int m_imgui_undo_redo_hovered_pos{ -1 };
+ mutable int m_imgui_undo_redo_hovered_pos{ -1 };
+ int m_selected_extruder;
public:
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
@@ -494,6 +530,7 @@ public:
const Camera& get_camera() const { return m_camera; }
const Shader& get_shader() const { return m_shader; }
+ Camera& get_camera() { return m_camera; }
BoundingBoxf3 volumes_bounding_box() const;
BoundingBoxf3 scene_bounding_box() const;
@@ -501,6 +538,12 @@ public:
bool is_layers_editing_enabled() const;
bool is_layers_editing_allowed() const;
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ void reset_layer_height_profile();
+ void adaptive_layer_height_profile(float cusp);
+ void smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params);
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+
bool is_reload_delayed() const;
void enable_layers_editing(bool enable);
@@ -514,9 +557,6 @@ public:
void enable_dynamic_background(bool enable);
void allow_multisample(bool allow);
- void enable_render(bool enable) { m_render_enabled = enable; }
- bool is_render_enabled() const { return m_render_enabled; }
-
void zoom_to_bed();
void zoom_to_volumes();
void zoom_to_selection();
@@ -530,7 +570,7 @@ public:
#if ENABLE_THUMBNAIL_GENERATOR
// printable_only == false -> render also non printable volumes as grayed
// parts_only == false -> render also sla support and pad
- void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
+ void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
#endif // ENABLE_THUMBNAIL_GENERATOR
void select_all();
@@ -550,7 +590,7 @@ public:
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
void load_sla_preview();
- void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<double>& color_print_values);
+ void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<Model::CustomGCode>& color_print_values);
void bind_event_handlers();
void unbind_event_handlers();
@@ -577,8 +617,6 @@ public:
void do_flatten(const Vec3d& normal, const std::string& snapshot_type);
void do_mirror(const std::string& snapshot_type);
- void set_camera_zoom(double zoom);
-
void update_gizmos_on_off_state();
void reset_all_gizmos() { m_gizmos.reset_all_states(); }
@@ -591,6 +629,7 @@ public:
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
+ void set_selected_extruder(int extruder) { m_selected_extruder = extruder;}
class WipeTowerInfo {
protected:
@@ -645,6 +684,9 @@ private:
bool _init_toolbars();
bool _init_main_toolbar();
bool _init_undoredo_toolbar();
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ bool _init_view_toolbar();
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
bool _set_current();
void _resize(unsigned int w, unsigned int h);
@@ -656,13 +698,14 @@ private:
#else
void _zoom_to_box(const BoundingBoxf3& box);
#endif // ENABLE_THUMBNAIL_GENERATOR
+ void _update_camera_zoom(double zoom);
void _refresh_if_shown_on_screen();
void _picking_pass() const;
void _rectangular_selection_picking_pass() const;
void _render_background() const;
- void _render_bed(float theta) const;
+ void _render_bed(float theta, bool show_axes) const;
void _render_objects() const;
void _render_selection() const;
#if ENABLE_RENDER_SELECTION_CENTER
@@ -682,14 +725,15 @@ private:
#endif // ENABLE_SHOW_CAMERA_TARGET
void _render_sla_slices() const;
void _render_selection_sidebar_hints() const;
- void _render_undo_redo_stack(const bool is_undo, float pos_x);
+ void _render_undo_redo_stack(const bool is_undo, float pos_x) const;
#if ENABLE_THUMBNAIL_GENERATOR
+ void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
// render thumbnail using an off-screen framebuffer
- void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
+ void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
// render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported
- void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
+ void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
// render thumbnail using the default framebuffer
- void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
+ void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const;
#endif // ENABLE_THUMBNAIL_GENERATOR
void _update_volumes_hover_state() const;
@@ -713,7 +757,7 @@ private:
// Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes,
// one for perimeters, one for infill and one for supports.
void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector<std::string>& str_tool_colors,
- const std::vector<double>& color_print_values);
+ const std::vector<Model::CustomGCode>& color_print_values);
// Create 3D thick extrusion lines for wipe tower extrusions
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp
index 577f6e1b5..11f109fd5 100644
--- a/src/slic3r/GUI/GLShader.cpp
+++ b/src/slic3r/GUI/GLShader.cpp
@@ -118,9 +118,9 @@ bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_sh
glsafe(::glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, &params));
if (params == GL_FALSE) {
// Linking failed. Get the log.
- glsafe(::glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params));
+ glsafe(::glGetProgramiv(this->shader_program_id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
- glsafe(::glGetProgramInfoLog(this->vertex_program_id, params, &params, msg.data()));
+ glsafe(::glGetProgramInfoLog(this->shader_program_id, params, &params, msg.data()));
this->last_error = std::string("Shader linking failed:\n") + msg.data();
this->release();
return false;
diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp
index 51d787d9d..2fc0590d5 100644
--- a/src/slic3r/GUI/GLToolbar.cpp
+++ b/src/slic3r/GUI/GLToolbar.cpp
@@ -368,7 +368,7 @@ void GLToolbar::get_additional_tooltip(int item_id, std::string& text)
}
}
- text = L("");
+ text.clear();
}
void GLToolbar::set_additional_tooltip(int item_id, const std::string& text)
@@ -443,7 +443,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
if (item_id == -1)
{
// mouse is outside the toolbar
- m_tooltip = L("");
+ m_tooltip.clear();
}
else
{
@@ -610,7 +610,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas
std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent)
{
if (!m_enabled)
- return L("");
+ return "";
switch (m_layout.type)
{
@@ -665,7 +665,7 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC
{
const std::string& additional_tooltip = item->get_additional_tooltip();
if (!additional_tooltip.empty())
- tooltip += L("\n") + additional_tooltip;
+ tooltip += "\n" + additional_tooltip;
}
}
@@ -769,7 +769,7 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan
{
const std::string& additional_tooltip = item->get_additional_tooltip();
if (!additional_tooltip.empty())
- tooltip += L("\n") + additional_tooltip;
+ tooltip += "\n" + additional_tooltip;
}
}
@@ -1194,7 +1194,12 @@ bool GLToolbar::generate_icons_texture() const
states.push_back(std::make_pair(1, true));
}
- bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale), true);
+ unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size * m_layout.scale);
+// // force even size
+// if (sprite_size_px % 2 != 0)
+// sprite_size_px += 1;
+
+ bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false);
if (res)
m_icons_texture_dirty = false;
diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp
index 07da836fc..eb73dcdfd 100644
--- a/src/slic3r/GUI/GLToolbar.hpp
+++ b/src/slic3r/GUI/GLToolbar.hpp
@@ -293,6 +293,9 @@ public:
bool is_any_item_pressed() const;
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ unsigned int get_items_count() const { return (unsigned int)m_items.size(); }
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
int get_item_id(const std::string& name) const;
void force_left_action(int item_id, GLCanvas3D& parent) { do_action(GLToolbarItem::Left, item_id, parent, false); }
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 7d37baa66..e02dd5f6e 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -51,10 +51,10 @@
#include <Shlobj.h>
#endif // __WXMSW__
-#if ENABLE_THUMBNAIL_GENERATOR
+#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
#include <boost/beast/core/detail/base64.hpp>
#include <boost/nowide/fstream.hpp>
-#endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
namespace Slic3r {
namespace GUI {
@@ -185,7 +185,9 @@ bool GUI_App::on_init_inner()
wxCHECK_MSG(wxDirExists(resources_dir), false,
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
- SetAppName(SLIC3R_APP_KEY);
+ // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
+ // SetAppName(SLIC3R_APP_KEY);
+ SetAppName(SLIC3R_APP_KEY "-alpha");
SetAppDisplayName(SLIC3R_APP_NAME);
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
@@ -281,7 +283,7 @@ bool GUI_App::on_init_inner()
PresetUpdater::UpdateResult updater_result;
try {
- updater_result = preset_updater->config_update();
+ updater_result = preset_updater->config_update(app_config->orig_version());
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close();
} else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) {
@@ -464,6 +466,9 @@ void GUI_App::recreate_GUI()
dlg.Update(30, _(L("Recreating")) + dots);
topwindow->Destroy();
+
+ // For this moment ConfigWizard is deleted, invalidate it
+ m_wizard = nullptr;
}
dlg.Update(80, _(L("Loading of current presets")) + dots);
@@ -1126,7 +1131,6 @@ void GUI_App::gcode_thumbnails_debug()
}
else if (reading_image && boost::starts_with(gcode_line, END_MASK))
{
-#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
if (out_file.good())
@@ -1138,46 +1142,6 @@ void GUI_App::gcode_thumbnails_debug()
out_file.write(decoded.c_str(), decoded.size());
out_file.close();
}
-#else
- if (!row.empty())
- {
- rows.push_back(row);
- row.clear();
- }
-
- if ((unsigned int)rows.size() == height)
- {
- std::vector<unsigned char> thumbnail(4 * width * height, 0);
- for (unsigned int r = 0; r < (unsigned int)rows.size(); ++r)
- {
- std::string decoded_row;
- decoded_row.resize(boost::beast::detail::base64::decoded_size(rows[r].size()));
- decoded_row.resize(boost::beast::detail::base64::decode((void*)&decoded_row[0], rows[r].data(), rows[r].size()).first);
-
- if ((unsigned int)decoded_row.size() == width * 4)
- {
- void* image_ptr = (void*)(thumbnail.data() + r * width * 4);
- ::memcpy(image_ptr, (const void*)decoded_row.c_str(), width * 4);
- }
- }
-
- wxImage image(width, height);
- image.InitAlpha();
-
- for (unsigned int r = 0; r < height; ++r)
- {
- unsigned int rr = r * width;
- for (unsigned int c = 0; c < width; ++c)
- {
- unsigned char* px = thumbnail.data() + 4 * (rr + c);
- image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
- image.SetAlpha((int)c, (int)r, px[3]);
- }
- }
-
- image.SaveFile(out_path + std::to_string(width) + "x" + std::to_string(height) + ".png", wxBITMAP_TYPE_PNG);
- }
-#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
reading_image = false;
width = 0;
@@ -1185,17 +1149,7 @@ void GUI_App::gcode_thumbnails_debug()
rows.clear();
}
else if (reading_image)
- {
-#if !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
- if (!row.empty() && (gcode_line[1] == ' '))
- {
- rows.push_back(row);
- row.clear();
- }
-#endif // !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
-
row += gcode_line.substr(2);
- }
}
}
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index bc912086e..a730ff95f 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -188,10 +188,10 @@ public:
void open_web_page_localized(const std::string &http_address);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
-#if ENABLE_THUMBNAIL_GENERATOR
+#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
// temporary and debug only -> extract thumbnails from selected gcode and save them as png files
void gcode_thumbnails_debug();
-#endif // ENABLE_THUMBNAIL_GENERATOR
+#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
private:
bool on_init_inner();
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 45d55c0b9..025262a23 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -59,6 +59,11 @@ static PrinterTechnology printer_technology()
return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology();
}
+static const Selection& scene_selection()
+{
+ return wxGetApp().plater()->canvas3D()->get_selection();
+}
+
// Config from current edited printer preset
static DynamicPrintConfig& printer_config()
{
@@ -329,7 +334,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /
return ""; // hide tooltip
// Create tooltip string, if there are errors
- wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors);
+ wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):")), errors) + "\n";
const stl_stats& stats = vol_idx == -1 ?
(*m_objects)[obj_idx]->get_object_stl_stats() :
@@ -526,6 +531,8 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
if (!m_config)
return;
+ take_snapshot(_(L("Change Extruder")));
+
const int extruder = m_objects_model->GetExtruderNumber(item);
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
@@ -806,7 +813,7 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
if (!item) {
if (col == nullptr) {
- if (wxOSX)
+ if (wxOSX && !multiple_selection())
UnselectAll();
else if (!evt_context_menu)
// Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED,
@@ -821,8 +828,13 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
}
if (wxOSX && item && col) {
+ wxDataViewItemArray sels;
+ GetSelections(sels);
UnselectAll();
- Select(item);
+ if (sels.Count() > 1)
+ SetSelections(sels);
+ else
+ Select(item);
}
const wxString title = col->GetTitle();
@@ -896,10 +908,6 @@ void ObjectList::extruder_editing()
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
return;
- std::vector<wxBitmap*> icons = get_extruder_color_icons();
- if (icons.empty())
- return;
-
const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5;
wxPoint pos = get_mouse_position_in_control();
@@ -907,29 +915,10 @@ void ObjectList::extruder_editing()
pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5;
pos.y -= GetTextExtent("m").y;
- if (!m_extruder_editor)
- m_extruder_editor = new wxBitmapComboBox(this, wxID_ANY, wxEmptyString, pos, size,
- 0, nullptr, wxCB_READONLY);
- else
- {
- m_extruder_editor->SetPosition(pos);
- m_extruder_editor->SetMinSize(size);
- m_extruder_editor->SetSize(size);
- m_extruder_editor->Clear();
- m_extruder_editor->Show();
- }
-
- int i = 0;
- for (wxBitmap* bmp : icons) {
- if (i == 0) {
- m_extruder_editor->Append(_(L("default")), *bmp);
- ++i;
- }
+ apply_extruder_selector(&m_extruder_editor, this, L("default"), pos, size);
- m_extruder_editor->Append(wxString::Format("%d", i), *bmp);
- ++i;
- }
m_extruder_editor->SetSelection(m_objects_model->GetExtruderNumber(item));
+ m_extruder_editor->Show();
auto set_extruder = [this]()
{
@@ -941,6 +930,7 @@ void ObjectList::extruder_editing()
m_objects_model->SetExtruder(m_extruder_editor->GetString(selection), item);
m_extruder_editor->Hide();
+ update_extruder_in_config(item);
};
// to avoid event propagation to other sidebar items
@@ -949,13 +939,6 @@ void ObjectList::extruder_editing()
set_extruder();
evt.StopPropagation();
});
- /*
- m_extruder_editor->Bind(wxEVT_KILL_FOCUS, [set_extruder](wxFocusEvent& evt)
- {
- set_extruder();
- evt.Skip();
- });*/
-
}
void ObjectList::copy()
@@ -1335,7 +1318,10 @@ void ObjectList::get_settings_choice(const wxString& category_name)
void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
{
std::vector<std::string> options = get_options_for_bundle(bundle_name);
- wxDataViewItem item = GetSelection();
+ const Selection& selection = scene_selection();
+ wxDataViewItem item = GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?
+ m_objects_model->GetItemById(selection.get_object_idx()) :
+ GetSelection();
ItemType item_type = m_objects_model->GetItemType(item);
@@ -1423,17 +1409,23 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu)
const ConfigOptionMode mode = wxGetApp().get_mode();
+ wxWindow* parent = wxGetApp().plater();
+
if (mode == comAdvanced) {
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].first), "",
- [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second);
+ [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); },
+ ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART)].second, nullptr,
+ [this]() { return is_instance_or_object_selected(); }, parent);
}
if (mode == comSimple) {
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].first), "",
[this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_ENFORCER); },
- ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second);
+ ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER)].second, nullptr,
+ [this]() { return is_instance_or_object_selected(); }, parent);
append_menu_item(menu, wxID_ANY, _(ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].first), "",
[this](wxCommandEvent&) { load_generic_subobject(L("Box"), ModelVolumeType::SUPPORT_BLOCKER); },
- ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second);
+ ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER)].second, nullptr,
+ [this]() { return is_instance_or_object_selected(); }, parent);
return;
}
@@ -1443,7 +1435,8 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu)
auto& item = ADD_VOLUME_MENU_ITEMS[type];
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType(type));
- append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second);
+ append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
+ [this]() { return is_instance_or_object_selected(); }, parent);
}
}
@@ -1454,10 +1447,17 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu)
[this]() { return is_splittable(); }, wxGetApp().plater());
}
-wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu)
+bool ObjectList::is_instance_or_object_selected()
+{
+ const Selection& selection = scene_selection();
+ return selection.is_single_full_instance() || selection.is_single_full_object();
+}
+
+wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent)
{
return append_menu_item(menu, wxID_ANY, _(L("Height range Modifier")), "",
- [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu);
+ [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu,
+ [this]() { return is_instance_or_object_selected(); }, parent);
}
wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
@@ -1498,6 +1498,12 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
#endif
menu->DestroySeparators(); // delete old separators
+ // If there are selected more then one instance but not all of them
+ // don't add settings menu items
+ const Selection& selection = scene_selection();
+ if (selection.is_multiple_full_instance() && !selection.is_single_full_object())
+ return nullptr;
+
const auto sel_vol = get_selected_model_volume();
if (sel_vol && sel_vol->type() >= ModelVolumeType::SUPPORT_ENFORCER)
return nullptr;
@@ -1513,7 +1519,8 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
menu->SetFirstSeparator();
// Add frequently settings
- const bool is_object_settings = m_objects_model->GetItemType(GetSelection()) == itObject;
+ const ItemType item_type = m_objects_model->GetItemType(GetSelection());
+ const bool is_object_settings = item_type & itObject || item_type & itInstance || selection.is_single_full_object();
create_freq_settings_popupmenu(menu, is_object_settings);
if (mode == comAdvanced)
@@ -1539,15 +1546,35 @@ wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu)
wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent)
{
- return append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "",
+ wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "",
[this](wxCommandEvent&) { split_instances(); }, "", menu, [](){return wxGetApp().plater()->can_set_instance_to_object(); }, parent);
+
+ /* New behavior logic:
+ * 1. Split Object to several separated object, if ALL instances are selected
+ * 2. Separate selected instances from the initial object to the separated object,
+ * if some (not all) instances are selected
+ */
+ parent->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
+ evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ?
+ _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));
+ }, menu_item->GetId());
+
+ return menu_item;
}
wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* /*parent*/)
{
- return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [](wxCommandEvent&) {
- wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state();
- }, menu);
+ return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) {
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ wxDataViewItem item;
+ if (GetSelectedItemsCount() > 1 && selection.is_single_full_object())
+ item = m_objects_model->GetItemById(selection.get_object_idx());
+ else
+ item = GetSelection();
+
+ if (item)
+ toggle_printable_state(item);
+ }, menu);
}
void ObjectList::append_menu_items_osx(wxMenu* menu)
@@ -1643,7 +1670,7 @@ void ObjectList::create_object_popupmenu(wxMenu *menu)
menu->AppendSeparator();
// Layers Editing for object
- append_menu_item_layers_editing(menu);
+ append_menu_item_layers_editing(menu, wxGetApp().plater());
menu->AppendSeparator();
// rest of a object_menu will be added later in:
@@ -1687,17 +1714,6 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
void ObjectList::create_instance_popupmenu(wxMenu*menu)
{
m_menu_item_split_instances = append_menu_item_instance_to_object(menu, wxGetApp().plater());
-
- /* New behavior logic:
- * 1. Split Object to several separated object, if ALL instances are selected
- * 2. Separate selected instances from the initial object to the separated object,
- * if some (not all) instances are selected
- */
- wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [](wxUpdateUIEvent& evt) {
-// evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId());
- evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ?
- _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));
- }, m_menu_item_split_instances->GetId());
}
void ObjectList::create_default_popupmenu(wxMenu*menu)
@@ -1711,7 +1727,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
wxMenu *menu = new wxMenu;
settings_menu_hierarchy settings_menu;
- const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(nullptr);
+ const bool is_part = !(m_objects_model->GetItemType(GetSelection()) == itObject || scene_selection().is_single_full_object());
get_options_menu(settings_menu, is_part);
for (auto cat : settings_menu) {
@@ -1880,7 +1896,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
if (obj_idx < 0)
return;
- const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ const Selection& selection = scene_selection();
assert(obj_idx == selection.get_object_idx());
/** Any changes of the Object's composition is duplicated for all Object's Instances
@@ -2205,9 +2221,13 @@ void ObjectList::split()
void ObjectList::layers_editing()
{
- const auto item = GetSelection();
- const int obj_idx = get_selected_obj_idx();
- if (!item || obj_idx < 0)
+ const Selection& selection = scene_selection();
+ const int obj_idx = selection.get_object_idx();
+ wxDataViewItem item = obj_idx >= 0 && GetSelectedItemsCount() > 1 && selection.is_single_full_object() ?
+ m_objects_model->GetItemById(obj_idx) :
+ GetSelection();
+
+ if (!item)
return;
const wxDataViewItem obj_item = m_objects_model->GetTopParent(item);
@@ -2320,7 +2340,7 @@ bool ObjectList::selected_instances_of_same_object()
bool ObjectList::can_split_instances()
{
- const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ const Selection& selection = scene_selection();
return selection.is_multiple_full_instance() || selection.is_single_full_instance();
}
@@ -2348,7 +2368,7 @@ void ObjectList::part_selection_changed()
{
og_name = _(L("Group manipulation"));
- const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ const Selection& selection = scene_selection();
// don't show manipulation panel for case of all Object's parts selection
update_and_show_manipulations = !selection.is_single_full_instance();
}
@@ -2956,7 +2976,7 @@ int ObjectList::get_selected_layers_range_idx() const
void ObjectList::update_selections()
{
- const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ const Selection& selection = scene_selection();
wxDataViewItemArray sels;
if ( ( m_selection_mode & (smSettings|smLayer|smLayerRoot) ) == 0)
@@ -3663,7 +3683,7 @@ void ObjectList::instances_to_separated_objects(const int obj_idx)
void ObjectList::split_instances()
{
- const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ const Selection& selection = scene_selection();
const int obj_idx = selection.get_object_idx();
if (obj_idx == -1)
return;
@@ -3876,6 +3896,9 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
wxDataViewItemArray sels;
GetSelections(sels);
+ if (!sels.empty())
+ take_snapshot(_(L("Change Extruders")));
+
for (const wxDataViewItem& item : sels)
{
DynamicPrintConfig& config = get_item_config(item);
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index 0874343b6..73b23453b 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -226,11 +226,12 @@ public:
void get_settings_choice(const wxString& category_name);
void get_freq_settings_choice(const wxString& bundle_name);
void show_settings(const wxDataViewItem settings_item);
+ bool is_instance_or_object_selected();
wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type);
void append_menu_items_add_volume(wxMenu* menu);
wxMenuItem* append_menu_item_split(wxMenu* menu);
- wxMenuItem* append_menu_item_layers_editing(wxMenu* menu);
+ wxMenuItem* append_menu_item_layers_editing(wxMenu* menu, wxWindow* parent);
wxMenuItem* append_menu_item_settings(wxMenu* menu);
wxMenuItem* append_menu_item_change_type(wxMenu* menu);
wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent);
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index 937e3dbdc..36293525a 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -243,11 +243,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
// Add Axes labels with icons
static const char axes[] = { 'X', 'Y', 'Z' };
+// std::vector<wxString> axes_color = {"#990000", "#009900","#000099"};
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
const char label = axes[axis_idx];
wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label));
set_font_and_background_style(axis_name, wxGetApp().bold_font());
+// axis_name->SetForegroundColour(wxColour(axes_color[axis_idx]));
sizer = new wxBoxSizer(wxHORIZONTAL);
// Under OSX we use font, smaller than default font, so
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index d89ac1bcb..edb244b34 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -492,18 +492,23 @@ void Preview::show_hide_ui_elements(const std::string& what)
m_choice_view_type->Show(visible);
}
-void Preview::reset_sliders()
+void Preview::reset_sliders(bool reset_all)
{
m_enabled = false;
// reset_double_slider();
- m_double_slider_sizer->Hide((size_t)0);
+ if (reset_all)
+ m_double_slider_sizer->Hide((size_t)0);
+ else
+ m_double_slider_sizer->GetItem(size_t(0))->GetSizer()->Hide(1);
}
void Preview::update_sliders(const std::vector<double>& layers_z, bool keep_z_range)
{
m_enabled = true;
+
update_double_slider(layers_z, keep_z_range);
m_double_slider_sizer->Show((size_t)0);
+
Layout();
}
@@ -560,12 +565,12 @@ void Preview::on_checkbox_legend(wxCommandEvent& evt)
m_canvas_widget->Refresh();
}
-void Preview::update_view_type()
+void Preview::update_view_type(bool slice_completed)
{
const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config;
- const wxString& choice = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() &&
- wxGetApp().extruders_edited_cnt()==1 ?
+ const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_height.empty() /*&&
+ (wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */?
_(L("Color Print")) :
config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ?
_(L("Tool")) :
@@ -583,6 +588,8 @@ void Preview::update_view_type()
void Preview::create_double_slider()
{
m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100);
+ m_slider->EnableTickManipulation(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF);
+
m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0);
// sizer, m_canvas_widget
@@ -592,10 +599,11 @@ void Preview::create_double_slider()
Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
- wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues();
+ Model& model = wxGetApp().plater()->model();
+ model.custom_gcode_per_height = m_slider->GetTicksValues();
m_schedule_background_process();
- update_view_type();
+ update_view_type(false);
reload_print();
});
@@ -628,6 +636,24 @@ static int find_close_layer_idx(const std::vector<double>& zs, double &z, double
return -1;
}
+void Preview::check_slider_values(std::vector<Model::CustomGCode>& ticks_from_model,
+ const std::vector<double>& layers_z)
+{
+ // All ticks that would end up outside the slider range should be erased.
+ // TODO: this should be placed into more appropriate part of code,
+ // this function is e.g. not called when the last object is deleted
+ unsigned int old_size = ticks_from_model.size();
+ ticks_from_model.erase(std::remove_if(ticks_from_model.begin(), ticks_from_model.end(),
+ [layers_z](Model::CustomGCode val)
+ {
+ auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.height - DoubleSlider::epsilon());
+ return it == layers_z.end();
+ }),
+ ticks_from_model.end());
+ if (ticks_from_model.size() != old_size)
+ m_schedule_background_process();
+}
+
void Preview::update_double_slider(const std::vector<double>& layers_z, bool keep_z_range)
{
// Save the initial slider span.
@@ -643,8 +669,8 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min();
bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max();
- std::vector<double> &ticks_from_config = (wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights"))->values;
- check_slider_values(ticks_from_config, layers_z);
+ std::vector<Model::CustomGCode> &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_height;
+ check_slider_values(ticks_from_model, layers_z);
m_slider->SetSliderValues(layers_z);
assert(m_slider->GetMinValue() == 0);
@@ -666,33 +692,12 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
}
m_slider->SetSelectionSpan(idx_low, idx_high);
- m_slider->SetTicksValues(ticks_from_config);
+ m_slider->SetTicksValues(ticks_from_model);
bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF);
- if (color_print_enable) {
- const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
- if (cfg.opt<ConfigOptionFloats>("nozzle_diameter")->values.size() > 1)
- color_print_enable = false;
- }
- m_slider->EnableTickManipulation(color_print_enable);
-}
-void Preview::check_slider_values(std::vector<double>& ticks_from_config,
- const std::vector<double> &layers_z)
-{
- // All ticks that would end up outside the slider range should be erased.
- // TODO: this should be placed into more appropriate part of code,
- // this function is e.g. not called when the last object is deleted
- unsigned int old_size = ticks_from_config.size();
- ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(),
- [layers_z](double val)
- {
- auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val - DoubleSlider::epsilon());
- return it == layers_z.end();
- }),
- ticks_from_config.end());
- if (ticks_from_config.size() != old_size)
- m_schedule_background_process();
+ m_slider->EnableTickManipulation(color_print_enable);
+ m_slider->SetManipulationState(wxGetApp().extruders_edited_cnt());
}
void Preview::reset_double_slider()
@@ -753,7 +758,7 @@ void Preview::load_print_as_fff(bool keep_z_range)
if (! has_layers)
{
- reset_sliders();
+ reset_sliders(true);
m_canvas->reset_legend_texture();
m_canvas_widget->Refresh();
return;
@@ -776,51 +781,30 @@ void Preview::load_print_as_fff(bool keep_z_range)
bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty();
// Collect colors per extruder.
std::vector<std::string> colors;
- std::vector<double> color_print_values = {};
+ std::vector<Model::CustomGCode> color_print_values = {};
// set color print values, if it si selected "ColorPrint" view type
if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint)
{
- colors = GCodePreviewData::ColorPrintColors();
- if (! gcode_preview_data_valid) {
- //FIXME accessing full_config() is pretty expensive.
- // Only initialize color_print_values for the initial preview, not for the full preview where the color_print_values is extracted from the G-code.
- const auto& config = wxGetApp().preset_bundle->project_config;
- color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
- }
+ colors = wxGetApp().plater()->get_colors_for_color_print();
+ colors.push_back("#808080"); // gray color for pause print or custom G-code
+
+ if (!gcode_preview_data_valid)
+ color_print_values = wxGetApp().plater()->model().custom_gcode_per_height;
}
else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) )
{
- const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("extruder_colour"));
- const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"));
- unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
-
- unsigned char rgb[3];
- for (unsigned int i = 0; i < colors_count; ++i)
- {
- std::string color = m_config->opt_string("extruder_colour", i);
- if (!PresetBundle::parse_color(color, rgb))
- {
- color = m_config->opt_string("filament_colour", i);
- if (!PresetBundle::parse_color(color, rgb))
- color = "#FFFFFF";
- }
-
- colors.emplace_back(color);
- }
+ colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
color_print_values.clear();
}
if (IsShown())
{
+ m_canvas->set_selected_extruder(0);
if (gcode_preview_data_valid) {
// Load the real G-code preview.
m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
m_loaded = true;
} else {
- // disable color change information for multi-material presets
- if (wxGetApp().extruders_edited_cnt() > 1)
- color_print_values.clear();
-
// Load the initial preview based on slices, not the final G-code.
m_canvas->load_preview(colors, color_print_values);
}
@@ -829,7 +813,7 @@ void Preview::load_print_as_fff(bool keep_z_range)
std::vector<double> zs = m_canvas->get_current_print_zs(true);
if (zs.empty()) {
// all layers filtered out
- reset_sliders();
+ reset_sliders(true);
m_canvas_widget->Refresh();
} else
update_sliders(zs, keep_z_range);
@@ -860,7 +844,7 @@ void Preview::load_print_as_sla()
n_layers = (unsigned int)zs.size();
if (n_layers == 0)
{
- reset_sliders();
+ reset_sliders(true);
m_canvas_widget->Refresh();
}
diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp
index 08d5991b4..b0dac4223 100644
--- a/src/slic3r/GUI/GUI_Preview.hpp
+++ b/src/slic3r/GUI/GUI_Preview.hpp
@@ -5,6 +5,7 @@
#include "libslic3r/Point.hpp"
#include <string>
+#include "libslic3r/Model.hpp"
class wxNotebook;
class wxGLCanvas;
@@ -12,6 +13,7 @@ class wxBoxSizer;
class wxStaticText;
class wxChoice;
class wxComboCtrl;
+class wxBitmapComboBox;
class wxCheckBox;
class DoubleSlider;
@@ -101,7 +103,7 @@ class Preview : public wxPanel
bool m_loaded;
bool m_enabled;
- DoubleSlider* m_slider {nullptr};
+ DoubleSlider* m_slider {nullptr};
public:
Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config,
@@ -128,7 +130,7 @@ public:
void move_double_slider(wxKeyEvent& evt);
void edit_double_slider(wxKeyEvent& evt);
- void update_view_type();
+ void update_view_type(bool slice_completed);
bool is_loaded() const { return m_loaded; }
@@ -140,7 +142,7 @@ private:
void show_hide_ui_elements(const std::string& what);
- void reset_sliders();
+ void reset_sliders(bool reset_all);
void update_sliders(const std::vector<double>& layers_z, bool keep_z_range = false);
void on_size(wxSizeEvent& evt);
@@ -154,9 +156,9 @@ private:
// Create/Update/Reset double slider on 3dPreview
void create_double_slider();
+ void check_slider_values(std::vector<Model::CustomGCode> &ticks_from_model,
+ const std::vector<double> &layers_z);
void update_double_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
- void check_slider_values(std::vector<double> &ticks_from_config,
- const std::vector<double> &layers_z);
void reset_double_slider();
// update DoubleSlider after keyDown in canvas
void update_double_slider_from_canvas(wxKeyEvent& event);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
index 76a9ed603..4406b1bf5 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
@@ -140,17 +140,22 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
- m_imgui->set_next_window_bg_alpha(0.5f);
+ m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
- m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text("Z");
+ ImGui::SameLine();
+ ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f);
+ ImGui::InputDouble("", &m_cut_z, 0.0f, 0.0f, "%.2f");
- ImGui::PushItemWidth(m_imgui->scaled(5.0f));
- ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f");
+ ImGui::Separator();
m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper);
m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower);
m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower);
+ ImGui::Separator();
+
m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower);
const bool cut_clicked = m_imgui->button(_(L("Perform cut")));
m_imgui->disabled_end();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
index 862ffe41a..925727e76 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp
@@ -48,6 +48,13 @@ std::string GLGizmoMove3D::on_get_name() const
return (_(L("Move")) + " [M]").ToUTF8().data();
}
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+bool GLGizmoMove3D::on_is_activable() const
+{
+ return !m_parent.get_selection().is_empty();
+}
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+
void GLGizmoMove3D::on_start_dragging()
{
if (m_hover_id != -1)
@@ -166,25 +173,6 @@ void GLGizmoMove3D::on_render_for_picking() const
render_grabber_extension(Z, box, true);
}
-#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
-void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit)
-{
- const Selection& selection = m_parent.get_selection();
- bool show_position = selection.is_single_full_instance();
- const Vec3d& position = selection.get_bounding_box().center();
-
- Vec3d displacement = show_position ? position : m_displacement;
- wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)"));
-
- m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
- m_imgui->set_next_window_bg_alpha(0.5f);
- m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
- m_imgui->input_vec3("", displacement, 100.0f, "%.2f");
-
- m_imgui->end();
-}
-#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
-
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{
double projection = 0.0;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
index 21b1d397b..19958615e 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp
@@ -33,14 +33,14 @@ public:
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+ virtual bool on_is_activable() const;
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
virtual void on_start_dragging();
virtual void on_stop_dragging();
virtual void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
-#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
- virtual void on_render_input_window(float x, float y, float bottom_limit);
-#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
private:
double calc_projection(const UpdateData& data) const;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
index 9a2c72633..5d3b6f365 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
@@ -449,6 +449,13 @@ std::string GLGizmoRotate3D::on_get_name() const
return (_(L("Rotate")) + " [R]").ToUTF8().data();
}
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+bool GLGizmoRotate3D::on_is_activable() const
+{
+ return !m_parent.get_selection().is_empty();
+}
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+
void GLGizmoRotate3D::on_start_dragging()
{
if ((0 <= m_hover_id) && (m_hover_id < 3))
@@ -475,21 +482,5 @@ void GLGizmoRotate3D::on_render() const
m_gizmos[Z].render();
}
-#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
-void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
-{
- Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle()));
- wxString label = _(L("Rotation (deg)"));
-
- m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
- m_imgui->set_next_window_bg_alpha(0.5f);
- m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
- m_imgui->input_vec3("", rotation, 100.0f, "%.2f");
- m_imgui->end();
-}
-#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
-
-
-
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
index c856e5465..930a76e2e 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp
@@ -104,6 +104,9 @@ protected:
if (id < 3)
m_gizmos[id].disable_grabber(0);
}
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+ virtual bool on_is_activable() const;
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
virtual void on_start_dragging();
virtual void on_stop_dragging();
virtual void on_update(const UpdateData& data)
@@ -121,13 +124,8 @@ protected:
g.render_for_picking();
}
}
-#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
- virtual void on_render_input_window(float x, float y, float bottom_limit);
-#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
};
-
-
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
index a685fb774..fa49b06ce 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
@@ -49,7 +49,12 @@ std::string GLGizmoScale3D::on_get_name() const
bool GLGizmoScale3D::on_is_activable() const
{
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+ const Selection& selection = m_parent.get_selection();
+ return !selection.is_empty() && !selection.is_wipe_tower();
+#else
return !m_parent.get_selection().is_wipe_tower();
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
}
void GLGizmoScale3D::on_start_dragging()
@@ -284,21 +289,6 @@ void GLGizmoScale3D::on_render_for_picking() const
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
}
-#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
-void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit)
-{
- const Selection& selection = m_parent.get_selection();
- bool single_instance = selection.is_single_full_instance();
- wxString label = _(L("Scale (%)"));
-
- m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
- m_imgui->set_next_window_bg_alpha(0.5f);
- m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
- m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f");
- m_imgui->end();
-}
-#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
-
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
{
unsigned int grabbers_count = (unsigned int)m_grabbers.size();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
index 7b77fe559..d49770dce 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp
@@ -50,9 +50,6 @@ protected:
virtual void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
-#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
- virtual void on_render_input_window(float x, float y, float bottom_limit);
-#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
private:
void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
index cf3f91dac..b10f57108 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
@@ -741,7 +741,6 @@ RENDER_AGAIN:
const float approx_height = m_imgui->scaled(18.0f);
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
- m_imgui->set_next_window_bg_alpha(0.5f);
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
@@ -765,6 +764,7 @@ RENDER_AGAIN:
float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value;
if (m_new_point_head_diameter > diameter_upper_cap)
m_new_point_head_diameter = diameter_upper_cap;
+ ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("head_diameter"));
ImGui::SameLine(diameter_slider_left);
ImGui::PushItemWidth(window_width - diameter_slider_left);
@@ -825,6 +825,7 @@ RENDER_AGAIN:
}
}
else { // not in editing mode:
+ ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("minimal_distance"));
ImGui::SameLine(settings_sliders_left);
ImGui::PushItemWidth(window_width - settings_sliders_left);
@@ -838,6 +839,7 @@ RENDER_AGAIN:
bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider
bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider
+ ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("points_density"));
ImGui::SameLine(settings_sliders_left);
@@ -868,7 +870,7 @@ RENDER_AGAIN:
if (generate)
auto_generate();
- m_imgui->text("");
+ ImGui::Separator();
if (m_imgui->button(m_desc.at("manual_editing")))
switch_to_editing_mode();
@@ -885,9 +887,12 @@ RENDER_AGAIN:
// Following is rendered in both editing and non-editing mode:
- m_imgui->text("");
+ ImGui::Separator();
if (m_clipping_plane_distance == 0.f)
+ {
+ ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
+ }
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
@@ -1226,10 +1231,10 @@ void GLGizmoSlaSupports::get_data_from_backend()
void GLGizmoSlaSupports::auto_generate()
{
- wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L(
- "Autogeneration will erase all manually edited points.\n\n"
- "Are you sure you want to do it?\n"
- )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO);
+ wxMessageDialog dlg(GUI::wxGetApp().plater(),
+ _(L("Autogeneration will erase all manually edited points.")) + "\n\n" +
+ _(L("Are you sure you want to do it?")) + "\n",
+ _(L("Warning")), wxICON_WARNING | wxYES | wxNO);
if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 1ca764a03..89a313445 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -905,7 +905,11 @@ void GLGizmosManager::do_render_overlay() const
GLGizmoBase* gizmo = m_gizmos[idx].get();
unsigned int sprite_id = gizmo->get_sprite_id();
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+ int icon_idx = (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3));
+#else
int icon_idx = m_current == idx ? 2 : (m_hover == idx ? 1 : 0);
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width;
float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height;
@@ -971,11 +975,19 @@ bool GLGizmosManager::generate_icons_texture() const
}
std::vector<std::pair<int, bool>> states;
- states.push_back(std::make_pair(1, false));
- states.push_back(std::make_pair(0, false));
- states.push_back(std::make_pair(0, true));
-
- bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale), true);
+ states.push_back(std::make_pair(1, false)); // Activable
+ states.push_back(std::make_pair(0, false)); // Hovered
+ states.push_back(std::make_pair(0, true)); // Selected
+#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+ states.push_back(std::make_pair(2, false)); // Disabled
+#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
+
+ unsigned int sprite_size_px = (unsigned int)(m_overlay_icons_size * m_overlay_scale);
+// // force even size
+// if (sprite_size_px % 2 != 0)
+// sprite_size_px += 1;
+
+ bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, sprite_size_px, false);
if (res)
m_icons_texture_dirty = false;
diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp
index 8e4d9eebf..33e526083 100644
--- a/src/slic3r/GUI/ImGuiWrapper.cpp
+++ b/src/slic3r/GUI/ImGuiWrapper.cpp
@@ -96,7 +96,7 @@ void ImGuiWrapper::set_language(const std::string &language)
ranges = ranges_turkish;
} else if (lang == "vi") {
ranges = ranges_vietnamese;
- } else if (lang == "jp") {
+ } else if (lang == "ja") {
ranges = ImGui::GetIO().Fonts->GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs
m_font_cjk = true;
} else if (lang == "ko") {
@@ -317,6 +317,22 @@ void ImGuiWrapper::text(const wxString &label)
this->text(label_utf8.c_str());
}
+bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
+{
+ return ImGui::SliderFloat(label, v, v_min, v_max, format, power);
+}
+
+bool ImGuiWrapper::slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
+{
+ return this->slider_float(label.c_str(), v, v_min, v_max, format, power);
+}
+
+bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
+{
+ auto label_utf8 = into_u8(label);
+ return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power);
+}
+
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection)
{
// this is to force the label to the left of the widget:
@@ -498,13 +514,18 @@ void ImGuiWrapper::init_style()
(hex_color & 0xff) / 255.0f);
};
- static const unsigned COL_GREY_DARK = 0x444444ff;
+ static const unsigned COL_WINDOW_BACKGROND = 0x222222cc;
+ static const unsigned COL_GREY_DARK = 0x555555ff;
static const unsigned COL_GREY_LIGHT = 0x666666ff;
static const unsigned COL_ORANGE_DARK = 0xc16737ff;
static const unsigned COL_ORANGE_LIGHT = 0xff7d38ff;
- // Generics
+ // Window
+ style.WindowRounding = 4.0f;
+ set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROND);
set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK);
+
+ // Generics
set_color(ImGuiCol_FrameBg, COL_GREY_DARK);
set_color(ImGuiCol_FrameBgHovered, COL_GREY_LIGHT);
set_color(ImGuiCol_FrameBgActive, COL_GREY_LIGHT);
@@ -528,6 +549,9 @@ void ImGuiWrapper::init_style()
// Slider
set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK);
set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT);
+
+ // Separator
+ set_color(ImGuiCol_Separator, COL_ORANGE_LIGHT);
}
void ImGuiWrapper::render_draw_data(ImDrawData *draw_data)
diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp
index c6550351e..7cce60367 100644
--- a/src/slic3r/GUI/ImGuiWrapper.hpp
+++ b/src/slic3r/GUI/ImGuiWrapper.hpp
@@ -66,6 +66,9 @@ public:
void text(const char *label);
void text(const std::string &label);
void text(const wxString &label);
+ bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
+ bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
+ bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection); // Use -1 to not mark any option as selected
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected);
diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp
index e3ef335e6..268682b81 100644
--- a/src/slic3r/GUI/KBShortcutsDialog.cpp
+++ b/src/slic3r/GUI/KBShortcutsDialog.cpp
@@ -157,6 +157,7 @@ void KBShortcutsDialog::fill_shortcuts()
plater_shortcuts.push_back(Shortcut("Z", L("Zoom to selected object")));
plater_shortcuts.push_back(Shortcut("I", L("Zoom in")));
plater_shortcuts.push_back(Shortcut("O", L("Zoom out")));
+ plater_shortcuts.push_back(Shortcut(ctrl+"M", L("Show/Hide 3Dconnexion devices settings dialog")));
plater_shortcuts.push_back(Shortcut("ESC", L("Unselect gizmo / Clear selection")));
#if ENABLE_RENDER_PICKING_PASS
// Don't localize debugging texts.
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index b76110a87..e613e6915 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -24,6 +24,7 @@
#include "PrintHostDialogs.hpp"
#include "wxExtensions.hpp"
#include "GUI_ObjectList.hpp"
+#include "Mouse3DController.hpp"
#include "I18N.hpp"
#include <fstream>
@@ -684,7 +685,7 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
helpMenu->AppendSeparator();
- append_menu_item(helpMenu, wxID_ANY, _(L("DEBUG gcode thumbnails")), _(L("DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails")),
+ append_menu_item(helpMenu, wxID_ANY, "DEBUG gcode thumbnails", "DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails",
[this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
}
diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp
index 8b37161e9..573952f75 100644
--- a/src/slic3r/GUI/MeshUtils.cpp
+++ b/src/slic3r/GUI/MeshUtils.cpp
@@ -95,6 +95,7 @@ void MeshClipper::recalculate_triangles()
}
+
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
{
diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp
index 5900cb820..56d726596 100644
--- a/src/slic3r/GUI/MeshUtils.hpp
+++ b/src/slic3r/GUI/MeshUtils.hpp
@@ -18,7 +18,7 @@ namespace GUI {
struct Camera;
-
+// lm_FIXME: Following class might possibly be replaced by Eigen::Hyperplane
class ClippingPlane
{
double m_data[4];
@@ -68,13 +68,23 @@ public:
};
-
+// MeshClipper class cuts a mesh and is able to return a triangulated cut.
class MeshClipper {
public:
+ // Inform MeshClipper about which plane we want to use to cut the mesh
+ // This is supposed to be in world coordinates.
void set_plane(const ClippingPlane& plane);
+
+ // Which mesh to cut. MeshClipper remembers const * to it, caller
+ // must make sure that it stays valid.
void set_mesh(const TriangleMesh& mesh);
+
+ // Inform the MeshClipper about the transformation that transforms the mesh
+ // into world coordinates.
void set_transformation(const Geometry::Transformation& trafo);
+ // Return the triangulated cut. The points are returned directly
+ // in world coordinates.
const std::vector<Vec3f>& get_triangles();
private:
@@ -91,21 +101,39 @@ private:
-
+// MeshRaycaster class answers queries such as where on the mesh someone clicked,
+// whether certain points are visible or obscured by the mesh etc.
class MeshRaycaster {
public:
+ // The class saves a const* to the mesh, calledz is responsible
+ // for making sure it does not get invalid.
MeshRaycaster(const TriangleMesh& mesh)
- : m_mesh(&mesh), m_emesh(mesh)
+ : m_emesh(mesh), m_mesh(&mesh)
{}
- void set_transformation(const Geometry::Transformation& trafo);
- void set_camera(const Camera& camera);
-
- bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
- Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane = nullptr) const;
-
- std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera,
- const std::vector<Vec3f>& points, const ClippingPlane* clipping_plane = nullptr) const;
+ // Given a mouse position, this returns true in case it is on the mesh.
+ bool unproject_on_mesh(
+ const Vec2d& mouse_pos,
+ const Transform3d& trafo, // how to get the mesh into world coords
+ const Camera& camera, // current camera position
+ Vec3f& position, // where to save the positibon of the hit (mesh coords)
+ Vec3f& normal, // normal of the triangle that was hit
+ const ClippingPlane* clipping_plane = nullptr // clipping plane (if active)
+ ) const;
+
+ // Given a vector of points in woorld coordinates, this returns vector
+ // of indices of points that are visible (i.e. not cut by clipping plane
+ // or obscured by part of the mesh.
+ std::vector<unsigned> get_unobscured_idxs(
+ const Geometry::Transformation& trafo, // how to get the mesh into world coords
+ const Camera& camera, // current camera position
+ const std::vector<Vec3f>& points, // points in world coords
+ const ClippingPlane* clipping_plane = nullptr // clipping plane (if active)
+ ) const;
+
+ // Given a point in world coords, the method returns closest point on the mesh.
+ // The output is in mesh coords.
+ // normal* can be used to also get normal of the respective triangle.
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
private:
diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp
new file mode 100644
index 000000000..b3d7c0e4f
--- /dev/null
+++ b/src/slic3r/GUI/Mouse3DController.cpp
@@ -0,0 +1,816 @@
+#include "libslic3r/libslic3r.h"
+#include "Mouse3DController.hpp"
+
+#include "Camera.hpp"
+#include "GUI_App.hpp"
+#include "PresetBundle.hpp"
+#include "AppConfig.hpp"
+
+#include <wx/glcanvas.h>
+
+#include <boost/nowide/convert.hpp>
+#include <boost/log/trivial.hpp>
+#include "I18N.hpp"
+
+#include <bitset>
+
+// WARN: If updating these lists, please also update resources/udev/90-3dconnexion.rules
+
+static const std::vector<int> _3DCONNEXION_VENDORS =
+{
+ 0x046d, // LOGITECH = 1133 // Logitech (3Dconnexion is made by Logitech)
+ 0x256F // 3DCONNECTION = 9583 // 3Dconnexion
+};
+
+// See: https://github.com/FreeSpacenav/spacenavd/blob/a9eccf34e7cac969ee399f625aef827f4f4aaec6/src/dev.c#L202
+static const std::vector<int> _3DCONNEXION_DEVICES =
+{
+ 0xc603, /* 50691 spacemouse plus XT */
+ 0xc605, /* 50693 cadman */
+ 0xc606, /* 50694 spacemouse classic */
+ 0xc621, /* 50721 spaceball 5000 */
+ 0xc623, /* 50723 space traveller */
+ 0xc625, /* 50725 space pilot */
+ 0xc626, /* 50726 space navigator *TESTED* */
+ 0xc627, /* 50727 space explorer */
+ 0xc628, /* 50728 space navigator for notebooks*/
+ 0xc629, /* 50729 space pilot pro*/
+ 0xc62b, /* 50731 space mouse pro*/
+ 0xc62e, /* 50734 spacemouse wireless (USB cable) *TESTED* */
+ 0xc62f, /* 50735 spacemouse wireless receiver */
+ 0xc631, /* 50737 spacemouse pro wireless *TESTED* */
+ 0xc632, /* 50738 spacemouse pro wireless receiver */
+ 0xc633, /* 50739 spacemouse enterprise */
+ 0xc635, /* 50741 spacemouse compact *TESTED* */
+ 0xc636, /* 50742 spacemouse module */
+ 0xc640, /* 50752 nulooq */
+ 0xc652, /* 50770 3Dconnexion universal receiver *TESTED* */
+};
+
+namespace Slic3r {
+namespace GUI {
+
+const double Mouse3DController::State::DefaultTranslationScale = 2.5;
+const double Mouse3DController::State::MaxTranslationDeadzone = 0.2;
+const double Mouse3DController::State::DefaultTranslationDeadzone = 0.5 * Mouse3DController::State::MaxTranslationDeadzone;
+const float Mouse3DController::State::DefaultRotationScale = 1.0f;
+const float Mouse3DController::State::MaxRotationDeadzone = (float)Mouse3DController::State::MaxTranslationDeadzone;
+const float Mouse3DController::State::DefaultRotationDeadzone = 0.5f * Mouse3DController::State::MaxRotationDeadzone;
+
+Mouse3DController::State::State()
+ : m_buttons_enabled(false)
+ , m_translation_params(DefaultTranslationScale, DefaultTranslationDeadzone)
+ , m_rotation_params(DefaultRotationScale, DefaultRotationDeadzone)
+ , m_mouse_wheel_counter(0)
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ , m_translation_queue_max_size(0)
+ , m_rotation_queue_max_size(0)
+ , m_buttons_queue_max_size(0)
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+{
+}
+
+void Mouse3DController::State::append_translation(const Vec3d& translation)
+{
+ while (m_translation.queue.size() >= m_translation.max_size)
+ {
+ m_translation.queue.pop();
+ }
+ m_translation.queue.push(translation);
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ m_translation_queue_max_size = std::max(m_translation_queue_max_size, m_translation.queue.size());
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+}
+
+void Mouse3DController::State::append_rotation(const Vec3f& rotation)
+{
+ while (m_rotation.queue.size() >= m_rotation.max_size)
+ {
+ m_rotation.queue.pop();
+ }
+ m_rotation.queue.push(rotation);
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size());
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ if (rotation(0) != 0.0f)
+ ++m_mouse_wheel_counter;
+}
+
+void Mouse3DController::State::append_button(unsigned int id)
+{
+ if (!m_buttons_enabled)
+ return;
+
+ m_buttons.push(id);
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ m_buttons_queue_max_size = std::max(m_buttons_queue_max_size, m_buttons.size());
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+}
+
+bool Mouse3DController::State::process_mouse_wheel()
+{
+ if (m_mouse_wheel_counter == 0)
+ return false;
+ else if (!m_rotation.queue.empty())
+ {
+ --m_mouse_wheel_counter;
+ return true;
+ }
+
+ m_mouse_wheel_counter = 0;
+ return true;
+}
+
+void Mouse3DController::State::set_queues_max_size(size_t size)
+{
+ if (size > 0)
+ {
+ m_translation.max_size = size;
+ m_rotation.max_size = size;
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ m_translation_queue_max_size = 0;
+ m_rotation_queue_max_size = 0;
+ m_buttons_queue_max_size = 0;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ }
+}
+
+bool Mouse3DController::State::apply(Camera& camera)
+{
+ if (!wxGetApp().IsActive())
+ return false;
+
+ bool ret = false;
+
+ if (has_translation())
+ {
+ const Vec3d& translation = m_translation.queue.front();
+ camera.set_target(camera.get_target() + m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(1) * camera.get_dir_forward() + translation(2) * camera.get_dir_up()));
+ m_translation.queue.pop();
+ ret = true;
+ }
+
+ if (has_rotation())
+ {
+ const Vec3f& rotation = m_rotation.queue.front();
+ float theta = m_rotation_params.scale * rotation(0);
+ float phi = m_rotation_params.scale * rotation(2);
+ float sign = camera.inverted_phi ? -1.0f : 1.0f;
+ camera.phi += sign * phi;
+ camera.set_theta(camera.get_theta() + theta, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
+ m_rotation.queue.pop();
+ ret = true;
+ }
+
+ if (m_buttons_enabled && has_button())
+ {
+ unsigned int button = m_buttons.front();
+ switch (button)
+ {
+ case 0: { camera.update_zoom(1.0); break; }
+ case 1: { camera.update_zoom(-1.0); break; }
+ default: { break; }
+ }
+ m_buttons.pop();
+ ret = true;
+ }
+
+ return ret;
+}
+
+Mouse3DController::Mouse3DController()
+ : m_initialized(false)
+ , m_device(nullptr)
+ , m_device_str("")
+ , m_running(false)
+ , m_settings_dialog(false)
+{
+ m_last_time = std::chrono::high_resolution_clock::now();
+}
+
+void Mouse3DController::init()
+{
+ if (m_initialized)
+ return;
+
+ // Initialize the hidapi library
+ int res = hid_init();
+ if (res != 0)
+ {
+ BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
+ return;
+ }
+
+ m_initialized = true;
+}
+
+void Mouse3DController::shutdown()
+{
+ if (!m_initialized)
+ return;
+
+ stop();
+ disconnect_device();
+
+ // Finalize the hidapi library
+ hid_exit();
+ m_initialized = false;
+}
+
+bool Mouse3DController::apply(Camera& camera)
+{
+ if (!m_initialized)
+ return false;
+
+ std::lock_guard<std::mutex> lock(m_mutex);
+
+ // check if the user unplugged the device
+ if (!m_running && is_device_connected())
+ {
+ disconnect_device();
+ // hides the settings dialog if the user re-plug the device
+ m_settings_dialog = false;
+ }
+
+ // check if the user plugged the device
+ if (connect_device())
+ start();
+
+ return is_device_connected() ? m_state.apply(camera) : false;
+}
+
+void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const
+{
+ if (!m_running || !m_settings_dialog)
+ return;
+
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+
+ imgui.set_next_window_pos(0.5f * (float)canvas_width, 0.5f * (float)canvas_height, ImGuiCond_Always, 0.5f, 0.5f);
+
+ imgui.begin(_(L("3Dconnexion settings")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
+
+ const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text(_(L("Device:")));
+ ImGui::PopStyleColor();
+ ImGui::SameLine();
+ imgui.text(m_device_str);
+
+ ImGui::Separator();
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text(_(L("Speed:")));
+ ImGui::PopStyleColor();
+
+ float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale;
+ if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.5f, 2.0f, "%.1f"))
+ m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale);
+
+ float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale;
+ if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.5f, 2.0f, "%.1f"))
+ m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale);
+
+ ImGui::Separator();
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text(_(L("Deadzone:")));
+ ImGui::PopStyleColor();
+
+ float translation_deadzone = (float)m_state.get_translation_deadzone();
+ if (imgui.slider_float(_(L("Translation")) + "##2", &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f"))
+ m_state.set_translation_deadzone((double)translation_deadzone);
+
+ float rotation_deadzone = m_state.get_rotation_deadzone();
+ if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f"))
+ m_state.set_rotation_deadzone(rotation_deadzone);
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ ImGui::Separator();
+ ImGui::Separator();
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text("DEBUG:");
+ imgui.text("Vectors:");
+ ImGui::PopStyleColor();
+ Vec3f translation = m_state.get_translation().cast<float>();
+ Vec3f rotation = m_state.get_rotation();
+ ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
+ ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
+
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text("Queue size:");
+ ImGui::PopStyleColor();
+
+ int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() };
+ int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() };
+ int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() };
+
+ ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly);
+ ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly);
+ ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly);
+
+ int queue_size = (int)m_state.get_queues_max_size();
+ if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly))
+ {
+ if (queue_size > 0)
+ m_state.set_queues_max_size(queue_size);
+ }
+
+ ImGui::Separator();
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ imgui.text("Camera:");
+ ImGui::PopStyleColor();
+ Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
+ ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ imgui.end();
+}
+
+bool Mouse3DController::connect_device()
+{
+ static const long long DETECTION_TIME_MS = 2000; // seconds
+
+ if (is_device_connected())
+ return false;
+
+ // check time since last detection took place
+ if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS)
+ return false;
+
+ m_last_time = std::chrono::high_resolution_clock::now();
+
+ // Enumerates devices
+ hid_device_info* devices = hid_enumerate(0, 0);
+ if (devices == nullptr)
+ {
+ BOOST_LOG_TRIVIAL(error) << "Unable to enumerate HID devices";
+ return false;
+ }
+
+ // Searches for 1st connected 3Dconnexion device
+ struct DeviceData
+ {
+ std::string path;
+ unsigned short usage_page;
+ unsigned short usage;
+
+ DeviceData()
+ : path(""), usage_page(0), usage(0)
+ {}
+ DeviceData(const std::string& path, unsigned short usage_page, unsigned short usage)
+ : path(path), usage_page(usage_page), usage(usage)
+ {}
+
+ bool has_valid_usage() const { return (usage_page == 1) && (usage == 8); }
+ };
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ hid_device_info* cur = devices;
+ std::cout << std::endl << "======================================================================================================================================" << std::endl;
+ std::cout << "Detected devices:" << std::endl;
+ while (cur != nullptr)
+ {
+ std::cout << "\"";
+ std::wcout << ((cur->manufacturer_string != nullptr) ? cur->manufacturer_string : L"Unknown");
+ std::cout << "/";
+ std::wcout << ((cur->product_string != nullptr) ? cur->product_string : L"Unknown");
+ std::cout << "\" code: " << cur->vendor_id << "/" << cur->product_id << " (" << std::hex << cur->vendor_id << "/" << cur->product_id << std::dec << ")";
+ std::cout << " serial number: '";
+ std::wcout << ((cur->serial_number != nullptr) ? cur->serial_number : L"Unknown");
+ std::cout << "' usage page: " << cur->usage_page << " usage: " << cur->usage << " interface number: " << cur->interface_number << std::endl;
+
+ cur = cur->next;
+ }
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ // When using 3Dconnexion universal receiver, multiple devices are detected sharing the same vendor_id and product_id.
+ // To choose from them the right one we use:
+ // On Windows and Mac: usage_page == 1 and usage == 8
+ // On Linux: as usage_page and usage are not defined (see hidapi.h) we try all detected devices until one is succesfully open
+ // When only a single device is detected, as for wired connections, vendor_id and product_id are enough
+
+ // First we count all the valid devices from the enumerated list,
+
+ hid_device_info* current = devices;
+ typedef std::pair<unsigned short, unsigned short> DeviceIds;
+ typedef std::vector<DeviceData> DeviceDataList;
+ typedef std::map<DeviceIds, DeviceDataList> DetectedDevices;
+ DetectedDevices detected_devices;
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << std::endl << "Detected 3D connexion devices:" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ while (current != nullptr)
+ {
+ unsigned short vendor_id = 0;
+ unsigned short product_id = 0;
+
+ for (size_t i = 0; i < _3DCONNEXION_VENDORS.size(); ++i)
+ {
+ if (_3DCONNEXION_VENDORS[i] == current->vendor_id)
+ {
+ vendor_id = current->vendor_id;
+ break;
+ }
+ }
+
+ if (vendor_id != 0)
+ {
+ for (size_t i = 0; i < _3DCONNEXION_DEVICES.size(); ++i)
+ {
+ if (_3DCONNEXION_DEVICES[i] == current->product_id)
+ {
+ product_id = current->product_id;
+ DeviceIds detected_device(vendor_id, product_id);
+ DetectedDevices::iterator it = detected_devices.find(detected_device);
+ if (it == detected_devices.end())
+ it = detected_devices.insert(DetectedDevices::value_type(detected_device, DeviceDataList())).first;
+
+ it->second.emplace_back(current->path, current->usage_page, current->usage);
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::wcout << "\"" << ((current->manufacturer_string != nullptr) ? current->manufacturer_string : L"Unknown");
+ std::cout << "/";
+ std::wcout << ((current->product_string != nullptr) ? current->product_string : L"Unknown");
+ std::cout << "\" code: " << current->vendor_id << "/" << current->product_id << " (" << std::hex << current->vendor_id << "/" << current->product_id << std::dec << ")";
+ std::cout << " serial number: '";
+ std::wcout << ((current->serial_number != nullptr) ? current->serial_number : L"Unknown");
+ std::cout << "' usage page: " << current->usage_page << " usage: " << current->usage << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ }
+ }
+ }
+
+ current = current->next;
+ }
+
+ // Free enumerated devices
+ hid_free_enumeration(devices);
+
+ if (detected_devices.empty())
+ return false;
+
+ std::string path = "";
+ unsigned short vendor_id = 0;
+ unsigned short product_id = 0;
+
+ // Then we'll decide the choosing logic to apply in dependence of the device count and operating system
+
+ for (const DetectedDevices::value_type& device : detected_devices)
+ {
+ if (device.second.size() == 1)
+ {
+#ifdef __linux__
+ hid_device* test_device = hid_open(device.first.first, device.first.second, nullptr);
+ if (test_device != nullptr)
+ {
+ hid_close(test_device);
+#else
+ if (device.second.front().has_valid_usage())
+ {
+#endif // __linux__
+ vendor_id = device.first.first;
+ product_id = device.first.second;
+ break;
+ }
+ }
+ else
+ {
+ bool found = false;
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ for (const DeviceData& data : device.second)
+ {
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << "Test device: " << std::hex << device.first.first << std::dec << "/" << std::hex << device.first.second << std::dec << " \"" << data.path << "\"";
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+#ifdef __linux__
+ hid_device* test_device = hid_open_path(data.path.c_str());
+ if (test_device != nullptr)
+ {
+ path = data.path;
+ vendor_id = device.first.first;
+ product_id = device.first.second;
+ found = true;
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << "-> PASSED" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ hid_close(test_device);
+ break;
+ }
+#else
+ if (data.has_valid_usage())
+ {
+ path = data.path;
+ vendor_id = device.first.first;
+ product_id = device.first.second;
+ found = true;
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << "-> PASSED" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ break;
+ }
+#endif // __linux__
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ else
+ std::cout << "-> NOT PASSED" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ }
+
+ if (found)
+ break;
+ }
+ }
+
+ if (path.empty())
+ {
+ if ((vendor_id != 0) && (product_id != 0))
+ {
+ // Open the 3Dconnexion device using vendor_id and product_id
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << " using hid_open()" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ m_device = hid_open(vendor_id, product_id, nullptr);
+ }
+ else
+ return false;
+ }
+ else
+ {
+ // Open the 3Dconnexion device using the device path
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << "\"" << path << "\" using hid_open_path()" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ m_device = hid_open_path(path.c_str());
+ }
+
+ if (m_device != nullptr)
+ {
+ std::vector<wchar_t> manufacturer(1024, 0);
+ hid_get_manufacturer_string(m_device, manufacturer.data(), 1024);
+ m_device_str = boost::nowide::narrow(manufacturer.data());
+
+ std::vector<wchar_t> product(1024, 0);
+ hid_get_product_string(m_device, product.data(), 1024);
+ m_device_str += "/" + boost::nowide::narrow(product.data());
+
+ BOOST_LOG_TRIVIAL(info) << "Connected device: " << m_device_str;
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << std::endl << "Connected device:" << std::endl;
+ std::cout << "Manufacturer/product: " << m_device_str << std::endl;
+ std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
+ std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
+ std::cout << "Path................: '" << path << "'" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ // get device parameters from the config, if present
+ double translation_speed = 1.0;
+ float rotation_speed = 1.0;
+ double translation_deadzone = State::DefaultTranslationDeadzone;
+ float rotation_deadzone = State::DefaultRotationDeadzone;
+ wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed);
+ wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone);
+ wxGetApp().app_config->get_mouse_device_rotation_speed(m_device_str, rotation_speed);
+ wxGetApp().app_config->get_mouse_device_rotation_deadzone(m_device_str, rotation_deadzone);
+ // clamp to valid values
+ m_state.set_translation_scale(State::DefaultTranslationScale * std::max(0.5, std::min(2.0, translation_speed)));
+ m_state.set_translation_deadzone(std::max(0.0, std::min(State::MaxTranslationDeadzone, translation_deadzone)));
+ m_state.set_rotation_scale(State::DefaultRotationScale * std::max(0.5f, std::min(2.0f, rotation_speed)));
+ m_state.set_rotation_deadzone(std::max(0.0f, std::min(State::MaxRotationDeadzone, rotation_deadzone)));
+ }
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ else
+ {
+ std::cout << std::endl << "Unable to connect to device:" << std::endl;
+ std::cout << "Manufacturer/product: " << m_device_str << std::endl;
+ std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
+ std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
+ std::cout << "Path................: '" << path << "'" << std::endl;
+ }
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ return (m_device != nullptr);
+}
+
+void Mouse3DController::disconnect_device()
+{
+ if (!is_device_connected())
+ return;
+
+ // Stop the secondary thread, if running
+ if (m_thread.joinable())
+ m_thread.join();
+
+ // Store current device parameters into the config
+ wxGetApp().app_config->set_mouse_device(m_device_str, m_state.get_translation_scale() / State::DefaultTranslationScale, m_state.get_translation_deadzone(),
+ m_state.get_rotation_scale() / State::DefaultRotationScale, m_state.get_rotation_deadzone());
+ wxGetApp().app_config->save();
+
+ // Close the 3Dconnexion device
+ hid_close(m_device);
+ m_device = nullptr;
+
+ BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
+
+ m_device_str = "";
+}
+
+void Mouse3DController::start()
+{
+ if (!is_device_connected() || m_running)
+ return;
+
+ m_thread = std::thread(&Mouse3DController::run, this);
+}
+
+void Mouse3DController::run()
+{
+ m_running = true;
+ while (m_running)
+ {
+ collect_input();
+ }
+}
+
+void Mouse3DController::collect_input()
+{
+ DataPacket packet = { 0 };
+ int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100);
+ if (res < 0)
+ {
+ // An error occourred (device detached from pc ?)
+ stop();
+ return;
+ }
+
+ if (!wxGetApp().IsActive())
+ return;
+
+ std::lock_guard<std::mutex> lock(m_mutex);
+
+ bool updated = false;
+
+ if (res == 7)
+ updated = handle_packet(packet);
+ else if (res == 13)
+ updated = handle_wireless_packet(packet);
+ else if ((res == 3) && (packet[0] == 3))
+ // On Mac button packets can be 3 bytes long
+ updated = handle_packet(packet);
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ else if (res > 0)
+ std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ if (updated)
+ // ask for an idle event to update 3D scene
+ wxWakeUpIdle();
+}
+
+bool Mouse3DController::handle_packet(const DataPacket& packet)
+{
+ switch (packet[0])
+ {
+ case 1: // Translation
+ {
+ if (handle_packet_translation(packet))
+ return true;
+
+ break;
+ }
+ case 2: // Rotation
+ {
+ if (handle_packet_rotation(packet, 1))
+ return true;
+
+ break;
+ }
+ case 3: // Button
+ {
+ if (handle_packet_button(packet, packet.size() - 1))
+ return true;
+
+ break;
+ }
+ case 23: // Battery charge
+ {
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ break;
+ }
+ default:
+ {
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ break;
+ }
+ }
+
+ return false;
+}
+
+bool Mouse3DController::handle_wireless_packet(const DataPacket& packet)
+{
+ switch (packet[0])
+ {
+ case 1: // Translation + Rotation
+ {
+ bool updated = handle_packet_translation(packet);
+ updated |= handle_packet_rotation(packet, 7);
+
+ if (updated)
+ return true;
+
+ break;
+ }
+ case 3: // Button
+ {
+ if (handle_packet_button(packet, 12))
+ return true;
+
+ break;
+ }
+ case 23: // Battery charge
+ {
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ break;
+ }
+ default:
+ {
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ break;
+ }
+ }
+
+ return false;
+}
+
+double convert_input(unsigned char first, unsigned char second, double deadzone)
+{
+ short value = first | second << 8;
+ double ret = (double)value / 350.0;
+ return (std::abs(ret) > deadzone) ? ret : 0.0;
+}
+
+bool Mouse3DController::handle_packet_translation(const DataPacket& packet)
+{
+ double deadzone = m_state.get_translation_deadzone();
+ Vec3d translation(-convert_input(packet[1], packet[2], deadzone),
+ convert_input(packet[3], packet[4], deadzone),
+ convert_input(packet[5], packet[6], deadzone));
+
+ if (!translation.isApprox(Vec3d::Zero()))
+ {
+ m_state.append_translation(translation);
+ return true;
+ }
+
+ return false;
+}
+
+bool Mouse3DController::handle_packet_rotation(const DataPacket& packet, unsigned int first_byte)
+{
+ double deadzone = (double)m_state.get_rotation_deadzone();
+ Vec3f rotation(-(float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone),
+ (float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone),
+ -(float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone));
+
+ if (!rotation.isApprox(Vec3f::Zero()))
+ {
+ m_state.append_rotation(rotation);
+ return true;
+ }
+
+ return false;
+}
+
+bool Mouse3DController::handle_packet_button(const DataPacket& packet, unsigned int packet_size)
+{
+ unsigned int data = 0;
+ for (unsigned int i = 1; i < packet_size; ++i)
+ {
+ data |= packet[i] << 8 * (i - 1);
+ }
+
+ const std::bitset<32> data_bits{ data };
+ for (size_t i = 0; i < data_bits.size(); ++i)
+ {
+ if (data_bits.test(i))
+ {
+ m_state.append_button((unsigned int)i);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace GUI
+} // namespace Slic3r
diff --git a/src/slic3r/GUI/Mouse3DController.hpp b/src/slic3r/GUI/Mouse3DController.hpp
new file mode 100644
index 000000000..cc03d4a24
--- /dev/null
+++ b/src/slic3r/GUI/Mouse3DController.hpp
@@ -0,0 +1,175 @@
+#ifndef slic3r_Mouse3DController_hpp_
+#define slic3r_Mouse3DController_hpp_
+
+// Enabled debug output to console and extended imgui dialog
+#define ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT 0
+
+#include "libslic3r/Point.hpp"
+
+#include "hidapi.h"
+
+#include <queue>
+#include <thread>
+#include <mutex>
+#include <vector>
+#include <chrono>
+
+namespace Slic3r {
+namespace GUI {
+
+struct Camera;
+
+class Mouse3DController
+{
+ class State
+ {
+ public:
+ static const double DefaultTranslationScale;
+ static const double MaxTranslationDeadzone;
+ static const double DefaultTranslationDeadzone;
+ static const float DefaultRotationScale;
+ static const float MaxRotationDeadzone;
+ static const float DefaultRotationDeadzone;
+
+ private:
+ template <typename Number>
+ struct CustomParameters
+ {
+ Number scale;
+ Number deadzone;
+
+ CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {}
+ };
+
+ template <class T>
+ struct InputQueue
+ {
+ size_t max_size;
+ std::queue<T> queue;
+
+ // The default value of 5 for max_size seems to work fine on all platforms
+ // The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
+ // and playing with the imgui dialog which shows by pressing CTRL+M
+ InputQueue() : max_size(5) {}
+ };
+
+ InputQueue<Vec3d> m_translation;
+ InputQueue<Vec3f> m_rotation;
+ std::queue<unsigned int> m_buttons;
+
+ bool m_buttons_enabled;
+
+ CustomParameters<double> m_translation_params;
+ CustomParameters<float> m_rotation_params;
+
+ // When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
+ // We want to filter these out because we are getting the data directly from the device, bypassing the driver, and those mouse wheel events interfere
+ // by triggering unwanted zoom in/out of the scene
+ // The following variable is used to count the potential mouse wheel events triggered and is updated by:
+ // Mouse3DController::collect_input() through the call to the append_rotation() method
+ // GLCanvas3D::on_mouse_wheel() through the call to the process_mouse_wheel() method
+ // GLCanvas3D::on_idle() through the call to the apply() method
+ unsigned int m_mouse_wheel_counter;
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ size_t m_translation_queue_max_size;
+ size_t m_rotation_queue_max_size;
+ size_t m_buttons_queue_max_size;
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ public:
+ State();
+
+ void append_translation(const Vec3d& translation);
+ void append_rotation(const Vec3f& rotation);
+ void append_button(unsigned int id);
+
+ bool has_translation() const { return !m_translation.queue.empty(); }
+ bool has_rotation() const { return !m_rotation.queue.empty(); }
+ bool has_button() const { return !m_buttons.empty(); }
+
+ bool process_mouse_wheel();
+
+ double get_translation_scale() const { return m_translation_params.scale; }
+ void set_translation_scale(double scale) { m_translation_params.scale = scale; }
+
+ float get_rotation_scale() const { return m_rotation_params.scale; }
+ void set_rotation_scale(float scale) { m_rotation_params.scale = scale; }
+
+ double get_translation_deadzone() const { return m_translation_params.deadzone; }
+ void set_translation_deadzone(double deadzone) { m_translation_params.deadzone = deadzone; }
+
+ float get_rotation_deadzone() const { return m_rotation_params.deadzone; }
+ void set_rotation_deadzone(float deadzone) { m_rotation_params.deadzone = deadzone; }
+
+#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+ Vec3d get_translation() const { return has_translation() ? m_translation.queue.front() : Vec3d::Zero(); }
+ Vec3f get_rotation() const { return has_rotation() ? m_rotation.queue.front() : Vec3f::Zero(); }
+ unsigned int get_button() const { return has_button() ? m_buttons.front() : 0; }
+
+ unsigned int get_translation_queue_size() const { return (unsigned int)m_translation.queue.size(); }
+ unsigned int get_rotation_queue_size() const { return (unsigned int)m_rotation.queue.size(); }
+ unsigned int get_buttons_queue_size() const { return (unsigned int)m_buttons.size(); }
+
+ size_t get_translation_queue_max_size() const { return m_translation_queue_max_size; }
+ size_t get_rotation_queue_max_size() const { return m_rotation_queue_max_size; }
+ size_t get_buttons_queue_max_size() const { return m_buttons_queue_max_size; }
+#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
+
+ size_t get_queues_max_size() const { return m_translation.max_size; }
+ void set_queues_max_size(size_t size);
+
+ // return true if any change to the camera took place
+ bool apply(Camera& camera);
+ };
+
+ bool m_initialized;
+ mutable State m_state;
+ std::mutex m_mutex;
+ std::thread m_thread;
+ hid_device* m_device;
+ std::string m_device_str;
+ bool m_running;
+ bool m_settings_dialog;
+ std::chrono::time_point<std::chrono::high_resolution_clock> m_last_time;
+
+public:
+ Mouse3DController();
+
+ void init();
+ void shutdown();
+
+ bool is_device_connected() const { return m_device != nullptr; }
+ bool is_running() const { return m_running; }
+
+ bool process_mouse_wheel() { std::lock_guard<std::mutex> lock(m_mutex); return m_state.process_mouse_wheel(); }
+
+ bool apply(Camera& camera);
+
+ bool is_settings_dialog_shown() const { return m_settings_dialog; }
+ void show_settings_dialog(bool show) { m_settings_dialog = show && is_running(); }
+ void render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const;
+
+private:
+ bool connect_device();
+ void disconnect_device();
+ void start();
+ void stop() { m_running = false; }
+
+ // secondary thread methods
+ void run();
+ void collect_input();
+
+ typedef std::array<unsigned char, 13> DataPacket;
+ bool handle_packet(const DataPacket& packet);
+ bool handle_wireless_packet(const DataPacket& packet);
+ bool handle_packet_translation(const DataPacket& packet);
+ bool handle_packet_rotation(const DataPacket& packet, unsigned int first_byte);
+ bool handle_packet_button(const DataPacket& packet, unsigned int packet_size);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_Mouse3DController_hpp_
+
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index d6c776692..df705f25f 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -67,6 +67,7 @@
#include "GUI_Preview.hpp"
#include "3DBed.hpp"
#include "Camera.hpp"
+#include "Mouse3DController.hpp"
#include "Tab.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
@@ -353,7 +354,10 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 *
// Call select_preset() only if there is new preset and not just modified
if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
- tab->select_preset(selected_preset);
+ {
+ const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
+ tab->select_preset(/*selected_preset*/preset_name);
+ }
}
}));
}
@@ -1389,9 +1393,6 @@ struct Plater::priv
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
Slic3r::GCodePreviewData gcode_preview_data;
-#if ENABLE_THUMBNAIL_GENERATOR
- std::vector<Slic3r::ThumbnailData> thumbnail_data;
-#endif // ENABLE_THUMBNAIL_GENERATOR
// GUI elements
wxSizer* panel_sizer{ nullptr };
@@ -1400,6 +1401,7 @@ struct Plater::priv
Sidebar *sidebar;
Bed3D bed;
Camera camera;
+ Mouse3DController mouse3d_controller;
View3D* view3D;
GLToolbar view_toolbar;
Preview *preview;
@@ -1839,6 +1841,10 @@ struct Plater::priv
bool is_preview_loaded() const { return preview->is_loaded(); }
bool is_view3D_shown() const { return current_panel == view3D; }
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ bool init_view_toolbar();
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+
void reset_all_gizmos();
void update_ui_from_settings();
ProgressStatusBar* statusbar();
@@ -1968,7 +1974,8 @@ struct Plater::priv
bool can_reload_from_disk() const;
#if ENABLE_THUMBNAIL_GENERATOR
- void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
+ void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
+ void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
#endif // ENABLE_THUMBNAIL_GENERATOR
void msw_rescale_object_menu();
@@ -1984,7 +1991,9 @@ private:
bool complit_init_object_menu();
bool complit_init_sla_object_menu();
bool complit_init_part_menu();
+#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
void init_view_toolbar();
+#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
bool can_split() const;
bool layers_height_allowed() const;
@@ -2039,7 +2048,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
background_process.set_sla_print(&sla_print);
background_process.set_gcode_preview_data(&gcode_preview_data);
#if ENABLE_THUMBNAIL_GENERATOR
- background_process.set_thumbnail_data(&thumbnail_data);
+ background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
+ {
+ std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
+ generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background);
+ });
+ std::future<void> result = task.get_future();
+ wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); });
+ result.wait();
+ });
#endif // ENABLE_THUMBNAIL_GENERATOR
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
@@ -2110,6 +2127,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
+#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
+ view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); });
+ view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); });
+ view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); });
+#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
// 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
@@ -2123,7 +2145,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
+#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); });
+#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
@@ -2159,12 +2183,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// updates camera type from .ini file
camera.set_type(get_config("use_perspective_camera"));
+ mouse3d_controller.init();
+
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_(L("New Project")));
}
Plater::priv::~priv()
{
+ mouse3d_controller.shutdown();
+
if (config != nullptr)
delete config;
}
@@ -2287,7 +2315,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
for (size_t i = 0; i < input_files.size(); i++) {
const auto &path = input_files[i];
const auto filename = path.filename();
- const auto dlg_info = wxString::Format(_(L("Processing input file %s\n")), from_path(filename));
+ const auto dlg_info = wxString::Format(_(L("Processing input file %s")), from_path(filename)) + "\n";
dlg.Update(100 * i / input_files.size(), dlg_info);
const bool type_3mf = std::regex_match(path.string(), pattern_3mf);
@@ -2328,6 +2356,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// and place the loaded config over the base.
config += std::move(config_loaded);
}
+
+ this->model.custom_gcode_per_height = model.custom_gcode_per_height;
}
if (load_config)
@@ -2359,17 +2389,17 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (! is_project_file) {
if (model.looks_like_multipart_object()) {
wxMessageDialog msg_dlg(q, _(L(
- "This file contains several objects positioned at multiple heights. "
+ "This file contains several objects positioned at multiple heights.\n"
"Instead of considering them as multiple objects, should I consider\n"
- "this file as a single object having multiple parts?\n"
- )), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
+ "this file as a single object having multiple parts?")) + "\n",
+ _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) {
model.convert_multipart_object(nozzle_dmrs->values.size());
}
}
}
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
- wxMessageDialog msg_dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
+ wxMessageDialog msg_dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?"))+"\n",
_(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES)
{
@@ -2414,8 +2444,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
wxMessageDialog msg_dlg(q, _(L(
"Multiple objects were loaded for a multi-material printer.\n"
"Instead of considering them as multiple objects, should I consider\n"
- "these files to represent a single object having multiple parts?\n"
- )), _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
+ "these files to represent a single object having multiple parts?")) + "\n",
+ _(L("Multi-part object detected")), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) {
new_model->convert_multipart_object(nozzle_dmrs->values.size());
}
@@ -2746,8 +2776,7 @@ void Plater::priv::reset()
// The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
this->sidebar->show_sliced_info_sizer(false);
- auto& config = wxGetApp().preset_bundle->project_config;
- config.option<ConfigOptionFloats>("colorprint_heights")->values.clear();
+ model.custom_gcode_per_height.clear();
}
void Plater::priv::mirror(Axis axis)
@@ -3046,6 +3075,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
this->update_print_volume_state();
// Apply new config to the possibly running background task.
bool was_running = this->background_process.running();
+ this->background_process.set_force_update_print_regions(force_validation);
Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
@@ -3153,37 +3183,6 @@ bool Plater::priv::restart_background_process(unsigned int state)
( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
(state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
(state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
-#if ENABLE_THUMBNAIL_GENERATOR
- if (((state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) == 0) &&
- (this->background_process.state() != BackgroundSlicingProcess::STATE_RUNNING))
- {
- // update thumbnail data
- const std::vector<Vec2d> &thumbnail_sizes = this->background_process.current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values;
- if (this->printer_technology == ptFFF)
- {
- // for ptFFF we need to generate the thumbnails before the export of gcode starts
- this->thumbnail_data.clear();
- for (const Vec2d &sized : thumbnail_sizes)
- {
- this->thumbnail_data.push_back(ThumbnailData());
- Point size(sized); // round to ints
- generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, true, false);
- }
- }
- else if (this->printer_technology == ptSLA)
- {
- // for ptSLA generate thumbnails without supports and pad (not yet calculated)
- // to render also supports and pad see on_slicing_update()
- this->thumbnail_data.clear();
- for (const Vec2d &sized : thumbnail_sizes)
- {
- this->thumbnail_data.push_back(ThumbnailData());
- Point size(sized); // round to ints
- generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, true, false);
- }
- }
- }
-#endif // ENABLE_THUMBNAIL_GENERATOR
// The print is valid and it can be started.
if (this->background_process.start()) {
this->statusbar()->set_cancel_callback([this]() {
@@ -3282,21 +3281,80 @@ void Plater::priv::reload_from_disk()
for (unsigned int idx : selected_volumes_idxs)
{
const GLVolume* v = selection.get_volume(idx);
- int o_idx = v->object_idx();
int v_idx = v->volume_idx();
- selected_volumes.push_back({ o_idx, v_idx });
+ if (v_idx >= 0)
+ {
+ int o_idx = v->object_idx();
+ if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
+ selected_volumes.push_back({ o_idx, v_idx });
+ }
}
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
// collects paths of files to load
std::vector<fs::path> input_paths;
+#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
+ std::vector<fs::path> missing_input_paths;
+#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
for (const SelectedVolume& v : selected_volumes)
{
+#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
+ const ModelObject* object = model.objects[v.object_idx];
+ const ModelVolume* volume = object->volumes[v.volume_idx];
+
+ if (!volume->source.input_file.empty())
+ {
+ if (fs::exists(volume->source.input_file))
+ input_paths.push_back(volume->source.input_file);
+ else
+ missing_input_paths.push_back(volume->source.input_file);
+ }
+#else
const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx];
if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file))
input_paths.push_back(volume->source.input_file);
+#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
}
+
+#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
+ std::sort(missing_input_paths.begin(), missing_input_paths.end());
+ missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end());
+
+ while (!missing_input_paths.empty())
+ {
+ // ask user to select the missing file
+ std::string search = missing_input_paths.back().string();
+ wxFileDialog dialog(q, _(L("Please select the file to reload:")), "", search, file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+ if (dialog.ShowModal() != wxID_OK)
+ return;
+
+ std::string sel_filename_path = dialog.GetPath().ToUTF8().data();
+ std::string sel_filename = fs::path(sel_filename_path).filename().string();
+ if (boost::algorithm::iends_with(search, sel_filename))
+ {
+ input_paths.push_back(sel_filename_path);
+ missing_input_paths.pop_back();
+
+ std::string sel_path = fs::path(sel_filename_path).remove_filename().string();
+
+ std::vector<fs::path>::iterator it = missing_input_paths.begin();
+ while (it != missing_input_paths.end())
+ {
+ // try to use the path of the selected file with all remaining missing files
+ std::string repathed_filename = sel_path + "/" + it->filename().string();
+ if (fs::exists(repathed_filename))
+ {
+ input_paths.push_back(repathed_filename);
+ it = missing_input_paths.erase(it);
+ }
+ else
+ ++it;
+ }
+ }
+ }
+#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
+
std::sort(input_paths.begin(), input_paths.end());
input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end());
@@ -3317,7 +3375,6 @@ void Plater::priv::reload_from_disk()
catch (std::exception&)
{
// error while loading
- view3D->get_canvas3d()->enable_render(true);
return;
}
@@ -3331,22 +3388,20 @@ void Plater::priv::reload_from_disk()
if (old_volume->source.input_file == path)
{
- if (new_object_idx < (int)new_model.objects.size())
+ assert(new_object_idx < (int)new_model.objects.size());
+ ModelObject* new_model_object = new_model.objects[new_object_idx];
+ if (new_volume_idx < (int)new_model_object->volumes.size())
{
- ModelObject* new_model_object = new_model.objects[new_object_idx];
- if (new_volume_idx < (int)new_model_object->volumes.size())
- {
- old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
- ModelVolume* new_volume = old_model_object->volumes.back();
- new_volume->set_new_unique_id();
- new_volume->config.apply(old_volume->config);
- new_volume->set_type(old_volume->type());
- new_volume->set_material_id(old_volume->material_id());
- new_volume->set_transformation(old_volume->get_transformation());
- new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
- std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back());
- old_model_object->delete_volume(old_model_object->volumes.size() - 1);
- }
+ old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
+ ModelVolume* new_volume = old_model_object->volumes.back();
+ new_volume->set_new_unique_id();
+ new_volume->config.apply(old_volume->config);
+ new_volume->set_type(old_volume->type());
+ new_volume->set_material_id(old_volume->material_id());
+ new_volume->set_transformation(old_volume->get_transformation());
+ new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
+ std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back());
+ old_model_object->delete_volume(old_model_object->volumes.size() - 1);
}
}
}
@@ -3430,6 +3485,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
} else
view3D->reload_scene(true);
}
+
// sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
view3D->set_as_dirty();
view_toolbar.select_item("3D");
@@ -3444,6 +3500,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
this->q->reslice();
// keeps current gcode preview, if any
preview->reload_print(true);
+
preview->set_canvas_as_dirty();
view_toolbar.select_item("Preview");
}
@@ -3466,9 +3523,10 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
//! combo->GetStringSelection().ToUTF8().data());
const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
+ const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, selected_string);
if (preset_type == Preset::TYPE_FILAMENT) {
- wxGetApp().preset_bundle->set_filament_preset(idx, selected_string);
+ wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
}
// TODO: ?
@@ -3478,7 +3536,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
}
else {
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
- wxGetApp().get_tab(preset_type)->select_preset(selected_string);
+ wxGetApp().get_tab(preset_type)->select_preset(preset_name);
}
// update plater with new config
@@ -3489,7 +3547,6 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
* and for SLA presets they should be deleted
*/
if (preset_type == Preset::TYPE_PRINTER)
-// wxGetApp().obj_list()->update_settings_items();
wxGetApp().obj_list()->update_object_list_by_printer_technology();
}
@@ -3521,25 +3578,6 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
this->preview->reload_print();
-
- // uncomment the following lines if you want to render into the thumbnail also supports and pad for SLA printer
-/*
-#if ENABLE_THUMBNAIL_GENERATOR
- // update thumbnail data
- // for ptSLA generate the thumbnail after supports and pad have been calculated to have them rendered
- if ((this->printer_technology == ptSLA) && (evt.status.percent == -3))
- {
- const std::vector<Vec2d>& thumbnail_sizes = this->background_process.current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values;
- this->thumbnail_data.clear();
- for (const Vec2d &sized : thumbnail_sizes)
- {
- this->thumbnail_data.push_back(ThumbnailData());
- Point size(sized); // round to ints
- generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, false, false);
- }
- }
-#endif // ENABLE_THUMBNAIL_GENERATOR
-*/
}
}
@@ -3655,17 +3693,29 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
wxMenu* menu = nullptr;
- if (obj_idx == -1)
- menu = &default_menu;
+ if (obj_idx == -1) // no one or several object are selected
+ {
+ if (evt.data.second) // right button was clicked on empty space
+ menu = &default_menu;
+ else
+ return;
+ }
else
{
// If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
if (evt.data.second)
return;
- menu = printer_technology == ptSLA ? &sla_object_menu :
- get_selection().is_single_full_instance() ? // show "Object menu" for each FullInstance instead of FullObject
- &object_menu : &part_menu;
+ if (printer_technology == ptSLA)
+ menu = &sla_object_menu;
+ else
+ {
+ // show "Object menu" for each one or several FullInstance instead of FullObject
+ const bool is_some_full_instances = get_selection().is_single_full_instance() ||
+ get_selection().is_single_full_object() ||
+ get_selection().is_multiple_full_instance();
+ menu = is_some_full_instances ? &object_menu : &part_menu;
+ }
sidebar->obj_list()->append_menu_item_settings(menu);
@@ -3766,9 +3816,22 @@ bool Plater::priv::init_object_menu()
}
#if ENABLE_THUMBNAIL_GENERATOR
-void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
+void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
+{
+ view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
+}
+
+void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
{
- view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, transparent_background);
+ thumbnails.clear();
+ for (const Vec2d& size : sizes)
+ {
+ thumbnails.push_back(ThumbnailData());
+ Point isize(size); // round to ints
+ generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background);
+ if (!thumbnails.back().is_valid())
+ thumbnails.pop_back();
+ }
}
#endif // ENABLE_THUMBNAIL_GENERATOR
@@ -3845,14 +3908,18 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
[this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q);
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
- [this](wxCommandEvent&) { q->export_stl(false, true); });
+ [this](wxCommandEvent&) { q->export_stl(false, true); }, "", nullptr,
+ [this]() {
+ const Selection& selection = get_selection();
+ return selection.is_single_full_instance() || selection.is_single_full_object();
+ }, q);
menu->AppendSeparator();
q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) {
const Selection& selection = get_selection();
int instance_idx = selection.get_instance_idx();
- evt.Enable(instance_idx != -1);
+ evt.Enable(selection.is_single_full_instance() || selection.is_single_full_object());
if (instance_idx != -1)
{
evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable);
@@ -3898,7 +3965,7 @@ bool Plater::priv::complit_init_object_menu()
object_menu.AppendSeparator();
// Layers Editing for object
- sidebar->obj_list()->append_menu_item_layers_editing(&object_menu);
+ sidebar->obj_list()->append_menu_item_layers_editing(&object_menu, q);
object_menu.AppendSeparator();
// "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
@@ -3933,8 +4000,18 @@ bool Plater::priv::complit_init_part_menu()
return true;
}
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+bool Plater::priv::init_view_toolbar()
+#else
void Plater::priv::init_view_toolbar()
+#endif //!ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
{
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ if (view_toolbar.get_items_count() > 0)
+ // already initialized
+ return true;
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+
BackgroundTexture::Metadata background_data;
background_data.filename = "toolbar_background.png";
background_data.left = 16;
@@ -3943,7 +4020,11 @@ void Plater::priv::init_view_toolbar()
background_data.bottom = 16;
if (!view_toolbar.init(background_data))
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ return false;
+#else
return;
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom);
@@ -3958,7 +4039,11 @@ void Plater::priv::init_view_toolbar()
item.sprite_id = 0;
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
if (!view_toolbar.add_item(item))
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ return false;
+#else
return;
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
item.name = "Preview";
item.icon_filename = "preview.svg";
@@ -3966,10 +4051,18 @@ void Plater::priv::init_view_toolbar()
item.sprite_id = 1;
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
if (!view_toolbar.add_item(item))
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ return false;
+#else
return;
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
view_toolbar.select_item("3D");
view_toolbar.set_enabled(true);
+
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ return true;
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
}
bool Plater::priv::can_set_instance_to_object() const
@@ -4030,7 +4123,11 @@ bool Plater::priv::can_reload_from_disk() const
for (const SelectedVolume& v : selected_volumes)
{
const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx];
+#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
+ if (!volume->source.input_file.empty())
+#else
if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file))
+#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION
paths.push_back(volume->source.input_file);
}
std::sort(paths.begin(), paths.end());
@@ -4282,6 +4379,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
// Disable layer editing before the Undo / Redo jump.
if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled())
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
+
// Make a copy of the snapshot, undo/redo could invalidate the iterator
const UndoRedo::Snapshot snapshot_copy = *it_snapshot;
// Do the jump in time.
@@ -4812,7 +4910,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
wxBusyCursor wait;
#if ENABLE_THUMBNAIL_GENERATOR
ThumbnailData thumbnail_data;
- p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true);
+ p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, &thumbnail_data)) {
#else
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
@@ -4893,7 +4991,7 @@ void Plater::reslice()
p->show_action_buttons(true);
// update type of preview
- p->preview->update_view_type();
+ p->preview->update_view_type(true);
}
void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages)
@@ -5172,6 +5270,9 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
return extruder_colors;
extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
+ if (!wxGetApp().plater())
+ return extruder_colors;
+
const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values;
for (size_t i = 0; i < extruder_colors.size(); ++i)
if (extruder_colors[i] == "" && i < filament_colours.size())
@@ -5180,6 +5281,17 @@ std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
return extruder_colors;
}
+std::vector<std::string> Plater::get_colors_for_color_print() const
+{
+ std::vector<std::string> colors = get_extruder_colors_from_plater_config();
+
+ for (const Model::CustomGCode& code : p->model.custom_gcode_per_height)
+ if (code.gcode == ColorChangeCode)
+ colors.push_back(code.color);
+
+ return colors;
+}
+
wxString Plater::get_project_filename(const wxString& extension) const
{
return p->get_project_filename(extension);
@@ -5332,11 +5444,28 @@ void Plater::msw_rescale()
GetParent()->Layout();
}
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+bool Plater::init_view_toolbar()
+{
+ return p->init_view_toolbar();
+}
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+
const Camera& Plater::get_camera() const
{
return p->camera;
}
+const Mouse3DController& Plater::get_mouse3d_controller() const
+{
+ return p->mouse3d_controller;
+}
+
+Mouse3DController& Plater::get_mouse3d_controller()
+{
+ return p->mouse3d_controller;
+}
+
bool Plater::can_delete() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 50a4d1f17..730ba1097 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -41,6 +41,7 @@ class ObjectSettings;
class ObjectLayers;
class ObjectList;
class GLCanvas3D;
+class Mouse3DController;
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
@@ -223,6 +224,7 @@ public:
void on_activate();
const DynamicPrintConfig* get_plater_config() const;
std::vector<std::string> get_extruder_colors_from_plater_config() const;
+ std::vector<std::string> get_colors_for_color_print() const;
void update_object_menu();
@@ -260,7 +262,13 @@ public:
void msw_rescale();
+#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+ bool init_view_toolbar();
+#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX
+
const Camera& get_camera() const;
+ const Mouse3DController& get_mouse3d_controller() const;
+ Mouse3DController& get_mouse3d_controller();
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
class SuppressSnapshots
diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp
index baf7edbfa..4f07a7cf0 100644
--- a/src/slic3r/GUI/Preset.cpp
+++ b/src/slic3r/GUI/Preset.cpp
@@ -303,60 +303,58 @@ std::string Preset::label() const
return this->name + (this->is_dirty ? g_suffix_modified : "");
}
-bool Preset::is_compatible_with_print(const Preset &active_print) const
+bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print)
{
- auto &condition = this->compatible_prints_condition();
- auto *compatible_prints = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_prints"));
+ if (preset.vendor != nullptr && preset.vendor != active_print.vendor)
+ // The current profile has a vendor assigned and it is different from the active print's vendor.
+ return false;
+ auto &condition = preset.preset.compatible_prints_condition();
+ auto *compatible_prints = dynamic_cast<const ConfigOptionStrings*>(preset.preset.config.option("compatible_prints"));
bool has_compatible_prints = compatible_prints != nullptr && ! compatible_prints->values.empty();
if (! has_compatible_prints && ! condition.empty()) {
try {
- return PlaceholderParser::evaluate_boolean_expression(condition, active_print.config);
+ return PlaceholderParser::evaluate_boolean_expression(condition, active_print.preset.config);
} catch (const std::runtime_error &err) {
//FIXME in case of an error, return "compatible with everything".
- printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.name.c_str(), err.what());
+ printf("Preset::is_compatible_with_print - parsing error of compatible_prints_condition %s:\n%s\n", active_print.preset.name.c_str(), err.what());
return true;
}
}
- return this->is_default || active_print.name.empty() || ! has_compatible_prints ||
- std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) !=
+ return preset.preset.is_default || active_print.preset.name.empty() || ! has_compatible_prints ||
+ std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.preset.name) !=
compatible_prints->values.end();
}
-bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const
+bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config)
{
- auto &condition = this->compatible_printers_condition();
- auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers"));
+ if (preset.vendor != nullptr && preset.vendor != active_printer.vendor)
+ // The current profile has a vendor assigned and it is different from the active print's vendor.
+ return false;
+ auto &condition = preset.preset.compatible_printers_condition();
+ auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(preset.preset.config.option("compatible_printers"));
bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty();
if (! has_compatible_printers && ! condition.empty()) {
try {
- return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.config, extra_config);
+ return PlaceholderParser::evaluate_boolean_expression(condition, active_printer.preset.config, extra_config);
} catch (const std::runtime_error &err) {
//FIXME in case of an error, return "compatible with everything".
- printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.name.c_str(), err.what());
+ printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.preset.name.c_str(), err.what());
return true;
}
}
- return this->is_default || active_printer.name.empty() || ! has_compatible_printers ||
- std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) !=
+ return preset.preset.is_default || active_printer.preset.name.empty() || ! has_compatible_printers ||
+ std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.preset.name) !=
compatible_printers->values.end();
}
-bool Preset::is_compatible_with_printer(const Preset &active_printer) const
+bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer)
{
DynamicPrintConfig config;
- config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
- const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
+ config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
+ const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter");
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
- return this->is_compatible_with_printer(active_printer, &config);
-}
-
-bool Preset::update_compatible(const Preset &active_printer, const DynamicPrintConfig *extra_config, const Preset *active_print)
-{
- this->is_compatible = is_compatible_with_printer(active_printer, extra_config);
- if (active_print != nullptr)
- this->is_compatible &= is_compatible_with_print(*active_print);
- return this->is_compatible;
+ return is_compatible_with_printer(preset, active_printer, &config);
}
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
@@ -814,6 +812,8 @@ void PresetCollection::save_current_preset(const std::string &new_name)
preset.is_external = false;
// The newly saved preset will be activated -> make it visible.
preset.is_visible = true;
+ // Just system presets have aliases
+ preset.alias.clear();
}
// 2) Activate the saved preset.
this->select_preset_by_name(new_name, true);
@@ -907,6 +907,35 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const
return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset;
}
+// Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
+PresetWithVendorProfile PresetCollection::get_preset_with_vendor_profile(const Preset &preset) const
+{
+ const Preset *p = &preset;
+ const VendorProfile *v = nullptr;
+ do {
+ if (p->vendor != nullptr) {
+ v = p->vendor;
+ break;
+ }
+ p = this->get_preset_parent(*p);
+ } while (p != nullptr);
+ return PresetWithVendorProfile(preset, v);
+}
+
+const std::string& PresetCollection::get_preset_name_by_alias(const std::string& alias) const
+{
+ for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
+ const Preset& preset = this->m_presets[i];
+ if (!preset.is_visible || (!preset.is_compatible && i != m_idx_selected))
+ continue;
+
+ if (preset.alias == alias)
+ return preset.name;
+ }
+
+ return alias;
+}
+
const std::string& PresetCollection::get_suffix_modified() {
return g_suffix_modified;
}
@@ -944,19 +973,22 @@ void PresetCollection::set_default_suppressed(bool default_suppressed)
}
}
-size_t PresetCollection::update_compatible_internal(const Preset &active_printer, const Preset *active_print, bool unselect_if_incompatible)
+size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool unselect_if_incompatible)
{
DynamicPrintConfig config;
- config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
- const ConfigOption *opt = active_printer.config.option("nozzle_diameter");
+ config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
+ const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter");
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
bool selected = idx_preset == m_idx_selected;
Preset &preset_selected = m_presets[idx_preset];
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
- if (! preset_edited.update_compatible(active_printer, &config, active_print) &&
- selected && unselect_if_incompatible)
+ const PresetWithVendorProfile this_preset_with_vendor_profile = this->get_preset_with_vendor_profile(preset_edited);
+ preset_edited.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config);
+ if (active_print != nullptr)
+ preset_edited.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print);
+ if (! preset_edited.is_compatible && selected && unselect_if_incompatible)
m_idx_selected = -1;
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
@@ -982,7 +1014,7 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
// Otherwise fill in the list from scratch.
ui->Freeze();
ui->Clear();
- size_t selected_preset_item = 0;
+ size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
const Preset &selected_preset = this->get_selected_preset();
// Show wide icons if the currently selected preset is not compatible with the current printer,
@@ -1001,6 +1033,7 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
std::map<wxString, wxBitmap*> nonsys_presets;
wxString selected = "";
+ wxString tooltip = "";
if (!this->m_presets.front().is_visible)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) {
@@ -1029,17 +1062,24 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
}
+ const std::string name = preset.alias.empty() ? preset.name : preset.alias;
if (preset.is_default || preset.is_system) {
- ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
+ ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
- if (i == m_idx_selected)
+ if (i == m_idx_selected ||
+ // just in case: mark selected_preset_item as a first added element
+ selected_preset_item == INT_MAX) {
selected_preset_item = ui->GetCount() - 1;
+ tooltip = wxString::FromUTF8(preset.name.c_str());
+ }
}
else
{
- nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
- if (i == m_idx_selected)
- selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
+ nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
+ if (i == m_idx_selected) {
+ selected = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
+ tooltip = wxString::FromUTF8(preset.name.c_str());
+ }
}
if (i + 1 == m_num_default_presets)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
@@ -1049,7 +1089,9 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap));
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
ui->Append(it->first, *it->second);
- if (it->first == selected)
+ if (it->first == selected ||
+ // just in case: mark selected_preset_item as a first added element
+ selected_preset_item == INT_MAX)
selected_preset_item = ui->GetCount() - 1;
}
}
@@ -1080,8 +1122,15 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS);
}
+ /* But, if selected_preset_item is still equal to INT_MAX, it means that
+ * there is no presets added to the list.
+ * So, select last combobox item ("Add/Remove preset")
+ */
+ if (selected_preset_item == INT_MAX)
+ selected_preset_item = ui->GetCount() - 1;
+
ui->SetSelection(selected_preset_item);
- ui->SetToolTip(ui->GetString(selected_preset_item));
+ ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
ui->check_selection();
ui->Thaw();
@@ -1096,7 +1145,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
return 0;
ui->Freeze();
ui->Clear();
- size_t selected_preset_item = 0;
+ size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
* So set sizes for solid_colored(empty) icons used for preset
@@ -1131,7 +1180,9 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
if (preset.is_default || preset.is_system) {
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp);
- if (i == m_idx_selected)
+ if (i == m_idx_selected ||
+ // just in case: mark selected_preset_item as a first added element
+ selected_preset_item == INT_MAX)
selected_preset_item = ui->GetCount() - 1;
}
else
@@ -1148,7 +1199,9 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap);
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
ui->Append(it->first, *it->second);
- if (it->first == selected)
+ if (it->first == selected ||
+ // just in case: mark selected_preset_item as a first added element
+ selected_preset_item == INT_MAX)
selected_preset_item = ui->GetCount() - 1;
}
}
@@ -1163,6 +1216,14 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati
}
ui->Append(PresetCollection::separator("Add a new printer"), *bmp);
}
+
+ /* But, if selected_preset_item is still equal to INT_MAX, it means that
+ * there is no presets added to the list.
+ * So, select last combobox item ("Add/Remove preset")
+ */
+ if (selected_preset_item == INT_MAX)
+ selected_preset_item = ui->GetCount() - 1;
+
ui->SetSelection(selected_preset_item);
ui->SetToolTip(ui->GetString(selected_preset_item));
ui->Thaw();
diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp
index e2e4baa88..785f52c42 100644
--- a/src/slic3r/GUI/Preset.hpp
+++ b/src/slic3r/GUI/Preset.hpp
@@ -79,6 +79,8 @@ public:
VendorProfile() {}
VendorProfile(std::string id) : id(std::move(id)) {}
+ bool valid() const { return ! name.empty() && ! id.empty() && config_version.valid(); }
+
// Load VendorProfile from an ini file.
// If `load_all` is false, only the header with basic info (name, version, URLs) is loaded.
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
@@ -91,6 +93,17 @@ public:
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
};
+class Preset;
+
+// Helper to hold a profile with its vendor definition, where the vendor definition may have been extracted from a parent system preset.
+// The parent preset is only accessible through PresetCollection, therefore to allow definition of the various is_compatible_with methods
+// outside of the PresetCollection, this composite is returned by PresetCollection::get_preset_with_vendor_profile() when needed.
+struct PresetWithVendorProfile {
+ PresetWithVendorProfile(const Preset &preset, const VendorProfile *vendor) : preset(preset), vendor(vendor) {}
+ const Preset &preset;
+ const VendorProfile *vendor;
+};
+
// Note: it is imporant that map is used here rather than unordered_map,
// because we need iterators to not be invalidated,
// because Preset and the ConfigWizard hold pointers to VendorProfiles.
@@ -148,6 +161,9 @@ public:
// Configuration data, loaded from a file, or set from the defaults.
DynamicPrintConfig config;
+ // Alias of the preset
+ std::string alias = "";
+
void save();
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
@@ -158,10 +174,6 @@ public:
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
void reset_dirty() { this->is_dirty = false; }
- bool is_compatible_with_print(const Preset &active_print) const;
- bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const;
- bool is_compatible_with_printer(const Preset &active_printer) const;
-
// Returns the name of the preset, from which this preset inherits.
static std::string& inherits(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("inherits", true)->value; }
std::string& inherits() { return Preset::inherits(this->config); }
@@ -195,9 +207,6 @@ public:
// This call returns a reference, it may add a new entry into the DynamicPrintConfig.
PrinterTechnology& printer_technology_ref() { return this->config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value; }
- // Mark this preset as compatible if it is compatible with active_printer.
- bool update_compatible(const Preset &active_printer, const DynamicPrintConfig *extra_config, const Preset *active_print = nullptr);
-
// Set is_visible according to application config
void set_visible_from_appconfig(const AppConfig &app_config);
@@ -230,6 +239,10 @@ protected:
static std::string remove_suffix_modified(const std::string &name);
};
+bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print);
+bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
+bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
+
// Collections of presets of the same type (one of the Print, Filament or Printer type).
class PresetCollection
{
@@ -326,6 +339,12 @@ public:
Preset& get_edited_preset() { return m_edited_preset; }
const Preset& get_edited_preset() const { return m_edited_preset; }
+ // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist.
+ PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const;
+ PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); }
+
+ const std::string& get_preset_name_by_alias(const std::string& alias) const;
+
// used to update preset_choice from Tab
const std::deque<Preset>& get_presets() const { return m_presets; }
int get_idx_selected() { return m_idx_selected; }
@@ -383,13 +402,13 @@ public:
// For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition>
- void update_compatible(const Preset &active_printer, const Preset *active_print, bool select_other_if_incompatible, PreferedCondition prefered_condition)
+ void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool select_other_if_incompatible, PreferedCondition prefered_condition)
{
if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_preset(this->first_compatible_idx(prefered_condition));
}
- void update_compatible(const Preset &active_printer, const Preset *active_print, bool select_other_if_incompatible)
+ void update_compatible(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool select_other_if_incompatible)
{ this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const std::string&){return true;}); }
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
@@ -472,7 +491,7 @@ private:
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
- size_t update_compatible_internal(const Preset &active_printer, const Preset *active_print, bool unselect_if_incompatible);
+ size_t update_compatible_internal(const PresetWithVendorProfile &active_printer, const PresetWithVendorProfile *active_print, bool unselect_if_incompatible);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp
index 01c42d3de..9b8eaa8ec 100644
--- a/src/slic3r/GUI/PresetBundle.cpp
+++ b/src/slic3r/GUI/PresetBundle.cpp
@@ -329,58 +329,64 @@ void PresetBundle::load_installed_printers(const AppConfig &config)
}
}
-void PresetBundle::load_installed_filaments(AppConfig &config)
+const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const
{
- if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
- std::unordered_set<const Preset*> comp_filaments;
+ // there are not aliases for Printers profiles
+ if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
+ return alias;
- for (const Preset &printer : printers) {
- if (! printer.is_visible || printer.printer_technology() != ptFFF) {
- continue;
- }
+ const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints :
+ preset_type == Preset::TYPE_SLA_PRINT ? sla_prints :
+ preset_type == Preset::TYPE_FILAMENT ? filaments :
+ sla_materials;
- for (const Preset &filament : filaments) {
- if (filament.is_compatible_with_printer(printer)) {
- comp_filaments.insert(&filament);
- }
- }
- }
+ return presets.get_preset_name_by_alias(alias);
+}
- for (const auto &filament: comp_filaments) {
+void PresetBundle::load_installed_filaments(AppConfig &config)
+{
+ if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
+ // Compatibility with the PrusaSlicer 2.1.1 and older, where the filament profiles were not installable yet.
+ // Find all filament profiles, which are compatible with installed printers, and act as if these filament profiles
+ // were installed.
+ std::unordered_set<const Preset*> compatible_filaments;
+ for (const Preset &printer : printers)
+ if (printer.is_visible && printer.printer_technology() == ptFFF) {
+ const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
+ for (const Preset &filament : filaments)
+ if (is_compatible_with_printer(filaments.get_preset_with_vendor_profile(filament), printer_with_vendor_profile))
+ compatible_filaments.insert(&filament);
+ }
+ // and mark these filaments as installed, therefore this code will not be executed at the next start of the application.
+ for (const auto &filament: compatible_filaments)
config.set(AppConfig::SECTION_FILAMENTS, filament->name, "1");
- }
}
- for (auto &preset : filaments) {
+ for (auto &preset : filaments)
preset.set_visible_from_appconfig(config);
- }
}
void PresetBundle::load_installed_sla_materials(AppConfig &config)
{
if (! config.has_section(AppConfig::SECTION_MATERIALS)) {
std::unordered_set<const Preset*> comp_sla_materials;
-
- for (const Preset &printer : printers) {
- if (! printer.is_visible || printer.printer_technology() != ptSLA) {
- continue;
- }
-
- for (const Preset &material : sla_materials) {
- if (material.is_compatible_with_printer(printer)) {
- comp_sla_materials.insert(&material);
- }
- }
- }
-
- for (const auto &material: comp_sla_materials) {
+ // Compatibility with the PrusaSlicer 2.1.1 and older, where the SLA material profiles were not installable yet.
+ // Find all SLA material profiles, which are compatible with installed printers, and act as if these SLA material profiles
+ // were installed.
+ for (const Preset &printer : printers)
+ if (printer.is_visible && printer.printer_technology() == ptSLA) {
+ const PresetWithVendorProfile printer_with_vendor_profile = printers.get_preset_with_vendor_profile(printer);
+ for (const Preset &material : sla_materials)
+ if (is_compatible_with_printer(sla_materials.get_preset_with_vendor_profile(material), printer_with_vendor_profile))
+ comp_sla_materials.insert(&material);
+ }
+ // and mark these SLA materials as installed, therefore this code will not be executed at the next start of the application.
+ for (const auto &material: comp_sla_materials)
config.set(AppConfig::SECTION_MATERIALS, material->name, "1");
- }
}
- for (auto &preset : sla_materials) {
+ for (auto &preset : sla_materials)
preset.set_visible_from_appconfig(config);
- }
}
// Load selections (current print, current filaments, current printer) from config.ini
@@ -1122,6 +1128,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
PresetCollection *presets = nullptr;
std::vector<std::string> *loaded = nullptr;
std::string preset_name;
+ std::string alias_name;
if (boost::starts_with(section.first, "print:")) {
presets = &this->prints;
loaded = &loaded_prints;
@@ -1130,6 +1137,12 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
presets = &this->filaments;
loaded = &loaded_filaments;
preset_name = section.first.substr(9);
+
+ for (const auto& item : section.second)
+ if (boost::starts_with(item.first, "alias")) {
+ alias_name = item.second.data();
+ break;
+ }
} else if (boost::starts_with(section.first, "sla_print:")) {
presets = &this->sla_prints;
loaded = &loaded_sla_prints;
@@ -1138,6 +1151,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
presets = &this->sla_materials;
loaded = &loaded_sla_materials;
preset_name = section.first.substr(13);
+
+ int end_pos = preset_name.find_first_of("0.");
+ alias_name = preset_name.substr(0, end_pos-1);
} else if (boost::starts_with(section.first, "printer:")) {
presets = &this->printers;
loaded = &loaded_printers;
@@ -1283,6 +1299,14 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
loaded.is_system = true;
loaded.vendor = vendor_profile;
}
+
+ // next step of an preset name aliasing
+ int end_pos = preset_name.find_first_of("@");
+ if (end_pos != std::string::npos)
+ alias_name = preset_name.substr(0, end_pos - 1);
+
+ loaded.alias = alias_name.empty() ? preset_name : alias_name;
+
++ presets_loaded;
}
}
@@ -1353,23 +1377,24 @@ void PresetBundle::update_multi_material_filament_presets()
void PresetBundle::update_compatible(bool select_other_if_incompatible)
{
- const Preset &printer_preset = this->printers.get_edited_preset();
+ const Preset &printer_preset = this->printers.get_edited_preset();
+ const PresetWithVendorProfile printer_preset_with_vendor_profile = this->printers.get_preset_with_vendor_profile(printer_preset);
switch (printer_preset.printer_technology()) {
case ptFFF:
{
assert(printer_preset.config.has("default_print_profile"));
assert(printer_preset.config.has("default_filament_profile"));
- const Preset &print_preset = this->prints.get_edited_preset();
const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile");
const std::vector<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
prefered_print_profile.empty() ?
- this->prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible) :
- this->prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible,
+ this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible) :
+ this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible,
[&prefered_print_profile](const std::string& profile_name) { return profile_name == prefered_print_profile; });
+ const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
prefered_filament_profiles.empty() ?
- this->filaments.update_compatible(printer_preset, &print_preset, select_other_if_incompatible) :
- this->filaments.update_compatible(printer_preset, &print_preset, select_other_if_incompatible,
+ this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_if_incompatible) :
+ this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_if_incompatible,
[&prefered_filament_profiles](const std::string& profile_name)
{ return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); });
if (select_other_if_incompatible) {
@@ -1401,16 +1426,16 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible)
{
assert(printer_preset.config.has("default_sla_print_profile"));
assert(printer_preset.config.has("default_sla_material_profile"));
- const Preset &sla_print_preset = this->sla_prints.get_edited_preset();
- const std::string &prefered_sla_print_profile = printer_preset.config.opt_string("default_sla_print_profile");
+ const PresetWithVendorProfile sla_print_preset_with_vendor_profile = this->sla_prints.get_edited_preset_with_vendor_profile();
+ const std::string &prefered_sla_print_profile = printer_preset.config.opt_string("default_sla_print_profile");
(prefered_sla_print_profile.empty()) ?
- this->sla_prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible) :
- this->sla_prints.update_compatible(printer_preset, nullptr, select_other_if_incompatible,
+ this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible) :
+ this->sla_prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_if_incompatible,
[&prefered_sla_print_profile](const std::string& profile_name){ return profile_name == prefered_sla_print_profile; });
const std::string &prefered_sla_material_profile = printer_preset.config.opt_string("default_sla_material_profile");
prefered_sla_material_profile.empty() ?
- this->sla_materials.update_compatible(printer_preset, &sla_print_preset, select_other_if_incompatible) :
- this->sla_materials.update_compatible(printer_preset, &sla_print_preset, select_other_if_incompatible,
+ this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_if_incompatible) :
+ this->sla_materials.update_compatible(printer_preset_with_vendor_profile, &sla_print_preset_with_vendor_profile, select_other_if_incompatible,
[&prefered_sla_material_profile](const std::string& profile_name){ return profile_name == prefered_sla_material_profile; });
break;
}
@@ -1538,7 +1563,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
// Fill in the list from scratch.
ui->Freeze();
ui->Clear();
- size_t selected_preset_item = 0;
+ size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
+
const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]);
// Show wide icons if the currently selected preset is not compatible with the current printer,
// and draw a red flag in front of the selected preset.
@@ -1568,6 +1594,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
// set a bitmap height to m_bitmapLock->GetHeight()
const int icon_height = m_bitmapLock->GetHeight();//2 * icon_unit; //16 * scale_f + 0.5f;
+ wxString tooltip = "";
+
for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) {
const Preset &preset = this->filaments.preset(i);
bool selected = this->filament_presets[idx_extruder] == preset.name;
@@ -1608,18 +1636,25 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
bitmap = m_bitmapCache->insert(bitmap_key, bmps);
}
- if (preset.is_default || preset.is_system) {
- ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
+ const std::string name = preset.alias.empty() ? preset.name : preset.alias;
+ if (preset.is_default || preset.is_system) {
+ ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
(bitmap == 0) ? wxNullBitmap : *bitmap);
- if (selected)
+ if (selected ||
+ // just in case: mark selected_preset_item as a first added element
+ selected_preset_item == INT_MAX ) {
selected_preset_item = ui->GetCount() - 1;
+ tooltip = wxString::FromUTF8(preset.name.c_str());
+ }
}
else
{
- nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
+ nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
(bitmap == 0) ? &wxNullBitmap : bitmap);
- if (selected)
- selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
+ if (selected) {
+ selected_str = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
+ tooltip = wxString::FromUTF8(preset.name.c_str());
+ }
}
if (preset.is_default)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
@@ -1630,15 +1665,25 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap));
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
ui->Append(it->first, *it->second);
- if (it->first == selected_str)
+ if (it->first == selected_str ||
+ // just in case: mark selected_preset_item as a first added element
+ selected_preset_item == INT_MAX) {
selected_preset_item = ui->GetCount() - 1;
+ }
}
}
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS);
+ /* But, if selected_preset_item is still equal to INT_MAX, it means that
+ * there is no presets added to the list.
+ * So, select last combobox item ("Add/Remove filaments")
+ */
+ if (selected_preset_item == INT_MAX)
+ selected_preset_item = ui->GetCount() - 1;
+
ui->SetSelection(selected_preset_item);
- ui->SetToolTip(ui->GetString(selected_preset_item));
+ ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
ui->check_selection();
ui->Thaw();
diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp
index b1010e07b..5205042c5 100644
--- a/src/slic3r/GUI/PresetBundle.hpp
+++ b/src/slic3r/GUI/PresetBundle.hpp
@@ -139,6 +139,8 @@ public:
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void load_installed_printers(const AppConfig &config);
+ const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const;
+
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();
diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp
index a02772c4d..f6281d7af 100644
--- a/src/slic3r/GUI/PresetHints.cpp
+++ b/src/slic3r/GUI/PresetHints.cpp
@@ -32,11 +32,11 @@ std::string PresetHints::cooling_description(const Preset &preset)
% slowdown_below_layer_time % max_fan_speed % slowdown_below_layer_time % min_print_speed).str();
if (fan_below_layer_time > slowdown_below_layer_time) {
- out += (boost::format(_utf8(L("\nIf estimated layer time is greater, but still below ~%1%s, "
+ out += "\n" + (boost::format(_utf8(L("If estimated layer time is greater, but still below ~%1%s, "
"fan will run at a proportionally decreasing speed between %2%%% and %3%%%.")))
% fan_below_layer_time % max_fan_speed % min_fan_speed).str();
}
- out += _utf8(L("\nDuring the other layers, fan")) + " ";
+ out += "\n" + _utf8(L("During the other layers, fan")) + " ";
} else {
out = _utf8(L("Fan")) + " ";
}
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index c49cbea1e..c8239e0b7 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -998,6 +998,11 @@ void Tab::update_preset_description_line()
default: break;
}
}
+ else
+ {
+ description_line += "\n\n\t" + _(L("full profile name")) + ": \n\t\t" + parent->name;
+ description_line += "\n\t" + _(L("symbolic profile name")) + ": \n\t\t" + parent->alias;
+ }
}
m_parent_preset_description_line->SetText(description_line, false);
@@ -2750,7 +2755,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current)
PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology();
PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials;
bool old_preset_dirty = dependent.current_is_dirty();
- bool new_preset_compatible = dependent.get_edited_preset().is_compatible_with_print(*m_presets->find_preset(preset_name, true));
+ bool new_preset_compatible = is_compatible_with_print(dependent.get_edited_preset_with_vendor_profile(), m_presets->get_preset_with_vendor_profile(*m_presets->find_preset(preset_name, true)));
if (! canceled)
canceled = old_preset_dirty && ! new_preset_compatible && ! may_discard_current_dirty_preset(&dependent, preset_name);
if (! canceled) {
@@ -2768,6 +2773,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current)
// With the introduction of the SLA printer types, we need to support switching between
// the FFF and SLA printers.
const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true);
+ const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset);
PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology();
PrinterTechnology new_printer_technology = new_printer_preset.printer_technology();
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset())
@@ -2788,7 +2794,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current)
};
for (PresetUpdate &pu : updates) {
pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty();
- pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset);
+ pu.new_preset_compatible = (new_printer_technology == pu.technology) && is_compatible_with_printer(pu.presets->get_edited_preset_with_vendor_profile(), new_printer_preset_with_vendor_profile);
if (!canceled)
canceled = pu.old_preset_dirty && !pu.new_preset_compatible && !may_discard_current_dirty_preset(pu.presets, preset_name);
}
@@ -2924,7 +2930,13 @@ void Tab::OnTreeSelChange(wxTreeEvent& event)
#ifdef __linux__
std::unique_ptr<wxWindowUpdateLocker> no_updates(new wxWindowUpdateLocker(this));
#else
-// wxWindowUpdateLocker noUpdates(this);
+ /* On Windows we use DoubleBuffering during rendering,
+ * so on Window is no needed to call a Freeze/Thaw functions.
+ * But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call.
+ */
+#ifdef __WXOSX__
+ wxWindowUpdateLocker noUpdates(this);
+#endif
#endif
if (m_pages.empty())
@@ -2985,7 +2997,8 @@ void Tab::save_preset(std::string name /*= ""*/)
if (name.empty()) {
const Preset &preset = m_presets->get_selected_preset();
auto default_name = preset.is_default ? "Untitled" :
- preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() :
+// preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() :
+ preset.is_system ? (boost::format(_CTX_utf8(L_CONTEXT("%1% - Copy", "PresetName"), "PresetName")) % preset.name).str() :
preset.name;
bool have_extention = boost::iends_with(default_name, ".ini");
@@ -3020,7 +3033,7 @@ void Tab::save_preset(std::string name /*= ""*/)
show_error(this, _(L("Cannot overwrite an external profile.")));
return;
}
- if (existing && name != preset.name)
+ if (existing/* && name != preset.name*/)
{
wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exist."))) % name).str());
msg_text += "\n" + _(L("Replace?"));
diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp
index d2a5e185a..00e7725ac 100644
--- a/src/slic3r/GUI/UpdateDialogs.cpp
+++ b/src/slic3r/GUI/UpdateDialogs.cpp
@@ -156,8 +156,8 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
"This probably happened as a result of running an older %s after using a newer one.\n\n"
"You may either exit %s and try again with a newer version, or you may re-run the initial configuration. "
- "Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s.\n"
- )), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME));
+ "Doing so will create a backup snapshot of the existing configuration before installing files compatible with this %s.")) + "\n",
+ SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME));
text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
content_sizer->Add(text);
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index eb47fd208..302a5e521 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -11,6 +11,7 @@
#include <wx/statline.h>
#include <wx/dcclient.h>
#include <wx/numformatter.h>
+#include <wx/colordlg.h>
#include <boost/algorithm/string/replace.hpp>
@@ -22,9 +23,11 @@
#include "I18N.hpp"
#include "GUI_Utils.hpp"
#include "PresetBundle.hpp"
+#include "ExtruderSequenceDialog.hpp"
#include "../Utils/MacDarkMode.hpp"
using Slic3r::GUI::from_u8;
+using Slic3r::GUI::into_u8;
wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);
@@ -404,6 +407,23 @@ int em_unit(wxWindow* win)
return Slic3r::GUI::wxGetApp().em_unit();
}
+static float get_svg_scale_factor(wxWindow *win)
+{
+#ifdef __APPLE__
+ // Note: win->GetContentScaleFactor() is not used anymore here because it tends to
+ // return bogus results quite often (such as 1.0 on Retina or even 0.0).
+ // We're using the max scaling factor across all screens because it's very likely to be good enough.
+
+ static float max_scaling_factor = NAN;
+ if (std::isnan(max_scaling_factor)) {
+ max_scaling_factor = Slic3r::GUI::mac_max_scaling_factor();
+ }
+ return win != nullptr ? max_scaling_factor : 1.0f;
+#else
+ return 1.0f;
+#endif
+}
+
// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/)
@@ -449,7 +469,7 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
-/*static*/ std::vector<wxBitmap*> get_extruder_color_icons()
+std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon/* = false*/)
{
// Create the bitmap with color bars.
std::vector<wxBitmap*> bmps;
@@ -465,16 +485,18 @@ Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
* and scale them in respect to em_unit value
*/
const double em = Slic3r::GUI::wxGetApp().em_unit();
- const int icon_width = lround(3.2 * em);
+ const int icon_width = lround((thin_icon ? 1 : 3.2) * em);
const int icon_height = lround(1.6 * em);
for (const std::string& color : colors)
{
- wxBitmap* bitmap = m_bitmap_cache->find(color);
+ std::string bitmap_key = color + "-h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width);
+
+ wxBitmap* bitmap = m_bitmap_cache->find(bitmap_key);
if (bitmap == nullptr) {
// Paint the color icon.
Slic3r::PresetBundle::parse_color(color, rgb);
- bitmap = m_bitmap_cache->insert(color, m_bitmap_cache->mksolid(icon_width, icon_height, rgb));
+ bitmap = m_bitmap_cache->insert(bitmap_key, m_bitmap_cache->mksolid(icon_width, icon_height, rgb));
}
bmps.emplace_back(bitmap);
}
@@ -483,16 +505,62 @@ Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
}
-static wxBitmap get_extruder_color_icon(size_t extruder_idx)
+static wxBitmap get_extruder_color_icon(size_t extruder_idx, bool thin_icon = false)
{
// Create the bitmap with color bars.
- std::vector<wxBitmap*> bmps = get_extruder_color_icons();
+ std::vector<wxBitmap*> bmps = get_extruder_color_icons(thin_icon);
if (bmps.empty())
return wxNullBitmap;
return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx];
}
+void apply_extruder_selector(wxBitmapComboBox** ctrl,
+ wxWindow* parent,
+ const std::string& first_item/* = ""*/,
+ wxPoint pos/* = wxDefaultPosition*/,
+ wxSize size/* = wxDefaultSize*/,
+ bool use_thin_icon/* = false*/)
+{
+ std::vector<wxBitmap*> icons = get_extruder_color_icons(use_thin_icon);
+
+ if (!*ctrl)
+ *ctrl = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, pos, size,
+ 0, nullptr, wxCB_READONLY);
+ else
+ {
+ (*ctrl)->SetPosition(pos);
+ (*ctrl)->SetMinSize(size);
+ (*ctrl)->SetSize(size);
+ (*ctrl)->Clear();
+ }
+ if (first_item.empty())
+ (*ctrl)->Hide(); // to avoid unwanted rendering before layout (ExtruderSequenceDialog)
+
+ if (icons.empty() && !first_item.empty()) {
+ (*ctrl)->Append(_(first_item), wxNullBitmap);
+ return;
+ }
+
+ // For ObjectList we use short extruder name (just a number)
+ const bool use_full_item_name = dynamic_cast<Slic3r::GUI::ObjectList*>(parent) == nullptr;
+
+ int i = 0;
+ wxString str = _(L("Extruder"));
+ for (wxBitmap* bmp : icons) {
+ if (i == 0) {
+ if (!first_item.empty())
+ (*ctrl)->Append(_(first_item), *bmp);
+ ++i;
+ }
+
+ (*ctrl)->Append(use_full_item_name ? wxString::Format("%s %d", str, i) : std::to_string(i), *bmp);
+ ++i;
+ }
+ (*ctrl)->SetSelection(0);
+}
+
+
// *****************************************************************************
// ----------------------------------------------------------------------------
// ObjectDataViewModelNode
@@ -2230,26 +2298,31 @@ DoubleSlider::DoubleSlider( wxWindow *parent,
if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
- m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "up_half_circle.png", 16, true));
- m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "down_half_circle.png", 16, true));
- m_thumb_size = m_bmp_thumb_lower.bmp().GetSize();
+ const float scale_factor = get_svg_scale_factor(this);
- m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add_on.png");
- m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_off.png");
- m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_delete_on.png");
- m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_delete_off.png");
- m_tick_icon_dim = m_bmp_add_tick_on.bmp().GetSize().x;
+ m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
+ m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
+ m_thumb_size = m_bmp_thumb_lower.bmp().GetSize()*(1.0/scale_factor);
- m_bmp_one_layer_lock_on = ScalableBitmap(this, "one_layer_lock_on.png");
- m_bmp_one_layer_lock_off = ScalableBitmap(this, "one_layer_lock_off.png");
- m_bmp_one_layer_unlock_on = ScalableBitmap(this, "one_layer_unlock_on.png");
- m_bmp_one_layer_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png");
- m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x;
+ m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
+ m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f");
+ m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del");
+ m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f");
+ m_tick_icon_dim = int((float)m_bmp_add_tick_on.bmp().GetSize().x / scale_factor);
+
+ m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed");
+ m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f");
+ m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open");
+ m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f");
+ m_lock_icon_dim = int((float)m_bmp_one_layer_lock_on.bmp().GetSize().x / scale_factor);
m_bmp_revert = ScalableBitmap(this, "undo");
- m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
+ m_revert_icon_dim = int((float)m_bmp_revert.bmp().GetSize().x / scale_factor);
+ m_bmp_cog = ScalableBitmap(this, "cog");
+ m_cog_icon_dim = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor);
m_selection = ssUndef;
+ m_pause_print_msg = _utf8(L("Place bearings in slots and resume"));
// slider events
Bind(wxEVT_PAINT, &DoubleSlider::OnPaint, this);
@@ -2268,7 +2341,7 @@ DoubleSlider::DoubleSlider( wxWindow *parent,
// control's view variables
SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit();
- DARK_ORANGE_PEN = wxPen(wxColour(253, 84, 2));
+ DARK_ORANGE_PEN = wxPen(wxColour(237, 107, 33));
ORANGE_PEN = wxPen(wxColour(253, 126, 66));
LIGHT_ORANGE_PEN = wxPen(wxColour(254, 177, 139));
@@ -2306,6 +2379,8 @@ void DoubleSlider::msw_rescale()
m_bmp_revert.msw_rescale();
m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
+ m_bmp_cog.msw_rescale();
+ m_cog_icon_dim = m_bmp_cog.bmp().GetSize().x;
SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit();
@@ -2453,41 +2528,45 @@ double DoubleSlider::get_double_value(const SelectedSlider& selection)
return m_values[selection == ssLower ? m_lower_value : m_higher_value];
}
-std::vector<double> DoubleSlider::GetTicksValues() const
+using t_custom_code = Slic3r::Model::CustomGCode;
+std::vector<t_custom_code> DoubleSlider::GetTicksValues() const
{
- std::vector<double> values;
+ std::vector<t_custom_code> values;
const int val_size = m_values.size();
if (!m_values.empty())
- for (int tick : m_ticks) {
- if (tick > val_size)
+ for (const TICK_CODE& tick : m_ticks_) {
+ if (tick.tick > val_size)
break;
- values.push_back(m_values[tick]);
+ values.push_back(t_custom_code(m_values[tick.tick], tick.gcode, tick.extruder, tick.color));
}
return values;
}
-void DoubleSlider::SetTicksValues(const std::vector<double>& heights)
+void DoubleSlider::SetTicksValues(const std::vector<t_custom_code>& heights)
{
if (m_values.empty())
return;
- const bool was_empty = m_ticks.empty();
+ const bool was_empty = m_ticks_.empty();
- m_ticks.clear();
+ m_ticks_.clear();
for (auto h : heights) {
- auto it = std::lower_bound(m_values.begin(), m_values.end(), h - epsilon());
+ auto it = std::lower_bound(m_values.begin(), m_values.end(), h.height - epsilon());
if (it == m_values.end())
continue;
- m_ticks.insert(it-m_values.begin());
+ m_ticks_.insert(TICK_CODE(it-m_values.begin(), h.gcode, h.extruder, h.color));
}
- if (!was_empty && m_ticks.empty())
+ if (!was_empty && m_ticks_.empty())
// Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+
+ Refresh();
+ Update();
}
void DoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
@@ -2538,17 +2617,20 @@ void DoubleSlider::render()
// //higher slider:
// draw_thumb(dc, higher_pos, ssHigher);
- // draw both sliders
- draw_thumbs(dc, lower_pos, higher_pos);
-
//draw color print ticks
draw_ticks(dc);
+ // draw both sliders
+ draw_thumbs(dc, lower_pos, higher_pos);
+
//draw lock/unlock
draw_one_layer_icon(dc);
//draw revert bitmap (if it's shown)
draw_revert_icon(dc);
+
+ //draw cog bitmap (if it's shown)
+ draw_cog_icon(dc);
}
void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
@@ -2560,7 +2642,7 @@ void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoin
return;
wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp();
- if (m_ticks.find(tick) != m_ticks.end())
+ if (m_ticks_.find(tick) != m_ticks_.end())
icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp();
wxCoord x_draw, y_draw;
@@ -2634,7 +2716,7 @@ void DoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedS
}
else {
x_draw = pos.x - int(0.5*m_thumb_size.x);
- y_draw = pos.y+1;
+ y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
else{
@@ -2644,7 +2726,7 @@ void DoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedS
}
else {
x_draw = pos.x - int(0.5*m_thumb_size.x);
- y_draw = pos.y - m_thumb_size.y;
+ y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
@@ -2703,14 +2785,26 @@ void DoubleSlider::draw_ticks(wxDC& dc)
int height, width;
get_size(&width, &height);
const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width;
- for (auto tick : m_ticks)
+ for (auto tick : m_ticks_)
{
- const wxCoord pos = get_position_from_value(tick);
+ const wxCoord pos = get_position_from_value(tick.tick);
is_horizontal() ? dc.DrawLine(pos, mid-14, pos, mid-9) :
dc.DrawLine(mid - 14, pos/* - 1*/, mid - 9, pos/* - 1*/);
is_horizontal() ? dc.DrawLine(pos, mid+14, pos, mid+9) :
dc.DrawLine(mid + 14, pos/* - 1*/, mid + 9, pos/* - 1*/);
+
+ // Draw icon for "Pause print" or "Custom Gcode"
+ if (tick.gcode != Slic3r::ColorChangeCode && tick.gcode != Slic3r::ExtruderChangeCode)
+ {
+ wxBitmap icon = create_scaled_bitmap(this, tick.gcode == Slic3r::PausePrintCode ? "pause_print" : "edit_gcode");
+
+ wxCoord x_draw, y_draw;
+ is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim;
+ is_horizontal() ? y_draw = mid + 22 : x_draw = mid + m_thumb_size.x + 3;
+
+ dc.DrawBitmap(icon, x_draw, y_draw);
+ }
}
}
@@ -2722,47 +2816,42 @@ void DoubleSlider::draw_colored_band(wxDC& dc)
int height, width;
get_size(&width, &height);
- wxRect main_band = m_rect_lower_thumb;
- if (is_horizontal()) {
- main_band.SetLeft(SLIDER_MARGIN);
- main_band.SetRight(width - SLIDER_MARGIN + 1);
- }
- else {
- const int cut = 2;
- main_band.x += cut;
- main_band.width -= 2*cut;
- main_band.SetTop(SLIDER_MARGIN);
- main_band.SetBottom(height - SLIDER_MARGIN + 1);
- }
+ const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width;
- if (m_ticks.empty()) {
- dc.SetPen(GetParent()->GetBackgroundColour());
- dc.SetBrush(GetParent()->GetBackgroundColour());
- dc.DrawRectangle(main_band);
- return;
- }
+ wxRect main_band = is_horizontal() ?
+ wxRect(SLIDER_MARGIN, lround(mid - 0.375 * m_thumb_size.y),
+ width - 2 * SLIDER_MARGIN + 1, lround(0.75 * m_thumb_size.y)) :
+ wxRect(lround(mid - 0.375 * m_thumb_size.x), SLIDER_MARGIN,
+ lround(0.75 * m_thumb_size.x), height - 2 * SLIDER_MARGIN + 1);
- const std::vector<std::string>& colors = Slic3r::GCodePreviewData::ColorPrintColors();
- const size_t colors_cnt = colors.size();
+ auto draw_band = [](wxDC& dc, const wxColour& clr, const wxRect& band_rc) {
+ dc.SetPen(clr);
+ dc.SetBrush(clr);
+ dc.DrawRectangle(band_rc);
+ };
+
+ const std::vector<std::string>& colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
+ int colors_cnt = colors.size();
- wxColour clr(colors[0]);
- dc.SetPen(clr);
- dc.SetBrush(clr);
- dc.DrawRectangle(main_band);
+ const wxColour bg_clr = GetParent()->GetBackgroundColour();
+
+ wxColour clr = m_state == msSingleExtruder ? wxColour(colors[0]) : bg_clr;
+ draw_band(dc, clr, main_band);
size_t i = 1;
- for (auto tick : m_ticks)
+ for (auto tick : m_ticks_)
{
- if (i == colors_cnt)
- i = 0;
- const wxCoord pos = get_position_from_value(tick);
- is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) :
- main_band.SetBottom(pos-1);
+ if ( (m_state == msSingleExtruder && tick.gcode != Slic3r::ColorChangeCode) ||
+ (m_state == msMultiExtruder && tick.gcode != Slic3r::ExtruderChangeCode) )
+ continue;
- clr = wxColour(colors[i]);
- dc.SetPen(clr);
- dc.SetBrush(clr);
- dc.DrawRectangle(main_band);
+ const wxCoord pos = get_position_from_value(tick.tick);
+ is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) :
+ main_band.SetBottom(pos - 1);
+
+ clr = (m_state == msMultiExtruder && tick.color.empty()) ? bg_clr :
+ m_state == msMultiExtruder ? wxColour(colors[std::min<int>(colors_cnt - 1, tick.extruder-1)]) : wxColour(tick.color);
+ draw_band(dc, clr, main_band);
i++;
}
}
@@ -2788,7 +2877,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc)
void DoubleSlider::draw_revert_icon(wxDC& dc)
{
- if (m_ticks.empty() || !m_is_enabled_tick_manipulation)
+ if (m_ticks_.empty() || !m_is_enabled_tick_manipulation)
return;
int width, height;
@@ -2804,9 +2893,27 @@ void DoubleSlider::draw_revert_icon(wxDC& dc)
m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim);
}
+void DoubleSlider::draw_cog_icon(wxDC& dc)
+{
+ if (m_state != msMultiExtruder)
+ return;
+
+ int width, height;
+ get_size(&width, &height);
+
+ wxCoord x_draw, y_draw;
+ is_horizontal() ? x_draw = width-2 : x_draw = width - m_cog_icon_dim - 2;
+ is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height-2;
+
+ dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw);
+
+ //update rect of the lock/unlock icon
+ m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim);
+}
+
void DoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
{
- const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y);
+ const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, int(m_thumb_size.y*0.5));
if (selection == ssLower)
m_rect_lower_thumb = rect;
else
@@ -2840,16 +2947,16 @@ bool DoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
int DoubleSlider::is_point_near_tick(const wxPoint& pt)
{
- for (auto tick : m_ticks) {
- const wxCoord pos = get_position_from_value(tick);
+ for (auto tick : m_ticks_) {
+ const wxCoord pos = get_position_from_value(tick.tick);
if (is_horizontal()) {
if (pos - 4 <= pt.x && pt.x <= pos + 4)
- return tick;
+ return tick.tick;
}
else {
if (pos - 4 <= pt.y && pt.y <= pos + 4)
- return tick;
+ return tick.tick;
}
}
return -1;
@@ -2901,9 +3008,13 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
if (!m_selection) m_selection = ssHigher;
- m_ticks.clear();
+ m_ticks_.clear();
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
}
+ else if (is_point_in_rect(pos, m_rect_cog_icon) && m_state == msMultiExtruder) {
+ // show dialog for set extruder sequence
+ m_edit_extruder_sequence = true;
+ }
else
detect_selected_slider(pos);
@@ -2916,7 +3027,8 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
get_value_from_position(pos.x, pos.y);
if (mouse_val >= 0)
{
- if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) {
+ // if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) {
+ if ( mouse_val <= m_lower_value ) {
SetLowerValue(mouse_val);
correct_lower_value();
m_selection = ssLower;
@@ -2956,6 +3068,38 @@ void DoubleSlider::correct_higher_value()
m_lower_value = m_higher_value;
}
+wxString DoubleSlider::get_tooltip(IconFocus icon_focus)
+{
+ wxString tooltip(wxEmptyString);
+ if (m_is_one_layer_icon_focesed)
+ tooltip = _(L("One layer mode"));
+
+ if (icon_focus == ifRevert)
+ tooltip = _(L("Discard all custom changes"));
+ if (icon_focus == ifCog)
+ tooltip = _(L("Set extruder sequence for whole print"));
+ else if (m_is_action_icon_focesed)
+ {
+ const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
+ const auto tick_code_it = m_ticks_.find(tick);
+ tooltip = tick_code_it == m_ticks_.end() ? (m_state == msSingleExtruder ?
+ _(L("For add color change use left mouse button click")) :
+ _(L("For add change extruder use left mouse button click"))) + "\n" +
+ _(L("For add another code use right mouse button click")) :
+ tick_code_it->gcode == Slic3r::ColorChangeCode ? ( m_state == msSingleExtruder ?
+ _(L("For Delete color change use left mouse button click\n"
+ "For Edit color use right mouse button click")) :
+ from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ):
+// tick_code_it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause")) :
+ tick_code_it->gcode == Slic3r::ExtruderChangeCode ?
+ from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) :
+ from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n"
+ "For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str());
+ }
+
+ return tooltip;
+}
+
void DoubleSlider::OnMotion(wxMouseEvent& event)
{
bool action = false;
@@ -2964,11 +3108,14 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
const wxPoint pos = event.GetLogicalPosition(dc);
m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon);
- bool is_revert_icon_focused = false;
+ IconFocus icon_focus = ifNone;
if (!m_is_left_down && !m_is_one_layer) {
m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
- is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon);
+ if (!m_ticks_.empty() && is_point_in_rect(pos, m_rect_revert_icon))
+ icon_focus = ifRevert;
+ else if (is_point_in_rect(pos, m_rect_cog_icon))
+ icon_focus = ifCog;
}
else if (m_is_left_down || m_is_right_down) {
if (m_selection == ssLower) {
@@ -2989,10 +3136,7 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
event.Skip();
// Set tooltips with information for each icon
- const wxString tooltip = m_is_one_layer_icon_focesed ? _(L("One layer mode")) :
- m_is_action_icon_focesed ? _(L("Add/Del color change")) :
- is_revert_icon_focused ? _(L("Discard all color changes")) : "";
- this->SetToolTip(tooltip);
+ this->SetToolTip(get_tooltip(icon_focus));
if (action)
{
@@ -3009,6 +3153,43 @@ void DoubleSlider::OnLeftUp(wxMouseEvent& event)
return;
this->ReleaseMouse();
m_is_left_down = false;
+
+ if (m_show_context_menu)
+ {
+ if (m_state == msMultiExtruder)
+ {
+ wxMenu menu;
+ const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt();
+ if (extruders_cnt > 1)
+ {
+ const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
+
+ wxMenu* change_extruder_menu = new wxMenu();
+
+ for (int i = 0; i <= extruders_cnt; i++) {
+ const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i);
+
+ append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "",
+ [this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder);
+ }
+
+ wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder")));
+ change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder"));
+ }
+
+ Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu);
+ }
+ else
+ add_code(Slic3r::ColorChangeCode);
+
+ m_show_context_menu = false;
+ }
+
+ if (m_edit_extruder_sequence) {
+ edit_extruder_sequence();
+ m_edit_extruder_sequence = false;
+ }
+
Refresh();
Update();
event.Skip();
@@ -3059,21 +3240,36 @@ void DoubleSlider::action_tick(const TicksAction action)
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
- if (action == taOnIcon) {
- if (!m_ticks.insert(tick).second)
- m_ticks.erase(tick);
+ const auto it = m_ticks_.find(tick);
+
+ if (it != m_ticks_.end()) // erase this tick
+ {
+ if (action == taAdd)
+ return;
+ m_ticks_.erase(TICK_CODE(tick));
+
+ wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+ Refresh();
+ Update();
+ return;
}
- else {
- const auto it = m_ticks.find(tick);
- if (it == m_ticks.end() && action == taAdd)
- m_ticks.insert(tick);
- else if (it != m_ticks.end() && action == taDel)
- m_ticks.erase(tick);
+
+ if (action == taDel)
+ return;
+ if (action == taAdd)
+ {
+ // OnChar() is called immediately after OnKeyDown(), which can cause call of add_code() twice.
+ // To avoid this case we should suppress second add_code() call.
+ if (m_suppress_add_code)
+ return;
+ m_suppress_add_code = true;
+ if (m_state != msMultiExtruder)
+ add_code(Slic3r::ColorChangeCode);
+ m_suppress_add_code = false;
+ return;
}
- wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
- Refresh();
- Update();
+ m_show_context_menu = true;
}
void DoubleSlider::OnWheel(wxMouseEvent& event)
@@ -3148,6 +3344,27 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event)
this->CaptureMouse();
const wxClientDC dc(this);
+
+ wxPoint pos = event.GetLogicalPosition(dc);
+ if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation)
+ {
+ const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
+ // if on this Z doesn't exist tick
+ auto it = m_ticks_.find(tick);
+ if (it == m_ticks_.end())
+ {
+ // show context menu on OnRightUp()
+ m_show_context_menu = true;
+ return;
+ }
+ if (it->gcode != Slic3r::ExtruderChangeCode)
+ {
+ // show "Edit" and "Delete" menu on OnRightUp()
+ m_show_edit_menu = true;
+ return;
+ }
+ }
+
detect_selected_slider(event.GetLogicalPosition(dc));
if (!m_selection)
return;
@@ -3157,13 +3374,29 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event)
else
m_lower_value = m_higher_value;
- m_is_right_down = m_is_one_layer = true;
+ // set slider to "one layer" mode
+ m_is_right_down = m_is_one_layer = true;
Refresh();
Update();
event.Skip();
}
+int DoubleSlider::get_extruder_for_tick(int tick)
+{
+ if (m_ticks_.empty())
+ return 0;
+
+ auto it = m_ticks_.lower_bound(tick);
+ while (it != m_ticks_.begin()) {
+ --it;
+ if(it->gcode == Slic3r::ExtruderChangeCode)
+ return it->extruder;
+ }
+
+ return 0;
+}
+
void DoubleSlider::OnRightUp(wxMouseEvent& event)
{
if (!HasCapture())
@@ -3171,11 +3404,296 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event)
this->ReleaseMouse();
m_is_right_down = m_is_one_layer = false;
+ if (m_show_context_menu) {
+ wxMenu menu;
+
+ if (m_state == msMultiExtruder)
+ {
+ const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt();
+ if (extruders_cnt > 1)
+ {
+ const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
+
+ wxMenu* change_extruder_menu = new wxMenu();
+ wxMenu* add_color_change_menu = new wxMenu();
+
+ for (int i = 0; i <= extruders_cnt; i++) {
+ const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i);
+
+ append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "",
+ [this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder);
+
+ if (i==0) // don't use M600 for default extruder, if multimaterial print is selected
+ continue;
+ append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
+ [this, i](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode, i); }, "", &menu);
+ }
+
+ wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder")));
+ change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder"));
+
+ const wxString menu_name = from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % Slic3r::ColorChangeCode).str());
+ wxMenuItem* add_color_change_menu_item = menu.AppendSubMenu(add_color_change_menu, menu_name, "");
+ add_color_change_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "colorchange_add_m"));
+ }
+ }
+ else
+ append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "",
+ [this](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode); }, "colorchange_add_m", &menu);
+
+ append_menu_item(&menu, wxID_ANY, _(L("Add pause print")) + " (M601)", "",
+ [this](wxCommandEvent&) { add_code(Slic3r::PausePrintCode); }, "pause_print", &menu);
+
+ append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "",
+ [this](wxCommandEvent&) { add_code(""); }, "edit_gcode", &menu);
+
+ Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu);
+
+ m_show_context_menu = false;
+ }
+ else if (m_show_edit_menu) {
+ wxMenu menu;
+
+ std::set<TICK_CODE>::iterator it = m_ticks_.find(m_selection == ssLower ? m_lower_value : m_higher_value);
+ const bool is_color_change = it->gcode == Slic3r::ColorChangeCode;
+
+ append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Edit color")) :
+ it->gcode == Slic3r::PausePrintCode ? _(L("Edit pause print message")) :
+ _(L("Edit custom G-code")), "",
+ [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu);
+
+ append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Delete color change")) :
+ it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause print")) :
+ _(L("Delete custom G-code")), "",
+ [this](wxCommandEvent&) { action_tick(taDel); }, "colorchange_del_f", &menu);
+
+ Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu);
+
+ m_show_edit_menu = false;
+ }
+
Refresh();
Update();
event.Skip();
}
+static std::string get_new_color(const std::string& color)
+{
+ wxColour clr(color);
+ if (!clr.IsOk())
+ clr = wxColour(0, 0, 0); // Don't set alfa to transparence
+
+ auto data = new wxColourData();
+ data->SetChooseFull(1);
+ data->SetColour(clr);
+
+ wxColourDialog dialog(nullptr, data);
+ dialog.CenterOnParent();
+ if (dialog.ShowModal() == wxID_OK)
+ return dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
+ return "";
+}
+
+static std::string get_custom_code(const std::string& code_in, double height)
+{
+ wxString msg_text = from_u8(_utf8(L("Enter custom G-code used on current layer"))) + ":";
+ wxString msg_header = from_u8((boost::format(_utf8(L("Custom Gcode on current layer (%1% mm)."))) % height).str());
+
+ // get custom gcode
+ wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in,
+ wxTextEntryDialogStyle | wxTE_MULTILINE);
+ if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
+ return "";
+
+ return dlg.GetValue().ToStdString();
+}
+
+static std::string get_pause_print_msg(const std::string& msg_in, double height)
+{
+ wxString msg_text = from_u8(_utf8(L("Enter short message shown on Printer display during pause print"))) + ":";
+ wxString msg_header = from_u8((boost::format(_utf8(L("Message for pause print on current layer (%1% mm)."))) % height).str());
+
+ // get custom gcode
+ wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in),
+ wxTextEntryDialogStyle);
+ if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
+ return "";
+
+ return into_u8(dlg.GetValue());
+}
+
+void DoubleSlider::add_code(std::string code, int selected_extruder/* = -1*/)
+{
+ const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
+ // if on this Z doesn't exist tick
+ auto it = m_ticks_.find(tick);
+ if (it == m_ticks_.end())
+ {
+ std::string color = "";
+ if (code == Slic3r::ColorChangeCode)
+ {
+ std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
+
+ if (m_state == msSingleExtruder && !m_ticks_.empty()) {
+ auto before_tick_it = std::lower_bound(m_ticks_.begin(), m_ticks_.end(), tick);
+ while (before_tick_it != m_ticks_.begin()) {
+ --before_tick_it;
+ if (before_tick_it->gcode == Slic3r::ColorChangeCode) {
+ color = before_tick_it->color;
+ break;
+ }
+ }
+
+ if (color.empty())
+ color = colors[0];
+ }
+ else
+ color = colors[selected_extruder > 0 ? selected_extruder-1 : 0];
+
+ color = get_new_color(color);
+ if (color.empty())
+ return;
+ }
+ else if (code == Slic3r::PausePrintCode)
+ {
+ /* PausePrintCode doesn't need a color, so
+ * this field is used for save a short message shown on Printer display
+ * */
+ color = get_pause_print_msg(m_pause_print_msg, m_values[tick]);
+ if (color.empty())
+ return;
+ m_pause_print_msg = color;
+ }
+ else if (code.empty())
+ {
+ code = get_custom_code(m_custom_gcode, m_values[tick]);
+ if (code.empty())
+ return;
+ m_custom_gcode = code;
+ }
+
+ int extruder = 1;
+ if (m_state == msMultiExtruder) {
+ if (code == Slic3r::ColorChangeCode && selected_extruder >= 0)
+ extruder = selected_extruder;
+ else
+ extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
+ }
+
+ m_ticks_.insert(TICK_CODE(tick, code, extruder, color));
+
+ wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+ Refresh();
+ Update();
+ }
+}
+
+void DoubleSlider::edit_tick()
+{
+ const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
+ // if on this Z exists tick
+ std::set<TICK_CODE>::iterator it = m_ticks_.find(tick);
+ if (it != m_ticks_.end())
+ {
+ std::string edited_value;
+ if (it->gcode == Slic3r::ColorChangeCode)
+ edited_value = get_new_color(it->color);
+ else if (it->gcode == Slic3r::PausePrintCode)
+ edited_value = get_pause_print_msg(it->color, m_values[it->tick]);
+ else
+ edited_value = get_custom_code(it->gcode, m_values[it->tick]);
+
+ if (edited_value.empty())
+ return;
+
+ TICK_CODE changed_tick = *it;
+ if (it->gcode == Slic3r::ColorChangeCode || it->gcode == Slic3r::PausePrintCode) {
+ if (it->color == edited_value)
+ return;
+ changed_tick.color = edited_value;
+ }
+ else {
+ if (it->gcode == edited_value)
+ return;
+ changed_tick.gcode = edited_value;
+ }
+
+ m_ticks_.erase(it);
+ m_ticks_.insert(changed_tick);
+
+ wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+ }
+}
+
+void DoubleSlider::change_extruder(int extruder)
+{
+ const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
+
+ std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
+
+ // if on this Y doesn't exist tick
+ if (m_ticks_.find(tick) == m_ticks_.end())
+ {
+ m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, extruder, extruder == 0 ? "" : colors[extruder-1]));
+
+ wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+ Refresh();
+ Update();
+ }
+}
+
+void DoubleSlider::edit_extruder_sequence()
+{
+ Slic3r::GUI::ExtruderSequenceDialog dlg(m_extruders_sequence);
+ if (dlg.ShowModal() != wxID_OK)
+ return;
+
+ const ExtrudersSequence& from_dlg_val = dlg.GetValue();
+ if (m_extruders_sequence == from_dlg_val)
+ return;
+
+ m_extruders_sequence = from_dlg_val;
+
+ auto it = m_ticks_.begin();
+ while (it != m_ticks_.end()) {
+ if (it->gcode == Slic3r::ExtruderChangeCode)
+ it = m_ticks_.erase(it);
+ else
+ ++it;
+ }
+
+ int tick = 0;
+ double value = 0.0;
+ int extruder = 0;
+ const int extr_cnt = m_extruders_sequence.extruders.size();
+
+ std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
+
+ while (tick <= m_max_value)
+ {
+ int cur_extruder = m_extruders_sequence.extruders[extruder];
+ m_ticks_.insert(TICK_CODE(tick, Slic3r::ExtruderChangeCode, cur_extruder + 1, colors[cur_extruder]));
+
+ extruder++;
+ if (extruder == extr_cnt)
+ extruder = 0;
+ if (m_extruders_sequence.is_mm_intervals)
+ {
+ value += m_extruders_sequence.interval_by_mm;
+ auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
+
+ if (it == m_values.end())
+ break;
+
+ tick = it - m_values.begin();
+ }
+ else
+ tick += m_extruders_sequence.interval_by_layers;
+ }
+
+ wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+}
+
// ----------------------------------------------------------------------------
// LockButton
diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp
index d4d5e7998..7841b62fe 100644
--- a/src/slic3r/GUI/wxExtensions.hpp
+++ b/src/slic3r/GUI/wxExtensions.hpp
@@ -16,6 +16,7 @@
#include <vector>
#include <set>
#include <functional>
+#include "libslic3r/Model.hpp"
namespace Slic3r {
enum class ModelVolumeType : int;
@@ -48,6 +49,8 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string,
std::function<void(wxCommandEvent& event)> cb, wxEvtHandler* event_handler);
class wxDialog;
+class wxBitmapComboBox;
+
void edit_tooltip(wxString& tooltip);
void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids);
int em_unit(wxWindow* win);
@@ -55,7 +58,13 @@ int em_unit(wxWindow* win);
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name,
const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false);
-std::vector<wxBitmap*> get_extruder_color_icons();
+std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false);
+void apply_extruder_selector(wxBitmapComboBox** ctrl,
+ wxWindow* parent,
+ const std::string& first_item = "",
+ wxPoint pos = wxDefaultPosition,
+ wxSize size = wxDefaultSize,
+ bool use_thin_icon = false);
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
@@ -749,6 +758,11 @@ enum TicksAction{
class DoubleSlider : public wxControl
{
+ enum IconFocus {
+ ifNone,
+ ifRevert,
+ ifCog
+ };
public:
DoubleSlider(
wxWindow *parent,
@@ -794,8 +808,8 @@ public:
m_values = values;
}
void ChangeOneLayerLock();
- std::vector<double> GetTicksValues() const;
- void SetTicksValues(const std::vector<double>& heights);
+ std::vector<Slic3r::Model::CustomGCode> GetTicksValues() const;
+ void SetTicksValues(const std::vector<Slic3r::Model::CustomGCode> &heights);
void EnableTickManipulation(bool enable = true) {
m_is_enabled_tick_manipulation = enable;
}
@@ -803,6 +817,18 @@ public:
EnableTickManipulation(false);
}
+ enum ManipulationState {
+ msSingleExtruder, // single extruder printer preset is selected
+ msMultiExtruder // multiple extruder printer preset is selected, and "Whole print" is selected
+ };
+ void SetManipulationState(ManipulationState state) {
+ m_state = state;
+ }
+ void SetManipulationState(int extruders_cnt) {
+ m_state = extruders_cnt ==1 ? msSingleExtruder : msMultiExtruder;
+ }
+ ManipulationState GetManipulationState() const { return m_state; }
+
bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
bool is_one_layer() const { return m_is_one_layer; }
bool is_lower_at_min() const { return m_lower_value == m_min_value; }
@@ -820,7 +846,12 @@ public:
void OnKeyUp(wxKeyEvent &event);
void OnChar(wxKeyEvent &event);
void OnRightDown(wxMouseEvent& event);
+ int get_extruder_for_tick(int tick);
void OnRightUp(wxMouseEvent& event);
+ void add_code(std::string code, int selected_extruder = -1);
+ void edit_tick();
+ void change_extruder(int extruder);
+ void edit_extruder_sequence();
protected:
@@ -834,6 +865,7 @@ protected:
void draw_colored_band(wxDC& dc);
void draw_one_layer_icon(wxDC& dc);
void draw_revert_icon(wxDC& dc);
+ void draw_cog_icon(wxDC &dc);
void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
@@ -842,6 +874,7 @@ protected:
void detect_selected_slider(const wxPoint& pt);
void correct_lower_value();
void correct_higher_value();
+ wxString get_tooltip(IconFocus icon_focus);
void move_current_thumb(const bool condition);
void action_tick(const TicksAction action);
void enter_window(wxMouseEvent& event, const bool enter);
@@ -876,6 +909,7 @@ private:
ScalableBitmap m_bmp_one_layer_unlock_on;
ScalableBitmap m_bmp_one_layer_unlock_off;
ScalableBitmap m_bmp_revert;
+ ScalableBitmap m_bmp_cog;
SelectedSlider m_selection;
bool m_is_left_down = false;
bool m_is_right_down = false;
@@ -884,16 +918,25 @@ private:
bool m_is_action_icon_focesed = false;
bool m_is_one_layer_icon_focesed = false;
bool m_is_enabled_tick_manipulation = true;
+ bool m_show_context_menu = false;
+ bool m_show_edit_menu = false;
+ bool m_edit_extruder_sequence = false;
+ bool m_suppress_add_code = false;
+ ManipulationState m_state = msSingleExtruder;
+ std::string m_custom_gcode = "";
+ std::string m_pause_print_msg;
wxRect m_rect_lower_thumb;
wxRect m_rect_higher_thumb;
wxRect m_rect_tick_action;
wxRect m_rect_one_layer_icon;
wxRect m_rect_revert_icon;
+ wxRect m_rect_cog_icon;
wxSize m_thumb_size;
int m_tick_icon_dim;
int m_lock_icon_dim;
int m_revert_icon_dim;
+ int m_cog_icon_dim;
long m_style;
float m_label_koef = 1.0;
@@ -912,6 +955,88 @@ private:
std::vector<wxPen*> m_segm_pens;
std::set<int> m_ticks;
std::vector<double> m_values;
+
+ struct TICK_CODE
+ {
+ TICK_CODE(int tick):tick(tick), gcode(Slic3r::ColorChangeCode), extruder(0), color("") {}
+ TICK_CODE(int tick, const std::string& code) :
+ tick(tick), gcode(code), extruder(0) {}
+ TICK_CODE(int tick, int extruder) :
+ tick(tick), gcode(Slic3r::ColorChangeCode), extruder(extruder) {}
+ TICK_CODE(int tick, const std::string& code, int extruder, const std::string& color) :
+ tick(tick), gcode(code), extruder(extruder), color(color) {}
+
+ bool operator<(const TICK_CODE& other) const { return other.tick > this->tick; }
+ bool operator>(const TICK_CODE& other) const { return other.tick < this->tick; }
+ TICK_CODE operator=(const TICK_CODE& other) const {
+ TICK_CODE ret_val(other.tick, other.gcode, other.extruder, other.color);
+ return ret_val;
+ }
+
+ int tick;
+ std::string gcode;
+ int extruder;
+ std::string color;
+ };
+
+ std::set<TICK_CODE> m_ticks_;
+
+public:
+ struct ExtrudersSequence
+ {
+ bool is_mm_intervals;
+ double interval_by_mm;
+ int interval_by_layers;
+ std::vector<size_t> extruders;
+
+ ExtrudersSequence() :
+ is_mm_intervals(true),
+ interval_by_mm(3.0),
+ interval_by_layers(10),
+ extruders({ 0 }) {}
+
+ ExtrudersSequence(const ExtrudersSequence& other) :
+ is_mm_intervals(other.is_mm_intervals),
+ interval_by_mm(other.interval_by_mm),
+ interval_by_layers(other.interval_by_layers),
+ extruders(other.extruders) {}
+
+ ExtrudersSequence& operator=(const ExtrudersSequence& other) {
+ this->is_mm_intervals = other.is_mm_intervals;
+ this->interval_by_mm = other.interval_by_mm;
+ this->interval_by_layers= other.interval_by_layers;
+ this->extruders = other.extruders;
+
+ return *this;
+ }
+ bool operator==(const ExtrudersSequence& other) const
+ {
+ return (other.is_mm_intervals == this->is_mm_intervals ) &&
+ (other.interval_by_mm == this->interval_by_mm ) &&
+ (other.interval_by_layers == this->interval_by_layers ) &&
+ (other.extruders == this->extruders ) ;
+ }
+ bool operator!=(const ExtrudersSequence& other) const
+ {
+ return (other.is_mm_intervals != this->is_mm_intervals ) &&
+ (other.interval_by_mm != this->interval_by_mm ) &&
+ (other.interval_by_layers != this->interval_by_layers ) &&
+ (other.extruders != this->extruders ) ;
+ }
+
+ void add_extruder(size_t pos)
+ {
+ extruders.insert(extruders.begin() + pos+1, size_t(0));
+ }
+
+ void delete_extruder(size_t pos)
+ {
+ if (extruders.size() == 1)
+ return;// last item can't be deleted
+ extruders.erase(extruders.begin() + pos);
+ }
+ }
+ m_extruders_sequence;
};
diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp
index d955d6a7e..b7eb12e02 100644
--- a/src/slic3r/Utils/FixModelByWin10.cpp
+++ b/src/slic3r/Utils/FixModelByWin10.cpp
@@ -423,7 +423,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT);
dlg.ShowModal();
} else {
- wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
+ wxMessageDialog dlg(nullptr, _(L("Model repair failed:")) + " \n" + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
dlg.ShowModal();
}
worker_thread.join();
diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp
index 3cebf2f89..55e3a5a73 100644
--- a/src/slic3r/Utils/PresetUpdater.cpp
+++ b/src/slic3r/Utils/PresetUpdater.cpp
@@ -73,6 +73,7 @@ struct Update
std::string vendor;
std::string changelog_url;
+ Update() {}
Update(fs::path &&source, fs::path &&target, const Version &version, std::string vendor, std::string changelog_url)
: source(std::move(source))
, target(std::move(target))
@@ -156,7 +157,7 @@ struct PresetUpdater::priv
void sync_config(const VendorMap vendors);
void check_install_indices() const;
- Updates get_config_updates() const;
+ Updates get_config_updates(const Semver& old_slic3r_version) const;
void perform_updates(Updates &&updates, bool snapshot = true) const;
};
@@ -167,7 +168,9 @@ PresetUpdater::priv::priv()
, cancel(false)
{
set_download_prefs(GUI::wxGetApp().app_config);
+ // Install indicies from resources. Only installs those that are either missing or older than in resources.
check_install_indices();
+ // Load indices from the cache directory.
index_db = Index::load_db();
}
@@ -273,6 +276,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
if (!enabled_config_update) { return; }
// Donwload vendor preset bundles
+ // Over all indices from the cache directory:
for (auto &index : index_db) {
if (cancel) { return; }
@@ -366,30 +370,33 @@ void PresetUpdater::priv::check_install_indices() const
}
}
-// Generates a list of bundle updates that are to be performed
-Updates PresetUpdater::priv::get_config_updates() const
+// Generates a list of bundle updates that are to be performed.
+// Version of slic3r that was running the last time and which was read out from PrusaSlicer.ini is provided
+// as a parameter.
+Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version) const
{
Updates updates;
BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates...";
+ // Over all indices from the cache directory:
for (const auto idx : index_db) {
auto bundle_path = vendor_path / (idx.vendor() + ".ini");
auto bundle_path_idx = vendor_path / idx.path().filename();
if (! fs::exists(bundle_path)) {
- BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
+ BOOST_LOG_TRIVIAL(info) << "Confing bundle not installed for vendor %1%, skipping: " << idx.vendor();
continue;
}
- // Perform a basic load and check the version
+ // Perform a basic load and check the version of the installed preset bundle.
auto vp = VendorProfile::from_ini(bundle_path, false);
// Getting a recommended version from the latest index, wich may have been downloaded
// from the internet, or installed / updated from the installation resources.
- const auto recommended = idx.recommended();
+ auto recommended = idx.recommended();
if (recommended == idx.end()) {
- BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
+ BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index? Giving up.") % idx.vendor();
// XXX: what should be done here?
continue;
}
@@ -404,6 +411,7 @@ Updates PresetUpdater::priv::get_config_updates() const
% recommended->config_version.to_string();
if (! ver_current_found) {
+ // Any published config shall be always found in the latest config index.
auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
BOOST_LOG_TRIVIAL(error) << message;
GUI::show_error(nullptr, GUI::from_u8(message));
@@ -411,11 +419,90 @@ Updates PresetUpdater::priv::get_config_updates() const
}
if (ver_current_found && !ver_current->is_current_slic3r_supported()) {
+ // "Reconfigure" situation.
BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name);
- } else if (recommended->config_version > vp.config_version) {
- // Config bundle update situation
+ continue;
+ }
+
+ if (recommended->config_version < vp.config_version) {
+ BOOST_LOG_TRIVIAL(warning) << (boost::format("Recommended config version for the currently running PrusaSlicer is older than the currently installed config for vendor %1%. This should not happen.") % idx.vendor()).str();
+ continue;
+ }
+
+ if (recommended->config_version == vp.config_version) {
+ // The recommended config bundle is already installed.
+ continue;
+ }
+
+ // Config bundle update situation. The recommended config bundle version for this PrusaSlicer version from the index from the cache is newer
+ // than the version of the currently installed config bundle.
+
+ // The config index inside the cache directory (given by idx.path()) is one of the following:
+ // 1) The last config index downloaded by any previously running PrusaSlicer instance
+ // 2) The last config index installed by any previously running PrusaSlicer instance (older or newer) from its resources.
+ // 3) The last config index installed by the currently running PrusaSlicer instance from its resources.
+ // The config index is always the newest one (given by its newest config bundle referenced), and older config indices shall fully contain
+ // the content of the older config indices.
+
+ // Config bundle inside the cache directory.
+ fs::path path_in_cache = cache_path / (idx.vendor() + ".ini");
+ // Config bundle inside the resources directory.
+ fs::path path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
+ // Config index inside the resources directory.
+ fs::path path_idx_in_rsrc = rsrc_path / (idx.vendor() + ".idx");
+
+ // Search for a valid config bundle in the cache directory.
+ bool found = false;
+ Update new_update;
+ fs::path bundle_path_idx_to_install;
+ if (fs::exists(path_in_cache)) {
+ try {
+ VendorProfile new_vp = VendorProfile::from_ini(path_in_cache, false);
+ if (new_vp.config_version == recommended->config_version) {
+ // The config bundle from the cache directory matches the recommended version of the index from the cache directory.
+ // This is the newest known recommended config. Use it.
+ new_update = Update(std::move(path_in_cache), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
+ // and install the config index from the cache into vendor's directory.
+ bundle_path_idx_to_install = idx.path();
+ found = true;
+ }
+ } catch (const std::exception &ex) {
+ BOOST_LOG_TRIVIAL(info) << boost::format("Failed to load the config bundle `%1%`: %2%") % path_in_cache.string() % ex.what();
+ }
+ }
+
+ // Keep the rsrc_idx outside of the next block, as we will reference the "recommended" version by an iterator.
+ Index rsrc_idx;
+ if (! found && fs::exists(path_in_rsrc) && fs::exists(path_idx_in_rsrc)) {
+ // Trying the config bundle from resources (from the installation).
+ // In that case, the recommended version number has to be compared against the recommended version reported by the config index from resources as well,
+ // as the config index in the cache directory may already be newer, recommending a newer config bundle than available in cache or resources.
+ VendorProfile rsrc_vp;
+ try {
+ rsrc_vp = VendorProfile::from_ini(path_in_rsrc, false);
+ } catch (const std::exception &ex) {
+ BOOST_LOG_TRIVIAL(info) << boost::format("Cannot load the config bundle at `%1%`: %2%") % path_in_rsrc.string() % ex.what();
+ }
+ if (rsrc_vp.valid()) {
+ try {
+ rsrc_idx.load(path_idx_in_rsrc);
+ } catch (const std::exception &ex) {
+ BOOST_LOG_TRIVIAL(info) << boost::format("Cannot load the config index at `%1%`: %2%") % path_idx_in_rsrc.string() % ex.what();
+ }
+ recommended = rsrc_idx.recommended();
+ if (recommended != rsrc_idx.end() && recommended->config_version == rsrc_vp.config_version && recommended->config_version > vp.config_version) {
+ new_update = Update(std::move(path_in_rsrc), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
+ bundle_path_idx_to_install = path_idx_in_rsrc;
+ found = true;
+ } else {
+ BOOST_LOG_TRIVIAL(warning) << (boost::format("The recommended config version for vendor `%1%` in resources does not match the recommended\n"
+ " config version for this version of PrusaSlicer. Corrupted installation?") % idx.vendor()).str();
+ }
+ }
+ }
+ if (found) {
// Load 'installed' idx, if any.
// 'Installed' indices are kept alongside the bundle in the `vendor` subdir
// for bookkeeping to remember a cancelled update and not offer it again.
@@ -423,15 +510,16 @@ Updates PresetUpdater::priv::get_config_updates() const
Index existing_idx;
try {
existing_idx.load(bundle_path_idx);
-
- const auto existing_recommended = existing_idx.recommended();
+ // Find a recommended config bundle version for the slic3r version last executed. This makes sure that a config bundle update will not be missed
+ // when upgrading an application. On the other side, the user will be bugged every time he will switch between slic3r versions.
+ const auto existing_recommended = existing_idx.recommended(old_slic3r_version);
if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) {
// The user has already seen (and presumably rejected) this update
BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor();
continue;
}
} catch (const std::exception &err) {
- BOOST_LOG_TRIVIAL(error) << boost::format("Could not load installed index at `%1%`: %2%") % bundle_path_idx % err.what();
+ BOOST_LOG_TRIVIAL(error) << boost::format("Cannot load the installed index at `%1%`: %2%") % bundle_path_idx % err.what();
}
}
@@ -445,41 +533,14 @@ Updates PresetUpdater::priv::get_config_updates() const
continue;
}
- auto path_src = cache_path / (idx.vendor() + ".ini");
- auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
- if (! fs::exists(path_src)) {
- if (! fs::exists(path_in_rsrc)) {
- BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources")
- % idx.vendor();
- continue;
- } else {
- path_src = std::move(path_in_rsrc);
- path_in_rsrc.clear();
- }
- }
-
- auto new_vp = VendorProfile::from_ini(path_src, false);
- bool found = false;
- if (new_vp.config_version == recommended->config_version) {
- updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
- found = true;
- } else if (! path_in_rsrc.empty() && fs::exists(path_in_rsrc)) {
- new_vp = VendorProfile::from_ini(path_in_rsrc, false);
- if (new_vp.config_version == recommended->config_version) {
- updates.updates.emplace_back(std::move(path_in_rsrc), std::move(bundle_path), *recommended, vp.name, vp.changelog_url);
- found = true;
- }
- }
-
- if (found) {
- // 'Install' the index in the vendor directory. This is used to memoize
- // offered updates and to not offer the same update again if it was cancelled by the user.
- copy_file_fix(idx.path(), bundle_path_idx);
- } else {
- BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
- % idx.vendor()
- % recommended->config_version.to_string();
- }
+ updates.updates.emplace_back(std::move(new_update));
+ // 'Install' the index in the vendor directory. This is used to memoize
+ // offered updates and to not offer the same update again if it was cancelled by the user.
+ copy_file_fix(bundle_path_idx_to_install, bundle_path_idx);
+ } else {
+ BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
+ % idx.vendor()
+ % recommended->config_version.to_string();
}
}
@@ -607,11 +668,11 @@ void PresetUpdater::slic3r_update_notify()
}
}
-PresetUpdater::UpdateResult PresetUpdater::config_update() const
+PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const
{
if (! p->enabled_config_update) { return R_NOOP; }
- auto updates = p->get_config_updates();
+ auto updates = p->get_config_updates(old_slic3r_version);
if (updates.incompats.size() > 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size();
diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp
index 7c2aab7cc..e18695828 100644
--- a/src/slic3r/Utils/PresetUpdater.hpp
+++ b/src/slic3r/Utils/PresetUpdater.hpp
@@ -11,6 +11,7 @@ namespace Slic3r {
class AppConfig;
class PresetBundle;
+class Semver;
class PresetUpdater
{
@@ -38,7 +39,9 @@ public:
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
// A false return value implies Slic3r should exit due to incompatibility of configuration.
- UpdateResult config_update() const;
+ // Providing old slic3r version upgrade profiles on upgrade of an application even in case
+ // that the config index installed from the Internet is equal to the index contained in the installation package.
+ UpdateResult config_update(const Semver &old_slic3r_version) const;
// "Update" a list of bundles from resources (behaves like an online update).
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;