Merge branch 'master' into fs_removes_SimplifyMesh

This commit is contained in:
Filip Sykala 2021-11-11 16:05:43 +01:00
commit 3c61d2b352
189 changed files with 34880 additions and 2818 deletions

View file

@ -154,7 +154,8 @@ namespace ImGui
const wchar_t DocumentationHoverButton = 0x2601;
const wchar_t ClippyMarker = 0x2602;
const wchar_t InfoMarker = 0x2603;
const wchar_t SliderFloatEditBtnIcon = 0x2604;
// void MyFunction(const char* name, const MyMatrix44& v);
}

View file

@ -71,8 +71,6 @@ void AppConfig::set_defaults()
if (get("drop_project_action").empty())
set("drop_project_action", "1");
if (get("version_check").empty())
set("version_check", "1");
if (get("preset_update").empty())
set("preset_update", "1");
@ -182,6 +180,9 @@ void AppConfig::set_defaults()
if (get("dark_color_mode").empty())
set("dark_color_mode", "0");
if (get("sys_menu_enabled").empty())
set("sys_menu_enabled", "1");
#endif // _WIN32
// Remove legacy window positions/sizes

View file

@ -135,7 +135,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname,
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
double px_w, double px_h)
{
ExPolygons polys; polys.reserve(rings.size());
auto polys = reserve_vector<ExPolygon>(rings.size());
for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points;
@ -147,7 +147,7 @@ ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
polys.emplace_back(poly);
}
// reverse the raster transformations
// TODO: Is a union necessary?
return union_ex(polys);
}
@ -270,11 +270,11 @@ std::vector<ExPolygons> extract_slices_from_sla_archive(
png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
if (!png::decode_png(rb, img)) return;
auto rings = marchsq::execute(img, 128, rstp.win);
uint8_t isoval = 128;
auto rings = marchsq::execute(img, isoval, rstp.win);
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
// Invert the raster transformations indicated in
// the profile metadata
// Invert the raster transformations indicated in the profile metadata
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
slices[i] = std::move(expolys);
@ -310,7 +310,24 @@ ConfigSubstitutions import_sla_archive(
std::string exclude_entries{"thumbnail"};
ArchiveData arch = extract_sla_archive(zipfname, exclude_entries);
DynamicPrintConfig profile_in, profile_use;
ConfigSubstitutions config_substitutions = profile_in.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
ConfigSubstitutions config_substitutions =
profile_in.load(arch.profile,
ForwardCompatibilitySubstitutionRule::Enable);
if (profile_in.empty()) { // missing profile... do guess work
// try to recover the layer height from the config.ini which was
// present in all versions of sl1 files.
if (auto lh_opt = arch.config.find("layerHeight");
lh_opt != arch.config.not_found())
{
auto lh_str = lh_opt->second.data();
try {
double lh = std::stod(lh_str); // TODO replace with std::from_chars
profile_out.set("layer_height", lh);
profile_out.set("initial_layer_height", lh);
} catch(...) {}
}
}
// If the archive contains an empty profile, use the one that was passed as output argument
// then replace it with the readed profile to report that it was empty.

View file

@ -769,27 +769,31 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Find the 'F' word.
const char *fpos = strstr(line_start + 2, " F") + 2;
int new_feedrate = current_feedrate;
// Modify the F word of the current G-code line.
bool modify = false;
// Remove the F word from the current G-code line.
bool remove = false;
assert(fpos != nullptr);
if (line->slowdown) {
modify = true;
new_feedrate = int(floor(60. * line->feedrate + 0.5));
} else {
new_feedrate = atoi(fpos);
if (new_feedrate != current_feedrate) {
// Append the line without the comment.
new_gcode.append(line_start, end - line_start);
current_feedrate = new_feedrate;
} else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) {
new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos);
if (new_feedrate == current_feedrate) {
// No need to change the F value.
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
end = line_end;
} else {
// Remove the feedrate from the G0/G1 line.
modify = true;
}
else
// Remove the feedrate from the G0/G1 line. The G-code line may become empty!
remove = true;
} else if (line->slowdown) {
// The F value will be overwritten.
modify = true;
} else {
// The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified.
// Emit the line without the comment.
new_gcode.append(line_start, end - line_start);
current_feedrate = new_feedrate;
}
if (modify) {
if (new_feedrate != current_feedrate) {
if (modify || remove) {
if (modify) {
// Replace the feedrate.
new_gcode.append(line_start, fpos - line_start);
current_feedrate = new_feedrate;
@ -805,12 +809,16 @@ std::string CoolingBuffer::apply_layer_cooldown(
new_gcode.append(line_start, f - line_start + 1);
}
// Skip the non-whitespaces of the F parameter up the comment or end of line.
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos);
// Append the rest of the line without the comment.
if (fpos < end)
// The G-code line is not empty yet. Emit the rest of it.
new_gcode.append(fpos, end - fpos);
// There should never be an empty G1 statement emited by the filter. Such lines should be removed completely.
assert(new_gcode.size() < 4 || new_gcode.substr(new_gcode.size() - 4) != "G1 \n");
else if (remove && new_gcode == "G1") {
// The G-code line only contained the F word, now it is empty. Remove it completely including the comments.
new_gcode.resize(new_gcode.size() - 2);
end = line_end;
}
}
// Process the rest of the line.
if (end < line_end) {

View file

@ -40,7 +40,8 @@ ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_
break;
}
}
const bool contained_z = -1e10 < obj_min_z && obj_max_z < print_volume_height;
const bool contained_z = -1e10 < obj_min_z && obj_max_z <= print_volume_height;
return (contained_xy && contained_z) ? ModelInstancePVS_Inside : ModelInstancePVS_Partly_Outside;
}
@ -1263,10 +1264,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
instances[instance]->get_mirror()
);
z -= instances[instance]->get_offset()(2);
z -= instances[instance]->get_offset().z();
// Lower part per-instance bounding boxes
std::vector<BoundingBoxf3> lower_bboxes { instances.size() };
// Displacement (in instance coordinates) to be applied to place the upper parts
Vec3d local_displace = Vec3d::Zero();
for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix();
@ -1286,8 +1287,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower->add_volume(*volume);
}
else if (! volume->mesh().empty()) {
else if (! volume->mesh().empty()) {
// Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed.
TriangleMesh mesh(volume->mesh());
@ -1327,13 +1327,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
// Compute the lower part instances' bounding boxes to figure out where to place
// the upper part
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
for (size_t i = 0; i < instances.size(); i++) {
lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true));
}
}
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
}
}
@ -1341,17 +1338,18 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
ModelObjectPtrs res;
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
upper->invalidate_bounding_box();
upper->center_around_origin();
if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
upper->center_around_origin();
upper->translate_instances(-upper->origin_translation);
upper->origin_translation = Vec3d::Zero();
}
// Reset instance transformation except offset and Z-rotation
for (size_t i = 0; i < instances.size(); i++) {
for (size_t i = 0; i < instances.size(); ++i) {
auto &instance = upper->instances[i];
const Vec3d offset = instance->get_offset();
const double rot_z = instance->get_rotation()(2);
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
const Vec3d displace = lower_bboxes[i].size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
const double rot_z = instance->get_rotation().z();
const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), instance->get_rotation()) * local_displace;
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset + displace);
@ -1361,14 +1359,16 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
res.push_back(upper);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
lower->invalidate_bounding_box();
lower->center_around_origin();
if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
lower->center_around_origin();
lower->translate_instances(-lower->origin_translation);
lower->origin_translation = Vec3d::Zero();
}
// Reset instance transformation except offset and Z-rotation
for (auto *instance : lower->instances) {
const Vec3d offset = instance->get_offset();
const double rot_z = instance->get_rotation()(2);
const double rot_z = instance->get_rotation().z();
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset);
instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
@ -1860,18 +1860,17 @@ size_t ModelVolume::split(unsigned int max_extruders)
size_t idx = 0;
size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
std::string name = this->name;
const std::string name = this->name;
unsigned int extruder_counter = 0;
Vec3d offset = this->get_offset();
const Vec3d offset = this->get_offset();
for (TriangleMesh &mesh : meshes) {
if (mesh.empty())
// Repair may have removed unconnected triangles, thus emptying the mesh.
continue;
if (idx == 0)
{
if (idx == 0) {
this->set_mesh(std::move(mesh));
this->calculate_convex_hull();
// Assign a new unique ID, so that a new GLVolume will be generated.
@ -1890,7 +1889,19 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->m_is_splittable = 0;
++ idx;
}
// discard volumes for which the convex hull was not generated or is degenerate
size_t i = 0;
while (i < this->object->volumes.size()) {
const std::shared_ptr<const TriangleMesh> &hull = this->object->volumes[i]->get_convex_hull_shared_ptr();
if (hull == nullptr || hull->its.vertices.empty() || hull->its.indices.empty()) {
this->object->delete_volume(i);
--idx;
--i;
}
++i;
}
return idx;
}

View file

@ -684,7 +684,7 @@ public:
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
std::shared_ptr<const TriangleMesh> get_convex_hull_shared_ptr() const { return m_convex_hull; }
const std::shared_ptr<const TriangleMesh>& get_convex_hull_shared_ptr() const { return m_convex_hull; }
// Get count of errors in the mesh
int get_repaired_errors_count() const;

View file

@ -44,6 +44,8 @@ struct segment_traits<Slic3r::ColoredLine> {
//#define MMU_SEGMENTATION_DEBUG_GRAPH
//#define MMU_SEGMENTATION_DEBUG_REGIONS
//#define MMU_SEGMENTATION_DEBUG_INPUT
//#define MMU_SEGMENTATION_DEBUG_PAINTED_LINES
//#define MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
namespace Slic3r {
@ -147,18 +149,6 @@ struct PaintedLineVisitor
static inline const double append_threshold2 = Slic3r::sqr(append_threshold);
};
static std::vector<ColoredLine> to_colored_lines(const EdgeGrid::Contour &contour, int color)
{
std::vector<ColoredLine> lines;
if (contour.num_segments() > 2) {
lines.reserve(contour.num_segments());
for (auto it = contour.begin(); it != contour.end() - 1; ++it)
lines.push_back({Line(*it, *(it + 1)), color});
lines.push_back({Line(contour.back(), contour.front()), color});
}
return lines;
}
static Polygon colored_points_to_polygon(const std::vector<ColoredLine> &lines)
{
Polygon out;
@ -242,25 +232,18 @@ static std::vector<std::vector<std::pair<size_t, size_t>>> get_all_segments(cons
return all_segments;
}
static std::vector<ColoredLine> colorize_line(const Line & line_to_process,
const size_t start_idx,
const size_t end_idx,
std::vector<PaintedLine> &painted_lines)
static std::vector<PaintedLine> filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector<PaintedLine> &painted_lines)
{
std::vector<PaintedLine> internal_painted;
for (size_t line_idx = start_idx; line_idx <= end_idx; ++line_idx)
internal_painted.emplace_back(painted_lines[line_idx]);
const int filter_eps_value = scale_(0.1f);
std::vector<PaintedLine> filtered_lines;
filtered_lines.emplace_back(internal_painted.front());
for (size_t line_idx = 1; line_idx < internal_painted.size(); ++line_idx) {
filtered_lines.emplace_back(painted_lines[start_idx]);
for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) {
// line_to_process is already all colored. Skip another possible duplicate coloring.
if(filtered_lines.back().projected_line.b == line_to_process.b)
break;
PaintedLine &prev = filtered_lines.back();
PaintedLine &curr = internal_painted[line_idx];
const PaintedLine &curr = painted_lines[line_idx];
double prev_length = prev.projected_line.length();
double curr_dist_start = (curr.projected_line.a - prev.projected_line.a).cast<double>().norm();
@ -278,31 +261,84 @@ static std::vector<ColoredLine> colorize_line(const Line & line_to_
}
} else {
double curr_dist_end = (curr.projected_line.b - prev.projected_line.a).cast<double>().norm();
if (curr_dist_end <= prev_length) {
} else {
if (prev.color == curr.color) {
if (curr_dist_end > prev_length) {
if (prev.color == curr.color)
prev.projected_line.b = curr.projected_line.b;
} else {
curr.projected_line.a = prev.projected_line.b;
filtered_lines.emplace_back(curr);
}
else
filtered_lines.push_back({curr.contour_idx, curr.line_idx, Line{prev.projected_line.b, curr.projected_line.b}, curr.color});
}
}
}
std::vector<ColoredLine> final_lines;
double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast<double>().norm();
if (dist_to_start <= filter_eps_value) {
if (double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast<double>().norm(); dist_to_start <= filter_eps_value)
filtered_lines.front().projected_line.a = line_to_process.a;
final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color});
} else {
final_lines.push_back({Line(line_to_process.a, filtered_lines.front().projected_line.a), 0});
final_lines.push_back({filtered_lines.front().projected_line, filtered_lines.front().color});
if (double dist_to_end = (filtered_lines.back().projected_line.b - line_to_process.b).cast<double>().norm(); dist_to_end <= filter_eps_value)
filtered_lines.back().projected_line.b = line_to_process.b;
return filtered_lines;
}
static std::vector<std::vector<PaintedLine>> post_process_painted_lines(const std::vector<EdgeGrid::Contour> &contours, std::vector<PaintedLine> &&painted_lines)
{
if (painted_lines.empty())
return {};
auto comp = [&contours](const PaintedLine &first, const PaintedLine &second) {
Point first_start_p = contours[first.contour_idx].segment_start(first.line_idx);
return first.contour_idx < second.contour_idx ||
(first.contour_idx == second.contour_idx &&
(first.line_idx < second.line_idx ||
(first.line_idx == second.line_idx &&
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() < (second.projected_line.a - first_start_p).cast<double>().squaredNorm() ||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() == (second.projected_line.a - first_start_p).cast<double>().squaredNorm() &&
(first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm())))));
};
std::sort(painted_lines.begin(), painted_lines.end(), comp);
std::vector<std::vector<PaintedLine>> filtered_painted_lines(contours.size());
size_t prev_painted_line_idx = 0;
for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_lines.size(); ++curr_painted_line_idx) {
size_t next_painted_line_idx = curr_painted_line_idx + 1;
if (next_painted_line_idx >= painted_lines.size() || painted_lines[curr_painted_line_idx].contour_idx != painted_lines[next_painted_line_idx].contour_idx || painted_lines[curr_painted_line_idx].line_idx != painted_lines[next_painted_line_idx].line_idx) {
const PaintedLine &start_line = painted_lines[prev_painted_line_idx];
const Line &line_to_process = contours[start_line.contour_idx].get_segment(start_line.line_idx);
Slic3r::append(filtered_painted_lines[painted_lines[curr_painted_line_idx].contour_idx], filter_painted_lines(line_to_process, prev_painted_line_idx, curr_painted_line_idx, painted_lines));
prev_painted_line_idx = next_painted_line_idx;
}
}
for (size_t line_idx = 1; line_idx < filtered_lines.size(); ++line_idx) {
ColoredLine &prev = final_lines.back();
PaintedLine &curr = filtered_lines[line_idx];
return filtered_painted_lines;
}
#ifndef NDEBUG
static bool are_lines_connected(const std::vector<ColoredLine> &colored_lines)
{
for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx)
if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a)
return false;
return true;
}
#endif
static std::vector<ColoredLine> colorize_line(const Line &line_to_process,
const size_t start_idx,
const size_t end_idx,
const std::vector<PaintedLine> &painted_contour)
{
assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx);
assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; }));
const int filter_eps_value = scale_(0.1f);
std::vector<ColoredLine> final_lines;
const PaintedLine &first_line = painted_contour[start_idx];
if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast<double>().norm(); dist_to_start > filter_eps_value)
final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0});
final_lines.push_back({first_line.projected_line, first_line.color});
for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) {
ColoredLine &prev = final_lines.back();
const PaintedLine &curr = painted_contour[line_idx];
double line_dist = (curr.projected_line.a - prev.line.b).cast<double>().norm();
if (line_dist <= filter_eps_value) {
@ -318,18 +354,16 @@ static std::vector<ColoredLine> colorize_line(const Line & line_to_
}
}
double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast<double>().norm();
if (dist_to_end <= filter_eps_value)
final_lines.back().line.b = line_to_process.b;
else
// If there is non-painted space, then inserts line painted by a default color.
if (double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast<double>().norm(); dist_to_end > filter_eps_value)
final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0});
for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx)
assert(final_lines[line_idx - 1].line.b == final_lines[line_idx].line.a);
// Make sure all the lines are connected.
assert(are_lines_connected(final_lines));
for (size_t line_idx = 2; line_idx < final_lines.size(); ++line_idx) {
const ColoredLine &line_0 = final_lines[line_idx - 2];
ColoredLine & line_1 = final_lines[line_idx - 1];
ColoredLine &line_1 = final_lines[line_idx - 1];
const ColoredLine &line_2 = final_lines[line_idx - 0];
if (line_0.color == line_2.color && line_0.color != line_1.color)
@ -349,52 +383,25 @@ static std::vector<ColoredLine> colorize_line(const Line & line_to_
final_lines = colored_lines_simple;
if (final_lines.size() > 1) {
if (final_lines.size() > 1)
if (final_lines.front().color != final_lines[1].color && final_lines.front().line.length() <= scale_(0.2)) {
final_lines[1].line.a = final_lines.front().line.a;
final_lines.erase(final_lines.begin());
}
}
if (final_lines.size() > 1) {
if (final_lines.size() > 1)
if (final_lines.back().color != final_lines[final_lines.size() - 2].color && final_lines.back().line.length() <= scale_(0.2)) {
final_lines[final_lines.size() - 2].line.b = final_lines.back().line.b;
final_lines.pop_back();
}
}
return final_lines;
}
static std::vector<ColoredLine> colorize_polygon(const EdgeGrid::Contour &contour, const size_t start_idx, const size_t end_idx, std::vector<PaintedLine> &painted_lines)
{
std::vector<ColoredLine> new_lines;
new_lines.reserve(end_idx - start_idx + 1);
for (size_t idx = 0; idx < painted_lines[start_idx].line_idx; ++idx)
new_lines.emplace_back(ColoredLine{contour.get_segment(idx), 0});
for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) {
size_t second_idx = first_idx;
while (second_idx <= end_idx && painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx) ++second_idx;
--second_idx;
assert(painted_lines[first_idx].line_idx == painted_lines[second_idx].line_idx);
std::vector<ColoredLine> lines_c_line = colorize_line(contour.get_segment(painted_lines[first_idx].line_idx), first_idx, second_idx, painted_lines);
new_lines.insert(new_lines.end(), lines_c_line.begin(), lines_c_line.end());
if (second_idx + 1 <= end_idx)
for (size_t idx = painted_lines[second_idx].line_idx + 1; idx < painted_lines[second_idx + 1].line_idx; ++idx)
new_lines.emplace_back(ColoredLine{contour.get_segment(idx), 0});
first_idx = second_idx;
}
for (size_t idx = painted_lines[end_idx].line_idx + 1; idx < contour.num_segments(); ++idx)
new_lines.emplace_back(ColoredLine{contour.get_segment(idx), 0});
static std::vector<ColoredLine> filter_colorized_polygon(std::vector<ColoredLine> &&new_lines) {
for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) {
const ColoredLine &line_0 = new_lines[line_idx - 2];
ColoredLine & line_1 = new_lines[line_idx - 1];
ColoredLine &line_1 = new_lines[line_idx - 1];
const ColoredLine &line_2 = new_lines[line_idx - 0];
if (line_0.color == line_2.color && line_0.color != line_1.color && line_0.color >= 1) {
@ -404,8 +411,8 @@ static std::vector<ColoredLine> colorize_polygon(const EdgeGrid::Contour &contou
for (size_t line_idx = 3; line_idx < new_lines.size(); ++line_idx) {
const ColoredLine &line_0 = new_lines[line_idx - 3];
ColoredLine & line_1 = new_lines[line_idx - 2];
ColoredLine & line_2 = new_lines[line_idx - 1];
ColoredLine &line_1 = new_lines[line_idx - 2];
ColoredLine &line_2 = new_lines[line_idx - 1];
const ColoredLine &line_3 = new_lines[line_idx - 0];
if (line_0.color == line_3.color && (line_0.color != line_1.color || line_0.color != line_2.color) && line_0.color >= 1 && line_3.color >= 1) {
@ -425,79 +432,110 @@ static std::vector<ColoredLine> colorize_polygon(const EdgeGrid::Contour &contou
return total_length;
};
for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) {
int color0 = new_lines[segments[pair_idx - 1].first].color;
int color1 = new_lines[segments[pair_idx - 0].first].color;
if (segments.size() >= 2)
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
assert(curr_idx != next_idx);
double seg0l = segment_length(segments[pair_idx - 1]);
double seg1l = segment_length(segments[pair_idx - 0]);
int color0 = new_lines[segments[curr_idx].first].color;
int color1 = new_lines[segments[next_idx].first].color;
if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) {
for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
new_lines[seg_start_idx].color = color0;
new_lines[segments[pair_idx].second].color = color0;
double seg0l = segment_length(segments[curr_idx]);
double seg1l = segment_length(segments[next_idx]);
if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) {
for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
new_lines[seg_start_idx].color = color0;
new_lines[segments[next_idx].second].color = color0;
}
}
}
segments = get_segments(new_lines);
for (size_t pair_idx = 1; pair_idx < segments.size(); ++pair_idx) {
int color0 = new_lines[segments[pair_idx - 1].first].color;
int color1 = new_lines[segments[pair_idx - 0].first].color;
double seg1l = segment_length(segments[pair_idx - 0]);
if (segments.size() >= 2)
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
assert(curr_idx != next_idx);
if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) {
for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
new_lines[seg_start_idx].color = color0;
new_lines[segments[pair_idx].second].color = color0;
int color0 = new_lines[segments[curr_idx].first].color;
int color1 = new_lines[segments[next_idx].first].color;
double seg1l = segment_length(segments[next_idx]);
if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) {
for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
new_lines[seg_start_idx].color = color0;
new_lines[segments[next_idx].second].color = color0;
}
}
}
for (size_t pair_idx = 2; pair_idx < segments.size(); ++pair_idx) {
int color0 = new_lines[segments[pair_idx - 2].first].color;
int color1 = new_lines[segments[pair_idx - 1].first].color;
int color2 = new_lines[segments[pair_idx - 0].first].color;
segments = get_segments(new_lines);
if (segments.size() >= 3)
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
size_t next_next_idx = next_idx_modulo(next_idx, segments.size());
if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[pair_idx - 1]) <= scale_(0.5)) {
for (size_t seg_start_idx = segments[pair_idx].first; seg_start_idx != segments[pair_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
new_lines[seg_start_idx].color = color0;
new_lines[segments[pair_idx].second].color = color0;
int color0 = new_lines[segments[curr_idx].first].color;
int color1 = new_lines[segments[next_idx].first].color;
int color2 = new_lines[segments[next_next_idx].first].color;
if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[next_idx]) <= scale_(0.5)) {
for (size_t seg_start_idx = segments[next_next_idx].first; seg_start_idx != segments[next_next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
new_lines[seg_start_idx].color = color0;
new_lines[segments[next_next_idx].second].color = color0;
}
}
}
return new_lines;
return std::move(new_lines);
}
static std::vector<std::vector<ColoredLine>> colorize_polygons(const std::vector<EdgeGrid::Contour> &contours, std::vector<PaintedLine> &painted_lines)
{
const size_t start_idx = 0;
const size_t end_idx = painted_lines.size() - 1;
static std::vector<ColoredLine> colorize_contour(const EdgeGrid::Contour &contour, const std::vector<PaintedLine> &painted_contour) {
assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; }));
std::vector<std::vector<ColoredLine>> new_polygons;
new_polygons.reserve(contours.size());
for (size_t idx = 0; idx < painted_lines[start_idx].contour_idx; ++idx)
new_polygons.emplace_back(to_colored_lines(contours[idx], 0));
for (size_t first_idx = start_idx; first_idx <= end_idx; ++first_idx) {
size_t second_idx = first_idx;
while (second_idx <= end_idx && painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx)
++second_idx;
--second_idx;
assert(painted_lines[first_idx].contour_idx == painted_lines[second_idx].contour_idx);
new_polygons.emplace_back(colorize_polygon(contours[painted_lines[first_idx].contour_idx], first_idx, second_idx, painted_lines));
if (second_idx + 1 <= end_idx)
for (size_t idx = painted_lines[second_idx].contour_idx + 1; idx < painted_lines[second_idx + 1].contour_idx; ++idx)
new_polygons.emplace_back(to_colored_lines(contours[idx], 0));
first_idx = second_idx;
std::vector<ColoredLine> colorized_contour;
if (painted_contour.empty()) {
// Appends contour with default color for lines before the first PaintedLine.
colorized_contour.reserve(contour.num_segments());
for (const Line &line : contour.get_segments())
colorized_contour.emplace_back(ColoredLine{line, 0});
return colorized_contour;
}
for (size_t idx = painted_lines[end_idx].contour_idx + 1; idx < contours.size(); ++idx)
new_polygons.emplace_back(to_colored_lines(contours[idx], 0));
colorized_contour.reserve(contour.num_segments() + painted_contour.size());
for (size_t idx = 0; idx < painted_contour.front().line_idx; ++idx)
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
return new_polygons;
size_t prev_painted_line_idx = 0;
for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_contour.size(); ++curr_painted_line_idx) {
size_t next_painted_line_idx = curr_painted_line_idx + 1;
if (next_painted_line_idx >= painted_contour.size() || painted_contour[curr_painted_line_idx].line_idx != painted_contour[next_painted_line_idx].line_idx) {
const std::vector<PaintedLine> &painted_contour_copy = painted_contour;
Slic3r::append(colorized_contour, colorize_line(contour.get_segment(painted_contour[prev_painted_line_idx].line_idx), prev_painted_line_idx, curr_painted_line_idx, painted_contour_copy));
// Appends contour with default color for lines between the current and the next PaintedLine.
if (next_painted_line_idx < painted_contour.size())
for (size_t idx = painted_contour[curr_painted_line_idx].line_idx + 1; idx < painted_contour[next_painted_line_idx].line_idx; ++idx)
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
prev_painted_line_idx = next_painted_line_idx;
}
}
// Appends contour with default color for lines after the last PaintedLine.
for (size_t idx = painted_contour.back().line_idx + 1; idx < contour.num_segments(); ++idx)
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
assert(!colorized_contour.empty());
return filter_colorized_polygon(std::move(colorized_contour));
}
static std::vector<std::vector<ColoredLine>> colorize_contours(const std::vector<EdgeGrid::Contour> &contours, const std::vector<std::vector<PaintedLine>> &painted_contours)
{
assert(contours.size() == painted_contours.size());
std::vector<std::vector<ColoredLine>> colorized_contours(contours.size());
for (const std::vector<PaintedLine> &painted_contour : painted_contours) {
size_t contour_idx = &painted_contour - &painted_contours.front();
colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]);
}
return colorized_contours;
}
using boost::polygon::voronoi_diagram;
@ -1075,7 +1113,7 @@ static inline Polygon to_polygon(const std::vector<Linef> &lines)
// It iterates through all nodes on the border between two different colors, and from this point,
// start selection always left most edges for every node to construct CCW polygons.
// Assumes that graph is planar (without self-intersection edges)
static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MMU_Graph &graph)
static std::vector<ExPolygons> extract_colored_segments(const MMU_Graph &graph, const size_t num_extruders)
{
std::vector<bool> used_arcs(graph.arcs.size(), false);
// When there is no next arc, then is returned original_arc or edge with is marked as used
@ -1115,7 +1153,7 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
return std::all_of(node.arc_idxs.cbegin(), node.arc_idxs.cend(), [&used_arcs](const size_t &arc_idx) -> bool { return used_arcs[arc_idx]; });
};
std::vector<std::pair<Polygon, size_t>> polygons_segments;
std::vector<ExPolygons> expolygons_segments(num_extruders + 1);
for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) {
const MMU_Graph::Node &node = graph.nodes[node_idx];
@ -1145,12 +1183,11 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
p_arc = &next;
} while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx]));
Polygon poly = to_polygon(face_lines);
if (poly.is_counter_clockwise() && poly.is_valid())
polygons_segments.emplace_back(poly, arc.color);
if (Polygon poly = to_polygon(face_lines); poly.is_counter_clockwise() && poly.is_valid())
expolygons_segments[arc.color].emplace_back(std::move(poly));
}
}
return polygons_segments;
return expolygons_segments;
}
// Used in remove_multiple_edges_in_vertices()
@ -1231,21 +1268,20 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
}
}
static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
std::vector<std::vector<std::pair<ExPolygon, size_t>>> &segmented_regions,
const float cut_width,
const std::function<void()> &throw_on_cancel_callback)
static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
std::vector<std::vector<ExPolygons>> &segmented_regions,
const float cut_width,
const std::function<void()> &throw_on_cancel_callback)
{
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &throw_on_cancel_callback](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
std::vector<std::pair<ExPolygon, size_t>> segmented_regions_cuts;
for (const std::pair<ExPolygon, size_t> &colored_expoly : segmented_regions[layer_idx]) {
ExPolygons cut_colored_expoly = diff_ex(colored_expoly.first, offset_ex(input_expolygons[layer_idx], cut_width));
for (ExPolygon &expoly : cut_colored_expoly)
segmented_regions_cuts.emplace_back(std::move(expoly), colored_expoly.second);
}
const size_t num_extruders_plus_one = segmented_regions[layer_idx].size();
std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id
for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx)
if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty())
segmented_regions_cuts[extruder_idx] = diff_ex(ex_polygons, offset_ex(input_expolygons[layer_idx], cut_width));
segmented_regions[layer_idx] = std::move(segmented_regions_cuts);
}
}); // end of parallel_for
@ -1285,7 +1321,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
// Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
std::vector<float> zs = zs_from_layers(print_object.layers());
std::vector<float> zs = zs_from_layers(layers);
Transform3d object_trafo = print_object.trafo_centered();
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
@ -1494,31 +1530,42 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
return triangles_by_color_merged;
}
static std::vector<std::vector<std::pair<ExPolygon, size_t>>> merge_segmented_layers(
const std::vector<std::vector<std::pair<ExPolygon, size_t>>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const std::function<void()> &throw_on_cancel_callback)
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(
const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_extruders,
const std::function<void()> &throw_on_cancel_callback)
{
std::vector<std::vector<std::pair<ExPolygon, size_t>>> segmented_regions_merged(segmented_regions.size());
const size_t num_layers = segmented_regions.size();
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_extruders));
assert(num_extruders + 1 == top_and_bottom_layers.size());
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
for (const std::pair<ExPolygon, size_t> &colored_expoly : segmented_regions[layer_idx]) {
assert(segmented_regions[layer_idx].size() == num_extruders + 1);
// Zero is skipped because it is the default color of the volume
for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) {
throw_on_cancel_callback();
// Zero is the default color of the volume.
if(colored_expoly.second == 0)
continue;
ExPolygons cut_colored_expoly = {colored_expoly.first};
for (const std::vector<ExPolygons> &top_and_bottom_layer : top_and_bottom_layers)
cut_colored_expoly = diff_ex(cut_colored_expoly, top_and_bottom_layer[layer_idx]);
for (ExPolygon &ex_poly : cut_colored_expoly)
segmented_regions_merged[layer_idx].emplace_back(std::move(ex_poly), colored_expoly.second - 1);
}
if (!segmented_regions[layer_idx][extruder_id].empty()) {
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers)
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
for (size_t color_idx = 1; color_idx < top_and_bottom_layers.size(); ++color_idx)
for (ExPolygon &expoly : top_and_bottom_layers[color_idx][layer_idx])
segmented_regions_merged[layer_idx].emplace_back(std::move(expoly), color_idx - 1);
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
}
if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) {
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
if (!was_top_and_bottom_empty)
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
}
}
}
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - end";
@ -1527,7 +1574,7 @@ static std::vector<std::vector<std::pair<ExPolygon, size_t>>> merge_segmented_la
}
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
static void export_regions_to_svg(const std::string &path, const std::vector<std::pair<ExPolygon, size_t>> &regions, const ExPolygons &lslices)
static void export_regions_to_svg(const std::string &path, const std::vector<ExPolygons> &regions, const ExPolygons &lslices)
{
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"};
coordf_t stroke_width = scale_(0.05);
@ -1536,12 +1583,12 @@ static void export_regions_to_svg(const std::string &path, const std::vector<std
::Slic3r::SVG svg(path.c_str(), bbox);
svg.draw_outline(lslices, "green", "lime", stroke_width);
for (const std::pair<ExPolygon, size_t> &region : regions) {
int region_color = int(region.second);
if (region_color >= 0 && region_color < int(colors.size()))
svg.draw(region.first, colors[region_color]);
for (const ExPolygons &by_extruder : regions) {
size_t extrude_idx = &by_extruder - &regions.front();
if (extrude_idx >= 0 && extrude_idx < int(colors.size()))
svg.draw(by_extruder, colors[extrude_idx], stroke_width);
else
svg.draw(region.first, "black");
svg.draw(by_extruder, "black", stroke_width);
}
}
#endif // MMU_SEGMENTATION_DEBUG_REGIONS
@ -1582,6 +1629,39 @@ void export_processed_input_expolygons_to_svg(const std::string &path, const Lay
}
#endif // MMU_SEGMENTATION_DEBUG_INPUT
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
static void export_painted_lines_to_svg(const std::string &path, const std::vector<std::vector<PaintedLine>> &all_painted_lines, const ExPolygons &lslices)
{
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"};
coordf_t stroke_width = scale_(0.05);
BoundingBox bbox = get_extents(lslices);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(path.c_str(), bbox);
for (const Line &line : to_lines(lslices))
svg.draw(line, "green", stroke_width);
for (const std::vector<PaintedLine> &painted_lines : all_painted_lines)
for (const PaintedLine &painted_line : painted_lines)
svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width);
}
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
static void export_colorized_polygons_to_svg(const std::string &path, const std::vector<std::vector<ColoredLine>> &colorized_polygons, const ExPolygons &lslices)
{
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
coordf_t stroke_width = scale_(0.05);
BoundingBox bbox = get_extents(lslices);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(path.c_str(), bbox);
for (const std::vector<ColoredLine> &colorized_polygon : colorized_polygons)
for (const ColoredLine &colorized_line : colorized_polygon)
svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width);
}
#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
// Check if all ColoredLine representing a single layer uses the same color.
static bool has_layer_only_one_color(const std::vector<std::vector<ColoredLine>> &colored_polygons)
{
@ -1596,20 +1676,23 @@ static bool has_layer_only_one_color(const std::vector<std::vector<ColoredLine>>
return true;
}
std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback)
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback)
{
std::vector<std::vector<std::pair<ExPolygon, size_t>>> segmented_regions(print_object.layers().size());
std::vector<std::vector<PaintedLine>> painted_lines(print_object.layers().size());
std::array<std::mutex, 64> painted_lines_mutex;
std::vector<EdgeGrid::Grid> edge_grids(print_object.layers().size());
const ConstLayerPtrsAdaptor layers = print_object.layers();
std::vector<ExPolygons> input_expolygons(layers.size());
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size();
const size_t num_layers = print_object.layers().size();
std::vector<std::vector<ExPolygons>> segmented_regions(num_layers);
segmented_regions.assign(num_layers, std::vector<ExPolygons>(num_extruders + 1));
std::vector<std::vector<PaintedLine>> painted_lines(num_layers);
std::array<std::mutex, 64> painted_lines_mutex;
std::vector<EdgeGrid::Grid> edge_grids(num_layers);
const ConstLayerPtrsAdaptor layers = print_object.layers();
std::vector<ExPolygons> input_expolygons(num_layers);
throw_on_cancel_callback();
// Merge all regions and remove small holes
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, layers.size()), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
ExPolygons ex_polygons;
@ -1640,7 +1723,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end";
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
throw_on_cancel_callback();
BoundingBox bbox(get_extents(layers[layer_idx]->regions()));
bbox.merge(get_extents(input_expolygons[layer_idx]));
@ -1652,8 +1735,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin";
for (const ModelVolume *mv : print_object.model_object()->volumes) {
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1;
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_extruders), [&mv, &print_object, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) {
throw_on_cancel_callback();
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
@ -1661,7 +1743,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
continue;
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
tbb::parallel_for(tbb::blocked_range<size_t>(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range<size_t> &range) {
for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest();
@ -1677,15 +1759,15 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
// Find lowest slice not below the triangle.
auto first_layer = std::upper_bound(print_object.layers().begin(), print_object.layers().end(), float(min_z - EPSILON),
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z; });
auto last_layer = std::upper_bound(print_object.layers().begin(), print_object.layers().end(), float(max_z + EPSILON),
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z + EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z; });
--last_layer;
for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) {
const Layer *layer = *layer_it;
size_t layer_idx = layer_it - print_object.layers().begin();
size_t layer_idx = layer_it - layers.begin();
if (input_expolygons[layer_idx].empty() || facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z())
continue;
@ -1728,31 +1810,40 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
<< std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector<PaintedLine> &pl) { return !pl.empty(); });
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, print_object.layers().size()), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
auto comp = [&edge_grids, layer_idx](const PaintedLine &first, const PaintedLine &second) {
Point first_start_p = edge_grids[layer_idx].contours()[first.contour_idx].segment_start(first.line_idx);
return first.contour_idx < second.contour_idx ||
(first.contour_idx == second.contour_idx &&
(first.line_idx < second.line_idx ||
(first.line_idx == second.line_idx &&
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() < (second.projected_line.a - first_start_p).cast<double>().squaredNorm() ||
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() == (second.projected_line.a - first_start_p).cast<double>().squaredNorm() &&
(first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm())))));
};
if (!painted_lines[layer_idx].empty()) {
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
{
static int iRun = 0;
export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun++), {painted_lines[layer_idx]}, input_expolygons[layer_idx]);
}
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
std::sort(painted_lines[layer_idx].begin(), painted_lines[layer_idx].end(), comp);
std::vector<PaintedLine> &painted_lines_single = painted_lines[layer_idx];
std::vector<std::vector<PaintedLine>> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx]));
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
{
static int iRun = 0;
export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun++), post_processed_painted_lines, input_expolygons[layer_idx]);
}
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
std::vector<std::vector<ColoredLine>> color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines);
#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
{
static int iRun = 0;
export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun++), color_poly, input_expolygons[layer_idx]);
}
#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
if (!painted_lines_single.empty()) {
std::vector<std::vector<ColoredLine>> color_poly = colorize_polygons(edge_grids[layer_idx].contours(), painted_lines_single);
assert(!color_poly.empty());
assert(!color_poly.front().empty());
if (has_layer_only_one_color(color_poly)) {
// If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer.
for (const ExPolygon &ex_polygon : input_expolygons[layer_idx])
segmented_regions[layer_idx].emplace_back(ex_polygon, size_t(color_poly.front().front().color));
segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
} else {
MMU_Graph graph = build_graph(layer_idx, color_poly);
remove_multiple_edges_in_vertices(graph, color_poly);
@ -1765,9 +1856,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
}
#endif // MMU_SEGMENTATION_DEBUG_GRAPH
std::vector<std::pair<Polygon, size_t>> segmentation = extract_colored_segments(graph);
for (std::pair<Polygon, size_t> &region : segmentation)
segmented_regions[layer_idx].emplace_back(std::move(region));
segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders);
}
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
@ -1787,11 +1876,11 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
throw_on_cancel_callback();
}
// return segmented_regions;
std::vector<std::vector<ExPolygons>> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback);
// The first index is extruder number (includes default extruder), and the second one is layer number
std::vector<std::vector<ExPolygons>> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback);
throw_on_cancel_callback();
std::vector<std::vector<std::pair<ExPolygon, size_t>>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), throw_on_cancel_callback);
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback);
throw_on_cancel_callback();
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS

View file

@ -11,7 +11,7 @@ class PrintObject;
class ExPolygon;
// Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
} // namespace Slic3r

View file

@ -534,6 +534,7 @@ static std::vector<std::string> s_Preset_sla_print_options {
};
static std::vector<std::string> s_Preset_sla_material_options {
"material_colour",
"material_type",
"initial_layer_height",
"bottle_cost",

View file

@ -556,13 +556,15 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
auto printer_technology = printers.get_selected_preset().printer_technology();
if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) {
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament);
if (auto it = filaments.find_preset_internal(preferred_preset_name); it != filaments.end() && it->is_visible) {
if (auto it = filaments.find_preset_internal(preferred_preset_name);
it != filaments.end() && it->is_visible && it->is_compatible) {
filaments.select_preset_by_name_strict(preferred_preset_name);
this->filament_presets.front() = filaments.get_selected_preset_name();
}
} else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) {
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material);
if (auto it = sla_materials.find_preset_internal(preferred_preset_name); it != sla_materials.end() && it->is_visible)
if (auto it = sla_materials.find_preset_internal(preferred_preset_name);
it != sla_materials.end() && it->is_visible && it->is_compatible)
sla_materials.select_preset_by_name_strict(preferred_preset_name);
}
}

View file

@ -3163,6 +3163,13 @@ void PrintConfigDef::init_sla_params()
// SLA Material settings.
def = this->add("material_colour", coString);
def->label = L("Color");
def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
def->gui_type = ConfigOptionDef::GUIType::color;
def->set_default_value(new ConfigOptionString("#29B2B2"));
def = this->add("material_type", coString);
def->label = L("SLA material type");
def->tooltip = L("SLA material type");

View file

@ -538,7 +538,7 @@ template<typename ThrowOnCancel>
static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{
// Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<std::pair<ExPolygon, size_t>>> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
std::vector<std::vector<ExPolygons>> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
@ -568,9 +568,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
bool layer_split = false;
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
ByExtruder &region = by_extruder[extruder_id];
for (const std::pair<ExPolygon, size_t> &colored_polygon : segmentation[layer_id])
if (colored_polygon.second == extruder_id)
region.expolygons.emplace_back(std::move(colored_polygon.first));
append(region.expolygons, std::move(segmentation[layer_id][extruder_id]));
if (! region.expolygons.empty()) {
region.bbox = get_extents(region.expolygons);
layer_split = true;
@ -632,6 +630,13 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
if (mine.empty())
break;
}
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
// This could, on some models, produce bulges with the model's base color (#7109).
if (! mine.empty())
mine = opening(union_ex(mine), float(scale_(5 * EPSILON)), float(scale_(5 * EPSILON)));
if (! mine.empty()) {
ByRegion &dst = by_region[layerm.region().print_object_region_id()];
if (dst.expolygons.empty()) {

View file

@ -1,3 +1,4 @@
#include <numeric>
#include "SlicesToTriangleMesh.hpp"
@ -22,11 +23,16 @@ inline indexed_triangle_set wall_strip(const Polygon &poly,
ret.vertices.reserve(ret.vertices.size() + 2 *offs);
// The expression unscaled(p).cast<float>().eval() is important here
// as it ensures identical conversion of 2D scaled coordinates to float 3D
// to that used by the tesselation. This way, the duplicated vertices in the
// output mesh can be found with the == operator of the points.
// its_merge_vertices will then reliably remove the duplicates.
for (const Point &p : poly.points)
ret.vertices.emplace_back(to_3d(unscaled<float>(p), float(lower_z_mm)));
ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(lower_z_mm)));
for (const Point &p : poly.points)
ret.vertices.emplace_back(to_3d(unscaled<float>(p), float(upper_z_mm)));
ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(upper_z_mm)));
for (size_t i = startidx + 1; i < startidx + offs; ++i) {
ret.indices.emplace_back(i - 1, i, i + offs - 1);
@ -84,12 +90,14 @@ indexed_triangle_set slices_to_mesh(
const ExPolygons &upper = slices[i + 1];
const ExPolygons &lower = slices[i];
ExPolygons dff1 = diff_ex(lower, upper);
ExPolygons dff2 = diff_ex(upper, lower);
its_merge(layers[i], triangulate_expolygons_3d(dff1, grid[i], NORMALS_UP));
its_merge(layers[i], triangulate_expolygons_3d(dff2, grid[i], NORMALS_DOWN));
// Small 0 area artefacts can be created by diff_ex, and the
// tesselation also can create 0 area triangles. These will be removed
// by its_remove_degenerate_faces.
ExPolygons free_top = diff_ex(lower, upper);
ExPolygons overhang = diff_ex(upper, lower);
its_merge(layers[i], triangulate_expolygons_3d(free_top, grid[i], NORMALS_UP));
its_merge(layers[i], triangulate_expolygons_3d(overhang, grid[i], NORMALS_DOWN));
its_merge(layers[i], straight_walls(upper, grid[i], grid[i + 1]));
});
auto merge_fn = []( const indexed_triangle_set &a, const indexed_triangle_set &b ) {
@ -99,37 +107,30 @@ indexed_triangle_set slices_to_mesh(
auto ret = execution::reduce(ex_tbb, layers.begin(), layers.end(),
indexed_triangle_set{}, merge_fn);
// sla::Contour3D ret = tbb::parallel_reduce(
// tbb::blocked_range(layers.begin(), layers.end()),
// sla::Contour3D{},
// [](const tbb::blocked_range<Layers::iterator>& r, sla::Contour3D
// init) {
// for(auto it = r.begin(); it != r.end(); ++it )
// init.merge(*it); return init;
// },
// []( const sla::Contour3D &a, const sla::Contour3D &b ) {
// sla::Contour3D res{a}; res.merge(b); return res;
// });
its_merge(ret, triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN));
its_merge(ret, straight_walls(slices.front(), zmin, grid.front()));
its_merge(ret, triangulate_expolygons_3d(slices.back(), grid.back(), NORMALS_UP));
// FIXME: these repairs do not fix the mesh entirely. There will be cracks
// in the output. It is very hard to do the meshing in a way that does not
// leave errors.
its_merge_vertices(ret);
its_remove_degenerate_faces(ret);
its_compactify_vertices(ret);
return ret;
}
void slices_to_mesh(indexed_triangle_set & mesh,
const std::vector<ExPolygons> &slices,
double zmin,
double lh,
double ilh)
const std::vector<ExPolygons> &slices,
double zmin,
double lh,
double ilh)
{
std::vector<indexed_triangle_set> wall_meshes(slices.size());
std::vector<float> grid(slices.size(), zmin + ilh);
for (size_t i = 1; i < grid.size(); ++i)
grid[i] = grid[i - 1] + lh;
for (size_t i = 1; i < grid.size(); ++i) grid[i] = grid[i - 1] + lh;
indexed_triangle_set cntr = slices_to_mesh(slices, zmin, grid);
its_merge(mesh, cntr);
}

View file

@ -7,10 +7,10 @@
namespace Slic3r {
void slices_to_mesh(indexed_triangle_set & mesh,
const std::vector<ExPolygons> &slices,
double zmin,
double lh,
double ilh);
const std::vector<ExPolygons> &slices,
double zmin,
double lh,
double ilh);
inline indexed_triangle_set slices_to_mesh(
const std::vector<ExPolygons> &slices, double zmin, double lh, double ilh)

View file

@ -74,16 +74,25 @@
//====================
// 2.4.0.alpha4 techs
// 2.4.0.beta1 techs
//====================
#define ENABLE_2_4_0_ALPHA4 1
#define ENABLE_2_4_0_BETA1 1
// Enable rendering modifiers and similar objects always as transparent
#define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_ALPHA4)
#define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_BETA1)
// Enable the fix for the detection of the out of bed state for sinking objects
// and detection of out of bed using the bed perimeter
#define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_ALPHA4)
#define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_BETA1)
//====================
// 2.4.0.beta2 techs
//====================
#define ENABLE_2_4_0_BETA2 1
// Enable modified ImGuiWrapper::slider_float() to create a compound widget where
// an additional button can be used to set the keyboard focus into the slider
// to allow the user to type in the desired value
#define ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT (1 && ENABLE_2_4_0_BETA2)
#endif // _prusaslicer_technologies_h_

View file

