Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_imgui_slider

This commit is contained in:
enricoturri1966 2021-11-08 13:44:13 +01:00
commit 67533da405
24 changed files with 523 additions and 255 deletions

View file

@ -135,7 +135,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname,
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings, ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
double px_w, double px_h) 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) { for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points; Polygon poly; Points &pts = poly.points;
@ -147,7 +147,7 @@ ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
polys.emplace_back(poly); polys.emplace_back(poly);
} }
// reverse the raster transformations // TODO: Is a union necessary?
return union_ex(polys); 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()}; png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
if (!png::decode_png(rb, img)) return; 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); ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
// Invert the raster transformations indicated in // Invert the raster transformations indicated in the profile metadata
// the profile metadata
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
slices[i] = std::move(expolys); slices[i] = std::move(expolys);
@ -310,7 +310,24 @@ ConfigSubstitutions import_sla_archive(
std::string exclude_entries{"thumbnail"}; std::string exclude_entries{"thumbnail"};
ArchiveData arch = extract_sla_archive(zipfname, exclude_entries); ArchiveData arch = extract_sla_archive(zipfname, exclude_entries);
DynamicPrintConfig profile_in, profile_use; 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 // 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. // then replace it with the readed profile to report that it was empty.

View file

@ -1263,10 +1263,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
instances[instance]->get_mirror() instances[instance]->get_mirror()
); );
z -= instances[instance]->get_offset()(2); z -= instances[instance]->get_offset().z();
// Lower part per-instance bounding boxes // Displacement (in instance coordinates) to be applied to place the upper parts
std::vector<BoundingBoxf3> lower_bboxes { instances.size() }; Vec3d local_displace = Vec3d::Zero();
for (ModelVolume *volume : volumes) { for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix(); const auto volume_matrix = volume->get_matrix();
@ -1287,7 +1287,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
lower->add_volume(*volume); lower->add_volume(*volume);
} }
else if (! volume->mesh().empty()) { else if (! volume->mesh().empty()) {
// Transform the mesh by the combined transformation matrix. // Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed. // Flip the triangles in case the composite transformation is left handed.
TriangleMesh mesh(volume->mesh()); TriangleMesh mesh(volume->mesh());
@ -1327,13 +1326,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
assert(vol->config.id() != volume->config.id()); assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material()); vol->set_material(volume->material_id(), *volume->material());
// Compute the lower part instances' bounding boxes to figure out where to place // Compute the displacement (in instance coordinates) to be applied to place the upper parts
// the upper part // The upper part displacement is set to half of the lower part bounding box
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { // this is done in hope at least a part of the upper part will always be visible and draggable
for (size_t i = 0; i < instances.size(); i++) { local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true));
}
}
} }
} }
} }
@ -1341,17 +1337,18 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
ModelObjectPtrs res; ModelObjectPtrs res;
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
upper->invalidate_bounding_box(); if (!upper->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
upper->center_around_origin(); upper->center_around_origin();
upper->translate_instances(-upper->origin_translation);
upper->origin_translation = Vec3d::Zero();
}
// Reset instance transformation except offset and Z-rotation // 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]; auto &instance = upper->instances[i];
const Vec3d offset = instance->get_offset(); const Vec3d offset = instance->get_offset();
const double rot_z = instance->get_rotation()(2); const double rot_z = instance->get_rotation().z();
// The upper part displacement is set to half of the lower part bounding box const Vec3d displace = Geometry::assemble_transform(Vec3d::Zero(), instance->get_rotation()) * local_displace;
// 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));
instance->set_transformation(Geometry::Transformation()); instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset + displace); instance->set_offset(offset + displace);
@ -1361,14 +1358,16 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
res.push_back(upper); res.push_back(upper);
} }
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
lower->invalidate_bounding_box(); if (!lower->origin_translation.isApprox(Vec3d::Zero()) && instances[instance]->get_offset().isApprox(Vec3d::Zero())) {
lower->center_around_origin(); lower->center_around_origin();
lower->translate_instances(-lower->origin_translation);
lower->origin_translation = Vec3d::Zero();
}
// Reset instance transformation except offset and Z-rotation // Reset instance transformation except offset and Z-rotation
for (auto *instance : lower->instances) { for (auto *instance : lower->instances) {
const Vec3d offset = instance->get_offset(); 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_transformation(Geometry::Transformation());
instance->set_offset(offset); instance->set_offset(offset);
instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z)); instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));

View file

