Merge remote-tracking branch 'origin/master' into ys_resin_cost

This commit is contained in:
YuSanka 2019-11-08 19:33:18 +01:00
commit 49175c3112
306 changed files with 91525 additions and 9504 deletions

View file

@ -144,6 +144,8 @@ set(SLIC3R_GUI_SOURCES
Utils/OctoPrint.hpp
Utils/Duet.cpp
Utils/Duet.hpp
Utils/FlashAir.cpp
Utils/FlashAir.hpp
Utils/PrintHost.cpp
Utils/PrintHost.hpp
Utils/Bonjour.cpp
@ -154,6 +156,7 @@ set(SLIC3R_GUI_SOURCES
Utils/UndoRedo.hpp
Utils/HexFile.cpp
Utils/HexFile.hpp
Utils/Thread.hpp
)
if (APPLE)

View file

@ -66,7 +66,7 @@ void Snapshot::load_ini(const std::string &path)
if (kvp.first == "id")
this->id = kvp.second.data();
else if (kvp.first == "time_captured") {
this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data());
this->time_captured = Slic3r::Utils::parse_iso_utc_timestamp(kvp.second.data());
if (this->time_captured == (time_t)-1)
throw_on_parse_error("invalid timestamp");
} else if (kvp.first == "slic3r_version_captured") {
@ -165,7 +165,7 @@ void Snapshot::save_ini(const std::string &path)
// Export the common "snapshot".
c << std::endl << "[snapshot]" << std::endl;
c << "id = " << this->id << std::endl;
c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl;
c << "time_captured = " << Slic3r::Utils::iso_utc_timestamp(this->time_captured) << std::endl;
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
c << "comment = " << this->comment << std::endl;
c << "reason = " << reason_string(this->reason) << std::endl;
@ -365,7 +365,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
Snapshot snapshot;
// Snapshot header.
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured);
snapshot.id = Slic3r::Utils::iso_utc_timestamp(snapshot.time_captured);
snapshot.slic3r_version_captured = Slic3r::SEMVER;
snapshot.comment = comment;
snapshot.reason = reason;
@ -393,9 +393,9 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
for (const VendorProfile &vp : bundle.vendors)
if (vp.id == cfg.name)
cfg.version.config_version = vp.config_version;
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
// Fill-in the min/max slic3r version from the config index, if possible.
try {
// Load the config index for the vendor.

View file

@ -235,9 +235,9 @@ size_t Index::load(const boost::filesystem::path &path)
value = left_trim(value + 1);
*key_end = 0;
boost::optional<Semver> semver;
if (maybe_semver)
if (maybe_semver)
semver = Semver::parse(key);
if (key_value_pair) {
if (key_value_pair) {
if (semver)
throw file_parser_error("Key cannot be a semantic version", path, idx_line);\
// Verify validity of the key / value pair.
@ -288,7 +288,6 @@ Index::const_iterator Index::find(const Semver &ver) const
Index::const_iterator Index::recommended() const
{
int idx = -1;
const_iterator highest = this->end();
for (const_iterator it = this->begin(); it != this->end(); ++ it)
if (it->is_current_slic3r_supported() &&

View file

@ -417,7 +417,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const
}
bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); }
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
std::vector<int> GLVolumeCollection::load_object(
const ModelObject *model_object,
@ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary(
TriangleMesh convex_hull = mesh.convex_hull_3d();
for (const std::pair<size_t, size_t>& instance_idx : instances) {
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
GLVolume& v = *this->volumes.back();
v.indexed_vertex_array.load_mesh(mesh);
v.indexed_vertex_array.finalize_geometry(opengl_initialized);
@ -1717,13 +1717,18 @@ static void thick_point_to_verts(const Vec3crd& point,
point_to_indexed_vertex_array(point, width, height, volume.indexed_vertex_array);
}
void _3DScene::extrusionentity_to_verts(const Polyline &polyline, float width, float height, float print_z, GLVolume& volume)
{
if (polyline.size() >= 2) {
size_t num_segments = polyline.size() - 1;
thick_lines_to_verts(polyline.lines(), std::vector<double>(num_segments, width), std::vector<double>(num_segments, height), false, print_z, volume);
}
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
void _3DScene::extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, GLVolume &volume)
{
Lines lines = extrusion_path.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, volume);
extrusionentity_to_verts(extrusion_path.polyline, extrusion_path.width, extrusion_path.height, print_z, volume);
}
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.

View file

@ -656,6 +656,7 @@ public:
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 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);

View file

@ -28,6 +28,9 @@ static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
void AppConfig::reset()
{
m_storage.clear();

View file

@ -80,6 +80,12 @@ public:
}
}
bool has_section(const std::string &section) const
{ return m_storage.find(section) != m_storage.end(); }
const std::map<std::string, std::string>& get_section(const std::string &section) const
{ return m_storage.find(section)->second; }
void set_section(const std::string &section, const std::map<std::string, std::string>& data)
{ m_storage[section] = data; }
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
@ -125,6 +131,8 @@ public:
std::vector<std::string> get_recent_projects() const;
void set_recent_projects(const std::vector<std::string>& recent_projects);
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;

View file

@ -10,12 +10,19 @@
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#if ENABLE_THUMBNAIL_GENERATOR
#include <miniz.h>
#endif // ENABLE_THUMBNAIL_GENERATOR
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/libslic3r.h"
#include <cassert>
@ -55,6 +62,7 @@ bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech)
switch (tech) {
case ptFFF: m_print = m_fff_print; break;
case ptSLA: m_print = m_sla_print; break;
default: assert(false); break;
}
changed = true;
}
@ -82,8 +90,12 @@ void BackgroundSlicingProcess::process_fff()
assert(m_print == m_fff_print);
m_print->process();
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
if (this->set_step_started(bspsGCodeFinalize)) {
#if ENABLE_THUMBNAIL_GENERATOR
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_data);
#else
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
//FIXME localize the messages
// Perform the final post-processing of the export path by applying the print statistics over the file name.
@ -99,17 +111,46 @@ void BackgroundSlicingProcess::process_fff()
m_print->set_status(100, _utf8(L("Slicing complete")));
}
this->set_step_done(bspsGCodeFinalize);
}
}
}
#if ENABLE_THUMBNAIL_GENERATOR
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);
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
void BackgroundSlicingProcess::process_sla()
{
assert(m_print == m_sla_print);
m_print->process();
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
m_sla_print->export_raster(export_path);
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
Zipper zipper(export_path);
m_sla_print->export_raster(zipper);
#if ENABLE_THUMBNAIL_GENERATOR
if (m_thumbnail_data != nullptr)
{
for (const ThumbnailData& data : *m_thumbnail_data)
{
if (data.is_valid())
write_thumbnail(zipper, data);
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
zipper.finalize();
m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
} else if (! m_upload_job.empty()) {
prepare_upload();
@ -220,11 +261,7 @@ bool BackgroundSlicingProcess::start()
if (m_state == STATE_INITIAL) {
// The worker thread is not running yet. Start it.
assert(! m_thread.joinable());
boost::thread::attributes attrs;
// Duplicating the stack allocation size of Thread Building Block worker threads of the thread pool:
// allocate 4MB on a 64bit system, allocate 2MB on a 32bit system by default.
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
m_thread = boost::thread(attrs, [this]{this->thread_proc_safe();});
m_thread = create_thread([this]{this->thread_proc_safe();});
// Wait until the worker thread is ready to execute the background processing task.
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
}
@ -417,13 +454,26 @@ void BackgroundSlicingProcess::prepare_upload()
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
}
run_post_process_scripts(source_path.string(), m_fff_print->config());
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
} else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string());
}
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(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());
Zipper zipper{source_path.string()};
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
#if ENABLE_THUMBNAIL_GENERATOR
if (m_thumbnail_data != nullptr)
{
for (const ThumbnailData& data : *m_thumbnail_data)
{
if (data.is_valid())
write_thumbnail(zipper, data);
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
zipper.finalize();
}
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());
m_upload_job.upload_data.source_path = std::move(source_path);

View file

@ -6,17 +6,20 @@
#include <mutex>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <wx/event.h>
#include "libslic3r/Print.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#include "slic3r/Utils/Thread.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class GCodePreviewData;
#if ENABLE_THUMBNAIL_GENERATOR
struct ThumbnailData;
#endif // ENABLE_THUMBNAIL_GENERATOR
class Model;
class SLAPrint;
@ -49,6 +52,10 @@ public:
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
#if ENABLE_THUMBNAIL_GENERATOR
void set_thumbnail_data(const std::vector<ThumbnailData>* data) { m_thumbnail_data = data; }
#endif // ENABLE_THUMBNAIL_GENERATOR
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
// and the background processing will transition into G-code export.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
@ -151,6 +158,10 @@ private:
SLAPrint *m_sla_print = nullptr;
// Data structure, to which the G-code export writes its annotations.
GCodePreviewData *m_gcode_preview_data = nullptr;
#if ENABLE_THUMBNAIL_GENERATOR
// Data structures, used to write thumbnails into gcode.
const std::vector<ThumbnailData>* m_thumbnail_data = nullptr;
#endif // ENABLE_THUMBNAIL_GENERATOR
// 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,

View file

@ -1,7 +1,9 @@
#include "libslic3r/libslic3r.h"
#include "Camera.hpp"
#if !ENABLE_THUMBNAIL_GENERATOR
#include "3DScene.hpp"
#endif // !ENABLE_THUMBNAIL_GENERATOR
#include "GUI_App.hpp"
#include "AppConfig.hpp"
@ -22,6 +24,10 @@ namespace Slic3r {
namespace GUI {
const double Camera::DefaultDistance = 1000.0;
#if ENABLE_THUMBNAIL_GENERATOR
const double Camera::DefaultZoomToBoxMarginFactor = 1.025;
const double Camera::DefaultZoomToVolumesMarginFactor = 1.025;
#endif // ENABLE_THUMBNAIL_GENERATOR
double Camera::FrustrumMinZRange = 50.0;
double Camera::FrustrumMinNearZ = 100.0;
double Camera::FrustrumZMargin = 10.0;
@ -266,10 +272,18 @@ void Camera::apply_projection(const BoundingBoxf3& box) const
glsafe(::glMatrixMode(GL_MODELVIEW));
}
#if ENABLE_THUMBNAIL_GENERATOR
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor)
#else
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
// Calculate the zoom factor needed to adjust the view around the given box.
#if ENABLE_THUMBNAIL_GENERATOR
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor);
#else
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h);
#endif // ENABLE_THUMBNAIL_GENERATOR
if (zoom > 0.0)
{
m_zoom = zoom;
@ -278,6 +292,20 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
}
}
#if ENABLE_THUMBNAIL_GENERATOR
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor)
{
Vec3d center;
double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor);
if (zoom > 0.0)
{
m_zoom = zoom;
// center view around the calculated center
m_target = center;
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_CAMERA_STATISTICS
void Camera::debug_render() const
{
@ -372,7 +400,11 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
return ret;
}
#if ENABLE_THUMBNAIL_GENERATOR
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const
#else
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const
#endif // ENABLE_THUMBNAIL_GENERATOR
{
double max_bb_size = box.max_size();
if (max_bb_size == 0.0)
@ -405,13 +437,15 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
double max_x = 0.0;
double max_y = 0.0;
#if !ENABLE_THUMBNAIL_GENERATOR
// margin factor to give some empty space around the box
double margin_factor = 1.25;
#endif // !ENABLE_THUMBNAIL_GENERATOR
for (const Vec3d& v : vertices)
{
// project vertex on the plane perpendicular to camera forward axis
Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
Vec3d pos = v - bb_center;
Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
// calculates vertex coordinate along camera xy axes
@ -431,6 +465,72 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y));
}
#if ENABLE_THUMBNAIL_GENERATOR
double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const
{
if (volumes.empty())
return -1.0;
// project the volumes vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
// ensure that the view matrix is updated
apply_view_matrix();
Vec3d right = get_dir_right();
Vec3d up = get_dir_up();
Vec3d forward = get_dir_forward();
BoundingBoxf3 box;
for (const GLVolume* volume : volumes)
{
box.merge(volume->transformed_bounding_box());
}
center = box.center();
double min_x = DBL_MAX;
double min_y = DBL_MAX;
double max_x = -DBL_MAX;
double max_y = -DBL_MAX;
for (const GLVolume* volume : volumes)
{
const Transform3d& transform = volume->world_matrix();
const TriangleMesh* hull = volume->convex_hull();
if (hull == nullptr)
continue;
for (const Vec3f& vertex : hull->its.vertices)
{
Vec3d v = transform * vertex.cast<double>();
// project vertex on the plane perpendicular to camera forward axis
Vec3d pos = v - center;
Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
// calculates vertex coordinate along camera xy axes
double x_on_plane = proj_on_plane.dot(right);
double y_on_plane = proj_on_plane.dot(up);
min_x = std::min(min_x, x_on_plane);
min_y = std::min(min_y, y_on_plane);
max_x = std::max(max_x, x_on_plane);
max_y = std::max(max_y, y_on_plane);
}
}
center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up;
double dx = margin_factor * (max_x - min_x);
double dy = margin_factor * (max_y - min_y);
if ((dx == 0.0) || (dy == 0.0))
return -1.0f;
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
}
#endif // ENABLE_THUMBNAIL_GENERATOR
void Camera::set_distance(double distance) const
{
m_distance = distance;

View file

@ -2,6 +2,9 @@
#define slic3r_Camera_hpp_
#include "libslic3r/BoundingBox.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
#include "3DScene.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include <array>
namespace Slic3r {
@ -10,6 +13,10 @@ namespace GUI {
struct Camera
{
static const double DefaultDistance;
#if ENABLE_THUMBNAIL_GENERATOR
static const double DefaultZoomToBoxMarginFactor;
static const double DefaultZoomToVolumesMarginFactor;
#endif // ENABLE_THUMBNAIL_GENERATOR
static double FrustrumMinZRange;
static double FrustrumMinNearZ;
static double FrustrumZMargin;
@ -90,7 +97,12 @@ public:
void apply_view_matrix() const;
void apply_projection(const BoundingBoxf3& box) const;
#if ENABLE_THUMBNAIL_GENERATOR
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor);
void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor);
#else
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h);
#endif // ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_CAMERA_STATISTICS
void debug_render() const;
@ -100,7 +112,12 @@ private:
// returns tight values for nearZ and farZ plane around the given bounding box
// the camera MUST be outside of the bounding box in eye coordinate of the given box
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const;
#if ENABLE_THUMBNAIL_GENERATOR
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const;
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
#else
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const;
#endif // ENABLE_THUMBNAIL_GENERATOR
void set_distance(double distance) const;
};

View file

@ -349,16 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
toggle_field("pad_wall_thickness", pad_en);
toggle_field("pad_wall_height", pad_en);
toggle_field("pad_brim_size", pad_en);
toggle_field("pad_max_merge_distance", pad_en);
// toggle_field("pad_edge_radius", supports_en);
toggle_field("pad_wall_slope", pad_en);
toggle_field("pad_around_object", pad_en);
toggle_field("pad_around_object_everywhere", pad_en);
bool has_suppad = pad_en && supports_en;
bool zero_elev = config->opt_bool("pad_around_object") && has_suppad;
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
toggle_field("support_object_elevation", supports_en && !zero_elev);
toggle_field("pad_object_gap", zero_elev);
toggle_field("pad_around_object_everywhere", zero_elev);
toggle_field("pad_object_connector_stride", zero_elev);
toggle_field("pad_object_connector_width", zero_elev);
toggle_field("pad_object_connector_penetration", zero_elev);

View file

@ -35,9 +35,14 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
text += "\">";
text += "<td>";
static const constexpr char *LOCALE_TIME_FMT = "%x %X";
wxString datetime = wxDateTime(snapshot.time_captured).Format(LOCALE_TIME_FMT);
// Format the row header.
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active")) + ": " : "") +
Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active")) + ": " : "") +
datetime + ": " + format_reason(snapshot.reason);
if (! snapshot.comment.empty())
text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
text += "</b></font><br>";

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,15 @@ public:
RR_USER, // User requested the Wizard from the menus
};
ConfigWizard(wxWindow *parent, RunReason run_reason);
// What page should wizard start on
enum StartPage {
SP_WELCOME,
SP_PRINTERS,
SP_FILAMENTS,
SP_MATERIALS,
};
ConfigWizard(wxWindow *parent);
ConfigWizard(ConfigWizard &&) = delete;
ConfigWizard(const ConfigWizard &) = delete;
ConfigWizard &operator=(ConfigWizard &&) = delete;
@ -34,7 +42,7 @@ public:
~ConfigWizard();
// Run the Wizard. Return whether it was completed.
bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
bool run(RunReason reason, StartPage start_page = SP_WELCOME);
static const wxString& name(const bool from_menu = false);

View file

