Merge branch 'master' into pa_reprap_g10_temperature_support

This commit is contained in:
ardenpm 2020-09-15 11:40:38 +10:00 committed by GitHub
commit 541c31afb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
323 changed files with 28621 additions and 8886 deletions

View file

@ -92,6 +92,7 @@ endif()
# Create a slic3r executable
# Process mainfests for various platforms.
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer-gcodeviewer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer-gcodeviewer.rc @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.manifest @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY)
if (WIN32)
@ -105,9 +106,9 @@ if (MINGW)
set_target_properties(PrusaSlicer PROPERTIES PREFIX "")
endif (MINGW)
if (NOT WIN32)
# Binary name on unix like systems (OSX, Linux)
set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
if (NOT WIN32 AND NOT APPLE)
# Binary name on unix like systems (Linux, Unix)
set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
endif ()
target_link_libraries(PrusaSlicer libslic3r cereal)
@ -161,12 +162,22 @@ if (WIN32)
add_executable(PrusaSlicer_app_console PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc)
# Generate debug symbols even in release mode.
if (MSVC)
target_link_options(PrusaSlicer_app_console PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
target_link_options(PrusaSlicer_app_console PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
endif ()
target_compile_definitions(PrusaSlicer_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE)
add_dependencies(PrusaSlicer_app_console PrusaSlicer)
set_target_properties(PrusaSlicer_app_console PROPERTIES OUTPUT_NAME "prusa-slicer-console")
target_link_libraries(PrusaSlicer_app_console PRIVATE boost_headeronly)
add_executable(PrusaSlicer_app_gcodeviewer WIN32 PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer-gcodeviewer.rc)
# Generate debug symbols even in release mode.
if (MSVC)
target_link_options(PrusaSlicer_app_gcodeviewer PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
endif ()
target_compile_definitions(PrusaSlicer_app_gcodeviewer PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE -DSLIC3R_WRAPPER_GCODEVIEWER)
add_dependencies(PrusaSlicer_app_gcodeviewer PrusaSlicer)
set_target_properties(PrusaSlicer_app_gcodeviewer PROPERTIES OUTPUT_NAME "prusa-gcodeviewer")
target_link_libraries(PrusaSlicer_app_gcodeviewer PRIVATE boost_headeronly)
endif ()
# Link the resources dir to where Slic3r GUI expects it
@ -198,20 +209,34 @@ if (WIN32)
add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer)
prusaslicer_copy_dlls(PrusaSlicerDllsCopy)
elseif (XCODE)
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
add_custom_command(TARGET PrusaSlicer POST_BUILD
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources"
COMMENT "Symlinking the resources directory into the build tree"
VERBATIM
)
else ()
if (APPLE)
# On OSX, the name of the binary matches the name of the Application.
add_custom_command(TARGET PrusaSlicer POST_BUILD
COMMAND ln -sf PrusaSlicer prusa-slicer
COMMAND ln -sf PrusaSlicer prusa-gcodeviewer
COMMAND ln -sf PrusaSlicer PrusaGCodeViewer
WORKING_DIRECTORY "$<TARGET_FILE_DIR:PrusaSlicer>"
COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer"
VERBATIM)
else ()
add_custom_command(TARGET PrusaSlicer POST_BUILD
COMMAND ln -sf prusa-slicer prusa-gcodeviewer
WORKING_DIRECTORY "$<TARGET_FILE_DIR:PrusaSlicer>"
COMMENT "Symlinking the G-code viewer to PrusaSlicer"
VERBATIM)
endif ()
if (XCODE)
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
else ()
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources")
endif ()
add_custom_command(TARGET PrusaSlicer POST_BUILD
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources"
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}"
COMMENT "Symlinking the resources directory into the build tree"
VERBATIM
)
endif()
VERBATIM)
endif ()
# Slic3r binary install target
if (WIN32)

View file

@ -22,6 +22,7 @@
#include <cstring>
#include <iostream>
#include <math.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/cenv.hpp>
@ -44,6 +45,7 @@
#include "libslic3r/Format/OBJ.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include "PrusaSlicer.hpp"
@ -52,7 +54,6 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/InstanceCheck.hpp"
#include "slic3r/GUI/AppConfig.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Plater.hpp"
#endif /* SLIC3R_GUI */
@ -101,7 +102,14 @@ int CLI::run(int argc, char **argv)
std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() &&
std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end();
bool start_as_gcodeviewer =
#ifdef _WIN32
false;
#else
// On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning.
boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
#endif // _WIN32
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
// load config files supplied via --load
@ -132,37 +140,57 @@ int CLI::run(int argc, char **argv)
m_print_config.apply(config);
}
// Read input file(s) if any.
for (const std::string &file : m_input_files) {
if (! boost::filesystem::exists(file)) {
boost::nowide::cerr << "No such file: " << file << std::endl;
exit(1);
#if ENABLE_GCODE_VIEWER
// are we starting as gcodeviewer ?
for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
if (*it == "gcodeviewer") {
start_gui = true;
start_as_gcodeviewer = true;
m_actions.erase(it);
break;
}
Model model;
try {
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
DynamicPrintConfig config;
model = Model::read_from_file(file, &config, true);
PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
} else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
}
#endif // ENABLE_GCODE_VIEWER
// Read input file(s) if any.
#if ENABLE_GCODE_VIEWER
if (!start_as_gcodeviewer) {
#endif // ENABLE_GCODE_VIEWER
for (const std::string& file : m_input_files) {
if (!boost::filesystem::exists(file)) {
boost::nowide::cerr << "No such file: " << file << std::endl;
exit(1);
}
Model model;
try {
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
DynamicPrintConfig config;
model = Model::read_from_file(file, &config, true);
PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
}
else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) {
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
return 1;
}
// config is applied to m_print_config before the current m_config values.
config += std::move(m_print_config);
m_print_config = std::move(config);
}
catch (std::exception& e) {
boost::nowide::cerr << file << ": " << e.what() << std::endl;
return 1;
}
// config is applied to m_print_config before the current m_config values.
config += std::move(m_print_config);
m_print_config = std::move(config);
} catch (std::exception &e) {
boost::nowide::cerr << file << ": " << e.what() << std::endl;
return 1;
if (model.objects.empty()) {
boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
continue;
}
m_models.push_back(model);
}
if (model.objects.empty()) {
boost::nowide::cerr << "Error: file is empty: " << file << std::endl;
continue;
}
m_models.push_back(model);
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
// Apply command line options to a more specific DynamicPrintConfig which provides normalize()
// (command line options override --load files)
@ -469,7 +497,11 @@ int CLI::run(int argc, char **argv)
print->process();
if (printer_technology == ptFFF) {
// The outfile is processed by a PlaceholderParser.
#if ENABLE_GCODE_VIEWER
outfile = fff_print.export_gcode(outfile, nullptr, nullptr);
#else
outfile = fff_print.export_gcode(outfile, nullptr);
#endif // ENABLE_GCODE_VIEWER
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
} else {
outfile = sla_print.output_filepath(outfile);
@ -517,6 +549,11 @@ int CLI::run(int argc, char **argv)
<< " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl;
*/
}
#if !ENABLE_GCODE_VIEWER
} else if (opt_key == "gcodeviewer") {
start_gui = true;
start_as_gcodeviewer = true;
#endif // !ENABLE_GCODE_VIEWER
} else {
boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl;
return 1;
@ -526,7 +563,11 @@ int CLI::run(int argc, char **argv)
if (start_gui) {
#ifdef SLIC3R_GUI
// #ifdef USE_WX
#if ENABLE_GCODE_VIEWER
GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor);
#else
GUI::GUI_App *gui = new GUI::GUI_App();
#endif // ENABLE_GCODE_VIEWER
bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1";
if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) {
@ -536,28 +577,42 @@ int CLI::run(int argc, char **argv)
// gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui);
#if ENABLE_GCODE_VIEWER
gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] {
#else
gui->CallAfter([gui, this, &load_configs] {
#endif // ENABLE_GCODE_VIEWER
if (!gui->initialized()) {
return;
}
#if ENABLE_GCODE_VIEWER
if (start_as_gcodeviewer) {
if (!m_input_files.empty())
gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str()));
} else {
#endif // ENABLE_GCODE_VIEWER_AS
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
gui->mainframe->load_config(m_print_config);
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
gui->mainframe->load_config(m_print_config);
#endif
if (! load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
gui->mainframe->load_config_file(load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (! m_input_files.empty())
gui->plater()->load_files(m_input_files, true, true);
if (! m_extra_config.empty())
gui->mainframe->load_config(m_extra_config);
if (!load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
gui->mainframe->load_config_file(load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (!m_input_files.empty())
gui->plater()->load_files(m_input_files, true, true);
if (!m_extra_config.empty())
gui->mainframe->load_config(m_extra_config);
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
});
int result = wxEntry(argc, argv);
return result;

View file

@ -221,6 +221,11 @@ int wmain(int argc, wchar_t **argv)
std::vector<wchar_t*> argv_extended;
argv_extended.emplace_back(argv[0]);
#ifdef SLIC3R_WRAPPER_GCODEVIEWER
wchar_t gcodeviewer_param[] = L"--gcodeviewer";
argv_extended.emplace_back(gcodeviewer_param);
#endif /* SLIC3R_WRAPPER_GCODEVIEWER */
#ifdef SLIC3R_GUI
// Here one may push some additional parameters based on the wrapper type.
bool force_mesa = false;

View file

@ -48,9 +48,19 @@
#include <ostream>
#include <functional>
#include <assert.h>
#include <Shiny/Shiny.h>
#include <libslic3r/Int128.hpp>
// Profiling support using the Shiny intrusive profiler
//#define CLIPPERLIB_PROFILE
#if defined(SLIC3R_PROFILE) && defined(CLIPPERLIB_PROFILE)
#include <Shiny/Shiny.h>
#define CLIPPERLIB_PROFILE_FUNC() PROFILE_FUNC()
#define CLIPPERLIB_PROFILE_BLOCK(name) PROFILE_BLOCK(name)
#else
#define CLIPPERLIB_PROFILE_FUNC()
#define CLIPPERLIB_PROFILE_BLOCK(name)
#endif
#ifdef use_xyz
namespace ClipperLib_Z {
#else /* use_xyz */
@ -263,7 +273,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op)
// This is potentially very expensive! O(n^2)!
bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
OutPt* op = OutPt1;
do
{
@ -714,7 +724,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward)
bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
// Remove duplicate end point from a closed input path.
// Remove duplicate points from the end of the input path.
int highI = (int)pg.size() -1;
@ -738,7 +748,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed)
bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
std::vector<int> num_edges(ppg.size(), 0);
int num_edges_total = 0;
for (size_t i = 0; i < ppg.size(); ++ i) {
@ -780,7 +790,7 @@ bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed)
bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
#ifdef use_lines
if (!Closed && PolyTyp == ptClip)
throw clipperException("AddPath: Open paths must be subject.");
@ -954,7 +964,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b
void ClipperBase::Clear()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
m_MinimaList.clear();
m_edges.clear();
m_UseFullRange = false;
@ -966,7 +976,7 @@ void ClipperBase::Clear()
// Sort the LML entries, initialize the left / right bound edges of each Local Minima.
void ClipperBase::Reset()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
if (m_MinimaList.empty()) return; //ie nothing to process
std::sort(m_MinimaList.begin(), m_MinimaList.end(), [](const LocalMinimum& lm1, const LocalMinimum& lm2){ return lm1.Y < lm2.Y; });
@ -995,7 +1005,7 @@ void ClipperBase::Reset()
// Returns (0,0,0,0) for an empty rectangle.
IntRect ClipperBase::GetBounds()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
IntRect result;
auto lm = m_MinimaList.begin();
if (lm == m_MinimaList.end())
@ -1056,7 +1066,7 @@ Clipper::Clipper(int initOptions) :
void Clipper::Reset()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
ClipperBase::Reset();
m_Scanbeam = std::priority_queue<cInt>();
m_Maxima.clear();
@ -1071,7 +1081,7 @@ void Clipper::Reset()
bool Clipper::Execute(ClipType clipType, Paths &solution,
PolyFillType subjFillType, PolyFillType clipFillType)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
if (m_HasOpenPaths)
throw clipperException("Error: PolyTree struct is needed for open path clipping.");
solution.resize(0);
@ -1089,7 +1099,7 @@ bool Clipper::Execute(ClipType clipType, Paths &solution,
bool Clipper::Execute(ClipType clipType, PolyTree& polytree,
PolyFillType subjFillType, PolyFillType clipFillType)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
m_SubjFillType = subjFillType;
m_ClipFillType = clipFillType;
m_ClipType = clipType;
@ -1103,10 +1113,10 @@ bool Clipper::Execute(ClipType clipType, PolyTree& polytree,
bool Clipper::ExecuteInternal()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
bool succeeded = true;
try {
PROFILE_BLOCK(Clipper_ExecuteInternal_Process);
CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Process);
Reset();
if (m_MinimaList.empty()) return true;
cInt botY = m_Scanbeam.top();
@ -1131,13 +1141,13 @@ bool Clipper::ExecuteInternal()
if (succeeded)
{
PROFILE_BLOCK(Clipper_ExecuteInternal_Fix);
CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Fix);
//fix orientations ...
//FIXME Vojtech: Does it not invalidate the loop hierarchy maintained as OutRec::FirstLeft pointers?
//FIXME Vojtech: The area is calculated with floats, it may not be numerically stable!
{
PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_orientations);
CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_orientations);
for (OutRec *outRec : m_PolyOuts)
if (outRec->Pts && !outRec->IsOpen && (outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
ReversePolyPtLinks(outRec->Pts);
@ -1147,7 +1157,7 @@ bool Clipper::ExecuteInternal()
//unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
{
PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_fixup);
CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_fixup);
for (OutRec *outRec : m_PolyOuts)
if (outRec->Pts) {
if (outRec->IsOpen)
@ -1401,7 +1411,7 @@ bool Clipper::IsContributing(const TEdge& edge) const
// Called from Clipper::InsertLocalMinimaIntoAEL() and Clipper::IntersectEdges().
OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
OutPt* result;
TEdge *e, *prevE;
if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx ))
@ -1493,7 +1503,7 @@ void Clipper::CopyAELToSEL()
// Called from Clipper::ExecuteInternal()
void Clipper::InsertLocalMinimaIntoAEL(const cInt botY)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
while (!m_MinimaList.empty() && m_MinimaList.back().Y == botY)
{
TEdge* lb = m_MinimaList.back().LeftBound;
@ -2043,7 +2053,7 @@ OutPt* Clipper::GetLastOutPt(TEdge *e)
void Clipper::ProcessHorizontals()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
TEdge* horzEdge = m_SortedEdges;
while(horzEdge)
{
@ -2414,7 +2424,7 @@ void Clipper::UpdateEdgeIntoAEL(TEdge *&e)
bool Clipper::ProcessIntersections(const cInt topY)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
if( !m_ActiveEdges ) return true;
try {
BuildIntersectList(topY);
@ -2569,7 +2579,7 @@ void Clipper::DoMaxima(TEdge *e)
void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY)
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
TEdge* e = m_ActiveEdges;
while( e )
{
@ -3177,7 +3187,7 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2)
// This is potentially very expensive! O(n^3)!
void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
//tests if NewOutRec contains the polygon before reassigning FirstLeft
for (OutRec *outRec : m_PolyOuts)
{
@ -3201,7 +3211,7 @@ void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const
void Clipper::JoinCommonEdges()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
for (Join &join : m_Joins)
{
OutRec *outRec1 = GetOutRec(join.OutPt1->Idx);
@ -3771,7 +3781,7 @@ void ClipperOffset::DoRound(int j, int k)
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/StrictlySimple.htm
void Clipper::DoSimplePolygons()
{
PROFILE_FUNC();
CLIPPERLIB_PROFILE_FUNC();
size_t i = 0;
while (i < m_PolyOuts.size())
{

View file

@ -108,12 +108,19 @@ namespace ImGui
const char ColorMarkerEnd = 0x3; // ETX
// Special ASCII characters are used here as an ikons markers
const char PrintIconMarker = 0x4;
const char PrinterIconMarker = 0x5;
const char PrinterSlaIconMarker = 0x6;
const char FilamentIconMarker = 0x7;
const char MaterialIconMarker = 0x8;
const char PrintIconMarker = 0x4;
const char PrinterIconMarker = 0x5;
const char PrinterSlaIconMarker = 0x6;
const char FilamentIconMarker = 0x7;
const char MaterialIconMarker = 0x8;
const char CloseIconMarker = 0xB;
const char CloseIconHoverMarker = 0xC;
// const char TimerDotMarker = 0xE;
// const char TimerDotEmptyMarker = 0xF;
const char MinimalizeMarker = 0xE;
const char MinimalizeHoverMarker = 0xF;
const char WarningMarker = 0x10;
const char ErrorMarker = 0x11;
// void MyFunction(const char* name, const MyMatrix44& v);
}

View file

@ -1,661 +1,165 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
}
// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
// even if the triangle mesh and the AABB Tree are built with floats.
// Returns true if exists some triangle in defined radius, false otherwise.
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
inline bool is_any_triangle_in_radius(
// Indexed triangle set - 3D vertices.
const std::vector<VertexType> &vertices,
// Indexed triangle set - triangular faces, references to vertices.
const std::vector<IndexedFaceType> &faces,
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
const TreeType &tree,
// Point to which the closest point on the indexed triangle set is searched for.
const VectorType &point,
// Maximum distance in which triangle is search for
typename VectorType::Scalar &max_distance)
{
using Scalar = typename VectorType::Scalar;
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
{ vertices, faces, tree, point };
size_t hit_idx;
VectorType hit_point = VectorType::Ones() * (std::nan(""));
if(tree.empty())
{
return false;
}
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point);
return hit_point.allFinite();
}
} // namespace AABBTreeIndirect
} // namespace Slic3r

View file

@ -1,6 +1,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "AppConfig.hpp"
#include "Exception.hpp"
#include <utility>
#include <vector>
@ -15,8 +16,8 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/format/format_fwd.hpp>
#include <wx/string.h>
#include "I18N.hpp"
//#include <wx/string.h>
//#include "I18N.hpp"
namespace Slic3r {
@ -101,6 +102,9 @@ void AppConfig::set_defaults()
if (get("use_inches").empty())
set("use_inches", "0");
if (get("show_splash_screen").empty())
set("show_splash_screen", "1");
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
@ -110,7 +114,7 @@ void AppConfig::set_defaults()
erase("", "object_settings_size");
}
void AppConfig::load()
std::string AppConfig::load()
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
@ -120,10 +124,15 @@ void AppConfig::load()
pt::read_ini(ifs, tree);
} catch (pt::ptree_error& ex) {
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
throw std::runtime_error(
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
/*
throw Slic3r::RuntimeError(
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
"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());
*/
return ex.what();
}
// 2) Parse the property_tree, extract the sections and key / value pairs.
@ -169,10 +178,16 @@ void AppConfig::load()
// Override missing or keys with their defaults.
this->set_defaults();
m_dirty = false;
return "";
}
void AppConfig::save()
{
#if ENABLE_GCODE_VIEWER
if (!m_save_enabled)
return;
#endif // ENABLE_GCODE_VIEWER
// The config is first written to a file with a PID suffix and then moved
// to avoid race conditions with multiple instances of Slic3r
const auto path = config_path();

View file

@ -18,6 +18,9 @@ public:
AppConfig() :
m_dirty(false),
m_orig_version(Semver::invalid()),
#if ENABLE_GCODE_VIEWER
m_save_enabled(true),
#endif // ENABLE_GCODE_VIEWER
m_legacy_datadir(false)
{
this->reset();
@ -29,7 +32,8 @@ public:
void set_defaults();
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
void load();
// return error string or empty strinf
std::string load();
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
void save();
@ -156,6 +160,10 @@ public:
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
#if ENABLE_GCODE_VIEWER
void enable_save(bool enable) { m_save_enabled = enable; }
#endif // ENABLE_GCODE_VIEWER
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
@ -182,6 +190,10 @@ private:
bool m_dirty;
// Original version found in the ini file before it was overwritten
Semver m_orig_version;
#if ENABLE_GCODE_VIEWER
// Whether or not calls to save() should take effect
bool m_save_enabled;
#endif // ENABLE_GCODE_VIEWER
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
};

View file

@ -2,6 +2,7 @@
#define slic3r_BoundingBox_hpp_
#include "libslic3r.h"
#include "Exception.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
@ -22,7 +23,7 @@ public:
{
if (points.empty()) {
this->defined = false;
// throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor");
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
} else {
typename std::vector<PointClass>::const_iterator it = points.begin();
this->min = *it;
@ -68,7 +69,7 @@ public:
BoundingBox3Base(const std::vector<PointClass>& points)
{
if (points.empty())
throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor");
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
typename std::vector<PointClass>::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
@ -192,6 +193,20 @@ inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), sc
inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
template<class Tout, class Tin>
auto cast(const BoundingBoxBase<Tin> &b)
{
return BoundingBoxBase<Vec<3, Tout>>{b.min.template cast<Tout>(),
b.max.template cast<Tout>()};
}
template<class Tout, class Tin>
auto cast(const BoundingBox3Base<Tin> &b)
{
return BoundingBox3Base<Vec<3, Tout>>{b.min.template cast<Tout>(),
b.max.template cast<Tout>()};
}
} // namespace Slic3r
// Serialization through the Cereal library

View file

@ -46,6 +46,8 @@ add_library(libslic3r STATIC
Fill/Fill.hpp
Fill/Fill3DHoneycomb.cpp
Fill/Fill3DHoneycomb.hpp
Fill/FillAdaptive.cpp
Fill/FillAdaptive.hpp
Fill/FillBase.cpp
Fill/FillBase.hpp
Fill/FillConcentric.cpp
@ -99,6 +101,8 @@ add_library(libslic3r STATIC
GCode/ToolOrdering.hpp
GCode/WipeTower.cpp
GCode/WipeTower.hpp
GCode/GCodeProcessor.cpp
GCode/GCodeProcessor.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp
@ -147,6 +151,12 @@ add_library(libslic3r STATIC
PolygonTrimmer.hpp
Polyline.cpp
Polyline.hpp
Preset.cpp
Preset.hpp
PresetBundle.cpp
PresetBundle.hpp
AppConfig.cpp
AppConfig.hpp
Print.cpp
Print.hpp
PrintBase.cpp
@ -155,6 +165,8 @@ add_library(libslic3r STATIC
PrintConfig.hpp
PrintObject.cpp
PrintRegion.cpp
PNGRead.hpp
PNGRead.cpp
Semver.cpp
ShortestPath.cpp
ShortestPath.hpp
@ -187,6 +199,8 @@ add_library(libslic3r STATIC
Utils.hpp
Time.cpp
Time.hpp
TriangleSelector.cpp
TriangleSelector.hpp
MTUtils.hpp
VoronoiOffset.cpp
VoronoiOffset.hpp
@ -201,12 +215,15 @@ add_library(libslic3r STATIC
SimplifyMeshImpl.hpp
SimplifyMesh.cpp
MarchingSquares.hpp
Optimize/Optimizer.hpp
Optimize/NLoptOptimizer.hpp
Optimize/BruteforceOptimizer.hpp
${OpenVDBUtils_SOURCES}
SLA/Common.hpp
SLA/Common.cpp
SLA/Pad.hpp
SLA/Pad.cpp
SLA/SupportTreeBuilder.hpp
SLA/SupportTreeMesher.hpp
SLA/SupportTreeMesher.cpp
SLA/SupportTreeBuildsteps.hpp
SLA/SupportTreeBuildsteps.cpp
SLA/SupportTreeBuilder.cpp
@ -218,6 +235,7 @@ add_library(libslic3r STATIC
SLA/Rotfinder.cpp
SLA/BoostAdapter.hpp
SLA/SpatIndex.hpp
SLA/SpatIndex.cpp
SLA/RasterBase.hpp
SLA/RasterBase.cpp
SLA/AGGRaster.hpp
@ -233,8 +251,10 @@ add_library(libslic3r STATIC
SLA/SupportPointGenerator.cpp
SLA/Contour3D.hpp
SLA/Contour3D.cpp
SLA/EigenMesh3D.hpp
SLA/IndexedMesh.hpp
SLA/IndexedMesh.cpp
SLA/Clustering.hpp
SLA/Clustering.cpp
SLA/ReprojectPointsOnMesh.hpp
)
@ -296,6 +316,8 @@ target_link_libraries(libslic3r
TBB::tbb
libslic3r_cgal
${CMAKE_DL_LIBS}
PNG::PNG
ZLIB::ZLIB
)
if (TARGET OpenVDB::openvdb)

View file

@ -8,7 +8,16 @@
#include "SVG.hpp"
#endif /* CLIPPER_UTILS_DEBUG */
#include <Shiny/Shiny.h>
// Profiling support using the Shiny intrusive profiler
//#define CLIPPER_UTILS_PROFILE
#if defined(SLIC3R_PROFILE) && defined(CLIPPER_UTILS_PROFILE)
#include <Shiny/Shiny.h>
#define CLIPPERUTILS_PROFILE_FUNC() PROFILE_FUNC()
#define CLIPPERUTILS_PROFILE_BLOCK(name) PROFILE_BLOCK(name)
#else
#define CLIPPERUTILS_PROFILE_FUNC()
#define CLIPPERUTILS_PROFILE_BLOCK(name)
#endif
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
@ -50,7 +59,7 @@ err:
void scaleClipperPolygon(ClipperLib::Path &polygon)
{
PROFILE_FUNC();
CLIPPERUTILS_PROFILE_FUNC();
for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
@ -59,7 +68,7 @@ void scaleClipperPolygon(ClipperLib::Path &polygon)
void scaleClipperPolygons(ClipperLib::Paths &polygons)
{
PROFILE_FUNC();
CLIPPERUTILS_PROFILE_FUNC();
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
@ -69,7 +78,7 @@ void scaleClipperPolygons(ClipperLib::Paths &polygons)
void unscaleClipperPolygon(ClipperLib::Path &polygon)
{
PROFILE_FUNC();
CLIPPERUTILS_PROFILE_FUNC();
for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
@ -80,7 +89,7 @@ void unscaleClipperPolygon(ClipperLib::Path &polygon)
void unscaleClipperPolygons(ClipperLib::Paths &polygons)
{
PROFILE_FUNC();
CLIPPERUTILS_PROFILE_FUNC();
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
@ -790,7 +799,7 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear
void safety_offset(ClipperLib::Paths* paths)
{
PROFILE_FUNC();
CLIPPERUTILS_PROFILE_FUNC();
// scale input
scaleClipperPolygons(*paths);
@ -812,11 +821,11 @@ void safety_offset(ClipperLib::Paths* paths)
if (! ccw)
std::reverse(path.begin(), path.end());
{
PROFILE_BLOCK(safety_offset_AddPaths);
CLIPPERUTILS_PROFILE_BLOCK(safety_offset_AddPaths);
co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
}
{
PROFILE_BLOCK(safety_offset_Execute);
CLIPPERUTILS_PROFILE_BLOCK(safety_offset_Execute);
// offset outside by 10um
ClipperLib::Paths out_this;
co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE));

View file

@ -5,7 +5,6 @@
#include <fstream>
#include <iostream>
#include <iomanip>
#include <exception> // std::runtime_error
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/erase.hpp>
@ -218,7 +217,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coInts: return new ConfigOptionIntsNullable();
case coPercents: return new ConfigOptionPercentsNullable();
case coBools: return new ConfigOptionBoolsNullable();
default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label);
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
}
} else {
switch (this->type) {
@ -238,7 +237,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coBool: return new ConfigOptionBool();
case coBools: return new ConfigOptionBools();
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label);
default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
}
}
}
@ -535,7 +534,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
return opt_def->ratio_over.empty() ? 0. :
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
}
// Return an absolute value of a possibly relative config variable.
@ -546,7 +545,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
}
@ -609,7 +608,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
throw std::runtime_error("Not a PrusaSlicer / Slic3r PE generated g-code.");
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
@ -621,7 +620,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
size_t key_value_pairs = load_from_gcode_string(data.data());
if (key_value_pairs < 80)
throw std::runtime_error(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
}
// Load the config keys from the given string.
@ -750,7 +749,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
// throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = optdef->create_default_option();

View file

@ -13,6 +13,7 @@
#include <vector>
#include "libslic3r.h"
#include "clonable_ptr.hpp"
#include "Exception.hpp"
#include "Point.hpp"
#include <boost/algorithm/string/trim.hpp>
@ -34,31 +35,31 @@ extern bool unescape_string_cstyle(const std::string &str, std::string &
extern bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out);
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public std::runtime_error {
class UnknownOptionException : public Slic3r::RuntimeError {
public:
UnknownOptionException() :
std::runtime_error("Unknown option exception") {}
Slic3r::RuntimeError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
std::runtime_error(std::string("Unknown option exception: ") + opt_key) {}
Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
};
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public std::runtime_error
class NoDefinitionException : public Slic3r::RuntimeError
{
public:
NoDefinitionException() :
std::runtime_error("No definition exception") {}
Slic3r::RuntimeError("No definition exception") {}
NoDefinitionException(const std::string &opt_key) :
std::runtime_error(std::string("No definition exception: ") + opt_key) {}
Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public std::runtime_error
class BadOptionTypeException : public Slic3r::RuntimeError
{
public:
BadOptionTypeException() : std::runtime_error("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : std::runtime_error(message) {}
BadOptionTypeException(const char* message) : std::runtime_error(message) {}
BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
};
// Type of a configuration value.
@ -167,7 +168,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
}
@ -175,7 +176,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
}
@ -239,7 +240,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
}
@ -256,12 +257,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
if (other->values.empty())
throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector");
throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
else
throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
@ -280,12 +281,12 @@ public:
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector");
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
const T& get_at(size_t i) const
@ -310,9 +311,9 @@ public:
else if (n > this->values.size()) {
if (this->values.empty()) {
if (opt_default == nullptr)
throw std::runtime_error("ConfigOptionVector::resize(): No default value provided.");
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided.");
if (opt_default->type() != this->type())
throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type.");
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
} else {
// Resize by duplicating the last value.
@ -329,7 +330,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw std::runtime_error("ConfigOptionVector: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
}
@ -341,9 +342,9 @@ public:
// An option overrides another option if it is not nil and not equal.
bool overriden_by(const ConfigOption *rhs) const override {
if (this->nullable())
throw std::runtime_error("Cannot override a nullable ConfigOption.");
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types.");
throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable())
// Overridding a non-nullable object with another non-nullable object.
@ -361,9 +362,9 @@ public:
// Apply an override option, possibly a nullable one.
bool apply_override(const ConfigOption *rhs) override {
if (this->nullable())
throw std::runtime_error("Cannot override a nullable ConfigOption.");
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types.");
throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable()) {
// Overridding a non-nullable object with another non-nullable object.
@ -452,7 +453,7 @@ public:
bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
}
@ -499,7 +500,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw std::runtime_error("Deserializing nil into a non-nullable object");
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@ -524,9 +525,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw std::runtime_error("Serializing NaN");
throw Slic3r::RuntimeError("Serializing NaN");
} else
throw std::runtime_error("Serializing invalid number");
throw Slic3r::RuntimeError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
if (NULLABLE) {
@ -645,7 +646,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw std::runtime_error("Deserializing nil into a non-nullable object");
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int value;
@ -662,7 +663,7 @@ private:
if (NULLABLE)
ss << "nil";
else
throw std::runtime_error("Serializing NaN");
throw Slic3r::RuntimeError("Serializing NaN");
} else
ss << v;
}
@ -847,7 +848,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
}
@ -858,7 +859,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
}
@ -1126,7 +1127,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw std::runtime_error("Deserializing nil into a non-nullable object");
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
} else
this->values.push_back(item_str.compare("1") == 0);
}
@ -1139,7 +1140,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw std::runtime_error("Serializing NaN");
throw Slic3r::RuntimeError("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@ -1175,14 +1176,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw std::runtime_error("ConfigOptionEnum<T>: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionEnum<T>: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = (T)rhs->getInt();
}
@ -1259,14 +1260,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types");
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type");
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = rhs->getInt();
}
@ -1321,7 +1322,7 @@ public:
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1340,7 +1341,7 @@ public:
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
}
}
}
@ -1352,7 +1353,7 @@ public:
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1371,7 +1372,7 @@ public:
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
}
}
// Make the compiler happy, shut up the warnings.

