Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2022-02-24 13:17:30 +01:00
commit cd32254190
126 changed files with 6807 additions and 1585 deletions
CMakeLists.txt
cmake/modules
deps/wxWidgets
resources
src

View file

@ -506,6 +506,12 @@ endif ()
# Find the Cereal serialization library
find_package(cereal REQUIRED)
add_library(libcereal INTERFACE)
if (NOT TARGET cereal::cereal)
target_link_libraries(libcereal INTERFACE cereal)
else()
target_link_libraries(libcereal INTERFACE cereal::cereal)
endif()
# l10n
set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization")

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

@ -12,8 +12,8 @@ endif()
prusaslicer_add_cmake_project(wxWidgets
# GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets"
# GIT_TAG tm_cross_compile #${_wx_git_tag}
URL https://github.com/prusa3d/wxWidgets/archive/73f029adfcc82fb3aa4b01220a013f716e57d110.zip
URL_HASH SHA256=c35fe0187db497b6a3f477e24ed5e307028657ff0c2554385810b6e7961ad2e4
URL https://github.com/prusa3d/wxWidgets/archive/489f6118256853cf5b299d595868641938566cdb.zip
URL_HASH SHA256=5b22d465377cedd8044bba69bea958b248953fd3628c1de4913a84d4e6f6175b
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG
CMAKE_ARGS
-DwxBUILD_PRECOMP=ON

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

@ -64,6 +64,13 @@ technology = FFF
family = PREDATOR
default_materials = Generic PLA @PREDATOR; Generic PETG @PREDATOR; Generic ABS @PREDATOR
[printer_model:PHOTON MONO X]
name = Photon Mono X
variants = default
technology = SLA
family = PHOTON MONO
default_materials = Generic Blue Resin @MONO 0.05
# All presets starting with asterisk, for example *common*, are intermediate and they will
# not make it into the user interface.
@ -1898,3 +1905,94 @@ default_print_profile = 0.24mm 0.8 nozzle DETAILED QUALITY @PREDATOR
#########################################
########## end printer presets ##########
#########################################
#########################################
########## SLA printer presets ##########
#########################################
[sla_print:*common print ANYCUBIC SLA*]
compatible_printers_condition = family=="PHOTON MONO"
layer_height = 0.05
output_filename_format = [input_filename_base].pwmx
pad_edge_radius = 0.5
pad_enable = 0
pad_max_merge_distance = 50
pad_wall_height = 0
pad_wall_thickness = 1
pad_wall_slope = 45
faded_layers = 8
slice_closing_radius = 0.005
support_base_diameter = 3
support_base_height = 1
support_critical_angle = 45
support_density_at_45 = 250
support_density_at_horizontal = 500
support_head_front_diameter = 0.4
support_head_penetration = 0.4
support_head_width = 3
support_max_bridge_length = 10
support_minimal_z = 0
support_object_elevation = 5
support_pillar_diameter = 1
support_pillar_connection_mode = zigzag
support_pillar_widening_factor = 0
supports_enable = 1
support_small_pillar_diameter_percent = 60%
[sla_print:0.05 Normal @ANYCUBIC]
inherits = *common print ANYCUBIC SLA*
layer_height = 0.05
########### Materials
[sla_material:*common ANYCUBIC SLA*]
compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/
compatible_prints_condition = layer_height == 0.05
exposure_time = 7
initial_exposure_time = 40
initial_layer_height = 0.05
material_correction = 1,1,1
material_notes = LIFT_DISTANCE=8.0\nLIFT_SPEED=2.5\nRETRACT_SPEED=3.0\nBOTTOM_LIFT_SPEED=2.0\nBOTTOM_LIFT_DISTANCE=9.0\nDELAY_BEFORE_EXPOSURE=0.5
[sla_material:*common 0.05 ANYCUBIC SLA*]
inherits = *common ANYCUBIC SLA*
[sla_material:Generic Blue Resin @MONO 0.05]
inherits = *common 0.05 ANYCUBIC SLA*
exposure_time = 2.5
initial_exposure_time = 40
material_type = Tough
material_vendor = Generic
material_colour = #6080EC
compatible_printers_condition = printer_notes=~/.*PHOTONMONOX.*/
########## Printers
[printer:Anycubic Photon Mono X]
printer_technology = SLA
printer_model = PHOTON MONO X
printer_variant = default
default_sla_material_profile = Generic Blue Resin @MONO 0.05
default_sla_print_profile = 0.05 Normal @ANYCUBIC
thumbnails = 224x168
sla_archive_format = pwmx
bed_shape = 1.48x1.02,193.48x1.02,193.48x121.02,1.48x121.02
display_height = 120
display_orientation = landscape
display_mirror_x = 1
display_mirror_y = 0
display_pixels_x = 3840
display_pixels_y = 2400
display_width = 192
max_print_height = 245
elefant_foot_compensation = 0.2
elefant_foot_min_width = 0.2
min_exposure_time = 1
max_exposure_time = 120
min_initial_exposure_time = 1
max_initial_exposure_time = 300
printer_correction = 1,1,1
gamma_correction = 1
area_fill = 45
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.'\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_PHOTONMONOX\n

Binary file not shown.

After

(image error) Size: 45 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)
@ -125,7 +126,7 @@ if (NOT WIN32 AND NOT APPLE)
set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer")
endif ()
target_link_libraries(PrusaSlicer libslic3r cereal)
target_link_libraries(PrusaSlicer libslic3r libcereal)
if (APPLE)
# add_compile_options(-stdlib=libc++)
# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)

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

@ -94,6 +94,12 @@ public:
// Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices.
bool all_paths_inside_vertices_and_normals_interleaved(const std::vector<float>& paths, const Eigen::AlignedBox<float, 3>& bbox, bool ignore_bottom = true) const;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
const std::pair<std::vector<Vec2d>, std::vector<Vec2d>>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; }
const std::pair<std::vector<Vec2d>, std::vector<Vec2d>>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; }
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
private:
// Source definition of the print bed geometry (PrintConfig::bed_shape)
std::vector<Vec2d> m_bed_shape;

View file

@ -94,10 +94,18 @@ set(SLIC3R_SOURCES
Format/objparser.hpp
Format/STL.cpp
Format/STL.hpp
Format/SLAArchive.hpp
Format/SLAArchive.cpp
Format/SL1.hpp
Format/SL1.cpp
Format/SL1_SVG.hpp
Format/SL1_SVG.cpp
Format/pwmx.hpp
Format/pwmx.cpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/Thumbnails.cpp
GCode/Thumbnails.hpp
GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp
GCode/FindReplace.cpp
@ -344,10 +352,13 @@ 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
cereal
libcereal
libigl
miniz
boost_libs
@ -362,6 +373,8 @@ target_link_libraries(libslic3r
${CMAKE_DL_LIBS}
PNG::PNG
ZLIB::ZLIB
JPEG::JPEG
qoi
)
if (TARGET OpenVDB::openvdb)

View file

@ -5,6 +5,7 @@
#include <utility>
#include <cstddef>
#include <iterator>
#include <algorithm>
#include "libslic3r/libslic3r.h"
@ -44,7 +45,8 @@ size_t max_concurrency(const EP &ep)
template<class EP, class It, class Fn, class = ExecutionPolicyOnly<EP>>
void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1)
{
AsTraits<EP>::for_each(ep, from, to, std::forward<Fn>(fn), granularity);
AsTraits<EP>::for_each(ep, from, to, std::forward<Fn>(fn),
std::max(granularity, size_t(1)));
}
// A reduce operation with the execution policy passed as argument.
@ -68,7 +70,7 @@ T reduce(const EP & ep,
return AsTraits<EP>::reduce(ep, from, to, init,
std::forward<MergeFn>(mergefn),
std::forward<AccessFn>(accessfn),
granularity);
std::max(granularity, size_t(1)));
}
// An overload of reduce method to be used with iterators as 'from' and 'to'
@ -87,7 +89,7 @@ T reduce(const EP &ep,
{
return reduce(
ep, from, to, init, std::forward<MergeFn>(mergefn),
[](const auto &i) { return i; }, granularity);
[](const auto &i) { return i; }, std::max(granularity, size_t(1)));
}
template<class EP,
@ -103,7 +105,8 @@ T accumulate(const EP & ep,
size_t granularity = 1)
{
return reduce(ep, from, to, init, std::plus<T>{},
std::forward<AccessFn>(accessfn), granularity);
std::forward<AccessFn>(accessfn),
std::max(granularity, size_t(1)));
}
@ -119,7 +122,7 @@ T accumulate(const EP &ep,
{
return reduce(
ep, from, to, init, std::plus<T>{}, [](const auto &i) { return i; },
granularity);
std::max(granularity, size_t(1)));
}
} // namespace execution_policy

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

@ -121,18 +121,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";
@ -816,6 +819,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;
@ -831,6 +848,7 @@ namespace Slic3r {
}
++object_idx;
}
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
// // fixes the min z of the model if negative
// model.adjust_min_z();
@ -2052,15 +2070,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);
}
@ -2981,7 +3003,7 @@ namespace Slic3r {
// 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();
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);
@ -3005,9 +3027,13 @@ namespace Slic3r {
}
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";
stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
else if (volume->source.is_converted_from_meters)
stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n";
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
@ -3110,6 +3136,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

@ -20,11 +20,14 @@
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/PNGReadWrite.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
#include <miniz.h>
namespace marchsq {
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
@ -446,8 +449,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 +471,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();
@ -482,10 +485,31 @@ sla::RasterEncoder SL1Archive::get_encoder() const
return sla::PNGRasterEncoder{};
}
void SL1Archive::export_print(Zipper& zipper,
const SLAPrint &print,
const std::string &prjname)
static void write_thumbnail(Zipper &zipper, const ThumbnailData &data)
{
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) {
zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) +
"x" + std::to_string(data.height) + ".png",
static_cast<const std::uint8_t *>(png_data),
png_size);
mz_free(png_data);
}
}
void SL1Archive::export_print(const std::string fname,
const SLAPrint &print,
const ThumbnailsList &thumbnails,
const std::string &prjname)
{
Zipper zipper{fname};
std::string project =
prjname.empty() ?
boost::filesystem::path(zipper.get_filename()).stem().string() :
@ -512,6 +536,12 @@ void SL1Archive::export_print(Zipper& zipper,
zipper.add_entry(imgname.c_str(), rst.data(), rst.size());
}
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);
zipper.finalize();
} catch(std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
// Rethrow the exception

View file

@ -3,8 +3,12 @@
#include <string>
#include "SLAArchive.hpp"
#include "libslic3r/Zipper.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/PrintConfig.hpp"
struct indexed_triangle_set;
namespace Slic3r {
@ -15,27 +19,19 @@ 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(const std::string fname,
const SLAPrint &print,
const ThumbnailsList &thumbnails,
const std::string &projectname = "") override;
};
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);

View file

@ -0,0 +1,227 @@
#include "SL1_SVG.hpp"
#include "SLA/RasterBase.hpp"
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
#include <limits>
#include <cstdint>
#include <algorithm>
#include <string_view>
using namespace std::literals;
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 += "<path d=\"M "sv;
buf += decimal_from(c.x(), intbuf);
buf += " "sv;
buf += decimal_from(c.y(), intbuf);
buf += " m"sv;
for (auto &p : poly) {
auto d = p - c;
if (d.squaredNorm() == 0) continue;
buf += " "sv;
buf += decimal_from(p.x() - c.x(), intbuf);
buf += " "sv;
buf += decimal_from(p.y() - c.y(), intbuf);
c = p;
}
buf += " z\""sv; // mark path as closed
buf += " />\n"sv;
}
} // 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 auto finish = "</svg>\n"sv;
data.reserve(m_svg.size() + std::size(finish));
std::copy(m_svg.begin(), m_svg.end(), std::back_inserter(data));
std::copy(finish.begin(), finish.end() - 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

@ -0,0 +1,74 @@
#include "SLAArchive.hpp"
#include "SL1.hpp"
#include "SL1_SVG.hpp"
#include "pwmx.hpp"
#include "libslic3r/libslic3r.h"
#include <string>
#include <map>
#include <memory>
#include <tuple>
namespace Slic3r {
using ArchiveFactory = std::function<std::unique_ptr<SLAArchive>(const SLAPrinterConfig&)>;
struct ArchiveEntry {
const char *ext;
ArchiveFactory factoryfn;
};
static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
{
"SL1",
{ "sl1", [] (const auto &cfg) { return std::make_unique<SL1Archive>(cfg); } }
},
{
"SL2",
{ "sl2", [] (const auto &cfg) { return std::make_unique<SL1_SVGArchive>(cfg); } }
},
{
"pwmx",
{ "pwmx", [] (const auto &cfg) { return std::make_unique<PwmxArchive>(cfg); } }
}
};
std::unique_ptr<SLAArchive>
SLAArchive::create(const std::string &archtype, const SLAPrinterConfig &cfg)
{
auto entry = REGISTERED_ARCHIVES.find(archtype);
if (entry != REGISTERED_ARCHIVES.end())
return entry->second.factoryfn(cfg);
return nullptr;
}
const std::vector<const char*>& SLAArchive::registered_archives()
{
static std::vector<const char*> archnames;
if (archnames.empty()) {
archnames.reserve(REGISTERED_ARCHIVES.size());
for (auto &[name, _] : REGISTERED_ARCHIVES)
archnames.emplace_back(name.c_str());
}
return archnames;
}
const char *SLAArchive::get_extension(const char *archtype)
{
static const char* DEFAULT_EXT = "zip";
auto entry = REGISTERED_ARCHIVES.find(archtype);
if (entry != REGISTERED_ARCHIVES.end())
return entry->second.ext;
return DEFAULT_EXT;
}
} // namespace Slic3r

View file

@ -0,0 +1,64 @@
#ifndef SLAARCHIVE_HPP
#define SLAARCHIVE_HPP
#include <vector>
#include "libslic3r/SLA/RasterBase.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
namespace Slic3r {
class SLAPrint;
class SLAPrinterConfig;
class SLAArchive {
protected:
std::vector<sla::EncodedRaster> m_layers;
virtual std::unique_ptr<sla::RasterBase> create_raster() const = 0;
virtual sla::RasterEncoder get_encoder() const = 0;
public:
virtual ~SLAArchive() = default;
// Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid);
template<class Fn, class CancelFn, class EP = ExecutionTBB>
void draw_layers(
size_t layer_num,
Fn && drawfn,
CancelFn cancelfn = []() { return false; },
const EP & ep = {})
{
m_layers.resize(layer_num);
execution::for_each(
ep, size_t(0), m_layers.size(),
[this, &drawfn, &cancelfn](size_t idx) {
if (cancelfn()) return;
sla::EncodedRaster &enc = m_layers[idx];
auto rst = create_raster();
drawfn(*rst, idx);
enc = rst->encode(get_encoder());
},
execution::max_concurrency(ep));
}
// Export the print into an archive using the provided filename.
virtual void export_print(const std::string fname,
const SLAPrint &print,
const ThumbnailsList &thumbnails,
const std::string &projectname = "") = 0;
// Factory method to create an archiver instance
static std::unique_ptr<SLAArchive> create(const std::string &archtype, const SLAPrinterConfig&);
// Get the names of currently known archiver implementations
static const std::vector<const char *> & registered_archives();
// Get the default file extension belonging to an archive format
static const char *get_extension(const char *archtype);
};
} // namespace Slic3r
#endif // SLAARCHIVE_HPP

View file