@ -1113,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, // 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. // start selection always left most edges for every node to construct CCW polygons.
// Assumes that graph is planar (without self-intersection edges) // 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); 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 // When there is no next arc, then is returned original_arc or edge with is marked as used
@ -1153,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]; }); 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) { for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) {
const MMU_Graph::Node &node = graph.nodes[node_idx]; const MMU_Graph::Node &node = graph.nodes[node_idx];
@ -1183,12 +1183,11 @@ static std::vector<std::pair<Polygon, size_t>> extract_colored_segments(const MM
p_arc = &next; p_arc = &next;
} while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx])); } 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 (Polygon poly = to_polygon(face_lines); poly.is_counter_clockwise() && poly.is_valid())
if (poly.is_counter_clockwise() && poly.is_valid()) expolygons_segments[arc.color].emplace_back(std::move(poly));
polygons_segments.emplace_back(poly, arc.color);
} }
} }
return polygons_segments; return expolygons_segments;
} }
// Used in remove_multiple_edges_in_vertices() // Used in remove_multiple_edges_in_vertices()
@ -1270,7 +1269,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
} }
static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons, static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
std::vector<std::vector<std::pair<ExPolygon, size_t>>> &segmented_regions, std::vector<std::vector<ExPolygons>> &segmented_regions,
const float cut_width, const float cut_width,
const std::function<void()> &throw_on_cancel_callback) const std::function<void()> &throw_on_cancel_callback)
{ {
@ -1278,12 +1277,11 @@ static void cut_segmented_layers(const std::vector<ExPolygons>
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) { 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) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback(); throw_on_cancel_callback();
std::vector<std::pair<ExPolygon, size_t>> segmented_regions_cuts; const size_t num_extruders_plus_one = segmented_regions[layer_idx].size();
for (const std::pair<ExPolygon, size_t> &colored_expoly : segmented_regions[layer_idx]) { std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id
ExPolygons cut_colored_expoly = diff_ex(colored_expoly.first, offset_ex(input_expolygons[layer_idx], cut_width)); for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx)
for (ExPolygon &expoly : cut_colored_expoly) if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty())
segmented_regions_cuts.emplace_back(std::move(expoly), colored_expoly.second); 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); segmented_regions[layer_idx] = std::move(segmented_regions_cuts);
} }
}); // end of parallel_for }); // end of parallel_for
@ -1323,7 +1321,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
// Project upwards pointing painted triangles over top surfaces, // Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces. // project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders); 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(); Transform3d object_trafo = print_object.trafo_centered();
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM #ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
@ -1532,31 +1530,42 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
return triangles_by_color_merged; return triangles_by_color_merged;
} }
static std::vector<std::vector<std::pair<ExPolygon, size_t>>> merge_segmented_layers( static std::vector<std::vector<ExPolygons>> merge_segmented_layers(
const std::vector<std::vector<std::pair<ExPolygon, size_t>>> &segmented_regions, const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers, std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_extruders,
const std::function<void()> &throw_on_cancel_callback) 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"; 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 (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(); throw_on_cancel_callback();
// Zero is the default color of the volume. if (!segmented_regions[layer_idx][extruder_id].empty()) {
if(colored_expoly.second == 0) ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
continue; for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers)
ExPolygons cut_colored_expoly = {colored_expoly.first}; if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
for (const std::vector<ExPolygons> &top_and_bottom_layer : top_and_bottom_layers) segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
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][extruder_id - 1] = std::move(segmented_regions_trimmed);
segmented_regions_merged[layer_idx].emplace_back(std::move(ex_poly), colored_expoly.second - 1);
} }
for (size_t color_idx = 1; color_idx < top_and_bottom_layers.size(); ++color_idx) if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) {
for (ExPolygon &expoly : top_and_bottom_layers[color_idx][layer_idx]) bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
segmented_regions_merged[layer_idx].emplace_back(std::move(expoly), color_idx - 1); 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 }); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - end";
@ -1565,7 +1574,7 @@ static std::vector<std::vector<std::pair<ExPolygon, size_t>>> merge_segmented_la
} }
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS #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"}; const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"};
coordf_t stroke_width = scale_(0.05); coordf_t stroke_width = scale_(0.05);
@ -1574,12 +1583,12 @@ static void export_regions_to_svg(const std::string &path, const std::vector<std
::Slic3r::SVG svg(path.c_str(), bbox); ::Slic3r::SVG svg(path.c_str(), bbox);
svg.draw_outline(lslices, "green", "lime", stroke_width); svg.draw_outline(lslices, "green", "lime", stroke_width);
for (const std::pair<ExPolygon, size_t> &region : regions) { for (const ExPolygons &by_extruder : regions) {
int region_color = int(region.second); size_t extrude_idx = &by_extruder - &regions.front();
if (region_color >= 0 && region_color < int(colors.size())) if (extrude_idx >= 0 && extrude_idx < int(colors.size()))
svg.draw(region.first, colors[region_color]); svg.draw(by_extruder, colors[extrude_idx], stroke_width);
else else
svg.draw(region.first, "black"); svg.draw(by_extruder, "black", stroke_width);
} }
} }
#endif // MMU_SEGMENTATION_DEBUG_REGIONS #endif // MMU_SEGMENTATION_DEBUG_REGIONS
@ -1667,20 +1676,23 @@ static bool has_layer_only_one_color(const std::vector<std::vector<ColoredLine>>
return true; 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()); const size_t num_extruders = print_object.print()->config().nozzle_diameter.size();
std::vector<std::vector<PaintedLine>> painted_lines(print_object.layers().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::array<std::mutex, 64> painted_lines_mutex;
std::vector<EdgeGrid::Grid> edge_grids(print_object.layers().size()); std::vector<EdgeGrid::Grid> edge_grids(num_layers);
const ConstLayerPtrsAdaptor layers = print_object.layers(); const ConstLayerPtrsAdaptor layers = print_object.layers();
std::vector<ExPolygons> input_expolygons(layers.size()); std::vector<ExPolygons> input_expolygons(num_layers);
throw_on_cancel_callback(); throw_on_cancel_callback();
// Merge all regions and remove small holes // Merge all regions and remove small holes
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - begin"; 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) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback(); throw_on_cancel_callback();
ExPolygons ex_polygons; ExPolygons ex_polygons;
@ -1711,7 +1723,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
}); // end of parallel_for }); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end"; 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(); throw_on_cancel_callback();
BoundingBox bbox(get_extents(layers[layer_idx]->regions())); BoundingBox bbox(get_extents(layers[layer_idx]->regions()));
bbox.merge(get_extents(input_expolygons[layer_idx])); bbox.merge(get_extents(input_expolygons[layer_idx]));
@ -1723,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"; BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin";
for (const ModelVolume *mv : print_object.model_object()->volumes) { 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 + 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) {
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) {
for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) {
throw_on_cancel_callback(); throw_on_cancel_callback();
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
@ -1732,7 +1743,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
continue; continue;
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>(); 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) { for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max(); float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest(); float max_z = std::numeric_limits<float>::lowest();
@ -1748,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(); }); std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
// Find lowest slice not below the triangle. // 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; }); [](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; }); [](float z, const Layer *l1) { return z < l1->slice_z; });
--last_layer; --last_layer;
for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) { for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) {
const Layer *layer = *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()) if (input_expolygons[layer_idx].empty() || facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z())
continue; continue;
@ -1799,7 +1810,7 @@ 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(); }); << 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"; 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) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback(); throw_on_cancel_callback();
if (!painted_lines[layer_idx].empty()) { if (!painted_lines[layer_idx].empty()) {
@ -1832,8 +1843,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
assert(!color_poly.front().empty()); assert(!color_poly.front().empty());
if (has_layer_only_one_color(color_poly)) { 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. // 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][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
segmented_regions[layer_idx].emplace_back(ex_polygon, size_t(color_poly.front().front().color));
} else { } else {
MMU_Graph graph = build_graph(layer_idx, color_poly); MMU_Graph graph = build_graph(layer_idx, color_poly);
remove_multiple_edges_in_vertices(graph, color_poly); remove_multiple_edges_in_vertices(graph, color_poly);
@ -1846,9 +1856,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
} }
#endif // MMU_SEGMENTATION_DEBUG_GRAPH #endif // MMU_SEGMENTATION_DEBUG_GRAPH
std::vector<std::pair<Polygon, size_t>> segmentation = extract_colored_segments(graph); segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders);
for (std::pair<Polygon, size_t> &region : segmentation)
segmented_regions[layer_idx].emplace_back(std::move(region));
} }
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS #ifdef MMU_SEGMENTATION_DEBUG_REGIONS
@ -1868,11 +1876,11 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
throw_on_cancel_callback(); throw_on_cancel_callback();
} }
// return segmented_regions; // 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); 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(); 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(); throw_on_cancel_callback();
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS #ifdef MMU_SEGMENTATION_DEBUG_REGIONS