@ -15,11 +15,14 @@
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/listbox.h>
#include <wx/checklst.h>
#include <wx/radiobut.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "AppConfig.hpp"
#include "Preset.hpp"
#include "PresetBundle.hpp"
#include "BedShapeDialog.hpp"
namespace fs = boost::filesystem;
@ -41,6 +44,76 @@ enum {
ROW_SPACING = 75,
};
// Configuration data structures extensions needed for the wizard
enum Technology {
// Bitflag equivalent of PrinterTechnology
T_FFF = 0x1,
T_SLA = 0x2,
T_ANY = ~0,
};
struct Materials
{
Technology technology;
std::set<const Preset*> presets;
std::set<std::string> types;
Materials(Technology technology) : technology(technology) {}
void push(const Preset *preset);
void clear();
bool containts(const Preset *preset) {
return presets.find(preset) != presets.end();
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset *preset) const;
const std::string& get_vendor(const Preset *preset) const;
template<class F> void filter_presets(const std::string &type, const std::string &vendor, F cb) {
for (const Preset *preset : presets) {
if ((type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
static const std::string& get_material_type(const Preset *preset);
static const std::string& get_material_vendor(const Preset *preset);
};
struct Bundle
{
std::unique_ptr<PresetBundle> preset_bundle;
VendorProfile *vendor_profile;
const bool is_in_resources;
const bool is_prusa_bundle;
Bundle(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false);
Bundle(Bundle &&other);
const std::string& vendor_id() const { return vendor_profile->id; }
};
struct BundleMap: std::unordered_map<std::string /* = vendor ID */, Bundle>
{
static BundleMap load();
Bundle& prusa_bundle();
const Bundle& prusa_bundle() const;
};
struct PrinterPickerEvent;
// GUI elements
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
struct PrinterPicker: wxPanel
@ -61,19 +134,22 @@ struct PrinterPicker: wxPanel
std::vector<Checkbox*> cboxes;
std::vector<Checkbox*> cboxes_alt;
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors, const ModelFilter &filter);
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig_vendors);
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter);
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig);
void select_all(bool select, bool alternates = false);
void select_one(size_t i, bool select);
void on_checkbox(const Checkbox *cbox, bool checked);
bool any_selected() const;
int get_width() const { return width; }
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
static const std::string PRINTER_PLACEHOLDER;
private:
int width;
std::vector<int> m_button_indexes;
void on_checkbox(const Checkbox *cbox, bool checked);
};
struct ConfigWizardPage: wxPanel
@ -87,43 +163,107 @@ struct ConfigWizardPage: wxPanel
virtual ~ConfigWizardPage();
template<class T>
void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
T* append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
{
content->Add(thing, proportion, flag, border);
return thing;
}
void append_text(wxString text);
wxStaticText* append_text(wxString text);
void append_spacer(int space);
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
virtual void apply_custom_config(DynamicPrintConfig &config) {}
virtual void set_run_reason(ConfigWizard::RunReason run_reason) {}
virtual void on_activate() {}
};
struct PageWelcome: ConfigWizardPage
{
wxStaticText *welcome_text;
wxCheckBox *cbox_reset;
PageWelcome(ConfigWizard *parent);
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
};
struct PagePrinters: ConfigWizardPage
{
enum Technology {
// Bitflag equivalent of PrinterTechnology
T_FFF = 0x1,
T_SLA = 0x2,
T_Any = ~0,
};
std::vector<PrinterPicker *> printer_pickers;
Technology technology;
bool install;
PagePrinters(ConfigWizard *parent, wxString title, wxString shortname, const VendorProfile &vendor, unsigned indent, Technology technology);
PagePrinters(ConfigWizard *parent,
wxString title,
wxString shortname,
const VendorProfile &vendor,
unsigned indent, Technology technology);
void select_all(bool select, bool alternates = false);
int get_width() const;
bool any_selected() const;
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
};
// Here we extend wxListBox and wxCheckListBox
// to make the client data API much easier to use.
template<class T, class D> struct DataList : public T
{
DataList(wxWindow *parent) : T(parent, wxID_ANY) {}
// Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing,
// eg. "ABS" is sorted before "(All)"
int append(const std::string &label, const D *data) {
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
return this->Append(from_u8(label), ptr);
}
int append(const wxString &label, const D *data) {
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
return this->Append(label, ptr);
}
const D& get_data(int n) {
return *reinterpret_cast<const D*>(this->GetClientData(n));
}
int find(const D &data) {
for (unsigned i = 0; i < this->GetCount(); i++) {
if (get_data(i) == data) { return i; }
}
return wxNOT_FOUND;
}
};
typedef DataList<wxListBox, std::string> StringList;
typedef DataList<wxCheckListBox, Preset> PresetList;
struct PageMaterials: ConfigWizardPage
{
Materials *materials;
StringList *list_l1, *list_l2;
PresetList *list_l3;
int sel1_prev, sel2_prev;
bool presets_loaded;
static const std::string EMPTY;
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
void reload_presets();
void update_lists(int sel1, int sel2);
void select_material(int i);
void select_all(bool select);
void clear();
virtual void on_activate() override;
};
struct PageCustom: ConfigWizardPage
@ -150,13 +290,22 @@ struct PageUpdate: ConfigWizardPage
PageUpdate(ConfigWizard *parent);
};
struct PageMode: ConfigWizardPage
{
wxRadioButton *radio_simple;
wxRadioButton *radio_advanced;
wxRadioButton *radio_expert;
PageMode(ConfigWizard *parent);
void serialize_mode(AppConfig *app_config) const;
virtual void on_activate();
};
struct PageVendors: ConfigWizardPage
{
std::vector<PrinterPicker*> pickers;
PageVendors(ConfigWizard *parent);
void on_vendor_pick(size_t i);
};
struct PageFirmware: ConfigWizardPage
@ -194,6 +343,8 @@ struct PageTemperatures: ConfigWizardPage
virtual void apply_custom_config(DynamicPrintConfig &config);
};
typedef std::map<std::string /* = vendor ID */, PagePrinters*> Pages3rdparty;
class ConfigWizardIndex: public wxPanel
{
@ -210,12 +361,14 @@ public:
void go_prev();
void go_next();
void go_to(size_t i);
void go_to(ConfigWizardPage *page);
void go_to(const ConfigWizardPage *page);
void clear();
void msw_rescale();
int em() const { return em_w; }
static const size_t NO_ITEM = size_t(-1);
private:
struct Item
{
@ -228,12 +381,6 @@ private:
int em_w;
int em_h;
/* #ys_FIXME_delete_after_testing by VK
const wxBitmap bg;
const wxBitmap bullet_black;
const wxBitmap bullet_blue;
const wxBitmap bullet_white;
*/
ScalableBitmap bg;
ScalableBitmap bullet_black;
ScalableBitmap bullet_blue;
@ -245,9 +392,6 @@ private:
ssize_t item_hover;
size_t last_page;
/* #ys_FIXME_delete_after_testing by VK
int item_height() const { return std::max(bullet_black.GetSize().GetHeight(), em_w) + em_w; }
*/
int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; }
void on_paint(wxPaintEvent &evt);
@ -256,14 +400,24 @@ private:
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
// ConfigWizard private data
struct ConfigWizard::priv
{
ConfigWizard *q;
ConfigWizard::RunReason run_reason;
AppConfig appconfig_vendors;
std::unordered_map<std::string, VendorProfile> vendors;
std::unordered_map<std::string, std::string> vendors_rsrc;
std::unique_ptr<DynamicPrintConfig> custom_config;
ConfigWizard::RunReason run_reason = RR_USER;
AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI
BundleMap bundles; // Holds all loaded config bundles, the key is the vendor names.
// Materials refers to Presets in those bundles by pointers.
// Also we update the is_visible flag in printer Presets according to the
// PrinterPickers state.
Materials filaments; // Holds available filament presets and their types & vendors
Materials sla_materials; // Ditto for SLA materials
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page
wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr;
@ -279,9 +433,13 @@ struct ConfigWizard::priv
PageWelcome *page_welcome = nullptr;
PagePrinters *page_fff = nullptr;
PagePrinters *page_msla = nullptr;
PageMaterials *page_filaments = nullptr;
PageMaterials *page_sla_materials = nullptr;
PageCustom *page_custom = nullptr;
PageUpdate *page_update = nullptr;
PageVendors *page_vendors = nullptr; // XXX: ?
PageMode *page_mode = nullptr;
PageVendors *page_vendors = nullptr;
Pages3rdparty pages_3rdparty;
// Custom setup pages
PageFirmware *page_firmware = nullptr;
@ -289,17 +447,30 @@ struct ConfigWizard::priv
PageDiameters *page_diams = nullptr;
PageTemperatures *page_temps = nullptr;
priv(ConfigWizard *q) : q(q) {}
// Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex)
std::vector<ConfigWizardPage*> all_pages;
void load_pages(bool custom_setup);
priv(ConfigWizard *q)
: q(q)
, filaments(T_FFF)
, sla_materials(T_SLA)
, any_sla_selected(false)
{}
void load_pages();
void init_dialog_size();
bool check_first_variant() const;
void load_vendors();
void add_page(ConfigWizardPage *page);
void enable_next(bool enable);
void set_start_page(ConfigWizard::StartPage start_page);
void create_3rdparty_pages();
void set_run_reason(RunReason run_reason);
void update_materials(Technology technology);
void on_custom_setup(bool custom_wanted);
void on_custom_setup();
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);

View file

@ -150,7 +150,13 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
case coFloat:{
if (m_opt.type == coPercent && !str.IsEmpty() && str.Last() == '%')
str.RemoveLast();
else if (check_value && !str.IsEmpty() && str.Last() == '%') {
else if (!str.IsEmpty() && str.Last() == '%')
{
if (!check_value) {
m_value.clear();
break;
}
wxString label = m_Label->GetLabel();
if (label.Last() == '\n') label.RemoveLast();
while (label.Last() == ' ') label.RemoveLast();
@ -169,13 +175,21 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
{
if (m_opt.nullable && str == na_value())
val = ConfigOptionFloatsNullable::nil_value();
else if (check_value && !str.ToCDouble(&val))
else if (!str.ToCDouble(&val))
{
if (!check_value) {
m_value.clear();
break;
}
show_error(m_parent, _(L("Invalid numeric input.")));
set_value(double_to_string(val), true);
}
if (check_value && (m_opt.min > val || val > m_opt.max))
if (m_opt.min > val || val > m_opt.max)
{
if (!check_value) {
m_value.clear();
break;
}
show_error(m_parent, _(L("Input value is out of range")));
if (m_opt.min > val) val = m_opt.min;
if (val > m_opt.max) val = m_opt.max;
@ -192,15 +206,24 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
double val = 0.;
// Replace the first occurence of comma in decimal number.
str.Replace(",", ".", false);
if (check_value && !str.ToCDouble(&val))
if (!str.ToCDouble(&val))
{
if (!check_value) {
m_value.clear();
break;
}
show_error(m_parent, _(L("Invalid numeric input.")));
set_value(double_to_string(val), true);
}
else if (check_value && ((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) ||
else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) ||
(m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1)) &&
(m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast<std::string>(m_value)))
{
if (!check_value) {
m_value.clear();
break;
}
const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm";
const wxString stVal = double_to_string(val, 2);
const wxString msg_text = wxString::Format(_(L("Do you mean %s%% instead of %s %s?\n"
@ -351,6 +374,7 @@ bool TextCtrl::value_was_changed()
boost::any val = m_value;
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
// update m_value!
// ret_str might be changed inside get_value_by_opt_type
get_value_by_opt_type(ret_str);
switch (m_opt.type) {
@ -396,8 +420,10 @@ void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/)
if (!change_event) {
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
// update m_value to correct work of next value_was_changed(),
// but don't check/change inputed value and don't show a warning message
/* Update m_value to correct work of next value_was_changed().
* But after checking of entered value, don't fix the "incorrect" value and don't show a warning message,
* just clear m_value in this case.
*/
get_value_by_opt_type(ret_str, false);
}
}

View file

@ -7,6 +7,9 @@
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/Geometry.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Utils.hpp"
@ -1116,6 +1119,10 @@ wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
#if ENABLE_THUMBNAIL_GENERATOR
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
#endif // ENABLE_THUMBNAIL_GENERATOR
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
: m_canvas(canvas)
, m_context(nullptr)
@ -1643,6 +1650,18 @@ void GLCanvas3D::render()
#endif // ENABLE_RENDER_STATISTICS
}
#if ENABLE_THUMBNAIL_GENERATOR
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
{
switch (GLCanvas3DManager::get_framebuffers_type())
{
case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
void GLCanvas3D::select_all()
{
m_selection.add_all();
@ -1747,101 +1766,114 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
_set_current();
struct ModelVolumeState {
ModelVolumeState(const GLVolume *volume) :
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) :
model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) :
model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
bool new_geometry() const { return this->volume_idx == size_t(-1); }
const ModelVolume *model_volume;
ModelVolumeState(const GLVolume* volume) :
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) :
model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) :
model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
bool new_geometry() const { return this->volume_idx == size_t(-1); }
const ModelVolume* model_volume;
// ObjectID of ModelVolume + ObjectID of ModelInstance
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
std::pair<size_t, size_t> geometry_id;
GLVolume::CompositeID composite_id;
// Volume index in the new GLVolume vector.
size_t volume_idx;
size_t volume_idx;
};
std::vector<ModelVolumeState> model_volume_state;
std::vector<ModelVolumeState> aux_volume_state;
std::vector<ModelVolumeState> aux_volume_state;
struct GLVolumeState {
GLVolumeState() :
volume_idx(-1) {}
GLVolumeState(const GLVolume* volume, unsigned int volume_idx) :
composite_id(volume->composite_id), volume_idx(volume_idx) {}
GLVolume::CompositeID composite_id;
// Volume index in the old GLVolume vector.
size_t volume_idx;
};
// SLA steps to pull the preview meshes for.
typedef std::array<SLAPrintObjectStep, 2> SLASteps;
SLASteps sla_steps = { slaposSupportTree, slaposBasePool };
SLASteps sla_steps = { slaposSupportTree, slaposPad };
struct SLASupportState {
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
};
// State of the sla_steps for all SLAPrintObjects.
std::vector<SLASupportState> sla_support_state;
std::vector<size_t> instance_ids_selected;
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
std::vector<GLVolumeState> deleted_volumes;
std::vector<GLVolume*> glvolumes_new;
glvolumes_new.reserve(m_volumes.volumes.size());
auto model_volume_state_lower = [](const ModelVolumeState &m1, const ModelVolumeState &m2) { return m1.geometry_id < m2.geometry_id; };
auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; };
m_reload_delayed = ! m_canvas->IsShown() && ! refresh_immediately && ! force_full_scene_refresh;
m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh;
PrinterTechnology printer_technology = m_process->current_printer_technology();
PrinterTechnology printer_technology = m_process->current_printer_technology();
int volume_idx_wipe_tower_old = -1;
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
// First initialize model_volumes_new_sorted & model_instances_new_sorted.
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) {
const ModelObject *model_object = m_model->objects[object_idx];
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) {
const ModelInstance *model_instance = model_object->instances[instance_idx];
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) {
const ModelVolume *model_volume = model_object->volumes[volume_idx];
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) {
const ModelObject* model_object = m_model->objects[object_idx];
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) {
const ModelInstance* model_instance = model_object->instances[instance_idx];
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) {
const ModelVolume* model_volume = model_object->volumes[volume_idx];
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
}
}
}
if (printer_technology == ptSLA) {
const SLAPrint *sla_print = this->sla_print();
#ifndef NDEBUG
const SLAPrint* sla_print = this->sla_print();
#ifndef NDEBUG
// Verify that the SLAPrint object is synchronized with m_model.
check_model_ids_equal(*m_model, sla_print->model());
#endif /* NDEBUG */
#endif /* NDEBUG */
sla_support_state.reserve(sla_print->objects().size());
for (const SLAPrintObject *print_object : sla_print->objects()) {
for (const SLAPrintObject* print_object : sla_print->objects()) {
SLASupportState state;
for (size_t istep = 0; istep < sla_steps.size(); ++ istep) {
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
if (state.step[istep].state == PrintStateBase::DONE) {
if (! print_object->has_mesh(sla_steps[istep]))
for (size_t istep = 0; istep < sla_steps.size(); ++istep) {
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
if (state.step[istep].state == PrintStateBase::DONE) {
if (!print_object->has_mesh(sla_steps[istep]))
// Consider the DONE step without a valid mesh as invalid for the purpose
// of mesh visualization.
state.step[istep].state = PrintStateBase::INVALID;
else
for (const ModelInstance *model_instance : print_object->model_object()->instances)
// Only the instances, which are currently printable, will have the SLA support structures kept.
// The instances outside the print bed will have the GLVolumes of their support structures released.
if (model_instance->is_printable())
for (const ModelInstance* model_instance : print_object->model_object()->instances)
// Only the instances, which are currently printable, will have the SLA support structures kept.
// The instances outside the print bed will have the GLVolumes of their support structures released.
if (model_instance->is_printable())
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
}
}
sla_support_state.emplace_back(state);
}
sla_support_state.emplace_back(state);
}
}
std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower);
std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower);
// Release all ModelVolume based GLVolumes not found in the current Model.
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) {
GLVolume *volume = m_volumes.volumes[volume_id];
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) {
GLVolume* volume = m_volumes.volumes[volume_id];
ModelVolumeState key(volume);
ModelVolumeState *mvs = nullptr;
ModelVolumeState* mvs = nullptr;
if (volume->volume_idx() < 0) {
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id)
// This can be an SLA support structure that should not be rendered (in case someone used undo
// to revert to before it was generated). We only reuse the volume if that's not the case.
if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints)
mvs = &(*it);
} else {
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
}
else {
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
mvs = &(*it);
mvs = &(*it);
}
// Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID.
// The wipe tower has its own wipe_tower_instance_id().
@ -1854,19 +1886,23 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
assert(volume_idx_wipe_tower_old == -1);
volume_idx_wipe_tower_old = (int)volume_id;
}
if (! m_reload_delayed)
if (!m_reload_delayed)
{
deleted_volumes.emplace_back(volume, volume_id);
delete volume;
} else {
}
}
else {
// This GLVolume will be reused.
volume->set_sla_shift_z(0.0);
map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
mvs->volume_idx = glvolumes_new.size();
glvolumes_new.emplace_back(volume);
// Update color of the volume based on the current extruder.
if (mvs->model_volume != nullptr) {
int extruder_id = mvs->model_volume->extruder_id();
if (extruder_id != -1)
volume->extruder_id = extruder_id;
if (mvs->model_volume != nullptr) {
int extruder_id = mvs->model_volume->extruder_id();
if (extruder_id != -1)
volume->extruder_id = extruder_id;
volume->is_modifier = !mvs->model_volume->is_model_part();
volume->set_color_from_model_volume(mvs->model_volume);
@ -1884,6 +1920,16 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
bool update_object_list = false;
auto find_old_volume_id = [&deleted_volumes](const GLVolume::CompositeID& id) -> unsigned int {
for (unsigned int i = 0; i < (unsigned int)deleted_volumes.size(); ++i)
{
const GLVolumeState& v = deleted_volumes[i];
if (v.composite_id == id)
return v.volume_idx;
}
return (unsigned int)-1;
};
if (m_volumes.volumes != glvolumes_new)
update_object_list = true;
m_volumes.volumes = std::move(glvolumes_new);
@ -1898,9 +1944,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
if (it->new_geometry()) {
// New volume.
unsigned int old_id = find_old_volume_id(it->composite_id);
if (old_id != (unsigned int)-1)
map_glvolume_old_to_new[old_id] = m_volumes.volumes.size();
m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized);
m_volumes.volumes.back()->geometry_id = key.geometry_id;
update_object_list = true;
update_object_list = true;
} else {
// Recycling an old GLVolume.
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
@ -1999,19 +2048,17 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
const Print *print = m_process->fff_print();
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4);
double nozzle_diameter = print->config().nozzle_diameter.values[0];
float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth;
float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width;
if (!print->is_step_done(psWipeTower))
depth = (900.f/w) * (float)(extruders_count - 1);
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_spacing * 4.5f, m_initialized);
brim_width, m_initialized);
if (volume_idx_wipe_tower_old != -1)
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
@ -2634,19 +2681,6 @@ std::string format_mouse_event_debug_message(const wxMouseEvent &evt)
void GLCanvas3D::on_mouse(wxMouseEvent& evt)
{
auto mouse_up_cleanup = [this](){
m_moving = false;
m_mouse.drag.move_volume_idx = -1;
m_mouse.set_start_position_3D_as_invalid();
m_mouse.set_start_position_2D_as_invalid();
m_mouse.dragging = false;
m_mouse.ignore_left_up = false;
m_dirty = true;
if (m_canvas->HasCapture())
m_canvas->ReleaseMouse();
};
#if ENABLE_RETINA_GL
const float scale = m_retina_helper->get_scale_factor();
evt.SetX(evt.GetX() * scale);
@ -3483,6 +3517,20 @@ void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
m_volumes.export_toolpaths_to_obj(filename);
}
void GLCanvas3D::mouse_up_cleanup()
{
m_moving = false;
m_mouse.drag.move_volume_idx = -1;
m_mouse.set_start_position_3D_as_invalid();
m_mouse.set_start_position_2D_as_invalid();
m_mouse.dragging = false;
m_mouse.ignore_left_up = false;
m_dirty = true;
if (m_canvas->HasCapture())
m_canvas->ReleaseMouse();
}
bool GLCanvas3D::_is_shown_on_screen() const
{
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
@ -3524,6 +3572,341 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
imgui->end();
}
#if ENABLE_THUMBNAIL_GENERATOR
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
{
// debug export of generated image
wxImage image(thumbnail_data.width, thumbnail_data.height);
image.InitAlpha();
for (unsigned int r = 0; r < thumbnail_data.height; ++r)
{
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
for (unsigned int c = 0; c < thumbnail_data.width; ++c)
{
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
image.SetAlpha((int)c, (int)r, px[3]);
}
}
image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG);
}
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volumes, ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool transparent_background)
{
auto is_visible = [](const GLVolume& v) -> bool
{
bool ret = v.printable;
ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside);
return ret;
};
static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f };
static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f };
GLVolumePtrs visible_volumes;
for (GLVolume* vol : volumes)
{
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0)))
{
if (!printable_only || is_visible(*vol))
visible_volumes.push_back(vol);
}
}
if (visible_volumes.empty())
return;
BoundingBoxf3 box;
for (const GLVolume* vol : visible_volumes)
{
box.merge(vol->transformed_bounding_box());
}
Camera camera;
camera.set_type(Camera::Ortho);
camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height);
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
camera.apply_view_matrix();
camera.apply_projection(box);
if (transparent_background)
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 0.0f));
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
shader.start_using();
GLint shader_id = shader.get_shader_program_id();
GLint color_id = ::glGetUniformLocation(shader_id, "uniform_color");
GLint print_box_detection_id = ::glGetUniformLocation(shader_id, "print_box.volume_detection");
glcheck();
if (print_box_detection_id != -1)
glsafe(::glUniform1i(print_box_detection_id, 0));
for (const GLVolume* vol : visible_volumes)
{
if (color_id >= 0)
glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray));
else
glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray));
vol->render();
}
shader.stop_using();
glsafe(::glDisable(GL_DEPTH_TEST));
if (transparent_background)
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
}
void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
{
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
return;
bool multisample = m_multisample_allowed;
if (multisample)
glsafe(::glEnable(GL_MULTISAMPLE));
GLint max_samples;
glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples));
GLsizei num_samples = max_samples / 2;
GLuint render_fbo;
glsafe(::glGenFramebuffers(1, &render_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
GLuint render_tex = 0;
GLuint render_tex_buffer = 0;
if (multisample)
{
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
glsafe(::glGenRenderbuffers(1, &render_tex_buffer));
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer));
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h));
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer));
}
else
{
glsafe(::glGenTextures(1, &render_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
}
GLuint render_depth;
glsafe(::glGenRenderbuffers(1, &render_depth));
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
if (multisample)
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h));
else
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h));
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
if (multisample)
{
GLuint resolve_fbo;
glsafe(::glGenFramebuffers(1, &resolve_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo));
GLuint resolve_tex;
glsafe(::glGenTextures(1, &resolve_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0));
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo));
glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo));
glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo));
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
}
glsafe(::glDeleteTextures(1, &resolve_tex));
glsafe(::glDeleteFramebuffers(1, &resolve_fbo));
}
else
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail(thumbnail_data);
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
}
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
glsafe(::glDeleteRenderbuffers(1, &render_depth));
if (render_tex_buffer != 0)
glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer));
if (render_tex != 0)
glsafe(::glDeleteTextures(1, &render_tex));
glsafe(::glDeleteFramebuffers(1, &render_fbo));
if (multisample)
glsafe(::glDisable(GL_MULTISAMPLE));
}
void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
{
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
return;
bool multisample = m_multisample_allowed;
if (multisample)
glsafe(::glEnable(GL_MULTISAMPLE));
GLint max_samples;
glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples));
GLsizei num_samples = max_samples / 2;
GLuint render_fbo;
glsafe(::glGenFramebuffersEXT(1, &render_fbo));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo));
GLuint render_tex = 0;
GLuint render_tex_buffer = 0;
if (multisample)
{
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer));
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer));
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h));
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer));
}
else
{
glsafe(::glGenTextures(1, &render_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
}
GLuint render_depth;
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
if (multisample)
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h));
else
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h));
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
{
render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
if (multisample)
{
GLuint resolve_fbo;
glsafe(::glGenFramebuffersEXT(1, &resolve_fbo));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo));
GLuint resolve_tex;
glsafe(::glGenTextures(1, &resolve_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0));
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
{
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo));
glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo));
glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo));
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
}
glsafe(::glDeleteTextures(1, &resolve_tex));
glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo));
}
else
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail(thumbnail_data);
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
}
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
if (render_tex_buffer != 0)
glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer));
if (render_tex != 0)
glsafe(::glDeleteTextures(1, &render_tex));
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
if (multisample)
glsafe(::glDisable(GL_MULTISAMPLE));
}
void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
{
// check that thumbnail size does not exceed the default framebuffer size
const Size& cnv_size = get_canvas_size();
unsigned int cnv_w = (unsigned int)cnv_size.get_width();
unsigned int cnv_h = (unsigned int)cnv_size.get_height();
if ((w > cnv_w) || (h > cnv_h))
{
float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h);
w = (unsigned int)(ratio * (float)w);
h = (unsigned int)(ratio * (float)h);
}
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
return;
render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
debug_output_thumbnail(thumbnail_data);
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
// restore the default framebuffer size to avoid flickering on the 3D scene
m_camera.apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height());
}
#endif // ENABLE_THUMBNAIL_GENERATOR
bool GLCanvas3D::_init_toolbars()
{
if (!_init_main_toolbar())
@ -3835,12 +4218,21 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
return bb;
}
#if ENABLE_THUMBNAIL_GENERATOR
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor)
{
const Size& cnv_size = get_canvas_size();
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor);
m_dirty = true;
}
#else
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box)
{
const Size& cnv_size = get_canvas_size();
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height());
m_dirty = true;
}
#endif // ENABLE_THUMBNAIL_GENERATOR
void GLCanvas3D::_refresh_if_shown_on_screen()
{
@ -4037,7 +4429,9 @@ void GLCanvas3D::_render_objects() const
if (m_volumes.empty())
return;
#if !ENABLE_THUMBNAIL_GENERATOR
glsafe(::glEnable(GL_LIGHTING));
#endif // !ENABLE_THUMBNAIL_GENERATOR
glsafe(::glEnable(GL_DEPTH_TEST));
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
@ -4081,7 +4475,9 @@ void GLCanvas3D::_render_objects() const
m_shader.stop_using();
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
#if !ENABLE_THUMBNAIL_GENERATOR
glsafe(::glDisable(GL_LIGHTING));
#endif // !ENABLE_THUMBNAIL_GENERATOR
}
void GLCanvas3D::_render_selection() const
@ -4987,19 +5383,21 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
// helper functions to select data in dependence of the extrusion view type
struct Helper
{
static float path_filter(GCodePreviewData::Extrusion::EViewType type, const ExtrusionPath& path)
static float path_filter(GCodePreviewData::Extrusion::EViewType type, const GCodePreviewData::Extrusion::Path& path)
{
switch (type)
{
case GCodePreviewData::Extrusion::FeatureType:
// The role here is used for coloring.
return (float)path.role();
return (float)path.extrusion_role;
case GCodePreviewData::Extrusion::Height:
return path.height;
case GCodePreviewData::Extrusion::Width:
return path.width;
case GCodePreviewData::Extrusion::Feedrate:
return path.feedrate;
case GCodePreviewData::Extrusion::FanSpeed:
return path.fan_speed;
case GCodePreviewData::Extrusion::VolumetricRate:
return path.feedrate * (float)path.mm3_per_mm;
case GCodePreviewData::Extrusion::Tool:
@ -5025,6 +5423,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
return data.get_width_color(value);
case GCodePreviewData::Extrusion::Feedrate:
return data.get_feedrate_color(value);
case GCodePreviewData::Extrusion::FanSpeed:
return data.get_fan_speed_color(value);
case GCodePreviewData::Extrusion::VolumetricRate:
return data.get_volumetric_rate_color(value);
case GCodePreviewData::Extrusion::Tool:
@ -5067,15 +5467,15 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
{
std::vector<size_t> num_paths_per_role(size_t(erCount), 0);
for (const GCodePreviewData::Extrusion::Layer &layer : preview_data.extrusion.layers)
for (const ExtrusionPath &path : layer.paths)
++ num_paths_per_role[size_t(path.role())];
for (const GCodePreviewData::Extrusion::Path &path : layer.paths)
++ num_paths_per_role[size_t(path.extrusion_role)];
std::vector<std::vector<float>> roles_values;
roles_values.assign(size_t(erCount), std::vector<float>());
for (size_t i = 0; i < roles_values.size(); ++ i)
roles_values[i].reserve(num_paths_per_role[i]);
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
for (const ExtrusionPath& path : layer.paths)
roles_values[size_t(path.role())].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path));
for (const GCodePreviewData::Extrusion::Path &path : layer.paths)
roles_values[size_t(path.extrusion_role)].emplace_back(Helper::path_filter(preview_data.extrusion.view_type, path));
roles_filters.reserve(size_t(erCount));
size_t num_buffers = 0;
for (std::vector<float> &values : roles_values) {
@ -5103,9 +5503,9 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
// populates volumes
for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers)
{
for (const ExtrusionPath& path : layer.paths)
for (const GCodePreviewData::Extrusion::Path& path : layer.paths)
{
std::vector<std::pair<float, GLVolume*>> &filters = roles_filters[size_t(path.role())];
std::vector<std::pair<float, GLVolume*>> &filters = roles_filters[size_t(path.extrusion_role)];
auto key = std::make_pair<float, GLVolume*>(Helper::path_filter(preview_data.extrusion.view_type, path), nullptr);
auto it_filter = std::lower_bound(filters.begin(), filters.end(), key);
assert(it_filter != filters.end() && key.first == it_filter->first);
@ -5115,7 +5515,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size());
vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size());
_3DScene::extrusionentity_to_verts(path, layer.z, vol);
_3DScene::extrusionentity_to_verts(path.polyline, path.width, path.height, layer.z, vol);
}
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
for (std::vector<std::pair<float, GLVolume*>> &filters : roles_filters) {
@ -5280,20 +5680,18 @@ void GLCanvas3D::_load_fff_shells()
// adds wipe tower's volume
double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
const PrintConfig& config = print->config();
unsigned int extruders_count = config.nozzle_diameter.size();
size_t extruders_count = config.nozzle_diameter.size();
if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) {
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4);
double nozzle_diameter = print->config().nozzle_diameter.values[0];
float depth = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth;
float brim_width = print->wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width;
if (!print->is_step_done(psWipeTower))
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1);
m_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_spacing * 4.5f, m_initialized);
!print->is_step_done(psWipeTower), brim_width, m_initialized);
}
}
}
@ -5336,8 +5734,8 @@ void GLCanvas3D::_load_sla_shells()
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool))
add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad))
add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
}
double shift_z = obj->get_current_elevation();
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {

View file

@ -36,6 +36,9 @@ class GLShader;
class ExPolygon;
class BackgroundSlicingProcess;
class GCodePreviewData;
#if ENABLE_THUMBNAIL_GENERATOR
struct ThumbnailData;
#endif // ENABLE_THUMBNAIL_GENERATOR
struct SlicingParameters;
enum LayerHeightEditActionType : unsigned int;
@ -104,6 +107,10 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
class GLCanvas3D
{
#if ENABLE_THUMBNAIL_GENERATOR
static const double DefaultCameraZoomToBoxMarginFactor;
#endif // ENABLE_THUMBNAIL_GENERATOR
public:
struct GCodePreviewVolumeIndex
{
@ -519,6 +526,11 @@ public:
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
void render();
#if ENABLE_THUMBNAIL_GENERATOR
// printable_only == false -> render also non printable volumes as grayed
// parts_only == false -> render also sla support and pad
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
#endif // ENABLE_THUMBNAIL_GENERATOR
void select_all();
void deselect_all();
@ -624,6 +636,8 @@ public:
bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj(const char* filename) const;
void mouse_up_cleanup();
private:
bool _is_shown_on_screen() const;
@ -636,7 +650,11 @@ private:
BoundingBoxf3 _max_bounding_box(bool include_gizmos, bool include_bed_model) const;
#if ENABLE_THUMBNAIL_GENERATOR
void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor);
#else
void _zoom_to_box(const BoundingBoxf3& box);
#endif // ENABLE_THUMBNAIL_GENERATOR
void _refresh_if_shown_on_screen();
@ -664,6 +682,14 @@ private:
void _render_sla_slices() const;
void _render_selection_sidebar_hints() const;
void _render_undo_redo_stack(const bool is_undo, float pos_x);
#if ENABLE_THUMBNAIL_GENERATOR
// render thumbnail using an off-screen framebuffer
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
// render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
// render thumbnail using the default framebuffer
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
#endif // ENABLE_THUMBNAIL_GENERATOR
void _update_volumes_hover_state() const;

View file

@ -107,7 +107,9 @@ void GLCanvas3DManager::GLInfo::detect() const
m_renderer = data;
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
m_max_tex_size /= 2;
if (GLEW_EXT_texture_filter_anisotropic)
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy));
@ -187,6 +189,7 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten
GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
bool GLCanvas3DManager::s_compressed_textures_supported = false;
GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::FB_None;
GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info;
GLCanvas3DManager::GLCanvas3DManager()
@ -267,6 +270,13 @@ void GLCanvas3DManager::init_gl()
else
s_compressed_textures_supported = false;
if (GLEW_ARB_framebuffer_object)
s_framebuffers_type = FB_Arb;
else if (GLEW_EXT_framebuffer_object)
s_framebuffers_type = FB_Ext;
else
s_framebuffers_type = FB_None;
if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) {
// Complain about the OpenGL version.
wxString message = wxString::Format(

View file

@ -30,6 +30,13 @@ struct Camera;
class GLCanvas3DManager
{
public:
enum EFramebufferType : unsigned char
{
FB_None,
FB_Arb,
FB_Ext
};
class GLInfo
{
mutable bool m_detected;
@ -77,6 +84,7 @@ private:
bool m_gl_initialized;
static EMultisampleState s_multisample;
static bool s_compressed_textures_supported;
static EFramebufferType s_framebuffers_type;
public:
GLCanvas3DManager();
@ -97,6 +105,8 @@ public:
static bool can_multisample() { return s_multisample == MS_Enabled; }
static bool are_compressed_textures_supported() { return s_compressed_textures_supported; }
static bool are_framebuffers_supported() { return (s_framebuffers_type != FB_None); }
static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; }
static wxGLCanvas* create_wxglcanvas(wxWindow *parent);

View file

@ -107,8 +107,8 @@ void GLTexture::Compressor::compress()
break;
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
// crashes if doing so, so we start with twice the required size
level.compressed_data = std::vector<unsigned char>(level.w * level.h * 2, 0);
// crashes if doing so, requiring a minimum of 16 bytes and up to a third of the source buffer size, so we set the destination buffer initial size to be half the source buffer size
level.compressed_data = std::vector<unsigned char>(std::max((unsigned int)16, level.w * level.h * 2), 0);
int compressed_size = 0;
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
level.compressed_data.resize(compressed_size);
@ -455,8 +455,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
// we do not need to generate all levels down to 1x1
while ((lod_w > 16) || (lod_h > 16))
while ((lod_w > 1) || (lod_h > 1))
{
++level;
@ -600,8 +599,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
// we do not need to generate all levels down to 1x1
while ((lod_w > 16) || (lod_h > 16))
while ((lod_w > 1) || (lod_h > 1))
{
++level;

View file

@ -101,49 +101,6 @@ const std::string& shortkey_alt_prefix()
return str;
}
bool config_wizard_startup(bool app_config_exists)
{
if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) {
config_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (get_app_config()->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
config_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}
return false;
}
void config_wizard(int reason)
{
// Exit wizard if there are unsaved changes and the user cancels the action.
if (! wxGetApp().check_unsaved_changes())
return;
try {
ConfigWizard wizard(nullptr, static_cast<ConfigWizard::RunReason>(reason));
wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater);
}
catch (const std::exception &e) {
show_error(nullptr, e.what());
}
wxGetApp().load_current_presets();
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model()))
{
show_info(nullptr,
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
_(L("Please check and fix your object list.")),
_(L("Attention!")));
}
}
// opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element)
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
{

View file

@ -35,14 +35,6 @@ extern AppConfig* get_app_config();
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
// Checks if configuration wizard needs to run, calls config_wizard if so.
// Returns whether the Wizard ran.
extern bool config_wizard_startup(bool app_config_exists);
// Opens the configuration wizard, returns true if wizard is finished & accepted.
// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl.
extern void config_wizard(int run_reason);
// Change option value in config
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);

View file

@ -38,7 +38,6 @@
#include "../Utils/PresetUpdater.hpp"
#include "../Utils/PrintHost.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "ConfigWizard.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp"
@ -46,11 +45,17 @@
#include "Tab.hpp"
#include "SysInfoDialog.hpp"
#include "KBShortcutsDialog.hpp"
#include "UpdateDialogs.hpp"
#ifdef __WXMSW__
#include <Shlobj.h>
#endif // __WXMSW__
#if ENABLE_THUMBNAIL_GENERATOR
#include <boost/beast/core/detail/base64.hpp>
#include <boost/nowide/fstream.hpp>
#endif // ENABLE_THUMBNAIL_GENERATOR
namespace Slic3r {
namespace GUI {
@ -148,6 +153,7 @@ GUI_App::GUI_App()
: wxApp()
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_wizard(nullptr)
{}
GUI_App::~GUI_App()
@ -204,7 +210,6 @@ bool GUI_App::on_init_inner()
// supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle->setup_directories();
app_conf_exists = app_config->exists();
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
@ -287,7 +292,7 @@ bool GUI_App::on_init_inner()
}
CallAfter([this] {
config_wizard_startup(app_conf_exists);
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
@ -826,7 +831,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
switch (event.GetId() - config_id_base) {
case ConfigMenuWizard:
config_wizard(ConfigWizard::RR_USER);
run_wizard(ConfigWizard::RR_USER);
break;
case ConfigMenuTakeSnapshot:
// Take a configuration snapshot.
@ -1057,6 +1062,142 @@ void GUI_App::open_web_page_localized(const std::string &http_address)
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe());
}
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (! m_wizard) {
m_wizard = new ConfigWizard(mainframe);
}
const bool res = m_wizard->run(reason, start_page);
if (res) {
load_current_presets();
if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA
&& Slic3r::model_has_multi_part_objects(wxGetApp().model())) {
GUI::show_info(nullptr,
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
_(L("Please check and fix your object list.")),
_(L("Attention!")));
}
}
return res;
}
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
void GUI_App::gcode_thumbnails_debug()
{
const std::string BEGIN_MASK = "; thumbnail begin";
const std::string END_MASK = "; thumbnail end";
std::string gcode_line;
bool reading_image = false;
unsigned int width = 0;
unsigned int height = 0;
wxFileDialog dialog(GetTopWindow(), _(L("Select a gcode file:")), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
std::string in_filename = into_u8(dialog.GetPath());
std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string();
boost::nowide::ifstream in_file(in_filename.c_str());
std::vector<std::string> rows;
std::string row;
if (in_file.good())
{
while (std::getline(in_file, gcode_line))
{
if (in_file.good())
{
if (boost::starts_with(gcode_line, BEGIN_MASK))
{
reading_image = true;
gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1);
std::string::size_type x_pos = gcode_line.find('x');
std::string width_str = gcode_line.substr(0, x_pos);
width = (unsigned int)::atoi(width_str.c_str());
std::string height_str = gcode_line.substr(x_pos + 1);
height = (unsigned int)::atoi(height_str.c_str());
row.clear();
}
else if (reading_image && boost::starts_with(gcode_line, END_MASK))
{
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
if (out_file.good())
{
std::string decoded = boost::beast::detail::base64_decode(row);
out_file.write(decoded.c_str(), decoded.length());
out_file.close();
}
#else
if (!row.empty())
{
rows.push_back(row);
row.clear();
}
if ((unsigned int)rows.size() == height)
{
std::vector<unsigned char> thumbnail(4 * width * height, 0);
for (unsigned int r = 0; r < (unsigned int)rows.size(); ++r)
{
std::string decoded_row = boost::beast::detail::base64_decode(rows[r]);
if ((unsigned int)decoded_row.length() == width * 4)
{
void* image_ptr = (void*)(thumbnail.data() + r * width * 4);
::memcpy(image_ptr, (const void*)decoded_row.c_str(), width * 4);
}
}
wxImage image(width, height);
image.InitAlpha();
for (unsigned int r = 0; r < height; ++r)
{
unsigned int rr = r * width;
for (unsigned int c = 0; c < width; ++c)
{
unsigned char* px = thumbnail.data() + 4 * (rr + c);
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
image.SetAlpha((int)c, (int)r, px[3]);
}
}
image.SaveFile(out_path + std::to_string(width) + "x" + std::to_string(height) + ".png", wxBITMAP_TYPE_PNG);
}
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
reading_image = false;
width = 0;
height = 0;
rows.clear();
}
else if (reading_image)
{
#if !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
if (!row.empty() && (gcode_line[1] == ' '))
{
rows.push_back(row);
row.clear();
}
#endif // !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
row += gcode_line.substr(2);
}
}
}
in_file.close();
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
{
if (name.empty()) { return; }
@ -1105,6 +1246,24 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
}
}
bool GUI_App::config_wizard_startup()
{
if (!app_conf_exists || preset_bundle->printers.size() <= 1) {
run_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (get_app_config()->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
run_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}
return false;
}
// static method accepting a wxWindow object as first parameter
// void warning_catcher{
// my($self, $message_dialog) = @_;

View file

@ -6,6 +6,7 @@
#include "libslic3r/PrintConfig.hpp"
#include "MainFrame.hpp"
#include "ImGuiWrapper.hpp"
#include "ConfigWizard.hpp"
#include <wx/app.h>
#include <wx/colour.h>
@ -69,6 +70,7 @@ enum ConfigMenuIDs {
};
class Tab;
class ConfigWizard;
static wxString dots("", wxConvUTF8);
@ -85,7 +87,7 @@ class GUI_App : public wxApp
wxFont m_bold_font;
wxFont m_normal_font;
size_t m_em_unit; // width of a "m"-symbol in pixels for current system font
int m_em_unit; // width of a "m"-symbol in pixels for current system font
// Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls
std::unique_ptr<wxLocale> m_wxLocale;
@ -96,13 +98,14 @@ class GUI_App : public wxApp
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
ConfigWizard* m_wizard; // Managed by wxWindow tree
public:
bool OnInit() override;
bool initialized() const { return m_initialized; }
GUI_App();
~GUI_App();
~GUI_App() override;
static unsigned get_colour_approx_luma(const wxColour &colour);
static bool dark_mode();
@ -121,8 +124,7 @@ public:
const wxFont& small_font() { return m_small_font; }
const wxFont& bold_font() { return m_bold_font; }
const wxFont& normal_font() { return m_normal_font; }
size_t em_unit() const { return m_em_unit; }
void set_em_unit(const size_t em_unit) { m_em_unit = em_unit; }
int em_unit() const { return m_em_unit; }
float toolbar_icon_scale(const bool is_limited = false) const;
void recreate_GUI();
@ -152,7 +154,7 @@ public:
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
wxString current_language_code_safe() const;
virtual bool OnExceptionInMainLoop();
virtual bool OnExceptionInMainLoop() override;
#ifdef __APPLE__
// wxWidgets override to get an event on open files.
@ -184,6 +186,12 @@ public:
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
void open_web_page_localized(const std::string &http_address);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
#if ENABLE_THUMBNAIL_GENERATOR
// temporary and debug only -> extract thumbnails from selected gcode and save them as png files
void gcode_thumbnails_debug();
#endif // ENABLE_THUMBNAIL_GENERATOR
private:
bool on_init_inner();
@ -191,6 +199,9 @@ private:
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
void window_pos_sanitize(wxTopLevelWindow* window);
bool select_language();
bool config_wizard_startup();
#ifdef __WXMSW__
void associate_3mf_files();
#endif // __WXMSW__

View file

@ -131,7 +131,7 @@ ObjectList::ObjectList(wxWindow* parent) :
{
wxDataViewItemArray sels;
GetSelections(sels);
if (sels.front() == m_last_selected_item)
if (! sels.empty() && sels.front() == m_last_selected_item)
m_last_selected_item = sels.back();
else
m_last_selected_item = event.GetItem();
@ -267,7 +267,8 @@ void ObjectList::create_objects_ctrl()
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
// column Extruder of the view control:
AppendColumn(create_objects_list_extruder_column(4));
AppendColumn(new wxDataViewColumn(_(L("Extruder")), new BitmapChoiceRenderer(),
colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE));
// column ItemEditing of the view control:
AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
@ -434,19 +435,6 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons
(*m_objects)[obj_idx]->config;
}
wxDataViewColumn* ObjectList::create_objects_list_extruder_column(size_t extruders_count)
{
wxArrayString choices;
choices.Add(_(L("default")));
for (int i = 1; i <= extruders_count; ++i)
choices.Add(wxString::Format("%d", i));
wxDataViewChoiceRenderer *c =
new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL);
wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, colExtruder,
8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
return column;
}
void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
{
for (size_t i = 0; i < m_objects->size(); ++i)
@ -457,24 +445,24 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder)
auto object = (*m_objects)[i];
wxString extruder;
if (!object->config.has("extruder") ||
object->config.option<ConfigOptionInt>("extruder")->value > max_extruder)
size_t(object->config.option<ConfigOptionInt>("extruder")->value) > max_extruder)
extruder = _(L("default"));
else
extruder = wxString::Format("%d", object->config.option<ConfigOptionInt>("extruder")->value);
m_objects_model->SetValue(extruder, item, colExtruder);
m_objects_model->SetExtruder(extruder, item);
if (object->volumes.size() > 1) {
for (size_t id = 0; id < object->volumes.size(); id++) {
item = m_objects_model->GetItemByVolumeId(i, id);
if (!item) continue;
if (!object->volumes[id]->config.has("extruder") ||
object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value > max_extruder)
size_t(object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value) > max_extruder)
extruder = _(L("default"));
else
extruder = wxString::Format("%d", object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value);
m_objects_model->SetValue(extruder, item, colExtruder);
m_objects_model->SetExtruder(extruder, item);
}
}
}
@ -486,19 +474,13 @@ void ObjectList::update_objects_list_extruder_column(size_t extruders_count)
if (printer_technology() == ptSLA)
extruders_count = 1;
wxDataViewChoiceRenderer* ch_render = dynamic_cast<wxDataViewChoiceRenderer*>(GetColumn(colExtruder)->GetRenderer());
if (ch_render->GetChoices().GetCount() - 1 == extruders_count)
return;
m_prevent_update_extruder_in_config = true;
if (m_objects && extruders_count > 1)
update_extruder_values_for_items(extruders_count);
// delete old extruder column
DeleteColumn(GetColumn(colExtruder));
// insert new created extruder column
InsertColumn(colExtruder, create_objects_list_extruder_column(extruders_count));
update_extruder_colors();
// set show/hide for this column
set_extruder_column_hidden(extruders_count <= 1);
//a workaround for a wrong last column width updating under OSX
@ -507,6 +489,11 @@ void ObjectList::update_objects_list_extruder_column(size_t extruders_count)
m_prevent_update_extruder_in_config = false;
}
void ObjectList::update_extruder_colors()
{
m_objects_model->UpdateColumValues(colExtruder);
}
void ObjectList::set_extruder_column_hidden(const bool hide) const
{
GetColumn(colExtruder)->SetHidden(hide);
@ -535,14 +522,10 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
m_config = &get_item_config(item);
}
wxVariant variant;
m_objects_model->GetValue(variant, item, colExtruder);
const wxString selection = variant.GetString();
if (!m_config || selection.empty())
if (!m_config)
return;
const int extruder = /*selection.size() > 1 ? 0 : */atoi(selection.c_str());
const int extruder = m_objects_model->GetExtruderNumber(item);
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
// update scene
@ -795,7 +778,13 @@ void ObjectList::OnChar(wxKeyEvent& event)
void ObjectList::OnContextMenu(wxDataViewEvent&)
{
list_manipulation(true);
// Do not show the context menu if the user pressed the right mouse button on the 3D scene and released it on the objects list
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true;
if (!evt_context_menu)
canvas->mouse_up_cleanup();
list_manipulation(evt_context_menu);
}
void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
@ -805,6 +794,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
const wxPoint pt = get_mouse_position_in_control();
HitTest(pt, item, col);
if (m_extruder_editor)
m_extruder_editor->Hide();
/* Note: Under OSX right click doesn't send "selection changed" event.
* It means that Selection() will be return still previously selected item.
* Thus under OSX we should force UnselectAll(), when item and col are nullptr,
@ -853,6 +845,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
fix_through_netfabb();
}
}
// workaround for extruder editing under OSX
else if (wxOSX && evt_context_menu && title == _("Extruder"))
extruder_editing();
#ifndef __WXMSW__
GetMainWindow()->SetToolTip(""); // hide tooltip
@ -894,6 +889,74 @@ void ObjectList::show_context_menu(const bool evt_context_menu)
wxGetApp().plater()->PopupMenu(menu);
}
void ObjectList::extruder_editing()
{
wxDataViewItem item = GetSelection();
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
return;
std::vector<wxBitmap*> icons = get_extruder_color_icons();
if (icons.empty())
return;
const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5;
wxPoint pos = get_mouse_position_in_control();
wxSize size = wxSize(column_width, -1);
pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5;
pos.y -= GetTextExtent("m").y;
if (!m_extruder_editor)
m_extruder_editor = new wxBitmapComboBox(this, wxID_ANY, wxEmptyString, pos, size,
0, nullptr, wxCB_READONLY);
else
{
m_extruder_editor->SetPosition(pos);
m_extruder_editor->SetMinSize(size);
m_extruder_editor->SetSize(size);
m_extruder_editor->Clear();
m_extruder_editor->Show();
}
int i = 0;
for (wxBitmap* bmp : icons) {
if (i == 0) {
m_extruder_editor->Append(_(L("default")), *bmp);
++i;
}
m_extruder_editor->Append(wxString::Format("%d", i), *bmp);
++i;
}
m_extruder_editor->SetSelection(m_objects_model->GetExtruderNumber(item));
auto set_extruder = [this]()
{
wxDataViewItem item = GetSelection();
if (!item) return;
const int selection = m_extruder_editor->GetSelection();
if (selection >= 0)
m_objects_model->SetExtruder(m_extruder_editor->GetString(selection), item);
m_extruder_editor->Hide();
};
// to avoid event propagation to other sidebar items
m_extruder_editor->Bind(wxEVT_COMBOBOX, [set_extruder](wxCommandEvent& evt)
{
set_extruder();
evt.StopPropagation();
});
/*
m_extruder_editor->Bind(wxEVT_KILL_FOCUS, [set_extruder](wxFocusEvent& evt)
{
set_extruder();
evt.Skip();
});*/
}
void ObjectList::copy()
{
// if (m_selection_mode & smLayer)
@ -1514,6 +1577,12 @@ void ObjectList::append_menu_item_export_stl(wxMenu* menu) const
menu->AppendSeparator();
}
void ObjectList::append_menu_item_reload_from_disk(wxMenu* menu) const
{
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")),
[this](wxCommandEvent&) { wxGetApp().plater()->reload_from_disk(); }, "", menu, []() { return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater());
}
void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const
{
const wxString name = _(L("Change extruder"));
@ -1563,6 +1632,7 @@ void ObjectList::create_object_popupmenu(wxMenu *menu)
append_menu_items_osx(menu);
#endif // __WXOSX__
append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_scale_selection_to_fit_print_volume(menu);
@ -1586,6 +1656,7 @@ void ObjectList::create_sla_object_popupmenu(wxMenu *menu)
append_menu_items_osx(menu);
#endif // __WXOSX__
append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
// rest of a object_sla_menu will be added later in:
@ -1598,8 +1669,9 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
append_menu_items_osx(menu);
#endif // __WXOSX__
append_menu_item_fix_through_netfabb(menu);
append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu);
append_menu_item_split(menu);
@ -2259,6 +2331,7 @@ void ObjectList::changed_object(const int obj_idx/* = -1*/) const
void ObjectList::part_selection_changed()
{
if (m_extruder_editor) m_extruder_editor->Hide();
int obj_idx = -1;
int volume_id = -1;
m_config = nullptr;
@ -2341,7 +2414,8 @@ void ObjectList::part_selection_changed()
wxGetApp().obj_manipul()->get_og()->set_name(" " + og_name + " ");
if (item) {
wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
// wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item));
wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id));
}
}
@ -2547,7 +2621,7 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
(*m_objects)[item->obj_idx]->config.has("extruder"))
{
const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option<ConfigOptionInt>("extruder")->value);
m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), colExtruder);
m_objects_model->SetExtruder(extruder, m_objects_model->GetItemById(item->obj_idx));
}
wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx);
}
@ -3822,7 +3896,7 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
/* We can change extruder for Object/Volume only.
* So, if Instance is selected, get its Object item and change it
*/
m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, colExtruder);
m_objects_model->SetExtruder(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item);
const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) :
m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));