@ -0,0 +1,564 @@
#include "pwmx.hpp"
#include "GCode/ThumbnailData.hpp"
#include "SLA/RasterBase.hpp"
#include "libslic3r/SLAPrint.hpp"
#include <sstream>
#include <iostream>
#include <fstream>
#include <boost/algorithm/string/replace.hpp>
#include <boost/log/trivial.hpp>
#define TAG_INTRO "ANYCUBIC\0\0\0\0"
#define TAG_HEADER "HEADER\0\0\0\0\0\0"
#define TAG_PREVIEW "PREVIEW\0\0\0\0\0"
#define TAG_LAYERS "LAYERDEF\0\0\0\0"
#define CFG_LIFT_DISTANCE "LIFT_DISTANCE"
#define CFG_LIFT_SPEED "LIFT_SPEED"
#define CFG_RETRACT_SPEED "RETRACT_SPEED"
#define CFG_DELAY_BEFORE_EXPOSURE "DELAY_BEFORE_EXPOSURE"
#define CFG_BOTTOM_LIFT_SPEED "BOTTOM_LIFT_SPEED"
#define CFG_BOTTOM_LIFT_DISTANCE "BOTTOM_LIFT_DISTANCE"
#define PREV_W 224
#define PREV_H 168
#define PREV_DPI 42
#define LAYER_SIZE_ESTIMATE (32 * 1024)
namespace Slic3r {
static void pwx_get_pixel_span(const std::uint8_t* ptr, const std::uint8_t* end,
std::uint8_t& pixel, size_t& span_len)
{
size_t max_len;
span_len = 0;
pixel = (*ptr) & 0xF0;
// the maximum length of the span depends on the pixel color
max_len = (pixel == 0 || pixel == 0xF0) ? 0xFFF : 0xF;
while (ptr < end && span_len < max_len && ((*ptr) & 0xF0) == pixel) {
span_len++;
ptr++;
}
}
struct PWXRasterEncoder
{
sla::EncodedRaster operator()(const void *ptr,
size_t w,
size_t h,
size_t num_components)
{
std::vector<uint8_t> dst;
size_t span_len;
std::uint8_t pixel;
auto size = w * h * num_components;
dst.reserve(size);
const std::uint8_t *src = reinterpret_cast<const std::uint8_t *>(ptr);
const std::uint8_t *src_end = src + size;
while (src < src_end) {
pwx_get_pixel_span(src, src_end, pixel, span_len);
src += span_len;
// fully transparent of fully opaque pixel
if (pixel == 0 || pixel == 0xF0) {
pixel = pixel | (span_len >> 8);
std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst));
pixel = span_len & 0xFF;
std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst));
}
// antialiased pixel
else {
pixel = pixel | span_len;
std::copy(&pixel, (&pixel) + 1, std::back_inserter(dst));
}
}
return sla::EncodedRaster(std::move(dst), "pwx");
}
};
using ConfMap = std::map<std::string, std::string>;
typedef struct pwmx_format_intro
{
char tag[12];
std::uint32_t version; // value 1
std::uint32_t area_num; // unknown - usually 4
std::uint32_t header_data_offset;
std::float_t intro24; // unknown - usually 0
std::uint32_t preview_data_offset;
std::float_t intro32; // unknown
std::uint32_t layer_data_offset;
std::float_t intro40; // unknown
std::uint32_t image_data_offset;
} pwmx_format_intro;
typedef struct pwmx_format_header
{
char tag[12];
std::uint32_t payload_size;
std::float_t pixel_size_um;
std::float_t layer_height_mm;
std::float_t exposure_time_s;
std::float_t delay_before_exposure_s;
std::float_t bottom_exposure_time_s;
std::float_t bottom_layer_count;
std::float_t lift_distance_mm;
std::float_t lift_speed_mms;
std::float_t retract_speed_mms;
std::float_t volume_ml;
std::uint32_t antialiasing;
std::uint32_t res_x;
std::uint32_t res_y;
std::float_t weight_g;
std::float_t price;
std::uint32_t price_currency;
std::uint32_t per_layer_override; // ? unknown meaning ?
std::uint32_t print_time_s;
std::uint32_t transition_layer_count;
std::uint32_t unknown; // ? usually 0 ?
} pwmx_format_header;
typedef struct pwmx_format_preview
{
char tag[12];
std::uint32_t payload_size;
std::uint32_t preview_w;
std::uint32_t preview_dpi;
std::uint32_t preview_h;
// raw image data in BGR565 format
std::uint8_t pixels[PREV_W * PREV_H * 2];
} pwmx_format_preview;
typedef struct pwmx_format_layers_header
{
char tag[12];
std::uint32_t payload_size;
std::uint32_t layer_count;
} pwmx_format_layers_header;
typedef struct pwmx_format_layer
{
std::uint32_t image_offset;
std::uint32_t image_size;
std::float_t lift_distance_mm;
std::float_t lift_speed_mms;
std::float_t exposure_time_s;
std::float_t layer_height_mm;
std::float_t layer44; // unkown - usually 0
std::float_t layer48; // unkown - usually 0
} pwmx_format_layer;
typedef struct pwmx_format_misc
{
std::float_t bottom_layer_height_mm;
std::float_t bottom_lift_distance_mm;
std::float_t bottom_lift_speed_mms;
} pwmx_format_misc;
class PwmxFormatConfigDef : public ConfigDef
{
public:
PwmxFormatConfigDef()
{
add(CFG_LIFT_DISTANCE, coFloat);
add(CFG_LIFT_SPEED, coFloat);
add(CFG_RETRACT_SPEED, coFloat);
add(CFG_DELAY_BEFORE_EXPOSURE, coFloat);
add(CFG_BOTTOM_LIFT_DISTANCE, coFloat);
add(CFG_BOTTOM_LIFT_SPEED, coFloat);
}
};
class PwmxFormatDynamicConfig : public DynamicConfig
{
public:
PwmxFormatDynamicConfig(){};
const ConfigDef *def() const override { return &config_def; }
private:
PwmxFormatConfigDef config_def;
};
namespace {
std::float_t get_cfg_value_f(const DynamicConfig &cfg,
const std::string &key,
const std::float_t &def = 0.f)
{
if (cfg.has(key)) {
if (auto opt = cfg.option(key))
return opt->getFloat();
}
return def;
}
int get_cfg_value_i(const DynamicConfig &cfg,
const std::string &key,
const int &def = 0)
{
if (cfg.has(key)) {
if (auto opt = cfg.option(key))
return opt->getInt();
}
return def;
}
template<class T> void crop_value(T &val, T val_min, T val_max)
{
if (val < val_min) {
val = val_min;
} else if (val > val_max) {
val = val_max;
}
}
void fill_preview(pwmx_format_preview &p,
pwmx_format_misc &/*m*/,
const ThumbnailsList &thumbnails)
{
p.preview_w = PREV_W;
p.preview_h = PREV_H;
p.preview_dpi = PREV_DPI;
p.payload_size = sizeof(p) - sizeof(p.tag) - sizeof(p.payload_size);
std::memset(p.pixels, 0 , sizeof(p.pixels));
if (!thumbnails.empty()) {
std::uint32_t dst_index;
std::uint32_t i = 0;
size_t len;
size_t pixel_x = 0;
auto t = thumbnails[0]; //use the first thumbnail
len = t.pixels.size();
//sanity check
if (len != PREV_W * PREV_H * 4) {
printf("incorrect thumbnail size. expected %ix%i\n", PREV_W, PREV_H);
return;
}
// rearange pixels: they seem to be stored from bottom to top.
dst_index = (PREV_W * (PREV_H - 1) * 2);
while (i < len) {
std::uint32_t pixel;
std::uint32_t r = t.pixels[i++];
std::uint32_t g = t.pixels[i++];
std::uint32_t b = t.pixels[i++];
i++; // Alpha
// convert to BGRA565
pixel = ((b >> 3) << 11) | ((g >>2) << 5) | (r >> 3);
p.pixels[dst_index++] = pixel & 0xFF;
p.pixels[dst_index++] = (pixel >> 8) & 0xFF;
pixel_x++;
if (pixel_x == PREV_W) {
pixel_x = 0;
dst_index -= (PREV_W * 4);
}
}
}
}
void fill_header(pwmx_format_header &h,
pwmx_format_misc &m,
const SLAPrint &print,
std::uint32_t layer_count)
{
CNumericLocalesSetter locales_setter;
std::float_t bottle_weight_g;
std::float_t bottle_volume_ml;
std::float_t bottle_cost;
std::float_t material_density;
auto &cfg = print.full_print_config();
auto mat_opt = cfg.option("material_notes");
std::string mnotes = mat_opt? cfg.option("material_notes")->serialize() : "";
// create a config parser from the material notes
Slic3r::PwmxFormatDynamicConfig mat_cfg;
SLAPrintStatistics stats = print.print_statistics();
// sanitize the string config
boost::replace_all(mnotes, "\\n", "\n");
boost::replace_all(mnotes, "\\r", "\r");
mat_cfg.load_from_ini_string(mnotes,
ForwardCompatibilitySubstitutionRule::Enable);
h.layer_height_mm = get_cfg_value_f(cfg, "layer_height");
m.bottom_layer_height_mm = get_cfg_value_f(cfg, "initial_layer_height");
h.exposure_time_s = get_cfg_value_f(cfg, "exposure_time");
h.bottom_exposure_time_s = get_cfg_value_f(cfg, "initial_exposure_time");
h.bottom_layer_count = get_cfg_value_i(cfg, "faded_layers");
if (layer_count < h.bottom_layer_count) {
h.bottom_layer_count = layer_count;
}
h.res_x = get_cfg_value_i(cfg, "display_pixels_x");
h.res_y = get_cfg_value_i(cfg, "display_pixels_y");
bottle_weight_g = get_cfg_value_f(cfg, "bottle_weight") * 1000.0f;
bottle_volume_ml = get_cfg_value_f(cfg, "bottle_volume");
bottle_cost = get_cfg_value_f(cfg, "bottle_cost");
material_density = bottle_weight_g / bottle_volume_ml;
h.volume_ml = (stats.objects_used_material + stats.support_used_material) / 1000;
h.weight_g = h.volume_ml * material_density;
h.price = (h.volume_ml * bottle_cost) / bottle_volume_ml;
h.price_currency = '$';
h.antialiasing = 1;
h.per_layer_override = 0;
// TODO - expose these variables to the UI rather than using material notes
h.delay_before_exposure_s = get_cfg_value_f(mat_cfg, CFG_DELAY_BEFORE_EXPOSURE, 0.5f);
crop_value(h.delay_before_exposure_s, 0.0f, 1000.0f);
h.lift_distance_mm = get_cfg_value_f(mat_cfg, CFG_LIFT_DISTANCE, 8.0f);
crop_value(h.lift_distance_mm, 0.0f, 100.0f);
if (mat_cfg.has(CFG_BOTTOM_LIFT_DISTANCE)) {
m.bottom_lift_distance_mm = get_cfg_value_f(mat_cfg,
CFG_BOTTOM_LIFT_DISTANCE,
8.0f);
crop_value(h.lift_distance_mm, 0.0f, 100.0f);
} else {
m.bottom_lift_distance_mm = h.lift_distance_mm;
}
h.lift_speed_mms = get_cfg_value_f(mat_cfg, CFG_LIFT_SPEED, 2.0f);
crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f);
if (mat_cfg.has(CFG_BOTTOM_LIFT_SPEED)) {
m.bottom_lift_speed_mms = get_cfg_value_f(mat_cfg, CFG_BOTTOM_LIFT_SPEED, 2.0f);
crop_value(m.bottom_lift_speed_mms, 0.1f, 20.0f);
} else {
m.bottom_lift_speed_mms = h.lift_speed_mms;
}
h.retract_speed_mms = get_cfg_value_f(mat_cfg, CFG_RETRACT_SPEED, 3.0f);
crop_value(h.lift_speed_mms, 0.1f, 20.0f);
h.print_time_s = (h.bottom_layer_count * h.bottom_exposure_time_s) +
((layer_count - h.bottom_layer_count) *
h.exposure_time_s) +
(layer_count * h.lift_distance_mm / h.retract_speed_mms) +
(layer_count * h.lift_distance_mm / h.lift_speed_mms) +
(layer_count * h.delay_before_exposure_s);
h.payload_size = sizeof(h) - sizeof(h.tag) - sizeof(h.payload_size);
h.pixel_size_um = 50;
}
} // namespace
std::unique_ptr<sla::RasterBase> PwmxArchive::create_raster() const
{
sla::Resolution res;
sla::PixelDim pxdim;
std::array<bool, 2> mirror;
double w = m_cfg.display_width.getFloat();
double h = m_cfg.display_height.getFloat();
auto pw = size_t(m_cfg.display_pixels_x.getInt());
auto ph = size_t(m_cfg.display_pixels_y.getInt());
mirror[X] = m_cfg.display_mirror_x.getBool();
mirror[Y] = m_cfg.display_mirror_y.getBool();
auto ro = m_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(pw, 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();
return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr);
}
sla::RasterEncoder PwmxArchive::get_encoder() const
{
return PWXRasterEncoder{};
}
// Endian safe write of little endian 32bit ints
static void pwmx_write_int32(std::ofstream &out, std::uint32_t val)
{
const char i1 = (val & 0xFF);
const char i2 = (val >> 8) & 0xFF;
const char i3 = (val >> 16) & 0xFF;
const char i4 = (val >> 24) & 0xFF;
out.write((const char *) &i1, 1);
out.write((const char *) &i2, 1);
out.write((const char *) &i3, 1);
out.write((const char *) &i4, 1);
}
static void pwmx_write_float(std::ofstream &out, std::float_t val)
{
std::uint32_t *f = (std::uint32_t *) &val;
pwmx_write_int32(out, *f);
}
static void pwmx_write_intro(std::ofstream &out, pwmx_format_intro &i)
{
out.write(TAG_INTRO, sizeof(i.tag));
pwmx_write_int32(out, i.version);
pwmx_write_int32(out, i.area_num);
pwmx_write_int32(out, i.header_data_offset);
pwmx_write_int32(out, i.intro24);
pwmx_write_int32(out, i.preview_data_offset);
pwmx_write_int32(out, i.intro32);
pwmx_write_int32(out, i.layer_data_offset);
pwmx_write_int32(out, i.intro40);
pwmx_write_int32(out, i.image_data_offset);
}
static void pwmx_write_header(std::ofstream &out, pwmx_format_header &h)
{
out.write(TAG_HEADER, sizeof(h.tag));
pwmx_write_int32(out, h.payload_size);
pwmx_write_float(out, h.pixel_size_um);
pwmx_write_float(out, h.layer_height_mm);
pwmx_write_float(out, h.exposure_time_s);
pwmx_write_float(out, h.delay_before_exposure_s);
pwmx_write_float(out, h.bottom_exposure_time_s);
pwmx_write_float(out, h.bottom_layer_count);
pwmx_write_float(out, h.lift_distance_mm);
pwmx_write_float(out, h.lift_speed_mms);
pwmx_write_float(out, h.retract_speed_mms);
pwmx_write_float(out, h.volume_ml);
pwmx_write_int32(out, h.antialiasing);
pwmx_write_int32(out, h.res_x);
pwmx_write_int32(out, h.res_y);
pwmx_write_float(out, h.weight_g);
pwmx_write_float(out, h.price);
pwmx_write_int32(out, h.price_currency);
pwmx_write_int32(out, h.per_layer_override);
pwmx_write_int32(out, h.print_time_s);
pwmx_write_int32(out, h.transition_layer_count);
pwmx_write_int32(out, h.unknown);
}
static void pwmx_write_preview(std::ofstream &out, pwmx_format_preview &p)
{
out.write(TAG_PREVIEW, sizeof(p.tag));
pwmx_write_int32(out, p.payload_size);
pwmx_write_int32(out, p.preview_w);
pwmx_write_int32(out, p.preview_dpi);
pwmx_write_int32(out, p.preview_h);
out.write((const char*) p.pixels, sizeof(p.pixels));
}
static void pwmx_write_layers_header(std::ofstream &out, pwmx_format_layers_header &h)
{
out.write(TAG_LAYERS, sizeof(h.tag));
pwmx_write_int32(out, h.payload_size);
pwmx_write_int32(out, h.layer_count);
}
static void pwmx_write_layer(std::ofstream &out, pwmx_format_layer &l)
{
pwmx_write_int32(out, l.image_offset);
pwmx_write_int32(out, l.image_size);
pwmx_write_float(out, l.lift_distance_mm);
pwmx_write_float(out, l.lift_speed_mms);
pwmx_write_float(out, l.exposure_time_s);
pwmx_write_float(out, l.layer_height_mm);
pwmx_write_float(out, l.layer44);
pwmx_write_float(out, l.layer48);
}
void PwmxArchive::export_print(const std::string fname,
const SLAPrint &print,
const ThumbnailsList &thumbnails,
const std::string &/*projectname*/)
{
std::uint32_t layer_count = m_layers.size();
pwmx_format_intro intro = {};
pwmx_format_header header = {};
pwmx_format_preview preview = {};
pwmx_format_layers_header layers_header = {};
pwmx_format_misc misc = {};
std::vector<uint8_t> layer_images;
std::uint32_t image_offset;
intro.version = 1;
intro.area_num = 4;
intro.header_data_offset = sizeof(intro);
intro.preview_data_offset = sizeof(intro) + sizeof(header);
intro.layer_data_offset = intro.preview_data_offset + sizeof(preview);
intro.image_data_offset = intro.layer_data_offset +
sizeof(layers_header) +
(sizeof(pwmx_format_layer) * layer_count);
fill_header(header, misc, print, layer_count);
fill_preview(preview, misc, thumbnails);
try {
// open the file and write the contents
std::ofstream out;
out.open(fname, std::ios::binary | std::ios::out | std::ios::trunc);
pwmx_write_intro(out, intro);
pwmx_write_header(out, header);
pwmx_write_preview(out, preview);
layers_header.payload_size = intro.image_data_offset - intro.layer_data_offset -
sizeof(layers_header.tag) - sizeof(layers_header.payload_size);
layers_header.layer_count = layer_count;
pwmx_write_layers_header(out, layers_header);
//layers
layer_images.reserve(layer_count * LAYER_SIZE_ESTIMATE);
image_offset = intro.image_data_offset;
size_t i = 0;
for (const sla::EncodedRaster &rst : m_layers) {
pwmx_format_layer l;
std::memset(&l, 0, sizeof(l));
l.image_offset = image_offset;
l.image_size = rst.size();
if (i < header.bottom_layer_count) {
l.exposure_time_s = header.bottom_exposure_time_s;
l.layer_height_mm = misc.bottom_layer_height_mm;
l.lift_distance_mm = misc.bottom_lift_distance_mm;
l.lift_speed_mms = misc.bottom_lift_speed_mms;
} else {
l.exposure_time_s = header.exposure_time_s;
l.layer_height_mm = header.layer_height_mm;
l.lift_distance_mm = header.lift_distance_mm;
l.lift_speed_mms = header.lift_speed_mms;
}
image_offset += l.image_size;
pwmx_write_layer(out, l);
// add the rle encoded layer image into the buffer
const char* img_start = reinterpret_cast<const char*>(rst.data());
const char* img_end = img_start + rst.size();
std::copy(img_start, img_end, std::back_inserter(layer_images));
i++;
}
const char* img_buffer = reinterpret_cast<const char*>(layer_images.data());
out.write(img_buffer, layer_images.size());
out.close();
} catch(std::exception& e) {
BOOST_LOG_TRIVIAL(error) << e.what();
// Rethrow the exception
throw;
}
}
} // namespace Slic3r

View file

