Merge branch 'master' into fs_emboss

# Conflicts:
#	src/libslic3r/Format/3mf.cpp
This commit is contained in:
Filip Sykala 2022-02-10 14:04:02 +01:00
commit faa63f3182
93 changed files with 3309 additions and 597 deletions

View file

@ -293,7 +293,7 @@ if(NOT TBB_FOUND)
# Create targets
##################################
if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND AND NOT TARGET TBB::tbb)
add_library(TBB::tbb UNKNOWN IMPORTED)
set_target_properties(TBB::tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<svg
version="1.1"
x="0px"
y="0px"
viewBox="0 0 1000 1000"
enable-background="new 0 0 1000 1000"
xml:space="preserve"
id="svg1405"
sodipodi:docname="legend_cog.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1409" /><sodipodi:namedview
id="namedview1407"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.57417071"
inkscape:cx="498.98052"
inkscape:cy="500.72217"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g1403" />
<metadata
id="metadata1400"> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g
id="g1403"><g
id="g3385"
transform="matrix(0.9,0,0,0.9,50,50)"><path
id="Delicious"
d="M 951.5,309.2 C 914.3,221.2 852.2,146.5 774,93.7 734.9,67.3 691.8,46.3 645.7,32 599.6,17.7 550.7,10 500,10 432.4,10 367.9,23.7 309.3,48.5 221.3,85.7 146.5,147.8 93.7,226 67.3,265.1 46.3,308.2 32,354.3 c -14.3,46 -22,95 -22,145.7 0,67.6 13.7,132.1 38.5,190.8 37.2,88 99.3,162.7 177.5,215.5 39.1,26.4 82.2,47.3 128.3,61.7 46,14.4 95,22 145.7,22 67.5,0 132.1,-13.7 190.7,-38.5 88,-37.2 162.7,-99.3 215.6,-177.5 26.4,-39.1 47.3,-82.3 61.7,-128.3 14.3,-46 22,-95 22,-145.7 0,-67.6 -13.7,-132.1 -38.5,-190.8 z m -61,355.7 c -32.2,76 -85.9,140.8 -153.6,186.4 -33.8,22.9 -71.1,41 -110.9,53.3 -39.8,12.3 -82.1,19 -126,19.1 v 0 -423.2 H 76.3 c 0,-0.2 0,-0.3 0,-0.5 0,-58.6 11.8,-114.2 33.3,-164.9 32.1,-76 85.9,-140.8 153.6,-186.5 33.8,-22.8 71.1,-40.9 110.9,-53.3 39.8,-12.4 82.1,-19 126,-19 V 500 h 423.7 c -0.1,58.6 -11.9,114.2 -33.3,164.9 z" /></g><path
style="fill:#ffffff;stroke-width:7.38916;stroke-miterlimit:10;fill-opacity:1"
d="m 77.139043,487.37685 c 3.697394,-84.56835 26.698247,-155.86557 72.010107,-223.21429 16.59166,-24.6608 30.37464,-41.20638 53.34679,-64.03941 27.30359,-27.13822 52.65045,-46.7668 84.88257,-65.73293 55.05852,-32.39773 124.18158,-53.51654 183.99427,-56.214822 7.95557,-0.358893 17.65123,-0.906877 21.54594,-1.217743 L 500,76.392444 V 288.19622 500 H 288.29357 76.58715 Z"
id="path1516" /><path
style="fill:#ffffff;fill-opacity:1;stroke-width:7.38916;stroke-miterlimit:10"
d="M 500,711.76464 V 500 h 211.90502 211.90504 l -0.7671,13.23892 c -1.37279,23.69173 -3.21854,41.23939 -6.18546,58.80541 -24.78089,146.71813 -128.58118,271.82029 -269.07425,324.29358 -38.30204,14.30558 -84.14865,23.99629 -120.68965,25.51049 -8.46675,0.35085 -18.02648,0.87257 -21.24385,1.1594 L 500,923.52927 Z"
id="path3257" /></g>
</svg>

After

(image error) Size: 3 KiB

View file

@ -1,6 +1,6 @@
#version 110
const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
const vec3 back_color_dark = vec3(0.235, 0.235, 0.235);
const vec3 back_color_light = vec3(0.365, 0.365, 0.365);
uniform sampler2D texture;

View file

@ -1,14 +1,9 @@
#version 110
attribute vec3 v_position;
attribute vec2 v_tex_coords;
varying vec2 tex_coords;
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position.x, v_position.y, v_position.z, 1.0);
// the following line leads to crash on some Intel graphics card
//gl_Position = gl_ModelViewProjectionMatrix * vec4(v_position, 1.0);
tex_coords = v_tex_coords;
gl_Position = ftransform();
tex_coords = gl_MultiTexCoord0.xy;
}

View file

@ -0,0 +1,18 @@
#version 110
const vec4 BLACK = vec4(vec3(0.1), 1.0);
const vec4 WHITE = vec4(vec3(1.0), 1.0);
const float emission_factor = 0.25;
// x = tainted, y = specular;
varying vec2 intensity;
varying vec3 world_position;
uniform vec3 world_center;
void main()
{
vec3 delta = world_position - world_center;
vec4 color = delta.x * delta.y * delta.z > 0.0 ? BLACK : WHITE;
gl_FragColor = vec4(vec3(intensity.y) + color.rgb * (intensity.x + emission_factor), 1.0);
}

View file

@ -0,0 +1,40 @@
#version 110
#define INTENSITY_CORRECTION 0.6
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SHININESS 20.0
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
#define INTENSITY_AMBIENT 0.3
// x = tainted, y = specular;
varying vec2 intensity;
varying vec3 world_position;
void main()
{
// First transform the normal into camera space and normalize the result.
vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz;
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
world_position = gl_Vertex.xyz;
gl_Position = ftransform();
}

View file

@ -14,6 +14,7 @@ add_subdirectory(Shiny)
add_subdirectory(semver)
add_subdirectory(libigl)
add_subdirectory(hints)
add_subdirectory(qoi)
# Adding libnest2d project for bin packing...
add_subdirectory(libnest2d)

View file

@ -498,8 +498,6 @@ int CLI::run(int argc, char **argv)
std::string outfile = m_config.opt_string("output");
Print fff_print;
SLAPrint sla_print;
SL1Archive sla_archive(sla_print.printer_config());
sla_print.set_printer(&sla_archive);
sla_print.set_status_callback(
[](const PrintBase::SlicingStatus& s)
{
@ -539,7 +537,7 @@ int CLI::run(int argc, char **argv)
outfile = sla_print.output_filepath(outfile);
// We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata
outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
sla_archive.export_print(outfile_final, sla_print);
sla_print.export_print(outfile_final);
}
if (outfile != outfile_final) {
if (Slic3r::rename_file(outfile, outfile_final)) {
@ -838,6 +836,7 @@ extern "C" {
"leak:libnvidia-glcore.so\n" // For NVidia driver.
"leak:libnvidia-tls.so\n" // For NVidia driver.
"leak:terminator_CreateDevice\n" // For Intel Vulkan drivers.
"leak:swrast_dri.so\n" // For Mesa 3D software driver.
;
}
}

View file

@ -165,8 +165,9 @@ namespace ImGui
const wchar_t LegendColorChanges = 0x2612;
const wchar_t LegendPausePrints = 0x2613;
const wchar_t LegendCustomGCodes = 0x2614;
const wchar_t LegendShells = 0x2615;
const wchar_t LegendToolMarker = 0x2616;
const wchar_t LegendCOG = 0x2615;
const wchar_t LegendShells = 0x2616;
const wchar_t LegendToolMarker = 0x2617;
// void MyFunction(const char* name, const MyMatrix44& v);
}

View file

@ -98,8 +98,12 @@ set(SLIC3R_SOURCES
Format/STL.hpp
Format/SL1.hpp
Format/SL1.cpp
Format/SL1_SVG.hpp
Format/SL1_SVG.cpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/Thumbnails.cpp
GCode/Thumbnails.hpp
GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp
GCode/FindReplace.cpp
@ -352,6 +356,9 @@ encoding_check(libslic3r)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS})
find_package(JPEG REQUIRED)
target_link_libraries(libslic3r
libnest2d
admesh
@ -370,6 +377,8 @@ target_link_libraries(libslic3r
${CMAKE_DL_LIBS}
PNG::PNG
ZLIB::ZLIB
JPEG::JPEG
qoi
)
if (TARGET OpenVDB::openvdb)

View file

@ -1,4 +1,5 @@
#include "Extruder.hpp"
#include "GCodeWriter.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
@ -7,24 +8,24 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config) :
m_id(id),
m_config(config)
{
reset();
// cache values that are going to be called often
m_e_per_mm3 = this->extrusion_multiplier();
if (! m_config->use_volumetric_e)
m_e_per_mm3 /= this->filament_crossection();
}
double Extruder::extrude(double dE)
std::pair<double, double> Extruder::extrude(double dE)
{
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
// Quantize extruder delta to G-code resolution.
dE = GCodeFormatter::quantize_e(dE);
m_E += dE;
m_absolute_E += dE;
if (dE < 0.)
m_retracted -= dE;
return dE;
return std::make_pair(dE, m_E);
}
/* This method makes sure the extruder is retracted by the specified amount
@ -34,28 +35,33 @@ double Extruder::extrude(double dE)
The restart_extra argument sets the extra length to be used for
unretraction. If we're actually performing a retraction, any restart_extra
value supplied will overwrite the previous one if any. */
double Extruder::retract(double length, double restart_extra)
std::pair<double, double> Extruder::retract(double retract_length, double restart_extra)
{
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
double to_retract = std::max(0., length - m_retracted);
// Quantize extruder delta to G-code resolution.
double to_retract = this->retract_to_go(retract_length);
if (to_retract > 0.) {
m_E -= to_retract;
m_absolute_E -= to_retract;
m_retracted += to_retract;
m_restart_extra = restart_extra;
m_restart_extra = restart_extra;
}
return to_retract;
return std::make_pair(to_retract, m_E);
}
double Extruder::unretract()
double Extruder::retract_to_go(double retract_length) const
{
double dE = m_retracted + m_restart_extra;
this->extrude(dE);
return std::max(0., GCodeFormatter::quantize_e(retract_length - m_retracted));
}
std::pair<double, double> Extruder::unretract()
{
auto [dE, emitE] = this->extrude(m_retracted + m_restart_extra);
m_retracted = 0.;
m_restart_extra = 0.;
return dE;
return std::make_pair(dE, emitE);
}
// Used filament volume in mm^3.

View file

@ -12,22 +12,24 @@ class Extruder
{
public:
Extruder(unsigned int id, GCodeConfig *config);
virtual ~Extruder() {}
void reset() {
m_E = 0;
m_absolute_E = 0;
m_retracted = 0;
m_restart_extra = 0;
}
~Extruder() = default;
unsigned int id() const { return m_id; }
double extrude(double dE);
double retract(double length, double restart_extra);
double unretract();
double E() const { return m_E; }
void reset_E() { m_E = 0.; }
// Following three methods emit:
// first - extrusion delta
// second - number to emit to G-code: This may be delta for relative mode or a distance from last reset_E() for absolute mode.
// They also quantize the E axis to G-code resolution.
std::pair<double, double> extrude(double dE);
std::pair<double, double> retract(double retract_length, double restart_extra);
std::pair<double, double> unretract();
// How much to retract yet before retract_length is reached?
// The value is quantized to G-code resolution.
double retract_to_go(double retract_length) const;
// Reset the current state of the E axis (this is only needed for relative extruder addressing mode anyways).
// Returns true if the extruder was non-zero before reset.
bool reset_E() { bool modified = m_E != 0; m_E = 0.; return modified; }
double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; }
double e_per_mm3() const { return m_e_per_mm3; }
// Used filament volume in mm^3.
@ -57,14 +59,16 @@ private:
GCodeConfig *m_config;
// Print-wide global ID of this extruder.
unsigned int m_id;
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
double m_E;
// Current state of the extruder axis.
// For absolute extruder addressing, it is the current state since the last reset (G92 E0) issued at the end of the last retraction.
// For relative extruder addressing, it is the E axis difference emitted into the G-code the last time.
double m_E { 0 };
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
double m_absolute_E;
double m_absolute_E { 0 };
// Current positive amount of retraction.
double m_retracted;
double m_retracted { 0 };
// When retracted, this value stores the extra amount of priming on deretraction.
double m_restart_extra;
double m_restart_extra { 0 };
double m_e_per_mm3;
};
@ -76,4 +80,4 @@ inline bool operator> (const Extruder &e1, const Extruder &e2) { return e1.id()
}
#endif
#endif // slic3r_Extruder_hpp_

View file

@ -125,18 +125,21 @@ static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid";
static constexpr const char* OBJECT_TYPE = "object";
static constexpr const char* VOLUME_TYPE = "volume";
static constexpr const char* NAME_KEY = "name";
static constexpr const char* MODIFIER_KEY = "modifier";
static constexpr const char* NAME_KEY = "name";
static constexpr const char* MODIFIER_KEY = "modifier";
static constexpr const char* VOLUME_TYPE_KEY = "volume_type";
static constexpr const char* MATRIX_KEY = "matrix";
static constexpr const char* SOURCE_FILE_KEY = "source_file";
static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
static constexpr const char* SOURCE_IN_INCHES = "source_in_inches";
static constexpr const char* SOURCE_IN_METERS = "source_in_meters";
static constexpr const char* MATRIX_KEY = "matrix";
static constexpr const char* SOURCE_FILE_KEY = "source_file";
static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches";
static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters";
#if ENABLE_RELOAD_FROM_DISK_REWORK
static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed";
static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets";
@ -843,6 +846,20 @@ namespace Slic3r {
return false;
}
#if ENABLE_RELOAD_FROM_DISK_REWORK
for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) {
ModelObject* o = model.objects[obj_id];
for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) {
ModelVolume* v = o->volumes[vol_id];
if (v->source.input_file.empty())
v->source.input_file = v->name.empty() ? filename : v->name;
if (v->source.volume_idx == -1)
v->source.volume_idx = vol_id;
if (v->source.object_idx == -1)
v->source.object_idx = obj_id;
}
}
#else
int object_idx = 0;
for (ModelObject* o : model.objects) {
int volume_idx = 0;
@ -858,6 +875,7 @@ namespace Slic3r {
}
++object_idx;
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
// // fixes the min z of the model if negative
// model.adjust_min_z();
@ -2120,15 +2138,19 @@ namespace Slic3r {
else if (metadata.key == SOURCE_VOLUME_ID_KEY)
volume->source.volume_idx = ::atoi(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_X_KEY)
volume->source.mesh_offset(0) = ::atof(metadata.value.c_str());
volume->source.mesh_offset.x() = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_Y_KEY)
volume->source.mesh_offset(1) = ::atof(metadata.value.c_str());
volume->source.mesh_offset.y() = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_OFFSET_Z_KEY)
volume->source.mesh_offset(2) = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_IN_INCHES)
volume->source.mesh_offset.z() = ::atof(metadata.value.c_str());
else if (metadata.key == SOURCE_IN_INCHES_KEY)
volume->source.is_converted_from_inches = metadata.value == "1";
else if (metadata.key == SOURCE_IN_METERS)
else if (metadata.key == SOURCE_IN_METERS_KEY)
volume->source.is_converted_from_meters = metadata.value == "1";
#if ENABLE_RELOAD_FROM_DISK_REWORK
else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY)
volume->source.is_from_builtin_objects = metadata.value == "1";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
else
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
@ -3063,36 +3085,40 @@ namespace Slic3r {
// stores volume's type (overrides the modifier field above)
add_metadata(stream, 3, MetadataType::volume, VOLUME_TYPE_KEY, ModelVolume::type_to_string(volume->type()));
// stores volume's local matrix
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\"";
Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix();
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
stream << matrix(r, c);
if (r != 3 || c != 3)
stream << " ";
}
}
stream << "\"/>\n";
// stores volume's local matrix
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\"";
const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix();
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
stream << matrix(r, c);
if (r != 3 || c != 3)
stream << " ";
}
}
stream << "\"/>\n";
// stores volume's source data
{
std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\"";
if (! volume->source.input_file.empty()) {
stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
}
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
if (volume->source.is_converted_from_inches)
stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n";
else if (volume->source.is_converted_from_meters)
stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n";
}
// stores volume's source data
{
std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\"";
if (! volume->source.input_file.empty()) {
stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
}
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
if (volume->source.is_converted_from_inches)
stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
else if (volume->source.is_converted_from_meters)
stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (volume->source.is_from_builtin_objects)
stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
}
// stores volume's config data
for (const std::string& key : volume->config.keys()) {
@ -3201,6 +3227,36 @@ static void handle_legacy_project_loaded(unsigned int version_project_file, Dyna
}
}
bool is_project_3mf(const std::string& filename)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, filename))
return false;
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
// loop the entries to search for config
mz_zip_archive_file_stat stat;
bool config_found = false;
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
std::string name(stat.m_filename);
std::replace(name.begin(), name.end(), '\\', '/');
if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) {
config_found = true;
break;
}
}
}
close_zip_reader(&archive);
return config_found;
}
bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version)
{
if (path == nullptr || model == nullptr)

View file

@ -29,6 +29,9 @@ namespace Slic3r {
class DynamicPrintConfig;
struct ThumbnailData;
// Returns true if the 3mf file with the given filename is a PrusaSlicer project file (i.e. if it contains a config).
extern bool is_project_3mf(const std::string& filename);
// Load the content of a 3mf file into the given model and preset bundle.
extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version);

View file

@ -657,11 +657,16 @@ void AMFParserContext::endElement(const char * /* name */)
if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform)
m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) {
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (m_volume->source.input_file.empty()) {
#else
if (m_volume->source.input_file.empty() && m_volume->type() == ModelVolumeType::MODEL_PART) {
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
m_volume->source.object_idx = (int)m_model.objects.size() - 1;
m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
m_volume->center_geometry_after_creation();
} else
}
else
// pass false if the mesh offset has been already taken from the data
m_volume->center_geometry_after_creation(m_volume->source.input_file.empty());
@ -792,46 +797,44 @@ void AMFParserContext::endElement(const char * /* name */)
// Is this volume a modifier volume?
// "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART);
} else if (strcmp(opt_key, "volume_type") == 0) {
}
else if (strcmp(opt_key, "volume_type") == 0)
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
else if (strcmp(opt_key, "matrix") == 0) {
else if (strcmp(opt_key, "matrix") == 0)
m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]);
}
else if (strcmp(opt_key, "source_file") == 0) {
else if (strcmp(opt_key, "source_file") == 0)
m_volume->source.input_file = m_value[1];
}
else if (strcmp(opt_key, "source_object_id") == 0) {
else if (strcmp(opt_key, "source_object_id") == 0)
m_volume->source.object_idx = ::atoi(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_volume_id") == 0) {
else if (strcmp(opt_key, "source_volume_id") == 0)
m_volume->source.volume_idx = ::atoi(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_x") == 0) {
m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_y") == 0) {
m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_offset_z") == 0) {
m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str());
}
else if (strcmp(opt_key, "source_in_inches") == 0) {
else if (strcmp(opt_key, "source_offset_x") == 0)
m_volume->source.mesh_offset.x() = ::atof(m_value[1].c_str());
else if (strcmp(opt_key, "source_offset_y") == 0)
m_volume->source.mesh_offset.y() = ::atof(m_value[1].c_str());
else if (strcmp(opt_key, "source_offset_z") == 0)
m_volume->source.mesh_offset.z() = ::atof(m_value[1].c_str());
else if (strcmp(opt_key, "source_in_inches") == 0)
m_volume->source.is_converted_from_inches = m_value[1] == "1";
}
else if (strcmp(opt_key, "source_in_meters") == 0) {
else if (strcmp(opt_key, "source_in_meters") == 0)
m_volume->source.is_converted_from_meters = m_value[1] == "1";
}
#if ENABLE_RELOAD_FROM_DISK_REWORK
else if (strcmp(opt_key, "source_is_builtin_volume") == 0)
m_volume->source.is_from_builtin_objects = m_value[1] == "1";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
}
} else if (m_path.size() == 3) {
}
else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) {
if (m_material)
m_material->attributes[m_value[0]] = m_value[1];
} else if (m_path[1] == NODE_TYPE_OBJECT) {
}
else if (m_path[1] == NODE_TYPE_OBJECT) {
if (m_object && m_value[0] == "name")
m_object->name = std::move(m_value[1]);
}
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) {
}
else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) {
if (m_volume && m_value[0] == "name")
m_volume->name = std::move(m_value[1]);
}
@ -919,7 +922,11 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut
unsigned int counter = 0;
for (ModelVolume* v : o->volumes) {
++counter;
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (v->source.input_file.empty())
#else
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART)
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
v->source.input_file = path;
if (v->name.empty()) {
v->name = o->name;
@ -1068,7 +1075,11 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti
for (ModelObject *o : model->objects)
for (ModelVolume *v : o->volumes)
if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART))
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (v->source.input_file.empty())
#else
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART)
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
v->source.input_file = path;
return true;
@ -1237,18 +1248,15 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
stream << " <metadata type=\"slic3r.matrix\">";
const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix();
stream << std::setprecision(std::numeric_limits<double>::max_digits10);
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
stream << matrix(r, c);
if ((r != 3) || (c != 3))
if (r != 3 || c != 3)
stream << " ";
}
}
stream << "</metadata>\n";
if (!volume->source.input_file.empty())
{
if (!volume->source.input_file.empty()) {
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";
stream << " <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
@ -1262,12 +1270,16 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
else if (volume->source.is_converted_from_meters)
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (volume->source.is_from_builtin_objects)
stream << " <metadata type=\"slic3r.source_is_builtin_volume\">1</metadata>\n";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
const indexed_triangle_set &its = volume->mesh().its;
for (size_t i = 0; i < its.indices.size(); ++i) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)
stream << " <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n";
stream << " <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n";
stream << " </triangle>\n";
}
stream << " </volume>\n";

View file

@ -446,8 +446,8 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print)
std::unique_ptr<sla::RasterBase> SL1Archive::create_raster() const
{
sla::RasterBase::Resolution res;
sla::RasterBase::PixelDim pxdim;
sla::Resolution res;
sla::PixelDim pxdim;
std::array<bool, 2> mirror;
double w = m_cfg.display_width.getFloat();
@ -468,8 +468,8 @@ std::unique_ptr<sla::RasterBase> SL1Archive::create_raster() const
std::swap(pw, ph);
}
res = sla::RasterBase::Resolution{pw, ph};
pxdim = sla::RasterBase::PixelDim{w / pw, h / ph};
res = sla::Resolution{pw, ph};
pxdim = sla::PixelDim{w / pw, h / ph};
sla::RasterBase::Trafo tr{orientation, mirror};
double gamma = m_cfg.gamma_correction.getFloat();

View file

@ -15,27 +15,16 @@ protected:
std::unique_ptr<sla::RasterBase> create_raster() const override;
sla::RasterEncoder get_encoder() const override;
SLAPrinterConfig & cfg() { return m_cfg; }
const SLAPrinterConfig & cfg() const { return m_cfg; }
public:
SL1Archive() = default;
explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "");
void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "")
{
Zipper zipper(fname);
export_print(zipper, print, projectname);
}
void apply(const SLAPrinterConfig &cfg) override
{
auto diff = m_cfg.diff(cfg);
if (!diff.empty()) {
m_cfg.apply_only(cfg, diff);
m_layers = {};
}
}
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override;
};
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);

View file

@ -0,0 +1,221 @@
#include "SL1_SVG.hpp"
#include "SLA/RasterBase.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include <limits>
#include <cstdint>
#include <algorithm>
namespace Slic3r {
namespace {
size_t constexpr coord_t_bufsize = 40;
char const* decimal_from(coord_t snumber, char* buffer)
{
std::make_unsigned_t<coord_t> number = 0;
char* ret = buffer;
if( snumber < 0 ) {
*buffer++ = '-';
number = -snumber;
} else
number = snumber;
if( number == 0 ) {
*buffer++ = '0';
} else {
char* p_first = buffer;
while( number != 0 ) {
*buffer++ = '0' + number % 10;
number /= 10;
}
std::reverse( p_first, buffer );
}
*buffer = '\0';
return ret;
}
inline std::string coord2str(coord_t crd)
{
char buf[coord_t_bufsize];
return decimal_from(crd, buf);
}
void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb)
{
if (tr.flipXY) {
for (auto &p : ep.contour.points) std::swap(p.x(), p.y());
for (auto &h : ep.holes)
for (auto &p : h.points) std::swap(p.x(), p.y());
}
if (tr.mirror_x){
for (auto &p : ep.contour.points) p.x() = bb.max.x() - p.x() + bb.min.x();
for (auto &h : ep.holes)
for (auto &p : h.points) p.x() = bb.max.x() - p.x() + bb.min.x();
}
if (tr.mirror_y){
for (auto &p : ep.contour.points) p.y() = bb.max.y() - p.y() + bb.min.y();
for (auto &h : ep.holes)
for (auto &p : h.points) p.y() = bb.max.y() - p.y() + bb.min.y();
}
}
void append_svg(std::string &buf, const Polygon &poly)
{
if (poly.points.empty())
return;
auto c = poly.points.front();
char intbuf[coord_t_bufsize];
buf += std::string("<path d=\"M ") + decimal_from(c.x(), intbuf);
buf += std::string(" ") + decimal_from(c.y(), intbuf) + " m";
for (auto &p : poly) {
auto d = p - c;
if (d.squaredNorm() == 0) continue;
buf += " ";
buf += decimal_from(p.x() - c.x(), intbuf);
buf += " ";
buf += decimal_from(p.y() - c.y(), intbuf);
c = p;
}
buf += " z\""; // mark path as closed
buf += " />\n";
}
} // namespace
// A fake raster from SVG
class SVGRaster : public sla::RasterBase {
// Resolution here will be used for svg boundaries
BoundingBox m_bb;
sla::Resolution m_res;
Trafo m_trafo;
Vec2d m_sc;
std::string m_svg;
public:
SVGRaster(const BoundingBox &svgarea, sla::Resolution res, Trafo tr = {})
: m_bb{svgarea}
, m_res{res}
, m_trafo{tr}
, m_sc{double(m_res.width_px) / m_bb.size().x(), double(m_res.height_px) / m_bb.size().y()}
{
// Inside the svg header, the boundaries will be defined in mm to
// the actual bed size. The viewport is then defined to work with our
// scaled coordinates. All the exported polygons will be in these scaled
// coordinates but svg rendering software will interpret them correctly
// in mm due to the header's definition.
std::string wf = float_to_string_decimal_point(unscaled<float>(m_bb.size().x()));
std::string hf = float_to_string_decimal_point(unscaled<float>(m_bb.size().y()));
std::string w = coord2str(coord_t(m_res.width_px));
std::string h = coord2str(coord_t(m_res.height_px));
// Notice the header also defines the fill-rule as nonzero which should
// generate correct results for our ExPolygons.
// Add svg header.
m_svg =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
"<svg height=\"" + hf + "mm" + "\" width=\"" + wf + "mm" + "\" viewBox=\"0 0 " + w + " " + h +
"\" style=\"fill: white; stroke: none; fill-rule: nonzero\" "
"xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n";
// Add black background;
m_svg += "<rect fill='black' stroke='none' x='0' y='0' width='" + w + "' height='" + h + "'/>\n";
}
void draw(const ExPolygon& poly) override
{
auto cpoly = poly;
double tol = std::min(m_bb.size().x() / double(m_res.width_px),
m_bb.size().y() / double(m_res.height_px));
ExPolygons cpolys = poly.simplify(tol);
for (auto &cpoly : cpolys) {
transform(cpoly, m_trafo, m_bb);
for (auto &p : cpoly.contour.points)
p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())};
for (auto &h : cpoly.holes)
for (auto &p : h)
p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())};
append_svg(m_svg, cpoly.contour);
for (auto &h : cpoly.holes)
append_svg(m_svg, h);
}
}
Trafo trafo() const override { return m_trafo; }
sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override
{
std::vector<uint8_t> data;
constexpr const char finish[] = "</svg>\n";
data.reserve(m_svg.size() + std::size(finish));
std::copy(m_svg.begin(), m_svg.end(), std::back_inserter(data));
std::copy(finish, finish + std::size(finish) - 1, std::back_inserter(data));
return sla::EncodedRaster{std::move(data), "svg"};
}
};
std::unique_ptr<sla::RasterBase> SL1_SVGArchive::create_raster() const
{
auto w = cfg().display_width.getFloat();
auto h = cfg().display_height.getFloat();
// auto res_x = size_t(cfg().display_pixels_x.getInt());
// auto res_y = size_t(cfg().display_pixels_y.getInt());
float precision_nm = scaled<float>(cfg().sla_output_precision.getFloat());
size_t res_x = std::round(scaled(w) / precision_nm);
size_t res_y = std::round(scaled(h) / precision_nm);
std::array<bool, 2> mirror;
mirror[X] = cfg().display_mirror_x.getBool();
mirror[Y] = cfg().display_mirror_y.getBool();
auto ro = cfg().display_orientation.getInt();
sla::RasterBase::Orientation orientation =
ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait :
sla::RasterBase::roLandscape;
if (orientation == sla::RasterBase::roPortrait) {
std::swap(w, h);
std::swap(res_x, res_y);
}
BoundingBox svgarea{{0, 0}, {scaled(w), scaled(h)}};
sla::RasterBase::Trafo tr{orientation, mirror};
// Gamma does not really make sense in an svg, right?
// double gamma = cfg().gamma_correction.getFloat();
return std::make_unique<SVGRaster>(svgarea, sla::Resolution{res_x, res_y}, tr);
}
sla::RasterEncoder SL1_SVGArchive::get_encoder() const
{
return nullptr;
}
} // namespace Slic3r

View file

@ -0,0 +1,22 @@
#ifndef SL1_SVG_HPP
#define SL1_SVG_HPP
#include "SL1.hpp"
namespace Slic3r {
class SL1_SVGArchive: public SL1Archive {
protected:
// Override the factory methods to produce svg instead of a real raster.
std::unique_ptr<sla::RasterBase> create_raster() const override;
sla::RasterEncoder get_encoder() const override;
public:
using SL1Archive::SL1Archive;
};
} // namespace Slic3r
#endif // SL1_SVG_HPP

View file

@ -6,6 +6,7 @@
#include "EdgeGrid.hpp"
#include "Geometry/ConvexHull.hpp"
#include "GCode/PrintExtents.hpp"
#include "GCode/Thumbnails.hpp"
#include "GCode/WipeTower.hpp"
#include "ShortestPath.hpp"
#include "Print.hpp"
@ -26,7 +27,6 @@
#include <boost/foreach.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/cstdio.hpp>
@ -54,8 +54,6 @@
#include <Shiny/Shiny.h>
#include "miniz_extension.hpp"
using namespace std::literals::string_view_literals;
#if 0
@ -156,63 +154,52 @@ namespace Slic3r {
std::string Wipe::wipe(GCode& gcodegen, bool toolchange)
{
std::string gcode;
std::string gcode;
const Extruder &extruder = *gcodegen.writer().extruder();
/* Reduce feedrate a bit; travel speed is often too high to move on existing material.
Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
// get the retraction length
double length = toolchange
? gcodegen.writer().extruder()->retract_length_toolchange()
: gcodegen.writer().extruder()->retract_length();
// Shorten the retraction length by the amount already retracted before wipe.
length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
if (length > 0) {
/* Calculate how long we need to travel in order to consume the required
amount of retraction. In other words, how far do we move in XY at wipe_speed
for the time needed to consume retract_length at retract_speed? */
double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
/* Take the stored wipe path and replace first point with the current actual position
(they might be different, for example, in case of loop clipping). */
Polyline wipe_path;
wipe_path.append(gcodegen.last_pos());
wipe_path.append(
this->path.points.begin() + 1,
this->path.points.end()
);
wipe_path.clip_end(wipe_path.length() - wipe_dist);
// subdivide the retraction in segments
if (!wipe_path.empty()) {
// add tag for processor
// Remaining quantized retraction length.
if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length());
retract_length > 0 && this->path.size() >= 2) {
// Reduce feedrate a bit; travel speed is often too high to move on existing material.
// Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
const double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
// Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
// due to rounding (TODO: test and/or better math for this).
const double xy_to_e = 0.95 * extruder.retract_speed() / wipe_speed;
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
auto it = this->path.points.begin();
Vec2d p = gcodegen.point_to_gcode_quantized(*(++ it));
if (p != prev) {
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n";
for (const Line& line : wipe_path.lines()) {
double segment_length = line.length();
/* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
due to rounding (TODO: test and/or better math for this) */
double dE = length * (segment_length / wipe_dist) * 0.95;
auto end = this->path.points.end();
bool done = false;
for (; it != end && ! done; ++ it) {
p = gcodegen.point_to_gcode_quantized(*it);
double segment_length = (p - prev).norm();
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
if (dE > retract_length - EPSILON) {
if (dE > retract_length + EPSILON)
// Shorten the segment.
p = prev + (p - prev) * (retract_length / dE);
dE = retract_length;
done = true;
}
//FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
// Is it here for the cooling markers? Or should it be outside of the cycle?
gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
gcode += gcodegen.writer().extrude_to_xy(
gcodegen.point_to_gcode(line.b),
-dE,
"wipe and retract"
);
gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
gcode += gcodegen.writer().extrude_to_xy(p, -dE, "wipe and retract");
prev = p;
retract_length -= dE;
}
// add tag for processor
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
gcodegen.set_last_pos(wipe_path.points.back());
gcodegen.set_last_pos(gcodegen.gcode_to_point(prev));
}
// prevent wiping again on same path
this->reset_path();
}
// Prevent wiping again on the same path.
this->reset_path();
return gcode;
}
@ -937,49 +924,6 @@ namespace DoExport {
}
}
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
{
// Write thumbnails using base64 encoding
if (thumbnail_cb != nullptr)
{
const size_t max_row_length = 78;
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true });
for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
{
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));
output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str());
unsigned int row_count = 0;
while (encoded.size() > max_row_length)
{
output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
encoded = encoded.substr(max_row_length);
++row_count;
}
if (encoded.size() > 0)
output((boost::format("; %s\n") % encoded).str().c_str());
output("; thumbnail end\n;\n");
mz_free(png_data);
}
}
throw_if_canceled();
}
}
}
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
static std::string update_print_stats_and_format_filament_stats(
const bool has_wipe_tower,
@ -1163,9 +1107,16 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Write information on the generator.
file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str());
DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
[&file](const char* sz) { file.write(sz); },
[&print]() { print.throw_if_canceled(); });
// Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
// If "thumbnails_format" is not defined, export to PNG.
if (const auto [thumbnails, thumbnails_format] = std::make_pair(
print.full_print_config().option<ConfigOptionPoints>("thumbnails"),
print.full_print_config().option<ConfigOptionEnum<GCodeThumbnailsFormat>>("thumbnails_format"));
thumbnails)
GCodeThumbnails::export_thumbnails_to_file(
thumbnail_cb, thumbnails->values, thumbnails_format ? thumbnails_format->value : GCodeThumbnailsFormat::PNG,
[&file](const char* sz) { file.write(sz); },
[&print]() { print.throw_if_canceled(); });
// Write notes (content of the Print Settings tab -> Notes)
{
@ -3047,13 +2998,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double path_length = 0.;
{
std::string comment = m_config.gcode_comments ? description : "";
for (const Line &line : path.polyline.lines()) {
const double line_length = line.length() * SCALING_FACTOR;
Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front());
auto it = path.polyline.points.begin();
auto end = path.polyline.points.end();
for (++ it; it != end; ++ it) {
Vec2d p = this->point_to_gcode_quantized(*it);
const double line_length = (p - prev).norm();
path_length += line_length;
gcode += m_writer.extrude_to_xy(
this->point_to_gcode(line.b),
e_per_mm * line_length,
comment);
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment);
prev = p;
}
}
if (m_enable_cooling_markers)
@ -3276,7 +3229,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
Vec2d GCode::point_to_gcode(const Point &point) const
{
Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
return unscale(point) + m_origin - extruder_offset;
return unscaled<double>(point) + m_origin - extruder_offset;
}
Vec2d GCode::point_to_gcode_quantized(const Point &point) const
{
Vec2d p = this->point_to_gcode(point);
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
}
// convert a model-space scaled point into G-code coordinates

View file

@ -55,9 +55,9 @@ public:
Polyline path;
Wipe() : enable(false) {}
bool has_path() const { return !this->path.points.empty(); }
void reset_path() { this->path = Polyline(); }
std::string wipe(GCode &gcodegen, bool toolchange = false);
bool has_path() const { return ! this->path.empty(); }
void reset_path() { this->path.clear(); }
std::string wipe(GCode &gcodegen, bool toolchange);
};
class WipeTowerIntegration {
@ -151,7 +151,10 @@ public:
void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
const Point& last_pos() const { return m_last_pos; }
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
Vec2d point_to_gcode(const Point &point) const;
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
Vec2d point_to_gcode_quantized(const Point &point) const;
Point gcode_to_point(const Vec2d &point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }

View file

@ -1196,6 +1196,7 @@ void GCodeProcessor::reset()
m_line_id = 0;
m_last_line_id = 0;
m_feedrate = 0.0f;
m_feed_multiply.reset();
m_width = 0.0f;
m_height = 0.0f;
m_forced_width = 0.0f;
@ -1698,6 +1699,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool
break;
case '2':
switch (cmd[3]) {
case '0': { process_M220(line); break; } // Set Feedrate Percentage
case '1': { process_M221(line); break; } // Set extrude factor override percentage
default: break;
}
@ -1955,7 +1957,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first)
m_result.spiral_vase_layers.back().second.second = move_id;
else
m_result.spiral_vase_layers.push_back({ m_end_position[Z], { move_id, move_id } });
m_result.spiral_vase_layers.push_back({ static_cast<float>(m_end_position[Z]), { move_id, move_id } });
}
#endif // ENABLE_SPIRAL_VASE_LAYERS
return;
@ -2498,14 +2500,14 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
// updates feedrate from line, if present
if (line.has_f())
m_feedrate = line.f() * MMMIN_TO_MMSEC;
m_feedrate = m_feed_multiply.current * line.f() * MMMIN_TO_MMSEC;
// calculates movement deltas
float max_abs_delta = 0.0f;
AxisCoords delta_pos;
for (unsigned char a = X; a <= E; ++a) {
delta_pos[a] = m_end_position[a] - m_start_position[a];
max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a]));
max_abs_delta = std::max<float>(max_abs_delta, std::abs(delta_pos[a]));
}
// no displacement, return
@ -2615,7 +2617,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
if (curr.abs_axis_feedrate[a] != 0.0f) {
float axis_max_feedrate = get_axis_max_feedrate(static_cast<PrintEstimatedStatistics::ETimeMode>(i), static_cast<Axis>(a));
if (axis_max_feedrate != 0.0f)
min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]);
min_feedrate_factor = std::min<float>(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]);
}
}
@ -2863,7 +2865,7 @@ void GCodeProcessor::process_G61(const GCodeReader::GCodeLine& line)
modified = true;
}
if (line.has_f())
m_feedrate = line.f();
m_feedrate = m_feed_multiply.current * line.f();
if (!modified)
m_end_position = m_saved_position;
@ -3136,6 +3138,20 @@ void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line)
}
}
void GCodeProcessor::process_M220(const GCodeReader::GCodeLine& line)
{
if (m_flavor != gcfMarlinLegacy && m_flavor != gcfMarlinFirmware)
return;
if (line.has('B'))
m_feed_multiply.saved = m_feed_multiply.current;
float value;
if (line.has_value('S', value))
m_feed_multiply.current = value * 0.01f;
if (line.has('R'))
m_feed_multiply.current = m_feed_multiply.saved;
}
void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line)
{
float value_s;
@ -3279,7 +3295,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type)
#else
Vec3f(m_end_position[X], m_end_position[Y], m_processing_start_custom_gcode ? m_first_layer_height : m_end_position[Z]) + m_extruder_offsets[m_extruder_id],
#endif // ENABLE_Z_OFFSET_CORRECTION
m_end_position[E] - m_start_position[E],
static_cast<float>(m_end_position[E] - m_start_position[E]),
m_feedrate,
m_width,
m_height,

View file

@ -178,7 +178,7 @@ namespace Slic3r {
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
private:
using AxisCoords = std::array<float, 4>;
using AxisCoords = std::array<double, 4>;
using ExtruderColors = std::vector<unsigned char>;
using ExtruderTemps = std::vector<float>;
@ -525,6 +525,17 @@ namespace Slic3r {
unsigned int m_line_id;
unsigned int m_last_line_id;
float m_feedrate; // mm/s
struct FeedMultiply
{
float current; // percentage
float saved; // percentage
void reset() {
current = 1.0f;
saved = 1.0f;
}
};
FeedMultiply m_feed_multiply;
float m_width; // mm
float m_height; // mm
float m_forced_width; // mm
@ -719,6 +730,9 @@ namespace Slic3r {
// Advanced settings
void process_M205(const GCodeReader::GCodeLine& line);
// Set Feedrate Percentage
void process_M220(const GCodeReader::GCodeLine& line);
// Set extrude factor override percentage
void process_M221(const GCodeReader::GCodeLine& line);

View file

@ -0,0 +1,119 @@
#include "Thumbnails.hpp"
#include "../miniz_extension.hpp"
#include <qoi/qoi.h>
#include <jpeglib.h>
#include <jerror.h>
namespace Slic3r::GCodeThumbnails {
using namespace std::literals;
struct CompressedPNG : CompressedImageBuffer
{
~CompressedPNG() override { if (data) mz_free(data); }
std::string_view tag() const override { return "thumbnail"sv; }
};
struct CompressedJPG : CompressedImageBuffer
{
~CompressedJPG() override { free(data); }
std::string_view tag() const override { return "thumbnail_JPG"sv; }
};
struct CompressedQOI : CompressedImageBuffer
{
~CompressedQOI() override { free(data); }
std::string_view tag() const override { return "thumbnail_QOI"sv; }
};
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_png(const ThumbnailData &data)
{
auto out = std::make_unique<CompressedPNG>();
out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1);
return out;
}
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_jpg(const ThumbnailData& data)
{
// Take vector of RGBA pixels and flip the image vertically
std::vector<unsigned char> rgba_pixels(data.pixels.size());
const unsigned int row_size = data.width * 4;
for (unsigned int y = 0; y < data.height; ++y) {
::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size);
}
// Store pointers to scanlines start for later use
std::vector<unsigned char*> rows_ptrs;
rows_ptrs.reserve(data.height);
for (unsigned int y = 0; y < data.height; ++y) {
rows_ptrs.emplace_back(&rgba_pixels[y * row_size]);
}
std::vector<unsigned char> compressed_data(data.pixels.size());
unsigned char* compressed_data_ptr = compressed_data.data();
unsigned long compressed_data_size = data.pixels.size();
jpeg_error_mgr err;
jpeg_compress_struct info;
info.err = jpeg_std_error(&err);
jpeg_create_compress(&info);
jpeg_mem_dest(&info, &compressed_data_ptr, &compressed_data_size);
info.image_width = data.width;
info.image_height = data.height;
info.input_components = 4;
info.in_color_space = JCS_EXT_RGBA;
jpeg_set_defaults(&info);
jpeg_set_quality(&info, 85, TRUE);
jpeg_start_compress(&info, TRUE);
jpeg_write_scanlines(&info, rows_ptrs.data(), data.height);
jpeg_finish_compress(&info);
jpeg_destroy_compress(&info);
// FIXME -> Add error checking
auto out = std::make_unique<CompressedJPG>();
out->data = malloc(compressed_data_size);
out->size = size_t(compressed_data_size);
::memcpy(out->data, (const void*)compressed_data.data(), out->size);
return out;
}
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_qoi(const ThumbnailData &data)
{
qoi_desc desc;
desc.width = data.width;
desc.height = data.height;
desc.channels = 4;
desc.colorspace = QOI_SRGB;
// Take vector of RGBA pixels and flip the image vertically
std::vector<uint8_t> rgba_pixels(data.pixels.size() * 4);
size_t row_size = data.width * 4;
for (size_t y = 0; y < data.height; ++ y)
memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size);
auto out = std::make_unique<CompressedQOI>();
int size;
out->data = qoi_encode((const void*)rgba_pixels.data(), &desc, &size);
out->size = size;
return out;
}
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format)
{
switch (format) {
case GCodeThumbnailsFormat::PNG:
default:
return compress_thumbnail_png(data);
case GCodeThumbnailsFormat::JPG:
return compress_thumbnail_jpg(data);
case GCodeThumbnailsFormat::QOI:
return compress_thumbnail_qoi(data);
}
}
} // namespace Slic3r::GCodeThumbnails

View file

@ -0,0 +1,60 @@
#ifndef slic3r_GCodeThumbnails_hpp_
#define slic3r_GCodeThumbnails_hpp_
#include "../Point.hpp"
#include "../PrintConfig.hpp"
#include "ThumbnailData.hpp"
#include <vector>
#include <memory>
#include <string_view>
#include <boost/beast/core/detail/base64.hpp>
namespace Slic3r::GCodeThumbnails {
struct CompressedImageBuffer
{
void *data { nullptr };
size_t size { 0 };
virtual ~CompressedImageBuffer() {}
virtual std::string_view tag() const = 0;
};
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format);
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
{
// Write thumbnails using base64 encoding
if (thumbnail_cb != nullptr) {
static constexpr const size_t max_row_length = 78;
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true });
for (const ThumbnailData& data : thumbnails)
if (data.is_valid()) {
auto compressed = compress_thumbnail(data, format);
if (compressed->data && compressed->size) {
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size));
encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size));
output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str());
while (encoded.size() > max_row_length) {
output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
encoded = encoded.substr(max_row_length);
}
if (encoded.size() > 0)
output((boost::format("; %s\n") % encoded).str().c_str());
output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str());
}
throw_if_canceled();
}
}
}
} // namespace Slic3r::GCodeThumbnails
#endif // slic3r_GCodeThumbnails_hpp_

View file

