Merge branch 'dev' into fs_simplify_multipart_object

This commit is contained in:
Filip Sykala 2022-01-19 13:59:57 +01:00
commit 80231a22f2
40 changed files with 3204 additions and 1650 deletions

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/refs/heads/v3.1.4-patched.zip
URL_HASH SHA256=69dec874981d2fc3d90345660c27f3450d8430c483e8446edcc87b6ed18bff8f
URL https://github.com/prusa3d/wxWidgets/archive/73f029adfcc82fb3aa4b01220a013f716e57d110.zip
URL_HASH SHA256=c35fe0187db497b6a3f477e24ed5e307028657ff0c2554385810b6e7961ad2e4
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG
CMAKE_ARGS
-DwxBUILD_PRECOMP=ON

View File

@ -139,7 +139,7 @@ documentation_link= https://help.prusa3d.com/en/article/per-model-settings_1674
disabled_tags = SLA
[hint:Solid infill threshold area]
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area</a>.(Expert mode only.)
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area.</a>(Expert mode only.)
hypertext_type = settings
hypertext_settings_opt = solid_infill_below_area
hypertext_settings_type = 1
@ -197,7 +197,7 @@ documentation_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-
disabled_tags = SLA
[hint:Configuration snapshots]
text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - <a>Configuration snapshots menu</a>.
text = Configuration snapshots\nDid you know that you can roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - <a>Configuration snapshots menu</a>.
documentation_link = https://help.prusa3d.com/en/article/configuration-snapshots_1776
hypertext_type = menubar
hypertext_menubar_menu_name = Configuration

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
#version 110
uniform vec4 uniform_color;
void main()
{
gl_FragColor = uniform_color;
}

View File

@ -0,0 +1,6 @@
#version 110
void main()
{
gl_Position = ftransform();
}

View File

@ -100,6 +100,8 @@ set(SLIC3R_SOURCES
GCode/ThumbnailData.hpp
GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp
GCode/FindReplace.cpp
GCode/FindReplace.hpp
GCode/PostProcessor.cpp
GCode/PostProcessor.hpp
# GCode/PressureEqualizer.cpp

View File

@ -1099,6 +1099,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// modifies m_silent_time_estimator_enabled
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
if (! print.config().gcode_substitutions.values.empty()) {
m_find_replace = make_unique<GCodeFindReplace>(print.config());
file.set_find_replace(m_find_replace.get());
}
// resets analyzer's tracking data
m_last_height = 0.f;
m_last_layer_z = 0.f;
@ -1560,15 +1565,25 @@ void GCode::process_layers(
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string {
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
});
const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&self = *this->m_find_replace.get()](std::string s) -> std::string {
return self.process_layer(std::move(s));
});
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
[&output_stream](std::string s) { output_stream.write(s); }
);
// The pipeline elements are joined using const references, thus no copying is performed.
if (m_spiral_vase)
output_stream.set_find_replace(nullptr);
if (m_spiral_vase && m_find_replace)
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output);
else if (m_spiral_vase)
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output);
else if (m_find_replace)
tbb::parallel_pipeline(12, generator & cooling & find_replace & output);
else
tbb::parallel_pipeline(12, generator & cooling & output);
output_stream.set_find_replace(m_find_replace.get());
}
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
@ -1603,15 +1618,25 @@ void GCode::process_layers(
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string {
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
});
const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&self = *this->m_find_replace.get()](std::string s) -> std::string {
return self.process_layer(std::move(s));
});
const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
[&output_stream](std::string s) { output_stream.write(s); }
);
// The pipeline elements are joined using const references, thus no copying is performed.
if (m_spiral_vase)
output_stream.set_find_replace(nullptr);
if (m_spiral_vase && m_find_replace)
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output);
else if (m_spiral_vase)
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output);
else if (m_find_replace)
tbb::parallel_pipeline(12, generator & cooling & find_replace & output);
else
tbb::parallel_pipeline(12, generator & cooling & output);
output_stream.set_find_replace(m_find_replace.get());
}
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
@ -2824,11 +2849,11 @@ void GCode::GCodeOutputStream::close()
void GCode::GCodeOutputStream::write(const char *what)
{
if (what != nullptr) {
const char* gcode = what;
// writes string to file
fwrite(gcode, 1, ::strlen(gcode), this->f);
//FIXME don't allocate a string, maybe process a batch of lines?
m_processor.process_buffer(std::string(gcode));
std::string gcode(m_find_replace ? m_find_replace->process_layer(what) : what);
// writes string to file
fwrite(gcode.c_str(), 1, gcode.size(), this->f);
m_processor.process_buffer(gcode);
}
}

View File

@ -10,6 +10,7 @@
#include "PrintConfig.hpp"
#include "GCode/AvoidCrossingPerimeters.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/FindReplace.hpp"
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
@ -189,6 +190,11 @@ private:
GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {}
~GCodeOutputStream() { this->close(); }
// Set a find-replace post-processor to modify the G-code before GCodePostProcessor.
// It is being set to null inside process_layers(), because the find-replace process
// is being called on a secondary thread to improve performance.
void set_find_replace(GCodeFindReplace *find_replace) { m_find_replace = find_replace; }
bool is_open() const { return f; }
bool is_error() const;
@ -208,7 +214,9 @@ private:
void write_format(const char* format, ...);
private:
FILE *f = nullptr;
FILE *f { nullptr };
// Find-replace post-processor to be called before GCodePostProcessor.
GCodeFindReplace *m_find_replace { nullptr };
GCodeProcessor &m_processor;
};
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);
@ -393,6 +401,7 @@ private:
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<GCodeFindReplace> m_find_replace;
#ifdef HAS_PRESSURE_EQUALIZER
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
#endif /* HAS_PRESSURE_EQUALIZER */

View File