View file

@ -11,7 +11,7 @@ class PrintObject;
class ExPolygon; class ExPolygon;
// Returns MMU segmentation based on painting in MMU segmentation gizmo // 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 } // 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 { static std::vector<std::string> s_Preset_sla_material_options {
"material_colour",
"material_type", "material_type",
"initial_layer_height", "initial_layer_height",
"bottle_cost", "bottle_cost",

View file

@ -3163,6 +3163,13 @@ void PrintConfigDef::init_sla_params()
// SLA Material settings. // SLA Material settings.
def = this->add("material_colour", coStrings);
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 ConfigOptionStrings{ "#29B2B2" });
def = this->add("material_type", coString); def = this->add("material_type", coString);
def->label = L("SLA material type"); def->label = L("SLA material type");
def->tooltip = 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) static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{ {
// Returns MMU segmentation based on painting in MMU segmentation gizmo // 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()); assert(segmentation.size() == print_object.layer_count());
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), 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; bool layer_split = false;
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) { for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
ByExtruder &region = by_extruder[extruder_id]; ByExtruder &region = by_extruder[extruder_id];
for (const std::pair<ExPolygon, size_t> &colored_polygon : segmentation[layer_id]) append(region.expolygons, std::move(segmentation[layer_id][extruder_id]));
if (colored_polygon.second == extruder_id)
region.expolygons.emplace_back(std::move(colored_polygon.first));
if (! region.expolygons.empty()) { if (! region.expolygons.empty()) {
region.bbox = get_extents(region.expolygons); region.bbox = get_extents(region.expolygons);
layer_split = true; layer_split = true;
@ -632,6 +630,13 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
if (mine.empty()) if (mine.empty())
break; 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()) { if (! mine.empty()) {
ByRegion &dst = by_region[layerm.region().print_object_region_id()]; ByRegion &dst = by_region[layerm.region().print_object_region_id()];
if (dst.expolygons.empty()) { if (dst.expolygons.empty()) {

View file

@ -1,3 +1,4 @@
#include <numeric>
#include "SlicesToTriangleMesh.hpp" #include "SlicesToTriangleMesh.hpp"
@ -22,11 +23,16 @@ inline indexed_triangle_set wall_strip(const Polygon &poly,
ret.vertices.reserve(ret.vertices.size() + 2 *offs); 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) 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) 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) { for (size_t i = startidx + 1; i < startidx + offs; ++i) {
ret.indices.emplace_back(i - 1, i, i + offs - 1); 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 &upper = slices[i + 1];
const ExPolygons &lower = slices[i]; const ExPolygons &lower = slices[i];
ExPolygons dff1 = diff_ex(lower, upper); // Small 0 area artefacts can be created by diff_ex, and the
ExPolygons dff2 = diff_ex(upper, lower); // tesselation also can create 0 area triangles. These will be removed
its_merge(layers[i], triangulate_expolygons_3d(dff1, grid[i], NORMALS_UP)); // by its_remove_degenerate_faces.
its_merge(layers[i], triangulate_expolygons_3d(dff2, grid[i], NORMALS_DOWN)); 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])); 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 ) { auto merge_fn = []( const indexed_triangle_set &a, const indexed_triangle_set &b ) {
@ -99,22 +107,17 @@ indexed_triangle_set slices_to_mesh(
auto ret = execution::reduce(ex_tbb, layers.begin(), layers.end(), auto ret = execution::reduce(ex_tbb, layers.begin(), layers.end(),
indexed_triangle_set{}, merge_fn); 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, triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN));
its_merge(ret, straight_walls(slices.front(), zmin, grid.front())); its_merge(ret, straight_walls(slices.front(), zmin, grid.front()));
its_merge(ret, triangulate_expolygons_3d(slices.back(), grid.back(), NORMALS_UP)); 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; return ret;
} }
@ -124,11 +127,9 @@ void slices_to_mesh(indexed_triangle_set & mesh,
double lh, double lh,
double ilh) double ilh)
{ {
std::vector<indexed_triangle_set> wall_meshes(slices.size());
std::vector<float> grid(slices.size(), zmin + ilh); std::vector<float> grid(slices.size(), zmin + ilh);
for (size_t i = 1; i < grid.size(); ++i) for (size_t i = 1; i < grid.size(); ++i) grid[i] = grid[i - 1] + lh;
grid[i] = grid[i - 1] + lh;
indexed_triangle_set cntr = slices_to_mesh(slices, zmin, grid); indexed_triangle_set cntr = slices_to_mesh(slices, zmin, grid);
its_merge(mesh, cntr); its_merge(mesh, cntr);

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 its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit)
{ {
int last = 0; auto it = std::remove_if(its.indices.begin(), its.indices.end(), [](auto &face) {
for (int i = 0; i < int(its.indices.size()); ++ i) { return face(0) == face(1) || face(0) == face(2) || face(1) == face(2);
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) int removed = std::distance(it, its.indices.end());
its.indices[last] = its.indices[i]; its.indices.erase(it, its.indices.end());
++ last;
} if (removed && shrink_to_fit)
}
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(); its.indices.shrink_to_fit();
}
return removed; return removed;
} }

View file

@ -22,6 +22,7 @@
#include "../GUI/GUI_App.hpp" #include "../GUI/GUI_App.hpp"
#include "../GUI/I18N.hpp" #include "../GUI/I18N.hpp"
#include "../GUI/MainFrame.hpp" #include "../GUI/MainFrame.hpp"
#include "../GUI/MsgDialog.hpp"
#include <wx/richmsgdlg.h> #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); SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
return true; return true;
} catch (std::exception &err) { } 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 has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
_L("PrusaSlicer error"), _L("PrusaSlicer error"),
wxYES_NO); wxYES_NO);