@ -74,7 +74,7 @@ static double calc_max_layer_height(const PrintConfig &config, double max_object
{
double max_layer_height = std::numeric_limits<double>::max();
for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) {
double mlh = config.max_layer_height.values[i];
double mlh = config.max_layer_height.get_at(i);
if (mlh == 0.)
mlh = 0.75 * config.nozzle_diameter.values[i];
max_layer_height = std::min(max_layer_height, mlh);

View file

@ -79,7 +79,7 @@ std::string GCodeWriter::postamble() const
std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const
{
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return "";
return {};
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
@ -192,32 +192,18 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration)
std::string GCodeWriter::reset_e(bool force)
{
if (FLAVOR_IS(gcfMach3)
|| FLAVOR_IS(gcfMakerWare)
|| FLAVOR_IS(gcfSailfish))
return "";
if (m_extruder != nullptr) {
if (m_extruder->E() == 0. && ! force)
return "";
m_extruder->reset_E();
}
if (! m_extrusion_axis.empty() && ! this->config.use_relative_e_distances) {
std::ostringstream gcode;
gcode << "G92 " << m_extrusion_axis << "0";
if (this->config.gcode_comments) gcode << " ; reset extrusion distance";
gcode << "\n";
return gcode.str();
} else {
return "";
}
return
FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish) || this->config.use_relative_e_distances ||
(m_extruder != nullptr && ! m_extruder->reset_E() && ! force) ||
m_extrusion_axis.empty() ?
std::string{} :
std::string("G92 ") + m_extrusion_axis + (this->config.gcode_comments ? "0 ; reset extrusion distance\n" : "0\n");
}
std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
{
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
return "";
return {};
unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5);
if (!allow_100) percent = std::min(percent, (unsigned int)99);
@ -269,8 +255,8 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
m_pos.x() = point.x();
m_pos.y() = point.y();
GCodeG1Formatter w;
w.emit_xy(point);
@ -290,9 +276,9 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
don't perform the Z move but we only move in the XY plane and
adjust the nominal Z by reducing the lift amount that will be
used for unlift. */
if (!this->will_move_z(point(2))) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted -= (point(2) - nominal_z);
if (!this->will_move_z(point.z())) {
double nominal_z = m_pos.z() - m_lifted;
m_lifted -= (point.z() - nominal_z);
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
if (std::abs(m_lifted) < EPSILON)
@ -318,11 +304,11 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
we don't perform the move but we only adjust the nominal Z by
reducing the lift amount that will be used for unlift. */
if (!this->will_move_z(z)) {
double nominal_z = m_pos(2) - m_lifted;
double nominal_z = m_pos.z() - m_lifted;
m_lifted -= (z - nominal_z);
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return "";
return {};
}
/* In all the other cases, we perform an actual Z move and cancel
@ -333,7 +319,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
{
m_pos(2) = z;
m_pos.z() = z;
double speed = this->config.travel_speed_z.value;
if (speed == 0.)
@ -351,8 +337,8 @@ bool GCodeWriter::will_move_z(double z) const
/* If target Z is lower than current Z but higher than nominal Z
we don't perform an actual Z move. */
if (m_lifted > 0) {
double nominal_z = m_pos(2) - m_lifted;
if (z >= nominal_z && z <= m_pos(2))
double nominal_z = m_pos.z() - m_lifted;
if (z >= nominal_z && z <= m_pos.z())
return false;
}
return true;
@ -360,17 +346,17 @@ bool GCodeWriter::will_move_z(double z) const
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
m_extruder->extrude(dE);
m_pos.x() = point.x();
m_pos.y() = point.y();
GCodeG1Formatter w;
w.emit_xy(point);
w.emit_e(m_extrusion_axis, m_extruder->E());
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
#if 0
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
{
m_pos = point;
@ -383,6 +369,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
#endif
std::string GCodeWriter::retract(bool before_wipe)
{
@ -422,14 +409,13 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
restart_extra = restart_extra * area;
}
std::string gcode;
if (double dE = m_extruder->retract(length, restart_extra); dE != 0) {
if (auto [dE, emitE] = m_extruder->retract(length, restart_extra); dE != 0) {
if (this->config.use_firmware_retraction) {
gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n";
} else if (! m_extrusion_axis.empty()) {
GCodeG1Formatter w;
w.emit_e(m_extrusion_axis, m_extruder->E());
w.emit_e(m_extrusion_axis, emitE);
w.emit_f(m_extruder->retract_speed() * 60.);
w.emit_comment(this->config.gcode_comments, comment);
gcode = w.string();
@ -449,14 +435,14 @@ std::string GCodeWriter::unretract()
if (FLAVOR_IS(gcfMakerWare))
gcode = "M101 ; extruder on\n";
if (double dE = m_extruder->unretract(); dE != 0) {
if (auto [dE, emitE] = m_extruder->unretract(); dE != 0) {
if (this->config.use_firmware_retraction) {
gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n";
gcode += this->reset_e();
} else if (! m_extrusion_axis.empty()) {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
GCodeG1Formatter w;
w.emit_e(m_extrusion_axis, m_extruder->E());
w.emit_e(m_extrusion_axis, emitE);
w.emit_f(m_extruder->deretract_speed() * 60.);
w.emit_comment(this->config.gcode_comments, " ; unretract");
gcode += w.string();
@ -476,21 +462,21 @@ std::string GCodeWriter::lift()
{
double above = this->config.retract_lift_above.get_at(m_extruder->id());
double below = this->config.retract_lift_below.get_at(m_extruder->id());
if (m_pos(2) >= above && (below == 0 || m_pos(2) <= below))
if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below))
target_lift = this->config.retract_lift.get_at(m_extruder->id());
}
if (m_lifted == 0 && target_lift > 0) {
m_lifted = target_lift;
return this->_travel_to_z(m_pos(2) + target_lift, "lift Z");
return this->_travel_to_z(m_pos.z() + target_lift, "lift Z");
}
return "";
return {};
}
std::string GCodeWriter::unlift()
{
std::string gcode;
if (m_lifted > 0) {
gcode += this->_travel_to_z(m_pos(2) - m_lifted, "restore layer Z");
gcode += this->_travel_to_z(m_pos.z() - m_lifted, "restore layer Z");
m_lifted = 0;
}
return gcode;

View file

@ -61,7 +61,7 @@ public:
std::string travel_to_z(double z, const std::string &comment = std::string());
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
@ -121,6 +121,14 @@ public:
// static constexpr const int E_EXPORT_DIGITS = 9;
#endif
static constexpr const std::array<double, 10> pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000.};
static constexpr const std::array<double, 10> pow_10_inv{1./1., 1./10., 1./100., 1./1000., 1./10000., 1./100000., 1./1000000., 1./10000000., 1./100000000., 1./1000000000.};
// Quantize doubles to a resolution of the G-code.
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
void emit_axis(const char axis, const double v, size_t digits);
void emit_xy(const Vec2d &point) {

View file

@ -484,7 +484,7 @@ static std::vector<std::string> s_Preset_printer_options {
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
"default_print_profile", "inherits",
"remaining_times", "silent_mode",
"machine_limits_usage", "thumbnails"
"machine_limits_usage", "thumbnails", "thumbnails_format"
};
static std::vector<std::string> s_Preset_sla_print_options {
@ -573,7 +573,7 @@ static std::vector<std::string> s_Preset_sla_printer_options {
"elefant_foot_min_width",
"gamma_correction",
"min_exposure_time", "max_exposure_time",
"min_initial_exposure_time", "max_initial_exposure_time",
"min_initial_exposure_time", "max_initial_exposure_time", "sla_archive_format", "sla_output_precision",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"print_host", "printhost_apikey", "printhost_cafile",
"printer_notes",

View file

@ -130,7 +130,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"start_gcode",
"start_filament_gcode",
"toolchange_gcode",
"threads",
"thumbnails",
"thumbnails_format",
"use_firmware_retraction",
"use_relative_e_distances",
"use_volumetric_e",

View file

@ -346,10 +346,11 @@ private:
friend class Print;
PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances);
~PrintObject() {
~PrintObject() override {
if (m_shared_regions && --m_shared_regions->m_ref_cnt == 0)
delete m_shared_regions;
clear_layers();
clear_support_layers();
}
void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); }

View file

@ -43,7 +43,7 @@ static t_config_enum_values s_keys_map_PrinterTechnology {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology)
static t_config_enum_values s_keys_map_GCodeFlavor {
static const t_config_enum_values s_keys_map_GCodeFlavor {
{ "reprap", gcfRepRapSprinter },
{ "reprapfirmware", gcfRepRapFirmware },
{ "repetier", gcfRepetier },
@ -59,14 +59,14 @@ static t_config_enum_values s_keys_map_GCodeFlavor {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor)
static t_config_enum_values s_keys_map_MachineLimitsUsage {
static const t_config_enum_values s_keys_map_MachineLimitsUsage {
{ "emit_to_gcode", int(MachineLimitsUsage::EmitToGCode) },
{ "time_estimate_only", int(MachineLimitsUsage::TimeEstimateOnly) },
{ "ignore", int(MachineLimitsUsage::Ignore) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage)
static t_config_enum_values s_keys_map_PrintHostType {
static const t_config_enum_values s_keys_map_PrintHostType {
{ "prusalink", htPrusaLink },
{ "octoprint", htOctoPrint },
{ "duet", htDuet },
@ -77,20 +77,20 @@ static t_config_enum_values s_keys_map_PrintHostType {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
static t_config_enum_values s_keys_map_AuthorizationType {
static const t_config_enum_values s_keys_map_AuthorizationType {
{ "key", atKeyPassword },
{ "user", atUserPassword }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType)
static t_config_enum_values s_keys_map_FuzzySkinType {
static const t_config_enum_values s_keys_map_FuzzySkinType {
{ "none", int(FuzzySkinType::None) },
{ "external", int(FuzzySkinType::External) },
{ "all", int(FuzzySkinType::All) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType)
static t_config_enum_values s_keys_map_InfillPattern {
static const t_config_enum_values s_keys_map_InfillPattern {
{ "rectilinear", ipRectilinear },
{ "monotonic", ipMonotonic },
{ "alignedrectilinear", ipAlignedRectilinear },
@ -114,41 +114,41 @@ static t_config_enum_values s_keys_map_InfillPattern {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern)
static t_config_enum_values s_keys_map_IroningType {
static const t_config_enum_values s_keys_map_IroningType {
{ "top", int(IroningType::TopSurfaces) },
{ "topmost", int(IroningType::TopmostOnly) },
{ "solid", int(IroningType::AllSolid) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType)
static t_config_enum_values s_keys_map_SlicingMode {
static const t_config_enum_values s_keys_map_SlicingMode {
{ "regular", int(SlicingMode::Regular) },
{ "even_odd", int(SlicingMode::EvenOdd) },
{ "close_holes", int(SlicingMode::CloseHoles) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode)
static t_config_enum_values s_keys_map_SupportMaterialPattern {
static const t_config_enum_values s_keys_map_SupportMaterialPattern {
{ "rectilinear", smpRectilinear },
{ "rectilinear-grid", smpRectilinearGrid },
{ "honeycomb", smpHoneycomb }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern)
static t_config_enum_values s_keys_map_SupportMaterialStyle {
static const t_config_enum_values s_keys_map_SupportMaterialStyle {
{ "grid", smsGrid },
{ "snug", smsSnug }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle)
static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern {
static const t_config_enum_values s_keys_map_SupportMaterialInterfacePattern {
{ "auto", smipAuto },
{ "rectilinear", smipRectilinear },
{ "concentric", smipConcentric }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern)
static t_config_enum_values s_keys_map_SeamPosition {
static const t_config_enum_values s_keys_map_SeamPosition {
{ "random", spRandom },
{ "nearest", spNearest },
{ "aligned", spAligned },
@ -190,6 +190,13 @@ static const t_config_enum_values s_keys_map_DraftShield = {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield)
static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = {
{ "PNG", int(GCodeThumbnailsFormat::PNG) },
{ "JPG", int(GCodeThumbnailsFormat::JPG) },
{ "QOI", int(GCodeThumbnailsFormat::QOI) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat)
static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = {
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
@ -259,6 +266,16 @@ void PrintConfigDef::init_common_params()
def->gui_type = ConfigOptionDef::GUIType::one_string;
def->set_default_value(new ConfigOptionPoints());
def = this->add("thumbnails_format", coEnum);
def->label = L("Format of G-code thumbnails");
def->tooltip = L("Format of G-code thumbnails: PNG for best quality, JPG for smallest size, QOI for low memory firmware");
def->mode = comExpert;
def->enum_keys_map = &ConfigOptionEnum<GCodeThumbnailsFormat>::get_enum_values();
def->enum_values.push_back("PNG");
def->enum_values.push_back("JPG");
def->enum_values.push_back("QOI");
def->set_default_value(new ConfigOptionEnum<GCodeThumbnailsFormat>(GCodeThumbnailsFormat::PNG));
def = this->add("layer_height", coFloat);
def->label = L("Layer height");
def->category = L("Layers and Perimeters");
@ -3783,6 +3800,19 @@ void PrintConfigDef::init_sla_params()
def->enum_labels.push_back(L("Fast"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SLAMaterialSpeed>(slamsFast));
def = this->add("sla_archive_format", coString);
def->label = L("Format of the output SLA archive");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString("SL1"));
def = this->add("sla_output_precision", coFloat);
def->label = L("SLA output precision");
def->tooltip = L("Minimum resolution in nanometers");
def->sidetext = L("mm");
def->min = SCALING_FACTOR;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.001));
}
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)

View file

@ -131,6 +131,10 @@ enum DraftShield {
dsDisabled, dsLimited, dsEnabled
};
enum class GCodeThumbnailsFormat {
PNG, JPG, QOI
};
#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
@ -152,6 +156,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@ -757,6 +762,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInt, standby_temperature_delta))
((ConfigOptionInts, temperature))
((ConfigOptionInt, threads))
((ConfigOptionPoints, thumbnails))
((ConfigOptionEnum<GCodeThumbnailsFormat>, thumbnails_format))
((ConfigOptionBools, wipe))
((ConfigOptionBool, wipe_tower))
((ConfigOptionFloat, wipe_tower_x))
@ -975,6 +982,8 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, max_exposure_time))
((ConfigOptionFloat, min_initial_exposure_time))
((ConfigOptionFloat, max_initial_exposure_time))
((ConfigOptionString, sla_archive_format))
((ConfigOptionFloat, sla_output_precision))
)
PRINT_CONFIG_CLASS_DERIVED_DEFINE0(

View file

@ -41,7 +41,7 @@ public:
using TValue = typename TColor::value_type;
using TPixel = typename PixelRenderer::pixel_type;
using TRawBuffer = agg::rendering_buffer;
protected:
Resolution m_resolution;
@ -153,8 +153,8 @@ public:
}
Trafo trafo() const override { return m_trafo; }
Resolution resolution() const override { return m_resolution; }
PixelDim pixel_dimensions() const override
Resolution resolution() const { return m_resolution; }
PixelDim pixel_dimensions() const
{
return {SCALING_FACTOR / m_pxdim_scaled.w_mm,
SCALING_FACTOR / m_pxdim_scaled.h_mm};
@ -186,11 +186,15 @@ class RasterGrayscaleAA : public _RasterGrayscaleAA {
using typename Base::TValue;
public:
template<class GammaFn>
RasterGrayscaleAA(const RasterBase::Resolution &res,
const RasterBase::PixelDim & pd,
const RasterBase::Trafo & trafo,
GammaFn && fn)
: Base(res, pd, trafo, Colors<TColor>::White, Colors<TColor>::Black,
RasterGrayscaleAA(const Resolution &res,
const PixelDim &pd,
const RasterBase::Trafo &trafo,
GammaFn &&fn)
: Base(res,
pd,
trafo,
Colors<TColor>::White,
Colors<TColor>::Black,
std::forward<GammaFn>(fn))
{}
@ -208,10 +212,10 @@ public:
class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA {
public:
RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res,
const RasterBase::PixelDim & pd,
const RasterBase::Trafo & trafo,
double gamma = 1.)
RasterGrayscaleAAGammaPower(const Resolution &res,
const PixelDim &pd,
const RasterBase::Trafo &trafo,
double gamma = 1.)
: RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma))
{}
};

View file

@ -68,10 +68,10 @@ EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h,
}
std::unique_ptr<RasterBase> create_raster_grayscale_aa(
const RasterBase::Resolution &res,
const RasterBase::PixelDim & pxdim,
double gamma,
const RasterBase::Trafo & tr)
const Resolution &res,
const PixelDim &pxdim,
double gamma,
const RasterBase::Trafo &tr)
{
std::unique_ptr<RasterBase> rst;

View file

@ -31,6 +31,27 @@ public:
const char * extension() const { return m_ext.c_str(); }
};
/// Type that represents a resolution in pixels.
struct Resolution {
size_t width_px = 0;
size_t height_px = 0;
Resolution() = default;
Resolution(size_t w, size_t h) : width_px(w), height_px(h) {}
size_t pixels() const { return width_px * height_px; }
};
/// Types that represents the dimension of a pixel in millimeters.
struct PixelDim {
double w_mm = 1.;
double h_mm = 1.;
PixelDim() = default;
PixelDim(double px_width_mm, double px_height_mm)
: w_mm(px_width_mm), h_mm(px_height_mm)
{}
};
using RasterEncoder =
std::function<EncodedRaster(const void *ptr, size_t w, size_t h, size_t num_components)>;
@ -63,35 +84,14 @@ public:
Point get_center() const { return {center_x, center_y}; }
};
/// Type that represents a resolution in pixels.
struct Resolution {
size_t width_px = 0;
size_t height_px = 0;
Resolution() = default;
Resolution(size_t w, size_t h) : width_px(w), height_px(h) {}
size_t pixels() const { return width_px * height_px; }
};
/// Types that represents the dimension of a pixel in millimeters.
struct PixelDim {
double w_mm = 1.;
double h_mm = 1.;
PixelDim() = default;
PixelDim(double px_width_mm, double px_height_mm)
: w_mm(px_width_mm), h_mm(px_height_mm)
{}
};
virtual ~RasterBase() = default;
/// Draw a polygon with holes.
virtual void draw(const ExPolygon& poly) = 0;
/// Get the resolution of the raster.
virtual Resolution resolution() const = 0;
virtual PixelDim pixel_dimensions() const = 0;
// virtual Resolution resolution() const = 0;
// virtual PixelDim pixel_dimensions() const = 0;
virtual Trafo trafo() const = 0;
virtual EncodedRaster encode(RasterEncoder encoder) const = 0;
@ -109,10 +109,10 @@ std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes);
// If gamma is zero, thresholding will be performed which disables AA.
std::unique_ptr<RasterBase> create_raster_grayscale_aa(
const RasterBase::Resolution &res,
const RasterBase::PixelDim & pxdim,
double gamma = 1.0,
const RasterBase::Trafo & tr = {});
const Resolution &res,
const PixelDim &pxdim,
double gamma = 1.0,
const RasterBase::Trafo &tr = {});
}} // namespace Slic3r::sla

View file

@ -1,6 +1,9 @@
#include "SLAPrint.hpp"
#include "SLAPrintSteps.hpp"
#include "Format/SL1.hpp"
#include "Format/SL1_SVG.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
@ -240,8 +243,13 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
m_material_config.apply_only(config, material_diff, true);
// Handle changes to object config defaults
m_default_object_config.apply_only(config, object_diff, true);
if (m_printer) m_printer->apply(m_printer_config);
if (!m_archiver || !printer_diff.empty()) {
if (m_printer_config.sla_archive_format.value == "SL1")
m_archiver = std::make_unique<SL1Archive>(m_printer_config);
else if (m_printer_config.sla_archive_format.value == "SL2")
m_archiver = std::make_unique<SL1_SVGArchive>(m_printer_config);
}
struct ModelObjectStatus {
enum Status {
@ -670,12 +678,6 @@ std::string SLAPrint::validate(std::string*) const
return "";
}
void SLAPrint::set_printer(SLAArchive *arch)
{
invalidate_step(slapsRasterize);
m_printer = arch;
}
bool SLAPrint::invalidate_step(SLAPrintStep step)
{
bool invalidated = Inherited::invalidate_step(step);
@ -835,7 +837,9 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
"display_pixels_y",
"display_mirror_x",
"display_mirror_y",
"display_orientation"
"display_orientation",
"sla_archive_format",
"sla_output_precision"
};
static std::unordered_set<std::string> steps_ignore = {

View file

@ -399,8 +399,6 @@ protected:
public:
virtual ~SLAArchive() = default;
virtual void apply(const SLAPrinterConfig &cfg) = 0;
// Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
template<class Fn, class CancelFn, class EP = ExecutionTBB>
void draw_layers(
@ -422,6 +420,14 @@ public:
},
execution::max_concurrency(ep));
}
// Export the print into an archive using the provided zipper.
// TODO: Use an archive writer interface instead of Zipper.
// This is quite limiting as the Zipper is a complete class, not an interface.
// The output can only be a zip archive.
virtual void export_print(Zipper &zipper,
const SLAPrint &print,
const std::string &projectname = "") = 0;
};
/**
@ -527,8 +533,17 @@ public:
// The aggregated and leveled print records from various objects.
// TODO: use this structure for the preview in the future.
const std::vector<PrintLayer>& print_layers() const { return m_printer_input; }
void set_printer(SLAArchive *archiver);
void export_print(Zipper &zipper, const std::string &projectname = "")
{
m_archiver->export_print(zipper, *this, projectname);
}
void export_print(const std::string &fname, const std::string &projectname = "")
{
Zipper zipper(fname);
export_print(zipper, projectname);
}
private:
@ -550,7 +565,7 @@ private:
std::vector<PrintLayer> m_printer_input;
// The archive object which collects the raster images after slicing
SLAArchive *m_printer = nullptr;
std::unique_ptr<SLAArchive> m_archiver;
// Estimated print time, material consumed.
SLAPrintStatistics m_print_statistics;

View file

@ -1044,7 +1044,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
// Rasterizing the model objects, and their supports
void SLAPrint::Steps::rasterize()
{
if(canceled() || !m_print->m_printer) return;
if(canceled() || !m_print->m_archiver) return;
// coefficient to map the rasterization state (0-99) to the allocated
// portion (slot) of the process state
@ -1089,7 +1089,7 @@ void SLAPrint::Steps::rasterize()
if(canceled()) return;
// Print all the layers in parallel
m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn,
m_print->m_archiver->draw_layers(m_print->m_printer_input.size(), lvlfn,
[this]() { return canceled(); }, ex_tbb);
}

View file

@ -3754,6 +3754,7 @@ void modulate_extrusion_by_overlapping_layers(
assert(path != nullptr);
polylines.emplace_back(Polyline(std::move(path->polyline)));
path_ends.emplace_back(std::pair<Point, Point>(polylines.back().points.front(), polylines.back().points.back()));
delete path;
}
}
// Destroy the original extrusion paths, their polylines were moved to path_fragments already.

View file

@ -68,8 +68,16 @@
#define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1)
// Enable removal of old OpenGL render calls
#define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1)
// Enable replace GLIndexedVertexArray with GLModel
#define ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL (1 && ENABLE_GLBEGIN_GLEND_REMOVAL)
// Enable show non-manifold edges
#define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1)
// Enable rework of Reload from disk command
#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1)
// Enable showing toolpaths center of gravity
#define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1)
// Enable recalculating toolpaths when switching to/from volumetric rate visualization
#define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1)
//====================

9
src/qoi/CMakeLists.txt Normal file
View file

@ -0,0 +1,9 @@
# PrusaSlicer specific CMake
cmake_minimum_required(VERSION 2.8.12)
project(qoi)
add_library(qoi STATIC
qoi.h
qoilib.c
)

108
src/qoi/README.md Normal file
View file

@ -0,0 +1,108 @@
Bundled with PrusaSlicer: commit 6c0831f91ffde5dfe2ceef32cbaff91d62b0e0ee
Original README follows:
![QOI Logo](https://qoiformat.org/qoi-logo.svg)
# QOI - The “Quite OK Image Format” for fast, lossless image compression
Single-file MIT licensed library for C/C++
See [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for
the documentation and format specification.
More info at https://qoiformat.org
## Why?
Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding,
3x-4x faster decoding and 20% better compression. It's also stupidly simple and
fits in about 300 lines of C.
## Example Usage
- [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c)
converts between png <> qoi
- [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c)
a simple wrapper to benchmark stbi, libpng and qoi
## Limitations
The QOI file format allows for huge images with up to 18 exa-pixels. A streaming
en-/decoder can handle these with minimal RAM requirements, assuming there is
enough storage space.
This particular implementation of QOI however is limited to images with a
maximum size of 400 million pixels. It will safely refuse to en-/decode anything
larger than that. This is not a streaming en-/decoder. It loads the whole image
file into RAM before doing any work and is not extensively optimized for
performance (but it's still very fast).
If this is a limitation for your use case, please look into any of the other
implementations listed below.
## Tools
- https://github.com/floooh/qoiview - native QOI viewer
- https://github.com/pfusik/qoi-ci/releases/tag/qoi-ci-1.1.0 - QOI Plugin installer for GIMP, Imagine, Paint.NET and XnView MP
- https://github.com/iOrange/QoiFileTypeNet/releases/tag/v0.2 - QOI Plugin for Paint.NET
- https://github.com/iOrange/QOIThumbnailProvider - Add thumbnails for QOI images in Windows Explorer
- https://github.com/Tom94/tev - another native QOI viewer (allows pixel peeping and comparison with other image formats)
- https://apps.apple.com/br/app/qoiconverterx/id1602159820 QOI <=> PNG converter available on the Mac App Store
- https://github.com/kaetemi/qoi-max - QOI Bitmap I/O Plugin for 3ds Max
- https://raylibtech.itch.io/rtexviewer - texture viewer, supports QOI
## Implementations & Bindings of QOI
- https://github.com/pfusik/qoi-ci (Ć, transpiled to C, C++, C#, Java, JavaScript, Python and Swift)
- https://github.com/kodonnell/qoi (Python)
- https://github.com/Cr4xy/lua-qoi (Lua)
- https://github.com/superzazu/SDL_QOI (C, SDL2 bindings)
- https://github.com/saharNooby/qoi-java (Java)
- https://github.com/MasterQ32/zig-qoi (Zig)
- https://github.com/rbino/qoix (Elixir)
- https://github.com/NUlliiON/QoiSharp (C#)
- https://github.com/aldanor/qoi-rust (Rust)
- https://github.com/zakarumych/rapid-qoi (Rust)
- https://github.com/takeyourhatoff/qoi (Go)
- https://github.com/DosWorld/pasqoi (Pascal)
- https://github.com/elihwyma/Swift-QOI (Swift)
- https://github.com/xfmoulet/qoi (Go)
- https://erratique.ch/software/qoic (OCaml)
- https://github.com/arian/go-qoi (Go)
- https://github.com/kchapelier/qoijs (JavaScript)
- https://github.com/KristofferC/QOI.jl (Julia)
- https://github.com/shadowMitia/libqoi/ (C++)
- https://github.com/MKCG/php-qoi (PHP)
- https://github.com/LightHouseSoftware/qoiformats (D)
- https://github.com/mhoward540/qoi-nim (Nim)
## QOI Support in Other Software
- [SerenityOS](https://github.com/SerenityOS/serenity) supports decoding QOI system wide through a custom [cpp implementation in LibGfx](https://github.com/SerenityOS/serenity/blob/master/Userland/Libraries/LibGfx/QOILoader.h)
- [Raylib](https://github.com/raysan5/raylib) supports decoding and encoding QOI textures through its [rtextures module](https://github.com/raysan5/raylib/blob/master/src/rtextures.c)
- [Rebol3](https://github.com/Oldes/Rebol3/issues/39) supports decoding and encoding QOI using a native codec
- [c-ray](https://github.com/vkoskiv/c-ray) supports QOI natively
- [SAIL](https://github.com/HappySeaFox/sail) image decoding library, supports decoding and encoding QOI images
- [Orx](https://github.com/orx/orx) 2D game engine, supports QOI natively
## Packages
[AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages.
## Implementations not yet conforming to the final specification
These implementations are based on the pre-release version of QOI. Resulting files are not compatible with the current version.
- https://github.com/ChevyRay/qoi_rs (Rust)
- https://github.com/panzi/jsqoi (TypeScript)
- https://github.com/0xd34df00d/hsqoi (Haskell)

671
src/qoi/qoi.h Normal file
View file

@ -0,0 +1,671 @@
/*
QOI - The "Quite OK Image" format for fast, lossless image compression
Dominic Szablewski - https://phoboslab.org
-- LICENSE: The MIT License(MIT)
Copyright(c) 2021 Dominic Szablewski
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files(the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-- About
QOI encodes and decodes images in a lossless format. Compared to stb_image and
stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
20% better compression.
-- Synopsis
// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
// library to create the implementation.
#define QOI_IMPLEMENTATION
#include "qoi.h"
// Encode and store an RGBA buffer to the file system. The qoi_desc describes
// the input pixel data.
qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
.width = 1920,
.height = 1080,
.channels = 4,
.colorspace = QOI_SRGB
});
// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
// The qoi_desc struct will be filled with the width, height, number of channels
// and colorspace read from the file header.
qoi_desc desc;
void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
-- Documentation
This library provides the following functions;
- qoi_read -- read and decode a QOI file
- qoi_decode -- decode the raw bytes of a QOI image from memory
- qoi_write -- encode and write a QOI file
- qoi_encode -- encode an rgba buffer into a QOI image in memory
See the function declaration below for the signature and more information.
If you don't want/need the qoi_read and qoi_write functions, you can define
QOI_NO_STDIO before including this library.
This library uses malloc() and free(). To supply your own malloc implementation
you can define QOI_MALLOC and QOI_FREE before including this library.
This library uses memset() to zero-initialize the index. To supply your own
implementation you can define QOI_ZEROARR before including this library.
-- Data Format
A QOI file has a 14 byte header, followed by any number of data "chunks" and an
8-byte end marker.
struct qoi_header_t {
char magic[4]; // magic bytes "qoif"
uint32_t width; // image width in pixels (BE)
uint32_t height; // image height in pixels (BE)
uint8_t channels; // 3 = RGB, 4 = RGBA
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
};
Images are encoded row by row, left to right, top to bottom. The decoder and
encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
image is complete when all pixels specified by width * height have been covered.
Pixels are encoded as
- a run of the previous pixel
- an index into an array of previously seen pixels
- a difference to the previous pixel value in r,g,b
- full r,g,b or r,g,b,a values
The color channels are assumed to not be premultiplied with the alpha channel
("un-premultiplied alpha").
A running array[64] (zero-initialized) of previously seen pixel values is
maintained by the encoder and decoder. Each pixel that is seen by the encoder
and decoder is put into this array at the position formed by a hash function of
the color value. In the encoder, if the pixel value at the index matches the
current pixel, this index position is written to the stream as QOI_OP_INDEX.
The hash function for the index is:
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
values encoded in these data bits have the most significant bit on the left.
The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
presence of an 8-bit tag first.
The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
The possible chunks are:
.- QOI_OP_INDEX ----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 0 0 | index |
`-------------------------`
2-bit tag b00
6-bit index into the color index array: 0..63
A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
same index. QOI_OP_RUN should be used instead.
.- QOI_OP_DIFF -----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----+-----+-----|
| 0 1 | dr | dg | db |
`-------------------------`
2-bit tag b01
2-bit red channel difference from the previous pixel between -2..1
2-bit green channel difference from the previous pixel between -2..1
2-bit blue channel difference from the previous pixel between -2..1
The difference to the current channel values are using a wraparound operation,
so "1 - 2" will result in 255, while "255 + 1" will result in 0.
Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
0 (b00). 1 is stored as 3 (b11).
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_LUMA -------------------------------------.
| Byte[0] | Byte[1] |
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
|-------+-----------------+-------------+-----------|
| 1 0 | green diff | dr - dg | db - dg |
`---------------------------------------------------`
2-bit tag b10
6-bit green channel difference from the previous pixel -32..31
4-bit red channel difference minus green channel difference -8..7
4-bit blue channel difference minus green channel difference -8..7
The green channel is used to indicate the general direction of change and is
encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
of the green channel difference and are encoded in 4 bits. I.e.:
dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
The difference to the current channel values are using a wraparound operation,
so "10 - 13" will result in 253, while "250 + 7" will result in 1.
Values are stored as unsigned integers with a bias of 32 for the green channel
and a bias of 8 for the red and blue channel.
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_RUN ------------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 1 1 | run |
`-------------------------`
2-bit tag b11
6-bit run-length repeating the previous pixel: 1..62
The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
QOI_OP_RGBA tags.
.- QOI_OP_RGB ------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------|
| 1 1 1 1 1 1 1 0 | red | green | blue |
`-------------------------------------------------------`
8-bit tag b11111110
8-bit red channel value
8-bit green channel value
8-bit blue channel value
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_RGBA ---------------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------+---------|
| 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
`-----------------------------------------------------------------`
8-bit tag b11111111
8-bit red channel value
8-bit green channel value
8-bit blue channel value
8-bit alpha channel value
*/
/* -----------------------------------------------------------------------------
Header - Public functions */
#ifndef QOI_H
#define QOI_H
#ifdef __cplusplus
extern "C" {
#endif
/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
It describes either the input format (for qoi_write and qoi_encode), or is
filled with the description read from the file header (for qoi_read and
qoi_decode).
The colorspace in this qoi_desc is an enum where
0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
1 = all channels are linear
You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
informative. It will be saved to the file header, but does not affect
how chunks are en-/decoded. */
#define QOI_SRGB 0
#define QOI_LINEAR 1
typedef struct {
unsigned int width;
unsigned int height;
unsigned char channels;
unsigned char colorspace;
} qoi_desc;
#ifndef QOI_NO_STDIO
/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
system. The qoi_desc struct must be filled with the image width, height,
number of channels (3 = RGB, 4 = RGBA) and the colorspace.
The function returns 0 on failure (invalid parameters, or fopen or malloc
failed) or the number of bytes written on success. */
int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
/* Read and decode a QOI image from the file system. If channels is 0, the
number of channels from the file header is used. If channels is 3 or 4 the
output format will be forced into this number of channels.
The function either returns NULL on failure (invalid data, or malloc or fopen
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
will be filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_read(const char *filename, qoi_desc *desc, int channels);
#endif /* QOI_NO_STDIO */
/* Encode raw RGB or RGBA pixels into a QOI image in memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the encoded data on success. On success the out_len
is set to the size in bytes of the encoded data.
The returned qoi data should be free()d after use. */
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
/* Decode a QOI image from memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
is filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
#ifdef __cplusplus
}
#endif
#endif /* QOI_H */
/* -----------------------------------------------------------------------------
Implementation */
#ifdef QOI_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
#ifndef QOI_MALLOC
#define QOI_MALLOC(sz) malloc(sz)
#define QOI_FREE(p) free(p)
#endif
#ifndef QOI_ZEROARR
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
#endif
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
#define QOI_OP_RGB 0xfe /* 11111110 */
#define QOI_OP_RGBA 0xff /* 11111111 */
#define QOI_MASK_2 0xc0 /* 11000000 */
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
#define QOI_MAGIC \
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
((unsigned int)'i') << 8 | ((unsigned int)'f'))
#define QOI_HEADER_SIZE 14
/* 2GB is the max file size that this implementation can safely handle. We guard
against anything larger than that, assuming the worst case with 5 bytes per
pixel, rounded down to a nice clean value. 400 million pixels ought to be
enough for anybody. */
#define QOI_PIXELS_MAX ((unsigned int)400000000)
typedef union {
struct { unsigned char r, g, b, a; } rgba;
unsigned int v;
} qoi_rgba_t;
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
bytes[(*p)++] = (0xff000000 & v) >> 24;
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
bytes[(*p)++] = (0x000000ff & v);
}
static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
unsigned int a = bytes[(*p)++];
unsigned int b = bytes[(*p)++];
unsigned int c = bytes[(*p)++];
unsigned int d = bytes[(*p)++];
return a << 24 | b << 16 | c << 8 | d;
}
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
int i, max_size, p, run;
int px_len, px_end, px_pos, channels;
unsigned char *bytes;
const unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px, px_prev;
if (
data == NULL || out_len == NULL || desc == NULL ||
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 1 ||
desc->height >= QOI_PIXELS_MAX / desc->width
) {
return NULL;
}
max_size =
desc->width * desc->height * (desc->channels + 1) +
QOI_HEADER_SIZE + sizeof(qoi_padding);
p = 0;
bytes = (unsigned char *) QOI_MALLOC(max_size);
if (!bytes) {
return NULL;
}
qoi_write_32(bytes, &p, QOI_MAGIC);
qoi_write_32(bytes, &p, desc->width);
qoi_write_32(bytes, &p, desc->height);
bytes[p++] = desc->channels;
bytes[p++] = desc->colorspace;
pixels = (const unsigned char *)data;
QOI_ZEROARR(index);
run = 0;
px_prev.rgba.r = 0;
px_prev.rgba.g = 0;
px_prev.rgba.b = 0;
px_prev.rgba.a = 255;
px = px_prev;
px_len = desc->width * desc->height * desc->channels;
px_end = px_len - desc->channels;
channels = desc->channels;
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (channels == 4) {
px = *(qoi_rgba_t *)(pixels + px_pos);
}
else {
px.rgba.r = pixels[px_pos + 0];
px.rgba.g = pixels[px_pos + 1];
px.rgba.b = pixels[px_pos + 2];
}
if (px.v == px_prev.v) {
run++;
if (run == 62 || px_pos == px_end) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
}
else {
int index_pos;
if (run > 0) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
index_pos = QOI_COLOR_HASH(px) % 64;
if (index[index_pos].v == px.v) {
bytes[p++] = QOI_OP_INDEX | index_pos;
}
else {
index[index_pos] = px;
if (px.rgba.a == px_prev.rgba.a) {
signed char vr = px.rgba.r - px_prev.rgba.r;
signed char vg = px.rgba.g - px_prev.rgba.g;
signed char vb = px.rgba.b - px_prev.rgba.b;
signed char vg_r = vr - vg;
signed char vg_b = vb - vg;
if (
vr > -3 && vr < 2 &&
vg > -3 && vg < 2 &&
vb > -3 && vb < 2
) {
bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
}
else if (
vg_r > -9 && vg_r < 8 &&
vg > -33 && vg < 32 &&
vg_b > -9 && vg_b < 8
) {
bytes[p++] = QOI_OP_LUMA | (vg + 32);
bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
}
else {
bytes[p++] = QOI_OP_RGB;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
}
}
else {
bytes[p++] = QOI_OP_RGBA;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
bytes[p++] = px.rgba.a;
}
}
}
px_prev = px;
}
for (i = 0; i < (int)sizeof(qoi_padding); i++) {
bytes[p++] = qoi_padding[i];
}
*out_len = p;
return bytes;
}
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
const unsigned char *bytes;
unsigned int header_magic;
unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px;
int px_len, chunks_len, px_pos;
int p = 0, run = 0;
if (
data == NULL || desc == NULL ||
(channels != 0 && channels != 3 && channels != 4) ||
size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
) {
return NULL;
}
bytes = (const unsigned char *)data;
header_magic = qoi_read_32(bytes, &p);
desc->width = qoi_read_32(bytes, &p);
desc->height = qoi_read_32(bytes, &p);
desc->channels = bytes[p++];
desc->colorspace = bytes[p++];
if (
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 1 ||
header_magic != QOI_MAGIC ||
desc->height >= QOI_PIXELS_MAX / desc->width
) {
return NULL;
}
if (channels == 0) {
channels = desc->channels;
}
px_len = desc->width * desc->height * channels;
pixels = (unsigned char *) QOI_MALLOC(px_len);
if (!pixels) {
return NULL;
}
QOI_ZEROARR(index);
px.rgba.r = 0;
px.rgba.g = 0;
px.rgba.b = 0;
px.rgba.a = 255;
chunks_len = size - (int)sizeof(qoi_padding);
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (run > 0) {
run--;
}
else if (p < chunks_len) {
int b1 = bytes[p++];
if (b1 == QOI_OP_RGB) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
}
else if (b1 == QOI_OP_RGBA) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
px.rgba.a = bytes[p++];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
px = index[b1];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
px.rgba.r += ((b1 >> 4) & 0x03) - 2;
px.rgba.g += ((b1 >> 2) & 0x03) - 2;
px.rgba.b += ( b1 & 0x03) - 2;
}
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
int b2 = bytes[p++];
int vg = (b1 & 0x3f) - 32;
px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
px.rgba.g += vg;
px.rgba.b += vg - 8 + (b2 & 0x0f);
}
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
run = (b1 & 0x3f);
}
index[QOI_COLOR_HASH(px) % 64] = px;
}
if (channels == 4) {
*(qoi_rgba_t*)(pixels + px_pos) = px;
}
else {
pixels[px_pos + 0] = px.rgba.r;
pixels[px_pos + 1] = px.rgba.g;
pixels[px_pos + 2] = px.rgba.b;
}
}
return pixels;
}
#ifndef QOI_NO_STDIO
#include <stdio.h>
int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
FILE *f = fopen(filename, "wb");
int size;
void *encoded;
if (!f) {
return 0;
}
encoded = qoi_encode(data, desc, &size);
if (!encoded) {
fclose(f);
return 0;
}
fwrite(encoded, 1, size, f);
fclose(f);
QOI_FREE(encoded);
return size;
}
void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
FILE *f = fopen(filename, "rb");
int size, bytes_read;
void *pixels, *data;
if (!f) {
return NULL;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
if (size <= 0) {
fclose(f);
return NULL;
}
fseek(f, 0, SEEK_SET);
data = QOI_MALLOC(size);
if (!data) {
fclose(f);
return NULL;
}
bytes_read = fread(data, 1, size, f);
fclose(f);
pixels = qoi_decode(data, bytes_read, desc, channels);
QOI_FREE(data);
return pixels;
}
#endif /* QOI_NO_STDIO */
#endif /* QOI_IMPLEMENTATION */

6
src/qoi/qoilib.c Normal file
View file

@ -0,0 +1,6 @@
// PrusaSlicer specific:
// Include and compile QOI library.
#define QOI_IMPLEMENTATION
#define QOI_NO_STDIO
#include "qoi.h"

View file

@ -27,6 +27,7 @@ static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0
namespace Slic3r {
namespace GUI {
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
bool GeometryBuffer::set_from_triangles(const std::vector<Vec2f> &triangles, float z)
{
if (triangles.empty()) {
@ -95,6 +96,7 @@ const float* GeometryBuffer::get_vertices_data() const
{
return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr;
}
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
const float Bed3D::Axes::DefaultStemRadius = 0.5f;
const float Bed3D::Axes::DefaultStemLength = 25.0f;
@ -198,6 +200,13 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c
m_model_filename = model_filename;
m_extended_bounding_box = this->calc_extended_bounding_box();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
m_contour = ExPolygon(Polygon::new_scale(bed_shape));
m_polygon = offset(m_contour.contour, (float)m_contour.contour.bounding_box().radius() * 1.7f, jtRound, scale_(0.5)).front();
m_triangles.reset();
m_gridlines.reset();
#else
ExPolygon poly{ Polygon::new_scale(bed_shape) };
calc_triangles(poly);
@ -205,9 +214,10 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c
const BoundingBox& bed_bbox = poly.contour.bounding_box();
calc_gridlines(poly, bed_bbox);
m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0];
m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5)).front();
this->release_VBOs();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
m_texture.reset();
m_model.reset();
@ -288,6 +298,107 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const
return out;
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Bed3D::init_triangles()
{
if (m_triangles.is_initialized())
return;
if (m_contour.empty())
return;
const std::vector<Vec2f> triangles = triangulate_expolygon_2f(m_contour, NORMALS_UP);
if (triangles.empty() || triangles.size() % 3 != 0)
return;
GLModel::Geometry init_data;
const GLModel::Geometry::EIndexType index_type = (triangles.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2, index_type };
init_data.reserve_vertices(triangles.size());
init_data.reserve_indices(triangles.size() / 3);
Vec2f min = triangles.front();
Vec2f max = min;
for (const Vec2f& v : triangles) {
min = min.cwiseMin(v).eval();
max = max.cwiseMax(v).eval();
}
const Vec2f size = max - min;
if (size.x() <= 0.0f || size.y() <= 0.0f)
return;
Vec2f inv_size = size.cwiseInverse();
inv_size.y() *= -1.0f;
// vertices + indices
unsigned int vertices_counter = 0;
for (const Vec2f& v : triangles) {
const Vec3f p = { v.x(), v.y(), GROUND_Z };
init_data.add_vertex(p, (Vec2f)v.cwiseProduct(inv_size).eval());
++vertices_counter;
if (vertices_counter % 3 == 0) {
if (index_type == GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_triangle((unsigned short)vertices_counter - 3, (unsigned short)vertices_counter - 2, (unsigned short)vertices_counter - 1);
else
init_data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
}
}
m_triangles.init_from(std::move(init_data));
}
void Bed3D::init_gridlines()
{
if (m_gridlines.is_initialized())
return;
if (m_contour.empty())
return;
const BoundingBox& bed_bbox = m_contour.contour.bounding_box();
const coord_t step = scale_(10.0);
Polylines axes_lines;
for (coord_t x = bed_bbox.min.x(); x <= bed_bbox.max.x(); x += step) {
Polyline line;
line.append(Point(x, bed_bbox.min.y()));
line.append(Point(x, bed_bbox.max.y()));
axes_lines.push_back(line);
}
for (coord_t y = bed_bbox.min.y(); y <= bed_bbox.max.y(); y += step) {
Polyline line;
line.append(Point(bed_bbox.min.x(), y));
line.append(Point(bed_bbox.max.x(), y));
axes_lines.push_back(line);
}
// clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped
Lines gridlines = to_lines(intersection_pl(axes_lines, offset(m_contour, float(SCALED_EPSILON))));
// append bed contours
Lines contour_lines = to_lines(m_contour);
std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines));
GLModel::Geometry init_data;
const GLModel::Geometry::EIndexType index_type = (2 * gridlines.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, index_type };
init_data.reserve_vertices(2 * gridlines.size());
init_data.reserve_indices(2 * gridlines.size());
for (const Line& l : gridlines) {
init_data.add_vertex(Vec3f(unscale<float>(l.a.x()), unscale<float>(l.a.y()), GROUND_Z));
init_data.add_vertex(Vec3f(unscale<float>(l.b.x()), unscale<float>(l.b.y()), GROUND_Z));
const unsigned int vertices_counter = (unsigned int)init_data.vertices_count();
if (index_type == GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_line((unsigned short)vertices_counter - 2, (unsigned short)vertices_counter - 1);
else
init_data.add_uint_line(vertices_counter - 2, vertices_counter - 1);
}
m_gridlines.init_from(std::move(init_data));
}
#else
void Bed3D::calc_triangles(const ExPolygon& poly)
{
if (! m_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z))
@ -320,6 +431,7 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
if (!m_gridlines.set_from_lines(gridlines, GROUND_Z))
BOOST_LOG_TRIVIAL(error) << "Unable to create bed grid lines\n";
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
// Try to match the print bed shape with the shape of an active profile. If such a match exists,
// return the print bed model.
@ -421,6 +533,44 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas)
canvas.request_extra_frame();
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
init_triangles();
GLShaderProgram* shader = wxGetApp().get_shader("printbed");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("transparent_background", bottom);
shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
glsafe(::glEnable(GL_DEPTH_TEST));
if (bottom)
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (bottom)
glsafe(::glFrontFace(GL_CW));
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)m_temp_texture.get_id();
if (tex_id == 0)
tex_id = (GLuint)m_texture.get_id();
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
m_triangles.render();
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
if (bottom)
glsafe(::glFrontFace(GL_CCW));
glsafe(::glDisable(GL_BLEND));
if (bottom)
glsafe(::glDepthMask(GL_TRUE));
shader->stop_using();
}
#else
if (m_triangles.get_vertices_count() > 0) {
GLShaderProgram* shader = wxGetApp().get_shader("printbed");
if (shader != nullptr) {
@ -488,6 +638,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas)
shader->stop_using();
}
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
void Bed3D::render_model()
@ -541,6 +692,40 @@ void Bed3D::render_default(bool bottom, bool picking)
{
m_texture.reset();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
init_gridlines();
init_triangles();
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
const bool has_model = !m_model.get_filename().empty();
if (!has_model && !bottom) {
// draw background
glsafe(::glDepthMask(GL_FALSE));
m_triangles.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR);
m_triangles.render();
glsafe(::glDepthMask(GL_TRUE));
}
if (!picking) {
// draw grid
glsafe(::glLineWidth(1.5f * m_scale_factor));
m_gridlines.set_color(has_model && !bottom ? DEFAULT_SOLID_GRID_COLOR : DEFAULT_TRANSPARENT_GRID_COLOR);
m_gridlines.render();
}
glsafe(::glDisable(GL_BLEND));
shader->stop_using();
}
#else
const unsigned int triangles_vcount = m_triangles.get_vertices_count();
if (triangles_vcount > 0) {
const bool has_model = !m_model.get_filename().empty();
@ -573,8 +758,10 @@ void Bed3D::render_default(bool bottom, bool picking)
glsafe(::glDisable(GL_BLEND));
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
void Bed3D::release_VBOs()
{
if (m_vbo_id > 0) {
@ -582,6 +769,7 @@ void Bed3D::release_VBOs()
m_vbo_id = 0;
}
}
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
} // GUI
} // Slic3r