@ -705,22 +705,16 @@ void its_flip_triangles(indexed_triangle_set &its)
int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit)
{
int last = 0;
for (int i = 0; i < int(its.indices.size()); ++ i) {
const stl_triangle_vertex_indices &face = its.indices[i];
if (face(0) != face(1) && face(0) != face(2) && face(1) != face(2)) {
if (last < i)
its.indices[last] = its.indices[i];
++ last;
}
}
int removed = int(its.indices.size()) - last;
if (removed) {
its.indices.erase(its.indices.begin() + last, its.indices.end());
// Optionally shrink the vertices.
if (shrink_to_fit)
its.indices.shrink_to_fit();
}
auto it = std::remove_if(its.indices.begin(), its.indices.end(), [](auto &face) {
return face(0) == face(1) || face(0) == face(2) || face(1) == face(2);
});
int removed = std::distance(it, its.indices.end());
its.indices.erase(it, its.indices.end());
if (removed && shrink_to_fit)
its.indices.shrink_to_fit();
return removed;
}
@ -1196,11 +1190,11 @@ std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its)
}
// Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices).
bool its_number_of_patches(const indexed_triangle_set &its)
size_t its_number_of_patches(const indexed_triangle_set &its)
{
return its_number_of_patches<>(its);
}
bool its_number_of_patches(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors)
size_t its_number_of_patches(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors)
{
return its_number_of_patches<>(ItsNeighborsWrapper{ its, face_neighbors });
}

View file

@ -219,8 +219,8 @@ std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors);
// Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices).
bool its_number_of_patches(const indexed_triangle_set &its);
bool its_number_of_patches(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors);
size_t its_number_of_patches(const indexed_triangle_set &its);
size_t its_number_of_patches(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors);
// Same as its_number_of_patches(its) > 1, but faster.
bool its_is_splittable(const indexed_triangle_set &its);
bool its_is_splittable(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors);

View file

@ -165,6 +165,11 @@ typedef struct NSVGimage
// Parses SVG file from a file, returns SVG image as paths.
NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi);
// Parses SVG file from a file, returns SVG image as paths.
// And makes replases befor parsing
// replace_map containes old_value->new_value
NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map<std::string, std::string>& replace_map);
// Parses SVG file from a null terminated string, returns SVG image as paths.
// Important note: changes the string.
NSVGimage* nsvgParse(char* input, const char* units, float dpi);
@ -189,6 +194,8 @@ void nsvgDelete(NSVGimage* image);
#include <stdlib.h>
#include <math.h>
#include <boost/algorithm/string/replace.hpp>
#define NSVG_PI (3.14159265358979323846264338327f)
#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs.
@ -2901,6 +2908,12 @@ NSVGimage* nsvgParse(char* input, const char* units, float dpi)
NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
{
return nsvgParseFromFileWithReplace(filename, units, dpi, { {} });
}
NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map<std::string, std::string>& replaces)
{
std::string str;
FILE* fp = NULL;
size_t size;
char* data = NULL;
@ -2916,9 +2929,16 @@ NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
if (fread(data, 1, size, fp) != size) goto error;
data[size] = '\0'; // Must be null terminated.
fclose(fp);
image = nsvgParse(data, units, dpi);
free(data);
if(replaces.empty())
image = nsvgParse(data, units, dpi);
else {
str.assign(data);
for (auto val : replaces)
boost::replace_all(str, val.first, val.second);
image = nsvgParse(str.data(), units, dpi);
}
free(data);
return image;
error:

View file

@ -22,6 +22,7 @@
#include "../GUI/GUI_App.hpp"
#include "../GUI/I18N.hpp"
#include "../GUI/MainFrame.hpp"
#include "../GUI/MsgDialog.hpp"
#include <wx/richmsgdlg.h>
@ -591,7 +592,7 @@ bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot:
SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
return true;
} catch (std::exception &err) {
wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
RichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
_L("PrusaSlicer error"),
wxYES_NO);

View file

@ -1131,29 +1131,41 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
if (config == nullptr)
return;
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("extruder_colour"));
if (extruders_opt == nullptr)
return;
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("filament_colour"));
if (filamemts_opt == nullptr)
return;
unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
if (colors_count == 0)
return;
std::vector<Color> colors(colors_count);
unsigned char rgb[3];
for (unsigned int i = 0; i < colors_count; ++i) {
const std::string& txt_color = config->opt_string("extruder_colour", i);
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
colors[i].set(txt_color, rgb);
else {
const std::string& txt_color = config->opt_string("filament_colour", i);
std::vector<Color> colors;
if (static_cast<PrinterTechnology>(config->opt_int("printer_technology")) == ptSLA)
{
const std::string& txt_color = config->opt_string("material_colour");
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) {
colors.resize(1);
colors[0].set(txt_color, rgb);
}
}
else
{
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("extruder_colour"));
if (extruders_opt == nullptr)
return;
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("filament_colour"));
if (filamemts_opt == nullptr)
return;
unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
if (colors_count == 0)
return;
colors.resize(colors_count);
for (unsigned int i = 0; i < colors_count; ++i) {
const std::string& txt_color = config->opt_string("extruder_colour", i);
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
colors[i].set(txt_color, rgb);
else {
const std::string& txt_color = config->opt_string("filament_colour", i);
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
colors[i].set(txt_color, rgb);
}
}
}

View file

@ -261,51 +261,28 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width,
}
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,
const bool grayscale/* = false*/, const bool dark_mode/* = false*/)
const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::string& new_color /*= ""*/)
{
std::string bitmap_key = bitmap_name + ( target_height !=0 ?
"-h" + std::to_string(target_height) :
"-w" + std::to_string(target_width))
+ (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "")
+ (grayscale ? "-gs" : "");
+ (dark_mode ? "-dm" : "")
+ (grayscale ? "-gs" : "")
+ new_color;
/* For the Dark mode of any platform, we should draw icons in respect to OS background
* Note: All standard(regular) icons are collected in "icons" folder,
* SVG-icons, which have "Dark mode" variant, are collected in "icons/white" folder
*/
std::string folder;
auto it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
// map of color replaces
std::map<std::string, std::string> replaces;
if (dark_mode)
{
#ifdef __WXMSW__
folder = "white\\";
#else
folder = "white/";
#endif
auto it = m_map.find(folder + bitmap_key);
if (it != m_map.end())
return it->second;
// It's expensive to check if the bitmap exists every time, but otherwise:
// For the case, when application was started in Light mode and then switched to the Dark,
// we will never get a white bitmaps, if check m_map.find(bitmap_key)
// before boost::filesystem::exists(var(folder + bitmap_name + ".svg"))
if (!boost::filesystem::exists(var(folder + bitmap_name + ".svg"))) {
folder.clear();
it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
}
replaces["#808080"] = "#FFFFFF";
if (!new_color.empty())
replaces["#ED6B21"] = new_color;
bitmap_key = folder + bitmap_key;
}
else
{
auto it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
}
NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f);
NSVGimage *image = ::nsvgParseFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f, replaces);
if (image == nullptr)
return nullptr;

View file

@ -33,7 +33,7 @@ public:
// 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 width = 0, unsigned height = 0, const bool grayscale = false);
// 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 width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false);
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = "");
wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false);
wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); }

View file

@ -53,7 +53,7 @@ struct LifetimeGuard
BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
: wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
, list(new wxListView(this, wxID_ANY))
, list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER))
, replies(new ReplySet)
, label(new wxStaticText(this, wxID_ANY, ""))
, timer(new wxTimer())

View file

@ -27,6 +27,10 @@
#include <wx/wupdlock.h>
#include <wx/debug.h>
#ifdef _MSW_DARK_MODE
#include <wx/msw/dark_mode.h>
#endif // _MSW_DARK_MODE
#include "libslic3r/Platform.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Config.hpp"
@ -551,7 +555,7 @@ PagePrinters::PagePrinters(ConfigWizard *parent,
wizard_p()->on_printer_pick(this, evt);
});
append(new wxStaticLine(this));
append(new StaticLine(this));
append(picker);
printer_pickers.push_back(picker);
@ -1202,7 +1206,7 @@ PageUpdate::PageUpdate(ConfigWizard *parent)
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates"));
box_slic3r->SetValue(app_config->get("version_check") == "1");
box_slic3r->SetValue(app_config->get("notify_release") != "none");
append(box_slic3r);
append_text(wxString::Format(_L(
"If enabled, %s checks for new application versions online. When a new version becomes available, "
@ -2693,7 +2697,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
app_config->set_vendors(appconfig_new);
app_config->set("version_check", page_update->version_check ? "1" : "0");
app_config->set("notify_release", page_update->version_check ? "all" : "none");
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
@ -2796,7 +2800,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
auto *vsizer = new wxBoxSizer(wxVERTICAL);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *hline = new wxStaticLine(this);
auto* hline = new StaticLine(this);
p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
// Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
@ -2872,7 +2876,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->index->go_to(size_t{0});
vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
vsizer->Add(hline, 0, wxEXPAND);
vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING);
vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
SetSizer(vsizer);

View file

@ -1320,7 +1320,7 @@ void ColourPicker::BUILD()
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
// Validate the color
wxString clr_str(m_opt.get_default_value<ConfigOptionStrings>()->get_at(m_opt_idx));
wxString clr_str(m_opt.type == coString ? m_opt.get_default_value<ConfigOptionString>()->value : m_opt.get_default_value<ConfigOptionStrings>()->get_at(m_opt_idx));
wxColour clr(clr_str);
if (clr_str.IsEmpty() || !clr.IsOk()) {
clr = wxTransparentColour;

View file

@ -286,8 +286,12 @@ void GCodeViewer::SequentialView::Marker::render() const
if (width != last_window_width || length != last_text_length) {
last_window_width = width;
last_text_length = length;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
#else
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
imgui.end();
@ -3379,8 +3383,12 @@ void GCodeViewer::render_legend(float& legend_height)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
// to avoid the tooltip to change size when moving the mouse
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
#else
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
}
@ -4115,8 +4123,12 @@ void GCodeViewer::render_legend(float& legend_height)
if (can_show_mode_button(mode)) {
if (imgui.button(label)) {
m_time_estimate_mode = mode;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
#else
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
}
};

View file

@ -722,8 +722,13 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
}
// force re-render while the windows gets to its final size (it takes several frames)
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
imgui.set_requires_extra_frame();
#else
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
m_canvas.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.end();
ImGui::PopStyleColor();
@ -734,47 +739,48 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
void GLCanvas3D::Tooltip::set_text(const std::string& text)
{
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
const std::string &new_text = m_in_imgui ? std::string() : text;
if (m_text != new_text) {
if (m_text.empty())
m_start_time = std::chrono::steady_clock::now();
m_text = new_text;
}
m_text = m_in_imgui ? std::string() : text;
}
void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas) const
void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas)
{
static ImVec2 size(0.0f, 0.0f);
auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) {
Size cnv_size = canvas.get_canvas_size();
float x = std::clamp((float)position(0), 0.0f, (float)cnv_size.get_width() - wnd_size.x);
float y = std::clamp((float)position(1) + 16, 0.0f, (float)cnv_size.get_height() - wnd_size.y);
const Size cnv_size = canvas.get_canvas_size();
const float x = std::clamp((float)position.x(), 0.0f, (float)cnv_size.get_width() - wnd_size.x);
const float y = std::clamp((float)position.y() + 16.0f, 0.0f, (float)cnv_size.get_height() - wnd_size.y);
return Vec2f(x, y);
};
if (m_text.empty())
if (m_text.empty()) {
m_start_time = std::chrono::steady_clock::now();
return;
}
// draw the tooltip as hidden until the delay is expired
// use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f
float alpha = (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f;
const float alpha = (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f;
Vec2f position = validate_position(mouse_position, canvas, size);
const Vec2f position = validate_position(mouse_position, canvas, size);
ImGuiWrapper& imgui = *wxGetApp().imgui();
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
imgui.set_next_window_pos(position(0), position(1), ImGuiCond_Always, 0.0f, 0.0f);
imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f);
imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
ImGui::TextUnformatted(m_text.c_str());
// force re-render while the windows gets to its final size (it may take several frames) or while hidden
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
imgui.set_requires_extra_frame();
#else
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
canvas.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
size = ImGui::GetWindowSize();
@ -2250,14 +2256,29 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current();
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
// ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism
bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame();
m_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (!m_dirty)
return;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
// this needs to be done here.
// during the render launched by the refresh the value may be set again
wxGetApp().imgui()->reset_requires_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
_refresh_if_shown_on_screen();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
#else
if (m_extra_frame_requested || mouse3d_controller_applied) {
m_dirty = true;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_extra_frame_requested = false;
evt.RequestMore();
}
@ -4176,7 +4197,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
shader->set_uniform("emission_factor", 0.0f);
for (GLVolume* vol : visible_volumes) {
shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray);
shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? (current_printer_technology() == ptSLA ? vol->color : orange) : gray);
// the volume may have been deactivated by an active gizmo
bool is_active = vol->is_active;
vol->is_active = true;

View file

@ -390,7 +390,7 @@ class GLCanvas3D
public:
bool is_empty() const { return m_text.empty(); }
void set_text(const std::string& text);
void render(const Vec2d& mouse_position, GLCanvas3D& canvas) const;
void render(const Vec2d& mouse_position, GLCanvas3D& canvas);
// Indicates that the mouse is inside an ImGUI dialog, therefore the tooltip should be suppressed.
void set_in_imgui(bool b) { m_in_imgui = b; }
bool is_in_imgui() const { return m_in_imgui; }

View file

@ -79,7 +79,9 @@ std::pair<bool, std::string> GLShadersManager::init()
// 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)
// Since macOS 12 (Monterey), this issue with the opposite direction on Apple's Arm CPU seems to be fixed, and computed
// triangle normals inside fragment shader have the right direction.
if (platform_flavor() == PlatformFlavor::OSXOnArm && wxPlatformInfo::Get().GetOSMajorVersion() < 12)
valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv});
else
valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"});

View file