View file

@ -1131,6 +1131,23 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
if (config == nullptr) if (config == nullptr)
return; return;
unsigned char rgb[3];
std::vector<Color> colors;
if (static_cast<PrinterTechnology>(config->opt_int("printer_technology")) == ptSLA)
{
const ConfigOptionStrings* resin_clr = dynamic_cast<const ConfigOptionStrings*>(config->option("material_colour"));
if (resin_clr == nullptr)
return;
assert(resin_clr->values.size() == 1);
colors.resize(1);
const std::string& txt_color = config->opt_string("material_colour", 0);
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
colors[0].set(txt_color, rgb);
}
else
{
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("extruder_colour")); const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("extruder_colour"));
if (extruders_opt == nullptr) if (extruders_opt == nullptr)
return; return;
@ -1142,10 +1159,8 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size()); unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
if (colors_count == 0) if (colors_count == 0)
return; return;
colors.resize(colors_count);
std::vector<Color> colors(colors_count);
unsigned char rgb[3];
for (unsigned int i = 0; i < colors_count; ++i) { for (unsigned int i = 0; i < colors_count; ++i) {
const std::string& txt_color = config->opt_string("extruder_colour", i); const std::string& txt_color = config->opt_string("extruder_colour", i);
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
@ -1156,6 +1171,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
colors[i].set(txt_color, rgb); colors[i].set(txt_color, rgb);
} }
} }
}
for (GLVolume* volume : volumes) { for (GLVolume* volume : volumes) {
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0))