@ -439,7 +439,15 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
visitor.pt_current = &current_point;
if (!next.do_not_remove)
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size() && !travel[point_idx_2].do_not_remove; ++point_idx_2) {
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
// Workaround for some issue in MSVC 19.29.30037 32-bit compiler.
#if defined(_WIN32) && !defined(_WIN64)
if (bool volatile do_not_remove = travel[point_idx_2].do_not_remove; do_not_remove)
break;
#else
if (travel[point_idx_2].do_not_remove)
break;
#endif
if (travel[point_idx_2].point == current_point) {
next = travel[point_idx_2];
point_idx = point_idx_2;

View File

@ -0,0 +1,153 @@
#include "FindReplace.hpp"
#include "../Utils.hpp"
#include <cctype> // isalpha
namespace Slic3r {
// Similar to https://npp-user-manual.org/docs/searching/#extended-search-mode
const void unescape_extended_search_mode(std::string &s)
{
boost::replace_all(s, "\\n", "\n"); // Line Feed control character LF (ASCII 0x0A)
boost::replace_all(s, "\\r", "\r"); // Carriage Return control character CR (ASCII 0x0D)
boost::replace_all(s, "\\t", "\t"); // TAB control character (ASCII 0x09)
boost::replace_all(s, "\\0", "\0x00"); // NUL control character (ASCII 0x00)
boost::replace_all(s, "\\\\", "\\"); // Backslash character (ASCII 0x5C)
// Notepad++ also supports:
// \o: the octal representation of a byte, made of 3 digits in the 0-7 range
// \d: the decimal representation of a byte, made of 3 digits in the 0-9 range
// \x: the hexadecimal representation of a byte, made of 2 digits in the 0-9, A-F/a-f range.
// \u: The hexadecimal representation of a two-byte character, made of 4 digits in the 0-9, A-F/a-f range.
}
GCodeFindReplace::GCodeFindReplace(const std::vector<std::string> &gcode_substitutions)
{
if ((gcode_substitutions.size() % 3) != 0)
throw RuntimeError("Invalid length of gcode_substitutions parameter");
m_substitutions.reserve(gcode_substitutions.size() / 3);
for (size_t i = 0; i < gcode_substitutions.size(); i += 3) {
Substitution out;
try {
out.plain_pattern = gcode_substitutions[i];
out.format = gcode_substitutions[i + 1];
const std::string &params = gcode_substitutions[i + 2];
out.regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr;
out.case_insensitive = strchr(params.c_str(), 'i') != nullptr || strchr(params.c_str(), 'I') != nullptr;
out.whole_word = strchr(params.c_str(), 'w') != nullptr || strchr(params.c_str(), 'W') != nullptr;
if (out.regexp) {
out.regexp_pattern.assign(
out.whole_word ?
std::string("\\b") + out.plain_pattern + "\\b" :
out.plain_pattern,
(out.case_insensitive ? boost::regex::icase : 0) | boost::regex::optimize);
} else {
unescape_extended_search_mode(out.plain_pattern);
unescape_extended_search_mode(out.format);
}
} catch (const std::exception &ex) {
throw RuntimeError(std::string("Invalid gcode_substitutions parameter, failed to compile regular expression: ") + ex.what());
}
m_substitutions.emplace_back(std::move(out));
}
}
class ToStringIterator
{
public:
using iterator_category = std::output_iterator_tag;
using value_type = void;
using difference_type = void;
using pointer = void;
using reference = void;
ToStringIterator(std::string &data) : m_data(&data) {}
ToStringIterator& operator=(const char val) {
size_t needs = m_data->size() + 1;
if (m_data->capacity() < needs)
m_data->reserve(next_highest_power_of_2(needs));
m_data->push_back(val);
return *this;
}
ToStringIterator& operator*() { return *this; }
ToStringIterator& operator++() { return *this; }
ToStringIterator operator++(int) { return *this; }
private:
std::string *m_data;
};
template<typename FindFn>
static void find_and_replace_whole_word(std::string &inout, const std::string &match, const std::string &replace, FindFn find_fn)
{
if (! match.empty() && inout.size() >= match.size() && match != replace) {
std::string out;
auto [i, j] = find_fn(inout, 0, match);
size_t k = 0;
for (; i != std::string::npos; std::tie(i, j) = find_fn(inout, i, match)) {
if ((i == 0 || ! std::isalnum(inout[i - 1])) && (j == inout.size() || ! std::isalnum(inout[j]))) {
out.reserve(inout.size());
out.append(inout, k, i - k);
out.append(replace);
i = k = j;
} else
i += match.size();
}
if (k > 0) {
out.append(inout, k, inout.size() - k);
inout.swap(out);
}
}
}
std::string GCodeFindReplace::process_layer(const std::string &ain)
{
std::string out;
const std::string *in = &ain;
std::string temp;
temp.reserve(in->size());
for (const Substitution &substitution : m_substitutions) {
if (substitution.regexp) {
temp.clear();
temp.reserve(in->size());
boost::regex_replace(ToStringIterator(temp), in->begin(), in->end(),
substitution.regexp_pattern, substitution.format, boost::match_default | boost::match_not_dot_newline | boost::match_not_dot_null | boost::format_all);
std::swap(out, temp);
} else {
if (in == &ain)
out = ain;
// Plain substitution
if (substitution.case_insensitive) {
if (substitution.whole_word)
find_and_replace_whole_word(out, substitution.plain_pattern, substitution.format,
[](const std::string &str, size_t start_pos, const std::string &match) {
auto begin = str.begin() + start_pos;
boost::iterator_range<std::string::const_iterator> r1(begin, str.end());
boost::iterator_range<std::string::const_iterator> r2(match.begin(), match.end());
auto res = boost::ifind_first(r1, r2);
return res ? std::make_pair(size_t(res.begin() - str.begin()), size_t(res.end() - str.begin())) : std::make_pair(std::string::npos, std::string::npos);
});
else
boost::ireplace_all(out, substitution.plain_pattern, substitution.format);
} else {
if (substitution.whole_word)
find_and_replace_whole_word(out, substitution.plain_pattern, substitution.format,
[](const std::string &str, size_t start_pos, const std::string &match) {
size_t pos = str.find(match, start_pos);
return std::make_pair(pos, pos + (pos == std::string::npos ? 0 : match.size()));
});
else
boost::replace_all(out, substitution.plain_pattern, substitution.format);
}
}
in = &out;
}
return out;
}
}

View File

@ -0,0 +1,33 @@
#ifndef slic3r_FindReplace_hpp_
#define slic3r_FindReplace_hpp_
#include "../PrintConfig.hpp"
#include <boost/regex.hpp>
namespace Slic3r {
class GCodeFindReplace {
public:
GCodeFindReplace(const PrintConfig &print_config) : GCodeFindReplace(print_config.gcode_substitutions.values) {}
GCodeFindReplace(const std::vector<std::string> &gcode_substitutions);
std::string process_layer(const std::string &gcode);
private:
struct Substitution {
std::string plain_pattern;
boost::regex regexp_pattern;
std::string format;
bool regexp { false };
bool case_insensitive { false };
bool whole_word { false };
};
std::vector<Substitution> m_substitutions;
};
}
#endif // slic3r_FindReplace_hpp_

View File

@ -439,7 +439,7 @@ static std::vector<std::string> s_Preset_print_options {
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
"support_material_contact_distance", "support_material_bottom_contact_distance",
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
@ -1133,6 +1133,20 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys&
}
}
// list of options with vector variable, which is independent from number of extruders
static const std::vector<std::string> independent_from_extruder_number_options = {
"bed_shape",
"filament_ramming_parameters",
"gcode_substitutions",
"compatible_prints",
"compatible_printers"
};
bool PresetCollection::is_independent_from_extruder_number_option(const std::string& opt_key)
{
return std::find(independent_from_extruder_number_options.begin(), independent_from_extruder_number_options.end(), opt_key) != independent_from_extruder_number_options.end();
}
// Use deep_diff to correct return of changed options, considering individual options for each extruder.
inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other)
{
@ -1142,7 +1156,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
const ConfigOption *other_opt = config_other.option(opt_key);
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
{
if (opt_key == "bed_shape" || opt_key == "thumbnails" || opt_key == "compatible_prints" || opt_key == "compatible_printers") {
if (PresetCollection::is_independent_from_extruder_number_option(opt_key)) {
// Scalar variable, or a vector variable, which is independent from number of extruders,
// thus the vector is presented to the user as a single input.
diff.emplace_back(opt_key);

View File

@ -558,6 +558,7 @@ private:
public:
static bool is_dirty(const Preset *edited, const Preset *reference);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
static bool is_independent_from_extruder_number_option(const std::string& opt_key);
private:
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type;

View File

@ -110,6 +110,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"output_filename_format",
"perimeter_acceleration",
"post_process",
"gcode_substitutions",
"printer_notes",
"retract_before_travel",
"retract_before_wipe",

View File

@ -1357,6 +1357,12 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(0));
def = this->add("gcode_substitutions", coStrings);
def->label = L("G-code substitutions");
def->tooltip = L("Find / replace patterns in G-code lines and substitute them.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionStrings());
def = this->add("high_current_on_filament_swap", coBool);
def->label = L("High extruder current on filament swap");
def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange"

View File

@ -641,6 +641,12 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, gcode_comments))
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
((ConfigOptionBool, gcode_label_objects))
// Triples of strings: "search pattern", "replace with pattern", "attribs"
// where "attribs" are one of:
// r - regular expression
// i - case insensitive
// w - whole word
((ConfigOptionStrings, gcode_substitutions))
((ConfigOptionString, layer_gcode))
((ConfigOptionFloat, max_print_speed))
((ConfigOptionFloat, max_volumetric_speed))

View File

@ -60,6 +60,10 @@
#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1)
// Enable not killing focus in object manipulator fields when hovering over 3D scene
#define ENABLE_OBJECT_MANIPULATOR_FOCUS (1 && ENABLE_2_5_0_ALPHA1)
// Enable removal of wipe tower magic object_id equal to 1000
#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)
#endif // _prusaslicer_technologies_h_

View File

@ -669,9 +669,15 @@ void GLVolumeCollection::load_object_auxiliary(
}
}
#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, bool opengl_initialized)
#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, bool opengl_initialized)
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
{
if (depth < 0.01f)
return int(this->volumes.size() - 1);
@ -733,7 +739,11 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
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
v.composite_id = GLVolume::CompositeID(INT_MAX, 0, 0);
#else
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
v.geometry_id.first = 0;
v.geometry_id.second = wipe_tower_instance_id().id;
v.is_wipe_tower = true;

View File

@ -590,8 +590,13 @@ public:
size_t timestamp,
bool opengl_initialized);
#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, bool opengl_initialized);
#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, bool opengl_initialized);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
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);

View File

@ -2135,8 +2135,13 @@ 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_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);
#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, initialized);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
}
}

View File