@ -3,6 +3,7 @@
#include "GUI_Init.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "GUI_Factories.hpp"
#include "format.hpp"
#include "I18N.hpp"
@ -82,6 +83,9 @@
#include <wx/msw/dark_mode.h>
#endif // _MSW_DARK_MODE
#endif
#ifdef _WIN32
#include <boost/dll/runtime_symbol_info.hpp>
#endif
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
#include <boost/beast/core/detail/base64.hpp>
@ -410,7 +414,7 @@ bool static check_old_linux_datadir(const wxString& app_name) {
"location again.\n\n"
"What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str());
wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str());
wxRichMessageDialog dlg(nullptr, msg, caption, wxYES_NO);
RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO);
dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application"));
if (dlg.ShowModal() != wxID_NO)
return false;
@ -423,6 +427,56 @@ bool static check_old_linux_datadir(const wxString& app_name) {
#endif
#ifdef _WIN32
static bool run_updater_win()
{
// find updater exe
boost::filesystem::path path_to_binary = boost::dll::program_location();
for (const auto& dir_entry : boost::filesystem::directory_iterator(path_to_binary.parent_path())) {
if (dir_entry.path().filename() == "prusaslicer-updater.exe") {
// run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst
// Using quoted string as mentioned in CreateProcessW docs.
std::wstring wcmd = L"\"" + dir_entry.path().wstring() + L"\"";
wcmd += L" /silent";
// additional information
STARTUPINFOW si;
PROCESS_INFORMATION pi;
// set the size of the structures
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// start the program up
if (CreateProcessW(NULL, // the path
wcmd.data(), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
)) {
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
} else {
BOOST_LOG_TRIVIAL(error) << "Failed to start prusaslicer-updater.exe with command " << wcmd;
}
break;
}
}
return false;
}
#endif //_WIN32
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
{
static const std::string defaults[FT_SIZE] = {
@ -677,7 +731,6 @@ void GUI_App::post_init()
this->check_updates(false);
CallAfter([this] {
bool cw_showed = this->config_wizard_startup();
this->preset_updater->slic3r_update_notify();
this->preset_updater->sync(preset_bundle);
if (! cw_showed) {
// The CallAfter is needed as well, without it, GL extensions did not show.
@ -685,6 +738,15 @@ void GUI_App::post_init()
// sees something else than "we want something" on the first start.
show_send_system_info_dialog_if_needed();
}
bool updater_running =
#ifdef _WIN32
// Run external updater on Windows.
run_updater_win();
#else
false;
#endif // _WIN32
if (!updater_running)
this->preset_updater->slic3r_update_notify();
});
}
@ -845,7 +907,7 @@ bool GUI_App::check_older_app_config(Semver current_version, bool backup)
return false;
BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path;
// ask about using older data folder
wxRichMessageDialog msg(nullptr, backup ?
RichMessageDialog msg(nullptr, backup ?
wxString::Format(_L("PrusaSlicer detected another configuration folder at %s."
"\nIts version is %s."
"\nLast version you used in current configuration folder is %s."
@ -864,7 +926,7 @@ bool GUI_App::check_older_app_config(Semver current_version, bool backup)
"\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one."
"\nIf you select no, you will start with clean installation with configuration wizard.")
, m_older_data_dir_path, last_semver.to_string())
, _L("PrusaSlicer"), wxICON_QUESTION | wxYES_NO);
, _L("PrusaSlicer"), /*wxICON_QUESTION | */wxYES_NO);
if (msg.ShowModal() == wxID_YES) {
std::string snapshot_id;
if (backup) {
@ -935,7 +997,7 @@ bool GUI_App::on_init_inner()
// win32 build on win64 and viceversa
#ifdef _WIN64
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "") {
wxRichMessageDialog dlg(nullptr,
RichMessageDialog dlg(nullptr,
_L("You have started PrusaSlicer for 64-bit architecture on 32-bit system."
"\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
"\nDo you wish to continue?"),
@ -945,7 +1007,7 @@ bool GUI_App::on_init_inner()
}
#elif _WIN32
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") {
wxRichMessageDialog dlg(nullptr,
RichMessageDialog dlg(nullptr,
_L("You have started PrusaSlicer for 32-bit architecture on 64-bit system."
"\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
"\nDo you wish to continue?"),
@ -990,7 +1052,7 @@ bool GUI_App::on_init_inner()
bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
if (!msg.empty() && !ssl_accept) {
wxRichMessageDialog
RichMessageDialog
dlg(nullptr,
wxString::Format(_L("%s\nDo you want to continue?"), msg),
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
@ -1020,11 +1082,7 @@ bool GUI_App::on_init_inner()
wxInitAllImageHandlers();
#ifdef _MSW_DARK_MODE
if (bool dark_mode = app_config->get("dark_color_mode") == "1") {
NppDarkMode::InitDarkMode();
if (dark_mode != NppDarkMode::IsDarkMode())
NppDarkMode::SetDarkMode(dark_mode);
}
NppDarkMode::InitDarkMode(app_config->get("dark_color_mode") == "1", app_config->get("sys_menu_enabled") == "1");
#endif
SplashScreen* scrn = nullptr;
if (app_config->get("show_splash_screen") == "1") {
@ -1073,26 +1131,30 @@ bool GUI_App::on_init_inner()
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
app_config->set("version_online", into_u8(evt.GetString()));
app_config->save();
if (this->plater_ != nullptr) {
std::string opt = app_config->get("notify_release");
if (this->plater_ != nullptr && (opt == "all" || opt == "release")) {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable);
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable
, NotificationManager::NotificationLevel::ImportantNotificationLevel
, Slic3r::format(_u8L("New release version %1% is available."), evt.GetString())
, _u8L("See Download page.")
, [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; }
);
}
}
});
Bind(EVT_SLIC3R_ALPHA_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
app_config->save();
if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAlphaAvailable);
}
}
});
Bind(EVT_SLIC3R_BETA_VERSION_ONLINE, [this](const wxCommandEvent& evt) {
app_config->save();
if (this->plater_ != nullptr && app_config->get("notify_testing_release") == "1") {
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) {
this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::NewAlphaAvailable);
this->plater_->get_notification_manager()->push_notification(NotificationType::NewBetaAvailable);
if (this->plater_ != nullptr && app_config->get("notify_release") == "all") {
std::string evt_string = into_u8(evt.GetString());
if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) {
auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable);
this->plater_->get_notification_manager()->push_notification( notif_type
, NotificationManager::NotificationLevel::ImportantNotificationLevel
, Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string)
, _u8L("See Releases page.")
, [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }
);
}
}
});
@ -1347,10 +1409,9 @@ void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
{
#ifdef _WIN32
UpdateDarkUI(dvc, highlited ? dark_mode() : false);
wxItemAttr attr(dark_mode() ? m_color_highlight_default : m_color_label_default,
m_color_window_default,
m_normal_font);
dvc->SetHeaderAttr(attr);
#ifdef _MSW_DARK_MODE
dvc->RefreshHeaderDarkMode(&m_normal_font);
#endif //_MSW_DARK_MODE
if (dvc->HasFlag(wxDV_ROW_LINES))
dvc->SetAlternateRowColour(m_color_highlight_default);
if (dvc->GetBorder() != wxBORDER_SIMPLE)
@ -1574,12 +1635,44 @@ void fatal_error(wxWindow* parent)
}
#ifdef _WIN32
#ifdef _MSW_DARK_MODE
static void update_scrolls(wxWindow* window)
{
wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst();
while (node)
{
wxWindow* win = node->GetData();
if (dynamic_cast<wxScrollHelper*>(win) ||
dynamic_cast<wxTreeCtrl*>(win) ||
dynamic_cast<wxTextCtrl*>(win))
NppDarkMode::SetDarkExplorerTheme(win->GetHWND());
update_scrolls(win);
node = node->GetNext();
}
}
#endif //_MSW_DARK_MODE
#ifdef _MSW_DARK_MODE
void GUI_App::force_menu_update()
{
NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1");
}
#endif //_MSW_DARK_MODE
void GUI_App::force_colors_update()
{
#ifdef _MSW_DARK_MODE
NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl())
NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND);
NppDarkMode::SetDarkTitleBar(mainframe->GetHWND());
#endif //_MSW_DARK_MODE
m_force_colors_update = true;
}
#endif
#endif //_WIN32
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
@ -1587,11 +1680,15 @@ void GUI_App::update_ui_from_settings()
{
update_label_colours();
#ifdef _WIN32
// Upadte UU colors before Update UI from settings
// Upadte UI colors before Update UI from settings
if (m_force_colors_update) {
m_force_colors_update = false;
mainframe->force_color_changed();
mainframe->diff_dialog.force_color_changed();
mainframe->printhost_queue_dlg()->force_color_changed();
#ifdef _MSW_DARK_MODE
update_scrolls(mainframe);
#endif //_MSW_DARK_MODE
}
#endif
mainframe->update_ui_from_settings();
@ -2000,7 +2097,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for updates"), _L("Check for configuration updates"));
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
//if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
@ -2824,7 +2921,7 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/*
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
wxRichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
RichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
launch = answer == wxID_YES;

View file

@ -210,6 +210,9 @@ public:
const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; }
const wxColour& get_color_selected_btn_bg() { return m_color_selected_btn_bg; }
void force_colors_update();
#ifdef _MSW_DARK_MODE
void force_menu_update();
#endif //_MSW_DARK_MODE
#endif
const wxFont& small_font() { return m_small_font; }

View file

@ -2044,8 +2044,7 @@ void ObjectList::split()
void ObjectList::merge(bool to_multipart_object)
{
// merge selected objects to the multipart object
if (to_multipart_object)
{
if (to_multipart_object) {
auto get_object_idxs = [this](std::vector<int>& obj_idxs, wxDataViewItemArray& sels)
{
// check selections and split instances to the separated objects...
@ -2056,8 +2055,7 @@ void ObjectList::merge(bool to_multipart_object)
break;
}
if (!instance_selection)
{
if (!instance_selection) {
for (wxDataViewItem item : sels) {
assert(m_objects_model->GetItemType(item) & itObject);
obj_idxs.emplace_back(m_objects_model->GetIdByItem(item));
@ -2069,8 +2067,7 @@ void ObjectList::merge(bool to_multipart_object)
std::map<int, std::set<int>> sel_map;
std::set<int> empty_set;
for (wxDataViewItem item : sels) {
if (m_objects_model->GetItemType(item) & itObject)
{
if (m_objects_model->GetItemType(item) & itObject) {
int obj_idx = m_objects_model->GetIdByItem(item);
int inst_cnt = (*m_objects)[obj_idx]->instances.size();
if (inst_cnt == 1)
@ -2087,8 +2084,7 @@ void ObjectList::merge(bool to_multipart_object)
// all objects, created from the instances will be added to the end of list
int new_objects_cnt = 0; // count of this new objects
for (auto map_item : sel_map)
{
for (auto map_item : sel_map) {
int obj_idx = map_item.first;
// object with just 1 instance
if (map_item.second.empty()) {
@ -2148,37 +2144,36 @@ void ObjectList::merge(bool to_multipart_object)
new_object->name = _u8L("Merged");
ModelConfig &config = new_object->config;
for (int obj_idx : obj_idxs)
{
for (int obj_idx : obj_idxs) {
ModelObject* object = (*m_objects)[obj_idx];
const Geometry::Transformation& transformation = object->instances[0]->get_transformation();
Vec3d scale = transformation.get_scaling_factor();
Vec3d mirror = transformation.get_mirror();
Vec3d rotation = transformation.get_rotation();
const Vec3d scale = transformation.get_scaling_factor();
const Vec3d mirror = transformation.get_mirror();
const Vec3d rotation = transformation.get_rotation();
if (object->id() == (*m_objects)[obj_idxs.front()]->id())
new_object->add_instance();
Transform3d volume_offset_correction = new_object->instances[0]->get_transformation().get_matrix().inverse() * transformation.get_matrix();
const Transform3d& volume_offset_correction = transformation.get_matrix();
// merge volumes
for (const ModelVolume* volume : object->volumes) {
ModelVolume* new_volume = new_object->add_volume(*volume);
//set rotation
Vec3d vol_rot = new_volume->get_rotation() + rotation;
const Vec3d vol_rot = new_volume->get_rotation() + rotation;
new_volume->set_rotation(vol_rot);
// set scale
Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale);
const Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale);
new_volume->set_scaling_factor(vol_sc_fact);
// set mirror
Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror);
const Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror);
new_volume->set_mirror(vol_mirror);
// set offset
Vec3d vol_offset = volume_offset_correction* new_volume->get_offset();
const Vec3d vol_offset = volume_offset_correction* new_volume->get_offset();
new_volume->set_offset(vol_offset);
}
new_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1");
@ -2211,6 +2206,11 @@ void ObjectList::merge(bool to_multipart_object)
for (const auto& range : object->layer_config_ranges)
new_object->layer_config_ranges.emplace(range);
}
new_object->center_around_origin();
new_object->translate_instances(-new_object->origin_translation);
new_object->origin_translation = Vec3d::Zero();
// remove selected objects
remove();
@ -2221,8 +2221,7 @@ void ObjectList::merge(bool to_multipart_object)
}
// merge all parts to the one single object
// all part's settings will be lost
else
{
else {
wxDataViewItem item = GetSelection();
if (!item)
return;

View file

@ -866,6 +866,9 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
void ObjectManipulation::change_scale_value(int axis, double value)
{
if (value <= 0.0)
return;
if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON)
return;
@ -882,6 +885,9 @@ void ObjectManipulation::change_scale_value(int axis, double value)
void ObjectManipulation::change_size_value(int axis, double value)
{
if (value <= 0.0)
return;
if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON)
return;
@ -947,10 +953,26 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
change_position_value(axis, new_value);
else if (opt_key == "rotation")
change_rotation_value(axis, new_value);
else if (opt_key == "scale")
change_scale_value(axis, new_value);
else if (opt_key == "size")
change_size_value(axis, new_value);
else if (opt_key == "scale") {
if (new_value > 0.0)
change_scale_value(axis, new_value);
else {
new_value = m_cache.scale(axis);
m_cache.scale(axis) = 0.0;
m_cache.scale_rounded(axis) = DBL_MAX;
change_scale_value(axis, new_value);
}
}
else if (opt_key == "size") {
if (new_value > 0.0)
change_size_value(axis, new_value);
else {
new_value = m_cache.size(axis);
m_cache.size(axis) = 0.0;
m_cache.size_rounded(axis) = DBL_MAX;
change_size_value(axis, new_value);
}
}
}
void ObjectManipulation::set_uniform_scaling(const bool new_value)

View file

@ -101,7 +101,7 @@ GalleryDialog::GalleryDialog(wxWindow* parent, bool modify_gallery/* = false*/)
ok_btn->SetToolTip(_L("Add selected shape(s) to the bed"));
}
static_cast<wxButton*>(FindWindowById(wxID_CLOSE, this))->Bind(wxEVT_BUTTON, [this](wxCommandEvent&){ this->EndModal(wxID_CLOSE); });
this->SetEscapeId(wxID_CLOSE);
auto add_btn = [this, buttons]( size_t pos, int& ID, wxString title, wxString tooltip,
void (GalleryDialog::* method)(wxEvent&),
std::function<bool()> enable_fn = []() {return true; }) {

View file

@ -167,7 +167,11 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
if (last_h != win_h || last_y != y) {
// ask canvas for another frame to render the window in the correct position
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->set_requires_extra_frame();
#else
m_parent.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (last_h != win_h)
last_h = win_h;
if (last_y != y)

View file

@ -170,7 +170,13 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
float slider_start_position = std::max(position_before_text_y, position_after_text_y - slider_height);
ImGui::SetCursorPosY(slider_start_position);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
"the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]);
if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) {
#else
if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) {
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
if (! m_parent.is_using_slope()) {
m_parent.use_slope(true);
@ -182,9 +188,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y));
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::IsItemHovered())
m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
"the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width);
#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f);
ImGui::NewLine();
@ -267,9 +275,13 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
@ -284,14 +296,20 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
#else
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data()))
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
ImGui::Separator();
@ -310,11 +328,16 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width);
auto clp_dist = float(m_c->object_clipper()->get_position());
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {

View file

@ -441,9 +441,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
@ -460,14 +464,20 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
#else
if(m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data()))
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
}
@ -484,11 +494,16 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
auto clp_dist = float(m_c->object_clipper()->get_position());
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {

View file

@ -512,9 +512,21 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
y = std::min(y, alignment.bottom_limit - win_h);
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
ImGui::PushItemWidth(300.f);
float max_text_w = 0.;
auto padding = ImGui::GetStyle().FramePadding;
padding.x *= 2.f;
padding.y *= 2.f;
if (ImGui::BeginCombo(_L("Choose goal").c_str(), RotoptimizeJob::get_method_name(state.method_id).c_str())) {
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
float w =
ImGui::CalcTextSize(RotoptimizeJob::get_method_name(i).c_str()).x +
padding.x + ImGui::GetFrameHeight();
max_text_w = std::max(w, max_text_w);
}
ImGui::PushItemWidth(max_text_w);
if (ImGui::BeginCombo("", RotoptimizeJob::get_method_name(state.method_id).c_str())) {
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
if (ImGui::Selectable(RotoptimizeJob::get_method_name(i).c_str())) {
state.method_id = i;
@ -530,12 +542,18 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
ImGui::EndCombo();
}
ImVec2 sz = ImGui::GetItemRectSize();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(state.method_id).c_str());
ImGui::Separator();
if ( imgui->button(_L("Optimize")) ) {
auto btn_txt = _L("Apply");
auto btn_txt_sz = ImGui::CalcTextSize(btn_txt.c_str());
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
if ( imgui->button(btn_txt) ) {
wxGetApp().plater()->optimize_rotation();
}
}

View file

@ -128,9 +128,13 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
@ -168,11 +172,16 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
auto clp_dist = float(m_c->object_clipper()->get_position());
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {

View file

@ -256,7 +256,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) {
if(m_imgui->slider_float("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)){
if (m_configuration.decimate_ratio < 0.f)
m_configuration.decimate_ratio = 0.01f;
if (m_configuration.decimate_ratio > 100.f)

View file

@ -644,7 +644,11 @@ RENDER_AGAIN:
if ((last_h != win_h) || (last_y != y))
{
// ask canvas for another frame to render the window in the correct position
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->set_requires_extra_frame();
#else
m_parent.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (last_h != win_h)
last_h = win_h;
if (last_y != y)

View file

@ -8,6 +8,9 @@
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#include <boost/nowide/convert.hpp>
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#include <wx/string.h>
#include <wx/event.h>
@ -48,7 +51,9 @@ static const std::map<const wchar_t, std::string> font_icons = {
{ImGui::RightArrowHoverButton , "notification_right_hover" },
{ImGui::PreferencesButton , "notification_preferences" },
{ImGui::PreferencesHoverButton , "notification_preferences_hover"},
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
{ImGui::SliderFloatEditBtnIcon, "edit_button" },
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
};
static const std::map<const wchar_t, std::string> font_icons_large = {
{ImGui::CloseNotifButton , "notification_close" },
@ -85,14 +90,6 @@ const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = COL_ORANGE_LIGHT;
const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED;
ImGuiWrapper::ImGuiWrapper()
: m_glyph_ranges(nullptr)
, m_font_cjk(false)
, m_font_size(18.0)
, m_font_texture(0)
, m_style_scaling(1.0)
, m_mouse_buttons(0)
, m_disabled(false)
, m_new_frame_open(false)
{
ImGui::CreateContext();
@ -484,6 +481,53 @@ void ImGuiWrapper::tooltip(const wxString &label, float wrap_width)
ImGui::EndTooltip();
}
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/, const wxString& tooltip /*= ""*/, bool show_edit_btn /*= true*/)
{
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);
if (!tooltip.empty() && ImGui::IsItemHovered())
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width);
if (clamp)
*v = std::clamp(*v, v_min, v_max);
if (show_edit_btn) {
const ImGuiStyle& style = ImGui::GetStyle();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y });
ImGui::SameLine();
std::wstring btn_name;
btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(std::string(label));
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.5f, 0.5f, 0.5f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.5f, 0.5f, 0.5f, 1.0f });
if (ImGui::Button(into_u8(btn_name).c_str())) {
ImGui::SetKeyboardFocusHere(-1);
this->set_requires_extra_frame();
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered())
this->tooltip(into_u8(_L("Edit")).c_str(), max_tooltip_width);
ImGui::PopStyleVar();
}
return ret;
}
bool ImGuiWrapper::slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/, const wxString& tooltip /*= ""*/, bool show_edit_btn /*= true*/)
{
return this->slider_float(label.c_str(), v, v_min, v_max, format, power, clamp, tooltip, show_edit_btn);
}
bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/, const wxString& tooltip /*= ""*/, bool show_edit_btn /*= true*/)
{
auto label_utf8 = into_u8(label);
return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp, tooltip, show_edit_btn);
}
#else
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/)
{
bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);
@ -502,6 +546,7 @@ bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, fl
auto label_utf8 = into_u8(label);
return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp);
}
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection)
{
@ -972,15 +1017,7 @@ std::vector<unsigned char> ImGuiWrapper::load_svg(const std::string& bitmap_name
{
std::vector<unsigned char> empty_vector;
#ifdef __WXMSW__
std::string folder = "white\\";
#else
std::string folder = "white/";
#endif
if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg")))
folder.clear();
NSVGimage* image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f);
NSVGimage* image = ::nsvgParseFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f, { { "#808080", "#FFFFFF" } });
if (image == nullptr)
return empty_vector;
@ -1152,6 +1189,7 @@ void ImGuiWrapper::init_input()
io.KeyMap[ImGuiKey_Backspace] = WXK_BACK;
io.KeyMap[ImGuiKey_Space] = WXK_SPACE;
io.KeyMap[ImGuiKey_Enter] = WXK_RETURN;
io.KeyMap[ImGuiKey_KeyPadEnter] = WXK_NUMPAD_ENTER;
io.KeyMap[ImGuiKey_Escape] = WXK_ESCAPE;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';

View file

@ -6,6 +6,8 @@
#include <imgui/imgui.h>
#include <wx/string.h>
#include "libslic3r/Point.hpp"
namespace Slic3r {namespace Search {
@ -22,15 +24,18 @@ namespace GUI {
class ImGuiWrapper
{
const ImWchar *m_glyph_ranges;
const ImWchar* m_glyph_ranges{ nullptr };
// Chinese, Japanese, Korean
bool m_font_cjk;
float m_font_size;
unsigned m_font_texture;
float m_style_scaling;
unsigned m_mouse_buttons;
bool m_disabled;
bool m_new_frame_open;
bool m_font_cjk{ false };
float m_font_size{ 18.0 };
unsigned m_font_texture{ 0 };
float m_style_scaling{ 1.0 };
unsigned m_mouse_buttons{ 0 };
bool m_disabled{ false };
bool m_new_frame_open{ false };
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool m_requires_extra_frame{ false };
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
std::string m_clipboard_text;
public:
@ -90,9 +95,15 @@ public:
void tooltip(const wxString &label, float wrap_width);
// Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true).
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = "", bool show_edit_btn = true);
bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = "", bool show_edit_btn = true);
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = "", bool show_edit_btn = true);
#else
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);
bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection); // Use -1 to not mark any option as selected
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel);
@ -108,6 +119,12 @@ public:
bool want_text_input() const;
bool want_any_input() const;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool requires_extra_frame() const { return m_requires_extra_frame; }
void set_requires_extra_frame() { m_requires_extra_frame = true; }
void reset_requires_extra_frame() { m_requires_extra_frame = false; }
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
static const ImVec4 COL_GREY_DARK;
static const ImVec4 COL_GREY_LIGHT;
static const ImVec4 COL_ORANGE_DARK;

View file

@ -122,7 +122,9 @@ public:
std::string err;
ConfigSubstitutions config_substitutions;
priv(Plater *plt) : plater{plt} {}
ImportDlg import_dlg;
priv(Plater *plt) : plater{plt}, import_dlg{plt} {}
};
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
@ -176,14 +178,12 @@ void SLAImportJob::prepare()
{
reset();
ImportDlg dlg{p->plater};
if (dlg.ShowModal() == wxID_OK) {
auto path = dlg.get_path();
if (p->import_dlg.ShowModal() == wxID_OK) {
auto path = p->import_dlg.get_path();
auto nm = wxFileName(path);
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
p->sel = dlg.get_selection();
p->win = dlg.get_marchsq_windowsize();
p->sel = p->import_dlg.get_selection();
p->win = p->import_dlg.get_marchsq_windowsize();
p->config_substitutions.clear();
} else {
p->path = "";
@ -236,7 +236,7 @@ void SLAImportJob::finalize()
if (!p->mesh.empty()) {
bool is_centered = false;
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{p->mesh},
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{std::move(p->mesh)},
name, is_centered);
}

View file

@ -25,9 +25,9 @@ namespace Slic3r {
namespace GUI {
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id, wxBitmap bitmap)
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style, wxBitmap bitmap)
: wxDialog(parent ? parent : dynamic_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, boldfont(wxGetApp().normal_font()/*wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)*/)
, boldfont(wxGetApp().normal_font())
, content_sizer(new wxBoxSizer(wxVERTICAL))
, btn_sizer(new wxBoxSizer(wxHORIZONTAL))
{
@ -36,6 +36,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
this->SetFont(wxGetApp().normal_font());
this->CenterOnParent();
auto *main_sizer = new wxBoxSizer(wxVERTICAL);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
@ -46,46 +47,41 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
rightsizer->AddSpacer(VERT_SPACING);
rightsizer->Add(content_sizer, 1, wxEXPAND);
btn_sizer->AddStretchSpacer();
if (button_id != wxID_NONE) {
auto *button = new wxButton(this, button_id);
button->SetFocus();
btn_sizer->Add(button);
}
rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT);
if (! bitmap.IsOk()) {
bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192);
}
logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
logo = new wxStaticBitmap(this, wxID_ANY, bitmap.IsOk() ? bitmap : wxNullBitmap);
topsizer->Add(logo, 0, wxALL, BORDER);
topsizer->Add(rightsizer, 1, wxTOP | wxBOTTOM | wxRIGHT | wxEXPAND, BORDER);
SetSizerAndFit(topsizer);
main_sizer->Add(topsizer, 1, wxEXPAND);
main_sizer->Add(new StaticLine(this), 0, wxEXPAND | wxLEFT | wxRIGHT, HORIZ_SPACING);
main_sizer->Add(btn_sizer, 0, wxALL | wxEXPAND, VERT_SPACING);
apply_style(style);
SetSizerAndFit(main_sizer);
}
void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/)
void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/, const wxString& label/* = wxString()*/)
{
wxButton* btn = new wxButton(this, btn_id);
wxButton* btn = new wxButton(this, btn_id, label);
if (set_focus)
btn->SetFocus();
btn_sizer->Add(btn, 0, wxRIGHT, HORIZ_SPACING);
btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, HORIZ_SPACING);
btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); });
};
void MsgDialog::apply_style(long style)
{
if (style & wxOK) add_btn(wxID_OK, true);
if (style & wxYES) add_btn(wxID_YES);
if (style & wxYES) add_btn(wxID_YES, true);
if (style & wxNO) add_btn(wxID_NO);
if (style & wxCANCEL) add_btn(wxID_CANCEL);
logo->SetBitmap(create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
style & wxICON_INFORMATION ? "info" :
style & wxICON_QUESTION ? "question" : "PrusaSlicer"/*"_192px_grayscale.png"*/, this, 84));
logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
style & wxICON_INFORMATION ? "info" :
style & wxICON_QUESTION ? "question" : "PrusaSlicer", this, 64, style & wxICON_ERROR));
}
void MsgDialog::finalize()
@ -122,7 +118,7 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxFont monospace = wxGetApp().code_font();
wxColour text_clr = wxGetApp().get_label_clr_default();
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
wxColour bgr_clr = parent->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int font_size = font.GetPointSize();
@ -161,18 +157,17 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
content_sizer->Add(html, 1, wxEXPAND);
wxGetApp().UpdateDarkUI(html);
}
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_font)
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_NAME),
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_NAME),
wxID_NONE)
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_NAME), wxOK)
, msg(msg)
{
add_msg_content(this, content_sizer, msg, monospaced_font);
add_btn(wxID_OK, true);
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84));
@ -189,10 +184,9 @@ WarningDialog::WarningDialog(wxWindow *parent,
const wxString& caption/* = wxEmptyString*/,
long style/* = wxOK*/)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s warning"), SLIC3R_APP_NAME) : caption,
wxString::Format(_L("%s has a warning")+":", SLIC3R_APP_NAME), wxID_NONE)
wxString::Format(_L("%s has a warning")+":", SLIC3R_APP_NAME), style)
{
add_msg_content(this, content_sizer, message);
apply_style(style);
finalize();
}
@ -203,50 +197,51 @@ MessageDialog::MessageDialog(wxWindow* parent,
const wxString& message,
const wxString& caption/* = wxEmptyString*/,
long style/* = wxOK*/)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, wxID_NONE)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style)
{
add_msg_content(this, content_sizer, message);
apply_style(style);
finalize();
}
// RichMessageDialog
RichMessageDialog::RichMessageDialog(wxWindow* parent,
const wxString& message,
const wxString& caption/* = wxEmptyString*/,
long style/* = wxOK*/)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style)
{
add_msg_content(this, content_sizer, message);
m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText);
wxGetApp().UpdateDarkUI(m_checkBox);
m_checkBox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_checkBoxValue = m_checkBox->GetValue(); });
btn_sizer->Insert(0, m_checkBox, wxALIGN_CENTER_VERTICAL);
finalize();
}
int RichMessageDialog::ShowModal()
{
if (m_checkBoxText.IsEmpty())
m_checkBox->Hide();
else
m_checkBox->SetLabelText(m_checkBoxText);
Layout();
return wxDialog::ShowModal();
}
#endif
// MessageWithCheckDialog
MessageWithCheckDialog::MessageWithCheckDialog( wxWindow* parent,
const wxString& message,
const wxString& checkbox_label,
const wxString& caption/* = wxEmptyString*/,
long style/* = wxOK*/)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, wxID_NONE)
{
add_msg_content(this, content_sizer, message);
m_check = new wxCheckBox(this, wxID_ANY, checkbox_label);
content_sizer->Add(m_check, 0, wxTOP, 10);
apply_style(style);
finalize();
}
bool MessageWithCheckDialog::GetCheckVal()
{
if (m_check)
return m_check->GetValue();
return false;
}
// InfoDialog
InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg)
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title)
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title, wxOK | wxICON_INFORMATION)
, msg(msg)
{
add_msg_content(this, content_sizer, msg);
// Set info bitmap
logo->SetBitmap(create_scaled_bitmap("info", this, 84));
finalize();
}

View file