View file

@ -12,6 +12,7 @@
#include "wxExtensions.hpp"
class wxBoxSizer;
class wxBitmapComboBox;
class wxMenuItem;
class ObjectDataViewModel;
class MenuWithSeparators;
@ -140,6 +141,8 @@ private:
DynamicPrintConfig *m_config {nullptr};
std::vector<ModelObject*> *m_objects{ nullptr };
wxBitmapComboBox *m_extruder_editor { nullptr };
std::vector<wxBitmap*> m_bmp_vector;
t_layer_config_ranges m_layer_config_ranges_cache;
@ -183,8 +186,8 @@ public:
void create_objects_ctrl();
void create_popup_menus();
wxDataViewColumn* create_objects_list_extruder_column(size_t extruders_count);
void update_objects_list_extruder_column(size_t extruders_count);
void update_extruder_colors();
// show/hide "Extruder" column for Objects List
void set_extruder_column_hidden(const bool hide) const;
// update extruder in current config
@ -210,6 +213,7 @@ public:
void selection_changed();
void show_context_menu(const bool evt_context_menu);
void extruder_editing();
#ifndef __WXOSX__
void key_event(wxKeyEvent& event);
#endif /* __WXOSX__ */
@ -233,7 +237,8 @@ public:
wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent);
void append_menu_items_osx(wxMenu* menu);
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
void append_menu_item_export_stl(wxMenu* menu) const ;
void append_menu_item_export_stl(wxMenu* menu) const;
void append_menu_item_reload_from_disk(wxMenu* menu) const;
void append_menu_item_change_extruder(wxMenu* menu) const;
void append_menu_item_delete(wxMenu* menu);
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);

View file

@ -112,40 +112,33 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo)
combo->SetValue(selection);
}
static void set_font_and_background_style(wxWindow* win, const wxFont& font)
{
win->SetFont(font);
win->SetBackgroundStyle(wxBG_STYLE_PAINT);
}
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
OG_Settings(parent, true)
#ifndef __APPLE__
, m_focused_option("")
#endif // __APPLE__
{
m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation");
m_og->set_name(_(L("Object Manipulation")));
m_og->label_width = 12;//125;
m_og->set_grid_vgap(5);
m_og->m_on_change = std::bind(&ObjectManipulation::on_change, this, std::placeholders::_1, std::placeholders::_2);
m_og->m_fill_empty_value = std::bind(&ObjectManipulation::on_fill_empty_value, this, std::placeholders::_1);
m_og->m_set_focus = [this](const std::string& opt_key)
{
#ifndef __APPLE__
m_focused_option = opt_key;
#endif // __APPLE__
// Load bitmaps to be used for the mirroring buttons:
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off");
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
// needed to show the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true);
};
const int border = wxOSX ? 0 : 4;
const int em = wxGetApp().em_unit();
m_main_grid_sizer = new wxFlexGridSizer(2, 3, 3); // "Name/label", "String name / Editors"
m_main_grid_sizer->SetFlexibleDirection(wxBOTH);
ConfigOptionDef def;
// Add "Name" label with warning icon
auto sizer = new wxBoxSizer(wxHORIZONTAL);
Line line = Line{ "Name", "Object name" };
auto manifold_warning_icon = [this](wxWindow* parent) {
m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
if (is_windows10())
m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e)
m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
if (is_windows10())
m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent& e)
{
// if object/sub-object has no errors
if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData())
@ -155,248 +148,269 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list());
});
return m_fix_throught_netfab_bitmap;
sizer->Add(m_fix_throught_netfab_bitmap);
auto name_label = new wxStaticText(m_parent, wxID_ANY, _(L("Name"))+":");
set_font_and_background_style(name_label, wxGetApp().normal_font());
name_label->SetToolTip(_(L("Object name")));
sizer->Add(name_label);
m_main_grid_sizer->Add(sizer);
// Add name of the item
const wxSize name_size = wxSize(20 * em, wxDefaultCoord);
m_item_name = new wxStaticText(m_parent, wxID_ANY, "", wxDefaultPosition, name_size, wxST_ELLIPSIZE_MIDDLE);
set_font_and_background_style(m_item_name, wxGetApp().bold_font());
m_main_grid_sizer->Add(m_item_name, 0, wxEXPAND);
// Add labels grid sizer
m_labels_grid_sizer = new wxFlexGridSizer(1, 3, 3); // "Name/label", "String name / Editors"
m_labels_grid_sizer->SetFlexibleDirection(wxBOTH);
// Add world local combobox
m_word_local_combo = create_word_local_combo(parent);
m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) {
this->set_world_coordinates(evt.GetSelection() != 1);
}), m_word_local_combo->GetId());
// Small trick to correct layouting in different view_mode :
// Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden
m_word_local_combo_sizer = new wxBoxSizer(wxHORIZONTAL);
m_empty_str = new wxStaticText(parent, wxID_ANY, "");
m_word_local_combo_sizer->Add(m_word_local_combo);
m_word_local_combo_sizer->Add(m_empty_str);
m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1)));
m_labels_grid_sizer->Add(m_word_local_combo_sizer);
// Text trick to grid sizer layout:
// Height of labels should be equivalent to the edit boxes
int height = wxTextCtrl(parent, wxID_ANY, "Br").GetBestHeight(-1);
#ifdef __WXGTK__
// On Linux button with bitmap has bigger height then regular button or regular TextCtrl
// It can cause a wrong alignment on show/hide of a reset buttons
const int bmp_btn_height = ScalableButton(parent, wxID_ANY, "undo") .GetBestHeight(-1);
if (bmp_btn_height > height)
height = bmp_btn_height;
#endif //__WXGTK__
auto add_label = [this, height](wxStaticText** label, const std::string& name, wxSizer* reciver = nullptr)
{
*label = new wxStaticText(m_parent, wxID_ANY, _(name) + ":");
set_font_and_background_style(m_move_Label, wxGetApp().normal_font());
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->SetMinSize(wxSize(-1, height));
sizer->Add(*label, 0, wxALIGN_CENTER_VERTICAL);
if (reciver)
reciver->Add(sizer);
else
m_labels_grid_sizer->Add(sizer);
m_rescalable_sizers.push_back(sizer);
};
line.near_label_widget = manifold_warning_icon;
def.label = "";
def.gui_type = "legend";
def.tooltip = L("Object name");
#ifdef __APPLE__
def.width = 20;
#else
def.width = 22;
#endif
def.set_default_value(new ConfigOptionString{ " " });
line.append_option(Option(def, "object_name"));
m_og->append_line(line);
// Add labels
add_label(&m_move_Label, L("Position"));
add_label(&m_rotate_Label, L("Rotation"));
const int field_width = 5;
// additional sizer for lock and labels "Scale" & "Size"
sizer = new wxBoxSizer(wxHORIZONTAL);
// Mirror button size:
const int mirror_btn_width = 3;
m_lock_bnt = new LockButton(parent, wxID_ANY);
m_lock_bnt->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
event.Skip();
wxTheApp->CallAfter([this]() { set_uniform_scaling(m_lock_bnt->IsLocked()); });
});
sizer->Add(m_lock_bnt, 0, wxALIGN_CENTER_VERTICAL);
// Legend for object modification
line = Line{ "", "" };
def.label = "";
def.type = coString;
def.width = field_width - mirror_btn_width;//field_width/*50*/;
auto v_sizer = new wxGridSizer(1, 3, 3);
// Load bitmaps to be used for the mirroring buttons:
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off");
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
add_label(&m_scale_Label, L("Scale"), v_sizer);
wxStaticText* size_Label {nullptr};
add_label(&size_Label, L("Size"), v_sizer);
if (wxOSX) set_font_and_background_style(size_Label, wxGetApp().normal_font());
sizer->Add(v_sizer, 0, wxLEFT, border);
m_labels_grid_sizer->Add(sizer);
m_main_grid_sizer->Add(m_labels_grid_sizer, 0, wxEXPAND);
// Add editors grid sizer
wxFlexGridSizer* editors_grid_sizer = new wxFlexGridSizer(5, 3, 3); // "Name/label", "String name / Editors"
editors_grid_sizer->SetFlexibleDirection(wxBOTH);
// Add Axes labels with icons
static const char axes[] = { 'X', 'Y', 'Z' };
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
const char label = axes[axis_idx];
def.set_default_value(new ConfigOptionString{ std::string(" ") + label });
Option option(def, std::string() + label + "_axis_legend");
wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label));
set_font_and_background_style(axis_name, wxGetApp().bold_font());
sizer = new wxBoxSizer(wxHORIZONTAL);
// Under OSX we use font, smaller than default font, so
// there is a next trick for an equivalent layout of coordinates combobox and axes labels in they own sizers
if (wxOSX)
sizer->SetMinSize(-1, m_word_local_combo->GetBestHeight(-1));
sizer->Add(axis_name, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
// We will add a button to toggle mirroring to each axis:
auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) {
wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
btn->SetBitmapDisabled_(m_mirror_bitmap_hidden);
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
btn->SetBitmapDisabled_(m_mirror_bitmap_hidden);
m_mirror_buttons[axis_idx].first = btn;
m_mirror_buttons[axis_idx].second = mbShown;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
m_mirror_buttons[axis_idx].first = btn;
m_mirror_buttons[axis_idx].second = mbShown;
btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) {
Axis axis = (Axis)(axis_idx + X);
if (m_mirror_buttons[axis_idx].second == mbHidden)
return;
sizer->AddStretchSpacer(2);
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL);
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) {
Axis axis = (Axis)(axis_idx + X);
if (m_mirror_buttons[axis_idx].second == mbHidden)
return;
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis));
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()){
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis));
}
}
else
return;
}
else
return;
// Update mirroring at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror(L("Set Mirror"));
UpdateAndShow(true);
});
// Update mirroring at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror(L("Set Mirror"));
UpdateAndShow(true);
});
return sizer;
};
option.side_widget = mirror_button;
line.append_option(option);
editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL);
}
line.near_label_widget = [this](wxWindow* parent) {
wxBitmapComboBox *combo = create_word_local_combo(parent);
combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent &evt) { this->set_world_coordinates(evt.GetSelection() != 1); }), combo->GetId());
m_word_local_combo = combo;
return combo;
};
m_og->append_line(line);
auto add_og_to_object_settings = [this, field_width](const std::string& option_name, const std::string& sidetext)
editors_grid_sizer->AddStretchSpacer(1);
editors_grid_sizer->AddStretchSpacer(1);
// add EditBoxes
auto add_edit_boxes = [this, editors_grid_sizer](const std::string& opt_key, int axis)
{
Line line = { _(option_name), "" };
ConfigOptionDef def;
def.type = coFloat;
def.set_default_value(new ConfigOptionFloat(0.0));
def.width = field_width/*50*/;
ManipulationEditor* editor = new ManipulationEditor(this, opt_key, axis);
m_editors.push_back(editor);
if (option_name == "Scale") {
// Add "uniform scaling" button in front of "Scale" option
line.near_label_widget = [this](wxWindow* parent) {
auto btn = new LockButton(parent, wxID_ANY);
btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){
event.Skip();
wxTheApp->CallAfter([btn, this]() { set_uniform_scaling(btn->IsLocked()); });
});
m_lock_bnt = btn;
return btn;
};
// Add reset scale button
auto reset_scale_button = [this](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
btn->SetToolTip(_(L("Reset scale")));
m_reset_scale_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale")));
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
});
return sizer;
};
line.append_widget(reset_scale_button);
}
else if (option_name == "Rotation") {
// Add reset rotation button
auto reset_rotation_button = [this](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
btn->SetToolTip(_(L("Reset rotation")));
m_reset_rotation_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) {
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
editors_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL);
};
// add Units
auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit)
{
wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
set_font_and_background_style(unit_text, wxGetApp().normal_font());
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_rotation(Vec3d::Zero());
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()){
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_rotation(Vec3d::Zero());
}
}
else
return;
// Unit text should be the same height as labels
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->SetMinSize(wxSize(-1, height));
sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL);
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate(L("Reset Rotation"));
UpdateAndShow(true);
});
return sizer;
};
line.append_widget(reset_rotation_button);
}
else if (option_name == "Position") {
// Add drop to bed button
auto drop_to_bed_button = [=](wxWindow* parent) {
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
btn->SetToolTip(_(L("Drop to bed")));
m_drop_to_bed_button = btn;
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn, wxBU_EXACTFIT);
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
// ???
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed")));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
});
return sizer;
};
line.append_widget(drop_to_bed_button);
}
// Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
else if (option_name == "Size") {
line.near_label_widget = [this](wxWindow* parent) {
return new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition,
create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize());
};
}
const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
for (const char *axis : { "_x", "_y", "_z" }) {
if (axis[1] == 'z')
def.sidetext = sidetext;
Option option = Option(def, lower_name + axis);
option.opt.full_width = true;
line.append_option(option);
}
return line;
editors_grid_sizer->Add(sizer);
m_rescalable_sizers.push_back(sizer);
};
// Settings table
m_og->sidetext_width = 3;
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label);
m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label);
m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label);
m_og->append_line(add_og_to_object_settings(L("Size"), "mm"));
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("position", axis_idx);
add_unit_text(L("mm"));
// call back for a rescale of button "Set uniform scale"
m_og->rescale_near_label_widget = [this](wxWindow* win) {
// rescale lock icon
auto *ctrl = dynamic_cast<LockButton*>(win);
if (ctrl != nullptr) {
ctrl->msw_rescale();
return;
// Add drop to bed button
m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
m_drop_to_bed_button->SetToolTip(_(L("Drop to bed")));
m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
// ???
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed")));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
});
editors_grid_sizer->Add(m_drop_to_bed_button);
if (win == m_fix_throught_netfab_bitmap)
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("rotation", axis_idx);
add_unit_text("°");
// Add reset rotation button
m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
m_reset_rotation_button->SetToolTip(_(L("Reset rotation")));
m_reset_rotation_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_rotation(Vec3d::Zero());
}
else if (selection.is_single_full_instance()) {
for (unsigned int idx : selection.get_volume_idxs()) {
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
volume->set_instance_rotation(Vec3d::Zero());
}
}
else
return;
// rescale "place" of the empty icon (to correct layout of the "Size" and "Scale")
if (dynamic_cast<wxStaticBitmap*>(win) != nullptr)
win->SetMinSize(create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize());
};
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate(L("Reset Rotation"));
UpdateAndShow(true);
});
editors_grid_sizer->Add(m_reset_rotation_button);
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("scale", axis_idx);
add_unit_text("%");
// Add reset scale button
m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
m_reset_scale_button->SetToolTip(_(L("Reset scale")));
m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale")));
change_scale_value(0, 100.);
change_scale_value(1, 100.);
change_scale_value(2, 100.);
});
editors_grid_sizer->Add(m_reset_scale_button);
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
add_edit_boxes("size", axis_idx);
add_unit_text("mm");
editors_grid_sizer->AddStretchSpacer(1);
m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
m_og->sizer->Clear(true);
m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border);
}
void ObjectManipulation::Show(const bool show)
{
@ -407,9 +421,9 @@ void ObjectManipulation::Show(const bool show)
if (show && wxGetApp().get_mode() != comSimple) {
// Show the label and the name of the STL in simple mode only.
// Label "Name: "
m_og->get_grid_sizer()->Show(size_t(0), false);
m_main_grid_sizer->Show(size_t(0), false);
// The actual name of the STL.
m_og->get_grid_sizer()->Show(size_t(1), false);
m_main_grid_sizer->Show(size_t(1), false);
}
}
@ -417,6 +431,7 @@ void ObjectManipulation::Show(const bool show)
// Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only.
bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple;
m_word_local_combo->Show(show_world_local_combo);
m_empty_str->Show(!show_world_local_combo);
}
}
@ -522,30 +537,40 @@ void ObjectManipulation::update_if_dirty()
if (label_cache != new_label_localized) {
label_cache = new_label_localized;
widget->SetLabel(new_label_localized);
if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font());
}
};
update_label(m_cache.move_label_string, m_new_move_label_string, m_move_Label);
update_label(m_cache.rotate_label_string, m_new_rotate_label_string, m_rotate_Label);
update_label(m_cache.scale_label_string, m_new_scale_label_string, m_scale_Label);
char axis[2] = "x";
for (int i = 0; i < 3; ++ i, ++ axis[0]) {
auto update = [this, i, &axis](Vec3d &cached, Vec3d &cached_rounded, const char *key, const Vec3d &new_value) {
enum ManipulationEditorKey
{
mePosition = 0,
meRotation,
meScale,
meSize
};
for (int i = 0; i < 3; ++ i) {
auto update = [this, i](Vec3d &cached, Vec3d &cached_rounded, ManipulationEditorKey key_id, const Vec3d &new_value) {
wxString new_text = double_to_string(new_value(i), 2);
double new_rounded;
new_text.ToDouble(&new_rounded);
if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
cached_rounded(i) = new_rounded;
m_og->set_value(std::string(key) + axis, new_text);
const int id = key_id*3+i;
if (id >= 0) m_editors[id]->set_value(new_text);
}
cached(i) = new_value(i);
};
update(m_cache.position, m_cache.position_rounded, "position_", m_new_position);
update(m_cache.scale, m_cache.scale_rounded, "scale_", m_new_scale);
update(m_cache.size, m_cache.size_rounded, "size_", m_new_size);
update(m_cache.rotation, m_cache.rotation_rounded, "rotation_", m_new_rotation);
update(m_cache.position, m_cache.position_rounded, mePosition, m_new_position);
update(m_cache.scale, m_cache.scale_rounded, meScale, m_new_scale);
update(m_cache.size, m_cache.size_rounded, meSize, m_new_size);
update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation);
}
if (selection.requires_uniform_scale()) {
m_lock_bnt->SetLock(true);
m_lock_bnt->SetToolTip(_(L("You cannot use non-uniform scaling mode for multiple objects/parts selection")));
@ -607,7 +632,11 @@ void ObjectManipulation::update_reset_buttons_visibility()
show_drop_to_bed = (std::abs(min_z) > EPSILON);
}
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] {
// There is a case (under OSX), when this function is called after the Manipulation panel is hidden
// So, let check if Manipulation panel is still shown for this moment
if (!this->IsShown())
return;
m_reset_rotation_button->Show(show_rotation);
m_reset_scale_button->Show(show_scale);
m_drop_to_bed_button->Show(show_drop_to_bed);
@ -673,20 +702,18 @@ void ObjectManipulation::update_mirror_buttons_visibility()
#ifndef __APPLE__
void ObjectManipulation::emulate_kill_focus()
{
if (m_focused_option.empty())
if (!m_focused_editor)
return;
// we need to use a copy because the value of m_focused_option is modified inside on_change() and on_fill_empty_value()
std::string option = m_focused_option;
// see TextCtrl::propagate_value()
if (static_cast<wxTextCtrl*>(m_og->get_fieldc(option, 0)->getWindow())->GetValue().empty())
on_fill_empty_value(option);
else
on_change(option, 0);
m_focused_editor->kill_focus(this);
}
#endif // __APPLE__
void ObjectManipulation::update_item_name(const wxString& item_name)
{
m_item_name->SetLabel(item_name);
}
void ObjectManipulation::update_warning_icon_state(const wxString& tooltip)
{
m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
@ -817,76 +844,21 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale"));
}
void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value)
void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value)
{
Field* field = m_og->get_field(opt_key);
bool enter_pressed = (field != nullptr) && field->get_enter_pressed();
if (!enter_pressed)
{
// if the change does not come from the user pressing the ENTER key
// we need to hide the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
#ifndef __APPLE__
m_focused_option = "";
#endif // __APPLE__
}
else
// if the change comes from the user pressing the ENTER key, restore the key state
field->set_enter_pressed(false);
if (!m_cache.is_valid())
return;
int axis = opt_key.back() - 'x';
double new_value = boost::any_cast<double>(m_og->get_value(opt_key));
if (boost::starts_with(opt_key, "position_"))
if (opt_key == "position")
change_position_value(axis, new_value);
else if (boost::starts_with(opt_key, "rotation_"))
else if (opt_key == "rotation")
change_rotation_value(axis, new_value);
else if (boost::starts_with(opt_key, "scale_"))
else if (opt_key == "scale")
change_scale_value(axis, new_value);
else if (boost::starts_with(opt_key, "size_"))
else if (opt_key == "size")
change_size_value(axis, new_value);
}
void ObjectManipulation::on_fill_empty_value(const std::string& opt_key)
{
// needed to hide the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false);
#ifndef __APPLE__
m_focused_option = "";
#endif // __APPLE__
if (!m_cache.is_valid())
return;
const Vec3d *vec = nullptr;
Vec3d *rounded = nullptr;
if (boost::starts_with(opt_key, "position_")) {
vec = &m_cache.position;
rounded = &m_cache.position_rounded;
} else if (boost::starts_with(opt_key, "rotation_")) {
vec = &m_cache.rotation;
rounded = &m_cache.rotation_rounded;
} else if (boost::starts_with(opt_key, "scale_")) {
vec = &m_cache.scale;
rounded = &m_cache.scale_rounded;
} else if (boost::starts_with(opt_key, "size_")) {
vec = &m_cache.size;
rounded = &m_cache.size_rounded;
} else
assert(false);
if (vec != nullptr) {
int axis = opt_key.back() - 'x';
wxString new_text = double_to_string((*vec)(axis));
m_og->set_value(opt_key, new_text);
new_text.ToDouble(&(*rounded)(axis));
}
}
void ObjectManipulation::set_uniform_scaling(const bool new_value)
{
const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
@ -923,7 +895,10 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
void ObjectManipulation::msw_rescale()
{
const int em = wxGetApp().em_unit();
m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord));
msw_rescale_word_local_combo(m_word_local_combo);
m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1)));
m_manifold_warning_bmp.msw_rescale();
const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText();
@ -936,12 +911,120 @@ void ObjectManipulation::msw_rescale()
m_reset_scale_button->msw_rescale();
m_reset_rotation_button->msw_rescale();
m_drop_to_bed_button->msw_rescale();
m_lock_bnt->msw_rescale();
for (int id = 0; id < 3; ++id)
m_mirror_buttons[id].first->msw_rescale();
// rescale label-heights
// Text trick to grid sizer layout:
// Height of labels should be equivalent to the edit boxes
const int height = wxTextCtrl(parent(), wxID_ANY, "Br").GetBestHeight(-1);
for (wxBoxSizer* sizer : m_rescalable_sizers)
sizer->SetMinSize(wxSize(-1, height));
// rescale edit-boxes
for (ManipulationEditor* editor : m_editors)
editor->msw_rescale();
get_og()->msw_rescale();
}
static const char axes[] = { 'x', 'y', 'z' };
ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
const std::string& opt_key,
int axis) :
wxTextCtrl(parent->parent(), wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(5*int(wxGetApp().em_unit()), wxDefaultCoord), wxTE_PROCESS_ENTER),
m_opt_key(opt_key),
m_axis(axis)
{
set_font_and_background_style(this, wxGetApp().normal_font());
#ifdef __WXOSX__
this->OSXDisableAllSmartSubstitutions();
#endif // __WXOSX__
// A name used to call handle_sidebar_focus_event()
m_full_opt_name = m_opt_key+"_"+axes[axis];
// Reset m_enter_pressed flag to _false_, when value is editing
this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
this->Bind(wxEVT_TEXT_ENTER, [this, parent](wxEvent&)
{
m_enter_pressed = true;
parent->on_change(m_opt_key, m_axis, get_value());
}, this->GetId());
this->Bind(wxEVT_KILL_FOCUS, [this, parent](wxFocusEvent& e)
{
parent->set_focused_editor(nullptr);
if (!m_enter_pressed)
kill_focus(parent);
e.Skip();
}, this->GetId());
this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e)
{
parent->set_focused_editor(this);
// needed to show the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, true);
e.Skip();
}, this->GetId());
this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event)
{
// select all text using Ctrl+A
if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
this->SetSelection(-1, -1); //select all
event.Skip();
}));
}
void ManipulationEditor::msw_rescale()
{
const int em = wxGetApp().em_unit();
SetMinSize(wxSize(5 * em, wxDefaultCoord));
}
double ManipulationEditor::get_value()
{
wxString str = GetValue();
double value;
// Replace the first occurence of comma in decimal number.
str.Replace(",", ".", false);
if (str == ".")
value = 0.0;
if ((str.IsEmpty() || !str.ToCDouble(&value)) && !m_valid_value.IsEmpty()) {
str = m_valid_value;
SetValue(str);
str.ToCDouble(&value);
}
return value;
}
void ManipulationEditor::set_value(const wxString& new_value)
{
if (new_value.IsEmpty())
return;
m_valid_value = new_value;
SetValue(m_valid_value);
}
void ManipulationEditor::kill_focus(ObjectManipulation* parent)
{
parent->on_change(m_opt_key, m_axis, get_value());
// if the change does not come from the user pressing the ENTER key
// we need to hide the visual hints in 3D scene
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, false);
}
} //namespace GUI
} //namespace Slic3r