@ -1123,11 +1123,18 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
{
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (current_printer_technology() != ptSLA)
return;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
m_render_sla_auxiliaries = visible;
for (GLVolume* vol : m_volumes.volumes) {
#if !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (vol->composite_id.object_id == 1000)
continue; // the wipe tower
#endif // !ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& vol->composite_id.volume_id < 0)
@ -1138,9 +1145,14 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv)
{
for (GLVolume* vol : m_volumes.volumes) {
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (vol->is_wipe_tower)
vol->is_active = (visible && mo == nullptr);
#else
if (vol->composite_id.object_id == 1000) { // wipe tower
vol->is_active = (visible && mo == nullptr);
}
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
else {
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
@ -1165,6 +1177,7 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
}
}
}
if (visible && !mo)
toggle_sla_auxiliaries_visibility(true, mo, instance_idx);
@ -1182,7 +1195,7 @@ void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx
ModelInstance* instance = model_object->instances[inst_idx];
for (GLVolume* volume : m_volumes.volumes) {
if ((volume->object_idx() == (int)obj_idx) && (volume->instance_idx() == inst_idx))
if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx)
volume->printable = instance->printable;
}
}
@ -2021,9 +2034,15 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
float depth = print->wipe_tower_data(extruders_count).depth;
float brim_width = print->wipe_tower_data(extruders_count).brim_width;
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
brim_width, m_initialized);
#else
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
brim_width, m_initialized);
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (volume_idx_wipe_tower_old != -1)
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
@ -3039,7 +3058,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
#if ENABLE_OBJECT_MANIPULATOR_FOCUS
if (evt.ButtonDown()) {
std::string curr_sidebar_field = m_sidebar_field;
handle_sidebar_focus_event("", false);
if (boost::algorithm::istarts_with(curr_sidebar_field, "layer"))
// restore visibility of layers hints after left clicking on the 3D scene
m_sidebar_field = curr_sidebar_field;
if (wxWindow::FindFocus() != m_canvas)
// Grab keyboard focus on any mouse click event.
m_canvas->SetFocus();
@ -3448,9 +3471,15 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
model_object->invalidate_bounding_box();
}
}
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
else if (v->is_wipe_tower)
// Move a wipe tower proxy.
wipe_tower_origin = v->get_volume_offset();
#else
else if (object_idx == 1000)
// Move a wipe tower proxy.
wipe_tower_origin = v->get_volume_offset();
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
}
// Fixes flying instances
@ -3510,11 +3539,18 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
Selection::EMode selection_mode = m_selection.get_mode();
for (const GLVolume* v : m_volumes.volumes) {
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (v->is_wipe_tower) {
#else
int object_idx = v->object_idx();
if (object_idx == 1000) { // the wipe tower
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
Vec3d offset = v->get_volume_offset();
post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2))));
}
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
int object_idx = v->object_idx();
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
continue;
@ -3708,7 +3744,6 @@ void GLCanvas3D::update_gizmos_on_off_state()
void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on)
{
m_sidebar_field = focus_on ? opt_key : "";
if (!m_sidebar_field.empty())
m_gizmos.reset_all_states();
@ -4852,19 +4887,31 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
// The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum
// A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any
if (include_gizmos && m_gizmos.is_running())
{
BoundingBoxf3 sel_bb = m_selection.get_bounding_box();
Vec3d sel_bb_center = sel_bb.center();
Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones();
if (include_gizmos && m_gizmos.is_running()) {
const BoundingBoxf3 sel_bb = m_selection.get_bounding_box();
const Vec3d sel_bb_center = sel_bb.center();
const Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones();
bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by));
}
bb.merge(include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume());
const BoundingBoxf3 bed_bb = include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume();
bb.merge(bed_bb);
if (!m_main_toolbar.is_enabled())
bb.merge(m_gcode_viewer.get_max_bounding_box());
// clamp max bb size with respect to bed bb size
static const double max_scale_factor = 1.5;
const Vec3d bb_size = bb.size();
const Vec3d bed_bb_size = bed_bb.size();
if (bb_size.x() > max_scale_factor * bed_bb_size.x() ||
bb_size.y() > max_scale_factor * bed_bb_size.y() ||
bb_size.z() > max_scale_factor * bed_bb_size.z()) {
const Vec3d bed_bb_center = bed_bb.center();
const Vec3d extend_by = max_scale_factor * bed_bb_size;
bb = BoundingBoxf3(bed_bb_center - extend_by, bed_bb_center + extend_by);
}
return bb;
}
@ -5208,7 +5255,11 @@ void GLCanvas3D::_render_gcode()
m_gcode_viewer.render();
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void GLCanvas3D::_render_selection()
#else
void GLCanvas3D::_render_selection() const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
float scale_factor = 1.0;
#if ENABLE_RETINA_GL
@ -5239,7 +5290,7 @@ void GLCanvas3D::_render_sequential_clearance()
}
#if ENABLE_RENDER_SELECTION_CENTER
void GLCanvas3D::_render_selection_center() const
void GLCanvas3D::_render_selection_center()
{
m_selection.render_center(m_gizmos.is_dragging());
}
@ -5626,7 +5677,11 @@ void GLCanvas3D::_render_sla_slices()
}
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void GLCanvas3D::_render_selection_sidebar_hints()
#else
void GLCanvas3D::_render_selection_sidebar_hints() const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
m_selection.render_sidebar_hints(m_sidebar_field);
}

View File

@ -915,10 +915,14 @@ private:
void _render_bed_for_picking(bool bottom);
void _render_objects(GLVolumeCollection::ERenderType type);
void _render_gcode();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void _render_selection();
#else
void _render_selection() const;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
void _render_sequential_clearance();
#if ENABLE_RENDER_SELECTION_CENTER
void _render_selection_center() const;
void _render_selection_center();
#endif // ENABLE_RENDER_SELECTION_CENTER
void _check_and_update_toolbar_icon_scale();
void _render_overlays();
@ -933,7 +937,11 @@ private:
void _render_camera_target() const;
#endif // ENABLE_SHOW_CAMERA_TARGET
void _render_sla_slices();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void _render_selection_sidebar_hints();
#else
void _render_selection_sidebar_hints() const;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
bool _render_undo_redo_stack(const bool is_undo, float pos_x);
bool _render_search_list(float pos_x);
bool _render_arrange_menu(float pos_x);

View File

@ -33,6 +33,10 @@ std::pair<bool, std::string> GLShadersManager::init()
bool valid = true;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
// basic shader, used to render selection bbox
valid &= append_shader("flat", { "flat.vs", "flat.fs" });
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
// 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

@ -143,7 +143,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
break;
case coStrings:{
if (opt_key == "compatible_prints" || opt_key == "compatible_printers") {
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "gcode_substitutions") {
config.option<ConfigOptionStrings>(opt_key)->values =
boost::any_cast<std::vector<std::string>>(value);
}

View File

@ -718,6 +718,12 @@ void MenuFactory::append_menu_item_export_stl(wxMenu* menu)
const Selection& selection = plater()->canvas3D()->get_selection();
return selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_single_volume() || selection.is_single_modifier();
}, m_parent);
append_menu_item(menu, wxID_ANY, _L("Export as OBJ") + dots, "",
[](wxCommandEvent&) { plater()->export_obj(false, true); }, "", nullptr,
[]() {
const Selection& selection = plater()->canvas3D()->get_selection();
return selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_single_volume() || selection.is_single_modifier();
}, m_parent);
menu->AppendSeparator();
}

View File

@ -1931,9 +1931,15 @@ void ObjectList::del_layers_from_object(const int obj_idx)
bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type)
{
assert(idx >= 0);
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (m_objects->empty() || int(m_objects->size()) <= obj_idx)
// Cannot delete a wipe tower
return false;
#else
if (obj_idx == 1000 || idx<0)
// Cannot delete a wipe tower or volume with negative id
return false;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
ModelObject* object = (*m_objects)[obj_idx];

View File

@ -102,6 +102,47 @@ void GLGizmoCut::on_render()
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
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) {
m_plane.reset();
GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles;
entity.positions.reserve(4);
entity.positions.emplace_back(Vec3f(min_x, min_y, plane_center.z()));
entity.positions.emplace_back(Vec3f(max_x, min_y, plane_center.z()));
entity.positions.emplace_back(Vec3f(max_x, max_y, plane_center.z()));
entity.positions.emplace_back(Vec3f(min_x, max_y, plane_center.z()));
entity.normals.reserve(4);
for (size_t i = 0; i < 4; ++i) {
entity.normals.emplace_back(Vec3f::UnitZ());
}
entity.indices.reserve(6);
entity.indices.emplace_back(0);
entity.indices.emplace_back(1);
entity.indices.emplace_back(2);
entity.indices.emplace_back(2);
entity.indices.emplace_back(3);
entity.indices.emplace_back(0);
init_data.entities.emplace_back(entity);
m_plane.init_from(init_data);
m_plane.set_color(-1, PLANE_COLOR);
}
m_plane.render();
#else
// Draw the cutting plane
::glBegin(GL_QUADS);
::glColor4fv(PLANE_COLOR.data());
@ -110,6 +151,7 @@ void GLGizmoCut::on_render()
::glVertex3f(max_x, max_y, plane_center.z());
::glVertex3f(min_x, max_y, plane_center.z());
glsafe(::glEnd());
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
@ -123,6 +165,37 @@ void GLGizmoCut::on_render()
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f));
#if ENABLE_GLBEGIN_GLEND_REMOVAL
if (!m_grabber_connection.is_initialized() || z_changed) {
m_grabber_connection.reset();
GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Lines;
entity.positions.reserve(2);
entity.positions.emplace_back(plane_center.cast<float>());
entity.positions.emplace_back(m_grabbers[0].center.cast<float>());
entity.normals.reserve(2);
for (size_t i = 0; i < 2; ++i) {
entity.normals.emplace_back(Vec3f::UnitZ());
}
entity.indices.reserve(2);
entity.indices.emplace_back(0);
entity.indices.emplace_back(1);
init_data.entities.emplace_back(entity);
m_grabber_connection.init_from(init_data);
m_grabber_connection.set_color(-1, ColorRGBA::YELLOW());
}
m_grabber_connection.render();
shader->stop_using();
shader = wxGetApp().get_shader("gouraud_light");
#else
glsafe(::glColor3f(1.0, 1.0, 0.0));
::glBegin(GL_LINES);
::glVertex3dv(plane_center.data());
@ -130,6 +203,8 @@ void GLGizmoCut::on_render()
glsafe(::glEnd());
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
if (shader == nullptr)
return;
shader->start_using();

