Merge branch 'master' of https://github.com/prusa3d/Slic3r into et_canvas_gui_refactoring
This commit is contained in:
commit
6ca49c05df
@ -1,25 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g>
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="layers">
|
||||
<g>
|
||||
<rect x="7.98" y="105" fill="#FFFFFF" width="112.04" height="15"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="7.98" y="85.67" fill="#FFFFFF" width="112.04" height="13"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="7.98" y="66.33" fill="#FFFFFF" width="112.04" height="11"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="7.98" y="47" fill="#ED6B21" width="112.04" height="9"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="7.98" y="27.67" fill="#ED6B21" width="112.04" height="7"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="7.98" y="8.33" fill="#ED6B21" width="112.04" height="5"/>
|
||||
<g>
|
||||
<rect x="1" y="13" fill="#808080" width="14" height="2"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="1" y="10.6" fill="#808080" width="14" height="1.74"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="1" y="8.19" fill="#808080" width="14" height="1.47"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="1" y="5.79" fill="#ED6B21" width="14" height="1.2"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="1" y="3.39" fill="#ED6B21" width="14" height="0.93"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="1" y="0.99" fill="#808080" width="14" height="0.67"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 841 B After Width: | Height: | Size: 845 B |
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "GCode/WipeTowerPrusaMM.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include "PrintExport.hpp"
|
||||
//#include "PrintExport.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include "Rasterizer/Rasterizer.hpp"
|
||||
//#include <tbb/parallel_for.h>
|
||||
@ -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<class T = void> struct VeryFalse { static const bool value = false; };
|
||||
template<class Fmt> class LayerWriter {
|
||||
public:
|
||||
|
||||
LayerWriter(const std::string& /*zipfile_path*/) {
|
||||
LayerWriter(const std::string& /*zipfile_path*/)
|
||||
{
|
||||
static_assert(VeryFalse<Fmt>::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<class LyrFmt>
|
||||
inline void save(const std::string& path) {
|
||||
inline void save(const std::string& fpath, const std::string& prjname = "")
|
||||
{
|
||||
try {
|
||||
LayerWriter<LyrFmt> writer(path);
|
||||
LayerWriter<LyrFmt> 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;
|
||||
|
@ -1790,8 +1790,13 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &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<ExPolygons> PrintObject::_slice_volume(const std::vector<float> &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
|
||||
|
@ -320,10 +320,8 @@ struct SLAPrintStatistics
|
||||
}
|
||||
};
|
||||
|
||||
struct SLAminzZipper {};
|
||||
|
||||
// The implementation of creating zipped archives with wxWidgets
|
||||
template<> class LayerWriter<SLAminzZipper> {
|
||||
template<> class LayerWriter<Zipper> {
|
||||
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<class T> inline LayerWriter& operator<<(T&& arg) {
|
||||
m_zip << std::forward<T>(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<class Fmt = SLAminzZipper>
|
||||
void export_raster(const std::string& fname) {
|
||||
if(m_printer) m_printer->save<Fmt>(fname);
|
||||
template<class Fmt = Zipper>
|
||||
inline void export_raster(const std::string& fpath,
|
||||
const std::string& projectname = "")
|
||||
{
|
||||
if(m_printer) m_printer->save<Fmt>(fpath, projectname);
|
||||
}
|
||||
|
||||
const PrintObjects& objects() const { return m_objects; }
|
||||
|
@ -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)
|
||||
|
@ -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); }
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
#include "Zipper.hpp"
|
||||
#include "miniz/miniz_zip.h"
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#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();
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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 <wx/rawbmp.h>
|
||||
#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<unsigned char> 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);
|
||||
|
@ -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<wxBitmap> &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); }
|
||||
|
@ -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;
|
||||
|
@ -13,15 +13,11 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
@ -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<int>& 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<int> 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)
|
||||
|
@ -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<int>& inst_idx);
|
||||
void instances_to_separated_objects(const int obj_idx);
|
||||
void split_instances();
|
||||
void rename_item();
|
||||
void fix_through_netfabb() const;
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
|
||||
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
|
||||
std::unique_ptr<CheckboxFileDialog> 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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<size_t>();
|
||||
return obj_idxs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1784,7 +1825,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
||||
return obj_idxs;
|
||||
}
|
||||
|
||||
std::unique_ptr<CheckboxFileDialog> 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<CheckboxFileDialog> 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<CheckboxFileDialog>(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<ConfigOptionPoints>("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<float>());
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
@ -133,10 +133,12 @@ private:
|
||||
const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; }
|
||||
};
|
||||
|
||||
public:
|
||||
typedef std::map<unsigned int, VolumeCache> VolumesCache;
|
||||
typedef std::set<int> InstanceIdxsList;
|
||||
typedef std::map<int, InstanceIdxsList> ObjectIdxsToInstanceIdxsMap;
|
||||
|
||||
private:
|
||||
struct Cache
|
||||
{
|
||||
// Cache of GLVolume derived transformation matrices, valid during mouse dragging.
|
||||
|
@ -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);
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/numformatter.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#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");
|
||||
|
@ -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<void(wxCommandEvent& event)> 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; }
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user