View file

@ -16,6 +16,29 @@ namespace GUI {
class Selection;
class ObjectManipulation;
class ManipulationEditor : public wxTextCtrl
{
std::string m_opt_key;
int m_axis;
bool m_enter_pressed { false };
wxString m_valid_value {wxEmptyString};
std::string m_full_opt_name;
public:
ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, int axis);
~ManipulationEditor() {}
void msw_rescale();
void set_value(const wxString& new_value);
void kill_focus(ObjectManipulation *parent);
private:
double get_value();
};
class ObjectManipulation : public OG_Settings
{
struct Cache
@ -53,6 +76,9 @@ class ObjectManipulation : public OG_Settings
wxStaticText* m_scale_Label = nullptr;
wxStaticText* m_rotate_Label = nullptr;
wxStaticText* m_item_name = nullptr;
wxStaticText* m_empty_str = nullptr;
// Non-owning pointers to the reset buttons, so we can hide and show them.
ScalableButton* m_reset_scale_button = nullptr;
ScalableButton* m_reset_rotation_button = nullptr;
@ -81,7 +107,7 @@ class ObjectManipulation : public OG_Settings
Vec3d m_new_rotation;
Vec3d m_new_scale;
Vec3d m_new_size;
bool m_new_enabled;
bool m_new_enabled {true};
bool m_uniform_scale {true};
// Does the object manipulation panel work in World or Local coordinates?
bool m_world_coordinates = true;
@ -92,10 +118,19 @@ class ObjectManipulation : public OG_Settings
wxStaticBitmap* m_fix_throught_netfab_bitmap;
#ifndef __APPLE__
// Currently focused option name (empty if none)
std::string m_focused_option;
// Currently focused editor (nullptr if none)
ManipulationEditor* m_focused_editor {nullptr};
#endif // __APPLE__
wxFlexGridSizer* m_main_grid_sizer;
wxFlexGridSizer* m_labels_grid_sizer;
// sizers, used for msw_rescale
wxBoxSizer* m_word_local_combo_sizer;
std::vector<wxBoxSizer*> m_rescalable_sizers;
std::vector<ManipulationEditor*> m_editors;
public:
ObjectManipulation(wxWindow* parent);
~ObjectManipulation() {}
@ -122,8 +157,15 @@ public:
void emulate_kill_focus();
#endif // __APPLE__
void update_item_name(const wxString &item_name);
void update_warning_icon_state(const wxString& tooltip);
void msw_rescale();
void on_change(const std::string& opt_key, int axis, double new_value);
void set_focused_editor(ManipulationEditor* focused_editor) {
#ifndef __APPLE__
m_focused_editor = focused_editor;
#endif // __APPLE__
}
private:
void reset_settings_value();
@ -140,9 +182,6 @@ private:
void change_scale_value(int axis, double value);
void change_size_value(int axis, double value);
void do_scale(int axis, const Vec3d &scale) const;
void on_change(t_config_option_key opt_key, const boost::any& value);
void on_fill_empty_value(const std::string& opt_key);
};
}}

View file

@ -221,6 +221,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view
m_choice_view_type->Append(_(L("Height")));
m_choice_view_type->Append(_(L("Width")));
m_choice_view_type->Append(_(L("Speed")));
m_choice_view_type->Append(_(L("Fan speed")));
m_choice_view_type->Append(_(L("Volumetric flow rate")));
m_choice_view_type->Append(_(L("Tool")));
m_choice_view_type->Append(_(L("Color Print")));
@ -374,6 +375,8 @@ void Preview::load_print(bool keep_z_range)
load_print_as_fff(keep_z_range);
else if (tech == ptSLA)
load_print_as_sla();
Layout();
}
void Preview::reload_print(bool keep_volumes)

View file

@ -417,6 +417,9 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
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());
@ -442,7 +445,14 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
else if (evt.MiddleUp())
m_mouse_capture.middle = false;
else if (evt.RightUp())
{
m_mouse_capture.right = false;
if (pending_right_up)
{
pending_right_up = false;
processed = true;
}
}
else if (evt.Dragging() && m_mouse_capture.any())
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
@ -473,8 +483,12 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
}
}
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown))
{
// 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.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
// don't allow dragging objects with the Sla gizmo on
processed = true;

View file

@ -114,8 +114,17 @@ public:
m_serializing = true;
// Following is needed to know which to be turn on, but not actually modify
// m_current prematurely, so activate_gizmo is not confused.
EType old_current = m_current;
ar(m_current);
EType new_current = m_current;
m_current = old_current;
// activate_gizmo call sets m_current and calls set_state for the gizmo
// it does nothing in case the gizmo is already activated
// it can safely be called for Undefined gizmo
activate_gizmo(new_current);
if (m_current != Undefined)
m_gizmos[m_current]->load(ar);
}

View file

@ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const
const PrintObjects& objects = m_plater->sla_print().objects();
for (const SLAPrintObject* object : objects)
{
if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree))
if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree))
{
can_export = true;
break;
@ -682,6 +682,11 @@ void MainFrame::init_menubar()
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")),
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _(L("DEBUG gcode thumbnails")), _(L("DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails")),
[this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
}
// menubar

View file

@ -233,7 +233,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n
add_undo_buttuns_to_sizer(sizer, field);
if (is_window_field(field))
sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) |
sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) |
wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2);
if (is_sizer_field(field))
sizer->Add(field->getSizer(), 1, /*(*/option.opt.full_width ? wxEXPAND : /*0) |*/ wxALIGN_CENTER_VERTICAL, 0);

View file

@ -10,6 +10,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
@ -31,6 +32,9 @@
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#if ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
@ -71,6 +75,7 @@
#include "../Utils/PrintHost.hpp"
#include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp"
#include "../Utils/Thread.hpp"
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
#include "WipeTowerDialog.hpp"
@ -81,6 +86,11 @@ using Slic3r::_3DScene;
using Slic3r::Preset;
using Slic3r::PrintHostJob;
#if ENABLE_THUMBNAIL_GENERATOR
static const std::vector < std::pair<unsigned int, unsigned int>> THUMBNAIL_SIZE_FFF = { { 240, 320 }, { 220, 165 }, { 16, 16 } };
static const std::vector<std::pair<unsigned int, unsigned int>> THUMBNAIL_SIZE_SLA = { { 800, 480 } };
static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
#endif // ENABLE_THUMBNAIL_GENERATOR
namespace Slic3r {
namespace GUI {
@ -174,7 +184,7 @@ void ObjectInfo::msw_rescale()
manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation"));
}
enum SlisedInfoIdx
enum SlicedInfoIdx
{
siFilament_m,
siFilament_mm3,
@ -191,7 +201,7 @@ class SlicedInfo : public wxStaticBoxSizer
{
public:
SlicedInfo(wxWindow *parent);
void SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label="");
void SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label="");
private:
std::vector<std::pair<wxStaticText*, wxStaticText*>> info_vec;
@ -229,7 +239,7 @@ SlicedInfo::SlicedInfo(wxWindow *parent) :
this->Show(false);
}
void SlicedInfo::SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/)
void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/)
{
const bool show = text != "N/A";
if (show)
@ -251,11 +261,18 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 *
auto selected_item = this->GetSelection();
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
if (marker == LABEL_ITEM_MARKER || marker == LABEL_ITEM_CONFIG_WIZARD) {
if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) {
this->SetSelection(this->last_selected);
evt.StopPropagation();
if (marker == LABEL_ITEM_CONFIG_WIZARD)
wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); });
if (marker >= LABEL_ITEM_WIZARD_PRINTERS) {
ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME;
switch (marker) {
case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break;
case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break;
case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
}
wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); });
}
} else if ( this->last_selected != selected_item ||
wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) {
this->last_selected = selected_item;
@ -521,12 +538,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
const DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
std::vector<std::string> extruder_colours = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
const std::vector<std::string>& filament_colours = (wxGetApp().plater()->get_plater_config()->option<ConfigOptionStrings>("filament_colour"))->values;
for (size_t i=0; i<extruder_colours.size(); ++i)
if (extruder_colours[i] == "" && i < filament_colours.size())
extruder_colours[i] = filament_colours[i];
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
@ -1215,7 +1227,7 @@ void Sidebar::update_sliced_info_sizer()
}
// if there is a wipe tower, insert number of toolchanges info into the array:
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", p->plater->fff_print().wipe_tower_data().number_of_toolchanges) : "N/A");
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A");
// Hide non-FFF sliced info parameters
p->sliced_info->SetTextAndShow(siMateril_unit, "N/A");
@ -1377,6 +1389,9 @@ struct Plater::priv
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
Slic3r::GCodePreviewData gcode_preview_data;
#if ENABLE_THUMBNAIL_GENERATOR
std::vector<Slic3r::ThumbnailData> thumbnail_data;
#endif // ENABLE_THUMBNAIL_GENERATOR
// GUI elements
wxSizer* panel_sizer{ nullptr };
@ -1443,7 +1458,7 @@ struct Plater::priv
class Job : public wxEvtHandler
{
int m_range = 100;
std::future<void> m_ftr;
boost::thread m_thread;
priv * m_plater = nullptr;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
@ -1484,7 +1499,8 @@ struct Plater::priv
// Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just
// reload the modified matrices.
if (!was_canceled()) plater().update((unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
if (!was_canceled())
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
}
public:
@ -1513,9 +1529,9 @@ struct Plater::priv
}
Job(const Job &) = delete;
Job(Job &&) = default;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = default;
Job &operator=(Job &&) = delete;
virtual void process() = 0;
@ -1539,7 +1555,7 @@ struct Plater::priv
wxBeginBusyCursor();
try { // Execute the job
m_ftr = std::async(std::launch::async, &Job::run, this);
m_thread = create_thread([this] { this->run(); });
} catch (std::exception &) {
update_status(status_range(),
_(L("ERROR: not enough resources to "
@ -1555,16 +1571,15 @@ struct Plater::priv
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0) const
bool join(int timeout_ms = 0)
{
if (!m_ftr.valid()) return true;
if (!m_thread.joinable()) return true;
if (timeout_ms <= 0)
m_ftr.wait();
else if (m_ftr.wait_for(std::chrono::milliseconds(
timeout_ms)) == std::future_status::timeout)
m_thread.join();
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
return false;
return true;
}
@ -1594,7 +1609,8 @@ struct Plater::priv
size_t count = 0; // To know how much space to reserve
for (auto obj : model.objects) count += obj->instances.size();
m_selected.clear(), m_unselected.clear();
m_selected.clear();
m_unselected.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
}
@ -1608,11 +1624,12 @@ struct Plater::priv
// Set up arrange polygon for a ModelInstance and Wipe tower
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
ArrangePolygon ap = obj->get_arrange_polygon();
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
if (p.is_arranged()) {
auto t = p.translation; t.x() += p.bed_idx * bed_stride();
auto t = p.translation;
t.x() += p.bed_idx * bed_stride();
obj->apply_arrange_result(t, p.rotation);
}
};
@ -1643,7 +1660,8 @@ struct Plater::priv
obj_sel(model.objects.size(), nullptr);
for (auto &s : plater().get_selection().get_content())
if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second;
if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = &s.second;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
@ -1653,7 +1671,8 @@ struct Plater::priv
std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist)
for (auto inst_id : *instlist) inst_sel[inst_id] = true;
for (auto inst_id : *instlist)
inst_sel[size_t(inst_id)] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
@ -1925,6 +1944,11 @@ struct Plater::priv
bool can_fix_through_netfabb() const;
bool can_set_instance_to_object() const;
bool can_mirror() const;
bool can_reload_from_disk() const;
#if ENABLE_THUMBNAIL_GENERATOR
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
#endif // ENABLE_THUMBNAIL_GENERATOR
void msw_rescale_object_menu();
@ -1961,7 +1985,6 @@ private:
* */
std::string m_last_fff_printer_profile_name;
std::string m_last_sla_printer_profile_name;
bool m_update_objects_list_on_loading{ true };
};
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
@ -1994,6 +2017,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
background_process.set_fff_print(&fff_print);
background_process.set_sla_print(&sla_print);
background_process.set_gcode_preview_data(&gcode_preview_data);
#if ENABLE_THUMBNAIL_GENERATOR
background_process.set_thumbnail_data(&thumbnail_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
// Default printer technology for default config.
@ -2487,11 +2513,8 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
_(L("Object too large?")));
}
if (m_update_objects_list_on_loading)
{
for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx);
}
for (const size_t idx : obj_idxs) {
wxGetApp().obj_list()->add_object_to_list(idx);
}
update();
@ -2782,9 +2805,8 @@ void Plater::priv::ArrangeJob::process() {
try {
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
[this, count](unsigned st) {
if (st >
0) // will not finalize after last one
update_status(count - st, arrangestr);
if (st > 0) // will not finalize after last one
update_status(int(count - st), arrangestr);
},
[this]() { return was_canceled(); });
} catch (std::exception & /*e*/) {
@ -3042,6 +3064,34 @@ bool Plater::priv::restart_background_process(unsigned int state)
( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
(state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
(state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
#if ENABLE_THUMBNAIL_GENERATOR
if (((state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) == 0) &&
(this->background_process.state() != BackgroundSlicingProcess::STATE_RUNNING))
{
// update thumbnail data
if (this->printer_technology == ptFFF)
{
// for ptFFF we need to generate the thumbnails before the export of gcode starts
this->thumbnail_data.clear();
for (const std::pair<unsigned int, unsigned int>& size : THUMBNAIL_SIZE_FFF)
{
this->thumbnail_data.push_back(ThumbnailData());
generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, true, false);
}
}
else if (this->printer_technology == ptSLA)
{
// for ptSLA generate thumbnails without supports and pad (not yet calculated)
// to render also supports and pad see on_slicing_update()
this->thumbnail_data.clear();
for (const std::pair<unsigned int, unsigned int>& size : THUMBNAIL_SIZE_SLA)
{
this->thumbnail_data.push_back(ThumbnailData());
generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, true, false);
}
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
// The print is valid and it can be started.
if (this->background_process.start()) {
this->statusbar()->set_cancel_callback([this]() {
@ -3116,88 +3166,110 @@ void Plater::priv::update_sla_scene()
void Plater::priv::reload_from_disk()
{
Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk")));
Plater::TakeSnapshot snapshot(q, _(L("Reload from disk")));
auto& selection = get_selection();
const auto obj_orig_idx = selection.get_object_idx();
if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; }
int instance_idx = selection.get_instance_idx();
const Selection& selection = get_selection();
auto *object_orig = model.objects[obj_orig_idx];
std::vector<fs::path> input_paths(1, object_orig->input_file);
// disable render to avoid to show intermediate states
view3D->get_canvas3d()->enable_render(false);
// disable update of objects list while loading to avoid to show intermediate states
m_update_objects_list_on_loading = false;
const auto new_idxs = load_files(input_paths, true, false);
if (new_idxs.empty())
{
// error while loading
view3D->get_canvas3d()->enable_render(true);
if (selection.is_wipe_tower())
return;
}
for (const auto idx : new_idxs)
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
ModelObject *object = model.objects[idx];
object->config.apply(object_orig->config);
int object_idx;
int volume_idx;
object->clear_instances();
for (const ModelInstance *instance : object_orig->instances)
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
};
std::vector<SelectedVolume> selected_volumes;
// collects selected ModelVolumes
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs)
{
const GLVolume* v = selection.get_volume(idx);
int o_idx = v->object_idx();
int v_idx = v->volume_idx();
selected_volumes.push_back({ o_idx, v_idx });
}
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
// collects paths of files to load
std::vector<fs::path> input_paths;
for (const SelectedVolume& v : selected_volumes)
{
const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx];
if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file))
input_paths.push_back(volume->source.input_file);
}
std::sort(input_paths.begin(), input_paths.end());
input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end());
// load one file at a time
for (size_t i = 0; i < input_paths.size(); ++i)
{
const auto& path = input_paths[i].string();
Model new_model;
try
{
object->add_instance(*instance);
}
for (const ModelVolume* v : object_orig->volumes)
{
if (v->is_modifier())
object->add_volume(*v);
}
Vec3d offset = object_orig->origin_translation - object->origin_translation;
if (object->volumes.size() == object_orig->volumes.size())
{
for (size_t i = 0; i < object->volumes.size(); i++)
new_model = Model::read_from_file(path, nullptr, true, false);
for (ModelObject* model_object : new_model.objects)
{
object->volumes[i]->config.apply(object_orig->volumes[i]->config);
object->volumes[i]->translate(offset);
model_object->center_around_origin();
model_object->ensure_on_bed();
}
}
catch (std::exception&)
{
// error while loading
view3D->get_canvas3d()->enable_render(true);
return;
}
// XXX: Restore more: layer_height_ranges, layer_height_profile (?)
// update the selected volumes whose source is the current file
for (const SelectedVolume& old_v : selected_volumes)
{
ModelObject* old_model_object = model.objects[old_v.object_idx];
ModelVolume* old_volume = old_model_object->volumes[old_v.volume_idx];
int new_volume_idx = old_volume->source.volume_idx;
int new_object_idx = old_volume->source.object_idx;
if (old_volume->source.input_file == path)
{
if (new_object_idx < (int)new_model.objects.size())
{
ModelObject* new_model_object = new_model.objects[new_object_idx];
if (new_volume_idx < (int)new_model_object->volumes.size())
{
old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]);
ModelVolume* new_volume = old_model_object->volumes.back();
new_volume->set_new_unique_id();
new_volume->config.apply(old_volume->config);
new_volume->set_type(old_volume->type());
new_volume->set_material_id(old_volume->material_id());
new_volume->set_transformation(old_volume->get_transformation());
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
}
}
}
}
}
// re-enable update of objects list
m_update_objects_list_on_loading = true;
model.adjust_min_z();
// puts the new objects into the list
for (const auto idx : new_idxs)
{
wxGetApp().obj_list()->add_object_to_list(idx);
}
remove(obj_orig_idx);
// update 3D scene
update();
// new GLVolumes have been created at this point, so update their printable state
for (size_t i = 0; i < model.objects.size(); ++i)
{
view3D->get_canvas3d()->update_instance_printable_state_for_object(i);
}
// re-enable render
view3D->get_canvas3d()->enable_render(true);
// the previous call to remove() clears the selection
// select newly added objects
selection.clear();
for (const auto idx : new_idxs)
{
selection.add_instance((unsigned int)idx - 1, instance_idx, false);
}
}
void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
@ -3357,6 +3429,23 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
this->preview->reload_print();
// uncomment the following lines if you want to render into the thumbnail also supports and pad for SLA printer
/*
#if ENABLE_THUMBNAIL_GENERATOR
// update thumbnail data
// for ptSLA generate the thumbnail after supports and pad have been calculated to have them rendered
if ((this->printer_technology == ptSLA) && (evt.status.percent == -3))
{
this->thumbnail_data.clear();
for (const std::pair<unsigned int, unsigned int>& size : THUMBNAIL_SIZE_SLA)
{
this->thumbnail_data.push_back(ThumbnailData());
generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, false, false);
}
}
#endif // ENABLE_THUMBNAIL_GENERATOR
*/
}
}
@ -3582,6 +3671,13 @@ bool Plater::priv::init_object_menu()
return true;
}
#if ENABLE_THUMBNAIL_GENERATOR
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
{
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, transparent_background);
}
#endif // ENABLE_THUMBNAIL_GENERATOR
void Plater::priv::msw_rescale_object_menu()
{
for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu })
@ -3622,6 +3718,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")),
[this](wxCommandEvent&) { q->remove_selected(); }, "delete", nullptr, [this]() { return can_delete(); }, q);
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected volumes from disk")),
[this](wxCommandEvent&) { q->reload_from_disk(); }, "", menu, [this]() { return can_reload_from_disk(); }, q);
sidebar->obj_list()->append_menu_item_export_stl(menu);
}
else {
@ -3648,8 +3747,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q);
menu->AppendSeparator();
append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")),
[this](wxCommandEvent&) { reload_from_disk(); });
append_menu_item(menu, wxID_ANY, _(L("Reload from disk")), _(L("Reload the selected object from disk")),
[this](wxCommandEvent&) { reload_from_disk(); }, "", nullptr, [this]() { return can_reload_from_disk(); }, q);
append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
[this](wxCommandEvent&) { q->export_stl(false, true); });
@ -3804,6 +3903,48 @@ bool Plater::priv::can_mirror() const
return get_selection().is_from_single_instance();
}
bool Plater::priv::can_reload_from_disk() const
{
// struct to hold selected ModelVolumes by their indices
struct SelectedVolume
{
int object_idx;
int volume_idx;
// operators needed by std::algorithms
bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); }
bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); }
};
std::vector<SelectedVolume> selected_volumes;
const Selection& selection = get_selection();
// collects selected ModelVolumes
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs)
{
const GLVolume* v = selection.get_volume(idx);
int v_idx = v->volume_idx();
if (v_idx >= 0)
selected_volumes.push_back({ v->object_idx(), v_idx });
}
std::sort(selected_volumes.begin(), selected_volumes.end());
selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end());
// collects paths of files to load
std::vector<fs::path> paths;
for (const SelectedVolume& v : selected_volumes)
{
const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx];
if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file))
paths.push_back(volume->source.input_file);
}
std::sort(paths.begin(), paths.end());
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
return !paths.empty();
}
void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
{
bool new_shape = bed.set_shape(shape, custom_texture, custom_model);
@ -4485,10 +4626,10 @@ void Plater::export_stl(bool extended, bool selection_only)
bool is_left_handed = object->is_left_handed();
TriangleMesh pad_mesh;
bool has_pad_mesh = object->has_mesh(slaposBasePool);
bool has_pad_mesh = object->has_mesh(slaposPad);
if (has_pad_mesh)
{
pad_mesh = object->get_mesh(slaposBasePool);
pad_mesh = object->get_mesh(slaposPad);
pad_mesh.transform(mesh_trafo_inv);
}
@ -4575,7 +4716,13 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
#if ENABLE_THUMBNAIL_GENERATOR
ThumbnailData thumbnail_data;
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true);
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, &thumbnail_data)) {
#else
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
#endif // ENABLE_THUMBNAIL_GENERATOR
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path));
p->set_project_filename(path);
@ -4586,6 +4733,11 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
}
}
void Plater::reload_from_disk()
{
p->reload_from_disk();
}
bool Plater::has_toolpaths_to_export() const
{
return p->preview->get_canvas3d()->has_toolpaths_to_export();
@ -4664,7 +4816,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error
// Otherwise calculate everything, but start with the provided object.
if (!this->p->background_processing_enabled()) {
task.single_model_instance_only = true;
task.to_object_step = slaposBasePool;
task.to_object_step = slaposPad;
}
this->p->background_process.set_task(task);
// and let the background processing start.
@ -4734,7 +4886,7 @@ bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** o
const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack().snapshots();
const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx);
if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) {
if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) {
*out_text = ss_stack[idx_in_ss_stack].name.c_str();
return true;
}
@ -4747,7 +4899,7 @@ void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& ou
const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack().snapshots();
const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0);
if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) {
if (0 < idx_in_ss_stack && (size_t)idx_in_ss_stack < ss_stack.size() - 1) {
out_text = ss_stack[idx_in_ss_stack].name;
return;
}
@ -4808,6 +4960,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
p->config->option<ConfigOptionStrings>(opt_key)->values = filament_colors;
p->sidebar->obj_list()->update_extruder_colors();
continue;
}
}
@ -4833,6 +4986,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
else if(opt_key == "extruder_colour") {
update_scheduled = true;
p->preview->set_number_extruders(p->config->option<ConfigOptionStrings>(opt_key)->values.size());
p->sidebar->obj_list()->update_extruder_colors();
} else if(opt_key == "max_print_height") {
update_scheduled = true;
}
@ -4860,6 +5014,36 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
this->p->schedule_background_process();
}
void Plater::force_filament_colors_update()
{
bool update_scheduled = false;
DynamicPrintConfig* config = p->config;
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
if (filament_presets.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
{
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
filament_colors.reserve(filament_presets.size());
for (const std::string& filament_preset : filament_presets)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
if (config->option<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
update_scheduled = true;
}
}
if (update_scheduled) {
update();
p->sidebar->obj_list()->update_extruder_colors();
}
if (p->main_frame->is_loaded())
this->p->schedule_background_process();
}
void Plater::on_activate()
{
#ifdef __linux__
@ -4881,6 +5065,22 @@ const DynamicPrintConfig* Plater::get_plater_config() const
return p->config;
}
std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
{
const Slic3r::DynamicPrintConfig* config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
std::vector<std::string> extruder_colors;
if (!config->has("extruder_colour")) // in case of a SLA print
return extruder_colors;
extruder_colors = (config->option<ConfigOptionStrings>("extruder_colour"))->values;
const std::vector<std::string>& filament_colours = (p->config->option<ConfigOptionStrings>("filament_colour"))->values;
for (size_t i = 0; i < extruder_colors.size(); ++i)
if (extruder_colors[i] == "" && i < filament_colours.size())
extruder_colors[i] = filament_colours[i];
return extruder_colors;
}
wxString Plater::get_project_filename(const wxString& extension) const
{
return p->get_project_filename(extension);
@ -5083,6 +5283,7 @@ bool Plater::can_copy_to_clipboard() const
bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }

View file

@ -56,8 +56,12 @@ public:
ScalableButton* edit_btn { nullptr };
enum LabelItemType {
LABEL_ITEM_MARKER = 0x4d,
LABEL_ITEM_CONFIG_WIZARD = 0x4e
LABEL_ITEM_MARKER = 0xffffff01,
LABEL_ITEM_WIZARD_PRINTERS,
LABEL_ITEM_WIZARD_FILAMENTS,
LABEL_ITEM_WIZARD_MATERIALS,
LABEL_ITEM_MAX,
};
void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER);
@ -184,6 +188,7 @@ public:
void export_stl(bool extended = false, bool selection_only = false);
void export_amf();
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reload_from_disk();
bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj() const;
void reslice();
@ -212,9 +217,11 @@ public:
void on_extruders_change(size_t extruders_count);
void on_config_change(const DynamicPrintConfig &config);
void force_filament_colors_update();
// On activating the parent window.
void on_activate();
const DynamicPrintConfig* get_plater_config() const;
std::vector<std::string> get_extruder_colors_from_plater_config() const;
void update_object_menu();
@ -248,6 +255,7 @@ public:
bool can_copy_to_clipboard() const;
bool can_undo() const;
bool can_redo() const;
bool can_reload_from_disk() const;
void msw_rescale();

View file

@ -99,6 +99,9 @@ static const std::unordered_map<std::string, std::string> pre_family_model_map {
VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all)
{
static const std::string printer_model_key = "printer_model:";
static const std::string filaments_section = "default_filaments";
static const std::string materials_section = "default_sla_materials";
const std::string id = path.stem().string();
if (! boost::filesystem::exists(path)) {
@ -107,6 +110,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
VendorProfile res(id);
// Helper to get compulsory fields
auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator
{
auto res = tree.find(key);
@ -116,6 +120,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
return res;
};
// Load the header
const auto &vendor_section = get_or_throw(tree, "vendor")->second;
res.name = get_or_throw(vendor_section, "name")->second.data();
@ -127,6 +132,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
res.config_version = std::move(*config_version);
}
// Load URLs
const auto config_update_url = vendor_section.find("config_update_url");
if (config_update_url != vendor_section.not_found()) {
res.config_update_url = config_update_url->second.data();
@ -141,6 +147,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
return res;
}
// Load printer models
for (auto &section : tree) {
if (boost::starts_with(section.first, printer_model_key)) {
VendorProfile::PrinterModel model;
@ -182,6 +189,24 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
}
}
// Load filaments and sla materials to be installed by default
const auto filaments = tree.find(filaments_section);
if (filaments != tree.not_found()) {
for (auto &pair : filaments->second) {
if (pair.second.data() == "1") {
res.default_filaments.insert(pair.first);
}
}
}
const auto materials = tree.find(materials_section);
if (materials != tree.not_found()) {
for (auto &pair : materials->second) {
if (pair.second.data() == "1") {
res.default_sla_materials.insert(pair.first);
}
}
}
return res;
}
@ -220,27 +245,13 @@ std::string Preset::remove_suffix_modified(const std::string &name)
name;
}
void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders)
{
const auto &defaults = FullPrintConfig::defaults();
for (const std::string &key : Preset::nozzle_options()) {
if (key == "default_filament_profile")
continue;
auto *opt = config.option(key, false);
assert(opt != nullptr);
assert(opt->is_vector());
if (opt != nullptr && opt->is_vector())
static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key));
}
}
// Update new extruder fields at the printer profile.
void Preset::normalize(DynamicPrintConfig &config)
{
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
if (nozzle_diameter != nullptr)
// Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values.
set_num_extruders(config, (unsigned int)nozzle_diameter->values.size());
config.set_num_extruders((unsigned int)nozzle_diameter->values.size());
if (config.option("filament_diameter") != nullptr) {
// This config contains single or multiple filament presets.
// Ensure that the filament preset vector options contain the correct number of values.
@ -351,10 +362,17 @@ bool Preset::update_compatible(const Preset &active_printer, const DynamicPrintC
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
{
if (vendor == nullptr) { return; }
const std::string &model = config.opt_string("printer_model");
const std::string &variant = config.opt_string("printer_variant");
if (model.empty() || variant.empty()) { return; }
is_visible = app_config.get_variant(vendor->id, model, variant);
if (type == TYPE_PRINTER) {
const std::string &model = config.opt_string("printer_model");
const std::string &variant = config.opt_string("printer_variant");
if (model.empty() || variant.empty()) { return; }
is_visible = app_config.get_variant(vendor->id, model, variant);
} else if (type == TYPE_FILAMENT) {
is_visible = app_config.has("filaments", name);
} else if (type == TYPE_SLA_MATERIAL) {
is_visible = app_config.has("sla_materials", name);
}
}
const std::vector<std::string>& Preset::print_options()
@ -404,7 +422,7 @@ const std::vector<std::string>& Preset::filament_options()
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
// Profile compatibility
"compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
"filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
};
return s_opts;
}
@ -437,15 +455,7 @@ const std::vector<std::string>& Preset::printer_options()
// of the nozzle_diameter vector.
const std::vector<std::string>& Preset::nozzle_options()
{
// ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings
static std::vector<std::string> s_opts {
"nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset",
"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed",
"retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe",
"retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour",
"default_filament_profile"
};
return s_opts;
return print_config_def.extruder_option_keys();
}
const std::vector<std::string>& Preset::sla_print_options()
@ -476,11 +486,13 @@ const std::vector<std::string>& Preset::sla_print_options()
"pad_enable",
"pad_wall_thickness",
"pad_wall_height",
"pad_brim_size",
"pad_max_merge_distance",
// "pad_edge_radius",
"pad_wall_slope",
"pad_object_gap",
"pad_around_object",
"pad_around_object_everywhere",
"pad_object_connector_stride",
"pad_object_connector_width",
"pad_object_connector_penetration",
@ -499,6 +511,7 @@ const std::vector<std::string>& Preset::sla_material_options()
static std::vector<std::string> s_opts;
if (s_opts.empty()) {
s_opts = {
"material_type",
"initial_layer_height",
"bottle_cost",
"bottle_volume",
@ -508,6 +521,7 @@ const std::vector<std::string>& Preset::sla_material_options()
"initial_exposure_time",
"material_correction",
"material_notes",
"material_vendor",
"default_sla_material_profile",
"compatible_prints", "compatible_prints_condition",
"compatible_printers", "compatible_printers_condition", "inherits"
@ -823,6 +837,21 @@ bool PresetCollection::delete_current_preset()
return true;
}
bool PresetCollection::delete_preset(const std::string& name)
{
auto it = this->find_preset_internal(name);
const Preset& preset = *it;
if (preset.is_default)
return false;
if (!preset.is_external && !preset.is_system) {
// Erase the preset file.
boost::nowide::remove(preset.file.c_str());
}
m_presets.erase(it);
return true;
}
void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name)
{
// XXX: See note in PresetBundle::load_compatible_bitmaps()
@ -1041,7 +1070,9 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap);
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
}
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD);
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_PRINTERS);
} else if (m_type == Preset::TYPE_SLA_MATERIAL) {
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS);
}
ui->SetSelection(selected_preset_item);
@ -1287,7 +1318,7 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors)
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const VendorMap &new_vendors)
{
std::vector<std::string> duplicates;
for (Preset &preset : other.m_presets) {
@ -1298,9 +1329,9 @@ std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&othe
if (it == m_presets.end() || it->name != preset.name) {
if (preset.vendor != nullptr) {
// Re-assign a pointer to the vendor structure in the new PresetBundle.
auto it = new_vendors.find(*preset.vendor);
auto it = new_vendors.find(preset.vendor->id);
assert(it != new_vendors.end());
preset.vendor = &(*it);
preset.vendor = &it->second;
}
this->m_presets.emplace(it, std::move(preset));
} else

View file

@ -2,6 +2,8 @@
#define slic3r_Preset_hpp_
#include <deque>
#include <set>
#include <unordered_map>
#include <boost/filesystem/path.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
@ -71,9 +73,14 @@ public:
};
std::vector<PrinterModel> models;
std::set<std::string> default_filaments;
std::set<std::string> default_sla_materials;
VendorProfile() {}
VendorProfile(std::string id) : id(std::move(id)) {}
// Load VendorProfile from an ini file.
// If `load_all` is false, only the header with basic info (name, version, URLs) is loaded.
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true);
@ -84,6 +91,12 @@ public:
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
};
// Note: it is imporant that map is used here rather than unordered_map,
// because we need iterators to not be invalidated,
// because Preset and the ConfigWizard hold pointers to VendorProfiles.
// XXX: maybe set is enough (cf. changes in Wizard)
typedef std::map<std::string, VendorProfile> VendorMap;
class Preset
{
public:
@ -189,7 +202,7 @@ public:
void set_visible_from_appconfig(const AppConfig &app_config);
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
void set_num_extruders(unsigned int n) { this->config.set_num_extruders(n); }
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
bool operator<(const Preset &other) const { return this->name < other.name; }
@ -214,8 +227,6 @@ public:
protected:
friend class PresetCollection;
friend class PresetBundle;
// Resize the extruder specific vectors ()
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
static std::string remove_suffix_modified(const std::string &name);
};
@ -276,6 +287,9 @@ public:
// Delete the current preset, activate the first visible preset.
// returns true if the preset was deleted successfully.
bool delete_current_preset();
// Delete the current preset, activate the first visible preset.
// returns true if the preset was deleted successfully.
bool delete_preset(const std::string& name);
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
void load_bitmap_default(wxWindow *window, const std::string &file_name);
@ -430,7 +444,7 @@ protected:
bool select_preset_by_name_strict(const std::string &name);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors);
std::vector<std::string> merge_presets(PresetCollection &&other, const VendorMap &new_vendors);
private:
PresetCollection();

View file

@ -8,6 +8,7 @@
#include <algorithm>
#include <fstream>
#include <unordered_set>
#include <boost/filesystem.hpp>
#include <boost/algorithm/clamp.hpp>
#include <boost/algorithm/string/predicate.hpp>
@ -41,6 +42,8 @@ static std::vector<std::string> s_project_options {
"wiping_volumes_matrix"
};
const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch";
PresetBundle::PresetBundle() :
prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())),
filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults())),
@ -194,7 +197,7 @@ void PresetBundle::setup_directories()
}
}
void PresetBundle::load_presets(const AppConfig &config, const std::string &preferred_model_id)
void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id)
{
// First load the vendor specific system presets.
std::string errors_cummulative = this->load_system_presets();
@ -325,13 +328,71 @@ void PresetBundle::load_installed_printers(const AppConfig &config)
}
}
void PresetBundle::load_installed_filaments(AppConfig &config)
{
if (! config.has_section(AppConfig::SECTION_FILAMENTS)) {
std::unordered_set<const Preset*> comp_filaments;
for (const Preset &printer : printers) {
if (! printer.is_visible || printer.printer_technology() != ptFFF) {
continue;
}
for (const Preset &filament : filaments) {
if (filament.is_compatible_with_printer(printer)) {
comp_filaments.insert(&filament);
}
}
}
for (const auto &filament: comp_filaments) {
config.set(AppConfig::SECTION_FILAMENTS, filament->name, "1");
}
}
for (auto &preset : filaments) {
preset.set_visible_from_appconfig(config);
}
}
void PresetBundle::load_installed_sla_materials(AppConfig &config)
{
if (! config.has_section(AppConfig::SECTION_MATERIALS)) {
std::unordered_set<const Preset*> comp_sla_materials;
for (const Preset &printer : printers) {
if (! printer.is_visible || printer.printer_technology() != ptSLA) {
continue;
}
for (const Preset &material : sla_materials) {
if (material.is_compatible_with_printer(printer)) {
comp_sla_materials.insert(&material);
}
}
}
for (const auto &material: comp_sla_materials) {
config.set(AppConfig::SECTION_MATERIALS, material->name, "1");
}
}
for (auto &preset : sla_materials) {
preset.set_visible_from_appconfig(config);
}
}
// Load selections (current print, current filaments, current printer) from config.ini
// This is done on application start up or after updates are applied.
void PresetBundle::load_selections(const AppConfig &config, const std::string &preferred_model_id)
void PresetBundle::load_selections(AppConfig &config, const std::string &preferred_model_id)
{
// Update visibility of presets based on application vendor / model / variant configuration.
this->load_installed_printers(config);
// Update visibility of filament and sla material presets
this->load_installed_filaments(config);
this->load_installed_sla_materials(config);
// Parse the initial print / filament / printer profile names.
std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print"));
std::string initial_sla_print_profile_name = remove_ini_suffix(config.get("presets", "sla_print"));
@ -1032,9 +1093,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
auto vp = VendorProfile::from_ini(tree, path);
if (vp.num_variants() == 0)
return 0;
vendor_profile = &(*this->vendors.insert(vp).first);
vendor_profile = &this->vendors.insert({vp.id, vp}).first->second;
}
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) {
return 0;
}
@ -1572,6 +1633,9 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
selected_preset_item = ui->GetCount() - 1;
}
}
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), wxNullBitmap), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS);
ui->SetSelection(selected_preset_item);
ui->SetToolTip(ui->GetString(selected_preset_item));
ui->check_selection();

View file

@ -4,7 +4,9 @@
#include "AppConfig.hpp"
#include "Preset.hpp"
#include <memory>
#include <set>
#include <unordered_map>
#include <boost/filesystem/path.hpp>
class wxWindow;
@ -31,7 +33,7 @@ public:
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_presets(const AppConfig &config, const std::string &preferred_model_id = "");
void load_presets(AppConfig &config, const std::string &preferred_model_id = "");
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
@ -52,7 +54,8 @@ public:
// There will be an entry for each system profile loaded,
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
std::set<VendorProfile> vendors;
// std::set<VendorProfile> vendors;
VendorMap vendors;
struct ObsoletePresets {
std::vector<std::string> prints;
@ -131,19 +134,25 @@ public:
void load_default_preset_bitmaps(wxWindow *window);
// Set the is_visible flag for printer vendors, printer models and printer variants
// based on the user configuration.
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void load_installed_printers(const AppConfig &config);
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Set the "enabled" flag for printer vendors, printer models and printer variants
// based on the user configuration.
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void load_installed_printers(const AppConfig &config);
// Set the is_visible flag for filaments and sla materials,
// apply defaults based on enabled printers when no filaments/materials are installed.
void load_installed_filaments(AppConfig &config);
void load_installed_sla_materials(AppConfig &config);
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_selections(const AppConfig &config, const std::string &preferred_model_id = "");
void load_selections(AppConfig &config, const std::string &preferred_model_id = "");
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
// and the external config is just referenced, not stored into user profile directory.

View file

@ -472,7 +472,7 @@ void Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new
for (unsigned int idx : m_list)
if (map_volume_old_to_new[idx] != size_t(-1)) {
unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx];
assert((*m_volumes)[new_idx]->selected);
(*m_volumes)[new_idx]->selected = true;
list_new.insert(new_idx);
}
m_list = std::move(list_new);

View file

@ -227,9 +227,9 @@ void Tab::create_preset_tab()
m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this);
m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) {
//! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox,
//! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox,
//! but the OSX version derived from wxOwnerDrawnCombo, instead of:
//! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data());
//! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data());
//! we doing next:
int selected_item = m_presets_choice->GetSelection();
if (m_selected_preset_item == size_t(selected_item) && !m_presets->current_is_dirty())
@ -241,7 +241,7 @@ void Tab::create_preset_tab()
selected_string == "------- User presets -------"*/) {
m_presets_choice->SetSelection(m_selected_preset_item);
if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer")))
wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); });
wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER); });
return;
}
m_selected_preset_item = selected_item;
@ -1808,7 +1808,10 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("single_extruder_multi_material");
optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) {
size_t extruders_count = boost::any_cast<size_t>(optgroup->get_value("extruders_count"));
// optgroup->get_value() return int for def.type == coInt,
// Thus, there should be boost::any_cast<int> !
// Otherwise, boost::any_cast<size_t> causes an "unhandled unknown exception"
size_t extruders_count = size_t(boost::any_cast<int>(optgroup->get_value("extruders_count")));
wxTheApp->CallAfter([this, opt_key, value, extruders_count]() {
if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") {
extruders_count_changed(extruders_count);
@ -3016,6 +3019,18 @@ void Tab::save_preset(std::string name /*= ""*/)
show_error(this, _(L("Cannot overwrite an external profile.")));
return;
}
if (existing && name != preset.name)
{
wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exist."))) % name).str());
msg_text += "\n" + _(L("Replace?"));
wxMessageDialog dialog(nullptr, msg_text, _(L("Warning")), wxICON_WARNING | wxYES | wxNO);
if (dialog.ShowModal() == wxID_NO)
return;
// Remove the preset from the list.
m_presets->delete_preset(name);
}
}
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
@ -3032,6 +3047,12 @@ void Tab::save_preset(std::string name /*= ""*/)
if (m_type == Preset::TYPE_PRINTER)
static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(this)->m_extruders_count;
update_changed_ui();
/* If filament preset is saved for multi-material printer preset,
* there are cases when filament comboboxs are updated for old (non-modified) colors,
* but in full_config a filament_colors option aren't.*/
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
wxGetApp().plater()->force_filament_colors_update();
}
// Called for a currently selected preset.
@ -3563,12 +3584,14 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("pad_enable");
optgroup->append_single_option_line("pad_wall_thickness");
optgroup->append_single_option_line("pad_wall_height");
optgroup->append_single_option_line("pad_brim_size");
optgroup->append_single_option_line("pad_max_merge_distance");
// TODO: Disabling this parameter for the beta release
// optgroup->append_single_option_line("pad_edge_radius");
optgroup->append_single_option_line("pad_wall_slope");
optgroup->append_single_option_line("pad_around_object");
optgroup->append_single_option_line("pad_around_object_everywhere");
optgroup->append_single_option_line("pad_object_gap");
optgroup->append_single_option_line("pad_object_connector_stride");
optgroup->append_single_option_line("pad_object_connector_width");

View file

@ -7,6 +7,7 @@
#include "libslic3r/Model.hpp"
#include <wx/sizer.h>
#include <wx/bmpcbox.h>
#include <wx/statline.h>
#include <wx/dcclient.h>
#include <wx/numformatter.h>
@ -20,6 +21,7 @@
#include "libslic3r/GCode/PreviewData.hpp"
#include "I18N.hpp"
#include "GUI_Utils.hpp"
#include "PresetBundle.hpp"
#include "../Utils/MacDarkMode.hpp"
using Slic3r::GUI::from_u8;
@ -445,6 +447,52 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
return *bmp;
}
Slic3r::GUI::BitmapCache* m_bitmap_cache = nullptr;
/*static*/ std::vector<wxBitmap*> get_extruder_color_icons()
{
// Create the bitmap with color bars.
std::vector<wxBitmap*> bmps;
std::vector<std::string> colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config();
if (colors.empty())
return bmps;
unsigned char rgb[3];
/* It's supposed that standard size of an icon is 36px*16px for 100% scaled display.
* So set sizes for solid_colored icons used for filament preset
* and scale them in respect to em_unit value
*/
const double em = Slic3r::GUI::wxGetApp().em_unit();
const int icon_width = lround(3.2 * em);
const int icon_height = lround(1.6 * em);
for (const std::string& color : colors)
{
wxBitmap* bitmap = m_bitmap_cache->find(color);
if (bitmap == nullptr) {
// Paint the color icon.
Slic3r::PresetBundle::parse_color(color, rgb);
bitmap = m_bitmap_cache->insert(color, m_bitmap_cache->mksolid(icon_width, icon_height, rgb));
}
bmps.emplace_back(bitmap);
}
return bmps;
}
static wxBitmap get_extruder_color_icon(size_t extruder_idx)
{
// Create the bitmap with color bars.
std::vector<wxBitmap*> bmps = get_extruder_color_icons();
if (bmps.empty())
return wxNullBitmap;
return *bmps[extruder_idx >= bmps.size() ? 0 : extruder_idx];
}
// *****************************************************************************
// ----------------------------------------------------------------------------
// ObjectDataViewModelNode
@ -477,7 +525,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent
m_idx = parent->GetChildCount();
m_name = wxString::Format(_(L("Instance %d")), m_idx + 1);
set_action_icon();
set_action_and_extruder_icons();
}
else if (type == itLayerRoot)
{
@ -512,7 +560,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent
m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")";
m_bmp = create_scaled_bitmap(nullptr, LAYER_ICON); // FIXME: pass window ptr
set_action_icon();
set_action_and_extruder_icons();
init_container();
}
@ -525,11 +573,19 @@ bool ObjectDataViewModelNode::valid()
}
#endif /* NDEBUG */
void ObjectDataViewModelNode::set_action_icon()
void ObjectDataViewModelNode::set_action_and_extruder_icons()
{
m_action_icon_name = m_type & itObject ? "advanced_plus" :
m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj";
m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr
if (m_type & itInstance)
return; // don't set colored bitmap for Instance
// set extruder bitmap
int extruder_idx = atoi(m_extruder.c_str());
if (extruder_idx > 0) --extruder_idx;
m_extruder_bmp = get_extruder_color_icon(extruder_idx);
}
void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable)
@ -539,7 +595,6 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable)
create_scaled_bitmap(nullptr, m_printable == piPrintable ? "eye_open.png" : "eye_closed.png");
}
Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr;
void ObjectDataViewModelNode::update_settings_digest_bitmaps()
{
m_bmp = m_empty_bmp;
@ -605,8 +660,10 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col)
m_name = data.GetText();
return true; }
case colExtruder: {
const wxString & val = variant.GetString();
m_extruder = val == "0" ? _(L("default")) : val;
DataViewBitmapText data;
data << variant;
m_extruder_bmp = data.GetBitmap();
m_extruder = data.GetText() == "0" ? _(L("default")) : data.GetText();
return true; }
case colEditing:
m_action_icon << variant;
@ -963,7 +1020,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
node_parent->GetChildren().Remove(node);
if (id > 0) {
if(id == node_parent->GetChildCount()) id--;
if (size_t(id) == node_parent->GetChildCount()) id--;
ret_item = wxDataViewItem(node_parent->GetChildren().Item(id));
}
@ -1379,6 +1436,51 @@ t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewIt
return node->GetLayerRange();
}
bool ObjectDataViewModel::UpdateColumValues(unsigned col)
{
switch (col)
{
case colPrint:
case colName:
case colEditing:
return true;
case colExtruder:
{
wxDataViewItemArray items;
GetAllChildren(wxDataViewItem(nullptr), items);
if (items.IsEmpty()) return false;
for (auto item : items)
UpdateExtruderBitmap(item);
return true;
}
default:
printf("MyObjectTreeModel::SetValue: wrong column");
}
return false;
}
void ObjectDataViewModel::UpdateExtruderBitmap(wxDataViewItem item)
{
wxString extruder = GetExtruder(item);
if (extruder.IsEmpty())
return;
// set extruder bitmap
int extruder_idx = atoi(extruder.c_str());
if (extruder_idx > 0) --extruder_idx;
const DataViewBitmapText extruder_val(extruder, get_extruder_color_icon(extruder_idx));
wxVariant value;
value << extruder_val;
SetValue(value, item, colExtruder);
}
void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx)
{
wxASSERT(item.IsOk());
@ -1475,6 +1577,24 @@ wxBitmap& ObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const
return node->m_bmp;
}
wxString ObjectDataViewModel::GetExtruder(const wxDataViewItem& item) const
{
ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID();
if (!node) // happens if item.IsOk()==false
return wxEmptyString;
return node->m_extruder;
}
int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const
{
ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID();
if (!node) // happens if item.IsOk()==false
return 0;
return atoi(node->m_extruder.c_str());
}
void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const
{
wxASSERT(item.IsOk());
@ -1489,7 +1609,7 @@ void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &ite
variant << DataViewBitmapText(node->m_name, node->m_bmp);
break;
case colExtruder:
variant = node->m_extruder;
variant << DataViewBitmapText(node->m_extruder, node->m_extruder_bmp);
break;
case colEditing:
variant << node->m_action_icon;
@ -1515,6 +1635,22 @@ bool ObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx,
return m_objects[item_idx]->SetValue(variant, col);
}
void ObjectDataViewModel::SetExtruder(const wxString& extruder, wxDataViewItem item)
{
DataViewBitmapText extruder_val;
extruder_val.SetText(extruder);
// set extruder bitmap
int extruder_idx = atoi(extruder.c_str());
if (extruder_idx > 0) --extruder_idx;
extruder_val.SetBitmap(get_extruder_color_icon(extruder_idx));
wxVariant value;
value << extruder_val;
SetValue(value, item, colExtruder);
}
wxDataViewItem ObjectDataViewModel::ReorganizeChildren( const int current_volume_id,
const int new_volume_id,
const wxDataViewItem &parent)
@ -1840,7 +1976,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo
}
//-----------------------------------------------------------------------------
// PrusaDataViewBitmapText
// DataViewBitmapText
//-----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject)
@ -1966,6 +2102,109 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value
return true;
}
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
bool BitmapChoiceRenderer::SetValue(const wxVariant& value)
{
m_value << value;
return true;
}
bool BitmapChoiceRenderer::GetValue(wxVariant& value) const
{
value << m_value;
return true;
}
bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state)
{
int xoffset = 0;
const wxBitmap& icon = m_value.GetBitmap();
if (icon.IsOk())
{
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2);
xoffset = icon.GetWidth() + 4;
}
if (rect.height==0)
rect.height= icon.GetHeight();
RenderText(m_value.GetText(), xoffset, rect, dc, state);
return true;
}
wxSize BitmapChoiceRenderer::GetSize() const
{
wxSize sz = GetTextExtent(m_value.GetText());
if (m_value.GetBitmap().IsOk())
sz.x += m_value.GetBitmap().GetWidth() + 4;
return sz;
}
wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
{
wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner();
ObjectDataViewModel* const model = dynamic_cast<ObjectDataViewModel*>(dv_ctrl->GetModel());
if (!(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itLayer | itObject)))
return nullptr;
std::vector<wxBitmap*> icons = get_extruder_color_icons();
if (icons.empty())
return nullptr;
DataViewBitmapText data;
data << value;
auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString,
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
0, nullptr , wxCB_READONLY);
int i=0;
for (wxBitmap* bmp : icons) {
if (i==0) {
c_editor->Append(_(L("default")), *bmp);
++i;
}
c_editor->Append(wxString::Format("%d", i), *bmp);
++i;
}
c_editor->SetSelection(atoi(data.GetText().c_str()));
// to avoid event propagation to other sidebar items
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
evt.StopPropagation();
// FinishEditing grabs new selection and triggers config update. We better call
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
this->FinishEditing();
});
return c_editor;
}
bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
{
wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl;
int selection = c->GetSelection();
if (selection < 0)
return false;
DataViewBitmapText bmpText;
bmpText.SetText(c->GetString(selection));
bmpText.SetBitmap(c->GetItemBitmap(selection));
value << bmpText;
return true;
}
// ----------------------------------------------------------------------------
// DoubleSlider
// ----------------------------------------------------------------------------
@ -2987,6 +3226,8 @@ void LockButton::msw_rescale()
m_bmp_lock_closed_f.msw_rescale();
m_bmp_lock_open.msw_rescale();
m_bmp_lock_open_f.msw_rescale();
update_button_bitmaps();
}
void LockButton::update_button_bitmaps()

View file

@ -55,6 +55,8 @@ int em_unit(wxWindow* win);
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name,
const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false);
std::vector<wxBitmap*> get_extruder_color_icons();
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
static const unsigned int DefaultWidth;
@ -209,6 +211,7 @@ class ObjectDataViewModelNode
int m_idx = -1;
bool m_container = false;
wxString m_extruder = "default";
wxBitmap m_extruder_bmp;
wxBitmap m_action_icon;
PrintIndicator m_printable {piUndef};
wxBitmap m_printable_icon;
@ -224,7 +227,7 @@ public:
m_type(itObject),
m_extruder(extruder)
{
set_action_icon();
set_action_and_extruder_icons();
init_container();
}
@ -240,7 +243,7 @@ public:
m_extruder (extruder)
{
m_bmp = bmp;
set_action_icon();
set_action_and_extruder_icons();
init_container();
}
@ -356,7 +359,7 @@ public:
}
// Set action icons for node
void set_action_icon();
void set_action_and_extruder_icons();
// Set printable icon for node
void set_printable_icon(PrintIndicator printable);
@ -438,6 +441,8 @@ public:
wxString GetName(const wxDataViewItem &item) const;
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
wxString GetExtruder(const wxDataViewItem &item) const;
int GetExtruderNumber(const wxDataViewItem &item) const;
// helper methods to change the model
@ -454,6 +459,8 @@ public:
const int item_idx,
unsigned int col);
void SetExtruder(const wxString& extruder, wxDataViewItem item);
// For parent move child from cur_volume_id place to new_volume_id
// Remaining items will moved up/down accordingly
wxDataViewItem ReorganizeChildren( const int cur_volume_id,
@ -504,6 +511,9 @@ public:
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
bool UpdateColumValues(unsigned col);
void UpdateExtruderBitmap(wxDataViewItem item);
private:
wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type);
wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item);
@ -563,6 +573,40 @@ private:
};
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
class BitmapChoiceRenderer : public wxDataViewCustomRenderer
{
public:
BitmapChoiceRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
,int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant& value);
bool GetValue(wxVariant& value) const;
virtual bool Render(wxRect cell, wxDC* dc, int state);
virtual wxSize GetSize() const;
bool HasEditorCtrl() const override { return true; }
wxWindow* CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override;
bool GetValueFromEditorCtrl( wxWindow* ctrl,
wxVariant& value) override;
private:
DataViewBitmapText m_value;
};
// ----------------------------------------------------------------------------
// MyCustomRenderer
// ----------------------------------------------------------------------------

View file

@ -0,0 +1,219 @@
#include "FlashAir.hpp"
#include <algorithm>
#include <ctime>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <wx/frame.h>
#include <wx/event.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "Http.hpp"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
FlashAir::FlashAir(DynamicPrintConfig *config) :
host(config->opt_string("print_host"))
{}
FlashAir::~FlashAir() {}
const char* FlashAir::get_name() const { return "FlashAir"; }
bool FlashAir::test(wxString &msg) const
{
// Since the request is performed synchronously here,
// it is ok to refer to `msg` from within the closure
const char *name = get_name();
bool res = false;
auto url = make_url("command.cgi", "op", "118");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url;
auto http = Http::get(std::move(url));
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
res = false;
msg = format_error(body, error, status);
})
.on_complete([&, this](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body;
res = boost::starts_with(body, "1");
if (! res) {
msg = _(L("Upload not enabled on FlashAir card."));
}
})
.perform_sync();
return res;
}
wxString FlashAir::get_test_ok_msg () const
{
return _(L("Connection to FlashAir works correctly and upload is enabled."));
}
wxString FlashAir::get_test_failed_msg (wxString &msg) const
{
return wxString::Format("%s: %s", _(L("Could not connect to FlashAir")), msg, _(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required.")));
}
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
{
const char *name = get_name();
const auto upload_filename = upload_data.upload_path.filename();
const auto upload_parent_path = upload_data.upload_path.parent_path();
wxString test_msg;
if (! test(test_msg)) {
error_fn(std::move(test_msg));
return false;
}
bool res = false;
auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str());
auto urlUpload = make_url("upload.cgi");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%")
% name
% upload_data.source_path
% urlPrepare
% urlUpload
% upload_filename.string();
// set filetime for upload and make card writeprotect to prevent filesystem damage
auto httpPrepare = Http::get(std::move(urlPrepare));
httpPrepare.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error prepareing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
error_fn(format_error(body, error, status));
res = false;
})
.on_complete([&, this](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body;
res = boost::icontains(body, "SUCCESS");
if (! res) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
error_fn(format_error(body, L("Unknown error occured"), 0));
}
})
.perform_sync();
if(! res ) {
return res;
}
// start file upload
auto http = Http::post(std::move(urlUpload));
http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
res = boost::icontains(body, "SUCCESS");
if (! res) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
error_fn(format_error(body, L("Unknown error occured"), 0));
}
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
error_fn(format_error(body, error, status));
res = false;
})
.on_progress([&](Http::Progress progress, bool &cancel) {
prorgess_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name;
res = false;
}
})
.perform_sync();
return res;
}
bool FlashAir::has_auto_discovery() const
{
return false;
}
bool FlashAir::can_test() const
{
return true;
}
bool FlashAir::can_start_print() const
{
return false;
}
std::string FlashAir::timestamp_str() const
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
const char *name = get_name();
unsigned long fattime = ((tm.tm_year - 80) << 25) |
((tm.tm_mon + 1) << 21) |
(tm.tm_mday << 16) |
(tm.tm_hour << 11) |
(tm.tm_min << 5) |
(tm.tm_sec >> 1);
return (boost::format("%1$#x") % fattime).str();
}
std::string FlashAir::make_url(const std::string &path) const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return (boost::format("%1%%2%") % host % path).str();
} else {
return (boost::format("%1%/%2%") % host % path).str();
}
} else {
if (host.back() == '/') {
return (boost::format("http://%1%%2%") % host % path).str();
} else {
return (boost::format("http://%1%/%2%") % host % path).str();
}
}
}
std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const
{
if (host.find("http://") == 0 || host.find("https://") == 0) {
if (host.back() == '/') {
return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str();
} else {
return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str();
}
} else {
if (host.back() == '/') {
return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str();
} else {
return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str();
}
}
}
}

View file

@ -0,0 +1,44 @@
#ifndef slic3r_FlashAir_hpp_
#define slic3r_FlashAir_hpp_
#include <string>
#include <wx/string.h>
#include "PrintHost.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class FlashAir : public PrintHost
{
public:
FlashAir(DynamicPrintConfig *config);
virtual ~FlashAir();
virtual const char* get_name() const;
virtual bool test(wxString &curl_msg) const;
virtual wxString get_test_ok_msg () const;
virtual wxString get_test_failed_msg (wxString &msg) const;
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const;
virtual bool has_auto_discovery() const;
virtual bool can_test() const;
virtual bool can_start_print() const;
virtual std::string get_host() const { return host; }
private:
std::string host;
std::string timestamp_str() const;
std::string make_url(const std::string &path) const;
std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const;
};
}
#endif

View file

@ -153,7 +153,7 @@ struct PresetUpdater::priv
bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const;
void sync_version() const;
void sync_config(const std::set<VendorProfile> vendors);
void sync_config(const VendorMap vendors);
void check_install_indices() const;
Updates get_config_updates() const;
@ -266,7 +266,7 @@ void PresetUpdater::priv::sync_version() const
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache.
void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
void PresetUpdater::priv::sync_config(const VendorMap vendors)
{
BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache";
@ -276,13 +276,13 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
for (auto &index : index_db) {
if (cancel) { return; }
const auto vendor_it = vendors.find(VendorProfile(index.vendor()));
const auto vendor_it = vendors.find(index.vendor());
if (vendor_it == vendors.end()) {
BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor();
continue;
}
const VendorProfile &vendor = *vendor_it;
const VendorProfile &vendor = vendor_it->second;
if (vendor.config_update_url.empty()) {
BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name;
continue;
@ -574,7 +574,7 @@ void PresetUpdater::sync(PresetBundle *preset_bundle)
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
std::set<VendorProfile> vendors = preset_bundle->vendors;
VendorMap vendors = preset_bundle->vendors;
p->thread = std::move(std::thread([this, vendors]() {
this->p->prune_tmps();
@ -643,13 +643,10 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
// (snapshot is taken beforehand)
p->perform_updates(std::move(updates));
GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT);
if (! wizard.run(GUI::wxGetApp().preset_bundle, this)) {
if (! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) {
return R_INCOMPAT_EXIT;
}
GUI::wxGetApp().load_current_presets();
return R_INCOMPAT_CONFIGURED;
} else {
BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye...";
@ -694,8 +691,8 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size();
for (const auto &bundle : bundles) {
auto path_in_rsrc = p->rsrc_path / bundle;
auto path_in_vendors = p->vendor_path / bundle;
auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini");
auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini");
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}

View file

@ -14,6 +14,7 @@
#include "libslic3r/Channel.hpp"
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "FlashAir.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
@ -43,6 +44,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
switch (host_type) {
case htOctoPrint: return new OctoPrint(config);
case htDuet: return new Duet(config);
case htFlashAir: return new FlashAir(config);
default: return nullptr;
}
} else {

View file

@ -0,0 +1,28 @@
#ifndef THREAD_HPP
#define THREAD_HPP
#include <utility>
#include <boost/thread.hpp>
namespace Slic3r {
template<class Fn>
inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn)
{
// Duplicating the stack allocation size of Thread Building Block worker
// threads of the thread pool: allocate 4MB on a 64bit system, allocate 2MB
// on a 32bit system by default.
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
return boost::thread{attrs, std::forward<Fn>(fn)};
}
template<class Fn> inline boost::thread create_thread(Fn &&fn)
{
boost::thread::attributes attrs;
return create_thread(attrs, std::forward<Fn>(fn));
}
}
#endif // THREAD_HPP