View file

@ -5,7 +5,10 @@
#include "3DScene.hpp"
#include "GLModel.hpp"
#include <libslic3r/BuildVolume.hpp>
#include "libslic3r/BuildVolume.hpp"
#if ENABLE_GLBEGIN_GLEND_REMOVAL
#include "libslic3r/ExPolygon.hpp"
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#include <tuple>
#include <array>
@ -15,6 +18,7 @@ namespace GUI {
class GLCanvas3D;
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
class GeometryBuffer
{
struct Vertex
@ -36,6 +40,7 @@ public:
size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); }
unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); }
};
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
class Bed3D
{
@ -79,23 +84,38 @@ private:
std::string m_model_filename;
// Print volume bounding box exteded with axes and model.
BoundingBoxf3 m_extended_bounding_box;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
// Print bed polygon
ExPolygon m_contour;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
// Slightly expanded print bed polygon, for collision detection.
Polygon m_polygon;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLModel m_triangles;
GLModel m_gridlines;
#else
GeometryBuffer m_triangles;
GeometryBuffer m_gridlines;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
GLTexture m_texture;
// temporary texture shown until the main texture has still no levels compressed
GLTexture m_temp_texture;
GLModel m_model;
Vec3d m_model_offset{ Vec3d::Zero() };
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
unsigned int m_vbo_id{ 0 };
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
Axes m_axes;
float m_scale_factor{ 1.0f };
public:
Bed3D() = default;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
~Bed3D() = default;
#else
~Bed3D() { release_VBOs(); }
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
// Update print bed model from configuration.
// Return true if the bed shape changed, so the calee will update the UI.
@ -125,8 +145,13 @@ public:
private:
// Calculate an extended bounding box from axes and current model for visualization purposes.
BoundingBoxf3 calc_extended_bounding_box() const;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void init_triangles();
void init_gridlines();
#else
void calc_triangles(const ExPolygon& poly);
void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
static std::tuple<Type, std::string, std::string> detect_type(const Pointfs& shape);
void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor,
bool show_axes, bool show_texture, bool picking);
@ -136,7 +161,9 @@ private:
void render_model();
void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking);
void render_default(bool bottom, bool picking);
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
void release_VBOs();
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
};
} // GUI

View file

@ -326,8 +326,8 @@ void GLVolume::SinkingContours::update()
for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
#if ENABLE_GLBEGIN_GLEND_REMOVAL
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
init_data.vertices.reserve(init_data.vertices.size() + triangulation.size() * GUI::GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(init_data.indices.size() + triangulation.size() * GUI::GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(init_data.vertices_count() + triangulation.size());
init_data.reserve_indices(init_data.indices_count() + triangulation.size());
for (const Vec3d& v : triangulation) {
init_data.add_vertex((Vec3f)(v.cast<float>() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting
++vertices_counter;
@ -400,9 +400,10 @@ void GLVolume::NonManifoldEdges::update()
if (!edges.empty()) {
GUI::GLModel::Geometry init_data;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, GUI::GLModel::Geometry::EIndexType::UINT };
init_data.vertices.reserve(2 * edges.size() * GUI::GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * edges.size() * GUI::GLModel::Geometry::index_stride_bytes(init_data.format));
const GUI::GLModel::Geometry::EIndexType index_type = (2 * edges.size() < 65536) ? GUI::GLModel::Geometry::EIndexType::USHORT : GUI::GLModel::Geometry::EIndexType::UINT;
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, index_type };
init_data.reserve_vertices(2 * edges.size());
init_data.reserve_indices(2 * edges.size());
// vertices + indices
unsigned int vertices_count = 0;
@ -410,7 +411,10 @@ void GLVolume::NonManifoldEdges::update()
init_data.add_vertex((Vec3f)mesh.its.vertices[edge.first].cast<float>());
init_data.add_vertex((Vec3f)mesh.its.vertices[edge.second].cast<float>());
vertices_count += 2;
init_data.add_uint_line(vertices_count - 2, vertices_count - 1);
if (index_type == GUI::GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_line((unsigned short)vertices_count - 2, (unsigned short)vertices_count - 1);
else
init_data.add_uint_line(vertices_count - 2, vertices_count - 1);
}
m_model.init_from(std::move(init_data));
#else

View file

@ -190,7 +190,7 @@ void BackgroundSlicingProcess::process_sla()
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
Zipper zipper(export_path);
m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad
m_sla_print->export_print(zipper);
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);
@ -741,7 +741,7 @@ void BackgroundSlicingProcess::prepare_upload()
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
// true, false, true, true); // renders also supports and pad
Zipper zipper{source_path.string()};
m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
m_sla_print->export_print(zipper, m_upload_job.upload_data.upload_path.string());
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);

View file

@ -11,7 +11,6 @@
#include "libslic3r/PrintBase.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
@ -84,7 +83,7 @@ public:
~BackgroundSlicingProcess();
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; }
@ -218,9 +217,9 @@ private:
// Data structure, to which the G-code export writes its annotations.
GCodeProcessorResult *m_gcode_result = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess,
// differentiated from the other processes by a process ID.
std::string m_temp_output_path;
// Output path provided by the user. The output path may be set even if the slicing is running,
// but once set, it cannot be re-set.

View file

@ -103,7 +103,11 @@ void GCodeViewer::IBuffer::reset()
count = 0;
}
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const
#else
bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
{
auto matches_percent = [](float value1, float value2, float max_percent) {
return std::abs(value2 - value1) / value1 <= max_percent;
@ -120,10 +124,22 @@ bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) co
case EMoveType::Seam:
case EMoveType::Extrude: {
// use rounding to reduce the number of generated paths
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
if (account_for_volumetric_rate)
return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
height == round_to_bin(move.height) && width == round_to_bin(move.width) &&
matches_percent(volumetric_rate, move.volumetric_rate(), 0.001f);
else
return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
height == round_to_bin(move.height) && width == round_to_bin(move.width);
#else
return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
height == round_to_bin(move.height) && width == round_to_bin(move.width) &&
matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f);
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
}
case EMoveType::Travel: {
return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id;
@ -160,6 +176,66 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move
move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } });
}
#if ENABLE_SHOW_TOOLPATHS_COG
void GCodeViewer::COG::render()
{
if (!m_visible)
return;
init();
GLShaderProgram* shader = wxGetApp().get_shader("toolpaths_cog");
if (shader == nullptr)
return;
shader->start_using();
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
const Vec3d position = cog();
glsafe(::glTranslated(position.x(), position.y(), position.z()));
if (m_fixed_size) {
const double inv_zoom = wxGetApp().plater()->get_camera().get_inv_zoom();
glsafe(::glScaled(inv_zoom, inv_zoom, inv_zoom));
}
m_model.render();
glsafe(::glPopMatrix());
shader->stop_using();
////Show ImGui window
//static float last_window_width = 0.0f;
//static size_t last_text_length = 0;
//ImGuiWrapper& imgui = *wxGetApp().imgui();
//const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
//imgui.set_next_window_pos(0.5f * static_cast<float>(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f);
//ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
//ImGui::SetNextWindowBgAlpha(0.25f);
//imgui.begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
//imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Center of mass") + ":");
//ImGui::SameLine();
//char buf[1024];
//const Vec3d position = cog();
//sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z());
//imgui.text(std::string(buf));
//// force extra frame to automatically update window size
//const float width = ImGui::GetWindowWidth();
//const size_t length = strlen(buf);
//if (width != last_window_width || length != last_text_length) {
// last_window_width = width;
// last_text_length = length;
// imgui.set_requires_extra_frame();
//}
//imgui.end();
//ImGui::PopStyleVar();
}
#endif // ENABLE_SHOW_TOOLPATHS_COG
#if ENABLE_PREVIEW_LAYER_TIME
float GCodeViewer::Extrusions::Range::step_size(EType type) const
{
@ -638,10 +714,19 @@ void GCodeViewer::init()
void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized)
{
// avoid processing if called with the same gcode_result
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
if (m_last_result_id == gcode_result.id &&
(m_last_view_type == m_view_type || (m_last_view_type != EViewType::VolumetricRate && m_view_type != EViewType::VolumetricRate)))
return;
#else
if (m_last_result_id == gcode_result.id)
return;
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
m_last_result_id = gcode_result.id;
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
m_last_view_type = m_view_type;
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
// release gpu memory, if used
reset();
@ -955,6 +1040,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print));
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode));
#if ENABLE_SHOW_TOOLPATHS_COG
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CenterOfGravity), m_cog.is_visible());
#endif // ENABLE_SHOW_TOOLPATHS_COG
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Shells), m_shells.visible);
flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible());
#if !ENABLE_PREVIEW_LAYOUT
@ -978,6 +1066,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags)
set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ColorChanges)));
set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast<unsigned int>(Preview::OptionType::PausePrints)));
set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast<unsigned int>(Preview::OptionType::CustomGCodes)));
#if ENABLE_SHOW_TOOLPATHS_COG
m_cog.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::CenterOfGravity)));
#endif // ENABLE_SHOW_TOOLPATHS_COG
m_shells.visible = is_flag_set(static_cast<unsigned int>(Preview::OptionType::Shells));
m_sequential_view.marker.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolMarker)));
#if !ENABLE_PREVIEW_LAYOUT
@ -1200,9 +1291,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
// add current vertex
add_vertex(curr);
};
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) {
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) {
#else
auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) {
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
// add starting index
indices.push_back(static_cast<IBufferType>(indices.size()));
buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1);
@ -1221,7 +1318,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
};
// format data into the buffers to be rendered as solid
auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) {
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) {
#else
auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) {
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) {
// append position
vertices.push_back(position.x());
@ -1233,7 +1336,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
vertices.push_back(normal.z());
};
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) {
#else
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1);
buffer.paths.back().sub_paths.back().first.position = prev.position;
}
@ -1278,8 +1385,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position };
};
auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, const GCodeProcessorResult::MoveVertex* next,
TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) {
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr,
const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id,
IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) {
#else
auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr,
const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id,
IndexBuffer& indices, size_t move_id) {
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
static Vec3f prev_dir;
static Vec3f prev_up;
static float sq_prev_length;
@ -1324,7 +1438,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]);
};
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) {
#else
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
buffer.paths.back().sub_paths.back().first.position = prev.position;
}
@ -1408,7 +1526,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
vbuffer_size += 6;
}
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate)))
#else
if (next != nullptr && (curr.type != next->type || !last_path.matches(*next)))
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
// ending cap triangles
append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets);
@ -1537,6 +1659,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
if (wxGetApp().is_editor())
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
#if ENABLE_SHOW_TOOLPATHS_COG
m_cog.reset();
#endif // ENABLE_SHOW_TOOLPATHS_COG
m_sequential_view.gcode_ids.clear();
for (size_t i = 0; i < gcode_result.moves.size(); ++i) {
const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i];
@ -1544,6 +1670,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
m_sequential_view.gcode_ids.push_back(move.gcode_id);
}
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate;
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
std::vector<MultiVertexBuffer> vertices(m_buffers.size());
std::vector<MultiIndexBuffer> indices(m_buffers.size());
std::vector<InstanceBuffer> instances(m_buffers.size());
@ -1551,18 +1681,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
std::vector<InstancesOffsets> instances_offsets(m_buffers.size());
std::vector<float> options_zs;
size_t seams_count = 0;
std::vector<size_t> biased_seams_ids;
// toolpaths data -> extract vertices from result
for (size_t i = 0; i < m_moves_count; ++i) {
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
if (curr.type == EMoveType::Seam) {
++seams_count;
if (curr.type == EMoveType::Seam)
biased_seams_ids.push_back(i - biased_seams_ids.size() - 1);
}
size_t move_id = i - seams_count;
const size_t move_id = i - biased_seams_ids.size();
// skip first vertex
if (i == 0)
@ -1570,6 +1697,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1];
#if ENABLE_SHOW_TOOLPATHS_COG
if (curr.type == EMoveType::Extrude &&
curr.extrusion_role != erSkirt &&
curr.extrusion_role != erSupportMaterial &&
curr.extrusion_role != erSupportMaterialInterface &&
curr.extrusion_role != erWipeTower &&
curr.extrusion_role != erCustom &&
curr.extrusion_role != erMixed) {
const Vec3d curr_pos = curr.position.cast<double>();
const Vec3d prev_pos = prev.position.cast<double>();
m_cog.add_segment(curr_pos, prev_pos, curr.mm3_per_mm * (curr_pos - prev_pos).norm());
}
#endif // ENABLE_SHOW_TOOLPATHS_COG
// update progress dialog
++progress_count;
if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
@ -1597,7 +1738,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
v_multibuffer.push_back(VertexBuffer());
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
Path& last_path = t_buffer.paths.back();
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate))
#else
if (prev.type == curr.type && last_path.matches(curr))
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, move_id - 1);
}
}
@ -1608,7 +1753,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
{
case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; }
case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; }
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; }
#else
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, move_id); break; }
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
case TBuffer::ERenderPrimitiveType::InstancedModel:
{
add_model_instance(curr, inst_buffer, inst_id_buffer, move_id);
@ -1893,14 +2042,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
using VboIndexList = std::vector<unsigned int>;
std::vector<VboIndexList> vbo_indices(m_buffers.size());
seams_count = 0;
size_t seams_count = 0;
for (size_t i = 0; i < m_moves_count; ++i) {
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
if (curr.type == EMoveType::Seam)
++seams_count;
size_t move_id = i - seams_count;
const size_t move_id = i - seams_count;
// skip first vertex
if (i == 0)
@ -1972,12 +2121,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
break;
}
case TBuffer::ERenderPrimitiveType::Line: {
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate);
#else
add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
break;
}
case TBuffer::ERenderPrimitiveType::Triangle: {
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate);
#else
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
break;
}
case TBuffer::ERenderPrimitiveType::BatchedModel: {
@ -4074,15 +4231,6 @@ void GCodeViewer::render_legend(float& legend_height)
};
#if ENABLE_LEGEND_TOOLBAR_ICONS
// auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) {
// const float margin = 3.0f;
// const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size));
// window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
// };
// auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) {
// const float margin = 3.0f;
// window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f);
// };
auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) {
ImGuiIO& io = ImGui::GetIO();
const ImTextureID tex_id = io.Fonts->TexID;
@ -4091,17 +4239,17 @@ void GCodeViewer::render_legend(float& legend_height)
const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id);
const ImVec2 uv0 = { static_cast<float>(rect->X) / tex_w, static_cast<float>(rect->Y) / tex_h };
const ImVec2 uv1 = { static_cast<float>(rect->X + rect->Width) / tex_w, static_cast<float>(rect->Y + rect->Height) / tex_h };
window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
};
#else
auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) {
const float margin = 3.0f;
auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) {
const float margin = 3.0f;
const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size));
window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGuiWrapper::to_ImU32(color), 16);
};
auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const Color& color) {
auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) {
const float margin = 3.0f;
window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f);
window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGuiWrapper::to_ImU32(color), 3.0f);
};
#endif // ENABLE_LEGEND_TOOLBAR_ICONS
@ -4190,12 +4338,41 @@ void GCodeViewer::render_legend(float& legend_height)
#endif // ENABLE_LEGEND_TOOLBAR_ICONS
});
ImGui::SameLine();
#if ENABLE_SHOW_TOOLPATHS_COG
#if ENABLE_LEGEND_TOOLBAR_ICONS
toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendCOG);
});
#else
toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [](ImGuiWindow& window, const ImVec2& pos, float size) {
const ImU32 black = ImGuiWrapper::to_ImU32({ 0.0f, 0.0f, 0.0f, 1.0f });
const ImU32 white = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f });
const float margin = 3.0f;
const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size));
const float radius = 0.5f * (size - 2.0f * margin);
window.DrawList->PathArcToFast(center, radius, 0, 3);
window.DrawList->PathLineTo(center);
window.DrawList->PathFillConvex(black);
window.DrawList->PathArcToFast(center, radius, 3, 6);
window.DrawList->PathLineTo(center);
window.DrawList->PathFillConvex(white);
window.DrawList->PathArcToFast(center, radius, 6, 9);
window.DrawList->PathLineTo(center);
window.DrawList->PathFillConvex(black);
window.DrawList->PathArcToFast(center, radius, 9, 12);
window.DrawList->PathLineTo(center);
window.DrawList->PathFillConvex(white);
window.DrawList->AddCircle(center, radius, black, 16);
});
#endif // ENABLE_LEGEND_TOOLBAR_ICONS
ImGui::SameLine();
#endif // ENABLE_SHOW_TOOLPATHS_COG
#if ENABLE_LEGEND_TOOLBAR_ICONS
toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendShells);
#else
toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) {
const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 1.0f });
const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f });
const float margin = 3.0f;
const float proj = 0.25f * size;
window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color);
@ -4212,11 +4389,11 @@ void GCodeViewer::render_legend(float& legend_height)
image_icon(window, pos, size, ImGui::LegendToolMarker);
#else
toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) {
const ImU32 color = ImGui::GetColorU32({ 1.0f, 1.0f, 1.0f, 0.8f });
const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 0.8f });
const float margin = 3.0f;
const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin);
const ImVec2 p2 = ImVec2(p1.x + 0.25f * size, p1.y - 0.25f * size);
const ImVec2 p3 = ImVec2(p1.x - 0.25f * size, p1.y - 0.25f * size);
const ImVec2 p2(p1.x + 0.25f * size, p1.y - 0.25f * size);
const ImVec2 p3(p1.x - 0.25f * size, p1.y - 0.25f * size);
window.DrawList->AddTriangleFilled(p1, p2, p3, color);
const float mid_x = 0.5f * (pos.x + pos.x + size);
window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color);

View file

@ -212,7 +212,11 @@ class GCodeViewer
unsigned char cp_color_id{ 0 };
std::vector<Sub_Path> sub_paths;
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const;
#else
bool matches(const GCodeProcessorResult::MoveVertex& move) const;
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
size_t vertices_count() const {
return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1;
}
@ -380,6 +384,52 @@ class GCodeViewer
bool visible{ false };
};
#if ENABLE_SHOW_TOOLPATHS_COG
// helper to render center of gravity
class COG
{
GLModel m_model;
bool m_visible{ false };
// whether or not to render the model with fixed screen size
bool m_fixed_size{ true };
double m_total_mass{ 0.0 };
Vec3d m_position{ Vec3d::Zero() };
public:
void render();
void reset() {
m_position = Vec3d::Zero();
m_total_mass = 0.0;
}
bool is_visible() const { return m_visible; }
void set_visible(bool visible) { m_visible = visible; }
void add_segment(const Vec3d& v1, const Vec3d& v2, double mass) {
assert(mass > 0.0);
m_position += mass * 0.5 * (v1 + v2);
m_total_mass += mass;
}
Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_position / m_total_mass) : Vec3d::Zero(); }
private:
void init() {
if (m_model.is_initialized())
return;
const float radius = m_fixed_size ? 10.0f : 1.0f;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
m_model.init_from(smooth_sphere(32, radius));
#else
m_model.init_from(its_make_sphere(radius, PI / 32.0));
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
};
#endif // ENABLE_SHOW_TOOLPATHS_COG
// helper to render extrusion paths
struct Extrusions
{
@ -716,6 +766,9 @@ public:
private:
bool m_gl_data_initialized{ false };
unsigned int m_last_result_id{ 0 };
#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
EViewType m_last_view_type{ EViewType::Count };
#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC
size_t m_moves_count{ 0 };
std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
// bounding box of toolpaths
@ -734,6 +787,9 @@ private:
Extrusions m_extrusions;
SequentialView m_sequential_view;
Shells m_shells;
#if ENABLE_SHOW_TOOLPATHS_COG
COG m_cog;
#endif // ENABLE_SHOW_TOOLPATHS_COG
EViewType m_view_type{ EViewType::FeatureType };
bool m_legend_enabled{ true };
#if ENABLE_PREVIEW_LAYOUT
@ -779,6 +835,9 @@ public:
void reset();
void render();
#if ENABLE_SHOW_TOOLPATHS_COG
void render_cog() { m_cog.render(); }
#endif // ENABLE_SHOW_TOOLPATHS_COG
bool has_data() const { return !m_roles.empty(); }
bool can_export_toolpaths() const;

View file

@ -368,8 +368,8 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
// vertices
const float l = bar_rect.get_left();
@ -428,8 +428,8 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect)
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::USHORT };
init_data.color = ColorRGBA::BLACK();
init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x;
@ -447,16 +447,20 @@ void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect)
m_profile.profile.reset();
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::UINT };
const GLModel::Geometry::EIndexType index_type = (m_layer_height_profile.size() / 2 < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT;
init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2, index_type };
init_data.color = ColorRGBA::BLUE();
init_data.vertices.reserve(m_layer_height_profile.size() * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(m_layer_height_profile.size() * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(m_layer_height_profile.size() / 2);
init_data.reserve_indices(m_layer_height_profile.size() / 2);
// vertices + indices
for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) {
init_data.add_vertex(Vec2f(bar_rect.get_left() + float(m_layer_height_profile[i + 1]) * scale_x,
bar_rect.get_bottom() + float(m_layer_height_profile[i]) * scale_y));
init_data.add_uint_index(i / 2);
if (index_type == GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_index((unsigned short)i / 2);
else
init_data.add_uint_index(i / 2);
}
m_profile.profile.init_from(std::move(init_data));
@ -898,6 +902,8 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons
unsigned int vertices_counter = 0;
for (const ExPolygon& poly : polygons_union) {
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(poly);
fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size());
fill_data.reserve_indices(fill_data.indices_count() + triangulation.size());
for (const Vec3d& v : triangulation) {
fill_data.add_vertex((Vec3f)(v.cast<float>() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting
++vertices_counter;
@ -1600,6 +1606,10 @@ void GLCanvas3D::render()
#if ENABLE_RENDER_SELECTION_CENTER
_render_selection_center();
#endif // ENABLE_RENDER_SELECTION_CENTER
#if ENABLE_SHOW_TOOLPATHS_COG
if (!m_main_toolbar.is_enabled())
_render_gcode_cog();
#endif // ENABLE_SHOW_TOOLPATHS_COG
// we need to set the mouse's scene position here because the depth buffer
// could be invalidated by the following gizmo render methods
@ -5234,8 +5244,8 @@ void GLCanvas3D::_render_background()
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
// vertices
init_data.add_vertex(Vec2f(-1.0f, -1.0f), Vec2f(0.0f, 0.0f));
@ -5415,6 +5425,13 @@ void GLCanvas3D::_render_gcode()
m_gcode_viewer.render();
}
#if ENABLE_SHOW_TOOLPATHS_COG
void GLCanvas3D::_render_gcode_cog()
{
m_gcode_viewer.render_cog();
}
#endif // ENABLE_SHOW_TOOLPATHS_COG
void GLCanvas3D::_render_selection()
{
float scale_factor = 1.0;

View file

@ -944,6 +944,9 @@ private:
void _render_bed_for_picking(bool bottom);
void _render_objects(GLVolumeCollection::ERenderType type);
void _render_gcode();
#if ENABLE_SHOW_TOOLPATHS_COG
void _render_gcode_cog();
#endif // ENABLE_SHOW_TOOLPATHS_COG
void _render_selection();
void _render_sequential_clearance();
#if ENABLE_RENDER_SELECTION_CENTER

View file

@ -18,6 +18,16 @@ namespace Slic3r {
namespace GUI {
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void GLModel::Geometry::reserve_vertices(size_t vertices_count)
{
vertices.reserve(vertices_count * vertex_stride_floats(format));
}
void GLModel::Geometry::reserve_indices(size_t indices_count)
{
indices.reserve(indices_count * index_stride_bytes(format));
}
void GLModel::Geometry::add_vertex(const Vec2f& position)
{
assert(format.vertex_layout == EVertexLayout::P2);
@ -42,6 +52,16 @@ void GLModel::Geometry::add_vertex(const Vec3f& position)
vertices.emplace_back(position.z());
}
void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec2f& tex_coord)
{
assert(format.vertex_layout == EVertexLayout::P3T2);
vertices.emplace_back(position.x());
vertices.emplace_back(position.y());
vertices.emplace_back(position.z());
vertices.emplace_back(tex_coord.x());
vertices.emplace_back(tex_coord.y());
}
void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal)
{
assert(format.vertex_layout == EVertexLayout::P3N3);
@ -228,6 +248,7 @@ size_t GLModel::Geometry::vertex_stride_floats(const Format& format)
case EVertexLayout::P2: { return 2; }
case EVertexLayout::P2T2: { return 4; }
case EVertexLayout::P3: { return 3; }
case EVertexLayout::P3T2: { return 5; }
case EVertexLayout::P3N3: { return 6; }
default: { assert(false); return 0; }
};
@ -240,6 +261,7 @@ size_t GLModel::Geometry::position_stride_floats(const Format& format)
case EVertexLayout::P2:
case EVertexLayout::P2T2: { return 2; }
case EVertexLayout::P3:
case EVertexLayout::P3T2:
case EVertexLayout::P3N3: { return 3; }
default: { assert(false); return 0; }
};
@ -252,6 +274,7 @@ size_t GLModel::Geometry::position_offset_floats(const Format& format)
case EVertexLayout::P2:
case EVertexLayout::P2T2:
case EVertexLayout::P3:
case EVertexLayout::P3T2:
case EVertexLayout::P3N3: { return 0; }
default: { assert(false); return 0; }
};
@ -279,7 +302,8 @@ size_t GLModel::Geometry::tex_coord_stride_floats(const Format& format)
{
switch (format.vertex_layout)
{
case EVertexLayout::P2T2: { return 2; }
case EVertexLayout::P2T2:
case EVertexLayout::P3T2: { return 2; }
default: { assert(false); return 0; }
};
}
@ -289,6 +313,7 @@ size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format)
switch (format.vertex_layout)
{
case EVertexLayout::P2T2: { return 2; }
case EVertexLayout::P3T2: { return 3; }
default: { assert(false); return 0; }
};
}
@ -310,6 +335,7 @@ bool GLModel::Geometry::has_position(const Format& format)
case EVertexLayout::P2:
case EVertexLayout::P2T2:
case EVertexLayout::P3:
case EVertexLayout::P3T2:
case EVertexLayout::P3N3: { return true; }
default: { assert(false); return false; }
};
@ -321,7 +347,8 @@ bool GLModel::Geometry::has_normal(const Format& format)
{
case EVertexLayout::P2:
case EVertexLayout::P2T2:
case EVertexLayout::P3: { return false; }
case EVertexLayout::P3:
case EVertexLayout::P3T2: { return false; }
case EVertexLayout::P3N3: { return true; }
default: { assert(false); return false; }
};
@ -331,7 +358,8 @@ bool GLModel::Geometry::has_tex_coord(const Format& format)
{
switch (format.vertex_layout)
{
case EVertexLayout::P2T2: { return true; }
case EVertexLayout::P2T2:
case EVertexLayout::P3T2: { return true; }
case EVertexLayout::P2:
case EVertexLayout::P3:
case EVertexLayout::P3N3: { return false; }
@ -452,8 +480,8 @@ void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bb
Geometry& data = m_render_data.geometry;
data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, Geometry::EIndexType::UINT };
data.vertices.reserve(3 * its.indices.size() * Geometry::vertex_stride_floats(data.format));
data.indices.reserve(3 * its.indices.size() * Geometry::index_stride_bytes(data.format));
data.reserve_vertices(3 * its.indices.size());
data.reserve_indices(3 * its.indices.size());
// vertices + indices
unsigned int vertices_counter = 0;
@ -534,8 +562,8 @@ void GLModel::init_from(const Polygons& polygons, float z)
segments_count += polygon.points.size();
}
data.vertices.reserve(2 * segments_count * Geometry::vertex_stride_floats(data.format));
data.indices.reserve(2 * segments_count * Geometry::index_stride_bytes(data.format));
data.reserve_vertices(2 * segments_count);
data.reserve_indices(2 * segments_count);
// vertices + indices
unsigned int vertices_counter = 0;
@ -702,8 +730,8 @@ void GLModel::render() const
const Geometry& data = m_render_data.geometry;
GLenum mode = get_primitive_mode(data.format);
GLenum index_type = get_index_type(data.format);
const GLenum mode = get_primitive_mode(data.format);
const GLenum index_type = get_index_type(data.format);
const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format);
const bool position = Geometry::has_position(data.format);
@ -1016,8 +1044,8 @@ GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, fl
GLModel::Geometry data;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT };
data.vertices.reserve((6 * resolution + 2) * GLModel::Geometry::vertex_stride_floats(data.format));
data.indices.reserve((6 * resolution * 3) * GLModel::Geometry::index_stride_bytes(data.format));
data.reserve_vertices(6 * resolution + 2);
data.reserve_indices(6 * resolution * 3);
#else
GLModel::Geometry::Entity entity;
entity.type = GLModel::EPrimitiveType::Triangles;
@ -1157,6 +1185,7 @@ GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, fl
data.entities.emplace_back(entity);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
return data;
}
@ -1182,8 +1211,8 @@ GLModel::Geometry circular_arrow(unsigned short resolution, float radius, float
GLModel::Geometry data;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT };
data.vertices.reserve((8 * (resolution + 1) + 30) * GLModel::Geometry::vertex_stride_floats(data.format));
data.indices.reserve(((8 * resolution + 16) * 3) * GLModel::Geometry::index_stride_bytes(data.format));
data.reserve_vertices(8 * (resolution + 1) + 30);
data.reserve_indices((8 * resolution + 16) * 3);
#else
GLModel::Geometry::Entity entity;
entity.type = GLModel::EPrimitiveType::Triangles;
@ -1488,6 +1517,7 @@ GLModel::Geometry circular_arrow(unsigned short resolution, float radius, float
data.entities.emplace_back(entity);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
return data;
}
@ -1508,8 +1538,8 @@ GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_w
GLModel::Geometry data;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT };
data.vertices.reserve(42 * GLModel::Geometry::vertex_stride_floats(data.format));
data.indices.reserve((24 * 3) * GLModel::Geometry::index_stride_bytes(data.format));
data.reserve_vertices(42);
data.reserve_indices(72);
#else
GLModel::Geometry::Entity entity;
entity.type = GLModel::EPrimitiveType::Triangles;
@ -1681,6 +1711,7 @@ GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_w
data.entities.emplace_back(entity);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
return data;
}
@ -1694,8 +1725,8 @@ GLModel::Geometry diamond(unsigned short resolution)
GLModel::Geometry data;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT };
data.vertices.reserve((resolution + 2) * GLModel::Geometry::vertex_stride_floats(data.format));
data.indices.reserve(((2 * (resolution + 1)) * 3) * GLModel::Geometry::index_stride_bytes(data.format));
data.reserve_vertices(resolution + 2);
data.reserve_indices((2 * (resolution + 1)) * 3);
#else
GLModel::Geometry::Entity entity;
entity.type = GLModel::EPrimitiveType::Triangles;
@ -1706,7 +1737,7 @@ GLModel::Geometry diamond(unsigned short resolution)
#if ENABLE_GLBEGIN_GLEND_REMOVAL
// vertices
for (unsigned short i = 0; i < resolution; ++i) {
float ii = float(i) * step;
const float ii = float(i) * step;
const Vec3f p = { 0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f };
append_vertex(data, p, p.normalized());
}
@ -1764,8 +1795,77 @@ GLModel::Geometry diamond(unsigned short resolution)
data.entities.emplace_back(entity);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
return data;
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_SHOW_TOOLPATHS_COG
GLModel::Geometry smooth_sphere(unsigned short resolution, float radius)
{
resolution = std::max<unsigned short>(4, resolution);
resolution = std::min<unsigned short>(256, resolution); // ensure no unsigned short overflow of indices
const unsigned short sectorCount = /*2 **/ resolution;
const unsigned short stackCount = resolution;
const float sectorStep = float(2.0 * M_PI / sectorCount);
const float stackStep = float(M_PI / stackCount);
GLModel::Geometry data;
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::USHORT };
data.reserve_vertices((stackCount - 1) * sectorCount + 2);
data.reserve_indices((2 * (stackCount - 1) * sectorCount) * 3);
// vertices
for (unsigned short i = 0; i <= stackCount; ++i) {
// from pi/2 to -pi/2
const double stackAngle = 0.5 * M_PI - stackStep * i;
const double xy = double(radius) * ::cos(stackAngle);
const double z = double(radius) * ::sin(stackAngle);
if (i == 0 || i == stackCount) {
const Vec3f v(float(xy), 0.0f, float(z));
data.add_vertex(v, (Vec3f)v.normalized());
}
else {
for (unsigned short j = 0; j < sectorCount; ++j) {
// from 0 to 2pi
const double sectorAngle = sectorStep * j;
const Vec3f v(float(xy * std::cos(sectorAngle)), float(xy * std::sin(sectorAngle)), float(z));
data.add_vertex(v, (Vec3f)v.normalized());
}
}
}
// triangles
for (unsigned short i = 0; i < stackCount; ++i) {
// Beginning of current stack.
unsigned short k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
const unsigned short k1_first = k1;
// Beginning of next stack.
unsigned short k2 = (i == 0) ? 1 : (k1 + sectorCount);
const unsigned short k2_first = k2;
for (unsigned short j = 0; j < sectorCount; ++j) {
// 2 triangles per sector excluding first and last stacks
unsigned short k1_next = k1;
unsigned short k2_next = k2;
if (i != 0) {
k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
data.add_ushort_triangle(k1, k2, k1_next);
}
if (i + 1 != stackCount) {
k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
data.add_ushort_triangle(k1_next, k2, k2_next);
}
k1 = k1_next;
k2 = k2_next;
}
}
return data;
}
#endif // ENABLE_SHOW_TOOLPATHS_COG
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
} // namespace GUI
} // namespace Slic3r

View file

@ -58,6 +58,7 @@ namespace GUI {
P2, // position 2 floats
P2T2, // position 2 floats + texture coords 2 floats
P3, // position 3 floats
P3T2, // position 3 floats + texture coords 2 floats
P3N3, // position 3 floats + normal 3 floats
};
@ -79,9 +80,13 @@ namespace GUI {
std::vector<unsigned char> indices;
ColorRGBA color{ ColorRGBA::BLACK() };
void reserve_vertices(size_t vertices_count);
void reserve_indices(size_t indices_count);
void add_vertex(const Vec2f& position);
void add_vertex(const Vec2f& position, const Vec2f& tex_coord);
void add_vertex(const Vec3f& position);
void add_vertex(const Vec3f& position, const Vec2f& tex_coord);
void add_vertex(const Vec3f& position, const Vec3f& normal);
void add_ushort_index(unsigned short id);
@ -101,6 +106,8 @@ namespace GUI {
unsigned int extract_uint_index(size_t id) const;
unsigned short extract_ushort_index(size_t id) const;
bool is_empty() const { return vertices.empty() || indices.empty(); }
size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); }
size_t indices_count() const { return indices.size() / index_stride_bytes(format); }
@ -254,6 +261,14 @@ namespace GUI {
// the diamond is contained into a box with size [1, 1, 1]
GLModel::Geometry diamond(unsigned short resolution);
#if ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_SHOW_TOOLPATHS_COG
// create a sphere with the given resolution and smooth normals
// the origin of the sphere is in its center
GLModel::Geometry smooth_sphere(unsigned short resolution, float radius);
#endif // ENABLE_SHOW_TOOLPATHS_COG
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
} // namespace GUI
} // namespace Slic3r

View file

@ -115,8 +115,8 @@ namespace GUI {
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(4 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(4);
// vertices
init_data.add_vertex(Vec2f(left, bottom));

View file

@ -41,6 +41,10 @@ std::pair<bool, std::string> GLShadersManager::init()
// used to render 3D scene background
valid &= append_shader("background", { "background.vs", "background.fs" });
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_SHOW_TOOLPATHS_COG
// used to render toolpaths center of gravity
valid &= append_shader("toolpaths_cog", { "toolpaths_cog.vs", "toolpaths_cog.fs" });
#endif // ENABLE_SHOW_TOOLPATHS_COG
// used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview
valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" });
// used to render printbed

View file

@ -342,8 +342,8 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right,
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
// vertices
init_data.add_vertex(Vec2f(left, bottom), Vec2f(uvs.left_bottom.u, uvs.left_bottom.v));

View file

@ -872,8 +872,8 @@ void GUI_App::init_app_config()
{
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
// SetAppName(SLIC3R_APP_KEY);
// SetAppName(SLIC3R_APP_KEY "-alpha");
SetAppName(SLIC3R_APP_KEY "-beta");
SetAppName(SLIC3R_APP_KEY "-alpha");
// SetAppName(SLIC3R_APP_KEY "-beta");
// SetAppDisplayName(SLIC3R_APP_NAME);
// Set the Slic3r data directory at the Slic3r XS module.

View file

@ -1706,16 +1706,16 @@ void ObjectList::load_shape_object(const std::string& type_name)
if (selection.get_object_idx() != -1)
return;
const int obj_idx = m_objects->size();
if (obj_idx < 0)
return;
take_snapshot(_L("Add Shape"));
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
load_mesh_object(mesh, _L("Shape") + "-" + _(type_name));
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (!m_objects->empty())
m_objects->back()->volumes.front()->source.is_from_builtin_objects = true;
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
wxGetApp().mainframe->update_title();
}

View file

@ -1438,8 +1438,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
parent->set_focused_editor(nullptr);
#if ENABLE_OBJECT_MANIPULATOR_FOCUS
// if the widget loosing focus is not a manipulator field, call kill_focus
if (dynamic_cast<ManipulationEditor*>(e.GetWindow()) == nullptr)
// if the widgets exchanging focus are both manipulator fields, call kill_focus
if (dynamic_cast<ManipulationEditor*>(e.GetEventObject()) != nullptr && dynamic_cast<ManipulationEditor*>(e.GetWindow()) != nullptr)
#else
if (!m_enter_pressed)
#endif // ENABLE_OBJECT_MANIPULATOR_FOCUS

View file

@ -126,6 +126,9 @@ public:
ColorChanges,
PausePrints,
CustomGCodes,
#if ENABLE_SHOW_TOOLPATHS_COG
CenterOfGravity,
#endif // ENABLE_SHOW_TOOLPATHS_COG
Shells,
ToolMarker,
#if !ENABLE_PREVIEW_LAYOUT

View file

@ -115,8 +115,8 @@ void GLGizmoCut::on_render()
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
// vertices
init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z()));
@ -160,8 +160,8 @@ void GLGizmoCut::on_render()
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.color = ColorRGBA::YELLOW();
init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
init_data.add_vertex((Vec3f)plane_center.cast<float>());

View file

@ -1301,23 +1301,39 @@ void GLGizmoEmboss::draw_advanced()
*font_prop.distance : .0f;
ImGui::SetNextItemWidth(item_width);
if (m_imgui->slider_optional_float(_u8L("Surface distance").c_str(), font_prop.distance,
-font_prop.emboss, font_prop.emboss, "%.2f mm", 1.f, false, _L("Distance from model surface"))) {
-2*font_prop.emboss, 2*font_prop.emboss, "%.2f mm", 1.f, false, _L("Distance from model surface"))) {
float act_distance = font_prop.distance.has_value() ?
*font_prop.distance : .0f;
float diff = prev_distance - act_distance;
float diff = prev_distance - act_distance;
Vec3d displacement_rot = Vec3d::UnitZ() * diff;
// move with volume by diff size in volume z
Vec3d r = m_volume->get_rotation();
Eigen::Matrix3d rot_mat =
(Eigen::AngleAxisd(r.z(), Vec3d::UnitZ()) *
Eigen::AngleAxisd(r.y(), Vec3d::UnitY()) *
Eigen::AngleAxisd(r.x(), Vec3d::UnitX())).toRotationMatrix();
Vec3d displacement_rot = rot_mat * (Vec3d::UnitZ() * diff);
m_volume->translate(displacement_rot);
m_volume->set_new_unique_id();
/*Selection &s = m_parent.get_selection();
const GLVolume *v = s.get_volume(*s.get_volume_idxs().begin());
s.translate(v->get_volume_offset() + displacement_rot*diff, ECoordinatesType::Local);*/
Selection &selection = m_parent.get_selection();
selection.start_dragging();
selection.translate(displacement_rot, ECoordinatesType::Local);
selection.stop_dragging();
std::string snapshot_name; // empty meand no store undo / redo
// NOTE: it use L instead of _L macro because prefix _ is appended inside function do_move
//snapshot_name = L("Set surface distance");
m_parent.do_move(snapshot_name);
}
float prev_angle = font_prop.angle.has_value() ? *font_prop.angle : .0f;
ImGui::SetNextItemWidth(item_width);
if (m_imgui->slider_optional_float(_u8L("Angle").c_str(), font_prop.angle,
-180.f, 180.f, u8"%.2f °", 1.f, false, _L("Rotation of text"))) {
float act_angle = font_prop.angle.has_value() ? *font_prop.angle : .0f;
float diff = prev_angle - act_angle;
Selection &selection = m_parent.get_selection();
selection.start_dragging();
selection.rotate(Vec3d(0., 0., M_PI / 180.0 * diff), TransformationType::Local);
selection.stop_dragging();
std::string snapshot_name; // empty meand no store undo / redo
// NOTE: it use L instead of _L macro because prefix _ is appended inside function do_move
//snapshot_name = L("Set surface distance");
m_parent.do_rotate(snapshot_name);
}
// when more collection add selector

View file

@ -1,6 +1,9 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoFlatten.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/GUI_App.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
@ -63,6 +66,14 @@ void GLGizmoFlatten::on_render()
{
const Selection& selection = m_parent.get_selection();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
@ -76,21 +87,38 @@ void GLGizmoFlatten::on_render()
if (this->is_plane_update_necessary())
update_planes();
for (int i = 0; i < (int)m_planes.size(); ++i) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR);
m_planes[i].vbo.render();
#else
glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data()));
if (m_planes[i].vbo.has_VBOs())
m_planes[i].vbo.render();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
glsafe(::glPopMatrix());
}
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
shader->stop_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
void GLGizmoFlatten::on_render_for_picking()
{
const Selection& selection = m_parent.get_selection();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));
@ -102,13 +130,21 @@ void GLGizmoFlatten::on_render_for_picking()
if (this->is_plane_update_necessary())
update_planes();
for (int i = 0; i < (int)m_planes.size(); ++i) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_planes[i].vbo.set_color(picking_color_component(i));
#else
glsafe(::glColor4fv(picking_color_component(i).data()));
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_planes[i].vbo.render();
}
glsafe(::glPopMatrix());
}
glsafe(::glEnable(GL_CULL_FACE));
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
shader->stop_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object)
@ -324,12 +360,29 @@ void GLGizmoFlatten::update_planes()
// And finally create respective VBOs. The polygon is convex with
// the vertices in order, so triangulation is trivial.
for (auto& plane : m_planes) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel::Geometry init_data;
const GLModel::Geometry::EIndexType index_type = (plane.vertices.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT;
init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3, index_type };
init_data.reserve_vertices(plane.vertices.size());
init_data.reserve_indices(plane.vertices.size());
// vertices + indices
for (size_t i = 0; i < plane.vertices.size(); ++i) {
init_data.add_vertex((Vec3f)plane.vertices[i].cast<float>(), (Vec3f)plane.normal.cast<float>());
if (index_type == GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_index((unsigned short)i);
else
init_data.add_uint_index((unsigned int)i);
}
plane.vbo.init_from(std::move(init_data));
#else
plane.vbo.reserve(plane.vertices.size());
for (const auto& vert : plane.vertices)
plane.vbo.push_geometry(vert, plane.normal);
for (size_t i=1; i<plane.vertices.size()-1; ++i)
plane.vbo.push_triangle(0, i, i+1); // triangle fan
plane.vbo.finalize_geometry(true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// FIXME: vertices should really be local, they need not
// persist now when we use VBOs
plane.vertices.clear();

View file

@ -2,7 +2,11 @@
#define slic3r_GLGizmoFlatten_hpp_
#include "GLGizmoBase.hpp"
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/GLModel.hpp"
#else
#include "slic3r/GUI/3DScene.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
namespace Slic3r {
@ -22,7 +26,11 @@ private:
struct PlaneData {
std::vector<Vec3d> vertices; // should be in fact local in update_planes()
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel vbo;
#else
GLIndexedVertexArray vbo;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
Vec3d normal;
float area;
};

View file

@ -589,6 +589,9 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
m_gizmo_scene.render(color_idx);
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
render_paint_contour();
#else
if (m_paint_contour.has_VBO()) {
ScopeGuard guard_mm_gouraud([shader]() { shader->start_using(); });
shader->stop_using();
@ -602,6 +605,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
contour_shader->stop_using();
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
m_update_render_data = false;
}
@ -636,6 +640,9 @@ void TriangleSelectorMmGui::update_render_data()
m_gizmo_scene.finalize_triangle_indices();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
update_paint_contour();
#else
m_paint_contour.release_geometry();
std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6);
@ -654,6 +661,7 @@ void TriangleSelectorMmGui::update_render_data()
m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size();
m_paint_contour.finalize_geometry();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const

View file

@ -117,8 +117,8 @@ void GLGizmoMove3D::on_render()
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.color = AXES_COLOR[id];
init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
init_data.add_vertex((Vec3f)center.cast<float>());

View file

@ -18,7 +18,11 @@
namespace Slic3r::GUI {
std::shared_ptr<GLIndexedVertexArray> GLGizmoPainterBase::s_sphere = nullptr;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
std::shared_ptr<GLModel> GLGizmoPainterBase::s_sphere = nullptr;
#else
std::shared_ptr<GLIndexedVertexArray> GLGizmoPainterBase::s_sphere = nullptr;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
@ -27,8 +31,13 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic
GLGizmoPainterBase::~GLGizmoPainterBase()
{
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
if (s_sphere != nullptr)
s_sphere.reset();
#else
if (s_sphere != nullptr && s_sphere->has_VBOs())
s_sphere->release_geometry();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
@ -185,8 +194,8 @@ void GLGizmoPainterBase::render_cursor_circle()
static const float StepSize = 2.0f * float(PI) / float(StepsCount);
init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f };
init_data.vertices.reserve(StepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(StepsCount * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(StepsCount);
init_data.reserve_indices(StepsCount);
// vertices + indices
for (unsigned short i = 0; i < StepsCount; ++i) {
@ -220,18 +229,27 @@ void GLGizmoPainterBase::render_cursor_circle()
void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
{
if (s_sphere == nullptr) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
s_sphere = std::make_shared<GLModel>();
s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0));
#else
s_sphere = std::make_shared<GLIndexedVertexArray>();
s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0));
s_sphere->finalize_geometry(true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse();
const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo.data()));
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)));
glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z()));
glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data()));
glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius));
@ -243,11 +261,22 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const
render_color = this->get_cursor_sphere_left_button_color();
else if (m_button_down == Button::Right)
render_color = this->get_cursor_sphere_right_button_color();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
shader->start_using();
assert(s_sphere != nullptr);
s_sphere->set_color(render_color);
#else
glsafe(::glColor4fv(render_color.data()));
assert(s_sphere != nullptr);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
s_sphere->render();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
shader->stop_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
if (is_left_handed)
glFrontFace(GL_CCW);
@ -763,13 +792,28 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
shader->set_uniform("offset_depth_buffer", true);
for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color),
std::make_pair(&m_iva_blockers, blockers_color)}) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
iva.first->set_color(iva.second);
iva.first->render();
#else
if (iva.first->has_VBOs()) {
shader->set_uniform("uniform_color", iva.second);
iva.first->render();
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
for (auto &iva : m_iva_seed_fills)
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (auto& iva : m_iva_seed_fills) {
size_t color_idx = &iva - &m_iva_seed_fills.front();
const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
color_idx == 2 ? blockers_color :
GLVolume::NEUTRAL_COLOR);
iva.set_color(color);
iva.render();
}
#else
for (auto& iva : m_iva_seed_fills)
if (iva.has_VBOs()) {
size_t color_idx = &iva - &m_iva_seed_fills.front();
const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color :
@ -778,7 +822,11 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
shader->set_uniform("uniform_color", color);
iva.render();
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
render_paint_contour();
#else
if (m_paint_contour.has_VBO()) {
ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
shader->stop_using();
@ -792,13 +840,14 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
contour_shader->stop_using();
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
if (imgui)
render_debug(imgui);
else
assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
#endif
#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
}
void TriangleSelectorGUI::update_render_data()
@ -807,20 +856,44 @@ void TriangleSelectorGUI::update_render_data()
int blc_cnt = 0;
std::vector<int> seed_fill_cnt(m_iva_seed_fills.size(), 0);
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) {
iva->reset();
}
for (auto& iva : m_iva_seed_fills) {
iva.reset();
}
GLModel::Geometry iva_enforcers_data;
iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT };
GLModel::Geometry iva_blockers_data;
iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT };
std::array<GLModel::Geometry, 3> iva_seed_fills_data;
for (auto& data : iva_seed_fills_data)
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT };
#else
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->release_geometry();
for (auto &iva : m_iva_seed_fills)
iva.release_geometry();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (const Triangle &tr : m_triangles) {
if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill()))
continue;
int tr_state = int(tr.get_state());
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] :
tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data :
iva_blockers_data;
#else
GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] :
tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers :
m_iva_blockers;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] :
tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt :
blc_cnt;
@ -830,19 +903,40 @@ void TriangleSelectorGUI::update_render_data()
//FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort
// or the current implementation may be more cache friendly.
const Vec3f n = (v1 - v0).cross(v2 - v1).normalized();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
iva.add_vertex(v0, n);
iva.add_vertex(v1, n);
iva.add_vertex(v2, n);
iva.add_uint_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2);
#else
iva.push_geometry(v0, n);
iva.push_geometry(v1, n);
iva.push_geometry(v2, n);
iva.push_triangle(cnt, cnt + 1, cnt + 2);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
cnt += 3;
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
if (!iva_enforcers_data.is_empty())
m_iva_enforcers.init_from(std::move(iva_enforcers_data));
if (!iva_blockers_data.is_empty())
m_iva_blockers.init_from(std::move(iva_blockers_data));
for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) {
if (!iva_seed_fills_data[i].is_empty())
m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i]));
}
#else
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->finalize_geometry(true);
for (auto &iva : m_iva_seed_fills)
iva.finalize_geometry(true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
update_paint_contour();
#else
m_paint_contour.release_geometry();
std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6);
@ -861,8 +955,10 @@ void TriangleSelectorGUI::update_render_data()
m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size();
m_paint_contour.finalize_geometry();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
void GLPaintContour::render() const
{
assert(this->m_contour_VBO_id != 0);
@ -920,6 +1016,7 @@ void GLPaintContour::release_geometry()
}
this->clear();
}
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
@ -956,45 +1053,111 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
INVALID
};
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (auto& va : m_varrays)
va.reset();
#else
for (auto& va : m_varrays)
va.release_geometry();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
std::array<int, 3> cnts;
::glScalef(1.01f, 1.01f, 1.01f);
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
std::array<GLModel::Geometry, 3> varrays_data;
for (auto& data : varrays_data)
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT };
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
const Triangle& tr = m_triangles[tr_id];
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel::Geometry* va = nullptr;
#else
GLIndexedVertexArray* va = nullptr;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
int* cnt = nullptr;
if (tr_id < m_orig_size_indices) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
va = &varrays_data[ORIGINAL];
#else
va = &m_varrays[ORIGINAL];
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
cnt = &cnts[ORIGINAL];
}
else if (tr.valid()) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
va = &varrays_data[SPLIT];
#else
va = &m_varrays[SPLIT];
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
cnt = &cnts[SPLIT];
}
else {
if (! m_show_invalid)
continue;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
va = &varrays_data[INVALID];
#else
va = &m_varrays[INVALID];
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
cnt = &cnts[INVALID];
}
for (int i=0; i<3; ++i)
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (int i = 0; i < 3; ++i) {
va->add_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f));
}
va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2);
#else
for (int i = 0; i < 3; ++i)
va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
0., 0., 1.);
va->push_triangle(*cnt,
*cnt+1,
*cnt+2);
*cnt + 1,
*cnt + 2);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
*cnt += 3;
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
for (int i = 0; i < 3; ++i) {
if (!varrays_data[i].is_empty())
m_varrays[i].init_from(std::move(varrays_data[i]));
}
#else
for (auto* iva : { &m_iva_enforcers, &m_iva_blockers })
iva->finalize_geometry(true);
for (auto& iva : m_iva_seed_fills)
iva.finalize_geometry(true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr)
curr_shader->stop_using();
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel& va = m_varrays[i];
switch (i) {
case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break;
case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break;
case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break;
}
va.render();
#else
GLIndexedVertexArray& va = m_varrays[i];
va.finalize_geometry(true);
if (va.has_VBOs()) {
@ -1005,11 +1168,67 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
}
va.render();
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
shader->stop_using();
}
if (curr_shader != nullptr)
curr_shader->start_using();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
#endif
#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void TriangleSelectorGUI::update_paint_contour()
{
m_paint_contour.reset();
GLModel::Geometry init_data;
const std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
const GLModel::Geometry::EIndexType index_type = (2 * contour_edges.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, index_type };
init_data.reserve_vertices(2 * contour_edges.size());
init_data.reserve_indices(2 * contour_edges.size());
// vertices + indices
unsigned int vertices_count = 0;
for (const Vec2i& edge : contour_edges) {
init_data.add_vertex(m_vertices[edge(0)].v);
init_data.add_vertex(m_vertices[edge(1)].v);
vertices_count += 2;
if (index_type == GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_line((unsigned short)vertices_count - 2, (unsigned short)vertices_count - 1);
else
init_data.add_uint_line(vertices_count - 2, vertices_count - 1);
}
if (!init_data.is_empty())
m_paint_contour.init_from(std::move(init_data));
}
void TriangleSelectorGUI::render_paint_contour()
{
auto* curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr)
curr_shader->stop_using();
auto* contour_shader = wxGetApp().get_shader("mm_contour");
if (contour_shader != nullptr) {
contour_shader->start_using();
glsafe(::glDepthFunc(GL_LEQUAL));
m_paint_contour.render();
glsafe(::glDepthFunc(GL_LESS));
contour_shader->stop_using();
}
if (curr_shader != nullptr)
curr_shader->start_using();
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
} // namespace Slic3r::GUI

View file

@ -3,7 +3,11 @@
#include "GLGizmoBase.hpp"
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/GLModel.hpp"
#else
#include "slic3r/GUI/3DScene.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/TriangleSelector.hpp"
@ -28,6 +32,7 @@ enum class PainterGizmoType {
MMU_SEGMENTATION
};
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
class GLPaintContour
{
public:
@ -63,6 +68,7 @@ public:
GLuint m_contour_VBO_id{0};
GLuint m_contour_EBO_id{0};
};
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
class TriangleSelectorGUI : public TriangleSelector {
public:
@ -75,13 +81,13 @@ public:
virtual void render(ImGuiWrapper *imgui);
void render() { this->render(nullptr); }
void request_update_render_data() { m_update_render_data = true; };
void request_update_render_data() { m_update_render_data = true; }
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void render_debug(ImGuiWrapper* imgui);
bool m_show_triangles{false};
bool m_show_invalid{false};
#endif
#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
protected:
bool m_update_render_data = false;
@ -91,13 +97,29 @@ protected:
private:
void update_render_data();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel m_iva_enforcers;
GLModel m_iva_blockers;
std::array<GLModel, 3> m_iva_seed_fills;
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
std::array<GLModel, 3> m_varrays;
#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
#else
GLIndexedVertexArray m_iva_enforcers;
GLIndexedVertexArray m_iva_blockers;
std::array<GLIndexedVertexArray, 3> m_iva_seed_fills;
std::array<GLIndexedVertexArray, 3> m_varrays;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
protected:
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLModel m_paint_contour;
void update_paint_contour();
void render_paint_contour();
#else
GLPaintContour m_paint_contour;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
};
@ -209,7 +231,11 @@ private:
const Camera& camera,
const std::vector<Transform3d>& trafo_matrices) const;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
static std::shared_ptr<GLModel> s_sphere;
#else
static std::shared_ptr<GLIndexedVertexArray> s_sphere;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool m_internal_stack_active = false;
bool m_schedule_update = false;

View file

@ -235,8 +235,8 @@ void GLGizmoRotate::render_circle() const
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(ScaleStepsCount);
init_data.reserve_indices(ScaleStepsCount);
// vertices + indices
for (unsigned short i = 0; i < ScaleStepsCount; ++i) {
@ -278,8 +278,8 @@ void GLGizmoRotate::render_scale() const
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(2 * ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2 * ScaleStepsCount);
init_data.reserve_indices(2 * ScaleStepsCount);
// vertices + indices
for (unsigned short i = 0; i < ScaleStepsCount; ++i) {
@ -337,8 +337,8 @@ void GLGizmoRotate::render_snap_radii() const
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(2 * ScaleStepsCount * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * ScaleStepsCount * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2 * ScaleStepsCount);
init_data.reserve_indices(2 * ScaleStepsCount);
// vertices + indices
for (unsigned short i = 0; i < ScaleStepsCount; ++i) {
@ -388,8 +388,8 @@ void GLGizmoRotate::render_reference_radius(const ColorRGBA& color, bool radius_
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f));
@ -429,8 +429,8 @@ void GLGizmoRotate::render_angle() const
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve((1 + AngleResolution) * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve((1 + AngleResolution) * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(1 + AngleResolution);
init_data.reserve_indices(1 + AngleResolution);
// vertices + indices
for (unsigned short i = 0; i <= AngleResolution; ++i) {
@ -466,8 +466,8 @@ void GLGizmoRotate::render_grabber_connection(const ColorRGBA& color, bool radiu
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f));

View file

@ -403,8 +403,8 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(2 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(2 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(2);
init_data.reserve_indices(2);
// vertices
init_data.add_vertex((Vec3f)m_grabbers[id_1].center.cast<float>());

View file

@ -213,15 +213,21 @@ void InstancesHider::render_cut() const
clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
glsafe(::glPushMatrix());
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
if (mv->is_model_part())
glsafe(::glColor3f(0.8f, 0.3f, 0.0f));
else {
const ColorRGBA color = color_from_model_volume(*mv);
glsafe(::glColor4fv(color.data()));
}
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glPushAttrib(GL_DEPTH_TEST));
glsafe(::glDisable(GL_DEPTH_TEST));
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv));
#else
clipper->render_cut();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
@ -417,8 +423,12 @@ void ObjectClipper::render_cut() const
clipper->set_transformation(trafo);
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
glsafe(::glPushMatrix());
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
#else
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
clipper->render_cut();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glPopMatrix());
++clipper_id;
@ -530,8 +540,12 @@ void SupportsClipper::render_cut() const
m_clipper->set_transformation(supports_trafo);
glsafe(::glPushMatrix());
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f });
#else
glsafe(::glColor3f(1.0f, 0.f, 0.37f));
m_clipper->render_cut();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glPopMatrix());
}

View file

@ -70,6 +70,9 @@ static const std::map<const wchar_t, std::string> font_icons = {
{ImGui::LegendColorChanges , "legend_colorchanges" },
{ImGui::LegendPausePrints , "legend_pauseprints" },
{ImGui::LegendCustomGCodes , "legend_customgcodes" },
#if ENABLE_SHOW_TOOLPATHS_COG
{ImGui::LegendCOG , "legend_cog" },
#endif // ENABLE_SHOW_TOOLPATHS_COG
{ImGui::LegendShells , "legend_shells" },
{ImGui::LegendToolMarker , "legend_toolmarker" },
#endif // ENABLE_LEGEND_TOOLBAR_ICONS

View file

@ -6,6 +6,9 @@
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Model.hpp"
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/GUI_App.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/Camera.hpp"
#include <GL/glew.h>
@ -66,13 +69,34 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void MeshClipper::render_cut(const ColorRGBA& color)
#else
void MeshClipper::render_cut()
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
if (! m_triangles_valid)
recalculate_triangles();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr)
curr_shader->stop_using();
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
m_model.set_color(color);
m_model.render();
shader->stop_using();
}
if (curr_shader != nullptr)
curr_shader->start_using();
#else
if (m_vertex_array.has_VBOs())
m_vertex_array.render();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
@ -161,6 +185,30 @@ void MeshClipper::recalculate_triangles()
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_model.reset();
GLModel::Geometry init_data;
const GLModel::Geometry::EIndexType index_type = (m_triangles2d.size() < 65536) ? GLModel::Geometry::EIndexType::USHORT : GLModel::Geometry::EIndexType::UINT;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, index_type };
init_data.reserve_vertices(m_triangles2d.size());
init_data.reserve_indices(m_triangles2d.size());
// vertices + indices
for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) {
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f)up.cast<float>());
const size_t idx = it - m_triangles2d.cbegin();
if (index_type == GLModel::Geometry::EIndexType::USHORT)
init_data.add_ushort_triangle((unsigned short)idx, (unsigned short)idx + 1, (unsigned short)idx + 2);
else
init_data.add_uint_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2);
}
if (!init_data.is_empty())
m_model.init_from(std::move(init_data));
#else
m_vertex_array.release_geometry();
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
@ -170,6 +218,7 @@ void MeshClipper::recalculate_triangles()
m_vertex_array.push_triangle(idx, idx+1, idx+2);
}
m_vertex_array.finalize_geometry(true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_triangles_valid = true;
}

View file

@ -6,7 +6,11 @@
#include "libslic3r/SLA/IndexedMesh.hpp"
#include "admesh/stl.h"
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/GLModel.hpp"
#else
#include "slic3r/GUI/3DScene.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include <cfloat>
@ -69,7 +73,8 @@ public:
// MeshClipper class cuts a mesh and is able to return a triangulated cut.
class MeshClipper {
class MeshClipper
{
public:
// Inform MeshClipper about which plane we want to use to cut the mesh
// This is supposed to be in world coordinates.
@ -92,7 +97,11 @@ public:
// Render the triangulated cut. Transformation matrices should
// be set in world coords.
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void render_cut(const ColorRGBA& color);
#else
void render_cut();
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
private:
void recalculate_triangles();
@ -103,7 +112,11 @@ private:
ClippingPlane m_plane;
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
std::vector<Vec2f> m_triangles2d;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel m_model;
#else
GLIndexedVertexArray m_vertex_array;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool m_triangles_valid = false;
};

View file

@ -95,9 +95,9 @@ wxButton* MsgDialog::get_button(wxWindowID btn_id){
void MsgDialog::apply_style(long style)
{
if (style & wxOK) add_button(wxID_OK, true);
if (style & wxYES) add_button(wxID_YES, true);
if (style & wxNO) add_button(wxID_NO);
if (style & wxCANCEL) add_button(wxID_CANCEL);
if (style & wxYES) add_button(wxID_YES, !(style & wxNO_DEFAULT));
if (style & wxNO) add_button(wxID_NO, (style & wxNO_DEFAULT));
if (style & wxCANCEL) add_button(wxID_CANCEL, (style & wxCANCEL_DEFAULT));
logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
style & wxICON_INFORMATION ? "info" :
@ -299,25 +299,12 @@ wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/)
for (size_t i = 0; i < in.size();) {
// Overwrite the character (space or newline) starting at ibreak?
bool overwrite = false;
#if wxUSE_UNICODE_WCHAR
// On Windows, most likely the internal representation of wxString is wide char.
size_t end = std::min(in.size(), i + line_len);
size_t ibreak = end;
for (size_t j = i; j < end; ++ j) {
if (bool newline = in[j] == '\n'; in[j] == ' ' || in[j] == '\t' || newline) {
ibreak = j;
overwrite = true;
if (newline)
break;
} else if (in[j] == '/' || in[j] == '\\')
ibreak = j + 1;
}
#else
// UTF8 representation of wxString.
// Where to break the line, index of character at the start of a UTF-8 sequence.
size_t ibreak = size_t(-1);
// Overwrite the character at ibreak (it is a whitespace) or not?
for (size_t cnt = 0, j = i; j < in.size();) {
size_t j = i;
for (size_t cnt = 0; j < in.size();) {
if (bool newline = in[j] == '\n'; in[j] == ' ' || in[j] == '\t' || newline) {
// Overwrite the whitespace.
ibreak = j ++;
@ -326,16 +313,23 @@ wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/)
break;
} else if (in[j] == '/') {
// Insert after the slash.
ibreak = ++ j;
ibreak = ++ j;
overwrite = false;
} else
j += get_utf8_sequence_length(in.c_str() + j, in.size() - j);
if (++ cnt == line_len) {
if (ibreak == size_t(-1))
ibreak = j;
if (ibreak == size_t(-1)) {
ibreak = j;
overwrite = false;
}
break;
}
}
#endif
if (j == in.size()) {
out.append(in.begin() + i, in.end());
break;
}
assert(ibreak != size_t(-1));
out.append(in.begin() + i, in.begin() + ibreak);
out.append('\n');
i = ibreak;

View file

@ -3539,8 +3539,45 @@ void Plater::priv::replace_with_stl()
}
}
#if ENABLE_RELOAD_FROM_DISK_REWORK
static std::vector<std::pair<int, int>> reloadable_volumes(const Model& model, const Selection& selection)
{
std::vector<std::pair<int, int>> ret;
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs) {
const GLVolume& v = *selection.get_volume(idx);
const int o_idx = v.object_idx();
if (0 <= o_idx && o_idx < int(model.objects.size())) {
const ModelObject* obj = model.objects[o_idx];
const int v_idx = v.volume_idx();
if (0 <= v_idx && v_idx < int(obj->volumes.size())) {
const ModelVolume* vol = obj->volumes[v_idx];
if (!vol->source.is_from_builtin_objects && !vol->source.input_file.empty() &&
!fs::path(vol->source.input_file).extension().string().empty())
ret.push_back({ o_idx, v_idx });
}
}
}
return ret;
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
void Plater::priv::reload_from_disk()
{
#if ENABLE_RELOAD_FROM_DISK_REWORK
// collect selected reloadable ModelVolumes
std::vector<std::pair<int, int>> selected_volumes = reloadable_volumes(model, get_selection());
// nothing to reload, return
if (selected_volumes.empty())
return;
std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair<int, int>& v1, const std::pair<int, int>& v2) {
return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second);
});
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair<int, int>& v1, const std::pair<int, int>& v2) {
return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end());
#else
Plater::TakeSnapshot snapshot(q, _L("Reload from disk"));
const Selection& selection = get_selection();
@ -3573,10 +3610,36 @@ void Plater::priv::reload_from_disk()
}
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
// collects paths of files to load
std::vector<fs::path> input_paths;
std::vector<fs::path> missing_input_paths;
#if ENABLE_RELOAD_FROM_DISK_REWORK
std::vector<std::pair<fs::path, fs::path>> replace_paths;
for (auto [obj_idx, vol_idx] : selected_volumes) {
const ModelObject* object = model.objects[obj_idx];
const ModelVolume* volume = object->volumes[vol_idx];
if (fs::exists(volume->source.input_file))
input_paths.push_back(volume->source.input_file);
else {
// searches the source in the same folder containing the object
bool found = false;
if (!object->input_file.empty()) {
fs::path object_path = fs::path(object->input_file).remove_filename();
if (!object_path.empty()) {
object_path /= fs::path(volume->source.input_file).filename();
if (fs::exists(object_path)) {
input_paths.push_back(object_path);
found = true;
}
}
}
if (!found)
missing_input_paths.push_back(volume->source.input_file);
}
}
#else
std::vector<fs::path> replace_paths;
for (const SelectedVolume& v : selected_volumes) {
const ModelObject* object = model.objects[v.object_idx];
@ -3606,6 +3669,7 @@ void Plater::priv::reload_from_disk()
else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects)
missing_input_paths.push_back(volume->name);
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
std::sort(missing_input_paths.begin(), missing_input_paths.end());
missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end());
@ -3649,7 +3713,11 @@ void Plater::priv::reload_from_disk()
//wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
MessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() == wxID_YES)
replace_paths.push_back(sel_filename_path);
#if ENABLE_RELOAD_FROM_DISK_REWORK
replace_paths.emplace_back(search, sel_filename_path);
#else
replace_paths.emplace_back(sel_filename_path);
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
missing_input_paths.pop_back();
}
}
@ -3660,6 +3728,10 @@ void Plater::priv::reload_from_disk()
std::sort(replace_paths.begin(), replace_paths.end());
replace_paths.erase(std::unique(replace_paths.begin(), replace_paths.end()), replace_paths.end());
#if ENABLE_RELOAD_FROM_DISK_REWORK
Plater::TakeSnapshot snapshot(q, _L("Reload from disk"));
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
std::vector<wxString> fail_list;
Busy busy(_L("Reload from:"), q->get_current_canvas3D()->get_wxglcanvas());
@ -3688,6 +3760,86 @@ void Plater::priv::reload_from_disk()
}
// update the selected volumes whose source is the current file
#if ENABLE_RELOAD_FROM_DISK_REWORK
for (auto [obj_idx, vol_idx] : selected_volumes) {
ModelObject* old_model_object = model.objects[obj_idx];
ModelVolume* old_volume = old_model_object->volumes[vol_idx];
bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD;
bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string());
if (has_source || has_name) {
int new_volume_idx = -1;
int new_object_idx = -1;
bool match_found = false;
// take idxs from the matching volume
if (has_source && old_volume->source.object_idx < int(new_model.objects.size())) {
const ModelObject* obj = new_model.objects[old_volume->source.object_idx];
if (old_volume->source.volume_idx < int(obj->volumes.size())) {
if (obj->volumes[old_volume->source.volume_idx]->name == old_volume->name) {
new_volume_idx = old_volume->source.volume_idx;
new_object_idx = old_volume->source.object_idx;
match_found = true;
}
}
}
if (!match_found && has_name) {
// take idxs from the 1st matching volume
for (size_t o = 0; o < new_model.objects.size(); ++o) {
ModelObject* obj = new_model.objects[o];
bool found = false;
for (size_t v = 0; v < obj->volumes.size(); ++v) {
if (obj->volumes[v]->name == old_volume->name) {
new_volume_idx = (int)v;
new_object_idx = (int)o;
found = true;
break;
}
}
if (found)
break;
}
}
if (new_object_idx < 0 || int(new_model.objects.size()) <= new_object_idx) {
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
continue;
}
ModelObject* new_model_object = new_model.objects[new_object_idx];
if (new_volume_idx < 0 || int(new_model_object->volumes.size()) <= new_volume_idx) {
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
continue;
}
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
ModelVolume* new_volume = old_model_object->volumes.back();
new_volume->set_new_unique_id();
new_volume->config.apply(old_volume->config);
new_volume->set_type(old_volume->type());
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) *
old_volume->get_transformation().get_matrix(true) *
old_volume->source.transform.get_matrix(true));
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
new_volume->source.object_idx = old_volume->source.object_idx;
new_volume->source.volume_idx = old_volume->source.volume_idx;
assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters);
if (old_volume->source.is_converted_from_inches)
new_volume->convert_from_imperial_units();
else if (old_volume->source.is_converted_from_meters)
new_volume->convert_from_meters();
std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
if (!sinking)
old_model_object->ensure_on_bed();
old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1");
sla::reproject_points_and_holes(old_model_object);
}
}
#else
for (const SelectedVolume& sel_v : selected_volumes) {
ModelObject* old_model_object = model.objects[sel_v.object_idx];
ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx];
@ -3764,10 +3916,19 @@ void Plater::priv::reload_from_disk()
sla::reproject_points_and_holes(old_model_object);
}
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
}
busy.reset();
#if ENABLE_RELOAD_FROM_DISK_REWORK
for (auto [src, dest] : replace_paths) {
for (auto [obj_idx, vol_idx] : selected_volumes) {
if (boost::algorithm::iequals(model.objects[obj_idx]->volumes[vol_idx]->source.input_file, src.string()))
replace_volume_with_stl(obj_idx, vol_idx, dest, "");
}
}
#else
for (size_t i = 0; i < replace_paths.size(); ++i) {
const auto& path = replace_paths[i].string();
for (const SelectedVolume& sel_v : selected_volumes) {
@ -3777,6 +3938,7 @@ void Plater::priv::reload_from_disk()
replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, "");
}
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
if (!fail_list.empty()) {
wxString message = _L("Unable to reload:") + "\n";
@ -4572,6 +4734,13 @@ bool Plater::priv::can_replace_with_stl() const
bool Plater::priv::can_reload_from_disk() const
{
#if ENABLE_RELOAD_FROM_DISK_REWORK
// collect selected reloadable ModelVolumes
std::vector<std::pair<int, int>> selected_volumes = reloadable_volumes(model, get_selection());
// nothing to reload, return
if (selected_volumes.empty())
return false;
#else
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
@ -4597,6 +4766,21 @@ bool Plater::priv::can_reload_from_disk() const
selected_volumes.push_back({ o_idx, v_idx });
}
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
#if ENABLE_RELOAD_FROM_DISK_REWORK
std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair<int, int>& v1, const std::pair<int, int>& v2) {
return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second);
});
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair<int, int>& v1, const std::pair<int, int>& v2) {
return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end());
// collects paths of files to load
std::vector<fs::path> paths;
for (auto [obj_idx, vol_idx] : selected_volumes) {
paths.push_back(model.objects[obj_idx]->volumes[vol_idx]->source.input_file);
}
#else
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
@ -4610,6 +4794,7 @@ bool Plater::priv::can_reload_from_disk() const
else if (!object->input_file.empty() && !volume->name.empty() && !volume->source.is_from_builtin_objects)
paths.push_back(volume->name);
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
std::sort(paths.begin(), paths.end());
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
@ -5375,17 +5560,22 @@ bool Plater::load_files(const wxArrayString& filenames)
if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
LoadType load_type = LoadType::Unknown;
if (!model().objects.empty()) {
if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") {
ProjectDropDialog dlg(filename);
if (dlg.ShowModal() == wxID_OK) {
int choice = dlg.get_action();
load_type = static_cast<LoadType>(choice);
wxGetApp().app_config->set("drop_project_action", std::to_string(choice));
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) ||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf")))
load_type = LoadType::OpenProject;
else {
if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") {
ProjectDropDialog dlg(filename);
if (dlg.ShowModal() == wxID_OK) {
int choice = dlg.get_action();
load_type = static_cast<LoadType>(choice);
wxGetApp().app_config->set("drop_project_action", std::to_string(choice));
}
}
else
load_type = static_cast<LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)));
}
else
load_type = static_cast<LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)));
}
else
load_type = LoadType::OpenProject;

View file

@ -790,10 +790,10 @@ void PreferencesDialog::create_settings_mode_widget()
}
std::string opt_key = "settings_layout_mode";
m_blinkers[opt_key] = new BlinkingBitmap(this);
m_blinkers[opt_key] = new BlinkingBitmap(parent);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
@ -810,13 +810,13 @@ void PreferencesDialog::create_settings_text_color_widget()
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
std::string opt_key = "text_colors";
m_blinkers[opt_key] = new BlinkingBitmap(this);
m_blinkers[opt_key] = new BlinkingBitmap(parent);
wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL);
ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2);
sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());

View file

@ -1427,10 +1427,10 @@ void Selection::render(float scale_factor)
return;
m_scale_factor = scale_factor;
// render cumulative bounding box of selected volumes
#if ENABLE_GLBEGIN_GLEND_REMOVAL
render_bounding_box(get_bounding_box(), ColorRGB::WHITE());
#else
// render cumulative bounding box of selected volumes
render_selected_volumes();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
render_synchronized_volumes();
@ -1442,6 +1442,14 @@ void Selection::render_center(bool gizmo_is_dragging)
if (!m_valid || is_empty())
return;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center();
glsafe(::glDisable(GL_DEPTH_TEST));
@ -1450,19 +1458,17 @@ void Selection::render_center(bool gizmo_is_dragging)
glsafe(::glTranslated(center.x(), center.y(), center.z()));
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
m_vbo_sphere.set_color(ColorRGBA::WHITE());
#else
m_vbo_sphere.set_color(-1, ColorRGBA::WHITE());
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
m_vbo_sphere.render();
glsafe(::glPopMatrix());
#if ENABLE_GLBEGIN_GLEND_REMOVAL
shader->stop_using();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
glsafe(::glPopMatrix());
}
#endif // ENABLE_RENDER_SELECTION_CENTER
@ -2037,8 +2043,8 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(48 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(48 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(48);
init_data.reserve_indices(48);
// vertices
init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z()));
@ -2346,8 +2352,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field)
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
// vertices
init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1));
@ -2368,8 +2374,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field)
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::EIndexType::USHORT };
init_data.vertices.reserve(4 * GLModel::Geometry::vertex_stride_floats(init_data.format));
init_data.indices.reserve(6 * GLModel::Geometry::index_stride_bytes(init_data.format));
init_data.reserve_vertices(4);
init_data.reserve_indices(6);
// vertices
init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2));

View file

@ -2324,6 +2324,7 @@ void TabPrinter::build_fff()
option = optgroup->get_option("thumbnails");
option.opt.full_width = true;
optgroup->append_single_option_line(option);
optgroup->append_single_option_line("thumbnails_format");
optgroup->append_single_option_line("silent_mode");
optgroup->append_single_option_line("remaining_times");
@ -2517,6 +2518,11 @@ void TabPrinter::build_sla()
optgroup->append_single_option_line("min_initial_exposure_time");
optgroup->append_single_option_line("max_initial_exposure_time");
optgroup = page->new_optgroup(L("Output"));
optgroup->append_single_option_line("sla_archive_format");
optgroup->append_single_option_line("sla_output_precision");
build_print_host_upload_group(page.get());
const int notes_field_height = 25; // 250
@ -4106,7 +4112,10 @@ wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent)
});
create_btn(&m_del_all_substitutions_btn, _L("Delete all"), "cross");
m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) {
if (MessageDialog(parent, _L("Are you sure you want to delete all substitutions?"), SLIC3R_APP_NAME, wxYES_NO | wxICON_QUESTION).
ShowModal() != wxID_YES)
return;
m_subst_manager.delete_all();
m_del_all_substitutions_btn->Hide();
});

View file

@ -656,6 +656,7 @@ void DiffViewCtrl::Clear()
{
model->Clear();
m_items_map.clear();
m_has_long_strings = false;
}
wxString DiffViewCtrl::get_short_string(wxString full_string)
@ -1030,7 +1031,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false)
{
const ConfigOptionDef& def = config.def()->options.at(opt_key);
const std::vector<std::string>& names = def.enum_labels;//ConfigOptionEnum<T>::get_enum_names();
const std::vector<std::string>& names = def.enum_labels.empty() ? def.enum_values : def.enum_labels;
int val = config.option(opt_key)->getInt();
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
@ -1523,8 +1524,8 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe)
topSizer->Add(m_top_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2 * border);
topSizer->Add(presets_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(m_show_all_presets, 0, wxEXPAND | wxALL, border);
topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border);
topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border);
topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border);
this->SetMinSize(wxSize(80 * em, 30 * em));
this->SetSizer(topSizer);
@ -1689,12 +1690,17 @@ void DiffPresetDialog::update_tree()
left_val, right_val, category_icon_map.at(option.category));
}
}
if (m_tree->has_long_strings())
bottom_info = _L("Some fields are too long to fit. Right mouse click reveals the full text.");
bool tree_was_shown = m_tree->IsShown();
m_tree->Show(show_tree);
if (!show_tree)
bool show_bottom_info = !show_tree || m_tree->has_long_strings();
if (show_bottom_info)
m_bottom_info_line->SetLabel(bottom_info);
m_bottom_info_line->Show(!show_tree);
m_bottom_info_line->Show(show_bottom_info);
if (tree_was_shown == m_tree->IsShown())
Layout();

View file

@ -375,8 +375,8 @@ void FontManager::create_texture(size_t index, const std::string &text, GLuint&
bb2.scale(scale);
tex_size.x = bb2.max.x() - bb2.min.x();
tex_size.y = bb2.max.y() - bb2.min.y();
sla::RasterBase::Resolution resolution(tex_size.x,tex_size.y);
sla::RasterBase::PixelDim dim(1/scale, 1/scale);
sla::Resolution resolution(tex_size.x,tex_size.y);
sla::PixelDim dim(1/scale, 1/scale);
const double no_gamma = 1.;
std::unique_ptr<sla::RasterBase> r =
sla::create_raster_grayscale_aa(resolution, dim, no_gamma);
@ -472,8 +472,8 @@ void FontManager::init_style_images(int max_width) {
for (Item &item : m_font_list) {
double scale = item.font_item.prop.size_in_mm;
StyleImage &image = *item.image;
sla::RasterBase::Resolution resolution(image.tex_size.x, image.tex_size.y);
sla::RasterBase::PixelDim dim(1 / scale, 1 / scale);
sla::Resolution resolution(image.tex_size.x, image.tex_size.y);
sla::PixelDim dim(1 / scale, 1 / scale);
double gamma = 1.;
std::unique_ptr<sla::RasterBase> r = sla::create_raster_grayscale_aa(resolution, dim, gamma);
size_t index = &item - &m_font_list.front();

View file

@ -21,6 +21,10 @@
#include <libslic3r/ObjectID.hpp>
#include <libslic3r/Utils.hpp>
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "slic3r/GUI/3DScene.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include <boost/foreach.hpp>
#ifndef NDEBUG

View file

@ -3,7 +3,7 @@
#define CATCH_CONFIG_EXTERNAL_INTERFACES
#define CATCH_CONFIG_MAIN
#define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole"
// #define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole"
#include <catch2/catch.hpp>
namespace Catch {

View file

@ -20,17 +20,17 @@
using namespace Slic3r;
static double area(const sla::RasterBase::PixelDim &pxd)
static double area(const sla::PixelDim &pxd)
{
return pxd.w_mm * pxd.h_mm;
}
static Slic3r::sla::RasterGrayscaleAA create_raster(
const sla::RasterBase::Resolution &res,
const sla::Resolution &res,
double disp_w = 100.,
double disp_h = 100.)
{
sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
sla::RasterBase::Trafo trafo;
@ -107,7 +107,7 @@ static void test_expolys(Rst && rst,
svg.Close();
double max_rel_err = 0.1;
sla::RasterBase::PixelDim pxd = rst.pixel_dimensions();
sla::PixelDim pxd = rst.pixel_dimensions();
double max_abs_err = area(pxd) * scaled(1.) * scaled(1.);
BoundingBox ref_bb;
@ -175,7 +175,7 @@ TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares
TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") {
sla::RasterBase::PixelDim pixdim{1, 1};
sla::PixelDim pixdim{1, 1};
// We need one additional row and column to detect edges
sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)};
@ -205,7 +205,7 @@ TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") {
TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") {
sla::RasterBase::PixelDim pixdim{1, 1};
sla::PixelDim pixdim{1, 1};
// We need one additional row and column to detect edges
sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)};
@ -321,7 +321,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) {
std::vector<ExPolygons> layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh));
sla::RasterBase::Resolution res{2560, 1440};
sla::Resolution res{2560, 1440};
double disp_w = 120.96;
double disp_h = 68.04;

View file

@ -9,9 +9,9 @@
using namespace Slic3r;
static sla::RasterGrayscaleAA create_raster(const sla::RasterBase::Resolution &res)
static sla::RasterGrayscaleAA create_raster(const sla::Resolution &res)
{
sla::RasterBase::PixelDim pixdim{1., 1.};
sla::PixelDim pixdim{1., 1.};
auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)});
sla::RasterBase::Trafo trafo;

View file

@ -159,8 +159,8 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") {
TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") {
// Default Prusa SL1 display parameters
sla::RasterBase::Resolution res{2560, 1440};
sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px};
sla::Resolution res{2560, 1440};
sla::PixelDim pixdim{120. / res.width_px, 68. / res.height_px};
sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.);
REQUIRE(raster.resolution().width_px == res.width_px);
@ -186,8 +186,8 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") {
TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") {
double disp_w = 120., disp_h = 68.;
sla::RasterBase::Resolution res{2560, 1440};
sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
sla::Resolution res{2560, 1440};
sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
double gamma = 1.;
sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma);

View file

@ -307,8 +307,8 @@ void check_validity(const TriangleMesh &input_mesh, int flags)
void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring)
{
double disp_w = 120., disp_h = 68.;
sla::RasterBase::Resolution res{2560, 1440};
sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
sla::Resolution res{2560, 1440};
sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
sla::RasterBase::Trafo trafo{o, mirroring};
@ -400,7 +400,7 @@ double raster_white_area(const sla::RasterGrayscaleAA &raster)
return a;
}
double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd)
double predict_error(const ExPolygon &p, const sla::PixelDim &pd)
{
auto lines = p.lines();
double pix_err = pixel_area(FullWhite, pd) / 2.;

View file

@ -175,7 +175,7 @@ void check_raster_transformations(sla::RasterBase::Orientation o,
ExPolygon square_with_hole(double v);
inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim)
inline double pixel_area(TPixel px, const sla::PixelDim &pxdim)
{
return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack);
}
@ -183,7 +183,7 @@ inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim)
double raster_white_area(const sla::RasterGrayscaleAA &raster);
long raster_pxsum(const sla::RasterGrayscaleAA &raster);
double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd);
double predict_error(const ExPolygon &p, const sla::PixelDim &pd);
sla::SupportPoints calc_support_pts(
const TriangleMesh & mesh,

View file

@ -3,7 +3,7 @@
set(SLIC3R_APP_NAME "PrusaSlicer")
set(SLIC3R_APP_KEY "PrusaSlicer")
set(SLIC3R_VERSION "2.4.1-beta1")
set(SLIC3R_VERSION "2.5.0-alpha0")
set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
set(SLIC3R_RC_VERSION "2,4,1,0")
set(SLIC3R_RC_VERSION_DOTS "2.4.1.0")
set(SLIC3R_RC_VERSION "2,5,0,0")
set(SLIC3R_RC_VERSION_DOTS "2.5.0.0")

View file

@ -208,5 +208,45 @@ if (MSVC)
else ()
set(PERL_PROVE "${PERL_BIN_PATH}/prove")
endif ()
add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PERL_LOCAL_LIB_DIR}/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/..)
set(PERL_ENV_VARS "")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT CMAKE_CROSSCOMPILING AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))
if (SLIC3R_ASAN OR SLIC3R_UBSAN)
set(PERL_ENV_VARS env)
endif ()
if (SLIC3R_ASAN)
# Find the location of libasan.so for passing it into LD_PRELOAD. It works with GCC and Clang on Linux.
# On Centos 7 calling "gcc -print-file-name=libasan.so" returns path to "ld script" instead of path to shared library.
set(_asan_compiled_bin ${CMAKE_CURRENT_BINARY_DIR}/detect_libasan)
set(_asan_source_file ${_asan_compiled_bin}.c)
# Compile and link simple C application with enabled address sanitizer.
file(WRITE ${_asan_source_file} "int main(){}")
include(GetPrerequisites)
execute_process(COMMAND ${CMAKE_C_COMPILER} ${_asan_source_file} -fsanitize=address -lasan -o ${_asan_compiled_bin})
# Extract from the compiled application absolute path of libasan.
get_prerequisites(${_asan_compiled_bin} _asan_shared_libraries_list 0 0 "" "")
list(FILTER _asan_shared_libraries_list INCLUDE REGEX libasan)
set(PERL_ENV_VARS ${PERL_ENV_VARS} "LD_PRELOAD=${_asan_shared_libraries_list}")
# Suppressed memory leak reports that come from Perl.
set(PERL_LEAK_SUPPRESSION_FILE ${CMAKE_CURRENT_BINARY_DIR}/leak_suppression.txt)
file(WRITE ${PERL_LEAK_SUPPRESSION_FILE}
"leak:Perl_safesysmalloc\n"
"leak:Perl_safesyscalloc\n"
"leak:Perl_safesysrealloc\n"
"leak:__newlocale\n")
# Suppress a few memory leak reports and disable informing about suppressions.
# Print reports about memory leaks but exit with zero exit code when any memory leaks is found to make unit tests pass.
set(PERL_ENV_VARS ${PERL_ENV_VARS} "LSAN_OPTIONS=suppressions=${PERL_LEAK_SUPPRESSION_FILE}:print_suppressions=0:exitcode=0")
endif ()
if (SLIC3R_UBSAN)
# Do not show full stacktrace for reports from UndefinedBehaviorSanitizer in Perl tests.
set(PERL_ENV_VARS ${PERL_ENV_VARS} "UBSAN_OPTIONS=print_stacktrace=0")
endif ()
endif ()
add_test (NAME xs COMMAND ${PERL_ENV_VARS} "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PERL_LOCAL_LIB_DIR}/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_test (NAME integration COMMAND ${PERL_ENV_VARS} "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/..)