@ -0,0 +1,37 @@
#ifndef _SLIC3R_FORMAT_PWMX_HPP_
#define _SLIC3R_FORMAT_PWMX_HPP_
#include <string>
#include "SLAArchive.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
class PwmxArchive: public SLAArchive {
SLAPrinterConfig m_cfg;
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:
PwmxArchive() = default;
explicit PwmxArchive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
explicit PwmxArchive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
void export_print(const std::string fname,
const SLAPrint &print,
const ThumbnailsList &thumbnails,
const std::string &projectname = "") override;
};
} // namespace Slic3r::sla
#endif // _SLIC3R_FORMAT_PWMX_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;
}
@ -287,7 +274,6 @@ namespace Slic3r {
// Otherwise, leave control to the user completely.
std::string toolchange_gcode_str;
const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
// m_max_layer_z = std::max(m_max_layer_z, tcr.print_z);
if (! toolchange_gcode.empty()) {
DynamicConfig config;
int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
@ -296,7 +282,7 @@ namespace Slic3r {
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
// config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
check_add_eol(toolchange_gcode_str);
}
@ -318,6 +304,9 @@ namespace Slic3r {
if (!start_filament_gcode.empty()) {
// Process the start_filament_gcode for the active filament only.
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(gcodegen.writer().get_position()(2) - gcodegen.m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
check_add_eol(start_filament_gcode_str);
@ -937,49 +926,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 +1109,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)
{
@ -1323,15 +1276,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Write the custom start G-code
file.writeln(start_gcode);
// Process filament-specific gcode.
/* if (has_wipe_tower) {
// Wipe tower will control the extruder switching, it will call the start_filament_gcode.
} else {
DynamicConfig config;
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id)));
file.writeln(this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config));
}
*/
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
print.throw_if_canceled();
@ -1948,6 +1892,8 @@ namespace ProcessLayer
// && !MMU1
) {
//! FIXME_in_fw show message during print pause
// FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"? Why is that not
// passed to color_change_gcode below?
DynamicConfig cfg;
cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer));
gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg);
@ -2158,10 +2104,10 @@ GCode::LayerResult GCode::process_layer(
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
gcode += this->placeholder_parser_process("layer_gcode",
print.config().layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
}
if (! first_layer && ! m_second_layer_things_done) {
@ -3047,13 +2993,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)
@ -3193,7 +3141,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
if (! start_filament_gcode.empty()) {
// Process the start_filament_gcode for the filament.
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
DynamicConfig config;
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
check_add_eol(gcode);
}
gcode += m_writer.toolchange(extruder_id);
@ -3262,7 +3212,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
if (! start_filament_gcode.empty()) {
// Process the start_filament_gcode for the new filament.
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
DynamicConfig config;
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
check_add_eol(gcode);
}
// Set the new extruder to the operating temperature.
@ -3276,7 +3228,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

@ -982,7 +982,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
if (w < 0.) {
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
BOOST_LOG_TRIVIAL(warning) << "Pinhead width is negative!";
w = 0.;
}

View file

@ -1,6 +1,10 @@
#include "SLAPrint.hpp"
#include "SLAPrintSteps.hpp"
#include "Format/SL1.hpp"
#include "Format/SL1_SVG.hpp"
#include "Format/pwmx.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
@ -240,8 +244,9 @@ 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())
m_archiver = SLAArchive::create(m_printer_config.sla_archive_format.value.c_str(), m_printer_config);
struct ModelObjectStatus {
enum Status {
@ -670,12 +675,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 +834,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

@ -9,6 +9,8 @@
#include "Point.hpp"
#include "MTUtils.hpp"
#include "Zipper.hpp"
#include "Format/SLAArchive.hpp"
#include "GCode/ThumbnailData.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
@ -389,41 +391,6 @@ struct SLAPrintStatistics
}
};
class SLAArchive {
protected:
std::vector<sla::EncodedRaster> m_layers;
virtual std::unique_ptr<sla::RasterBase> create_raster() const = 0;
virtual sla::RasterEncoder get_encoder() const = 0;
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(
size_t layer_num,
Fn && drawfn,
CancelFn cancelfn = []() { return false; },
const EP & ep = {})
{
m_layers.resize(layer_num);
execution::for_each(
ep, size_t(0), m_layers.size(),
[this, &drawfn, &cancelfn](size_t idx) {
if (cancelfn()) return;
sla::EncodedRaster &enc = m_layers[idx];
auto rst = create_raster();
drawfn(*rst, idx);
enc = rst->encode(get_encoder());
},
execution::max_concurrency(ep));
}
};
/**
* @brief This class is the high level FSM for the SLA printing process.
*
@ -527,8 +494,19 @@ 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(const std::string &fname, const std::string &projectname = "")
{
ThumbnailsList thumbnails; //empty thumbnail list
export_print(fname, thumbnails, projectname);
}
void export_print(const std::string &fname,
const ThumbnailsList &thumbnails,
const std::string &projectname = "")
{
m_archiver->export_print(fname, *this, thumbnails, projectname);
}
private:
@ -550,7 +528,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)
// Enable modified camera control and selection using mouse
#define ENABLE_NEW_CAMERA_MOVEMENTS_CTRL_ROTATE (1 && ENABLE_2_5_0_ALPHA1)
#define ENABLE_NEW_CAMERA_MOVEMENTS_MIDMOUSE_ROTATE (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

@ -270,7 +270,7 @@ endforeach()
encoding_check(libslic3r_gui)
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES})
target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES})
if (MSVC)
target_link_libraries(libslic3r_gui Setupapi.lib)

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,105 @@ 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;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2, GLModel::Geometry::index_type(triangles.size()) };
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 - min).cwiseProduct(inv_size).eval());
++vertices_counter;
if (vertices_counter % 3 == 0) {
if (init_data.format.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;
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::index_type(2 * gridlines.size()) };
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 (init_data.format.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 +429,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 +531,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 +636,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas)
shader->stop_using();
}
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
void Bed3D::render_model()
@ -541,6 +690,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 +756,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 +767,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

@ -1,10 +1,12 @@
#include <GL/glew.h>
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_SMOOTH_NORMALS
#include <igl/per_face_normals.h>
#include <igl/per_corner_normals.h>
#include <igl/per_vertex_normals.h>
#endif // ENABLE_SMOOTH_NORMALS
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "3DScene.hpp"
#include "GLShader.hpp"
@ -69,6 +71,7 @@ void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char
namespace Slic3r {
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_SMOOTH_NORMALS
static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& normals)
{
@ -287,6 +290,7 @@ void GLIndexedVertexArray::render(
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
const float GLVolume::SinkingContours::HalfWidth = 0.25f;
@ -326,8 +330,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 +404,9 @@ 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));
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Lines, GUI::GLModel::Geometry::EVertexLayout::P3, GUI::GLModel::Geometry::index_type(2 * edges.size()) };
init_data.reserve_vertices(2 * edges.size());
init_data.reserve_indices(2 * edges.size());
// vertices + indices
unsigned int vertices_count = 0;
@ -410,7 +414,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 (init_data.format.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
@ -480,7 +487,9 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, force_neutral_color(false)
, force_sinking_contours(false)
, tverts_range(0, size_t(-1))
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
, qverts_range(0, size_t(-1))
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
color = { r, g, b, a };
set_render_color(color);
@ -596,6 +605,36 @@ const BoundingBoxf3& GLVolume::transformed_non_sinking_bounding_box() const
return *m_transformed_non_sinking_bounding_box;
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GLVolume::set_range(double min_z, double max_z)
{
this->tverts_range.first = 0;
this->tverts_range.second = this->model.indices_count();
if (!this->print_zs.empty()) {
// The Z layer range is specified.
// First test whether the Z span of this object is not out of (min_z, max_z) completely.
if (this->print_zs.front() > max_z || this->print_zs.back() < min_z)
this->tverts_range.second = 0;
else {
// Then find the lowest layer to be displayed.
size_t i = 0;
for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++i);
if (i == this->print_zs.size())
// This shall not happen.
this->tverts_range.second = 0;
else {
// Remember start of the layer.
this->tverts_range.first = this->offsets[i];
// Some layers are above $min_z. Which?
for (; i < this->print_zs.size() && this->print_zs[i] <= max_z; ++i);
if (i < this->print_zs.size())
this->tverts_range.second = this->offsets[i];
}
}
}
}
#else
void GLVolume::set_range(double min_z, double max_z)
{
this->qverts_range.first = 0;
@ -608,7 +647,8 @@ void GLVolume::set_range(double min_z, double max_z)
if (this->print_zs.front() > max_z || this->print_zs.back() < min_z) {
this->qverts_range.second = 0;
this->tverts_range.second = 0;
} else {
}
else {
// Then find the lowest layer to be displayed.
size_t i = 0;
for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++ i);
@ -616,7 +656,8 @@ void GLVolume::set_range(double min_z, double max_z)
// This shall not happen.
this->qverts_range.second = 0;
this->tverts_range.second = 0;
} else {
}
else {
// Remember start of the layer.
this->qverts_range.first = this->offsets[i * 2];
this->tverts_range.first = this->offsets[i * 2 + 1];
@ -630,8 +671,9 @@ void GLVolume::set_range(double min_z, double max_z)
}
}
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GLVolume::render() const
void GLVolume::render()
{
if (!is_active)
return;
@ -642,7 +684,14 @@ void GLVolume::render() const
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(world_matrix().data()));
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
model.render();
else
model.render(this->tverts_range);
#else
this->indexed_vertex_array.render(this->tverts_range, this->qverts_range);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
glsafe(::glPopMatrix());
if (this->is_left_handed())
@ -677,46 +726,68 @@ void GLVolume::render_non_manifold_edges()
}
#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
std::vector<int> GLVolumeCollection::load_object(
const ModelObject* model_object,
int obj_idx,
const std::vector<int>& instance_idxs)
#else
std::vector<int> GLVolumeCollection::load_object(
const ModelObject *model_object,
int obj_idx,
const std::vector<int> &instance_idxs,
const std::string &color_by,
bool opengl_initialized)
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
std::vector<int> volumes_idx;
for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx)
for (int instance_idx : instance_idxs)
volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized));
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx));
#else
volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, opengl_initialized));
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
return volumes_idx;
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
int GLVolumeCollection::load_object_volume(
const ModelObject* model_object,
int obj_idx,
int volume_idx,
int instance_idx)
#else
int GLVolumeCollection::load_object_volume(
const ModelObject *model_object,
int obj_idx,
int volume_idx,
int instance_idx,
const std::string &color_by,
bool opengl_initialized)
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
const ModelVolume *model_volume = model_object->volumes[volume_idx];
const int extruder_id = model_volume->extruder_id();
const ModelInstance *instance = model_object->instances[instance_idx];
const TriangleMesh &mesh = model_volume->mesh();
ColorRGBA color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4];
color.a(model_volume->is_model_part() ? 1.0f : 0.5f);
this->volumes.emplace_back(new GLVolume(color));
this->volumes.emplace_back(new GLVolume());
GLVolume& v = *this->volumes.back();
v.set_color(color_from_model_volume(*model_volume));
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_SMOOTH_NORMALS
v.model.init_from(mesh, true);
#else
v.model.init_from(mesh);
#endif // ENABLE_SMOOTH_NORMALS
#else
#if ENABLE_SMOOTH_NORMALS
v.indexed_vertex_array.load_mesh(mesh, true);
#else
v.indexed_vertex_array.load_mesh(mesh);
#endif // ENABLE_SMOOTH_NORMALS
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
if (model_volume->is_model_part())
{
if (model_volume->is_model_part()) {
// GLVolume will reference a convex hull from model_volume!
v.set_convex_hull(model_volume->get_convex_hull_shared_ptr());
if (extruder_id != -1)
@ -733,6 +804,16 @@ int GLVolumeCollection::load_object_volume(
// Load SLA auxiliary GLVolumes (for support trees or pad).
// This function produces volumes for multiple instances in a single shot,
// as some object specific mesh conversions may be expensive.
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GLVolumeCollection::load_object_auxiliary(
const SLAPrintObject* print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>>& instances,
SLAPrintObjectStep milestone,
// Timestamp of the last change of the milestone
size_t timestamp)
#else
void GLVolumeCollection::load_object_auxiliary(
const SLAPrintObject *print_object,
int obj_idx,
@ -742,6 +823,7 @@ void GLVolumeCollection::load_object_auxiliary(
// Timestamp of the last change of the milestone
size_t timestamp,
bool opengl_initialized)
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
assert(print_object->is_step_done(milestone));
Transform3d mesh_trafo_inv = print_object->trafo().inverse();
@ -754,12 +836,21 @@ void GLVolumeCollection::load_object_auxiliary(
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
GLVolume& v = *this->volumes.back();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_SMOOTH_NORMALS
v.model.init_from(mesh, true);
#else
v.model.init_from(mesh);
#endif // ENABLE_SMOOTH_NORMALS
v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR);
#else
#if ENABLE_SMOOTH_NORMALS
v.indexed_vertex_array.load_mesh(mesh, true);
#else
v.indexed_vertex_array.load_mesh(mesh);
#endif // ENABLE_SMOOTH_NORMALS
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
@ -775,6 +866,17 @@ void GLVolumeCollection::load_object_auxiliary(
}
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height,
float rotation_angle, bool size_unknown, float brim_width)
#else
int GLVolumeCollection::load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height,
float rotation_angle, bool size_unknown, float brim_width)
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
#else
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height,
@ -784,6 +886,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height,
float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized)
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
if (depth < 0.01f)
return int(this->volumes.size() - 1);
@ -840,9 +943,16 @@ int GLVolumeCollection::load_wipe_tower_preview(
volumes.emplace_back(new GLVolume(color));
GLVolume& v = *volumes.back();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
v.model.init_from(mesh);
v.model.set_color(color);
#else
v.indexed_vertex_array.load_mesh(mesh);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
v.set_convex_hull(mesh.convex_hull_3d());
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle));
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
@ -857,6 +967,22 @@ int GLVolumeCollection::load_wipe_tower_preview(
return int(volumes.size() - 1);
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba)
{
GLVolume* out = new_nontoolpath_volume(rgba);
out->is_extrusion_path = true;
return out;
}
GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba)
{
GLVolume* out = new GLVolume(rgba);
out->is_extrusion_path = false;
this->volumes.emplace_back(out);
return out;
}
#else
GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats)
{
GLVolume *out = new_nontoolpath_volume(rgba, reserve_vbo_floats);
@ -873,6 +999,7 @@ GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba, size
this->volumes.emplace_back(out);
return out;
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func)
{
@ -961,7 +1088,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
shader->set_uniform("uniform_color", volume.first->render_color);
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
if (!volume.first->model.is_initialized())
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
shader->set_uniform("uniform_color", volume.first->render_color);
shader->set_uniform("z_range", m_z_range, 2);
shader->set_uniform("clipping_plane", m_clipping_plane, 4);
shader->set_uniform("print_volume.type", static_cast<int>(m_print_volume.type));
@ -981,6 +1111,9 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
#endif // ENABLE_ENVIRONMENT_MAP
glcheck();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
volume.first->model.set_color(volume.first->render_color);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
volume.first->render();
#if ENABLE_ENVIRONMENT_MAP
@ -1216,6 +1349,466 @@ std::string GLVolumeCollection::log_memory_info() const
return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")";
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
static void thick_lines_to_geometry(
const Lines& lines,
const std::vector<double>& widths,
const std::vector<double>& heights,
bool closed,
double top_z,
GUI::GLModel::Geometry& geometry)
{
assert(!lines.empty());
if (lines.empty())
return;
enum Direction : unsigned char
{
Left,
Right,
Top,
Bottom
};
// right, left, top, bottom
std::array<int, 4> idx_prev = { -1, -1, -1, -1 };
std::array<int, 4> idx_initial = { -1, -1, -1, -1 };
double bottom_z_prev = 0.0;
Vec2d b1_prev(Vec2d::Zero());
Vec2d v_prev(Vec2d::Zero());
double len_prev = 0.0;
double width_initial = 0.0;
double bottom_z_initial = 0.0;
// loop once more in case of closed loops
const size_t lines_end = closed ? (lines.size() + 1) : lines.size();
for (size_t ii = 0; ii < lines_end; ++ii) {
const size_t i = (ii == lines.size()) ? 0 : ii;
const Line& line = lines[i];
const double bottom_z = top_z - heights[i];
const double middle_z = 0.5 * (top_z + bottom_z);
const double width = widths[i];
const bool is_first = (ii == 0);
const bool is_last = (ii == lines_end - 1);
const bool is_closing = closed && is_last;
const Vec2d v = unscale(line.vector()).normalized();
const double len = unscale<double>(line.length());
const Vec2d a = unscale(line.a);
const Vec2d b = unscale(line.b);
Vec2d a1 = a;
Vec2d a2 = a;
Vec2d b1 = b;
Vec2d b2 = b;
{
const double dist = 0.5 * width; // scaled
const double dx = dist * v.x();
const double dy = dist * v.y();
a1 += Vec2d(+dy, -dx);
a2 += Vec2d(-dy, +dx);
b1 += Vec2d(+dy, -dx);
b2 += Vec2d(-dy, +dx);
}
// calculate new XY normals
const Vec2d xy_right_normal = unscale(line.normal()).normalized();
std::array<int, 4> idx_a = { 0, 0, 0, 0 };
std::array<int, 4> idx_b = { 0, 0, 0, 0 };
int idx_last = int(geometry.vertices_count());
const bool bottom_z_different = bottom_z_prev != bottom_z;
bottom_z_prev = bottom_z;
if (!is_first && bottom_z_different) {
// Found a change of the layer thickness -> Add a cap at the end of the previous segment.
geometry.add_uint_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]);
geometry.add_uint_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]);
}
// Share top / bottom vertices if possible.
if (is_first) {
idx_a[Top] = idx_last++;
geometry.add_vertex(Vec3f(a.x(), a.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f));
}
else
idx_a[Top] = idx_prev[Top];
if (is_first || bottom_z_different) {
// Start of the 1st line segment or a change of the layer thickness while maintaining the print_z.
idx_a[Bottom] = idx_last++;
geometry.add_vertex(Vec3f(a.x(), a.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f));
idx_a[Left] = idx_last++;
geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f));
idx_a[Right] = idx_last++;
geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f));
}
else
idx_a[Bottom] = idx_prev[Bottom];
if (is_first) {
// Start of the 1st line segment.
width_initial = width;
bottom_z_initial = bottom_z;
idx_initial = idx_a;
}
else {
// Continuing a previous segment.
// Share left / right vertices if possible.
const double v_dot = v_prev.dot(v);
// To reduce gpu memory usage, we try to reuse vertices
// To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges
// is longer than a fixed threshold.
// The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts
const double len_threshold = 2.5;
// Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met
const bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold);
if (sharp) {
if (!bottom_z_different) {
// Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
idx_a[Right] = idx_last++;
geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f));
idx_a[Left] = idx_last++;
geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f));
if (cross2(v_prev, v) > 0.0) {
// Right turn. Fill in the right turn wedge.
geometry.add_uint_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]);
geometry.add_uint_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]);
}
else {
// Left turn. Fill in the left turn wedge.
geometry.add_uint_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]);
geometry.add_uint_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]);
}
}
}
else {
if (!bottom_z_different) {
// The two successive segments are nearly collinear.
idx_a[Left] = idx_prev[Left];
idx_a[Right] = idx_prev[Right];
}
}
if (is_closing) {
if (!sharp) {
if (!bottom_z_different) {
// Closing a loop with smooth transition. Unify the closing left / right vertices.
geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left]));
geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right]));
geometry.remove_vertex(geometry.vertices_count() - 1);
geometry.remove_vertex(geometry.vertices_count() - 1);
// Replace the left / right vertex indices to point to the start of the loop.
const size_t indices_count = geometry.indices_count();
for (size_t u = indices_count - 24; u < indices_count; ++u) {
const unsigned int id = geometry.extract_uint_index(u);
if (id == (unsigned int)idx_prev[Left])
geometry.set_uint_index(u, (unsigned int)idx_initial[Left]);
else if (id == (unsigned int)idx_prev[Right])
geometry.set_uint_index(u, (unsigned int)idx_initial[Right]);
}
}
}
// This is the last iteration, only required to solve the transition.
break;
}
}
// Only new allocate top / bottom vertices, if not closing a loop.
if (is_closing)
idx_b[Top] = idx_initial[Top];
else {
idx_b[Top] = idx_last++;
geometry.add_vertex(Vec3f(b.x(), b.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f));
}
if (is_closing && width == width_initial && bottom_z == bottom_z_initial)
idx_b[Bottom] = idx_initial[Bottom];
else {
idx_b[Bottom] = idx_last++;
geometry.add_vertex(Vec3f(b.x(), b.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f));
}
// Generate new vertices for the end of this line segment.
idx_b[Left] = idx_last++;
geometry.add_vertex(Vec3f(b2.x(), b2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f));
idx_b[Right] = idx_last++;
geometry.add_vertex(Vec3f(b1.x(), b1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f));
idx_prev = idx_b;
bottom_z_prev = bottom_z;
b1_prev = b1;
v_prev = v;
len_prev = len;
if (bottom_z_different && (closed || (!is_first && !is_last))) {
// Found a change of the layer thickness -> Add a cap at the beginning of this segment.
geometry.add_uint_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]);
geometry.add_uint_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]);
}
if (!closed) {
// Terminate open paths with caps.
if (is_first) {
geometry.add_uint_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]);
geometry.add_uint_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]);
}
// We don't use 'else' because both cases are true if we have only one line.
if (is_last) {
geometry.add_uint_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]);
geometry.add_uint_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]);
}
}
// Add quads for a straight hollow tube-like segment.
// bottom-right face
geometry.add_uint_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]);
geometry.add_uint_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]);
// top-right face
geometry.add_uint_triangle(idx_a[Right], idx_b[Right], idx_b[Top]);
geometry.add_uint_triangle(idx_a[Right], idx_b[Top], idx_a[Top]);
// top-left face
geometry.add_uint_triangle(idx_a[Top], idx_b[Top], idx_b[Left]);
geometry.add_uint_triangle(idx_a[Top], idx_b[Left], idx_a[Left]);
// bottom-left face
geometry.add_uint_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]);
geometry.add_uint_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]);
}
}
// caller is responsible for supplying NO lines with zero length
static void thick_lines_to_geometry(
const Lines3& lines,
const std::vector<double>& widths,
const std::vector<double>& heights,
bool closed,
GUI::GLModel::Geometry& geometry)
{
assert(!lines.empty());
if (lines.empty())
return;
enum Direction : unsigned char
{
Left,
Right,
Top,
Bottom
};
// left, right, top, bottom
std::array<int, 4> idx_prev = { -1, -1, -1, -1 };
std::array<int, 4> idx_initial = { -1, -1, -1, -1 };
double z_prev = 0.0;
double len_prev = 0.0;
Vec3d n_right_prev = Vec3d::Zero();
Vec3d n_top_prev = Vec3d::Zero();
Vec3d unit_v_prev = Vec3d::Zero();
double width_initial = 0.0;
// new vertices around the line endpoints
// left, right, top, bottom
std::array<Vec3d, 4> a = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
std::array<Vec3d, 4> b = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
// loop once more in case of closed loops
const size_t lines_end = closed ? (lines.size() + 1) : lines.size();
for (size_t ii = 0; ii < lines_end; ++ii) {
const size_t i = (ii == lines.size()) ? 0 : ii;
const Line3& line = lines[i];
const double height = heights[i];
const double width = widths[i];
const Vec3d unit_v = unscale(line.vector()).normalized();
const double len = unscale<double>(line.length());
Vec3d n_top = Vec3d::Zero();
Vec3d n_right = Vec3d::Zero();
if (line.a.x() == line.b.x() && line.a.y() == line.b.y()) {
// vertical segment
n_top = Vec3d::UnitY();
n_right = Vec3d::UnitX();
if (line.a.z() < line.b.z())
n_right = -n_right;
}
else {
// horizontal segment
n_right = unit_v.cross(Vec3d::UnitZ()).normalized();
n_top = n_right.cross(unit_v).normalized();
}
const Vec3d rl_displacement = 0.5 * width * n_right;
const Vec3d tb_displacement = 0.5 * height * n_top;
const Vec3d l_a = unscale(line.a);
const Vec3d l_b = unscale(line.b);
a[Right] = l_a + rl_displacement;
a[Left] = l_a - rl_displacement;
a[Top] = l_a + tb_displacement;
a[Bottom] = l_a - tb_displacement;
b[Right] = l_b + rl_displacement;
b[Left] = l_b - rl_displacement;
b[Top] = l_b + tb_displacement;
b[Bottom] = l_b - tb_displacement;
const Vec3d n_bottom = -n_top;
const Vec3d n_left = -n_right;
std::array<int, 4> idx_a = { 0, 0, 0, 0};
std::array<int, 4> idx_b = { 0, 0, 0, 0 };
int idx_last = int(geometry.vertices_count());
const bool z_different = (z_prev != l_a.z());
z_prev = l_b.z();
// Share top / bottom vertices if possible.
if (ii == 0) {
idx_a[Top] = idx_last++;
geometry.add_vertex((Vec3f)a[Top].cast<float>(), (Vec3f)n_top.cast<float>());
}
else
idx_a[Top] = idx_prev[Top];
if (ii == 0 || z_different) {
// Start of the 1st line segment or a change of the layer thickness while maintaining the print_z.
idx_a[Bottom] = idx_last++;
geometry.add_vertex((Vec3f)a[Bottom].cast<float>(), (Vec3f)n_bottom.cast<float>());
idx_a[Left] = idx_last++;
geometry.add_vertex((Vec3f)a[Left].cast<float>(), (Vec3f)n_left.cast<float>());
idx_a[Right] = idx_last++;
geometry.add_vertex((Vec3f)a[Right].cast<float>(), (Vec3f)n_right.cast<float>());
}
else
idx_a[Bottom] = idx_prev[Bottom];
if (ii == 0) {
// Start of the 1st line segment.
width_initial = width;
idx_initial = idx_a;
}
else {
// Continuing a previous segment.
// Share left / right vertices if possible.
const double v_dot = unit_v_prev.dot(unit_v);
const bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0;
// To reduce gpu memory usage, we try to reuse vertices
// To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges
// is longer than a fixed threshold.
// The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts
const double len_threshold = 2.5;
// Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met
const bool is_sharp = v_dot < 0.707 || len_prev > len_threshold || len > len_threshold;
if (is_sharp) {
// Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
idx_a[Right] = idx_last++;
geometry.add_vertex((Vec3f)a[Right].cast<float>(), (Vec3f)n_right.cast<float>());
idx_a[Left] = idx_last++;
geometry.add_vertex((Vec3f)a[Left].cast<float>(), (Vec3f)n_left.cast<float>());
if (is_right_turn) {
// Right turn. Fill in the right turn wedge.
geometry.add_uint_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]);
geometry.add_uint_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]);
}
else {
// Left turn. Fill in the left turn wedge.
geometry.add_uint_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]);
geometry.add_uint_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]);
}
}
else {
// The two successive segments are nearly collinear.
idx_a[Left] = idx_prev[Left];
idx_a[Right] = idx_prev[Right];
}
if (ii == lines.size()) {
if (!is_sharp) {
// Closing a loop with smooth transition. Unify the closing left / right vertices.
geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left]));
geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right]));
geometry.remove_vertex(geometry.vertices_count() - 1);
geometry.remove_vertex(geometry.vertices_count() - 1);
// Replace the left / right vertex indices to point to the start of the loop.
const size_t indices_count = geometry.indices_count();
for (size_t u = indices_count - 24; u < indices_count; ++u) {
const unsigned int id = geometry.extract_uint_index(u);
if (id == (unsigned int)idx_prev[Left])
geometry.set_uint_index(u, (unsigned int)idx_initial[Left]);
else if (id == (unsigned int)idx_prev[Right])
geometry.set_uint_index(u, (unsigned int)idx_initial[Right]);
}
}
// This is the last iteration, only required to solve the transition.
break;
}
}
// Only new allocate top / bottom vertices, if not closing a loop.
if (closed && ii + 1 == lines.size())
idx_b[Top] = idx_initial[Top];
else {
idx_b[Top] = idx_last++;
geometry.add_vertex((Vec3f)b[Top].cast<float>(), (Vec3f)n_top.cast<float>());
}
if (closed && ii + 1 == lines.size() && width == width_initial)
idx_b[Bottom] = idx_initial[Bottom];
else {
idx_b[Bottom] = idx_last++;
geometry.add_vertex((Vec3f)b[Bottom].cast<float>(), (Vec3f)n_bottom.cast<float>());
}
// Generate new vertices for the end of this line segment.
idx_b[Left] = idx_last++;
geometry.add_vertex((Vec3f)b[Left].cast<float>(), (Vec3f)n_left.cast<float>());
idx_b[Right] = idx_last++;
geometry.add_vertex((Vec3f)b[Right].cast<float>(), (Vec3f)n_right.cast<float>());
idx_prev = idx_b;
n_right_prev = n_right;
n_top_prev = n_top;
unit_v_prev = unit_v;
len_prev = len;
if (!closed) {
// Terminate open paths with caps.
if (i == 0) {
geometry.add_uint_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]);
geometry.add_uint_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]);
}
// We don't use 'else' because both cases are true if we have only one line.
if (i + 1 == lines.size()) {
geometry.add_uint_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]);
geometry.add_uint_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]);
}
}
// Add quads for a straight hollow tube-like segment.
// bottom-right face
geometry.add_uint_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]);
geometry.add_uint_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]);
// top-right face
geometry.add_uint_triangle(idx_a[Right], idx_b[Right], idx_b[Top]);
geometry.add_uint_triangle(idx_a[Right], idx_b[Top], idx_a[Top]);
// top-left face
geometry.add_uint_triangle(idx_a[Top], idx_b[Top], idx_b[Left]);
geometry.add_uint_triangle(idx_a[Top], idx_b[Left], idx_a[Left]);
// bottom-left face
geometry.add_uint_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]);
geometry.add_uint_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]);
}
}
#else
// caller is responsible for supplying NO lines with zero length
static void thick_lines_to_indexed_vertex_array(
const Lines &lines,
@ -1725,7 +2318,30 @@ static void point_to_indexed_vertex_array(const Vec3crd& point,
volume.push_triangle(idxs[3], idxs[1], idxs[4]);
volume.push_triangle(idxs[0], idxs[3], idxs[4]);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void _3DScene::thick_lines_to_verts(
const Lines& lines,
const std::vector<double>& widths,
const std::vector<double>& heights,
bool closed,
double top_z,
GUI::GLModel::Geometry& geometry)
{
thick_lines_to_geometry(lines, widths, heights, closed, top_z, geometry);
}
void _3DScene::thick_lines_to_verts(
const Lines3& lines,
const std::vector<double>& widths,
const std::vector<double>& heights,
bool closed,
GUI::GLModel::Geometry& geometry)
{
thick_lines_to_geometry(lines, widths, heights, closed, geometry);
}
#else
void _3DScene::thick_lines_to_verts(
const Lines &lines,
const std::vector<double> &widths,
@ -1767,8 +2383,21 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, flo
{
extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
{
Polyline polyline = extrusion_path.polyline;
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines = polyline.lines();
std::vector<double> widths(lines.size(), extrusion_path.width);
std::vector<double> heights(lines.size(), extrusion_path.height);
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
}
#else
void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point &copy, GLVolume &volume)
{
Polyline polyline = extrusion_path.polyline;
@ -1779,8 +2408,27 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, flo
std::vector<double> heights(lines.size(), extrusion_path.height);
thick_lines_to_verts(lines, widths, heights, false, print_z, volume);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Fill in the qverts and tverts with quads and triangles for the extrusion_loop.
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
{
Lines lines;
std::vector<double> widths;
std::vector<double> heights;
for (const ExtrusionPath& extrusion_path : extrusion_loop.paths) {
Polyline polyline = extrusion_path.polyline;
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines_this = polyline.lines();
append(lines, lines_this);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
}
thick_lines_to_verts(lines, widths, heights, true, print_z, geometry);
}
#else
void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point &copy, GLVolume &volume)
{
Lines lines;
@ -1797,8 +2445,27 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, flo
}
thick_lines_to_verts(lines, widths, heights, true, print_z, volume);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path.
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
{
Lines lines;
std::vector<double> widths;
std::vector<double> heights;
for (const ExtrusionPath& extrusion_path : extrusion_multi_path.paths) {
Polyline polyline = extrusion_path.polyline;
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines_this = polyline.lines();
append(lines, lines_this);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
}
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
}
#else
void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point &copy, GLVolume &volume)
{
Lines lines;
@ -1815,13 +2482,49 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_mult
}
thick_lines_to_verts(lines, widths, heights, false, print_z, volume);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
{
for (const ExtrusionEntity* extrusion_entity : extrusion_entity_collection.entities)
extrusionentity_to_verts(extrusion_entity, print_z, copy, geometry);
}
#else
void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point &copy, GLVolume &volume)
{
for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
extrusionentity_to_verts(extrusion_entity, print_z, copy, volume);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void _3DScene::extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
{
if (extrusion_entity != nullptr) {
auto* extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
if (extrusion_path != nullptr)
extrusionentity_to_verts(*extrusion_path, print_z, copy, geometry);
else {
auto* extrusion_loop = dynamic_cast<const ExtrusionLoop*>(extrusion_entity);
if (extrusion_loop != nullptr)
extrusionentity_to_verts(*extrusion_loop, print_z, copy, geometry);
else {
auto* extrusion_multi_path = dynamic_cast<const ExtrusionMultiPath*>(extrusion_entity);
if (extrusion_multi_path != nullptr)
extrusionentity_to_verts(*extrusion_multi_path, print_z, copy, geometry);
else {
auto* extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
if (extrusion_entity_collection != nullptr)
extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, geometry);
else
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()");
}
}
}
}
}
#else
void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point &copy, GLVolume &volume)
{
if (extrusion_entity != nullptr) {
@ -1840,9 +2543,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity,
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
if (extrusion_entity_collection != nullptr)
extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume);
else {
else
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()");
}
}
}
}
@ -1861,5 +2563,6 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height
{
thick_point_to_verts(point, width, height, volume);
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
} // namespace Slic3r

View file

@ -46,6 +46,7 @@ enum ModelInstanceEPrintVolumeState : unsigned char;
// Return appropriate color based on the ModelVolume.
extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume);
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// A container for interleaved arrays of 3D vertices and normals,
// possibly indexed by triangles and / or quads.
class GLIndexedVertexArray {
@ -246,6 +247,7 @@ public:
private:
BoundingBox m_bounding_box;
};
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
class GLVolume {
public:
@ -388,11 +390,17 @@ public:
// Is mouse or rectangle selection over this object to select/deselect it ?
EHoverState hover;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GUI::GLModel model;
#else
// Interleaved triangles & normals with indexed triangles & quads.
GLIndexedVertexArray indexed_vertex_array;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Ranges of triangle and quad indices to be rendered.
std::pair<size_t, size_t> tverts_range;
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
std::pair<size_t, size_t> qverts_range;
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
// of the extrusions per layer.
@ -402,13 +410,17 @@ public:
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box() const {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
return this->model.get_bounding_box();
#else
BoundingBoxf3 out;
if (! this->indexed_vertex_array.bounding_box().isEmpty()) {
if (!this->indexed_vertex_array.bounding_box().isEmpty()) {
out.min = this->indexed_vertex_array.bounding_box().min().cast<double>();
out.max = this->indexed_vertex_array.bounding_box().max().cast<double>();
out.defined = true;
};
}
return out;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
void set_color(const ColorRGBA& rgba) { color = rgba; }
@ -498,14 +510,20 @@ public:
// convex hull
const TriangleMesh* convex_hull() const { return m_convex_hull.get(); }
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool empty() const { return this->model.is_empty(); }
#else
bool empty() const { return this->indexed_vertex_array.empty(); }
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void set_range(double low, double high);
void render() const;
void render();
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); }
void release_geometry() { this->indexed_vertex_array.release_geometry(); }
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void set_bounding_boxes_as_dirty() {
m_transformed_bounding_box.reset();
@ -524,12 +542,20 @@ public:
#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const {
//FIXME what to do wih m_convex_hull?
size_t cpu_memory_used() const {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) +
this->offsets.capacity() * sizeof(size_t);
}
// Return an estimate of the memory held by GPU vertex buffers.
size_t gpu_memory_used() const { return this->model.gpu_memory_used(); }
#else
//FIXME what to do wih m_convex_hull?
return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t);
}
// Return an estimate of the memory held by GPU vertex buffers.
size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); }
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
};
@ -589,11 +615,40 @@ public:
GLVolumeCollection() { set_default_slope_normal_z(); }
~GLVolumeCollection() { clear(); }
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
std::vector<int> load_object(
const ModelObject* model_object,
int obj_idx,
const std::vector<int>& instance_idxs);
int load_object_volume(
const ModelObject* model_object,
int obj_idx,
int volume_idx,
int instance_idx);
// Load SLA auxiliary GLVolumes (for support trees or pad).
void load_object_auxiliary(
const SLAPrintObject* print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>>& instances,
SLAPrintObjectStep milestone,
// Timestamp of the last change of the milestone
size_t timestamp);
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
#else
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
#else
std::vector<int> load_object(
const ModelObject *model_object,
int obj_idx,
const std::vector<int> &instance_idxs,
const std::string &color_by,
bool opengl_initialized);
int load_object_volume(
@ -601,7 +656,6 @@ public:
int obj_idx,
int volume_idx,
int instance_idx,
const std::string &color_by,
bool opengl_initialized);
// Load SLA auxiliary GLVolumes (for support trees or pad).
@ -622,13 +676,20 @@ public:
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLVolume* new_toolpath_volume(const ColorRGBA& rgba);
GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba);
#else
GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0);
GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Render the volumes by OpenGL.
void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
@ -636,11 +697,12 @@ public:
// Release the geometry data assigned to the volumes.
// If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// Clear the geometry
void clear() { for (auto *v : volumes) delete v; volumes.clear(); }
bool empty() const { return volumes.empty(); }
void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); }
void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); }
void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; }
@ -685,9 +747,18 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
struct _3DScene
{
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry);
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GUI::GLModel::Geometry& geometry);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
#else
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
static void extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
@ -696,6 +767,7 @@ struct _3DScene
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
};
}

View file

@ -165,17 +165,6 @@ void BackgroundSlicingProcess::process_fff()
}
}
static void write_thumbnail(Zipper& zipper, const ThumbnailData& data)
{
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)
{
zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size);
mz_free(png_data);
}
}
void BackgroundSlicingProcess::process_sla()
{
assert(m_print == m_sla_print);
@ -189,12 +178,7 @@ void BackgroundSlicingProcess::process_sla()
ThumbnailsList thumbnails = this->render_thumbnails(
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
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);
zipper.finalize();
m_sla_print->export_print(export_path, thumbnails);
m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) {
@ -739,13 +723,7 @@ void BackgroundSlicingProcess::prepare_upload()
ThumbnailsList thumbnails = this->render_thumbnails(
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());
for (const ThumbnailData& data : thumbnails)
if (data.is_valid())
write_thumbnail(zipper, data);
zipper.finalize();
m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.string());
}
m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str());

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

@ -1085,6 +1085,8 @@ void Control::Ruler::update(wxWindow* win, const std::vector<double>& values, do
}
long_step = step == 0 ? -1.0 : (double)step* std::pow(10, pow);
if (long_step < 0)
short_step = long_step;
}
void Control::draw_ruler(wxDC& dc)
@ -1103,44 +1105,76 @@ void Control::draw_ruler(wxDC& dc)
wxColour old_clr = dc.GetTextForeground();
dc.SetTextForeground(GREY_PEN.GetColour());
if (m_ruler.long_step < 0)
for (size_t tick = 1; tick < m_values.size(); tick++) {
wxCoord pos = get_position_from_value(tick);
draw_ticks_pair(dc, pos, mid, 5);
draw_tick_text(dc, wxPoint(mid, pos), tick);
auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) {
if (m_ruler.short_step <= 0.0)
return;
while (current_tick < max_tick) {
wxCoord pos = get_position_from_value(lround(current_tick));
draw_ticks_pair(dc, pos, mid, 2);
current_tick += m_ruler.short_step;
if (current_tick > m_max_value)
break;
}
else {
auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) {
while (current_tick < max_tick) {
wxCoord pos = get_position_from_value(lround(current_tick));
draw_ticks_pair(dc, pos, mid, 2);
current_tick += m_ruler.short_step;
if (current_tick > m_max_value)
};
double short_tick = std::nan("");
int tick = 0;
double value = 0.0;
size_t sequence = 0;
int prev_y_pos = -1;
wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2;
int values_size = (int)m_values.size();
if (m_ruler.long_step < 0) {
// sequential print when long_step wasn't detected because of a lot of printed objects
if (m_ruler.max_values.size() > 1) {
while (tick <= m_max_value && sequence < m_ruler.count()) {
// draw just ticks with max value
value = m_ruler.max_values[sequence];
short_tick = tick;
for (; tick < values_size; tick++) {
if (m_values[tick] == value)
break;
if (m_values[tick] > value) {
if (tick > 0)
tick--;
break;
}
}
if (tick > m_max_value)
break;
wxCoord pos = get_position_from_value(tick);
draw_ticks_pair(dc, pos, mid, 5);
if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) {
draw_tick_text(dc, wxPoint(mid, pos), tick);
prev_y_pos = pos;
}
draw_short_ticks(dc, short_tick, tick);
sequence++;
tick++;
}
};
double short_tick = std::nan("");
int tick = 0;
double value = 0.0;
size_t sequence = 0;
int prev_y_pos = -1;
wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2;
int values_size = (int)m_values.size();
}
// very short object or some non-trivial ruler with non-regular step (see https://github.com/prusa3d/PrusaSlicer/issues/7263)
else {
if (get_scroll_step() < 1) // step less then 1 px indicates very tall object with non-regular laayer step (probably in vase mode)
return;
for (size_t tick = 1; tick < m_values.size(); tick++) {
wxCoord pos = get_position_from_value(tick);
draw_ticks_pair(dc, pos, mid, 5);
draw_tick_text(dc, wxPoint(mid, pos), tick);
}
}
}
else {
while (tick <= m_max_value) {
value += m_ruler.long_step;
if (value > m_ruler.max_values[sequence] && sequence < m_ruler.count()) {
value = m_ruler.long_step;
for (; tick < values_size; tick++)
if (m_values[tick] < value)
break;
// short ticks from the last tick to the end of current sequence
assert(! std::isnan(short_tick));
draw_short_ticks(dc, short_tick, tick);
sequence++;
}
if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence])
value = m_ruler.max_values[sequence];
short_tick = tick;
for (; tick < values_size; tick++) {
@ -1164,7 +1198,7 @@ void Control::draw_ruler(wxDC& dc)
draw_short_ticks(dc, short_tick, tick);
if (value == m_ruler.max_values[sequence] && sequence < m_ruler.count()) {
if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) {
value = 0.0;
sequence++;
tick++;

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
{
@ -635,13 +711,26 @@ void GCodeViewer::init()
m_gl_data_initialized = true;
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print)
#else
void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized)
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
// 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();
@ -665,7 +754,11 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr
m_filament_densities = gcode_result.filament_densities;
if (wxGetApp().is_editor())
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
load_shells(print);
#else
load_shells(print, initialized);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
else {
Pointfs bed_shape;
std::string texture;
@ -955,6 +1048,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 +1074,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 +1299,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 +1326,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 +1344,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 +1393,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 +1446,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 +1534,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 +1667,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 +1678,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 +1689,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 +1705,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 +1746,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 +1761,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 +2050,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 +2129,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: {
@ -2132,7 +2297,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
progress_dialog->Destroy();
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GCodeViewer::load_shells(const Print& print)
#else
void GCodeViewer::load_shells(const Print& print, bool initialized)
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
{
if (print.objects().empty())
// no shells, return
@ -2149,7 +2318,11 @@ void GCodeViewer::load_shells(const Print& print, bool initialized)
}
size_t current_volumes_count = m_shells.volumes.volumes.size();
m_shells.volumes.load_object(model_obj, object_id, instance_ids, "object", initialized);
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
m_shells.volumes.load_object(model_obj, object_id, instance_ids);
#else
m_shells.volumes.load_object(model_obj, object_id, instance_ids, initialized);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// adjust shells' z if raft is present
const SlicingParameters& slicing_parameters = obj->slicing_parameters();
@ -2173,6 +2346,15 @@ void GCodeViewer::load_shells(const Print& print, bool initialized)
const float depth = print.wipe_tower_data(extruders_count).depth;
const float brim_width = print.wipe_tower_data(extruders_count).brim_width;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
!print.is_step_done(psWipeTower), brim_width);
#else
m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
!print.is_step_done(psWipeTower), brim_width);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
#else
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
!print.is_step_done(psWipeTower), brim_width, initialized);
@ -2180,6 +2362,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized)
m_shells.volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
!print.is_step_done(psWipeTower), brim_width, initialized);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
}
@ -3042,6 +3225,7 @@ void GCodeViewer::render_shells()
if (shader == nullptr)
return;
#if !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// when the background processing is enabled, it may happen that the shells data have been loaded
// before opengl has been initialized for the preview canvas.
// when this happens, the volumes' data have not been sent to gpu yet.
@ -3049,6 +3233,7 @@ void GCodeViewer::render_shells()
if (!v->indexed_vertex_array.has_VBOs())
v->finalize_geometry(true);
}
#endif // !ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// glsafe(::glDepthMask(GL_FALSE));
@ -4074,15 +4259,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 +4267,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 +4366,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 +4417,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
@ -767,7 +823,11 @@ public:
void init();
// extract rendering data from the given parameters
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void load(const GCodeProcessorResult& gcode_result, const Print& print);
#else
void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// recalculate ranges in dependence of what is visible and sets tool/print colors
void refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors);
#if ENABLE_PREVIEW_LAYOUT
@ -779,6 +839,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;
@ -824,7 +887,11 @@ public:
private:
void load_toolpaths(const GCodeProcessorResult& gcode_result);
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void load_shells(const Print& print);
#else
void load_shells(const Print& print, bool initialized);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if !ENABLE_PREVIEW_LAYOUT
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
#endif // !ENABLE_PREVIEW_LAYOUT

File diff suppressed because it is too large Load diff

View file

@ -335,6 +335,16 @@ class GLCanvas3D
struct SlaCap
{
#if ENABLE_GLBEGIN_GLEND_REMOVAL
struct Triangles
{
GLModel object;
GLModel supports;
};
typedef std::map<unsigned int, Triangles> ObjectIdToModelsMap;
double z;
ObjectIdToModelsMap triangles;
#else
struct Triangles
{
Pointf3s object;
@ -343,6 +353,7 @@ class GLCanvas3D
typedef std::map<unsigned int, Triangles> ObjectIdToTrianglesMap;
double z;
ObjectIdToTrianglesMap triangles;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
SlaCap() { reset(); }
void reset() { z = DBL_MAX; triangles.clear(); }
@ -473,7 +484,7 @@ private:
std::array<ClippingPlane, 2> m_clipping_planes;
ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes;
SlaCap m_sla_caps[2];
std::array<SlaCap, 2> m_sla_caps;
std::string m_sidebar_field;
// when true renders an extra frame by not resetting m_dirty to false
// see request_extra_frame()
@ -511,8 +522,6 @@ private:
// I just don't want to do it now before a release (Lukas Matena 24.3.2019)
bool m_render_sla_auxiliaries;
std::string m_color_by;
bool m_reload_delayed;
#if ENABLE_RENDER_PICKING_PASS
@ -681,8 +690,6 @@ public:
bool get_use_clipping_planes() const { return m_use_clipping_planes; }
const std::array<ClippingPlane, 2> &get_clipping_planes() const { return m_clipping_planes; };
void set_color_by(const std::string& value);
void refresh_camera_scene_box();
BoundingBoxf3 volumes_bounding_box() const;
@ -786,7 +793,6 @@ public:
void do_move(const std::string& snapshot_type);
void do_rotate(const std::string& snapshot_type);
void do_scale(const std::string& snapshot_type);
void do_flatten(const Vec3d& normal, const std::string& snapshot_type);
void do_mirror(const std::string& snapshot_type);
void update_gizmos_on_off_state();
@ -938,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

@ -8,16 +8,67 @@
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp"
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/Geometry/ConvexHull.hpp"
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include <boost/filesystem/operations.hpp>
#include <boost/algorithm/string/predicate.hpp>
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_SMOOTH_NORMALS
#include <igl/per_face_normals.h>
#include <igl/per_corner_normals.h>
#include <igl/per_vertex_normals.h>
#endif // ENABLE_SMOOTH_NORMALS
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_SMOOTH_NORMALS
static void smooth_normals_corner(const TriangleMesh& mesh, std::vector<stl_normal>& normals)
{
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
std::vector<Vec3f> face_normals = its_face_normals(mesh.its);
Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
Eigen::Index(mesh.its.vertices.size()), 3).cast<double>();
Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(),
Eigen::Index(mesh.its.indices.size()), 3);
Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(),
Eigen::Index(face_normals.size()), 3).cast<double>();
Eigen::MatrixXd out_normals;
igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals);
normals = std::vector<stl_normal>(mesh.its.vertices.size());
for (size_t i = 0; i < mesh.its.indices.size(); ++i) {
for (size_t j = 0; j < 3; ++j) {
normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast<float>();
}
}
}
#endif // ENABLE_SMOOTH_NORMALS
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#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 +93,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);
@ -187,6 +248,37 @@ Vec2f GLModel::Geometry::extract_tex_coord_2(size_t id) const
return { *(start + 0), *(start + 1) };
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GLModel::Geometry::set_vertex(size_t id, const Vec3f& position, const Vec3f& normal)
{
assert(format.vertex_layout == EVertexLayout::P3N3);
assert(id < vertices_count());
if (id < vertices_count()) {
float* start = &vertices[id * vertex_stride_floats(format)];
*(start + 0) = position.x();
*(start + 1) = position.y();
*(start + 2) = position.z();
*(start + 3) = normal.x();
*(start + 4) = normal.y();
*(start + 5) = normal.z();
}
}
void GLModel::Geometry::set_ushort_index(size_t id, unsigned short index)
{
assert(id < indices_count());
if (id < indices_count())
::memcpy(indices.data() + id * sizeof(unsigned short), &index, sizeof(unsigned short));
}
void GLModel::Geometry::set_uint_index(size_t id, unsigned int index)
{
assert(id < indices_count());
if (id < indices_count())
::memcpy(indices.data() + id * sizeof(unsigned int), &index, sizeof(unsigned int));
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
unsigned int GLModel::Geometry::extract_uint_index(size_t id) const
{
if (format.index_type != EIndexType::UINT) {
@ -199,7 +291,7 @@ unsigned int GLModel::Geometry::extract_uint_index(size_t id) const
return -1;
}
unsigned int ret = -1;
unsigned int ret = (unsigned int)-1;
::memcpy(&ret, indices.data() + id * index_stride_bytes(format), sizeof(unsigned int));
return ret;
}
@ -216,11 +308,23 @@ unsigned short GLModel::Geometry::extract_ushort_index(size_t id) const
return -1;
}
unsigned short ret = -1;
unsigned short ret = (unsigned short)-1;
::memcpy(&ret, indices.data() + id * index_stride_bytes(format), sizeof(unsigned short));
return ret;
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GLModel::Geometry::remove_vertex(size_t id)
{
assert(id < vertices_count());
if (id < vertices_count()) {
size_t stride = vertex_stride_floats(format);
std::vector<float>::iterator it = vertices.begin() + id * stride;
vertices.erase(it, it + stride);
}
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
size_t GLModel::Geometry::vertex_stride_floats(const Format& format)
{
switch (format.vertex_layout)
@ -228,6 +332,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 +345,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 +358,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 +386,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 +397,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; }
};
}
@ -303,6 +412,11 @@ size_t GLModel::Geometry::index_stride_bytes(const Format& format)
};
}
GLModel::Geometry::EIndexType GLModel::Geometry::index_type(size_t vertices_count)
{
return (vertices_count < 65536) ? EIndexType::USHORT : EIndexType::UINT;
}
bool GLModel::Geometry::has_position(const Format& format)
{
switch (format.vertex_layout)
@ -310,6 +424,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 +436,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 +447,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; }
@ -428,10 +545,58 @@ void GLModel::init_from(const Geometry& data)
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_SMOOTH_NORMALS
void GLModel::init_from(const TriangleMesh& mesh, bool smooth_normals)
{
if (smooth_normals) {
if (is_initialized()) {
// call reset() if you want to reuse this model
assert(false);
return;
}
if (mesh.its.vertices.empty() || mesh.its.indices.empty()) {
assert(false);
return;
}
std::vector<stl_normal> normals;
smooth_normals_corner(mesh, normals);
const indexed_triangle_set& its = mesh.its;
Geometry& data = m_render_data.geometry;
data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(3 * its.indices.size()) };
data.reserve_vertices(3 * its.indices.size());
data.reserve_indices(3 * its.indices.size());
// vertices
for (size_t i = 0; i < its.vertices.size(); ++i) {
data.add_vertex(its.vertices[i], normals[i]);
}
// indices
for (size_t i = 0; i < its.indices.size(); ++i) {
const stl_triangle_vertex_indices& idx = its.indices[i];
if (data.format.index_type == GLModel::Geometry::EIndexType::USHORT)
data.add_ushort_triangle((unsigned short)idx(0), (unsigned short)idx(1), (unsigned short)idx(2));
else
data.add_uint_triangle((unsigned int)idx(0), (unsigned int)idx(1), (unsigned int)idx(2));
}
// update bounding box
for (size_t i = 0; i < vertices_count(); ++i) {
m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast<double>());
}
}
else
init_from(mesh.its);
}
#else
void GLModel::init_from(const TriangleMesh& mesh)
{
init_from(mesh.its);
}
#endif // ENABLE_SMOOTH_NORMALS
void GLModel::init_from(const indexed_triangle_set& its)
#else
@ -451,21 +616,24 @@ 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.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(3 * its.indices.size()) };
data.reserve_vertices(3 * its.indices.size());
data.reserve_indices(3 * its.indices.size());
// vertices + indices
unsigned int vertices_counter = 0;
for (uint32_t i = 0; i < its.indices.size(); ++i) {
stl_triangle_vertex_indices face = its.indices[i];
stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] };
stl_vertex n = face_normal_normalized(vertex);
const stl_triangle_vertex_indices face = its.indices[i];
const stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] };
const stl_vertex n = face_normal_normalized(vertex);
for (size_t j = 0; j < 3; ++j) {
data.add_vertex(vertex[j], n);
}
vertices_counter += 3;
data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
if (data.format.index_type == GLModel::Geometry::EIndexType::USHORT)
data.add_ushort_triangle((unsigned short)vertices_counter - 3, (unsigned short)vertices_counter - 2, (unsigned short)vertices_counter - 1);
else
data.add_uint_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
}
// update bounding box
@ -534,8 +702,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;
@ -688,6 +856,9 @@ void GLModel::render()
void GLModel::render() const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
render(std::make_pair<size_t, size_t>(0, indices_count()));
#else
GLShaderProgram* shader = wxGetApp().get_current_shader();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
@ -702,8 +873,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);
@ -776,8 +947,71 @@ void GLModel::render() const
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
}
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void GLModel::render(const std::pair<size_t, size_t>& range)
{
if (m_render_disabled)
return;
if (range.second == range.first)
return;
GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader == nullptr)
return;
// sends data to gpu if not done yet
if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) {
if (m_render_data.geometry.vertices_count() > 0 && m_render_data.geometry.indices_count() > 0 && !send_to_gpu())
return;
}
const Geometry& data = m_render_data.geometry;
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);
const bool normal = Geometry::has_normal(data.format);
const bool tex_coord = Geometry::has_tex_coord(data.format);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id));
if (position) {
glsafe(::glVertexPointer(Geometry::position_stride_floats(data.format), GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format)));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
}
if (normal) {
glsafe(::glNormalPointer(GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format)));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
}
if (tex_coord) {
glsafe(::glTexCoordPointer(Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, vertex_stride_bytes, (const void*)Geometry::tex_coord_offset_bytes(data.format)));
glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY));
}
shader->set_uniform("uniform_color", data.color);
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id));
glsafe(::glDrawElements(mode, range.second - range.first + 1, index_type, (const void*)(range.first * Geometry::index_stride_bytes(data.format))));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
if (tex_coord)
glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY));
if (normal)
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
if (position)
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count)
#else
@ -994,6 +1228,62 @@ static void append_triangle(GLModel::Geometry& data, unsigned short v1, unsigned
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
template<typename Fn>
inline bool all_vertices_inside(const GLModel::Geometry& geometry, Fn fn)
{
const size_t position_stride_floats = geometry.position_stride_floats(geometry.format);
const size_t position_offset_floats = geometry.position_offset_floats(geometry.format);
assert(position_stride_floats == 3);
if (geometry.vertices.empty() || position_stride_floats != 3)
return false;
for (auto it = geometry.vertices.begin(); it != geometry.vertices.end(); ) {
it += position_offset_floats;
if (!fn({ *it, *(it + 1), *(it + 2) }))
return false;
it += (geometry.vertex_stride_floats(geometry.format) - position_offset_floats - position_stride_floats);
}
return true;
}
bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom)
{
static constexpr const double epsilon = BuildVolume::BedEpsilon;
switch (volume.type()) {
case BuildVolume::Type::Rectangle:
{
BoundingBox3Base<Vec3d> build_volume = volume.bounding_volume().inflated(epsilon);
if (volume.max_print_height() == 0.0)
build_volume.max.z() = std::numeric_limits<double>::max();
if (ignore_bottom)
build_volume.min.z() = -std::numeric_limits<double>::max();
const BoundingBoxf3& model_box = model.get_bounding_box();
return build_volume.contains(model_box.min) && build_volume.contains(model_box.max);
}
case BuildVolume::Type::Circle:
{
const Geometry::Circled& circle = volume.circle();
const Vec2f c = unscaled<float>(circle.center);
const float r = unscaled<double>(circle.radius) + float(epsilon);
const float r2 = sqr(r);
return volume.max_print_height() == 0.0 ?
all_vertices_inside(model.get_geometry(), [c, r2](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2; }) :
all_vertices_inside(model.get_geometry(), [c, r2, z = volume.max_print_height() + epsilon](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; });
}
case BuildVolume::Type::Convex:
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
case BuildVolume::Type::Custom:
return volume.max_print_height() == 0.0 ?
all_vertices_inside(model.get_geometry(), [&volume](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast<double>()); }) :
all_vertices_inside(model.get_geometry(), [&volume, z = volume.max_print_height() + epsilon](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast<double>()) && p.z() <= z; });
default:
return true;
}
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
GLModel::Geometry stilized_arrow(unsigned short resolution, float tip_radius, float tip_height, float stem_radius, float stem_height)
{
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
@ -1016,8 +1306,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 +1447,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 +1473,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 +1779,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 +1800,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 +1973,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 +1987,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 +1999,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 +2057,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

@ -14,6 +14,9 @@ namespace Slic3r {
class TriangleMesh;
class Polygon;
using Polygons = std::vector<Polygon>;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
class BuildVolume;
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
namespace GUI {
@ -58,6 +61,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,10 +83,21 @@ namespace GUI {
std::vector<unsigned char> indices;
ColorRGBA color{ ColorRGBA::BLACK() };
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 Vec3f& normal);
void reserve_vertices(size_t vertices_count);
void reserve_indices(size_t indices_count);
void add_vertex(const Vec2f& position); // EVertexLayout::P2
void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2
void add_vertex(const Vec3f& position); // EVertexLayout::P3
void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2
void add_vertex(const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3
void set_ushort_index(size_t id, unsigned short index);
void set_uint_index(size_t id, unsigned int index);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void add_ushort_index(unsigned short id);
void add_uint_index(unsigned int id);
@ -101,6 +116,12 @@ namespace GUI {
unsigned int extract_uint_index(size_t id) const;
unsigned short extract_ushort_index(size_t id) const;
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void remove_vertex(size_t id);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; }
size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); }
size_t indices_count() const { return indices.size() / index_stride_bytes(format); }
@ -128,6 +149,8 @@ namespace GUI {
static size_t index_stride_bytes(const Format& format);
static EIndexType index_type(size_t vertices_count);
static bool has_position(const Format& format);
static bool has_normal(const Format& format);
static bool has_tex_coord(const Format& format);
@ -170,6 +193,16 @@ namespace GUI {
std::vector<RenderData> m_render_data;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// By default the vertex and index buffers data are sent to gpu at the first call to render() method.
// If you need to initialize a model from outside the main thread, so that a call to render() may happen
// before the initialization is complete, use the methods:
// disable_render()
// ... do your initialization ...
// enable_render()
// to keep the data on cpu side until needed.
bool m_render_disabled{ false };
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
BoundingBoxf3 m_bounding_box;
std::string m_filename;
@ -188,8 +221,16 @@ namespace GUI {
size_t indices_size_bytes() const { return indices_count() * Geometry::index_stride_bytes(m_render_data.geometry.format); }
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
const Geometry& get_geometry() const { return m_render_data.geometry; }
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void init_from(Geometry&& data);
#if ENABLE_SMOOTH_NORMALS
void init_from(const TriangleMesh& mesh, bool smooth_normals = false);
#else
void init_from(const TriangleMesh& mesh);
#endif // ENABLE_SMOOTH_NORMALS
#else
void init_from(const Geometry& data);
void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox);
@ -210,9 +251,15 @@ namespace GUI {
void reset();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void render();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void render(const std::pair<size_t, size_t>& range);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
void render_instanced(unsigned int instances_vbo, unsigned int instances_count);
bool is_initialized() const { return vertices_count() > 0 && indices_count() > 0; }
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool is_empty() const { return m_render_data.geometry.is_empty(); }
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
#else
void render() const;
void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const;
@ -223,6 +270,29 @@ namespace GUI {
const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; }
const std::string& get_filename() const { return m_filename; }
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool is_render_disabled() const { return m_render_disabled; }
void enable_render() { m_render_disabled = false; }
void disable_render() { m_render_disabled = true; }
size_t cpu_memory_used() const {
size_t ret = 0;
if (!m_render_data.geometry.vertices.empty())
ret += vertices_size_bytes();
if (!m_render_data.geometry.indices.empty())
ret += indices_size_bytes();
return ret;
}
size_t gpu_memory_used() const {
size_t ret = 0;
if (m_render_data.geometry.vertices.empty())
ret += vertices_size_bytes();
if (m_render_data.geometry.indices.empty())
ret += indices_size_bytes();
return ret;
}
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
private:
#if ENABLE_GLBEGIN_GLEND_REMOVAL
bool send_to_gpu();
@ -231,6 +301,10 @@ namespace GUI {
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
};
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom = true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
// create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution
// the origin of the arrow is in the center of the stem cap
// the arrow has its axis of symmetry along the Z axis and is pointing upward
@ -254,6 +328,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

@ -98,7 +98,7 @@ namespace GUI {
color[1] = (m_state == EState::Select) ? 1.0f : 0.3f;
color[2] = 0.3f;
glsafe(::glColor3fv(color));
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
glsafe(::glDisable(GL_DEPTH_TEST));
@ -126,8 +126,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

@ -211,7 +211,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::stri
float scale = (float)sprite_size_px / std::max(image->width, image->height);
// offset by 1 to leave the first pixel empty (both in x and y)
nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride);
nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px_ex, sprite_size_px_ex, sprite_stride);
// makes white only copy of the sprite
::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes);
@ -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

@ -496,7 +496,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
/* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } },
/* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } },
/* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } },
};
// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.
@ -754,7 +754,7 @@ void GUI_App::post_init()
if (boost::algorithm::iends_with(filename, ".amf") ||
boost::algorithm::iends_with(filename, ".amf.xml") ||
boost::algorithm::iends_with(filename, ".3mf"))
this->plater()->set_project_filename(filename);
this->plater()->set_project_filename(from_u8(filename));
}
}
if (! this->init_params->extra_config.empty())
@ -871,8 +871,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

@ -1691,16 +1691,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

@ -1104,8 +1104,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

@ -274,9 +274,13 @@ static void generate_thumbnail_from_model(const std::string& filename)
GLVolumeCollection volumes;
volumes.volumes.push_back(new GLVolume());
GLVolume* volume = volumes.volumes[0];
GLVolume* volume = volumes.volumes.back();
#if ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
volume->model.init_from(model.mesh());
#else
volume->indexed_vertex_array.load_mesh(model.mesh());
volume->indexed_vertex_array.finalize_geometry(true);
#endif // ENABLE_GLINDEXEDVERTEXARRAY_REMOVAL
volume->set_instance_transformation(model.objects[0]->instances[0]->get_transformation());
volume->set_volume_transformation(model.objects[0]->volumes[0]->get_transformation());

View file

@ -5,6 +5,8 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
// TODO: Display tooltips quicker on Linux
namespace Slic3r {
@ -74,63 +76,19 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
, m_first_input_window_render(true)
, m_dirty(false)
{
m_base_color = DEFAULT_BASE_COLOR;
m_drag_color = DEFAULT_DRAG_COLOR;
m_highlight_color = DEFAULT_HIGHLIGHT_COLOR;
}
void GLGizmoBase::set_hover_id(int id)
{
if (m_grabbers.empty() || id < (int)m_grabbers.size()) {
m_hover_id = id;
on_set_hover_id();
}
}
// do not change hover id during dragging
assert(!m_dragging);
void GLGizmoBase::enable_grabber(unsigned int id)
{
if (id < m_grabbers.size())
m_grabbers[id].enabled = true;
on_enable_grabber(id);
}
void GLGizmoBase::disable_grabber(unsigned int id)
{
if (id < m_grabbers.size())
m_grabbers[id].enabled = false;
on_disable_grabber(id);
}
void GLGizmoBase::start_dragging()
{
m_dragging = true;
for (int i = 0; i < (int)m_grabbers.size(); ++i)
{
m_grabbers[i].dragging = (m_hover_id == i);
}
on_start_dragging();
}
void GLGizmoBase::stop_dragging()
{
m_dragging = false;
for (int i = 0; i < (int)m_grabbers.size(); ++i)
{
m_grabbers[i].dragging = false;
}
on_stop_dragging();
}
void GLGizmoBase::update(const UpdateData& data)
{
if (m_hover_id != -1)
on_update(data);
// allow empty grabbers when not using grabbers but use hover_id - flatten, rotate
if (!m_grabbers.empty() && id >= (int) m_grabbers.size())
return;
m_hover_id = id;
on_set_hover_id();
}
bool GLGizmoBase::update_items_state()
@ -138,7 +96,7 @@ bool GLGizmoBase::update_items_state()
bool res = m_dirty;
m_dirty = false;
return res;
};
}
ColorRGBA GLGizmoBase::picking_color_component(unsigned int id) const
{
@ -189,6 +147,81 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
// help function to process grabbers
// call start_dragging, stop_dragging, on_dragging
bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
bool is_dragging_finished = false;
if (mouse_event.Moving()) {
// it should not happen but for sure
assert(!m_dragging);
if (m_dragging) is_dragging_finished = true;
else return false;
}
if (mouse_event.LeftDown()) {
Selection &selection = m_parent.get_selection();
if (!selection.is_empty() && m_hover_id != -1 &&
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))) {
// TODO: investigate if it is neccessary -> there was no stop dragging
selection.start_dragging();
m_dragging = true;
for (auto &grabber : m_grabbers) grabber.dragging = false;
if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
m_grabbers[m_hover_id].dragging = true;
// prevent change of hover_id during dragging
m_parent.set_mouse_as_dragging();
on_start_dragging();
// Let the plater know that the dragging started
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED));
m_parent.set_as_dirty();
return true;
}
} else if (m_dragging) {
// when mouse cursor leave window than finish actual dragging operation
bool is_leaving = mouse_event.Leaving();
if (mouse_event.Dragging()) {
m_parent.set_mouse_as_dragging();
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
auto ray = m_parent.mouse_ray(mouse_coord);
UpdateData data(ray, mouse_coord);
on_dragging(data);
wxGetApp().obj_manipul()->set_dirty();
m_parent.set_as_dirty();
return true;
} else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) {
for (auto &grabber : m_grabbers) grabber.dragging = false;
m_dragging = false;
// NOTE: This should be part of GLCanvas3D
// Reset hover_id when leave window
if (is_leaving) m_parent.mouse_up_cleanup();
on_stop_dragging();
// There is prediction that after draggign, data are changed
// Data are updated twice also by canvas3D::reload_scene.
// Should be fixed.
m_parent.get_gizmos_manager().update_data();
wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed
// refresh of the scene with the background processing data should
// be performed.
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
m_parent.refresh_camera_scene_box();
return true;
}
}
return false;
}
std::string GLGizmoBase::format(float value, unsigned int decimals) const
{
return Slic3r::string_printf("%.*f", decimals, value);

View file

@ -10,6 +10,7 @@
#include <cereal/archives/binary.hpp>
class wxWindow;
class wxMouseEvent;
namespace Slic3r {
@ -29,7 +30,6 @@ class ImGuiWrapper;
class GLCanvas3D;
enum class CommonGizmosDataID;
class CommonGizmosDataPool;
class Selection;
class GLGizmoBase
{
@ -85,28 +85,22 @@ public:
protected:
GLCanvas3D& m_parent;
int m_group_id;
int m_group_id; // TODO: remove only for rotate
EState m_state;
int m_shortcut_key;
std::string m_icon_filename;
unsigned int m_sprite_id;
int m_hover_id;
bool m_dragging;
ColorRGBA m_base_color;
ColorRGBA m_drag_color;
ColorRGBA m_highlight_color;
mutable std::vector<Grabber> m_grabbers;
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
mutable std::string m_tooltip;
CommonGizmosDataPool* m_c;
public:
GLGizmoBase(GLCanvas3D& parent,
const std::string& icon_filename,
unsigned int sprite_id);
virtual ~GLGizmoBase() {}
virtual ~GLGizmoBase() = default;
bool init() { return on_init(); }
@ -115,9 +109,6 @@ public:
std::string get_name(bool include_shortcut = true) const;
int get_group_id() const { return m_group_id; }
void set_group_id(int id) { m_group_id = id; }
EState get_state() const { return m_state; }
void set_state(EState state) { m_state = state; on_set_state(); }
@ -139,27 +130,33 @@ public:
int get_hover_id() const { return m_hover_id; }
void set_hover_id(int id);
void set_highlight_color(const ColorRGBA& color) { m_highlight_color = color; }
void enable_grabber(unsigned int id);
void disable_grabber(unsigned int id);
void start_dragging();
void stop_dragging();
bool is_dragging() const { return m_dragging; }
void update(const UpdateData& data);
// returns True when Gizmo changed its state
bool update_items_state();
void render() { m_tooltip.clear(); on_render(); }
void render() { on_render(); }
void render_for_picking() { on_render_for_picking(); }
void render_input_window(float x, float y, float bottom_limit);
/// <summary>
/// Mouse tooltip text
/// </summary>
/// <returns>Text to be visible in mouse tooltip</returns>
virtual std::string get_tooltip() const { return ""; }
/// <summary>
/// Is called when data (Selection) is changed
/// </summary>
virtual void data_changed(){};
/// <summary>
/// Implement when want to process mouse events in gizmo
/// Click, Right click, move, drag, ...
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information and don't want to propagate it otherwise False.</returns>
virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
protected:
virtual bool on_init() = 0;
virtual void on_load(cereal::BinaryInputArchive& ar) {}
@ -172,9 +169,12 @@ protected:
virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
virtual void on_enable_grabber(unsigned int id) {}
virtual void on_disable_grabber(unsigned int id) {}
// called inside use_grabbers
virtual void on_start_dragging() {}
virtual void on_stop_dragging() {}
virtual void on_update(const UpdateData& data) {}
virtual void on_dragging(const UpdateData& data) {}
virtual void on_render() = 0;
virtual void on_render_for_picking() = 0;
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
@ -191,6 +191,15 @@ protected:
// Mark gizmo as dirty to Re-Render when idle()
void set_dirty();
/// <summary>
/// function which
/// Set up m_dragging and call functions
/// on_start_dragging / on_dragging / on_stop_dragging
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>same as on_mouse</returns>
bool use_grabbers(const wxMouseEvent &mouse_event);
private:
// Flag for dirty visible state of Gizmo
// When True then need new rendering

View file

@ -38,6 +38,11 @@ std::string GLGizmoCut::get_tooltip() const
return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : "";
}
bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event)
{
return use_grabbers(mouse_event);
}
bool GLGizmoCut::on_init()
{
m_grabbers.emplace_back();
@ -53,7 +58,7 @@ std::string GLGizmoCut::on_get_name() const
void GLGizmoCut::on_set_state()
{
// Reset m_cut_z on gizmo activation
if (get_state() == On)
if (m_state == On)
m_cut_z = bounding_box().center().z();
}
@ -76,10 +81,10 @@ void GLGizmoCut::on_start_dragging()
m_drag_center.z() = m_cut_z;
}
void GLGizmoCut::on_update(const UpdateData& data)
void GLGizmoCut::on_dragging(const UpdateData &data)
{
if (m_hover_id != -1)
set_cut_z(m_start_z + calc_projection(data.mouse_ray));
assert(m_hover_id != -1);
set_cut_z(m_start_z + calc_projection(data.mouse_ray));
}
void GLGizmoCut::on_render()
@ -105,18 +110,22 @@ void GLGizmoCut::on_render()
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
Vec3d diff = plane_center - m_old_center;
// Z changed when move with cut plane
// X and Y changed when move with cutted object
bool is_changed = std::abs(diff.x()) > EPSILON ||
std::abs(diff.y()) > EPSILON ||
std::abs(diff.z()) > EPSILON;
m_old_center = plane_center;
const bool z_changed = std::abs(plane_center.z() - m_old_z) > EPSILON;
m_old_z = plane_center.z();
if (!m_plane.is_initialized() || z_changed) {
if (!m_plane.is_initialized() || is_changed) {
m_plane.reset();
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()));
@ -154,14 +163,14 @@ void GLGizmoCut::on_render()
glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
#if ENABLE_GLBEGIN_GLEND_REMOVAL
if (!m_grabber_connection.is_initialized() || z_changed) {
if (!m_grabber_connection.is_initialized() || is_changed) {
m_grabber_connection.reset();
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>());
@ -329,6 +338,8 @@ BoundingBoxf3 GLGizmoCut::bounding_box() const
BoundingBoxf3 ret;
const Selection& selection = m_parent.get_selection();
const Selection::IndicesList& idxs = selection.get_volume_idxs();
return selection.get_bounding_box();
for (unsigned int i : idxs) {
const GLVolume* volume = selection.get_volume(i);
if (!volume->is_modifier)

View file

@ -8,6 +8,7 @@
namespace Slic3r {
namespace GUI {
class Selection;
class GLGizmoCut : public GLGizmoBase
{
@ -25,7 +26,7 @@ class GLGizmoCut : public GLGizmoBase
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLModel m_plane;
GLModel m_grabber_connection;
float m_old_z{ 0.0f };
Vec3d m_old_center;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
struct CutContours
@ -50,6 +51,13 @@ public:
std::string get_tooltip() const override;
/// <summary>
/// Drag of plane
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
protected:
virtual bool on_init() override;
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
@ -58,7 +66,7 @@ protected:
virtual void on_set_state() override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_dragging(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;

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"
@ -18,9 +21,52 @@ static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0
GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_normal(Vec3d::Zero())
, m_starting_center(Vec3d::Zero())
{}
bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event)
{
if (mouse_event.Moving()) {
// only for sure
m_mouse_left_down = false;
return false;
}
if (mouse_event.LeftDown()) {
if (m_hover_id != -1) {
m_mouse_left_down = true;
Selection &selection = m_parent.get_selection();
if (selection.is_single_full_instance()) {
// Rotate the object so the normal points downward:
selection.flattening_rotate(m_planes[m_hover_id].normal);
m_parent.do_rotate(L("Gizmo-Place on Face"));
}
return true;
}
// fix: prevent restart gizmo when reselect object
// take responsibility for left up
if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true;
} else if (mouse_event.LeftUp()) {
if (m_mouse_left_down) {
// responsible for mouse left up after selecting plane
m_mouse_left_down = false;
return true;
}
} else if (mouse_event.Leaving()) {
m_mouse_left_down = false;
}
return false;
}
void GLGizmoFlatten::data_changed()
{
const Selection & selection = m_parent.get_selection();
const ModelObject *model_object = nullptr;
if (selection.is_single_full_instance() ||
selection.is_from_single_object() ) {
model_object = selection.get_model()->objects[selection.get_object_idx()];
}
set_flattening_data(model_object);
}
bool GLGizmoFlatten::on_init()
@ -50,19 +96,18 @@ bool GLGizmoFlatten::on_is_activable() const
return m_parent.get_selection().is_single_full_instance();
}
void GLGizmoFlatten::on_start_dragging()
{
if (m_hover_id != -1) {
assert(m_planes_valid);
m_normal = m_planes[m_hover_id].normal;
m_starting_center = m_parent.get_selection().get_bounding_box().center();
}
}
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 +121,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,18 +164,25 @@ 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)
{
m_starting_center = Vec3d::Zero();
if (model_object != m_old_model_object) {
m_planes.clear();
m_planes_valid = false;
@ -324,12 +393,28 @@ 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;
init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::index_type(plane.vertices.size()) };
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 (init_data.format.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();
@ -363,13 +448,5 @@ bool GLGizmoFlatten::is_plane_update_necessary() const
return false;
}
Vec3d GLGizmoFlatten::get_flattening_normal() const
{
Vec3d out = m_normal;
m_normal = Vec3d::Zero();
m_starting_center = Vec3d::Zero();
return out;
}
} // namespace GUI
} // namespace Slic3r

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 {
@ -18,11 +22,14 @@ class GLGizmoFlatten : public GLGizmoBase
// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself.
private:
mutable Vec3d m_normal;
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;
};
@ -34,8 +41,8 @@ private:
Vec3d m_first_instance_mirror;
std::vector<PlaneData> m_planes;
bool m_mouse_left_down = false; // for detection left_up of this gizmo
bool m_planes_valid = false;
mutable Vec3d m_starting_center;
const ModelObject* m_old_model_object = nullptr;
std::vector<const Transform3d*> instances_matrices;
@ -46,17 +53,23 @@ public:
GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
void set_flattening_data(const ModelObject* model_object);
Vec3d get_flattening_normal() const;
/// <summary>
/// Apply rotation on select plane
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void data_changed() override;
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_set_state() override;
virtual CommonGizmosDataID on_get_requirements() const override;
bool on_init() override;
std::string on_get_name() const override;
bool on_is_activable() const override;
void on_render() override;
void on_render_for_picking() override;
void on_set_state() override;
CommonGizmosDataID on_get_requirements() const override;
};
} // namespace GUI

View file

@ -42,7 +42,7 @@ bool GLGizmoHollow::on_init()
return true;
}
void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&)
void GLGizmoHollow::data_changed()
{
if (! m_c->selection_info())
return;
@ -417,20 +417,70 @@ void GLGizmoHollow::delete_selected_points()
select_point(NoPoints);
}
void GLGizmoHollow::on_update(const UpdateData& data)
bool GLGizmoHollow::on_mouse(const wxMouseEvent &mouse_event)
{
sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
if (mouse_event.Moving()) return false;
if (use_grabbers(mouse_event)) return true;
if (m_hover_id != -1) {
std::pair<Vec3f, Vec3f> pos_and_normal;
if (! unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
drain_holes[m_hover_id].pos = pos_and_normal.first;
drain_holes[m_hover_id].normal = -pos_and_normal.second;
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
Vec2d mouse_pos = mouse_coord.cast<double>();
static bool pending_right_up = false;
if (mouse_event.LeftDown()) {
bool control_down = mouse_event.CmdDown();
bool grabber_contains_mouse = (get_hover_id() != -1);
if ((!control_down || grabber_contains_mouse) &&
gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
// the gizmo got the event and took some action, there is no need
// to do anything more
return true;
} else if (mouse_event.Dragging()) {
if (m_parent.get_move_volume_id() != -1)
// don't allow dragging objects with the Sla gizmo on
return true;
bool control_down = mouse_event.CmdDown();
if (control_down) {
// CTRL has been pressed while already dragging -> stop current action
if (mouse_event.LeftIsDown())
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
else if (mouse_event.RightIsDown()) {
pending_right_up = false;
}
} else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
// the gizmo got the event and took some action, no need to do
// anything more here
m_parent.set_as_dirty();
return true;
}
} else if (mouse_event.LeftUp()) {
if (!m_parent.is_mouse_dragging()) {
bool control_down = mouse_event.CmdDown();
// in case gizmo is selected, we just pass the LeftUp event
// and stop processing - neither object moving or selecting is
// suppressed in that case
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
return true;
}
} else if (mouse_event.RightDown()) {
if (m_parent.get_selection().get_object_idx() != -1 &&
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
// we need to set the following right up as processed to avoid showing
// the context menu if the user release the mouse over the object
pending_right_up = true;
// event was taken care of by the SlaSupports gizmo
return true;
}
} else if (mouse_event.RightUp()) {
if (pending_right_up) {
pending_right_up = false;
return true;
}
}
return false;
}
void GLGizmoHollow::hollow_mesh(bool postpone_error_messages)
{
wxGetApp().CallAfter([this, postpone_error_messages]() {
@ -820,6 +870,17 @@ void GLGizmoHollow::on_stop_dragging()
}
void GLGizmoHollow::on_dragging(const UpdateData &data)
{
assert(m_hover_id != -1);
std::pair<Vec3f, Vec3f> pos_and_normal;
if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes;
drain_holes[m_hover_id].pos = pos_and_normal.first;
drain_holes[m_hover_id].normal = -pos_and_normal.second;
}
void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar)
{

View file

@ -19,7 +19,7 @@ class ConfigOptionDef;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
class Selection;
class GLGizmoHollow : public GLGizmoBase
{
private:
@ -29,16 +29,22 @@ private:
public:
GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoHollow() = default;
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
void data_changed() override;
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points();
bool is_selection_rectangle_dragging() const {
return m_selection_rectangle.is_dragging();
}
/// <summary>
/// Postpone to Grabber for move
/// Detect move of object by dragging
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
private:
bool on_init() override;
void on_update(const UpdateData& data) override;
void on_render() override;
void on_render_for_picking() override;
@ -94,6 +100,7 @@ protected:
void on_set_hover_id() override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData &data) override;
void on_render_input_window(float x, float y, float bottom_limit) override;
virtual CommonGizmosDataID on_get_requirements() const override;

View file

@ -146,10 +146,9 @@ void GLGizmoMmuSegmentation::render_painter_gizmo()
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
void GLGizmoMmuSegmentation::data_changed()
{
GLGizmoPainterBase::set_painter_gizmo_data(selection);
GLGizmoPainterBase::data_changed();
if (m_state != On || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF || wxGetApp().extruders_edited_cnt() <= 1)
return;
@ -589,6 +588,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 +604,7 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
contour_shader->stop_using();
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
m_update_render_data = false;
}
@ -636,6 +639,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 +660,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

@ -87,7 +87,7 @@ public:
void render_painter_gizmo() override;
void set_painter_gizmo_data(const Selection& selection) override;
void data_changed() override;
void render_triangles(const Selection& selection) const override;

View file

@ -32,6 +32,16 @@ std::string GLGizmoMove3D::get_tooltip() const
return "";
}
bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
return use_grabbers(mouse_event);
}
void GLGizmoMove3D::data_changed() {
const Selection &selection = m_parent.get_selection();
bool is_wipe_tower = selection.is_wipe_tower();
m_grabbers[2].enabled = !is_wipe_tower;
}
bool GLGizmoMove3D::on_init()
{
for (int i = 0; i < 3; ++i) {
@ -55,22 +65,23 @@ bool GLGizmoMove3D::on_is_activable() const
void GLGizmoMove3D::on_start_dragging()
{
if (m_hover_id != -1) {
m_displacement = Vec3d::Zero();
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center(2) = box.min(2);
}
assert(m_hover_id != -1);
m_displacement = Vec3d::Zero();
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center(2) = box.min(2);
}
void GLGizmoMove3D::on_stop_dragging()
{
m_parent.do_move(L("Gizmo-Move"));
m_displacement = Vec3d::Zero();
}
void GLGizmoMove3D::on_update(const UpdateData& data)
void GLGizmoMove3D::on_dragging(const UpdateData& data)
{
if (m_hover_id == 0)
m_displacement.x() = calc_projection(data);
@ -78,6 +89,9 @@ void GLGizmoMove3D::on_update(const UpdateData& data)
m_displacement.y() = calc_projection(data);
else if (m_hover_id == 2)
m_displacement.z() = calc_projection(data);
Selection &selection = m_parent.get_selection();
selection.translate(m_displacement);
}
void GLGizmoMove3D::on_render()
@ -117,8 +131,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

@ -34,19 +34,28 @@ public:
double get_snap_step(double step) const { return m_snap_step; }
void set_snap_step(double step) { m_snap_step = step; }
const Vec3d& get_displacement() const { return m_displacement; }
std::string get_tooltip() const override;
/// <summary>
/// Postpone to Grabber for move
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
/// <summary>
/// Detect reduction of move for wipetover on selection change
/// </summary>
void data_changed() override;
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_stop_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
bool on_init() override;
std::string on_get_name() const override;
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData& data) override;
void on_render() override;
void on_render_for_picking() override;
private:
double calc_projection(const UpdateData& data) const;

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,17 +31,22 @@ 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)
void GLGizmoPainterBase::data_changed()
{
if (m_state != On)
return;
const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
const Selection & selection = m_parent.get_selection();
if (mo && selection.is_from_single_instance()
&& (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
{
@ -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);
@ -612,7 +641,72 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
return false;
}
bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
{
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
Vec2d mouse_pos = mouse_coord.cast<double>();
if (mouse_event.Moving()) {
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false);
return false;
}
// when control is down we allow scene pan and rotation even when clicking
// over some object
bool control_down = mouse_event.CmdDown();
bool grabber_contains_mouse = (get_hover_id() != -1);
const Selection &selection = m_parent.get_selection();
int selected_object_idx = selection.get_object_idx();
if (mouse_event.LeftDown()) {
if ((!control_down || grabber_contains_mouse) &&
gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
// the gizmo got the event and took some action, there is no need
// to do anything more
return true;
} else if (mouse_event.RightDown()){
if (!control_down && selected_object_idx != -1 &&
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false))
// event was taken care of
return true;
} else if (mouse_event.Dragging()) {
if (m_parent.get_move_volume_id() != -1)
// don't allow dragging objects with the Sla gizmo on
return true;
if (!control_down && gizmo_event(SLAGizmoEventType::Dragging,
mouse_pos, mouse_event.ShiftDown(),
mouse_event.AltDown(), false)) {
// the gizmo got the event and took some action, no need to do
// anything more here
m_parent.set_as_dirty();
return true;
}
if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown()))
{
// CTRL has been pressed while already dragging -> stop current action
if (mouse_event.LeftIsDown())
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
else if (mouse_event.RightIsDown())
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
return false;
}
} else if (mouse_event.LeftUp()) {
if (!m_parent.is_mouse_dragging()) {
// in case SLA/FDM gizmo is selected, we just pass the LeftUp
// event and stop processing - neither object moving or selecting
// is suppressed in that case
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
return true;
}
} else if (mouse_event.RightUp()) {
if (!m_parent.is_mouse_dragging()) {
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down);
return true;
}
}
return false;
}
void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position,
const Camera& camera,
@ -763,13 +857,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 +887,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 +905,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 +921,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 +968,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 +1020,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 +1081,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 +1118,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 +1233,66 @@ 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();
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3, GLModel::Geometry::index_type(2 * contour_edges.size()) };
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 (init_data.format.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"
@ -21,6 +25,7 @@ enum class SLAGizmoEventType : unsigned char;
class ClippingPlane;
struct Camera;
class GLGizmoMmuSegmentation;
class Selection;
enum class PainterGizmoType {
FDM_SUPPORTS,
@ -28,6 +33,7 @@ enum class PainterGizmoType {
MMU_SEGMENTATION
};
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
class GLPaintContour
{
public:
@ -63,6 +69,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 +82,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 +98,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
};
@ -113,8 +136,8 @@ private:
void on_render_for_picking() override {}
public:
GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoPainterBase() override;
virtual void set_painter_gizmo_data(const Selection& selection);
~GLGizmoPainterBase() override;
void data_changed() override;
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
// Following function renders the triangles and cursor. Having this separated
@ -127,6 +150,15 @@ public:
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
/// <summary>
/// Implement when want to process mouse events in gizmo
/// Click, Right click, move, drag, ...
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information and don't want to
/// propagate it otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
protected:
virtual void render_triangles(const Selection& selection) const;
void render_cursor();
@ -209,7 +241,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;
@ -231,9 +267,6 @@ private:
protected:
void on_set_state() override;
void on_start_dragging() override {}
void on_stop_dragging() override {}
virtual void on_opening() = 0;
virtual void on_shutdown() = 0;
virtual PainterGizmoType get_painter_type() const = 0;

View file

@ -28,7 +28,23 @@ const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius
GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis)
: GLGizmoBase(parent, "", -1)
, m_axis(axis)
{}
, m_angle(0.0)
, m_center(0.0, 0.0, 0.0)
, m_radius(0.0f)
, m_snap_coarse_in_radius(0.0f)
, m_snap_coarse_out_radius(0.0f)
, m_snap_fine_in_radius(0.0f)
, m_snap_fine_out_radius(0.0f)
, m_drag_color(DEFAULT_DRAG_COLOR)
, m_highlight_color(DEFAULT_HIGHLIGHT_COLOR)
{
m_group_id = static_cast<int>(axis);
}
void GLGizmoRotate::set_highlight_color(const ColorRGBA &color)
{
m_highlight_color = color;
}
void GLGizmoRotate::set_angle(double angle)
{
@ -50,6 +66,28 @@ std::string GLGizmoRotate::get_tooltip() const
return (m_hover_id == 0 || m_grabbers.front().dragging) ? axis + ": " + format(float(Geometry::rad2deg(m_angle)), 4) : "";
}
bool GLGizmoRotate::on_mouse(const wxMouseEvent &mouse_event)
{
return use_grabbers(mouse_event);
}
void GLGizmoRotate::dragging(const UpdateData &data) { on_dragging(data); }
void GLGizmoRotate::start_dragging()
{
m_grabbers[0].dragging = true;
on_start_dragging();
}
void GLGizmoRotate::stop_dragging()
{
m_grabbers[0].dragging = false;
on_stop_dragging();
}
void GLGizmoRotate::enable_grabber() { m_grabbers[0].enabled = true; }
void GLGizmoRotate::disable_grabber() { m_grabbers[0].enabled = false; }
bool GLGizmoRotate::on_init()
{
m_grabbers.push_back(Grabber());
@ -67,7 +105,7 @@ void GLGizmoRotate::on_start_dragging()
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
}
void GLGizmoRotate::on_update(const UpdateData& data)
void GLGizmoRotate::on_dragging(const UpdateData &data)
{
const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection()));
@ -134,15 +172,14 @@ void GLGizmoRotate::on_render()
if (shader != nullptr) {
shader->start_using();
const float radius = Offset + m_parent.get_selection().get_bounding_box().radius();
const bool radius_changed = std::abs(m_old_radius - radius) > EPSILON;
m_old_radius = radius;
const bool radius_changed = std::abs(m_old_radius - m_radius) > EPSILON;
m_old_radius = m_radius;
ColorRGBA color((m_hover_id != -1) ? m_drag_color : m_highlight_color);
render_circle(color, radius_changed);
if (m_hover_id != -1) {
const bool hover_radius_changed = std::abs(m_old_hover_radius - radius) > EPSILON;
m_old_hover_radius = radius;
const bool hover_radius_changed = std::abs(m_old_hover_radius - m_radius) > EPSILON;
m_old_hover_radius = m_radius;
render_scale(color, hover_radius_changed);
render_snap_radii(color, hover_radius_changed);
@ -235,8 +272,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 +315,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 +374,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 +425,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 +466,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 +503,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));
@ -624,25 +661,50 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons
GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_gizmos({ GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) })
{
for (unsigned int i = 0; i < 3; ++i) {
m_gizmos[i].set_group_id(i);
}
, m_gizmos({
GLGizmoRotate(parent, GLGizmoRotate::X),
GLGizmoRotate(parent, GLGizmoRotate::Y),
GLGizmoRotate(parent, GLGizmoRotate::Z) })
{}
load_rotoptimize_state();
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
{
if (mouse_event.Dragging() && m_dragging) {
// Apply new temporary rotations
TransformationType transformation_type(
TransformationType::World_Relative_Joint);
if (mouse_event.AltDown()) transformation_type.set_independent();
m_parent.get_selection().rotate(get_rotation(), transformation_type);
}
return use_grabbers(mouse_event);
}
void GLGizmoRotate3D::data_changed() {
const Selection &selection = m_parent.get_selection();
bool is_wipe_tower = selection.is_wipe_tower();
if (is_wipe_tower) {
DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
float wipe_tower_rotation_angle =
dynamic_cast<const ConfigOptionFloat *>(
config.option("wipe_tower_rotation_angle"))
->value;
set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle));
m_gizmos[0].disable_grabber();
m_gizmos[1].disable_grabber();
} else {
set_rotation(Vec3d::Zero());
m_gizmos[0].enable_grabber();
m_gizmos[1].enable_grabber();
}
}
bool GLGizmoRotate3D::on_init()
{
for (GLGizmoRotate& g : m_gizmos) {
if (!g.init())
return false;
}
for (GLGizmoRotate& g : m_gizmos)
if (!g.init()) return false;
for (unsigned int i = 0; i < 3; ++i) {
for (unsigned int i = 0; i < 3; ++i)
m_gizmos[i].set_highlight_color(AXES_COLOR[i]);
}
m_shortcut_key = WXK_CONTROL_R;
@ -661,14 +723,21 @@ bool GLGizmoRotate3D::on_is_activable() const
void GLGizmoRotate3D::on_start_dragging()
{
if (0 <= m_hover_id && m_hover_id < 3)
m_gizmos[m_hover_id].start_dragging();
assert(0 <= m_hover_id && m_hover_id < 3);
m_gizmos[m_hover_id].start_dragging();
}
void GLGizmoRotate3D::on_stop_dragging()
{
if (0 <= m_hover_id && m_hover_id < 3)
m_gizmos[m_hover_id].stop_dragging();
assert(0 <= m_hover_id && m_hover_id < 3);
m_parent.do_rotate(L("Gizmo-Rotate"));
m_gizmos[m_hover_id].stop_dragging();
}
void GLGizmoRotate3D::on_dragging(const UpdateData &data)
{
assert(0 <= m_hover_id && m_hover_id < 3);
m_gizmos[m_hover_id].dragging(data);
}
void GLGizmoRotate3D::on_render()

View file

@ -5,7 +5,7 @@
namespace Slic3r {
namespace GUI {
class Selection;
class GLGizmoRotate : public GLGizmoBase
{
static const float Offset;
@ -20,9 +20,9 @@ class GLGizmoRotate : public GLGizmoBase
public:
enum Axis : unsigned char
{
X,
Y,
Z
X=0,
Y=1,
Z=2
};
private:
@ -52,6 +52,8 @@ private:
float m_old_hover_radius{ 0.0f };
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
ColorRGBA m_drag_color;
ColorRGBA m_highlight_color;
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
virtual ~GLGizmoRotate() = default;
@ -61,11 +63,27 @@ public:
std::string get_tooltip() const override;
void start_dragging();
void stop_dragging();
void enable_grabber();
void disable_grabber();
void set_highlight_color(const ColorRGBA &color);
/// <summary>
/// Postpone to Grabber for move
/// Detect move of object by dragging
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void dragging(const UpdateData &data);
protected:
bool on_init() override;
std::string on_get_name() const override { return ""; }
void on_start_dragging() override;
void on_update(const UpdateData& data) override;
void on_dragging(const UpdateData &data) override;
void on_render() override;
void on_render_for_picking() override;
@ -111,6 +129,14 @@ public:
return tooltip;
}
/// <summary>
/// Postpone to Rotation
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void data_changed() override;
protected:
bool on_init() override;
std::string on_get_name() const override;
@ -124,20 +150,17 @@ protected:
}
void on_enable_grabber(unsigned int id) override {
if (id < 3)
m_gizmos[id].enable_grabber(0);
m_gizmos[id].enable_grabber();
}
void on_disable_grabber(unsigned int id) override {
if (id < 3)
m_gizmos[id].disable_grabber(0);
m_gizmos[id].disable_grabber();
}
bool on_is_activable() const override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_update(const UpdateData& data) override {
for (GLGizmoRotate& g : m_gizmos) {
g.update(data);
}
}
void on_dragging(const UpdateData &data) override;
void on_render() override;
void on_render_for_picking() override {
for (GLGizmoRotate& g : m_gizmos) {

View file

@ -14,7 +14,13 @@ namespace GUI {
const float GLGizmoScale3D::Offset = 5.0f;
GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_scale(Vec3d::Ones())
, m_offset(Vec3d::Zero())
, m_snap_step(0.05)
, m_base_color(DEFAULT_BASE_COLOR)
, m_drag_color(DEFAULT_DRAG_COLOR)
, m_highlight_color(DEFAULT_HIGHLIGHT_COLOR)
{
#if ENABLE_GLBEGIN_GLEND_REMOVAL
m_grabber_connections[0].grabber_indices = { 0, 1 };
@ -58,6 +64,46 @@ std::string GLGizmoScale3D::get_tooltip() const
return "";
}
bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event)
{
if (mouse_event.Dragging()) {
if (m_dragging) {
// Apply new temporary scale factors
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
if (mouse_event.AltDown()) transformation_type.set_independent();
Selection &selection = m_parent.get_selection();
selection.scale(get_scale(), transformation_type);
if (mouse_event.CmdDown()) selection.translate(m_offset, true);
}
}
return use_grabbers(mouse_event);
}
void GLGizmoScale3D::data_changed()
{
const Selection &selection = m_parent.get_selection();
bool enable_scale_xyz = selection.is_single_full_instance() ||
selection.is_single_volume() ||
selection.is_single_modifier();
for (unsigned int i = 0; i < 6; ++i)
m_grabbers[i].enabled = enable_scale_xyz;
if (enable_scale_xyz) {
// all volumes in the selection belongs to the same instance, any of
// them contains the needed data, so we take the first
const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin());
if (selection.is_single_full_instance()) {
set_scale(volume->get_instance_scaling_factor());
} else if (selection.is_single_volume() ||
selection.is_single_modifier()) {
set_scale(volume->get_volume_scaling_factor());
}
} else {
set_scale(Vec3d::Ones());
}
}
bool GLGizmoScale3D::on_init()
{
for (int i = 0; i < 10; ++i) {
@ -92,22 +138,25 @@ bool GLGizmoScale3D::on_is_activable() const
void GLGizmoScale3D::on_start_dragging()
{
if (m_hover_id != -1) {
m_starting.drag_position = m_grabbers[m_hover_id].center;
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box();
assert(m_hover_id != -1);
m_starting.drag_position = m_grabbers[m_hover_id].center;
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box();
const Vec3d& center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z());
m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z());
m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z());
m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z());
m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z());
m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z());
}
const Vec3d& center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z());
m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z());
m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z());
m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z());
m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z());
m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z());
}
void GLGizmoScale3D::on_update(const UpdateData& data)
void GLGizmoScale3D::on_stop_dragging() {
m_parent.do_scale(L("Gizmo-Scale"));
}
void GLGizmoScale3D::on_dragging(const UpdateData& data)
{
if (m_hover_id == 0 || m_hover_id == 1)
do_scale_along_axis(X, data);
@ -403,8 +452,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

@ -44,6 +44,9 @@ class GLGizmoScale3D : public GLGizmoBase
std::array<GrabberConnection, 7> m_grabber_connections;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
ColorRGBA m_base_color;
ColorRGBA m_drag_color;
ColorRGBA m_highlight_color;
public:
GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -53,16 +56,23 @@ public:
const Vec3d& get_scale() const { return m_scale; }
void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; }
const Vec3d& get_offset() const { return m_offset; }
std::string get_tooltip() const override;
/// <summary>
/// Postpone to Grabber for scale
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void data_changed() override;
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_stop_dragging() override;
virtual void on_dragging(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;

View file

@ -28,9 +28,7 @@ namespace GUI {
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{
}
{}
bool GLGizmoSlaSupports::on_init()
{
@ -48,11 +46,15 @@ bool GLGizmoSlaSupports::on_init()
m_desc["manual_editing"] = _L("Manual editing");
m_desc["clipping_of_view"] = _L("Clipping of view")+ ": ";
m_desc["reset_direction"] = _L("Reset direction");
m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24));
m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.));
m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.));
return true;
}
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
void GLGizmoSlaSupports::data_changed()
{
if (! m_c->selection_info())
return;
@ -127,6 +129,14 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
if (! has_points && ! has_holes)
return;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light");
if (shader == nullptr)
return;
shader->start_using();
ScopeGuard guard([shader]() { shader->stop_using(); });
#else
GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light");
if (shader != nullptr)
shader->start_using();
@ -134,6 +144,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
if (shader != nullptr)
shader->stop_using();
});
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
@ -177,11 +188,12 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
#if ENABLE_GLBEGIN_GLEND_REMOVAL
m_cone.set_color(render_color);
m_sphere.set_color(render_color);
if (!picking)
#else
m_cone.set_color(-1, render_color);
m_sphere.set_color(-1, render_color);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
if (shader && !picking)
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
shader->set_uniform("emission_factor", 0.5f);
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
@ -236,9 +248,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
m_cylinder.set_color(render_color);
#else
m_cylinder.set_color(-1, render_color);
if (shader != nu)
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
if (shader)
shader->set_uniform("emission_factor", 0.5f);
shader->set_uniform("emission_factor", 0.5f);
for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) {
if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
continue;
@ -543,22 +555,6 @@ void GLGizmoSlaSupports::delete_selected_points(bool force)
select_point(NoPoints);
}
void GLGizmoSlaSupports::on_update(const UpdateData& data)
{
if (! m_editing_mode)
return;
else {
if (m_hover_id != -1 && (! m_editing_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) {
std::pair<Vec3f, Vec3f> pos_and_normal;
if (! unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first;
m_editing_cache[m_hover_id].support_point.is_new_island = false;
m_editing_cache[m_hover_id].normal = pos_and_normal.second;
}
}
}
std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const
{
std::vector<const ConfigOption*> out;
@ -975,7 +971,21 @@ void GLGizmoSlaSupports::on_stop_dragging()
m_point_before_drag = CacheEntry();
}
void GLGizmoSlaSupports::on_dragging(const UpdateData &data)
{
assert(m_hover_id != -1);
if (!m_editing_mode) return;
if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands)
return;
std::pair<Vec3f, Vec3f> pos_and_normal;
if (!unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first;
m_editing_cache[m_hover_id].support_point.is_new_island = false;
m_editing_cache[m_hover_id].normal = pos_and_normal.second;
}
void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
{
@ -1112,6 +1122,61 @@ void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) cons
});
}
bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){
if (mouse_event.Moving()) return false;
if (use_grabbers(mouse_event)) return true;
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
Vec2d mouse_pos = mouse_coord.cast<double>();
static bool pending_right_up = false;
if (mouse_event.LeftDown()) {
bool grabber_contains_mouse = (get_hover_id() != -1);
bool control_down = mouse_event.CmdDown();
if ((!control_down || grabber_contains_mouse) &&
gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false))
return true;
} else if (mouse_event.Dragging()) {
bool control_down = mouse_event.CmdDown();
if (m_parent.get_move_volume_id() != -1) {
// don't allow dragging objects with the Sla gizmo on
return true;
} else if (!control_down &&
gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) {
// the gizmo got the event and took some action, no need to do
// anything more here
m_parent.set_as_dirty();
return true;
} else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){
// CTRL has been pressed while already dragging -> stop current action
if (mouse_event.LeftIsDown())
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true);
else if (mouse_event.RightIsDown())
pending_right_up = false;
}
} else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) {
// in case SLA/FDM gizmo is selected, we just pass the LeftUp event
// and stop processing - neither object moving or selecting is
// suppressed in that case
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown());
return true;
}else if (mouse_event.RightDown()){
if (m_parent.get_selection().get_object_idx() != -1 &&
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
// we need to set the following right up as processed to avoid showing
// the context menu if the user release the mouse over the object
pending_right_up = true;
// event was taken care of by the SlaSupports gizmo
return true;
}
} else if (pending_right_up && mouse_event.RightUp()) {
pending_right_up = false;
return true;
}
return false;
}
void GLGizmoSlaSupports::get_data_from_backend()
{
if (! has_backend_supports())

View file

@ -16,7 +16,7 @@ namespace Slic3r {
class ConfigOption;
namespace GUI {
class Selection;
enum class SLAGizmoEventType : unsigned char;
class GLGizmoSlaSupports : public GLGizmoBase
@ -57,7 +57,7 @@ private:
public:
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSlaSupports() = default;
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
void data_changed() override;
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points(bool force = false);
//ClippingPlane get_sla_clipping_plane() const;
@ -70,10 +70,15 @@ public:
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return _u8L("Entering SLA support points"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving SLA support points"); }
/// <summary>
/// Process mouse event
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
private:
bool on_init() override;
void on_update(const UpdateData& data) override;
void on_render() override;
void on_render_for_picking() override;
@ -107,7 +112,6 @@ private:
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
bool is_point_in_hole(const Vec3f& pt) const;
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
// Methods that do the model_object and editing cache synchronization,
@ -130,13 +134,13 @@ private:
protected:
void on_set_state() override;
void on_set_hover_id() override
{
if (! m_editing_mode || (int)m_editing_cache.size() <= m_hover_id)
m_hover_id = -1;
}
void on_start_dragging() override;
void on_stop_dragging() override;
void on_dragging(const UpdateData &data) override;
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;

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

@ -38,6 +38,7 @@ GLGizmosManager::GLGizmosManager(GLCanvas3D& parent)
, m_enabled(false)
, m_icons_texture_dirty(true)
, m_current(Undefined)
, m_hover(Undefined)
, m_tooltip("")
, m_serializing(false)
{
@ -46,33 +47,34 @@ GLGizmosManager::GLGizmosManager(GLCanvas3D& parent)
std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
{
std::vector<size_t> out;
out.reserve(m_gizmos.size());
for (size_t i=0; i<m_gizmos.size(); ++i)
if (m_gizmos[i]->is_selectable())
out.push_back(i);
return out;
}
size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const
GLGizmosManager::EType GLGizmosManager::get_gizmo_from_mouse(const Vec2d &mouse_pos) const
{
if (! m_enabled)
return Undefined;
if (!m_enabled) return Undefined;
float cnv_h = (float)m_parent.get_canvas_size().get_height();
float height = get_scaled_total_height();
float cnv_h = (float) m_parent.get_canvas_size().get_height();
float height = get_scaled_total_height();
float icons_size = m_layout.scaled_icons_size();
float border = m_layout.scaled_border();
float stride_y = m_layout.scaled_stride_y();
float top_y = 0.5f * (cnv_h - height) + border;
float border = m_layout.scaled_border();
float stride_y = m_layout.scaled_stride_y();
float top_y = 0.5f * (cnv_h - height) + border;
// is mouse horizontally in the area?
if ((border <= (float)mouse_pos(0) && ((float)mouse_pos(0) <= border + icons_size))) {
if ((border <= (float) mouse_pos(0) &&
((float) mouse_pos(0) <= border + icons_size))) {
// which icon is it on?
size_t from_top = (size_t)((float)mouse_pos(1) - top_y) / stride_y;
size_t from_top = (size_t) ((float) mouse_pos(1) - top_y) / stride_y;
// is it really on the icon or already past the border?
if ((float)mouse_pos(1) <= top_y + from_top * stride_y + icons_size) {
if ((float) mouse_pos(1) <= top_y + from_top * stride_y + icons_size) {
std::vector<size_t> selectable = get_selectable_idxs();
if (from_top < selectable.size())
return selectable[from_top];
if (from_top < selectable.size())
return static_cast<EType>(selectable[from_top]);
}
}
return Undefined;
@ -161,9 +163,9 @@ void GLGizmosManager::refresh_on_off_state()
if (m_serializing || m_current == Undefined || m_gizmos.empty())
return;
if (m_current != Undefined
&& ! m_gizmos[m_current]->is_activable() && activate_gizmo(Undefined))
update_data();
// FS: Why update data after Undefined gizmo activation?
if (!m_gizmos[m_current]->is_activable() && activate_gizmo(Undefined))
update_data();
}
void GLGizmosManager::reset_all_states()
@ -177,9 +179,13 @@ void GLGizmosManager::reset_all_states()
bool GLGizmosManager::open_gizmo(EType type)
{
int idx = int(type);
if (m_gizmos[idx]->is_activable()
&& activate_gizmo(m_current == idx ? Undefined : (EType)idx)) {
int idx = static_cast<int>(type);
// re-open same type cause closing
if (m_current == type) type = Undefined;
if (m_gizmos[idx]->is_activable() && activate_gizmo(type)) {
// remove update data into gizmo itself
update_data();
return true;
}
@ -208,87 +214,14 @@ void GLGizmosManager::set_hover_id(int id)
m_gizmos[m_current]->set_hover_id(id);
}
void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable)
{
if (!m_enabled || type == Undefined || m_gizmos.empty())
return;
if (enable)
m_gizmos[type]->enable_grabber(id);
else
m_gizmos[type]->disable_grabber(id);
}
void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos)
{
if (!m_enabled)
return;
GLGizmoBase* curr = get_current();
if (curr != nullptr)
curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos));
}
void GLGizmosManager::update_data()
{
if (!m_enabled)
return;
const Selection& selection = m_parent.get_selection();
bool is_wipe_tower = selection.is_wipe_tower();
enable_grabber(Move, 2, !is_wipe_tower);
enable_grabber(Rotate, 0, !is_wipe_tower);
enable_grabber(Rotate, 1, !is_wipe_tower);
bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier();
for (unsigned int i = 0; i < 6; ++i)
{
enable_grabber(Scale, i, enable_scale_xyz);
}
if (!m_enabled) return;
if (m_common_gizmos_data)
m_common_gizmos_data->update(get_current()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
if (selection.is_single_full_instance())
{
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
set_scale(volume->get_instance_scaling_factor());
set_rotation(Vec3d::Zero());
ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()];
set_flattening_data(model_object);
set_sla_support_data(model_object);
set_painter_gizmo_data();
}
else if (selection.is_single_volume() || selection.is_single_modifier())
{
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
set_scale(volume->get_volume_scaling_factor());
set_rotation(Vec3d::Zero());
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
set_painter_gizmo_data();
}
else if (is_wipe_tower)
{
DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
set_scale(Vec3d::Ones());
set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value));
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
set_painter_gizmo_data();
}
else
{
set_scale(Vec3d::Ones());
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_painter_gizmo_data();
}
if (m_current != Undefined) m_gizmos[m_current]->data_changed();
}
bool GLGizmosManager::is_running() const
@ -328,107 +261,6 @@ bool GLGizmosManager::is_dragging() const
return m_gizmos[m_current]->is_dragging();
}
void GLGizmosManager::start_dragging()
{
if (! m_enabled || m_current == Undefined)
return;
m_gizmos[m_current]->start_dragging();
}
void GLGizmosManager::stop_dragging()
{
if (! m_enabled || m_current == Undefined)
return;
m_gizmos[m_current]->stop_dragging();
}
Vec3d GLGizmosManager::get_displacement() const
{
if (!m_enabled)
return Vec3d::Zero();
return dynamic_cast<GLGizmoMove3D*>(m_gizmos[Move].get())->get_displacement();
}
Vec3d GLGizmosManager::get_scale() const
{
if (!m_enabled)
return Vec3d::Ones();
return dynamic_cast<GLGizmoScale3D*>(m_gizmos[Scale].get())->get_scale();
}
void GLGizmosManager::set_scale(const Vec3d& scale)
{
if (!m_enabled || m_gizmos.empty())
return;
dynamic_cast<GLGizmoScale3D*>(m_gizmos[Scale].get())->set_scale(scale);
}
Vec3d GLGizmosManager::get_scale_offset() const
{
if (!m_enabled || m_gizmos.empty())
return Vec3d::Zero();
return dynamic_cast<GLGizmoScale3D*>(m_gizmos[Scale].get())->get_offset();
}
Vec3d GLGizmosManager::get_rotation() const
{
if (!m_enabled || m_gizmos.empty())
return Vec3d::Zero();
return dynamic_cast<GLGizmoRotate3D*>(m_gizmos[Rotate].get())->get_rotation();
}
void GLGizmosManager::set_rotation(const Vec3d& rotation)
{
if (!m_enabled || m_gizmos.empty())
return;
dynamic_cast<GLGizmoRotate3D*>(m_gizmos[Rotate].get())->set_rotation(rotation);
}
Vec3d GLGizmosManager::get_flattening_normal() const
{
if (!m_enabled || m_gizmos.empty())
return Vec3d::Zero();
return dynamic_cast<GLGizmoFlatten*>(m_gizmos[Flatten].get())->get_flattening_normal();
}
void GLGizmosManager::set_flattening_data(const ModelObject* model_object)
{
if (!m_enabled || m_gizmos.empty())
return;
dynamic_cast<GLGizmoFlatten*>(m_gizmos[Flatten].get())->set_flattening_data(model_object);
}
void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
{
if (! m_enabled
|| m_gizmos.empty()
|| wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
return;
auto* gizmo_hollow = dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get());
auto* gizmo_supports = dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get());
gizmo_hollow->set_sla_support_data(model_object, m_parent.get_selection());
gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection());
}
void GLGizmosManager::set_painter_gizmo_data()
{
if (!m_enabled || m_gizmos.empty())
return;
dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection());
dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->set_painter_gizmo_data(m_parent.get_selection());
dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->set_painter_gizmo_data(m_parent.get_selection());
}
// Returns true if the gizmo used the event to do something, false otherwise.
bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
@ -517,7 +349,7 @@ std::string GLGizmosManager::get_tooltip() const
return (curr != nullptr) ? curr->get_tooltip() : "";
}
bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt)
{
bool processed = false;
@ -530,227 +362,108 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
return processed;
}
bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
{
// used to set a right up event as processed when needed
static bool pending_right_up = false;
Point pos(evt.GetX(), evt.GetY());
Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY());
Selection& selection = m_parent.get_selection();
int selected_object_idx = selection.get_object_idx();
bool processed = false;
// when control is down we allow scene pan and rotation even when clicking over some object
bool control_down = evt.CmdDown();
// mouse anywhere
if (evt.Moving()) {
m_tooltip = update_hover_state(mouse_pos);
if (m_current == MmuSegmentation || m_current == FdmSupports)
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown());
} else if (evt.LeftUp()) {
if (m_mouse_capture.left) {
processed = true;
m_mouse_capture.left = false;
bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) {
assert(m_enabled);
// keep information about events to process
struct MouseCapture
{
bool left = false;
bool middle = false;
bool right = false;
bool exist_tooltip = false;
MouseCapture() = default;
bool any() const { return left || middle || right; }
void reset() {
left = false;
middle = false;
right = false;
}
else if (is_dragging()) {
switch (m_current) {
case Move: { m_parent.do_move(L("Gizmo-Move")); break; }
case Scale: { m_parent.do_scale(L("Gizmo-Scale")); break; }
case Rotate: { m_parent.do_rotate(L("Gizmo-Rotate")); break; }
default: break;
}
};
static MouseCapture mc;
stop_dragging();
update_data();
// wxCoord == int --> wx/types.h
Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY());
Vec2d mouse_pos = mouse_coord.cast<double>();
wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
m_parent.refresh_camera_scene_box();
EType gizmo = get_gizmo_from_mouse(mouse_pos);
bool selected_gizmo = gizmo != Undefined;
processed = true;
}
// else
// return false;
}
else if (evt.MiddleUp()) {
if (m_mouse_capture.middle) {
processed = true;
m_mouse_capture.middle = false;
}
else
// fast reaction on move mouse
if (mouse_event.Moving()) {
assert(!mc.any());
if (selected_gizmo) {
mc.exist_tooltip = true;
update_hover_state(gizmo);
// at this moment is enebled to process mouse move under gizmo
// tools bar e.g. Do not interupt dragging.
return false;
} else if (mc.exist_tooltip) {
// first move out of gizmo tool bar - unselect tooltip
mc.exist_tooltip = false;
update_hover_state(Undefined);
return false;
}
return false;
}
else if (evt.RightUp()) {
if (pending_right_up) {
pending_right_up = false;
if (selected_gizmo) {
// mouse is above toolbar
if (mouse_event.LeftDown() || mouse_event.LeftDClick()) {
mc.left = true;
open_gizmo(gizmo);
return true;
} else if (mouse_event.RightDown()) {
mc.right = true;
return true;
} else if (mouse_event.MiddleDown()) {
mc.middle = true;
return true;
}
if (m_mouse_capture.right) {
processed = true;
m_mouse_capture.right = false;
}
// else
// return false;
}
else if (evt.Dragging() && !is_dragging()) {
if (m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
// else
// return false;
}
else if (evt.Dragging() && is_dragging()) {
if (!m_parent.get_wxglcanvas()->HasCapture())
m_parent.get_wxglcanvas()->CaptureMouse();
m_parent.set_mouse_as_dragging();
update(m_parent.mouse_ray(pos), pos);
switch (m_current)
{
case Move:
{
// Apply new temporary offset
selection.translate(get_displacement());
wxGetApp().obj_manipul()->set_dirty();
break;
}
case Scale:
{
// Apply new temporary scale factors
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
if (evt.AltDown())
transformation_type.set_independent();
selection.scale(get_scale(), transformation_type);
if (control_down)
selection.translate(get_scale_offset(), true);
wxGetApp().obj_manipul()->set_dirty();
break;
}
case Rotate:
{
// Apply new temporary rotations
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (evt.AltDown())
transformation_type.set_independent();
selection.rotate(get_rotation(), transformation_type);
wxGetApp().obj_manipul()->set_dirty();
break;
}
default:
break;
}
m_parent.set_as_dirty();
processed = true;
}
if (get_gizmo_idx_from_mouse(mouse_pos) == Undefined) {
// mouse is outside the toolbar
m_tooltip.clear();
if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) {
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation)
&& gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown()))
// the gizmo got the event and took some action, there is no need to do anything more
processed = true;
else if (!selection.is_empty() && grabber_contains_mouse()) {
update_data();
selection.start_dragging();
start_dragging();
// Let the plater know that the dragging started
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED));
if (m_current == Flatten) {
// Rotate the object so the normal points downward:
m_parent.do_flatten(get_flattening_normal(), L("Gizmo-Place on Face"));
wxGetApp().obj_manipul()->set_dirty();
}
m_parent.set_as_dirty();
processed = true;
if (mc.any()) {
// Check if exist release of event started above toolbar?
if (mouse_event.Dragging()) {
if (!selected_gizmo && mc.exist_tooltip) {
// dragging out of gizmo let tooltip disapear
mc.exist_tooltip = false;
update_hover_state(Undefined);
}
// draging start on toolbar so no propagation into scene
return true;
} else if (mc.left && mouse_event.LeftUp()) {
mc.left = false;
return true;
} else if (mc.right && mouse_event.RightUp()) {
mc.right = false;
return true;
} else if (mc.middle && mouse_event.MiddleUp()) {
mc.middle = false;
return true;
}
else if (evt.RightDown() && selected_object_idx != -1 && (m_current == SlaSupports || m_current == Hollow)
&& gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) {
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
pending_right_up = true;
// event was taken care of by the SlaSupports gizmo
processed = true;
}
else if (evt.RightDown() && !control_down && selected_object_idx != -1 && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation)
&& gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) {
// event was taken care of by the FdmSupports / Seam / MMUPainting gizmo
processed = true;
}
else if (evt.Dragging() && m_parent.get_move_volume_id() != -1
&& (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation))
// don't allow dragging objects with the Sla gizmo on
processed = true;
else if (evt.Dragging() && !control_down && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation)
&& gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown())) {
// the gizmo got the event and took some action, no need to do anything more here
m_parent.set_as_dirty();
processed = true;
}
else if (evt.Dragging() && control_down && (evt.LeftIsDown() || evt.RightIsDown())) {
// CTRL has been pressed while already dragging -> stop current action
if (evt.LeftIsDown())
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), true);
else if (evt.RightIsDown())
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), true);
}
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && !m_parent.is_mouse_dragging()) {
// in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither
// object moving or selecting is suppressed in that case
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down);
processed = true;
}
else if (evt.LeftUp() && m_current == Flatten && m_gizmos[m_current]->get_hover_id() != -1) {
// to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
selection.stop_dragging();
wxGetApp().obj_manipul()->set_dirty();
processed = true;
}
else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && !m_parent.is_mouse_dragging()) {
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down);
processed = true;
}
else if (evt.LeftUp()) {
selection.stop_dragging();
wxGetApp().obj_manipul()->set_dirty();
}
}
else {
// mouse inside toolbar
if (evt.LeftDown() || evt.LeftDClick()) {
m_mouse_capture.left = true;
m_mouse_capture.parent = &m_parent;
processed = true;
if (!selection.is_empty()) {
update_on_off_state(mouse_pos);
update_data();
m_parent.set_as_dirty();
}
}
else if (evt.MiddleDown()) {
m_mouse_capture.middle = true;
m_mouse_capture.parent = &m_parent;
}
else if (evt.RightDown()) {
m_mouse_capture.right = true;
m_mouse_capture.parent = &m_parent;
}
// event out of window is not porocessed
// left down on gizmo -> keep down -> move out of window -> release left
if (mouse_event.Leaving()) mc.reset();
}
return false;
}
return processed;
bool GLGizmosManager::on_mouse(const wxMouseEvent &mouse_event)
{
if (!m_enabled) return false;
// tool bar wants to use event?
if (gizmos_toolbar_on_mouse(mouse_event)) return true;
// current gizmo wants to use event?
if (m_current != Undefined &&
// check if gizmo override method could be slower than simple call virtual function
// &m_gizmos[m_current]->on_mouse != &GLGizmoBase::on_mouse &&
m_gizmos[m_current]->on_mouse(mouse_event))
return true;
return false;
}
bool GLGizmosManager::on_char(wxKeyEvent& evt)
@ -1180,68 +893,64 @@ bool GLGizmosManager::generate_icons_texture() const
return res;
}
void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos)
void GLGizmosManager::update_hover_state(const EType &type)
{
if (!m_enabled)
assert(m_enabled);
if (type == Undefined) {
m_hover = Undefined;
m_tooltip.clear();
return;
size_t idx = get_gizmo_idx_from_mouse(mouse_pos);
if (idx != Undefined && m_gizmos[idx]->is_activable() && m_hover == idx) {
activate_gizmo(m_current == idx ? Undefined : (EType)idx);
wxGetApp().obj_list()->select_object_item((EType)idx <= Rotate);
}
}
std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos)
{
std::string name = "";
if (!m_enabled)
return name;
m_hover = Undefined;
size_t idx = get_gizmo_idx_from_mouse(mouse_pos);
if (idx != Undefined) {
name = m_gizmos[idx]->get_name();
if (m_gizmos[idx]->is_activable())
m_hover = (EType)idx;
}
return name;
const GLGizmoBase &hovered_gizmo = *m_gizmos[type];
m_hover = hovered_gizmo.is_activable() ? type : Undefined;
m_tooltip = hovered_gizmo.get_name();
}
bool GLGizmosManager::activate_gizmo(EType type)
{
if (m_gizmos.empty() || m_current == type)
return true;
assert(!m_gizmos.empty());
GLGizmoBase* old_gizmo = m_current == Undefined ? nullptr : m_gizmos[m_current].get();
GLGizmoBase* new_gizmo = type == Undefined ? nullptr : m_gizmos[type].get();
// already activated
if (m_current == type) return true;
if (old_gizmo) {
old_gizmo->set_state(GLGizmoBase::Off);
if (old_gizmo->get_state() != GLGizmoBase::Off)
if (m_current != Undefined) {
// clean up previous gizmo
GLGizmoBase &old_gizmo = *m_gizmos[m_current];
old_gizmo.set_state(GLGizmoBase::Off);
if (old_gizmo.get_state() != GLGizmoBase::Off)
return false; // gizmo refused to be turned off, do nothing.
if (! m_parent.get_gizmos_manager().is_serializing()
&& old_gizmo->wants_enter_leave_snapshots())
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
old_gizmo->get_gizmo_leaving_text(),
UndoRedo::SnapshotType::LeavingGizmoWithAction);
if (!m_serializing && old_gizmo.wants_enter_leave_snapshots())
Plater::TakeSnapshot
snapshot(wxGetApp().plater(),
old_gizmo.get_gizmo_leaving_text(),
UndoRedo::SnapshotType::LeavingGizmoWithAction);
}
if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing()
&& new_gizmo->wants_enter_leave_snapshots())
if (type == Undefined) {
// it is deactivation of gizmo
m_current = Undefined;
return true;
}
// set up new gizmo
GLGizmoBase& new_gizmo = *m_gizmos[type];
if (!new_gizmo.is_activable()) return false;
if (!m_serializing && new_gizmo.wants_enter_leave_snapshots())
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
new_gizmo->get_gizmo_entering_text(),
UndoRedo::SnapshotType::EnteringGizmo);
new_gizmo.get_gizmo_entering_text(),
UndoRedo::SnapshotType::EnteringGizmo);
m_current = type;
new_gizmo.set_state(GLGizmoBase::On);
if (new_gizmo.get_state() != GLGizmoBase::On) {
m_current = Undefined;
return false; // gizmo refused to be turned on.
}
if (new_gizmo)
new_gizmo->set_state(GLGizmoBase::On);
// sucessful activation of gizmo
return true;
}
@ -1279,11 +988,5 @@ bool GLGizmosManager::is_hiding_instances() const
&& m_common_gizmos_data->instances_hider()->is_valid());
}
int GLGizmosManager::get_shortcut_key(GLGizmosManager::EType type) const
{
return m_gizmos[type]->get_shortcut_key();
}
} // namespace GUI
} // namespace Slic3r

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