View file

@ -1,6 +1,10 @@
#include "CustomGCode.hpp"
#include "Config.hpp"
#if ENABLE_GCODE_VIEWER
#include "GCode.hpp"
#else
#include "GCode/PreviewData.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "GCodeWriter.hpp"
namespace Slic3r {
@ -17,8 +21,12 @@ extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrint
return;
if (info.gcodes.empty() && ! colorprint_heights->values.empty()) {
// Convert the old colorprint_heighs only if there is no equivalent data in a new format.
const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors();
const auto& colorprint_values = colorprint_heights->values;
#if ENABLE_GCODE_VIEWER
const std::vector<std::string>& colors = ColorPrintColors::get();
#else
const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors();
#endif // ENABLE_GCODE_VIEWER
const auto& colorprint_values = colorprint_heights->values;
info.gcodes.clear();
info.gcodes.reserve(colorprint_values.size());
int i = 0;

View file

@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Exception.hpp"
#include "Geometry.hpp"
#include "Polygon.hpp"
#include "Line.hpp"
@ -435,7 +436,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const
std::list<TPPLPoly> output;
int res = TPPLPartition().Triangulate_MONO(&input, &output);
if (res != 1)
throw std::runtime_error("Triangulation failed");
throw Slic3r::RuntimeError("Triangulation failed");
// convert output polygons
for (std::list<TPPLPoly>::iterator poly = output.begin(); poly != output.end(); ++poly) {
@ -548,7 +549,7 @@ void ExPolygon::triangulate_pp(Points *triangles) const
int res = TPPLPartition().Triangulate_MONO(&input, &output);
// int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) {
if (res != 1)
throw std::runtime_error("Triangulation failed");
throw Slic3r::RuntimeError("Triangulation failed");
*triangles = polypartition_output_to_triangles(output);
}
@ -591,7 +592,7 @@ void ExPolygon::triangulate_p2t(Polygons* polygons) const
}
polygons->push_back(p);
}
} catch (const std::runtime_error & /* err */) {
} catch (const Slic3r::RuntimeError & /* err */) {
assert(false);
// just ignore, don't triangulate
}

View file

@ -333,6 +333,14 @@ extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygons &expp)
extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygon &ex);
extern std::vector<Point> polypartition_output_to_triangles(const std::list<TPPLPoly> &output);
inline double area(const ExPolygons &polys)
{
double s = 0.;
for (auto &p : polys) s += p.area();
return s;
}
} // namespace Slic3r
// start Boost

View file

@ -0,0 +1,28 @@
#ifndef _libslic3r_Exception_h_
#define _libslic3r_Exception_h_
#include <stdexcept>
namespace Slic3r {
// PrusaSlicer's own exception hierarchy is derived from std::runtime_error.
// Base for Slicer's own exceptions.
class Exception : public std::runtime_error { using std::runtime_error::runtime_error; };
#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \
class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; }
// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread.
// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed.
SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception);
SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException);
SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException);
SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError);
SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError);
SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException);
SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError);
// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications.
SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception);
#undef SLIC3R_DERIVE_EXCEPTION
} // namespace Slic3r
#endif // _libslic3r_Exception_h_

View file

@ -306,14 +306,18 @@ double ExtrusionLoop::min_mm3_per_mm() const
std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
{
switch (role) {
#if ENABLE_GCODE_VIEWER
case erNone : return L("Unknown");
#else
case erNone : return L("None");
#endif // ENABLE_GCODE_VIEWER
case erPerimeter : return L("Perimeter");
case erExternalPerimeter : return L("External perimeter");
case erOverhangPerimeter : return L("Overhang perimeter");
case erInternalInfill : return L("Internal infill");
case erSolidInfill : return L("Solid infill");
case erIroning : return L("Ironing");
case erTopSolidInfill : return L("Top solid infill");
case erIroning : return L("Ironing");
case erBridgeInfill : return L("Bridge infill");
case erGapFill : return L("Gap fill");
case erSkirt : return L("Skirt");
@ -327,4 +331,40 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
return "";
}
ExtrusionRole ExtrusionEntity::string_to_role(const std::string& role)
{
if (role == L("Perimeter"))
return erPerimeter;
else if (role == L("External perimeter"))
return erExternalPerimeter;
else if (role == L("Overhang perimeter"))
return erOverhangPerimeter;
else if (role == L("Internal infill"))
return erInternalInfill;
else if (role == L("Solid infill"))
return erSolidInfill;
else if (role == L("Top solid infill"))
return erTopSolidInfill;
else if (role == L("Ironing"))
return erIroning;
else if (role == L("Bridge infill"))
return erBridgeInfill;
else if (role == L("Gap fill"))
return erGapFill;
else if (role == L("Skirt"))
return erSkirt;
else if (role == L("Support material"))
return erSupportMaterial;
else if (role == L("Support material interface"))
return erSupportMaterialInterface;
else if (role == L("Wipe tower"))
return erWipeTower;
else if (role == L("Custom"))
return erCustom;
else if (role == L("Mixed"))
return erMixed;
else
return erNone;
}
}

View file

@ -106,6 +106,7 @@ public:
virtual double total_volume() const = 0;
static std::string role_to_string(ExtrusionRole role);
static ExtrusionRole string_to_role(const std::string& role);
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
@ -121,8 +122,8 @@ public:
// Height of the extrusion, used for visualization purposes.
float height;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {};
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {};
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}

View file

@ -2,6 +2,7 @@
#define slic3r_ExtrusionEntityCollection_hpp_
#include "libslic3r.h"
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
namespace Slic3r {
@ -107,7 +108,7 @@ public:
// Following methods shall never be called on an ExtrusionEntityCollection.
Polyline as_polyline() const override {
throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection");
throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
@ -117,7 +118,7 @@ public:
}
double length() const override {
throw std::runtime_error("Calling length() on a ExtrusionEntityCollection");
throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection");
return 0.;
}
};

View file

@ -10,14 +10,14 @@
namespace Slic3r {
// Generic file parser error, mostly copied from boost::property_tree::file_parser_error
class file_parser_error: public std::runtime_error
class file_parser_error: public Slic3r::RuntimeError
{
public:
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
std::runtime_error(format_what(msg, file, line)),
Slic3r::RuntimeError(format_what(msg, file, line)),
m_message(msg), m_filename(file), m_line(line) {}
file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) :
std::runtime_error(format_what(msg, file.string(), line)),
Slic3r::RuntimeError(format_what(msg, file.string(), line)),
m_message(msg), m_filename(file.string()), m_line(line) {}
// gcc 3.4.2 complains about lack of throw specifier on compiler
// generated dtor
@ -35,7 +35,7 @@ private:
std::string m_filename;
unsigned long m_line;
// Format error message to be returned by std::runtime_error::what()
// Format error message to be returned by Slic3r::RuntimeError::what()
static std::string format_what(const std::string &msg, const std::string &file, unsigned long l)
{
std::stringstream stream;

View file

@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
#endif
// friend to Layer
void Layer::make_fills()
void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree)
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
@ -345,6 +345,8 @@ void Layer::make_fills()
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->adapt_fill_octree = adaptive_fill_octree;
f->support_fill_octree = support_fill_octree;
// calculate flow spacing for infill pattern generation
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;

View file

@ -0,0 +1,520 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../Surface.hpp"
#include "../Geometry.hpp"
#include "../AABBTreeIndirect.hpp"
#include "../Layer.hpp"
#include "../Print.hpp"
#include "../ShortestPath.hpp"
#include "FillAdaptive.hpp"
namespace Slic3r {
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object)
{
// Output, spacing for icAdaptiveCubic and icSupportCubic
double adaptive_line_spacing = 0.;
double support_line_spacing = 0.;
enum class Tristate {
Yes,
No,
Maybe
};
struct RegionFillData {
Tristate has_adaptive_infill;
Tristate has_support_infill;
double density;
double extrusion_width;
};
std::vector<RegionFillData> region_fill_data;
region_fill_data.reserve(print_object.print()->regions().size());
bool build_octree = false;
for (const PrintRegion *region : print_object.print()->regions()) {
const PrintRegionConfig &config = region->config();
bool nonempty = config.fill_density > 0;
bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic;
bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic;
region_fill_data.push_back(RegionFillData({
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
has_support_infill ? Tristate::Maybe : Tristate::No,
config.fill_density,
config.infill_extrusion_width
}));
build_octree |= has_adaptive_infill || has_support_infill;
}
if (build_octree) {
// Compute the average of above parameters over all layers
for (const Layer *layer : print_object.layers())
for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
RegionFillData &rd = region_fill_data[region_id];
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
rd.has_adaptive_infill = Tristate::Yes;
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
rd.has_support_infill = Tristate::Yes;
}
double adaptive_fill_density = 0.;
double adaptive_infill_extrusion_width = 0.;
int adaptive_cnt = 0;
double support_fill_density = 0.;
double support_infill_extrusion_width = 0.;
int support_cnt = 0;
for (const RegionFillData &rd : region_fill_data) {
if (rd.has_adaptive_infill == Tristate::Yes) {
adaptive_fill_density += rd.density;
adaptive_infill_extrusion_width += rd.extrusion_width;
++ adaptive_cnt;
} else if (rd.has_support_infill == Tristate::Yes) {
support_fill_density += rd.density;
support_infill_extrusion_width += rd.extrusion_width;
++ support_cnt;
}
}
auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
if (cnt) {
density /= double(cnt);
extrusion_width /= double(cnt);
return extrusion_width / ((density / 100.0f) * 0.333333333f);
} else
return 0.;
};
adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width);
support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width);
}
return std::make_pair(adaptive_line_spacing, support_line_spacing);
}
void FillAdaptive::_fill_surface_single(const FillParams & params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon & expolygon,
Polylines & polylines_out)
{
if(this->adapt_fill_octree != nullptr)
this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->adapt_fill_octree);
}
void FillAdaptive::generate_infill(const FillParams & params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon & expolygon,
Polylines & polylines_out,
FillAdaptive_Internal::Octree *octree)
{
Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0);
Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones());
// Store grouped lines by its direction (multiple of 120°)
std::vector<Lines> infill_lines_dir(3);
this->generate_infill_lines(octree->root_cube.get(),
this->z, octree->origin, rotation_matrix,
infill_lines_dir, octree->cubes_properties,
int(octree->cubes_properties.size()) - 1);
Polylines all_polylines;
all_polylines.reserve(infill_lines_dir[0].size() * 3);
for (Lines &infill_lines : infill_lines_dir)
{
for (const Line &line : infill_lines)
{
all_polylines.emplace_back(line.a, line.b);
}
}
if (params.dont_connect)
{
// Crop all polylines
polylines_out = intersection_pl(all_polylines, to_polygons(expolygon));
}
else
{
// Crop all polylines
all_polylines = intersection_pl(all_polylines, to_polygons(expolygon));
Polylines boundary_polylines;
Polylines non_boundary_polylines;
for (const Polyline &polyline : all_polylines)
{
// connect_infill required all polylines to touch the boundary.
if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b))
{
boundary_polylines.push_back(polyline);
}
else
{
non_boundary_polylines.push_back(polyline);
}
}
if(!boundary_polylines.empty())
{
boundary_polylines = chain_polylines(boundary_polylines);
FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params);
}
polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end());
}
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
static int iRuna = 0;
BoundingBox bbox_svg = this->bounding_box;
{
::Slic3r::SVG svg(debug_out_path("FillAdaptive-%d.svg", iRuna), bbox_svg);
for (const Polyline &polyline : polylines_out)
{
for (const Line &line : polyline.lines())
{
Point from = line.a;
Point to = line.b;
Point diff = to - from;
float shrink_length = scale_(0.4);
float line_slope = (float)diff.y() / diff.x();
float shrink_x = shrink_length / (float)std::sqrt(1.0 + (line_slope * line_slope));
float shrink_y = line_slope * shrink_x;
to.x() -= shrink_x;
to.y() -= shrink_y;
from.x() += shrink_x;
from.y() += shrink_y;
svg.draw(Line(from, to));
}
}
}
iRuna++;
}
#endif /* SLIC3R_DEBUG */
}
void FillAdaptive::generate_infill_lines(
FillAdaptive_Internal::Cube *cube,
double z_position,
const Vec3d &origin,
const Transform3d &rotation_matrix,
std::vector<Lines> &dir_lines_out,
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
int depth)
{
using namespace FillAdaptive_Internal;
if(cube == nullptr)
{
return;
}
Vec3d cube_center_tranformed = rotation_matrix * cube->center;
double z_diff = std::abs(z_position - cube_center_tranformed.z());
if (z_diff > cubes_properties[depth].height / 2)
{
return;
}
if (z_diff < cubes_properties[depth].line_z_distance)
{
Point from(
scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance),
scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube_center_tranformed.z() - cubes_properties[depth].line_z_distance)) / sqrt(2))));
Point to(-from.x(), from.y());
// Relative to cube center
double rotation_angle = (2.0 * M_PI) / 3.0;
for (Lines &lines : dir_lines_out)
{
Vec3d offset = cube_center_tranformed - (rotation_matrix * origin);
Point from_abs(from), to_abs(to);
from_abs.x() += int(scale_(offset.x()));
from_abs.y() += int(scale_(offset.y()));
to_abs.x() += int(scale_(offset.x()));
to_abs.y() += int(scale_(offset.y()));
// lines.emplace_back(from_abs, to_abs);
this->connect_lines(lines, Line(from_abs, to_abs));
from.rotate(rotation_angle);
to.rotate(rotation_angle);
}
}
for(const std::unique_ptr<Cube> &child : cube->children)
{
if(child != nullptr)
{
generate_infill_lines(child.get(), z_position, origin, rotation_matrix, dir_lines_out, cubes_properties, depth - 1);
}
}
}
void FillAdaptive::connect_lines(Lines &lines, Line new_line)
{
auto eps = int(scale_(0.10));
for (size_t i = 0; i < lines.size(); ++i)
{
if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps)
{
new_line.a = lines[i].a;
lines.erase(lines.begin() + i);
--i;
continue;
}
if (std::abs(new_line.b.x() - lines[i].a.x()) < eps && std::abs(new_line.b.y() - lines[i].a.y()) < eps)
{
new_line.b = lines[i].b;
lines.erase(lines.begin() + i);
--i;
continue;
}
}
lines.emplace_back(new_line.a, new_line.b);
}
std::unique_ptr<FillAdaptive_Internal::Octree> FillAdaptive::build_octree(
TriangleMesh &triangle_mesh,
coordf_t line_spacing,
const Vec3d &cube_center)
{
using namespace FillAdaptive_Internal;
if(line_spacing <= 0 || std::isnan(line_spacing))
{
return nullptr;
}
Vec3d bb_size = triangle_mesh.bounding_box().size();
// The furthest point from the center of the bottom of the mesh bounding box.
double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) +
((bb_size.y() * bb_size.y()) / 4.0) +
(bb_size.z() * bb_size.z()));
double max_cube_edge_length = furthest_point * 2;
std::vector<CubeProperties> cubes_properties;
for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2)
{
CubeProperties props{};
props.edge_length = edge_length;
props.height = edge_length * sqrt(3);
props.diagonal_length = edge_length * sqrt(2);
props.line_z_distance = edge_length / sqrt(3);
props.line_xy_distance = edge_length / sqrt(6);
cubes_properties.push_back(props);
}
if (triangle_mesh.its.vertices.empty())
{
triangle_mesh.require_shared_vertices();
}
AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
triangle_mesh.its.vertices, triangle_mesh.its.indices);
auto octree = std::make_unique<Octree>(std::make_unique<Cube>(cube_center), cube_center, cubes_properties);
FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1);
return octree;
}
void FillAdaptive::expand_cube(
FillAdaptive_Internal::Cube *cube,
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
const AABBTreeIndirect::Tree3f &distance_tree,
const TriangleMesh &triangle_mesh, int depth)
{
using namespace FillAdaptive_Internal;
if (cube == nullptr || depth == 0)
{
return;
}
std::vector<Vec3d> child_centers = {
Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1),
Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1)
};
double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16;
for (size_t i = 0; i < 8; ++i)
{
const Vec3d &child_center = child_centers[i];
Vec3d child_center_transformed = cube->center + (child_center * (cubes_properties[depth].edge_length / 4));
if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices,
distance_tree, child_center_transformed, cube_radius_squared))
{
cube->children[i] = std::make_unique<Cube>(child_center_transformed);
FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, distance_tree, triangle_mesh, depth - 1);
}
}
}
void FillAdaptive_Internal::Octree::propagate_point(
Vec3d point,
FillAdaptive_Internal::Cube * current,
int depth,
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties)
{
using namespace FillAdaptive_Internal;
if(depth <= 0)
{
return;
}
size_t octant_idx = Octree::find_octant(point, current->center);
Cube * child = current->children[octant_idx].get();
// Octant not exists, then create it
if(child == nullptr) {
std::vector<Vec3d> child_centers = {
Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1),
Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1)
};
const Vec3d &child_center = child_centers[octant_idx];
Vec3d child_center_transformed = current->center + (child_center * (cubes_properties[depth].edge_length / 4));
current->children[octant_idx] = std::make_unique<Cube>(child_center_transformed);
child = current->children[octant_idx].get();
}
Octree::propagate_point(point, child, (depth - 1), cubes_properties);
}
std::unique_ptr<FillAdaptive_Internal::Octree> FillSupportCubic::build_octree(
TriangleMesh & triangle_mesh,
coordf_t line_spacing,
const Vec3d & cube_center,
const Transform3d &rotation_matrix)
{
using namespace FillAdaptive_Internal;
if(line_spacing <= 0 || std::isnan(line_spacing))
{
return nullptr;
}
Vec3d bb_size = triangle_mesh.bounding_box().size();
// The furthest point from the center of the bottom of the mesh bounding box.
double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) +
((bb_size.y() * bb_size.y()) / 4.0) +
(bb_size.z() * bb_size.z()));
double max_cube_edge_length = furthest_point * 2;
std::vector<CubeProperties> cubes_properties;
for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2)
{
CubeProperties props{};
props.edge_length = edge_length;
props.height = edge_length * sqrt(3);
props.diagonal_length = edge_length * sqrt(2);
props.line_z_distance = edge_length / sqrt(3);
props.line_xy_distance = edge_length / sqrt(6);
cubes_properties.push_back(props);
}
if (triangle_mesh.its.vertices.empty())
{
triangle_mesh.require_shared_vertices();
}
AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
triangle_mesh.its.vertices, triangle_mesh.its.indices);
auto octree = std::make_unique<Octree>(std::make_unique<Cube>(cube_center), cube_center, cubes_properties);
double cube_edge_length = line_spacing / 2.0;
int max_depth = int(octree->cubes_properties.size()) - 1;
BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box();
Vec3f vertical(0, 0, 1);
for (size_t facet_idx = 0; facet_idx < triangle_mesh.stl.facet_start.size(); ++facet_idx)
{
if(triangle_mesh.stl.facet_start[facet_idx].normal.dot(vertical) <= 0.707)
{
// The angle is smaller than PI/4, than infill don't to be there
continue;
}
stl_vertex v_1 = triangle_mesh.stl.facet_start[facet_idx].vertex[0];
stl_vertex v_2 = triangle_mesh.stl.facet_start[facet_idx].vertex[1];
stl_vertex v_3 = triangle_mesh.stl.facet_start[facet_idx].vertex[2];
std::vector<Vec3d> triangle_vertices =
{Vec3d(v_1.x(), v_1.y(), v_1.z()),
Vec3d(v_2.x(), v_2.y(), v_2.z()),
Vec3d(v_3.x(), v_3.y(), v_3.z())};
BoundingBoxf3 triangle_bb(triangle_vertices);
Vec3d triangle_start_relative = triangle_bb.min - mesh_bb.min;
Vec3d triangle_end_relative = triangle_bb.max - mesh_bb.min;
Vec3crd triangle_start_idx = Vec3crd(
int(std::floor(triangle_start_relative.x() / cube_edge_length)),
int(std::floor(triangle_start_relative.y() / cube_edge_length)),
int(std::floor(triangle_start_relative.z() / cube_edge_length)));
Vec3crd triangle_end_idx = Vec3crd(
int(std::floor(triangle_end_relative.x() / cube_edge_length)),
int(std::floor(triangle_end_relative.y() / cube_edge_length)),
int(std::floor(triangle_end_relative.z() / cube_edge_length)));
for (int z = triangle_start_idx.z(); z <= triangle_end_idx.z(); ++z)
{
for (int y = triangle_start_idx.y(); y <= triangle_end_idx.y(); ++y)
{
for (int x = triangle_start_idx.x(); x <= triangle_end_idx.x(); ++x)
{
Vec3d cube_center_relative(x * cube_edge_length + (cube_edge_length / 2.0), y * cube_edge_length + (cube_edge_length / 2.0), z * cube_edge_length);
Vec3d cube_center_absolute = cube_center_relative + mesh_bb.min;
double cube_center_absolute_arr[3] = {cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z()};
double distance = 0, cord_u = 0, cord_v = 0;
double dir[3] = {0.0, 0.0, 1.0};
double vert_0[3] = {triangle_vertices[0].x(),
triangle_vertices[0].y(),
triangle_vertices[0].z()};
double vert_1[3] = {triangle_vertices[1].x(),
triangle_vertices[1].y(),
triangle_vertices[1].z()};
double vert_2[3] = {triangle_vertices[2].x(),
triangle_vertices[2].y(),
triangle_vertices[2].z()};
if(intersect_triangle(cube_center_absolute_arr, dir, vert_0, vert_1, vert_2, &distance, &cord_u, &cord_v) && distance > 0 && distance <= cube_edge_length)
{
Vec3d cube_center_transformed(cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z() + (cube_edge_length / 2.0));
Octree::propagate_point(rotation_matrix * cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties);
}
}
}
}
}
return octree;
}
void FillSupportCubic::_fill_surface_single(const FillParams & params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon & expolygon,
Polylines & polylines_out)
{
if (this->support_fill_octree != nullptr)
this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->support_fill_octree);
}
} // namespace Slic3r

View file

@ -0,0 +1,139 @@
#ifndef slic3r_FillAdaptive_hpp_
#define slic3r_FillAdaptive_hpp_
#include "../AABBTreeIndirect.hpp"
#include "FillBase.hpp"
namespace Slic3r {
class PrintObject;
class TriangleMesh;
namespace FillAdaptive_Internal
{
struct CubeProperties
{
double edge_length; // Lenght of edge of a cube
double height; // Height of rotated cube (standing on the corner)
double diagonal_length; // Length of diagonal of a cube a face
double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created
double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created
};
struct Cube
{
Vec3d center;
std::unique_ptr<Cube> children[8] = {};
Cube(const Vec3d &center) : center(center) {}
};
struct Octree
{
std::unique_ptr<Cube> root_cube;
Vec3d origin;
std::vector<CubeProperties> cubes_properties;
Octree(std::unique_ptr<Cube> rootCube, const Vec3d &origin, const std::vector<CubeProperties> &cubes_properties)
: root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {}
inline static int find_octant(const Vec3d &i_cube, const Vec3d &current)
{
return (i_cube.z() > current.z()) * 4 + (i_cube.y() > current.y()) * 2 + (i_cube.x() > current.x());
}
static void propagate_point(
Vec3d point,
FillAdaptive_Internal::Cube *current_cube,
int depth,
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties);
};
}; // namespace FillAdaptive_Internal
//
// Some of the algorithms used by class FillAdaptive were inspired by
// Cura Engine's class SubDivCube
// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h
//
class FillAdaptive : public Fill
{
public:
virtual ~FillAdaptive() {}
protected:
virtual Fill* clone() const { return new FillAdaptive(*this); };
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
virtual bool no_sort() const { return true; }
void generate_infill_lines(
FillAdaptive_Internal::Cube *cube,
double z_position,
const Vec3d & origin,
const Transform3d & rotation_matrix,
std::vector<Lines> & dir_lines_out,
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
int depth);
static void connect_lines(Lines &lines, Line new_line);
void generate_infill(const FillParams & params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon & expolygon,
Polylines & polylines_out,
FillAdaptive_Internal::Octree *octree);
public:
static std::unique_ptr<FillAdaptive_Internal::Octree> build_octree(
TriangleMesh &triangle_mesh,
coordf_t line_spacing,
const Vec3d & cube_center);
static void expand_cube(
FillAdaptive_Internal::Cube *cube,
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
const AABBTreeIndirect::Tree3f &distance_tree,
const TriangleMesh & triangle_mesh,
int depth);
};
class FillSupportCubic : public FillAdaptive
{
public:
virtual ~FillSupportCubic() = default;
protected:
virtual Fill* clone() const { return new FillSupportCubic(*this); };
virtual bool no_sort() const { return true; }
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
public:
static std::unique_ptr<FillAdaptive_Internal::Octree> build_octree(
TriangleMesh & triangle_mesh,
coordf_t line_spacing,
const Vec3d & cube_center,
const Transform3d &rotation_matrix);
};
// Calculate line spacing for
// 1) adaptive cubic infill
// 2) adaptive internal support cubic infill
// Returns zero for a particular infill type if no such infill is to be generated.
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
} // namespace Slic3r
#endif // slic3r_FillAdaptive_hpp_

View file

@ -16,6 +16,7 @@
#include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp"
#include "FillRectilinear3.hpp"
#include "FillAdaptive.hpp"
namespace Slic3r {
@ -37,7 +38,9 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipArchimedeanChords: return new FillArchimedeanChords();
case ipHilbertCurve: return new FillHilbertCurve();
case ipOctagramSpiral: return new FillOctagramSpiral();
default: throw std::invalid_argument("unknown type");
case ipAdaptiveCubic: return new FillAdaptive();
case ipSupportCubic: return new FillSupportCubic();
default: throw Slic3r::InvalidArgument("unknown type");
}
}

View file

@ -11,6 +11,7 @@
#include "../libslic3r.h"
#include "../BoundingBox.hpp"
#include "../Exception.hpp"
#include "../Utils.hpp"
namespace Slic3r {
@ -19,9 +20,14 @@ class ExPolygon;
class Surface;
enum InfillPattern : int;
class InfillFailedException : public std::runtime_error {
namespace FillAdaptive_Internal {
struct Octree;
};
// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError.
class InfillFailedException : public Slic3r::RuntimeError {
public:
InfillFailedException() : std::runtime_error("Infill failed") {}
InfillFailedException() : Slic3r::RuntimeError("Infill failed") {}
};
struct FillParams
@ -69,6 +75,11 @@ public:
// In scaled coordinates. Bounding box of the 2D projection of the object.
BoundingBox bounding_box;
// Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr;
// Octree builds on mesh for usage in the support cubic infill
FillAdaptive_Internal::Octree* support_fill_octree = nullptr;
public:
virtual ~Fill() {}

View file

@ -53,7 +53,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
else if (opt_key == "support_material_extrusion_width")
return frSupportMaterial;
else
throw std::runtime_error("opt_key_to_flow_role: invalid argument");
throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument");
};
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
@ -126,7 +126,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
{
// we need layer height unless it's a bridge
if (height <= 0 && bridge_flow_ratio == 0)
throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()");
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
float w;
if (bridge_flow_ratio > 0) {
@ -151,7 +151,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height,
{
// we need layer height unless it's a bridge
if (height <= 0 && !bridge)
throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()");
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
// Calculate width from spacing.
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.

View file

@ -3,6 +3,7 @@
#include "libslic3r.h"
#include "Config.hpp"
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
namespace Slic3r {
@ -27,11 +28,11 @@ enum FlowRole {
frSupportMaterialInterface,
};
class FlowError : public std::invalid_argument
class FlowError : public Slic3r::InvalidArgument
{
public:
FlowError(const std::string& what_arg) : invalid_argument(what_arg) {}
FlowError(const char* what_arg) : invalid_argument(what_arg) {}
FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {}
FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {}
};
class FlowErrorNegativeSpacing : public FlowError

View file

@ -1,9 +1,11 @@
#include "../libslic3r.h"
#include "../Exception.hpp"
#include "../Model.hpp"
#include "../Utils.hpp"
#include "../GCode.hpp"
#include "../Geometry.hpp"
#include "../GCode/ThumbnailData.hpp"
#include "../Time.hpp"
#include "../I18N.hpp"
@ -85,6 +87,8 @@ const char* OBJECTID_ATTR = "objectid";
const char* TRANSFORM_ATTR = "transform";
const char* PRINTABLE_ATTR = "printable";
const char* INSTANCESCOUNT_ATTR = "instances_count";
const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
const char* KEY_ATTR = "key";
const char* VALUE_ATTR = "value";
@ -120,11 +124,11 @@ const char* INVALID_OBJECT_TYPES[] =
"other"
};
class version_error : public std::runtime_error
class version_error : public Slic3r::FileIOError
{
public:
version_error(const std::string& what_arg) : std::runtime_error(what_arg) {}
version_error(const char* what_arg) : std::runtime_error(what_arg) {}
version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {}
version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {}
};
const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key)
@ -282,6 +286,8 @@ namespace Slic3r {
{
std::vector<float> vertices;
std::vector<unsigned int> triangles;
std::vector<std::string> custom_supports;
std::vector<std::string> custom_seam;
bool empty()
{
@ -292,6 +298,8 @@ namespace Slic3r {
{
vertices.clear();
triangles.clear();
custom_supports.clear();
custom_seam.clear();
}
};
@ -600,7 +608,7 @@ namespace Slic3r {
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
throw std::runtime_error(e.what());
throw Slic3r::FileIOError(e.what());
}
}
}
@ -773,7 +781,7 @@ namespace Slic3r {
{
char error_buf[1024];
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
throw std::runtime_error(error_buf);
throw Slic3r::FileIOError(error_buf);
}
return n;
@ -782,7 +790,7 @@ namespace Slic3r {
catch (const version_error& e)
{
// rethrow the exception
throw std::runtime_error(e.what());
throw Slic3r::FileIOError(e.what());
}
catch (std::exception& e)
{
@ -1110,6 +1118,15 @@ namespace Slic3r {
float(std::atof(object_data_points[i+6].c_str())),
float(std::atof(object_data_points[i+7].c_str())));
}
// The holes are saved elevated above the mesh and deeper (bad idea indeed).
// This is retained for compatibility.
// Place the hole to the mesh and make it shallower to compensate.
// The offset is 1 mm above the mesh.
for (sla::DrainHole& hole : sla_drain_holes) {
hole.pos += hole.normal.normalized();
hole.height -= 1.f;
}
if (!sla_drain_holes.empty())
m_sla_drain_holes.insert(IdToSlaDrainHolesMap::value_type(object_id, sla_drain_holes));
@ -1538,6 +1555,9 @@ namespace Slic3r {
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR));
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR));
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR));
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
return true;
}
@ -1871,6 +1891,18 @@ namespace Slic3r {
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
volume->calculate_convex_hull();
// recreate custom supports and seam from previously loaded attribute
for (unsigned i=0; i<triangles_count; ++i) {
size_t index = src_start_id/3 + i;
assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size());
if (! geometry.custom_supports[index].empty())
volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
if (! geometry.custom_seam[index].empty())
volume->m_seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
}
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata)
{
@ -1991,7 +2023,7 @@ namespace Slic3r {
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data);
bool _add_relationships_file_to_archive(mz_zip_archive& archive);
bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data);
bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data);
bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets);
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
@ -2054,7 +2086,7 @@ namespace Slic3r {
// Adds model file ("3D/3dmodel.model").
// This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes.
IdToObjectDataMap objects_data;
if (!_add_model_file_to_archive(archive, model, objects_data))
if (!_add_model_file_to_archive(filename, archive, model, objects_data))
{
close_zip_writer(&archive);
boost::filesystem::remove(filename);
@ -2203,7 +2235,7 @@ namespace Slic3r {
return true;
}
bool _3MF_Exporter::_add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data)
bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data)
{
std::stringstream stream;
// https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10
@ -2214,6 +2246,19 @@ namespace Slic3r {
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n";
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n";
std::string name = boost::filesystem::path(filename).stem().string();
stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Designer\">" << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Rating\">" << "</" << METADATA_TAG << ">\n";
std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc());
// keep only the date part of the string
date = date.substr(0, 10);
stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "</" << METADATA_TAG << ">\n";
stream << " <" << RESOURCES_TAG << ">\n";
// Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects).
@ -2316,9 +2361,9 @@ namespace Slic3r {
continue;
if (!volume->mesh().repaired)
throw std::runtime_error("store_3mf() requires repair()");
throw Slic3r::FileIOError("store_3mf() requires repair()");
if (!volume->mesh().has_shared_vertices())
throw std::runtime_error("store_3mf() requires shared vertices");
throw Slic3r::FileIOError("store_3mf() requires shared vertices");
volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;
@ -2369,6 +2414,15 @@ namespace Slic3r {
{
stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" ";
}
std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i);
if (! custom_supports_data_string.empty())
stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" ";
std::string custom_seam_data_string = volume->m_seam_facets.get_triangle_as_string(i);
if (! custom_seam_data_string.empty())
stream << CUSTOM_SEAM_ATTR << "=\"" << custom_seam_data_string << "\" ";
stream << "/>\n";
}
}
@ -2559,7 +2613,18 @@ namespace Slic3r {
for (const ModelObject* object : model.objects)
{
++count;
auto& drain_holes = object->sla_drain_holes;
sla::DrainHoles drain_holes = object->sla_drain_holes;
// The holes were placed 1mm above the mesh in the first implementation.
// This was a bad idea and the reference point was changed in 2.3 so
// to be on the mesh exactly. The elevated position is still saved
// in 3MFs for compatibility reasons.
for (sla::DrainHole& hole : drain_holes) {
hole.pos -= hole.normal.normalized();
hole.height += 1.f;
}
if (!drain_holes.empty())
{
out += string_printf(fmt, count);

View file

@ -35,6 +35,6 @@ namespace Slic3r {
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
}; // namespace Slic3r
} // namespace Slic3r
#endif /* slic3r_Format_3mf_hpp_ */

View file

@ -7,6 +7,7 @@
#include <boost/nowide/cstdio.hpp>
#include "../libslic3r.h"
#include "../Exception.hpp"
#include "../Model.hpp"
#include "../GCode.hpp"
#include "../PrintConfig.hpp"
@ -923,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
{
char error_buf[1024];
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
throw std::runtime_error(error_buf);
throw Slic3r::FileIOError(error_buf);
}
return n;
@ -948,9 +949,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE))
{
// 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());
// throw Slic3r::FileIOError(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);
throw Slic3r::FileIOError(msg);
}
return true;
@ -994,7 +995,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
throw std::runtime_error(e.what());
throw Slic3r::FileIOError(e.what());
}
break;
@ -1147,9 +1148,9 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
for (ModelVolume *volume : object->volumes) {
vertices_offsets.push_back(num_vertices);
if (! volume->mesh().repaired)
throw std::runtime_error("store_amf() requires repair()");
throw Slic3r::FileIOError("store_amf() requires repair()");
if (! volume->mesh().has_shared_vertices())
throw std::runtime_error("store_amf() requires shared vertices");
throw Slic3r::FileIOError("store_amf() requires shared vertices");
const indexed_triangle_set &its = volume->mesh().its;
const Transform3d& matrix = volume->get_matrix();
for (size_t i = 0; i < its.vertices.size(); ++i) {

View file

@ -147,7 +147,7 @@ static void extract_model_from_archive(
}
}
if (! trafo_set)
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
// Extract the STL.
StlHeader header;
@ -266,7 +266,7 @@ static void extract_model_from_archive(
}
if (! mesh_valid)
throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
// Add this mesh to the model.
ModelVolume *volume = nullptr;
@ -303,7 +303,7 @@ bool load_prus(const char *path, Model *model)
mz_bool res = MZ_FALSE;
try {
if (!open_zip_reader(&archive, path))
throw std::runtime_error(std::string("Unable to init zip reader for ") + path);
throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path);
std::vector<char> scene_xml_data;
// For grouping multiple STLs into a single ModelObject for multi-material prints.
std::map<int, ModelObject*> group_to_model_object;
@ -316,10 +316,10 @@ bool load_prus(const char *path, Model *model)
buffer.assign((size_t)stat.m_uncomp_size, 0);
res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == MZ_FALSE)
std::runtime_error(std::string("Error while extracting a file from ") + path);
throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path);
if (strcmp(stat.m_filename, "scene.xml") == 0) {
if (! scene_xml_data.empty())
throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path);
throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path);
scene_xml_data = std::move(buffer);
} else if (boost::iends_with(stat.m_filename, ".stl")) {
// May throw std::exception

View file

@ -8,8 +8,317 @@
#include "libslic3r/Zipper.hpp"
#include "libslic3r/SLAPrint.hpp"
#include <sstream>
#include "libslic3r/Exception.hpp"
#include "libslic3r/SlicesToTriangleMesh.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLA/RasterBase.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/PNGRead.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
namespace marchsq {
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
using Rst = Slic3r::png::ImageGreyscale;
// The type of pixel cell in the raster
using ValueType = uint8_t;
// Value at a given position
static uint8_t get(const Rst &rst, size_t row, size_t col)
{
return rst.get(row, col);
}
// Number of rows and cols of the raster
static size_t rows(const Rst &rst) { return rst.rows; }
static size_t cols(const Rst &rst) { return rst.cols; }
};
} // namespace marchsq
namespace Slic3r {
namespace {
struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
struct ArchiveData {
boost::property_tree::ptree profile, config;
std::vector<PNGBuffer> images;
};
static const constexpr char *CONFIG_FNAME = "config.ini";
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
MZ_Archive & zip)
{
std::string buf(size_t(entry.m_uncomp_size), '\0');
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw Slic3r::FileIOError(zip.get_errorstr());
boost::property_tree::ptree tree;
std::stringstream ss(buf);
boost::property_tree::read_ini(ss, tree);
return tree;
}
PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
MZ_Archive & zip,
const std::string & name)
{
std::vector<uint8_t> buf(entry.m_uncomp_size);
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw Slic3r::FileIOError(zip.get_errorstr());
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
}
ArchiveData extract_sla_archive(const std::string &zipfname,
const std::string &exclude)
{
ArchiveData arch;
// Little RAII
struct Arch: public MZ_Archive {
Arch(const std::string &fname) {
if (!open_zip_reader(&arch, fname))
throw Slic3r::FileIOError(get_errorstr());
}
~Arch() { close_zip_reader(&arch); }
} zip (zipfname);
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
for (mz_uint i = 0; i < num_entries; ++i)
{
mz_zip_archive_file_stat entry;
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
{
std::string name = entry.m_filename;
boost::algorithm::to_lower(name);
if (boost::algorithm::contains(name, exclude)) continue;
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
if (boost::filesystem::path(name).extension().string() == ".png") {
auto it = std::lower_bound(
arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
[](const PNGBuffer &r1, const PNGBuffer &r2) {
return std::less<std::string>()(r1.fname, r2.fname);
});
arch.images.insert(it, read_png(entry, zip, name));
}
}
}
return arch;
}
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
double px_w, double px_h)
{
ExPolygons polys; polys.reserve(rings.size());
for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points;
pts.reserve(ring.size());
for (const marchsq::Coord &crd : ring)
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
polys.emplace_back(poly);
}
// reverse the raster transformations
return union_ex(polys);
}
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
{
for (auto &p : poly.contour.points) fn(p);
for (auto &h : poly.holes)
for (auto &p : h.points) fn(p);
}
void invert_raster_trafo(ExPolygons & expolys,
const sla::RasterBase::Trafo &trafo,
coord_t width,
coord_t height)
{
if (trafo.flipXY) std::swap(height, width);
for (auto &expoly : expolys) {
if (trafo.mirror_y)
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
if (trafo.mirror_x)
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
expoly.translate(-trafo.center_x, -trafo.center_y);
if (trafo.flipXY)
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
expoly.contour.reverse();
for (auto &h : expoly.holes) h.reverse();
}
}
}
struct RasterParams {
sla::RasterBase::Trafo trafo; // Raster transformations
coord_t width, height; // scaled raster dimensions (not resolution)
double px_h, px_w; // pixel dimesions
marchsq::Coord win; // marching squares window size
};
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
{
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw Slic3r::FileIOError("Invalid SL1 file");
RasterParams rstp;
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ?
sla::RasterBase::roLandscape :
sla::RasterBase::roPortrait,
{opt_mirror_x->value, opt_mirror_y->value}};
rstp.height = scaled(opt_disp_h->value);
rstp.width = scaled(opt_disp_w->value);
return rstp;
}
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
{
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw Slic3r::FileIOError("Invalid SL1 file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}
std::vector<ExPolygons> extract_slices_from_sla_archive(
ArchiveData & arch,
const RasterParams & rstp,
std::function<bool(int)> progr)
{
auto jobdir = arch.config.get<std::string>("jobDir");
for (auto &c : jobdir) c = std::tolower(c);
std::vector<ExPolygons> slices(arch.images.size());
struct Status
{
double incr, val, prev;
bool stop = false;
tbb::spin_mutex mutex;
} st {100. / slices.size(), 0., 0.};
tbb::parallel_for(size_t(0), arch.images.size(),
[&arch, &slices, &st, &rstp, progr](size_t i) {
// Status indication guarded with the spinlock
{
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
if (st.stop) return;
st.val += st.incr;
double curr = std::round(st.val);
if (curr > st.prev) {
st.prev = curr;
st.stop = !progr(int(curr));
}
}
png::ImageGreyscale img;
png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
if (!png::decode_png(rb, img)) return;
auto rings = marchsq::execute(img, 128, rstp.win);
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
// Invert the raster transformations indicated in
// the profile metadata
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
slices[i] = std::move(expolys);
});
if (st.stop) slices = {};
return slices;
}
} // namespace
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
{
ArchiveData arch = extract_sla_archive(zipfname, "png");
out.load(arch.profile);
}
void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr)
{
// Ensure minimum window size for marching squares
windowsize.x() = std::max(2, windowsize.x());
windowsize.y() = std::max(2, windowsize.y());
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
profile.load(arch.profile);
RasterParams rstp = get_raster_params(profile);
rstp.win = {windowsize.y(), windowsize.x()};
SliceParams slicp = get_slice_params(profile);
std::vector<ExPolygons> slices =
extract_slices_from_sla_archive(arch, rstp, progr);
if (!slices.empty())
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
}
using ConfMap = std::map<std::string, std::string>;
namespace {
@ -126,9 +435,9 @@ uqptr<sla::RasterBase> SL1Archive::create_raster() const
return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr);
}
sla::EncodedRaster SL1Archive::encode_raster(const sla::RasterBase &rst) const
sla::RasterEncoder SL1Archive::get_encoder() const
{
return rst.encode(sla::PNGRasterEncoder());
return sla::PNGRasterEncoder{};
}
void SL1Archive::export_print(Zipper& zipper,

View file

@ -13,7 +13,7 @@ class SL1Archive: public SLAPrinter {
protected:
uqptr<sla::RasterBase> create_raster() const override;
sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const override;
sla::RasterEncoder get_encoder() const override;
public:
@ -38,6 +38,24 @@ public:
}
};
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
std::function<bool(int)> progr = [](int) { return true; })
{
DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr);
}
} // namespace Slic3r::sla

File diff suppressed because it is too large Load diff

View file

@ -13,9 +13,13 @@
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCodeTimeEstimator.hpp"
#include "EdgeGrid.hpp"
#if ENABLE_GCODE_VIEWER
#include "GCode/GCodeProcessor.hpp"
#else
#include "GCode/Analyzer.hpp"
#include "GCodeTimeEstimator.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "EdgeGrid.hpp"
#include "GCode/ThumbnailData.hpp"
#include <memory>
@ -29,7 +33,9 @@ namespace Slic3r {
// Forward declarations.
class GCode;
#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
#endif // !ENABLE_GCODE_VIEWER
namespace { struct Item; }
struct PrintInstance;
@ -138,6 +144,15 @@ private:
double m_last_wipe_tower_print_z = 0.f;
};
#if ENABLE_GCODE_VIEWER
class ColorPrintColors
{
static const std::vector<std::string> Colors;
public:
static const std::vector<std::string>& get() { return Colors; }
};
#endif // ENABLE_GCODE_VIEWER
class GCode {
public:
GCode() :
@ -145,21 +160,33 @@ public:
m_enable_loop_clipping(true),
m_enable_cooling_markers(false),
m_enable_extrusion_role_markers(false),
#if ENABLE_GCODE_VIEWER
m_last_processor_extrusion_role(erNone),
#else
m_enable_analyzer(false),
m_last_analyzer_extrusion_role(erNone),
#endif // ENABLE_GCODE_VIEWER
m_layer_count(0),
m_layer_index(-1),
m_layer(nullptr),
m_volumetric_speed(0),
m_last_pos_defined(false),
m_last_extrusion_role(erNone),
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_last_mm3_per_mm(0.0),
m_last_width(0.0f),
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
#if !ENABLE_GCODE_VIEWER
m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm),
m_last_width(GCodeAnalyzer::Default_Width),
m_last_height(GCodeAnalyzer::Default_Height),
#endif // !ENABLE_GCODE_VIEWER
m_brim_done(false),
m_second_layer_things_done(false),
#if !ENABLE_GCODE_VIEWER
m_normal_time_estimator(GCodeTimeEstimator::Normal),
m_silent_time_estimator(GCodeTimeEstimator::Silent),
#endif // !ENABLE_GCODE_VIEWER
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{}
@ -167,7 +194,11 @@ public:
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
#if ENABLE_GCODE_VIEWER
void do_export(Print* print, const char* path, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
#else
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
#endif // ENABLE_GCODE_VIEWER
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
@ -327,11 +358,16 @@ private:
// Markers for the Pressure Equalizer to recognize the extrusion type.
// The Pressure Equalizer removes the markers from the final G-code.
bool m_enable_extrusion_role_markers;
#if ENABLE_GCODE_VIEWER
// Keeps track of the last extrusion role passed to the processor
ExtrusionRole m_last_processor_extrusion_role;
#else
// Enableds the G-code Analyzer.
// Extended markers will be added during G-code generation.
// The G-code Analyzer will remove these comments from the final G-code.
bool m_enable_analyzer;
ExtrusionRole m_last_analyzer_extrusion_role;
#endif // ENABLE_GCODE_VIEWER
// How many times will change_layer() be called?
// change_layer() will update the progress bar.
unsigned int m_layer_count;
@ -344,10 +380,20 @@ private:
double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active?
ExtrusionRole m_last_extrusion_role;
#if ENABLE_GCODE_VIEWER
// Support for G-Code Processor
float m_last_height{ 0.0f };
float m_last_layer_z{ 0.0f };
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
double m_last_mm3_per_mm;
float m_last_width{ 0.0f };
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
#else
// Support for G-Code Analyzer
double m_last_mm3_per_mm;
float m_last_width;
float m_last_height;
#endif // ENABLE_GCODE_VIEWER
Point m_last_pos;
bool m_last_pos_defined;
@ -368,13 +414,20 @@ private:
// Index of a last object copy extruded.
std::pair<const PrintObject*, Point> m_last_obj_copy;
#if !ENABLE_GCODE_VIEWER
// Time estimators
GCodeTimeEstimator m_normal_time_estimator;
GCodeTimeEstimator m_silent_time_estimator;
#endif // !ENABLE_GCODE_VIEWER
bool m_silent_time_estimator_enabled;
#if ENABLE_GCODE_VIEWER
// Processor
GCodeProcessor m_processor;
#else
// Analyzer
GCodeAnalyzer m_analyzer;
#endif // ENABLE_GCODE_VIEWER
// Write a string into a file.
void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); }

View file

@ -12,6 +12,8 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
#if !ENABLE_GCODE_VIEWER
static const std::string AXIS_STR = "XYZE";
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float INCHES_TO_MM = 25.4f;
@ -350,7 +352,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
if (delta_pos[E] < 0.0f)
{
if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
type = GCodeMove::Move;
type = GCodeMove::Move;
else
type = GCodeMove::Retract;
}
@ -651,7 +653,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
// color change tag
// pause print tag
pos = comment.find(Pause_Print_Tag);
if (pos != comment.npos)
{
@ -659,7 +661,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
// color change tag
// custom code tag
pos = comment.find(Custom_Code_Tag);
if (pos != comment.npos)
{
@ -667,7 +669,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
return true;
}
// color change tag
// end pause print or custom code tag
pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag);
if (pos != comment.npos)
{
@ -1191,3 +1193,5 @@ size_t GCodeAnalyzer::memory_used() const
}
} // namespace Slic3r
#endif // !ENABLE_GCODE_VIEWER

View file

@ -1,6 +1,8 @@
#ifndef slic3r_GCode_Analyzer_hpp_
#define slic3r_GCode_Analyzer_hpp_
#if !ENABLE_GCODE_VIEWER
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
@ -302,4 +304,6 @@ private:
} // namespace Slic3r
#endif // !ENABLE_GCODE_VIEWER
#endif /* slic3r_GCode_Analyzer_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,560 @@
#ifndef slic3r_GCodeProcessor_hpp_
#define slic3r_GCodeProcessor_hpp_
#if ENABLE_GCODE_VIEWER
#include "libslic3r/GCodeReader.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/CustomGCode.hpp"
#include <array>
#include <vector>
#include <string>
namespace Slic3r {
enum class EMoveType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Color_change,
Pause_Print,
Custom_GCode,
Travel,
Extrude,
Count
};
struct PrintEstimatedTimeStatistics
{
enum class ETimeMode : unsigned char
{
Normal,
Stealth,
Count
};
struct Mode
{
float time;
std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> custom_gcode_times;
std::vector<std::pair<EMoveType, float>> moves_times;
std::vector<std::pair<ExtrusionRole, float>> roles_times;
std::vector<float> layers_times;
void reset() {
time = 0.0f;
custom_gcode_times.clear();
moves_times.clear();
roles_times.clear();
layers_times.clear();
}
};
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
PrintEstimatedTimeStatistics() { reset(); }
void reset() {
for (auto m : modes) {
m.reset();
}
}
};
class GCodeProcessor
{
public:
static const std::string Extrusion_Role_Tag;
static const std::string Height_Tag;
static const std::string Layer_Change_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 First_Line_M73_Placeholder_Tag;
static const std::string Last_Line_M73_Placeholder_Tag;
static const std::string Estimated_Printing_Time_Placeholder_Tag;
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
static const std::string Width_Tag;
static const std::string Mm3_Per_Mm_Tag;
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
private:
using AxisCoords = std::array<float, 4>;
using ExtruderColors = std::vector<unsigned char>;
enum class EUnits : unsigned char
{
Millimeters,
Inches
};
enum class EPositioningType : unsigned char
{
Absolute,
Relative
};
struct CachedPosition
{
AxisCoords position; // mm
float feedrate; // mm/s
void reset();
};
struct CpColor
{
unsigned char counter;
unsigned char current;
void reset();
};
public:
struct FeedrateProfile
{
float entry{ 0.0f }; // mm/s
float cruise{ 0.0f }; // mm/s
float exit{ 0.0f }; // mm/s
};
struct Trapezoid
{
float accelerate_until{ 0.0f }; // mm
float decelerate_after{ 0.0f }; // mm
float cruise_feedrate{ 0.0f }; // mm/sec
float acceleration_time(float entry_feedrate, float acceleration) const;
float cruise_time() const;
float deceleration_time(float distance, float acceleration) const;
float cruise_distance() const;
};
struct TimeBlock
{
struct Flags
{
bool recalculate{ false };
bool nominal_length{ false };
};
EMoveType move_type{ EMoveType::Noop };
ExtrusionRole role{ erNone };
unsigned int layer_id{ 0 };
float distance{ 0.0f }; // mm
float acceleration{ 0.0f }; // mm/s^2
float max_entry_speed{ 0.0f }; // mm/s
float safe_feedrate{ 0.0f }; // mm/s
Flags flags;
FeedrateProfile feedrate_profile;
Trapezoid trapezoid;
// Calculates this block's trapezoid
void calculate_trapezoid();
float time() const;
};
private:
struct TimeMachine
{
struct State
{
float feedrate; // mm/s
float safe_feedrate; // mm/s
AxisCoords axis_feedrate; // mm/s
AxisCoords abs_axis_feedrate; // mm/s
void reset();
};
struct CustomGCodeTime
{
bool needed;
float cache;
std::vector<std::pair<CustomGCode::Type, float>> times;
void reset();
};
bool enabled;
float acceleration; // mm/s^2
// hard limit for the acceleration, to which the firmware will clamp.
float max_acceleration; // mm/s^2
float extrude_factor_override_percentage;
float time; // s
std::string line_m73_mask;
State curr;
State prev;
CustomGCodeTime gcode_time;
std::vector<TimeBlock> blocks;
std::vector<float> g1_times_cache;
std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
std::vector<float> layers_time;
void reset();
// Simulates firmware st_synchronize() call
void simulate_st_synchronize(float additional_time = 0.0f);
void calculate_time(size_t keep_last_n_blocks = 0);
};
struct TimeProcessor
{
struct Planner
{
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
// Let's be conservative and plan for newer boards with more memory.
static constexpr size_t queue_size = 64;
// The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
// We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
static constexpr size_t refresh_threshold = queue_size * 4;
};
// extruder_id is currently used to correctly calculate filament load / unload times into the total print time.
// This is currently only really used by the MK3 MMU2:
// extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
bool extruder_unloaded;
// whether or not to export post-process the gcode to export lines M73 in it
bool export_remaining_time_enabled;
// allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope()
bool machine_envelope_processing_enabled;
MachineEnvelopeConfig machine_limits;
// Additional load / unload times for a filament exchange sequence.
std::vector<float> filament_load_times;
std::vector<float> filament_unload_times;
std::array<TimeMachine, static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines;
void reset();
// post process the file with the given filename to add remaining time lines M73
void post_process(const std::string& filename);
};
public:
struct MoveVertex
{
EMoveType type{ EMoveType::Noop };
ExtrusionRole extrusion_role{ erNone };
unsigned char extruder_id{ 0 };
unsigned char cp_color_id{ 0 };
Vec3f position{ Vec3f::Zero() }; // mm
float delta_extruder{ 0.0f }; // mm
float feedrate{ 0.0f }; // mm/s
float width{ 0.0f }; // mm
float height{ 0.0f }; // mm
float mm3_per_mm{ 0.0f };
float fan_speed{ 0.0f }; // percentage
float time{ 0.0f }; // s
float volumetric_rate() const { return feedrate * mm3_per_mm; }
};
struct Result
{
unsigned int id;
std::vector<MoveVertex> moves;
Pointfs bed_shape;
std::string printer_settings_id;
std::vector<std::string> extruder_colors;
PrintEstimatedTimeStatistics time_statistics;
#if ENABLE_GCODE_VIEWER_STATISTICS
long long time{ 0 };
void reset()
{
time = 0;
moves = std::vector<MoveVertex>();
bed_shape = Pointfs();
extruder_colors = std::vector<std::string>();
}
#else
void reset()
{
moves = std::vector<MoveVertex>();
bed_shape = Pointfs();
extruder_colors = std::vector<std::string>();
}
#endif // ENABLE_GCODE_VIEWER_STATISTICS
};
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
struct DataChecker
{
struct Error
{
float value;
float tag_value;
ExtrusionRole role;
};
std::string type;
float threshold{ 0.01f };
float last_tag_value{ 0.0f };
unsigned int count{ 0 };
std::vector<Error> errors;
DataChecker(const std::string& type, float threshold)
: type(type), threshold(threshold)
{}
void update(float value, ExtrusionRole role) {
++count;
if (last_tag_value != 0.0f) {
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
errors.push_back({ value, last_tag_value, role });
}
}
void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; }
std::pair<float, float> get_min() const {
float delta_min = FLT_MAX;
float perc_min = 0.0f;
for (const Error& e : errors) {
if (delta_min > e.value - e.tag_value) {
delta_min = e.value - e.tag_value;
perc_min = 100.0f * delta_min / e.tag_value;
}
}
return { delta_min, perc_min };
}
std::pair<float, float> get_max() const {
float delta_max = -FLT_MAX;
float perc_max = 0.0f;
for (const Error& e : errors) {
if (delta_max < e.value - e.tag_value) {
delta_max = e.value - e.tag_value;
perc_max = 100.0f * delta_max / e.tag_value;
}
}
return { delta_max, perc_max };
}
void output() const {
if (!errors.empty()) {
std::cout << type << ":\n";
std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n";
auto [min, perc_min] = get_min();
auto [max, perc_max] = get_max();
std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n";
}
}
};
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
private:
GCodeReader m_parser;
EUnits m_units;
EPositioningType m_global_positioning_type;
EPositioningType m_e_local_positioning_type;
std::vector<Vec3f> m_extruder_offsets;
GCodeFlavor m_flavor;
AxisCoords m_start_position; // mm
AxisCoords m_end_position; // mm
AxisCoords m_origin; // mm
CachedPosition m_cached_position;
float m_feedrate; // mm/s
float m_width; // mm
float m_height; // mm
float m_mm3_per_mm;
float m_fan_speed; // percentage
ExtrusionRole m_extrusion_role;
unsigned char m_extruder_id;
ExtruderColors m_extruder_colors;
std::vector<float> m_filament_diameters;
float m_extruded_last_z;
unsigned int m_layer_id;
CpColor m_cp_color;
enum class EProducer
{
Unknown,
PrusaSlicer,
Cura,
Simplify3D,
CraftWare,
ideaMaker
};
static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> Producers;
EProducer m_producer;
bool m_producers_enabled;
TimeProcessor m_time_processor;
Result m_result;
static unsigned int s_result_id;
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f };
DataChecker m_height_compare{ "height", 0.01f };
DataChecker m_width_compare{ "width", 0.01f };
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
public:
GCodeProcessor();
void apply_config(const PrintConfig& config);
void apply_config(const DynamicPrintConfig& config);
void enable_stealth_time_estimator(bool enabled);
bool is_stealth_time_estimator_enabled() const {
return m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled;
}
void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; }
void enable_producers(bool enabled) { m_producers_enabled = enabled; }
void reset();
const Result& get_result() const { return m_result; }
Result&& extract_result() { return std::move(m_result); }
// Process the gcode contained in the file with the given filename
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void process_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const;
std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const;
std::vector<std::pair<EMoveType, float>> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
std::vector<std::pair<ExtrusionRole, float>> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
std::vector<float> get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const;
private:
void process_gcode_line(const GCodeReader::GCodeLine& line);
// Process tags embedded into comments
void process_tags(const std::string& comment);
bool process_producers_tags(const std::string& comment);
bool process_prusaslicer_tags(const std::string& comment);
bool process_cura_tags(const std::string& comment);
bool process_simplify3d_tags(const std::string& comment);
bool process_craftware_tags(const std::string& comment);
bool process_ideamaker_tags(const std::string& comment);
bool detect_producer(const std::string& comment);
// Move
void process_G0(const GCodeReader::GCodeLine& line);
void process_G1(const GCodeReader::GCodeLine& line);
// Retract
void process_G10(const GCodeReader::GCodeLine& line);
// Unretract
void process_G11(const GCodeReader::GCodeLine& line);
// Set Units to Inches
void process_G20(const GCodeReader::GCodeLine& line);
// Set Units to Millimeters
void process_G21(const GCodeReader::GCodeLine& line);
// Firmware controlled Retract
void process_G22(const GCodeReader::GCodeLine& line);
// Firmware controlled Unretract
void process_G23(const GCodeReader::GCodeLine& line);
// Set to Absolute Positioning
void process_G90(const GCodeReader::GCodeLine& line);
// Set to Relative Positioning
void process_G91(const GCodeReader::GCodeLine& line);
// Set Position
void process_G92(const GCodeReader::GCodeLine& line);
// Sleep or Conditional stop
void process_M1(const GCodeReader::GCodeLine& line);
// Set extruder to absolute mode
void process_M82(const GCodeReader::GCodeLine& line);
// Set extruder to relative mode
void process_M83(const GCodeReader::GCodeLine& line);
// Set fan speed
void process_M106(const GCodeReader::GCodeLine& line);
// Disable fan
void process_M107(const GCodeReader::GCodeLine& line);
// Set tool (Sailfish)
void process_M108(const GCodeReader::GCodeLine& line);
// Recall stored home offsets
void process_M132(const GCodeReader::GCodeLine& line);
// Set tool (MakerWare)
void process_M135(const GCodeReader::GCodeLine& line);
// Set max printing acceleration
void process_M201(const GCodeReader::GCodeLine& line);
// Set maximum feedrate
void process_M203(const GCodeReader::GCodeLine& line);
// Set default acceleration
void process_M204(const GCodeReader::GCodeLine& line);
// Advanced settings
void process_M205(const GCodeReader::GCodeLine& line);
// Set extrude factor override percentage
void process_M221(const GCodeReader::GCodeLine& line);
// Repetier: Store x, y and z position
void process_M401(const GCodeReader::GCodeLine& line);
// Repetier: Go to stored position
void process_M402(const GCodeReader::GCodeLine& line);
// Set allowable instantaneous speed change
void process_M566(const GCodeReader::GCodeLine& line);
// Unload the current filament into the MK3 MMU2 unit at the end of print.
void process_M702(const GCodeReader::GCodeLine& line);
// Processes T line (Select Tool)
void process_T(const GCodeReader::GCodeLine& line);
void process_T(const std::string& command);
void store_move_vertex(EMoveType type);
float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const;
float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const;
float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const;
float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const;
float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const;
void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value);
float get_filament_load_time(size_t extruder_id);
float get_filament_unload_time(size_t extruder_id);
void process_custom_gcode_time(CustomGCode::Type code);
// Simulates firmware st_synchronize() call
void simulate_st_synchronize(float additional_time = 0.0f);
void update_estimated_times_stats();
};
} /* namespace Slic3r */
#endif // ENABLE_GCODE_VIEWER
#endif /* slic3r_GCodeProcessor_hpp_ */