View File

@ -22,6 +22,11 @@ class GLGizmoCut : public GLGizmoBase
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_rotate_lower{ false };
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLModel m_plane;
GLModel m_grabber_connection;
float m_old_z{ 0.0f };
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
struct CutContours
{

View File

@ -15,6 +15,7 @@
#include "libslic3r/Exception.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Preset.hpp"
#include "I18N.hpp"
namespace Slic3r { namespace GUI {
@ -596,8 +597,7 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
}
else if (m_opt_map.find(opt_key) == m_opt_map.end() ||
// This option don't have corresponded field
opt_key == "bed_shape" || opt_key == "filament_ramming_parameters" ||
opt_key == "compatible_printers" || opt_key == "compatible_prints" ) {
PresetCollection::is_independent_from_extruder_number_option(opt_key) ) {
value = get_config_value(config, opt_key);
this->change_opt_value(opt_key, value);
OptionsGroup::on_change_OG(opt_key, value);
@ -876,7 +876,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = from_u8(config.opt_string(opt_key));
break;
case coStrings:
if (opt_key == "compatible_printers" || opt_key == "compatible_prints") {
if (opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "gcode_substitutions") {
ret = config.option<ConfigOptionStrings>(opt_key)->values;
break;
}

View File

@ -37,6 +37,7 @@
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/Format/OBJ.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
@ -1245,7 +1246,11 @@ void Sidebar::show_info_sizer()
ModelObjectPtrs objects = p->plater->model().objects;
int obj_idx = selection.get_object_idx();
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (m_mode < comExpert || objects.empty() || obj_idx < 0 || int(objects.size()) <= obj_idx ||
#else
if (m_mode < comExpert || objects.empty() || obj_idx < 0 || obj_idx == 1000 ||
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
objects[obj_idx]->volumes.empty() || // hack to avoid crash when deleting the last object on the bed
(selection.is_single_full_object() && objects[obj_idx]->instances.size()> 1) ||
!(selection.is_single_full_instance() || selection.is_single_volume())) {
@ -2866,14 +2871,22 @@ Selection& Plater::priv::get_selection()
int Plater::priv::get_selected_object_idx() const
{
int idx = get_selection().get_object_idx();
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
return (0 <= idx && idx < int(model.objects.size())) ? idx : -1;
#else
return ((0 <= idx) && (idx < 1000)) ? idx : -1;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
}
int Plater::priv::get_selected_volume_idx() const
{
auto& selection = get_selection();
int idx = selection.get_object_idx();
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (idx < 0 || int(model.objects.size()) <= idx)
#else
if ((0 > idx) || (idx > 1000))
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
return-1;
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
if (model.objects[idx]->volumes.size() > 1)
@ -5843,7 +5856,7 @@ void Plater::export_stl(bool extended, bool selection_only)
inst_mesh.merge(inst_object_mesh);
// ensure that the instance lays on the bed
inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]);
inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z());
// merge instance with global mesh
mesh.merge(inst_mesh);
@ -5859,6 +5872,144 @@ void Plater::export_stl(bool extended, bool selection_only)
// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path));
}
void Plater::export_obj(bool extended, bool selection_only)
{
if (p->model.objects.empty()) { return; }
wxString path = p->get_export_file(FT_OBJ);
if (path.empty()) { return; }
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
const auto& selection = p->get_selection();
const auto obj_idx = selection.get_object_idx();
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
return;
// Following lambda generates a combined mesh for export with normals pointing outwards.
auto mesh_to_export = [](const ModelObject& mo, int instance_id) {
TriangleMesh mesh;
for (const ModelVolume* v : mo.volumes)
if (v->is_model_part()) {
TriangleMesh vol_mesh(v->mesh());
vol_mesh.transform(v->get_matrix(), true);
mesh.merge(vol_mesh);
}
if (instance_id == -1) {
TriangleMesh vols_mesh(mesh);
mesh = TriangleMesh();
for (const ModelInstance* i : mo.instances) {
TriangleMesh m = vols_mesh;
m.transform(i->get_matrix(), true);
mesh.merge(m);
}
}
else if (0 <= instance_id && instance_id < int(mo.instances.size()))
mesh.transform(mo.instances[instance_id]->get_matrix(), true);
return mesh;
};
TriangleMesh mesh;
if (p->printer_technology == ptFFF) {
if (selection_only) {
const ModelObject* model_object = p->model.objects[obj_idx];
if (selection.get_mode() == Selection::Instance)
mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx());
else {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
mesh = model_object->volumes[volume->volume_idx()]->mesh();
mesh.transform(volume->get_volume_transformation().get_matrix(), true);
}
if (!selection.is_single_full_object() || model_object->instances.size() == 1)
mesh.translate(-model_object->origin_translation.cast<float>());
}
else {
for (const ModelObject* o : p->model.objects) {
mesh.merge(mesh_to_export(*o, -1));
}
}
}
else {
// This is SLA mode, all objects have only one volume.
// However, we must have a look at the backend to load
// hollowed mesh and/or supports
const PrintObjects& objects = p->sla_print.objects();
for (const SLAPrintObject* object : objects) {
const ModelObject* model_object = object->model_object();
if (selection_only) {
if (model_object->id() != p->model.objects[obj_idx]->id())
continue;
}
const Transform3d mesh_trafo_inv = object->trafo().inverse();
const bool is_left_handed = object->is_left_handed();
TriangleMesh pad_mesh;
const bool has_pad_mesh = extended && object->has_mesh(slaposPad);
if (has_pad_mesh) {
pad_mesh = object->get_mesh(slaposPad);
pad_mesh.transform(mesh_trafo_inv);
}
TriangleMesh supports_mesh;
const bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree);
if (has_supports_mesh) {
supports_mesh = object->get_mesh(slaposSupportTree);
supports_mesh.transform(mesh_trafo_inv);
}
const std::vector<SLAPrintObject::Instance>& obj_instances = object->instances();
for (const SLAPrintObject::Instance& obj_instance : obj_instances) {
auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
[&obj_instance](const ModelInstance* mi) { return mi->id() == obj_instance.instance_id; });
assert(it != model_object->instances.end());
if (it != model_object->instances.end()) {
const bool one_inst_only = selection_only && !selection.is_single_full_object();
const int instance_idx = it - model_object->instances.begin();
const Transform3d& inst_transform = one_inst_only
? Transform3d::Identity()
: object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
TriangleMesh inst_mesh;
if (has_pad_mesh) {
TriangleMesh inst_pad_mesh = pad_mesh;
inst_pad_mesh.transform(inst_transform, is_left_handed);
inst_mesh.merge(inst_pad_mesh);
}
if (has_supports_mesh) {
TriangleMesh inst_supports_mesh = supports_mesh;
inst_supports_mesh.transform(inst_transform, is_left_handed);
inst_mesh.merge(inst_supports_mesh);
}
TriangleMesh inst_object_mesh = object->get_mesh_to_slice();
inst_object_mesh.transform(mesh_trafo_inv);
inst_object_mesh.transform(inst_transform, is_left_handed);
inst_mesh.merge(inst_object_mesh);
// ensure that the instance lays on the bed
inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z());
// merge instance with global mesh
mesh.merge(inst_mesh);
if (one_inst_only)
break;
}
}
}
}
Slic3r::store_obj(path_u8.c_str(), &mesh);
}
void Plater::export_amf()
{
if (p->model.objects.empty()) { return; }

View File

@ -257,6 +257,7 @@ public:
void export_gcode(bool prefer_removable);
void export_stl(bool extended = false, bool selection_only = false);
void export_obj(bool extended = false, bool selection_only = false);
void export_amf();
bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reload_from_disk();

View File

@ -76,7 +76,8 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
txt_filename->SetValue(recent_path);
txt_filename->SetFocus();
m_valid_suffix = recent_path.substr(recent_path.find_last_of('.'));
if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos)
m_valid_suffix = recent_path.substr(extension_start);
// .gcode suffix control
auto validate_path = [this](const wxString &path) -> bool {
if (! path.Lower().EndsWith(m_valid_suffix.Lower())) {

View File

@ -132,9 +132,8 @@ bool Selection::init()
{
m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f));
m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f));
#if ENABLE_RENDER_SELECTION_CENTER
m_vbo_sphere.init_from(make_sphere(0.75, 2*PI/24));
m_vbo_sphere.init_from(its_make_sphere(0.75, PI / 12.0));
#endif // ENABLE_RENDER_SELECTION_CENTER
return true;
@ -539,7 +538,11 @@ bool Selection::is_single_full_instance() const
bool Selection::is_from_single_object() const
{
const int idx = get_object_idx();
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
return 0 <= idx && idx < int(m_model->objects.size());
#else
return 0 <= idx && idx < 1000;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
}
bool Selection::is_sla_compliant() const
@ -1067,9 +1070,16 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement)
if (done.size() == m_volumes->size())
break;
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if ((*m_volumes)[i]->is_wipe_tower)
continue;
int object_idx = (*m_volumes)[i]->object_idx();
#else
int object_idx = (*m_volumes)[i]->object_idx();
if (object_idx >= 1000)
continue;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
// Process unselected volumes of the object.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
@ -1109,9 +1119,16 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co
if (done.size() == m_volumes->size())
break;
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if ((*m_volumes)[i]->is_wipe_tower)
continue;
int object_idx = (*m_volumes)[i]->object_idx();
#else
int object_idx = (*m_volumes)[i]->object_idx();
if (object_idx >= 1000)
continue;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
// Process unselected volumes of the object.
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
@ -1238,20 +1255,29 @@ void Selection::erase()
}
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render(float scale_factor)
#else
void Selection::render(float scale_factor) const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
if (!m_valid || is_empty())
return;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
m_scale_factor = scale_factor;
render_bounding_box(get_bounding_box(), ColorRGB::WHITE());
#else
*const_cast<float*>(&m_scale_factor) = scale_factor;
// render cumulative bounding box of selected volumes
render_selected_volumes();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
render_synchronized_volumes();
}
#if ENABLE_RENDER_SELECTION_CENTER
void Selection::render_center(bool gizmo_is_dragging) const
void Selection::render_center(bool gizmo_is_dragging)
{
if (!m_valid || is_empty())
return;
@ -1260,15 +1286,31 @@ void Selection::render_center(bool gizmo_is_dragging) const
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glColor3f(1.0f, 1.0f, 1.0f));
glsafe(::glPushMatrix());
glsafe(::glTranslated(center(0), center(1), center(2)));
glsafe(::glTranslated(center.x(), center.y(), center.z()));
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
m_vbo_sphere.set_color(-1, ColorRGBA::WHITE());
m_vbo_sphere.render();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
shader->stop_using();
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
glsafe(::glPopMatrix());
}
#endif // ENABLE_RENDER_SELECTION_CENTER
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_sidebar_hints(const std::string& sidebar_field)
#else
void Selection::render_sidebar_hints(const std::string& sidebar_field) const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
if (sidebar_field.empty())
return;
@ -1292,7 +1334,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
const Vec3d& center = get_bounding_box().center();
if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
glsafe(::glTranslated(center.x(), center.y(), center.z()));
if (!boost::starts_with(sidebar_field, "position")) {
Transform3d orient_matrix = Transform3d::Identity();
if (boost::starts_with(sidebar_field, "scale"))
@ -1312,7 +1354,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
glsafe(::glMultMatrixd(orient_matrix.data()));
}
} else if (is_single_volume() || is_single_modifier()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
glsafe(::glTranslated(center.x(), center.y(), center.z()));
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
if (!boost::starts_with(sidebar_field, "position"))
orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
@ -1780,50 +1822,172 @@ void Selection::do_remove_object(unsigned int object_idx)
}
}
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_selected_volumes() const
{
float color[3] = { 1.0f, 1.0f, 1.0f };
render_bounding_box(get_bounding_box(), color);
}
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_synchronized_volumes()
#else
void Selection::render_synchronized_volumes() const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
if (m_mode == Instance)
return;
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
float color[3] = { 1.0f, 1.0f, 0.0f };
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
int object_idx = volume->object_idx();
int volume_idx = volume->volume_idx();
const GLVolume& volume = *(*m_volumes)[i];
int object_idx = volume.object_idx();
int volume_idx = volume.volume_idx();
for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) {
if (i == j)
continue;
const GLVolume* v = (*m_volumes)[j];
if (v->object_idx() != object_idx || v->volume_idx() != volume_idx)
const GLVolume& v = *(*m_volumes)[j];
if (v.object_idx() != object_idx || v.volume_idx() != volume_idx)
continue;
render_bounding_box(v->transformed_convex_hull_bounding_box(), color);
#if ENABLE_GLBEGIN_GLEND_REMOVAL
render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW());
#else
render_bounding_box(v.transformed_convex_hull_bounding_box(), color);
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
}
}
void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) const
#if ENABLE_GLBEGIN_GLEND_REMOVAL
static bool is_approx(const Vec3d& v1, const Vec3d& v2)
{
for (int i = 0; i < 3; ++i) {
if (std::abs(v1[i] - v2[i]) > EPSILON)
return false;
}
return true;
}
void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color)
{
#else
void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const
{
if (color == nullptr)
return;
Vec3f b_min = box.min.cast<float>();
Vec3f b_max = box.max.cast<float>();
Vec3f size = 0.2f * box.size().cast<float>();
const Vec3f b_min = box.min.cast<float>();
const Vec3f b_max = box.max.cast<float>();
const Vec3f size = 0.2f * box.size().cast<float>();
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glColor3fv(color));
glsafe(::glLineWidth(2.0f * m_scale_factor));
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
const BoundingBoxf3& curr_box = m_box.get_bounding_box();
if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) {
m_box.reset();
const Vec3f b_min = box.min.cast<float>();
const Vec3f b_max = box.max.cast<float>();
const Vec3f size = 0.2f * box.size().cast<float>();
GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Lines;
entity.positions.reserve(48);
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_min.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_max.z()));
entity.positions.emplace_back(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z()));
entity.normals.reserve(48);
for (size_t i = 0; i < 48; ++i) {
entity.normals.emplace_back(Vec3f::UnitZ());
}
entity.indices.reserve(48);
for (size_t i = 0; i < 48; ++i) {
entity.indices.emplace_back(i);
}
init_data.entities.emplace_back(entity);
m_box.init_from(init_data);
}
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glColor3fv(color));
glsafe(::glLineWidth(2.0f * m_scale_factor));
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
shader->start_using();
m_box.set_color(-1, to_rgba(color));
m_box.render();
shader->stop_using();
#else
::glBegin(GL_LINES);
::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2));
@ -1859,6 +2023,7 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons
::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2));
glsafe(::glEnd());
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
}
static ColorRGBA get_color(Axis axis)
@ -1866,6 +2031,25 @@ static ColorRGBA get_color(Axis axis)
return AXES_COLOR[axis];
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_sidebar_position_hints(const std::string& sidebar_field)
{
if (boost::ends_with(sidebar_field, "x")) {
glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0));
m_arrow.set_color(-1, get_color(X));
m_arrow.render();
}
else if (boost::ends_with(sidebar_field, "y")) {
m_arrow.set_color(-1, get_color(Y));
m_arrow.render();
}
else if (boost::ends_with(sidebar_field, "z")) {
glsafe(::glRotated(90.0, 1.0, 0.0, 0.0));
m_arrow.set_color(-1, get_color(Z));
m_arrow.render();
}
}
#else
void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const
{
if (boost::ends_with(sidebar_field, "x")) {
@ -1883,7 +2067,33 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field)
m_arrow.render();
}
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field)
{
auto render_sidebar_rotation_hint = [this]() {
m_curved_arrow.render();
glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
m_curved_arrow.render();
};
if (boost::ends_with(sidebar_field, "x")) {
glsafe(::glRotated(90.0, 0.0, 1.0, 0.0));
m_curved_arrow.set_color(-1, get_color(X));
render_sidebar_rotation_hint();
}
else if (boost::ends_with(sidebar_field, "y")) {
glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0));
m_curved_arrow.set_color(-1, get_color(Y));
render_sidebar_rotation_hint();
}
else if (boost::ends_with(sidebar_field, "z")) {
m_curved_arrow.set_color(-1, get_color(Z));
render_sidebar_rotation_hint();
}
}
#else
void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const
{
auto render_sidebar_rotation_hint = [this]() {
@ -1907,13 +2117,22 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field)
render_sidebar_rotation_hint();
}
}
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_sidebar_scale_hints(const std::string& sidebar_field)
#else
void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling();
auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) {
#if ENABLE_GLBEGIN_GLEND_REMOVAL
m_arrow.set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis));
#else
const_cast<GLModel*>(&m_arrow)->set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis));
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader != nullptr)
shader->set_uniform("emission_factor", 0.0f);
@ -1947,9 +2166,13 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con
}
}
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void Selection::render_sidebar_layers_hints(const std::string& sidebar_field)
#else
void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
{
static const double Margin = 10.0;
static const float Margin = 10.0f;
std::string field = sidebar_field;
@ -1958,7 +2181,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
if (pos == std::string::npos)
return;
double max_z = string_to_double_decimal_point(field.substr(pos + 1));
const float max_z = float(string_to_double_decimal_point(field.substr(pos + 1)));
// extract min_z
field = field.substr(0, pos);
@ -1966,7 +2189,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
if (pos == std::string::npos)
return;
const double min_z = string_to_double_decimal_point(field.substr(pos + 1));
const float min_z = float(string_to_double_decimal_point(field.substr(pos + 1)));
// extract type
field = field.substr(0, pos);
@ -1978,24 +2201,101 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
const BoundingBoxf3& box = get_bounding_box();
const float min_x = box.min(0) - Margin;
const float max_x = box.max(0) + Margin;
const float min_y = box.min(1) - Margin;
const float max_y = box.max(1) + Margin;
#if !ENABLE_GLBEGIN_GLEND_REMOVAL
const float min_x = float(box.min.x()) - Margin;
const float max_x = float(box.max.x()) + Margin;
const float min_y = float(box.min.y()) - Margin;
const float max_y = float(box.max.y()) + Margin;
#endif // !ENABLE_GLBEGIN_GLEND_REMOVAL
// view dependend order of rendering to keep correct transparency
bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward();
const bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward();
const float z1 = camera_on_top ? min_z : max_z;
const float z2 = camera_on_top ? max_z : min_z;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
const Vec3f p1 = { float(box.min.x()) - Margin, float(box.min.y()) - Margin, z1 };
const Vec3f p2 = { float(box.max.x()) + Margin, float(box.max.y()) + Margin, z2 };
#else
const float min_x = float(box.min.x()) - Margin;
const float max_x = float(box.max.x()) + Margin;
const float min_y = float(box.min.y()) - Margin;
const float max_y = float(box.max.y()) + Margin;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
#if ENABLE_GLBEGIN_GLEND_REMOVAL
if (!m_planes.models[0].is_initialized() || !is_approx(m_planes.check_points[0].cast<double>(), p1.cast<double>())) {
m_planes.check_points[0] = p1;
m_planes.models[0].reset();
GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles;
entity.positions.reserve(4);
entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z1));
entity.positions.emplace_back(Vec3f(p2.x(), p1.y(), z1));
entity.positions.emplace_back(Vec3f(p2.x(), p2.y(), z1));
entity.positions.emplace_back(Vec3f(p1.x(), p2.y(), z1));
entity.normals.reserve(4);
for (size_t i = 0; i < 4; ++i) {
entity.normals.emplace_back(Vec3f::UnitZ());
}
entity.indices.reserve(6);
entity.indices.emplace_back(0);
entity.indices.emplace_back(1);
entity.indices.emplace_back(2);
entity.indices.emplace_back(2);
entity.indices.emplace_back(3);
entity.indices.emplace_back(0);
init_data.entities.emplace_back(entity);
m_planes.models[0].init_from(init_data);
}
if (!m_planes.models[1].is_initialized() || !is_approx(m_planes.check_points[1].cast<double>(), p2.cast<double>())) {
m_planes.check_points[1] = p2;
m_planes.models[1].reset();
GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles;
entity.positions.reserve(4);
entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z2));
entity.positions.emplace_back(Vec3f(p2.x(), p1.y(), z2));
entity.positions.emplace_back(Vec3f(p2.x(), p2.y(), z2));
entity.positions.emplace_back(Vec3f(p1.x(), p2.y(), z2));
entity.normals.reserve(4);
for (size_t i = 0; i < 4; ++i) {
entity.normals.emplace_back(Vec3f::UnitZ());
}
entity.indices.reserve(6);
entity.indices.emplace_back(0);
entity.indices.emplace_back(1);
entity.indices.emplace_back(2);
entity.indices.emplace_back(2);
entity.indices.emplace_back(3);
entity.indices.emplace_back(0);
init_data.entities.emplace_back(entity);
m_planes.models[1].init_from(init_data);
}
m_planes.models[0].set_color(-1, (camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR);
m_planes.models[0].render();
m_planes.models[1].set_color(-1, (camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR);
m_planes.models[1].render();
#else
::glBegin(GL_QUADS);
::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ?
SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data());
::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data());
::glVertex3f(min_x, min_y, z1);
::glVertex3f(max_x, min_y, z1);
::glVertex3f(max_x, max_y, z1);
@ -2003,13 +2303,13 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
glsafe(::glEnd());
::glBegin(GL_QUADS);
::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ?
SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data());
::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data());
::glVertex3f(min_x, min_y, z2);
::glVertex3f(max_x, min_y, z2);
::glVertex3f(max_x, max_y, z2);
::glVertex3f(min_x, max_y, z2);
glsafe(::glEnd());
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
@ -2062,9 +2362,16 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
break;
const GLVolume* volume = (*m_volumes)[i];
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (volume->is_wipe_tower)
continue;
const int object_idx = volume->object_idx();
#else
const int object_idx = volume->object_idx();
if (object_idx >= 1000)
continue;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
const int instance_idx = volume->instance_idx();
const Vec3d& rotation = volume->get_instance_rotation();
@ -2116,9 +2423,16 @@ void Selection::synchronize_unselected_volumes()
{
for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
if (volume->is_wipe_tower)
continue;
const int object_idx = volume->object_idx();
#else
const int object_idx = volume->object_idx();
if (object_idx >= 1000)
continue;
#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL
const int volume_idx = volume->volume_idx();
const Vec3d& offset = volume->get_volume_offset();

View File

@ -218,6 +218,15 @@ private:
GLModel m_arrow;
GLModel m_curved_arrow;
#if ENABLE_GLBEGIN_GLEND_REMOVAL
GLModel m_box;
struct Planes
{
std::array<Vec3f, 2> check_points{ Vec3f::Zero(), Vec3f::Zero() };
std::array<GLModel, 2> models;
};
Planes m_planes;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
float m_scale_factor;
bool m_dragging;
@ -329,11 +338,16 @@ public:
void erase();
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void render(float scale_factor = 1.0);
void render_sidebar_hints(const std::string& sidebar_field);
#else
void render(float scale_factor = 1.0) const;
#if ENABLE_RENDER_SELECTION_CENTER
void render_center(bool gizmo_is_dragging) const;
#endif // ENABLE_RENDER_SELECTION_CENTER
void render_sidebar_hints(const std::string& sidebar_field) const;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
#if ENABLE_RENDER_SELECTION_CENTER
void render_center(bool gizmo_is_dragging);
#endif // ENABLE_RENDER_SELECTION_CENTER
bool requires_local_axes() const;
@ -363,6 +377,14 @@ private:
void do_remove_instance(unsigned int object_idx, unsigned int instance_idx);
void do_remove_object(unsigned int object_idx);
void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); }
#if ENABLE_GLBEGIN_GLEND_REMOVAL
void render_synchronized_volumes();
void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color);
void render_sidebar_position_hints(const std::string& sidebar_field);
void render_sidebar_rotation_hints(const std::string& sidebar_field);
void render_sidebar_scale_hints(const std::string& sidebar_field);
void render_sidebar_layers_hints(const std::string& sidebar_field);
#else
void render_selected_volumes() const;
void render_synchronized_volumes() const;
void render_bounding_box(const BoundingBoxf3& box, float* color) const;
@ -370,6 +392,7 @@ private:
void render_sidebar_rotation_hints(const std::string& sidebar_field) const;
void render_sidebar_scale_hints(const std::string& sidebar_field) const;
void render_sidebar_layers_hints(const std::string& sidebar_field) const;
#endif // ENABLE_GLBEGIN_GLEND_REMOVAL
public:
enum SyncRotationType {

View File

@ -479,8 +479,7 @@ void Tab::update_label_colours()
else
color = &m_modified_label_clr;
}
if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" ||
opt.first == "compatible_prints" || opt.first == "compatible_printers" ) {
if (PresetCollection::is_independent_from_extruder_number_option(opt.first)) {
if (Line* line = get_line(opt.first))
line->set_label_colour(color);
continue;
@ -521,8 +520,7 @@ void Tab::decorate()
Field* field = nullptr;
bool option_without_field = false;
if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" ||
opt.first == "compatible_prints" || opt.first == "compatible_printers")
if(PresetCollection::is_independent_from_extruder_number_option(opt.first))
option_without_field = true;
if (!option_without_field) {
@ -812,6 +810,12 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/)
if ((m_options_list["filament_ramming_parameters"] & os) == 0)
to_sys ? group->back_to_sys_value("filament_ramming_parameters") : group->back_to_initial_value("filament_ramming_parameters");
}
if (group->title == "G-code Substitutions") {
if ((m_options_list["gcode_substitutions"] & os) == 0) {
to_sys ? group->back_to_sys_value("gcode_substitutions") : group->back_to_initial_value("gcode_substitutions");
load_key_value("gcode_substitutions", true/*some value*/, true);
}
}
if (group->title == "Profile dependencies") {
// "compatible_printers" option doesn't exists in Printer Settimgs Tab
if (m_type != Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) {
@ -906,7 +910,7 @@ void Tab::update_visibility()
page->update_visibility(m_mode, page.get() == m_active_page);
rebuild_page_tree();
if (m_type == Preset::TYPE_SLA_PRINT)
if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT)
update_description_lines();
Layout();
@ -1655,6 +1659,18 @@ void TabPrint::build()
option.opt.height = 5;//50;
optgroup->append_single_option_line(option);
optgroup = page->new_optgroup(L("G-code Substitutions"));
create_line_with_widget(optgroup.get(), "gcode_substitutions", "", [this](wxWindow* parent) {
return create_manage_substitution_widget(parent);
});
line = { "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
return create_substitutions_widget(parent);
};
optgroup->append_line(line);
page = add_options_page(L("Notes"), "note.png");
optgroup = page->new_optgroup(L("Notes"), 0);
option = optgroup->get_option("notes");
@ -1699,13 +1715,21 @@ void TabPrint::update_description_lines()
from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle)));
}
if (m_active_page && m_active_page->title() == "Output options" && m_post_process_explanation) {
if (m_active_page && m_active_page->title() == "Output options") {
if (m_post_process_explanation) {
m_post_process_explanation->SetText(
_L("Post processing scripts shall modify G-code file in place."));
#ifndef __linux__
m_post_process_explanation->SetPathEnd("post-processing-scripts_283913");
#endif // __linux__
}
// upadte G-code substitutions from the current configuration
{
m_subst_manager.update_from_config();
if (m_del_all_substitutions_btn)
m_del_all_substitutions_btn->Show(!m_subst_manager.is_empty_substitutions());
}
}
}
void TabPrint::toggle_options()
@ -3824,6 +3848,244 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep
return sizer;
}
// G-code substitutions
void SubstitutionManager::init(DynamicPrintConfig* config, wxWindow* parent, wxFlexGridSizer* grid_sizer)
{
m_config = config;
m_parent = parent;
m_grid_sizer = grid_sizer;
m_em = em_unit(parent);
}
void SubstitutionManager::create_legend()
{
if (!m_grid_sizer->IsEmpty())
return;
// name of the first column is empty
m_grid_sizer->Add(new wxStaticText(m_parent, wxID_ANY, wxEmptyString));
// Legend for another columns
for (const std::string col : { L("Plain pattern"), L("Format"), L("Params") }) {
auto temp = new wxStaticText(m_parent, wxID_ANY, _(col), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_MIDDLE);
// temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
m_grid_sizer->Add(temp);
}
}
// delete substitution_id from substitutions
void SubstitutionManager::delete_substitution(int substitution_id)
{
// delete substitution
std::vector<std::string>& substitutions = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
if ((substitutions.size() % 3) != 0)
throw RuntimeError("Invalid length of gcode_substitutions parameter");
if ((substitutions.size() / 3) < substitution_id)
throw RuntimeError("Invalid substitution_id to delete");
substitutions.erase(std::next(substitutions.begin(), substitution_id * 3), std::next(substitutions.begin(), substitution_id * 3 + 3));
call_ui_update();
// update grid_sizer
update_from_config();
}
// Add substitution line
void SubstitutionManager::add_substitution(int substitution_id, const std::string& plain_pattern, const std::string& format, const std::string& params)
{
bool call_after_layout = false;
if (substitution_id < 0) {
if (m_grid_sizer->IsEmpty()) {
create_legend();
substitution_id = 0;
}
substitution_id = m_grid_sizer->GetEffectiveRowsCount() - 1;
// create new substitution
// it have to be added toconfig too
std::vector<std::string>& substitutions = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
for (size_t i = 0; i < 3; i ++)
substitutions.push_back(std::string());
call_after_layout = true;
}
auto del_btn = new ScalableButton(m_parent, wxID_ANY, "cross");
del_btn->Bind(wxEVT_BUTTON, [substitution_id, this](wxEvent&) {
delete_substitution(substitution_id);
});
m_grid_sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, m_em);
auto add_text_editor = [substitution_id, this](const wxString& value, int opt_pos) {
auto editor = new wxTextCtrl(m_parent, wxID_ANY, value, wxDefaultPosition, wxSize(15 * m_em, wxDefaultCoord), wxTE_PROCESS_ENTER
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
);
editor->SetFont(wxGetApp().normal_font());
wxGetApp().UpdateDarkUI(editor);
m_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL);
editor->Bind(wxEVT_TEXT_ENTER, [this, editor, substitution_id, opt_pos](wxEvent& e) {
#if !defined(__WXGTK__)
e.Skip();
#endif // __WXGTK__
edit_substitution(substitution_id, opt_pos, into_u8(editor->GetValue()));
});
editor->Bind(wxEVT_KILL_FOCUS, [this, editor, substitution_id, opt_pos](wxEvent& e) {
e.Skip();
edit_substitution(substitution_id, opt_pos, into_u8(editor->GetValue()));
});
};
add_text_editor(from_u8(plain_pattern), 0);
add_text_editor(from_u8(format), 1);
auto params_sizer = new wxBoxSizer(wxHORIZONTAL);
bool regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr;
bool case_insensitive = strchr(params.c_str(), 'i') != nullptr || strchr(params.c_str(), 'I') != nullptr;
bool whole_word = strchr(params.c_str(), 'w') != nullptr || strchr(params.c_str(), 'W') != nullptr;
auto chb_regexp = new wxCheckBox(m_parent, wxID_ANY, wxString("Regular expression"));
chb_regexp->SetValue(regexp);
params_sizer->Add(chb_regexp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, m_em);
auto chb_case_insensitive = new wxCheckBox(m_parent, wxID_ANY, wxString("Case insensitive"));
chb_case_insensitive->SetValue(case_insensitive);
params_sizer->Add(chb_case_insensitive, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, m_em);
auto chb_whole_word = new wxCheckBox(m_parent, wxID_ANY, wxString("Whole word"));
chb_whole_word->SetValue(whole_word);
params_sizer->Add(chb_whole_word, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, m_em);
for (wxCheckBox* chb : std::initializer_list<wxCheckBox*>{ chb_regexp, chb_case_insensitive, chb_whole_word }) {
chb->SetFont(wxGetApp().normal_font());
chb->Bind(wxEVT_CHECKBOX, [this, substitution_id, chb_regexp, chb_case_insensitive, chb_whole_word](wxCommandEvent e) {
std::string value = std::string();
if (chb_regexp->GetValue())
value += "r";
if (chb_case_insensitive->GetValue())
value += "i";
if (chb_whole_word->GetValue())
value += "w";
edit_substitution(substitution_id, 2, value);
});
}
m_grid_sizer->Add(params_sizer);
if (call_after_layout) {
m_parent->GetParent()->Layout();
call_ui_update();
}
}
void SubstitutionManager::update_from_config()
{
if (!m_grid_sizer->IsEmpty())
m_grid_sizer->Clear(true);
std::vector<std::string>& subst = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
if (!subst.empty())
create_legend();
if ((subst.size() % 3) != 0)
throw RuntimeError("Invalid length of gcode_substitutions parameter");
int subst_id = 0;
for (size_t i = 0; i < subst.size(); i += 3)
add_substitution(subst_id++, subst[i], subst[i + 1], subst[i + 2]);
m_parent->GetParent()->Layout();
}
void SubstitutionManager::delete_all()
{
m_config->option<ConfigOptionStrings>("gcode_substitutions")->values.clear();
call_ui_update();
if (!m_grid_sizer->IsEmpty())
m_grid_sizer->Clear(true);
m_parent->GetParent()->Layout();
}
void SubstitutionManager::edit_substitution(int substitution_id, int opt_pos, const std::string& value)
{
std::vector<std::string>& substitutions = m_config->option<ConfigOptionStrings>("gcode_substitutions")->values;
if ((substitutions.size() % 3) != 0)
throw RuntimeError("Invalid length of gcode_substitutions parameter");
if ((substitutions.size() / 3) != m_grid_sizer->GetEffectiveRowsCount()-1)
throw RuntimeError("Invalid compatibility between UI and BE");
if ((substitutions.size() / 3) < substitution_id)
throw RuntimeError("Invalid substitution_id to edit");
substitutions[substitution_id * 3 + opt_pos] = value;
call_ui_update();
}
bool SubstitutionManager::is_empty_substitutions()
{
return m_config->option<ConfigOptionStrings>("gcode_substitutions")->values.empty();
}
// Return a callback to create a TabPrint widget to edit G-code substitutions
wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent)
{
auto create_btn = [parent](ScalableButton** btn, const wxString& label, const std::string& icon_name) {
*btn = new ScalableButton(parent, wxID_ANY, icon_name, " " + label + " ", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true);
(*btn)->SetFont(wxGetApp().normal_font());
(*btn)->SetSize((*btn)->GetBestSize());
};
ScalableButton* add_substitution_btn;
create_btn(&add_substitution_btn, _L("Add G-code substitution"), "add_copies");
add_substitution_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
m_subst_manager.add_substitution();
m_del_all_substitutions_btn->Show();
});
create_btn(&m_del_all_substitutions_btn, _L("Delete all G-code substitution"), "cross");
m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
m_subst_manager.delete_all();
m_del_all_substitutions_btn->Hide();
});
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(add_substitution_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, em_unit(parent));
sizer->Add(m_del_all_substitutions_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, em_unit(parent));
parent->GetParent()->Layout();
return sizer;
}
// Return a callback to create a TabPrint widget to edit G-code substitutions
wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent)
{
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(4, 5, wxGetApp().em_unit()); // delete_button, "Old val", "New val", "Params"
grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
m_subst_manager.init(m_config, parent, grid_sizer);
m_subst_manager.set_cb_edited_substitution([this]() {
update_dirty();
wxGetApp().mainframe->on_config_changed(m_config); // invalidate print
});
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(grid_sizer, 0, wxALIGN_CENTER_VERTICAL);
parent->GetParent()->Layout();
return sizer;
}
// Return a callback to create a TabPrinter widget to edit bed shape
wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent)
{

View File

@ -43,6 +43,44 @@ namespace GUI {
class TabPresetComboBox;
class OG_CustomCtrl;
// G-code substitutions
// Substitution Manager - helper for manipuation of the substitutions
class SubstitutionManager
{
DynamicPrintConfig* m_config{ nullptr };
wxWindow* m_parent{ nullptr };
wxFlexGridSizer* m_grid_sizer{ nullptr };
int m_em{10};
std::function<void()> m_cb_edited_substitution{ nullptr };
public:
SubstitutionManager() {};
~SubstitutionManager() {};
void init(DynamicPrintConfig* config, wxWindow* parent, wxFlexGridSizer* grid_sizer);
void create_legend();
void delete_substitution(int substitution_id);
void add_substitution( int substitution_id = -1,
const std::string& plain_pattern = std::string(),
const std::string& format = std::string(),
const std::string& params = std::string());
void update_from_config();
void delete_all();
void edit_substitution(int substitution_id,
int opt_pos, // option position insubstitution [0, 2]
const std::string& value);
void set_cb_edited_substitution(std::function<void()> cb_edited_substitution) {
m_cb_edited_substitution = cb_edited_substitution;
}
void call_ui_update() {
if (m_cb_edited_substitution)
m_cb_edited_substitution();
}
bool is_empty_substitutions();
};
// Single Tab page containing a{ vsizer } of{ optgroups }
// package Slic3r::GUI::Tab::Page;
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
@ -368,11 +406,15 @@ public:
void update() override;
void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
wxSizer* create_manage_substitution_widget(wxWindow* parent);
wxSizer* create_substitutions_widget(wxWindow* parent);
private:
ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr;
ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr;
ogStaticText* m_post_process_explanation = nullptr;
ScalableButton* m_del_all_substitutions_btn{nullptr};
SubstitutionManager m_subst_manager;
};
class TabFilament : public Tab

View File

@ -1156,6 +1156,14 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig&
out.RemoveLast(1);
return out;
}
if (opt_key == "gcode_substitutions") {
if (!strings->empty())
for (size_t id = 0; id < strings->size(); id += 3)
out += from_u8(strings->get_at(id)) + ";\t" +
from_u8(strings->get_at(id + 1)) + ";\t" +
from_u8(strings->get_at(id + 2)) + ";\n";
return out;
}
if (!strings->empty() && opt_idx < strings->values.size())
return from_u8(strings->get_at(opt_idx));
}