@ -8,6 +8,9 @@
#include <wx/font.h>
#include <wx/bitmap.h>
#include <wx/msgdlg.h>
#include <wx/richmsgdlg.h>
#include <wx/textctrl.h>
#include <wx/statline.h>
class wxBoxSizer;
class wxCheckBox;
@ -17,7 +20,6 @@ namespace Slic3r {
namespace GUI {
// A message / query dialog with a bitmap on the left and any content on the right
// with buttons underneath.
struct MsgDialog : wxDialog
@ -39,10 +41,9 @@ protected:
HORIZ_SPACING = 5,
};
// button_id is an id of a button that can be added by default, use wxID_NONE to disable
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK, wxBitmap bitmap = wxNullBitmap);
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style = wxOK, wxBitmap bitmap = wxNullBitmap);
void add_btn(wxWindowID btn_id, bool set_focus = false);
void add_btn(wxWindowID btn_id, bool set_focus = false, const wxString& label = wxString());
void apply_style(long style);
void finalize();
@ -87,6 +88,23 @@ public:
};
#ifdef _WIN32
// Generic static line, used intead of wxStaticLine
class StaticLine: public wxTextCtrl
{
public:
StaticLine( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxLI_HORIZONTAL,
const wxString& name = wxString::FromAscii(wxTextCtrlNameStr))
: wxTextCtrl(parent, id, wxEmptyString, pos, size!=wxDefaultSize ? size : (style == wxLI_HORIZONTAL ? wxSize(10, 1) : wxSize(1, 10)), wxSIMPLE_BORDER, wxDefaultValidator, name)
{
this->Enable(false);
}
~StaticLine() {}
};
// Generic message dialog, used intead of wxMessageDialog
class MessageDialog : public MsgDialog
{
@ -101,7 +119,158 @@ public:
MessageDialog &operator=(const MessageDialog&) = delete;
virtual ~MessageDialog() = default;
};
// Generic rich message dialog, used intead of wxRichMessageDialog
class RichMessageDialog : public MsgDialog
{
wxCheckBox* m_checkBox{ nullptr };
wxString m_checkBoxText;
bool m_checkBoxValue{ false };
public:
RichMessageDialog( wxWindow *parent,
const wxString& message,
const wxString& caption = wxEmptyString,
long style = wxOK);
RichMessageDialog(RichMessageDialog&&) = delete;
RichMessageDialog(const RichMessageDialog&) = delete;
RichMessageDialog &operator=(RichMessageDialog&&) = delete;
RichMessageDialog &operator=(const RichMessageDialog&) = delete;
virtual ~RichMessageDialog() = default;
int ShowModal() override;
void ShowCheckBox(const wxString& checkBoxText, bool checked = false)
{
m_checkBoxText = checkBoxText;
m_checkBoxValue = checked;
}
wxString GetCheckBoxText() const { return m_checkBoxText; }
bool IsCheckBoxChecked() const { return m_checkBoxValue; }
// This part o fcode isported from the "wx\msgdlg.h"
using wxMD = wxMessageDialogBase;
// customization of the message box buttons
virtual bool SetYesNoLabels(const wxMD::ButtonLabel& yes, const wxMD::ButtonLabel& no)
{
DoSetCustomLabel(m_yes, yes);
DoSetCustomLabel(m_no, no);
return true;
}
virtual bool SetYesNoCancelLabels(const wxMD::ButtonLabel& yes,
const wxMD::ButtonLabel& no,
const wxMD::ButtonLabel& cancel)
{
DoSetCustomLabel(m_yes, yes);
DoSetCustomLabel(m_no, no);
DoSetCustomLabel(m_cancel, cancel);
return true;
}
virtual bool SetOKLabel(const wxMD::ButtonLabel& ok)
{
DoSetCustomLabel(m_ok, ok);
return true;
}
virtual bool SetOKCancelLabels(const wxMD::ButtonLabel& ok,
const wxMD::ButtonLabel& cancel)
{
DoSetCustomLabel(m_ok, ok);
DoSetCustomLabel(m_cancel, cancel);
return true;
}
virtual bool SetHelpLabel(const wxMD::ButtonLabel& help)
{
DoSetCustomLabel(m_help, help);
return true;
}
// test if any custom labels were set
bool HasCustomLabels() const
{
return !(m_ok.empty() && m_cancel.empty() && m_help.empty() &&
m_yes.empty() && m_no.empty());
}
// these functions return the label to be used for the button which is
// either a custom label explicitly set by the user or the default label,
// i.e. they always return a valid string
wxString GetYesLabel() const
{
return m_yes.empty() ? GetDefaultYesLabel() : m_yes;
}
wxString GetNoLabel() const
{
return m_no.empty() ? GetDefaultNoLabel() : m_no;
}
wxString GetOKLabel() const
{
return m_ok.empty() ? GetDefaultOKLabel() : m_ok;
}
wxString GetCancelLabel() const
{
return m_cancel.empty() ? GetDefaultCancelLabel() : m_cancel;
}
wxString GetHelpLabel() const
{
return m_help.empty() ? GetDefaultHelpLabel() : m_help;
}
protected:
// this function is called by our public SetXXXLabels() and should assign
// the value to var with possibly some transformation (e.g. Cocoa version
// currently uses this to remove any accelerators from the button strings
// while GTK+ one handles stock items specifically here)
void DoSetCustomLabel(wxString& var, const wxMD::ButtonLabel& label)
{
var = label.GetAsString();
}
// these functions return the custom label or empty string and should be
// used only in specific circumstances such as creating the buttons with
// these labels (in which case it makes sense to only use a custom label if
// it was really given and fall back on stock label otherwise), use the
// Get{Yes,No,OK,Cancel}Label() methods above otherwise
const wxString& GetCustomYesLabel() const { return m_yes; }
const wxString& GetCustomNoLabel() const { return m_no; }
const wxString& GetCustomOKLabel() const { return m_ok; }
const wxString& GetCustomHelpLabel() const { return m_help; }
const wxString& GetCustomCancelLabel() const { return m_cancel; }
private:
// these functions may be overridden to provide different defaults for the
// default button labels (this is used by wxGTK)
virtual wxString GetDefaultYesLabel() const { return wxGetTranslation("Yes"); }
virtual wxString GetDefaultNoLabel() const { return wxGetTranslation("No"); }
virtual wxString GetDefaultOKLabel() const { return wxGetTranslation("OK"); }
virtual wxString GetDefaultCancelLabel() const { return wxGetTranslation("Cancel"); }
virtual wxString GetDefaultHelpLabel() const { return wxGetTranslation("Help"); }
// labels for the buttons, initially empty meaning that the defaults should
// be used, use GetYes/No/OK/CancelLabel() to access them
wxString m_yes,
m_no,
m_ok,
m_cancel,
m_help;
};
#else
// just a wrapper for wxStaticLine to use the same code on all platforms
class StaticLine : public wxStaticLine
{
public:
StaticLine(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxLI_HORIZONTAL,
const wxString& name = wxString::FromAscii(wxStaticLineNameStr))
: wxStaticLine(parent, id, pos, size, style, name) {}
~StaticLine() {}
};
// just a wrapper to wxMessageBox to use the same code on all platforms
class MessageDialog : public wxMessageDialog
{
@ -113,25 +282,19 @@ public:
: wxMessageDialog(parent, message, caption, style) {}
~MessageDialog() {}
};
#endif
class MessageWithCheckDialog : public MsgDialog
// just a wrapper to wxRichMessageBox to use the same code on all platforms
class RichMessageDialog : public wxRichMessageDialog
{
wxCheckBox* m_check{ nullptr };
public:
MessageWithCheckDialog(wxWindow* parent,
RichMessageDialog(wxWindow* parent,
const wxString& message,
const wxString& checkbox_label,
const wxString& caption = wxEmptyString,
long style = wxOK);
MessageWithCheckDialog(MessageWithCheckDialog&&) = delete;
MessageWithCheckDialog(const MessageWithCheckDialog&) = delete;
MessageWithCheckDialog& operator=(MessageWithCheckDialog&&) = delete;
MessageWithCheckDialog& operator=(const MessageWithCheckDialog&) = delete;
virtual ~MessageWithCheckDialog() = default;
bool GetCheckVal();
long style = wxOK)
: wxRichMessageDialog(parent, message, caption, style) {}
~RichMessageDialog() {}
};
#endif
// Generic info dialog, used for displaying exceptions
class InfoDialog : public MsgDialog

View file

@ -41,12 +41,6 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
return true;
}
},
{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
{NotificationType::NewAlphaAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New alpha release is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
{NotificationType::NewBetaAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New beta release is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
{NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10,
_u8L("You have just added a G-code for color change, but its value is empty.\n"
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
@ -61,6 +55,8 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
_u8L("Undo desktop integration failed.") },
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") },
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification

View file

@ -150,7 +150,7 @@ public:
// Push a NotificationType::CustomNotification with NotificationLevel::RegularNotificationLevel and 10s fade out interval.
void push_notification(const std::string& text, int timestamp = 0);
// Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotificationLevel.
// ErrorNotificationLevel and ImportantNotificationLevel are never faded out.
// ErrorNotificationLevel are never faded out.
void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "",
std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>(), int timestamp = 0);
// Pushes basic_notification with delay. See push_delayed_notification_data.
@ -720,7 +720,7 @@ private:
case NotificationLevel::ErrorNotificationLevel: return 0;
case NotificationLevel::WarningNotificationLevel: return 0;
case NotificationLevel::ImportantNotificationLevel: return 0;
case NotificationLevel::ImportantNotificationLevel: return 20;
case NotificationLevel::ProgressBarNotificationLevel: return 2;
case NotificationLevel::PrintInfoShortNotificationLevel: return 5;
case NotificationLevel::RegularNotificationLevel: return 10;

View file

@ -534,7 +534,7 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
[model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; });
if (it != models.end() && (it->family == "MK3" || it->family == "MINI"))
continue;
} else if (!preset->vendor && (boost::ends_with(model_id, "MK3") || boost::ends_with(model_id, "MINI"))) {
} else if (!preset->vendor && (boost::starts_with(model_id, "MK3") || boost::starts_with(model_id, "MINI"))) {
continue;
}

View file

@ -1087,6 +1087,8 @@ void Sidebar::msw_rescale()
p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height));
p->scrolled->Layout();
p->searcher.dlg_msw_rescale();
}
void Sidebar::sys_color_changed()
@ -1125,6 +1127,8 @@ void Sidebar::sys_color_changed()
p->btn_export_gcode_removable->msw_rescale();
p->scrolled->Layout();
p->searcher.dlg_sys_color_changed();
}
void Sidebar::search()
@ -1916,7 +1920,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width",
"extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology",
"extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
"brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers",
@ -2478,15 +2482,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
model.convert_from_meters(true);
};
if (answer_convert_from_meters == wxOK_DEFAULT) {
MessageWithCheckDialog dlg(q, format_wxstr(_L_PLURAL(
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in meters.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
"The dimensions of some objects from file %s seem to be defined in meters.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
_L("Apply to all the remaining small objects being loaded."),
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
_L("The object is too small"), wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal();
if (dlg.GetCheckVal())
if (dlg.IsCheckBoxChecked())
answer_convert_from_meters = answer;
else
convert_model_if(model, answer == wxID_YES);
@ -2500,15 +2504,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
convert_from_imperial_units(model, true);
};
if (answer_convert_from_imperial_units == wxOK_DEFAULT) {
MessageWithCheckDialog dlg(q, format_wxstr(_L_PLURAL(
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in inches.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
"The dimensions of some objects from file %s seem to be defined in inches.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
_L("Apply to all the remaining small objects being loaded."),
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
_L("The object is too small"), wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal();
if (dlg.GetCheckVal())
if (dlg.IsCheckBoxChecked())
answer_convert_from_imperial_units = answer;
else
convert_model_if(model, answer == wxID_YES);
@ -5055,6 +5059,7 @@ void Plater::new_project()
Plater::SuppressSnapshots suppress(this);
reset();
reset_project_dirty_initial_presets();
wxGetApp().update_saved_preset_from_current_preset();
update_project_dirty_from_presets();
}
@ -6217,6 +6222,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
continue;
}
}
if (opt_key == "material_colour") {
update_scheduled = true; // update should be scheduled (for update 3DScene)
}
p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") {

View file

@ -141,14 +141,6 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "background_processing");
m_optgroup_general->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Check for application updates");
def.type = coBool;
def.tooltip = L("If enabled, PrusaSlicer will check for the new versions of itself online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.");
def.set_default_value(new ConfigOptionBool(app_config->get("version_check") == "1"));
option = Option(def, "version_check");
m_optgroup_general->append_single_option_line(option);
m_optgroup_general->append_separator();
// Please keep in sync with ConfigWizard
@ -350,8 +342,6 @@ void PreferencesDialog::build(size_t selected_tab)
tabs->Layout();
this->layout();
}
};
def.label = L("Sequential slider applied only to top layer");
@ -395,16 +385,6 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_gui->append_single_option_line(option);
#ifdef _MSW_DARK_MODE
}
def.label = L("Use Dark color mode (experimental)");
def.type = coBool;
def.tooltip = L("If enabled, UI will use Dark mode colors. "
"If disabled, old UI will be used.");
def.set_default_value(new ConfigOptionBool{ app_config->get("dark_color_mode") == "1" });
option = Option(def, "dark_color_mode");
m_optgroup_gui->append_single_option_line(option);
if (is_editor) {
def.label = L("Set settings tabs as menu items (experimental)");
def.type = coBool;
def.tooltip = L("If enabled, Settings Tabs will be placed as menu items. "
@ -484,6 +464,36 @@ void PreferencesDialog::build(size_t selected_tab)
}
#endif // ENABLE_ENVIRONMENT_MAP
#ifdef _WIN32
// Add "Dark Mode" tab
if (is_editor) {
// Add "Dark Mode" tab
m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs);
m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
def.label = L("Enable dark mode");
def.type = coBool;
def.tooltip = L("If enabled, UI will use Dark mode colors. "
"If disabled, old UI will be used.");
def.set_default_value(new ConfigOptionBool{ app_config->get("dark_color_mode") == "1" });
option = Option(def, "dark_color_mode");
m_optgroup_dark_mode->append_single_option_line(option);
def.label = L("Use system menu for application");
def.type = coBool;
def.tooltip = L("If enabled, application will use standart Windows system menu,\n"
"but on some combination od display scales it can looks ugly. "
"If disabled, old UI will be used.");
def.set_default_value(new ConfigOptionBool{ app_config->get("sys_menu_enabled") == "1" });
option = Option(def, "sys_menu_enabled");
m_optgroup_dark_mode->append_single_option_line(option);
activate_options_tab(m_optgroup_dark_mode);
}
#endif //_WIN32
// update alignment of the controls for all tabs
update_ctrls_alignment();
@ -525,7 +535,7 @@ void PreferencesDialog::accept(wxEvent&)
// if (m_values.find("no_defaults") != m_values.end()
// warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME));
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu" };
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" };
for (const std::string& option : options_to_recreate_GUI) {
if (m_values.find(option) != m_values.end()) {
@ -588,11 +598,14 @@ void PreferencesDialog::accept(wxEvent&)
EndModal(wxID_OK);
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
if (m_values.find("dark_color_mode") != m_values.end())
wxGetApp().force_colors_update();
#endif
#ifdef _MSW_DARK_MODE
if (m_values.find("sys_menu_enabled") != m_values.end())
wxGetApp().force_menu_update();
#endif //_MSW_DARK_MODE
#endif // _WIN32
if (m_settings_layout_changed)
;// application will be recreated after Preference dialog will be destroyed
else

View file

@ -29,6 +29,9 @@ class PreferencesDialog : public DPIDialog
std::shared_ptr<ConfigOptionsGroup> m_optgroup_general;
std::shared_ptr<ConfigOptionsGroup> m_optgroup_camera;
std::shared_ptr<ConfigOptionsGroup> m_optgroup_gui;
#ifdef _WIN32
std::shared_ptr<ConfigOptionsGroup> m_optgroup_dark_mode;
#endif //_WIN32
#if ENABLE_ENVIRONMENT_MAP
std::shared_ptr<ConfigOptionsGroup> m_optgroup_render;
#endif // ENABLE_ENVIRONMENT_MAP

View file

@ -419,7 +419,7 @@ wxString PresetComboBox::separator(const std::string& label)
wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name,
bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/,
std::string filament_rgb/* = ""*/, std::string extruder_rgb/* = ""*/)
const std::string& filament_rgb/* = ""*/, const std::string& extruder_rgb/* = ""*/, const std::string& material_rgb/* = ""*/)
{
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
// to the filament color image.
@ -431,6 +431,7 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con
bool dark_mode = wxGetApp().dark_mode();
if (dark_mode)
bitmap_key += ",dark";
bitmap_key += material_rgb;
wxBitmap* bmp = bitmap_cache().find(bitmap_key);
if (bmp == nullptr) {
@ -457,7 +458,10 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con
{
// Paint the color bars.
bmps.emplace_back(bitmap_cache().mkclear(thin_space_icon_width, icon_height));
bmps.emplace_back(create_scaled_bitmap(main_icon_name));
if (m_type == Preset::TYPE_SLA_MATERIAL)
bmps.emplace_back(create_scaled_bitmap(main_icon_name, this, 16, false, material_rgb));
else
bmps.emplace_back(create_scaled_bitmap(main_icon_name));
// Paint a lock at the system presets.
bmps.emplace_back(bitmap_cache().mkclear(wide_space_icon_width, icon_height));
}
@ -789,7 +793,7 @@ void PlaterPresetComboBox::update()
if (!preset.is_visible || (!preset.is_compatible && !is_selected))
continue;
std::string bitmap_key, filament_rgb, extruder_rgb;
std::string bitmap_key, filament_rgb, extruder_rgb, material_rgb;
std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
bool single_bar = false;
@ -803,10 +807,12 @@ void PlaterPresetComboBox::update()
bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb;
}
else if (m_type == Preset::TYPE_SLA_MATERIAL)
material_rgb = is_selected ? m_preset_bundle->sla_materials.get_edited_preset().config.opt_string("material_colour") : preset.config.opt_string("material_colour");
wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name,
preset.is_compatible, preset.is_system || preset.is_default,
single_bar, filament_rgb, extruder_rgb);
single_bar, filament_rgb, extruder_rgb, material_rgb);
assert(bmp);
const std::string name = preset.alias.empty() ? preset.name : preset.alias;

View file

@ -122,7 +122,7 @@ protected:
wxBitmap* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name,
bool is_compatible = true, bool is_system = false, bool is_single_bar = false,
std::string filament_rgb = "", std::string extruder_rgb = "");
const std::string& filament_rgb = "", const std::string& extruder_rgb = "", const std::string& material_rgb = "");
wxBitmap* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name,
bool is_enabled = true, bool is_compatible = true, bool is_system = false);

View file