View file

@ -79,7 +79,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line)
if (! ::CreateProcessW(
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
throw std::runtime_error(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
::WaitForSingleObject(process_info.hProcess, INFINITE);
ULONG rc = 0;
::GetExitCodeProcess(process_info.hProcess, &rc);
@ -98,13 +98,13 @@ static int run_script(const std::string &script, const std::string &gcode, std::
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
if (szArglist == nullptr || nArgs <= 0) {
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
throw std::runtime_error(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
}
std::wstring command_line;
std::wstring command = szArglist[0];
if (! boost::filesystem::exists(boost::filesystem::path(command)))
throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
if (boost::iends_with(command, L".pl")) {
// This is a perl script. Run it through the perl interpreter.
// The current process may be slic3r.exe or slic3r-console.exe.
@ -115,7 +115,7 @@ static int run_script(const std::string &script, const std::string &gcode, std::
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
if (! boost::filesystem::exists(path_perl)) {
LocalFree(szArglist);
throw std::runtime_error(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
}
// Replace it with the current perl interpreter.
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
@ -187,7 +187,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
config.setenv_();
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw std::runtime_error(std::string("Post-processor can't find exported gcode file"));
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
for (const std::string &scripts : config.post_process.values) {
std::vector<std::string> lines;
@ -205,7 +205,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw std::runtime_error(msg);
throw Slic3r::RuntimeError(msg);
}
}
}

View file

@ -148,7 +148,7 @@ static inline int parse_int(const char *&line)
char *endptr = NULL;
long result = strtol(line, &endptr, 10);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("PressureEqualizer: Error parsing an int");
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int");
line = endptr;
return int(result);
};
@ -160,7 +160,7 @@ static inline float parse_float(const char *&line)
char *endptr = NULL;
float result = strtof(line, &endptr);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("PressureEqualizer: Error parsing a float");
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float");
line = endptr;
return result;
};
@ -229,7 +229,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
assert(false);
}
if (i == -1)
throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
buf.pos_provided[i] = true;
new_pos[i] = parse_float(line);
if (i == 3 && m_config->use_relative_e_distances.value)
@ -298,7 +298,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi
set = true;
break;
default:
throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
}
eatws(line);
}

View file

@ -5,6 +5,8 @@
#include <boost/format.hpp>
#if !ENABLE_GCODE_VIEWER
//! macro used to mark string used at localization,
#define L(s) (s)
@ -516,3 +518,5 @@ Color operator * (float f, const Color& color)
}
} // namespace Slic3r
#endif // !ENABLE_GCODE_VIEWER

View file

@ -1,6 +1,8 @@
#ifndef slic3r_GCode_PreviewData_hpp_
#define slic3r_GCode_PreviewData_hpp_
#if !ENABLE_GCODE_VIEWER
#include "../libslic3r.h"
#include "../ExtrusionEntity.hpp"
#include "../Point.hpp"
@ -56,8 +58,7 @@ public:
// Color mapping to convert a float into a smooth rainbow of 10 colors.
class RangeBase
{
public:
public:
virtual void reset() = 0;
virtual bool empty() const = 0;
virtual float min() const = 0;
@ -73,7 +74,7 @@ public:
// Color mapping converting a float in a range between a min and a max into a smooth rainbow of 10 colors.
class Range : public RangeBase
{
public:
public:
Range();
// RangeBase Overrides
@ -97,8 +98,7 @@ public:
template <typename EnumRangeType>
class MultiRange : public RangeBase
{
public:
public:
void reset() override
{
bounds = decltype(bounds){};
@ -160,8 +160,7 @@ public:
mode.set(static_cast<std::size_t>(range_type_value), enable);
}
private:
private:
// Interval bounds
struct Bounds
{
@ -394,4 +393,6 @@ public:
} // namespace Slic3r
#endif // !ENABLE_GCODE_VIEWER
#endif /* slic3r_GCode_PreviewData_hpp_ */

View file

@ -94,7 +94,7 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
if (extrusion_entity_collection != nullptr)
return extrusionentity_extents(*extrusion_entity_collection);
throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()");
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()");
return BoundingBoxf();
}

View file

@ -21,7 +21,11 @@ TODO LIST
#include <vector>
#include <numeric>
#if ENABLE_GCODE_VIEWER
#include "GCodeProcessor.hpp"
#else
#include "Analyzer.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "BoundingBox.hpp"
#if defined(__linux) || defined(__GNUC__ )
@ -47,36 +51,69 @@ public:
m_extrusion_flow(0.f),
m_preview_suppressed(false),
m_elapsed_time(0.f),
#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
m_default_analyzer_line_width(line_width),
#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
m_gcode_flavor(flavor),
m_filpar(filament_parameters)
{
// adds tag for analyzer:
char buf[64];
#if ENABLE_GCODE_VIEWER
sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
m_gcode += buf;
sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str());
m_gcode += buf;
#else
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
m_gcode += buf;
sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
m_gcode += buf;
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
change_analyzer_line_width(line_width);
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
}
WipeTowerWriter& change_analyzer_line_width(float line_width) {
// adds tag for analyzer:
char buf[64];
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width);
m_gcode += buf;
return *this;
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& change_analyzer_line_width(float line_width) {
// adds tag for analyzer:
char buf[64];
sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width);
m_gcode += buf;
return *this;
}
WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
// adds tag for analyzer:
char buf[64];
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
m_gcode += buf;
return *this;
WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
// adds tag for processor:
char buf[64];
sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
m_gcode += buf;
return *this;
}
#else
#if !ENABLE_GCODE_VIEWER
WipeTowerWriter& change_analyzer_line_width(float line_width) {
// adds tag for analyzer:
char buf[64];
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width);
m_gcode += buf;
return *this;
}
WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) {
static const float area = float(M_PI) * 1.75f * 1.75f / 4.f;
float mm3_per_mm = (len == 0.f ? 0.f : area * e / len);
// adds tag for analyzer:
char buf[64];
sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm);
m_gcode += buf;
return *this;
}
#endif // !ENABLE_GCODE_VIEWER
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
m_wipe_tower_width = width;
@ -111,8 +148,13 @@ public:
// Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
// filament loading and cooling moves from normal extrusion moves. Therefore the writer
// is asked to suppres output of some lines, which look like extrusions.
WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
#else
WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; }
WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; }
#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
WipeTowerWriter& feedrate(float f)
{
@ -149,8 +191,14 @@ public:
Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go
if (! m_preview_suppressed && e > 0.f && len > 0.f) {
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
change_analyzer_mm3_per_mm(len, e);
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
#else
#if !ENABLE_GCODE_VIEWER
change_analyzer_mm3_per_mm(len, e);
#endif // !ENABLE_GCODE_VIEWER
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
// This is left zero if it is a travel move.
float width = e * m_filpar[0].filament_area / (len * m_layer_height);
// Correct for the roundings of a squished extrusion.
@ -411,7 +459,9 @@ private:
float m_wipe_tower_depth = 0.f;
unsigned m_last_fan_speed = 0;
int current_temp = -1;
#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
const float m_default_analyzer_line_width;
#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING
float m_used_filament_length = 0.f;
GCodeFlavor m_gcode_flavor;
const std::vector<WipeTower::FilamentParameters>& m_filpar;
@ -852,8 +902,12 @@ void WipeTower::toolchange_Unload(
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
writer.append("; CP TOOLCHANGE UNLOAD\n")
.change_analyzer_line_width(line_width);
.change_analyzer_line_width(line_width);
#else
writer.append("; CP TOOLCHANGE UNLOAD\n");
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
unsigned i = 0; // iterates through ramming_speed
m_left_to_right = true; // current direction of ramming
@ -930,7 +984,9 @@ void WipeTower::toolchange_Unload(
}
}
Vec2f end_of_ramming(writer.x(),writer.y());
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// Retraction:
float old_x = writer.x();

View file

@ -1,6 +1,9 @@
#include "GCodeReader.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#if ENABLE_GCODE_VIEWER
#include <boost/nowide/fstream.hpp>
#endif // ENABLE_GCODE_VIEWER
#include <fstream>
#include <iostream>
#include <iomanip>
@ -113,9 +116,18 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
void GCodeReader::parse_file(const std::string &file, callback_t callback)
{
#if ENABLE_GCODE_VIEWER
boost::nowide::ifstream f(file);
#else
std::ifstream f(file);
#endif // ENABLE_GCODE_VIEWER
std::string line;
#if ENABLE_GCODE_VIEWER
m_parsing_file = true;
while (m_parsing_file && std::getline(f, line))
#else
while (std::getline(f, line))
#endif // ENABLE_GCODE_VIEWER
this->parse_line(line, callback);
}

View file

@ -107,6 +107,9 @@ public:
{ GCodeLine gline; this->parse_line(line.c_str(), gline, callback); }
void parse_file(const std::string &file, callback_t callback);
#if ENABLE_GCODE_VIEWER
void quit_parsing_file() { m_parsing_file = false; }
#endif // ENABLE_GCODE_VIEWER
float& x() { return m_position[X]; }
float x() const { return m_position[X]; }
@ -145,6 +148,9 @@ private:
char m_extrusion_axis;
float m_position[NUM_AXES];
bool m_verbose;
#if ENABLE_GCODE_VIEWER
bool m_parsing_file{ false };
#endif // ENABLE_GCODE_VIEWER
};
} /* namespace Slic3r */

View file

@ -153,7 +153,7 @@ GCodeSender::set_baud_rate(unsigned int baud_rate)
if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
printf("Failed to set baud rate: %s\n", strerror(errno));
#else
//throw invalid_argument ("OS does not currently support custom bauds");
//throw Slic3r::InvalidArgument("OS does not currently support custom bauds");
#endif
}
}

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "GCodeTimeEstimator.hpp"
#include "Utils.hpp"
#include <boost/bind.hpp>
@ -9,6 +10,8 @@
#include <boost/nowide/cstdio.hpp>
#include <boost/algorithm/string/predicate.hpp>
#if !ENABLE_GCODE_VIEWER
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float MILLISEC_TO_SEC = 0.001f;
static const float INCHES_TO_MM = 25.4f;
@ -252,13 +255,13 @@ namespace Slic3r {
{
boost::nowide::ifstream in(filename);
if (!in.good())
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
std::string path_tmp = filename + ".postprocess";
FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb");
if (out == nullptr)
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
std::string normal_time_mask = "M73 P%s R%s\n";
std::string silent_time_mask = "M73 Q%s S%s\n";
@ -276,7 +279,7 @@ namespace Slic3r {
in.close();
fclose(out);
boost::nowide::remove(path_tmp.c_str());
throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
}
export_line.clear();
};
@ -324,7 +327,7 @@ namespace Slic3r {
if (!in.good())
{
fclose(out);
throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
}
// check tags
@ -381,7 +384,7 @@ namespace Slic3r {
in.close();
if (rename_file(path_tmp, filename))
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
"Is " + path_tmp + " locked?" + '\n');
return true;
@ -1672,3 +1675,5 @@ namespace Slic3r {
}
#endif // ENABLE_MOVE_STATS
}
#endif // !ENABLE_GCODE_VIEWER

View file

@ -6,6 +6,8 @@
#include "GCodeReader.hpp"
#include "CustomGCode.hpp"
#if !ENABLE_GCODE_VIEWER
#define ENABLE_MOVE_STATS 0
namespace Slic3r {
@ -481,4 +483,6 @@ namespace Slic3r {
} /* namespace Slic3r */
#endif // !ENABLE_GCODE_VIEWER
#endif /* slic3r_GCodeTimeEstimator_hpp_ */

View file

@ -1,4 +1,5 @@
#include "libslic3r.h"
#include "Exception.hpp"
#include "Geometry.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
@ -471,7 +472,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo
size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0)));
size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1)));
if (num_parts > cellw * cellh)
throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts);
throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts);
// Get a bounding box of cellw x cellh cells, centered at the center of the bed.
Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);

View file

@ -13,6 +13,10 @@ class Layer;
class PrintRegion;
class PrintObject;
namespace FillAdaptive_Internal {
struct Octree;
};
class LayerRegion
{
public:
@ -134,7 +138,8 @@ public:
return false;
}
void make_perimeters();
void make_fills();
void make_fills() { this->make_fills(nullptr, nullptr); };
void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree);
void make_ironing();
void export_region_slices_to_svg(const char *path) const;

View file

@ -33,24 +33,6 @@ bool Line::intersection_infinite(const Line &other, Point* point) const
return true;
}
// Distance to the closest point of line.
double Line::distance_to_squared(const Point &point, const Point &a, const Point &b)
{
const Vec2d v = (b - a).cast<double>();
const Vec2d va = (point - a).cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt
if (l2 == 0.0)
// a == b case
return va.squaredNorm();
// Consider the line extending the segment, parameterized as a + t (b - a).
// We find projection of this point onto the line.
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
const double t = va.dot(v) / l2;
if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment
else if (t > 1.0) return (point - b).cast<double>().squaredNorm(); // beyond the 'b' end of the segment
return (t * v - va).squaredNorm();
}
double Line::perp_distance_to(const Point &point) const
{
const Line &line = *this;

View file

@ -18,6 +18,35 @@ typedef std::vector<ThickLine> ThickLines;
Linef3 transform(const Linef3& line, const Transform3d& t);
namespace line_alg {
// Distance to the closest point of line.
template<class L, class T, int N>
double distance_to_squared(const L &line, const Vec<N, T> &point)
{
const Vec<N, double> v = line.vector().template cast<double>();
const Vec<N, double> va = (point - line.a).template cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt
if (l2 == 0.0)
// a == b case
return va.squaredNorm();
// Consider the line extending the segment, parameterized as a + t (b - a).
// We find projection of this point onto the line.
// It falls where t = [(this-a) . (b-a)] / |b-a|^2
const double t = va.dot(v) / l2;
if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment
else if (t > 1.0) return (point - line.b).template cast<double>().squaredNorm(); // beyond the 'b' end of the segment
return (t * v - va).squaredNorm();
}
template<class L, class T, int N>
double distance_to(const L &line, const Vec<N, T> &point)
{
return std::sqrt(distance_to_squared(line, point));
}
} // namespace line_alg
class Line
{
public:
@ -47,7 +76,7 @@ public:
// 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 inline double distance_to_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_squared(Line{a, b}, Vec<2, coord_t>{point}); }
static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); }
Point a;

View file

@ -114,15 +114,6 @@ template<class T> struct remove_cvref
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
template<class T, class I, class... Args> // Arbitrary allocator can be used
inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
{
std::vector<T, Args...> ret;
if (capacity > I(0)) ret.reserve(size_t(capacity));
return ret;
}
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
template<class T, class I, class = IntegerOnly<I>>
inline std::vector<T> linspace_vector(const ArithmeticOnly<T> &start,

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "MeshBoolean.hpp"
#include "libslic3r/TriangleMesh.hpp"
#undef PI
@ -136,7 +137,7 @@ template<class _Mesh> void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &o
if(CGAL::is_closed(out))
CGALProc::orient_to_bound_a_volume(out);
else
std::runtime_error("Mesh not watertight");
throw Slic3r::RuntimeError("Mesh not watertight");
}
inline Vec3d to_vec3d(const _EpicMesh::Point &v)
@ -222,7 +223,7 @@ template<class Op> void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B)
}
if (! success)
throw std::runtime_error("CGAL mesh boolean operation failed.");
throw Slic3r::RuntimeError("CGAL mesh boolean operation failed.");
}
void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); }

View file

@ -1,7 +1,9 @@
#include "Exception.hpp"
#include "Model.hpp"
#include "ModelArrange.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
#include "TriangleSelector.hpp"
#include "Format/AMF.hpp"
#include "Format/OBJ.hpp"
@ -20,7 +22,9 @@
#include "SVG.hpp"
#include <Eigen/Dense>
#include "GCodeWriter.hpp"
#if !ENABLE_GCODE_VIEWER
#include "GCode/PreviewData.hpp"
#endif // !ENABLE_GCODE_VIEWER
namespace Slic3r {
@ -113,13 +117,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
else if (boost::algorithm::iends_with(input_file, ".prusa"))
result = load_prus(input_file.c_str(), &model);
else
throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
if (! result)
throw std::runtime_error("Loading of a model file failed.");
throw Slic3r::RuntimeError("Loading of a model file failed.");
if (model.objects.empty())
throw std::runtime_error("The supplied file couldn't be read because it's empty");
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects)
o->input_file = input_file;
@ -143,13 +147,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
result = load_amf(input_file.c_str(), config, &model, check_version);
else
throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension.");
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
if (!result)
throw std::runtime_error("Loading of a model file failed.");
throw Slic3r::RuntimeError("Loading of a model file failed.");
if (model.objects.empty())
throw std::runtime_error("The supplied file couldn't be read because it's empty");
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects)
{
@ -814,7 +818,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
m_raw_bounding_box_valid = true;
m_raw_bounding_box.reset();
if (this->instances.empty())
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances");
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
for (const ModelVolume *v : this->volumes)
@ -1006,6 +1010,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
for (ModelVolume* volume : volumes)
{
volume->m_supported_facets.clear();
volume->m_seam_facets.clear();
if (!volume->mesh().empty()) {
TriangleMesh mesh(volume->mesh());
mesh.require_shared_vertices();
@ -1111,6 +1116,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
const auto volume_matrix = volume->get_matrix();
volume->m_supported_facets.clear();
volume->m_seam_facets.clear();
if (! volume->is_model_part()) {
// Modifiers are not cut, but we still need to add the instance transformation
@ -1830,28 +1836,25 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
}
std::vector<int> FacetsAnnotation::get_facets(FacetSupportType type) const
indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const
{
std::vector<int> out;
for (auto& [facet_idx, this_type] : m_data)
if (this_type == type)
out.push_back(facet_idx);
TriangleSelector selector(mv.mesh());
selector.deserialize(m_data);
indexed_triangle_set out = selector.get_facets(type);
return out;
}
void FacetsAnnotation::set_facet(int idx, FacetSupportType type)
bool FacetsAnnotation::set(const TriangleSelector& selector)
{
bool changed = true;
if (type == FacetSupportType::NONE)
changed = m_data.erase(idx) != 0;
else
m_data[idx] = type;
if (changed)
std::map<int, std::vector<bool>> sel_map = selector.serialize();
if (sel_map != m_data) {
m_data = sel_map;
update_timestamp();
return true;
}
return false;
}
@ -1864,6 +1867,64 @@ void FacetsAnnotation::clear()
// Following function takes data from a triangle and encodes it as string
// of hexadecimal numbers (one digit per triangle). Used for 3MF export,
// changing it may break backwards compatibility !!!!!
std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const
{
std::string out;
auto triangle_it = m_data.find(triangle_idx);
if (triangle_it != m_data.end()) {
const std::vector<bool>& code = triangle_it->second;
int offset = 0;
while (offset < int(code.size())) {
int next_code = 0;
for (int i=3; i>=0; --i) {
next_code = next_code << 1;
next_code |= int(code[offset + i]);
}
offset += 4;
assert(next_code >=0 && next_code <= 15);
char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A';
out.insert(out.begin(), digit);
}
}
return out;
}
// Recover triangle splitting & state from string of hexadecimal values previously
// generated by get_triangle_as_string. Used to load from 3MF.
void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str)
{
assert(! str.empty());
m_data[triangle_id] = std::vector<bool>(); // zero current state or create new
std::vector<bool>& code = m_data[triangle_id];
for (auto it = str.crbegin(); it != str.crend(); ++it) {
const char ch = *it;
int dec = 0;
if (ch >= '0' && ch<='9')
dec = int(ch - '0');
else if (ch >='A' && ch <= 'F')
dec = 10 + int(ch - 'A');
else
assert(false);
// Convert to binary and append into code.
for (int i=0; i<4; ++i) {
code.insert(code.end(), bool(dec & (1 << i)));
}
}
}
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
bool model_object_list_equal(const Model &model_old, const Model &model_new)
@ -1935,7 +1996,17 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
return true;
}
return false;
};
}
bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
assert(mo.volumes.size() == mo_new.volumes.size());
for (size_t i=0; i<mo.volumes.size(); ++i) {
if (! mo_new.volumes[i]->m_seam_facets.is_same_as(mo.volumes[i]->m_seam_facets))
return true;
}
return false;
}
extern bool model_has_multi_part_objects(const Model &model)
{

View file

@ -39,6 +39,7 @@ class ModelVolume;
class ModelWipeTower;
class Print;
class SLAPrint;
class TriangleSelector;
namespace UndoRedo {
class StackImpl;
@ -393,7 +394,8 @@ enum class ModelVolumeType : int {
SUPPORT_BLOCKER,
};
enum class FacetSupportType : int8_t {
enum class EnforcerBlockerType : int8_t {
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
NONE = 0,
ENFORCER = 1,
BLOCKER = 2
@ -403,9 +405,12 @@ class FacetsAnnotation {
public:
using ClockType = std::chrono::steady_clock;
std::vector<int> get_facets(FacetSupportType type) const;
void set_facet(int idx, FacetSupportType type);
const std::map<int, std::vector<bool>>& get_data() const { return m_data; }
bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
void clear();
std::string get_triangle_as_string(int i) const;
void set_triangle_from_string(int triangle_id, const std::string& str);
ClockType::time_point get_timestamp() const { return timestamp; }
bool is_same_as(const FacetsAnnotation& other) const {
@ -418,7 +423,7 @@ public:
}
private:
std::map<int, FacetSupportType> m_data;
std::map<int, std::vector<bool>> m_data;
ClockType::time_point timestamp;
void update_timestamp() {
@ -459,6 +464,9 @@ public:
// List of mesh facets to be supported/unsupported.
FacetsAnnotation m_supported_facets;
// List of seam enforcers/blockers.
FacetsAnnotation m_seam_facets;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
@ -588,7 +596,7 @@ private:
ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
m_supported_facets(other.m_supported_facets)
m_supported_facets(other.m_supported_facets), m_seam_facets(other.m_seam_facets)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
@ -607,6 +615,7 @@ private:
assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
m_supported_facets.clear();
m_seam_facets.clear();
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@ -620,7 +629,7 @@ private:
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation,
m_is_splittable, has_convex_hull, m_supported_facets);
m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
@ -634,7 +643,7 @@ private:
template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation,
m_is_splittable, has_convex_hull, m_supported_facets);
m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
@ -899,6 +908,10 @@ extern bool model_volume_list_changed(const ModelObject &model_object_old, const
// The function assumes that volumes list is synchronized.
extern bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// Test whether the now ModelObject has newer custom seam data than the old one.
// The function assumes that volumes list is synchronized.
extern bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
extern bool model_has_multi_part_objects(const Model &model);

View file

@ -20,7 +20,7 @@ using VirtualBedFn = std::function<void(arrangement::ArrangePolygon&)>;
[[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&)
{
throw std::runtime_error("Objects could not fit on the bed");
throw Slic3r::RuntimeError("Objects could not fit on the bed");
}
ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances);

View file

@ -42,6 +42,8 @@ private:
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
// Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject
// with their corresponding Print / PrintObject objects by the notification center at the UI when processing back-end warnings.
// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances
// are only instantiated from the main thread.
class ObjectBase

View file

@ -2,6 +2,7 @@
#include "OpenVDBUtils.hpp"
#include <openvdb/tools/MeshToVolume.h>
#include <openvdb/tools/VolumeToMesh.h>
#include <openvdb/tools/Composite.h>
#include <openvdb/tools/LevelSetRebuild.h>
//#include "MTUtils.hpp"
@ -57,17 +58,42 @@ void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
// TODO: Do I need to call initialize? Seems to work without it as well but the
// docs say it should be called ones. It does a mutex lock-unlock sequence all
// even if was called previously.
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
const openvdb::math::Transform &tr,
float exteriorBandWidth,
float interiorBandWidth,
int flags)
{
// openvdb::initialize();
// return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
// TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
// interiorBandWidth, flags);
openvdb::initialize();
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
interiorBandWidth, flags);
TriangleMeshPtrs meshparts = mesh.split();
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](TriangleMesh *m){
m->require_shared_vertices();
return !m->is_manifold() || m->volume() < EPSILON;
});
meshparts.erase(it, meshparts.end());
openvdb::FloatGrid::Ptr grid;
for (TriangleMesh *m : meshparts) {
auto gridptr = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
interiorBandWidth, flags);
if (grid && gridptr) openvdb::tools::csgUnion(*grid, *gridptr);
else if (gridptr) grid = std::move(gridptr);
}
grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, interiorBandWidth);
return grid;
}
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,