View file

@ -555,7 +555,7 @@ PagePrinters::PagePrinters(ConfigWizard *parent,
wizard_p()->on_printer_pick(this, evt); wizard_p()->on_printer_pick(this, evt);
}); });
append(new wxStaticLine(this)); append(new StaticLine(this));
append(picker); append(picker);
printer_pickers.push_back(picker); printer_pickers.push_back(picker);
@ -2800,11 +2800,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
auto *vsizer = new wxBoxSizer(wxVERTICAL); auto *vsizer = new wxBoxSizer(wxVERTICAL);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL); auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticLine* hline = nullptr; auto* hline = new StaticLine(this);
#ifdef _MSW_DARK_MODE
if (!NppDarkMode::IsEnabled())
#endif //_MSW_DARK_MODE
hline = new wxStaticLine(this);
p->btnsizer = new wxBoxSizer(wxHORIZONTAL); p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
// Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
@ -2880,8 +2876,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->index->go_to(size_t{0}); p->index->go_to(size_t{0});
vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
if (hline) vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING);
vsizer->Add(hline, 0, wxEXPAND);
vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
SetSizer(vsizer); SetSizer(vsizer);

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. // 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. // 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. // 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}); valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}, {"FLIP_TRIANGLE_NORMALS"sv});
else else
valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"}); valid &= append_shader("mm_gouraud", {"mm_gouraud.vs", "mm_gouraud.fs"});

View file

@ -411,7 +411,7 @@ bool static check_old_linux_datadir(const wxString& app_name) {
"location again.\n\n" "location again.\n\n"
"What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); "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()); 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")); dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application"));
if (dlg.ShowModal() != wxID_NO) if (dlg.ShowModal() != wxID_NO)
return false; return false;
@ -846,7 +846,7 @@ bool GUI_App::check_older_app_config(Semver current_version, bool backup)
return false; return false;
BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path; BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path;
// ask about using older data folder // ask about using older data folder
wxRichMessageDialog msg(nullptr, backup ? RichMessageDialog msg(nullptr, backup ?
wxString::Format(_L("PrusaSlicer detected another configuration folder at %s." wxString::Format(_L("PrusaSlicer detected another configuration folder at %s."
"\nIts version is %s." "\nIts version is %s."
"\nLast version you used in current configuration folder is %s." "\nLast version you used in current configuration folder is %s."
@ -936,7 +936,7 @@ bool GUI_App::on_init_inner()
// win32 build on win64 and viceversa // win32 build on win64 and viceversa
#ifdef _WIN64 #ifdef _WIN64
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "") { 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." _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/." "\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
"\nDo you wish to continue?"), "\nDo you wish to continue?"),
@ -946,7 +946,7 @@ bool GUI_App::on_init_inner()
} }
#elif _WIN32 #elif _WIN32
if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { 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." _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/." "\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/."
"\nDo you wish to continue?"), "\nDo you wish to continue?"),
@ -991,7 +991,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(); bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
if (!msg.empty() && !ssl_accept) { if (!msg.empty() && !ssl_accept) {
wxRichMessageDialog RichMessageDialog
dlg(nullptr, dlg(nullptr,
wxString::Format(_L("%s\nDo you want to continue?"), msg), wxString::Format(_L("%s\nDo you want to continue?"), msg),
"PrusaSlicer", wxICON_QUESTION | wxYES_NO); "PrusaSlicer", wxICON_QUESTION | wxYES_NO);
@ -1620,6 +1620,7 @@ void GUI_App::update_ui_from_settings()
m_force_colors_update = false; m_force_colors_update = false;
mainframe->force_color_changed(); mainframe->force_color_changed();
mainframe->diff_dialog.force_color_changed(); mainframe->diff_dialog.force_color_changed();
mainframe->printhost_queue_dlg()->force_color_changed();
#ifdef _MSW_DARK_MODE #ifdef _MSW_DARK_MODE
update_scrolls(mainframe); update_scrolls(mainframe);
#endif //_MSW_DARK_MODE #endif //_MSW_DARK_MODE
@ -2855,7 +2856,7 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/*
bool launch = true; bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) { 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")); dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal(); int answer = dialog.ShowModal();
launch = answer == wxID_YES; launch = answer == wxID_YES;

View file

@ -2044,8 +2044,7 @@ void ObjectList::split()
void ObjectList::merge(bool to_multipart_object) void ObjectList::merge(bool to_multipart_object)
{ {
// merge selected objects to the 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) auto get_object_idxs = [this](std::vector<int>& obj_idxs, wxDataViewItemArray& sels)
{ {
// check selections and split instances to the separated objects... // check selections and split instances to the separated objects...
@ -2056,8 +2055,7 @@ void ObjectList::merge(bool to_multipart_object)
break; break;
} }
if (!instance_selection) if (!instance_selection) {
{
for (wxDataViewItem item : sels) { for (wxDataViewItem item : sels) {
assert(m_objects_model->GetItemType(item) & itObject); assert(m_objects_model->GetItemType(item) & itObject);
obj_idxs.emplace_back(m_objects_model->GetIdByItem(item)); 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::map<int, std::set<int>> sel_map;
std::set<int> empty_set; std::set<int> empty_set;
for (wxDataViewItem item : sels) { 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 obj_idx = m_objects_model->GetIdByItem(item);
int inst_cnt = (*m_objects)[obj_idx]->instances.size(); int inst_cnt = (*m_objects)[obj_idx]->instances.size();
if (inst_cnt == 1) 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 // all objects, created from the instances will be added to the end of list
int new_objects_cnt = 0; // count of this new objects 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; int obj_idx = map_item.first;
// object with just 1 instance // object with just 1 instance
if (map_item.second.empty()) { if (map_item.second.empty()) {
@ -2148,37 +2144,36 @@ void ObjectList::merge(bool to_multipart_object)
new_object->name = _u8L("Merged"); new_object->name = _u8L("Merged");
ModelConfig &config = new_object->config; ModelConfig &config = new_object->config;
for (int obj_idx : obj_idxs) for (int obj_idx : obj_idxs) {
{
ModelObject* object = (*m_objects)[obj_idx]; ModelObject* object = (*m_objects)[obj_idx];
const Geometry::Transformation& transformation = object->instances[0]->get_transformation(); const Geometry::Transformation& transformation = object->instances[0]->get_transformation();
Vec3d scale = transformation.get_scaling_factor(); const Vec3d scale = transformation.get_scaling_factor();
Vec3d mirror = transformation.get_mirror(); const Vec3d mirror = transformation.get_mirror();
Vec3d rotation = transformation.get_rotation(); const Vec3d rotation = transformation.get_rotation();
if (object->id() == (*m_objects)[obj_idxs.front()]->id()) if (object->id() == (*m_objects)[obj_idxs.front()]->id())
new_object->add_instance(); 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 // merge volumes
for (const ModelVolume* volume : object->volumes) { for (const ModelVolume* volume : object->volumes) {
ModelVolume* new_volume = new_object->add_volume(*volume); ModelVolume* new_volume = new_object->add_volume(*volume);
//set rotation //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); new_volume->set_rotation(vol_rot);
// set scale // 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); new_volume->set_scaling_factor(vol_sc_fact);
// set mirror // 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); new_volume->set_mirror(vol_mirror);
// set offset // 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_volume->set_offset(vol_offset);
} }
new_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); 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) for (const auto& range : object->layer_config_ranges)
new_object->layer_config_ranges.emplace(range); 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 selected objects
remove(); remove();
@ -2221,8 +2221,7 @@ void ObjectList::merge(bool to_multipart_object)
} }
// merge all parts to the one single object // merge all parts to the one single object
// all part's settings will be lost // all part's settings will be lost
else else {
{
wxDataViewItem item = GetSelection(); wxDataViewItem item = GetSelection();
if (!item) if (!item)
return; 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) void ObjectManipulation::change_scale_value(int axis, double value)
{ {
if (value <= 0.0)
return;
if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON) if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON)
return; return;
@ -882,6 +885,9 @@ void ObjectManipulation::change_scale_value(int axis, double value)
void ObjectManipulation::change_size_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) if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON)
return; return;
@ -947,10 +953,26 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
change_position_value(axis, new_value); change_position_value(axis, new_value);
else if (opt_key == "rotation") else if (opt_key == "rotation")
change_rotation_value(axis, new_value); change_rotation_value(axis, new_value);
else if (opt_key == "scale") else if (opt_key == "scale") {
if (new_value > 0.0)
change_scale_value(axis, new_value); change_scale_value(axis, new_value);
else if (opt_key == "size") 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); 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) void ObjectManipulation::set_uniform_scaling(const bool new_value)

View file

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

View file

@ -27,7 +27,7 @@ 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, wxWindowID button_id, wxBitmap bitmap)
: wxDialog(parent ? parent : dynamic_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) : 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)) , content_sizer(new wxBoxSizer(wxVERTICAL))
, btn_sizer(new wxBoxSizer(wxHORIZONTAL)) , 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->SetFont(wxGetApp().normal_font());
this->CenterOnParent(); this->CenterOnParent();
auto *main_sizer = new wxBoxSizer(wxVERTICAL);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL); auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *rightsizer = new wxBoxSizer(wxVERTICAL); auto *rightsizer = new wxBoxSizer(wxVERTICAL);
@ -46,6 +47,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
rightsizer->AddSpacer(VERT_SPACING); rightsizer->AddSpacer(VERT_SPACING);
rightsizer->Add(content_sizer, 1, wxEXPAND); rightsizer->Add(content_sizer, 1, wxEXPAND);
btn_sizer->AddStretchSpacer();
if (button_id != wxID_NONE) { if (button_id != wxID_NONE) {
auto *button = new wxButton(this, button_id); auto *button = new wxButton(this, button_id);
@ -53,8 +55,6 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
btn_sizer->Add(button); btn_sizer->Add(button);
} }
rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT);
if (! bitmap.IsOk()) { if (! bitmap.IsOk()) {
bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192); bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192);
} }
@ -64,7 +64,11 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
topsizer->Add(logo, 0, wxALL, BORDER); topsizer->Add(logo, 0, wxALL, BORDER);
topsizer->Add(rightsizer, 1, wxTOP | wxBOTTOM | wxRIGHT | wxEXPAND, 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);
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*/)
@ -72,7 +76,7 @@ void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/)
wxButton* btn = new wxButton(this, btn_id); wxButton* btn = new wxButton(this, btn_id);
if (set_focus) if (set_focus)
btn->SetFocus(); 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); }); btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); });
}; };
@ -209,33 +213,38 @@ MessageDialog::MessageDialog(wxWindow* parent,
apply_style(style); apply_style(style);
finalize(); finalize();
} }
#endif
// MessageWithCheckDialog // RichMessageDialog
MessageWithCheckDialog::MessageWithCheckDialog( wxWindow* parent, RichMessageDialog::RichMessageDialog(wxWindow* parent,
const wxString& message, const wxString& message,
const wxString& checkbox_label,
const wxString& caption/* = wxEmptyString*/, const wxString& caption/* = wxEmptyString*/,
long style/* = wxOK*/) 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, wxID_NONE)
{ {
add_msg_content(this, content_sizer, message); add_msg_content(this, content_sizer, message);
m_check = new wxCheckBox(this, wxID_ANY, checkbox_label); m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText);
content_sizer->Add(m_check, 0, wxTOP, 10); m_checkBox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_checkBoxValue = m_checkBox->GetValue(); });
btn_sizer->Insert(0, m_checkBox, wxALIGN_CENTER_VERTICAL);
apply_style(style); apply_style(style);
finalize(); finalize();
} }
bool MessageWithCheckDialog::GetCheckVal() int RichMessageDialog::ShowModal()
{ {
if (m_check) if (m_checkBoxText.IsEmpty())
return m_check->GetValue(); m_checkBox->Hide();
return false; else
m_checkBox->SetLabelText(m_checkBoxText);
Layout();
return wxDialog::ShowModal();
} }
#endif
// InfoDialog // InfoDialog

View file

@ -8,6 +8,9 @@
#include <wx/font.h> #include <wx/font.h>
#include <wx/bitmap.h> #include <wx/bitmap.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/richmsgdlg.h>
#include <wx/textctrl.h>
#include <wx/statline.h>
class wxBoxSizer; class wxBoxSizer;
class wxCheckBox; class wxCheckBox;
@ -17,7 +20,6 @@ namespace Slic3r {
namespace GUI { namespace GUI {
// A message / query dialog with a bitmap on the left and any content on the right // A message / query dialog with a bitmap on the left and any content on the right
// with buttons underneath. // with buttons underneath.
struct MsgDialog : wxDialog struct MsgDialog : wxDialog
@ -87,6 +89,23 @@ public:
}; };
#ifdef _WIN32 #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 // Generic message dialog, used intead of wxMessageDialog
class MessageDialog : public MsgDialog class MessageDialog : public MsgDialog
{ {
@ -101,7 +120,158 @@ public:
MessageDialog &operator=(const MessageDialog&) = delete; MessageDialog &operator=(const MessageDialog&) = delete;
virtual ~MessageDialog() = default; 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 #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 // just a wrapper to wxMessageBox to use the same code on all platforms
class MessageDialog : public wxMessageDialog class MessageDialog : public wxMessageDialog
{ {
@ -113,25 +283,19 @@ public:
: wxMessageDialog(parent, message, caption, style) {} : wxMessageDialog(parent, message, caption, style) {}
~MessageDialog() {} ~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: public:
MessageWithCheckDialog(wxWindow* parent, RichMessageDialog(wxWindow* parent,
const wxString& message, const wxString& message,
const wxString& checkbox_label,
const wxString& caption = wxEmptyString, const wxString& caption = wxEmptyString,
long style = wxOK); long style = wxOK)
MessageWithCheckDialog(MessageWithCheckDialog&&) = delete; : wxRichMessageDialog(parent, message, caption, style) {}
MessageWithCheckDialog(const MessageWithCheckDialog&) = delete; ~RichMessageDialog() {}
MessageWithCheckDialog& operator=(MessageWithCheckDialog&&) = delete;
MessageWithCheckDialog& operator=(const MessageWithCheckDialog&) = delete;
virtual ~MessageWithCheckDialog() = default;
bool GetCheckVal();
}; };
#endif
// Generic info dialog, used for displaying exceptions // Generic info dialog, used for displaying exceptions
class InfoDialog : public MsgDialog class InfoDialog : public MsgDialog

View file

@ -1920,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", "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", "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", "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. // 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", "layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
"brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers",
@ -2482,15 +2482,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
model.convert_from_meters(true); model.convert_from_meters(true);
}; };
if (answer_convert_from_meters == wxOK_DEFAULT) { 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 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 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 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", "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_WARNING | wxYES | wxNO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal(); int answer = dlg.ShowModal();
if (dlg.GetCheckVal()) if (dlg.IsCheckBoxChecked())
answer_convert_from_meters = answer; answer_convert_from_meters = answer;
else else
convert_model_if(model, answer == wxID_YES); convert_model_if(model, answer == wxID_YES);
@ -2504,15 +2504,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
convert_from_imperial_units(model, true); convert_from_imperial_units(model, true);
}; };
if (answer_convert_from_imperial_units == wxOK_DEFAULT) { 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 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 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 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", "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_WARNING | wxYES | wxNO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal(); int answer = dlg.ShowModal();
if (dlg.GetCheckVal()) if (dlg.IsCheckBoxChecked())
answer_convert_from_imperial_units = answer; answer_convert_from_imperial_units = answer;
else else
convert_model_if(model, answer == wxID_YES); convert_model_if(model, answer == wxID_YES);
@ -6222,6 +6222,15 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
} }
} }
if (opt_key == "material_colour") {
update_scheduled = true; // update should be scheduled (for update 3DScene)
// update material color in full config
std::vector<std::string> material_colors = { config.opt_string("material_colour", (unsigned)0) };
p->config->option<ConfigOptionStrings>("material_colour")->values = material_colors;
continue;
}
p->config->set_key_value(opt_key, config.option(opt_key)->clone()); p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") { if (opt_key == "printer_technology") {
this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key)); this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key));