View File

@ -7,6 +7,7 @@ add_executable(${_TEST_NAME}_tests
test_fill.cpp
test_flow.cpp
test_gcode.cpp
test_gcodefindreplace.cpp
test_gcodewriter.cpp
test_model.cpp
test_print.cpp

View File

@ -0,0 +1,218 @@
#include <catch2/catch.hpp>
#include <memory>
#include "libslic3r/GCode/FindReplace.hpp"
using namespace Slic3r;
SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") {
GIVEN("G-code") {
const std::string gcode =
"G1 Z0; home\n"
"G1 Z1; move up\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n";
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
GCodeFindReplace find_replace({ "move up", "move down", "" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move up\" with \"move down\", case insensitive") {
GCodeFindReplace find_replace({ "move up", "move down", "i" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move UP\" with \"move down\", case insensitive") {
GCodeFindReplace find_replace({ "move UP", "move down", "i" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
GCodeFindReplace find_replace({ "move UP", "move down", "" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
// Whole word
WHEN("Replace \"move up\" with \"move down\", whole word") {
GCodeFindReplace find_replace({ "move up", "move down", "w" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move u\" with \"move down\", whole word") {
GCodeFindReplace find_replace({ "move u", "move down", "w" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
WHEN("Replace \"ove up\" with \"move down\", whole word") {
GCodeFindReplace find_replace({ "move u", "move down", "w" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
// Multi-line replace
WHEN("Replace \"move up\\nG1 X0 \" with \"move down\\nG0 X1 \"") {
GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G0 X1 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
// Multi-line replace, whole word.
WHEN("Replace \"move up\\nG1 X0\" with \"move down\\nG0 X1\", whole word") {
GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "w" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G0 X1 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
// Multi-line replace, whole word, fails.
WHEN("Replace \"move up\\nG1 X\" with \"move down\\nG0 X\", whole word") {
GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "w" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
}
GIVEN("G-code with decimals") {
const std::string gcode =
"G1 Z0.123; home\n"
"G1 Z1.21; move up\n"
"G1 X0 Y.33 Z.431 E1.2; perimeter\n";
WHEN("Regular expression NOT processed in non-regex mode") {
GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
}
}
SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") {
GIVEN("G-code") {
const std::string gcode =
"G1 Z0; home\n"
"G1 Z1; move up\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n";
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
GCodeFindReplace find_replace({ "move up", "move down", "r" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move up\" with \"move down\", case insensitive") {
GCodeFindReplace find_replace({ "move up", "move down", "ri" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move UP\" with \"move down\", case insensitive") {
GCodeFindReplace find_replace({ "move UP", "move down", "ri" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move up\" with \"move down\", case sensitive") {
GCodeFindReplace find_replace({ "move UP", "move down", "r" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
// Whole word
WHEN("Replace \"move up\" with \"move down\", whole word") {
GCodeFindReplace find_replace({ "move up", "move down", "rw" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G1 X0 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
WHEN("Replace \"move u\" with \"move down\", whole word") {
GCodeFindReplace find_replace({ "move u", "move down", "rw" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
WHEN("Replace \"ove up\" with \"move down\", whole word") {
GCodeFindReplace find_replace({ "move u", "move down", "rw" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
// Multi-line replace
WHEN("Replace \"move up\\nG1 X0 \" with \"move down\\nG0 X1 \"") {
GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "r" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G0 X1 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
// Multi-line replace, whole word.
WHEN("Replace \"move up\\nG1 X0\" with \"move down\\nG0 X1\", whole word") {
GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "rw" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0; home\n"
// substituted
"G1 Z1; move down\n"
"G0 X1 Y1 Z1; perimeter\n"
"G1 X13 Y32 Z1; infill\n"
"G1 X13 Y32 Z1; wipe\n");
}
// Multi-line replace, whole word, fails.
WHEN("Replace \"move up\\nG1 X\" with \"move down\\nG0 X\", whole word") {
GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "rw" });
REQUIRE(find_replace.process_layer(gcode) == gcode);
}
}
GIVEN("G-code with decimals") {
const std::string gcode =
"G1 Z0.123; home\n"
"G1 Z1.21; move up\n"
"G1 X0 Y.33 Z.431 E1.2; perimeter\n";
WHEN("Missing zeros before dot filled in") {
GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "r" });
REQUIRE(find_replace.process_layer(gcode) ==
"G1 Z0.123; home\n"
"G1 Z1.21; move up\n"
"G1 X0 Y0.33 Z0.431 E1.2; perimeter\n");
}
}
}

View File

@ -95,26 +95,28 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri};
std::array<bool, 4> jobres = {false};
std::array<bool, 4> jobres = {false, false, false, false};
queue_job(worker, [&jobres](Job::Ctl &) {
jobres[0] = true;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// FIXME: the long wait time is needed to prevent fail in MSVC
// where the sleep_for function is behaving stupidly.
// see https://developercommunity.visualstudio.com/t/bogus-stdthis-threadsleep-for-implementation/58530
std::this_thread::sleep_for(std::chrono::seconds(1));
});
queue_job(worker, [&jobres](Job::Ctl &) {
jobres[1] = true;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
});
queue_job(worker, [&jobres](Job::Ctl &) {
jobres[2] = true;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
});
queue_job(worker, [&jobres](Job::Ctl &) {
jobres[3] = true;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
});
std::this_thread::sleep_for(std::chrono::microseconds(500));
// wait until the first job's half time to be sure, the cancel is made
// during the first job's execution.
std::this_thread::sleep_for(std::chrono::milliseconds(500));
worker.cancel_all();
REQUIRE(jobres[0] == true);