View file

@ -2,7 +2,6 @@
#define OPENVDBUTILS_HPP
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
#include <openvdb/openvdb.h>

View file

@ -0,0 +1,140 @@
#ifndef BRUTEFORCEOPTIMIZER_HPP
#define BRUTEFORCEOPTIMIZER_HPP
#include <libslic3r/Optimize/Optimizer.hpp>
namespace Slic3r { namespace opt {
namespace detail {
// Implementing a bruteforce optimizer
// Return the number of iterations needed to reach a specific grid position (idx)
template<size_t N>
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
{
long ret = 0;
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
return ret;
}
// Implementation of a grid search where the search interval is sampled in
// equidistant points for each dimension. Grid size determines the number of
// samples for one dimension so the number of function calls is gridsize ^ dimension.
struct AlgBurteForce {
bool to_min;
StopCriteria stc;
size_t gridsz;
AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {}
// This function is called recursively for each dimension and generates
// the grid values for the particular dimension. If D is less than zero,
// the object function input values are generated for each dimension and it
// can be evaluated. The current best score is compared with the newly
// returned score and changed appropriately.
template<int D, size_t N, class Fn, class Cmp>
bool run(std::array<size_t, N> &idx,
Result<N> &result,
const Bounds<N> &bounds,
Fn &&fn,
Cmp &&cmp)
{
if (stc.stop_condition()) return false;
if constexpr (D < 0) { // Let's evaluate fn
Input<N> inp;
auto max_iter = stc.max_iterations();
if (max_iter && num_iter(idx, gridsz) >= max_iter)
return false;
for (size_t d = 0; d < N; ++d) {
const Bound &b = bounds[d];
double step = (b.max() - b.min()) / (gridsz - 1);
inp[d] = b.min() + idx[d] * step;
}
auto score = fn(inp);
if (cmp(score, result.score)) { // Change current score to the new
double absdiff = std::abs(score - result.score);
result.score = score;
result.optimum = inp;
// Check if the required precision is reached.
if (absdiff < stc.abs_score_diff() ||
absdiff < stc.rel_score_diff() * std::abs(score))
return false;
}
} else {
for (size_t i = 0; i < gridsz; ++i) {
idx[D] = i; // Mark the current grid position and dig down
if (!run<D - 1>(idx, result, bounds, std::forward<Fn>(fn),
std::forward<Cmp>(cmp)))
return false;
}
}
return true;
}
template<class Fn, size_t N>
Result<N> optimize(Fn&& fn,
const Input<N> &/*initvals*/,
const Bounds<N>& bounds)
{
std::array<size_t, N> idx = {};
Result<N> result;
if (to_min) {
result.score = std::numeric_limits<double>::max();
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
std::less<double>{});
}
else {
result.score = std::numeric_limits<double>::lowest();
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
std::greater<double>{});
}
return result;
}
};
} // namespace detail
using AlgBruteForce = detail::AlgBurteForce;
template<>
class Optimizer<AlgBruteForce> {
AlgBruteForce m_alg;
public:
Optimizer(const StopCriteria &cr = {}, size_t gridsz = 100)
: m_alg{cr, gridsz}
{}
Optimizer& to_max() { m_alg.to_min = false; return *this; }
Optimizer& to_min() { m_alg.to_min = true; return *this; }
template<class Func, size_t N>
Result<N> optimize(Func&& func,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
return m_alg.optimize(std::forward<Func>(func), initvals, bounds);
}
Optimizer &set_criteria(const StopCriteria &cr)
{
m_alg.stc = cr; return *this;
}
const StopCriteria &get_criteria() const { return m_alg.stc; }
};
}} // namespace Slic3r::opt
#endif // BRUTEFORCEOPTIMIZER_HPP

View file

@ -0,0 +1,233 @@
#ifndef NLOPTOPTIMIZER_HPP
#define NLOPTOPTIMIZER_HPP
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
#include <nlopt.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <utility>
#include <libslic3r/Optimize/Optimizer.hpp>
namespace Slic3r { namespace opt {
namespace detail {
// Helper types for NLopt algorithm selection in template contexts
template<nlopt_algorithm alg> struct NLoptAlg {};
// NLopt can combine multiple algorithms if one is global an other is a local
// method. This is how template specializations can be informed about this fact.
template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD>
struct NLoptAlgComb {};
template<class M> struct IsNLoptAlg {
static const constexpr bool value = false;
};
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> {
static const constexpr bool value = true;
};
template<nlopt_algorithm a1, nlopt_algorithm a2>
struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
static const constexpr bool value = true;
};
template<class M, class T = void>
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
enum class OptDir { MIN, MAX }; // Where to optimize
struct NLopt { // Helper RAII class for nlopt_opt
nlopt_opt ptr = nullptr;
template<class...A> explicit NLopt(A&&...a)
{
ptr = nlopt_create(std::forward<A>(a)...);
}
NLopt(const NLopt&) = delete;
NLopt(NLopt&&) = delete;
NLopt& operator=(const NLopt&) = delete;
NLopt& operator=(NLopt&&) = delete;
~NLopt() { nlopt_destroy(ptr); }
};
template<class Method> class NLoptOpt {};
// Optimizers based on NLopt.
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
protected:
StopCriteria m_stopcr;
OptDir m_dir;
template<class Fn> using TOptData =
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
template<class Fn, size_t N>
static double optfunc(unsigned n, const double *params,
double *gradient,
void *data)
{
assert(n >= N);
auto tdata = static_cast<TOptData<Fn>*>(data);
if (std::get<1>(*tdata)->m_stopcr.stop_condition())
nlopt_force_stop(std::get<2>(*tdata));
auto fnptr = std::get<0>(*tdata);
auto funval = to_arr<N>(params);
double scoreval = 0.;
using RetT = decltype((*fnptr)(funval));
if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
ScoreGradient<N> score = (*fnptr)(funval);
for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
scoreval = score.score;
} else {
scoreval = (*fnptr)(funval);
}
return scoreval;
}
template<size_t N>
void set_up(NLopt &nl, const Bounds<N>& bounds)
{
std::array<double, N> lb, ub;
for (size_t i = 0; i < N; ++i) {
lb[i] = bounds[i].min();
ub[i] = bounds[i].max();
}
nlopt_set_lower_bounds(nl.ptr, lb.data());
nlopt_set_upper_bounds(nl.ptr, ub.data());
double abs_diff = m_stopcr.abs_score_diff();
double rel_diff = m_stopcr.rel_score_diff();
double stopval = m_stopcr.stop_score();
if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval);
if(this->m_stopcr.max_iterations() > 0)
nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations());
}
template<class Fn, size_t N>
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
{
Result<N> r;
TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
switch(m_dir) {
case OptDir::MIN:
nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break;
case OptDir::MAX:
nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break;
}
r.optimum = initvals;
r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score);
return r;
}
public:
template<class Func, size_t N>
Result<N> optimize(Func&& func,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
NLopt nl{alg, N};
set_up(nl, bounds);
return optimize(nl, std::forward<Func>(func), initvals);
}
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
void set_dir(OptDir dir) noexcept { m_dir = dir; }
void seed(long s) { nlopt_srand(s); }
};
template<nlopt_algorithm glob, nlopt_algorithm loc>
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
{
using Base = NLoptOpt<NLoptAlg<glob>>;
public:
template<class Fn, size_t N>
Result<N> optimize(Fn&& f,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
NLopt nl_glob{glob, N}, nl_loc{loc, N};
Base::set_up(nl_glob, bounds);
Base::set_up(nl_loc, bounds);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
}
explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
};
} // namespace detail;
// Optimizers based on NLopt.
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
detail::NLoptOpt<M> m_opt;
public:
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
template<class Func, size_t N>
Result<N> optimize(Func&& func,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
}
explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
Optimizer &set_criteria(const StopCriteria &cr)
{
m_opt.set_criteria(cr); return *this;
}
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
void seed(long s) { m_opt.seed(s); }
};
// Predefinded NLopt algorithms
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
}} // namespace Slic3r::opt
#endif // NLOPTOPTIMIZER_HPP

View file

@ -0,0 +1,182 @@
#ifndef OPTIMIZER_HPP
#define OPTIMIZER_HPP
#include <utility>
#include <tuple>
#include <array>
#include <cmath>
#include <functional>
#include <limits>
#include <cassert>
namespace Slic3r { namespace opt {
// A type to hold the complete result of the optimization.
template<size_t N> struct Result {
int resultcode; // Method dependent
std::array<double, N> optimum;
double score;
};
// An interval of possible input values for optimization
class Bound {
double m_min, m_max;
public:
Bound(double min = std::numeric_limits<double>::min(),
double max = std::numeric_limits<double>::max())
: m_min(min), m_max(max)
{}
double min() const noexcept { return m_min; }
double max() const noexcept { return m_max; }
};
// Helper types for optimization function input and bounds
template<size_t N> using Input = std::array<double, N>;
template<size_t N> using Bounds = std::array<Bound, N>;
// A type for specifying the stop criteria. Setter methods can be concatenated
class StopCriteria {
// If the absolute value difference between two scores.
double m_abs_score_diff = std::nan("");
// If the relative value difference between two scores.
double m_rel_score_diff = std::nan("");
// Stop if this value or better is found.
double m_stop_score = std::nan("");
// A predicate that if evaluates to true, the optimization should terminate
// and the best result found prior to termination should be returned.
std::function<bool()> m_stop_condition = [] { return false; };
// The max allowed number of iterations.
unsigned m_max_iterations = 0;
public:
StopCriteria & abs_score_diff(double val)
{
m_abs_score_diff = val; return *this;
}
double abs_score_diff() const { return m_abs_score_diff; }
StopCriteria & rel_score_diff(double val)
{
m_rel_score_diff = val; return *this;
}
double rel_score_diff() const { return m_rel_score_diff; }
StopCriteria & stop_score(double val)
{
m_stop_score = val; return *this;
}
double stop_score() const { return m_stop_score; }
StopCriteria & max_iterations(double val)
{
m_max_iterations = val; return *this;
}
double max_iterations() const { return m_max_iterations; }
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
{
m_stop_condition = cond; return *this;
}
bool stop_condition() { return m_stop_condition(); }
};
// Helper class to use optimization methods involving gradient.
template<size_t N> struct ScoreGradient {
double score;
std::optional<std::array<double, N>> gradient;
ScoreGradient(double s, const std::array<double, N> &grad)
: score{s}, gradient{grad}
{}
};
// Helper to be used in static_assert.
template<class T> struct always_false { enum { value = false }; };
// Basic interface to optimizer object
template<class Method, class Enable = void> class Optimizer {
public:
Optimizer(const StopCriteria &)
{
static_assert (always_false<Method>::value,
"Optimizer unimplemented for given method!");
}
// Switch optimization towards function minimum
Optimizer &to_min() { return *this; }
// Switch optimization towards function maximum
Optimizer &to_max() { return *this; }
// Set criteria for successive optimizations
Optimizer &set_criteria(const StopCriteria &) { return *this; }
// Get current criteria
StopCriteria get_criteria() const { return {}; };
// Find function minimum or maximum for Func which has has signature:
// double(const Input<N> &input) and input with dimension N
//
// Initial starting point can be given as the second parameter.
//
// For each dimension an interval (Bound) has to be given marking the bounds
// for that dimension.
//
// initvals have to be within the specified bounds, otherwise its undefined
// behavior.
//
// Func can return a score of type double or optionally a ScoreGradient
// class to indicate the function gradient for a optimization methods that
// make use of the gradient.
template<class Func, size_t N>
Result<N> optimize(Func&& /*func*/,
const Input<N> &/*initvals*/,
const Bounds<N>& /*bounds*/) { return {}; }
// optional for randomized methods:
void seed(long /*s*/) {}
};
namespace detail {
// Helper to convert C style array to std::array. The copy should be optimized
// away with modern compilers.
template<size_t N, class T> auto to_arr(const T *a)
{
std::array<T, N> r;
std::copy(a, a + N, std::begin(r));
return r;
}
template<size_t N, class T> auto to_arr(const T (&a) [N])
{
return to_arr<N>(static_cast<const T *>(a));
}
} // namespace detail
// Helper functions to create bounds, initial value
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
{
return ScoreGradient<N>(s, detail::to_arr(grad));
}
}} // namespace Slic3r::opt
#endif // OPTIMIZER_HPP

100
src/libslic3r/PNGRead.cpp Normal file
View file

@ -0,0 +1,100 @@
#include "PNGRead.hpp"
#include <memory>
#include <cstdio>
#include <png.h>
namespace Slic3r { namespace png {
struct PNGDescr {
png_struct *png = nullptr; png_info *info = nullptr;
PNGDescr() = default;
PNGDescr(const PNGDescr&) = delete;
PNGDescr(PNGDescr&&) = delete;
PNGDescr& operator=(const PNGDescr&) = delete;
PNGDescr& operator=(PNGDescr&&) = delete;
~PNGDescr()
{
if (png && info) png_destroy_info_struct(png, &info);
if (png) png_destroy_read_struct( &png, nullptr, nullptr);
}
};
bool is_png(const ReadBuf &rb)
{
static const constexpr int PNG_SIG_BYTES = 8;
#if PNG_LIBPNG_VER_MINOR <= 2
// Earlier libpng versions had png_sig_cmp(png_bytep, ...) which is not
// a const pointer. It is not possible to cast away the const qualifier from
// the input buffer so... yes... life is challenging...
png_byte buf[PNG_SIG_BYTES];
auto inbuf = static_cast<const std::uint8_t *>(rb.buf);
std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf);
#else
auto buf = static_cast<png_const_bytep>(rb.buf);
#endif
return rb.sz >= PNG_SIG_BYTES && !png_sig_cmp(buf, 0, PNG_SIG_BYTES);
}
// Buffer read callback for libpng. It provides an allocated output buffer and
// the amount of data it desires to read from the input.
void png_read_callback(png_struct *png_ptr,
png_bytep outBytes,
png_size_t byteCountToRead)
{
// Retrieve our input buffer through the png_ptr
auto reader = static_cast<IStream *>(png_get_io_ptr(png_ptr));
if (!reader || !reader->is_ok()) return;
reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead);
}
bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
{
static const constexpr int PNG_SIG_BYTES = 8;
std::vector<png_byte> sig(PNG_SIG_BYTES, 0);
in_buf.read(sig.data(), PNG_SIG_BYTES);
if (!png_check_sig(sig.data(), PNG_SIG_BYTES))
return false;
PNGDescr dsc;
dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
nullptr);
if(!dsc.png) return false;
dsc.info = png_create_info_struct(dsc.png);
if(!dsc.info) return false;
png_set_read_fn(dsc.png, static_cast<void *>(&in_buf), png_read_callback);
// Tell that we have already read the first bytes to check the signature
png_set_sig_bytes(dsc.png, PNG_SIG_BYTES);
png_read_info(dsc.png, dsc.info);
out_img.cols = png_get_image_width(dsc.png, dsc.info);
out_img.rows = png_get_image_height(dsc.png, dsc.info);
size_t color_type = png_get_color_type(dsc.png, dsc.info);
size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info);
if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8)
return false;
out_img.buf.resize(out_img.rows * out_img.cols);
auto readbuf = static_cast<png_bytep>(out_img.buf.data());
for (size_t r = 0; r < out_img.rows; ++r)
png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr);
return true;
}
}} // namespace Slic3r::png

70
src/libslic3r/PNGRead.hpp Normal file
View file

@ -0,0 +1,70 @@
#ifndef PNGREAD_HPP
#define PNGREAD_HPP
#include <vector>
#include <string>
#include <istream>
namespace Slic3r { namespace png {
// Interface for an input stream of encoded png image data.
struct IStream {
virtual ~IStream() = default;
virtual size_t read(std::uint8_t *outp, size_t amount) = 0;
virtual bool is_ok() const = 0;
};
// The output format of decode_png: a 2D pixel matrix stored continuously row
// after row (row major layout).
template<class PxT> struct Image {
std::vector<PxT> buf;
size_t rows, cols;
PxT get(size_t row, size_t col) const { return buf[row * cols + col]; }
};
using ImageGreyscale = Image<uint8_t>;
// Only decodes true 8 bit grayscale png images. Returns false for other formats
// TODO (if needed): implement transformation of rgb images into grayscale...
bool decode_png(IStream &stream, ImageGreyscale &out_img);
// TODO (if needed)
// struct RGB { uint8_t r, g, b; };
// using ImageRGB = Image<RGB>;
// bool decode_png(IStream &stream, ImageRGB &img);
// Encoded png data buffer: a simple read-only buffer and its size.
struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; };
bool is_png(const ReadBuf &pngbuf);
template<class Img> bool decode_png(const ReadBuf &in_buf, Img &out_img)
{
struct ReadBufStream: public IStream {
const ReadBuf &rbuf_ref; size_t pos = 0;
explicit ReadBufStream(const ReadBuf &buf): rbuf_ref{buf} {}
size_t read(std::uint8_t *outp, size_t amount) override
{
if (amount > rbuf_ref.sz - pos) return 0;
auto buf = static_cast<const std::uint8_t *>(rbuf_ref.buf);
std::copy(buf + pos, buf + (pos + amount), outp);
pos += amount;
return amount;
}
bool is_ok() const override { return pos < rbuf_ref.sz; }
} stream{in_buf};
return decode_png(stream, out_img);
}
// TODO: std::istream of FILE* could be similarly adapted in case its needed...
}} // namespace Slic3r::png
#endif // PNGREAD_HPP

View file

@ -1,4 +1,5 @@
#include "PlaceholderParser.hpp"
#include "Exception.hpp"
#include "Flow.hpp"
#include <cstring>
#include <ctime>
@ -1303,7 +1304,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co
if (!context.error_message.empty()) {
if (context.error_message.back() != '\n' && context.error_message.back() != '\r')
context.error_message += '\n';
throw std::runtime_error(context.error_message);
throw Slic3r::RuntimeError(context.error_message);
}
return output;
}
@ -1319,7 +1320,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu
}
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws std::runtime_error on syntax or runtime error.
// Throws Slic3r::RuntimeError on syntax or runtime error.
bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override)
{
client::MyContext context;

View file

@ -40,11 +40,11 @@ public:
const DynamicConfig* external_config() const { return m_external_config; }
// Fill in the template using a macro processing language.
// Throws std::runtime_error on syntax or runtime error.
// Throws Slic3r::RuntimeError on syntax or runtime error.
std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr) const;
// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
// Throws std::runtime_error on syntax or runtime error.
// Throws Slic3r::RuntimeError on syntax or runtime error.
static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr);
// Update timestamp, year, month, day, hour, minute, second variables at the provided config.

View file

@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(
inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); }
inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); }
template<class T, int N> Eigen::Matrix<T, 2, 1, Eigen::DontAlign>
to_2d(const Eigen::Matrix<T, N, 1, Eigen::DontAlign> &ptN) { return {ptN(0), ptN(1)}; }
//inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
//inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
//inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); }
//inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); }
inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
@ -85,6 +88,8 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + std:
std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
Pointf3s transform(const Pointf3s& points, const Transform3d& t);
template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
class Point : public Vec2crd
{
public:

View file

@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
@ -16,7 +17,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const
for (const Point &pt : this->points)
if (pt == point)
return this->split_at_index(int(&pt - &this->points.front()));
throw std::invalid_argument("Point not found");
throw Slic3r::InvalidArgument("Point not found");
return Polyline();
}

View file

@ -86,6 +86,14 @@ inline double total_length(const Polygons &polylines) {
return total;
}
inline double area(const Polygons &polys)
{
double s = 0.;
for (auto &p : polys) s += p.area();
return s;
}
// Remove sticks (tentacles with zero area) from the polygon.
extern bool remove_sticks(Polygon &poly);
extern bool remove_sticks(Polygons &polys);

View file

@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "Polyline.hpp"
#include "Exception.hpp"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
#include "Line.hpp"
@ -19,7 +20,7 @@ Polyline::operator Polylines() const
Polyline::operator Line() const
{
if (this->points.size() > 2)
throw std::invalid_argument("Can't convert polyline with more than two points to a line");
throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line");
return Line(this->points.front(), this->points.back());
}
@ -207,7 +208,7 @@ BoundingBox get_extents(const Polylines &polylines)
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
const Point *p = &it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {

View file

@ -1,10 +1,8 @@
#include <cassert>
#include "Exception.hpp"
#include "Preset.hpp"
#include "AppConfig.hpp"
#include "BitmapCache.hpp"
#include "I18N.hpp"
#include "wxExtensions.hpp"
#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
@ -12,6 +10,16 @@
#include <Windows.h>
#endif /* _MSC_VER */
// instead of #include "slic3r/GUI/I18N.hpp" :
#ifndef L
// !!! If you needed to translate some string,
// !!! please use _L(string)
// !!! _() - is a standard wxWidgets macro to translate
// !!! L() is used only for marking localizable string
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
#define L(s) s
#endif /* L */
#include <algorithm>
#include <fstream>
#include <stdexcept>
@ -19,6 +27,7 @@
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/cenv.hpp>
@ -30,15 +39,9 @@
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <wx/image.h>
#include <wx/choice.h>
#include <wx/bmpcbox.h>
#include <wx/wupdlock.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "libslic3r/PlaceholderParser.hpp"
#include "Plater.hpp"
#include "libslic3r.h"
#include "Utils.hpp"
#include "PlaceholderParser.hpp"
using boost::property_tree::ptree;
@ -105,7 +108,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
const std::string id = path.stem().string();
if (! boost::filesystem::exists(path)) {
throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
}
VendorProfile res(id);
@ -115,7 +118,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
{
auto res = tree.find(key);
if (res == tree.not_found()) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
}
return res;
};
@ -127,7 +130,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data();
auto config_version = Semver::parse(config_version_str);
if (! config_version) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
} else {
res.config_version = std::move(*config_version);
}
@ -245,9 +248,9 @@ const std::string& Preset::suffix_modified()
return g_suffix_modified;
}
void Preset::update_suffix_modified()
void Preset::update_suffix_modified(const std::string& new_suffix_modified)
{
g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data();
g_suffix_modified = new_suffix_modified;
}
// Remove an optional "(modified)" suffix from a name.
// This converts a UI name to a unique preset identifier.
@ -496,6 +499,7 @@ const std::vector<std::string>& Preset::sla_print_options()
"support_head_penetration",
"support_head_width",
"support_pillar_diameter",
"support_small_pillar_diameter_percent",
"support_max_bridges_on_pillar",
"support_pillar_connection_mode",
"support_buildplate_only",
@ -590,10 +594,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
m_type(type),
m_edited_preset(type, "", false),
m_idx_selected(0),
m_bitmap_main_frame(new wxBitmap),
m_bitmap_add(new wxBitmap),
m_bitmap_cache(new GUI::BitmapCache)
m_idx_selected(0)
{
// Insert just the default preset.
this->add_default_preset(keys, defaults, default_name);
@ -602,12 +603,6 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::str
PresetCollection::~PresetCollection()
{
delete m_bitmap_main_frame;
m_bitmap_main_frame = nullptr;
delete m_bitmap_add;
m_bitmap_add = nullptr;
delete m_bitmap_cache;
m_bitmap_cache = nullptr;
}
void PresetCollection::reset(bool delete_files)
@ -640,7 +635,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
// Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
{
boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred();
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred();
m_dir_path = dir.string();
std::string errors_cummulative;
// Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken.
@ -676,9 +673,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
preset.loaded = true;
} catch (const std::ifstream::failure &err) {
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
}
presets_loaded.emplace_back(preset);
} catch (const std::runtime_error &err) {
@ -690,7 +687,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
std::sort(m_presets.begin() + m_num_default_presets, m_presets.end());
this->select_preset(first_visible_idx());
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
}
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
@ -951,16 +948,6 @@ bool PresetCollection::delete_preset(const std::string& name)
return true;
}
void PresetCollection::load_bitmap_default(const std::string &file_name)
{
*m_bitmap_main_frame = create_scaled_bitmap(file_name);
}
void PresetCollection::load_bitmap_add(const std::string &file_name)
{
*m_bitmap_add = create_scaled_bitmap(file_name);
}
const Preset* PresetCollection::get_selected_preset_parent() const
{
if (this->get_selected_idx() == size_t(-1))
@ -1119,279 +1106,15 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
// Delete the current preset, activate the first visible preset.
//void PresetCollection::delete_current_preset();
// Update the wxChoice UI component from this list of presets.
// Hide the
void PresetCollection::update_plater_ui(GUI::PresetComboBox *ui)
{
if (ui == nullptr)
return;
// Otherwise fill in the list from scratch.
ui->Freeze();
ui->Clear();
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,
// and draw a red flag in front of the selected preset.
bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr;
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
* So set sizes for solid_colored icons used for filament preset
* and scale them in respect to em_unit value
*/
const float scale_f = ui->em_unit() * 0.1f;
const int icon_height = 16 * scale_f + 0.5f;
const int icon_width = 16 * scale_f + 0.5f;
const int thin_space_icon_width = 4 * scale_f + 0.5f;
const int wide_space_icon_width = 6 * scale_f + 0.5f;
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) {
const Preset &preset = this->m_presets[i];
if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected))
continue;
std::string bitmap_key = "";
// !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame
wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap;
if (m_type == Preset::TYPE_PRINTER && preset.printer_technology()==ptSLA ) {
bitmap_key = "sla_printer";
main_bmp = create_scaled_bitmap("sla_printer");
}
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if (wide_icons)
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
if (wide_icons)
// Paint a red flag for incompatible presets.
bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible);
// Paint the color bars.
bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height));
bmps.emplace_back(main_bmp);
// Paint a lock at the system presets.
bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height));
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height));
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()),
(bmp == 0) ? main_bmp : *bmp);
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());
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));
}
if (!nonsys_presets.empty())
{
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 ||
// just in case: mark selected_preset_item as a first added element
selected_preset_item == INT_MAX)
selected_preset_item = ui->GetCount() - 1;
}
}
if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) {
std::string bitmap_key = "";
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if (wide_icons)
bitmap_key += "wide,";
bitmap_key += "edit_preset_list";
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
if (wide_icons)
// Paint a red flag for incompatible presets.
bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height));
// Paint the color bars.
bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height));
bmps.emplace_back(*m_bitmap_main_frame);
// Paint a lock at the system presets.
bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height));
// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap);
bmps.emplace_back(create_scaled_bitmap("edit_uni"));
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
}
if (m_type == Preset::TYPE_SLA_MATERIAL)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS);
else
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove printers")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_PRINTERS);
}
/* 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(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
ui->check_selection(selected_preset_item);
ui->Thaw();
// Update control min size after rescale (changed Display DPI under MSW)
if (ui->GetMinWidth() != 20 * ui->em_unit())
ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight()));
}
size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em/* = 10*/)
{
if (ui == nullptr)
return 0;
ui->Freeze();
ui->Clear();
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
* and scale them in respect to em_unit value
*/
const float scale_f = em * 0.1f;
const int icon_height = 16 * scale_f + 0.5f;
const int icon_width = 16 * scale_f + 0.5f;
std::map<wxString, wxBitmap*> nonsys_presets;
wxString selected = "";
if (!this->m_presets.front().is_visible)
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) {
const Preset &preset = this->m_presets[i];
if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected))
continue;
std::string bitmap_key = "tab";
// !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame
wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap;
if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) {
bitmap_key = "sla_printer";
main_bmp = create_scaled_bitmap("sla_printer");
}
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
bmps.emplace_back((tmp_bmp == 0) ? main_bmp : *tmp_bmp);
// Paint a lock at the system presets.
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height));
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
}
if (preset.is_default || preset.is_system) {
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
(bmp == 0) ? main_bmp : *bmp);
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
{
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());
}
if (i + 1 == m_num_default_presets)
ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap);
}
if (!nonsys_presets.empty())
{
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 ||
// just in case: mark selected_preset_item as a first added element
selected_preset_item == INT_MAX)
selected_preset_item = ui->GetCount() - 1;
}
}
if (m_type == Preset::TYPE_PRINTER) {
wxBitmap *bmp = m_bitmap_cache->find("edit_printer_list");
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
bmps.emplace_back(*m_bitmap_main_frame);
// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap);
bmps.emplace_back(create_scaled_bitmap("edit_uni"));
bmp = m_bitmap_cache->insert("add_printer_tab", bmps);
}
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();
return selected_preset_item;
}
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Update a dirty flag of the current preset
// Return true if the dirty flag changed.
bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
bool PresetCollection::update_dirty()
{
wxWindowUpdateLocker noUpdates(ui);
// 1) Update the dirty flag of the current preset.
bool was_dirty = this->get_selected_preset().is_dirty;
bool is_dirty = current_is_dirty();
this->get_selected_preset().is_dirty = is_dirty;
this->get_edited_preset().is_dirty = is_dirty;
// 2) Update the labels.
for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) {
std::string old_label = ui->GetString(ui_id).utf8_str().data();
std::string preset_name = Preset::remove_suffix_modified(old_label);
const Preset *preset = this->find_preset(preset_name, false);
// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator.
// assert(preset != nullptr);
if (preset != nullptr) {
std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name;
if (old_label != new_label)
ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
}
}
#ifdef __APPLE__
// wxWidgets on OSX do not upload the text of the combo box line automatically.
// Force it to update by re-selecting.
ui->SetSelection(ui->GetSelection());
#endif /* __APPLE __ */
return was_dirty != is_dirty;
}
@ -1605,16 +1328,6 @@ std::string PresetCollection::path_from_name(const std::string &new_name) const
return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
}
void PresetCollection::clear_bitmap_cache()
{
m_bitmap_cache->clear();
}
wxString PresetCollection::separator(const std::string &label)
{
return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail());
}
const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const
{
const ConfigOptionEnumGeneric *opt_printer_technology = config.opt<ConfigOptionEnumGeneric>("printer_technology");
@ -1632,6 +1345,472 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model
return it != cend() ? &*it : nullptr;
}
// -------------------------
// *** PhysicalPrinter ***
// -------------------------
std::string PhysicalPrinter::separator()
{
return " * ";
}
const std::vector<std::string>& PhysicalPrinter::printer_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"preset_name",
"printer_technology",
"printer_model",
"host_type",
"print_host",
"printhost_apikey",
"printhost_cafile",
"printhost_authorization_type",
// HTTP digest authentization (RFC 2617)
"printhost_user",
"printhost_password"
};
}
return s_opts;
}
const std::vector<std::string>& PhysicalPrinter::print_host_options()
{
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"print_host",
"printhost_apikey",
"printhost_cafile"
};
}
return s_opts;
}
std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets)
{
std::vector<std::string> presets;
for (const Preset& preset : printer_presets)
if (has_print_host_information(preset.config))
presets.emplace_back(preset.name);
return presets;
}
bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config)
{
for (const std::string& opt : print_host_options())
if (!config.opt_string(opt).empty())
return true;
return false;
}
const std::set<std::string>& PhysicalPrinter::get_preset_names() const
{
return preset_names;
}
bool PhysicalPrinter::has_empty_config() const
{
return config.opt_string("print_host" ).empty() &&
config.opt_string("printhost_apikey" ).empty() &&
config.opt_string("printhost_cafile" ).empty() &&
config.opt_string("printhost_user" ).empty() &&
config.opt_string("printhost_password").empty();
}
void PhysicalPrinter::update_preset_names_in_config()
{
if (!preset_names.empty()) {
std::string name;
for (auto el : preset_names)
name += el + ";";
name.pop_back();
config.set_key_value("preset_name", new ConfigOptionString(name));
}
}
void PhysicalPrinter::save(const std::string& file_name_from, const std::string& file_name_to)
{
// rename the file
boost::nowide::rename(file_name_from.data(), file_name_to.data());
this->file = file_name_to;
// save configuration
this->config.save(this->file);
}
void PhysicalPrinter::update_from_preset(const Preset& preset)
{
config.apply_only(preset.config, printer_options(), true);
// add preset names to the options list
auto ret = preset_names.emplace(preset.name);
update_preset_names_in_config();
}
void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config)
{
config.apply_only(new_config, printer_options(), false);
std::string str = config.opt_string("preset_name");
std::set<std::string> values{};
if (!str.empty()) {
boost::split(values, str, boost::is_any_of(";"));
for (const std::string& val : values)
preset_names.emplace(val);
}
preset_names = values;
}
void PhysicalPrinter::reset_presets()
{
return preset_names.clear();
}
bool PhysicalPrinter::add_preset(const std::string& preset_name)
{
return preset_names.emplace(preset_name).second;
}
bool PhysicalPrinter::delete_preset(const std::string& preset_name)
{
return preset_names.erase(preset_name) > 0;
}
PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset) :
name(name), config(default_config)
{
update_from_preset(preset);
}
void PhysicalPrinter::set_name(const std::string& name)
{
this->name = name;
}
std::string PhysicalPrinter::get_full_name(std::string preset_name) const
{
return name + separator() + preset_name;
}
std::string PhysicalPrinter::get_short_name(std::string full_name)
{
int pos = full_name.find(separator());
if (pos > 0)
boost::erase_tail(full_name, full_name.length() - pos);
return full_name;
}
std::string PhysicalPrinter::get_preset_name(std::string name)
{
int pos = name.find(separator());
boost::erase_head(name, pos + 3);
return Preset::remove_suffix_modified(name);
}
// -----------------------------------
// *** PhysicalPrinterCollection ***
// -----------------------------------
PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::string>& keys)
{
// Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options().
for (const std::string &key : keys) {
const ConfigOptionDef *opt = print_config_def.get(key);
assert(opt);
assert(opt->default_value);
m_default_config.set_key_value(key, opt->default_value->clone());
}
}
// Load all printers found in dir_path.
// Throws an exception on error.
void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir)
{
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred();
m_dir_path = dir.string();
std::string errors_cummulative;
// Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken.
std::deque<PhysicalPrinter> printers_loaded;
for (auto& dir_entry : boost::filesystem::directory_iterator(dir))
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
// Remove the .ini suffix.
name.erase(name.size() - 4);
if (this->find_printer(name, false)) {
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
// that's already been loaded from a bundle.
BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name;
continue;
}
try {
PhysicalPrinter printer(name, this->default_config());
printer.file = dir_entry.path().string();
// Load the preset file, apply preset values on top of defaults.
try {
DynamicPrintConfig config;
config.load_from_ini(printer.file);
printer.update_from_config(config);
printer.loaded = true;
}
catch (const std::ifstream::failure& err) {
throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what());
}
catch (const std::runtime_error& err) {
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what());
}
printers_loaded.emplace_back(printer);
}
catch (const std::runtime_error& err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
}
}
m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end()));
std::sort(m_printers.begin(), m_printers.end());
if (!errors_cummulative.empty())
throw Slic3r::RuntimeError(errors_cummulative);
}
// if there is saved user presets, contains information about "Print Host upload",
// Create default printers with this presets
// Note! "Print Host upload" options will be cleared after physical printer creations
void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets)
{
int cnt=0;
for (Preset& preset: printer_presets) {
DynamicPrintConfig& config = preset.config;
const std::vector<std::string>& options = PhysicalPrinter::print_host_options();
for(const std::string& option : options) {
if (!config.opt_string(option).empty()) {
// check if printer with those "Print Host upload" options already exist
PhysicalPrinter* existed_printer = find_printer_with_same_config(config);
if (existed_printer)
// just add preset for this printer
existed_printer->add_preset(preset.name);
else {
std::string new_printer_name = (boost::format("Printer %1%") % ++cnt ).str();
while (find_printer(new_printer_name))
new_printer_name = (boost::format("Printer %1%") % ++cnt).str();
// create new printer from this preset
PhysicalPrinter printer(new_printer_name, this->default_config(), preset);
printer.loaded = true;
save_printer(printer);
}
// erase "Print Host upload" information from the preset
for (const std::string& opt : options)
config.opt_string(opt).clear();
// save changes for preset
preset.save();
// update those changes for edited preset if it's equal to the preset
Preset& edited = printer_presets.get_edited_preset();
if (preset.name == edited.name) {
for (const std::string& opt : options)
edited.config.opt_string(opt).clear();
}
break;
}
}
}
}
PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found)
{
auto it = this->find_printer_internal(name);
// Ensure that a temporary copy is returned if the preset found is currently selected.
return (it != m_printers.end() && it->name == name) ? &this->printer(it - m_printers.begin()) :
first_visible_if_not_found ? &this->printer(0) : nullptr;
}
PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const DynamicPrintConfig& config)
{
for (const PhysicalPrinter& printer :*this) {
bool is_equal = true;
for (const std::string& opt : PhysicalPrinter::print_host_options())
if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt))
is_equal = false;
if (is_equal)
return find_printer(printer.name);
}
return nullptr;
}
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const
{
std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini");
return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
}
void PhysicalPrinterCollection::save_printer(PhysicalPrinter& edited_printer, const std::string& renamed_from/* = ""*/)
{
// controll and update preset_names in edited_printer config
edited_printer.update_preset_names_in_config();
std::string name = renamed_from.empty() ? edited_printer.name : renamed_from;
// 1) Find the printer with a new_name or create a new one,
// initialize it with the edited config.
auto it = this->find_printer_internal(name);
if (it != m_printers.end() && it->name == name) {
// Printer with the same name found.
// Overwriting an existing preset.
it->config = std::move(edited_printer.config);
it->name = edited_printer.name;
it->preset_names = edited_printer.preset_names;
}
else {
// Creating a new printer.
it = m_printers.insert(it, edited_printer);
}
assert(it != m_printers.end());
// 2) Save printer
PhysicalPrinter& printer = *it;
if (printer.file.empty())
printer.file = this->path_from_name(printer.name);
if (printer.file == this->path_from_name(printer.name))
printer.save();
else
// if printer was renamed, we should rename a file and than save the config
printer.save(printer.file, this->path_from_name(printer.name));
// update idx_selected
m_idx_selected = it - m_printers.begin();
}
bool PhysicalPrinterCollection::delete_printer(const std::string& name)
{
auto it = this->find_printer_internal(name);
if (it == m_printers.end())
return false;
const PhysicalPrinter& printer = *it;
// Erase the preset file.
boost::nowide::remove(printer.file.c_str());
m_printers.erase(it);
return true;
}
bool PhysicalPrinterCollection::delete_selected_printer()
{
if (!has_selection())
return false;
const PhysicalPrinter& printer = this->get_selected_printer();
// Erase the preset file.
boost::nowide::remove(printer.file.c_str());
// Remove the preset from the list.
m_printers.erase(m_printers.begin() + m_idx_selected);
// unselect all printers
unselect_printer();
return true;
}
bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name)
{
std::vector<std::string> printers_for_delete;
for (PhysicalPrinter& printer : m_printers) {
if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name)
printers_for_delete.emplace_back(printer.name);
else if (printer.delete_preset(preset_name))
save_printer(printer);
}
if (!printers_for_delete.empty())
for (const std::string& printer_name : printers_for_delete)
delete_printer(printer_name);
unselect_printer();
return true;
}
// Get list of printers which have more than one preset and "preset_name" preset is one of them
std::vector<std::string> PhysicalPrinterCollection::get_printers_with_preset(const std::string& preset_name)
{
std::vector<std::string> printers;
for (auto printer : m_printers) {
if (printer.preset_names.size() == 1)
continue;
if (printer.preset_names.find(preset_name) != printer.preset_names.end())
printers.emplace_back(printer.name);
}
return printers;
}
// Get list of printers which has only "preset_name" preset
std::vector<std::string> PhysicalPrinterCollection::get_printers_with_only_preset(const std::string& preset_name)
{
std::vector<std::string> printers;
for (auto printer : m_printers)
if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name)
printers.emplace_back(printer.name);
return printers;
}
std::string PhysicalPrinterCollection::get_selected_full_printer_name() const
{
return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset);
}
void PhysicalPrinterCollection::select_printer(const std::string& full_name)
{
std::string printer_name = PhysicalPrinter::get_short_name(full_name);
auto it = this->find_printer_internal(printer_name);
if (it == m_printers.end()) {
unselect_printer();
return;
}
// update idx_selected
m_idx_selected = it - m_printers.begin();
// update name of the currently selected preset
if (printer_name == full_name)
// use first preset in the list
m_selected_preset = *it->preset_names.begin();
else
m_selected_preset = it->get_preset_name(full_name);
}
void PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer)
{
return select_printer(printer.name);
}
bool PhysicalPrinterCollection::has_selection() const
{
return m_idx_selected != size_t(-1);
}
void PhysicalPrinterCollection::unselect_printer()
{
m_idx_selected = size_t(-1);
m_selected_preset.clear();
}
bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const
{
return m_idx_selected == it - m_printers.begin() &&
m_selected_preset == preset_name;
}
namespace PresetUtils {
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
{
@ -1646,6 +1825,26 @@ namespace PresetUtils {
}
return out;
}
#if ENABLE_GCODE_VIEWER
std::string system_printer_bed_model(const Preset& preset)
{
std::string out;
const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset);
if (pm != nullptr && !pm->bed_model.empty())
out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model;
return out;
}
std::string system_printer_bed_texture(const Preset& preset)
{
std::string out;
const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset);
if (pm != nullptr && !pm->bed_texture.empty())
out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
return out;
}
#endif // ENABLE_GCODE_VIEWER
} // namespace PresetUtils
} // namespace Slic3r

View file

@ -8,27 +8,14 @@
#include <boost/filesystem/path.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Semver.hpp"
class wxBitmap;
class wxBitmapComboBox;
class wxChoice;
class wxItemContainer;
class wxString;
class wxWindow;
#include "PrintConfig.hpp"
#include "Semver.hpp"
namespace Slic3r {
class AppConfig;
class PresetBundle;
namespace GUI {
class BitmapCache;
class PresetComboBox;
}
enum ConfigFileType
{
CONFIG_FILE_TYPE_UNKNOWN,
@ -236,7 +223,7 @@ public:
static const std::vector<std::string>& sla_material_options();
static const std::vector<std::string>& sla_print_options();
static void update_suffix_modified();
static void update_suffix_modified(const std::string& new_suffix_modified);
static const std::string& suffix_modified();
static std::string remove_suffix_modified(const std::string& name);
static void normalize(DynamicPrintConfig &config);
@ -322,18 +309,6 @@ public:
// returns true if the preset was deleted successfully.
bool delete_preset(const std::string& name);
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
void load_bitmap_default(const std::string &file_name);
// Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame.
void load_bitmap_add(const std::string &file_name);
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }
void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; }
void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; }
void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; }
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
bool is_default_suppressed() const { return m_default_suppressed; }
@ -446,18 +421,9 @@ public:
// Return a sorted list of system preset names.
std::vector<std::string> system_preset_names() const;
// Update the choice UI from the list of presets.
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em = 10);
// Update the choice UI from the list of presets.
// Only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
void update_plater_ui(GUI::PresetComboBox *ui);
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Update a dirty flag of the current preset
// Return true if the dirty flag changed.
bool update_dirty_ui(wxBitmapComboBox *ui);
bool update_dirty();
// Select a profile by its name. Return true if the selection changed.
// Without force, the selection is only updated if the index changes.
@ -467,16 +433,7 @@ public:
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string &new_name) const;
void clear_bitmap_cache();
#ifdef __linux__
static const char* separator_head() { return "------- "; }
static const char* separator_tail() { return " -------"; }
#else /* __linux__ */
static const char* separator_head() { return "————— "; }
static const char* separator_tail() { return " —————"; }
#endif /* __linux__ */
static wxString separator(const std::string &label);
size_t num_default_presets() { return m_num_default_presets; }
protected:
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
@ -503,8 +460,7 @@ private:
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
{
Preset key(m_type, name);
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(), [&name](const auto& l) { return l.name < name; });
if (it == m_presets.end() || it->name != name) {
// Preset has not been not found in the sorted list of non-default presets. Try the defaults.
for (size_t i = 0; i < m_num_default_presets; ++ i)
@ -547,23 +503,10 @@ private:
// Is the "- default -" preset suppressed?
bool m_default_suppressed = true;
size_t m_num_default_presets = 0;
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Plater.
// These bitmaps are not owned by PresetCollection, but by a PresetBundle.
const wxBitmap *m_bitmap_compatible = nullptr;
const wxBitmap *m_bitmap_incompatible = nullptr;
const wxBitmap *m_bitmap_lock = nullptr;
const wxBitmap *m_bitmap_lock_open = nullptr;
// Marks placed at the wxBitmapComboBox of a MainFrame.
// These bitmaps are owned by PresetCollection.
wxBitmap *m_bitmap_main_frame;
// "Add printer profile" icon, owned by PresetCollection.
wxBitmap *m_bitmap_add;
// Path to the directory to store the config files into.
std::string m_dir_path;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmap_cache = nullptr;
// to access select_preset_by_name_strict()
friend class PresetBundle;
};
@ -583,8 +526,214 @@ public:
namespace PresetUtils {
// PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile.
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
#if ENABLE_GCODE_VIEWER
std::string system_printer_bed_model(const Preset& preset);
std::string system_printer_bed_texture(const Preset& preset);
#endif // ENABLE_GCODE_VIEWER
} // namespace PresetUtils
//////////////////////////////////////////////////////////////////////
class PhysicalPrinter
{
public:
PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config) : name(name), config(default_config) {}
PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset);
void set_name(const std::string &name);
// Name of the Physical Printer, usually derived form the file name.
std::string name;
// File name of the Physical Printer.
std::string file;
// Configuration data, loaded from a file, or set from the defaults.
DynamicPrintConfig config;
// set of presets used with this physical printer
std::set<std::string> preset_names;
// Has this profile been loaded?
bool loaded = false;
static std::string separator();
static const std::vector<std::string>& printer_options();
static const std::vector<std::string>& print_host_options();
static std::vector<std::string> presets_with_print_host_information(const PrinterPresetCollection& printer_presets);
static bool has_print_host_information(const DynamicPrintConfig& config);
const std::set<std::string>& get_preset_names() const;
bool has_empty_config() const;
void update_preset_names_in_config();
void save() { this->config.save(this->file); }
void save(const std::string& file_name_from, const std::string& file_name_to);
void update_from_preset(const Preset& preset);
void update_from_config(const DynamicPrintConfig &new_config);
// add preset to the preset_names
// return false, if preset with this name is already exist in the set
bool add_preset(const std::string& preset_name);
bool delete_preset(const std::string& preset_name);
void reset_presets();
// Return a printer technology, return ptFFF if the printer technology is not set.
static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) {
auto* opt = cfg.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
// The following assert may trigger when importing some legacy profile,
// but it is safer to keep it here to capture the cases where the "printer_technology" key is queried, where it should not.
return (opt == nullptr) ? ptFFF : opt->value;
}
PrinterTechnology printer_technology() const { return printer_technology(this->config); }
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; }
// get full printer name included a name of the preset
std::string get_full_name(std::string preset_name) const;
// get printer name from the full name uncluded preset name
static std::string get_short_name(std::string full_name);
// get preset name from the full name uncluded printer name
static std::string get_preset_name(std::string full_name);
protected:
friend class PhysicalPrinterCollection;
};
// ---------------------------------
// *** PhysicalPrinterCollection ***
// ---------------------------------
// Collections of physical printers
class PhysicalPrinterCollection
{
public:
PhysicalPrinterCollection(const std::vector<std::string>& keys);
~PhysicalPrinterCollection() {}
typedef std::deque<PhysicalPrinter>::iterator Iterator;
typedef std::deque<PhysicalPrinter>::const_iterator ConstIterator;
Iterator begin() { return m_printers.begin(); }
ConstIterator begin() const { return m_printers.cbegin(); }
ConstIterator cbegin() const { return m_printers.cbegin(); }
Iterator end() { return m_printers.end(); }
ConstIterator end() const { return m_printers.cend(); }
ConstIterator cend() const { return m_printers.cend(); }
bool empty() const {return m_printers.empty(); }
void reset(bool delete_files) {};
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
// Load ini files of the particular type from the provided directory path.
void load_printers(const std::string& dir_path, const std::string& subdir);
void load_printers_from_presets(PrinterPresetCollection &printer_presets);
// Save the printer under a new name. If the name is different from the old one,
// a new printer is stored into the list of printers.
// New printer is activated.
void save_printer(PhysicalPrinter& printer, const std::string& renamed_from = "");
// Delete the current preset, activate the first visible preset.
// returns true if the preset was deleted successfully.
bool delete_printer(const std::string& name);
// Delete the selected preset
// returns true if the preset was deleted successfully.
bool delete_selected_printer();
// Delete preset_name preset from all printers:
// If there is last preset for the printer and first_check== false, then delete this printer
// returns true if all presets were deleted successfully.
bool delete_preset_from_printers(const std::string& preset_name);
// Get list of printers which have more than one preset and "preset_name" preset is one of them
std::vector<std::string> get_printers_with_preset( const std::string &preset_name);
// Get list of printers which has only "preset_name" preset
std::vector<std::string> get_printers_with_only_preset( const std::string &preset_name);
// Return the selected preset, without the user modifications applied.
PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; }
const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; }
size_t get_selected_idx() const { return m_idx_selected; }
// Returns the name of the selected preset, or an empty string if no preset is selected.
std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; }
// Returns the config of the selected printer, or nullptr if no printer is selected.
DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); }
// Returns the config of the selected printer, or nullptr if no printer is selected.
PrinterTechnology get_selected_printer_technology() { return (m_idx_selected == size_t(-1)) ? PrinterTechnology::ptAny : this->get_selected_printer().printer_technology(); }
// Each physical printer can have a several related preset,
// so, use the next functions to get an exact names of selections in the list:
// Returns the full name of the selected printer, or an empty string if no preset is selected.
std::string get_selected_full_printer_name() const;
// Returns the printer model of the selected preset, or an empty string if no preset is selected.
std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : m_selected_preset; }
// Select printer by the full printer name, which contains name of printer, separator and name of selected preset
// If full_name doesn't contain name of selected preset, then select first preset in the list for this printer
void select_printer(const std::string& full_name);
void select_printer(const PhysicalPrinter& printer);
bool has_selection() const;
void unselect_printer() ;
bool is_selected(ConstIterator it, const std::string &preset_name) const;
// Return a printer by an index. If the printer is active, a temporary copy is returned.
PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; }
const PhysicalPrinter& printer(size_t idx) const { return const_cast<PhysicalPrinterCollection*>(this)->printer(idx); }
// Return a preset by its name. If the preset is active, a temporary copy is returned.
// If a preset is not found by its name, null is returned.
PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false);
const PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false) const
{
return const_cast<PhysicalPrinterCollection*>(this)->find_printer(name, first_visible_if_not_found);
}
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string& new_name) const;
const DynamicPrintConfig& default_config() const { return m_default_config; }
private:
PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other);
// Find a preset position in the sorted list of presets.
// The "-- default -- " preset is always the first, so it needs
// to be handled differently.
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<PhysicalPrinter>::iterator find_printer_internal(const std::string& name)
{
return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; });
}
std::deque<PhysicalPrinter>::const_iterator find_printer_internal(const std::string& name) const
{
return const_cast<PhysicalPrinterCollection*>(this)->find_printer_internal(name);
}
PhysicalPrinter* find_printer_with_same_config( const DynamicPrintConfig &config);
// List of printers
// Use deque to force the container to allocate an object per each entry,
// so that the addresses of the presets don't change during resizing of the container.
std::deque<PhysicalPrinter> m_printers;
// Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options().
DynamicPrintConfig m_default_config;
// Selected printer.
size_t m_idx_selected = size_t(-1);
// The name of the preset which is currently select for this printer
std::string m_selected_preset;
// Path to the directory to store the config files into.
std::string m_dir_path;
};
} // namespace Slic3r
#endif /* slic3r_Preset_hpp_ */

View file

@ -1,12 +1,12 @@
#include <cassert>
#include "PresetBundle.hpp"
#include "BitmapCache.hpp"
#include "Plater.hpp"
#include "I18N.hpp"
#include "wxExtensions.hpp"
#include "libslic3r.h"
#include "Utils.hpp"
#include "Model.hpp"
#include <algorithm>
#include <set>
#include <fstream>
#include <unordered_set>
#include <boost/filesystem.hpp>
@ -21,17 +21,6 @@
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <wx/dcmemory.h>
#include <wx/image.h>
#include <wx/choice.h>
#include <wx/bmpcbox.h>
#include <wx/wupdlock.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "GUI_App.hpp"
// Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir.
// This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions.
@ -53,15 +42,8 @@ PresetBundle::PresetBundle() :
sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())),
sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())),
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
m_bitmapCompatible(new wxBitmap),
m_bitmapIncompatible(new wxBitmap),
m_bitmapLock(new wxBitmap),
m_bitmapLockOpen(new wxBitmap),
m_bitmapCache(new GUI::BitmapCache)
physical_printers(PhysicalPrinter::printer_options())
{
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
wxImage::AddHandler(new wxPNGHandler);
// The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes,
// therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being
// initialized based on PrintConfigDef(), but to empty values (zeros, empty vectors, empty strings).
@ -112,16 +94,6 @@ PresetBundle::PresetBundle() :
preset.inherits();
}
// Load the default preset bitmaps.
// #ys_FIXME_to_delete we'll load them later, using em_unit()
// this->prints .load_bitmap_default("cog");
// this->sla_prints .load_bitmap_default("package_green.png");
// this->filaments .load_bitmap_default("spool.png");
// this->sla_materials.load_bitmap_default("package_green.png");
// this->printers .load_bitmap_default("printer_empty.png");
// this->printers .load_bitmap_add("add.png");
// this->load_compatible_bitmaps();
// Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above.
this->prints .select_preset(0);
this->sla_prints .select_preset(0);
@ -134,20 +106,6 @@ PresetBundle::PresetBundle() :
PresetBundle::~PresetBundle()
{
assert(m_bitmapCompatible != nullptr);
assert(m_bitmapIncompatible != nullptr);
assert(m_bitmapLock != nullptr);
assert(m_bitmapLockOpen != nullptr);
delete m_bitmapCompatible;
m_bitmapCompatible = nullptr;
delete m_bitmapIncompatible;
m_bitmapIncompatible = nullptr;
delete m_bitmapLock;
m_bitmapLock = nullptr;
delete m_bitmapLockOpen;
m_bitmapLockOpen = nullptr;
delete m_bitmapCache;
m_bitmapCache = nullptr;
}
void PresetBundle::reset(bool delete_files)
@ -182,14 +140,16 @@ void PresetBundle::setup_directories()
data_dir / "presets" / "filament",
data_dir / "presets" / "sla_print",
data_dir / "presets" / "sla_material",
data_dir / "presets" / "printer"
data_dir / "presets" / "printer",
data_dir / "presets" / "physical_printer"
#else
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
data_dir / "print",
data_dir / "filament",
data_dir / "sla_print",
data_dir / "sla_material",
data_dir / "printer"
data_dir / "printer",
data_dir / "physical_printer"
#endif
};
for (const boost::filesystem::path &path : paths) {
@ -197,7 +157,7 @@ void PresetBundle::setup_directories()
subdir.make_preferred();
if (! boost::filesystem::is_directory(subdir) &&
! boost::filesystem::create_directory(subdir))
throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create its data directory at ") + subdir.string());
}
}
@ -239,10 +199,15 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->physical_printers.load_printers(dir_user_presets, "physical_printer");
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
this->update_multi_material_filament_presets();
this->update_compatible(PresetSelectCompatibleType::Never);
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
throw Slic3r::RuntimeError(errors_cummulative);
this->load_selections(config, preferred_model_id);
}
@ -362,6 +327,32 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p
return presets.get_preset_name_by_alias(alias);
}
void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type,
const std::vector<std::string>& unselected_options)
{
PresetCollection& presets = type == Preset::TYPE_PRINT ? prints :
type == Preset::TYPE_SLA_PRINT ? sla_prints :
type == Preset::TYPE_FILAMENT ? filaments :
type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers;
// if we want to save just some from selected options
if (!unselected_options.empty()) {
// revert unselected options to the old values
presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options);
}
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
presets.save_current_preset(new_name);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
update_compatible(PresetSelectCompatibleType::Never);
if (type == Preset::TYPE_FILAMENT) {
// synchronize the first filament presets.
set_filament_preset(0, filaments.get_selected_preset_name());
}
}
void PresetBundle::load_installed_filaments(AppConfig &config)
{
if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
@ -465,6 +456,13 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr
// exist.
this->update_compatible(PresetSelectCompatibleType::Always);
this->update_multi_material_filament_presets();
// Parse the initial physical printer name.
std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer"));
// Activate physical printer from the config
if (!initial_physical_printer_name.empty())
physical_printers.select_printer(initial_physical_printer_name);
}
// Export selections (current print, current filaments, current printer) into config.ini
@ -484,36 +482,8 @@ void PresetBundle::export_selections(AppConfig &config)
config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
config.set("presets", "printer", printers.get_selected_preset_name());
}
void PresetBundle::load_compatible_bitmaps()
{
*m_bitmapCompatible = create_scaled_bitmap("flag_green");
*m_bitmapIncompatible = create_scaled_bitmap("flag_red");
*m_bitmapLock = create_scaled_bitmap("lock_closed");
*m_bitmapLockOpen = create_scaled_bitmap("lock_open");
prints .set_bitmap_compatible(m_bitmapCompatible);
filaments .set_bitmap_compatible(m_bitmapCompatible);
sla_prints .set_bitmap_compatible(m_bitmapCompatible);
sla_materials.set_bitmap_compatible(m_bitmapCompatible);
prints .set_bitmap_incompatible(m_bitmapIncompatible);
filaments .set_bitmap_incompatible(m_bitmapIncompatible);
sla_prints .set_bitmap_incompatible(m_bitmapIncompatible);
sla_materials.set_bitmap_incompatible(m_bitmapIncompatible);
prints .set_bitmap_lock(m_bitmapLock);
filaments .set_bitmap_lock(m_bitmapLock);
sla_prints .set_bitmap_lock(m_bitmapLock);
sla_materials.set_bitmap_lock(m_bitmapLock);
printers .set_bitmap_lock(m_bitmapLock);
prints .set_bitmap_lock_open(m_bitmapLock);
filaments .set_bitmap_lock_open(m_bitmapLock);
sla_prints .set_bitmap_lock_open(m_bitmapLock);
sla_materials.set_bitmap_lock_open(m_bitmapLock);
printers .set_bitmap_lock_open(m_bitmapLock);
config.set("extras", "physical_printer", physical_printers.get_selected_full_printer_name());
}
DynamicPrintConfig PresetBundle::full_config() const
@ -709,21 +679,21 @@ void PresetBundle::load_config_file(const std::string &path)
boost::nowide::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
} catch (const std::ifstream::failure &err) {
throw std::runtime_error(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const boost::property_tree::file_parser_error &err) {
throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
% err.filename() % err.message() % err.line()).str());
} catch (const std::runtime_error &err) {
throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw std::runtime_error(std::string("Unknown configuration file type: ") + path);
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
@ -886,8 +856,6 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
// 4) Load the project config values (the per extruder wipe matrix etc).
this->project_config.apply_only(config, s_project_options);
update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config);
break;
}
case ptSLA:
@ -1544,207 +1512,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
// an optional "(modified)" suffix will be removed from the filament name.
void PresetBundle::set_filament_preset(size_t idx, const std::string &name)
{
if (name.find_first_of(PresetCollection::separator_head()) == 0)
return;
if (idx >= filament_presets.size())
if (idx >= filament_presets.size())
filament_presets.resize(idx + 1, filaments.default_preset().name);
filament_presets[idx] = Preset::remove_suffix_modified(name);
}
void PresetBundle::load_default_preset_bitmaps()
{
// Clear bitmap cache, before load new scaled default preset bitmaps
m_bitmapCache->clear();
this->prints.clear_bitmap_cache();
this->sla_prints.clear_bitmap_cache();
this->filaments.clear_bitmap_cache();
this->sla_materials.clear_bitmap_cache();
this->printers.clear_bitmap_cache();
this->prints.load_bitmap_default("cog");
this->sla_prints.load_bitmap_default("cog");
this->filaments.load_bitmap_default("spool.png");
this->sla_materials.load_bitmap_default("resin");
this->printers.load_bitmap_default("printer");
this->printers.load_bitmap_add("add.png");
this->load_compatible_bitmaps();
}
void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui)
{
if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA ||
this->filament_presets.size() <= idx_extruder )
return;
unsigned char rgb[3];
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
if (!m_bitmapCache->parse_color(extruder_color, rgb))
// Extruder color is not defined.
extruder_color.clear();
// Fill in the list from scratch.
ui->Freeze();
ui->Clear();
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.
bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr;
assert(selected_preset != nullptr);
std::map<wxString, wxBitmap*> nonsys_presets;
wxString selected_str = "";
if (!this->filaments().front().is_visible)
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
* So set sizes for solid_colored icons used for filament preset
* and scale them in respect to em_unit value
*/
const float scale_f = ui->em_unit() * 0.1f;
// To avoid the errors of number rounding for different combination of monitor configuration,
// let use scaled 8px, as a smallest icon unit
const int icon_unit = 8 * scale_f + 0.5f;
const int normal_icon_width = 2 * icon_unit; //16 * scale_f + 0.5f;
const int thin_icon_width = icon_unit; //8 * scale_f + 0.5f;
const int wide_icon_width = 3 * icon_unit; //24 * scale_f + 0.5f;
const int space_icon_width = 2 * scale_f + 0.5f;
// To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
// set a bitmap height to m_bitmapLock->GetHeight()
//
// To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size.
// But for some display scaling (for example 125% or 175%) normal_icon_width differs from icon width.
// So:
// for nonsystem presets set a width of empty bitmap to m_bitmapLock->GetWidth()
// for compatible presets set a width of empty bitmap to m_bitmapIncompatible->GetWidth()
//
// Note, under OSX we should use a Scaled Height/Width because of Retina scale
#ifdef __APPLE__
const int icon_height = m_bitmapLock->GetScaledHeight();
const int lock_icon_width = m_bitmapLock->GetScaledWidth();
const int flag_icon_width = m_bitmapIncompatible->GetScaledWidth();
#else
const int icon_height = m_bitmapLock->GetHeight();
const int lock_icon_width = m_bitmapLock->GetWidth();
const int flag_icon_width = m_bitmapIncompatible->GetWidth();
#endif
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;
if (! preset.is_visible || (! preset.is_compatible && ! selected))
continue;
// Assign an extruder color to the selected item if the extruder color is defined.
std::string filament_rgb = preset.config.opt_string("filament_colour", 0);
std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb;
bool single_bar = filament_rgb == extruder_rgb;
std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb;
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
if (wide_icons)
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
if (preset.is_dirty)
bitmap_key += ",drty";
wxBitmap *bitmap = m_bitmapCache->find(bitmap_key);
if (bitmap == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
if (wide_icons)
// Paint a red flag for incompatible presets.
bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(flag_icon_width, icon_height) : *m_bitmapIncompatible);
// Paint the color bars.
m_bitmapCache->parse_color(filament_rgb, rgb);
bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb));
if (! single_bar) {
m_bitmapCache->parse_color(extruder_rgb, rgb);
bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb));
}
// Paint a lock at the system presets.
bmps.emplace_back(m_bitmapCache->mkclear(space_icon_width, icon_height));
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmapLock : m_bitmapCache->mkclear(lock_icon_width, icon_height));
// (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16));
bitmap = m_bitmapCache->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 ? Preset::suffix_modified() : "")).c_str()),
(bitmap == 0) ? wxNullBitmap : *bitmap);
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()),
(bitmap == 0) ? &wxNullBitmap : bitmap);
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));
}
if (!nonsys_presets.empty())
{
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 ||
// just in case: mark selected_preset_item as a first added element
selected_preset_item == INT_MAX) {
selected_preset_item = ui->GetCount() - 1;
}
}
}
std::string bitmap_key = "";
if (wide_icons)
bitmap_key += "wide,";
bitmap_key += "edit_preset_list";
wxBitmap* bmp = m_bitmapCache->find(bitmap_key);
if (bmp == nullptr) {
// Create the bitmap with color bars.
std::vector<wxBitmap> bmps;
if (wide_icons)
// Paint a red flag for incompatible presets.
bmps.emplace_back(m_bitmapCache->mkclear(flag_icon_width, icon_height));
// Paint the color bars + a lock at the system presets.
bmps.emplace_back(m_bitmapCache->mkclear(wide_icon_width+space_icon_width, icon_height));
bmps.emplace_back(create_scaled_bitmap("edit_uni"));
bmp = m_bitmapCache->insert(bitmap_key, bmps);
}
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), *bmp), 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(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
ui->check_selection(selected_preset_item);
ui->Thaw();
// Update control min size after rescale (changed Display DPI under MSW)
if (ui->GetMinWidth() != 20 * ui->em_unit())
ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight()));
}
void PresetBundle::set_default_suppressed(bool default_suppressed)
{
prints.set_default_suppressed(default_suppressed);

View file

@ -1,22 +1,15 @@
#ifndef slic3r_PresetBundle_hpp_
#define slic3r_PresetBundle_hpp_
#include "AppConfig.hpp"
#include "Preset.hpp"
#include "AppConfig.hpp"
#include <memory>
#include <set>
#include <unordered_map>
#include <boost/filesystem/path.hpp>
class wxWindow;
namespace Slic3r {
namespace GUI {
class BitmapCache;
};
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
@ -45,6 +38,7 @@ public:
PresetCollection& materials(PrinterTechnology pt) { return pt == ptFFF ? this->filaments : this->sla_materials; }
const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; }
PrinterPresetCollection printers;
PhysicalPrinterCollection physical_printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<std::string> filament_presets;
@ -110,9 +104,6 @@ public:
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false);
// Update a filament selection combo box on the plater for an idx_extruder.
void update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
@ -132,8 +123,6 @@ public:
void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible);
void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); }
void load_default_preset_bitmaps();
// Set the is_visible flag for printer vendors, printer models and printer variants
// based on the user configuration.
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
@ -141,6 +130,10 @@ public:
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const;
// Save current preset of a required type under a new name. If the name is different from the old one,
// Unselected option would be reverted to the beginning values
void save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector<std::string>& unselected_options);
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();
@ -163,21 +156,9 @@ private:
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
void load_compatible_bitmaps();
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;
// Indicator, that the preset is compatible with the selected printer.
wxBitmap *m_bitmapCompatible;
// Indicator, that the preset is NOT compatible with the selected printer.
wxBitmap *m_bitmapIncompatible;
// Indicator, that the preset is system and not modified.
wxBitmap *m_bitmapLock;
// Indicator, that the preset is system and user modified.
wxBitmap *m_bitmapLockOpen;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmapCache;
};
} // namespace Slic3r

View file

@ -1,5 +1,6 @@
#include "clipper/clipper_z.hpp"
#include "Exception.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@ -404,6 +405,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.name = mv_src.name;
static_cast<DynamicPrintConfig&>(mv_dst.config) = static_cast<const DynamicPrintConfig&>(mv_src.config);
mv_dst.m_supported_facets = mv_src.m_supported_facets;
mv_dst.m_seam_facets = mv_src.m_seam_facets;
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@ -867,6 +869,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_volume_list_update_supports(model_object, model_object_new);
}
}
if (model_custom_seam_data_changed(model_object, model_object_new)) {
update_apply_status(this->invalidate_step(psGCodeExport));
}
if (! model_parts_differ && ! modifiers_differ) {
// Synchronize Object's config.
bool object_config_changed = model_object.config != model_object_new.config;
@ -1504,7 +1509,7 @@ BoundingBox Print::total_bounding_box() const
double Print::skirt_first_layer_height() const
{
if (m_objects.empty())
throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects");
throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects");
return m_objects.front()->config().get_abs_value("first_layer_height");
}
@ -1580,7 +1585,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
// Slicing process, running at a background thread.
void Print::process()
{
BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info();
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
for (PrintObject *obj : m_objects)
obj->make_perimeters();
this->set_status(70, L("Infilling layers"));
@ -1600,7 +1605,7 @@ void Print::process()
// Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches.
m_tool_ordering = ToolOrdering(*this, -1, false);
if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1))
throw std::runtime_error("The print is empty. The model is not printable with current print settings.");
throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings.");
}
this->set_done(psWipeTower);
}
@ -1633,13 +1638,21 @@ void Print::process()
// The export_gcode may die for various reasons (fails to process output_filename_format,
// write error into the G-code, cannot execute post-processing scripts).
// It is up to the caller to show an error message.
#if ENABLE_GCODE_VIEWER
std::string Print::export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb)
#else
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
#endif // ENABLE_GCODE_VIEWER
{
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);
std::string message;
#if ENABLE_GCODE_VIEWER
if (!path.empty() && result == nullptr) {
#else
if (! path.empty() && preview_data == nullptr) {
#endif // ENABLE_GCODE_VIEWER
// Only show the path if preview_data is not set -> running from command line.
message = L("Exporting G-code");
message += " to ";
@ -1650,7 +1663,11 @@ std::string Print::export_gcode(const std::string& path_template, GCodePreviewDa
// The following line may die for multiple reasons.
GCode gcode;
#if ENABLE_GCODE_VIEWER
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
#else
gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb);
#endif // ENABLE_GCODE_VIEWER
return path.c_str();
}
@ -2181,16 +2198,16 @@ DynamicConfig PrintStatistics::config() const
DynamicConfig config;
std::string normal_print_time = short_time(this->estimated_normal_print_time);
std::string silent_print_time = short_time(this->estimated_silent_print_time);
config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.));
config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume));
config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost));
config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.));
config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume));
config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost));
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight));
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost));
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament));
config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost));
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament));
return config;
}

View file

@ -11,6 +11,9 @@
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/ThumbnailData.hpp"
#if ENABLE_GCODE_VIEWER
#include "GCode/GCodeProcessor.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "libslic3r.h"
@ -20,11 +23,17 @@ class Print;
class PrintObject;
class ModelObject;
class GCode;
#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
#endif // !ENABLE_GCODE_VIEWER
enum class SlicingMode : uint32_t;
class Layer;
class SupportLayer;
namespace FillAdaptive_Internal {
struct Octree;
};
// Print step IDs for keeping track of the print state.
enum PrintStep {
psSkirt,
@ -186,10 +195,8 @@ public:
std::vector<ExPolygons> slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
std::vector<ExPolygons> slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
// Helpers to project custom supports on slices
void project_and_append_custom_supports(FacetSupportType type, std::vector<ExPolygons>& expolys) const;
void project_and_append_custom_enforcers(std::vector<ExPolygons>& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); }
void project_and_append_custom_blockers(std::vector<ExPolygons>& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); }
// Helpers to project custom facets on slices
void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const;
private:
// to be called from Print only.
@ -232,6 +239,7 @@ private:
void discover_horizontal_shells();
void combine_infill();
void _generate_support_material();
std::pair<std::unique_ptr<FillAdaptive_Internal::Octree>, std::unique_ptr<FillAdaptive_Internal::Octree>> prepare_adaptive_infill_data();
// XYZ in scaled coordinates
Vec3crd m_size;
@ -300,8 +308,10 @@ struct PrintStatistics
PrintStatistics() { clear(); }
std::string estimated_normal_print_time;
std::string estimated_silent_print_time;
#if !ENABLE_GCODE_VIEWER
std::vector<std::pair<CustomGCode::Type, std::string>> estimated_normal_custom_gcode_print_times;
std::vector<std::pair<CustomGCode::Type, std::string>> estimated_silent_custom_gcode_print_times;
#endif // !ENABLE_GCODE_VIEWER
double total_used_filament;
double total_extruded_volume;
double total_cost;
@ -319,10 +329,12 @@ struct PrintStatistics
std::string finalize_output_path(const std::string &path_in) const;
void clear() {
#if !ENABLE_GCODE_VIEWER
estimated_normal_print_time.clear();
estimated_silent_print_time.clear();
estimated_normal_custom_gcode_print_times.clear();
estimated_silent_custom_gcode_print_times.clear();
#endif // !ENABLE_GCODE_VIEWER
total_used_filament = 0.;
total_extruded_volume = 0.;
total_cost = 0.;
@ -362,7 +374,11 @@ public:
void process() override;
// 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_GCODE_VIEWER
std::string export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
#else
std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
#endif // ENABLE_GCODE_VIEWER
// methods for handling state
bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); }
@ -399,7 +415,7 @@ public:
// in the notification center.
const PrintObject* get_object(ObjectID object_id) const {
auto it = std::find_if(m_objects.begin(), m_objects.end(),
[object_id](const PrintObject *obj) { return *static_cast<const ObjectID*>(obj) == object_id; });
[object_id](const PrintObject *obj) { return obj->id() == object_id; });
return (it == m_objects.end()) ? nullptr : *it;
}
const PrintRegionPtrs& regions() const { return m_regions; }
@ -417,6 +433,7 @@ public:
const Polygon& first_layer_convex_hull() const { return m_first_layer_convex_hull; }
const PrintStatistics& print_statistics() const { return m_print_statistics; }
PrintStatistics& print_statistics() { return m_print_statistics; }
// Wipe tower support.
bool has_wipe_tower() const;

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "PrintBase.hpp"
#include <boost/filesystem.hpp>
@ -68,7 +69,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
filename = boost::filesystem::change_extension(filename, default_ext);
return filename.string();
} catch (std::runtime_error &err) {
throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
throw Slic3r::RuntimeError(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
}
}
@ -93,7 +94,7 @@ void PrintBase::status_update_warnings(ObjectID object_id, int step, PrintStateB
if (this->m_status_callback)
m_status_callback(SlicingStatus(*this, step));
else if (! message.empty())
printf("%s warning: %s\n", (object_id == ObjectID(*this)) ? "print" : "print object", message.c_str());
printf("%s warning: %s\n", (object_id == this->id()) ? "print" : "print object", message.c_str());
}
tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print)
@ -108,7 +109,7 @@ std::function<void()> PrintObjectBase::cancel_callback(PrintBase *print)
void PrintObjectBase::status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message)
{
print->status_update_warnings(*this, step, warning_level, message);
print->status_update_warnings(this->id(), step, warning_level, message);
}
} // namespace Slic3r

View file

@ -304,7 +304,7 @@ private:
class PrintBase;
class PrintObjectBase : public ObjectID
class PrintObjectBase : public ObjectBase
{
public:
const ModelObject* model_object() const { return m_model_object; }
@ -335,7 +335,7 @@ protected:
* The PrintBase class will abstract this flow for different technologies.
*
*/
class PrintBase : public ObjectID
class PrintBase : public ObjectBase
{
public:
PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); }
@ -386,9 +386,9 @@ public:
struct SlicingStatus {
SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {}
SlicingStatus(const PrintBase &print, int warning_step) :
flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print), warning_step(warning_step) {}
flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print.id()), warning_step(warning_step) {}
SlicingStatus(const PrintObjectBase &print_object, int warning_step) :
flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object), warning_step(warning_step) {}
flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object.id()), warning_step(warning_step) {}
int percent { -1 };
std::string text;
// Bitmap of flags.
@ -507,9 +507,9 @@ protected:
bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
PrintStateBase::TimeStamp set_done(PrintStepEnum step) {
std::pair<PrintStateBase::TimeStamp, bool> status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); });
if (status.second)
this->status_update_warnings(*this, static_cast<int>(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string());
return status.first;
if (status.second)
this->status_update_warnings(this->id(), static_cast<int>(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string());
return status.first;
}
bool invalidate_step(PrintStepEnum step)
{ return m_state.invalidate(step, this->cancel_callback()); }
@ -530,7 +530,7 @@ protected:
std::pair<PrintStepEnum, bool> active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex());
if (active_step.second)
// Update UI.
this->status_update_warnings(*this, static_cast<int>(active_step.first), warning_level, message);
this->status_update_warnings(this->id(), static_cast<int>(active_step.first), warning_level, message);
}
private:
@ -556,9 +556,9 @@ protected:
{ return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); }
PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) {
std::pair<PrintStateBase::TimeStamp, bool> status = m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); });
if (status.second)
this->status_update_warnings(m_print, static_cast<int>(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string());
return status.first;
if (status.second)
this->status_update_warnings(m_print, static_cast<int>(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string());
return status.first;
}
bool invalidate_step(PrintObjectStepEnum step)

View file

@ -130,6 +130,37 @@ void PrintConfigDef::init_common_params()
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.2));
// Options used by physical printers
def = this->add("printhost_user", coString);
def->label = L("User");
// def->tooltip = L("");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_password", coString);
def->label = L("Password");
// def->tooltip = L("");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("preset_name", coString);
def->label = L("Printer preset name");
def->tooltip = L("Related printer preset name");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("printhost_authorization_type", coEnum);
def->label = L("Authorization Type");
// def->tooltip = L("");
def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values();
def->enum_values.push_back("key");
def->enum_values.push_back("user");
def->enum_labels.push_back("KeyPassword");
def->enum_labels.push_back("UserPassword");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<AuthorizationType>(atKeyPassword));
}
void PrintConfigDef::init_fff_params()
@ -850,6 +881,8 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords");
def->enum_values.push_back("octagramspiral");
def->enum_values.push_back("adaptivecubic");
def->enum_values.push_back("supportcubic");
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Grid"));
def->enum_labels.push_back(L("Triangles"));
@ -863,6 +896,8 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
def->enum_labels.push_back(L("Adaptive Cubic"));
def->enum_labels.push_back(L("Support Cubic"));
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
def = this->add("first_layer_acceleration", coFloat);
@ -1101,6 +1136,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("ironing_type", coEnum);
def->label = L("Ironing Type");
def->category = L("Ironing");
def->tooltip = L("Ironing Type");
def->enum_keys_map = &ConfigOptionEnum<IroningType>::get_enum_values();
def->enum_values.push_back("top");
@ -1124,7 +1160,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("ironing_spacing", coFloat);
def->label = L("Spacing between ironing passes");
def->tooltip = L("Distance between ironing lins");
def->category = L("Ironing");
def->tooltip = L("Distance between ironing lines");
def->sidetext = L("mm");
def->min = 0;
def->mode = comExpert;
@ -2715,7 +2752,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionBool(true));
def = this->add("support_head_front_diameter", coFloat);
def->label = L("Support head front diameter");
def->label = L("Pinhead front diameter");
def->category = L("Supports");
def->tooltip = L("Diameter of the pointing side of the head");
def->sidetext = L("mm");
@ -2724,7 +2761,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(0.4));
def = this->add("support_head_penetration", coFloat);
def->label = L("Support head penetration");
def->label = L("Head penetration");
def->category = L("Supports");
def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm");
@ -2733,7 +2770,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add("support_head_width", coFloat);
def->label = L("Support head width");
def->label = L("Pinhead width");
def->category = L("Supports");
def->tooltip = L("Width from the back sphere center to the front sphere center");
def->sidetext = L("mm");
@ -2743,7 +2780,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_pillar_diameter", coFloat);
def->label = L("Support pillar diameter");
def->label = L("Pillar diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the support pillars");
def->sidetext = L("mm");
@ -2751,6 +2788,17 @@ void PrintConfigDef::init_sla_params()
def->max = 15;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_small_pillar_diameter_percent", coPercent);
def->label = L("Small pillar diameter percent");
def->category = L("Supports");
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
"which are used in problematic areas where a normal pilla cannot fit.");
def->sidetext = L("%");
def->min = 1;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercent(50));
def = this->add("support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar");
@ -2763,7 +2811,7 @@ void PrintConfigDef::init_sla_params()
def->set_default_value(new ConfigOptionInt(3));
def = this->add("support_pillar_connection_mode", coEnum);
def->label = L("Support pillar connection mode");
def->label = L("Pillar connection mode");
def->tooltip = L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending"
@ -3489,6 +3537,12 @@ CLIActionsConfigDef::CLIActionsConfigDef()
def->cli = "export-gcode|gcode|g";
def->set_default_value(new ConfigOptionBool(false));
def = this->add("gcodeviewer", coBool);
def->label = L("G-code viewer");
def->tooltip = L("Visualize an already sliced and saved G-code");
def->cli = "gcodeviewer";
def->set_default_value(new ConfigOptionBool(false));
def = this->add("slice", coBool);
def->label = L("Slice");
def->tooltip = L("Slice the model as FFF or SLA based on the printer_technology configuration value.");

View file

@ -33,9 +33,13 @@ enum PrintHostType {
htOctoPrint, htDuet, htFlashAir, htAstroBox
};
enum AuthorizationType {
atKeyPassword, atUserPassword
};
enum InfillPattern : int {
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount,
};
enum class IroningType {
@ -110,6 +114,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g
return keys_map;
}
template<> inline const t_config_enum_values& ConfigOptionEnum<AuthorizationType>::get_enum_values() {
static t_config_enum_values keys_map;
if (keys_map.empty()) {
keys_map["key"] = atKeyPassword;
keys_map["user"] = atUserPassword;
}
return keys_map;
}
template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enum_values() {
static t_config_enum_values keys_map;
if (keys_map.empty()) {
@ -127,6 +140,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
keys_map["hilbertcurve"] = ipHilbertCurve;
keys_map["archimedeanchords"] = ipArchimedeanChords;
keys_map["octagramspiral"] = ipOctagramSpiral;
keys_map["adaptivecubic"] = ipAdaptiveCubic;
keys_map["supportcubic"] = ipSupportCubic;
}
return keys_map;
}
@ -227,9 +242,13 @@ class DynamicPrintConfig : public DynamicConfig
public:
DynamicPrintConfig() {}
DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {}
DynamicPrintConfig(DynamicPrintConfig &&rhs) noexcept : DynamicConfig(std::move(rhs)) {}
explicit DynamicPrintConfig(const StaticPrintConfig &rhs);
explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {}
DynamicPrintConfig& operator=(const DynamicPrintConfig &rhs) { DynamicConfig::operator=(rhs); return *this; }
DynamicPrintConfig& operator=(DynamicPrintConfig &&rhs) noexcept { DynamicConfig::operator=(std::move(rhs)); return *this; }
static DynamicPrintConfig full_print_config();
static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys);
@ -1019,6 +1038,10 @@ public:
// Radius in mm of the support pillars.
ConfigOptionFloat support_pillar_diameter /*= 0.8*/;
// The percentage of smaller pillars compared to the normal pillar diameter
// which are used in problematic areas where a normal pilla cannot fit.
ConfigOptionPercent support_small_pillar_diameter_percent;
// How much bridge (supporting another pinhead) can be placed on a pillar.
ConfigOptionInt support_max_bridges_on_pillar;
@ -1143,6 +1166,7 @@ protected:
OPT_PTR(support_head_penetration);
OPT_PTR(support_head_width);
OPT_PTR(support_pillar_diameter);
OPT_PTR(support_small_pillar_diameter_percent);
OPT_PTR(support_max_bridges_on_pillar);
OPT_PTR(support_pillar_connection_mode);
OPT_PTR(support_buildplate_only);

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@ -9,6 +10,9 @@
#include "Surface.hpp"
#include "Slicing.hpp"
#include "Utils.hpp"
#include "AABBTreeIndirect.hpp"
#include "Fill/FillAdaptive.hpp"
#include "Format/STL.hpp"
#include <utility>
#include <boost/log/trivial.hpp>
@ -135,7 +139,7 @@ void PrintObject::slice()
}
});
if (m_layers.empty())
throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
this->set_done(posSlice);
}
@ -369,13 +373,15 @@ void PrintObject::infill()
this->prepare_infill();
if (this->set_started(posInfill)) {
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this](const tbb::blocked_range<size_t>& range) {
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills();
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get());
}
}
);
@ -421,13 +427,83 @@ void PrintObject::generate_support_material()
// therefore they cannot be printed without supports.
for (const Layer *layer : m_layers)
if (layer->empty())
throw std::runtime_error("Levitating objects cannot be printed without supports.");
throw Slic3r::SlicingError("Levitating objects cannot be printed without supports.");
#endif
}
this->set_done(posSupportMaterial);
}
}
//#define ADAPTIVE_SUPPORT_SIMPLE
std::pair<std::unique_ptr<FillAdaptive_Internal::Octree>, std::unique_ptr<FillAdaptive_Internal::Octree>> PrintObject::prepare_adaptive_infill_data()
{
using namespace FillAdaptive_Internal;
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
std::unique_ptr<Octree> adaptive_fill_octree = {}, support_fill_octree = {};
if (adaptive_line_spacing == 0. && support_line_spacing == 0.)
return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree));
TriangleMesh mesh = this->model_object()->raw_mesh();
mesh.transform(m_trafo, true);
// Apply XY shift
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0);
// Center of the first cube in octree
Vec3d mesh_origin = mesh.bounding_box().center();
#ifdef ADAPTIVE_SUPPORT_SIMPLE
if (mesh.its.vertices.empty())
{
mesh.require_shared_vertices();
}
Vec3f vertical(0, 0, 1);
indexed_triangle_set its_set;
its_set.vertices = mesh.its.vertices;
// Filter out non overhanging faces
for (size_t i = 0; i < mesh.its.indices.size(); ++i) {
stl_triangle_vertex_indices vertex_idx = mesh.its.indices[i];
auto its_calculate_normal = [](const stl_triangle_vertex_indices &index, const std::vector<stl_vertex> &vertices) {
stl_normal normal = (vertices[index.y()] - vertices[index.x()]).cross(vertices[index.z()] - vertices[index.x()]);
return normal;
};
stl_normal normal = its_calculate_normal(vertex_idx, mesh.its.vertices);
stl_normalize_vector(normal);
if(normal.dot(vertical) >= 0.707) {
its_set.indices.push_back(vertex_idx);
}
}
mesh = TriangleMesh(its_set);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
Slic3r::store_stl(debug_out_path("overhangs.stl").c_str(), &mesh, false);
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
#endif /* ADAPTIVE_SUPPORT_SIMPLE */
Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0);
Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()).inverse();
if (adaptive_line_spacing != 0.) {
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes
mesh.transform(rotation_matrix);
adaptive_fill_octree = FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin);
}
if (support_line_spacing != 0.)
support_fill_octree = FillSupportCubic::build_octree(mesh, support_line_spacing, rotation_matrix * mesh_origin, rotation_matrix);
return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree));
}
void PrintObject::clear_layers()
{
for (Layer *l : m_layers)
@ -2669,18 +2745,20 @@ void PrintObject::_generate_support_material()
}
void PrintObject::project_and_append_custom_supports(
FacetSupportType type, std::vector<ExPolygons>& expolys) const
void PrintObject::project_and_append_custom_facets(
bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const
{
for (const ModelVolume* mv : this->model_object()->volumes) {
const std::vector<int> custom_facets = mv->m_supported_facets.get_facets(type);
if (custom_facets.empty())
const indexed_triangle_set custom_facets = seam
? mv->m_seam_facets.get_facets(*mv, type)
: mv->m_supported_facets.get_facets(*mv, type);
if (! mv->is_model_part() || custom_facets.indices.empty())
continue;
const TriangleMesh& mesh = mv->mesh();
const Transform3f& tr1 = mv->get_matrix().cast<float>();
const Transform3f& tr2 = this->trafo().cast<float>();
const Transform3f tr = tr2 * tr1;
const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f);
// The projection will be at most a pentagon. Let's minimize heap
@ -2705,11 +2783,11 @@ void PrintObject::project_and_append_custom_supports(
};
// Vector to collect resulting projections from each triangle.
std::vector<TriangleProjections> projections_of_triangles(custom_facets.size());
std::vector<TriangleProjections> projections_of_triangles(custom_facets.indices.size());
// Iterate over all triangles.
tbb::parallel_for(
tbb::blocked_range<size_t>(0, custom_facets.size()),
tbb::blocked_range<size_t>(0, custom_facets.indices.size()),
[&](const tbb::blocked_range<size_t>& range) {
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
@ -2717,10 +2795,11 @@ void PrintObject::project_and_append_custom_supports(
// Transform the triangle into worlds coords.
for (int i=0; i<3; ++i)
facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)];
facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)];
// Ignore triangles with upward-pointing normal.
if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.)
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
if (! seam && tr_det_sign * z_comp > 0.)
continue;
// Sort the three vertices according to z-coordinate.
@ -2732,7 +2811,7 @@ void PrintObject::project_and_append_custom_supports(
std::array<Vec2f, 3> trianglef;
for (int i=0; i<3; ++i) {
trianglef[i] = Vec2f(facet[i].x(), facet[i].y());
trianglef[i] += Vec2f(unscale<float>(this->center_offset().x()),
trianglef[i] -= Vec2f(unscale<float>(this->center_offset().x()),
unscale<float>(this->center_offset().y()));
}
@ -2822,8 +2901,9 @@ void PrintObject::project_and_append_custom_supports(
// Now append the collected polygons to respective layers.
for (auto& trg : projections_of_triangles) {
int layer_id = trg.first_layer_id;
for (const LightPolygon& poly : trg.polygons) {
if (layer_id >= int(expolys.size()))
break; // part of triangle could be projected above top layer
expolys[layer_id].emplace_back(std::move(poly.pts));
++layer_id;
}

View file

@ -1,3 +1,4 @@
#include "Exception.hpp"
#include "Print.hpp"
namespace Slic3r {
@ -13,7 +14,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const
else if (role == frSolidInfill || role == frTopSolidInfill)
extruder = m_config.solid_infill_extruder;
else
throw std::invalid_argument("Unknown role");
throw Slic3r::InvalidArgument("Unknown role");
return extruder;
}
@ -40,7 +41,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
} else if (role == frTopSolidInfill) {
config_width = m_config.top_infill_extrusion_width;
} else {
throw std::invalid_argument("Unknown role");
throw Slic3r::InvalidArgument("Unknown role");
}
}

View file

@ -128,12 +128,13 @@ protected:
}
public:
template<class GammaFn> AGGRaster(const Resolution &res,
template<class GammaFn>
AGGRaster(const Resolution &res,
const PixelDim & pd,
const Trafo & trafo,
const TColor & foreground,
const TColor & background,
GammaFn && gammafn)
const TColor & foreground,
const TColor & background,
GammaFn && gammafn)
: m_resolution(res)
, m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm)
, m_buf(res.pixels())