View file

@ -101,6 +101,8 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
EndDialog(wxID_OK); EndDialog(wxID_OK);
}); });
wxGetApp().UpdateDlgDarkUI(this);
Fit(); Fit();
CenterOnParent(); CenterOnParent();
@ -331,6 +333,14 @@ void PrintHostQueueDialog::on_dpi_changed(const wxRect &suggested_rect)
save_user_data(UDT_SIZE | UDT_POSITION | UDT_COLS); 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) PrintHostQueueDialog::JobState PrintHostQueueDialog::get_state(int idx)
{ {
wxCHECK_MSG(idx >= 0 && idx < job_list->GetItemCount(), ST_ERROR, "Out of bounds access to job list"); 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: protected:
void on_dpi_changed(const wxRect &suggested_rect) override; void on_dpi_changed(const wxRect &suggested_rect) override;
void on_sys_color_changed() override;
private: private:
enum Column { enum Column {

View file

@ -4157,6 +4157,7 @@ void TabSLAMaterial::build()
auto page = add_options_page(L("Material"), "resin"); auto page = add_options_page(L("Material"), "resin");
auto optgroup = page->new_optgroup(L("Material")); 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_cost");
optgroup->append_single_option_line("bottle_volume"); optgroup->append_single_option_line("bottle_volume");
optgroup->append_single_option_line("bottle_weight"); 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) 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; DynamicPrintConfig new_conf = *m_config;
if (opt_key == "bottle_volume") { if (opt_key == "bottle_volume") {