Merge remote-tracking branch 'remotes/origin/master' into fs_QuadricEdgeCollapse
@ -64,6 +64,9 @@ void main()
|
||||
float world_normal_z_fs = world_normal_z;
|
||||
if (compute_triangle_normals_in_fs) {
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
#ifdef FLIP_TRIANGLE_NORMALS
|
||||
triangle_normal = -triangle_normal;
|
||||
#endif
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal);
|
||||
|
BIN
resources/shapes/OTHER_recycling_symbol.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
resources/shapes/PETG_recycling_symbol.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
resources/shapes/box.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
resources/shapes/bunny.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
resources/shapes/cylinder.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
resources/shapes/pyramid.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
resources/shapes/sphere.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -330,6 +330,8 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
} else if (opt_key == "dont_arrange") {
|
||||
// do nothing - this option alters other transform options
|
||||
} else if (opt_key == "ensure_on_bed") {
|
||||
// do nothing, the value is used later
|
||||
} else if (opt_key == "rotate") {
|
||||
for (auto &model : m_models)
|
||||
for (auto &o : model.objects)
|
||||
@ -432,6 +434,13 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
// All transforms have been dealt with. Now ensure that the objects are on bed.
|
||||
// (Unless the user said otherwise.)
|
||||
if (m_config.opt_bool("ensure_on_bed"))
|
||||
for (auto &model : m_models)
|
||||
for (auto &o : model.objects)
|
||||
o->ensure_on_bed();
|
||||
|
||||
// loop through action options
|
||||
for (auto const &opt_key : m_actions) {
|
||||
if (opt_key == "help") {
|
||||
|
@ -871,6 +871,7 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
||||
token.erase(equals_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the cli -> option mapping.
|
||||
const auto it = opts.find(token);
|
||||
if (it == opts.end()) {
|
||||
@ -879,15 +880,46 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
||||
}
|
||||
const t_config_option_key opt_key = it->second;
|
||||
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
|
||||
|
||||
// If the option type expects a value and it was not already provided,
|
||||
// look for it in the next token.
|
||||
if (optdef.type != coBool && optdef.type != coBools && value.empty()) {
|
||||
if (value.empty()) {
|
||||
if (optdef.type != coBool && optdef.type != coBools) {
|
||||
if (i == (argc-1)) {
|
||||
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
value = argv[++ i];
|
||||
} else {
|
||||
// This is a bool or bools. The value is optional, but may still be there.
|
||||
// Check if the next token can be deserialized into ConfigOptionBool.
|
||||
// If it is in fact bools, it will be rejected later anyway.
|
||||
if (i != argc-1) { // There is still a token to read.
|
||||
ConfigOptionBool cobool;
|
||||
if (cobool.deserialize(argv[i+1]))
|
||||
value = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (no) {
|
||||
if (optdef.type != coBool && optdef.type != coBools) {
|
||||
boost::nowide::cerr << "Only boolean config options can be negated with --no- prefix." << std::endl;
|
||||
return false;
|
||||
}
|
||||
else if (! value.empty()) {
|
||||
boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (optdef.type == coBools && ! value.empty()) {
|
||||
boost::nowide::cerr << "Vector boolean options cannot have a value. Fill them in by "
|
||||
"repeating them and negate by --no- prefix." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Store the option value.
|
||||
const bool existing = this->has(opt_key);
|
||||
if (keys != nullptr && ! existing) {
|
||||
@ -911,7 +943,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
||||
// unescaped by the calling shell.
|
||||
opt_vector->deserialize(value, true);
|
||||
} else if (opt_base->type() == coBool) {
|
||||
if (value.empty())
|
||||
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
|
||||
else
|
||||
opt_base->deserialize(value);
|
||||
} else if (opt_base->type() == coString) {
|
||||
// Do not unescape single string values, the unescaping is left to the calling shell.
|
||||
static_cast<ConfigOptionString*>(opt_base)->value = value;
|
||||
|
@ -2860,9 +2860,10 @@ namespace Slic3r {
|
||||
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
|
||||
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
|
||||
}
|
||||
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
|
||||
if (volume->source.is_converted_from_inches)
|
||||
stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
if (volume->source.is_converted_from_meters)
|
||||
else if (volume->source.is_converted_from_meters)
|
||||
stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
}
|
||||
|
||||
|
@ -1241,9 +1241,10 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
|
||||
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
|
||||
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
|
||||
}
|
||||
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
|
||||
if (volume->source.is_converted_from_inches)
|
||||
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
|
||||
if (volume->source.is_converted_from_meters)
|
||||
else if (volume->source.is_converted_from_meters)
|
||||
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
|
||||
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
|
@ -460,13 +460,15 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
||||
this->objects.push_back(object);
|
||||
}
|
||||
|
||||
static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
|
||||
|
||||
bool Model::looks_like_imperial_units() const
|
||||
{
|
||||
if (this->objects.size() == 0)
|
||||
return false;
|
||||
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3;
|
||||
if (obj->get_object_stl_stats().volume < volume_threshold_inches)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@ -474,22 +476,26 @@ bool Model::looks_like_imperial_units() const
|
||||
|
||||
void Model::convert_from_imperial_units(bool only_small_volumes)
|
||||
{
|
||||
double in_to_mm = 25.4;
|
||||
static constexpr const in_to_mm = 25.4;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3;
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) {
|
||||
obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
for (ModelVolume* v : obj->volumes)
|
||||
for (ModelVolume* v : obj->volumes) {
|
||||
assert(! v->source.is_converted_from_meters);
|
||||
v->source.is_converted_from_inches = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1
|
||||
|
||||
bool Model::looks_like_saved_in_meters() const
|
||||
{
|
||||
if (this->objects.size() == 0)
|
||||
return false;
|
||||
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (obj->get_object_stl_stats().volume < 0.001) // 0.001 = 0.1*0.1*0.1;
|
||||
if (obj->get_object_stl_stats().volume < volume_threshold_meters)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@ -497,13 +503,15 @@ bool Model::looks_like_saved_in_meters() const
|
||||
|
||||
void Model::convert_from_meters(bool only_small_volumes)
|
||||
{
|
||||
double m_to_mm = 1000;
|
||||
static constexpr const double m_to_mm = 1000;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < 0.001) { // 0.001 = 0.1*0.1*0.1;
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) {
|
||||
obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
|
||||
for (ModelVolume* v : obj->volumes)
|
||||
for (ModelVolume* v : obj->volumes) {
|
||||
assert(! v->source.is_converted_from_inches);
|
||||
v->source.is_converted_from_meters = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Model::adjust_min_z()
|
||||
@ -725,6 +733,11 @@ void ModelObject::clear_volumes()
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
bool ModelObject::is_mm_painted() const
|
||||
{
|
||||
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
}
|
||||
|
||||
void ModelObject::sort_volumes(bool full_sort)
|
||||
{
|
||||
// sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. "
|
||||
@ -1070,6 +1083,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH;
|
||||
if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER)
|
||||
vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER;
|
||||
assert(! vol->source.is_converted_from_inches || ! vol->source.is_converted_from_meters);
|
||||
}
|
||||
else
|
||||
vol->set_offset(volume->get_offset());
|
||||
@ -1740,18 +1754,15 @@ void ModelVolume::scale(const Vec3d& scaling_factors)
|
||||
|
||||
void ModelObject::scale_to_fit(const Vec3d &size)
|
||||
{
|
||||
/*
|
||||
BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
|
||||
Vec3d orig_size = this->bounding_box().size();
|
||||
float factor = fminf(
|
||||
size.x / orig_size.x,
|
||||
fminf(
|
||||
size.y / orig_size.y,
|
||||
size.z / orig_size.z
|
||||
double factor = std::min(
|
||||
size.x() / orig_size.x(),
|
||||
std::min(
|
||||
size.y() / orig_size.y(),
|
||||
size.z() / orig_size.z()
|
||||
)
|
||||
);
|
||||
this->scale(factor);
|
||||
*/
|
||||
}
|
||||
|
||||
void ModelVolume::assign_new_unique_ids_recursive()
|
||||
@ -1825,6 +1836,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand
|
||||
|
||||
void ModelVolume::convert_from_imperial_units()
|
||||
{
|
||||
assert(! this->source.is_converted_from_meters);
|
||||
double in_to_mm = 25.4;
|
||||
this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
this->set_offset(Vec3d(0, 0, 0));
|
||||
@ -1833,6 +1845,7 @@ void ModelVolume::convert_from_imperial_units()
|
||||
|
||||
void ModelVolume::convert_from_meters()
|
||||
{
|
||||
assert(! this->source.is_converted_from_inches);
|
||||
double m_to_mm = 1000;
|
||||
this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
|
||||
this->set_offset(Vec3d(0, 0, 0));
|
||||
|
@ -285,6 +285,8 @@ public:
|
||||
void clear_volumes();
|
||||
void sort_volumes(bool full_sort);
|
||||
bool is_multiparts() const { return volumes.size() > 1; }
|
||||
// Checks if any of object volume is painted using the multi-material painting gizmo.
|
||||
bool is_mm_painted() const;
|
||||
|
||||
ModelInstance* add_instance();
|
||||
ModelInstance* add_instance(const ModelInstance &instance);
|
||||
@ -715,6 +717,8 @@ public:
|
||||
this->mmu_segmentation_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
friend class SLAPrint;
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "Print.hpp"
|
||||
#include "VoronoiVisualUtils.hpp"
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <cfloat>
|
||||
@ -502,11 +503,13 @@ static std::vector<std::vector<ColoredLine>> colorize_polygons(const std::vector
|
||||
|
||||
using boost::polygon::voronoi_diagram;
|
||||
|
||||
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
|
||||
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; }
|
||||
|
||||
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
|
||||
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return {coord_t(point.x()), coord_t(point.y())}; }
|
||||
|
||||
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
|
||||
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; }
|
||||
|
||||
static inline Vec2d mk_vec2(const voronoi_diagram<double>::vertex_type *point) { return {point->x(), point->y()}; }
|
||||
|
||||
struct MMU_Graph
|
||||
{
|
||||
@ -696,9 +699,9 @@ struct MMU_Graph
|
||||
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
||||
vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
|
||||
} else if (bbox.contains(vertex_point)) {
|
||||
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) {
|
||||
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) {
|
||||
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
|
||||
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) {
|
||||
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(3 * SCALED_EPSILON)) {
|
||||
closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count()));
|
||||
vertex.color(this->nodes_count());
|
||||
this->nodes.push_back({vertex_point});
|
||||
@ -796,6 +799,27 @@ static inline void init_polygon_indices(const MMU_Graph
|
||||
}
|
||||
}
|
||||
|
||||
// Voronoi edges produced by Voronoi generator cloud have coordinates that don't fit inside coord_t (int32_t).
|
||||
// Because of that, this function tries to clip edges that have one endpoint of the edge inside the BoundingBox.
|
||||
static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, const BoundingBoxf &bbox)
|
||||
{
|
||||
assert(edge.is_finite());
|
||||
Vec2d v0 = mk_vec2(edge.vertex0());
|
||||
Vec2d v1 = mk_vec2(edge.vertex1());
|
||||
bool contains_v0 = bbox.contains(v0);
|
||||
bool contains_v1 = bbox.contains(v1);
|
||||
if ((contains_v0 && contains_v1) || (!contains_v0 && !contains_v1))
|
||||
return {mk_point(edge.vertex0()), mk_point(edge.vertex1())};
|
||||
|
||||
Vec2d vector = (v1 - v0).normalized() * bbox.size().norm();
|
||||
if (!contains_v0)
|
||||
v0 = (v1 - vector);
|
||||
else
|
||||
v1 = (v0 + vector);
|
||||
|
||||
return {v0.cast<coord_t>(), v1.cast<coord_t>()};
|
||||
}
|
||||
|
||||
static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<ColoredLine>> &color_poly)
|
||||
{
|
||||
Geometry::VoronoiDiagram vd;
|
||||
@ -852,6 +876,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
|
||||
};
|
||||
|
||||
bbox.offset(scale_(10.));
|
||||
const BoundingBoxf bbox_clip(bbox.min.cast<double>(), bbox.max.cast<double>());
|
||||
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
|
||||
|
||||
// Make a copy of the input segments with the double type.
|
||||
@ -890,72 +915,74 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
|
||||
}
|
||||
}
|
||||
} else if (edge_it->is_finite()) {
|
||||
const Point v0 = mk_point(edge_it->vertex0());
|
||||
const Point v1 = mk_point(edge_it->vertex1());
|
||||
const size_t from_idx = edge_it->vertex0()->color();
|
||||
const size_t to_idx = edge_it->vertex1()->color();
|
||||
|
||||
// Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points.
|
||||
if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color())) continue;
|
||||
if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color()))
|
||||
continue;
|
||||
|
||||
const Line edge_line(v0, v1);
|
||||
const Line edge_line = clip_finite_voronoi_edge(*edge_it, bbox_clip);
|
||||
const Line contour_line = lines_colored[edge_it->cell()->source_index()].line;
|
||||
const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()];
|
||||
const ColoredLine contour_line_prev = get_prev_contour_line(edge_it);
|
||||
const ColoredLine contour_line_next = get_next_contour_line(edge_it);
|
||||
|
||||
Point intersection;
|
||||
if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) {
|
||||
// if(edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0())) {
|
||||
//
|
||||
// }
|
||||
if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1())) {
|
||||
Line contour_line_twin = lines_colored[edge_it->twin()->cell()->source_index()].line;
|
||||
enum class Vertex { VERTEX0, VERTEX1 };
|
||||
auto append_edge_if_intersects_with_contour = [&graph, &lines_colored, &edge_line, &contour_line](const voronoi_diagram<double>::const_edge_iterator &edge_iterator, const Vertex vertex) {
|
||||
Point intersection;
|
||||
Line contour_line_twin = lines_colored[edge_iterator->twin()->cell()->source_index()].line;
|
||||
if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) {
|
||||
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->twin()->cell()->source_index());
|
||||
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->twin()->cell()->source_index());
|
||||
const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx :
|
||||
graph_arc.to_idx;
|
||||
graph.append_edge(edge_it->vertex1()->color(), to_idx_l);
|
||||
graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l);
|
||||
} else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
|
||||
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->cell()->source_index());
|
||||
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->cell()->source_index());
|
||||
const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx;
|
||||
graph.append_edge(edge_it->vertex1()->color(), to_idx_l);
|
||||
}
|
||||
mark_processed(edge_it);
|
||||
graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l);
|
||||
}
|
||||
mark_processed(edge_iterator);
|
||||
};
|
||||
|
||||
if (edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0()))
|
||||
append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX0);
|
||||
|
||||
if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1()))
|
||||
append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX1);
|
||||
} else if (graph.is_edge_attach_to_contour(edge_it)) {
|
||||
mark_processed(edge_it);
|
||||
// Skip edges witch connection two points on a contour
|
||||
if (graph.is_edge_connecting_two_contour_vertices(edge_it))
|
||||
continue;
|
||||
|
||||
const size_t from_idx = edge_it->vertex0()->color();
|
||||
const size_t to_idx = edge_it->vertex1()->color();
|
||||
if (graph.is_vertex_on_contour(edge_it->vertex0())) {
|
||||
if (is_point_closer_to_beginning_of_line(contour_line, v0)) {
|
||||
if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, v1)) {
|
||||
if (is_point_closer_to_beginning_of_line(contour_line, edge_line.a)) {
|
||||
if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.b)) {
|
||||
graph.append_edge(from_idx, to_idx);
|
||||
force_edge_adding[colored_line.poly_idx] = false;
|
||||
}
|
||||
} else {
|
||||
if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, v1)) {
|
||||
if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.b)) {
|
||||
graph.append_edge(from_idx, to_idx);
|
||||
force_edge_adding[colored_line.poly_idx] = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(graph.is_vertex_on_contour(edge_it->vertex1()));
|
||||
if (is_point_closer_to_beginning_of_line(contour_line, v1)) {
|
||||
if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, v0)) {
|
||||
if (is_point_closer_to_beginning_of_line(contour_line, edge_line.b)) {
|
||||
if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.a)) {
|
||||
graph.append_edge(from_idx, to_idx);
|
||||
force_edge_adding[colored_line.poly_idx] = false;
|
||||
}
|
||||
} else {
|
||||
if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, v0)) {
|
||||
if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.a)) {
|
||||
graph.append_edge(from_idx, to_idx);
|
||||
force_edge_adding[colored_line.poly_idx] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
|
||||
} else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
|
||||
mark_processed(edge_it);
|
||||
Point real_v0 = graph.nodes[edge_it->vertex0()->color()].point;
|
||||
Point real_v1 = graph.nodes[edge_it->vertex1()->color()].point;
|
||||
@ -1202,7 +1229,7 @@ static void cut_segmented_layers(const std::vector<ExPolygons>
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end";
|
||||
}
|
||||
|
||||
// #define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
|
||||
// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo
|
||||
static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
||||
@ -1671,7 +1698,7 @@ static void export_regions_to_svg(const std::string &path, const std::vector<std
|
||||
|
||||
svg.draw_outline(lslices, "green", "lime", stroke_width);
|
||||
for (const std::pair<ExPolygon, size_t> ®ion : regions) {
|
||||
int region_color = region.second;
|
||||
int region_color = int(region.second);
|
||||
if (region_color >= 0 && region_color < int(colors.size()))
|
||||
svg.draw(region.first, colors[region_color]);
|
||||
else
|
||||
|
@ -3,6 +3,12 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <mach/machine.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static auto s_platform = Platform::Uninitialized;
|
||||
@ -17,7 +23,38 @@ void detect_platform()
|
||||
#elif defined(__APPLE__)
|
||||
BOOST_LOG_TRIVIAL(info) << "Platform: OSX";
|
||||
s_platform = Platform::OSX;
|
||||
s_platform_flavor = PlatformFlavor::Generic;
|
||||
s_platform_flavor = PlatformFlavor::GenericOSX;
|
||||
{
|
||||
cpu_type_t type = 0;
|
||||
size_t size = sizeof(type);
|
||||
if (sysctlbyname("hw.cputype", &type, &size, NULL, 0) == 0) {
|
||||
type &= ~CPU_ARCH_MASK;
|
||||
if (type == CPU_TYPE_X86) {
|
||||
int proc_translated = 0;
|
||||
size = sizeof(proc_translated);
|
||||
// Detect if native CPU is really X86 or PrusaSlicer runs through Rosetta.
|
||||
if (sysctlbyname("sysctl.proc_translated", &proc_translated, &size, NULL, 0) == -1) {
|
||||
if (errno == ENOENT) {
|
||||
// Native CPU is X86, and property sysctl.proc_translated doesn't exist.
|
||||
s_platform_flavor = PlatformFlavor::OSXOnX86;
|
||||
BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnX86";
|
||||
}
|
||||
} else if (proc_translated == 1) {
|
||||
// Native CPU is ARM and PrusaSlicer runs through Rosetta.
|
||||
s_platform_flavor = PlatformFlavor::OSXOnArm;
|
||||
BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnArm";
|
||||
} else {
|
||||
// Native CPU is X86.
|
||||
s_platform_flavor = PlatformFlavor::OSXOnX86;
|
||||
BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnX86";
|
||||
}
|
||||
} else if (type == CPU_TYPE_ARM) {
|
||||
// Native CPU is ARM
|
||||
s_platform_flavor = PlatformFlavor::OSXOnArm;
|
||||
BOOST_LOG_TRIVIAL(info) << "Platform flavor: OSXOnArm";
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
BOOST_LOG_TRIVIAL(info) << "Platform: Linux";
|
||||
s_platform = Platform::Linux;
|
||||
|
@ -28,6 +28,12 @@ enum class PlatformFlavor
|
||||
WSL2,
|
||||
// For Platform::BSDUnix
|
||||
OpenBSD,
|
||||
// For Platform::OSX
|
||||
GenericOSX,
|
||||
// For Apple's on Intel X86 CPU
|
||||
OSXOnX86,
|
||||
// For Apple's on Arm CPU
|
||||
OSXOnArm,
|
||||
};
|
||||
|
||||
// To be called on program start-up.
|
||||
|
@ -317,6 +317,8 @@ public:
|
||||
bool has_support() const { return m_config.support_material || m_config.support_material_enforce_layers > 0; }
|
||||
bool has_raft() const { return m_config.raft_layers > 0; }
|
||||
bool has_support_material() const { return this->has_support() || this->has_raft(); }
|
||||
// Checks if the model object is painted using the multi-material painting gizmo.
|
||||
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); };
|
||||
|
||||
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
|
||||
std::vector<unsigned int> object_extruders() const;
|
||||
|
@ -812,7 +812,8 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
layer_ranges_regions.push_back({ range.layer_height_range, range.config });
|
||||
}
|
||||
|
||||
update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, std::max(0.f, xy_size_compensation));
|
||||
const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation));
|
||||
|
||||
std::vector<PrintRegion*> region_set;
|
||||
auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* {
|
||||
@ -1313,7 +1314,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
m_default_region_config,
|
||||
model_object_status.print_instances.front().trafo,
|
||||
num_extruders,
|
||||
float(print_object.config().xy_size_compensation.value),
|
||||
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value),
|
||||
painting_extruders);
|
||||
}
|
||||
for (auto it = it_print_object; it != it_print_object_end; ++it)
|
||||
|
@ -4154,6 +4154,11 @@ CLITransformConfigDef::CLITransformConfigDef()
|
||||
def->label = L("Don't arrange");
|
||||
def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates.");
|
||||
|
||||
def = this->add("ensure_on_bed", coBool);
|
||||
def->label = L("Ensure on bed");
|
||||
def->tooltip = L("Lift the object above the bed when it is partially below. Enabled by default, use --no-ensure-on-bed to disable.");
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("duplicate", coInt);
|
||||
def->label = L("Duplicate");
|
||||
def->tooltip =L("Multiply copies by this factor.");
|
||||
|
@ -509,13 +509,32 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
} else if (
|
||||
opt_key == "perimeters"
|
||||
|| opt_key == "extra_perimeters"
|
||||
|| opt_key == "gap_fill_enabled"
|
||||
|| opt_key == "gap_fill_speed"
|
||||
|| opt_key == "first_layer_extrusion_width"
|
||||
|| opt_key == "perimeter_extrusion_width"
|
||||
|| opt_key == "infill_overlap"
|
||||
|| opt_key == "external_perimeters_first") {
|
||||
steps.emplace_back(posPerimeters);
|
||||
} else if (
|
||||
opt_key == "gap_fill_enabled"
|
||||
|| opt_key == "gap_fill_speed") {
|
||||
// Return true if gap-fill speed has changed from zero value to non-zero or from non-zero value to zero.
|
||||
auto is_gap_fill_changed_state_due_to_speed = [&opt_key, &old_config, &new_config]() -> bool {
|
||||
if (opt_key == "gap_fill_speed") {
|
||||
const auto *old_gap_fill_speed = old_config.option<ConfigOptionFloat>(opt_key);
|
||||
const auto *new_gap_fill_speed = new_config.option<ConfigOptionFloat>(opt_key);
|
||||
assert(old_gap_fill_speed && new_gap_fill_speed);
|
||||
return (old_gap_fill_speed->value > 0.f && new_gap_fill_speed->value == 0.f) ||
|
||||
(old_gap_fill_speed->value == 0.f && new_gap_fill_speed->value > 0.f);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Filtering of unprintable regions in multi-material segmentation depends on if gap-fill is enabled or not.
|
||||
// So step posSlice is invalidated when gap-fill was enabled/disabled by option "gap_fill_enabled" or by
|
||||
// changing "gap_fill_speed" to force recomputation of the multi-material segmentation.
|
||||
if (this->is_mm_painted() && (opt_key == "gap_fill_enabled" || (opt_key == "gap_fill_speed" && is_gap_fill_changed_state_due_to_speed())))
|
||||
steps.emplace_back(posSlice);
|
||||
steps.emplace_back(posPerimeters);
|
||||
} else if (
|
||||
opt_key == "layer_height"
|
||||
|| opt_key == "first_layer_height"
|
||||
|
@ -167,7 +167,8 @@ static std::vector<VolumeSlices> slice_volumes_inner(
|
||||
|
||||
params_base.mode_below = params_base.mode;
|
||||
|
||||
const auto extra_offset = std::max(0.f, float(print_object_config.xy_size_compensation.value));
|
||||
const bool is_mm_painted = std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
|
||||
const auto extra_offset = is_mm_painted ? 0.f : std::max(0.f, float(print_object_config.xy_size_compensation.value));
|
||||
|
||||
for (const ModelVolume *model_volume : model_volumes)
|
||||
if (model_volume_needs_slicing(*model_volume)) {
|
||||
@ -725,6 +726,17 @@ void PrintObject::slice_volumes()
|
||||
// Is any ModelVolume MMU painted?
|
||||
if (const auto& volumes = this->model_object()->volumes;
|
||||
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
|
||||
|
||||
// If XY Size compensation is also enabled, notify the user that XY Size compensation
|
||||
// would not be used because the object is multi-material painted.
|
||||
if (m_config.xy_size_compensation.value != 0.f) {
|
||||
this->active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::CRITICAL,
|
||||
L("An object has enabled XY Size compensation which will not be used because it is also multi-material painted.\nXY Size "
|
||||
"compensation cannot be combined with multi-material painting.") +
|
||||
"\n" + (L("Object name")) + ": " + this->model_object()->name);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - MMU segmentation";
|
||||
apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); });
|
||||
}
|
||||
@ -733,7 +745,7 @@ void PrintObject::slice_volumes()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
||||
{
|
||||
// Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing.
|
||||
const auto xy_compensation_scaled = scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
|
||||
const auto xy_compensation_scaled = this->is_mm_painted() ? scaled<float>(0.f) : scaled<float>(std::min(m_config.xy_size_compensation.value, 0.));
|
||||
const float elephant_foot_compensation_scaled = (m_config.raft_layers == 0) ?
|
||||
// Only enable Elephant foot compensation if printing directly on the print bed.
|
||||
float(scale_(m_config.elefant_foot_compensation.value)) :
|
||||
|
@ -372,7 +372,7 @@ static inline IntersectionLines slice_make_lines(
|
||||
FaceFilter face_filter)
|
||||
{
|
||||
IntersectionLines lines;
|
||||
for (int face_idx = 0; face_idx < mesh_faces.size(); ++ face_idx)
|
||||
for (int face_idx = 0; face_idx < int(mesh_faces.size()); ++ face_idx)
|
||||
if (face_filter(face_idx)) {
|
||||
const Vec3i &indices = mesh_faces[face_idx];
|
||||
stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) };
|
||||
|
@ -277,22 +277,22 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
|
||||
if (itriangle == -1)
|
||||
return;
|
||||
|
||||
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx) -> void {
|
||||
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx, Partition partition) -> void {
|
||||
assert(subtriangle_idx == -1);
|
||||
if (!m_triangles[subtriangle_idx].is_split())
|
||||
touching_subtriangles_out.emplace_back(subtriangle_idx);
|
||||
else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
|
||||
append_touching_subtriangles(subtriangle_idx, vertexi, midpoint, touching_subtriangles_out);
|
||||
append_touching_subtriangles(subtriangle_idx, partition == Partition::First ? vertexi : midpoint, partition == Partition::First ? midpoint : vertexj, touching_subtriangles_out);
|
||||
else
|
||||
append_touching_subtriangles(subtriangle_idx, vertexi, vertexj, touching_subtriangles_out);
|
||||
};
|
||||
|
||||
std::pair<int, int> touching = this->triangle_subtriangles(itriangle, vertexi, vertexj);
|
||||
if (touching.first != -1)
|
||||
process_subtriangle(touching.first);
|
||||
process_subtriangle(touching.first, Partition::First);
|
||||
|
||||
if (touching.second != -1)
|
||||
process_subtriangle(touching.second);
|
||||
process_subtriangle(touching.second, Partition::Second);
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate)
|
||||
@ -437,7 +437,7 @@ int TriangleSelector::neighbor_child(int itriangle, int vertexi, int vertexj, Pa
|
||||
|
||||
std::pair<int, int> TriangleSelector::triangle_subtriangles(int itriangle, int vertexi, int vertexj) const
|
||||
{
|
||||
return itriangle == -1 ? std::make_pair(-1, -1) : this->triangle_subtriangles(m_triangles[itriangle], vertexi, vertexj);
|
||||
return itriangle == -1 ? std::make_pair(-1, -1) : Slic3r::TriangleSelector::triangle_subtriangles(m_triangles[itriangle], vertexi, vertexj);
|
||||
}
|
||||
|
||||
std::pair<int, int> TriangleSelector::triangle_subtriangles(const Triangle &tr, int vertexi, int vertexj)
|
||||
|
@ -100,8 +100,8 @@ extern bool is_ini_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_idx_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_gcode_file(const std::string &path);
|
||||
extern bool is_img_file(const std::string& path);
|
||||
extern bool is_stl_file(const boost::filesystem::directory_entry& path);
|
||||
extern bool is_stl_file(const std::string& path);
|
||||
extern bool is_gallery_file(const boost::filesystem::directory_entry& path, char const* type);
|
||||
extern bool is_gallery_file(const std::string& path, char const* type);
|
||||
extern bool is_shapes_dir(const std::string& dir);
|
||||
|
||||
// File path / name / extension splitting utilities, working with UTF-8,
|
||||
|
@ -766,14 +766,14 @@ bool is_img_file(const std::string &path)
|
||||
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
|
||||
}
|
||||
|
||||
bool is_stl_file(const boost::filesystem::directory_entry& dir_entry)
|
||||
bool is_gallery_file(const boost::filesystem::directory_entry& dir_entry, char const* type)
|
||||
{
|
||||
return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".stl") == 0;
|
||||
return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), type) == 0;
|
||||
}
|
||||
|
||||
bool is_stl_file(const std::string &path)
|
||||
bool is_gallery_file(const std::string &path, char const* type)
|
||||
{
|
||||
return boost::iends_with(path, ".stl");
|
||||
return boost::iends_with(path, type);
|
||||
}
|
||||
|
||||
bool is_shapes_dir(const std::string& dir)
|
||||
|
@ -499,10 +499,10 @@ void Bed3D::render_model() const
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.0);
|
||||
::glPushMatrix();
|
||||
::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z());
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z()));
|
||||
model->render();
|
||||
::glPopMatrix();
|
||||
glsafe(::glPopMatrix());
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
|
||||
GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
BonjourDialog::~BonjourDialog()
|
||||
|
@ -335,3 +335,36 @@ bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& val
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool TextRenderer::SetValue(const wxVariant& value)
|
||||
{
|
||||
m_value = value.GetString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextRenderer::GetValue(wxVariant& value) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextRenderer::Render(wxRect rect, wxDC* dc, int state)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
|
||||
RenderText(m_value, 0, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state);
|
||||
#else
|
||||
RenderText(m_value, 0, rect, dc, state);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxSize TextRenderer::GetSize() const
|
||||
{
|
||||
return GetTextExtent(m_value);
|
||||
}
|
||||
|
||||
|
||||
|
@ -161,4 +161,29 @@ private:
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class TextRenderer : public wxDataViewCustomRenderer
|
||||
{
|
||||
public:
|
||||
TextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT
|
||||
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
|
||||
) : wxDataViewCustomRenderer(wxT("string"), mode, align) {}
|
||||
|
||||
bool SetValue(const wxVariant& value) override;
|
||||
bool GetValue(wxVariant& value) const override;
|
||||
|
||||
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
|
||||
virtual wxSize GetSize() const override;
|
||||
|
||||
bool HasEditorCtrl() const override { return false; }
|
||||
|
||||
private:
|
||||
wxString m_value;
|
||||
};
|
||||
|
||||
|
||||
#endif // slic3r_GUI_ExtraRenderers_hpp_
|
||||
|
@ -2379,7 +2379,11 @@ void GCodeViewer::render_toolpaths() const
|
||||
shader.set_uniform("uniform_color", color4);
|
||||
};
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color]
|
||||
#else
|
||||
auto render_as_points = [zoom, point_size, near_plane_height, set_uniform_color]
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
|
||||
shader.set_uniform("use_fixed_screen_size", 1);
|
||||
@ -2409,7 +2413,12 @@ void GCodeViewer::render_toolpaths() const
|
||||
glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE));
|
||||
};
|
||||
|
||||
auto render_as_lines = [light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_lines = [this, light_intensity, set_uniform_color]
|
||||
#else
|
||||
auto render_as_lines = [light_intensity, set_uniform_color]
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
shader.set_uniform("light_intensity", light_intensity);
|
||||
for (const RenderPath& path : buffer.render_paths) {
|
||||
if (path.ibuffer_id == ibuffer_id) {
|
||||
@ -2422,7 +2431,12 @@ void GCodeViewer::render_toolpaths() const
|
||||
}
|
||||
};
|
||||
|
||||
auto render_as_triangles = [set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_triangles = [this, set_uniform_color]
|
||||
#else
|
||||
auto render_as_triangles = [set_uniform_color]
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
for (const RenderPath& path : buffer.render_paths) {
|
||||
if (path.ibuffer_id == ibuffer_id) {
|
||||
set_uniform_color(path.color, shader);
|
||||
@ -2495,7 +2509,12 @@ void GCodeViewer::render_toolpaths() const
|
||||
}
|
||||
}
|
||||
|
||||
auto render_sequential_range_cap = [set_uniform_color](const SequentialRangeCap& cap) {
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_sequential_range_cap = [this, set_uniform_color]
|
||||
#else
|
||||
auto render_sequential_range_cap = [set_uniform_color]
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const SequentialRangeCap& cap) {
|
||||
GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str());
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "GLShadersManager.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
@ -43,6 +44,16 @@ std::pair<bool, std::string> GLShadersManager::init()
|
||||
// used to render extrusion and travel paths as lines in gcode preview
|
||||
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
|
||||
// used to render objects in 3d editor
|
||||
// For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction.
|
||||
// Because of this, objects had darker colors inside the multi-material gizmo.
|
||||
// Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU.
|
||||
if (platform_flavor() == PlatformFlavor::OSXOnArm)
|
||||
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, "ENABLE_ENVIRONMENT_MAP"sv
|
||||
#endif
|
||||
});
|
||||
else
|
||||
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, { "ENABLE_ENVIRONMENT_MAP"sv }
|
||||
|
@ -428,6 +428,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
||||
/* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
|
||||
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
|
||||
/* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF",
|
||||
/* FT_GALLERY */ "Known files (*.stl, *.obj)|*.stl;*.STL;*.obj;*.OBJ",
|
||||
|
||||
/* FT_INI */ "INI files (*.ini)|*.ini;*.INI",
|
||||
/* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG",
|
||||
@ -662,7 +663,7 @@ void GUI_App::post_init()
|
||||
}
|
||||
|
||||
// show "Did you know" notification
|
||||
if (app_config->get("show_hints") == "1")
|
||||
if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
|
||||
plater_->get_notification_manager()->push_hint_notification();
|
||||
|
||||
// The extra CallAfter() is needed because of Mac, where this is the only way
|
||||
|
@ -59,6 +59,7 @@ enum FileType
|
||||
FT_GCODE,
|
||||
FT_MODEL,
|
||||
FT_PROJECT,
|
||||
FT_GALLERY,
|
||||
|
||||
FT_INI,
|
||||
FT_SVG,
|
||||
|
@ -433,7 +433,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
|
||||
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
|
||||
}
|
||||
|
||||
if (wxGetApp().get_mode() == comExpert) {
|
||||
if (wxGetApp().get_mode() >= comAdvanced) {
|
||||
sub_menu->AppendSeparator();
|
||||
append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "",
|
||||
[type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu);
|
||||
|
@ -2517,7 +2517,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D
|
||||
}
|
||||
|
||||
|
||||
void ObjectList::update_info_items(size_t obj_idx)
|
||||
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/)
|
||||
{
|
||||
const ModelObject* model_object = (*m_objects)[obj_idx];
|
||||
wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx);
|
||||
@ -2565,8 +2565,20 @@ void ObjectList::update_info_items(size_t obj_idx)
|
||||
|
||||
}
|
||||
else if (shows && ! should_show) {
|
||||
if (!selections)
|
||||
Unselect(item);
|
||||
m_objects_model->Delete(item);
|
||||
if (selections) {
|
||||
if (selections->Index(item) != wxNOT_FOUND) {
|
||||
// If info item was deleted from the list,
|
||||
// it's need to be deleted from selection array, if it was there
|
||||
selections->Remove(item);
|
||||
// Select item_obj, if info_item doesn't exist for item anymore, but was selected
|
||||
if (selections->Index(item_obj) == wxNOT_FOUND)
|
||||
selections->Add(item_obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
Select(item_obj);
|
||||
}
|
||||
}
|
||||
@ -3760,7 +3772,7 @@ void ObjectList::update_object_list_by_printer_technology()
|
||||
|
||||
for (auto& object_item : object_items) {
|
||||
// update custom supports info
|
||||
update_info_items(m_objects_model->GetObjectIdByItem(object_item));
|
||||
update_info_items(m_objects_model->GetObjectIdByItem(object_item), &sel);
|
||||
|
||||
// Update Settings Item for object
|
||||
update_settings_item_and_selection(object_item, sel);
|
||||
|
@ -350,7 +350,7 @@ public:
|
||||
void update_and_show_object_settings_item();
|
||||
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
|
||||
void update_object_list_by_printer_technology();
|
||||
void update_info_items(size_t obj_idx);
|
||||
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr);
|
||||
|
||||
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
|
||||
void instances_to_separated_objects(const int obj_idx);
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Format/OBJ.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@ -205,7 +206,11 @@ static void add_lock(wxImage& image)
|
||||
|
||||
static void add_default_image(wxImageList* img_list, bool is_system)
|
||||
{
|
||||
wxBitmap bmp = create_scaled_bitmap("cog", nullptr, IMG_PX_CNT, true);
|
||||
int sz = IMG_PX_CNT;
|
||||
#ifdef __APPLE__
|
||||
sz /= mac_max_scaling_factor();
|
||||
#endif
|
||||
wxBitmap bmp = create_scaled_bitmap("cog", nullptr, sz, true);
|
||||
|
||||
if (is_system) {
|
||||
wxImage image = bmp.ConvertToImage();
|
||||
@ -232,10 +237,11 @@ static std::string get_dir_path(bool sys_dir)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void generate_thumbnail_from_stl(const std::string& filename)
|
||||
static void generate_thumbnail_from_model(const std::string& filename)
|
||||
{
|
||||
if (!boost::algorithm::iends_with(filename, ".stl")) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_stl() [" << filename << "]";
|
||||
if (!boost::algorithm::iends_with(filename, ".stl") &&
|
||||
!boost::algorithm::iends_with(filename, ".obj")) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_model() [" << filename << "]";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -244,7 +250,7 @@ static void generate_thumbnail_from_stl(const std::string& filename)
|
||||
model = Model::read_from_file(filename);
|
||||
}
|
||||
catch (std::exception&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_stl()";
|
||||
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -294,25 +300,28 @@ static void generate_thumbnail_from_stl(const std::string& filename)
|
||||
void GalleryDialog::load_label_icon_list()
|
||||
{
|
||||
// load names from files
|
||||
auto add_files_from_gallery = [](std::vector<Item>& items, bool sys_dir, std::string& dir_path)
|
||||
auto add_files_from_gallery = [](std::vector<Item>& items, bool is_sys_dir, std::string& dir_path)
|
||||
{
|
||||
fs::path dir = get_dir(sys_dir);
|
||||
fs::path dir = get_dir(is_sys_dir);
|
||||
if (!fs::exists(dir))
|
||||
return;
|
||||
|
||||
dir_path = get_dir_path(sys_dir);
|
||||
dir_path = get_dir_path(is_sys_dir);
|
||||
|
||||
std::vector<std::string> sorted_names;
|
||||
for (auto& dir_entry : fs::directory_iterator(dir))
|
||||
if (TriangleMesh mesh; is_stl_file(dir_entry) && mesh.ReadSTLFile(dir_entry.path().string().c_str()))
|
||||
sorted_names.push_back(dir_entry.path().stem().string());
|
||||
for (auto& dir_entry : fs::directory_iterator(dir)) {
|
||||
TriangleMesh mesh;
|
||||
if ((is_gallery_file(dir_entry, ".stl") && mesh.ReadSTLFile(dir_entry.path().string().c_str())) ||
|
||||
(is_gallery_file(dir_entry, ".obj") && load_obj(dir_entry.path().string().c_str(), &mesh) ) )
|
||||
sorted_names.push_back(dir_entry.path().filename().string());
|
||||
}
|
||||
|
||||
// sort the filename case insensitive
|
||||
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
|
||||
{ return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); });
|
||||
|
||||
for (const std::string& name : sorted_names)
|
||||
items.push_back(Item{ name, sys_dir });
|
||||
items.push_back(Item{ name, is_sys_dir });
|
||||
};
|
||||
|
||||
wxBusyCursor busy;
|
||||
@ -330,10 +339,24 @@ void GalleryDialog::load_label_icon_list()
|
||||
std::string ext = ".png";
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
std::string img_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ext;
|
||||
std::string stl_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ".stl";
|
||||
if (!fs::exists(img_name))
|
||||
generate_thumbnail_from_stl(stl_name);
|
||||
fs::path model_path = fs::path((item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name);
|
||||
std::string model_name = model_path.string();
|
||||
model_path.replace_extension("png");
|
||||
std::string img_name = model_path.string();
|
||||
|
||||
#if 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes
|
||||
bool can_generate_thumbnail = true;
|
||||
#else
|
||||
bool can_generate_thumbnail = !item.is_system;
|
||||
#endif //DEBUG
|
||||
if (!fs::exists(img_name)) {
|
||||
if (can_generate_thumbnail)
|
||||
generate_thumbnail_from_model(model_name);
|
||||
else {
|
||||
add_default_image(m_image_list, item.is_system);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wxImage image;
|
||||
if (!image.CanRead(from_u8(img_name)) ||
|
||||
@ -363,15 +386,15 @@ void GalleryDialog::load_label_icon_list()
|
||||
void GalleryDialog::get_input_files(wxArrayString& input_files)
|
||||
{
|
||||
for (const Item& item : m_selected_items)
|
||||
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name + ".stl"));
|
||||
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name));
|
||||
}
|
||||
|
||||
void GalleryDialog::add_custom_shapes(wxEvent& event)
|
||||
{
|
||||
wxArrayString input_files;
|
||||
wxFileDialog dialog(this, _L("Choose one or more files (STL):"),
|
||||
wxFileDialog dialog(this, _L("Choose one or more files (STL, OBJ):"),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), "",
|
||||
file_wildcards(FT_STL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
file_wildcards(FT_GALLERY), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
dialog.GetPaths(input_files);
|
||||
@ -398,8 +421,10 @@ void GalleryDialog::del_custom_shapes(wxEvent& event)
|
||||
};
|
||||
|
||||
for (const Item& item : m_selected_items) {
|
||||
remove_file(item.name + ".stl");
|
||||
remove_file(item.name + ".png");
|
||||
remove_file(item.name);
|
||||
fs::path path = fs::path(item.name);
|
||||
path.replace_extension("png");
|
||||
remove_file(path.string());
|
||||
}
|
||||
|
||||
update();
|
||||
@ -490,26 +515,32 @@ bool GalleryDialog::load_files(const wxArrayString& input_files)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate through the source directory
|
||||
// Iterate through the input files
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
std::string input_file = into_u8(input_files.Item(i));
|
||||
|
||||
if (TriangleMesh mesh; !mesh.ReadSTLFile(input_file.c_str())) {
|
||||
TriangleMesh mesh;
|
||||
if (is_gallery_file(input_file, ".stl") && !mesh.ReadSTLFile(input_file.c_str())) {
|
||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_gallery_file(input_file, ".obj") && !load_obj(input_file.c_str(), &mesh)) {
|
||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "OBJ");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
fs::path current = fs::path(input_file);
|
||||
if (!fs::exists(dest_dir / current.filename()))
|
||||
fs::copy_file(current, dest_dir / current.filename());
|
||||
else {
|
||||
std::string filename = current.stem().string();
|
||||
std::string filename = current.filename().string();
|
||||
|
||||
int file_idx = 0;
|
||||
for (auto& dir_entry : fs::directory_iterator(dest_dir))
|
||||
if (is_stl_file(dir_entry)) {
|
||||
std::string name = dir_entry.path().stem().string();
|
||||
if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
if (filename == name) {
|
||||
if (file_idx == 0)
|
||||
file_idx++;
|
||||
@ -524,7 +555,7 @@ bool GalleryDialog::load_files(const wxArrayString& input_files)
|
||||
file_idx = cur_idx+1;
|
||||
}
|
||||
if (file_idx > 0) {
|
||||
filename += " (" + std::to_string(file_idx) + ").stl";
|
||||
filename += " (" + std::to_string(file_idx) + ")." + (is_gallery_file(input_file, ".stl") ? "stl" : "obj");
|
||||
fs::copy_file(current, dest_dir / filename);
|
||||
}
|
||||
}
|
||||
|
@ -359,10 +359,10 @@ void ObjectClipper::render_cut() const
|
||||
clipper->set_plane(*m_clp);
|
||||
clipper->set_transformation(trafo);
|
||||
|
||||
::glPushMatrix();
|
||||
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
|
||||
clipper->render_cut();
|
||||
::glPopMatrix();
|
||||
glsafe(::glPopMatrix());
|
||||
|
||||
++clipper_id;
|
||||
}
|
||||
@ -472,10 +472,10 @@ void SupportsClipper::render_cut() const
|
||||
m_clipper->set_plane(*ocl->get_clipping_plane());
|
||||
m_clipper->set_transformation(supports_trafo);
|
||||
|
||||
::glPushMatrix();
|
||||
::glColor3f(1.0f, 0.f, 0.37f);
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(1.0f, 0.f, 0.37f));
|
||||
m_clipper->render_cut();
|
||||
::glPopMatrix();
|
||||
glsafe(::glPopMatrix());
|
||||
}
|
||||
|
||||
|
||||
|
@ -434,6 +434,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
e.Skip();
|
||||
temp->GetToolTip()->Enable(true);
|
||||
#endif // __WXGTK__
|
||||
// Remove all leading and trailing spaces from the input
|
||||
std::string trimed_str, str = trimed_str = temp->GetValue().ToStdString();
|
||||
boost::trim(trimed_str);
|
||||
if (trimed_str != str)
|
||||
temp->SetValue(trimed_str);
|
||||
|
||||
TextCtrl* field = dynamic_cast<TextCtrl*>(printhost_field);
|
||||
if (field)
|
||||
field->propagate_value();
|
||||
|
@ -2389,8 +2389,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
//wxMessageDialog msg_dlg(q, _L(
|
||||
MessageDialog msg_dlg(q, _L(
|
||||
"This file contains several objects positioned at multiple heights.\n"
|
||||
"Instead of considering them as multiple objects, should I consider\n"
|
||||
"this file as a single object having multiple parts?") + "\n",
|
||||
"Instead of considering them as multiple objects, should \n"
|
||||
"should the file be loaded as a single object having multiple parts?") + "\n",
|
||||
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES) {
|
||||
model.convert_multipart_object(nozzle_dmrs->values.size());
|
||||
@ -3212,9 +3212,10 @@ void Plater::priv::replace_with_stl()
|
||||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_transformation(old_volume->get_transformation());
|
||||
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
|
||||
assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters);
|
||||
if (old_volume->source.is_converted_from_inches)
|
||||
new_volume->convert_from_imperial_units();
|
||||
if (old_volume->source.is_converted_from_meters)
|
||||
else if (old_volume->source.is_converted_from_meters)
|
||||
new_volume->convert_from_meters();
|
||||
new_volume->supported_facets.assign(old_volume->supported_facets);
|
||||
new_volume->seam_facets.assign(old_volume->seam_facets);
|
||||
@ -3420,13 +3421,11 @@ void Plater::priv::reload_from_disk()
|
||||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_transformation(old_volume->get_transformation());
|
||||
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
|
||||
assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters);
|
||||
if (old_volume->source.is_converted_from_inches)
|
||||
new_volume->convert_from_imperial_units();
|
||||
if (old_volume->source.is_converted_from_meters)
|
||||
else if (old_volume->source.is_converted_from_meters)
|
||||
new_volume->convert_from_meters();
|
||||
new_volume->supported_facets.assign(old_volume->supported_facets);
|
||||
new_volume->seam_facets.assign(old_volume->seam_facets);
|
||||
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
|
||||
std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back());
|
||||
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
|
||||
if (!sinking)
|
||||
@ -5223,7 +5222,7 @@ void Plater::convert_unit(ConversionType conv_type)
|
||||
void Plater::toggle_layers_editing(bool enable)
|
||||
{
|
||||
if (canvas3D()->is_layers_editing_enabled() != enable)
|
||||
wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
|
||||
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
|
||||
}
|
||||
|
||||
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes)
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "MainFrame.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
#include "ExtraRenderers.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
@ -214,14 +215,25 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
|
||||
}
|
||||
|
||||
job_list = new wxDataViewListCtrl(this, wxID_ANY);
|
||||
|
||||
// MSW DarkMode: workaround for the selected item in the list
|
||||
auto append_text_column = [this](const wxString& label, int width, wxAlignment align = wxALIGN_LEFT,
|
||||
int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE) {
|
||||
#ifdef _WIN32
|
||||
job_list->AppendColumn(new wxDataViewColumn(label, new TextRenderer(), job_list->GetColumnCount(), width, align, flags));
|
||||
#else
|
||||
job_list->AppendTextColumn(label, wxDATAVIEW_CELL_INERT, width, align, flags);
|
||||
#endif
|
||||
};
|
||||
|
||||
// Note: Keep these in sync with Column
|
||||
job_list->AppendTextColumn(_L("ID"), wxDATAVIEW_CELL_INERT, widths[0], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||
append_text_column(_L("ID"), widths[0]);
|
||||
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||
job_list->AppendTextColumn(_L("Status"), wxDATAVIEW_CELL_INERT, widths[2], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||
job_list->AppendTextColumn(_L("Host"), wxDATAVIEW_CELL_INERT, widths[3], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||
job_list->AppendTextColumn(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), wxDATAVIEW_CELL_INERT, widths[4], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||
job_list->AppendTextColumn(_L("Filename"), wxDATAVIEW_CELL_INERT, widths[5], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||
job_list->AppendTextColumn(_L("Error Message"), wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
|
||||
append_text_column(_L("Status"),widths[2]);
|
||||
append_text_column(_L("Host"), widths[3]);
|
||||
append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
|
||||
append_text_column(_L("Filename"), widths[5]);
|
||||
append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
|
||||
|
||||
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));
|
||||
|