View file

@ -1,7 +1,9 @@
#ifndef SLA_BOOSTADAPTER_HPP
#define SLA_BOOSTADAPTER_HPP
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <boost/geometry.hpp>
namespace boost {

View file

@ -0,0 +1,152 @@
#include "Clustering.hpp"
#include "boost/geometry/index/rtree.hpp"
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/SLA/BoostAdapter.hpp>
namespace Slic3r { namespace sla {
namespace bgi = boost::geometry::index;
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
namespace {
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
{
return e1.second < e2.second;
};
ClusteredPoints cluster(Index3D &sindex,
unsigned max_points,
std::function<std::vector<PointIndexEl>(
const Index3D &, const PointIndexEl &)> qfn)
{
using Elems = std::vector<PointIndexEl>;
// Recursive function for visiting all the points in a given distance to
// each other
std::function<void(Elems&, Elems&)> group =
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
{
for(auto& p : pts) {
std::vector<PointIndexEl> tmp = qfn(sindex, p);
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
Elems newpts;
std::set_difference(tmp.begin(), tmp.end(),
cluster.begin(), cluster.end(),
std::back_inserter(newpts), cmp_ptidx_elements);
int c = max_points && newpts.size() + cluster.size() > max_points?
int(max_points - cluster.size()) : int(newpts.size());
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
group(newpts, cluster);
}
};
std::vector<Elems> clusters;
for(auto it = sindex.begin(); it != sindex.end();) {
Elems cluster = {};
Elems pts = {*it};
group(pts, cluster);
for(auto& c : cluster) sindex.remove(c);
it = sindex.begin();
clusters.emplace_back(cluster);
}
ClusteredPoints result;
for(auto& cluster : clusters) {
result.emplace_back();
for(auto c : cluster) result.back().emplace_back(c.second);
}
return result;
}
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
const PointIndexEl& p,
double dist,
unsigned max_points)
{
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
sindex.query(
bgi::nearest(p.first, max_points),
std::back_inserter(tmp)
);
for(auto it = tmp.begin(); it < tmp.end(); ++it)
if((p.first - it->first).norm() > dist) it = tmp.erase(it);
return tmp;
}
} // namespace
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
double dist,
unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
return cluster(sindex, max_points,
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
{
return distance_queryfn(sidx, p, dist, max_points);
});
}
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
return cluster(sindex, max_points,
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
{
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
return predicate(p, e);
}), std::back_inserter(tmp));
return tmp;
});
}
ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(Eigen::Index i = 0; i < pts.rows(); i++)
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
return cluster(sindex, max_points,
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
{
return distance_queryfn(sidx, p, dist, max_points);
});
}
}} // namespace Slic3r::sla

View file

@ -2,7 +2,8 @@
#define SLA_CLUSTERING_HPP
#include <vector>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
namespace Slic3r { namespace sla {
@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices,
double dist,
unsigned max_points);
ClusteredPoints cluster(const PointSet& points,
ClusteredPoints cluster(const Eigen::MatrixXd& points,
double dist,
unsigned max_points);
@ -26,5 +27,56 @@ ClusteredPoints cluster(
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
unsigned max_points);
}}
// This function returns the position of the centroid in the input 'clust'
// vector of point indices.
template<class DistFn, class PointFn>
long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df)
{
switch(clust.size()) {
case 0: /* empty cluster */ return -1;
case 1: /* only one element */ return 0;
case 2: /* if two elements, there is no center */ return 0;
default: ;
}
// The function works by calculating for each point the average distance
// from all the other points in the cluster. We create a selector bitmask of
// the same size as the cluster. The bitmask will have two true bits and
// false bits for the rest of items and we will loop through all the
// permutations of the bitmask (combinations of two points). Get the
// distance for the two points and add the distance to the averages.
// The point with the smallest average than wins.
// The complexity should be O(n^2) but we will mostly apply this function
// for small clusters only (cca 3 elements)
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
do {
std::array<size_t, 2> idx;
for(size_t i = 0, j = 0; i < clust.size(); i++)
if(sel[i]) idx[j++] = i;
double d = df(pointfn(clust[idx[0]]),
pointfn(clust[idx[1]]));
// add the distance to the sums for both associated points
for(auto i : idx) avgs[i] += d;
// now continue with the next permutation of the bitmask with two 1s
} while(std::next_permutation(sel.begin(), sel.end()));
// Divide by point size in the cluster to get the average (may be redundant)
for(auto& a : avgs) a /= clust.size();
// get the lowest average distance and return the index
auto minit = std::min_element(avgs.begin(), avgs.end());
return long(minit - avgs.begin());
}
}} // namespace Slic3r::sla
#endif // CLUSTERING_HPP

View file

@ -1,27 +0,0 @@
#ifndef SLA_COMMON_HPP
#define SLA_COMMON_HPP
#include <memory>
#include <vector>
#include <numeric>
#include <functional>
#include <Eigen/Geometry>
namespace Slic3r {
// Typedefs from Point.hpp
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
namespace sla {
using PointSet = Eigen::MatrixXd;
} // namespace sla
} // namespace Slic3r
#endif // SLASUPPORTTREE_HPP

View file

@ -4,6 +4,12 @@
#include <tbb/spin_mutex.h>
#include <tbb/mutex.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <algorithm>
#include <numeric>
#include <libslic3r/libslic3r.h>
namespace Slic3r {
namespace sla {
@ -17,18 +23,59 @@ template<bool> struct _ccr {};
template<> struct _ccr<true>
{
using SpinningMutex = tbb::spin_mutex;
using BlockingMutex = tbb::mutex;
template<class It, class Fn>
static inline void enumerate(It from, It to, Fn fn)
using BlockingMutex = tbb::mutex;
template<class Fn, class It>
static IteratorOnly<It, void> loop_(const tbb::blocked_range<It> &range, Fn &&fn)
{
auto iN = to - from;
size_t N = iN < 0 ? 0 : size_t(iN);
tbb::parallel_for(size_t(0), N, [from, fn](size_t n) {
fn(*(from + decltype(iN)(n)), n);
for (auto &el : range) fn(el);
}
template<class Fn, class I>
static IntegerOnly<I, void> loop_(const tbb::blocked_range<I> &range, Fn &&fn)
{
for (I i = range.begin(); i < range.end(); ++i) fn(i);
}
template<class It, class Fn>
static void for_each(It from, It to, Fn &&fn, size_t granularity = 1)
{
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
[&fn, from](const auto &range) {
loop_(range, std::forward<Fn>(fn));
});
}
template<class I, class MergeFn, class T, class AccessFn>
static T reduce(I from,
I to,
const T &init,
MergeFn &&mergefn,
AccessFn &&access,
size_t granularity = 1
)
{
return tbb::parallel_reduce(
tbb::blocked_range{from, to, granularity}, init,
[&](const auto &range, T subinit) {
T acc = subinit;
loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); });
return acc;
},
std::forward<MergeFn>(mergefn));
}
template<class I, class MergeFn, class T>
static IteratorOnly<I, T> reduce(I from,
I to,
const T & init,
MergeFn &&mergefn,
size_t granularity = 1)
{
return reduce(
from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; }, granularity);
}
};
template<> struct _ccr<false>
@ -39,11 +86,52 @@ private:
public:
using SpinningMutex = _Mtx;
using BlockingMutex = _Mtx;
template<class It, class Fn>
static inline void enumerate(It from, It to, Fn fn)
template<class Fn, class It>
static IteratorOnly<It, void> loop_(It from, It to, Fn &&fn)
{
for (auto it = from; it != to; ++it) fn(*it, size_t(it - from));
for (auto it = from; it != to; ++it) fn(*it);
}
template<class Fn, class I>
static IntegerOnly<I, void> loop_(I from, I to, Fn &&fn)
{
for (I i = from; i < to; ++i) fn(i);
}
template<class It, class Fn>
static void for_each(It from,
It to,
Fn &&fn,
size_t /* ignore granularity */ = 1)
{
loop_(from, to, std::forward<Fn>(fn));
}
template<class I, class MergeFn, class T, class AccessFn>
static T reduce(I from,
I to,
const T & init,
MergeFn &&mergefn,
AccessFn &&access,
size_t /*granularity*/ = 1
)
{
T acc = init;
loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); });
return acc;
}
template<class I, class MergeFn, class T>
static IteratorOnly<I, T> reduce(I from,
I to,
const T &init,
MergeFn &&mergefn,
size_t /*granularity*/ = 1
)
{
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; });
}
};

View file

@ -1,5 +1,5 @@
#include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/Format/objparser.hpp>
@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh)
faces3.swap(trmesh.its.indices);
}
Contour3D::Contour3D(const EigenMesh3D &emesh) {
Contour3D::Contour3D(const IndexedMesh &emesh) {
points.reserve(emesh.vertices().size());
faces3.reserve(emesh.indices().size());

View file

@ -1,13 +1,16 @@
#ifndef SLA_CONTOUR3D_HPP
#define SLA_CONTOUR3D_HPP
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/TriangleMesh.hpp>
namespace Slic3r { namespace sla {
namespace Slic3r {
class EigenMesh3D;
// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils)
using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
namespace sla {
class IndexedMesh;
/// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
/// other meshes of this type and converting to and from other mesh formats.
@ -19,7 +22,7 @@ struct Contour3D {
Contour3D() = default;
Contour3D(const TriangleMesh &trmesh);
Contour3D(TriangleMesh &&trmesh);
Contour3D(const EigenMesh3D &emesh);
Contour3D(const IndexedMesh &emesh);
Contour3D& merge(const Contour3D& ctr);
Contour3D& merge(const Pointf3s& triangles);

View file

@ -3,11 +3,10 @@
#include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/SimplifyMesh.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp>
#include <boost/log/trivial.hpp>
@ -160,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
for (size_t i=0; i<2; ++i)
out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero());
out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero());
const float sqr_radius = pow(radius, 2.f);
@ -274,4 +273,13 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
}
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg)
{
std::unique_ptr<Slic3r::TriangleMesh> inter_ptr =
Slic3r::sla::generate_interior(mesh);
if (inter_ptr) mesh.merge(*inter_ptr);
mesh.require_shared_vertices();
}
}} // namespace Slic3r::sla

View file

@ -2,7 +2,6 @@
#define SLA_HOLLOWING_HPP
#include <memory>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/JobController.hpp>
@ -59,10 +58,14 @@ struct DrainHole
using DrainHoles = std::vector<DrainHole>;
constexpr float HoleStickOutLength = 1.f;
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
const HollowingConfig & = {},
const JobController &ctl = {});
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg);
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
const std::vector<float> &slicegrid,
float closing_radius,

View file

@ -1,187 +1,18 @@
#include <cmath>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Concurrency.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/Clustering.hpp>
#include "IndexedMesh.hpp"
#include "Concurrency.hpp"
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/TriangleMesh.hpp>
// for concave hull merging decisions
#include <libslic3r/SLA/BoostAdapter.hpp>
#include "boost/geometry/index/rtree.hpp"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
#include <igl/remove_duplicate_vertices.h>
#include <numeric>
#ifdef SLIC3R_HOLE_RAYCASTER
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#endif
namespace Slic3r { namespace sla {
#ifdef _MSC_VER
#pragma warning(pop)
#endif
namespace Slic3r {
namespace sla {
/* **************************************************************************
* PointIndex implementation
* ************************************************************************** */
class PointIndex::Impl {
public:
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
boost::geometry::index::rstar<16, 4> /* ? */ >;
BoostIndex m_store;
};
PointIndex::PointIndex(): m_impl(new Impl()) {}
PointIndex::~PointIndex() {}
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
PointIndex& PointIndex::operator=(const PointIndex &cpy)
{
m_impl.reset(new Impl(*cpy.m_impl));
return *this;
}
PointIndex& PointIndex::operator=(PointIndex &&cpy)
{
m_impl.swap(cpy.m_impl);
return *this;
}
void PointIndex::insert(const PointIndexEl &el)
{
m_impl->m_store.insert(el);
}
bool PointIndex::remove(const PointIndexEl& el)
{
return m_impl->m_store.remove(el) == 1;
}
std::vector<PointIndexEl>
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
{
namespace bgi = boost::geometry::index;
std::vector<PointIndexEl> ret;
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
return ret;
}
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
{
namespace bgi = boost::geometry::index;
std::vector<PointIndexEl> ret; ret.reserve(k);
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
return ret;
}
size_t PointIndex::size() const
{
return m_impl->m_store.size();
}
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
{
for(auto& el : m_impl->m_store) fn(el);
}
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
{
for(const auto &el : m_impl->m_store) fn(el);
}
/* **************************************************************************
* BoxIndex implementation
* ************************************************************************** */
class BoxIndex::Impl {
public:
using BoostIndex = boost::geometry::index::
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
BoostIndex m_store;
};
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
BoxIndex::~BoxIndex() {}
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
{
m_impl.reset(new Impl(*cpy.m_impl));
return *this;
}
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
{
m_impl.swap(cpy.m_impl);
return *this;
}
void BoxIndex::insert(const BoxIndexEl &el)
{
m_impl->m_store.insert(el);
}
bool BoxIndex::remove(const BoxIndexEl& el)
{
return m_impl->m_store.remove(el) == 1;
}
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
BoxIndex::QueryType qt)
{
namespace bgi = boost::geometry::index;
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
switch (qt) {
case qtIntersects:
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
break;
case qtWithin:
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
}
return ret;
}
size_t BoxIndex::size() const
{
return m_impl->m_store.size();
}
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
{
for(auto& el : m_impl->m_store) fn(el);
}
/* ****************************************************************************
* EigenMesh3D implementation
* ****************************************************************************/
class EigenMesh3D::AABBImpl {
class IndexedMesh::AABBImpl {
private:
AABBTreeIndirect::Tree3f m_tree;
@ -189,7 +20,7 @@ public:
void init(const TriangleMesh& tm)
{
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
tm.its.vertices, tm.its.indices);
tm.its.vertices, tm.its.indices);
}
void intersect_ray(const TriangleMesh& tm,
@ -215,9 +46,9 @@ public:
size_t idx_unsigned = 0;
Vec3d closest_vec3d(closest);
double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
tm.its.vertices,
tm.its.indices,
m_tree, point, idx_unsigned, closest_vec3d);
tm.its.vertices,
tm.its.indices,
m_tree, point, idx_unsigned, closest_vec3d);
i = int(idx_unsigned);
closest = closest_vec3d;
return dist;
@ -226,72 +57,71 @@ public:
static const constexpr double MESH_EPS = 1e-6;
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh)
IndexedMesh::IndexedMesh(const TriangleMesh& tmesh)
: m_aabb(new AABBImpl()), m_tm(&tmesh)
{
auto&& bb = tmesh.bounding_box();
m_ground_level += bb.min(Z);
// Build the AABB accelaration tree
m_aabb->init(tmesh);
}
EigenMesh3D::~EigenMesh3D() {}
IndexedMesh::~IndexedMesh() {}
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
IndexedMesh::IndexedMesh(const IndexedMesh &other):
m_tm(other.m_tm), m_ground_level(other.m_ground_level),
m_aabb( new AABBImpl(*other.m_aabb) ) {}
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other)
{
m_tm = other.m_tm;
m_ground_level = other.m_ground_level;
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
}
EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default;
EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
IndexedMesh::IndexedMesh(IndexedMesh &&other) = default;
const std::vector<Vec3f>& EigenMesh3D::vertices() const
const std::vector<Vec3f>& IndexedMesh::vertices() const
{
return m_tm->its.vertices;
}
const std::vector<Vec3i>& EigenMesh3D::indices() const
const std::vector<Vec3i>& IndexedMesh::indices() const
{
return m_tm->its.indices;
}
const Vec3f& EigenMesh3D::vertices(size_t idx) const
const Vec3f& IndexedMesh::vertices(size_t idx) const
{
return m_tm->its.vertices[idx];
}
const Vec3i& EigenMesh3D::indices(size_t idx) const
const Vec3i& IndexedMesh::indices(size_t idx) const
{
return m_tm->its.indices[idx];
}
Vec3d EigenMesh3D::normal_by_face_id(int face_id) const {
Vec3d IndexedMesh::normal_by_face_id(int face_id) const {
return m_tm->stl.facet_start[face_id].normal.cast<double>();
}
EigenMesh3D::hit_result
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
IndexedMesh::hit_result
IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{
assert(is_approx(dir.norm(), 1.));
igl::Hit hit;
@ -319,13 +149,13 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
return ret;
}
std::vector<EigenMesh3D::hit_result>
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
std::vector<IndexedMesh::hit_result>
IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
{
std::vector<EigenMesh3D::hit_result> outs;
std::vector<IndexedMesh::hit_result> outs;
std::vector<igl::Hit> hits;
m_aabb->intersect_ray(*m_tm, s, dir, hits);
// The sort is necessary, the hits are not always sorted.
std::sort(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
@ -334,13 +164,13 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
// along an axis of a cube due to floating-point approximations in igl (?)
hits.erase(std::unique(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b)
{ return a.t == b.t; }),
{ return a.t == b.t; }),
hits.end());
// Convert the igl::Hit into hit_result
outs.reserve(hits.size());
for (const igl::Hit& hit : hits) {
outs.emplace_back(EigenMesh3D::hit_result(*this));
outs.emplace_back(IndexedMesh::hit_result(*this));
outs.back().m_t = double(hit.t);
outs.back().m_dir = dir;
outs.back().m_source = s;
@ -355,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
#ifdef SLIC3R_HOLE_RAYCASTER
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
const std::vector<EigenMesh3D::hit_result>& object_hits) const
IndexedMesh::hit_result IndexedMesh::filter_hits(
const std::vector<IndexedMesh::hit_result>& object_hits) const
{
assert(! m_holes.empty());
hit_result out(*this);
@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
};
std::vector<HoleHit> hole_isects;
hole_isects.reserve(m_holes.size());
auto sf = s.cast<float>();
auto dirf = dir.cast<float>();
@ -452,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
#endif
double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
double sqdst = 0;
Eigen::Matrix<double, 1, 3> pp = p;
Eigen::Matrix<double, 1, 3> cc;
@ -461,31 +291,19 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
return sqdst;
}
/* ****************************************************************************
* Misc functions
* ****************************************************************************/
namespace {
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05)
static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05)
{
using Line3D = Eigen::ParametrizedLine<double, 3>;
auto line = Line3D::Through(e1, e2);
double d = line.distance(p);
return std::abs(d) < eps;
}
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
auto p = pp2 - pp1;
return std::sqrt(p.transpose() * p);
}
}
PointSet normals(const PointSet& points,
const EigenMesh3D& mesh,
const IndexedMesh& mesh,
double eps,
std::function<void()> thr, // throw on cancel
const std::vector<unsigned>& pt_indices)
@ -502,10 +320,10 @@ PointSet normals(const PointSet& points,
PointSet ret(range.size(), 3);
// for (size_t ridx = 0; ridx < range.size(); ++ridx)
ccr::enumerate(
range.begin(), range.end(),
[&ret, &mesh, &points, thr, eps](unsigned el, size_t ridx) {
ccr::for_each(size_t(0), range.size(),
[&ret, &mesh, &points, thr, eps, &range](size_t ridx) {
thr();
unsigned el = range[ridx];
auto eidx = Eigen::Index(el);
int faceid = 0;
Vec3d p;
@ -531,11 +349,11 @@ PointSet normals(const PointSet& points,
// ic will mark a single vertex.
int ia = -1, ib = -1, ic = -1;
if (std::abs(distance(p, p1)) < eps) {
if (std::abs((p - p1).norm()) < eps) {
ic = trindex(0);
} else if (std::abs(distance(p, p2)) < eps) {
} else if (std::abs((p - p2).norm()) < eps) {
ic = trindex(1);
} else if (std::abs(distance(p, p3)) < eps) {
} else if (std::abs((p - p3).norm()) < eps) {
ic = trindex(2);
} else if (point_on_edge(p, p1, p2, eps)) {
ia = trindex(0);
@ -612,148 +430,4 @@ PointSet normals(const PointSet& points,
return ret;
}
namespace bgi = boost::geometry::index;
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
namespace {
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
{
return e1.second < e2.second;
};
ClusteredPoints cluster(Index3D &sindex,
unsigned max_points,
std::function<std::vector<PointIndexEl>(
const Index3D &, const PointIndexEl &)> qfn)
{
using Elems = std::vector<PointIndexEl>;
// Recursive function for visiting all the points in a given distance to
// each other
std::function<void(Elems&, Elems&)> group =
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
{
for(auto& p : pts) {
std::vector<PointIndexEl> tmp = qfn(sindex, p);
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
Elems newpts;
std::set_difference(tmp.begin(), tmp.end(),
cluster.begin(), cluster.end(),
std::back_inserter(newpts), cmp_ptidx_elements);
int c = max_points && newpts.size() + cluster.size() > max_points?
int(max_points - cluster.size()) : int(newpts.size());
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
group(newpts, cluster);
}
};
std::vector<Elems> clusters;
for(auto it = sindex.begin(); it != sindex.end();) {
Elems cluster = {};
Elems pts = {*it};
group(pts, cluster);
for(auto& c : cluster) sindex.remove(c);
it = sindex.begin();
clusters.emplace_back(cluster);
}
ClusteredPoints result;
for(auto& cluster : clusters) {
result.emplace_back();
for(auto c : cluster) result.back().emplace_back(c.second);
}
return result;
}
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
const PointIndexEl& p,
double dist,
unsigned max_points)
{
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
sindex.query(
bgi::nearest(p.first, max_points),
std::back_inserter(tmp)
);
for(auto it = tmp.begin(); it < tmp.end(); ++it)
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
return tmp;
}
} // namespace
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
double dist,
unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
return cluster(sindex, max_points,
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
{
return distance_queryfn(sidx, p, dist, max_points);
});
}
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
return cluster(sindex, max_points,
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
{
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
return predicate(p, e);
}), std::back_inserter(tmp));
return tmp;
});
}
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(Eigen::Index i = 0; i < pts.rows(); i++)
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
return cluster(sindex, max_points,
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
{
return distance_queryfn(sidx, p, dist, max_points);
});
}
} // namespace sla
} // namespace Slic3r
}} // namespace Slic3r::sla

View file

@ -1,8 +1,10 @@
#ifndef SLA_EIGENMESH3D_H
#define SLA_EIGENMESH3D_H
#ifndef SLA_INDEXEDMESH_H
#define SLA_INDEXEDMESH_H
#include <libslic3r/SLA/Common.hpp>
#include <memory>
#include <vector>
#include <libslic3r/Point.hpp>
// There is an implementation of a hole-aware raycaster that was eventually
// not used in production version. It is now hidden under following define
@ -19,10 +21,12 @@ class TriangleMesh;
namespace sla {
using PointSet = Eigen::MatrixXd;
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree.
// Implemented in libslic3r/SLA/Common.cpp
class EigenMesh3D {
class IndexedMesh {
class AABBImpl;
const TriangleMesh* m_tm;
@ -38,15 +42,15 @@ class EigenMesh3D {
public:
explicit EigenMesh3D(const TriangleMesh&);
explicit IndexedMesh(const TriangleMesh&);
EigenMesh3D(const EigenMesh3D& other);
EigenMesh3D& operator=(const EigenMesh3D&);
IndexedMesh(const IndexedMesh& other);
IndexedMesh& operator=(const IndexedMesh&);
EigenMesh3D(EigenMesh3D &&other);
EigenMesh3D& operator=(EigenMesh3D &&other);
IndexedMesh(IndexedMesh &&other);
IndexedMesh& operator=(IndexedMesh &&other);
~EigenMesh3D();
~IndexedMesh();
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
inline void ground_level_offset(double o) { m_gnd_offset = o; }
@ -62,15 +66,15 @@ public:
// m_t holds a distance from m_source to the intersection.
double m_t = infty();
int m_face_id = -1;
const EigenMesh3D *m_mesh = nullptr;
const IndexedMesh *m_mesh = nullptr;
Vec3d m_dir;
Vec3d m_source;
Vec3d m_normal;
friend class EigenMesh3D;
friend class IndexedMesh;
// A valid object of this class can only be obtained from
// EigenMesh3D::query_ray_hit method.
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
// IndexedMesh::query_ray_hit method.
explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {}
public:
// This denotes no hit on the mesh.
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
@ -83,7 +87,7 @@ public:
inline Vec3d position() const { return m_source + m_dir * m_t; }
inline int face() const { return m_face_id; }
inline bool is_valid() const { return m_mesh != nullptr; }
inline bool is_hit() const { return !std::isinf(m_t); }
inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
inline const Vec3d& normal() const {
assert(is_valid());
@ -107,7 +111,7 @@ public:
// This function is currently not used anywhere, it was written when the
// holes were subtracted on slices, that is, before we started using CGAL
// to actually cut the holes into the mesh.
hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
hit_result filter_hits(const std::vector<IndexedMesh::hit_result>& obj_hits) const;
#endif
// Casting a ray on the mesh, returns the distance where the hit occures.
@ -125,16 +129,18 @@ public:
}
Vec3d normal_by_face_id(int face_id) const;
const TriangleMesh * get_triangle_mesh() const { return m_tm; }
};
// Calculate the normals for the selected points (from 'points' set) on the
// mesh. This will call squared distance for each point.
PointSet normals(const PointSet& points,
const EigenMesh3D& convert_mesh,
const IndexedMesh& convert_mesh,
double eps = 0.05, // min distance from edges
std::function<void()> throw_on_cancel = [](){},
const std::vector<unsigned>& selected_points = {});
}} // namespace Slic3r::sla
#endif // EIGENMESH3D_H
#endif // INDEXEDMESH_H

View file

@ -2,6 +2,7 @@
#define SLA_JOBCONTROLLER_HPP
#include <functional>
#include <string>
namespace Slic3r { namespace sla {

Some files were not shown because too many files have changed in this diff Show more