@ -39,7 +39,7 @@ static const char *CONFIG_KEY_PRINT = "printhost_print";
static const char *CONFIG_KEY_GROUP = "printhost_group";
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print, const wxArrayString &groups)
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), wxID_NONE)
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), wxOK | wxCANCEL)
, txt_filename(new wxTextCtrl(this, wxID_ANY))
, box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _L("Start printing after upload")) : nullptr)
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
@ -70,10 +70,6 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
combo_groups->SetValue(recent_group);
}
auto* szr = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
auto* btn_ok = szr->GetAffirmativeButton();
btn_sizer->Add(szr);
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
recent_path += '/';
@ -88,21 +84,19 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
wxString suffix = recent_path.substr(recent_path.find_last_of('.'));
btn_ok->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
static_cast<wxButton*>(FindWindowById(wxID_OK, this))->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
wxString path = txt_filename->GetValue();
// .gcode suffix control
if (!path.Lower().EndsWith(suffix.Lower()))
{
//wxMessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
if (msg_wingow.ShowModal() == wxID_NO)
return;
}
EndDialog(wxID_OK);
});
});
Fit();
CenterOnParent();
finalize();
#ifdef __linux__
// On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
@ -331,6 +325,14 @@ void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect)
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS);
}
void PrintHostQueueDialog::on_sys_color_changed()
{
#ifdef _WIN32
wxGetApp().UpdateDlgDarkUI(this);
wxGetApp().UpdateDVCDarkUI(job_list);
#endif
}
PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx)
{
wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list");

View file

@ -72,6 +72,7 @@ public:
}
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
void on_sys_color_changed() override;
private:
enum Column {

View file

@ -401,6 +401,18 @@ void OptionsSearcher::show_dialog()
search_dialog->Popup();
}
void OptionsSearcher::dlg_sys_color_changed()
{
if (search_dialog)
search_dialog->on_sys_color_changed();
}
void OptionsSearcher::dlg_msw_rescale()
{
if (search_dialog)
search_dialog->msw_rescale();
}
void OptionsSearcher::add_key(const std::string& opt_key, Preset::Type type, const wxString& group, const wxString& category)
{
groups_and_categories[get_key(opt_key, type)] = GroupAndCategory{group, category};
@ -666,7 +678,7 @@ void SearchDialog::OnLeftDown(wxMouseEvent& event)
ProcessSelection(search_list->GetSelection());
}
void SearchDialog::on_dpi_changed(const wxRect& suggested_rect)
void SearchDialog::msw_rescale()
{
const int& em = em_unit();

View file

@ -138,6 +138,8 @@ public:
void sort_options_by_label() { sort_options(); }
void show_dialog();
void dlg_sys_color_changed();
void dlg_msw_rescale();
};
@ -180,9 +182,11 @@ public:
void Popup(wxPoint position = wxDefaultPosition);
void ProcessSelection(wxDataViewItem selection);
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void msw_rescale();
void on_sys_color_changed() override;
protected:
void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); }
};

View file

@ -1,6 +1,7 @@
#include "SendSystemInfoDialog.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/BlacklistedLibraryCheck.hpp"
#include "libslic3r/Platform.hpp"
#include "libslic3r/Utils.hpp"
@ -479,6 +480,26 @@ static std::string generate_system_info_json()
hw_node.add_child("Monitors", monitors_node);
data_node.add_child("Hardware", hw_node);
#ifdef _WIN32
{
pt::ptree blacklisted_node;
std::vector<std::wstring> blacklisted_libraries;
BlacklistedLibraryCheck::get_instance().get_blacklisted(blacklisted_libraries);
for (const std::wstring& wstr : blacklisted_libraries) {
std::string utf8 = boost::nowide::narrow(wstr);
if (size_t last_bs_pos = utf8.find_last_of("\\"); last_bs_pos < utf8.size() - 1) {
// Remove anything before last backslash so we don't send the path to the DLL.
utf8.erase(0, last_bs_pos + 1);
}
pt::ptree node; // Create an unnamed node containing the value
node.put("", utf8);
blacklisted_node.push_back(std::make_pair("", node)); // Add this node to the list.
}
if (! blacklisted_libraries.empty())
data_node.add_child("Blacklisted libraries", blacklisted_node);
}
#endif // _WIN32
pt::ptree opengl_node;
opengl_node.put("Version", OpenGLManager::get_gl_info().get_version());
opengl_node.put("GLSLVersion", OpenGLManager::get_gl_info().get_glsl_version());

View file

@ -4157,6 +4157,7 @@ void TabSLAMaterial::build()
auto page = add_options_page(L("Material"), "resin");
auto optgroup = page->new_optgroup(L("Material"));
optgroup->append_single_option_line("material_colour");
optgroup->append_single_option_line("bottle_cost");
optgroup->append_single_option_line("bottle_volume");
optgroup->append_single_option_line("bottle_weight");
@ -4164,6 +4165,12 @@ void TabSLAMaterial::build()
optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value)
{
if (opt_key == "material_colour") {
update_dirty();
on_value_change(opt_key, value);
return;
}
DynamicPrintConfig new_conf = *m_config;
if (opt_key == "bottle_volume") {

View file

@ -26,7 +26,7 @@ namespace GUI {
static const char* URL_CHANGELOG = "https://files.prusa3d.com/?latest=slicer-stable&lng=%1%";
static const char* URL_DOWNLOAD = "https://www.prusa3d.com/downloads&lng=%1%";
static const char* URL_DOWNLOAD = "https://www.prusa3d.com/slicerweb&lng=%1%";
static const char* URL_DEV = "https://github.com/prusa3d/PrusaSlicer/releases/tag/version_%1%";
static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/PrusaSlicer/wiki/Slic3r-PE-1.40-configuration-update");
@ -89,7 +89,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
MsgDialog(nullptr, force_before_wizard ? _L("Opening Configuration Wizard") : _L("Configuration update"),
force_before_wizard ? _L("PrusaSlicer is not using the newest configuration available.\n"
"Configuration Wizard may not offer the latest printers, filaments and SLA materials to be installed. ") :
_L("Configuration update is available"), wxID_NONE)
_L("Configuration update is available"), wxICON_ERROR)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"Would you like to install it?\n\n"
@ -133,22 +133,14 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto* btn_ok = new wxButton(this, wxID_OK, force_before_wizard ? _L("Install") : "OK");
btn_sizer->Add(btn_ok);
btn_sizer->AddSpacer(HORIZ_SPACING);
add_btn(wxID_OK, true, force_before_wizard ? _L("Install") : "OK");
if (force_before_wizard) {
auto* btn_no_install = new wxButton(this, wxID_ANY, "Don't install");
btn_no_install->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CLOSE); });
btn_sizer->Add(btn_no_install);
btn_sizer->AddSpacer(HORIZ_SPACING);
add_btn(wxID_CLOSE, false, _L("Don't install"));
static_cast<wxButton*>(FindWindowById(wxID_CLOSE, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); });
}
auto* btn_cancel = new wxButton(this, wxID_CANCEL);
btn_sizer->Add(btn_cancel);
btn_ok->SetFocus();
add_btn(wxID_CANCEL);
wxGetApp().UpdateDlgDarkUI(this);
Fit();
finalize();
}
MsgUpdateConfig::~MsgUpdateConfig() {}
@ -156,7 +148,7 @@ MsgUpdateConfig::~MsgUpdateConfig() {}
//MsgUpdateForced
MsgUpdateForced::MsgUpdateForced(const std::vector<Update>& updates) :
MsgDialog(nullptr, wxString::Format(_(L("%s incompatibility")), SLIC3R_APP_NAME), _(L("You must install a configuration update.")) + " ", wxID_NONE)
MsgDialog(nullptr, wxString::Format(_(L("%s incompatibility")), SLIC3R_APP_NAME), _(L("You must install a configuration update.")) + " ", wxOK | wxICON_ERROR)
{
auto* text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L(
"%s will now start updates. Otherwise it won't be able to start.\n\n"
@ -165,7 +157,6 @@ MsgUpdateForced::MsgUpdateForced(const std::vector<Update>& updates) :
"Updated configuration bundles:"
)), SLIC3R_APP_NAME));
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192));
text->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
content_sizer->Add(text);
@ -198,19 +189,12 @@ MsgUpdateForced::MsgUpdateForced(const std::vector<Update>& updates) :
content_sizer->Add(versions);
content_sizer->AddSpacer(2 * VERT_SPACING);
auto* btn_exit = new wxButton(this, wxID_EXIT, wxString::Format(_(L("Exit %s")), SLIC3R_APP_NAME));
btn_sizer->Add(btn_exit);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto* btn_ok = new wxButton(this, wxID_OK);
btn_sizer->Add(btn_ok);
btn_ok->SetFocus();
auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); };
btn_exit->Bind(wxEVT_BUTTON, exiter);
btn_ok->Bind(wxEVT_BUTTON, exiter);
add_btn(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
for (auto ID : { wxID_EXIT, wxID_OK })
static_cast<wxButton*>(FindWindowById(ID, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
Fit();
finalize();
}
MsgUpdateForced::~MsgUpdateForced() {}
@ -219,10 +203,8 @@ MsgUpdateForced::~MsgUpdateForced() {}
MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats) :
MsgDialog(nullptr, wxString::Format(_(L("%s incompatibility")), SLIC3R_APP_NAME),
wxString::Format(_(L("%s configuration is incompatible")), SLIC3R_APP_NAME), wxID_NONE)
wxString::Format(_(L("%s configuration is incompatible")), SLIC3R_APP_NAME), wxICON_ERROR)
{
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192));
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L(
"This version of %s is not compatible with currently installed configuration bundles.\n"
"This probably happened as a result of running an older %s after using a newer one.\n\n"
@ -254,18 +236,13 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_exit = new wxButton(this, wxID_EXIT, wxString::Format(_(L("Exit %s")), SLIC3R_APP_NAME));
btn_sizer->Add(btn_exit);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure")));
btn_sizer->Add(btn_reconf);
btn_exit->SetFocus();
add_btn(wxID_REPLACE, true, _L("Re-configure"));
add_btn(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); };
btn_exit->Bind(wxEVT_BUTTON, exiter);
btn_reconf->Bind(wxEVT_BUTTON, exiter);
for (auto ID : {wxID_EXIT, wxID_REPLACE})
static_cast<wxButton*>(FindWindowById(ID, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
Fit();
finalize();
}
MsgDataIncompatible::~MsgDataIncompatible() {}
@ -303,9 +280,7 @@ MsgDataLegacy::MsgDataLegacy() :
content_sizer->Add(link);
content_sizer->AddSpacer(VERT_SPACING);
wxGetApp().UpdateDlgDarkUI(this);
Fit();
finalize();
}
MsgDataLegacy::~MsgDataLegacy() {}
@ -314,7 +289,7 @@ MsgDataLegacy::~MsgDataLegacy() {}
// MsgNoUpdate
MsgNoUpdates::MsgNoUpdates() :
MsgDialog(nullptr, _(L("Configuration updates")), _(L("No updates available")))
MsgDialog(nullptr, _(L("Configuration updates")), _(L("No updates available")), wxICON_ERROR | wxOK)
{
auto* text = new wxStaticText(this, wxID_ANY, wxString::Format(
@ -327,11 +302,7 @@ MsgNoUpdates::MsgNoUpdates() :
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192));
wxGetApp().UpdateDlgDarkUI(this);
Fit();
finalize();
}
MsgNoUpdates::~MsgNoUpdates() {}

View file

@ -419,7 +419,7 @@ int mode_icon_px_size()
wxBitmap create_menu_bitmap(const std::string& bmp_name)
{
return create_scaled_bitmap(bmp_name, nullptr, 16, false, true);
return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true);
}
// win is used to get a correct em_unit value
@ -429,6 +429,7 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in,
wxWindow *win/* = nullptr*/,
const int px_cnt/* = 16*/,
const bool grayscale/* = false*/,
const std::string& new_color/* = std::string()*/, // color witch will used instead of orange
const bool menu_bitmap/* = false*/)
{
static Slic3r::GUI::BitmapCache cache;
@ -446,7 +447,7 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in,
Slic3r::GUI::wxGetApp().dark_mode();
// Try loading an SVG first, then PNG if SVG is not found:
wxBitmap *bmp = cache.load_svg(bmp_name, width, height, grayscale, dark_mode);
wxBitmap *bmp = cache.load_svg(bmp_name, width, height, grayscale, dark_mode, new_color);
if (bmp == nullptr) {
bmp = cache.load_png(bmp_name, width, height, grayscale);
}

View file

@ -51,7 +51,9 @@ int mode_icon_px_size();
wxBitmap create_menu_bitmap(const std::string& bmp_name);
wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr,
const int px_cnt = 16, const bool grayscale = false, const bool menu_bitmap = false);
const int px_cnt = 16, const bool grayscale = false,
const std::string& new_color = std::string(), // color witch will used instead of orange
const bool menu_bitmap = false);
std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false);

View file

@ -137,8 +137,7 @@ struct Updates
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_ALPHA_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_BETA_VERSION_ONLINE, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
struct PresetUpdater::priv
{
@ -189,7 +188,7 @@ PresetUpdater::priv::priv()
// Pull relevant preferences from AppConfig
void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
{
enabled_version_check = app_config->get("version_check") == "1";
enabled_version_check = app_config->get("notify_release") != "none";
version_check_url = app_config->version_check_url();
enabled_config_update = app_config->get("preset_update") == "1" && !app_config->legacy_datadir();
}
@ -276,8 +275,9 @@ void PresetUpdater::priv::parse_version_string(const std::string& body) const
version = body.substr(0, first_nl_pos);
else
version = body;
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
boost::optional<Semver> release_version = Semver::parse(version);
if (!release_version) {
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
@ -286,6 +286,7 @@ void PresetUpdater::priv::parse_version_string(const std::string& body) const
GUI::wxGetApp().QueueEvent(evt);
// alpha / beta version
std::vector<std::string> prerelease_versions;
size_t nexn_nl_pos = first_nl_pos;
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
const auto last_nl_pos = nexn_nl_pos;
@ -300,28 +301,36 @@ void PresetUpdater::priv::parse_version_string(const std::string& body) const
if (line.substr(0, 6) == "alpha=") {
version = line.substr(6);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of alpha release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_ALPHA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
prerelease_versions.emplace_back(version);
// beta
}
else if (line.substr(0, 5) == "beta=") {
version = line.substr(5);
if (!Semver::parse(version)) {
BOOST_LOG_TRIVIAL(warning) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
return;
}
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version of beta release: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_BETA_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
prerelease_versions.emplace_back(version);
}
}
// find recent version that is newer than last full release.
boost::optional<Semver> recent_version;
for (const std::string& ver_string : prerelease_versions) {
boost::optional<Semver> ver = Semver::parse(ver_string);
if (ver && *release_version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
recent_version = ver;
version = ver_string;
}
}
if (recent_version) {
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
evt->SetString(GUI::from_u8(version));
GUI::wxGetApp().QueueEvent(evt);
}
}
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
@ -741,8 +750,8 @@ void PresetUpdater::sync(PresetBundle *preset_bundle)
void PresetUpdater::slic3r_update_notify()
{
if (! p->enabled_version_check) { return; }
if (! p->enabled_version_check)
return;
auto* app_config = GUI::wxGetApp().app_config;
const auto ver_online_str = app_config->get("version_online");
const auto ver_online = Semver::parse(ver_online_str);
@ -754,7 +763,7 @@ void PresetUpdater::slic3r_update_notify()
GUI::MsgUpdateSlic3r notification(Slic3r::SEMVER, *ver_online);
notification.ShowModal();
if (notification.disable_version_check()) {
app_config->set("version_check", "0");
app_config->set("notify_release", "none");
p->enabled_version_check = false;
}
}

View file

@ -63,7 +63,6 @@ private:
};
wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_ALPHA_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_BETA_VERSION_ONLINE, wxCommandEvent);
wxDECLARE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
}
#endif

View file

@ -107,7 +107,7 @@ bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error
bool res = true;
auto url = make_url((boost::format("printer/model/%1%") % port).str());
auto url = upload_data.start_print?make_url((boost::format("printer/job/%1%") % port).str()):make_url((boost::format("printer/model/%1%") % port).str());
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, group: %7%")
% name
@ -125,6 +125,10 @@ bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error
http.form_add("group", upload_data.group);
}
if(upload_data.start_print) {
http.form_add("name", upload_filename.string());
}
http.form_add("a", "upload")
.form_add_file("filename", upload_data.source_path.string(), upload_filename.string())
.on_complete([&](std::string body, unsigned status) {

View file

@ -27,7 +27,7 @@ public:
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
bool has_auto_discovery() const override { return false; }
bool can_test() const override { return true; }
bool can_start_print() const override { return false; }
bool can_start_print() const override { return true; }
bool supports_multiple_printers() const override { return true; }
std::string get_host() const override { return host; }