diff --git a/resources/icons/layers.svg b/resources/icons/layers.svg
index 7718a8cbd..da5dec21d 100644
--- a/resources/icons/layers.svg
+++ b/resources/icons/layers.svg
@@ -1,25 +1,27 @@
-
+
diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp
index 5fc992b63..fb3213219 100644
--- a/src/admesh/connect.cpp
+++ b/src/admesh/connect.cpp
@@ -39,8 +39,7 @@ static void stl_record_neighbors(stl_file *stl,
stl_hash_edge *edge_a, stl_hash_edge *edge_b);
static void stl_initialize_facet_check_exact(stl_file *stl);
static void stl_initialize_facet_check_nearby(stl_file *stl);
-static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge,
- stl_vertex *a, stl_vertex *b);
+static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b);
static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge,
stl_vertex *a, stl_vertex *b, float tolerance);
static void insert_hash_edge(stl_file *stl, stl_hash_edge edge,
@@ -60,41 +59,40 @@ extern int stl_check_normal_vector(stl_file *stl,
int facet_num, int normal_fix_flag);
static void stl_update_connects_remove_1(stl_file *stl, int facet_num);
-
-void
-stl_check_facets_exact(stl_file *stl) {
- /* This function builds the neighbors list. No modifications are made
- * to any of the facets. The edges are said to match only if all six
- * floats of the first edge matches all six floats of the second edge.
- */
-
- stl_hash_edge edge;
- stl_facet facet;
- int i;
- int j;
-
- if (stl->error) return;
+// This function builds the neighbors list. No modifications are made
+// to any of the facets. The edges are said to match only if all six
+// floats of the first edge matches all six floats of the second edge.
+void stl_check_facets_exact(stl_file *stl)
+{
+ if (stl->error)
+ return;
stl->stats.connected_edges = 0;
stl->stats.connected_facets_1_edge = 0;
stl->stats.connected_facets_2_edge = 0;
stl->stats.connected_facets_3_edge = 0;
- stl_initialize_facet_check_exact(stl);
+ // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet.
+ // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet
+ // will break the references.
+ for (int i = 0; i < stl->stats.number_of_facets;) {
+ stl_facet &facet = stl->facet_start[i];
+ if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) {
+ // Remove the degenerate facet.
+ facet = stl->facet_start[--stl->stats.number_of_facets];
+ stl->stats.facets_removed += 1;
+ stl->stats.degenerate_facets += 1;
+ } else
+ ++ i;
+ }
- for(i = 0; i < stl->stats.number_of_facets; i++) {
- facet = stl->facet_start[i];
- // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet.
- if (facet.vertex[0] == facet.vertex[1] ||
- facet.vertex[1] == facet.vertex[2] ||
- facet.vertex[0] == facet.vertex[2]) {
- stl->stats.degenerate_facets += 1;
- stl_remove_facet(stl, i);
- -- i;
- continue;
- }
- for(j = 0; j < 3; j++) {
- edge.facet_number = i;
+ // Connect neighbor edges.
+ stl_initialize_facet_check_exact(stl);
+ for (int i = 0; i < stl->stats.number_of_facets; i++) {
+ const stl_facet &facet = stl->facet_start[i];
+ for (int j = 0; j < 3; j++) {
+ stl_hash_edge edge;
+ edge.facet_number = i;
edge.which_edge = j;
stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]);
insert_hash_edge(stl, edge, stl_record_neighbors);
@@ -109,9 +107,7 @@ stl_check_facets_exact(stl_file *stl) {
#endif
}
-static void
-stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge,
- stl_vertex *a, stl_vertex *b) {
+static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b) {
if (stl->error) return;
@@ -333,7 +329,9 @@ static void stl_free_edges(stl_file *stl)
}
}
free(stl->heads);
+ stl->heads = nullptr;
free(stl->tail);
+ stl->tail = nullptr;
}
static void stl_initialize_facet_check_nearby(stl_file *stl)
diff --git a/src/admesh/stl.h b/src/admesh/stl.h
index afff3deac..2c436b426 100644
--- a/src/admesh/stl.h
+++ b/src/admesh/stl.h
@@ -127,7 +127,6 @@ typedef struct {
typedef struct {
FILE *fp;
stl_facet *facet_start;
- stl_edge *edge_start;
stl_hash_edge **heads;
stl_hash_edge *tail;
int M;
@@ -142,7 +141,6 @@ typedef struct {
extern void stl_open(stl_file *stl, const char *file);
extern void stl_close(stl_file *stl);
extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file);
-extern void stl_print_edges(stl_file *stl, FILE *file);
extern void stl_print_neighbors(stl_file *stl, char *file);
extern void stl_put_little_int(FILE *fp, int value_in);
extern void stl_put_little_float(FILE *fp, float value_in);
diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp
index 71e434cbc..85f66785b 100644
--- a/src/admesh/stl_io.cpp
+++ b/src/admesh/stl_io.cpp
@@ -33,24 +33,6 @@
#define SEEK_END 2
#endif
-void
-stl_print_edges(stl_file *stl, FILE *file) {
- int i;
- int edges_allocated;
-
- if (stl->error) return;
-
- edges_allocated = stl->stats.number_of_facets * 3;
- for(i = 0; i < edges_allocated; i++) {
- fprintf(file, "%d, %f, %f, %f, %f, %f, %f\n",
- stl->edge_start[i].facet_number,
- stl->edge_start[i].p1(0), stl->edge_start[i].p1(1),
- stl->edge_start[i].p1(2), stl->edge_start[i].p2(0),
- stl->edge_start[i].p2(1), stl->edge_start[i].p2(2));
- }
-}
-
-
void
stl_stats_out(stl_file *stl, FILE *file, char *input_file) {
if (stl->error) return;
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index ba898d9d5..e634dd138 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -556,19 +556,9 @@ std::string Model::propose_export_file_name_and_path() const
for (const ModelObject *model_object : this->objects)
for (ModelInstance *model_instance : model_object->instances)
if (model_instance->is_printable()) {
- input_file = model_object->input_file;
- if (! model_object->name.empty()) {
- if (input_file.empty())
- // model_object->input_file was empty, just use model_object->name
- input_file = model_object->name;
- else {
- // Replace file name in input_file with model_object->name, but keep the path and file extension.
- input_file = (boost::filesystem::path(model_object->name).parent_path().empty()) ?
- (boost::filesystem::path(input_file).parent_path() / model_object->name).make_preferred().string() :
- model_object->name;
- }
- }
- if (! input_file.empty())
+ input_file = model_object->get_export_filename();
+
+ if (!input_file.empty())
goto end;
// Other instances will produce the same name, skip them.
break;
@@ -1433,6 +1423,26 @@ void ModelObject::print_info() const
cout << "volume = " << mesh.volume() << endl;
}
+std::string ModelObject::get_export_filename() const
+{
+ std::string ret = input_file;
+
+ if (!name.empty())
+ {
+ if (ret.empty())
+ // input_file was empty, just use name
+ ret = name;
+ else
+ {
+ // Replace file name in input_file with name, but keep the path and file extension.
+ ret = (boost::filesystem::path(name).parent_path().empty()) ?
+ (boost::filesystem::path(ret).parent_path() / name).make_preferred().string() : name;
+ }
+ }
+
+ return ret;
+}
+
void ModelVolume::set_material_id(t_model_material_id material_id)
{
m_material_id = material_id;
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 5cf7f49ca..951401243 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -275,6 +275,8 @@ public:
// Print object statistics to console.
void print_info() const;
+ std::string get_export_filename() const;
+
protected:
friend class Print;
friend class SLAPrint;
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 794390133..c13f0bc2a 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -10,7 +10,7 @@
#include "GCode/WipeTowerPrusaMM.hpp"
#include "Utils.hpp"
-#include "PrintExport.hpp"
+//#include "PrintExport.hpp"
#include
#include
diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp
index 04b993a52..ce62f7cb0 100644
--- a/src/libslic3r/PrintExport.hpp
+++ b/src/libslic3r/PrintExport.hpp
@@ -7,6 +7,7 @@
#include
#include
+#include
#include "Rasterizer/Rasterizer.hpp"
//#include
@@ -72,7 +73,8 @@ public:
void finish_layer();
// Save all the layers into the file (or dir) specified in the path argument
- void save(const std::string& path);
+ // An optional project name can be added to be used for the layer file names
+ void save(const std::string& path, const std::string& projectname = "");
// Save only the selected layer to the file specified in path argument.
void save_layer(unsigned lyr, const std::string& path);
@@ -86,7 +88,8 @@ template struct VeryFalse { static const bool value = false; };
template class LayerWriter {
public:
- LayerWriter(const std::string& /*zipfile_path*/) {
+ LayerWriter(const std::string& /*zipfile_path*/)
+ {
static_assert(VeryFalse::value,
"No layer writer implementation provided!");
}
@@ -99,10 +102,6 @@ public:
void binary_entry(const std::string& /*fname*/,
const std::uint8_t* buf, size_t len);
- // Get the name of the archive but only the name part without the path or
- // the extension.
- std::string get_name() { return ""; }
-
// Test whether the object can still be used for writing.
bool is_ok() { return false; }
@@ -253,12 +252,14 @@ public:
}
template
- inline void save(const std::string& path) {
+ inline void save(const std::string& fpath, const std::string& prjname = "")
+ {
try {
- LayerWriter writer(path);
+ LayerWriter writer(fpath);
if(!writer.is_ok()) return;
- std::string project = writer.get_name();
+ std::string project = prjname.empty()?
+ boost::filesystem::path(fpath).stem().string() : prjname;
writer.next_entry("config.ini");
if(!writer.is_ok()) return;
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index 42c6fbf75..954e583f7 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -1790,8 +1790,13 @@ std::vector PrintObject::_slice_volumes(const std::vector &z,
if (! volumes.empty()) {
// Compose mesh.
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
- TriangleMesh mesh(volumes.front()->mesh);
+ TriangleMesh mesh(volumes.front()->mesh);
mesh.transform(volumes.front()->get_matrix(), true);
+ assert(mesh.repaired);
+ if (volumes.size() == 1 && mesh.repaired) {
+ //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
+ stl_check_facets_exact(&mesh.stl);
+ }
for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) {
const ModelVolume &model_volume = *volumes[idx_volume];
TriangleMesh vol_mesh(model_volume.mesh);
@@ -1821,6 +1826,10 @@ std::vector PrintObject::_slice_volume(const std::vector &z,
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
TriangleMesh mesh(volume.mesh);
mesh.transform(volume.get_matrix(), true);
+ if (mesh.repaired) {
+ //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
+ stl_check_facets_exact(&mesh.stl);
+ }
if (mesh.stl.stats.number_of_facets > 0) {
mesh.transform(m_trafo, true);
// apply XY shift
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index a1e382acb..d4443d915 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -320,10 +320,8 @@ struct SLAPrintStatistics
}
};
-struct SLAminzZipper {};
-
// The implementation of creating zipped archives with wxWidgets
-template<> class LayerWriter {
+template<> class LayerWriter {
Zipper m_zip;
public:
@@ -332,16 +330,12 @@ public:
void next_entry(const std::string& fname) { m_zip.add_entry(fname); }
void binary_entry(const std::string& fname,
- const std::uint8_t* buf,
- size_t l)
+ const std::uint8_t* buf,
+ size_t l)
{
m_zip.add_entry(fname, buf, l);
}
- std::string get_name() const {
- return m_zip.get_name();
- }
-
template inline LayerWriter& operator<<(T&& arg) {
m_zip << std::forward(arg); return *this;
}
@@ -389,9 +383,11 @@ public:
// Returns true if the last step was finished with success.
bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); }
- template
- void export_raster(const std::string& fname) {
- if(m_printer) m_printer->save(fname);
+ template
+ inline void export_raster(const std::string& fpath,
+ const std::string& projectname = "")
+ {
+ if(m_printer) m_printer->save(fpath, projectname);
}
const PrintObjects& objects() const { return m_objects; }
diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp
index 0d9a79978..bfba364af 100644
--- a/src/libslic3r/TriangleMesh.cpp
+++ b/src/libslic3r/TriangleMesh.cpp
@@ -273,6 +273,11 @@ void TriangleMesh::translate(float x, float y, float z)
stl_invalidate_shared_vertices(&this->stl);
}
+void TriangleMesh::translate(const Vec3f &displacement)
+{
+ translate(displacement(0), displacement(1), displacement(2));
+}
+
void TriangleMesh::rotate(float angle, const Axis &axis)
{
if (angle == 0.f)
diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp
index 527846f9d..d389500c6 100644
--- a/src/libslic3r/TriangleMesh.hpp
+++ b/src/libslic3r/TriangleMesh.hpp
@@ -40,6 +40,7 @@ public:
void scale(float factor);
void scale(const Vec3d &versor);
void translate(float x, float y, float z);
+ void translate(const Vec3f &displacement);
void rotate(float angle, const Axis &axis);
void rotate(float angle, const Vec3d& axis);
void rotate_x(float angle) { this->rotate(angle, X); }
diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp
index 6b7faaddc..4466f1b04 100644
--- a/src/libslic3r/Zipper.cpp
+++ b/src/libslic3r/Zipper.cpp
@@ -4,7 +4,6 @@
#include "Zipper.hpp"
#include "miniz/miniz_zip.h"
-#include
#include
#include "I18N.hpp"
@@ -213,10 +212,6 @@ void Zipper::finish_entry()
m_entry.clear();
}
-std::string Zipper::get_name() const {
- return boost::filesystem::path(m_impl->m_zipname).stem().string();
-}
-
void Zipper::finalize()
{
finish_entry();
diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp
index 6566dad42..7d95ffdac 100644
--- a/src/libslic3r/Zipper.hpp
+++ b/src/libslic3r/Zipper.hpp
@@ -81,9 +81,6 @@ public:
/// file is up to minz after the erroneous write.
void finish_entry();
- /// Gets the name of the archive without the path or extension.
- std::string get_name() const;
-
void finalize();
};
diff --git a/src/slic3r.cpp b/src/slic3r.cpp
index 958b66305..780efea7b 100644
--- a/src/slic3r.cpp
+++ b/src/slic3r.cpp
@@ -397,8 +397,9 @@ int CLI::run(int argc, char **argv)
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
} else {
outfile = sla_print.output_filepath(outfile);
- sla_print.export_raster(outfile);
- outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
+ // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata
+ outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
+ sla_print.export_raster(outfile_final);
}
if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) {
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
index 2601842ef..c6a73864d 100644
--- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp
+++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp
@@ -98,8 +98,9 @@ void BackgroundSlicingProcess::process_sla()
m_print->process();
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
- m_sla_print->export_raster(m_export_path);
- m_print->set_status(100, "Masked SLA file exported to " + m_export_path);
+ const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
+ m_sla_print->export_raster(export_path);
+ m_print->set_status(100, "Masked SLA file exported to " + export_path);
} else if (! m_upload_job.empty()) {
prepare_upload();
} else {
@@ -389,7 +390,7 @@ void BackgroundSlicingProcess::prepare_upload()
// Generate a unique temp path to which the gcode/zip file is copied/exported
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
- / boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode");
+ / boost::filesystem::unique_path(".Slic3rPE.upload.%%%%-%%%%-%%%%-%%%%");
if (m_print == m_fff_print) {
m_print->set_status(95, "Running post-processing scripts");
@@ -399,8 +400,8 @@ void BackgroundSlicingProcess::prepare_upload()
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());
} else {
- m_sla_print->export_raster(source_path.string());
- // TODO: Also finalize upload path like with FFF when there are statistics for SLA print
+ 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_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str());
diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp
index 16baa1629..bea242470 100644
--- a/src/slic3r/GUI/BitmapCache.cpp
+++ b/src/slic3r/GUI/BitmapCache.cpp
@@ -1,5 +1,7 @@
#include "BitmapCache.hpp"
+#include "libslic3r/Utils.hpp"
+
#if ! defined(WIN32) && ! defined(__APPLE__)
#define BROKEN_ALPHA
#endif
@@ -9,6 +11,11 @@
#include
#endif /* BROKEN_ALPHA */
+#define NANOSVG_IMPLEMENTATION
+#include "nanosvg/nanosvg.h"
+#define NANOSVGRAST_IMPLEMENTATION
+#include "nanosvg/nanosvgrast.h"
+
namespace Slic3r { namespace GUI {
void BitmapCache::clear()
@@ -155,6 +162,86 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
#endif
}
+wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data)
+{
+ wxImage image(width, height);
+ image.InitAlpha();
+ unsigned char *rgb = image.GetData();
+ unsigned char *alpha = image.GetAlpha();
+ unsigned int pixels = width * height;
+ for (unsigned int i = 0; i < pixels; ++ i) {
+ *rgb ++ = *raw_data ++;
+ *rgb ++ = *raw_data ++;
+ *rgb ++ = *raw_data ++;
+ *alpha ++ = *raw_data ++;
+ }
+ return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
+}
+
+wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned int width, unsigned int height)
+{
+ std::string bitmap_key = bitmap_name + ( height !=0 ?
+ "-h" + std::to_string(height) :
+ "-w" + std::to_string(width));
+ auto it = m_map.find(bitmap_key);
+ if (it != m_map.end())
+ return it->second;
+
+ wxImage image;
+ if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) ||
+ image.GetWidth() == 0 || image.GetHeight() == 0)
+ return nullptr;
+
+ if (height != 0 && image.GetHeight() != height)
+ width = int(0.5f + float(image.GetWidth()) * height / image.GetHeight());
+ else if (width != 0 && image.GetWidth() != width)
+ height = int(0.5f + float(image.GetHeight()) * width / image.GetWidth());
+
+ if (height != 0 && width != 0)
+ image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
+
+ return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
+}
+
+wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned int target_width, unsigned int target_height)
+{
+ std::string bitmap_key = bitmap_name + (target_height != 0 ?
+ "-h" + std::to_string(target_height) :
+ "-w" + std::to_string(target_width));
+ auto it = m_map.find(bitmap_key);
+ if (it != m_map.end())
+ return it->second;
+
+ NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f);
+ if (image == nullptr)
+ return nullptr;
+
+ float scale = target_height != 0 ?
+ (float)target_height / image->height : target_width != 0 ?
+ (float)target_width / image->width : 1;
+
+ int width = (int)(scale * image->width + 0.5f);
+ int height = (int)(scale * image->height + 0.5f);
+ int n_pixels = width * height;
+ if (n_pixels <= 0) {
+ ::nsvgDelete(image);
+ return nullptr;
+ }
+
+ NSVGrasterizer *rast = ::nsvgCreateRasterizer();
+ if (rast == nullptr) {
+ ::nsvgDelete(image);
+ return nullptr;
+ }
+
+ std::vector data(n_pixels * 4, 0);
+ ::nsvgRasterize(rast, image, 0, 0, scale, data.data(), width, height, width * 4);
+ ::nsvgDeleteRasterizer(rast);
+ ::nsvgDelete(image);
+
+ return this->insert_raw_rgba(bitmap_key, width, height, data.data());
+}
+
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency)
{
wxImage image(width, height);
diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp
index 0cb70d28b..8915783a3 100644
--- a/src/slic3r/GUI/BitmapCache.hpp
+++ b/src/slic3r/GUI/BitmapCache.hpp
@@ -29,6 +29,12 @@ public:
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
wxBitmap* insert(const std::string &name, const std::vector &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
+ wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned int width, unsigned int height, const unsigned char *raw_data);
+
+ // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
+ wxBitmap* load_png(const std::string &bitmap_key, unsigned int width = 0, unsigned int height = 0);
+ // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
+ wxBitmap* load_svg(const std::string &bitmap_key, unsigned int width = 0, unsigned int height = 0);
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index a2f4e0a03..80348308f 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -3283,7 +3283,7 @@ bool GLCanvas3D::_init_toolbar()
item.sprite_id = 8;
item.is_toggable = true;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
- item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
+ item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
if (!m_toolbar.add_item(item))
return false;
diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp
index cf218820b..2fff0869a 100644
--- a/src/slic3r/GUI/GLTexture.cpp
+++ b/src/slic3r/GUI/GLTexture.cpp
@@ -13,15 +13,11 @@
#include
#include
-#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
-#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/Utils.hpp"
-#include "libslic3r/Utils.hpp"
-
namespace Slic3r {
namespace GUI {
@@ -380,6 +376,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns
if (n_pixels <= 0)
{
reset();
+ nsvgDelete(image);
return false;
}
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index c0dcc659d..8b574fc8d 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -82,13 +82,31 @@ ObjectList::ObjectList(wxWindow* parent) :
init_icons();
// describe control behavior
- Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) {
+ Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent& event) {
#ifndef __APPLE__
// On Windows and Linux, forces a kill focus emulation on the object manipulator fields because this event handler is called
// before the kill focus event handler on the object manipulator when changing selection in the list, invalidating the object
// manipulator cache with the following call to selection_changed()
wxGetApp().obj_manipul()->emulate_kill_focus();
#endif // __APPLE__
+
+ /* For multiple selection with pressed SHIFT,
+ * event.GetItem() returns value of a first item in selection list
+ * instead of real last clicked item.
+ * So, let check last selected item in such strange way
+ */
+ if (wxGetKeyState(WXK_SHIFT))
+ {
+ wxDataViewItemArray sels;
+ GetSelections(sels);
+ if (sels.front() == m_last_selected_item)
+ m_last_selected_item = sels.back();
+ else
+ m_last_selected_item = event.GetItem();
+ }
+ else
+ m_last_selected_item = event.GetItem();
+
selection_changed();
#ifndef __WXMSW__
set_tooltip_for_item(get_mouse_position_in_control());
@@ -509,7 +527,7 @@ void ObjectList::key_event(wxKeyEvent& event)
) {
remove();
}
- else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT))
+ else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/))
select_item_all_children();
else
event.Skip();
@@ -1005,8 +1023,7 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu)
void ObjectList::append_menu_items_osx(wxMenu* menu)
{
- append_menu_item(menu, wxID_ANY, _(L("Delete item")), "",
- [this](wxCommandEvent&) { remove(); }, "", menu);
+ append_menu_item_delete(menu);
append_menu_item(menu, wxID_ANY, _(L("Rename")), "",
[this](wxCommandEvent&) { rename_item(); }, "", menu);
@@ -1025,7 +1042,7 @@ void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu)
void ObjectList::append_menu_item_export_stl(wxMenu* menu) const
{
- append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, "",
+ append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, "",
[](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu);
menu->AppendSeparator();
}
@@ -1061,6 +1078,12 @@ void ObjectList::append_menu_item_change_extruder(wxMenu* menu) const
}
}
+void ObjectList::append_menu_item_delete(wxMenu* menu)
+{
+ append_menu_item(menu, wxID_ANY, _(L("Delete")), "",
+ [this](wxCommandEvent&) { remove(); }, "", menu);
+}
+
void ObjectList::create_object_popupmenu(wxMenu *menu)
{
#ifdef __WXOSX__
@@ -1101,6 +1124,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
#endif // __WXOSX__
append_menu_item_fix_through_netfabb(menu);
+ append_menu_item_export_stl(menu);
m_menu_item_split_part = append_menu_item_split(menu);
@@ -1117,10 +1141,21 @@ void ObjectList::create_part_popupmenu(wxMenu *menu)
void ObjectList::create_instance_popupmenu(wxMenu*menu)
{
+#ifdef __WXOSX__
+ append_menu_item_delete(menu);
+#endif // __WXOSX__
m_menu_item_split_instances = append_menu_item_instance_to_object(menu);
+ /* New behavior logic:
+ * 1. Split Object to several separated object, if ALL instances are selected
+ * 2. Separate selected instances from the initial object to the separated object,
+ * if some (not all) instances are selected
+ */
wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) {
- evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId());
+// evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId());
+ evt.SetText(wxGetApp().plater()->canvas3D()->get_selection().is_single_full_object() ?
+ _(L("Set as a Separated Objects")) : _(L("Set as a Separated Object")));
+ }, m_menu_item_split_instances->GetId());
}
wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
@@ -1551,7 +1586,7 @@ void ObjectList::split()
auto opt_keys = model_object->volumes[id]->config.keys();
if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) {
select_item(m_objects_model->AddSettingsChild(vol_item));
- Collapse(vol_item);
+ /*Collapse*/Expand(vol_item);
}
}
@@ -1638,16 +1673,20 @@ void ObjectList::part_selection_changed()
bool update_and_show_manipulations = false;
bool update_and_show_settings = false;
- if (multiple_selection()) {
+ const auto item = GetSelection();
+
+ if ( multiple_selection() || item && m_objects_model->GetItemType(item) == itInstanceRoot )
+ {
og_name = _(L("Group manipulation"));
- update_and_show_manipulations = true;
+
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ // don't show manipulation panel for case of all Object's parts selection
+ update_and_show_manipulations = !selection.is_single_full_instance();
}
else
{
- const auto item = GetSelection();
if (item)
{
- bool is_part = false;
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
obj_idx = m_objects_model->GetIdByItem(item);
og_name = _(L("Object manipulation"));
@@ -1665,7 +1704,6 @@ void ObjectList::part_selection_changed()
}
else {
og_name = _(L("Part Settings to modify"));
- is_part = true;
auto main_parent = m_objects_model->GetParent(parent);
obj_idx = m_objects_model->GetIdByItem(main_parent);
const auto volume_id = m_objects_model->GetVolumeIdByItem(parent);
@@ -1675,7 +1713,6 @@ void ObjectList::part_selection_changed()
}
else if (m_objects_model->GetItemType(item) == itVolume) {
og_name = _(L("Part manipulation"));
- is_part = true;
const auto volume_id = m_objects_model->GetVolumeIdByItem(item);
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config;
update_and_show_manipulations = true;
@@ -1743,7 +1780,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
auto opt_keys = model_object->volumes[id]->config.keys();
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
select_item(m_objects_model->AddSettingsChild(vol_item));
- Collapse(vol_item);
+ /*Collapse*/Expand(vol_item);
}
}
Expand(item);
@@ -1757,7 +1794,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
auto opt_keys = model_object->config.keys();
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
select_item(m_objects_model->AddSettingsChild(item));
- Collapse(item);
+ /*Collapse*/Expand(item);
}
#ifndef __WXOSX__
@@ -1923,11 +1960,22 @@ bool ObjectList::multiple_selection() const
return GetSelectedItemsCount() > 1;
}
+bool ObjectList::is_selected(const ItemType type) const
+{
+ const wxDataViewItem& item = GetSelection();
+ if (item)
+ return m_objects_model->GetItemType(item) == type;
+
+ return false;
+}
+
void ObjectList::update_selections()
{
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
wxDataViewItemArray sels;
+ m_selection_mode = smInstance;
+
// We doesn't update selection if SettingsItem for the current object/part is selected
if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings )
{
@@ -1941,32 +1989,56 @@ void ObjectList::update_selections()
return;
}
}
-
- if (selection.is_single_full_object())
+ else if (selection.is_single_full_object() || selection.is_multiple_full_object())
{
- sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
+ const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content();
+ for (const auto& object : objects_content) {
+ if (object.second.size() == 1) // object with 1 instance
+ sels.Add(m_objects_model->GetItemById(object.first));
+ else if (object.second.size() > 1) // object with several instances
+ {
+ wxDataViewItemArray current_sels;
+ GetSelections(current_sels);
+ const wxDataViewItem frst_inst_item = m_objects_model->GetItemByInstanceId(object.first, 0);
+
+ bool root_is_selected = false;
+ for (const auto& item:current_sels)
+ if (item == m_objects_model->GetParent(frst_inst_item) ||
+ item == m_objects_model->GetTopParent(frst_inst_item)) {
+ root_is_selected = true;
+ sels.Add(item);
+ break;
+ }
+ if (root_is_selected)
+ continue;
+
+ const Selection::InstanceIdxsList& instances = object.second;
+ for (const auto& inst : instances)
+ sels.Add(m_objects_model->GetItemByInstanceId(object.first, inst));
+ }
+ }
}
- else if (selection.is_single_volume() || selection.is_modifier() ||
- selection.is_multiple_volume() || selection.is_multiple_full_object()) {
+ else if (selection.is_single_volume() || selection.is_modifier() || selection.is_multiple_volume())
+ {
for (auto idx : selection.get_volume_idxs()) {
const auto gl_vol = selection.get_volume(idx);
- if (selection.is_multiple_full_object())
- sels.Add(m_objects_model->GetItemById(gl_vol->object_idx()));
- else if (gl_vol->volume_idx() >= 0)
+ if (gl_vol->volume_idx() >= 0)
// Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids
// are not associated with ModelVolumes, but they are temporarily generated by the backend
// (for example, SLA supports or SLA pad).
sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx()));
}
+ m_selection_mode = smVolume;
}
- else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) {
+ else if (selection.is_single_full_instance() || selection.is_multiple_full_instance())
+ {
for (auto idx : selection.get_instance_idxs()) {
sels.Add(m_objects_model->GetItemByInstanceId(selection.get_object_idx(), idx));
}
}
else if (selection.is_mixed())
{
- auto& objects_content_list = selection.get_content();
+ const Selection::ObjectIdxsToInstanceIdxsMap& objects_content_list = selection.get_content();
for (auto idx : selection.get_volume_idxs()) {
const auto gl_vol = selection.get_volume(idx);
@@ -1999,6 +2071,9 @@ void ObjectList::update_selections()
sels.Add(m_objects_model->GetItemByVolumeId(glv_obj_idx, glv_vol_idx));
}
}
+
+ if (sels.size() == 0)
+ m_selection_mode = smUndef;
select_items(sels);
@@ -2034,29 +2109,34 @@ void ObjectList::update_selections_on_canvas()
auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection)
{
- if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
- selection.add_object(m_objects_model->GetIdByItem(item), as_single_selection);
+ const ItemType& type = m_objects_model->GetItemType(item);
+ if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) {
+ wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item;
+ selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection);
return;
}
- if (m_objects_model->GetItemType(item) == itVolume) {
+ if (type == itVolume) {
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item));
const int vol_idx = m_objects_model->GetVolumeIdByItem(item);
selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection);
}
- else if (m_objects_model->GetItemType(item) == itInstance) {
+ else if (type == itInstance) {
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
const int inst_idx = m_objects_model->GetInstanceIdByItem(item);
selection.add_instance(obj_idx, inst_idx, as_single_selection);
}
};
+ // stores current instance idx before to clear the selection
+ int instance_idx = selection.get_instance_idx();
+
if (sel_cnt == 1) {
wxDataViewItem item = GetSelection();
if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot))
- add_to_selection(m_objects_model->GetParent(item), selection, -1, true);
+ add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true);
else
- add_to_selection(item, selection, -1, true);
+ add_to_selection(item, selection, instance_idx, true);
wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state();
return;
@@ -2065,8 +2145,6 @@ void ObjectList::update_selections_on_canvas()
wxDataViewItemArray sels;
GetSelections(sels);
- // stores current instance idx before to clear the selection
- int instance_idx = selection.get_instance_idx();
selection.clear();
for (auto item: sels)
add_to_selection(item, selection, instance_idx, false);
@@ -2112,22 +2190,101 @@ void ObjectList::select_item_all_children()
if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) {
for (int i = 0; i < m_objects->size(); i++)
sels.Add(m_objects_model->GetItemById(i));
+ m_selection_mode = smInstance;
}
else {
const auto item = GetSelection();
// Some volume(instance) is selected => select all volumes(instances) inside the current object
- if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) {
+ if (m_objects_model->GetItemType(item) & (itVolume | itInstance))
m_objects_model->GetChildren(m_objects_model->GetParent(item), sels);
- }
+
+ m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance;
}
SetSelections(sels);
selection_changed();
}
+// update selection mode for non-multiple selection
+void ObjectList::update_selection_mode()
+{
+ // All items are unselected
+ if (!GetSelection())
+ {
+ m_last_selected_item = wxDataViewItem(0);
+ m_selection_mode = smUndef;
+ return;
+ }
+
+ const ItemType type = m_objects_model->GetItemType(GetSelection());
+ m_selection_mode = type&itSettings ? smUndef :
+ type&itVolume ? smVolume : smInstance;
+}
+
+// check last selected item. If is it possible to select it
+bool ObjectList::check_last_selection(wxString& msg_str)
+{
+ if (!m_last_selected_item)
+ return true;
+
+ const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT);
+
+ /* We can't mix Parts and Objects/Instances.
+ * So, show information about it
+ */
+ const ItemType type = m_objects_model->GetItemType(m_last_selected_item);
+
+ // check a case of a selection of the Parts from different Objects
+ bool impossible_multipart_selection = false;
+ if (type & itVolume && m_selection_mode == smVolume)
+ {
+ wxDataViewItemArray sels;
+ GetSelections(sels);
+ for (const auto& sel: sels)
+ if (sel != m_last_selected_item &&
+ m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item))
+ {
+ impossible_multipart_selection = true;
+ break;
+ }
+ }
+
+ if (impossible_multipart_selection ||
+ type & itSettings ||
+ type & itVolume && m_selection_mode == smInstance ||
+ !(type & itVolume) && m_selection_mode == smVolume)
+ {
+ // Inform user why selection isn't complited
+ const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part"));
+
+ msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" +
+ _(L("You started your selection with %s Item.")) + "\n" +
+ _(L("In this mode you can select only other %s Items%s")),
+ item_type, item_type,
+ m_selection_mode == smInstance ? "." :
+ " " + _(L("of a current Object")));
+
+ // Unselect last selected item, if selection is without SHIFT
+ if (!is_shift_pressed) {
+ Unselect(m_last_selected_item);
+ show_info(this, msg_str, _(L("Info")));
+ }
+
+ return is_shift_pressed;
+ }
+
+ return true;
+}
+
void ObjectList::fix_multiselection_conflicts()
{
- if (GetSelectedItemsCount() <= 1)
+ if (GetSelectedItemsCount() <= 1) {
+ update_selection_mode();
+ return;
+ }
+
+ wxString msg_string;
+ if (!check_last_selection(msg_string))
return;
m_prevent_list_events = true;
@@ -2135,12 +2292,58 @@ void ObjectList::fix_multiselection_conflicts()
wxDataViewItemArray sels;
GetSelections(sels);
- for (auto item : sels) {
- if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot))
- Unselect(item);
- else if (m_objects_model->GetParent(item) != wxDataViewItem(0))
- Unselect(m_objects_model->GetParent(item));
+ if (m_selection_mode == smVolume)
+ {
+ // identify correct parent of the initial selected item
+ const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front());
+
+ sels.clear();
+ wxDataViewItemArray children; // selected volumes from current parent
+ m_objects_model->GetChildren(parent, children);
+
+ for (const auto child : children)
+ if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume)
+ sels.Add(child);
+
+ // If some part is selected, unselect all items except of selected parts of the current object
+ UnselectAll();
+ SetSelections(sels);
}
+ else
+ {
+ for (const auto item : sels)
+ {
+ if (!IsSelected(item)) // if this item is unselected now (from previous actions)
+ continue;
+
+ if (m_objects_model->GetItemType(item) & itSettings) {
+ Unselect(item);
+ continue;
+ }
+
+ const wxDataViewItem& parent = m_objects_model->GetParent(item);
+ if (parent != wxDataViewItem(0) && IsSelected(parent))
+ Unselect(parent);
+ else
+ {
+ wxDataViewItemArray unsels;
+ m_objects_model->GetAllChildren(item, unsels);
+ for (const auto unsel_item : unsels)
+ Unselect(unsel_item);
+ }
+
+ if (m_objects_model->GetItemType(item) & itVolume)
+ Unselect(item);
+
+ m_selection_mode = smInstance;
+ }
+ }
+
+ if (!msg_string.IsEmpty())
+ show_info(this, msg_string, _(L("Info")));
+
+ if (!IsSelected(m_last_selected_item))
+ m_last_selected_item = wxDataViewItem(0);
m_prevent_list_events = false;
}
@@ -2272,6 +2475,12 @@ void ObjectList::update_object_menu()
void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs)
{
+ if ((*m_objects)[obj_idx]->instances.size() == inst_idxs.size())
+ {
+ instances_to_separated_objects(obj_idx);
+ return;
+ }
+
// create new object from selected instance
ModelObject* model_object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]);
for (int inst_idx = model_object->instances.size() - 1; inst_idx >= 0; inst_idx--)
@@ -2292,6 +2501,31 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set
}
}
+void ObjectList::instances_to_separated_objects(const int obj_idx)
+{
+ const int inst_cnt = (*m_objects)[obj_idx]->instances.size();
+
+ for (int i = inst_cnt-1; i > 0 ; i--)
+ {
+ // create new object from initial
+ ModelObject* object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]);
+ for (int inst_idx = object->instances.size() - 1; inst_idx >= 0; inst_idx--)
+ {
+ if (inst_idx == i)
+ continue;
+ // delete unnecessary instances
+ object->delete_instance(inst_idx);
+ }
+
+ // Add new object to the object_list
+ add_object_to_list(m_objects->size() - 1);
+
+ // delete current instance from the initial object
+ del_subobject_from_object(obj_idx, i, itInstance);
+ delete_instance_from_list(obj_idx, i);
+ }
+}
+
void ObjectList::split_instances()
{
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
@@ -2299,6 +2533,12 @@ void ObjectList::split_instances()
if (obj_idx == -1)
return;
+ if (selection.is_single_full_object())
+ {
+ instances_to_separated_objects(obj_idx);
+ return;
+ }
+
const int inst_idx = selection.get_instance_idx();
const std::set inst_idxs = inst_idx < 0 ?
selection.get_instance_idxs() :
@@ -2424,8 +2664,7 @@ void ObjectList::show_multi_selection_menu()
wxMenu* menu = new wxMenu();
#ifdef __WXOSX__
- append_menu_item(menu, wxID_ANY, _(L("Delete items")), "",
- [this](wxCommandEvent&) { remove(); }, "", menu);
+ append_menu_item_delete(menu);
#endif //__WXOSX__
if (extruders_count() > 1)
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index 4ddc6ea71..2f465a4ab 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -60,6 +60,12 @@ struct ItemForDelete
class ObjectList : public wxDataViewCtrl
{
+ enum SELECTION_MODE
+ {
+ smUndef,
+ smVolume,
+ smInstance
+ } m_selection_mode {smUndef};
struct dragged_item_data
{
@@ -135,6 +141,7 @@ class ObjectList : public wxDataViewCtrl
bool m_part_settings_changed = false;
int m_selected_row = 0;
+ wxDataViewItem m_last_selected_item {nullptr};
#if 0
FreqSettingsBundle m_freq_settings_fff;
@@ -188,6 +195,7 @@ public:
void append_menu_item_fix_through_netfabb(wxMenu* menu);
void append_menu_item_export_stl(wxMenu* menu) const ;
void append_menu_item_change_extruder(wxMenu* menu) const;
+ void append_menu_item_delete(wxMenu* menu);
void create_object_popupmenu(wxMenu *menu);
void create_sla_object_popupmenu(wxMenu*menu);
void create_part_popupmenu(wxMenu*menu);
@@ -251,12 +259,15 @@ public:
void init_objects();
bool multiple_selection() const ;
+ bool is_selected(const ItemType type) const;
void update_selections();
void update_selections_on_canvas();
void select_item(const wxDataViewItem& item);
void select_items(const wxDataViewItemArray& sels);
void select_all();
void select_item_all_children();
+ void update_selection_mode();
+ bool check_last_selection(wxString& msg_str);
// correct current selections to avoid of the possible conflicts
void fix_multiselection_conflicts();
@@ -269,6 +280,7 @@ public:
void update_object_menu();
void instances_to_separated_object(const int obj_idx, const std::set& inst_idx);
+ void instances_to_separated_objects(const int obj_idx);
void split_instances();
void rename_item();
void fix_through_netfabb() const;
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
index 61e98d542..48bb1f690 100644
--- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -161,6 +161,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_move_label_string = L("Position");
m_new_rotate_label_string = L("Rotation");
m_new_scale_label_string = L("Scale factors");
+
+ ObjectList* obj_list = wxGetApp().obj_list();
if (selection.is_single_full_instance())
{
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
@@ -187,7 +189,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_enabled = true;
}
- else if (selection.is_single_full_object())
+ else if (selection.is_single_full_object() && obj_list->is_selected(itObject))
{
m_cache.instance.reset();
@@ -212,7 +214,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_size = (volume->get_volume_transformation().get_matrix(true, true) * volume->bounding_box.size()).cwiseAbs();
m_new_enabled = true;
}
- else if (wxGetApp().obj_list()->multiple_selection())
+ else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot))
{
reset_settings_value();
m_new_move_label_string = L("Translate");
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
index d6ff53301..cabede8cd 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
@@ -16,12 +16,6 @@ namespace Slic3r {
namespace GUI {
-
-
-
-
-// GLGizmoCut
-
class GLGizmoCutPanel : public wxPanel
{
public:
@@ -193,7 +187,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co
m_imgui->set_next_window_bg_alpha(0.5f);
m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
- ImGui::PushItemWidth(100.0f);
+ ImGui::PushItemWidth(m_imgui->scaled(5.0f));
bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f");
m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper);
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 435d9548f..3e7ab3bbc 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -38,6 +38,8 @@
#include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp"
+#include "libnest2d/optimizers/nlopt/genetic.hpp"
+
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
@@ -1199,7 +1201,7 @@ struct Plater::priv
BoundingBox scaled_bed_shape_bb() const;
std::vector load_files(const std::vector& input_files, bool load_model, bool load_config);
std::vector load_model_objects(const ModelObjectPtrs &model_objects);
- std::unique_ptr get_export_file(GUI::FileType file_type);
+ wxString get_export_file(GUI::FileType file_type);
const Selection& get_selection() const;
Selection& get_selection();
@@ -1617,6 +1619,45 @@ std::vector Plater::priv::load_files(const std::vector& input_
}
#if ENABLE_VOLUMES_CENTERING_FIXES
}
+ else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf))
+ {
+ bool advanced = false;
+ for (const ModelObject* model_object : model.objects)
+ {
+ // is there more than one instance ?
+ if (model_object->instances.size() > 1)
+ {
+ advanced = true;
+ break;
+ }
+
+ // is there any modifier ?
+ for (const ModelVolume* model_volume : model_object->volumes)
+ {
+ if (!model_volume->is_model_part())
+ {
+ advanced = true;
+ break;
+ }
+ }
+
+ if (advanced)
+ break;
+ }
+
+ if (advanced)
+ {
+ wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")),
+ _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
+ if (dlg.ShowModal() == wxID_YES)
+ {
+ Slic3r::GUI::wxGetApp().save_mode(comExpert);
+ view3D->set_as_dirty();
+ }
+ else
+ return obj_idxs;
+ }
+ }
#endif // ENABLE_VOLUMES_CENTERING_FIXES
#if !ENABLE_VOLUMES_CENTERING_FIXES
@@ -1642,7 +1683,7 @@ std::vector Plater::priv::load_files(const std::vector& input_
Slic3r::GUI::show_error(nullptr,
wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")),
from_path(filename)));
- return std::vector();
+ return obj_idxs;
}
}
@@ -1784,7 +1825,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode
return obj_idxs;
}
-std::unique_ptr Plater::priv::get_export_file(GUI::FileType file_type)
+wxString Plater::priv::get_export_file(GUI::FileType file_type)
{
wxString wildcard;
switch (file_type) {
@@ -1801,34 +1842,56 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType
// Update printbility state of each of the ModelInstances.
this->update_print_volume_state();
- // Find the file name of the first printable object.
- fs::path output_file = this->model.propose_export_file_name_and_path();
+ const Selection& selection = get_selection();
+ int obj_idx = selection.get_object_idx();
+
+ fs::path output_file;
+ // first try to get the file name from the current selection
+ if ((0 <= obj_idx) && (obj_idx < (int)this->model.objects.size()))
+ output_file = this->model.objects[obj_idx]->get_export_filename();
+
+ if (output_file.empty())
+ // Find the file name of the first printable object.
+ output_file = this->model.propose_export_file_name_and_path();
+
+ wxString dlg_title;
switch (file_type) {
- case FT_STL: output_file.replace_extension("stl"); break;
- case FT_AMF: output_file.replace_extension("zip.amf"); break; // XXX: Problem on OS X with double extension?
- case FT_3MF: output_file.replace_extension("3mf"); break;
+ case FT_STL:
+ {
+ output_file.replace_extension("stl");
+ dlg_title = _(L("Export STL file:"));
+ break;
+ }
+ case FT_AMF:
+ {
+ // XXX: Problem on OS X with double extension?
+ output_file.replace_extension("zip.amf");
+ dlg_title = _(L("Export AMF file:"));
+ break;
+ }
+ case FT_3MF:
+ {
+ output_file.replace_extension("3mf");
+ dlg_title = _(L("Save file as:"));
+ break;
+ }
default: break;
}
- auto dlg = Slic3r::make_unique(q,
- ((file_type == FT_AMF) || (file_type == FT_3MF)) ? _(L("Export print config")) : "",
- true,
- _(L("Save file as:")),
- from_path(output_file.parent_path()),
- from_path(output_file.filename()),
- wildcard,
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT
- );
+ wxFileDialog* dlg = new wxFileDialog(q, dlg_title,
+ from_path(output_file.parent_path()), from_path(output_file.filename()),
+ wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dlg->ShowModal() != wxID_OK) {
- return nullptr;
+ return wxEmptyString;
}
- fs::path path(into_path(dlg->GetPath()));
+ wxString out_path = dlg->GetPath();
+ fs::path path(into_path(out_path));
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
- return dlg;
+ return out_path;
}
const Selection& Plater::priv::get_selection() const
@@ -2039,7 +2102,9 @@ void Plater::priv::sla_optimize_rotation() {
rotoptimizing.store(true);
int obj_idx = get_selected_object_idx();
- ModelObject * o = model.objects[obj_idx];
+ if(obj_idx < 0) { rotoptimizing.store(false); return; }
+
+ ModelObject * o = model.objects[size_t(obj_idx)];
background_process.stop();
@@ -2047,7 +2112,7 @@ void Plater::priv::sla_optimize_rotation() {
statusbar()->set_range(100);
auto stfn = [this] (unsigned st, const std::string& msg) {
- statusbar()->set_progress(st);
+ statusbar()->set_progress(int(st));
statusbar()->set_status_text(msg);
// could be problematic, but we need the cancel button.
@@ -2065,8 +2130,59 @@ void Plater::priv::sla_optimize_rotation() {
[this](){ return !rotoptimizing.load(); }
);
+ const auto *bed_shape_opt = config->opt("bed_shape");
+ assert(bed_shape_opt);
+
+ auto& bedpoints = bed_shape_opt->values;
+ Polyline bed; bed.points.reserve(bedpoints.size());
+ for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
+
+ double mindist = 6.0; // FIXME
+ double offs = mindist / 2.0 - EPSILON;
+
if(rotoptimizing.load()) // wasn't canceled
- for(ModelInstance * oi : o->instances) oi->set_rotation({r[X], r[Y], r[Z]});
+ for(ModelInstance * oi : o->instances) {
+ oi->set_rotation({r[X], r[Y], r[Z]});
+
+ auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
+
+ namespace opt = libnest2d::opt;
+ opt::StopCriteria stopcr;
+ stopcr.relative_score_difference = 0.01;
+ stopcr.max_iterations = 10000;
+ stopcr.stop_score = 0.0;
+ opt::GeneticOptimizer solver(stopcr);
+ Polygon pbed(bed);
+
+ auto bin = pbed.bounding_box();
+ double binw = bin.size()(X) * SCALING_FACTOR - offs;
+ double binh = bin.size()(Y) * SCALING_FACTOR - offs;
+
+ auto result = solver.optimize_min([&trchull, binw, binh](double rot){
+ auto chull = trchull;
+ chull.rotate(rot);
+
+ auto bb = chull.bounding_box();
+ double bbw = bb.size()(X) * SCALING_FACTOR;
+ double bbh = bb.size()(Y) * SCALING_FACTOR;
+
+ auto wdiff = bbw - binw;
+ auto hdiff = bbh - binh;
+ double diff = 0;
+ if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
+ if(wdiff > 0) diff += wdiff;
+ if(hdiff > 0) diff += hdiff;
+
+ return diff;
+ }, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
+
+ double r = std::get<0>(result.optimum);
+
+ Vec3d rt = oi->get_rotation(); rt(Z) += r;
+ oi->set_rotation(rt);
+ }
+
+ arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed);
// Correct the z offset of the object which was corrupted be the rotation
o->ensure_on_bed();
@@ -2714,7 +2830,10 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
if (is_part) {
item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")),
[this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png");
- } else {
+
+ sidebar->obj_list()->append_menu_item_export_stl(menu);
+ }
+ else {
wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")),
[this](wxCommandEvent&) { q->increase_instances(); }, "add.png");
wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")),
@@ -2745,10 +2864,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
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("Export object as STL")) + dots, _(L("Export this single object as STL file")),
+ append_menu_item(menu, wxID_ANY, _(L("Export as STL")) + dots, _(L("Export the selected object as STL file")),
[this](wxCommandEvent&) { q->export_stl(true); });
+
+ menu->AppendSeparator();
}
- menu->AppendSeparator();
sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu);
@@ -2907,6 +3027,9 @@ bool Plater::priv::can_split() const
bool Plater::priv::layers_height_allowed() const
{
+ if (printer_technology != ptFFF)
+ return false;
+
int obj_idx = get_selected_object_idx();
return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed();
}
@@ -3243,11 +3366,8 @@ void Plater::export_stl(bool selection_only)
{
if (p->model.objects.empty()) { return; }
- auto dialog = p->get_export_file(FT_STL);
- if (! dialog) { return; }
-
- // Store a binary STL
- const wxString path = dialog->GetPath();
+ wxString path = p->get_export_file(FT_STL);
+ if (path.empty()) { return; }
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
@@ -3259,10 +3379,25 @@ void Plater::export_stl(bool selection_only)
const auto obj_idx = selection.get_object_idx();
if (obj_idx == -1) { return; }
- mesh = p->model.objects[obj_idx]->mesh();
- } else {
- mesh = p->model.mesh();
+
+ const ModelObject* model_object = p->model.objects[obj_idx];
+ if (selection.get_mode() == Selection::Instance)
+ {
+ if (selection.is_single_full_object())
+ mesh = model_object->mesh();
+ else
+ mesh = model_object->full_raw_mesh();
+ }
+ else
+ {
+ const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
+ mesh = model_object->volumes[volume->volume_idx()]->mesh;
+ mesh.transform(volume->get_volume_transformation().get_matrix());
+ mesh.translate(-model_object->origin_translation.cast());
+ }
}
+ else
+ mesh = p->model.mesh();
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
p->statusbar()->set_status_text(wxString::Format(_(L("STL file exported to %s")), path));
@@ -3272,15 +3407,14 @@ void Plater::export_amf()
{
if (p->model.objects.empty()) { return; }
- auto dialog = p->get_export_file(FT_AMF);
- if (! dialog) { return; }
-
- const wxString path = dialog->GetPath();
+ wxString path = p->get_export_file(FT_AMF);
+ if (path.empty()) { return; }
const std::string path_u8 = into_u8(path);
- DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
wxBusyCursor wait;
- if (Slic3r::store_amf(path_u8.c_str(), &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) {
+ bool export_config = true;
+ DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
+ if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
} else {
@@ -3297,10 +3431,8 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
bool export_config = true;
if (output_path.empty())
{
- auto dialog = p->get_export_file(FT_3MF);
- if (!dialog) { return; }
- path = dialog->GetPath();
- export_config = dialog->get_checkbox_value();
+ path = p->get_export_file(FT_3MF);
+ if (path.empty()) { return; }
}
else
path = from_path(output_path);
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index 5058c72f8..ae121f942 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -133,10 +133,12 @@ private:
const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; }
};
+public:
typedef std::map VolumesCache;
typedef std::set InstanceIdxsList;
typedef std::map ObjectIdxsToInstanceIdxsMap;
+private:
struct Cache
{
// Cache of GLVolume derived transformation matrices, valid during mouse dragging.
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 631050f29..c2cdf8f03 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -1446,7 +1446,7 @@ void TabFilament::build()
line.append_option(optgroup->get_option("bed_temperature"));
optgroup->append_line(line);
- page = add_options_page(_(L("Cooling")), "hourglass.png");
+ page = add_options_page(_(L("Cooling")), "cooling");
optgroup = page->new_optgroup(_(L("Enable")));
optgroup->append_single_option_line("fan_always_on");
optgroup->append_single_option_line("cooling");
@@ -1638,7 +1638,8 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup)
}
auto printhost_browse = [=](wxWindow* parent) {
- auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
+ auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots,
+ wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
btn->SetBitmap(create_scaled_bitmap("zoom.png"));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index 55544f28e..7755bc30a 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -8,6 +8,8 @@
#include
#include
+#include
+
#include "BitmapCache.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
@@ -421,19 +423,21 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse)
// PrusaObjectDataViewModelNode
// ----------------------------------------------------------------------------
-wxBitmap create_scaled_bitmap(const std::string& bmp_name)
+// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
+wxBitmap create_scaled_bitmap(const std::string& bmp_name_in, const bool is_horizontal /* = false*/)
{
- const double scale_f = Slic3r::GUI::wxGetApp().em_unit()* 0.1;//GetContentScaleFactor();
- if (scale_f == 1.0)
- return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG);
-// else if (scale_f == 2.0) // use biger icon
-// return wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(bmp_name_X2)), wxBITMAP_TYPE_PNG);
+ static Slic3r::GUI::BitmapCache cache;
- wxImage img = wxImage(Slic3r::GUI::from_u8(Slic3r::var(bmp_name)), wxBITMAP_TYPE_PNG);
- const int sz_w = int(img.GetWidth()*scale_f);
- const int sz_h = int(img.GetHeight()*scale_f);
- img.Rescale(sz_w, sz_h, wxIMAGE_QUALITY_BILINEAR);
- return wxBitmap(img);
+ unsigned int height, width = height = 0;
+ unsigned int& scale_base = is_horizontal ? width : height;
+ scale_base = (unsigned int)(Slic3r::GUI::wxGetApp().em_unit() * 1.6f + 0.5f);
+
+ std::string bmp_name = bmp_name_in;
+ boost::replace_last(bmp_name, ".png", "");
+ wxBitmap *bmp = cache.load_svg(bmp_name, height, width);
+ if (bmp == nullptr)
+ bmp = cache.load_png(bmp_name, height, width);
+ return *bmp;
}
void PrusaObjectDataViewModelNode::set_object_action_icon() {
@@ -1239,6 +1243,32 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent,
return count;
}
+void PrusaObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const
+{
+ PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID();
+ if (!node) {
+ for (auto object : m_objects)
+ array.Add(wxDataViewItem((void*)object));
+ }
+ else if (node->GetChildCount() == 0)
+ return;
+ else {
+ const size_t count = node->GetChildren().GetCount();
+ for (size_t pos = 0; pos < count; pos++) {
+ PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos);
+ array.Add(wxDataViewItem((void*)child));
+ }
+ }
+
+ wxDataViewItemArray new_array = array;
+ for (const auto item : new_array)
+ {
+ wxDataViewItemArray children;
+ GetAllChildren(item, children);
+ WX_APPEND_ARRAY(array, children);
+ }
+}
+
ItemType PrusaObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
{
if (!item.IsOk())
@@ -1456,8 +1486,8 @@ PrusaDoubleSlider::PrusaDoubleSlider(wxWindow *parent,
if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
- m_bmp_thumb_higher = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "right_half_circle.png" : "up_half_circle.png"));
- m_bmp_thumb_lower = wxBitmap(create_scaled_bitmap(style == wxSL_HORIZONTAL ? "left_half_circle.png" : "down_half_circle.png"));
+ m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? create_scaled_bitmap("right_half_circle.png") : create_scaled_bitmap("up_half_circle.png", true));
+ m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? create_scaled_bitmap("left_half_circle.png" ) : create_scaled_bitmap("down_half_circle.png", true));
m_thumb_size = m_bmp_thumb_lower.GetSize();
m_bmp_add_tick_on = create_scaled_bitmap("colorchange_add_on.png");
diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp
index 27da67deb..912475ced 100644
--- a/src/slic3r/GUI/wxExtensions.hpp
+++ b/src/slic3r/GUI/wxExtensions.hpp
@@ -31,7 +31,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin
wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
std::function cb, wxEvtHandler* event_handler);
-wxBitmap create_scaled_bitmap(const std::string& bmp_name);
+wxBitmap create_scaled_bitmap(const std::string& bmp_name, const bool is_horizontal = false);
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
@@ -511,7 +511,7 @@ public:
virtual bool IsContainer(const wxDataViewItem &item) const override;
virtual unsigned int GetChildren(const wxDataViewItem &parent,
wxDataViewItemArray &array) const override;
-
+ void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp
index 48c166406..380d30cf4 100644
--- a/src/slic3r_app_msvc.cpp
+++ b/src/slic3r_app_msvc.cpp
@@ -212,7 +212,12 @@ int wmain(int argc, wchar_t **argv)
argv_extended.emplace_back(nullptr);
OpenGLVersionCheck opengl_version_check;
- bool load_mesa = ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0);
+ bool load_mesa =
+ // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context.
+ // In that case, use Mesa.
+ ::GetSystemMetrics(SM_REMOTESESSION) ||
+ // Try to load the default OpenGL driver and test its context version.
+ ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0);
wchar_t path_to_exe[MAX_PATH + 1] = { 0 };
::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH);