mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-24 01:01:15 -06:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_gcode_viewer
This commit is contained in:
commit
ebbebe3727
241 changed files with 43747 additions and 15023 deletions
|
|
@ -9,6 +9,11 @@ if (MINGW)
|
|||
add_compile_options(-Wa,-mbig-obj)
|
||||
endif ()
|
||||
|
||||
set(OpenVDBUtils_SOURCES "")
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
|
||||
endif()
|
||||
|
||||
add_library(libslic3r STATIC
|
||||
pchheader.cpp
|
||||
pchheader.hpp
|
||||
|
|
@ -116,6 +121,8 @@ add_library(libslic3r STATIC
|
|||
Line.hpp
|
||||
Model.cpp
|
||||
Model.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange.hpp
|
||||
Arrange.cpp
|
||||
MotionPlanner.cpp
|
||||
|
|
@ -149,9 +156,9 @@ add_library(libslic3r STATIC
|
|||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
SLAPrint.cpp
|
||||
SLAPrintSteps.cpp
|
||||
SLAPrintSteps.hpp
|
||||
SLAPrint.hpp
|
||||
SLA/SLAAutoSupports.hpp
|
||||
SLA/SLAAutoSupports.cpp
|
||||
Slicing.cpp
|
||||
Slicing.hpp
|
||||
SlicingAdaptive.cpp
|
||||
|
|
@ -180,35 +187,80 @@ add_library(libslic3r STATIC
|
|||
MinAreaBoundingBox.cpp
|
||||
miniz_extension.hpp
|
||||
miniz_extension.cpp
|
||||
SLA/SLACommon.hpp
|
||||
SLA/SLABoilerPlate.hpp
|
||||
SLA/SLAPad.hpp
|
||||
SLA/SLAPad.cpp
|
||||
SLA/SLASupportTreeBuilder.hpp
|
||||
SLA/SLASupportTreeBuildsteps.hpp
|
||||
SLA/SLASupportTreeBuildsteps.cpp
|
||||
SLA/SLASupportTreeBuilder.cpp
|
||||
SLA/SLAConcurrency.hpp
|
||||
SLA/SLASupportTree.hpp
|
||||
SLA/SLASupportTree.cpp
|
||||
SLA/SLASupportTreeIGL.cpp
|
||||
SLA/SLARotfinder.hpp
|
||||
SLA/SLARotfinder.cpp
|
||||
SLA/SLABoostAdapter.hpp
|
||||
SLA/SLASpatIndex.hpp
|
||||
SLA/SLARaster.hpp
|
||||
SLA/SLARaster.cpp
|
||||
SLA/SLARasterWriter.hpp
|
||||
SLA/SLARasterWriter.cpp
|
||||
SimplifyMesh.hpp
|
||||
SimplifyMeshImpl.hpp
|
||||
SimplifyMesh.cpp
|
||||
${OpenVDBUtils_SOURCES}
|
||||
SLA/Common.hpp
|
||||
SLA/Common.cpp
|
||||
SLA/Pad.hpp
|
||||
SLA/Pad.cpp
|
||||
SLA/SupportTreeBuilder.hpp
|
||||
SLA/SupportTreeBuildsteps.hpp
|
||||
SLA/SupportTreeBuildsteps.cpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
SLA/Concurrency.hpp
|
||||
SLA/SupportTree.hpp
|
||||
SLA/SupportTree.cpp
|
||||
# SLA/SupportTreeIGL.cpp
|
||||
SLA/Rotfinder.hpp
|
||||
SLA/Rotfinder.cpp
|
||||
SLA/BoostAdapter.hpp
|
||||
SLA/SpatIndex.hpp
|
||||
SLA/Raster.hpp
|
||||
SLA/Raster.cpp
|
||||
SLA/RasterWriter.hpp
|
||||
SLA/RasterWriter.cpp
|
||||
SLA/ConcaveHull.hpp
|
||||
SLA/ConcaveHull.cpp
|
||||
SLA/Hollowing.hpp
|
||||
SLA/Hollowing.cpp
|
||||
SLA/JobController.hpp
|
||||
SLA/SupportPoint.hpp
|
||||
SLA/SupportPointGenerator.hpp
|
||||
SLA/SupportPointGenerator.cpp
|
||||
SLA/Contour3D.hpp
|
||||
SLA/Contour3D.cpp
|
||||
SLA/EigenMesh3D.hpp
|
||||
SLA/Clustering.hpp
|
||||
)
|
||||
|
||||
encoding_check(libslic3r)
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||
if (SLIC3R_STATIC)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL REQUIRED)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp)
|
||||
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options
|
||||
# (-frounding-math) still propagate to dependent libs which is not desired.
|
||||
get_target_property(_cgal_tgt CGAL::CGAL ALIASED_TARGET)
|
||||
if (NOT TARGET ${_cgal_tgt})
|
||||
set (_cgal_tgt CGAL::CGAL)
|
||||
endif ()
|
||||
get_target_property(_opts ${_cgal_tgt} INTERFACE_COMPILE_OPTIONS)
|
||||
if (_opts)
|
||||
set(_opts_bad "${_opts}")
|
||||
set(_opts_good "${_opts}")
|
||||
list(FILTER _opts_bad INCLUDE REGEX frounding-math)
|
||||
list(FILTER _opts_good EXCLUDE REGEX frounding-math)
|
||||
set_target_properties(${_cgal_tgt} PROPERTIES INTERFACE_COMPILE_OPTIONS "${_opts_good}")
|
||||
target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}")
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl)
|
||||
|
||||
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
|
||||
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF)
|
||||
endif ()
|
||||
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
|
@ -228,10 +280,14 @@ target_link_libraries(libslic3r
|
|||
qhull
|
||||
semver
|
||||
TBB::tbb
|
||||
# OpenVDB::openvdb
|
||||
libslic3r_cgal
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
target_link_libraries(libslic3r OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(libslic3r Psapi.lib)
|
||||
endif()
|
||||
|
|
@ -239,3 +295,7 @@ endif()
|
|||
if(SLIC3R_PROFILE)
|
||||
target_link_libraries(slic3r Shiny)
|
||||
endif()
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
|
|
|
|||
|
|
@ -757,6 +757,12 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
|
|||
return opt;
|
||||
}
|
||||
|
||||
const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) const
|
||||
{
|
||||
auto it = options.find(opt_key);
|
||||
return (it == options.end()) ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
void DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys)
|
||||
{
|
||||
std::vector<const char*> args;
|
||||
|
|
|
|||
|
|
@ -1494,8 +1494,49 @@ protected:
|
|||
ConfigOptionDef* add_nullable(const t_config_option_key &opt_key, ConfigOptionType type);
|
||||
};
|
||||
|
||||
// A pure interface to resolving ConfigOptions.
|
||||
// This pure interface is useful as a base of ConfigBase, also it may be overriden to combine
|
||||
// various config sources.
|
||||
class ConfigOptionResolver
|
||||
{
|
||||
public:
|
||||
ConfigOptionResolver() {}
|
||||
virtual ~ConfigOptionResolver() {}
|
||||
|
||||
// Find a ConfigOption instance for a given name.
|
||||
virtual const ConfigOption* optptr(const t_config_option_key &opt_key) const = 0;
|
||||
|
||||
bool has(const t_config_option_key &opt_key) const { return this->optptr(opt_key) != nullptr; }
|
||||
|
||||
const ConfigOption* option(const t_config_option_key &opt_key) const { return this->optptr(opt_key); }
|
||||
|
||||
template<typename TYPE>
|
||||
const TYPE* option(const t_config_option_key& opt_key) const
|
||||
{
|
||||
const ConfigOption* opt = this->optptr(opt_key);
|
||||
return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<const TYPE*>(opt);
|
||||
}
|
||||
|
||||
const ConfigOption* option_throw(const t_config_option_key& opt_key) const
|
||||
{
|
||||
const ConfigOption* opt = this->optptr(opt_key);
|
||||
if (opt == nullptr)
|
||||
throw UnknownOptionException(opt_key);
|
||||
return opt;
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
const TYPE* option_throw(const t_config_option_key& opt_key) const
|
||||
{
|
||||
const ConfigOption* opt = this->option_throw(opt_key);
|
||||
if (opt->type() != TYPE::static_type())
|
||||
throw BadOptionTypeException("Conversion to a wrong type");
|
||||
return static_cast<TYPE*>(opt);
|
||||
}
|
||||
};
|
||||
|
||||
// An abstract configuration store.
|
||||
class ConfigBase
|
||||
class ConfigBase : public ConfigOptionResolver
|
||||
{
|
||||
public:
|
||||
// Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling.
|
||||
|
|
@ -1503,7 +1544,7 @@ public:
|
|||
// but it carries the defaults of the configuration values.
|
||||
|
||||
ConfigBase() {}
|
||||
virtual ~ConfigBase() {}
|
||||
~ConfigBase() override {}
|
||||
|
||||
// Virtual overridables:
|
||||
public:
|
||||
|
|
@ -1513,6 +1554,7 @@ public:
|
|||
virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0;
|
||||
// Collect names of all configuration values maintained by this configuration store.
|
||||
virtual t_config_option_keys keys() const = 0;
|
||||
|
||||
protected:
|
||||
// Verify whether the opt_key has not been obsoleted or renamed.
|
||||
// Both opt_key and value may be modified by handle_legacy().
|
||||
|
|
@ -1521,12 +1563,10 @@ protected:
|
|||
virtual void handle_legacy(t_config_option_key &/*opt_key*/, std::string &/*value*/) const {}
|
||||
|
||||
public:
|
||||
using ConfigOptionResolver::option;
|
||||
using ConfigOptionResolver::option_throw;
|
||||
|
||||
// Non-virtual methods:
|
||||
bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; }
|
||||
|
||||
const ConfigOption* option(const t_config_option_key &opt_key) const
|
||||
{ return const_cast<ConfigBase*>(this)->option(opt_key, false); }
|
||||
|
||||
ConfigOption* option(const t_config_option_key &opt_key, bool create = false)
|
||||
{ return this->optptr(opt_key, create); }
|
||||
|
||||
|
|
@ -1537,10 +1577,6 @@ public:
|
|||
return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt);
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
const TYPE* option(const t_config_option_key &opt_key) const
|
||||
{ return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
|
||||
|
||||
ConfigOption* option_throw(const t_config_option_key &opt_key, bool create = false)
|
||||
{
|
||||
ConfigOption *opt = this->optptr(opt_key, create);
|
||||
|
|
@ -1549,9 +1585,6 @@ public:
|
|||
return opt;
|
||||
}
|
||||
|
||||
const ConfigOption* option_throw(const t_config_option_key &opt_key) const
|
||||
{ return const_cast<ConfigBase*>(this)->option_throw(opt_key, false); }
|
||||
|
||||
template<typename TYPE>
|
||||
TYPE* option_throw(const t_config_option_key &opt_key, bool create = false)
|
||||
{
|
||||
|
|
@ -1561,10 +1594,6 @@ public:
|
|||
return static_cast<TYPE*>(opt);
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
const TYPE* option_throw(const t_config_option_key &opt_key) const
|
||||
{ return const_cast<ConfigBase*>(this)->option_throw<TYPE>(opt_key, false); }
|
||||
|
||||
// Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
|
||||
// An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
|
||||
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
|
||||
|
|
@ -1735,6 +1764,8 @@ public:
|
|||
{ return dynamic_cast<T*>(this->option(opt_key, create)); }
|
||||
template<class T> const T* opt(const t_config_option_key &opt_key) const
|
||||
{ return dynamic_cast<const T*>(this->option(opt_key)); }
|
||||
// Overrides ConfigResolver::optptr().
|
||||
const ConfigOption* optptr(const t_config_option_key &opt_key) const override;
|
||||
// Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name.
|
||||
ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override;
|
||||
// Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store.
|
||||
|
|
|
|||
72
src/libslic3r/CustomGCode.cpp
Normal file
72
src/libslic3r/CustomGCode.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include "CustomGCode.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "GCode/PreviewData.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace CustomGCode {
|
||||
|
||||
// If loaded configuration has a "colorprint_heights" option (if it was imported from older Slicer),
|
||||
// and if CustomGCode::Info.gcodes is empty (there is no color print data available in a new format
|
||||
// then CustomGCode::Info.gcodes should be updated considering this option.
|
||||
extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrintConfig* config)
|
||||
{
|
||||
auto *colorprint_heights = config->option<ConfigOptionFloats>("colorprint_heights");
|
||||
if (colorprint_heights == nullptr)
|
||||
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;
|
||||
info.gcodes.clear();
|
||||
info.gcodes.reserve(colorprint_values.size());
|
||||
int i = 0;
|
||||
for (auto val : colorprint_values)
|
||||
info.gcodes.emplace_back(Item{ val, ColorChangeCode, 1, colors[(++i)%7] });
|
||||
|
||||
info.mode = SingleExtruder;
|
||||
}
|
||||
|
||||
// The "colorprint_heights" config value has been deprecated. At this point of time it has been converted
|
||||
// to a new format and therefore it shall be erased.
|
||||
config->erase("colorprint_heights");
|
||||
}
|
||||
|
||||
// If information for custom Gcode per print Z was imported from older Slicer, mode will be undefined.
|
||||
// So, we should set CustomGCode::Info.mode should be updated considering code values from items.
|
||||
extern void check_mode_for_custom_gcode_per_print_z(Info& info)
|
||||
{
|
||||
if (info.mode != Undef)
|
||||
return;
|
||||
|
||||
bool is_single_extruder = true;
|
||||
for (auto item : info.gcodes)
|
||||
{
|
||||
if (item.gcode == ToolChangeCode) {
|
||||
info.mode = MultiAsSingle;
|
||||
return;
|
||||
}
|
||||
if (item.gcode == ColorChangeCode && item.extruder > 1)
|
||||
is_single_extruder = false;
|
||||
}
|
||||
|
||||
info.mode = is_single_extruder ? SingleExtruder : MultiExtruder;
|
||||
}
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders)
|
||||
{
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes;
|
||||
for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes)
|
||||
if (custom_gcode.gcode == ToolChangeCode) {
|
||||
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
|
||||
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(custom_gcode.extruder > num_extruders ? 1 : custom_gcode.extruder));
|
||||
}
|
||||
return custom_tool_changes;
|
||||
}
|
||||
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
86
src/libslic3r/CustomGCode.hpp
Normal file
86
src/libslic3r/CustomGCode.hpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#ifndef slic3r_CustomGCode_hpp_
|
||||
#define slic3r_CustomGCode_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
|
||||
// Additional Codes which can be set by user using DoubleSlider
|
||||
static constexpr char ColorChangeCode[] = "M600";
|
||||
static constexpr char PausePrintCode[] = "M601";
|
||||
static constexpr char ToolChangeCode[] = "tool_change";
|
||||
|
||||
namespace CustomGCode {
|
||||
|
||||
struct Item
|
||||
{
|
||||
bool operator<(const Item& rhs) const { return this->print_z < rhs.print_z; }
|
||||
bool operator==(const Item& rhs) const
|
||||
{
|
||||
return (rhs.print_z == this->print_z ) &&
|
||||
(rhs.gcode == this->gcode ) &&
|
||||
(rhs.extruder == this->extruder ) &&
|
||||
(rhs.color == this->color );
|
||||
}
|
||||
bool operator!=(const Item& rhs) const { return ! (*this == rhs); }
|
||||
|
||||
double print_z;
|
||||
std::string gcode;
|
||||
int extruder; // Informative value for ColorChangeCode and ToolChangeCode
|
||||
// "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder
|
||||
// "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder
|
||||
std::string color; // if gcode is equal to PausePrintCode,
|
||||
// this field is used for save a short message shown on Printer display
|
||||
};
|
||||
|
||||
enum Mode
|
||||
{
|
||||
Undef,
|
||||
SingleExtruder, // Single extruder printer preset is selected
|
||||
MultiAsSingle, // Multiple extruder printer preset is selected, but
|
||||
// this mode works just for Single extruder print
|
||||
// (The same extruder is assigned to all ModelObjects and ModelVolumes).
|
||||
MultiExtruder // Multiple extruder printer preset is selected
|
||||
};
|
||||
|
||||
// string anlogue of custom_code_per_height mode
|
||||
static constexpr char SingleExtruderMode[] = "SingleExtruder";
|
||||
static constexpr char MultiAsSingleMode [] = "MultiAsSingle";
|
||||
static constexpr char MultiExtruderMode [] = "MultiExtruder";
|
||||
|
||||
struct Info
|
||||
{
|
||||
Mode mode = Undef;
|
||||
std::vector<Item> gcodes;
|
||||
|
||||
bool operator==(const Info& rhs) const
|
||||
{
|
||||
return (rhs.mode == this->mode ) &&
|
||||
(rhs.gcodes == this->gcodes );
|
||||
}
|
||||
bool operator!=(const Info& rhs) const { return !(*this == rhs); }
|
||||
};
|
||||
|
||||
// If loaded configuration has a "colorprint_heights" option (if it was imported from older Slicer),
|
||||
// and if CustomGCode::Info.gcodes is empty (there is no color print data available in a new format
|
||||
// then CustomGCode::Info.gcodes should be updated considering this option.
|
||||
extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrintConfig* config);
|
||||
|
||||
// If information for custom Gcode per print Z was imported from older Slicer, mode will be undefined.
|
||||
// So, we should set CustomGCode::Info.mode should be updated considering code values from items.
|
||||
extern void check_mode_for_custom_gcode_per_print_z(Info& info);
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
|
||||
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
|
||||
#endif /* slic3r_CustomGCode_hpp_ */
|
||||
|
|
@ -657,4 +657,23 @@ bool remove_sticks(ExPolygon &poly)
|
|||
return remove_sticks(poly.contour) || remove_sticks(poly.holes);
|
||||
}
|
||||
|
||||
void keep_largest_contour_only(ExPolygons &polygons)
|
||||
{
|
||||
if (polygons.size() > 1) {
|
||||
double max_area = 0.;
|
||||
ExPolygon* max_area_polygon = nullptr;
|
||||
for (ExPolygon& p : polygons) {
|
||||
double a = p.contour.area();
|
||||
if (a > max_area) {
|
||||
max_area = a;
|
||||
max_area_polygon = &p;
|
||||
}
|
||||
}
|
||||
assert(max_area_polygon != nullptr);
|
||||
ExPolygon p(std::move(*max_area_polygon));
|
||||
polygons.clear();
|
||||
polygons.emplace_back(std::move(p));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ extern BoundingBox get_extents_rotated(const ExPolygons &polygons, double angle)
|
|||
extern std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
|
||||
|
||||
extern bool remove_sticks(ExPolygon &poly);
|
||||
extern void keep_largest_contour_only(ExPolygons &polygons);
|
||||
|
||||
extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygons &expp);
|
||||
extern std::list<TPPLPoly> expoly_to_polypartition_input(const ExPolygon &ex);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
#include "Flow.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Print.hpp"
|
||||
#include <cmath>
|
||||
#include <assert.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
// Mark string for localization and translate.
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// This static method returns a sane extrusion width default.
|
||||
static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height)
|
||||
float Flow::auto_extrusion_width(FlowRole role, float nozzle_diameter)
|
||||
{
|
||||
switch (role) {
|
||||
case frSupportMaterial:
|
||||
|
|
@ -22,6 +28,92 @@ static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, f
|
|||
}
|
||||
}
|
||||
|
||||
// Used by the Flow::extrusion_width() funtion to provide hints to the user on default extrusion width values,
|
||||
// and to provide reasonable values to the PlaceholderParser.
|
||||
static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
{
|
||||
if (opt_key == "perimeter_extrusion_width" ||
|
||||
// or all the defaults:
|
||||
opt_key == "extrusion_width" || opt_key == "first_layer_extrusion_width")
|
||||
return frPerimeter;
|
||||
else if (opt_key == "external_perimeter_extrusion_width")
|
||||
return frExternalPerimeter;
|
||||
else if (opt_key == "infill_extrusion_width")
|
||||
return frInfill;
|
||||
else if (opt_key == "solid_infill_extrusion_width")
|
||||
return frSolidInfill;
|
||||
else if (opt_key == "top_infill_extrusion_width")
|
||||
return frTopSolidInfill;
|
||||
else if (opt_key == "support_material_extrusion_width")
|
||||
return frSupportMaterial;
|
||||
else
|
||||
throw std::runtime_error("opt_key_to_flow_role: invalid argument");
|
||||
};
|
||||
|
||||
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
|
||||
{
|
||||
throw std::runtime_error((boost::format(L("Cannot calculate extrusion width for %1%: Variable \"%2%\" not accessible.")) % opt_key % dependent_opt_key).str());
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloatOrPercent* opt, const ConfigOptionResolver& config, const unsigned int first_printing_extruder)
|
||||
{
|
||||
assert(opt != nullptr);
|
||||
|
||||
bool first_layer = boost::starts_with(opt_key, "first_layer_");
|
||||
|
||||
#if 0
|
||||
// This is the logic used for skit / brim, but not for the rest of the 1st layer.
|
||||
if (opt->value == 0. && first_layer) {
|
||||
// The "first_layer_extrusion_width" was set to zero, try a substitute.
|
||||
opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
|
||||
if (opt == nullptr)
|
||||
throw_on_missing_variable(opt_key, "perimeter_extrusion_width");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opt->value == 0.) {
|
||||
// The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
|
||||
opt = config.option<ConfigOptionFloatOrPercent>("extrusion_width");
|
||||
if (opt == nullptr)
|
||||
throw_on_missing_variable(opt_key, "extrusion_width");
|
||||
// Use the "layer_height" instead of "first_layer_height".
|
||||
first_layer = false;
|
||||
}
|
||||
|
||||
if (opt->percent) {
|
||||
auto opt_key_layer_height = first_layer ? "first_layer_height" : "layer_height";
|
||||
auto opt_layer_height = config.option(opt_key_layer_height);
|
||||
if (opt_layer_height == nullptr)
|
||||
throw_on_missing_variable(opt_key, opt_key_layer_height);
|
||||
double layer_height = opt_layer_height->getFloat();
|
||||
if (first_layer && static_cast<const ConfigOptionFloatOrPercent*>(opt_layer_height)->percent) {
|
||||
// first_layer_height depends on layer_height.
|
||||
opt_layer_height = config.option("layer_height");
|
||||
if (opt_layer_height == nullptr)
|
||||
throw_on_missing_variable(opt_key, "layer_height");
|
||||
layer_height *= 0.01 * opt_layer_height->getFloat();
|
||||
}
|
||||
return opt->get_abs_value(layer_height);
|
||||
}
|
||||
|
||||
if (opt->value == 0.) {
|
||||
// If user left option to 0, calculate a sane default width.
|
||||
auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (opt_nozzle_diameters == nullptr)
|
||||
throw_on_missing_variable(opt_key, "nozzle_diameter");
|
||||
return auto_extrusion_width(opt_key_to_flow_role(opt_key), float(opt_nozzle_diameters->get_at(first_printing_extruder)));
|
||||
}
|
||||
|
||||
return opt->value;
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder)
|
||||
{
|
||||
return extrusion_width(opt_key, config.option<ConfigOptionFloatOrPercent>(opt_key), config, first_printing_extruder);
|
||||
}
|
||||
|
||||
// This constructor builds a Flow object from an extrusion width config setting
|
||||
// and other context properties.
|
||||
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio)
|
||||
|
|
@ -39,7 +131,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
|
|||
sqrt(bridge_flow_ratio) * nozzle_diameter;
|
||||
} else if (! width.percent && width.value == 0.) {
|
||||
// If user left option to 0, calculate a sane default width.
|
||||
w = auto_extrusion_width(role, nozzle_diameter, height);
|
||||
w = auto_extrusion_width(role, nozzle_diameter);
|
||||
} else {
|
||||
// If user set a manual value, use it.
|
||||
w = float(width.get_abs_value(height));
|
||||
|
|
|
|||
|
|
@ -64,6 +64,16 @@ public:
|
|||
// This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
|
||||
// to fit a region with integer number of lines.
|
||||
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
|
||||
|
||||
// Sane extrusion width defautl based on nozzle diameter.
|
||||
// The defaults were derived from manual Prusa MK3 profiles.
|
||||
static float auto_extrusion_width(FlowRole role, float nozzle_diameter);
|
||||
|
||||
// Extrusion width from full config, taking into account the defaults (when set to zero) and ratios (percentages).
|
||||
// Precise value depends on layer index (1st layer vs. other layers vs. variable layer height),
|
||||
// on active extruder etc. Therefore the value calculated by this function shall be used as a hint only.
|
||||
static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
|
||||
static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
|
||||
};
|
||||
|
||||
extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f);
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
|
|||
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
|
||||
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
|
||||
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
|
||||
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
|
||||
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
|
||||
|
||||
const char* MODEL_TAG = "model";
|
||||
|
|
@ -385,6 +386,7 @@ namespace Slic3r {
|
|||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
|
||||
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
|
||||
|
||||
// Version of the 3mf file
|
||||
unsigned int m_version;
|
||||
|
|
@ -403,6 +405,7 @@ namespace Slic3r {
|
|||
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
||||
IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||
IdToSlaSupportPointsMap m_sla_support_points;
|
||||
IdToSlaDrainHolesMap m_sla_drain_holes;
|
||||
std::string m_curr_metadata_name;
|
||||
std::string m_curr_characters;
|
||||
std::string m_name;
|
||||
|
|
@ -422,6 +425,7 @@ namespace Slic3r {
|
|||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
|
|
@ -629,6 +633,11 @@ namespace Slic3r {
|
|||
// extract sla support points file
|
||||
_extract_sla_support_points_from_archive(archive, stat);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE))
|
||||
{
|
||||
// extract sla support points file
|
||||
_extract_sla_drain_holes_from_archive(archive, stat);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
|
||||
{
|
||||
// extract slic3r print config file
|
||||
|
|
@ -683,6 +692,11 @@ namespace Slic3r {
|
|||
model_object->sla_support_points = obj_sla_support_points->second;
|
||||
model_object->sla_points_status = sla::PointsStatus::UserModified;
|
||||
}
|
||||
|
||||
IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1);
|
||||
if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) {
|
||||
model_object->sla_drain_holes = obj_drain_holes->second;
|
||||
}
|
||||
|
||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
|
||||
if (obj_metadata != m_objects_metadata.end())
|
||||
|
|
@ -955,8 +969,9 @@ namespace Slic3r {
|
|||
|
||||
// Info on format versioning - see 3mf.hpp
|
||||
int version = 0;
|
||||
if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) {
|
||||
objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string
|
||||
std::string key("support_points_format_version=");
|
||||
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
||||
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
||||
version = std::stoi(objects[0]);
|
||||
objects.erase(objects.begin()); // pop the header
|
||||
}
|
||||
|
|
@ -1022,6 +1037,90 @@ namespace Slic3r {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _3MF_Importer::_extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0)
|
||||
{
|
||||
std::string buffer(size_t(stat.m_uncomp_size), 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == 0)
|
||||
{
|
||||
add_error("Error while reading sla support points data to buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.back() == '\n')
|
||||
buffer.pop_back();
|
||||
|
||||
std::vector<std::string> objects;
|
||||
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
|
||||
|
||||
// Info on format versioning - see 3mf.hpp
|
||||
int version = 0;
|
||||
std::string key("drain_holes_format_version=");
|
||||
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
||||
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
||||
version = std::stoi(objects[0]);
|
||||
objects.erase(objects.begin()); // pop the header
|
||||
}
|
||||
|
||||
for (const std::string& object : objects)
|
||||
{
|
||||
std::vector<std::string> object_data;
|
||||
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
|
||||
|
||||
if (object_data.size() != 2)
|
||||
{
|
||||
add_error("Error while reading object data");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> object_data_id;
|
||||
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
|
||||
if (object_data_id.size() != 2)
|
||||
{
|
||||
add_error("Error while reading object id");
|
||||
continue;
|
||||
}
|
||||
|
||||
int object_id = std::atoi(object_data_id[1].c_str());
|
||||
if (object_id == 0)
|
||||
{
|
||||
add_error("Found invalid object id");
|
||||
continue;
|
||||
}
|
||||
|
||||
IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id);
|
||||
if (object_item != m_sla_drain_holes.end())
|
||||
{
|
||||
add_error("Found duplicated SLA drain holes");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> object_data_points;
|
||||
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
|
||||
|
||||
sla::DrainHoles sla_drain_holes;
|
||||
|
||||
if (version == 1) {
|
||||
for (unsigned int i=0; i<object_data_points.size(); i+=8)
|
||||
sla_drain_holes.emplace_back(Vec3f{float(std::atof(object_data_points[i+0].c_str())),
|
||||
float(std::atof(object_data_points[i+1].c_str())),
|
||||
float(std::atof(object_data_points[i+2].c_str()))},
|
||||
Vec3f{float(std::atof(object_data_points[i+3].c_str())),
|
||||
float(std::atof(object_data_points[i+4].c_str())),
|
||||
float(std::atof(object_data_points[i+5].c_str()))},
|
||||
float(std::atof(object_data_points[i+6].c_str())),
|
||||
float(std::atof(object_data_points[i+7].c_str())));
|
||||
}
|
||||
|
||||
if (!sla_drain_holes.empty())
|
||||
m_sla_drain_holes.insert(IdToSlaDrainHolesMap::value_type(object_id, sla_drain_holes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
|
||||
|
|
@ -1092,6 +1191,14 @@ namespace Slic3r {
|
|||
|
||||
for (const auto& code : code_tree)
|
||||
{
|
||||
if (code.first == "mode")
|
||||
{
|
||||
pt::ptree tree = code.second;
|
||||
std::string mode = tree.get<std::string>("<xmlattr>.value");
|
||||
m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
|
||||
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
|
||||
CustomGCode::Mode::MultiExtruder;
|
||||
}
|
||||
if (code.first != "code")
|
||||
continue;
|
||||
pt::ptree tree = code.second;
|
||||
|
|
@ -1100,7 +1207,7 @@ namespace Slic3r {
|
|||
int extruder = tree.get<int> ("<xmlattr>.extruder" );
|
||||
std::string color = tree.get<std::string> ("<xmlattr>.color" );
|
||||
|
||||
m_model->custom_gcode_per_print_z.gcodes.push_back(Model::CustomGCode{print_z, gcode, extruder, color}) ;
|
||||
m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, gcode, extruder, color}) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1859,24 +1966,14 @@ namespace Slic3r {
|
|||
typedef std::vector<BuildItem> BuildItemsList;
|
||||
typedef std::map<int, ObjectData> IdToObjectDataMap;
|
||||
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
bool m_fullpath_sources{ true };
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
|
||||
public:
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
|
||||
#else
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#else
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
|
||||
#else
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
|
||||
private:
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
@ -1896,12 +1993,12 @@ namespace Slic3r {
|
|||
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
|
||||
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
|
||||
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
};
|
||||
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
|
||||
{
|
||||
|
|
@ -1916,21 +2013,6 @@ namespace Slic3r {
|
|||
return _save_model_to_file(filename, model, config);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#else
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
|
||||
{
|
||||
clear_errors();
|
||||
return _save_model_to_file(filename, model, config, thumbnail_data);
|
||||
}
|
||||
#else
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
|
||||
{
|
||||
clear_errors();
|
||||
return _save_model_to_file(filename, model, config);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
|
||||
|
|
@ -2017,6 +2099,14 @@ namespace Slic3r {
|
|||
boost::filesystem::remove(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_add_sla_drain_holes_file_to_archive(archive, model))
|
||||
{
|
||||
close_zip_writer(&archive);
|
||||
boost::filesystem::remove(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
|
||||
// All custom gcode per height of whole Model are stored here
|
||||
|
|
@ -2474,6 +2564,50 @@ namespace Slic3r {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model)
|
||||
{
|
||||
const char *const fmt = "object_id=%d|";
|
||||
std::string out;
|
||||
|
||||
unsigned int count = 0;
|
||||
for (const ModelObject* object : model.objects)
|
||||
{
|
||||
++count;
|
||||
auto& drain_holes = object->sla_drain_holes;
|
||||
if (!drain_holes.empty())
|
||||
{
|
||||
out += string_printf(fmt, count);
|
||||
|
||||
// Store the layer height profile as a single space separated list.
|
||||
for (size_t i = 0; i < drain_holes.size(); ++i)
|
||||
out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"),
|
||||
drain_holes[i].pos(0),
|
||||
drain_holes[i].pos(1),
|
||||
drain_holes[i].pos(2),
|
||||
drain_holes[i].normal(0),
|
||||
drain_holes[i].normal(1),
|
||||
drain_holes[i].normal(2),
|
||||
drain_holes[i].radius,
|
||||
drain_holes[i].height);
|
||||
|
||||
out += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.empty())
|
||||
{
|
||||
// Adds version header at the beginning:
|
||||
out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out;
|
||||
|
||||
if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast<const void*>(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION)))
|
||||
{
|
||||
add_error("Unable to add sla support points file to archive");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
|
||||
{
|
||||
|
|
@ -2565,12 +2699,8 @@ namespace Slic3r {
|
|||
// stores volume's source data
|
||||
if (!volume->source.input_file.empty())
|
||||
{
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
|
||||
#else
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n";
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
|
||||
|
|
@ -2615,7 +2745,7 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
|
|||
pt::ptree tree;
|
||||
pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
|
||||
|
||||
for (const Model::CustomGCode& code : model.custom_gcode_per_print_z.gcodes)
|
||||
for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes)
|
||||
{
|
||||
pt::ptree& code_tree = main_tree.add("code", "");
|
||||
// store minX and maxZ
|
||||
|
|
@ -2623,7 +2753,13 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
|
|||
code_tree.put("<xmlattr>.gcode" , code.gcode );
|
||||
code_tree.put("<xmlattr>.extruder" , code.extruder );
|
||||
code_tree.put("<xmlattr>.color" , code.color );
|
||||
}
|
||||
}
|
||||
|
||||
pt::ptree& mode_tree = main_tree.add("mode", "");
|
||||
// store mode of a custom_gcode_per_print_z
|
||||
mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
|
||||
model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
|
||||
CustomGCode::MultiExtruderMode);
|
||||
|
||||
if (!tree.empty())
|
||||
{
|
||||
|
|
@ -2659,37 +2795,21 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c
|
|||
return res;
|
||||
}
|
||||
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
|
||||
#else
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#else
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
|
||||
#else
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
||||
_3MF_Exporter exporter;
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data);
|
||||
#else
|
||||
bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#else
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data);
|
||||
#else
|
||||
bool res = exporter.save_model_to_file(path, *model, config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
|
||||
if (!res)
|
||||
exporter.log_errors();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ namespace Slic3r {
|
|||
enum {
|
||||
support_points_format_version = 1
|
||||
};
|
||||
|
||||
enum {
|
||||
drain_holes_format_version = 1
|
||||
};
|
||||
|
||||
class Model;
|
||||
class DynamicPrintConfig;
|
||||
|
|
@ -31,19 +35,11 @@ namespace Slic3r {
|
|||
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
|
||||
#else
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#else
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
|
||||
#else
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "../Utils.hpp"
|
||||
#include "../I18N.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../CustomGCode.hpp"
|
||||
|
||||
#include "AMF.hpp"
|
||||
|
||||
|
|
@ -156,6 +157,7 @@ struct AMFParserContext
|
|||
NODE_TYPE_PRINTABLE, // amf/constellation/instance/mirrorz
|
||||
NODE_TYPE_CUSTOM_GCODE, // amf/custom_code_per_height
|
||||
NODE_TYPE_GCODE_PER_HEIGHT, // amf/custom_code_per_height/code
|
||||
NODE_TYPE_CUSTOM_GCODE_MODE, // amf/custom_code_per_height/mode
|
||||
NODE_TYPE_METADATA, // anywhere under amf/*/metadata
|
||||
};
|
||||
|
||||
|
|
@ -308,12 +310,18 @@ void AMFParserContext::startElement(const char *name, const char **atts)
|
|||
else
|
||||
this->stop();
|
||||
}
|
||||
else if (strcmp(name, "code") == 0 && m_path[1] == NODE_TYPE_CUSTOM_GCODE) {
|
||||
node_type_new = NODE_TYPE_GCODE_PER_HEIGHT;
|
||||
m_value[0] = get_attribute(atts, "height");
|
||||
m_value[1] = get_attribute(atts, "gcode");
|
||||
m_value[2] = get_attribute(atts, "extruder");
|
||||
m_value[3] = get_attribute(atts, "color");
|
||||
else if (m_path[1] == NODE_TYPE_CUSTOM_GCODE) {
|
||||
if (strcmp(name, "code") == 0) {
|
||||
node_type_new = NODE_TYPE_GCODE_PER_HEIGHT;
|
||||
m_value[0] = get_attribute(atts, "print_z");
|
||||
m_value[1] = get_attribute(atts, "gcode");
|
||||
m_value[2] = get_attribute(atts, "extruder");
|
||||
m_value[3] = get_attribute(atts, "color");
|
||||
}
|
||||
else if (strcmp(name, "mode") == 0) {
|
||||
node_type_new = NODE_TYPE_CUSTOM_GCODE_MODE;
|
||||
m_value[0] = get_attribute(atts, "value");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
|
|
@ -632,18 +640,29 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
break;
|
||||
|
||||
case NODE_TYPE_GCODE_PER_HEIGHT: {
|
||||
double height = double(atof(m_value[0].c_str()));
|
||||
double print_z = double(atof(m_value[0].c_str()));
|
||||
const std::string& gcode = m_value[1];
|
||||
int extruder = atoi(m_value[2].c_str());
|
||||
const std::string& color = m_value[3];
|
||||
|
||||
m_model.custom_gcode_per_print_z.gcodes.push_back(Model::CustomGCode{height, gcode, extruder, color});
|
||||
m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, gcode, extruder, color});
|
||||
|
||||
for (std::string& val: m_value)
|
||||
val.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_TYPE_CUSTOM_GCODE_MODE: {
|
||||
const std::string& mode = m_value[0];
|
||||
|
||||
m_model.custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
|
||||
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
|
||||
CustomGCode::Mode::MultiExtruder;
|
||||
for (std::string& val: m_value)
|
||||
val.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_TYPE_METADATA:
|
||||
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0)
|
||||
m_config->load_from_gcode_string(m_value[1].c_str());
|
||||
|
|
@ -1003,11 +1022,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
|
|||
return false;
|
||||
}
|
||||
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
|
||||
#else
|
||||
bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
|
@ -1161,12 +1176,8 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
|||
stream << "</metadata>\n";
|
||||
if (!volume->source.input_file.empty())
|
||||
{
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
|
||||
stream << " <metadata type=\"slic3r.source_file\">" << input_file << "</metadata>\n";
|
||||
#else
|
||||
stream << " <metadata type=\"slic3r.source_file\">" << xml_escape(volume->source.input_file) << "</metadata>\n";
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
stream << " <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
|
||||
stream << " <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
|
||||
stream << " <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";
|
||||
|
|
@ -1237,16 +1248,23 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
|||
|
||||
pt::ptree& main_tree = tree.add("custom_gcodes_per_height", "");
|
||||
|
||||
for (const Model::CustomGCode& code : model->custom_gcode_per_print_z.gcodes)
|
||||
for (const CustomGCode::Item& code : model->custom_gcode_per_print_z.gcodes)
|
||||
{
|
||||
pt::ptree& code_tree = main_tree.add("code", "");
|
||||
// store minX and maxZ
|
||||
// store custom_gcode_per_print_z gcodes information
|
||||
code_tree.put("<xmlattr>.print_z" , code.print_z );
|
||||
code_tree.put("<xmlattr>.gcode" , code.gcode );
|
||||
code_tree.put("<xmlattr>.extruder" , code.extruder );
|
||||
code_tree.put("<xmlattr>.color" , code.color );
|
||||
}
|
||||
|
||||
pt::ptree& mode_tree = main_tree.add("mode", "");
|
||||
// store mode of a custom_gcode_per_print_z
|
||||
mode_tree.put("<xmlattr>.value",
|
||||
model->custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
|
||||
model->custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ?
|
||||
CustomGCode::MultiAsSingleMode : CustomGCode::MultiExtruderMode);
|
||||
|
||||
if (!tree.empty())
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
|
@ -1259,6 +1277,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
|||
|
||||
// Post processing("beautification") of the output string
|
||||
boost::replace_all(out, "><code", ">\n <code");
|
||||
boost::replace_all(out, "><mode", ">\n <mode");
|
||||
boost::replace_all(out, "><", ">\n<");
|
||||
|
||||
stream << out << "\n";
|
||||
|
|
|
|||
|
|
@ -11,11 +11,7 @@ extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model,
|
|||
|
||||
// Save the given model and the config data into an amf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
|
||||
#else
|
||||
extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config);
|
||||
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -355,6 +355,35 @@ bool objparse(const char *path, ObjData &data)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool objparse(std::istream &stream, ObjData &data)
|
||||
{
|
||||
try {
|
||||
char buf[65536 * 2];
|
||||
size_t len = 0;
|
||||
size_t lenPrev = 0;
|
||||
while ((len = size_t(stream.read(buf + lenPrev, 65536).gcount())) != 0) {
|
||||
len += lenPrev;
|
||||
size_t lastLine = 0;
|
||||
for (size_t i = 0; i < len; ++ i)
|
||||
if (buf[i] == '\r' || buf[i] == '\n') {
|
||||
buf[i] = 0;
|
||||
char *c = buf + lastLine;
|
||||
while (*c == ' ' || *c == '\t')
|
||||
++ c;
|
||||
obj_parseline(c, data);
|
||||
lastLine = i + 1;
|
||||
}
|
||||
lenPrev = len - lastLine;
|
||||
memmove(buf, buf + lastLine, lenPrev);
|
||||
}
|
||||
}
|
||||
catch (std::bad_alloc&) {
|
||||
printf("Out of memory\r\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool savevector(FILE *pFile, const std::vector<T> &v)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <istream>
|
||||
|
||||
namespace ObjParser {
|
||||
|
||||
|
|
@ -97,6 +98,7 @@ struct ObjData {
|
|||
};
|
||||
|
||||
extern bool objparse(const char *path, ObjData &data);
|
||||
extern bool objparse(std::istream &stream, ObjData &data);
|
||||
|
||||
extern bool objbinsave(const char *path, const ObjData &data);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "GCode/WipeTower.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
|
@ -164,12 +165,12 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP
|
|||
cnt = (cnt + 1) / 2;
|
||||
}
|
||||
// And collect copies of the objects.
|
||||
for (const Point © : object->copies()) {
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
// All the layers were reduced to the 1st item of polygons_per_layer.
|
||||
size_t i = islands.size();
|
||||
polygons_append(islands, polygons_per_layer.front());
|
||||
for (; i < islands.size(); ++ i)
|
||||
islands[i].translate(copy);
|
||||
islands[i].translate(instance.shift);
|
||||
}
|
||||
}
|
||||
return islands;
|
||||
|
|
@ -965,7 +966,7 @@ namespace DoExport {
|
|||
skirts.emplace_back(std::move(s));
|
||||
}
|
||||
ooze_prevention.enable = true;
|
||||
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
|
||||
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.)));
|
||||
#if 0
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
|
|
@ -1038,8 +1039,8 @@ namespace DoExport {
|
|||
std::string filament_stats_string_out;
|
||||
|
||||
print_statistics.clear();
|
||||
print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhms();
|
||||
print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhms() : "N/A";
|
||||
print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/();
|
||||
print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A";
|
||||
print_statistics.estimated_normal_color_print_times = normal_time_estimator.get_color_times_dhms(true);
|
||||
if (silent_time_estimator_enabled)
|
||||
print_statistics.estimated_silent_color_print_times = silent_time_estimator.get_color_times_dhms(true);
|
||||
|
|
@ -1094,6 +1095,41 @@ namespace DoExport {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints.
|
||||
static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
|
||||
{
|
||||
std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end());
|
||||
std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
|
||||
std::vector<const PrintInstance*> instances;
|
||||
instances.reserve(objects.size());
|
||||
for (const PrintObject *object : objects)
|
||||
for (size_t i = 0; i < object->instances().size(); ++ i)
|
||||
instances.emplace_back(&object->instances()[i]);
|
||||
return instances;
|
||||
}
|
||||
|
||||
// Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model().
|
||||
std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Print& print)
|
||||
{
|
||||
// Build up map from ModelInstance* to PrintInstance*
|
||||
std::vector<std::pair<const ModelInstance*, const PrintInstance*>> model_instance_to_print_instance;
|
||||
model_instance_to_print_instance.reserve(print.num_object_instances());
|
||||
for (const PrintObject *print_object : print.objects())
|
||||
for (const PrintInstance &print_instance : print_object->instances())
|
||||
model_instance_to_print_instance.emplace_back(print_instance.model_instance, &print_instance);
|
||||
std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), [](auto &l, auto &r) { return l.first < r.first; });
|
||||
|
||||
std::vector<const PrintInstance*> instances;
|
||||
instances.reserve(model_instance_to_print_instance.size());
|
||||
for (const ModelObject *model_object : print.model().objects)
|
||||
for (const ModelInstance *model_instance : model_object->instances) {
|
||||
auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), std::make_pair(model_instance, nullptr), [](auto &l, auto &r) { return l.first < r.first; });
|
||||
if (it != model_instance_to_print_instance.end() && it->first == model_instance)
|
||||
instances.emplace_back(it->second);
|
||||
}
|
||||
return instances;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
#else
|
||||
|
|
@ -1125,7 +1161,7 @@ void GCode::_do_export(Print& print, FILE* file)
|
|||
for (auto layer : object->support_layers())
|
||||
zs.push_back(layer->print_z);
|
||||
std::sort(zs.begin(), zs.end());
|
||||
m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
|
||||
m_layer_count += (unsigned int)(object->instances().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
|
||||
}
|
||||
} else {
|
||||
// Print all objects with the same print_z together.
|
||||
|
|
@ -1218,13 +1254,18 @@ void GCode::_do_export(Print& print, FILE* file)
|
|||
ToolOrdering tool_ordering;
|
||||
unsigned int initial_extruder_id = (unsigned int)-1;
|
||||
unsigned int final_extruder_id = (unsigned int)-1;
|
||||
size_t initial_print_object_id = 0;
|
||||
bool has_wipe_tower = false;
|
||||
std::vector<const PrintInstance*> print_object_instances_ordering;
|
||||
std::vector<const PrintInstance*>::const_iterator print_object_instance_sequential_active;
|
||||
if (print.config().complete_objects.value) {
|
||||
// Order object instances for sequential print.
|
||||
print_object_instances_ordering = sort_object_instances_by_model_order(print);
|
||||
// print_object_instances_ordering = sort_object_instances_by_max_z(print);
|
||||
// Find the 1st printing object, find its tool ordering and the initial extruder ID.
|
||||
for (; initial_print_object_id < print.objects().size(); ++initial_print_object_id) {
|
||||
tool_ordering = ToolOrdering(*print.objects()[initial_print_object_id], initial_extruder_id);
|
||||
if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
|
||||
print_object_instance_sequential_active = print_object_instances_ordering.begin();
|
||||
for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) {
|
||||
tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
|
||||
if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast<unsigned int>(-1))
|
||||
break;
|
||||
}
|
||||
// We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
|
||||
|
|
@ -1244,6 +1285,8 @@ void GCode::_do_export(Print& print, FILE* file)
|
|||
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
|
||||
// Therefore initialize the printing extruders from there.
|
||||
this->set_extruders(tool_ordering.all_extruders());
|
||||
// Order object instances using a nearest neighbor search.
|
||||
print_object_instances_ordering = chain_print_object_instances(print);
|
||||
}
|
||||
if (initial_extruder_id == (unsigned int)-1) {
|
||||
// Nothing to print!
|
||||
|
|
@ -1324,72 +1367,64 @@ void GCode::_do_export(Print& print, FILE* file)
|
|||
|
||||
// Do all objects for each layer.
|
||||
if (print.config().complete_objects.value) {
|
||||
// Print objects from the smallest to the tallest to avoid collisions
|
||||
// when moving onto next object starting point.
|
||||
std::vector<PrintObject*> objects(print.objects());
|
||||
std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size(2) < po2->size(2); });
|
||||
size_t finished_objects = 0;
|
||||
for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) {
|
||||
const PrintObject &object = *objects[object_id];
|
||||
for (const Point © : object.copies()) {
|
||||
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
|
||||
if (object_id != initial_print_object_id || © != object.copies().data()) {
|
||||
// Don't initialize for the first object and first copy.
|
||||
tool_ordering = ToolOrdering(object, final_extruder_id);
|
||||
unsigned int new_extruder_id = tool_ordering.first_extruder();
|
||||
if (new_extruder_id == (unsigned int)-1)
|
||||
// Skip this object.
|
||||
continue;
|
||||
initial_extruder_id = new_extruder_id;
|
||||
final_extruder_id = tool_ordering.last_extruder();
|
||||
assert(final_extruder_id != (unsigned int)-1);
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
this->set_origin(unscale(copy));
|
||||
if (finished_objects > 0) {
|
||||
// Move to the origin position for the copy we're going to print.
|
||||
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
|
||||
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
|
||||
m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
_write(file, this->retract());
|
||||
_write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
|
||||
m_enable_cooling_markers = true;
|
||||
// Disable motion planner when traveling to first object point.
|
||||
m_avoid_crossing_perimeters.disable_once = true;
|
||||
// Ff we are printing the bottom layer of an object, and we have already finished
|
||||
// another one, set first layer temperatures. This happens before the Z move
|
||||
// is triggered, so machine has more time to reach such temperatures.
|
||||
m_placeholder_parser.set("current_object_idx", int(finished_objects));
|
||||
std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config().between_objects_gcode.value, initial_extruder_id);
|
||||
// Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
|
||||
this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
_writeln(file, between_objects_gcode);
|
||||
}
|
||||
// Reset the cooling buffer internal state (the current position, feed rate, accelerations).
|
||||
m_cooling_buffer->reset();
|
||||
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
||||
// Pair the object layers with the support layers by z, extrude them.
|
||||
std::vector<LayerToPrint> layers_to_print = collect_layers_to_print(object);
|
||||
for (const LayerToPrint <p : layers_to_print) {
|
||||
std::vector<LayerToPrint> lrs;
|
||||
lrs.emplace_back(std::move(ltp));
|
||||
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, © - object.copies().data());
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
if (m_pressure_equalizer)
|
||||
_write(file, m_pressure_equalizer->process("", true));
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
++ finished_objects;
|
||||
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
|
||||
// Reset it when starting another object from 1st layer.
|
||||
m_second_layer_things_done = false;
|
||||
const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object;
|
||||
for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) {
|
||||
const PrintObject &object = *(*print_object_instance_sequential_active)->print_object;
|
||||
if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) {
|
||||
tool_ordering = ToolOrdering(object, final_extruder_id);
|
||||
unsigned int new_extruder_id = tool_ordering.first_extruder();
|
||||
if (new_extruder_id == (unsigned int)-1)
|
||||
// Skip this object.
|
||||
continue;
|
||||
initial_extruder_id = new_extruder_id;
|
||||
final_extruder_id = tool_ordering.last_extruder();
|
||||
assert(final_extruder_id != (unsigned int)-1);
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
this->set_origin(unscale((*print_object_instance_sequential_active)->shift));
|
||||
if (finished_objects > 0) {
|
||||
// Move to the origin position for the copy we're going to print.
|
||||
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
|
||||
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
|
||||
m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
_write(file, this->retract());
|
||||
_write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
|
||||
m_enable_cooling_markers = true;
|
||||
// Disable motion planner when traveling to first object point.
|
||||
m_avoid_crossing_perimeters.disable_once = true;
|
||||
// Ff we are printing the bottom layer of an object, and we have already finished
|
||||
// another one, set first layer temperatures. This happens before the Z move
|
||||
// is triggered, so machine has more time to reach such temperatures.
|
||||
m_placeholder_parser.set("current_object_idx", int(finished_objects));
|
||||
std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config().between_objects_gcode.value, initial_extruder_id);
|
||||
// Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
|
||||
this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
|
||||
_writeln(file, between_objects_gcode);
|
||||
}
|
||||
// Reset the cooling buffer internal state (the current position, feed rate, accelerations).
|
||||
m_cooling_buffer->reset();
|
||||
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
||||
// Pair the object layers with the support layers by z, extrude them.
|
||||
std::vector<LayerToPrint> layers_to_print = collect_layers_to_print(object);
|
||||
for (const LayerToPrint <p : layers_to_print) {
|
||||
std::vector<LayerToPrint> lrs;
|
||||
lrs.emplace_back(std::move(ltp));
|
||||
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, *print_object_instance_sequential_active - object.instances().data());
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
if (m_pressure_equalizer)
|
||||
_write(file, m_pressure_equalizer->process("", true));
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
++ finished_objects;
|
||||
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
|
||||
// Reset it when starting another object from 1st layer.
|
||||
m_second_layer_things_done = false;
|
||||
prev_object = &object;
|
||||
}
|
||||
} else {
|
||||
// Order object instances using a nearest neighbor search.
|
||||
std::vector<std::pair<size_t, size_t>> print_object_instances_ordering = chain_print_object_instances(print);
|
||||
// Sort layers by Z.
|
||||
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
|
||||
|
|
@ -1691,12 +1726,12 @@ inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
|
|||
}
|
||||
|
||||
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
|
||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
|
||||
const std::vector<LayerToPrint> &layers,
|
||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
|
||||
const std::vector<LayerToPrint> &layers,
|
||||
// Ordering must be defined for normal (non-sequential print).
|
||||
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||
const std::vector<const PrintInstance*> *ordering,
|
||||
// For sequential print, the instance of the object to be printing has to be defined.
|
||||
const size_t single_object_instance_idx)
|
||||
const size_t single_object_instance_idx)
|
||||
{
|
||||
std::vector<InstanceToPrint> out;
|
||||
|
||||
|
|
@ -1723,13 +1758,13 @@ std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
|
|||
if (! sorted.empty()) {
|
||||
const Print &print = *sorted.front().first->print();
|
||||
out.reserve(sorted.size());
|
||||
for (const std::pair<size_t, size_t> &instance_id : *ordering) {
|
||||
const PrintObject &print_object = *print.objects()[instance_id.first];
|
||||
for (const PrintInstance *instance : *ordering) {
|
||||
const PrintObject &print_object = *instance->print_object;
|
||||
std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
|
||||
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
|
||||
if (it != sorted.end() && it->first == &print_object)
|
||||
// ObjectByExtruder for this PrintObject was found.
|
||||
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance_id.second);
|
||||
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1740,10 +1775,10 @@ namespace ProcessLayer
|
|||
{
|
||||
|
||||
static std::string emit_custom_gcode_per_print_z(
|
||||
const Model::CustomGCode *custom_gcode,
|
||||
const CustomGCode::Item *custom_gcode,
|
||||
// ID of the first extruder printing this layer.
|
||||
unsigned int first_extruder_id,
|
||||
bool single_material_print)
|
||||
bool single_extruder_printer)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
|
|
@ -1751,31 +1786,39 @@ namespace ProcessLayer
|
|||
// Extruder switches are processed by LayerTools, they should be filtered out.
|
||||
assert(custom_gcode->gcode != ToolChangeCode);
|
||||
|
||||
const std::string &custom_code = custom_gcode->gcode;
|
||||
const std::string &custom_code = custom_gcode->gcode;
|
||||
bool color_change = custom_code == ColorChangeCode;
|
||||
bool tool_change = custom_code == ToolChangeCode;
|
||||
// Tool Change is applied as Color Change for a single extruder printer only.
|
||||
assert(! tool_change || single_extruder_printer);
|
||||
|
||||
std::string pause_print_msg;
|
||||
int m600_extruder_before_layer = -1;
|
||||
if (custom_code == ColorChangeCode && custom_gcode->extruder > 0)
|
||||
if (color_change && custom_gcode->extruder > 0)
|
||||
m600_extruder_before_layer = custom_gcode->extruder - 1;
|
||||
else if (custom_code == PausePrintCode)
|
||||
pause_print_msg = custom_gcode->color;
|
||||
|
||||
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
|
||||
if (custom_code == ColorChangeCode) // color change
|
||||
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
|
||||
if (color_change || tool_change)
|
||||
{
|
||||
// Color Change or Tool Change as Color Change.
|
||||
// add tag for analyzer
|
||||
gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
|
||||
// add tag for time estimator
|
||||
gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
|
||||
|
||||
if (!single_material_print && m600_extruder_before_layer >= 0 && first_extruder_id != m600_extruder_before_layer
|
||||
if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != m600_extruder_before_layer
|
||||
// && !MMU1
|
||||
) {
|
||||
//! FIXME_in_fw show message during print pause
|
||||
gcode += "M601\n"; // pause print
|
||||
gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
|
||||
}
|
||||
else
|
||||
gcode += custom_code + "\n";
|
||||
else {
|
||||
gcode += ColorChangeCode;
|
||||
gcode += "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1836,7 +1879,9 @@ namespace Skirt {
|
|||
const Print &print,
|
||||
const std::vector<GCode::LayerToPrint> &layers,
|
||||
const LayerTools &layer_tools,
|
||||
// Heights (print_z) at which the skirt has already been extruded.
|
||||
// First non-empty support layer.
|
||||
const SupportLayer *support_layer,
|
||||
// Heights (print_z) at which the skirt has already been extruded.
|
||||
std::vector<coordf_t> &skirt_done)
|
||||
{
|
||||
// Extrude skirt at the print_z of the raft layers and normal object layers
|
||||
|
|
@ -1849,7 +1894,7 @@ namespace Skirt {
|
|||
// This print_z has not been extruded yet (sequential print)
|
||||
skirt_done.back() < layer_tools.print_z - EPSILON &&
|
||||
// and this layer is an object layer, or it is a raft layer.
|
||||
(layer_tools.has_object || layers.front().support_layer->id() < (size_t)layers.front().support_layer->object()->config().raft_layers.value)) {
|
||||
(layer_tools.has_object || support_layer->id() < (size_t)support_layer->object()->config().raft_layers.value)) {
|
||||
#if 0
|
||||
// Prime just the first printing extruder. This is original Slic3r's implementation.
|
||||
skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair<size_t, size_t>(0, print.config().skirts.value);
|
||||
|
|
@ -1873,16 +1918,16 @@ namespace Skirt {
|
|||
// and performing the extruder specific extrusions together.
|
||||
void GCode::process_layer(
|
||||
// Write into the output file.
|
||||
FILE *file,
|
||||
const Print &print,
|
||||
FILE *file,
|
||||
const Print &print,
|
||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||
const std::vector<LayerToPrint> &layers,
|
||||
const LayerTools &layer_tools,
|
||||
const std::vector<LayerToPrint> &layers,
|
||||
const LayerTools &layer_tools,
|
||||
// Pairs of PrintObject index and its instance index.
|
||||
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||
const std::vector<const PrintInstance*> *ordering,
|
||||
// If set to size_t(-1), then print all copies of all objects.
|
||||
// Otherwise print a single copy of a single object.
|
||||
const size_t single_object_instance_idx)
|
||||
const size_t single_object_instance_idx)
|
||||
{
|
||||
assert(! layers.empty());
|
||||
// assert(! layer_tools.extruders.empty());
|
||||
|
|
@ -1977,7 +2022,7 @@ void GCode::process_layer(
|
|||
// not at the print_z of the interlaced support material layers.
|
||||
skirt_loops_per_extruder = first_layer ?
|
||||
Skirt::make_skirt_loops_per_extruder_1st_layer(print, layers, layer_tools, m_skirt_done) :
|
||||
Skirt::make_skirt_loops_per_extruder_other_layers(print, layers, layer_tools, m_skirt_done);
|
||||
Skirt::make_skirt_loops_per_extruder_other_layers(print, layers, layer_tools, support_layer, m_skirt_done);
|
||||
|
||||
// Group extrusions by an extruder, then by an object, an island and a region.
|
||||
std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
|
||||
|
|
@ -2084,16 +2129,16 @@ void GCode::process_layer(
|
|||
|
||||
// Let's recover vector of extruder overrides:
|
||||
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
|
||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||
correct_extruder_id = layer_tools.extruders.back();
|
||||
}
|
||||
printing_extruders.clear();
|
||||
if (is_anything_overridden) {
|
||||
printing_extruders.clear();
|
||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||
correct_extruder_id = layer_tools.extruders.back();
|
||||
}
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->copies().size());
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
if (entity_overrides == nullptr) {
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
} else {
|
||||
printing_extruders.reserve(entity_overrides->size());
|
||||
for (int extruder : *entity_overrides)
|
||||
|
|
@ -2102,10 +2147,10 @@ void GCode::process_layer(
|
|||
extruder :
|
||||
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
|
||||
static_cast<unsigned int>(- extruder - 1));
|
||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
||||
}
|
||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
||||
} else
|
||||
printing_extruders = { (unsigned int)correct_extruder_id };
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
|
||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
|
||||
for (unsigned int extruder : printing_extruders)
|
||||
|
|
@ -2205,7 +2250,7 @@ void GCode::process_layer(
|
|||
if (this->config().gcode_label_objects)
|
||||
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
|
||||
// When starting a new object, use the external motion planner for the first travel move.
|
||||
const Point &offset = instance_to_print.print_object.copies()[instance_to_print.instance_id];
|
||||
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
|
||||
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
|
||||
if (m_last_obj_copy != this_object_copy)
|
||||
m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
|
|
@ -2577,8 +2622,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
|
|||
}
|
||||
}
|
||||
else if (seam_position == spRear) {
|
||||
last_pos = m_layer->object()->bounding_box().center();
|
||||
last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius());
|
||||
// Object is centered around (0,0) in its current coordinate system.
|
||||
last_pos.x() = 0;
|
||||
last_pos.y() += coord_t(3. * m_layer->object()->bounding_box().radius());
|
||||
last_pos_weight = 5.f;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ private:
|
|||
const std::vector<LayerToPrint> &layers,
|
||||
const LayerTools &layer_tools,
|
||||
// Pairs of PrintObject index and its instance index.
|
||||
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||
const std::vector<const PrintInstance*> *ordering,
|
||||
// If set to size_t(-1), then print all copies of all objects.
|
||||
// Otherwise print a single copy of a single object.
|
||||
const size_t single_object_idx = size_t(-1));
|
||||
|
|
@ -305,7 +305,7 @@ private:
|
|||
// Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
|
||||
const std::vector<LayerToPrint> &layers,
|
||||
// Ordering must be defined for normal (non-sequential print).
|
||||
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||
const std::vector<const PrintInstance*> *ordering,
|
||||
// For sequential print, the instance of the object to be printing has to be defined.
|
||||
const size_t single_object_instance_idx);
|
||||
|
||||
|
|
@ -430,6 +430,8 @@ private:
|
|||
friend class WipeTowerIntegration;
|
||||
};
|
||||
|
||||
std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Print& print);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -121,9 +121,9 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
|
|||
if (support_layer)
|
||||
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
|
||||
bbox_this.merge(extrusionentity_extents(extrusion_entity));
|
||||
for (const Point &offset : print_object.copies()) {
|
||||
for (const PrintInstance &instance : print_object.instances()) {
|
||||
BoundingBoxf bbox_translated(bbox_this);
|
||||
bbox_translated.translate(unscale(offset));
|
||||
bbox_translated.translate(unscale(instance.shift));
|
||||
bbox.merge(bbox_translated);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||
|
||||
// If we're not going to modify G-code, just feed it to the reader
|
||||
// in order to update positions.
|
||||
if (!this->enable) {
|
||||
this->_reader.parse_buffer(gcode);
|
||||
if (! this->enable) {
|
||||
m_reader.parse_buffer(gcode);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||
|
||||
{
|
||||
//FIXME Performance warning: This copies the GCodeConfig of the reader.
|
||||
GCodeReader r = this->_reader; // clone
|
||||
GCodeReader r = m_reader; // clone
|
||||
r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z]
|
||||
(GCodeReader &reader, const GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
|
|
@ -50,7 +50,7 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||
z -= layer_height;
|
||||
|
||||
std::string new_gcode;
|
||||
this->_reader.parse_buffer(gcode, [&new_gcode, &z, &layer_height, &total_layer_length]
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, &layer_height, &total_layer_length]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
|
|
|
|||
|
|
@ -7,20 +7,19 @@
|
|||
namespace Slic3r {
|
||||
|
||||
class SpiralVase {
|
||||
public:
|
||||
bool enable;
|
||||
public:
|
||||
bool enable = false;
|
||||
|
||||
SpiralVase(const PrintConfig &config)
|
||||
: enable(false), _config(&config)
|
||||
SpiralVase(const PrintConfig &config) : m_config(&config)
|
||||
{
|
||||
this->_reader.z() = (float)this->_config->z_offset;
|
||||
this->_reader.apply_config(*this->_config);
|
||||
m_reader.z() = (float)m_config->z_offset;
|
||||
m_reader.apply_config(*m_config);
|
||||
};
|
||||
std::string process_layer(const std::string &gcode);
|
||||
|
||||
private:
|
||||
const PrintConfig* _config;
|
||||
GCodeReader _reader;
|
||||
private:
|
||||
const PrintConfig *m_config;
|
||||
GCodeReader m_reader;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,10 +130,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
|||
// Do it only if all the objects were configured to be printed with a single extruder.
|
||||
std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
|
||||
if (auto num_extruders = unsigned(print.config().nozzle_diameter.size());
|
||||
num_extruders > 1 && print.object_extruders().size() == 1) {
|
||||
num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
|
||||
print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) {
|
||||
// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
|
||||
// There may be custom per-layer tool changes available at the model.
|
||||
per_layer_extruder_switches = custom_tool_changes(print.model(), num_extruders);
|
||||
per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders);
|
||||
}
|
||||
|
||||
// Collect extruders reuqired to print the layers.
|
||||
|
|
@ -462,15 +463,23 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
|
|||
// Only valid for non-sequential print.
|
||||
assert(! print.config().complete_objects.value);
|
||||
|
||||
const Model::CustomGCodeInfo &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
|
||||
const CustomGCode::Info &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
|
||||
if (custom_gcode_per_print_z.gcodes.empty())
|
||||
return;
|
||||
|
||||
unsigned int num_extruders = *std::max_element(m_all_printing_extruders.begin(), m_all_printing_extruders.end()) + 1;
|
||||
std::vector<unsigned char> extruder_printing_above(num_extruders, false);
|
||||
auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin();
|
||||
auto num_extruders = unsigned(print.config().nozzle_diameter.size());
|
||||
CustomGCode::Mode mode =
|
||||
(num_extruders == 1) ? CustomGCode::SingleExtruder :
|
||||
print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder;
|
||||
CustomGCode::Mode model_mode = print.model().custom_gcode_per_print_z.mode;
|
||||
std::vector<unsigned char> extruder_printing_above(num_extruders, false);
|
||||
auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin();
|
||||
// Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode
|
||||
// or vice versa.
|
||||
bool ignore_tool_and_color_changes = (mode == CustomGCode::MultiExtruder) != (model_mode == CustomGCode::MultiExtruder);
|
||||
// If printing on a single extruder machine, make the tool changes trigger color change (M600) events.
|
||||
bool tool_changes_as_color_changes = num_extruders == 1;
|
||||
bool tool_changes_as_color_changes = mode == CustomGCode::SingleExtruder && model_mode == CustomGCode::MultiAsSingle;
|
||||
|
||||
// From the last layer to the first one:
|
||||
for (auto it_lt = m_layer_tools.rbegin(); it_lt != m_layer_tools.rend(); ++ it_lt) {
|
||||
LayerTools < = *it_lt;
|
||||
|
|
@ -483,16 +492,23 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
|
|||
// Custom G-codes were processed.
|
||||
break;
|
||||
// Some custom G-code is configured for this layer or a layer below.
|
||||
const Model::CustomGCode &custom_gcode = *custom_gcode_it;
|
||||
const CustomGCode::Item &custom_gcode = *custom_gcode_it;
|
||||
// print_z of the layer below the current layer.
|
||||
coordf_t print_z_below = 0.;
|
||||
if (auto it_lt_below = it_lt; ++ it_lt_below != m_layer_tools.rend())
|
||||
print_z_below = it_lt_below->print_z;
|
||||
if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) {
|
||||
// The custom G-code applies to the current layer.
|
||||
if ( tool_changes_as_color_changes || custom_gcode.gcode != ColorChangeCode ||
|
||||
(custom_gcode.extruder <= num_extruders && extruder_printing_above[unsigned(custom_gcode.extruder - 1)]))
|
||||
bool color_change = custom_gcode.gcode == ColorChangeCode;
|
||||
bool tool_change = custom_gcode.gcode == ToolChangeCode;
|
||||
bool pause_or_custom_gcode = ! color_change && ! tool_change;
|
||||
bool apply_color_change = ! ignore_tool_and_color_changes &&
|
||||
// If it is color change, it will actually be useful as the exturder above will print.
|
||||
(color_change ?
|
||||
mode == CustomGCode::SingleExtruder ||
|
||||
(custom_gcode.extruder <= int(num_extruders) && extruder_printing_above[unsigned(custom_gcode.extruder - 1)]) :
|
||||
tool_change && tool_changes_as_color_changes);
|
||||
if (pause_or_custom_gcode || apply_color_change)
|
||||
lt.custom_gcode = &custom_gcode;
|
||||
// Consume that custom G-code event.
|
||||
++ custom_gcode_it;
|
||||
|
|
@ -602,7 +618,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
|
||||
if (this_layer == nullptr)
|
||||
continue;
|
||||
size_t num_of_copies = object->copies().size();
|
||||
size_t num_of_copies = object->instances().size();
|
||||
|
||||
// iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves
|
||||
for (unsigned int copy = 0; copy < num_of_copies; ++copy) {
|
||||
|
|
@ -677,7 +693,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
|
||||
if (this_layer == nullptr)
|
||||
continue;
|
||||
size_t num_of_copies = object->copies().size();
|
||||
size_t num_of_copies = object->instances().size();
|
||||
|
||||
for (size_t copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public:
|
|||
size_t wipe_tower_partitions = 0;
|
||||
coordf_t wipe_tower_layer_height = 0.;
|
||||
// Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print.
|
||||
const Model::CustomGCode *custom_gcode = nullptr;
|
||||
const CustomGCode::Item *custom_gcode = nullptr;
|
||||
|
||||
WipingExtrusions& wiping_extrusions() {
|
||||
m_wiping_extrusions.set_layer_tools_ptr(this);
|
||||
|
|
|
|||
|
|
@ -707,6 +707,11 @@ namespace Slic3r {
|
|||
return _get_time_dhms(get_time());
|
||||
}
|
||||
|
||||
std::string GCodeTimeEstimator::get_time_dhm() const
|
||||
{
|
||||
return _get_time_dhm(get_time());
|
||||
}
|
||||
|
||||
std::string GCodeTimeEstimator::get_time_minutes() const
|
||||
{
|
||||
return _get_time_minutes(get_time());
|
||||
|
|
@ -1616,6 +1621,29 @@ namespace Slic3r {
|
|||
return buffer;
|
||||
}
|
||||
|
||||
std::string GCodeTimeEstimator::_get_time_dhm(float time_in_secs)
|
||||
{
|
||||
char buffer[64];
|
||||
|
||||
int minutes = std::round(time_in_secs / 60.);
|
||||
if (minutes <= 0) {
|
||||
::sprintf(buffer, "%ds", (int)time_in_secs);
|
||||
} else {
|
||||
int days = minutes / 1440;
|
||||
minutes -= days * 1440;
|
||||
int hours = minutes / 60;
|
||||
minutes -= hours * 60;
|
||||
if (days > 0)
|
||||
::sprintf(buffer, "%dd %dh %dm", days, hours, minutes);
|
||||
else if (hours > 0)
|
||||
::sprintf(buffer, "%dh %dm", hours, minutes);
|
||||
else
|
||||
::sprintf(buffer, "%dm", minutes);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string GCodeTimeEstimator::_get_time_minutes(float time_in_secs)
|
||||
{
|
||||
return std::to_string((int)(::roundf(time_in_secs / 60.0f)));
|
||||
|
|
|
|||
|
|
@ -363,6 +363,9 @@ namespace Slic3r {
|
|||
// Returns the estimated time, in format DDd HHh MMm SSs
|
||||
std::string get_time_dhms() const;
|
||||
|
||||
// Returns the estimated time, in format DDd HHh MMm
|
||||
std::string get_time_dhm() const;
|
||||
|
||||
// Returns the estimated time, in minutes (integer)
|
||||
std::string get_time_minutes() const;
|
||||
|
||||
|
|
@ -473,6 +476,8 @@ namespace Slic3r {
|
|||
|
||||
// Returns the given time is seconds in format DDd HHh MMm SSs
|
||||
static std::string _get_time_dhms(float time_in_secs);
|
||||
// Returns the given time is minutes in format DDd HHh MMm
|
||||
static std::string _get_time_dhm(float time_in_secs);
|
||||
|
||||
// Returns the given, in minutes (integer)
|
||||
static std::string _get_time_minutes(float time_in_secs);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "GCodeWriter.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
|
|
|||
|
|
@ -10,11 +10,6 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
// Additional Codes which can be set by user using DoubleSlider
|
||||
static constexpr char ColorChangeCode[] = "M600";
|
||||
static constexpr char PausePrintCode[] = "M601";
|
||||
static constexpr char ToolChangeCode[] = "tool_change";
|
||||
|
||||
class GCodeWriter {
|
||||
public:
|
||||
GCodeConfig config;
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ public:
|
|||
coordf_t slice_z; // Z used for slicing in unscaled coordinates
|
||||
coordf_t print_z; // Z used for printing in unscaled coordinates
|
||||
coordf_t height; // layer height in unscaled coordinates
|
||||
coordf_t bottom_z() const { return this->print_z - this->height; }
|
||||
|
||||
// Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry
|
||||
// (with possibly differing extruder ID and slicing parameters) and merged.
|
||||
|
|
|
|||
|
|
@ -362,8 +362,10 @@ void LayerRegion::prepare_fill_surfaces()
|
|||
alter fill_surfaces boundaries on which our idempotency relies since that's
|
||||
the only meaningful information returned by psPerimeters. */
|
||||
|
||||
bool spiral_vase = this->layer()->object()->print()->config().spiral_vase;
|
||||
|
||||
// if no solid layers are requested, turn top/bottom surfaces to internal
|
||||
if (this->region()->config().top_solid_layers == 0) {
|
||||
if (! spiral_vase && this->region()->config().top_solid_layers == 0) {
|
||||
for (Surface &surface : this->fill_surfaces.surfaces)
|
||||
if (surface.is_top())
|
||||
surface.surface_type = this->layer()->object()->config().infill_only_where_needed ? stInternalVoid : stInternal;
|
||||
|
|
@ -375,7 +377,7 @@ void LayerRegion::prepare_fill_surfaces()
|
|||
}
|
||||
|
||||
// turn too small internal regions into solid regions according to the user setting
|
||||
if (this->region()->config().fill_density.value > 0) {
|
||||
if (! spiral_vase && this->region()->config().fill_density.value > 0) {
|
||||
// scaling an area requires two calls!
|
||||
double min_area = scale_(scale_(this->region()->config().solid_infill_below_area.value));
|
||||
for (Surface &surface : this->fill_surfaces.surfaces)
|
||||
|
|
|
|||
|
|
@ -252,46 +252,6 @@ template<class T> struct remove_cvref
|
|||
|
||||
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
template<class T> using DefaultContainer = std::vector<T>;
|
||||
|
||||
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
||||
template<class T, class I, template<class> class Container = DefaultContainer>
|
||||
inline Container<remove_cvref_t<T>> linspace(const T &start,
|
||||
const T &stop,
|
||||
const I &n)
|
||||
{
|
||||
Container<remove_cvref_t<T>> vals(n, T());
|
||||
|
||||
T stride = (stop - start) / n;
|
||||
size_t i = 0;
|
||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||
return start + i++ * stride;
|
||||
});
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
/// A set of equidistant values starting from 'start' (inclusive), ending
|
||||
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
||||
/// leaving 'stride' space between each value.
|
||||
/// Very similar to Matlab [start:stride:end] notation.
|
||||
template<class T, template<class> class Container = DefaultContainer>
|
||||
inline Container<remove_cvref_t<T>> grid(const T &start,
|
||||
const T &stop,
|
||||
const T &stride)
|
||||
{
|
||||
Container<remove_cvref_t<T>>
|
||||
vals(size_t(std::ceil((stop - start) / stride)), T());
|
||||
|
||||
int i = 0;
|
||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||
return start + i++ * stride;
|
||||
});
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
|
||||
// A shorter C++14 style form of the enable_if metafunction
|
||||
template<bool B, class T>
|
||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
|
@ -392,6 +352,56 @@ inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
|
||||
template<class T, class I>
|
||||
inline std::vector<T> linspace_vector(const ArithmeticOnly<T> &start,
|
||||
const T &stop,
|
||||
const IntegerOnly<I> &n)
|
||||
{
|
||||
std::vector<T> vals(n, T());
|
||||
|
||||
T stride = (stop - start) / n;
|
||||
size_t i = 0;
|
||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||
return start + i++ * stride;
|
||||
});
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
template<size_t N, class T>
|
||||
inline std::array<ArithmeticOnly<T>, N> linspace_array(const T &start, const T &stop)
|
||||
{
|
||||
std::array<T, N> vals = {T()};
|
||||
|
||||
T stride = (stop - start) / N;
|
||||
size_t i = 0;
|
||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||
return start + i++ * stride;
|
||||
});
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
/// A set of equidistant values starting from 'start' (inclusive), ending
|
||||
/// in the closest multiple of 'stride' less than or equal to 'end' and
|
||||
/// leaving 'stride' space between each value.
|
||||
/// Very similar to Matlab [start:stride:end] notation.
|
||||
template<class T>
|
||||
inline std::vector<ArithmeticOnly<T>> grid(const T &start,
|
||||
const T &stop,
|
||||
const T &stride)
|
||||
{
|
||||
std::vector<T> vals(size_t(std::ceil((stop - start) / stride)), T());
|
||||
|
||||
int i = 0;
|
||||
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
|
||||
return start + i++ * stride;
|
||||
});
|
||||
|
||||
return vals;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MTUTILS_HPP
|
||||
|
|
|
|||
276
src/libslic3r/MeshBoolean.cpp
Normal file
276
src/libslic3r/MeshBoolean.cpp
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#include "MeshBoolean.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#undef PI
|
||||
|
||||
// Include igl first. It defines "L" macro which then clashes with our localization
|
||||
#include <igl/copyleft/cgal/mesh_boolean.h>
|
||||
#undef L
|
||||
|
||||
// CGAL headers
|
||||
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
||||
#include <CGAL/Exact_integer.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair.h>
|
||||
#include <CGAL/Polygon_mesh_processing/remesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orientation.h>
|
||||
#include <CGAL/Cartesian_converter.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace MeshBoolean {
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh)
|
||||
{
|
||||
auto &VC = emesh.first; auto &FC = emesh.second;
|
||||
|
||||
Pointf3s points(size_t(VC.rows()));
|
||||
std::vector<Vec3crd> facets(size_t(FC.rows()));
|
||||
|
||||
for (Eigen::Index i = 0; i < VC.rows(); ++i)
|
||||
points[size_t(i)] = VC.row(i);
|
||||
|
||||
for (Eigen::Index i = 0; i < FC.rows(); ++i)
|
||||
facets[size_t(i)] = FC.row(i);
|
||||
|
||||
TriangleMesh out{points, facets};
|
||||
out.require_shared_vertices();
|
||||
return out;
|
||||
}
|
||||
|
||||
EigenMesh triangle_mesh_to_eigen(const TriangleMesh &mesh)
|
||||
{
|
||||
EigenMesh emesh;
|
||||
emesh.first = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
|
||||
Eigen::Index(mesh.its.vertices.size()),
|
||||
3).cast<double>();
|
||||
|
||||
emesh.second = MapMatrixXiUnaligned(mesh.its.indices.front().data(),
|
||||
Eigen::Index(mesh.its.indices.size()),
|
||||
3);
|
||||
return emesh;
|
||||
}
|
||||
|
||||
void minus(EigenMesh &A, const EigenMesh &B)
|
||||
{
|
||||
auto &[VA, FA] = A;
|
||||
auto &[VB, FB] = B;
|
||||
|
||||
Eigen::MatrixXd VC;
|
||||
Eigen::MatrixXi FC;
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_MINUS);
|
||||
igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, boolean_type, VC, FC);
|
||||
|
||||
VA = std::move(VC); FA = std::move(FC);
|
||||
}
|
||||
|
||||
void minus(TriangleMesh& A, const TriangleMesh& B)
|
||||
{
|
||||
EigenMesh eA = triangle_mesh_to_eigen(A);
|
||||
minus(eA, triangle_mesh_to_eigen(B));
|
||||
A = eigen_to_triangle_mesh(eA);
|
||||
}
|
||||
|
||||
void self_union(EigenMesh &A)
|
||||
{
|
||||
EigenMesh result;
|
||||
auto &[V, F] = A;
|
||||
auto &[VC, FC] = result;
|
||||
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
|
||||
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
|
||||
|
||||
A = std::move(result);
|
||||
}
|
||||
|
||||
void self_union(TriangleMesh& mesh)
|
||||
{
|
||||
auto eM = triangle_mesh_to_eigen(mesh);
|
||||
self_union(eM);
|
||||
mesh = eigen_to_triangle_mesh(eM);
|
||||
}
|
||||
|
||||
namespace cgal {
|
||||
|
||||
namespace CGALProc = CGAL::Polygon_mesh_processing;
|
||||
namespace CGALParams = CGAL::Polygon_mesh_processing::parameters;
|
||||
|
||||
using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel;
|
||||
using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
|
||||
using _EpicMesh = CGAL::Surface_mesh<EpicKernel::Point_3>;
|
||||
using _EpecMesh = CGAL::Surface_mesh<EpecKernel::Point_3>;
|
||||
|
||||
struct CGALMesh { _EpicMesh m; };
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Converions from and to CGAL mesh
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<class _Mesh> void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &out)
|
||||
{
|
||||
using Index3 = std::array<size_t, 3>;
|
||||
|
||||
if (M.empty()) return;
|
||||
|
||||
std::vector<typename _Mesh::Point> points;
|
||||
std::vector<Index3> indices;
|
||||
points.reserve(M.its.vertices.size());
|
||||
indices.reserve(M.its.indices.size());
|
||||
for (auto &v : M.its.vertices) points.emplace_back(v.x(), v.y(), v.z());
|
||||
for (auto &_f : M.its.indices) {
|
||||
auto f = _f.cast<size_t>();
|
||||
indices.emplace_back(Index3{f(0), f(1), f(2)});
|
||||
}
|
||||
|
||||
CGALProc::orient_polygon_soup(points, indices);
|
||||
CGALProc::polygon_soup_to_polygon_mesh(points, indices, out);
|
||||
|
||||
// Number the faces because 'orient_to_bound_a_volume' needs a face <--> index map
|
||||
unsigned index = 0;
|
||||
for (auto face : out.faces()) face = CGAL::SM_Face_index(index++);
|
||||
|
||||
if(CGAL::is_closed(out))
|
||||
CGALProc::orient_to_bound_a_volume(out);
|
||||
else
|
||||
std::runtime_error("Mesh not watertight");
|
||||
}
|
||||
|
||||
inline Vec3d to_vec3d(const _EpicMesh::Point &v)
|
||||
{
|
||||
return {v.x(), v.y(), v.z()};
|
||||
}
|
||||
|
||||
inline Vec3d to_vec3d(const _EpecMesh::Point &v)
|
||||
{
|
||||
CGAL::Cartesian_converter<EpecKernel, EpicKernel> cvt;
|
||||
auto iv = cvt(v);
|
||||
return {iv.x(), iv.y(), iv.z()};
|
||||
}
|
||||
|
||||
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
{
|
||||
Pointf3s points;
|
||||
std::vector<Vec3crd> facets;
|
||||
points.reserve(cgalmesh.num_vertices());
|
||||
facets.reserve(cgalmesh.num_faces());
|
||||
|
||||
for (auto &vi : cgalmesh.vertices()) {
|
||||
auto &v = cgalmesh.point(vi); // Don't ask...
|
||||
points.emplace_back(to_vec3d(v));
|
||||
}
|
||||
|
||||
for (auto &face : cgalmesh.faces()) {
|
||||
auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
|
||||
int i = 0;
|
||||
Vec3crd trface;
|
||||
for (auto v : vtc) trface(i++) = static_cast<int>(v);
|
||||
facets.emplace_back(trface);
|
||||
}
|
||||
|
||||
TriangleMesh out{points, facets};
|
||||
out.require_shared_vertices();
|
||||
return out;
|
||||
}
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const TriangleMesh &M)
|
||||
{
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter> out(new CGALMesh{});
|
||||
triangle_mesh_to_cgal(M, out->m);
|
||||
return out;
|
||||
}
|
||||
|
||||
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh)
|
||||
{
|
||||
return cgal_to_triangle_mesh(cgalmesh.m);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Boolean operations for CGAL meshes
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static bool _cgal_diff(CGALMesh &A, CGALMesh &B, CGALMesh &R)
|
||||
{
|
||||
const auto &p = CGALParams::throw_on_self_intersection(true);
|
||||
return CGALProc::corefine_and_compute_difference(A.m, B.m, R.m, p, p);
|
||||
}
|
||||
|
||||
static bool _cgal_union(CGALMesh &A, CGALMesh &B, CGALMesh &R)
|
||||
{
|
||||
const auto &p = CGALParams::throw_on_self_intersection(true);
|
||||
return CGALProc::corefine_and_compute_union(A.m, B.m, R.m, p, p);
|
||||
}
|
||||
|
||||
static bool _cgal_intersection(CGALMesh &A, CGALMesh &B, CGALMesh &R)
|
||||
{
|
||||
const auto &p = CGALParams::throw_on_self_intersection(true);
|
||||
return CGALProc::corefine_and_compute_intersection(A.m, B.m, R.m, p, p);
|
||||
}
|
||||
|
||||
template<class Op> void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B)
|
||||
{
|
||||
bool success = false;
|
||||
try {
|
||||
CGALMesh result;
|
||||
success = op(A, B, result);
|
||||
A = std::move(result); // In-place operation does not work
|
||||
} catch (...) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (! success)
|
||||
throw std::runtime_error("CGAL mesh boolean operation failed.");
|
||||
}
|
||||
|
||||
void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); }
|
||||
void plus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_union, A, B); }
|
||||
void intersect(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_intersection, A, B); }
|
||||
bool does_self_intersect(const CGALMesh &mesh) { return CGALProc::does_self_intersect(mesh.m); }
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Now the public functions for TriangleMesh input:
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B)
|
||||
{
|
||||
CGALMesh meshA;
|
||||
CGALMesh meshB;
|
||||
triangle_mesh_to_cgal(A, meshA.m);
|
||||
triangle_mesh_to_cgal(B, meshB.m);
|
||||
|
||||
_cgal_do(op, meshA, meshB);
|
||||
|
||||
A = cgal_to_triangle_mesh(meshA.m);
|
||||
}
|
||||
|
||||
void minus(TriangleMesh &A, const TriangleMesh &B)
|
||||
{
|
||||
_mesh_boolean_do(_cgal_diff, A, B);
|
||||
}
|
||||
|
||||
void plus(TriangleMesh &A, const TriangleMesh &B)
|
||||
{
|
||||
_mesh_boolean_do(_cgal_union, A, B);
|
||||
}
|
||||
|
||||
void intersect(TriangleMesh &A, const TriangleMesh &B)
|
||||
{
|
||||
_mesh_boolean_do(_cgal_intersection, A, B);
|
||||
}
|
||||
|
||||
bool does_self_intersect(const TriangleMesh &mesh)
|
||||
{
|
||||
CGALMesh cgalm;
|
||||
triangle_mesh_to_cgal(mesh, cgalm.m);
|
||||
return CGALProc::does_self_intersect(cgalm.m);
|
||||
}
|
||||
|
||||
void CGALMeshDeleter::operator()(CGALMesh *ptr) { delete ptr; }
|
||||
|
||||
} // namespace cgal
|
||||
|
||||
} // namespace MeshBoolean
|
||||
} // namespace Slic3r
|
||||
49
src/libslic3r/MeshBoolean.hpp
Normal file
49
src/libslic3r/MeshBoolean.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef libslic3r_MeshBoolean_hpp_
|
||||
#define libslic3r_MeshBoolean_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace MeshBoolean {
|
||||
|
||||
using EigenMesh = std::pair<Eigen::MatrixXd, Eigen::MatrixXi>;
|
||||
|
||||
TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh);
|
||||
EigenMesh triangle_mesh_to_eigen(const TriangleMesh &mesh);
|
||||
|
||||
void minus(EigenMesh &A, const EigenMesh &B);
|
||||
void self_union(EigenMesh &A);
|
||||
|
||||
void minus(TriangleMesh& A, const TriangleMesh& B);
|
||||
void self_union(TriangleMesh& mesh);
|
||||
|
||||
namespace cgal {
|
||||
|
||||
struct CGALMesh;
|
||||
struct CGALMeshDeleter { void operator()(CGALMesh *ptr); };
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const TriangleMesh &M);
|
||||
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh);
|
||||
|
||||
// Do boolean mesh difference with CGAL bypassing igl.
|
||||
void minus(TriangleMesh &A, const TriangleMesh &B);
|
||||
void plus(TriangleMesh &A, const TriangleMesh &B);
|
||||
void intersect(TriangleMesh &A, const TriangleMesh &B);
|
||||
|
||||
void minus(CGALMesh &A, CGALMesh &B);
|
||||
void plus(CGALMesh &A, CGALMesh &B);
|
||||
void intersect(CGALMesh &A, CGALMesh &B);
|
||||
|
||||
bool does_self_intersect(const TriangleMesh &mesh);
|
||||
bool does_self_intersect(const CGALMesh &mesh);
|
||||
|
||||
}
|
||||
|
||||
} // namespace MeshBoolean
|
||||
} // namespace Slic3r
|
||||
#endif // libslic3r_MeshBoolean_hpp_
|
||||
|
|
@ -126,7 +126,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
if (add_default_instances)
|
||||
model.add_default_instances();
|
||||
|
||||
update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z.gcodes, config);
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
|
||||
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
|
@ -163,7 +164,8 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
|||
if (add_default_instances)
|
||||
model.add_default_instances();
|
||||
|
||||
update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z.gcodes, config);
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
|
||||
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
|
@ -618,6 +620,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
|||
assert(this->config.id() == rhs.config.id());
|
||||
this->sla_support_points = rhs.sla_support_points;
|
||||
this->sla_points_status = rhs.sla_points_status;
|
||||
this->sla_drain_holes = rhs.sla_drain_holes;
|
||||
this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment
|
||||
this->layer_height_profile = rhs.layer_height_profile;
|
||||
this->printable = rhs.printable;
|
||||
|
|
@ -658,6 +661,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
|
|||
assert(this->config.id() == rhs.config.id());
|
||||
this->sla_support_points = std::move(rhs.sla_support_points);
|
||||
this->sla_points_status = std::move(rhs.sla_points_status);
|
||||
this->sla_drain_holes = std::move(rhs.sla_drain_holes);
|
||||
this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
|
||||
this->layer_height_profile = std::move(rhs.layer_height_profile);
|
||||
this->origin_translation = std::move(rhs.origin_translation);
|
||||
|
|
@ -903,10 +907,8 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
|
|||
|
||||
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
{
|
||||
if (v->is_model_part())
|
||||
m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
|
||||
}
|
||||
}
|
||||
return m_raw_bounding_box;
|
||||
}
|
||||
|
|
@ -1111,17 +1113,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
if (keep_upper) {
|
||||
upper->set_model(nullptr);
|
||||
upper->sla_support_points.clear();
|
||||
upper->sla_drain_holes.clear();
|
||||
upper->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
upper->clear_volumes();
|
||||
upper->input_file = "";
|
||||
upper->input_file.clear();
|
||||
}
|
||||
|
||||
if (keep_lower) {
|
||||
lower->set_model(nullptr);
|
||||
lower->sla_support_points.clear();
|
||||
lower->sla_drain_holes.clear();
|
||||
lower->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
lower->clear_volumes();
|
||||
lower->input_file = "";
|
||||
lower->input_file.clear();
|
||||
}
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
|
|
@ -1154,7 +1158,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
if (keep_upper) { upper->add_volume(*volume); }
|
||||
if (keep_lower) { lower->add_volume(*volume); }
|
||||
}
|
||||
else {
|
||||
else if (! volume->mesh().empty()) {
|
||||
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
|
|
@ -1162,7 +1167,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(instance_matrix * volume_matrix, true);
|
||||
volume->reset_mesh();
|
||||
|
||||
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
// Perform cut
|
||||
TriangleMeshSlicer tms(&mesh);
|
||||
tms.cut(float(z), &upper_mesh, &lower_mesh);
|
||||
|
|
@ -1841,19 +1848,6 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Model &model, size_t num_extruders)
|
||||
{
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes;
|
||||
for (const Model::CustomGCode &custom_gcode : model.custom_gcode_per_print_z.gcodes)
|
||||
if (custom_gcode.gcode == ToolChangeCode) {
|
||||
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
|
||||
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(custom_gcode.extruder > num_extruders ? 1 : custom_gcode.extruder));
|
||||
}
|
||||
return custom_tool_changes;
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
|
@ -1942,28 +1936,6 @@ extern bool model_has_advanced_features(const Model &model)
|
|||
return false;
|
||||
}
|
||||
|
||||
extern void update_custom_gcode_per_print_z_from_config(std::vector<Model::CustomGCode>& custom_gcode_per_print_z, DynamicPrintConfig* config)
|
||||
{
|
||||
auto *colorprint_heights = config->option<ConfigOptionFloats>("colorprint_heights");
|
||||
if (colorprint_heights == nullptr)
|
||||
return;
|
||||
|
||||
if (custom_gcode_per_print_z.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;
|
||||
custom_gcode_per_print_z.clear();
|
||||
custom_gcode_per_print_z.reserve(colorprint_values.size());
|
||||
int i = 0;
|
||||
for (auto val : colorprint_values)
|
||||
custom_gcode_per_print_z.emplace_back(Model::CustomGCode{ val, ColorChangeCode, 1, colors[(++i)%7] });
|
||||
}
|
||||
|
||||
// The "colorprint_heights" config value has been deprecated. At this point of time it has been converted
|
||||
// to a new format and therefore it shall be erased.
|
||||
config->erase("colorprint_heights");
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
|
||||
void check_model_ids_validity(const Model &model)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@
|
|||
#include "Point.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Slicing.hpp"
|
||||
#include "SLA/SLACommon.hpp"
|
||||
#include "SLA/SupportPoint.hpp"
|
||||
#include "SLA/Hollowing.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "Arrange.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
|
@ -198,10 +200,13 @@ public:
|
|||
// This vector holds position of selected support points for SLA. The data are
|
||||
// saved in mesh coordinates to allow using them for several instances.
|
||||
// The format is (x, y, z, point_size, supports_island)
|
||||
std::vector<sla::SupportPoint> sla_support_points;
|
||||
sla::SupportPoints sla_support_points;
|
||||
// To keep track of where the points came from (used for synchronization between
|
||||
// the SLA gizmo and the backend).
|
||||
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
|
||||
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
|
||||
|
||||
// Holes to be drilled into the object so resin can flow out
|
||||
sla::DrainHoles sla_drain_holes;
|
||||
|
||||
/* This vector accumulates the total translation applied to the object by the
|
||||
center_around_origin() method. Callers might want to apply the same translation
|
||||
|
|
@ -372,7 +377,7 @@ private:
|
|||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
|
||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation,
|
||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
||||
}
|
||||
};
|
||||
|
|
@ -669,6 +674,7 @@ public:
|
|||
set_rotation(Z, rotation);
|
||||
set_offset(X, unscale<double>(offs(X)));
|
||||
set_offset(Y, unscale<double>(offs(Y)));
|
||||
this->object->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
|
@ -749,48 +755,7 @@ public:
|
|||
ModelWipeTower wipe_tower;
|
||||
|
||||
// Extensions for color print
|
||||
struct CustomGCode
|
||||
{
|
||||
bool operator<(const CustomGCode& rhs) const { return this->print_z < rhs.print_z; }
|
||||
bool operator==(const CustomGCode& rhs) const
|
||||
{
|
||||
return (rhs.print_z == this->print_z ) &&
|
||||
(rhs.gcode == this->gcode ) &&
|
||||
(rhs.extruder == this->extruder ) &&
|
||||
(rhs.color == this->color );
|
||||
}
|
||||
bool operator!=(const CustomGCode& rhs) const { return ! (*this == rhs); }
|
||||
|
||||
double print_z;
|
||||
std::string gcode;
|
||||
int extruder; // Informative value for ColorChangeCode and ToolChangeCode
|
||||
// "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder
|
||||
// "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder
|
||||
std::string color; // if gcode is equal to PausePrintCode,
|
||||
// this field is used for save a short message shown on Printer display
|
||||
};
|
||||
|
||||
struct CustomGCodeInfo
|
||||
{
|
||||
enum MODE
|
||||
{
|
||||
SingleExtruder, // single extruder printer preset is selected
|
||||
MultiAsSingle, // multiple extruder printer preset is selected, but
|
||||
// this mode works just for Single extruder print
|
||||
// (For all print from objects settings is used just one extruder)
|
||||
MultiExtruder // multiple extruder printer preset is selected
|
||||
} mode;
|
||||
|
||||
std::vector<CustomGCode> gcodes;
|
||||
|
||||
bool operator==(const CustomGCodeInfo& rhs) const
|
||||
{
|
||||
return (rhs.mode == this->mode ) &&
|
||||
(rhs.gcodes == this->gcodes );
|
||||
}
|
||||
bool operator!=(const CustomGCodeInfo& rhs) const { return !(*this == rhs); }
|
||||
}
|
||||
custom_gcode_per_print_z;
|
||||
CustomGCode::Info custom_gcode_per_print_z;
|
||||
|
||||
// Default constructor assigns a new ID to the model.
|
||||
Model() { assert(this->id().valid()); }
|
||||
|
|
@ -872,10 +837,6 @@ private:
|
|||
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
||||
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
extern std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Model &model, size_t num_extruders);
|
||||
|
||||
// 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.
|
||||
extern bool model_object_list_equal(const Model &model_old, const Model &model_new);
|
||||
|
|
@ -893,10 +854,6 @@ extern bool model_volume_list_changed(const ModelObject &model_object_old, const
|
|||
extern bool model_has_multi_part_objects(const Model &model);
|
||||
// If the model has advanced features, then it cannot be processed in simple mode.
|
||||
extern bool model_has_advanced_features(const Model &model);
|
||||
// If loaded configuration has a "colorprint_heights" option (if it was imported from older Slicer),
|
||||
// and if model.custom_gcode_per_print_z is empty (there is no color print data available in a new format
|
||||
// then model.custom_gcode_per_print_z should be updated considering this option.
|
||||
extern void update_custom_gcode_per_print_z_from_config(std::vector<Model::CustomGCode>& custom_gcode_per_print_z, DynamicPrintConfig* config);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
|
||||
|
|
|
|||
135
src/libslic3r/OpenVDBUtils.cpp
Normal file
135
src/libslic3r/OpenVDBUtils.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#define NOMINMAX
|
||||
#include "OpenVDBUtils.hpp"
|
||||
#include <openvdb/tools/MeshToVolume.h>
|
||||
#include <openvdb/tools/VolumeToMesh.h>
|
||||
#include <openvdb/tools/LevelSetRebuild.h>
|
||||
|
||||
//#include "MTUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMeshDataAdapter {
|
||||
public:
|
||||
const TriangleMesh &mesh;
|
||||
|
||||
size_t polygonCount() const { return mesh.its.indices.size(); }
|
||||
size_t pointCount() const { return mesh.its.vertices.size(); }
|
||||
size_t vertexCount(size_t) const { return 3; }
|
||||
|
||||
// Return position pos in local grid index space for polygon n and vertex v
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||
};
|
||||
|
||||
class Contour3DDataAdapter {
|
||||
public:
|
||||
const sla::Contour3D &mesh;
|
||||
|
||||
size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
|
||||
size_t pointCount() const { return mesh.points.size(); }
|
||||
size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
|
||||
|
||||
// Return position pos in local grid index space for polygon n and vertex v
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||
};
|
||||
|
||||
void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
|
||||
size_t v,
|
||||
openvdb::Vec3d &pos) const
|
||||
{
|
||||
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
|
||||
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>();
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
|
||||
size_t v,
|
||||
openvdb::Vec3d &pos) const
|
||||
{
|
||||
size_t vidx = 0;
|
||||
if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
|
||||
else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
|
||||
|
||||
Slic3r::Vec3d p = mesh.points[vidx];
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
|
||||
// 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::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
|
||||
const openvdb::math::Transform &tr,
|
||||
float exteriorBandWidth,
|
||||
float interiorBandWidth,
|
||||
int flags)
|
||||
{
|
||||
openvdb::initialize();
|
||||
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
|
||||
flags);
|
||||
}
|
||||
|
||||
template<class Grid>
|
||||
sla::Contour3D _volumeToMesh(const Grid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
openvdb::initialize();
|
||||
|
||||
std::vector<openvdb::Vec3s> points;
|
||||
std::vector<openvdb::Vec3I> triangles;
|
||||
std::vector<openvdb::Vec4I> quads;
|
||||
|
||||
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
|
||||
adaptivity, relaxDisorientedTriangles);
|
||||
|
||||
sla::Contour3D ret;
|
||||
ret.points.reserve(points.size());
|
||||
ret.faces3.reserve(triangles.size());
|
||||
ret.faces4.reserve(quads.size());
|
||||
|
||||
for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
|
||||
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
|
||||
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
return to_triangle_mesh(
|
||||
_volumeToMesh(grid, isovalue, adaptivity, relaxDisorientedTriangles));
|
||||
}
|
||||
|
||||
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
return _volumeToMesh(grid, isovalue, adaptivity,
|
||||
relaxDisorientedTriangles);
|
||||
}
|
||||
|
||||
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
|
||||
{
|
||||
return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
45
src/libslic3r/OpenVDBUtils.hpp
Normal file
45
src/libslic3r/OpenVDBUtils.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef OPENVDBUTILS_HPP
|
||||
#define OPENVDBUTILS_HPP
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <openvdb/openvdb.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; }
|
||||
inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>(); }
|
||||
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
|
||||
inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; }
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
|
||||
const openvdb::math::Transform &tr = {},
|
||||
float exteriorBandWidth = 3.0f,
|
||||
float interiorBandWidth = 3.0f,
|
||||
int flags = 0);
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh,
|
||||
const openvdb::math::Transform &tr = {},
|
||||
float exteriorBandWidth = 3.0f,
|
||||
float interiorBandWidth = 3.0f,
|
||||
int flags = 0);
|
||||
|
||||
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles = true);
|
||||
|
||||
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||
double isovalue = 0.0,
|
||||
double adaptivity = 0.0,
|
||||
bool relaxDisorientedTriangles = true);
|
||||
|
||||
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
|
||||
double iso,
|
||||
double ext_range = 3.,
|
||||
double int_range = 3.);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // OPENVDBUTILS_HPP
|
||||
|
|
@ -312,6 +312,10 @@ void PerimeterGenerator::process()
|
|||
for (ExPolygon &ex : expp)
|
||||
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
|
||||
}
|
||||
if (print_config->spiral_vase && offsets.size() > 1) {
|
||||
// Remove all but the largest area polygon.
|
||||
keep_largest_contour_only(offsets);
|
||||
}
|
||||
} else {
|
||||
//FIXME Is this offset correct if the line width of the inner perimeters differs
|
||||
// from the line width of the infill?
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "PlaceholderParser.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
|
@ -99,11 +100,7 @@ static inline bool opts_equal(const DynamicConfig &config_old, const DynamicConf
|
|||
const ConfigOption *opt_old = config_old.option(opt_key);
|
||||
const ConfigOption *opt_new = config_new.option(opt_key);
|
||||
assert(opt_new != nullptr);
|
||||
if (opt_old == nullptr)
|
||||
return false;
|
||||
return (opt_new->type() == coFloatOrPercent) ?
|
||||
dynamic_cast<const ConfigOptionFloat*>(opt_old)->value == config_new.get_abs_value(opt_key) :
|
||||
*opt_new == *opt_old;
|
||||
return opt_old != nullptr && *opt_new == *opt_old;
|
||||
}
|
||||
|
||||
std::vector<std::string> PlaceholderParser::config_diff(const DynamicPrintConfig &rhs)
|
||||
|
|
@ -126,14 +123,7 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs)
|
|||
bool modified = false;
|
||||
for (const t_config_option_key &opt_key : rhs.keys()) {
|
||||
if (! opts_equal(m_config, rhs, opt_key)) {
|
||||
// Store a copy of the config option.
|
||||
// Convert FloatOrPercent values to floats first.
|
||||
//FIXME there are some ratio_over chains, which end with empty ratio_with.
|
||||
// For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
|
||||
const ConfigOption *opt_rhs = rhs.option(opt_key);
|
||||
this->set(opt_key, (opt_rhs->type() == coFloatOrPercent) ?
|
||||
new ConfigOptionFloat(rhs.get_abs_value(opt_key)) :
|
||||
opt_rhs->clone());
|
||||
this->set(opt_key, rhs.option(opt_key)->clone());
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -142,16 +132,8 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs)
|
|||
|
||||
void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vector<std::string> &keys)
|
||||
{
|
||||
for (const t_config_option_key &opt_key : keys) {
|
||||
// Store a copy of the config option.
|
||||
// Convert FloatOrPercent values to floats first.
|
||||
//FIXME there are some ratio_over chains, which end with empty ratio_with.
|
||||
// For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
|
||||
const ConfigOption *opt_rhs = rhs.option(opt_key);
|
||||
this->set(opt_key, (opt_rhs->type() == coFloatOrPercent) ?
|
||||
new ConfigOptionFloat(rhs.get_abs_value(opt_key)) :
|
||||
opt_rhs->clone());
|
||||
}
|
||||
for (const t_config_option_key &opt_key : keys)
|
||||
this->set(opt_key, rhs.option(opt_key)->clone());
|
||||
}
|
||||
|
||||
void PlaceholderParser::apply_config(DynamicPrintConfig &&rhs)
|
||||
|
|
@ -635,7 +617,7 @@ namespace client
|
|||
return os;
|
||||
}
|
||||
|
||||
struct MyContext {
|
||||
struct MyContext : public ConfigOptionResolver {
|
||||
const DynamicConfig *external_config = nullptr;
|
||||
const DynamicConfig *config = nullptr;
|
||||
const DynamicConfig *config_override = nullptr;
|
||||
|
|
@ -650,7 +632,7 @@ namespace client
|
|||
|
||||
static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; }
|
||||
|
||||
const ConfigOption* resolve_symbol(const std::string &opt_key) const
|
||||
const ConfigOption* optptr(const t_config_option_key &opt_key) const override
|
||||
{
|
||||
const ConfigOption *opt = nullptr;
|
||||
if (config_override != nullptr)
|
||||
|
|
@ -662,6 +644,8 @@ namespace client
|
|||
return opt;
|
||||
}
|
||||
|
||||
const ConfigOption* resolve_symbol(const std::string &opt_key) const { return this->optptr(opt_key); }
|
||||
|
||||
template <typename Iterator>
|
||||
static void legacy_variable_expansion(
|
||||
const MyContext *ctx,
|
||||
|
|
@ -758,7 +742,43 @@ namespace client
|
|||
case coPoint: output.set_s(opt.opt->serialize()); break;
|
||||
case coBool: output.set_b(opt.opt->getBool()); break;
|
||||
case coFloatOrPercent:
|
||||
ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range);
|
||||
{
|
||||
std::string opt_key(opt.it_range.begin(), opt.it_range.end());
|
||||
if (boost::ends_with(opt_key, "extrusion_width")) {
|
||||
// Extrusion width supports defaults and a complex graph of dependencies.
|
||||
output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast<unsigned int>(ctx->current_extruder_id)));
|
||||
} else if (! static_cast<const ConfigOptionFloatOrPercent*>(opt.opt)->percent) {
|
||||
// Not a percent, just return the value.
|
||||
output.set_d(opt.opt->getFloat());
|
||||
} else {
|
||||
// Resolve dependencies using the "ratio_over" link to a parent value.
|
||||
const ConfigOptionDef *opt_def = print_config_def.get(opt_key);
|
||||
assert(opt_def != nullptr);
|
||||
double v = opt.opt->getFloat() * 0.01; // percent to ratio
|
||||
for (;;) {
|
||||
const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over);
|
||||
if (opt_parent == nullptr)
|
||||
ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range);
|
||||
if (boost::ends_with(opt_def->ratio_over, "extrusion_width")) {
|
||||
// Extrusion width supports defaults and a complex graph of dependencies.
|
||||
assert(opt_parent->type() == coFloatOrPercent);
|
||||
v *= Flow::extrusion_width(opt_def->ratio_over, static_cast<const ConfigOptionFloatOrPercent*>(opt_parent), *ctx, static_cast<unsigned int>(ctx->current_extruder_id));
|
||||
break;
|
||||
}
|
||||
if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) {
|
||||
v *= opt_parent->getFloat();
|
||||
if (opt_parent->type() == coFloat || ! static_cast<const ConfigOptionFloatOrPercent*>(opt_parent)->percent)
|
||||
break;
|
||||
v *= 0.01; // percent to ratio
|
||||
}
|
||||
// Continue one level up in the "ratio_over" hierarchy.
|
||||
opt_def = print_config_def.get(opt_def->ratio_over);
|
||||
assert(opt_def != nullptr);
|
||||
}
|
||||
output.set_d(v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ctx->throw_exception("Unknown scalar variable type", opt.it_range);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@
|
|||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
// Mark string for localization and translate.
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -174,7 +173,11 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
steps.emplace_back(psSkirt);
|
||||
} else if (
|
||||
opt_key == "nozzle_diameter"
|
||||
|| opt_key == "resolution") {
|
||||
|| opt_key == "resolution"
|
||||
// Spiral Vase forces different kind of slicing than the normal model:
|
||||
// In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer.
|
||||
// Therefore toggling the Spiral Vase on / off requires complete reslicing.
|
||||
|| opt_key == "spiral_vase") {
|
||||
osteps.emplace_back(posSlice);
|
||||
} else if (
|
||||
opt_key == "complete_objects"
|
||||
|
|
@ -196,7 +199,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
|| opt_key == "high_current_on_filament_swap"
|
||||
|| opt_key == "infill_first"
|
||||
|| opt_key == "single_extruder_multi_material"
|
||||
|| opt_key == "spiral_vase"
|
||||
|| opt_key == "temperature"
|
||||
|| opt_key == "wipe_tower"
|
||||
|| opt_key == "wipe_tower_width"
|
||||
|
|
@ -326,7 +328,7 @@ unsigned int Print::num_object_instances() const
|
|||
{
|
||||
unsigned int instances = 0;
|
||||
for (const PrintObject *print_object : m_objects)
|
||||
instances += (unsigned int)print_object->copies().size();
|
||||
instances += (unsigned int)print_object->instances().size();
|
||||
return instances;
|
||||
}
|
||||
|
||||
|
|
@ -447,33 +449,30 @@ static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &
|
|||
return true;
|
||||
}
|
||||
|
||||
struct PrintInstances
|
||||
struct PrintObjectTrafoAndInstances
|
||||
{
|
||||
Transform3d trafo;
|
||||
Points copies;
|
||||
bool operator<(const PrintInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); }
|
||||
Transform3d trafo;
|
||||
PrintInstances instances;
|
||||
bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); }
|
||||
};
|
||||
|
||||
// Generate a list of trafos and XY offsets for instances of a ModelObject
|
||||
static std::vector<PrintInstances> print_objects_from_model_object(const ModelObject &model_object)
|
||||
static std::vector<PrintObjectTrafoAndInstances> print_objects_from_model_object(const ModelObject &model_object)
|
||||
{
|
||||
std::set<PrintInstances> trafos;
|
||||
PrintInstances trafo;
|
||||
trafo.copies.assign(1, Point());
|
||||
std::set<PrintObjectTrafoAndInstances> trafos;
|
||||
PrintObjectTrafoAndInstances trafo;
|
||||
for (ModelInstance *model_instance : model_object.instances)
|
||||
if (model_instance->is_printable()) {
|
||||
trafo.trafo = model_instance->get_matrix();
|
||||
// Set the Z axis of the transformation.
|
||||
trafo.copies.front() = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
|
||||
auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]);
|
||||
// Reset the XY axes of the transformation.
|
||||
trafo.trafo.data()[12] = 0;
|
||||
trafo.trafo.data()[13] = 0;
|
||||
auto it = trafos.find(trafo);
|
||||
if (it == trafos.end())
|
||||
trafos.emplace(trafo);
|
||||
else
|
||||
const_cast<PrintInstances&>(*it).copies.emplace_back(trafo.copies.front());
|
||||
// Search or insert a trafo.
|
||||
auto it = trafos.emplace(trafo).first;
|
||||
const_cast<PrintObjectTrafoAndInstances&>(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift });
|
||||
}
|
||||
return std::vector<PrintInstances>(trafos.begin(), trafos.end());
|
||||
return std::vector<PrintObjectTrafoAndInstances>(trafos.begin(), trafos.end());
|
||||
}
|
||||
|
||||
// Compare just the layer ranges and their layer heights, not the associated configs.
|
||||
|
|
@ -494,11 +493,11 @@ static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_
|
|||
}
|
||||
|
||||
// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored.
|
||||
static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<Model::CustomGCode> &va, const std::vector<Model::CustomGCode> &vb)
|
||||
static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<CustomGCode::Item> &va, const std::vector<CustomGCode::Item> &vb)
|
||||
{
|
||||
auto it_a = va.begin();
|
||||
auto it_b = vb.begin();
|
||||
while (it_a != va.end() && it_b != vb.end()) {
|
||||
while (it_a != va.end() || it_b != vb.end()) {
|
||||
if (it_a != va.end() && it_a->gcode != ToolChangeCode) {
|
||||
// Skip any CustomGCode items, which are not tool changes.
|
||||
++ it_a;
|
||||
|
|
@ -530,7 +529,6 @@ void Print::config_diffs(
|
|||
const DynamicPrintConfig &new_full_config,
|
||||
t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff,
|
||||
t_config_option_keys &full_config_diff,
|
||||
DynamicPrintConfig &placeholder_parser_overrides,
|
||||
DynamicPrintConfig &filament_overrides) const
|
||||
{
|
||||
// Collect changes to print config, account for overrides of extruder retract values by filament presets.
|
||||
|
|
@ -566,19 +564,11 @@ void Print::config_diffs(
|
|||
object_diff = m_default_object_config.diff(new_full_config);
|
||||
region_diff = m_default_region_config.diff(new_full_config);
|
||||
// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser.
|
||||
// As the PlaceholderParser does not interpret the FloatOrPercent values itself, these values are stored into the PlaceholderParser converted to floats.
|
||||
for (const t_config_option_key &opt_key : new_full_config.keys()) {
|
||||
const ConfigOption *opt_old = m_full_print_config.option(opt_key);
|
||||
const ConfigOption *opt_new = new_full_config.option(opt_key);
|
||||
if (opt_old == nullptr || *opt_new != *opt_old)
|
||||
full_config_diff.emplace_back(opt_key);
|
||||
if (opt_new->type() == coFloatOrPercent) {
|
||||
// The m_placeholder_parser is never modified by the background processing, GCode.cpp/hpp makes a copy.
|
||||
const ConfigOption *opt_old_pp = this->placeholder_parser().config().option(opt_key);
|
||||
double new_value = new_full_config.get_abs_value(opt_key);
|
||||
if (opt_old_pp == nullptr || static_cast<const ConfigOptionFloat*>(opt_old_pp)->value != new_value)
|
||||
placeholder_parser_overrides.set_key_value(opt_key, new ConfigOptionFloat(new_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -596,8 +586,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
|
||||
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
|
||||
t_config_option_keys print_diff, object_diff, region_diff, full_config_diff;
|
||||
DynamicPrintConfig placeholder_parser_overrides, filament_overrides;
|
||||
this->config_diffs(new_full_config, print_diff, object_diff, region_diff, full_config_diff, placeholder_parser_overrides, filament_overrides);
|
||||
DynamicPrintConfig filament_overrides;
|
||||
this->config_diffs(new_full_config, print_diff, object_diff, region_diff, full_config_diff, filament_overrides);
|
||||
|
||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||
unsigned int apply_status = APPLY_STATUS_UNCHANGED;
|
||||
|
|
@ -617,9 +607,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
// which should be stopped if print_diff is not empty.
|
||||
size_t num_extruders = m_config.nozzle_diameter.size();
|
||||
bool num_extruders_changed = false;
|
||||
if (! full_config_diff.empty() || ! placeholder_parser_overrides.empty()) {
|
||||
if (! full_config_diff.empty()) {
|
||||
update_apply_status(this->invalidate_step(psGCodeExport));
|
||||
m_placeholder_parser.apply_config(std::move(placeholder_parser_overrides));
|
||||
// Set the profile aliases for the PrintBase::output_filename()
|
||||
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
|
||||
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
|
||||
|
|
@ -723,7 +712,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
model_object_status.emplace(model_object->id(), ModelObjectStatus::New);
|
||||
} else {
|
||||
if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
|
||||
update_apply_status(custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes) ?
|
||||
update_apply_status(num_extruders_changed ||
|
||||
// Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering.
|
||||
//FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable
|
||||
// to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same.
|
||||
(num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ?
|
||||
// The Tool Ordering and the Wipe Tower are no more valid.
|
||||
this->invalidate_steps({ psWipeTower, psGCodeExport }) :
|
||||
// There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.
|
||||
|
|
@ -839,7 +832,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
// Update the ModelObject instance, possibly invalidate the linked PrintObjects.
|
||||
assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved);
|
||||
// Check whether a model part volume was added or removed, their transformations or order changed.
|
||||
// Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked.
|
||||
// Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
|
||||
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
|
||||
bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER);
|
||||
bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER);
|
||||
|
|
@ -891,12 +884,31 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
// Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step.
|
||||
model_object.name = model_object_new.name;
|
||||
model_object.input_file = model_object_new.input_file;
|
||||
model_object.clear_instances();
|
||||
model_object.instances.reserve(model_object_new.instances.size());
|
||||
for (const ModelInstance *model_instance : model_object_new.instances) {
|
||||
model_object.instances.emplace_back(new ModelInstance(*model_instance));
|
||||
model_object.instances.back()->set_model_object(&model_object);
|
||||
}
|
||||
// Only refresh ModelInstances if there is any change.
|
||||
if (model_object.instances.size() != model_object_new.instances.size() ||
|
||||
! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) {
|
||||
// G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list.
|
||||
update_apply_status(this->invalidate_step(psGCodeExport));
|
||||
model_object.clear_instances();
|
||||
model_object.instances.reserve(model_object_new.instances.size());
|
||||
for (const ModelInstance *model_instance : model_object_new.instances) {
|
||||
model_object.instances.emplace_back(new ModelInstance(*model_instance));
|
||||
model_object.instances.back()->set_model_object(&model_object);
|
||||
}
|
||||
} else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(),
|
||||
[](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable &&
|
||||
l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) {
|
||||
// If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid.
|
||||
// This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only.
|
||||
model_object.invalidate_bounding_box();
|
||||
// Synchronize the content of instances.
|
||||
auto new_instance = model_object_new.instances.begin();
|
||||
for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) {
|
||||
(*old_instance)->set_transformation((*new_instance)->get_transformation());
|
||||
(*old_instance)->print_volume_state = (*new_instance)->print_volume_state;
|
||||
(*old_instance)->printable = (*new_instance)->printable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -917,13 +929,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
}
|
||||
// Generate a list of trafos and XY offsets for instances of a ModelObject
|
||||
PrintObjectConfig config = PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders);
|
||||
std::vector<PrintInstances> new_print_instances = print_objects_from_model_object(*model_object);
|
||||
std::vector<PrintObjectTrafoAndInstances> new_print_instances = print_objects_from_model_object(*model_object);
|
||||
if (old.empty()) {
|
||||
// Simple case, just generate new instances.
|
||||
for (const PrintInstances &print_instances : new_print_instances) {
|
||||
PrintObject *print_object = new PrintObject(this, model_object, false);
|
||||
print_object->set_trafo(print_instances.trafo);
|
||||
print_object->set_copies(print_instances.copies);
|
||||
for (PrintObjectTrafoAndInstances &print_instances : new_print_instances) {
|
||||
PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances));
|
||||
print_object->config_apply(config);
|
||||
print_objects_new.emplace_back(print_object);
|
||||
// print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
|
||||
|
|
@ -936,13 +946,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); });
|
||||
// Merge the old / new lists.
|
||||
auto it_old = old.begin();
|
||||
for (const PrintInstances &new_instances : new_print_instances) {
|
||||
for (PrintObjectTrafoAndInstances &new_instances : new_print_instances) {
|
||||
for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old);
|
||||
if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) {
|
||||
// This is a new instance (or a set of instances with the same trafo). Just add it.
|
||||
PrintObject *print_object = new PrintObject(this, model_object, false);
|
||||
print_object->set_trafo(new_instances.trafo);
|
||||
print_object->set_copies(new_instances.copies);
|
||||
PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances));
|
||||
print_object->config_apply(config);
|
||||
print_objects_new.emplace_back(print_object);
|
||||
// print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New));
|
||||
|
|
@ -951,7 +959,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||
const_cast<PrintObjectStatus*>(*it_old)->status = PrintObjectStatus::Deleted;
|
||||
} else {
|
||||
// The PrintObject already exists and the copies differ.
|
||||
PrintBase::ApplyStatus status = (*it_old)->print_object->set_copies(new_instances.copies);
|
||||
PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances));
|
||||
if (status != PrintBase::APPLY_STATUS_UNCHANGED)
|
||||
update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED);
|
||||
print_objects_new.emplace_back((*it_old)->print_object);
|
||||
|
|
@ -1144,6 +1152,62 @@ bool Print::has_skirt() const
|
|||
|| this->has_infinite_skirt();
|
||||
}
|
||||
|
||||
static inline bool sequential_print_horizontal_clearance_valid(const Print &print)
|
||||
{
|
||||
Polygons convex_hulls_other;
|
||||
std::map<ObjectID, Polygon> map_model_object_to_convex_hull;
|
||||
for (const PrintObject *print_object : print.objects()) {
|
||||
assert(! print_object->model_object()->instances.empty());
|
||||
assert(! print_object->instances().empty());
|
||||
ObjectID model_object_id = print_object->model_object()->id();
|
||||
auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id);
|
||||
// Get convex hull of all printable volumes assigned to this print object.
|
||||
ModelInstance *model_instance0 = print_object->model_object()->instances.front();
|
||||
if (it_convex_hull == map_model_object_to_convex_hull.end()) {
|
||||
// Calculate the convex hull of a printable object.
|
||||
// Grow convex hull with the clearance margin.
|
||||
// FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2)
|
||||
// which causes that the warning will be showed after arrangement with the
|
||||
// appropriate object distance. Even if I set this to jtMiter the warning still shows up.
|
||||
it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id,
|
||||
offset(print_object->model_object()->convex_hull_2d(
|
||||
Geometry::assemble_transform(Vec3d::Zero(), model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())),
|
||||
// Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects
|
||||
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
|
||||
float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)),
|
||||
jtRound, float(scale_(0.1))).front());
|
||||
}
|
||||
// Make a copy, so it may be rotated for instances.
|
||||
Polygon convex_hull0 = it_convex_hull->second;
|
||||
double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation());
|
||||
if (std::abs(z_diff) > EPSILON)
|
||||
convex_hull0.rotate(z_diff);
|
||||
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
|
||||
for (const PrintInstance &instance : print_object->instances()) {
|
||||
Polygon convex_hull = convex_hull0;
|
||||
// instance.shift is a position of a centered object, while model object may not be centered.
|
||||
// Conver the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset.
|
||||
convex_hull.translate(instance.shift - print_object->center_offset());
|
||||
if (! intersection(convex_hulls_other, convex_hull).empty())
|
||||
return false;
|
||||
polygons_append(convex_hulls_other, convex_hull);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool sequential_print_vertical_clearance_valid(const Print &print)
|
||||
{
|
||||
std::vector<const PrintInstance*> print_instances_ordered = sort_object_instances_by_model_order(print);
|
||||
// Ignore the last instance printed.
|
||||
print_instances_ordered.pop_back();
|
||||
// Find the other highest instance.
|
||||
auto it = std::max_element(print_instances_ordered.begin(), print_instances_ordered.end(), [](auto l, auto r) {
|
||||
return l->print_object->height() < r->print_object->height();
|
||||
});
|
||||
return it == print_instances_ordered.end() || (*it)->print_object->height() <= scale_(print.config().extruder_clearance_height.value);
|
||||
}
|
||||
|
||||
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
|
||||
std::string Print::validate() const
|
||||
{
|
||||
|
|
@ -1154,53 +1218,16 @@ std::string Print::validate() const
|
|||
return L("The supplied settings will cause an empty print.");
|
||||
|
||||
if (m_config.complete_objects) {
|
||||
// Check horizontal clearance.
|
||||
{
|
||||
Polygons convex_hulls_other;
|
||||
for (const PrintObject *print_object : m_objects) {
|
||||
assert(! print_object->model_object()->instances.empty());
|
||||
assert(! print_object->copies().empty());
|
||||
// Get convex hull of all meshes assigned to this print object.
|
||||
ModelInstance *model_instance0 = print_object->model_object()->instances.front();
|
||||
Vec3d rotation = model_instance0->get_rotation();
|
||||
rotation.z() = 0.;
|
||||
// Calculate the convex hull of a printable object centered around X=0,Y=0.
|
||||
// Grow convex hull with the clearance margin.
|
||||
// FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2)
|
||||
// which causes that the warning will be showed after arrangement with the
|
||||
// appropriate object distance. Even if I set this to jtMiter the warning still shows up.
|
||||
Polygon convex_hull0 = offset(
|
||||
print_object->model_object()->convex_hull_2d(
|
||||
Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())),
|
||||
float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front();
|
||||
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
|
||||
for (const Point © : print_object->copies()) {
|
||||
Polygon convex_hull = convex_hull0;
|
||||
convex_hull.translate(copy);
|
||||
if (! intersection(convex_hulls_other, convex_hull).empty())
|
||||
return L("Some objects are too close; your extruder will collide with them.");
|
||||
polygons_append(convex_hulls_other, convex_hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check vertical clearance.
|
||||
{
|
||||
std::vector<coord_t> object_height;
|
||||
for (const PrintObject *object : m_objects)
|
||||
object_height.insert(object_height.end(), object->copies().size(), object->size(2));
|
||||
std::sort(object_height.begin(), object_height.end());
|
||||
// Ignore the tallest *copy* (this is why we repeat height for all of them):
|
||||
// it will be printed as last one so its height doesn't matter.
|
||||
object_height.pop_back();
|
||||
if (! object_height.empty() && object_height.back() > scale_(m_config.extruder_clearance_height.value))
|
||||
return L("Some objects are too tall and cannot be printed without extruder collisions.");
|
||||
}
|
||||
} // end if (m_config.complete_objects)
|
||||
if (! sequential_print_horizontal_clearance_valid(*this))
|
||||
return L("Some objects are too close; your extruder will collide with them.");
|
||||
if (! sequential_print_vertical_clearance_valid(*this))
|
||||
return L("Some objects are too tall and cannot be printed without extruder collisions.");
|
||||
}
|
||||
|
||||
if (m_config.spiral_vase) {
|
||||
size_t total_copies_count = 0;
|
||||
for (const PrintObject *object : m_objects)
|
||||
total_copies_count += object->copies().size();
|
||||
total_copies_count += object->instances().size();
|
||||
// #4043
|
||||
if (total_copies_count > 1 && ! m_config.complete_objects.value)
|
||||
return L("The Spiral Vase option can only be used when printing a single object.");
|
||||
|
|
@ -1411,16 +1438,17 @@ std::string Print::validate() const
|
|||
return std::string();
|
||||
}
|
||||
|
||||
#if 0
|
||||
// the bounding box of objects placed in copies position
|
||||
// (without taking skirt/brim/support material into account)
|
||||
BoundingBox Print::bounding_box() const
|
||||
{
|
||||
BoundingBox bb;
|
||||
for (const PrintObject *object : m_objects)
|
||||
for (Point copy : object->m_copies) {
|
||||
bb.merge(copy);
|
||||
copy += to_2d(object->size);
|
||||
bb.merge(copy);
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
BoundingBox bb2(object->bounding_box());
|
||||
bb.merge(bb2.min + instance.shift);
|
||||
bb.merge(bb2.max + instance.shift);
|
||||
}
|
||||
return bb;
|
||||
}
|
||||
|
|
@ -1465,6 +1493,7 @@ BoundingBox Print::total_bounding_box() const
|
|||
|
||||
return bb;
|
||||
}
|
||||
#endif
|
||||
|
||||
double Print::skirt_first_layer_height() const
|
||||
{
|
||||
|
|
@ -1562,6 +1591,8 @@ void Print::process()
|
|||
} else if (! this->config().complete_objects.value) {
|
||||
// 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.");
|
||||
}
|
||||
this->set_done(psWipeTower);
|
||||
}
|
||||
|
|
@ -1657,10 +1688,10 @@ void Print::_make_skirt()
|
|||
append(object_points, extrusion_entity->as_polyline().points);
|
||||
}
|
||||
// Repeat points for each object copy.
|
||||
for (const Point &shift : object->m_copies) {
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
Points copy_points = object_points;
|
||||
for (Point &pt : copy_points)
|
||||
pt += shift;
|
||||
pt += instance.shift;
|
||||
append(points, copy_points);
|
||||
}
|
||||
}
|
||||
|
|
@ -1778,11 +1809,11 @@ void Print::_make_brim()
|
|||
object_islands.push_back(expoly.contour);
|
||||
if (! object->support_layers().empty())
|
||||
object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
|
||||
islands.reserve(islands.size() + object_islands.size() * object->m_copies.size());
|
||||
for (const Point &pt : object->m_copies)
|
||||
islands.reserve(islands.size() + object_islands.size() * object->instances().size());
|
||||
for (const PrintInstance &instance : object->instances())
|
||||
for (Polygon &poly : object_islands) {
|
||||
islands.push_back(poly);
|
||||
islands.back().translate(pt);
|
||||
islands.back().translate(instance.shift);
|
||||
}
|
||||
}
|
||||
Polygons loops;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class PrintObject;
|
|||
class ModelObject;
|
||||
class GCode;
|
||||
class GCodePreviewData;
|
||||
enum class SlicingMode : uint32_t;
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum PrintStep {
|
||||
|
|
@ -84,7 +85,7 @@ private:
|
|||
|
||||
PrintRegion(Print* print) : m_refcnt(0), m_print(print) {}
|
||||
PrintRegion(Print* print, const PrintRegionConfig &config) : m_refcnt(0), m_print(print), m_config(config) {}
|
||||
~PrintRegion() {}
|
||||
~PrintRegion() = default;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -92,6 +93,21 @@ typedef std::vector<Layer*> LayerPtrs;
|
|||
typedef std::vector<SupportLayer*> SupportLayerPtrs;
|
||||
class BoundingBoxf3; // TODO: for temporary constructor parameter
|
||||
|
||||
// Single instance of a PrintObject.
|
||||
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
|
||||
// ModelObject's instancess will be distributed among these multiple PrintObjects.
|
||||
struct PrintInstance
|
||||
{
|
||||
// Parent PrintObject
|
||||
PrintObject *print_object;
|
||||
// Source ModelInstance of a ModelObject, for which this print_object was created.
|
||||
const ModelInstance *model_instance;
|
||||
// Shift of this instance's center into the world coordinates.
|
||||
Point shift;
|
||||
};
|
||||
|
||||
typedef std::vector<PrintInstance> PrintInstances;
|
||||
|
||||
class PrintObject : public PrintObjectBaseWithState<Print, PrintObjectStep, posCount>
|
||||
{
|
||||
private: // Prevents erroneous use by other classes.
|
||||
|
|
@ -101,21 +117,22 @@ public:
|
|||
// vector of (layer height ranges and vectors of volume ids), indexed by region_id
|
||||
std::vector<std::vector<std::pair<t_layer_height_range, int>>> region_volumes;
|
||||
|
||||
// this is set to true when LayerRegion->slices is split in top/internal/bottom
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool typed_slices;
|
||||
|
||||
Vec3crd size; // XYZ in scaled coordinates
|
||||
|
||||
// Size of an object: XYZ in scaled coordinates. The size might not be quite snug in XY plane.
|
||||
const Vec3crd& size() const { return m_size; }
|
||||
const PrintObjectConfig& config() const { return m_config; }
|
||||
const LayerPtrs& layers() const { return m_layers; }
|
||||
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
|
||||
const Transform3d& trafo() const { return m_trafo; }
|
||||
const Points& copies() const { return m_copies; }
|
||||
const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); }
|
||||
const PrintInstances& instances() const { return m_instances; }
|
||||
|
||||
// since the object is aligned to origin, bounding box coincides with size
|
||||
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
|
||||
// Bounding box is used to align the object infill patterns, and to calculate attractor for the rear seam.
|
||||
// The bounding box may not be quite snug.
|
||||
BoundingBox bounding_box() const { return BoundingBox(Point(- m_size.x() / 2, - m_size.y() / 2), Point(m_size.x() / 2, m_size.y() / 2)); }
|
||||
// Height is used for slicing, for sorting the objects by height for sequential printing and for checking vertical clearence in sequential print mode.
|
||||
// The height is snug.
|
||||
coord_t height() const { return m_size.z(); }
|
||||
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
|
||||
const Point& center_offset() const { return m_center_offset; }
|
||||
|
||||
// adds region_id, too, if necessary
|
||||
void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) {
|
||||
|
|
@ -126,9 +143,9 @@ public:
|
|||
// This is the *total* layer count (including support layers)
|
||||
// this value is not supposed to be compared with Layer::id
|
||||
// since they have different semantics.
|
||||
size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
|
||||
size_t layer_count() const { return m_layers.size(); }
|
||||
void clear_layers();
|
||||
size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
|
||||
size_t layer_count() const { return m_layers.size(); }
|
||||
void clear_layers();
|
||||
const Layer* get_layer(int idx) const { return m_layers[idx]; }
|
||||
Layer* get_layer(int idx) { return m_layers[idx]; }
|
||||
// Get a layer exactly at print_z.
|
||||
|
|
@ -177,17 +194,16 @@ 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); }
|
||||
|
||||
protected:
|
||||
private:
|
||||
// to be called from Print only.
|
||||
friend class Print;
|
||||
|
||||
PrintObject(Print* print, ModelObject* model_object, bool add_instances = true);
|
||||
~PrintObject() {}
|
||||
PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances);
|
||||
~PrintObject() = default;
|
||||
|
||||
void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); }
|
||||
void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); }
|
||||
void set_trafo(const Transform3d& trafo) { m_trafo = trafo; }
|
||||
PrintBase::ApplyStatus set_copies(const Points &points);
|
||||
PrintBase::ApplyStatus set_instances(PrintInstances &&instances);
|
||||
// Invalidates the step, and its depending steps in PrintObject and Print.
|
||||
bool invalidate_step(PrintObjectStep step);
|
||||
// Invalidates all PrintObject and Print steps.
|
||||
|
|
@ -219,25 +235,30 @@ private:
|
|||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
|
||||
// XYZ in scaled coordinates
|
||||
Vec3crd m_size;
|
||||
PrintObjectConfig m_config;
|
||||
// Translation in Z + Rotation + Scaling / Mirroring.
|
||||
Transform3d m_trafo = Transform3d::Identity();
|
||||
// Slic3r::Point objects in scaled G-code coordinates
|
||||
Points m_copies;
|
||||
// scaled coordinates to add to copies (to compensate for the alignment
|
||||
// operated when creating the object but still preserving a coherent API
|
||||
// for external callers)
|
||||
Point m_copies_shift;
|
||||
std::vector<PrintInstance> m_instances;
|
||||
// The mesh is being centered before thrown to Clipper, so that the Clipper's fixed coordinates require less bits.
|
||||
// This is the adjustment of the the Object's coordinate system towards PrintObject's coordinate system.
|
||||
Point m_center_offset;
|
||||
|
||||
SlicingParameters m_slicing_params;
|
||||
LayerPtrs m_layers;
|
||||
SupportLayerPtrs m_support_layers;
|
||||
|
||||
std::vector<ExPolygons> slice_region(size_t region_id, const std::vector<float> &z) const;
|
||||
// this is set to true when LayerRegion->slices is split in top/internal/bottom
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool m_typed_slices = false;
|
||||
|
||||
std::vector<ExPolygons> slice_region(size_t region_id, const std::vector<float> &z, SlicingMode mode) const;
|
||||
std::vector<ExPolygons> slice_modifiers(size_t region_id, const std::vector<float> &z) const;
|
||||
std::vector<ExPolygons> slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
|
||||
std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const ModelVolume &volume) const;
|
||||
std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, const ModelVolume &volume) const;
|
||||
std::vector<ExPolygons> slice_volumes(const std::vector<float> &z, SlicingMode mode, const std::vector<const ModelVolume*> &volumes) const;
|
||||
std::vector<ExPolygons> slice_volume(const std::vector<float> &z, SlicingMode mode, const ModelVolume &volume) const;
|
||||
std::vector<ExPolygons> slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, SlicingMode mode, const ModelVolume &volume) const;
|
||||
};
|
||||
|
||||
struct WipeTowerData
|
||||
|
|
@ -325,7 +346,7 @@ private: // Prevents erroneous use by other classes.
|
|||
typedef PrintBaseWithState<PrintStep, psCount> Inherited;
|
||||
|
||||
public:
|
||||
Print() {}
|
||||
Print() = default;
|
||||
virtual ~Print() { this->clear(); }
|
||||
|
||||
PrinterTechnology technology() const noexcept { return ptFFF; }
|
||||
|
|
@ -361,8 +382,6 @@ public:
|
|||
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
std::string validate() const override;
|
||||
BoundingBox bounding_box() const;
|
||||
BoundingBox total_bounding_box() const;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
|
|
@ -417,7 +436,6 @@ private:
|
|||
const DynamicPrintConfig &new_full_config,
|
||||
t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff,
|
||||
t_config_option_keys &full_config_diff,
|
||||
DynamicPrintConfig &placeholder_parser_overrides,
|
||||
DynamicPrintConfig &filament_overrides) const;
|
||||
|
||||
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
|
|||
boost::filesystem::path filename = format.empty() ?
|
||||
cfg.opt_string("input_filename_base") + default_ext :
|
||||
this->placeholder_parser().process(format, 0, &cfg);
|
||||
if (filename.extension().empty())
|
||||
filename = boost::filesystem::change_extension(filename, default_ext);
|
||||
if (filename.extension().empty())
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -168,6 +168,17 @@ void PrintConfigDef::init_fff_params()
|
|||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionInt(3));
|
||||
|
||||
def = this->add("bottom_solid_min_thickness", coFloat);
|
||||
//TRN To be shown in Print Settings "Top solid layers"
|
||||
def->label = L("Bottom");
|
||||
def->category = L("Layers and Perimeters");
|
||||
def->tooltip = L("The number of bottom solid layers is increased above bottom_solid_layers if necessary to satisfy "
|
||||
"minimum thickness of bottom shell.");
|
||||
def->full_label = L("Minimum bottom shell thickness");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
|
||||
def = this->add("bridge_acceleration", coFloat);
|
||||
def->label = L("Bridge");
|
||||
def->tooltip = L("This is the acceleration your printer will use for bridges. "
|
||||
|
|
@ -562,7 +573,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("If layer print time is estimated below this number of seconds, fan will be enabled "
|
||||
"and its speed will be calculated by interpolating the minimum and maximum speeds.");
|
||||
def->sidetext = L("approximate seconds");
|
||||
def->width = 6;
|
||||
def->min = 0;
|
||||
def->max = 1000;
|
||||
def->mode = comExpert;
|
||||
|
|
@ -718,8 +728,9 @@ void PrintConfigDef::init_fff_params()
|
|||
def->gui_type = "f_enum_open";
|
||||
def->gui_flags = "show_value";
|
||||
def->enum_values.push_back("PLA");
|
||||
def->enum_values.push_back("ABS");
|
||||
def->enum_values.push_back("PET");
|
||||
def->enum_values.push_back("ABS");
|
||||
def->enum_values.push_back("ASA");
|
||||
def->enum_values.push_back("FLEX");
|
||||
def->enum_values.push_back("HIPS");
|
||||
def->enum_values.push_back("EDGE");
|
||||
|
|
@ -1092,7 +1103,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
const int machine_limits_opt_width = 7;
|
||||
{
|
||||
struct AxisDefault {
|
||||
std::string name;
|
||||
|
|
@ -1124,7 +1134,6 @@ void PrintConfigDef::init_fff_params()
|
|||
(void)L("Maximum feedrate of the E axis");
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats(axis.max_feedrate));
|
||||
// Add the machine acceleration limits for XYZE axes (M201)
|
||||
|
|
@ -1142,7 +1151,6 @@ void PrintConfigDef::init_fff_params()
|
|||
(void)L("Maximum acceleration of the E axis");
|
||||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats(axis.max_acceleration));
|
||||
// Add the machine jerk limits for XYZE axes (M205)
|
||||
|
|
@ -1160,7 +1168,6 @@ void PrintConfigDef::init_fff_params()
|
|||
(void)L("Maximum jerk of the E axis");
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats(axis.max_jerk));
|
||||
}
|
||||
|
|
@ -1173,7 +1180,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Minimum feedrate when extruding (M205 S)");
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats{ 0., 0. });
|
||||
|
||||
|
|
@ -1184,7 +1190,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Minimum travel feedrate (M205 T)");
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats{ 0., 0. });
|
||||
|
||||
|
|
@ -1195,7 +1200,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Maximum acceleration when extruding (M204 S)");
|
||||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats{ 1500., 1250. });
|
||||
|
||||
|
|
@ -1206,7 +1210,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Maximum acceleration when retracting (M204 T)");
|
||||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->width = machine_limits_opt_width;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats{ 1500., 1250. });
|
||||
|
||||
|
|
@ -1703,7 +1706,6 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("If layer print time is estimated below this number of seconds, print moves "
|
||||
"speed will be scaled down to extend duration to this value.");
|
||||
def->sidetext = L("approximate seconds");
|
||||
def->width = 6;
|
||||
def->min = 0;
|
||||
def->max = 1000;
|
||||
def->mode = comExpert;
|
||||
|
|
@ -1781,6 +1783,13 @@ void PrintConfigDef::init_fff_params()
|
|||
def->shortcut.push_back("bottom_solid_layers");
|
||||
def->min = 0;
|
||||
|
||||
def = this->add("solid_min_thickness", coFloat);
|
||||
def->label = L("Minimum thickness of a top / bottom shell");
|
||||
def->tooltip = L("Minimum thickness of a top / bottom shell");
|
||||
def->shortcut.push_back("top_solid_min_thickness");
|
||||
def->shortcut.push_back("bottom_solid_min_thickness");
|
||||
def->min = 0;
|
||||
|
||||
def = this->add("spiral_vase", coBool);
|
||||
def->label = L("Spiral vase");
|
||||
def->tooltip = L("This feature will raise Z gradually while printing a single-walled object "
|
||||
|
|
@ -2127,6 +2136,18 @@ void PrintConfigDef::init_fff_params()
|
|||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionInt(3));
|
||||
|
||||
def = this->add("top_solid_min_thickness", coFloat);
|
||||
//TRN To be shown in Print Settings "Top solid layers"
|
||||
def->label = L("Top");
|
||||
def->category = L("Layers and Perimeters");
|
||||
def->tooltip = L("The number of top solid layers is increased above top_solid_layers if necessary to satisfy "
|
||||
"minimum thickness of top shell."
|
||||
" This is useful to prevent pillowing effect when printing with variable layer height.");
|
||||
def->full_label = L("Minimum top shell thickness");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
|
||||
def = this->add("travel_speed", coFloat);
|
||||
def->label = L("Travel");
|
||||
def->tooltip = L("Speed for travel moves (jumps between distant extrusion points).");
|
||||
|
|
@ -2873,6 +2894,47 @@ void PrintConfigDef::init_sla_params()
|
|||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.3));
|
||||
|
||||
def = this->add("hollowing_enable", coBool);
|
||||
def->label = L("Enable hollowing");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("Hollow out a model to have an empty interior");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("hollowing_min_thickness", coFloat);
|
||||
def->label = L("Wall thickness");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("Minimum wall thickness of a hollowed model.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 1;
|
||||
def->max = 10;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(3.));
|
||||
|
||||
def = this->add("hollowing_quality", coFloat);
|
||||
def->label = L("Accuracy");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("Performance vs accuracy of calculation. Lower values may produce unwanted artifacts.");
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.5));
|
||||
|
||||
def = this->add("hollowing_closing_distance", coFloat);
|
||||
def->label = L("Closing distance");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L(
|
||||
"Hollowing is done in two steps: first, an imaginary interior is "
|
||||
"calculated deeper (offset plus the closing distance) in the object and "
|
||||
"then it's inflated back to the specified offset. A greater closing "
|
||||
"distance makes the interior more rounded. At zero, the interior will "
|
||||
"resemble the exterior the most.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 10;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(2.0));
|
||||
}
|
||||
|
||||
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
|
||||
|
|
|
|||
|
|
@ -46,12 +46,6 @@ enum SeamPosition {
|
|||
spRandom, spNearest, spAligned, spRear
|
||||
};
|
||||
|
||||
/*
|
||||
enum FilamentType {
|
||||
ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA
|
||||
};
|
||||
*/
|
||||
|
||||
enum SLAMaterial {
|
||||
slamTough,
|
||||
slamFlex,
|
||||
|
|
@ -149,24 +143,6 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<SeamPosition>::ge
|
|||
return keys_map;
|
||||
}
|
||||
|
||||
/*
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<FilamentType>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["PLA"] = ftPLA;
|
||||
keys_map["ABS"] = ftABS;
|
||||
keys_map["PET"] = ftPET;
|
||||
keys_map["HIPS"] = ftHIPS;
|
||||
keys_map["FLEX"] = ftFLEX;
|
||||
keys_map["SCAFF"] = ftSCAFF;
|
||||
keys_map["EDGE"] = ftEDGE;
|
||||
keys_map["NGEN"] = ftNGEN;
|
||||
keys_map["PVA"] = ftPVA;
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
*/
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<SLADisplayOrientation>::get_enum_values() {
|
||||
static const t_config_enum_values keys_map = {
|
||||
{ "landscape", sladoLandscape},
|
||||
|
|
@ -354,6 +330,9 @@ protected:
|
|||
#define STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \
|
||||
public: \
|
||||
/* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \
|
||||
const ConfigOption* optptr(const t_config_option_key &opt_key) const override \
|
||||
{ return s_cache_##CLASS_NAME.optptr(opt_key, this); } \
|
||||
/* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \
|
||||
ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override \
|
||||
{ return s_cache_##CLASS_NAME.optptr(opt_key, this); } \
|
||||
/* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \
|
||||
|
|
@ -487,6 +466,7 @@ class PrintRegionConfig : public StaticPrintConfig
|
|||
public:
|
||||
ConfigOptionFloat bridge_angle;
|
||||
ConfigOptionInt bottom_solid_layers;
|
||||
ConfigOptionFloat bottom_solid_min_thickness;
|
||||
ConfigOptionFloat bridge_flow_ratio;
|
||||
ConfigOptionFloat bridge_speed;
|
||||
ConfigOptionBool ensure_vertical_shell_thickness;
|
||||
|
|
@ -522,6 +502,7 @@ public:
|
|||
ConfigOptionBool thin_walls;
|
||||
ConfigOptionFloatOrPercent top_infill_extrusion_width;
|
||||
ConfigOptionInt top_solid_layers;
|
||||
ConfigOptionFloat top_solid_min_thickness;
|
||||
ConfigOptionFloatOrPercent top_solid_infill_speed;
|
||||
ConfigOptionBool wipe_into_infill;
|
||||
|
||||
|
|
@ -530,6 +511,7 @@ protected:
|
|||
{
|
||||
OPT_PTR(bridge_angle);
|
||||
OPT_PTR(bottom_solid_layers);
|
||||
OPT_PTR(bottom_solid_min_thickness);
|
||||
OPT_PTR(bridge_flow_ratio);
|
||||
OPT_PTR(bridge_speed);
|
||||
OPT_PTR(ensure_vertical_shell_thickness);
|
||||
|
|
@ -563,6 +545,7 @@ protected:
|
|||
OPT_PTR(top_infill_extrusion_width);
|
||||
OPT_PTR(top_solid_infill_speed);
|
||||
OPT_PTR(top_solid_layers);
|
||||
OPT_PTR(top_solid_min_thickness);
|
||||
OPT_PTR(wipe_into_infill);
|
||||
}
|
||||
};
|
||||
|
|
@ -1017,7 +1000,7 @@ public:
|
|||
ConfigOptionFloat support_base_height /*= 1.0*/;
|
||||
|
||||
// The minimum distance of the pillar base from the model in mm.
|
||||
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/;
|
||||
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/
|
||||
|
||||
// The default angle for connecting support sticks and junctions.
|
||||
ConfigOptionFloat support_critical_angle /*= 45*/;
|
||||
|
|
@ -1062,7 +1045,7 @@ public:
|
|||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Zero elevation mode parameters:
|
||||
// - The object pad will be derived from the the model geometry.
|
||||
// - The object pad will be derived from the model geometry.
|
||||
// - There will be a gap between the object pad and the generated pad
|
||||
// according to the support_base_safety_distance parameter.
|
||||
// - The two pads will be connected with tiny connector sticks
|
||||
|
|
@ -1084,6 +1067,28 @@ public:
|
|||
|
||||
// How much should the tiny connectors penetrate into the model body
|
||||
ConfigOptionFloat pad_object_connector_penetration;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Model hollowing parameters:
|
||||
// - Models can be hollowed out as part of the SLA print process
|
||||
// - Thickness of the hollowed model walls can be adjusted
|
||||
// -
|
||||
// - Additional holes will be drilled into the hollow model to allow for
|
||||
// - resin removal.
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ConfigOptionBool hollowing_enable;
|
||||
|
||||
// The minimum thickness of the model walls to maintain. Note that the
|
||||
// resulting walls may be thicker due to smoothing out fine cavities where
|
||||
// resin could stuck.
|
||||
ConfigOptionFloat hollowing_min_thickness;
|
||||
|
||||
// Indirectly controls the voxel size (resolution) used by openvdb
|
||||
ConfigOptionFloat hollowing_quality;
|
||||
|
||||
// Indirectly controls the minimum size of created cavities.
|
||||
ConfigOptionFloat hollowing_closing_distance;
|
||||
|
||||
protected:
|
||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
|
|
@ -1121,6 +1126,10 @@ protected:
|
|||
OPT_PTR(pad_object_connector_stride);
|
||||
OPT_PTR(pad_object_connector_width);
|
||||
OPT_PTR(pad_object_connector_penetration);
|
||||
OPT_PTR(hollowing_enable);
|
||||
OPT_PTR(hollowing_min_thickness);
|
||||
OPT_PTR(hollowing_quality);
|
||||
OPT_PTR(hollowing_closing_distance);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,52 +40,54 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) :
|
||||
// Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid.
|
||||
PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances) :
|
||||
PrintObjectBaseWithState(print, model_object),
|
||||
typed_slices(false),
|
||||
size(Vec3crd::Zero())
|
||||
m_trafo(trafo)
|
||||
{
|
||||
// Compute the translation to be applied to our meshes so that we work with smaller coordinates
|
||||
{
|
||||
// Translate meshes so that our toolpath generation algorithms work with smaller
|
||||
// XY coordinates; this translation is an optimization and not strictly required.
|
||||
// A cloned mesh will be aligned to 0 before slicing in slice_region() since we
|
||||
// don't assume it's already aligned and we don't alter the original position in model.
|
||||
// We store the XY translation so that we can place copies correctly in the output G-code
|
||||
// (copies are expressed in G-code coordinates and this translation is not publicly exposed).
|
||||
const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box();
|
||||
m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1));
|
||||
// Scale the object size and store it
|
||||
this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>();
|
||||
}
|
||||
|
||||
if (add_instances) {
|
||||
Points copies;
|
||||
copies.reserve(m_model_object->instances.size());
|
||||
for (const ModelInstance *mi : m_model_object->instances) {
|
||||
assert(mi->is_printable());
|
||||
const Vec3d& offset = mi->get_offset();
|
||||
copies.emplace_back(Point::new_scale(offset(0), offset(1)));
|
||||
}
|
||||
this->set_copies(copies);
|
||||
}
|
||||
// Compute centering offet to be applied to our meshes so that we work with smaller coordinates
|
||||
// requiring less bits to represent Clipper coordinates.
|
||||
|
||||
// Snug bounding box of a rotated and scaled object by the 1st instantion, without the instance translation applied.
|
||||
// All the instances share the transformation matrix with the exception of translation in XY and rotation by Z,
|
||||
// therefore a bounding box from 1st instance of a ModelObject is good enough for calculating the object center,
|
||||
// snug height and an approximate bounding box in XY.
|
||||
BoundingBoxf3 bbox = model_object->raw_bounding_box();
|
||||
Vec3d bbox_center = bbox.center();
|
||||
// We may need to rotate the bbox / bbox_center from the original instance to the current instance.
|
||||
double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_rotation(), instances.front().model_instance->get_rotation());
|
||||
if (std::abs(z_diff) > EPSILON) {
|
||||
auto z_rot = Eigen::AngleAxisd(z_diff, Vec3d::UnitZ());
|
||||
bbox = bbox.transformed(Transform3d(z_rot));
|
||||
bbox_center = (z_rot * bbox_center).eval();
|
||||
}
|
||||
|
||||
// Center of the transformed mesh (without translation).
|
||||
m_center_offset = Point::new_scale(bbox_center.x(), bbox_center.y());
|
||||
// Size of the transformed mesh. This bounding may not be snug in XY plane, but it is snug in Z.
|
||||
m_size = (bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>();
|
||||
|
||||
this->set_instances(std::move(instances));
|
||||
}
|
||||
|
||||
PrintBase::ApplyStatus PrintObject::set_copies(const Points &points)
|
||||
PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
|
||||
{
|
||||
// Order copies with a nearest-neighbor search.
|
||||
std::vector<Point> copies;
|
||||
copies.reserve(points.size());
|
||||
for (const Point &pt : points)
|
||||
copies.emplace_back(pt + m_copies_shift);
|
||||
for (PrintInstance &i : instances)
|
||||
// Add the center offset, which will be subtracted from the mesh when slicing.
|
||||
i.shift += m_center_offset;
|
||||
// Invalidate and set copies.
|
||||
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
|
||||
if (copies != m_copies) {
|
||||
bool equal_length = instances.size() == m_instances.size();
|
||||
bool equal = equal_length && std::equal(instances.begin(), instances.end(), m_instances.begin(),
|
||||
[](const PrintInstance& lhs, const PrintInstance& rhs) { return lhs.model_instance == rhs.model_instance && lhs.shift == rhs.shift; });
|
||||
if (! equal) {
|
||||
status = PrintBase::APPLY_STATUS_CHANGED;
|
||||
if (m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }) ||
|
||||
(copies.size() != m_copies.size() && m_print->invalidate_step(psWipeTower)))
|
||||
(! equal_length && m_print->invalidate_step(psWipeTower)))
|
||||
status = PrintBase::APPLY_STATUS_INVALIDATED;
|
||||
m_copies = copies;
|
||||
m_instances = std::move(instances);
|
||||
for (PrintInstance &i : m_instances)
|
||||
i.print_object = this;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
|
@ -151,12 +153,12 @@ void PrintObject::make_perimeters()
|
|||
BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info();
|
||||
|
||||
// merge slices if they were split into types
|
||||
if (this->typed_slices) {
|
||||
if (m_typed_slices) {
|
||||
for (Layer *layer : m_layers) {
|
||||
layer->merge_slices();
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
this->typed_slices = false;
|
||||
m_typed_slices = false;
|
||||
}
|
||||
|
||||
// compare each layer to the one below, and mark those slices needing
|
||||
|
|
@ -463,8 +465,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|
|||
|| opt_key == "raft_layers"
|
||||
|| opt_key == "slice_closing_radius") {
|
||||
steps.emplace_back(posSlice);
|
||||
}
|
||||
else if (
|
||||
} else if (
|
||||
opt_key == "clip_multipart_objects"
|
||||
|| opt_key == "elefant_foot_compensation"
|
||||
|| opt_key == "support_material_contact_distance"
|
||||
|
|
@ -505,7 +506,9 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|
|||
|| opt_key == "infill_every_layers"
|
||||
|| opt_key == "solid_infill_every_layers"
|
||||
|| opt_key == "bottom_solid_layers"
|
||||
|| opt_key == "bottom_solid_min_thickness"
|
||||
|| opt_key == "top_solid_layers"
|
||||
|| opt_key == "top_solid_min_thickness"
|
||||
|| opt_key == "solid_infill_below_area"
|
||||
|| opt_key == "infill_extruder"
|
||||
|| opt_key == "solid_infill_extruder"
|
||||
|
|
@ -620,6 +623,14 @@ bool PrintObject::has_support_material() const
|
|||
|| m_config.support_material_enforce_layers > 0;
|
||||
}
|
||||
|
||||
static const PrintRegion* first_printing_region(const PrintObject &print_object)
|
||||
{
|
||||
for (size_t idx_region = 0; idx_region < print_object.region_volumes.size(); ++ idx_region)
|
||||
if (!print_object.region_volumes.empty())
|
||||
return print_object.print()->regions()[idx_region];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This function analyzes slices of a region (SurfaceCollection slices).
|
||||
// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
|
||||
// Initially all slices are of type stInternal.
|
||||
|
|
@ -638,7 +649,9 @@ void PrintObject::detect_surfaces_type()
|
|||
// are completely hidden inside a collective body of intersecting parts.
|
||||
// This is useful if one of the parts is to be dissolved, or if it is transparent and the internal shells
|
||||
// should be visible.
|
||||
bool interface_shells = m_config.interface_shells.value;
|
||||
bool spiral_vase = this->print()->config().spiral_vase.value;
|
||||
bool interface_shells = ! spiral_vase && m_config.interface_shells.value;
|
||||
size_t num_layers = spiral_vase ? first_printing_region(*this)->config().bottom_solid_layers : m_layers.size();
|
||||
|
||||
for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start";
|
||||
|
|
@ -651,10 +664,15 @@ void PrintObject::detect_surfaces_type()
|
|||
// Cache the result of the following parallel_loop.
|
||||
std::vector<Surfaces> surfaces_new;
|
||||
if (interface_shells)
|
||||
surfaces_new.assign(m_layers.size(), Surfaces());
|
||||
surfaces_new.assign(num_layers, Surfaces());
|
||||
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
tbb::blocked_range<size_t>(0,
|
||||
spiral_vase ?
|
||||
// In spiral vase mode, reserve the last layer for the top surface if more than 1 layer is planned for the vase bottom.
|
||||
((num_layers > 1) ? num_layers - 1 : num_layers) :
|
||||
// In non-spiral vase mode, go over all layers.
|
||||
m_layers.size()),
|
||||
[this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
|
||||
// If we have raft layers, consider bottom layer as a bridge just like any other bottom surface lying on the void.
|
||||
SurfaceType surface_type_bottom_1st =
|
||||
|
|
@ -669,7 +687,7 @@ void PrintObject::detect_surfaces_type()
|
|||
m_print->throw_if_canceled();
|
||||
// BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z;
|
||||
Layer *layer = m_layers[idx_layer];
|
||||
LayerRegion *layerm = layer->get_region(idx_region);
|
||||
LayerRegion *layerm = layer->m_regions[idx_region];
|
||||
// comparison happens against the *full* slices (considering all regions)
|
||||
// unless internal shells are requested
|
||||
Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? m_layers[idx_layer + 1] : nullptr;
|
||||
|
|
@ -684,7 +702,7 @@ void PrintObject::detect_surfaces_type()
|
|||
Surfaces top;
|
||||
if (upper_layer) {
|
||||
Polygons upper_slices = interface_shells ?
|
||||
to_polygons(upper_layer->get_region(idx_region)->slices.surfaces) :
|
||||
to_polygons(upper_layer->m_regions[idx_region]->slices.surfaces) :
|
||||
to_polygons(upper_layer->lslices);
|
||||
surfaces_append(top,
|
||||
//FIXME implement offset2_ex working over ExPolygons, that should be a bit more efficient than calling offset_ex twice.
|
||||
|
|
@ -727,7 +745,7 @@ void PrintObject::detect_surfaces_type()
|
|||
offset2_ex(
|
||||
diff(
|
||||
intersection(layerm_slices_surfaces, to_polygons(lower_layer->lslices)), // supported
|
||||
to_polygons(lower_layer->get_region(idx_region)->slices.surfaces),
|
||||
to_polygons(lower_layer->m_regions[idx_region]->slices.surfaces),
|
||||
true),
|
||||
-offset, offset),
|
||||
stBottom);
|
||||
|
|
@ -795,18 +813,25 @@ void PrintObject::detect_surfaces_type()
|
|||
|
||||
if (interface_shells) {
|
||||
// Move surfaces_new to layerm->slices.surfaces
|
||||
for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++ idx_layer)
|
||||
m_layers[idx_layer]->get_region(idx_region)->slices.surfaces = std::move(surfaces_new[idx_layer]);
|
||||
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer)
|
||||
m_layers[idx_layer]->m_regions[idx_region]->slices.surfaces = std::move(surfaces_new[idx_layer]);
|
||||
}
|
||||
|
||||
if (spiral_vase && num_layers > 1) {
|
||||
// Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern.
|
||||
Surfaces &surfaces = m_layers[num_layers - 1]->m_regions[idx_region]->slices.surfaces;
|
||||
for (Surface &surface : surfaces)
|
||||
surface.surface_type = stTop;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - start";
|
||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
|
||||
[this, idx_region, interface_shells](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
LayerRegion *layerm = m_layers[idx_layer]->get_region(idx_region);
|
||||
LayerRegion *layerm = m_layers[idx_layer]->m_regions[idx_region];
|
||||
layerm->slices_to_fill_surfaces_clipped();
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final");
|
||||
|
|
@ -818,7 +843,7 @@ void PrintObject::detect_surfaces_type()
|
|||
} // for each this->print->region_count
|
||||
|
||||
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
|
||||
this->typed_slices = true;
|
||||
m_typed_slices = true;
|
||||
}
|
||||
|
||||
void PrintObject::process_external_surfaces()
|
||||
|
|
@ -912,18 +937,33 @@ void PrintObject::discover_vertical_shells()
|
|||
Polygons bottom_surfaces;
|
||||
Polygons holes;
|
||||
};
|
||||
std::vector<DiscoverVerticalShellsCacheEntry> cache_top_botom_regions(m_layers.size(), DiscoverVerticalShellsCacheEntry());
|
||||
bool spiral_vase = this->print()->config().spiral_vase.value;
|
||||
size_t num_layers = spiral_vase ? first_printing_region(*this)->config().bottom_solid_layers : m_layers.size();
|
||||
coordf_t min_layer_height = this->slicing_parameters().min_layer_height;
|
||||
// Does this region possibly produce more than 1 top or bottom layer?
|
||||
auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) {
|
||||
auto num_extra_layers = [min_layer_height](int num_solid_layers, coordf_t min_shell_thickness) {
|
||||
if (num_solid_layers == 0)
|
||||
return 0;
|
||||
int n = num_solid_layers - 1;
|
||||
int n2 = int(ceil(min_shell_thickness / min_layer_height));
|
||||
return std::max(n, n2 - 1);
|
||||
};
|
||||
return num_extra_layers(config.top_solid_layers, config.top_solid_min_thickness) +
|
||||
num_extra_layers(config.bottom_solid_layers, config.bottom_solid_min_thickness) > 0;
|
||||
};
|
||||
std::vector<DiscoverVerticalShellsCacheEntry> cache_top_botom_regions(num_layers, DiscoverVerticalShellsCacheEntry());
|
||||
bool top_bottom_surfaces_all_regions = this->region_volumes.size() > 1 && ! m_config.interface_shells.value;
|
||||
if (top_bottom_surfaces_all_regions) {
|
||||
// This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness
|
||||
// is calculated over all materials.
|
||||
// Is the "ensure vertical wall thickness" applicable to any region?
|
||||
bool has_extra_layers = false;
|
||||
for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) {
|
||||
const PrintRegion ®ion = *m_print->get_region(idx_region);
|
||||
if (region.config().ensure_vertical_shell_thickness.value &&
|
||||
(region.config().top_solid_layers.value > 1 || region.config().bottom_solid_layers.value > 1)) {
|
||||
for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++idx_region) {
|
||||
const PrintRegionConfig &config = m_print->get_region(idx_region)->config();
|
||||
if (config.ensure_vertical_shell_thickness.value && has_extra_layers_fn(config)) {
|
||||
has_extra_layers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! has_extra_layers)
|
||||
|
|
@ -931,9 +971,9 @@ void PrintObject::discover_vertical_shells()
|
|||
return;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
|
||||
//FIXME Improve the heuristics for a grain size.
|
||||
size_t grain_size = std::max(m_layers.size() / 16, size_t(1));
|
||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
|
||||
const size_t num_regions = this->region_volumes.size();
|
||||
|
|
@ -982,8 +1022,8 @@ void PrintObject::discover_vertical_shells()
|
|||
polygons_append(cache.holes, offset(offset_ex(layer.lslices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing));
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices));
|
||||
svg.draw(layer.slices, "blue");
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices));
|
||||
svg.draw(layer.lslices, "blue");
|
||||
svg.draw(union_ex(cache.holes), "red");
|
||||
svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
|
|
@ -1004,21 +1044,19 @@ void PrintObject::discover_vertical_shells()
|
|||
if (! region.config().ensure_vertical_shell_thickness.value)
|
||||
// This region will be handled by discover_horizontal_shells().
|
||||
continue;
|
||||
int n_extra_top_layers = std::max(0, region.config().top_solid_layers.value - 1);
|
||||
int n_extra_bottom_layers = std::max(0, region.config().bottom_solid_layers.value - 1);
|
||||
if (n_extra_top_layers + n_extra_bottom_layers == 0)
|
||||
if (! has_extra_layers_fn(region.config()))
|
||||
// Zero or 1 layer, there is no additional vertical wall thickness enforced.
|
||||
continue;
|
||||
|
||||
//FIXME Improve the heuristics for a grain size.
|
||||
size_t grain_size = std::max(m_layers.size() / 16, size_t(1));
|
||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
||||
|
||||
if (! top_bottom_surfaces_all_regions) {
|
||||
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
|
||||
// is calculated over a single material.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
|
|
@ -1046,8 +1084,8 @@ void PrintObject::discover_vertical_shells()
|
|||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
|
||||
[this, idx_region, n_extra_top_layers, n_extra_bottom_layers, &cache_top_botom_regions]
|
||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||
[this, idx_region, &cache_top_botom_regions]
|
||||
(const tbb::blocked_range<size_t>& range) {
|
||||
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
|
|
@ -1058,8 +1096,9 @@ void PrintObject::discover_vertical_shells()
|
|||
++ debug_idx;
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
Layer *layer = m_layers[idx_layer];
|
||||
LayerRegion *layerm = layer->m_regions[idx_region];
|
||||
Layer *layer = m_layers[idx_layer];
|
||||
LayerRegion *layerm = layer->m_regions[idx_region];
|
||||
const PrintRegionConfig ®ion_config = layerm->region()->config();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial");
|
||||
|
|
@ -1099,30 +1138,47 @@ void PrintObject::discover_vertical_shells()
|
|||
}
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
// Reset the top / bottom inflated regions caches of entries, which are out of the moving window.
|
||||
bool hole_first = true;
|
||||
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n)
|
||||
if (n >= 0 && n < (int)m_layers.size()) {
|
||||
const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n];
|
||||
if (hole_first) {
|
||||
hole_first = false;
|
||||
polygons_append(holes, cache.holes);
|
||||
}
|
||||
else if (! holes.empty()) {
|
||||
holes = intersection(holes, cache.holes);
|
||||
}
|
||||
size_t n_shell_old = shell.size();
|
||||
if (n > int(idx_layer))
|
||||
// Collect top surfaces.
|
||||
polygons_append(shell, cache.top_surfaces);
|
||||
else if (n < int(idx_layer))
|
||||
// Collect bottom and bottom bridge surfaces.
|
||||
polygons_append(shell, cache.bottom_surfaces);
|
||||
// Running the union_ using the Clipper library piece by piece is cheaper
|
||||
// than running the union_ all at once.
|
||||
if (n_shell_old < shell.size())
|
||||
shell = union_(shell, false);
|
||||
}
|
||||
polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
|
||||
{
|
||||
// Gather top regions projected to this layer.
|
||||
coordf_t print_z = layer->print_z;
|
||||
int n_top_layers = region_config.top_solid_layers.value;
|
||||
for (int i = int(idx_layer) + 1;
|
||||
i < int(cache_top_botom_regions.size()) &&
|
||||
(i < int(idx_layer) + n_top_layers ||
|
||||
m_layers[i]->print_z - print_z < region_config.top_solid_min_thickness - EPSILON);
|
||||
++ i) {
|
||||
const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i];
|
||||
if (! holes.empty())
|
||||
holes = intersection(holes, cache.holes);
|
||||
if (! cache.top_surfaces.empty()) {
|
||||
polygons_append(shell, cache.top_surfaces);
|
||||
// Running the union_ using the Clipper library piece by piece is cheaper
|
||||
// than running the union_ all at once.
|
||||
shell = union_(shell, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Gather bottom regions projected to this layer.
|
||||
coordf_t bottom_z = layer->bottom_z();
|
||||
int n_bottom_layers = region_config.bottom_solid_layers.value;
|
||||
for (int i = int(idx_layer) - 1;
|
||||
i >= 0 &&
|
||||
(i > int(idx_layer) - n_bottom_layers ||
|
||||
bottom_z - m_layers[i]->bottom_z() < region_config.bottom_solid_min_thickness - EPSILON);
|
||||
-- i) {
|
||||
const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i];
|
||||
if (! holes.empty())
|
||||
holes = intersection(holes, cache.holes);
|
||||
if (! cache.bottom_surfaces.empty()) {
|
||||
polygons_append(shell, cache.bottom_surfaces);
|
||||
// Running the union_ using the Clipper library piece by piece is cheaper
|
||||
// than running the union_ all at once.
|
||||
shell = union_(shell, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
|
||||
|
|
@ -1448,7 +1504,7 @@ void PrintObject::update_slicing_parameters()
|
|||
{
|
||||
if (! m_slicing_params.valid)
|
||||
m_slicing_params = SlicingParameters::create_from_config(
|
||||
this->print()->config(), m_config, unscale<double>(this->size(2)), this->object_extruders());
|
||||
this->print()->config(), m_config, unscale<double>(this->height()), this->object_extruders());
|
||||
}
|
||||
|
||||
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z)
|
||||
|
|
@ -1535,7 +1591,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
|||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing objects..." << log_memory_info();
|
||||
|
||||
this->typed_slices = false;
|
||||
m_typed_slices = false;
|
||||
|
||||
#ifdef SLIC3R_PROFILE
|
||||
// Disable parallelization so the Shiny profiler works
|
||||
|
|
@ -1602,13 +1658,14 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
|||
// Slice all non-modifier volumes.
|
||||
bool clipped = false;
|
||||
bool upscaled = false;
|
||||
auto slicing_mode = this->print()->config().spiral_vase ? SlicingMode::PositiveLargestContour : SlicingMode::Regular;
|
||||
if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) {
|
||||
// Cheap path: Slice regions without mutual clipping.
|
||||
// The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region.
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id;
|
||||
// slicing in parallel
|
||||
std::vector<ExPolygons> expolygons_by_layer = this->slice_region(region_id, slice_zs);
|
||||
std::vector<ExPolygons> expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode);
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start";
|
||||
for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id)
|
||||
|
|
@ -1646,7 +1703,7 @@ void PrintObject::_slice(const std::vector<coordf_t> &layer_height_profile)
|
|||
else
|
||||
ranges.emplace_back(volumes_and_ranges[j].first);
|
||||
// slicing in parallel
|
||||
sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, *model_volume));
|
||||
sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, slicing_mode, *model_volume));
|
||||
i = j;
|
||||
} else
|
||||
++ i;
|
||||
|
|
@ -1855,7 +1912,7 @@ end:
|
|||
}
|
||||
|
||||
// To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region.
|
||||
std::vector<ExPolygons> PrintObject::slice_region(size_t region_id, const std::vector<float> &z) const
|
||||
std::vector<ExPolygons> PrintObject::slice_region(size_t region_id, const std::vector<float> &z, SlicingMode mode) const
|
||||
{
|
||||
std::vector<const ModelVolume*> volumes;
|
||||
if (region_id < this->region_volumes.size()) {
|
||||
|
|
@ -1865,7 +1922,7 @@ std::vector<ExPolygons> PrintObject::slice_region(size_t region_id, const std::v
|
|||
volumes.emplace_back(volume);
|
||||
}
|
||||
}
|
||||
return this->slice_volumes(z, volumes);
|
||||
return this->slice_volumes(z, mode, volumes);
|
||||
}
|
||||
|
||||
// Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once.
|
||||
|
|
@ -1915,7 +1972,7 @@ std::vector<ExPolygons> PrintObject::slice_modifiers(size_t region_id, const std
|
|||
if (volume->is_modifier())
|
||||
volumes.emplace_back(volume);
|
||||
}
|
||||
out = this->slice_volumes(slice_zs, volumes);
|
||||
out = this->slice_volumes(slice_zs, SlicingMode::Regular, volumes);
|
||||
} else {
|
||||
// Some modifier in this region was split to layer spans.
|
||||
std::vector<char> merge;
|
||||
|
|
@ -1933,7 +1990,7 @@ std::vector<ExPolygons> PrintObject::slice_modifiers(size_t region_id, const std
|
|||
for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j)
|
||||
ranges.emplace_back(volumes_and_ranges[j].first);
|
||||
// slicing in parallel
|
||||
std::vector<ExPolygons> this_slices = this->slice_volume(slice_zs, ranges, *model_volume);
|
||||
std::vector<ExPolygons> this_slices = this->slice_volume(slice_zs, ranges, SlicingMode::Regular, *model_volume);
|
||||
if (out.empty()) {
|
||||
out = std::move(this_slices);
|
||||
merge.assign(out.size(), false);
|
||||
|
|
@ -1972,10 +2029,10 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
|
|||
zs.reserve(this->layers().size());
|
||||
for (const Layer *l : this->layers())
|
||||
zs.emplace_back((float)l->slice_z);
|
||||
return this->slice_volumes(zs, volumes);
|
||||
return this->slice_volumes(zs, SlicingMode::Regular, volumes);
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
|
||||
std::vector<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z, SlicingMode mode, const std::vector<const ModelVolume*> &volumes) const
|
||||
{
|
||||
std::vector<ExPolygons> layers;
|
||||
if (! volumes.empty()) {
|
||||
|
|
@ -1997,7 +2054,7 @@ std::vector<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z,
|
|||
if (mesh.stl.stats.number_of_facets > 0) {
|
||||
mesh.transform(m_trafo, true);
|
||||
// apply XY shift
|
||||
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
|
||||
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0);
|
||||
// perform actual slicing
|
||||
const Print *print = this->print();
|
||||
auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
|
||||
|
|
@ -2005,14 +2062,14 @@ std::vector<ExPolygons> PrintObject::slice_volumes(const std::vector<float> &z,
|
|||
mesh.require_shared_vertices();
|
||||
TriangleMeshSlicer mslicer;
|
||||
mslicer.init(&mesh, callback);
|
||||
mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
|
||||
mslicer.slice(z, mode, float(m_config.slice_closing_radius.value), &layers, callback);
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const ModelVolume &volume) const
|
||||
std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, SlicingMode mode, const ModelVolume &volume) const
|
||||
{
|
||||
std::vector<ExPolygons> layers;
|
||||
if (! z.empty()) {
|
||||
|
|
@ -2027,7 +2084,7 @@ std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, c
|
|||
if (mesh.stl.stats.number_of_facets > 0) {
|
||||
mesh.transform(m_trafo, true);
|
||||
// apply XY shift
|
||||
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
|
||||
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0);
|
||||
// perform actual slicing
|
||||
TriangleMeshSlicer mslicer;
|
||||
const Print *print = this->print();
|
||||
|
|
@ -2035,7 +2092,7 @@ std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, c
|
|||
// TriangleMeshSlicer needs the shared vertices.
|
||||
mesh.require_shared_vertices();
|
||||
mslicer.init(&mesh, callback);
|
||||
mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback);
|
||||
mslicer.slice(z, mode, float(m_config.slice_closing_radius.value), &layers, callback);
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
}
|
||||
|
|
@ -2043,13 +2100,13 @@ std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, c
|
|||
}
|
||||
|
||||
// Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping.
|
||||
std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, const ModelVolume &volume) const
|
||||
std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, const std::vector<t_layer_height_range> &ranges, SlicingMode mode, const ModelVolume &volume) const
|
||||
{
|
||||
std::vector<ExPolygons> out;
|
||||
if (! z.empty() && ! ranges.empty()) {
|
||||
if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) {
|
||||
// All layers fit into a single range.
|
||||
out = this->slice_volume(z, volume);
|
||||
out = this->slice_volume(z, mode, volume);
|
||||
} else {
|
||||
std::vector<float> z_filtered;
|
||||
std::vector<std::pair<size_t, size_t>> n_filtered;
|
||||
|
|
@ -2065,7 +2122,7 @@ std::vector<ExPolygons> PrintObject::slice_volume(const std::vector<float> &z, c
|
|||
n_filtered.emplace_back(std::make_pair(first, i));
|
||||
}
|
||||
if (! n_filtered.empty()) {
|
||||
std::vector<ExPolygons> layers = this->slice_volume(z_filtered, volume);
|
||||
std::vector<ExPolygons> layers = this->slice_volume(z_filtered, mode, volume);
|
||||
out.assign(z.size(), ExPolygons());
|
||||
i = 0;
|
||||
for (const std::pair<size_t, size_t> &span : n_filtered)
|
||||
|
|
@ -2278,7 +2335,8 @@ void PrintObject::discover_horizontal_shells()
|
|||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (size_t i = 0; i < m_layers.size(); ++ i) {
|
||||
m_print->throw_if_canceled();
|
||||
LayerRegion *layerm = m_layers[i]->regions()[region_id];
|
||||
Layer *layer = m_layers[i];
|
||||
LayerRegion *layerm = layer->regions()[region_id];
|
||||
const PrintRegionConfig ®ion_config = layerm->region()->config();
|
||||
if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 &&
|
||||
(i % region_config.solid_infill_every_layers) == 0) {
|
||||
|
|
@ -2293,6 +2351,8 @@ void PrintObject::discover_horizontal_shells()
|
|||
if (region_config.ensure_vertical_shell_thickness.value)
|
||||
continue;
|
||||
|
||||
coordf_t print_z = layer->print_z;
|
||||
coordf_t bottom_z = layer->bottom_z();
|
||||
for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) {
|
||||
m_print->throw_if_canceled();
|
||||
SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge;
|
||||
|
|
@ -2321,10 +2381,15 @@ void PrintObject::discover_horizontal_shells()
|
|||
continue;
|
||||
// Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom';
|
||||
|
||||
size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value;
|
||||
for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) {
|
||||
if (n < 0 || n >= int(m_layers.size()))
|
||||
continue;
|
||||
// Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking.
|
||||
for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1;
|
||||
(type == stTop) ?
|
||||
(n >= 0 && (int(i) - n < region_config.top_solid_layers.value ||
|
||||
print_z - m_layers[n]->print_z < region_config.top_solid_min_thickness.value - EPSILON)) :
|
||||
(n < int(m_layers.size()) && (n - int(i) < region_config.bottom_solid_layers.value ||
|
||||
m_layers[n]->bottom_z() - bottom_z < region_config.bottom_solid_min_thickness.value - EPSILON));
|
||||
(type == stTop) ? -- n : ++ n)
|
||||
{
|
||||
// Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
|
||||
// Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface.
|
||||
LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id];
|
||||
|
|
@ -2412,7 +2477,8 @@ void PrintObject::discover_horizontal_shells()
|
|||
// is grown, and that little space is an internal solid shell so
|
||||
// it triggers this too_narrow logic.)
|
||||
internal));
|
||||
solid = new_internal_solid;
|
||||
// see https://github.com/prusa3d/PrusaSlicer/pull/3426
|
||||
// solid = new_internal_solid;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef SLABOOSTADAPTER_HPP
|
||||
#define SLABOOSTADAPTER_HPP
|
||||
#ifndef SLA_BOOSTADAPTER_HPP
|
||||
#define SLA_BOOSTADAPTER_HPP
|
||||
|
||||
#include "SLA/SLABoilerPlate.hpp"
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <boost/geometry.hpp>
|
||||
|
||||
namespace boost {
|
||||
30
src/libslic3r/SLA/Clustering.hpp
Normal file
30
src/libslic3r/SLA/Clustering.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef SLA_CLUSTERING_HPP
|
||||
#define SLA_CLUSTERING_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
using ClusterEl = std::vector<unsigned>;
|
||||
using ClusteredPoints = std::vector<ClusterEl>;
|
||||
|
||||
// Clustering a set of points by the given distance.
|
||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
}}
|
||||
#endif // CLUSTERING_HPP
|
||||
769
src/libslic3r/SLA/Common.cpp
Normal file
769
src/libslic3r/SLA/Common.cpp
Normal file
|
|
@ -0,0 +1,769 @@
|
|||
#include <cmath>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
|
||||
|
||||
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||
#undef PI
|
||||
|
||||
// HEAVY headers... takes eternity to compile
|
||||
|
||||
// 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/ray_mesh_intersect.h>
|
||||
#include <igl/point_mesh_squared_distance.h>
|
||||
#include <igl/remove_duplicate_vertices.h>
|
||||
#include <igl/collapse_small_triangles.h>
|
||||
#include <igl/signed_distance.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
// Bring back PI from the igl namespace
|
||||
using igl::PI;
|
||||
|
||||
/* **************************************************************************
|
||||
* 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: public igl::AABB<Eigen::MatrixXd, 3> {
|
||||
public:
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
};
|
||||
|
||||
static const constexpr double MESH_EPS = 1e-6;
|
||||
|
||||
void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F)
|
||||
{
|
||||
const stl_file& stl = tmesh.stl;
|
||||
|
||||
V.resize(3*stl.stats.number_of_facets, 3);
|
||||
F.resize(stl.stats.number_of_facets, 3);
|
||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet &facet = stl.facet_start[i];
|
||||
V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast<double>();
|
||||
V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast<double>();
|
||||
V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast<double>();
|
||||
F(i, 0) = int(3*i+0);
|
||||
F(i, 1) = int(3*i+1);
|
||||
F(i, 2) = int(3*i+2);
|
||||
}
|
||||
|
||||
if (!tmesh.has_shared_vertices())
|
||||
{
|
||||
Eigen::MatrixXd rV;
|
||||
Eigen::MatrixXi rF;
|
||||
// We will convert this to a proper 3d mesh with no duplicate points.
|
||||
Eigen::VectorXi SVI, SVJ;
|
||||
igl::remove_duplicate_vertices(V, F, MESH_EPS, rV, SVI, SVJ, rF);
|
||||
V = std::move(rV);
|
||||
F = std::move(rF);
|
||||
}
|
||||
}
|
||||
|
||||
void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &out)
|
||||
{
|
||||
Pointf3s points(size_t(V.rows()));
|
||||
std::vector<Vec3crd> facets(size_t(F.rows()));
|
||||
|
||||
for (Eigen::Index i = 0; i < V.rows(); ++i)
|
||||
points[size_t(i)] = V.row(i);
|
||||
|
||||
for (Eigen::Index i = 0; i < F.rows(); ++i)
|
||||
facets[size_t(i)] = F.row(i);
|
||||
|
||||
out = {points, facets};
|
||||
}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
||||
auto&& bb = tmesh.bounding_box();
|
||||
m_ground_level += bb.min(Z);
|
||||
|
||||
to_eigen_mesh(tmesh, m_V, m_F);
|
||||
|
||||
// Build the AABB accelaration tree
|
||||
m_aabb->init(m_V, m_F);
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
m_aabb->windtree.set_mesh(m_V, m_F);
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
}
|
||||
|
||||
EigenMesh3D::~EigenMesh3D() {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
||||
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const Contour3D &other)
|
||||
{
|
||||
m_V.resize(Eigen::Index(other.points.size()), 3);
|
||||
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
|
||||
m_V.row(i) = other.points[size_t(i)];
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
|
||||
m_F.row(i) = other.faces3[size_t(i)];
|
||||
|
||||
size_t N = other.faces3.size() + 2 * other.faces4.size();
|
||||
for (size_t i = other.faces3.size(); i < N; i += 2) {
|
||||
size_t quad_idx = (i - other.faces3.size()) / 2;
|
||||
auto & quad = other.faces4[quad_idx];
|
||||
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
|
||||
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
|
||||
}
|
||||
}
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||
{
|
||||
m_V = other.m_V;
|
||||
m_F = other.m_F;
|
||||
m_ground_level = other.m_ground_level;
|
||||
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
||||
}
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
|
||||
|
||||
EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
|
||||
|
||||
EigenMesh3D::hit_result
|
||||
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
assert(is_approx(dir.norm(), 1.));
|
||||
igl::Hit hit;
|
||||
hit.t = std::numeric_limits<float>::infinity();
|
||||
|
||||
if (m_holes.empty()) {
|
||||
m_aabb->intersect_ray(m_V, m_F, s, dir, hit);
|
||||
hit_result ret(*this);
|
||||
ret.m_t = double(hit.t);
|
||||
ret.m_dir = dir;
|
||||
ret.m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||
ret.m_normal = this->normal_by_face_id(hit.id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
// If there are holes, the hit_results will be made by
|
||||
// query_ray_hits (object) and filter_hits (holes):
|
||||
return filter_hits(query_ray_hits(s, dir));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<EigenMesh3D::hit_result>
|
||||
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<EigenMesh3D::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(m_V, m_F, 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; });
|
||||
|
||||
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||
// 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; }),
|
||||
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.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||
outs.back().m_normal = this->normal_by_face_id(hit.id);
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||
const std::vector<EigenMesh3D::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
|
||||
if (object_hits.empty())
|
||||
return out;
|
||||
|
||||
const Vec3d& s = object_hits.front().source();
|
||||
const Vec3d& dir = object_hits.front().direction();
|
||||
|
||||
// A helper struct to save an intersetion with a hole
|
||||
struct HoleHit {
|
||||
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
|
||||
t(t_p), normal(normal_p), entry(entry_p) {}
|
||||
float t;
|
||||
Vec3d normal;
|
||||
bool entry;
|
||||
};
|
||||
std::vector<HoleHit> hole_isects;
|
||||
hole_isects.reserve(m_holes.size());
|
||||
|
||||
auto sf = s.cast<float>();
|
||||
auto dirf = dir.cast<float>();
|
||||
|
||||
// Collect hits on all holes, preserve information about entry/exit
|
||||
for (const sla::DrainHole& hole : m_holes) {
|
||||
std::array<std::pair<float, Vec3d>, 2> isects;
|
||||
if (hole.get_intersections(sf, dirf, isects)) {
|
||||
// Ignore hole hits behind the source
|
||||
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
|
||||
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Holes can intersect each other, sort the hits by t
|
||||
std::sort(hole_isects.begin(), hole_isects.end(),
|
||||
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
|
||||
|
||||
// Now inspect the intersections with object and holes, in the order of
|
||||
// increasing distance. Keep track how deep are we nested in mesh/holes and
|
||||
// pick the correct intersection.
|
||||
// This needs to be done twice - first to find out how deep in the structure
|
||||
// the source is, then to pick the correct intersection.
|
||||
int hole_nested = 0;
|
||||
int object_nested = 0;
|
||||
for (int dry_run=1; dry_run>=0; --dry_run) {
|
||||
hole_nested = -hole_nested;
|
||||
object_nested = -object_nested;
|
||||
|
||||
bool is_hole = false;
|
||||
bool is_entry = false;
|
||||
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
|
||||
const hit_result* next_mesh_hit = &object_hits.front();
|
||||
|
||||
while (next_hole_hit || next_mesh_hit) {
|
||||
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
|
||||
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
|
||||
else
|
||||
is_hole = next_hole_hit; // one or the other ran out
|
||||
|
||||
// Is this entry or exit hit?
|
||||
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
|
||||
|
||||
if (! dry_run) {
|
||||
if (! is_hole && hole_nested == 0) {
|
||||
// This is a valid object hit
|
||||
return *next_mesh_hit;
|
||||
}
|
||||
if (is_hole && ! is_entry && object_nested != 0) {
|
||||
// This holehit is the one we seek
|
||||
out.m_t = next_hole_hit->t;
|
||||
out.m_normal = next_hole_hit->normal;
|
||||
out.m_source = s;
|
||||
out.m_dir = dir;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase/decrease the counter
|
||||
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
|
||||
|
||||
// Advance the respective pointer
|
||||
if (is_hole && next_hole_hit++ == &hole_isects.back())
|
||||
next_hole_hit = nullptr;
|
||||
if (! is_hole && next_mesh_hit++ == &object_hits.back())
|
||||
next_mesh_hit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, the ray ended up in infinity
|
||||
return out;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
||||
igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
|
||||
p, sign, sqdst, i, c);
|
||||
|
||||
return si_result(sign * std::sqrt(sqdst), i, c);
|
||||
}
|
||||
|
||||
bool EigenMesh3D::inside(const Vec3d &p) const {
|
||||
return m_aabb->windtree.inside(p);
|
||||
}
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
|
||||
double EigenMesh3D::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;
|
||||
sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc);
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
/* ****************************************************************************
|
||||
* Misc functions
|
||||
* ****************************************************************************/
|
||||
|
||||
namespace {
|
||||
|
||||
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,
|
||||
double eps,
|
||||
std::function<void()> thr, // throw on cancel
|
||||
const std::vector<unsigned>& pt_indices)
|
||||
{
|
||||
if (points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
||||
return {};
|
||||
|
||||
std::vector<unsigned> range = pt_indices;
|
||||
if (range.empty()) {
|
||||
range.resize(size_t(points.rows()), 0);
|
||||
std::iota(range.begin(), range.end(), 0);
|
||||
}
|
||||
|
||||
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) {
|
||||
thr();
|
||||
auto eidx = Eigen::Index(el);
|
||||
int faceid = 0;
|
||||
Vec3d p;
|
||||
|
||||
mesh.squared_distance(points.row(eidx), faceid, p);
|
||||
|
||||
auto trindex = mesh.F().row(faceid);
|
||||
|
||||
const Vec3d &p1 = mesh.V().row(trindex(0));
|
||||
const Vec3d &p2 = mesh.V().row(trindex(1));
|
||||
const Vec3d &p3 = mesh.V().row(trindex(2));
|
||||
|
||||
// We should check if the point lies on an edge of the hosting
|
||||
// triangle. If it does then all the other triangles using the
|
||||
// same two points have to be searched and the final normal should
|
||||
// be some kind of aggregation of the participating triangle
|
||||
// normals. We should also consider the cases where the support
|
||||
// point lies right on a vertex of its triangle. The procedure is
|
||||
// the same, get the neighbor triangles and calculate an average
|
||||
// normal.
|
||||
|
||||
// mark the vertex indices of the edge. ia and ib marks and edge
|
||||
// ic will mark a single vertex.
|
||||
int ia = -1, ib = -1, ic = -1;
|
||||
|
||||
if (std::abs(distance(p, p1)) < eps) {
|
||||
ic = trindex(0);
|
||||
} else if (std::abs(distance(p, p2)) < eps) {
|
||||
ic = trindex(1);
|
||||
} else if (std::abs(distance(p, p3)) < eps) {
|
||||
ic = trindex(2);
|
||||
} else if (point_on_edge(p, p1, p2, eps)) {
|
||||
ia = trindex(0);
|
||||
ib = trindex(1);
|
||||
} else if (point_on_edge(p, p2, p3, eps)) {
|
||||
ia = trindex(1);
|
||||
ib = trindex(2);
|
||||
} else if (point_on_edge(p, p1, p3, eps)) {
|
||||
ia = trindex(0);
|
||||
ib = trindex(2);
|
||||
}
|
||||
|
||||
// vector for the neigboring triangles including the detected one.
|
||||
std::vector<Vec3i> neigh;
|
||||
if (ic >= 0) { // The point is right on a vertex of the triangle
|
||||
for (int n = 0; n < mesh.F().rows(); ++n) {
|
||||
thr();
|
||||
Vec3i ni = mesh.F().row(n);
|
||||
if ((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
|
||||
neigh.emplace_back(ni);
|
||||
}
|
||||
} else if (ia >= 0 && ib >= 0) { // the point is on and edge
|
||||
// now get all the neigboring triangles
|
||||
for (int n = 0; n < mesh.F().rows(); ++n) {
|
||||
thr();
|
||||
Vec3i ni = mesh.F().row(n);
|
||||
if ((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
|
||||
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
|
||||
neigh.emplace_back(ni);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the normals for the neighboring triangles
|
||||
std::vector<Vec3d> neighnorms;
|
||||
neighnorms.reserve(neigh.size());
|
||||
for (const Vec3i &tri : neigh) {
|
||||
const Vec3d & pt1 = mesh.V().row(tri(0));
|
||||
const Vec3d & pt2 = mesh.V().row(tri(1));
|
||||
const Vec3d & pt3 = mesh.V().row(tri(2));
|
||||
Eigen::Vector3d U = pt2 - pt1;
|
||||
Eigen::Vector3d V = pt3 - pt1;
|
||||
neighnorms.emplace_back(U.cross(V).normalized());
|
||||
}
|
||||
|
||||
// Throw out duplicates. They would cause trouble with summing. We
|
||||
// will use std::unique which works on sorted ranges. We will sort
|
||||
// by the coefficient-wise sum of the normals. It should force the
|
||||
// same elements to be consecutive.
|
||||
std::sort(neighnorms.begin(), neighnorms.end(),
|
||||
[](const Vec3d &v1, const Vec3d &v2) {
|
||||
return v1.sum() < v2.sum();
|
||||
});
|
||||
|
||||
auto lend = std::unique(neighnorms.begin(), neighnorms.end(),
|
||||
[](const Vec3d &n1, const Vec3d &n2) {
|
||||
// Compare normals for equivalence.
|
||||
// This is controvers stuff.
|
||||
auto deq = [](double a, double b) {
|
||||
return std::abs(a - b) < 1e-3;
|
||||
};
|
||||
return deq(n1(X), n2(X)) &&
|
||||
deq(n1(Y), n2(Y)) &&
|
||||
deq(n1(Z), n2(Z));
|
||||
});
|
||||
|
||||
if (!neighnorms.empty()) { // there were neighbors to count with
|
||||
// sum up the normals and then normalize the result again.
|
||||
// This unification seems to be enough.
|
||||
Vec3d sumnorm(0, 0, 0);
|
||||
sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm);
|
||||
sumnorm.normalize();
|
||||
ret.row(long(ridx)) = sumnorm;
|
||||
} else { // point lies safely within its triangle
|
||||
Eigen::Vector3d U = p2 - p1;
|
||||
Eigen::Vector3d V = p3 - p1;
|
||||
ret.row(long(ridx)) = U.cross(V).normalized();
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
32
src/libslic3r/SLA/Common.hpp
Normal file
32
src/libslic3r/SLA/Common.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef SLA_COMMON_HPP
|
||||
#define SLA_COMMON_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
#include <functional>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
//#include "SLASpatIndex.hpp"
|
||||
|
||||
//#include <libslic3r/ExPolygon.hpp>
|
||||
//#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||
|
||||
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, 4, 1, Eigen::DontAlign> Vec4i;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // SLASUPPORTTREE_HPP
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
#include "ConcaveHull.hpp"
|
||||
#include <libslic3r/SLA/ConcaveHull.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include "SLASpatIndex.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -40,9 +42,9 @@ Point ConcaveHull::centroid(const Points &pp)
|
|||
|
||||
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
|
||||
// mode
|
||||
ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
||||
coord_t delta,
|
||||
ClipperLib::JoinType jointype)
|
||||
static ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
||||
coord_t delta,
|
||||
ClipperLib::JoinType jointype)
|
||||
{
|
||||
using ClipperLib::ClipperOffset;
|
||||
using ClipperLib::etClosedPolygon;
|
||||
|
|
@ -73,7 +75,7 @@ Points ConcaveHull::calculate_centroids() const
|
|||
Points centroids = reserve_vector<Point>(m_polys.size());
|
||||
std::transform(m_polys.begin(), m_polys.end(),
|
||||
std::back_inserter(centroids),
|
||||
[this](const Polygon &poly) { return centroid(poly); });
|
||||
[](const Polygon &poly) { return centroid(poly); });
|
||||
|
||||
return centroids;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef CONCAVEHULL_HPP
|
||||
#define CONCAVEHULL_HPP
|
||||
#ifndef SLA_CONCAVEHULL_HPP
|
||||
#define SLA_CONCAVEHULL_HPP
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLACONCURRENCY_H
|
||||
#define SLACONCURRENCY_H
|
||||
#ifndef SLA_CONCURRENCY_H
|
||||
#define SLA_CONCURRENCY_H
|
||||
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/mutex.h>
|
||||
149
src/libslic3r/SLA/Contour3D.cpp
Normal file
149
src/libslic3r/SLA/Contour3D.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
|
||||
#include <libslic3r/Format/objparser.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
Contour3D::Contour3D(const TriangleMesh &trmesh)
|
||||
{
|
||||
points.reserve(trmesh.its.vertices.size());
|
||||
faces3.reserve(trmesh.its.indices.size());
|
||||
|
||||
for (auto &v : trmesh.its.vertices)
|
||||
points.emplace_back(v.cast<double>());
|
||||
|
||||
std::copy(trmesh.its.indices.begin(), trmesh.its.indices.end(),
|
||||
std::back_inserter(faces3));
|
||||
}
|
||||
|
||||
Contour3D::Contour3D(TriangleMesh &&trmesh)
|
||||
{
|
||||
points.reserve(trmesh.its.vertices.size());
|
||||
|
||||
for (auto &v : trmesh.its.vertices)
|
||||
points.emplace_back(v.cast<double>());
|
||||
|
||||
faces3.swap(trmesh.its.indices);
|
||||
}
|
||||
|
||||
Contour3D::Contour3D(const EigenMesh3D &emesh) {
|
||||
points.reserve(size_t(emesh.V().rows()));
|
||||
faces3.reserve(size_t(emesh.F().rows()));
|
||||
|
||||
for (int r = 0; r < emesh.V().rows(); r++)
|
||||
points.emplace_back(emesh.V().row(r).cast<double>());
|
||||
|
||||
for (int i = 0; i < emesh.F().rows(); i++)
|
||||
faces3.emplace_back(emesh.F().row(i));
|
||||
}
|
||||
|
||||
Contour3D &Contour3D::merge(const Contour3D &ctr)
|
||||
{
|
||||
auto N = coord_t(points.size());
|
||||
auto N_f3 = faces3.size();
|
||||
auto N_f4 = faces4.size();
|
||||
|
||||
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
||||
faces3.insert(faces3.end(), ctr.faces3.begin(), ctr.faces3.end());
|
||||
faces4.insert(faces4.end(), ctr.faces4.begin(), ctr.faces4.end());
|
||||
|
||||
for(size_t n = N_f3; n < faces3.size(); n++) {
|
||||
auto& idx = faces3[n]; idx.x() += N; idx.y() += N; idx.z() += N;
|
||||
}
|
||||
|
||||
for(size_t n = N_f4; n < faces4.size(); n++) {
|
||||
auto& idx = faces4[n]; for (int k = 0; k < 4; k++) idx(k) += N;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Contour3D &Contour3D::merge(const Pointf3s &triangles)
|
||||
{
|
||||
const size_t offs = points.size();
|
||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||
faces3.reserve(faces3.size() + points.size() / 3);
|
||||
|
||||
for(int i = int(offs); i < int(points.size()); i += 3)
|
||||
faces3.emplace_back(i, i + 1, i + 2);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Contour3D::to_obj(std::ostream &stream)
|
||||
{
|
||||
for(auto& p : points)
|
||||
stream << "v " << p.transpose() << "\n";
|
||||
|
||||
for(auto& f : faces3)
|
||||
stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n";
|
||||
|
||||
for(auto& f : faces4)
|
||||
stream << "f " << (f + Vec4i(1, 1, 1, 1)).transpose() << "\n";
|
||||
}
|
||||
|
||||
void Contour3D::from_obj(std::istream &stream)
|
||||
{
|
||||
ObjParser::ObjData data;
|
||||
ObjParser::objparse(stream, data);
|
||||
|
||||
points.reserve(data.coordinates.size() / 4 + 1);
|
||||
auto &coords = data.coordinates;
|
||||
for (size_t i = 0; i < coords.size(); i += 4)
|
||||
points.emplace_back(coords[i], coords[i + 1], coords[i + 2]);
|
||||
|
||||
Vec3i triangle;
|
||||
Vec4i quad;
|
||||
size_t v = 0;
|
||||
while(v < data.vertices.size()) {
|
||||
size_t N = 0;
|
||||
size_t i = v;
|
||||
while (data.vertices[v++].coordIdx != -1) ++N;
|
||||
|
||||
std::function<void(int, int)> setfn;
|
||||
if (N < 3 || N > 4) continue;
|
||||
else if (N == 3) setfn = [&triangle](int k, int f) { triangle(k) = f; };
|
||||
else setfn = [&quad](int k, int f) { quad(k) = f; };
|
||||
|
||||
for (size_t j = 0; j < N; ++j)
|
||||
setfn(int(j), data.vertices[i + j].coordIdx);
|
||||
}
|
||||
}
|
||||
|
||||
TriangleMesh to_triangle_mesh(const Contour3D &ctour) {
|
||||
if (ctour.faces4.empty()) return {ctour.points, ctour.faces3};
|
||||
|
||||
std::vector<Vec3i> triangles;
|
||||
|
||||
triangles.reserve(ctour.faces3.size() + 2 * ctour.faces4.size());
|
||||
std::copy(ctour.faces3.begin(), ctour.faces3.end(),
|
||||
std::back_inserter(triangles));
|
||||
|
||||
for (auto &quad : ctour.faces4) {
|
||||
triangles.emplace_back(quad(0), quad(1), quad(2));
|
||||
triangles.emplace_back(quad(2), quad(3), quad(0));
|
||||
}
|
||||
|
||||
return {ctour.points, std::move(triangles)};
|
||||
}
|
||||
|
||||
TriangleMesh to_triangle_mesh(Contour3D &&ctour) {
|
||||
if (ctour.faces4.empty())
|
||||
return {std::move(ctour.points), std::move(ctour.faces3)};
|
||||
|
||||
std::vector<Vec3i> triangles;
|
||||
|
||||
triangles.reserve(ctour.faces3.size() + 2 * ctour.faces4.size());
|
||||
std::copy(ctour.faces3.begin(), ctour.faces3.end(),
|
||||
std::back_inserter(triangles));
|
||||
|
||||
for (auto &quad : ctour.faces4) {
|
||||
triangles.emplace_back(quad(0), quad(1), quad(2));
|
||||
triangles.emplace_back(quad(2), quad(3), quad(0));
|
||||
}
|
||||
|
||||
return {std::move(ctour.points), std::move(triangles)};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
45
src/libslic3r/SLA/Contour3D.hpp
Normal file
45
src/libslic3r/SLA/Contour3D.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef SLA_CONTOUR3D_HPP
|
||||
#define SLA_CONTOUR3D_HPP
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
class EigenMesh3D;
|
||||
|
||||
/// 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.
|
||||
struct Contour3D {
|
||||
std::vector<Vec3d> points;
|
||||
std::vector<Vec3i> faces3;
|
||||
std::vector<Vec4i> faces4;
|
||||
|
||||
Contour3D() = default;
|
||||
Contour3D(const TriangleMesh &trmesh);
|
||||
Contour3D(TriangleMesh &&trmesh);
|
||||
Contour3D(const EigenMesh3D &emesh);
|
||||
|
||||
Contour3D& merge(const Contour3D& ctr);
|
||||
Contour3D& merge(const Pointf3s& triangles);
|
||||
|
||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
||||
void to_obj(std::ostream& stream);
|
||||
void from_obj(std::istream &stream);
|
||||
|
||||
inline bool empty() const
|
||||
{
|
||||
return points.empty() || (faces4.empty() && faces3.empty());
|
||||
}
|
||||
};
|
||||
|
||||
/// Mesh from an existing contour.
|
||||
TriangleMesh to_triangle_mesh(const Contour3D& ctour);
|
||||
|
||||
/// Mesh from an evaporating 3D contour
|
||||
TriangleMesh to_triangle_mesh(Contour3D&& ctour);
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // CONTOUR3D_HPP
|
||||
160
src/libslic3r/SLA/EigenMesh3D.hpp
Normal file
160
src/libslic3r/SLA/EigenMesh3D.hpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#ifndef SLA_EIGENMESH3D_H
|
||||
#define SLA_EIGENMESH3D_H
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
struct Contour3D;
|
||||
|
||||
void to_eigen_mesh(const TriangleMesh &mesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F);
|
||||
void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &);
|
||||
|
||||
/// 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 AABBImpl;
|
||||
|
||||
Eigen::MatrixXd m_V;
|
||||
Eigen::MatrixXi m_F;
|
||||
double m_ground_level = 0, m_gnd_offset = 0;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
|
||||
// This holds a copy of holes in the mesh. Initialized externally
|
||||
// by load_mesh setter.
|
||||
std::vector<DrainHole> m_holes;
|
||||
|
||||
public:
|
||||
|
||||
explicit EigenMesh3D(const TriangleMesh&);
|
||||
explicit EigenMesh3D(const Contour3D &other);
|
||||
|
||||
EigenMesh3D(const EigenMesh3D& other);
|
||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||
|
||||
EigenMesh3D(EigenMesh3D &&other);
|
||||
EigenMesh3D& operator=(EigenMesh3D &&other);
|
||||
|
||||
~EigenMesh3D();
|
||||
|
||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
||||
inline double ground_level_offset() const { return m_gnd_offset; }
|
||||
|
||||
inline const Eigen::MatrixXd& V() const { return m_V; }
|
||||
inline const Eigen::MatrixXi& F() const { return m_F; }
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
// m_t holds a distance from m_source to the intersection.
|
||||
double m_t = infty();
|
||||
const EigenMesh3D *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
Vec3d m_normal;
|
||||
friend class EigenMesh3D;
|
||||
|
||||
// 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) {}
|
||||
public:
|
||||
// This denotes no hit on the mesh.
|
||||
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||
|
||||
explicit inline hit_result(double val = infty()) : m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
inline const Vec3d& source() const { return m_source; }
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
inline bool is_hit() const { return !std::isinf(m_t); }
|
||||
|
||||
inline const Vec3d& normal() const {
|
||||
assert(is_valid());
|
||||
return m_normal;
|
||||
}
|
||||
|
||||
inline bool is_inside() const {
|
||||
return is_hit() && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Inform the object about location of holes
|
||||
// creates internal copy of the vector
|
||||
void load_holes(const std::vector<DrainHole>& holes) {
|
||||
m_holes = holes;
|
||||
}
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Casts a ray on the mesh and returns all hits
|
||||
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Iterates over hits and holes and returns the true hit, possibly
|
||||
// on the inside of a hole.
|
||||
hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
|
||||
|
||||
class si_result {
|
||||
double m_value;
|
||||
int m_fidx;
|
||||
Vec3d m_p;
|
||||
si_result(double val, int i, const Vec3d& c):
|
||||
m_value(val), m_fidx(i), m_p(c) {}
|
||||
friend class EigenMesh3D;
|
||||
public:
|
||||
|
||||
si_result() = delete;
|
||||
|
||||
double value() const { return m_value; }
|
||||
operator double() const { return m_value; }
|
||||
const Vec3d& point_on_mesh() const { return m_p; }
|
||||
int F_idx() const { return m_fidx; }
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
// The signed distance from a point to the mesh. Outputs the distance,
|
||||
// the index of the triangle and the closest point in mesh coordinate space.
|
||||
si_result signed_distance(const Vec3d& p) const;
|
||||
|
||||
bool inside(const Vec3d& p) const;
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
|
||||
Vec3d normal_by_face_id(int face_id) const {
|
||||
auto trindex = F().row(face_id);
|
||||
const Vec3d& p1 = V().row(trindex(0));
|
||||
const Vec3d& p2 = V().row(trindex(1));
|
||||
const Vec3d& p3 = V().row(trindex(2));
|
||||
Eigen::Vector3d U = p2 - p1;
|
||||
Eigen::Vector3d V = p3 - p1;
|
||||
return U.cross(V).normalized();
|
||||
}
|
||||
};
|
||||
|
||||
// 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,
|
||||
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
|
||||
277
src/libslic3r/SLA/Hollowing.cpp
Normal file
277
src/libslic3r/SLA/Hollowing.cpp
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
#include <functional>
|
||||
|
||||
#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/ClipperUtils.hpp>
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/I18N.hpp>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
template<class S, class = FloatingOnly<S>>
|
||||
inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
|
||||
|
||||
template<class S, class = FloatingOnly<S>>
|
||||
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
|
||||
|
||||
static TriangleMesh _generate_interior(const TriangleMesh &mesh,
|
||||
const JobController &ctl,
|
||||
double min_thickness,
|
||||
double voxel_scale,
|
||||
double closing_dist)
|
||||
{
|
||||
TriangleMesh imesh{mesh};
|
||||
|
||||
_scale(voxel_scale, imesh);
|
||||
|
||||
double offset = voxel_scale * min_thickness;
|
||||
double D = voxel_scale * closing_dist;
|
||||
float out_range = 0.1f * float(offset);
|
||||
float in_range = 1.1f * float(offset + D);
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(0, L("Hollowing"));
|
||||
|
||||
auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
|
||||
|
||||
assert(gridptr);
|
||||
|
||||
if (!gridptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(30, L("Hollowing"));
|
||||
|
||||
if (closing_dist > .0) {
|
||||
gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
|
||||
} else {
|
||||
D = -offset;
|
||||
}
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(70, L("Hollowing"));
|
||||
|
||||
double iso_surface = D;
|
||||
double adaptivity = 0.;
|
||||
auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
|
||||
|
||||
_scale(1. / voxel_scale, omesh);
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(100, L("Hollowing"));
|
||||
|
||||
return omesh;
|
||||
}
|
||||
|
||||
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
|
||||
const HollowingConfig &hc,
|
||||
const JobController & ctl)
|
||||
{
|
||||
static const double MIN_OVERSAMPL = 3.;
|
||||
static const double MAX_OVERSAMPL = 8.;
|
||||
|
||||
// I can't figure out how to increase the grid resolution through openvdb
|
||||
// API so the model will be scaled up before conversion and the result
|
||||
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
|
||||
// scales the whole geometry down, and doesn't increase the number of
|
||||
// voxels.
|
||||
//
|
||||
// max 8x upscale, min is native voxel size
|
||||
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
|
||||
auto meshptr = std::make_unique<TriangleMesh>(
|
||||
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
|
||||
hc.closing_distance));
|
||||
|
||||
if (meshptr) {
|
||||
|
||||
// This flips the normals to be outward facing...
|
||||
meshptr->require_shared_vertices();
|
||||
indexed_triangle_set its = std::move(meshptr->its);
|
||||
|
||||
Slic3r::simplify_mesh(its);
|
||||
|
||||
// flip normals back...
|
||||
for (stl_triangle_vertex_indices &ind : its.indices)
|
||||
std::swap(ind(0), ind(2));
|
||||
|
||||
*meshptr = Slic3r::TriangleMesh{its};
|
||||
}
|
||||
|
||||
return meshptr;
|
||||
}
|
||||
|
||||
Contour3D DrainHole::to_mesh() const
|
||||
{
|
||||
auto r = double(radius);
|
||||
auto h = double(height);
|
||||
sla::Contour3D hole = sla::cylinder(r, h, steps);
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, normal.cast<double>());
|
||||
for(auto& p : hole.points) p = q * p + pos.cast<double>();
|
||||
|
||||
return hole;
|
||||
}
|
||||
|
||||
bool DrainHole::operator==(const DrainHole &sp) const
|
||||
{
|
||||
return (pos == sp.pos) && (normal == sp.normal) &&
|
||||
is_approx(radius, sp.radius) &&
|
||||
is_approx(height, sp.height);
|
||||
}
|
||||
|
||||
bool DrainHole::is_inside(const Vec3f& pt) const
|
||||
{
|
||||
Eigen::Hyperplane<float, 3> plane(normal, pos);
|
||||
float dist = plane.signedDistance(pt);
|
||||
if (dist < float(EPSILON) || dist > height)
|
||||
return false;
|
||||
|
||||
Eigen::ParametrizedLine<float, 3> axis(pos, normal);
|
||||
if ( axis.squaredDistance(pt) < pow(radius, 2.f))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Given a line s+dir*t, find parameter t of intersections with the hole
|
||||
// and the normal (points inside the hole). Outputs through out reference,
|
||||
// returns true if two intersections were found.
|
||||
bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||
std::array<std::pair<float, Vec3d>, 2>& out)
|
||||
const
|
||||
{
|
||||
assert(is_approx(normal.norm(), 1.f));
|
||||
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());
|
||||
|
||||
const float sqr_radius = pow(radius, 2.f);
|
||||
|
||||
// first check a bounding sphere of the hole:
|
||||
Vec3f center = pos+normal*height/2.f;
|
||||
float sqr_dist_limit = pow(height/2.f, 2.f) + sqr_radius ;
|
||||
if (ray.squaredDistance(center) > sqr_dist_limit)
|
||||
return false;
|
||||
|
||||
// The line intersects the bounding sphere, look for intersections with
|
||||
// bases of the cylinder.
|
||||
|
||||
size_t found = 0; // counts how many intersections were found
|
||||
Eigen::Hyperplane<float, 3> base;
|
||||
if (! is_approx(ray.direction().dot(normal), 0.f)) {
|
||||
for (size_t i=1; i<=1; --i) {
|
||||
Vec3f cylinder_center = pos+i*height*normal;
|
||||
if (i == 0) {
|
||||
// The hole base can be identical to mesh surface if it is flat
|
||||
// let's better move the base outward a bit
|
||||
cylinder_center -= EPSILON*normal;
|
||||
}
|
||||
base = Eigen::Hyperplane<float, 3>(normal, cylinder_center);
|
||||
Vec3f intersection = ray.intersectionPoint(base);
|
||||
// Only accept the point if it is inside the cylinder base.
|
||||
if ((cylinder_center-intersection).squaredNorm() < sqr_radius) {
|
||||
out[found].first = ray.intersectionParameter(base);
|
||||
out[found].second = (i==0 ? 1. : -1.) * normal.cast<double>();
|
||||
++found;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// In case the line was perpendicular to the cylinder axis, previous
|
||||
// block was skipped, but base will later be assumed to be valid.
|
||||
base = Eigen::Hyperplane<float, 3>(normal, pos-EPSILON*normal);
|
||||
}
|
||||
|
||||
// In case there is still an intersection to be found, check the wall
|
||||
if (found != 2 && ! is_approx(std::abs(ray.direction().dot(normal)), 1.f)) {
|
||||
// Project the ray onto the base plane
|
||||
Vec3f proj_origin = base.projection(ray.origin());
|
||||
Vec3f proj_dir = base.projection(ray.origin()+ray.direction())-proj_origin;
|
||||
// save how the parameter scales and normalize the projected direction
|
||||
float par_scale = proj_dir.norm();
|
||||
proj_dir = proj_dir/par_scale;
|
||||
Eigen::ParametrizedLine<float, 3> projected_ray(proj_origin, proj_dir);
|
||||
// Calculate point on the secant that's closest to the center
|
||||
// and its distance to the circle along the projected line
|
||||
Vec3f closest = projected_ray.projection(pos);
|
||||
float dist = sqrt((sqr_radius - (closest-pos).squaredNorm()));
|
||||
// Unproject both intersections on the original line and check
|
||||
// they are on the cylinder and not past it:
|
||||
for (int i=-1; i<=1 && found !=2; i+=2) {
|
||||
Vec3f isect = closest + i*dist * projected_ray.direction();
|
||||
Vec3f to_isect = isect-proj_origin;
|
||||
float par = to_isect.norm() / par_scale;
|
||||
if (to_isect.normalized().dot(proj_dir.normalized()) < 0.f)
|
||||
par *= -1.f;
|
||||
Vec3d hit_normal = (pos-isect).normalized().cast<double>();
|
||||
isect = ray.pointAt(par);
|
||||
// check that the intersection is between the base planes:
|
||||
float vert_dist = base.signedDistance(isect);
|
||||
if (vert_dist > 0.f && vert_dist < height) {
|
||||
out[found].first = par;
|
||||
out[found].second = hit_normal;
|
||||
++found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If only one intersection was found, it is some corner case,
|
||||
// no intersection will be returned:
|
||||
if (found != 2)
|
||||
return false;
|
||||
|
||||
// Sort the intersections:
|
||||
if (out[0].first > out[1].first)
|
||||
std::swap(out[0], out[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const std::vector<float> &slicegrid,
|
||||
float closing_radius,
|
||||
const sla::DrainHoles & holes,
|
||||
std::function<void(void)> thr)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
for (const sla::DrainHole &holept : holes)
|
||||
mesh.merge(sla::to_triangle_mesh(holept.to_mesh()));
|
||||
|
||||
if (mesh.empty()) return;
|
||||
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
std::vector<ExPolygons> hole_slices;
|
||||
slicer.slice(slicegrid, SlicingMode::Regular, closing_radius, &hole_slices, thr);
|
||||
|
||||
if (obj_slices.size() != hole_slices.size())
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Sliced object and drain-holes layer count does not match!";
|
||||
|
||||
size_t until = std::min(obj_slices.size(), hole_slices.size());
|
||||
|
||||
for (size_t i = 0; i < until; ++i)
|
||||
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
75
src/libslic3r/SLA/Hollowing.hpp
Normal file
75
src/libslic3r/SLA/Hollowing.hpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#ifndef SLA_HOLLOWING_HPP
|
||||
#define SLA_HOLLOWING_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
struct HollowingConfig
|
||||
{
|
||||
double min_thickness = 2.;
|
||||
double quality = 0.5;
|
||||
double closing_distance = 0.5;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
struct DrainHole
|
||||
{
|
||||
Vec3f pos;
|
||||
Vec3f normal;
|
||||
float radius;
|
||||
float height;
|
||||
|
||||
DrainHole()
|
||||
: pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f)
|
||||
{}
|
||||
|
||||
DrainHole(Vec3f p, Vec3f n, float r, float h)
|
||||
: pos(p), normal(n), radius(r), height(h)
|
||||
{}
|
||||
|
||||
DrainHole(const DrainHole& rhs) :
|
||||
DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height) {}
|
||||
|
||||
bool operator==(const DrainHole &sp) const;
|
||||
|
||||
bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); }
|
||||
|
||||
bool is_inside(const Vec3f& pt) const;
|
||||
|
||||
bool get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||
std::array<std::pair<float, Vec3d>, 2>& out) const;
|
||||
|
||||
Contour3D to_mesh() const;
|
||||
|
||||
template<class Archive> inline void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, normal, radius, height);
|
||||
}
|
||||
|
||||
static constexpr size_t steps = 32;
|
||||
};
|
||||
|
||||
using DrainHoles = std::vector<DrainHole>;
|
||||
|
||||
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
|
||||
const HollowingConfig & = {},
|
||||
const JobController &ctl = {});
|
||||
|
||||
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const std::vector<float> &slicegrid,
|
||||
float closing_radius,
|
||||
const sla::DrainHoles & holes,
|
||||
std::function<void(void)> thr);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HOLLOWINGFILTER_H
|
||||
31
src/libslic3r/SLA/JobController.hpp
Normal file
31
src/libslic3r/SLA/JobController.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef SLA_JOBCONTROLLER_HPP
|
||||
#define SLA_JOBCONTROLLER_HPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
struct JobController
|
||||
{
|
||||
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||
using StopCond = std::function<bool(void)>;
|
||||
using CancelFn = std::function<void(void)>;
|
||||
|
||||
// This will signal the status of the calculation to the front-end
|
||||
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||
|
||||
// Returns true if the calculation should be aborted.
|
||||
StopCond stopcondition = [](){ return false; };
|
||||
|
||||
// Similar to cancel callback. This should check the stop condition and
|
||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||
// terminate itself
|
||||
CancelFn cancelfn = [](){};
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // JOBCONTROLLER_HPP
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
#include "SLAPad.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
#include "ConcaveHull.hpp"
|
||||
|
||||
#include "boost/log/trivial.hpp"
|
||||
#include "SLABoostAdapter.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
|
@ -69,7 +71,7 @@ Contour3D walls(
|
|||
|
||||
// Shorthand for the vertex arrays
|
||||
auto& upts = upper.points, &lpts = lower.points;
|
||||
auto& rpts = ret.points; auto& ind = ret.indices;
|
||||
auto& rpts = ret.points; auto& ind = ret.faces3;
|
||||
|
||||
// If the Z levels are flipped, or the offset difference is negative, we
|
||||
// will interpret that as the triangles normals should be inverted.
|
||||
|
|
@ -636,7 +638,7 @@ void pad_blueprint(const TriangleMesh & mesh,
|
|||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
auto out = reserve_vector<ExPolygons>(heights.size());
|
||||
slicer.slice(heights, 0.f, &out, thrfn);
|
||||
slicer.slice(heights, SlicingMode::Regular, 0.f, &out, thrfn);
|
||||
|
||||
size_t count = 0;
|
||||
for(auto& o : out) count += o.size();
|
||||
|
|
@ -676,7 +678,7 @@ void create_pad(const ExPolygons &sup_blueprint,
|
|||
ThrowOnCancel thr)
|
||||
{
|
||||
Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr);
|
||||
out.merge(mesh(std::move(t)));
|
||||
out.merge(to_triangle_mesh(std::move(t)));
|
||||
}
|
||||
|
||||
std::string PadConfig::validate() const
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLABASEPOOL_HPP
|
||||
#define SLABASEPOOL_HPP
|
||||
#ifndef SLA_PAD_HPP
|
||||
#define SLA_PAD_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <functional>
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include <libslic3r/SLA/Raster.hpp>
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLARASTER_HPP
|
||||
#define SLARASTER_HPP
|
||||
#ifndef SLA_RASTER_HPP
|
||||
#define SLA_RASTER_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <memory>
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
#include "SLARasterWriter.hpp"
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include <string_view>
|
||||
|
||||
#include <libslic3r/SLA/RasterWriter.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include <libslic3r/Zipper.hpp>
|
||||
#include <libslic3r/Time.hpp>
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
|
@ -10,14 +14,16 @@
|
|||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
std::string RasterWriter::createIniContent(const std::string& projectname) const
|
||||
void RasterWriter::write_ini(const std::map<std::string, std::string> &m, std::string &ini)
|
||||
{
|
||||
for (auto ¶m : m) ini += param.first + " = " + param.second + "\n";
|
||||
}
|
||||
|
||||
std::string RasterWriter::create_ini_content(const std::string& projectname) const
|
||||
{
|
||||
std::string out("action = print\njobDir = ");
|
||||
out += projectname + "\n";
|
||||
|
||||
for (auto ¶m : m_config)
|
||||
out += param.first + " = " + param.second + "\n";
|
||||
|
||||
write_ini(m_config, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +57,12 @@ void RasterWriter::save(Zipper &zipper, const std::string &prjname)
|
|||
|
||||
zipper.add_entry("config.ini");
|
||||
|
||||
zipper << createIniContent(project);
|
||||
zipper << create_ini_content(project);
|
||||
|
||||
zipper.add_entry("prusaslicer.ini");
|
||||
std::string prusaslicer_ini;
|
||||
write_ini(m_slicer_config, prusaslicer_ini);
|
||||
zipper << prusaslicer_ini;
|
||||
|
||||
for(unsigned i = 0; i < m_layers_rst.size(); i++)
|
||||
{
|
||||
|
|
@ -87,6 +98,29 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
|||
return ret;
|
||||
}
|
||||
|
||||
void append_full_config(const DynamicPrintConfig &cfg, std::map<std::string, std::string> &keys)
|
||||
{
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
// Sorted list of config keys, which shall not be stored into the ini.
|
||||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv,
|
||||
"print_host"sv,
|
||||
"printhost_apikey"sv,
|
||||
"printhost_cafile"sv
|
||||
};
|
||||
|
||||
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
|
||||
auto is_banned = [](const std::string &key) {
|
||||
return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
|
||||
};
|
||||
|
||||
for (const std::string &key : cfg.keys())
|
||||
if (! is_banned(key) && ! cfg.option(key)->is_nil())
|
||||
keys[key] = cfg.opt_serialize(key);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RasterWriter::set_config(const DynamicPrintConfig &cfg)
|
||||
|
|
@ -99,9 +133,9 @@ void RasterWriter::set_config(const DynamicPrintConfig &cfg)
|
|||
m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant");
|
||||
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
||||
m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
|
||||
|
||||
m_config["fileCreationTimestamp"] = Utils::utc_timestamp();
|
||||
m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
|
||||
append_full_config(cfg, m_slicer_config);
|
||||
}
|
||||
|
||||
void RasterWriter::set_statistics(const PrintStatistics &stats)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLARASTERWRITER_HPP
|
||||
#define SLARASTERWRITER_HPP
|
||||
#ifndef SLA_RASTERWRITER_HPP
|
||||
#define SLA_RASTERWRITER_HPP
|
||||
|
||||
// For png export of the sliced model
|
||||
#include <fstream>
|
||||
|
|
@ -9,12 +9,14 @@
|
|||
#include <map>
|
||||
#include <array>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include <libslic3r/SLA/Raster.hpp>
|
||||
#include <libslic3r/Zipper.hpp>
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
namespace Slic3r {
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
class DynamicPrintConfig;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// API to write the zipped sla output layers and metadata.
|
||||
// Implementation uses PNG raster output.
|
||||
|
|
@ -64,8 +66,10 @@ private:
|
|||
double m_gamma;
|
||||
|
||||
std::map<std::string, std::string> m_config;
|
||||
std::map<std::string, std::string> m_slicer_config;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) const;
|
||||
static void write_ini(const std::map<std::string, std::string> &m, std::string &ini);
|
||||
std::string create_ini_content(const std::string& projectname) const;
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -116,7 +120,7 @@ public:
|
|||
void save(Zipper &zipper, const std::string &prjname = "");
|
||||
|
||||
void set_statistics(const PrintStatistics &statistics);
|
||||
|
||||
|
||||
void set_config(const DynamicPrintConfig &cfg);
|
||||
};
|
||||
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
#include <exception>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLARotfinder.hpp"
|
||||
#include "SLASupportTree.hpp"
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include "Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLAROTFINDER_HPP
|
||||
#define SLAROTFINDER_HPP
|
||||
#ifndef SLA_ROTFINDER_HPP
|
||||
#define SLA_ROTFINDER_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <array>
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
#ifndef SLABOILERPLATE_HPP
|
||||
#define SLABOILERPLATE_HPP
|
||||
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/// Intermediate struct for a 3D mesh
|
||||
struct Contour3D {
|
||||
Pointf3s points;
|
||||
std::vector<Vec3i> indices;
|
||||
|
||||
Contour3D& merge(const Contour3D& ctr)
|
||||
{
|
||||
auto s3 = coord_t(points.size());
|
||||
auto s = indices.size();
|
||||
|
||||
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
||||
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
||||
|
||||
for(size_t n = s; n < indices.size(); n++) {
|
||||
auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Contour3D& merge(const Pointf3s& triangles)
|
||||
{
|
||||
const size_t offs = points.size();
|
||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||
indices.reserve(indices.size() + points.size() / 3);
|
||||
|
||||
for(int i = int(offs); i < int(points.size()); i += 3)
|
||||
indices.emplace_back(i, i + 1, i + 2);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
||||
void to_obj(std::ostream& stream)
|
||||
{
|
||||
for(auto& p : points) {
|
||||
stream << "v " << p.transpose() << "\n";
|
||||
}
|
||||
|
||||
for(auto& f : indices) {
|
||||
stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ClusterEl = std::vector<unsigned>;
|
||||
using ClusteredPoints = std::vector<ClusterEl>;
|
||||
|
||||
// Clustering a set of points by the given distance.
|
||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
|
||||
// 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& mesh,
|
||||
double eps = 0.05, // min distance from edges
|
||||
std::function<void()> throw_on_cancel = [](){},
|
||||
const std::vector<unsigned>& selected_points = {});
|
||||
|
||||
/// Mesh from an existing contour.
|
||||
inline TriangleMesh mesh(const Contour3D& ctour) {
|
||||
return {ctour.points, ctour.indices};
|
||||
}
|
||||
|
||||
/// Mesh from an evaporating 3D contour
|
||||
inline TriangleMesh mesh(Contour3D&& ctour) {
|
||||
return {std::move(ctour.points), std::move(ctour.indices)};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SLABOILERPLATE_HPP
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
#ifndef SLACOMMON_HPP
|
||||
#define SLACOMMON_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Typedefs from Point.hpp
|
||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// An enum to keep track of where the current points on the ModelObject came from.
|
||||
enum class PointsStatus {
|
||||
NoPoints, // No points were generated so far.
|
||||
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
||||
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
||||
UserModified // User has done some edits.
|
||||
};
|
||||
|
||||
struct SupportPoint
|
||||
{
|
||||
Vec3f pos;
|
||||
float head_front_radius;
|
||||
bool is_new_island;
|
||||
|
||||
SupportPoint()
|
||||
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
|
||||
{}
|
||||
|
||||
SupportPoint(float pos_x,
|
||||
float pos_y,
|
||||
float pos_z,
|
||||
float head_radius,
|
||||
bool new_island)
|
||||
: pos(pos_x, pos_y, pos_z)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
||||
: pos(position)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
|
||||
: pos(data(0), data(1), data(2))
|
||||
, head_front_radius(data(3))
|
||||
, is_new_island(data(4) != 0.f)
|
||||
{}
|
||||
|
||||
bool operator==(const SupportPoint &sp) const
|
||||
{
|
||||
return (pos == sp.pos) && head_front_radius == sp.head_front_radius &&
|
||||
is_new_island == sp.is_new_island;
|
||||
}
|
||||
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
|
||||
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, head_front_radius, is_new_island);
|
||||
}
|
||||
};
|
||||
|
||||
using SupportPoints = std::vector<SupportPoint>;
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
/// alternative (raw) input format for the SLASupportTree
|
||||
class EigenMesh3D {
|
||||
class AABBImpl;
|
||||
|
||||
Eigen::MatrixXd m_V;
|
||||
Eigen::MatrixXi m_F;
|
||||
double m_ground_level = 0, m_gnd_offset = 0;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
public:
|
||||
|
||||
EigenMesh3D(const TriangleMesh&);
|
||||
EigenMesh3D(const EigenMesh3D& other);
|
||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||
|
||||
~EigenMesh3D();
|
||||
|
||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
||||
inline double ground_level_offset() const { return m_gnd_offset; }
|
||||
|
||||
inline const Eigen::MatrixXd& V() const { return m_V; }
|
||||
inline const Eigen::MatrixXi& F() const { return m_F; }
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
double m_t = std::nan("");
|
||||
int m_face_id = -1;
|
||||
const EigenMesh3D *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
friend class EigenMesh3D;
|
||||
|
||||
// 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) {}
|
||||
public:
|
||||
|
||||
// This can create a placeholder object which is invalid (not created
|
||||
// by a query_ray_hit call) but the distance can be preset to
|
||||
// a specific value for distinguishing the placeholder.
|
||||
inline hit_result(double val = std::nan("")): m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
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; }
|
||||
|
||||
// Hit_result can decay into a double as the hit distance.
|
||||
inline operator double() const { return distance(); }
|
||||
|
||||
inline Vec3d normal() const {
|
||||
if(m_face_id < 0 || !is_valid()) return {};
|
||||
auto trindex = m_mesh->m_F.row(m_face_id);
|
||||
const Vec3d& p1 = m_mesh->V().row(trindex(0));
|
||||
const Vec3d& p2 = m_mesh->V().row(trindex(1));
|
||||
const Vec3d& p3 = m_mesh->V().row(trindex(2));
|
||||
Eigen::Vector3d U = p2 - p1;
|
||||
Eigen::Vector3d V = p3 - p1;
|
||||
return U.cross(V).normalized();
|
||||
}
|
||||
|
||||
inline bool is_inside() {
|
||||
return m_face_id >= 0 && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
class si_result {
|
||||
double m_value;
|
||||
int m_fidx;
|
||||
Vec3d m_p;
|
||||
si_result(double val, int i, const Vec3d& c):
|
||||
m_value(val), m_fidx(i), m_p(c) {}
|
||||
friend class EigenMesh3D;
|
||||
public:
|
||||
|
||||
si_result() = delete;
|
||||
|
||||
double value() const { return m_value; }
|
||||
operator double() const { return m_value; }
|
||||
const Vec3d& point_on_mesh() const { return m_p; }
|
||||
int F_idx() const { return m_fidx; }
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
// The signed distance from a point to the mesh. Outputs the distance,
|
||||
// the index of the triangle and the closest point in mesh coordinate space.
|
||||
si_result signed_distance(const Vec3d& p) const;
|
||||
|
||||
bool inside(const Vec3d& p) const;
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
};
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // SLASUPPORTTREE_HPP
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SPATINDEX_HPP
|
||||
#define SPATINDEX_HPP
|
||||
#ifndef SLA_SPATINDEX_HPP
|
||||
#define SLA_SPATINDEX_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
69
src/libslic3r/SLA/SupportPoint.hpp
Normal file
69
src/libslic3r/SLA/SupportPoint.hpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef SLA_SUPPORTPOINT_HPP
|
||||
#define SLA_SUPPORTPOINT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
// An enum to keep track of where the current points on the ModelObject came from.
|
||||
enum class PointsStatus {
|
||||
NoPoints, // No points were generated so far.
|
||||
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
||||
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
||||
UserModified // User has done some edits.
|
||||
};
|
||||
|
||||
struct SupportPoint
|
||||
{
|
||||
Vec3f pos;
|
||||
float head_front_radius;
|
||||
bool is_new_island;
|
||||
|
||||
SupportPoint()
|
||||
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
|
||||
{}
|
||||
|
||||
SupportPoint(float pos_x,
|
||||
float pos_y,
|
||||
float pos_z,
|
||||
float head_radius,
|
||||
bool new_island)
|
||||
: pos(pos_x, pos_y, pos_z)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
||||
: pos(position)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
|
||||
: pos(data(0), data(1), data(2))
|
||||
, head_front_radius(data(3))
|
||||
, is_new_island(data(4) != 0.f)
|
||||
{}
|
||||
|
||||
bool operator==(const SupportPoint &sp) const
|
||||
{
|
||||
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
|
||||
return (pos == sp.pos) && rdiff < float(EPSILON) &&
|
||||
is_new_island == sp.is_new_island;
|
||||
}
|
||||
|
||||
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
|
||||
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, head_front_radius, is_new_island);
|
||||
}
|
||||
};
|
||||
|
||||
using SupportPoints = std::vector<SupportPoint>;
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTPOINT_HPP
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "SLAAutoSupports.hpp"
|
||||
#include "SupportPointGenerator.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||
/*float SupportPointGenerator::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||
{
|
||||
n1.normalize();
|
||||
n2.normalize();
|
||||
|
|
@ -36,7 +36,7 @@ namespace sla {
|
|||
}
|
||||
|
||||
|
||||
float SLAAutoSupports::get_required_density(float angle) const
|
||||
float SupportPointGenerator::get_required_density(float angle) const
|
||||
{
|
||||
// calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
|
||||
// to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle).
|
||||
|
|
@ -44,27 +44,45 @@ float SLAAutoSupports::get_required_density(float angle) const
|
|||
return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle)));
|
||||
}
|
||||
|
||||
float SLAAutoSupports::distance_limit(float angle) const
|
||||
float SupportPointGenerator::distance_limit(float angle) const
|
||||
{
|
||||
return 1./(2.4*get_required_density(angle));
|
||||
}*/
|
||||
|
||||
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights,
|
||||
const Config & config,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
SupportPointGenerator::SupportPointGenerator(
|
||||
const sla::EigenMesh3D &emesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights,
|
||||
const Config & config,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
: SupportPointGenerator(emesh, config, throw_on_cancel, statusfn)
|
||||
{
|
||||
std::random_device rd;
|
||||
m_rng.seed(rd());
|
||||
execute(slices, heights);
|
||||
}
|
||||
|
||||
SupportPointGenerator::SupportPointGenerator(
|
||||
const EigenMesh3D &emesh,
|
||||
const SupportPointGenerator::Config &config,
|
||||
std::function<void ()> throw_on_cancel,
|
||||
std::function<void (int)> statusfn)
|
||||
: m_config(config)
|
||||
, m_emesh(emesh)
|
||||
, m_throw_on_cancel(throw_on_cancel)
|
||||
, m_statusfn(statusfn)
|
||||
{
|
||||
}
|
||||
|
||||
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights)
|
||||
{
|
||||
process(slices, heights);
|
||||
project_onto_mesh(m_output);
|
||||
}
|
||||
|
||||
void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
|
||||
void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
|
||||
{
|
||||
// The function makes sure that all the points are really exactly placed on the mesh.
|
||||
|
||||
|
|
@ -77,36 +95,29 @@ void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points)
|
|||
m_throw_on_cancel();
|
||||
Vec3f& p = points[point_id].pos;
|
||||
// Project the point upward and downward and choose the closer intersection with the mesh.
|
||||
//bool up = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., 1.), m_V, m_F, hit_up);
|
||||
//bool down = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., -1.), m_V, m_F, hit_down);
|
||||
|
||||
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
|
||||
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
|
||||
|
||||
bool up = hit_up.face() != -1;
|
||||
bool down = hit_down.face() != -1;
|
||||
bool up = hit_up.is_hit();
|
||||
bool down = hit_down.is_hit();
|
||||
|
||||
if (!up && !down)
|
||||
continue;
|
||||
|
||||
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
|
||||
//int fid = hit.face();
|
||||
//Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
|
||||
//p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<float>();
|
||||
|
||||
p = p + (hit.distance() * hit.direction()).cast<float>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
||||
static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
||||
const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
||||
std::function<void(void)> throw_on_cancel)
|
||||
{
|
||||
assert(slices.size() == heights.size());
|
||||
|
||||
// Allocate empty layers.
|
||||
std::vector<SLAAutoSupports::MyLayer> layers;
|
||||
std::vector<SupportPointGenerator::MyLayer> layers;
|
||||
layers.reserve(slices.size());
|
||||
for (size_t i = 0; i < slices.size(); ++ i)
|
||||
layers.emplace_back(i, heights[i]);
|
||||
|
|
@ -122,7 +133,7 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
if ((layer_id % 8) == 0)
|
||||
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
||||
throw_on_cancel();
|
||||
SLAAutoSupports::MyLayer &layer = layers[layer_id];
|
||||
SupportPointGenerator::MyLayer &layer = layers[layer_id];
|
||||
const ExPolygons &islands = slices[layer_id];
|
||||
//FIXME WTF?
|
||||
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
|
||||
|
|
@ -143,8 +154,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
if ((layer_id % 2) == 0)
|
||||
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
||||
throw_on_cancel();
|
||||
SLAAutoSupports::MyLayer &layer_above = layers[layer_id];
|
||||
SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1];
|
||||
SupportPointGenerator::MyLayer &layer_above = layers[layer_id];
|
||||
SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
|
||||
//FIXME WTF?
|
||||
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
|
||||
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
|
|
@ -152,8 +163,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
|
||||
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
|
||||
for (SLAAutoSupports::Structure &top : layer_above.islands) {
|
||||
for (SLAAutoSupports::Structure &bottom : layer_below.islands) {
|
||||
for (SupportPointGenerator::Structure &top : layer_above.islands) {
|
||||
for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
|
||||
float overlap_area = top.overlap_area(bottom);
|
||||
if (overlap_area > 0) {
|
||||
top.islands_below.emplace_back(&bottom, overlap_area);
|
||||
|
|
@ -191,13 +202,13 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
return layers;
|
||||
}
|
||||
|
||||
void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
||||
void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
||||
{
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
std::vector<std::pair<ExPolygon, coord_t>> islands;
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
|
||||
std::vector<SLAAutoSupports::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
|
||||
std::vector<SupportPointGenerator::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
|
||||
|
||||
PointGrid3D point_grid;
|
||||
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
|
||||
|
|
@ -206,8 +217,8 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|||
double status = 0;
|
||||
|
||||
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
|
||||
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id];
|
||||
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
||||
SupportPointGenerator::MyLayer *layer_top = &layers[layer_id];
|
||||
SupportPointGenerator::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
||||
std::vector<float> support_force_bottom;
|
||||
if (layer_bottom != nullptr) {
|
||||
support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
|
||||
|
|
@ -263,13 +274,13 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|||
status += increment;
|
||||
m_statusfn(int(std::round(status)));
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
|
||||
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
|
||||
output_expolygons(diff, "diff" + layer_num_str + ".svg");
|
||||
if (!islands.empty())
|
||||
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -449,7 +460,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
|
|||
return out;
|
||||
}
|
||||
|
||||
void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
||||
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
||||
{
|
||||
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
||||
|
||||
|
|
@ -470,9 +481,8 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
|||
float min_spacing = poisson_radius;
|
||||
|
||||
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
||||
std::random_device rd;
|
||||
std::mt19937 rng(rd());
|
||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
|
||||
|
||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng);
|
||||
std::vector<Vec2f> poisson_samples;
|
||||
for (size_t iter = 0; iter < 4; ++ iter) {
|
||||
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
|
||||
|
|
@ -488,7 +498,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
|||
min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
|
||||
}
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
{
|
||||
static int irun = 0;
|
||||
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
|
||||
|
|
@ -503,7 +513,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
|||
|
||||
// assert(! poisson_samples.empty());
|
||||
if (poisson_samples_target < poisson_samples.size()) {
|
||||
std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng);
|
||||
std::shuffle(poisson_samples.begin(), poisson_samples.end(), m_rng);
|
||||
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
|
||||
}
|
||||
for (const Vec2f &pt : poisson_samples) {
|
||||
|
|
@ -528,8 +538,8 @@ void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double
|
|||
pts.erase(endit, pts.end());
|
||||
}
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
void SupportPointGenerator::output_structures(const std::vector<Structure>& structures)
|
||||
{
|
||||
for (unsigned int i=0 ; i<structures.size(); ++i) {
|
||||
std::stringstream ss;
|
||||
|
|
@ -538,7 +548,7 @@ void SLAAutoSupports::output_structures(const std::vector<Structure>& structures
|
|||
}
|
||||
}
|
||||
|
||||
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename)
|
||||
void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const std::string &filename)
|
||||
{
|
||||
BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
|
||||
Slic3r::SVG svg_cummulative(filename, bb);
|
||||
|
|
@ -1,43 +1,50 @@
|
|||
#ifndef SLAAUTOSUPPORTS_HPP_
|
||||
#define SLAAUTOSUPPORTS_HPP_
|
||||
#ifndef SLA_SUPPORTPOINTGENERATOR_HPP
|
||||
#define SLA_SUPPORTPOINTGENERATOR_HPP
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/SLACommon.hpp>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
// #define SLA_AUTOSUPPORTS_DEBUG
|
||||
// #define SLA_SUPPORTPOINTGEN_DEBUG
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
class SLAAutoSupports {
|
||||
class SupportPointGenerator {
|
||||
public:
|
||||
struct Config {
|
||||
float density_relative {1.f};
|
||||
float minimal_distance {1.f};
|
||||
float head_diameter {0.4f};
|
||||
///////////////
|
||||
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
float density_relative {1.f};
|
||||
float minimal_distance {1.f};
|
||||
float head_diameter {0.4f};
|
||||
///////////////
|
||||
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
const std::vector<SupportPoint>& output() const { return m_output; }
|
||||
std::vector<SupportPoint>& output() { return m_output; }
|
||||
|
||||
struct MyLayer;
|
||||
|
||||
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
||||
|
||||
struct MyLayer;
|
||||
|
||||
struct Structure {
|
||||
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) :
|
||||
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
{}
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
{}
|
||||
MyLayer *layer;
|
||||
const ExPolygon* polygon = nullptr;
|
||||
const BoundingBox bbox;
|
||||
|
|
@ -49,24 +56,24 @@ public:
|
|||
float supports_force_this_layer = 0.f;
|
||||
float supports_force_inherited = 0.f;
|
||||
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
std::chrono::milliseconds unique_id;
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
|
||||
struct Link {
|
||||
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
|
||||
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
|
||||
Structure *island;
|
||||
float overlap_area;
|
||||
};
|
||||
|
||||
#ifdef NDEBUG
|
||||
// In release mode, use the optimized container.
|
||||
// In release mode, use the optimized container.
|
||||
boost::container::small_vector<Link, 4> islands_above;
|
||||
boost::container::small_vector<Link, 4> islands_below;
|
||||
#else
|
||||
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
|
||||
std::vector<Link> islands_above;
|
||||
std::vector<Link> islands_below;
|
||||
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
|
||||
std::vector<Link> islands_above;
|
||||
std::vector<Link> islands_below;
|
||||
#endif
|
||||
// Overhangs, that are dangling considerably.
|
||||
ExPolygons dangling_areas;
|
||||
|
|
@ -74,16 +81,16 @@ public:
|
|||
ExPolygons overhangs;
|
||||
// Overhangs, where the surface must slope.
|
||||
ExPolygons overhangs_slopes;
|
||||
float overhangs_area;
|
||||
|
||||
float overhangs_area = 0.f;
|
||||
|
||||
bool overlaps(const Structure &rhs) const {
|
||||
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
|
||||
}
|
||||
float overlap_area(const Structure &rhs) const {
|
||||
double out = 0.;
|
||||
if (this->bbox.overlap(rhs.bbox)) {
|
||||
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
|
||||
for (const Polygon &poly : polys)
|
||||
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
|
||||
for (const Polygon &poly : polys)
|
||||
out += poly.area();
|
||||
}
|
||||
return float(out);
|
||||
|
|
@ -96,13 +103,13 @@ public:
|
|||
}
|
||||
Polygons polygons_below() const {
|
||||
size_t cnt = 0;
|
||||
for (const Link &below : this->islands_below)
|
||||
for (const Link &below : this->islands_below)
|
||||
cnt += 1 + below.island->polygon->holes.size();
|
||||
Polygons out;
|
||||
out.reserve(cnt);
|
||||
for (const Link &below : this->islands_below) {
|
||||
for (const Link &below : this->islands_below) {
|
||||
out.emplace_back(below.island->polygon->contour);
|
||||
append(out, below.island->polygon->holes);
|
||||
append(out, below.island->polygon->holes);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -116,19 +123,19 @@ public:
|
|||
// Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added.
|
||||
float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); }
|
||||
};
|
||||
|
||||
|
||||
struct MyLayer {
|
||||
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
|
||||
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
|
||||
size_t layer_id;
|
||||
coordf_t print_z;
|
||||
std::vector<Structure> islands;
|
||||
};
|
||||
|
||||
|
||||
struct RichSupportPoint {
|
||||
Vec3f position;
|
||||
Structure *island;
|
||||
};
|
||||
|
||||
|
||||
struct PointGrid3D {
|
||||
struct GridHash {
|
||||
std::size_t operator()(const Vec3i &cell_id) const {
|
||||
|
|
@ -136,23 +143,23 @@ public:
|
|||
}
|
||||
};
|
||||
typedef std::unordered_multimap<Vec3i, RichSupportPoint, GridHash> Grid;
|
||||
|
||||
|
||||
Vec3f cell_size;
|
||||
Grid grid;
|
||||
|
||||
|
||||
Vec3i cell_id(const Vec3f &pos) {
|
||||
return Vec3i(int(floor(pos.x() / cell_size.x())),
|
||||
int(floor(pos.y() / cell_size.y())),
|
||||
int(floor(pos.z() / cell_size.z())));
|
||||
}
|
||||
|
||||
|
||||
void insert(const Vec2f &pos, Structure *island) {
|
||||
RichSupportPoint pt;
|
||||
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
pt.island = island;
|
||||
grid.emplace(cell_id(pt.position), pt);
|
||||
}
|
||||
|
||||
|
||||
bool collides_with(const Vec2f &pos, Structure *island, float radius) {
|
||||
Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
Vec3i cell = cell_id(pos3d);
|
||||
|
|
@ -170,41 +177,45 @@ public:
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) {
|
||||
for (Grid::const_iterator it = it_begin; it != it_end; ++ it) {
|
||||
float dist2 = (it->second.position - pos).squaredNorm();
|
||||
float dist2 = (it->second.position - pos).squaredNorm();
|
||||
if (dist2 < radius * radius)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void execute(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights);
|
||||
|
||||
void seed(std::mt19937::result_type s) { m_rng.seed(s); }
|
||||
private:
|
||||
std::vector<sla::SupportPoint> m_output;
|
||||
|
||||
SLAAutoSupports::Config m_config;
|
||||
|
||||
std::vector<SupportPoint> m_output;
|
||||
|
||||
SupportPointGenerator::Config m_config;
|
||||
|
||||
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
||||
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
|
||||
void project_onto_mesh(std::vector<sla::SupportPoint>& points) const;
|
||||
void project_onto_mesh(std::vector<SupportPoint>& points) const;
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
|
||||
static void output_structures(const std::vector<Structure> &structures);
|
||||
#endif // SLA_AUTOSUPPORTS_DEBUG
|
||||
|
||||
const sla::EigenMesh3D& m_emesh;
|
||||
#endif // SLA_SUPPORTPOINTGEN_DEBUG
|
||||
|
||||
const EigenMesh3D& m_emesh;
|
||||
std::function<void(void)> m_throw_on_cancel;
|
||||
std::function<void(int)> m_statusfn;
|
||||
|
||||
std::mt19937 m_rng;
|
||||
};
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
|
||||
#endif // SLAAUTOSUPPORTS_HPP_
|
||||
#endif // SUPPORTPOINTGENERATOR_HPP
|
||||
|
|
@ -4,10 +4,10 @@
|
|||
*/
|
||||
|
||||
#include <numeric>
|
||||
#include "SLASupportTree.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
|
|
@ -61,7 +61,7 @@ std::vector<ExPolygons> SupportTree::slice(
|
|||
slices.emplace_back();
|
||||
|
||||
TriangleMeshSlicer sup_slicer(&sup_mesh);
|
||||
sup_slicer.slice(grid, cr, &slices.back(), ctl().cancelfn);
|
||||
sup_slicer.slice(grid, SlicingMode::Regular, cr, &slices.back(), ctl().cancelfn);
|
||||
}
|
||||
|
||||
if (!pad_mesh.empty()) {
|
||||
|
|
@ -75,7 +75,7 @@ std::vector<ExPolygons> SupportTree::slice(
|
|||
std::copy(grid.begin(), maxzit, std::back_inserter(padgrid));
|
||||
|
||||
TriangleMeshSlicer pad_slicer(&pad_mesh);
|
||||
pad_slicer.slice(padgrid, cr, &slices.back(), ctl().cancelfn);
|
||||
pad_slicer.slice(padgrid, SlicingMode::Regular, cr, &slices.back(), ctl().cancelfn);
|
||||
}
|
||||
|
||||
size_t len = grid.size();
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
#ifndef SLASUPPORTTREE_HPP
|
||||
#define SLASUPPORTTREE_HPP
|
||||
#ifndef SLA_SUPPORTTREE_HPP
|
||||
#define SLA_SUPPORTTREE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
#include "SLAPad.hpp"
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
|
@ -105,27 +108,6 @@ struct SupportConfig
|
|||
|
||||
enum class MeshType { Support, Pad };
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
struct JobController
|
||||
{
|
||||
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||
using StopCond = std::function<bool(void)>;
|
||||
using CancelFn = std::function<void(void)>;
|
||||
|
||||
// This will signal the status of the calculation to the front-end
|
||||
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||
|
||||
// Returns true if the calculation should be aborted.
|
||||
StopCond stopcondition = [](){ return false; };
|
||||
|
||||
// Similar to cancel callback. This should check the stop condition and
|
||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||
// terminate itself
|
||||
CancelFn cancelfn = [](){};
|
||||
};
|
||||
|
||||
struct SupportableMesh
|
||||
{
|
||||
EigenMesh3D emesh;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include "SLASupportTreeBuildsteps.hpp"
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
|
@ -12,7 +13,7 @@ Contour3D sphere(double rho, Portion portion, double fa) {
|
|||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||
|
||||
auto& vertices = ret.points;
|
||||
auto& facets = ret.indices;
|
||||
auto& facets = ret.faces3;
|
||||
|
||||
// Algorithm:
|
||||
// Add points one-by-one to the sphere grid and form facets using relative
|
||||
|
|
@ -102,7 +103,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
|||
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.indices;
|
||||
auto& indices = ret.faces3;
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
|
|
@ -211,8 +212,8 @@ Head::Head(double r_big_mm,
|
|||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||
|
||||
mesh.indices.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.indices.emplace_back(i1s1, i2s2, i1s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||
}
|
||||
|
||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||
|
|
@ -220,8 +221,8 @@ Head::Head(double r_big_mm,
|
|||
auto i1s2 = coord_t(s1.points.size());
|
||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||
|
||||
mesh.indices.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.indices.emplace_back(i1s2, i2s2, i1s1);
|
||||
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
// To simplify further processing, we translate the mesh so that the
|
||||
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
||||
|
|
@ -240,7 +241,7 @@ Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
|
|||
// move the data.
|
||||
Contour3D body = cylinder(radius, height, st, endp);
|
||||
mesh.points.swap(body.points);
|
||||
mesh.indices.swap(body.indices);
|
||||
mesh.faces3.swap(body.faces3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +276,7 @@ Pillar &Pillar::add_base(double baseheight, double radius)
|
|||
base.points.emplace_back(endpt);
|
||||
base.points.emplace_back(ep);
|
||||
|
||||
auto& indices = base.indices;
|
||||
auto& indices = base.faces3;
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
|
|
@ -466,7 +467,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
|||
return m_meshcache;
|
||||
}
|
||||
|
||||
m_meshcache = mesh(merged);
|
||||
m_meshcache = to_triangle_mesh(merged);
|
||||
|
||||
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
|
||||
// which will need this.
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
#ifndef SUPPORTTREEBUILDER_HPP
|
||||
#define SUPPORTTREEBUILDER_HPP
|
||||
#ifndef SLA_SUPPORTTREEBUILDER_HPP
|
||||
#define SLA_SUPPORTTREEBUILDER_HPP
|
||||
|
||||
#include "SLAConcurrency.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASupportTree.hpp"
|
||||
#include "SLAPad.hpp"
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -73,7 +74,7 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
|||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0});
|
||||
Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
|
||||
|
||||
const constexpr long ID_UNSET = -1;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include "SLASupportTreeBuildsteps.hpp"
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
|
|
@ -7,8 +7,16 @@
|
|||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::SubplexOptimizer;
|
||||
|
||||
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||
const SupportableMesh &sm)
|
||||
const SupportableMesh &sm)
|
||||
: m_cfg(sm.cfg)
|
||||
, m_mesh(sm.emesh)
|
||||
, m_support_pts(sm.pts)
|
||||
|
|
@ -30,7 +38,7 @@ SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
|||
}
|
||||
|
||||
bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
|
||||
const SupportableMesh &sm)
|
||||
const SupportableMesh &sm)
|
||||
{
|
||||
if(sm.pts.empty()) return false;
|
||||
|
||||
|
|
@ -158,190 +166,182 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
|
|||
return pc == ABORT;
|
||||
}
|
||||
|
||||
// Give points on a 3D ring with given center, radius and orientation
|
||||
// method based on:
|
||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||
template<size_t N>
|
||||
class PointRing {
|
||||
std::array<double, N> m_phis;
|
||||
|
||||
// Two vectors that will be perpendicular to each other and to the
|
||||
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
||||
// placeholder.
|
||||
// a and b vectors are perpendicular to the ring direction and to each other.
|
||||
// Together they define the plane where we have to iterate with the
|
||||
// given angles in the 'm_phis' vector
|
||||
Vec3d a = {0, 1, 0}, b;
|
||||
double m_radius = 0.;
|
||||
|
||||
static inline bool constexpr is_one(double val)
|
||||
{
|
||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
PointRing(const Vec3d &n)
|
||||
{
|
||||
m_phis = linspace_array<N>(0., 2 * PI);
|
||||
|
||||
// We have to address the case when the direction vector v (same as
|
||||
// dir) is coincident with one of the world axes. In this case two of
|
||||
// its components will be completely zero and one is 1.0. Our method
|
||||
// becomes dangerous here due to division with zero. Instead, vector
|
||||
// 'a' can be an element-wise rotated version of 'v'
|
||||
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
|
||||
a = {n(Z), n(X), n(Y)};
|
||||
b = {n(Y), n(Z), n(X)};
|
||||
}
|
||||
else {
|
||||
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
|
||||
b = a.cross(n);
|
||||
}
|
||||
}
|
||||
|
||||
Vec3d get(size_t idx, const Vec3d src, double r) const
|
||||
{
|
||||
double phi = m_phis[idx];
|
||||
double sinphi = std::sin(phi);
|
||||
double cosphi = std::cos(phi);
|
||||
|
||||
double rpscos = r * cosphi;
|
||||
double rpssin = r * sinphi;
|
||||
|
||||
// Point on the sphere
|
||||
return {src(X) + rpscos * a(X) + rpssin * b(X),
|
||||
src(Y) + rpscos * a(Y) + rpssin * b(Y),
|
||||
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
|
||||
}
|
||||
};
|
||||
|
||||
template<class C, class Hit = EigenMesh3D::hit_result>
|
||||
static Hit min_hit(const C &hits)
|
||||
{
|
||||
auto mit = std::min_element(hits.begin(), hits.end(),
|
||||
[](const Hit &h1, const Hit &h2) {
|
||||
return h1.distance() < h2.distance();
|
||||
});
|
||||
|
||||
return *mit;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
|
||||
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
|
||||
{
|
||||
static const size_t SAMPLES = 8;
|
||||
|
||||
// method based on:
|
||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||
// Move away slightly from the touching point to avoid raycasting on the
|
||||
// inner surface of the mesh.
|
||||
|
||||
const double& sd = m_cfg.safety_distance_mm;
|
||||
|
||||
auto& m = m_mesh;
|
||||
using HitResult = EigenMesh3D::hit_result;
|
||||
|
||||
// Hit results
|
||||
std::array<HitResult, SAMPLES> hits;
|
||||
|
||||
struct Rings {
|
||||
double rpin;
|
||||
double rback;
|
||||
Vec3d spin;
|
||||
Vec3d sback;
|
||||
PointRing<SAMPLES> ring;
|
||||
|
||||
Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
|
||||
Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
|
||||
} rings {r_pin + sd, r_back + sd, s, s + width * dir, dir};
|
||||
|
||||
// We will shoot multiple rays from the head pinpoint in the direction
|
||||
// of the pinhead robe (side) surface. The result will be the smallest
|
||||
// hit distance.
|
||||
|
||||
// Move away slightly from the touching point to avoid raycasting on the
|
||||
// inner surface of the mesh.
|
||||
Vec3d v = dir; // Our direction (axis)
|
||||
Vec3d c = s + width * dir;
|
||||
const double& sd = m_cfg.safety_distance_mm;
|
||||
ccr::enumerate(hits.begin(), hits.end(),
|
||||
[&m, &rings, sd](HitResult &hit, size_t i) {
|
||||
|
||||
// Two vectors that will be perpendicular to each other and to the
|
||||
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
||||
// placeholder.
|
||||
Vec3d a(0, 1, 0), b;
|
||||
// Point on the circle on the pin sphere
|
||||
Vec3d ps = rings.pinring(i);
|
||||
// This is the point on the circle on the back sphere
|
||||
Vec3d p = rings.backring(i);
|
||||
|
||||
// Point ps is not on mesh but can be inside or
|
||||
// outside as well. This would cause many problems
|
||||
// with ray-casting. To detect the position we will
|
||||
// use the ray-casting result (which has an is_inside
|
||||
// predicate).
|
||||
|
||||
// The portions of the circle (the head-back circle) for which we will
|
||||
// shoot rays.
|
||||
std::array<double, SAMPLES> phis;
|
||||
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
|
||||
Vec3d n = (p - ps).normalized();
|
||||
auto q = m.query_ray_hit(ps + sd * n, n);
|
||||
|
||||
auto& m = m_mesh;
|
||||
using HitResult = EigenMesh3D::hit_result;
|
||||
if (q.is_inside()) { // the hit is inside the model
|
||||
if (q.distance() > rings.rpin) {
|
||||
// If we are inside the model and the hit
|
||||
// distance is bigger than our pin circle
|
||||
// diameter, it probably indicates that the
|
||||
// support point was already inside the
|
||||
// model, or there is really no space
|
||||
// around the point. We will assign a zero
|
||||
// hit distance to these cases which will
|
||||
// enforce the function return value to be
|
||||
// an invalid ray with zero hit distance.
|
||||
// (see min_element at the end)
|
||||
hit = HitResult(0.0);
|
||||
} else {
|
||||
// re-cast the ray from the outside of the
|
||||
// object. The starting point has an offset
|
||||
// of 2*safety_distance because the
|
||||
// original ray has also had an offset
|
||||
auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
|
||||
hit = q2;
|
||||
}
|
||||
} else
|
||||
hit = q;
|
||||
});
|
||||
|
||||
// Hit results
|
||||
std::array<HitResult, SAMPLES> hits;
|
||||
|
||||
// We have to address the case when the direction vector v (same as
|
||||
// dir) is coincident with one of the world axes. In this case two of
|
||||
// its components will be completely zero and one is 1.0. Our method
|
||||
// becomes dangerous here due to division with zero. Instead, vector
|
||||
// 'a' can be an element-wise rotated version of 'v'
|
||||
auto chk1 = [] (double val) {
|
||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
||||
};
|
||||
|
||||
if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) {
|
||||
a = {v(Z), v(X), v(Y)};
|
||||
b = {v(Y), v(Z), v(X)};
|
||||
}
|
||||
else {
|
||||
a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize();
|
||||
b = a.cross(v);
|
||||
}
|
||||
|
||||
// Now a and b vectors are perpendicular to v and to each other.
|
||||
// Together they define the plane where we have to iterate with the
|
||||
// given angles in the 'phis' vector
|
||||
ccr::enumerate(
|
||||
phis.begin(), phis.end(),
|
||||
[&hits, &m, sd, r_pin, r_back, s, a, b, c](double phi, size_t i) {
|
||||
double sinphi = std::sin(phi);
|
||||
double cosphi = std::cos(phi);
|
||||
|
||||
// Let's have a safety coefficient for the radiuses.
|
||||
double rpscos = (sd + r_pin) * cosphi;
|
||||
double rpssin = (sd + r_pin) * sinphi;
|
||||
double rpbcos = (sd + r_back) * cosphi;
|
||||
double rpbsin = (sd + r_back) * sinphi;
|
||||
|
||||
// Point on the circle on the pin sphere
|
||||
Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X),
|
||||
s(Y) + rpscos * a(Y) + rpssin * b(Y),
|
||||
s(Z) + rpscos * a(Z) + rpssin * b(Z));
|
||||
|
||||
// Point ps is not on mesh but can be inside or
|
||||
// outside as well. This would cause many problems
|
||||
// with ray-casting. To detect the position we will
|
||||
// use the ray-casting result (which has an is_inside
|
||||
// predicate).
|
||||
|
||||
// This is the point on the circle on the back sphere
|
||||
Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X),
|
||||
c(Y) + rpbcos * a(Y) + rpbsin * b(Y),
|
||||
c(Z) + rpbcos * a(Z) + rpbsin * b(Z));
|
||||
|
||||
Vec3d n = (p - ps).normalized();
|
||||
auto q = m.query_ray_hit(ps + sd * n, n);
|
||||
|
||||
if (q.is_inside()) { // the hit is inside the model
|
||||
if (q.distance() > r_pin + sd) {
|
||||
// If we are inside the model and the hit
|
||||
// distance is bigger than our pin circle
|
||||
// diameter, it probably indicates that the
|
||||
// support point was already inside the
|
||||
// model, or there is really no space
|
||||
// around the point. We will assign a zero
|
||||
// hit distance to these cases which will
|
||||
// enforce the function return value to be
|
||||
// an invalid ray with zero hit distance.
|
||||
// (see min_element at the end)
|
||||
hits[i] = HitResult(0.0);
|
||||
} else {
|
||||
// re-cast the ray from the outside of the
|
||||
// object. The starting point has an offset
|
||||
// of 2*safety_distance because the
|
||||
// original ray has also had an offset
|
||||
auto q2 = m.query_ray_hit(
|
||||
ps + (q.distance() + 2 * sd) * n, n);
|
||||
hits[i] = q2;
|
||||
}
|
||||
} else
|
||||
hits[i] = q;
|
||||
});
|
||||
|
||||
auto mit = std::min_element(hits.begin(), hits.end());
|
||||
|
||||
return *mit;
|
||||
return min_hit(hits);
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
|
||||
const Vec3d &s, const Vec3d &dir, double r, bool ins_check)
|
||||
const Vec3d &src, const Vec3d &dir, double r, bool ins_check)
|
||||
{
|
||||
static const size_t SAMPLES = 8;
|
||||
PointRing<SAMPLES> ring{dir};
|
||||
|
||||
// helper vector calculations
|
||||
Vec3d a(0, 1, 0), b;
|
||||
const double& sd = m_cfg.safety_distance_mm;
|
||||
|
||||
// INFO: for explanation of the method used here, see the previous
|
||||
// method's comments.
|
||||
|
||||
auto chk1 = [] (double val) {
|
||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
||||
};
|
||||
|
||||
if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) {
|
||||
a = {dir(Z), dir(X), dir(Y)};
|
||||
b = {dir(Y), dir(Z), dir(X)};
|
||||
}
|
||||
else {
|
||||
a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize();
|
||||
b = a.cross(dir);
|
||||
}
|
||||
|
||||
// circle portions
|
||||
std::array<double, SAMPLES> phis;
|
||||
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
|
||||
|
||||
auto& m = m_mesh;
|
||||
using HitResult = EigenMesh3D::hit_result;
|
||||
using Hit = EigenMesh3D::hit_result;
|
||||
|
||||
// Hit results
|
||||
std::array<HitResult, SAMPLES> hits;
|
||||
std::array<Hit, SAMPLES> hits;
|
||||
|
||||
ccr::enumerate(
|
||||
phis.begin(), phis.end(),
|
||||
[&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) {
|
||||
double sinphi = std::sin(phi);
|
||||
double cosphi = std::cos(phi);
|
||||
|
||||
// Let's have a safety coefficient for the radiuses.
|
||||
double rcos = (sd + r) * cosphi;
|
||||
double rsin = (sd + r) * sinphi;
|
||||
|
||||
// Point on the circle on the pin sphere
|
||||
Vec3d p (s(X) + rcos * a(X) + rsin * b(X),
|
||||
s(Y) + rcos * a(Y) + rsin * b(Y),
|
||||
s(Z) + rcos * a(Z) + rsin * b(Z));
|
||||
|
||||
auto hr = m.query_ray_hit(p + sd*dir, dir);
|
||||
|
||||
if(ins_check && hr.is_inside()) {
|
||||
if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0);
|
||||
else {
|
||||
// re-cast the ray from the outside of the object
|
||||
auto hr2 =
|
||||
m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir);
|
||||
|
||||
hits[i] = hr2;
|
||||
}
|
||||
} else hits[i] = hr;
|
||||
});
|
||||
ccr::enumerate(hits.begin(), hits.end(),
|
||||
[this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) {
|
||||
|
||||
const double sd = m_cfg.safety_distance_mm;
|
||||
|
||||
// Point on the circle on the pin sphere
|
||||
Vec3d p = ring.get(i, src, r + sd);
|
||||
|
||||
auto hr = m_mesh.query_ray_hit(p + sd * dir, dir);
|
||||
|
||||
if(ins_check && hr.is_inside()) {
|
||||
if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
|
||||
else {
|
||||
// re-cast the ray from the outside of the object
|
||||
hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
|
||||
}
|
||||
} else hit = hr;
|
||||
});
|
||||
|
||||
auto mit = std::min_element(hits.begin(), hits.end());
|
||||
|
||||
return *mit;
|
||||
return min_hit(hits);
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
||||
|
|
@ -411,7 +411,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
|||
|
||||
// TODO: This is a workaround to not have a faulty last bridge
|
||||
while(ej(Z) >= eupper(Z) /*endz*/) {
|
||||
if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
|
||||
if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
|
||||
{
|
||||
m_builder.add_crossbridge(sj, ej, pillar.r);
|
||||
was_connected = true;
|
||||
|
|
@ -422,7 +422,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
|
|||
Vec3d sjback(ej(X), ej(Y), sj(Z));
|
||||
Vec3d ejback(sj(X), sj(Y), ej(Z));
|
||||
if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
|
||||
bridge_mesh_intersect(sjback, dirv(sjback, ejback),
|
||||
bridge_mesh_distance(sjback, dirv(sjback, ejback),
|
||||
pillar.r) >= bridge_distance) {
|
||||
// need to check collision for the cross stick
|
||||
m_builder.add_crossbridge(sjback, ejback, pillar.r);
|
||||
|
|
@ -479,7 +479,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
|||
bridgestart(Z) -= zdiff;
|
||||
touchjp(Z) = Zdown;
|
||||
|
||||
double t = bridge_mesh_intersect(headjp, {0,0,-1}, r);
|
||||
double t = bridge_mesh_distance(headjp, DOWN, r);
|
||||
|
||||
// We can't insert a pillar under the source head to connect
|
||||
// with the nearby pillar's starting junction
|
||||
|
|
@ -497,8 +497,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
|
|||
double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm;
|
||||
if(bridgeend(Z) < minz) return false;
|
||||
|
||||
double t = bridge_mesh_intersect(bridgestart,
|
||||
dirv(bridgestart, bridgeend), r);
|
||||
double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
|
||||
|
||||
// Cannot insert the bridge. (further search might not worth the hassle)
|
||||
if(t < distance(bridgestart, bridgeend)) return false;
|
||||
|
|
@ -560,9 +559,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
double radius,
|
||||
long head_id)
|
||||
{
|
||||
// People were killed for this number (seriously)
|
||||
static const double SQR2 = std::sqrt(2.0);
|
||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
|
||||
|
||||
double gndlvl = m_builder.ground_level;
|
||||
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
||||
|
|
@ -573,38 +570,47 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
bool can_add_base = true;
|
||||
bool normal_mode = true;
|
||||
|
||||
// If in zero elevation mode and the pillar is too close to the model body,
|
||||
// the support pillar can not be placed in the gap between the model and
|
||||
// the pad, and the pillar bases must not touch the model body either.
|
||||
// To solve this, a corrector bridge is inserted between the starting point
|
||||
// (jp) and the new pillar.
|
||||
if (m_cfg.object_elevation_mm < EPSILON
|
||||
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
||||
// Get the distance from the mesh. This can be later optimized
|
||||
// to get the distance in 2D plane because we are dealing with
|
||||
// the ground level only.
|
||||
|
||||
normal_mode = false;
|
||||
|
||||
// The min distance needed to move away from the model in XY plane.
|
||||
double current_d = min_dist - dist;
|
||||
double current_bride_d = SLOPE * current_d;
|
||||
|
||||
// get a suitable direction for the corrector bridge. It is the
|
||||
// original sourcedir's azimuth but the polar angle is saturated to the
|
||||
// configured bridge slope.
|
||||
auto [polar, azimuth] = dir_to_spheric(sourcedir);
|
||||
polar = PI - m_cfg.bridge_slope;
|
||||
auto dir = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
normal_mode = false;
|
||||
double mind = min_dist - dist;
|
||||
double azimuth = std::atan2(sourcedir(Y), sourcedir(X));
|
||||
double sinpolar = std::sin(PI - m_cfg.bridge_slope);
|
||||
double cospolar = std::cos(PI - m_cfg.bridge_slope);
|
||||
double cosazm = std::cos(azimuth);
|
||||
double sinazm = std::sin(azimuth);
|
||||
|
||||
auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar)
|
||||
.normalized();
|
||||
|
||||
using namespace libnest2d::opt;
|
||||
StopCriteria scr;
|
||||
scr.stop_score = min_dist;
|
||||
SubplexOptimizer solver(scr);
|
||||
|
||||
// Search for a distance along the corrector bridge to move the endpoint
|
||||
// sufficiently away form the model body. The first few optimization
|
||||
// cycles should succeed here.
|
||||
auto result = solver.optimize_max(
|
||||
[this, dir, jp, gndlvl](double mv) {
|
||||
Vec3d endpt = jp + SQR2 * mv * dir;
|
||||
Vec3d endpt = jp + mv * dir;
|
||||
endpt(Z) = gndlvl;
|
||||
return std::sqrt(m_mesh.squared_distance(endpt));
|
||||
},
|
||||
initvals(mind), bound(0.0, 2 * min_dist));
|
||||
initvals(current_bride_d),
|
||||
bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d));
|
||||
|
||||
mind = std::get<0>(result.optimum);
|
||||
endp = jp + SQR2 * mind * dir;
|
||||
endp = jp + std::get<0>(result.optimum) * dir;
|
||||
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
||||
can_add_base = result.score > min_dist;
|
||||
|
||||
|
|
@ -618,12 +624,12 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
};
|
||||
|
||||
// We have to check if the bridge is feasible.
|
||||
if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm())
|
||||
if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm())
|
||||
abort_in_shame();
|
||||
else {
|
||||
// If the new endpoint is below ground, do not make a pillar
|
||||
if (endp(Z) < gndlvl)
|
||||
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
|
||||
endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
|
||||
else {
|
||||
|
||||
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
|
||||
|
|
@ -685,11 +691,6 @@ void SupportTreeBuildsteps::filter()
|
|||
// not be enough space for the pinhead. Filtering is applied for
|
||||
// these reasons.
|
||||
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
|
||||
ccr::SpinningMutex mutex;
|
||||
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||
|
|
@ -708,10 +709,7 @@ void SupportTreeBuildsteps::filter()
|
|||
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
||||
// arrow head.
|
||||
|
||||
double z = n(2);
|
||||
double r = 1.0; // for normalized vector
|
||||
double polar = std::acos(z / r);
|
||||
double azimuth = std::atan2(n(1), n(0));
|
||||
auto [polar, azimuth] = dir_to_spheric(n);
|
||||
|
||||
// skip if the tilt is not sane
|
||||
if(polar >= PI - m_cfg.normal_cutoff_angle) {
|
||||
|
|
@ -729,9 +727,7 @@ void SupportTreeBuildsteps::filter()
|
|||
double pin_r = double(m_support_pts[fidx].head_front_radius);
|
||||
|
||||
// Reassemble the now corrected normal
|
||||
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar),
|
||||
std::cos(polar)).normalized();
|
||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
// check available distance
|
||||
EigenMesh3D::hit_result t
|
||||
|
|
@ -757,28 +753,23 @@ void SupportTreeBuildsteps::filter()
|
|||
auto oresult = solver.optimize_max(
|
||||
[this, pin_r, w, hp](double plr, double azm)
|
||||
{
|
||||
auto dir = Vec3d(std::cos(azm) * std::sin(plr),
|
||||
std::sin(azm) * std::sin(plr),
|
||||
std::cos(plr)).normalized();
|
||||
auto dir = spheric_to_dir(plr, azm).normalized();
|
||||
|
||||
double score = pinhead_mesh_intersect(
|
||||
double score = pinhead_mesh_distance(
|
||||
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
||||
|
||||
return score;
|
||||
},
|
||||
initvals(polar, azimuth), // start with what we have
|
||||
bound(3 * PI / 4,
|
||||
PI), // Must not exceed the tilt limit
|
||||
bound(3 * PI / 4, PI), // Must not exceed the tilt limit
|
||||
bound(-PI, PI) // azimuth can be a full search
|
||||
);
|
||||
|
||||
if(oresult.score > w) {
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
azimuth = std::get<1>(oresult.optimum);
|
||||
nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar),
|
||||
std::cos(polar)).normalized();
|
||||
t = oresult.score;
|
||||
nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
t = EigenMesh3D::hit_result(oresult.score);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -837,16 +828,17 @@ void SupportTreeBuildsteps::classify()
|
|||
m_thr();
|
||||
|
||||
auto& head = m_builder.head(i);
|
||||
Vec3d n(0, 0, -1);
|
||||
double r = head.r_back_mm;
|
||||
Vec3d headjp = head.junction_point();
|
||||
|
||||
// collision check
|
||||
auto hit = bridge_mesh_intersect(headjp, n, r);
|
||||
auto hit = bridge_mesh_intersect(headjp, DOWN, r);
|
||||
|
||||
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
|
||||
else if(m_cfg.ground_facing_only) head.invalidate();
|
||||
else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
|
||||
else m_iheads_onmodel.emplace_back(i);
|
||||
|
||||
m_head_to_ground_scans[i] = hit;
|
||||
}
|
||||
|
||||
// We want to search for clusters of points that are far enough
|
||||
|
|
@ -893,13 +885,14 @@ void SupportTreeBuildsteps::routing_to_ground()
|
|||
// get the current cluster centroid
|
||||
auto & thr = m_thr;
|
||||
const auto &points = m_points;
|
||||
long lcid = cluster_centroid(
|
||||
|
||||
long lcid = cluster_centroid(
|
||||
cl, [&points](size_t idx) { return points.row(long(idx)); },
|
||||
[thr](const Vec3d &p1, const Vec3d &p2) {
|
||||
thr();
|
||||
return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
|
||||
});
|
||||
|
||||
|
||||
assert(lcid >= 0);
|
||||
unsigned hid = cl[size_t(lcid)]; // Head ID
|
||||
|
||||
|
|
@ -944,192 +937,138 @@ void SupportTreeBuildsteps::routing_to_ground()
|
|||
}
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
|
||||
{
|
||||
auto hjp = head.junction_point();
|
||||
double r = head.r_back_mm;
|
||||
double t = bridge_mesh_distance(hjp, dir, head.r_back_mm);
|
||||
double d = 0, tdown = 0;
|
||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||
|
||||
while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r)))
|
||||
d += r;
|
||||
|
||||
if(!std::isinf(tdown)) return false;
|
||||
|
||||
Vec3d endp = hjp + d * dir;
|
||||
m_builder.add_bridge(head.id, endp);
|
||||
m_builder.add_junction(endp, head.r_back_mm);
|
||||
|
||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
||||
{
|
||||
if (connect_to_ground(head, head.dir)) return true;
|
||||
|
||||
// Optimize bridge direction:
|
||||
// Straight path failed so we will try to search for a suitable
|
||||
// direction out of the cavity.
|
||||
auto [polar, azimuth] = dir_to_spheric(head.dir);
|
||||
|
||||
StopCriteria stc;
|
||||
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
||||
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
|
||||
stc.stop_score = 1e6;
|
||||
GeneticOptimizer solver(stc);
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
|
||||
double r_back = head.r_back_mm;
|
||||
Vec3d hjp = head.junction_point();
|
||||
auto oresult = solver.optimize_max(
|
||||
[this, hjp, r_back](double plr, double azm) {
|
||||
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
||||
return bridge_mesh_distance(hjp, n, r_back);
|
||||
},
|
||||
initvals(polar, azimuth), // let's start with what we have
|
||||
bound(3*PI/4, PI), // Must not exceed the slope limit
|
||||
bound(-PI, PI) // azimuth can be a full range search
|
||||
);
|
||||
|
||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||
return connect_to_ground(head, bridgedir);
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
|
||||
{
|
||||
if (head.id <= ID_UNSET) return false;
|
||||
|
||||
auto it = m_head_to_ground_scans.find(unsigned(head.id));
|
||||
if (it == m_head_to_ground_scans.end()) return false;
|
||||
|
||||
auto &hit = it->second;
|
||||
Vec3d hjp = head.junction_point();
|
||||
double zangle = std::asin(hit.direction()(Z));
|
||||
zangle = std::max(zangle, PI/4);
|
||||
double h = std::sin(zangle) * head.fullwidth();
|
||||
|
||||
// The width of the tail head that we would like to have...
|
||||
h = std::min(hit.distance() - head.r_back_mm, h);
|
||||
|
||||
if(h <= 0.) return false;
|
||||
|
||||
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
||||
auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
|
||||
|
||||
double hitdiff = center_hit.distance() - hit.distance();
|
||||
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
||||
center_hit.position() : hit.position();
|
||||
|
||||
head.transform();
|
||||
|
||||
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
||||
Pillar &pill = m_builder.pillar(pillar_id);
|
||||
|
||||
Vec3d taildir = endp - hitp;
|
||||
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
|
||||
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
|
||||
|
||||
if (w < 0.) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
|
||||
w = 0.;
|
||||
}
|
||||
|
||||
Head tailhead(head.r_back_mm, head.r_pin_mm, w,
|
||||
m_cfg.head_penetration_mm, taildir, hitp);
|
||||
|
||||
tailhead.transform();
|
||||
pill.base = tailhead.mesh;
|
||||
|
||||
m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SupportTreeBuildsteps::routing_to_model()
|
||||
{
|
||||
// We need to check if there is an easy way out to the bed surface.
|
||||
// If it can be routed there with a bridge shorter than
|
||||
// min_bridge_distance.
|
||||
|
||||
// First we want to index the available pillars. The best is to connect
|
||||
// these points to the available pillars
|
||||
|
||||
auto routedown = [this](Head& head, const Vec3d& dir, double dist)
|
||||
{
|
||||
head.transform();
|
||||
Vec3d endp = head.junction_point() + dist * dir;
|
||||
m_builder.add_bridge(head.id, endp);
|
||||
m_builder.add_junction(endp, head.r_back_mm);
|
||||
|
||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||
};
|
||||
|
||||
std::vector<unsigned> modelpillars;
|
||||
ccr::SpinningMutex mutex;
|
||||
|
||||
auto onmodelfn =
|
||||
[this, routedown, &modelpillars, &mutex]
|
||||
(const std::pair<unsigned, EigenMesh3D::hit_result> &el, size_t)
|
||||
{
|
||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
|
||||
[this] (const unsigned idx, size_t) {
|
||||
m_thr();
|
||||
unsigned idx = el.first;
|
||||
EigenMesh3D::hit_result hit = el.second;
|
||||
|
||||
auto& head = m_builder.head(idx);
|
||||
Vec3d hjp = head.junction_point();
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Search nearby pillar
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
if(search_pillar_and_connect(head)) { head.transform(); return; }
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Try straight path
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Cannot connect to nearby pillar. We will try to search for
|
||||
// a route to the ground.
|
||||
if(connect_to_ground(head)) { head.transform(); return; }
|
||||
|
||||
double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm);
|
||||
double d = 0, tdown = 0;
|
||||
Vec3d dirdown(0.0, 0.0, -1.0);
|
||||
|
||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||
|
||||
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
|
||||
hjp + d*head.dir,
|
||||
dirdown, head.r_back_mm))) {
|
||||
d += head.r_back_mm;
|
||||
}
|
||||
|
||||
if(std::isinf(tdown)) { // we heave found a route to the ground
|
||||
routedown(head, head.dir, d); return;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Optimize bridge direction
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Straight path failed so we will try to search for a suitable
|
||||
// direction out of the cavity.
|
||||
|
||||
// Get the spherical representation of the normal. its easier to
|
||||
// work with.
|
||||
double z = head.dir(Z);
|
||||
double r = 1.0; // for normalized vector
|
||||
double polar = std::acos(z / r);
|
||||
double azimuth = std::atan2(head.dir(Y), head.dir(X));
|
||||
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
|
||||
StopCriteria stc;
|
||||
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
||||
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
|
||||
stc.stop_score = 1e6;
|
||||
GeneticOptimizer solver(stc);
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
|
||||
double r_back = head.r_back_mm;
|
||||
|
||||
auto oresult = solver.optimize_max(
|
||||
[this, hjp, r_back](double plr, double azm)
|
||||
{
|
||||
Vec3d n = Vec3d(std::cos(azm) * std::sin(plr),
|
||||
std::sin(azm) * std::sin(plr),
|
||||
std::cos(plr)).normalized();
|
||||
return bridge_mesh_intersect(hjp, n, r_back);
|
||||
},
|
||||
initvals(polar, azimuth), // let's start with what we have
|
||||
bound(3*PI/4, PI), // Must not exceed the slope limit
|
||||
bound(-PI, PI) // azimuth can be a full range search
|
||||
);
|
||||
|
||||
d = 0; t = oresult.score;
|
||||
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
azimuth = std::get<1>(oresult.optimum);
|
||||
Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar),
|
||||
std::cos(polar)).normalized();
|
||||
|
||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||
|
||||
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
|
||||
hjp + d*bridgedir,
|
||||
dirdown,
|
||||
head.r_back_mm))) {
|
||||
d += head.r_back_mm;
|
||||
}
|
||||
|
||||
if(std::isinf(tdown)) { // we heave found a route to the ground
|
||||
routedown(head, bridgedir, d); return;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Route to model body
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
double zangle = std::asin(hit.direction()(Z));
|
||||
zangle = std::max(zangle, PI/4);
|
||||
double h = std::sin(zangle) * head.fullwidth();
|
||||
|
||||
// The width of the tail head that we would like to have...
|
||||
h = std::min(hit.distance() - head.r_back_mm, h);
|
||||
|
||||
if(h > 0) {
|
||||
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
||||
auto center_hit = m_mesh.query_ray_hit(hjp, dirdown);
|
||||
|
||||
double hitdiff = center_hit.distance() - hit.distance();
|
||||
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
||||
center_hit.position() : hit.position();
|
||||
|
||||
head.transform();
|
||||
|
||||
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
||||
Pillar &pill = m_builder.pillar(pillar_id);
|
||||
|
||||
Vec3d taildir = endp - hitp;
|
||||
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
|
||||
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
|
||||
|
||||
if (w < 0.) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
|
||||
w = 0.;
|
||||
}
|
||||
|
||||
Head tailhead(head.r_back_mm,
|
||||
head.r_pin_mm,
|
||||
w,
|
||||
m_cfg.head_penetration_mm,
|
||||
taildir,
|
||||
hitp);
|
||||
|
||||
tailhead.transform();
|
||||
pill.base = tailhead.mesh;
|
||||
|
||||
// Experimental: add the pillar to the index for cascading
|
||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||
modelpillars.emplace_back(unsigned(pill.id));
|
||||
return;
|
||||
}
|
||||
// No route to the ground, so connect to the model body as a last resort
|
||||
if (connect_to_model_body(head)) { return; }
|
||||
|
||||
// We have failed to route this head.
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Failed to route model facing support point."
|
||||
<< " ID: " << idx;
|
||||
<< "Failed to route model facing support point. ID: " << idx;
|
||||
|
||||
head.invalidate();
|
||||
};
|
||||
|
||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), onmodelfn);
|
||||
|
||||
for(auto pillid : modelpillars) {
|
||||
auto& pillar = m_builder.pillar(pillid);
|
||||
m_pillar_index.insert(pillar.endpoint(), pillid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SupportTreeBuildsteps::interconnect_pillars()
|
||||
|
|
@ -1280,7 +1219,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||
spts[n] = s;
|
||||
|
||||
// Check the path vertically down
|
||||
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
|
||||
Vec3d check_from = s + Vec3d{0., 0., pillar().r};
|
||||
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
|
||||
Vec3d gndsp{s(X), s(Y), gnd};
|
||||
|
||||
// If the path is clear, check for pillar base collisions
|
||||
|
|
@ -1310,9 +1250,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||
m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
|
||||
|
||||
m_builder.add_junction(s, pillar().r);
|
||||
double t = bridge_mesh_intersect(pillarsp,
|
||||
dirv(pillarsp, s),
|
||||
pillar().r);
|
||||
double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s),
|
||||
pillar().r);
|
||||
if (distance(pillarsp, s) < t)
|
||||
m_builder.add_bridge(pillarsp, s, pillar().r);
|
||||
|
||||
|
|
@ -1360,12 +1299,11 @@ void SupportTreeBuildsteps::routing_headless()
|
|||
Vec3d n = m_support_nmls.row(i); // mesh outward normal
|
||||
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
||||
|
||||
Vec3d dir = {0, 0, -1};
|
||||
Vec3d sj = sp + R * n; // stick start point
|
||||
|
||||
// This is only for checking
|
||||
double idist = bridge_mesh_intersect(sph, dir, R, true);
|
||||
double realdist = ray_mesh_intersect(sj, dir);
|
||||
double idist = bridge_mesh_distance(sph, DOWN, R, true);
|
||||
double realdist = ray_mesh_intersect(sj, DOWN).distance();
|
||||
double dist = realdist;
|
||||
|
||||
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
||||
|
|
@ -1378,7 +1316,7 @@ void SupportTreeBuildsteps::routing_headless()
|
|||
}
|
||||
|
||||
bool use_endball = !std::isinf(realdist);
|
||||
Vec3d ej = sj + (dist + HWIDTH_MM) * dir;
|
||||
Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ;
|
||||
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
|
@ -19,6 +20,32 @@ inline Vec2d to_vec2(const Vec3d& v3) {
|
|||
return {v3(X), v3(Y)};
|
||||
}
|
||||
|
||||
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
|
||||
{
|
||||
double z = n.z();
|
||||
double r = norm;
|
||||
double polar = std::acos(z / r);
|
||||
double azimuth = std::atan2(n(1), n(0));
|
||||
return {polar, azimuth};
|
||||
}
|
||||
|
||||
inline Vec3d spheric_to_dir(double polar, double azimuth)
|
||||
{
|
||||
return {std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar), std::cos(polar)};
|
||||
}
|
||||
|
||||
inline Vec3d spheric_to_dir(const std::tuple<double, double> &v)
|
||||
{
|
||||
auto [plr, azm] = v;
|
||||
return spheric_to_dir(plr, azm);
|
||||
}
|
||||
|
||||
inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
||||
{
|
||||
return spheric_to_dir(v.first, v.second);
|
||||
}
|
||||
|
||||
// This function returns the position of the centroid in the input 'clust'
|
||||
// vector of point indices.
|
||||
template<class DistFn>
|
||||
|
|
@ -150,10 +177,10 @@ class SupportTreeBuildsteps {
|
|||
using PtIndices = std::vector<unsigned>;
|
||||
|
||||
PtIndices m_iheads; // support points with pinhead
|
||||
PtIndices m_iheads_onmodel;
|
||||
PtIndices m_iheadless; // headless support points
|
||||
|
||||
// supp. pts. connecting to model: point index and the ray hit data
|
||||
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
|
||||
|
||||
std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
|
||||
|
||||
// normals for support points from model faces.
|
||||
PointSet m_support_nmls;
|
||||
|
|
@ -179,10 +206,10 @@ class SupportTreeBuildsteps {
|
|||
// When bridging heads to pillars... TODO: find a cleaner solution
|
||||
ccr::BlockingMutex m_bridge_mutex;
|
||||
|
||||
inline double ray_mesh_intersect(const Vec3d& s,
|
||||
const Vec3d& dir)
|
||||
inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,
|
||||
const Vec3d& dir)
|
||||
{
|
||||
return m_mesh.query_ray_hit(s, dir).distance();
|
||||
return m_mesh.query_ray_hit(s, dir);
|
||||
}
|
||||
|
||||
// This function will test if a future pinhead would not collide with the
|
||||
|
|
@ -202,6 +229,11 @@ class SupportTreeBuildsteps {
|
|||
double r_pin,
|
||||
double r_back,
|
||||
double width);
|
||||
|
||||
template<class...Args>
|
||||
inline double pinhead_mesh_distance(Args&&...args) {
|
||||
return pinhead_mesh_intersect(std::forward<Args>(args)...).distance();
|
||||
}
|
||||
|
||||
// Checking bridge (pillar and stick as well) intersection with the model.
|
||||
// If the function is used for headless sticks, the ins_check parameter
|
||||
|
|
@ -216,21 +248,40 @@ class SupportTreeBuildsteps {
|
|||
const Vec3d& dir,
|
||||
double r,
|
||||
bool ins_check = false);
|
||||
|
||||
template<class...Args>
|
||||
inline double bridge_mesh_distance(Args&&...args) {
|
||||
return bridge_mesh_intersect(std::forward<Args>(args)...).distance();
|
||||
}
|
||||
|
||||
// Helper function for interconnecting two pillars with zig-zag bridges.
|
||||
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);
|
||||
|
||||
// For connecting a head to a nearby pillar.
|
||||
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
||||
|
||||
|
||||
// Find route for a head to the ground. Inserts additional bridge from the
|
||||
// head to the pillar if cannot create pillar directly.
|
||||
// The optional dir parameter is the direction of the bridge which is the
|
||||
// direction of the pinhead if omitted.
|
||||
bool connect_to_ground(Head& head, const Vec3d &dir);
|
||||
inline bool connect_to_ground(Head& head);
|
||||
|
||||
bool connect_to_model_body(Head &head);
|
||||
|
||||
bool search_pillar_and_connect(const Head& head);
|
||||
|
||||
|
||||
// This is a proxy function for pillar creation which will mind the gap
|
||||
// between the pad and the model bottom in zero elevation mode.
|
||||
// jp is the starting junction point which needs to be routed down.
|
||||
// sourcedir is the allowed direction of an optional bridge between the
|
||||
// jp junction and the final pillar.
|
||||
void create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = ID_UNSET);
|
||||
|
||||
|
||||
public:
|
||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <cmath>
|
||||
#include "SLA/SLASupportTree.hpp"
|
||||
#include "SLA/SLABoilerPlate.hpp"
|
||||
#include "SLA/SLACommon.hpp"
|
||||
#include "SLA/SLASpatIndex.hpp"
|
||||
|
||||
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||
|
|
@ -228,6 +228,26 @@ EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
|||
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const Contour3D &other)
|
||||
{
|
||||
m_V.resize(Eigen::Index(other.points.size()), 3);
|
||||
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
|
||||
m_V.row(i) = other.points[size_t(i)];
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
|
||||
m_F.row(i) = other.faces3[size_t(i)];
|
||||
|
||||
size_t N = other.faces3.size() + 2 * other.faces4.size();
|
||||
for (size_t i = other.faces3.size(); i < N; i += 2) {
|
||||
size_t quad_idx = (i - other.faces3.size()) / 2;
|
||||
auto & quad = other.faces4[quad_idx];
|
||||
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
|
||||
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
|
||||
}
|
||||
}
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||
{
|
||||
m_V = other.m_V;
|
||||
|
|
@ -252,6 +272,31 @@ 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<EigenMesh3D::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(m_V, m_F, 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; });
|
||||
|
||||
// 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.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||
outs.back().m_face_id = hit.id;
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
#include <mutex>
|
||||
#include "PrintBase.hpp"
|
||||
//#include "PrintExport.hpp"
|
||||
#include "SLA/SLARasterWriter.hpp"
|
||||
#include "SLA/RasterWriter.hpp"
|
||||
#include "SLA/SupportTree.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include "Zipper.hpp"
|
||||
|
|
@ -19,6 +19,8 @@ enum SLAPrintStep : unsigned int {
|
|||
};
|
||||
|
||||
enum SLAPrintObjectStep : unsigned int {
|
||||
slaposHollowing,
|
||||
slaposDrillHoles,
|
||||
slaposObjectSlice,
|
||||
slaposSupportPoints,
|
||||
slaposSupportTree,
|
||||
|
|
@ -73,13 +75,23 @@ public:
|
|||
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
|
||||
const TriangleMesh& support_mesh() const;
|
||||
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied.
|
||||
// Support mesh is only valid if this->is_step_done(slaposBasePool) is true.
|
||||
// Support mesh is only valid if this->is_step_done(slaposPad) is true.
|
||||
const TriangleMesh& pad_mesh() const;
|
||||
|
||||
// Ready after this->is_step_done(slaposDrillHoles) is true
|
||||
const TriangleMesh& hollowed_interior_mesh() const;
|
||||
|
||||
// Get the mesh that is going to be printed with all the modifications
|
||||
// like hollowing and drilled holes.
|
||||
const TriangleMesh & get_mesh_to_print() const {
|
||||
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh();
|
||||
}
|
||||
|
||||
// This will return the transformed mesh which is cached
|
||||
const TriangleMesh& transformed_mesh() const;
|
||||
|
||||
std::vector<sla::SupportPoint> transformed_support_points() const;
|
||||
sla::SupportPoints transformed_support_points() const;
|
||||
sla::DrainHoles transformed_drainhole_points() const;
|
||||
|
||||
// Get the needed Z elevation for the model geometry if supports should be
|
||||
// displayed. This Z offset should also be applied to the support
|
||||
|
|
@ -126,9 +138,9 @@ public:
|
|||
// Returns the current layer height
|
||||
float layer_height() const { return m_height; }
|
||||
|
||||
bool is_valid() const { return ! std::isnan(m_slice_z); }
|
||||
bool is_valid() const { return m_po && ! std::isnan(m_slice_z); }
|
||||
|
||||
const SLAPrintObject* print_obj() const { assert(m_po); return m_po; }
|
||||
const SLAPrintObject* print_obj() const { return m_po; }
|
||||
|
||||
// Methods for setting the indices into the slice vectors.
|
||||
void set_model_slice_idx(const SLAPrintObject &po, size_t id) {
|
||||
|
|
@ -287,9 +299,35 @@ private:
|
|||
|
||||
// Caching the transformed (m_trafo) raw mesh of the object
|
||||
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
|
||||
|
||||
class SupportData;
|
||||
|
||||
class SupportData : public sla::SupportableMesh
|
||||
{
|
||||
public:
|
||||
sla::SupportTree::UPtr support_tree_ptr; // the supports
|
||||
std::vector<ExPolygons> support_slices; // sliced supports
|
||||
|
||||
inline SupportData(const TriangleMesh &t)
|
||||
: sla::SupportableMesh{t, {}, {}}
|
||||
{}
|
||||
|
||||
sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
|
||||
{
|
||||
support_tree_ptr = sla::SupportTree::create(*this, ctl);
|
||||
return support_tree_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<SupportData> m_supportdata;
|
||||
|
||||
class HollowingData
|
||||
{
|
||||
public:
|
||||
|
||||
TriangleMesh interior;
|
||||
mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
|
||||
};
|
||||
|
||||
std::unique_ptr<HollowingData> m_hollowing_data;
|
||||
};
|
||||
|
||||
using PrintObjects = std::vector<SLAPrintObject*>;
|
||||
|
|
@ -339,7 +377,9 @@ class SLAPrint : public PrintBaseWithState<SLAPrintStep, slapsCount>
|
|||
{
|
||||
private: // Prevents erroneous use by other classes.
|
||||
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
|
||||
|
||||
|
||||
class Steps; // See SLAPrintSteps.cpp
|
||||
|
||||
public:
|
||||
|
||||
SLAPrint(): m_stepmask(slapsCount, true) {}
|
||||
|
|
@ -381,6 +421,9 @@ public:
|
|||
// Extracted value from the configuration objects
|
||||
Vec3d relative_correction() const;
|
||||
|
||||
// Return sla tansformation for a given model_object
|
||||
Transform3d sla_trafo(const ModelObject &model_object) const;
|
||||
|
||||
std::string output_filename(const std::string &filename_base = std::string()) const override;
|
||||
|
||||
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
|
||||
|
|
@ -401,8 +444,8 @@ public:
|
|||
template<class Container> void transformed_slices(Container&& c) {
|
||||
m_transformed_slices = std::forward<Container>(c);
|
||||
}
|
||||
|
||||
friend void SLAPrint::process();
|
||||
|
||||
friend class SLAPrint::Steps;
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -478,6 +521,19 @@ private:
|
|||
friend SLAPrintObject;
|
||||
};
|
||||
|
||||
// Helper functions:
|
||||
|
||||
bool is_zero_elevation(const SLAPrintObjectConfig &c);
|
||||
|
||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg);
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SLAPrint_hpp_ */
|
||||
|
|
|
|||
916
src/libslic3r/SLAPrintSteps.cpp
Normal file
916
src/libslic3r/SLAPrintSteps.cpp
Normal file
|
|
@ -0,0 +1,916 @@
|
|||
#include <libslic3r/SLAPrintSteps.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
|
||||
// Need the cylinder method for the the drainholes in hollowing step
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/SupportPointGenerator.hpp>
|
||||
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
|
||||
// For geometry algorithms with native Clipper types (no copies and conversions)
|
||||
#include <libnest2d/backends/clipper/geometries.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "I18N.hpp"
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
|
||||
10, // slaposHollowing,
|
||||
10, // slaposDrillHoles
|
||||
10, // slaposObjectSlice,
|
||||
20, // slaposSupportPoints,
|
||||
10, // slaposSupportTree,
|
||||
10, // slaposPad,
|
||||
30, // slaposSliceSupports,
|
||||
};
|
||||
|
||||
std::string OBJ_STEP_LABELS(size_t idx)
|
||||
{
|
||||
switch (idx) {
|
||||
case slaposHollowing: return L("Hollowing model");
|
||||
case slaposDrillHoles: return L("Drilling holes into model.");
|
||||
case slaposObjectSlice: return L("Slicing model");
|
||||
case slaposSupportPoints: return L("Generating support points");
|
||||
case slaposSupportTree: return L("Generating support tree");
|
||||
case slaposPad: return L("Generating pad");
|
||||
case slaposSliceSupports: return L("Slicing supports");
|
||||
default:;
|
||||
}
|
||||
assert(false);
|
||||
return "Out of bounds!";
|
||||
}
|
||||
|
||||
const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS = {
|
||||
10, // slapsMergeSlicesAndEval
|
||||
90, // slapsRasterize
|
||||
};
|
||||
|
||||
std::string PRINT_STEP_LABELS(size_t idx)
|
||||
{
|
||||
switch (idx) {
|
||||
case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics");
|
||||
case slapsRasterize: return L("Rasterizing layers");
|
||||
default:;
|
||||
}
|
||||
assert(false); return "Out of bounds!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SLAPrint::Steps::Steps(SLAPrint *print)
|
||||
: m_print{print}
|
||||
, m_rng{std::random_device{}()}
|
||||
, objcount{m_print->m_objects.size()}
|
||||
, ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
|
||||
, ilh{float(ilhd)}
|
||||
, ilhs{scaled(ilhd)}
|
||||
, objectstep_scale{(max_objstatus - min_objstatus) / (objcount * 100.0)}
|
||||
{}
|
||||
|
||||
void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
|
||||
{
|
||||
po.m_hollowing_data.reset();
|
||||
|
||||
if (! po.m_config.hollowing_enable.getBool()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
|
||||
|
||||
double thickness = po.m_config.hollowing_min_thickness.getFloat();
|
||||
double quality = po.m_config.hollowing_quality.getFloat();
|
||||
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
|
||||
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
|
||||
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
|
||||
|
||||
if (meshptr->empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
|
||||
else {
|
||||
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
|
||||
po.m_hollowing_data->interior = *meshptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Drill holes into the hollowed/original mesh.
|
||||
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
||||
{
|
||||
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
|
||||
bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty());
|
||||
|
||||
if (! is_hollowed && ! needs_drilling) {
|
||||
// In this case we can dump any data that might have been
|
||||
// generated on previous runs.
|
||||
po.m_hollowing_data.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (! po.m_hollowing_data)
|
||||
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
|
||||
|
||||
// Hollowing and/or drilling is active, m_hollowing_data is valid.
|
||||
|
||||
// Regenerate hollowed mesh, even if it was there already. It may contain
|
||||
// holes that are no longer on the frontend.
|
||||
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
|
||||
hollowed_mesh = po.transformed_mesh();
|
||||
if (! po.m_hollowing_data->interior.empty()) {
|
||||
hollowed_mesh.merge(po.m_hollowing_data->interior);
|
||||
hollowed_mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
if (! needs_drilling) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
|
||||
sla::DrainHoles drainholes = po.transformed_drainhole_points();
|
||||
|
||||
std::uniform_real_distribution<float> dist(0., float(EPSILON));
|
||||
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({});
|
||||
for (sla::DrainHole holept : drainholes) {
|
||||
holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
|
||||
holept.normal.normalize();
|
||||
holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
|
||||
TriangleMesh m = sla::to_triangle_mesh(holept.to_mesh());
|
||||
m.require_shared_vertices();
|
||||
auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
|
||||
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m);
|
||||
}
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
|
||||
throw std::runtime_error(L("Too much overlapping holes."));
|
||||
|
||||
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
|
||||
|
||||
try {
|
||||
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
|
||||
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
|
||||
} catch (const std::runtime_error &) {
|
||||
throw std::runtime_error(L(
|
||||
"Drilling holes into the mesh failed. "
|
||||
"This is usually caused by broken model. Try to fix it first."));
|
||||
}
|
||||
}
|
||||
|
||||
// The slicing will be performed on an imaginary 1D grid which starts from
|
||||
// the bottom of the bounding box created around the supported model. So
|
||||
// the first layer which is usually thicker will be part of the supports
|
||||
// not the model geometry. Exception is when the model is not in the air
|
||||
// (elevation is zero) and no pad creation was requested. In this case the
|
||||
// model geometry starts on the ground level and the initial layer is part
|
||||
// of it. In any case, the model and the supports have to be sliced in the
|
||||
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
|
||||
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
||||
{
|
||||
const TriangleMesh &mesh = po.get_mesh_to_print();
|
||||
|
||||
// We need to prepare the slice index...
|
||||
|
||||
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
|
||||
float lh = float(lhd);
|
||||
coord_t lhs = scaled(lhd);
|
||||
auto && bb3d = mesh.bounding_box();
|
||||
double minZ = bb3d.min(Z) - po.get_elevation();
|
||||
double maxZ = bb3d.max(Z);
|
||||
auto minZf = float(minZ);
|
||||
coord_t minZs = scaled(minZ);
|
||||
coord_t maxZs = scaled(maxZ);
|
||||
|
||||
po.m_slice_index.clear();
|
||||
|
||||
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
|
||||
po.m_slice_index.reserve(cap);
|
||||
|
||||
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
|
||||
|
||||
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
|
||||
po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
|
||||
|
||||
// Just get the first record that is from the model:
|
||||
auto slindex_it =
|
||||
po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
|
||||
|
||||
if(slindex_it == po.m_slice_index.end())
|
||||
//TRN To be shown at the status bar on SLA slicing error.
|
||||
throw std::runtime_error(
|
||||
L("Slicing had to be stopped due to an internal error: "
|
||||
"Inconsistent slice index."));
|
||||
|
||||
po.m_model_height_levels.clear();
|
||||
po.m_model_height_levels.reserve(po.m_slice_index.size());
|
||||
for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
|
||||
po.m_model_height_levels.emplace_back(it->slice_level());
|
||||
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
po.m_model_slices.clear();
|
||||
float closing_r = float(po.config().slice_closing_radius.value);
|
||||
auto thr = [this]() { m_print->throw_if_canceled(); };
|
||||
auto &slice_grid = po.m_model_height_levels;
|
||||
slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr);
|
||||
|
||||
if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) {
|
||||
po.m_hollowing_data->interior.repair(true);
|
||||
TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior);
|
||||
std::vector<ExPolygons> interior_slices;
|
||||
interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr);
|
||||
|
||||
sla::ccr::enumerate(interior_slices.begin(), interior_slices.end(),
|
||||
[&po](const ExPolygons &slice, size_t i) {
|
||||
po.m_model_slices[i] =
|
||||
diff_ex(po.m_model_slices[i], slice);
|
||||
});
|
||||
}
|
||||
|
||||
auto mit = slindex_it;
|
||||
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
for(size_t id = 0;
|
||||
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
|
||||
id++)
|
||||
{
|
||||
// We apply the printer correction offset here.
|
||||
if(clpr_offs != 0)
|
||||
po.m_model_slices[id] =
|
||||
offset_ex(po.m_model_slices[id], float(clpr_offs));
|
||||
|
||||
mit->set_model_slice_idx(po, id); ++mit;
|
||||
}
|
||||
|
||||
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
|
||||
{
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
}
|
||||
}
|
||||
|
||||
// In this step we check the slices, identify island and cover them with
|
||||
// support points. Then we sprinkle the rest of the mesh.
|
||||
void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
||||
{
|
||||
// If supports are disabled, we can skip the model scan.
|
||||
if(!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
const TriangleMesh &mesh = po.get_mesh_to_print();
|
||||
|
||||
if (!po.m_supportdata)
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
|
||||
const ModelObject& mo = *po.m_model_object;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Support point count "
|
||||
<< mo.sla_support_points.size();
|
||||
|
||||
// Unless the user modified the points or we already did the calculation,
|
||||
// we will do the autoplacement. Otherwise we will just blindly copy the
|
||||
// frontend data into the backend cache.
|
||||
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
|
||||
|
||||
// calculate heights of slices (slices are calculated already)
|
||||
const std::vector<float>& heights = po.m_model_height_levels;
|
||||
|
||||
// Tell the mesh where drain holes are. Although the points are
|
||||
// calculated on slices, the algorithm then raycasts the points
|
||||
// so they actually lie on the mesh.
|
||||
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
||||
throw_if_canceled();
|
||||
sla::SupportPointGenerator::Config config;
|
||||
const SLAPrintObjectConfig& cfg = po.config();
|
||||
|
||||
// the density config value is in percents:
|
||||
config.density_relative = float(cfg.support_points_density_relative / 100.f);
|
||||
config.minimal_distance = float(cfg.support_points_minimal_distance);
|
||||
config.head_diameter = float(cfg.support_head_front_diameter);
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
|
||||
double init = current_status();
|
||||
|
||||
auto statuscb = [this, d, init](unsigned st)
|
||||
{
|
||||
double current = init + st * d;
|
||||
if(std::round(current_status()) < std::round(current))
|
||||
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
|
||||
};
|
||||
|
||||
// Construction of this object does the calculation.
|
||||
throw_if_canceled();
|
||||
sla::SupportPointGenerator auto_supports(
|
||||
po.m_supportdata->emesh, po.get_model_slices(), heights, config,
|
||||
[this]() { throw_if_canceled(); }, statuscb);
|
||||
|
||||
// Now let's extract the result.
|
||||
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||
throw_if_canceled();
|
||||
po.m_supportdata->pts = points;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||
// the update status to GLGizmoSlaSupports
|
||||
report_status(-1, L("Generating support points"),
|
||||
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
||||
} else {
|
||||
// There are either some points on the front-end, or the user
|
||||
// removed them on purpose. No calculation will be done.
|
||||
po.m_supportdata->pts = po.transformed_support_points();
|
||||
}
|
||||
|
||||
// If the zero elevation mode is engaged, we have to filter out all the
|
||||
// points that are on the bottom of the object
|
||||
if (is_zero_elevation(po.config())) {
|
||||
double tolerance = po.config().pad_enable.getBool() ?
|
||||
po.m_config.pad_wall_thickness.getFloat() :
|
||||
po.m_config.support_base_height.getFloat();
|
||||
|
||||
remove_bottom_points(po.m_supportdata->pts,
|
||||
po.m_supportdata->emesh.ground_level(),
|
||||
tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
||||
{
|
||||
if(!po.m_supportdata) return;
|
||||
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
if (pcfg.embed_object)
|
||||
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||
|
||||
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||
double init = current_status();
|
||||
sla::JobController ctl;
|
||||
|
||||
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
|
||||
double current = init + st * d;
|
||||
if (std::round(current_status()) < std::round(current))
|
||||
report_status(current, OBJ_STEP_LABELS(slaposSupportTree),
|
||||
SlicingStatus::DEFAULT, logmsg);
|
||||
};
|
||||
ctl.stopcondition = [this]() { return canceled(); };
|
||||
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||
|
||||
po.m_supportdata->create_support_tree(ctl);
|
||||
|
||||
if (!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
throw_if_canceled();
|
||||
|
||||
// Create the unified mesh
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
|
||||
// This is to prevent "Done." being displayed during merged_mesh()
|
||||
report_status(-1, L("Visualizing supports"));
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
// Check the mesh for later troubleshooting.
|
||||
if(po.support_mesh().empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
|
||||
|
||||
report_status(-1, L("Visualizing supports"), rc);
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
||||
// this step can only go after the support tree has been created
|
||||
// and before the supports had been sliced. (or the slicing has to be
|
||||
// repeated)
|
||||
|
||||
if(po.m_config.pad_enable.getBool()) {
|
||||
// Get the distilled pad configuration from the config
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
ExPolygons bp; // This will store the base plate of the pad.
|
||||
double pad_h = pcfg.full_height();
|
||||
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||
|
||||
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
|
||||
// No support (thus no elevation) or zero elevation mode
|
||||
// we sometimes call it "builtin pad" is enabled so we will
|
||||
// get a sample from the bottom of the mesh and use it for pad
|
||||
// creation.
|
||||
sla::pad_blueprint(trmesh, bp, float(pad_h),
|
||||
float(po.m_config.layer_height.getFloat()),
|
||||
[this](){ throw_if_canceled(); });
|
||||
}
|
||||
|
||||
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
||||
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
|
||||
|
||||
if (!validate_pad(pad_mesh, pcfg))
|
||||
throw std::runtime_error(
|
||||
L("No pad can be generated for this model with the "
|
||||
"current configuration"));
|
||||
|
||||
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
|
||||
po.m_supportdata->support_tree_ptr->remove_pad();
|
||||
}
|
||||
|
||||
throw_if_canceled();
|
||||
report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
|
||||
}
|
||||
|
||||
// Slicing the support geometries similarly to the model slicing procedure.
|
||||
// If the pad had been added previously (see step "base_pool" than it will
|
||||
// be part of the slices)
|
||||
void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
|
||||
auto& sd = po.m_supportdata;
|
||||
|
||||
if(sd) sd->support_slices.clear();
|
||||
|
||||
// Don't bother if no supports and no pad is present.
|
||||
if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
|
||||
return;
|
||||
|
||||
if(sd && sd->support_tree_ptr) {
|
||||
auto heights = reserve_vector<float>(po.m_slice_index.size());
|
||||
|
||||
for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
|
||||
|
||||
sd->support_slices = sd->support_tree_ptr->slice(
|
||||
heights, float(po.config().slice_closing_radius.value));
|
||||
}
|
||||
|
||||
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
|
||||
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) {
|
||||
// We apply the printer correction offset here.
|
||||
if (clpr_offs != 0)
|
||||
sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs));
|
||||
|
||||
po.m_slice_index[i].set_support_slice_idx(po, i);
|
||||
}
|
||||
|
||||
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
|
||||
// status to the 3D preview to load the SLA slices.
|
||||
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
}
|
||||
|
||||
using ClipperPoint = ClipperLib::IntPoint;
|
||||
using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
|
||||
using ClipperPolygons = std::vector<ClipperPolygon>;
|
||||
|
||||
static ClipperPolygons polyunion(const ClipperPolygons &subjects)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
bool closed = true;
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
|
||||
}
|
||||
|
||||
static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
bool closed = true;
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
for(auto& path : clips) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
|
||||
}
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
|
||||
}
|
||||
|
||||
// get polygons for all instances in the object
|
||||
static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
|
||||
{
|
||||
namespace sl = libnest2d::sl;
|
||||
|
||||
if (!record.print_obj()) return {};
|
||||
|
||||
ClipperPolygons polygons;
|
||||
auto &input_polygons = record.get_slice(o);
|
||||
auto &instances = record.print_obj()->instances();
|
||||
bool is_lefthanded = record.print_obj()->is_left_handed();
|
||||
polygons.reserve(input_polygons.size() * instances.size());
|
||||
|
||||
for (const ExPolygon& polygon : input_polygons) {
|
||||
if(polygon.contour.empty()) continue;
|
||||
|
||||
for (size_t i = 0; i < instances.size(); ++i)
|
||||
{
|
||||
ClipperPolygon poly;
|
||||
|
||||
// We need to reverse if is_lefthanded is true but
|
||||
bool needreverse = is_lefthanded;
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
|
||||
auto& cntr = polygon.contour.points;
|
||||
if(needreverse)
|
||||
for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
|
||||
poly.Contour.emplace_back(it->x(), it->y());
|
||||
else
|
||||
for(auto& p : cntr)
|
||||
poly.Contour.emplace_back(p.x(), p.y());
|
||||
|
||||
for(auto& h : polygon.holes) {
|
||||
poly.Holes.emplace_back();
|
||||
auto& hole = poly.Holes.back();
|
||||
hole.reserve(h.points.size() + 1);
|
||||
|
||||
if(needreverse)
|
||||
for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
|
||||
hole.emplace_back(it->x(), it->y());
|
||||
else
|
||||
for(auto& p : h.points)
|
||||
hole.emplace_back(p.x(), p.y());
|
||||
}
|
||||
|
||||
if(is_lefthanded) {
|
||||
for(auto& p : poly.Contour) p.X = -p.X;
|
||||
for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
|
||||
}
|
||||
|
||||
sl::rotate(poly, double(instances[i].rotation));
|
||||
sl::translate(poly, ClipperPoint{instances[i].shift.x(),
|
||||
instances[i].shift.y()});
|
||||
|
||||
polygons.emplace_back(std::move(poly));
|
||||
}
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::initialize_printer_input()
|
||||
{
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
// clear the rasterizer input
|
||||
printer_input.clear();
|
||||
|
||||
size_t mx = 0;
|
||||
for(SLAPrintObject * o : m_print->m_objects) {
|
||||
if(auto m = o->get_slice_index().size() > mx) mx = m;
|
||||
}
|
||||
|
||||
printer_input.reserve(mx);
|
||||
|
||||
auto eps = coord_t(SCALED_EPSILON);
|
||||
|
||||
for(SLAPrintObject * o : m_print->m_objects) {
|
||||
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
|
||||
|
||||
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
||||
if (!slicerecord.is_valid())
|
||||
throw std::runtime_error(
|
||||
L("There are unprintable objects. Try to "
|
||||
"adjust support settings to make the "
|
||||
"objects printable."));
|
||||
|
||||
coord_t lvlid = slicerecord.print_level() - gndlvl;
|
||||
|
||||
// Neat trick to round the layer levels to the grid.
|
||||
lvlid = eps * (lvlid / eps);
|
||||
|
||||
auto it = std::lower_bound(printer_input.begin(),
|
||||
printer_input.end(),
|
||||
PrintLayer(lvlid));
|
||||
|
||||
if(it == printer_input.end() || it->level() != lvlid)
|
||||
it = printer_input.insert(it, PrintLayer(lvlid));
|
||||
|
||||
|
||||
it->add(slicerecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merging the slices from all the print objects into one slice grid and
|
||||
// calculating print statistics from the merge result.
|
||||
void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
||||
|
||||
initialize_printer_input();
|
||||
|
||||
auto &print_statistics = m_print->m_print_statistics;
|
||||
auto &printer_config = m_print->m_printer_config;
|
||||
auto &material_config = m_print->m_material_config;
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
print_statistics.clear();
|
||||
|
||||
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
|
||||
auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
|
||||
|
||||
const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
|
||||
const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
|
||||
const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
|
||||
|
||||
const double init_exp_time = material_config.initial_exposure_time.getFloat();
|
||||
const double exp_time = material_config.exposure_time.getFloat();
|
||||
|
||||
const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||
|
||||
const auto width = scaled<double>(printer_config.display_width.getFloat());
|
||||
const auto height = scaled<double>(printer_config.display_height.getFloat());
|
||||
const double display_area = width*height;
|
||||
|
||||
double supports_volume(0.0);
|
||||
double models_volume(0.0);
|
||||
|
||||
double estim_time(0.0);
|
||||
|
||||
size_t slow_layers = 0;
|
||||
size_t fast_layers = 0;
|
||||
|
||||
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
|
||||
double fade_layer_time = init_exp_time;
|
||||
|
||||
sla::ccr::SpinningMutex mutex;
|
||||
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||
|
||||
// Going to parallel:
|
||||
auto printlayerfn = [
|
||||
// functions and read only vars
|
||||
areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
|
||||
|
||||
// write vars
|
||||
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
|
||||
&fast_layers, &fade_layer_time](PrintLayer& layer, size_t sliced_layer_cnt)
|
||||
{
|
||||
// vector of slice record references
|
||||
auto& slicerecord_references = layer.slices();
|
||||
|
||||
if(slicerecord_references.empty()) return;
|
||||
|
||||
// Layer height should match for all object slices for a given level.
|
||||
const auto l_height = double(slicerecord_references.front().get().layer_height());
|
||||
|
||||
// Calculation of the consumed material
|
||||
|
||||
ClipperPolygons model_polygons;
|
||||
ClipperPolygons supports_polygons;
|
||||
|
||||
size_t c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
model_polygons.reserve(c);
|
||||
|
||||
c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
supports_polygons.reserve(c);
|
||||
|
||||
for(const SliceRecord& record : layer.slices()) {
|
||||
|
||||
ClipperPolygons modelslices = get_all_polygons(record, soModel);
|
||||
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
|
||||
|
||||
ClipperPolygons supportslices = get_all_polygons(record, soSupport);
|
||||
for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
|
||||
|
||||
}
|
||||
|
||||
model_polygons = polyunion(model_polygons);
|
||||
double layer_model_area = 0;
|
||||
for (const ClipperPolygon& polygon : model_polygons)
|
||||
layer_model_area += areafn(polygon);
|
||||
|
||||
if (layer_model_area < 0 || layer_model_area > 0) {
|
||||
Lock lck(mutex); models_volume += layer_model_area * l_height;
|
||||
}
|
||||
|
||||
if(!supports_polygons.empty()) {
|
||||
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
|
||||
else supports_polygons = polydiff(supports_polygons, model_polygons);
|
||||
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
|
||||
}
|
||||
|
||||
double layer_support_area = 0;
|
||||
for (const ClipperPolygon& polygon : supports_polygons)
|
||||
layer_support_area += areafn(polygon);
|
||||
|
||||
if (layer_support_area < 0 || layer_support_area > 0) {
|
||||
Lock lck(mutex); supports_volume += layer_support_area * l_height;
|
||||
}
|
||||
|
||||
// Here we can save the expensively calculated polygons for printing
|
||||
ClipperPolygons trslices;
|
||||
trslices.reserve(model_polygons.size() + supports_polygons.size());
|
||||
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
|
||||
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
|
||||
|
||||
layer.transformed_slices(polyunion(trslices));
|
||||
|
||||
// Calculation of the slow and fast layers to the future controlling those values on FW
|
||||
|
||||
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
|
||||
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
|
||||
|
||||
{ Lock lck(mutex);
|
||||
if (is_fast_layer)
|
||||
fast_layers++;
|
||||
else
|
||||
slow_layers++;
|
||||
|
||||
|
||||
// Calculation of the printing time
|
||||
|
||||
if (sliced_layer_cnt < 3)
|
||||
estim_time += init_exp_time;
|
||||
else if (fade_layer_time > exp_time)
|
||||
{
|
||||
fade_layer_time -= delta_fade_time;
|
||||
estim_time += fade_layer_time;
|
||||
}
|
||||
else
|
||||
estim_time += exp_time;
|
||||
|
||||
estim_time += tilt_time;
|
||||
}
|
||||
};
|
||||
|
||||
// sequential version for debugging:
|
||||
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
|
||||
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), printlayerfn);
|
||||
|
||||
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
|
||||
print_statistics.support_used_material = supports_volume * SCALING2;
|
||||
print_statistics.objects_used_material = models_volume * SCALING2;
|
||||
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
if (printer_input.size() == 0)
|
||||
print_statistics.estimated_print_time = std::nan("");
|
||||
else
|
||||
print_statistics.estimated_print_time = estim_time;
|
||||
|
||||
print_statistics.fast_layers_count = fast_layers;
|
||||
print_statistics.slow_layers_count = slow_layers;
|
||||
|
||||
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
}
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
void SLAPrint::Steps::rasterize()
|
||||
{
|
||||
if(canceled()) return;
|
||||
|
||||
auto &print_statistics = m_print->m_print_statistics;
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
// Set up the printer, allocate space for all the layers
|
||||
sla::RasterWriter &printer = m_print->init_printer();
|
||||
|
||||
auto lvlcnt = unsigned(printer_input.size());
|
||||
printer.layers(lvlcnt);
|
||||
|
||||
// coefficient to map the rasterization state (0-99) to the allocated
|
||||
// portion (slot) of the process state
|
||||
double sd = (100 - max_objstatus) / 100.0;
|
||||
|
||||
// slot is the portion of 100% that is realted to rasterization
|
||||
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||
|
||||
// pst: previous state
|
||||
double pst = current_status();
|
||||
|
||||
double increment = (slot * sd) / printer_input.size();
|
||||
double dstatus = current_status();
|
||||
|
||||
sla::ccr::SpinningMutex slck;
|
||||
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||
(PrintLayer& printlayer, size_t idx)
|
||||
{
|
||||
if(canceled()) return;
|
||||
auto level_id = unsigned(idx);
|
||||
|
||||
// Switch to the appropriate layer in the printer
|
||||
printer.begin_layer(level_id);
|
||||
|
||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||
printer.draw_polygon(poly, level_id);
|
||||
|
||||
// Finish the layer for later saving it.
|
||||
printer.finish_layer(level_id);
|
||||
|
||||
// Status indication guarded with the spinlock
|
||||
{
|
||||
Lock lck(slck);
|
||||
dstatus += increment;
|
||||
double st = std::round(dstatus);
|
||||
if(st > pst) {
|
||||
report_status(st, PRINT_STEP_LABELS(slapsRasterize));
|
||||
pst = st;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// last minute escape
|
||||
if(canceled()) return;
|
||||
|
||||
// Sequential version (for testing)
|
||||
// for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l);
|
||||
|
||||
// Print all the layers in parallel
|
||||
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn);
|
||||
|
||||
// Set statistics values to the printer
|
||||
sla::RasterWriter::PrintStatistics stats;
|
||||
stats.used_material = (print_statistics.objects_used_material +
|
||||
print_statistics.support_used_material) / 1000;
|
||||
|
||||
int num_fade = m_print->m_default_object_config.faded_layers.getInt();
|
||||
stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
|
||||
stats.num_fast = print_statistics.fast_layers_count;
|
||||
stats.num_slow = print_statistics.slow_layers_count;
|
||||
stats.estimated_print_time_s = print_statistics.estimated_print_time;
|
||||
|
||||
printer.set_statistics(stats);
|
||||
}
|
||||
|
||||
std::string SLAPrint::Steps::label(SLAPrintObjectStep step)
|
||||
{
|
||||
return OBJ_STEP_LABELS(step);
|
||||
}
|
||||
|
||||
std::string SLAPrint::Steps::label(SLAPrintStep step)
|
||||
{
|
||||
return PRINT_STEP_LABELS(step);
|
||||
}
|
||||
|
||||
double SLAPrint::Steps::progressrange(SLAPrintObjectStep step) const
|
||||
{
|
||||
return OBJ_STEP_LEVELS[step] * objectstep_scale;
|
||||
}
|
||||
|
||||
double SLAPrint::Steps::progressrange(SLAPrintStep step) const
|
||||
{
|
||||
return PRINT_STEP_LEVELS[step] * (100 - max_objstatus) / 100.0;
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
|
||||
{
|
||||
switch(step) {
|
||||
case slaposHollowing: hollow_model(obj); break;
|
||||
case slaposDrillHoles: drill_holes(obj); break;
|
||||
case slaposObjectSlice: slice_model(obj); break;
|
||||
case slaposSupportPoints: support_points(obj); break;
|
||||
case slaposSupportTree: support_tree(obj); break;
|
||||
case slaposPad: generate_pad(obj); break;
|
||||
case slaposSliceSupports: slice_supports(obj); break;
|
||||
case slaposCount: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::execute(SLAPrintStep step)
|
||||
{
|
||||
switch (step) {
|
||||
case slapsMergeSlicesAndEval: merge_slices_and_eval_stats(); break;
|
||||
case slapsRasterize: rasterize(); break;
|
||||
case slapsCount: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
src/libslic3r/SLAPrintSteps.hpp
Normal file
72
src/libslic3r/SLAPrintSteps.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef SLAPRINTSTEPS_HPP
|
||||
#define SLAPRINTSTEPS_HPP
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SLAPrint::Steps
|
||||
{
|
||||
private:
|
||||
SLAPrint *m_print = nullptr;
|
||||
std::mt19937 m_rng;
|
||||
|
||||
public:
|
||||
// where the per object operations start and end
|
||||
static const constexpr unsigned min_objstatus = 0;
|
||||
static const constexpr unsigned max_objstatus = 50;
|
||||
|
||||
private:
|
||||
const size_t objcount;
|
||||
|
||||
// shortcut to initial layer height
|
||||
const double ilhd;
|
||||
const float ilh;
|
||||
const coord_t ilhs;
|
||||
|
||||
// the coefficient that multiplies the per object status values which
|
||||
// are set up for <0, 100>. They need to be scaled into the whole process
|
||||
const double objectstep_scale;
|
||||
|
||||
template<class...Args> void report_status(Args&&...args)
|
||||
{
|
||||
m_print->m_report_status(*m_print, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
double current_status() const { return m_print->m_report_status.status(); }
|
||||
void throw_if_canceled() const { m_print->throw_if_canceled(); }
|
||||
bool canceled() const { return m_print->canceled(); }
|
||||
void initialize_printer_input();
|
||||
|
||||
public:
|
||||
Steps(SLAPrint *print);
|
||||
|
||||
void hollow_model(SLAPrintObject &po);
|
||||
void drill_holes (SLAPrintObject &po);
|
||||
void slice_model(SLAPrintObject& po);
|
||||
void support_points(SLAPrintObject& po);
|
||||
void support_tree(SLAPrintObject& po);
|
||||
void generate_pad(SLAPrintObject& po);
|
||||
void slice_supports(SLAPrintObject& po);
|
||||
|
||||
void merge_slices_and_eval_stats();
|
||||
void rasterize();
|
||||
|
||||
void execute(SLAPrintObjectStep step, SLAPrintObject &obj);
|
||||
void execute(SLAPrintStep step);
|
||||
|
||||
static std::string label(SLAPrintObjectStep step);
|
||||
static std::string label(SLAPrintStep step);
|
||||
|
||||
double progressrange(SLAPrintObjectStep step) const;
|
||||
double progressrange(SLAPrintStep step) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SLAPRINTSTEPS_HPP
|
||||
|
|
@ -1946,24 +1946,27 @@ ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const Clippe
|
|||
return chain_path_items(points, items);
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print)
|
||||
std::vector<const PrintInstance*> chain_print_object_instances(const Print &print)
|
||||
{
|
||||
// Order objects using a nearest neighbor search.
|
||||
Points object_reference_points;
|
||||
std::vector<std::pair<size_t, size_t>> instances;
|
||||
for (size_t i = 0; i < print.objects().size(); ++ i) {
|
||||
const PrintObject &object = *print.objects()[i];
|
||||
for (size_t j = 0; j < object.copies().size(); ++ j) {
|
||||
object_reference_points.emplace_back(object.copy_center(j));
|
||||
for (size_t j = 0; j < object.instances().size(); ++ j) {
|
||||
// Sliced PrintObjects are centered, object.instances()[j].shift is the center of the PrintObject in G-code coordinates.
|
||||
object_reference_points.emplace_back(object.instances()[j].shift);
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), nullptr);
|
||||
std::vector<std::pair<size_t, size_t>> out;
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(instances.size());
|
||||
for (auto &segment_and_reversal : ordered)
|
||||
out.emplace_back(instances[segment_and_reversal.first]);
|
||||
for (auto &segment_and_reversal : ordered) {
|
||||
const std::pair<size_t, size_t> &inst = instances[segment_and_reversal.first];
|
||||
out.emplace_back(&print.objects()[inst.first]->instances()[inst.second]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ std::vector<ClipperLib::PolyNode*> chain_clipper_polynodes(const Points &points
|
|||
// Chain instances of print objects by an approximate shortest path.
|
||||
// Returns pairs of PrintObject idx and instance of that PrintObject.
|
||||
class Print;
|
||||
std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print);
|
||||
struct PrintInstance;
|
||||
std::vector<const PrintInstance*> chain_print_object_instances(const Print &print);
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
66
src/libslic3r/SimplifyMesh.cpp
Normal file
66
src/libslic3r/SimplifyMesh.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "SimplifyMesh.hpp"
|
||||
#include "SimplifyMeshImpl.hpp"
|
||||
|
||||
namespace SimplifyMesh {
|
||||
|
||||
template<> struct vertex_traits<stl_vertex> {
|
||||
using coord_type = float;
|
||||
using compute_type = double;
|
||||
|
||||
static inline float x(const stl_vertex &v) { return v.x(); }
|
||||
static inline float& x(stl_vertex &v) { return v.x(); }
|
||||
|
||||
static inline float y(const stl_vertex &v) { return v.y(); }
|
||||
static inline float& y(stl_vertex &v) { return v.y(); }
|
||||
|
||||
static inline float z(const stl_vertex &v) { return v.z(); }
|
||||
static inline float& z(stl_vertex &v) { return v.z(); }
|
||||
};
|
||||
|
||||
template<> struct mesh_traits<indexed_triangle_set> {
|
||||
using vertex_t = stl_vertex;
|
||||
static size_t face_count(const indexed_triangle_set &m)
|
||||
{
|
||||
return m.indices.size();
|
||||
}
|
||||
static size_t vertex_count(const indexed_triangle_set &m)
|
||||
{
|
||||
return m.vertices.size();
|
||||
}
|
||||
static vertex_t vertex(const indexed_triangle_set &m, size_t idx)
|
||||
{
|
||||
return m.vertices[idx];
|
||||
}
|
||||
static void vertex(indexed_triangle_set &m, size_t idx, const vertex_t &v)
|
||||
{
|
||||
m.vertices[idx] = v;
|
||||
}
|
||||
static Index3 triangle(const indexed_triangle_set &m, size_t idx)
|
||||
{
|
||||
std::array<size_t, 3> t;
|
||||
for (size_t i = 0; i < 3; ++i) t[i] = size_t(m.indices[idx](int(i)));
|
||||
return t;
|
||||
}
|
||||
static void triangle(indexed_triangle_set &m, size_t fidx, const Index3 &t)
|
||||
{
|
||||
auto &face = m.indices[fidx];
|
||||
face(0) = int(t[0]); face(1) = int(t[1]); face(2) = int(t[2]);
|
||||
}
|
||||
static void update(indexed_triangle_set &m, size_t vc, size_t fc)
|
||||
{
|
||||
m.vertices.resize(vc);
|
||||
m.indices.resize(fc);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace SimplifyMesh
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void simplify_mesh(indexed_triangle_set &m)
|
||||
{
|
||||
SimplifyMesh::implementation::SimplifiableMesh sm{&m};
|
||||
sm.simplify_mesh_lossless();
|
||||
}
|
||||
|
||||
}
|
||||
25
src/libslic3r/SimplifyMesh.hpp
Normal file
25
src/libslic3r/SimplifyMesh.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef MESHSIMPLIFY_HPP
|
||||
#define MESHSIMPLIFY_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void simplify_mesh(indexed_triangle_set &);
|
||||
|
||||
// TODO: (but this can be done with IGL as well)
|
||||
// void simplify_mesh(indexed_triangle_set &, int face_count, float agressiveness = 0.5f);
|
||||
|
||||
template<class...Args> void simplify_mesh(TriangleMesh &m, Args &&...a)
|
||||
{
|
||||
m.require_shared_vertices();
|
||||
simplify_mesh(m.its, std::forward<Args>(a)...);
|
||||
m = TriangleMesh{m.its};
|
||||
m.require_shared_vertices();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MESHSIMPLIFY_H
|
||||
670
src/libslic3r/SimplifyMeshImpl.hpp
Normal file
670
src/libslic3r/SimplifyMeshImpl.hpp
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
// ///////////////////////////////////////////
|
||||
//
|
||||
// Mesh Simplification Tutorial
|
||||
//
|
||||
// (C) by Sven Forstmann in 2014
|
||||
//
|
||||
// License : MIT
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
|
||||
//
|
||||
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
|
||||
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/
|
||||
//
|
||||
// libslic3r refactor by tamasmeszaros
|
||||
|
||||
#ifndef SIMPLIFYMESHIMPL_HPP
|
||||
#define SIMPLIFYMESHIMPL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <ostream>
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
namespace SimplifyMesh {
|
||||
|
||||
using Bary = std::array<double, 3>;
|
||||
using Index3 = std::array<size_t, 3>;
|
||||
|
||||
template<class Vertex> struct vertex_traits {
|
||||
using coord_type = typename Vertex::coord_type;
|
||||
using compute_type = coord_type;
|
||||
|
||||
static coord_type x(const Vertex &v);
|
||||
static coord_type& x(Vertex &v);
|
||||
|
||||
static coord_type y(const Vertex &v);
|
||||
static coord_type& y(Vertex &v);
|
||||
|
||||
static coord_type z(const Vertex &v);
|
||||
static coord_type& z(Vertex &v);
|
||||
};
|
||||
|
||||
template<class Mesh> struct mesh_traits {
|
||||
using vertex_t = typename Mesh::vertex_t;
|
||||
|
||||
static size_t face_count(const Mesh &m);
|
||||
static size_t vertex_count(const Mesh &m);
|
||||
static vertex_t vertex(const Mesh &m, size_t vertex_idx);
|
||||
static void vertex(Mesh &m, size_t vertex_idx, const vertex_t &v);
|
||||
static Index3 triangle(const Mesh &m, size_t face_idx);
|
||||
static void triangle(Mesh &m, size_t face_idx, const Index3 &t);
|
||||
static void update(Mesh &m, size_t vertex_count, size_t face_count);
|
||||
};
|
||||
|
||||
namespace implementation {
|
||||
|
||||
// A shorter C++14 style form of the enable_if metafunction
|
||||
template<bool B, class T>
|
||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
||||
// Meta predicates for floating, integer and generic arithmetic types
|
||||
template<class T, class O = T>
|
||||
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
|
||||
|
||||
template<class T, class O = T>
|
||||
using IntegerOnly = enable_if_t<std::is_integral<T>::value, O>;
|
||||
|
||||
template<class T, class O = T>
|
||||
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>;
|
||||
|
||||
template< class T >
|
||||
struct remove_cvref {
|
||||
using type = typename std::remove_cv<
|
||||
typename std::remove_reference<T>::type>::type;
|
||||
};
|
||||
|
||||
template< class T >
|
||||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
template<class T> FloatingOnly<T, bool> is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; }
|
||||
template<class T> IntegerOnly <T, bool> is_approx(T val, T ref) { val == ref; }
|
||||
|
||||
template<class T> class SymetricMatrix {
|
||||
static const constexpr size_t N = 10;
|
||||
public:
|
||||
|
||||
explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
|
||||
|
||||
// Make plane
|
||||
SymetricMatrix(T a, T b, T c, T d)
|
||||
{
|
||||
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
|
||||
m[4] = b * b; m[5] = b * c; m[6] = b * d;
|
||||
m[7] = c * c; m[8] = c * d;
|
||||
m[9] = d * d;
|
||||
}
|
||||
|
||||
T operator[](int c) const { return m[c]; }
|
||||
|
||||
// Determinant
|
||||
T det(int a11, int a12, int a13,
|
||||
int a21, int a22, int a23,
|
||||
int a31, int a32, int a33)
|
||||
{
|
||||
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
|
||||
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
|
||||
m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
|
||||
|
||||
return det;
|
||||
}
|
||||
|
||||
const SymetricMatrix& operator+=(const SymetricMatrix& n)
|
||||
{
|
||||
for (size_t i = 0; i < N; ++i) m[i] += n[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
SymetricMatrix operator+(const SymetricMatrix& n)
|
||||
{
|
||||
SymetricMatrix self = *this;
|
||||
return self += n;
|
||||
}
|
||||
|
||||
T m[N];
|
||||
};
|
||||
|
||||
template<class V> using TCoord = typename vertex_traits<remove_cvref_t<V>>::coord_type;
|
||||
template<class V> using TCompute = typename vertex_traits<remove_cvref_t<V>>::compute_type;
|
||||
template<class V> inline TCoord<V> x(const V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
|
||||
template<class V> inline TCoord<V> y(const V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
|
||||
template<class V> inline TCoord<V> z(const V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
|
||||
template<class V> inline TCoord<V>& x(V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
|
||||
template<class V> inline TCoord<V>& y(V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
|
||||
template<class V> inline TCoord<V>& z(V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
|
||||
template<class M> using TVertex = typename mesh_traits<remove_cvref_t<M>>::vertex_t;
|
||||
template<class Mesh> using TMeshCoord = TCoord<TVertex<Mesh>>;
|
||||
|
||||
template<class Vertex> TCompute<Vertex> dot(const Vertex &v1, const Vertex &v2)
|
||||
{
|
||||
return TCompute<Vertex>(x(v1)) * x(v2) +
|
||||
TCompute<Vertex>(y(v1)) * y(v2) +
|
||||
TCompute<Vertex>(z(v1)) * z(v2);
|
||||
}
|
||||
|
||||
template<class Vertex> Vertex cross(const Vertex &a, const Vertex &b)
|
||||
{
|
||||
return Vertex{y(a) * z(b) - z(a) * y(b),
|
||||
z(a) * x(b) - x(a) * z(b),
|
||||
x(a) * y(b) - y(a) * x(b)};
|
||||
}
|
||||
|
||||
template<class Vertex> TCompute<Vertex> lengthsq(const Vertex &v)
|
||||
{
|
||||
return TCompute<Vertex>(x(v)) * x(v) + TCompute<Vertex>(y(v)) * y(v) +
|
||||
TCompute<Vertex>(z(v)) * z(v);
|
||||
}
|
||||
|
||||
template<class Vertex> void normalize(Vertex &v)
|
||||
{
|
||||
double square = std::sqrt(lengthsq(v));
|
||||
x(v) /= square; y(v) /= square; z(v) /= square;
|
||||
}
|
||||
|
||||
using Bary = std::array<double, 3>;
|
||||
|
||||
template<class Vertex>
|
||||
Bary barycentric(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c)
|
||||
{
|
||||
Vertex v0 = (b - a);
|
||||
Vertex v1 = (c - a);
|
||||
Vertex v2 = (p - a);
|
||||
|
||||
double d00 = dot(v0, v0);
|
||||
double d01 = dot(v0, v1);
|
||||
double d11 = dot(v1, v1);
|
||||
double d20 = dot(v2, v0);
|
||||
double d21 = dot(v2, v1);
|
||||
double denom = d00 * d11 - d01 * d01;
|
||||
double v = (d11 * d20 - d01 * d21) / denom;
|
||||
double w = (d00 * d21 - d01 * d20) / denom;
|
||||
double u = 1.0 - v - w;
|
||||
|
||||
return {u, v, w};
|
||||
}
|
||||
|
||||
template<class Mesh> class SimplifiableMesh {
|
||||
Mesh *m_mesh;
|
||||
|
||||
using Vertex = TVertex<Mesh>;
|
||||
using Coord = TMeshCoord<Mesh>;
|
||||
using HiPrecison = TCompute<TVertex<Mesh>>;
|
||||
using SymMat = SymetricMatrix<HiPrecison>;
|
||||
|
||||
struct FaceInfo {
|
||||
size_t idx;
|
||||
double err[4] = {0.};
|
||||
bool deleted = false, dirty = false;
|
||||
Vertex n;
|
||||
explicit FaceInfo(size_t id): idx(id) {}
|
||||
};
|
||||
|
||||
struct VertexInfo {
|
||||
size_t idx;
|
||||
size_t tstart = 0, tcount = 0;
|
||||
bool border = false;
|
||||
SymMat q;
|
||||
explicit VertexInfo(size_t id): idx(id) {}
|
||||
};
|
||||
|
||||
struct Ref { size_t face; size_t vertex; };
|
||||
|
||||
std::vector<Ref> m_refs;
|
||||
std::vector<FaceInfo> m_faceinfo;
|
||||
std::vector<VertexInfo> m_vertexinfo;
|
||||
|
||||
void compact_faces();
|
||||
void compact();
|
||||
|
||||
size_t mesh_vcount() const { return mesh_traits<Mesh>::vertex_count(*m_mesh); }
|
||||
size_t mesh_facecount() const { return mesh_traits<Mesh>::face_count(*m_mesh); }
|
||||
|
||||
size_t vcount() const { return m_vertexinfo.size(); }
|
||||
|
||||
inline Vertex read_vertex(size_t vi) const
|
||||
{
|
||||
return mesh_traits<Mesh>::vertex(*m_mesh, vi);
|
||||
}
|
||||
|
||||
inline Vertex read_vertex(const VertexInfo &vinf) const
|
||||
{
|
||||
return read_vertex(vinf.idx);
|
||||
}
|
||||
|
||||
inline void write_vertex(size_t idx, const Vertex &v) const
|
||||
{
|
||||
mesh_traits<Mesh>::vertex(*m_mesh, idx, v);
|
||||
}
|
||||
|
||||
inline void write_vertex(const VertexInfo &vinf, const Vertex &v) const
|
||||
{
|
||||
write_vertex(vinf.idx, v);
|
||||
}
|
||||
|
||||
inline Index3 read_triangle(size_t fi) const
|
||||
{
|
||||
return mesh_traits<Mesh>::triangle(*m_mesh, fi);
|
||||
}
|
||||
|
||||
inline Index3 read_triangle(const FaceInfo &finf) const
|
||||
{
|
||||
return read_triangle(finf.idx);
|
||||
}
|
||||
|
||||
inline void write_triangle(size_t idx, const Index3 &t)
|
||||
{
|
||||
return mesh_traits<Mesh>::triangle(*m_mesh, idx, t);
|
||||
}
|
||||
|
||||
inline void write_triangle(const FaceInfo &finf, const Index3 &t)
|
||||
{
|
||||
return write_triangle(finf.idx, t);
|
||||
}
|
||||
|
||||
inline std::array<Vertex, 3> triangle_vertices(const Index3 &f) const
|
||||
{
|
||||
std::array<Vertex, 3> p;
|
||||
for (size_t i = 0; i < 3; ++i) p[i] = read_vertex(f[i]);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Error between vertex and Quadric
|
||||
static double vertex_error(const SymMat &q, const Vertex &v)
|
||||
{
|
||||
Coord _x = x(v) , _y = y(v), _z = z(v);
|
||||
return q[0] * _x * _x + 2 * q[1] * _x * _y + 2 * q[2] * _x * _z +
|
||||
2 * q[3] * _x + q[4] * _y * _y + 2 * q[5] * _y * _z +
|
||||
2 * q[6] * _y + q[7] * _z * _z + 2 * q[8] * _z + q[9];
|
||||
}
|
||||
|
||||
// Error for one edge
|
||||
double calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result);
|
||||
|
||||
void calculate_error(FaceInfo &fi)
|
||||
{
|
||||
Vertex p;
|
||||
Index3 t = read_triangle(fi);
|
||||
for (size_t j = 0; j < 3; ++j)
|
||||
fi.err[j] = calculate_error(t[j], t[(j + 1) % 3], p);
|
||||
|
||||
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
|
||||
}
|
||||
|
||||
void update_mesh(int iteration);
|
||||
|
||||
// Update triangle connections and edge error after a edge is collapsed
|
||||
void update_triangles(size_t i, VertexInfo &vi, std::vector<bool> &deleted, int &deleted_triangles);
|
||||
|
||||
// Check if a triangle flips when this edge is removed
|
||||
bool flipped(const Vertex &p, size_t i0, size_t i1, VertexInfo &v0, VertexInfo &v1, std::vector<bool> &deleted);
|
||||
|
||||
public:
|
||||
|
||||
explicit SimplifiableMesh(Mesh *m) : m_mesh{m}
|
||||
{
|
||||
static_assert(
|
||||
std::is_arithmetic<Coord>::value,
|
||||
"Coordinate type of mesh has to be an arithmetic type!");
|
||||
|
||||
m_faceinfo.reserve(mesh_traits<Mesh>::face_count(*m));
|
||||
m_vertexinfo.reserve(mesh_traits<Mesh>::vertex_count(*m));
|
||||
for (size_t i = 0; i < mesh_facecount(); ++i) m_faceinfo.emplace_back(i);
|
||||
for (size_t i = 0; i < mesh_vcount(); ++i) m_vertexinfo.emplace_back(i);
|
||||
|
||||
}
|
||||
|
||||
template<class ProgressFn> void simplify_mesh_lossless(ProgressFn &&fn);
|
||||
void simplify_mesh_lossless() { simplify_mesh_lossless([](int){}); }
|
||||
};
|
||||
|
||||
template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces()
|
||||
{
|
||||
auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(),
|
||||
[](const FaceInfo &inf) { return inf.deleted; });
|
||||
|
||||
m_faceinfo.erase(it, m_faceinfo.end());
|
||||
}
|
||||
|
||||
template<class M> void SimplifiableMesh<M>::compact()
|
||||
{
|
||||
for (auto &vi : m_vertexinfo) vi.tcount = 0;
|
||||
|
||||
compact_faces();
|
||||
|
||||
for (FaceInfo &fi : m_faceinfo)
|
||||
for (size_t vidx : read_triangle(fi)) m_vertexinfo[vidx].tcount = 1;
|
||||
|
||||
size_t dst = 0;
|
||||
for (VertexInfo &vi : m_vertexinfo) {
|
||||
if (vi.tcount) {
|
||||
vi.tstart = dst;
|
||||
write_vertex(dst++, read_vertex(vi));
|
||||
}
|
||||
}
|
||||
|
||||
size_t vertex_count = dst;
|
||||
|
||||
dst = 0;
|
||||
for (const FaceInfo &fi : m_faceinfo) {
|
||||
Index3 t = read_triangle(fi);
|
||||
for (size_t &idx : t) idx = m_vertexinfo[idx].tstart;
|
||||
write_triangle(dst++, t);
|
||||
}
|
||||
|
||||
mesh_traits<M>::update(*m_mesh, vertex_count, m_faceinfo.size());
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
double SimplifiableMesh<Mesh>::calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result)
|
||||
{
|
||||
// compute interpolated vertex
|
||||
|
||||
SymMat q = m_vertexinfo[id_v1].q + m_vertexinfo[id_v2].q;
|
||||
|
||||
bool border = m_vertexinfo[id_v1].border & m_vertexinfo[id_v2].border;
|
||||
double error = 0;
|
||||
HiPrecison det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
|
||||
|
||||
if (!is_approx(det, HiPrecison(0)) && !border)
|
||||
{
|
||||
// q_delta is invertible
|
||||
x(p_result) = Coord(-1) / det * q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
|
||||
y(p_result) = Coord( 1) / det * q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
|
||||
z(p_result) = Coord(-1) / det * q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
|
||||
|
||||
error = vertex_error(q, p_result);
|
||||
} else {
|
||||
// det = 0 -> try to find best result
|
||||
Vertex p1 = read_vertex(id_v1);
|
||||
Vertex p2 = read_vertex(id_v2);
|
||||
Vertex p3 = (p1 + p2) / 2;
|
||||
double error1 = vertex_error(q, p1);
|
||||
double error2 = vertex_error(q, p2);
|
||||
double error3 = vertex_error(q, p3);
|
||||
error = std::min(error1, std::min(error2, error3));
|
||||
|
||||
if (is_approx(error1, error)) p_result = p1;
|
||||
if (is_approx(error2, error)) p_result = p2;
|
||||
if (is_approx(error3, error)) p_result = p3;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
template<class Mesh> void SimplifiableMesh<Mesh>::update_mesh(int iteration)
|
||||
{
|
||||
if (iteration > 0) compact_faces();
|
||||
|
||||
assert(mesh_vcount() == m_vertexinfo.size());
|
||||
|
||||
//
|
||||
// Init Quadrics by Plane & Edge Errors
|
||||
//
|
||||
// required at the beginning ( iteration == 0 )
|
||||
// recomputing during the simplification is not required,
|
||||
// but mostly improves the result for closed meshes
|
||||
//
|
||||
if (iteration == 0) {
|
||||
|
||||
for (VertexInfo &vinf : m_vertexinfo) vinf.q = SymMat{};
|
||||
for (FaceInfo &finf : m_faceinfo) {
|
||||
Index3 t = read_triangle(finf);
|
||||
std::array<Vertex, 3> p = triangle_vertices(t);
|
||||
Vertex n = cross(Vertex(p[1] - p[0]), Vertex(p[2] - p[0]));
|
||||
normalize(n);
|
||||
finf.n = n;
|
||||
|
||||
for (size_t fi : t)
|
||||
m_vertexinfo[fi].q += SymMat(x(n), y(n), z(n), -dot(n, p[0]));
|
||||
|
||||
calculate_error(finf);
|
||||
}
|
||||
}
|
||||
|
||||
// Init Reference ID list
|
||||
for (VertexInfo &vi : m_vertexinfo) { vi.tstart = 0; vi.tcount = 0; }
|
||||
|
||||
for (FaceInfo &fi : m_faceinfo)
|
||||
for (size_t vidx : read_triangle(fi))
|
||||
m_vertexinfo[vidx].tcount++;
|
||||
|
||||
size_t tstart = 0;
|
||||
for (VertexInfo &vi : m_vertexinfo) {
|
||||
vi.tstart = tstart;
|
||||
tstart += vi.tcount;
|
||||
vi.tcount = 0;
|
||||
}
|
||||
|
||||
// Write References
|
||||
m_refs.resize(m_faceinfo.size() * 3);
|
||||
for (size_t i = 0; i < m_faceinfo.size(); ++i) {
|
||||
const FaceInfo &fi = m_faceinfo[i];
|
||||
Index3 t = read_triangle(fi);
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
VertexInfo &vi = m_vertexinfo[t[j]];
|
||||
|
||||
assert(vi.tstart + vi.tcount < m_refs.size());
|
||||
|
||||
Ref &ref = m_refs[vi.tstart + vi.tcount];
|
||||
ref.face = i;
|
||||
ref.vertex = j;
|
||||
vi.tcount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Identify boundary : vertices[].border=0,1
|
||||
if (iteration == 0) {
|
||||
for (VertexInfo &vi: m_vertexinfo) vi.border = false;
|
||||
|
||||
std::vector<size_t> vcount, vids;
|
||||
|
||||
for (VertexInfo &vi: m_vertexinfo) {
|
||||
vcount.clear();
|
||||
vids.clear();
|
||||
|
||||
for(size_t j = 0; j < vi.tcount; ++j) {
|
||||
assert(vi.tstart + j < m_refs.size());
|
||||
FaceInfo &fi = m_faceinfo[m_refs[vi.tstart + j].face];
|
||||
Index3 t = read_triangle(fi);
|
||||
|
||||
for (size_t fid : t) {
|
||||
size_t ofs=0;
|
||||
while (ofs < vcount.size())
|
||||
{
|
||||
if (vids[ofs] == fid) break;
|
||||
ofs++;
|
||||
}
|
||||
if (ofs == vcount.size())
|
||||
{
|
||||
vcount.emplace_back(1);
|
||||
vids.emplace_back(fid);
|
||||
}
|
||||
else
|
||||
vcount[ofs]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < vcount.size(); ++j)
|
||||
if(vcount[j] == 1) m_vertexinfo[vids[j]].border = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
void SimplifiableMesh<Mesh>::update_triangles(size_t i0,
|
||||
VertexInfo & vi,
|
||||
std::vector<bool> &deleted,
|
||||
int &deleted_triangles)
|
||||
{
|
||||
Vertex p;
|
||||
for (size_t k = 0; k < vi.tcount; ++k) {
|
||||
assert(vi.tstart + k < m_refs.size());
|
||||
|
||||
Ref &r = m_refs[vi.tstart + k];
|
||||
FaceInfo &fi = m_faceinfo[r.face];
|
||||
|
||||
if (fi.deleted) continue;
|
||||
|
||||
if (deleted[k]) {
|
||||
fi.deleted = true;
|
||||
deleted_triangles++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Index3 t = read_triangle(fi);
|
||||
t[r.vertex] = i0;
|
||||
write_triangle(fi, t);
|
||||
|
||||
fi.dirty = true;
|
||||
fi.err[0] = calculate_error(t[0], t[1], p);
|
||||
fi.err[1] = calculate_error(t[1], t[2], p);
|
||||
fi.err[2] = calculate_error(t[2], t[0], p);
|
||||
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
|
||||
m_refs.emplace_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
bool SimplifiableMesh<Mesh>::flipped(const Vertex & p,
|
||||
size_t /*i0*/,
|
||||
size_t i1,
|
||||
VertexInfo & v0,
|
||||
VertexInfo & /*v1*/,
|
||||
std::vector<bool> &deleted)
|
||||
{
|
||||
for (size_t k = 0; k < v0.tcount; ++k) {
|
||||
size_t ridx = v0.tstart + k;
|
||||
assert(ridx < m_refs.size());
|
||||
|
||||
FaceInfo &fi = m_faceinfo[m_refs[ridx].face];
|
||||
if (fi.deleted) continue;
|
||||
|
||||
Index3 t = read_triangle(fi);
|
||||
int s = m_refs[ridx].vertex;
|
||||
size_t id1 = t[(s+1) % 3];
|
||||
size_t id2 = t[(s+2) % 3];
|
||||
|
||||
if(id1 == i1 || id2 == i1) // delete ?
|
||||
{
|
||||
deleted[k] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Vertex d1 = read_vertex(id1) - p;
|
||||
normalize(d1);
|
||||
Vertex d2 = read_vertex(id2) - p;
|
||||
normalize(d2);
|
||||
|
||||
if (std::abs(dot(d1, d2)) > 0.999) return true;
|
||||
|
||||
Vertex n = cross(d1, d2);
|
||||
normalize(n);
|
||||
|
||||
deleted[k] = false;
|
||||
if (dot(n, fi.n) < 0.2) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
template<class Fn> void SimplifiableMesh<Mesh>::simplify_mesh_lossless(Fn &&fn)
|
||||
{
|
||||
// init
|
||||
for (FaceInfo &fi : m_faceinfo) fi.deleted = false;
|
||||
|
||||
// main iteration loop
|
||||
int deleted_triangles=0;
|
||||
std::vector<bool> deleted0, deleted1;
|
||||
|
||||
for (int iteration = 0; iteration < 9999; iteration ++) {
|
||||
// update mesh constantly
|
||||
update_mesh(iteration);
|
||||
|
||||
// clear dirty flag
|
||||
for (FaceInfo &fi : m_faceinfo) fi.dirty = false;
|
||||
|
||||
//
|
||||
// All triangles with edges below the threshold will be removed
|
||||
//
|
||||
// The following numbers works well for most models.
|
||||
// If it does not, try to adjust the 3 parameters
|
||||
//
|
||||
double threshold = std::numeric_limits<double>::epsilon(); //1.0E-3 EPS; // Really? (tm)
|
||||
|
||||
fn(iteration);
|
||||
|
||||
for (FaceInfo &fi : m_faceinfo) {
|
||||
if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue;
|
||||
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
if (fi.err[j] > threshold) continue;
|
||||
|
||||
Index3 t = read_triangle(fi);
|
||||
size_t i0 = t[j];
|
||||
VertexInfo &v0 = m_vertexinfo[i0];
|
||||
|
||||
size_t i1 = t[(j + 1) % 3];
|
||||
VertexInfo &v1 = m_vertexinfo[i1];
|
||||
|
||||
// Border check
|
||||
if(v0.border != v1.border) continue;
|
||||
|
||||
// Compute vertex to collapse to
|
||||
Vertex p;
|
||||
calculate_error(i0, i1, p);
|
||||
|
||||
deleted0.resize(v0.tcount); // normals temporarily
|
||||
deleted1.resize(v1.tcount); // normals temporarily
|
||||
|
||||
// don't remove if flipped
|
||||
if (flipped(p, i0, i1, v0, v1, deleted0)) continue;
|
||||
if (flipped(p, i1, i0, v1, v0, deleted1)) continue;
|
||||
|
||||
// not flipped, so remove edge
|
||||
write_vertex(v0, p);
|
||||
v0.q = v1.q + v0.q;
|
||||
size_t tstart = m_refs.size();
|
||||
|
||||
update_triangles(i0, v0, deleted0, deleted_triangles);
|
||||
update_triangles(i0, v1, deleted1, deleted_triangles);
|
||||
|
||||
assert(m_refs.size() >= tstart);
|
||||
|
||||
size_t tcount = m_refs.size() - tstart;
|
||||
|
||||
if(tcount <= v0.tcount)
|
||||
{
|
||||
// save ram
|
||||
if (tcount) {
|
||||
auto from = m_refs.begin() + tstart, to = from + tcount;
|
||||
std::copy(from, to, m_refs.begin() + v0.tstart);
|
||||
}
|
||||
}
|
||||
else
|
||||
// append
|
||||
v0.tstart = tstart;
|
||||
|
||||
v0.tcount = tcount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted_triangles <= 0) break;
|
||||
deleted_triangles = 0;
|
||||
}
|
||||
|
||||
compact();
|
||||
}
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace SimplifyMesh
|
||||
|
||||
#endif // SIMPLIFYMESHIMPL_HPP
|
||||
|
|
@ -41,6 +41,23 @@ inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, in
|
|||
return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height);
|
||||
}
|
||||
|
||||
// Minimum layer height for the variable layer height algorithm.
|
||||
coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle)
|
||||
{
|
||||
coordf_t min_layer_height = print_config.opt_float("min_layer_height", idx_nozzle - 1);
|
||||
return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height);
|
||||
}
|
||||
|
||||
// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default,
|
||||
// it should not be smaller than the minimum layer height.
|
||||
coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle)
|
||||
{
|
||||
coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle);
|
||||
coordf_t max_layer_height = print_config.opt_float("max_layer_height", idx_nozzle - 1);
|
||||
coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle - 1);
|
||||
return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height);
|
||||
}
|
||||
|
||||
SlicingParameters SlicingParameters::create_from_config(
|
||||
const PrintConfig &print_config,
|
||||
const PrintObjectConfig &object_config,
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ struct SlicingParameters
|
|||
};
|
||||
static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
||||
// The two slicing parameters lead to the same layering as long as the variable layer thickness is not in action.
|
||||
inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters &sp2)
|
||||
{
|
||||
|
|
@ -183,7 +182,17 @@ extern int generate_layer_height_texture(
|
|||
const std::vector<coordf_t> &layers,
|
||||
void *data, int rows, int cols, bool level_of_detail_2nd_level);
|
||||
|
||||
}; // namespace Slic3r
|
||||
namespace Slicing {
|
||||
// Minimum layer height for the variable layer height algorithm. Nozzle index is 1 based.
|
||||
coordf_t min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle);
|
||||
|
||||
// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default,
|
||||
// it should not be smaller than the minimum layer height.
|
||||
// Nozzle index is 1 based.
|
||||
coordf_t max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle);
|
||||
} // namespace Slicing
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,21 +47,6 @@
|
|||
//==================
|
||||
#define ENABLE_2_2_0_BETA1 1
|
||||
|
||||
// Enable using Y axis of 3Dconnexion devices as zoom
|
||||
#define ENABLE_3DCONNEXION_Y_AS_ZOOM (1 && ENABLE_2_2_0_BETA1)
|
||||
|
||||
// Enable a modified version of the toolbar textures where all the icons are separated by 1 pixel
|
||||
#define ENABLE_MODIFIED_TOOLBAR_TEXTURES (1 && ENABLE_2_2_0_BETA1)
|
||||
|
||||
// Enable configurable paths export (fullpath or not) to 3mf and amf
|
||||
#define ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF (1 && ENABLE_2_2_0_BETA1)
|
||||
|
||||
// Enable 6 degrees of freedom camera
|
||||
#define ENABLE_6DOF_CAMERA (1 && ENABLE_2_2_0_BETA1)
|
||||
|
||||
// Enhance reload from disk to be able to work with 3mf/amf files saved with PrusaSlicer 2.1.0 and earlier
|
||||
#define ENABLE_BACKWARD_COMPATIBLE_RELOAD_FROM_DISK (1 && ENABLE_2_2_0_BETA1)
|
||||
|
||||
|
||||
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
//==================
|
||||
|
|
|
|||
|
|
@ -70,6 +70,34 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f
|
|||
stl_get_size(&stl);
|
||||
}
|
||||
|
||||
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
|
||||
{
|
||||
stl.stats.type = inmemory;
|
||||
|
||||
// count facets and allocate memory
|
||||
stl.stats.number_of_facets = uint32_t(M.indices.size());
|
||||
stl.stats.original_num_facets = int(stl.stats.number_of_facets);
|
||||
stl_allocate(&stl);
|
||||
|
||||
for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) {
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = M.vertices[size_t(M.indices[i](0))];
|
||||
facet.vertex[1] = M.vertices[size_t(M.indices[i](1))];
|
||||
facet.vertex[2] = M.vertices[size_t(M.indices[i](2))];
|
||||
facet.extra[0] = 0;
|
||||
facet.extra[1] = 0;
|
||||
|
||||
stl_normal normal;
|
||||
stl_calculate_normal(normal, &facet);
|
||||
stl_normalize_vector(normal);
|
||||
facet.normal = normal;
|
||||
|
||||
stl.facet_start[i] = facet;
|
||||
}
|
||||
|
||||
stl_get_size(&stl);
|
||||
}
|
||||
|
||||
// #define SLIC3R_TRACE_REPAIR
|
||||
|
||||
void TriangleMesh::repair(bool update_shared_vertices)
|
||||
|
|
@ -599,7 +627,7 @@ std::vector<ExPolygons> TriangleMesh::slice(const std::vector<double> &z)
|
|||
std::vector<float> z_f(z.begin(), z.end());
|
||||
TriangleMeshSlicer mslicer(this);
|
||||
std::vector<ExPolygons> layers;
|
||||
mslicer.slice(z_f, 0.0004f, &layers, [](){});
|
||||
mslicer.slice(z_f, SlicingMode::Regular, 0.0004f, &layers, [](){});
|
||||
return layers;
|
||||
}
|
||||
|
||||
|
|
@ -748,7 +776,7 @@ void TriangleMeshSlicer::set_up_direction(const Vec3f& up)
|
|||
|
||||
|
||||
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, SlicingMode mode, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
|
||||
|
||||
|
|
@ -803,11 +831,38 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
|
|||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&lines, &layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
|
||||
[&lines, &layers, mode, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) {
|
||||
if ((line_idx & 0x0ffff) == 0)
|
||||
throw_on_cancel();
|
||||
this->make_loops(lines[line_idx], &(*layers)[line_idx]);
|
||||
|
||||
Polygons &polygons = (*layers)[line_idx];
|
||||
this->make_loops(lines[line_idx], &polygons);
|
||||
|
||||
if (! polygons.empty()) {
|
||||
if (mode == SlicingMode::Positive) {
|
||||
// Reorient all loops to be CCW.
|
||||
for (Polygon& p : polygons)
|
||||
p.make_counter_clockwise();
|
||||
} else if (mode == SlicingMode::PositiveLargestContour) {
|
||||
// Keep just the largest polygon, make it CCW.
|
||||
double max_area = 0.;
|
||||
Polygon* max_area_polygon = nullptr;
|
||||
for (Polygon& p : polygons) {
|
||||
double a = p.area();
|
||||
if (std::abs(a) > std::abs(max_area)) {
|
||||
max_area = a;
|
||||
max_area_polygon = &p;
|
||||
}
|
||||
}
|
||||
assert(max_area_polygon != nullptr);
|
||||
if (max_area < 0.)
|
||||
max_area_polygon->reverse();
|
||||
Polygon p(std::move(*max_area_polygon));
|
||||
polygons.clear();
|
||||
polygons.emplace_back(std::move(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -885,22 +940,25 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin
|
|||
}
|
||||
}
|
||||
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, const float closing_radius, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
|
||||
void TriangleMeshSlicer::slice(const std::vector<float> &z, SlicingMode mode, const float closing_radius, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
|
||||
{
|
||||
std::vector<Polygons> layers_p;
|
||||
this->slice(z, &layers_p, throw_on_cancel);
|
||||
this->slice(z, (mode == SlicingMode::PositiveLargestContour) ? SlicingMode::Positive : mode, &layers_p, throw_on_cancel);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
|
||||
layers->resize(z.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, z.size()),
|
||||
[&layers_p, closing_radius, layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
|
||||
[&layers_p, mode, closing_radius, layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]);
|
||||
#endif
|
||||
throw_on_cancel();
|
||||
this->make_expolygons(layers_p[layer_id], closing_radius, &(*layers)[layer_id]);
|
||||
ExPolygons &expolygons = (*layers)[layer_id];
|
||||
this->make_expolygons(layers_p[layer_id], closing_radius, &expolygons);
|
||||
if (mode == SlicingMode::PositiveLargestContour)
|
||||
keep_largest_contour_only(expolygons);
|
||||
}
|
||||
});
|
||||
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end";
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class TriangleMesh
|
|||
public:
|
||||
TriangleMesh() : repaired(false) {}
|
||||
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
||||
explicit TriangleMesh(const indexed_triangle_set &M);
|
||||
void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
|
||||
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
|
||||
bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
|
||||
|
|
@ -161,6 +162,16 @@ public:
|
|||
typedef std::vector<IntersectionLine> IntersectionLines;
|
||||
typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
|
||||
|
||||
enum class SlicingMode : uint32_t {
|
||||
// Regular slicing, maintain all contours and their orientation.
|
||||
Regular,
|
||||
// Maintain all contours, orient all contours CCW, therefore all holes are being closed.
|
||||
Positive,
|
||||
// Orient all contours CCW and keep only the contour with the largest area.
|
||||
// This mode is useful for slicing complex objects in vase mode.
|
||||
PositiveLargestContour,
|
||||
};
|
||||
|
||||
class TriangleMeshSlicer
|
||||
{
|
||||
public:
|
||||
|
|
@ -168,8 +179,8 @@ public:
|
|||
TriangleMeshSlicer() : mesh(nullptr) {}
|
||||
TriangleMeshSlicer(const TriangleMesh* mesh) { this->init(mesh, [](){}); }
|
||||
void init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel);
|
||||
void slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
|
||||
void slice(const std::vector<float> &z, const float closing_radius, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
|
||||
void slice(const std::vector<float> &z, SlicingMode mode, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
|
||||
void slice(const std::vector<float> &z, SlicingMode mode, const float closing_radius, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
|
||||
enum FacetSliceType {
|
||||
NoSlice = 0,
|
||||
Slicing = 1,
|
||||
|
|
@ -206,7 +217,7 @@ inline void slice_mesh(
|
|||
{
|
||||
if (mesh.empty()) return;
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
slicer.slice(z, &layers, thr);
|
||||
slicer.slice(z, SlicingMode::Regular, &layers, thr);
|
||||
}
|
||||
|
||||
inline void slice_mesh(
|
||||
|
|
@ -218,7 +229,7 @@ inline void slice_mesh(
|
|||
{
|
||||
if (mesh.empty()) return;
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
slicer.slice(z, closing_radius, &layers, thr);
|
||||
slicer.slice(z, SlicingMode::Regular, closing_radius, &layers, thr);
|
||||
}
|
||||
|
||||
TriangleMesh make_cube(double x, double y, double z);
|
||||
|
|
|
|||
|
|
@ -584,15 +584,20 @@ std::string string_printf(const char *format, ...)
|
|||
va_start(args1, format);
|
||||
va_list args2;
|
||||
va_copy(args2, args1);
|
||||
|
||||
size_t needed_size = ::vsnprintf(nullptr, 0, format, args1) + 1;
|
||||
va_end(args1);
|
||||
|
||||
std::string res(needed_size, '\0');
|
||||
::vsnprintf(&res.front(), res.size(), format, args2);
|
||||
va_end(args2);
|
||||
|
||||
return res;
|
||||
|
||||
static const size_t INITIAL_LEN = 200;
|
||||
std::string buffer(INITIAL_LEN, '\0');
|
||||
|
||||
int bufflen = ::vsnprintf(buffer.data(), INITIAL_LEN - 1, format, args1);
|
||||
|
||||
if (bufflen >= int(INITIAL_LEN)) {
|
||||
buffer.resize(size_t(bufflen) + 1);
|
||||
::vsnprintf(buffer.data(), buffer.size(), format, args2);
|
||||
}
|
||||
|
||||
buffer.resize(bufflen);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string header_slic3r_generated()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue