mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_imgui_slider
This commit is contained in:
		
						commit
						67533da405
					
				
					 24 changed files with 523 additions and 255 deletions
				
			
		|  | @ -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.
 | ||||
|  |  | |||
|  | @ -1263,10 +1263,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 +1286,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 +1326,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 +1337,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 +1358,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)); | ||||
|  |  | |||
|  | @ -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,
 | ||||
| // 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
 | ||||
|  | @ -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]; }); | ||||
|     }; | ||||
| 
 | ||||
|     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]; | ||||
| 
 | ||||
|  | @ -1183,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()
 | ||||
|  | @ -1269,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
 | ||||
|  | @ -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 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 | ||||
|  | @ -1532,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"; | ||||
|  | @ -1565,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>> ®ions, const ExPolygons &lslices) | ||||
| static void export_regions_to_svg(const std::string &path, const std::vector<ExPolygons> ®ions, const ExPolygons &lslices) | ||||
| { | ||||
|     const std::vector<std::string> colors       = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; | ||||
|     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); | ||||
| 
 | ||||
|     svg.draw_outline(lslices, "green", "lime", stroke_width); | ||||
|     for (const std::pair<ExPolygon, size_t> ®ion : 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 - ®ions.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
 | ||||
|  | @ -1667,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; | ||||
|  | @ -1711,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])); | ||||
|  | @ -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"; | ||||
|     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)); | ||||
|  | @ -1732,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(); | ||||
|  | @ -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(); }); | ||||
| 
 | ||||
|                         // 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; | ||||
| 
 | ||||
|  | @ -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(); }); | ||||
| 
 | ||||
|     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(); | ||||
|             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()); | ||||
|                 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); | ||||
|  | @ -1846,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> ®ion : 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 | ||||
|  | @ -1868,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 | ||||
|  |  | |||
|  | @ -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
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -3163,6 +3163,13 @@ void PrintConfigDef::init_sla_params() | |||
| 
 | ||||
| 
 | ||||
|     // 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->label = L("SLA material type"); | ||||
|     def->tooltip = L("SLA material type"); | ||||
|  |  | |||
|  | @ -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 ®ion = 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()) { | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -1131,29 +1131,45 @@ 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); | ||||
|     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[i].set(txt_color, rgb); | ||||
|         else { | ||||
|             const std::string& txt_color = config->opt_string("filament_colour", i); | ||||
|             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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -555,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); | ||||
|  | @ -2800,11 +2800,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) | |||
| 
 | ||||
|     auto *vsizer = new wxBoxSizer(wxVERTICAL); | ||||
|     auto *topsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     wxStaticLine* hline = nullptr; | ||||
| #ifdef _MSW_DARK_MODE | ||||
|     if (!NppDarkMode::IsEnabled()) | ||||
| #endif //_MSW_DARK_MODE
 | ||||
|         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.
 | ||||
|  | @ -2880,8 +2876,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) | |||
|     p->index->go_to(size_t{0}); | ||||
| 
 | ||||
|     vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); | ||||
|     if (hline) | ||||
|         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); | ||||
|  |  | |||
|  | @ -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"}); | ||||
|  |  | |||
|  | @ -411,7 +411,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; | ||||
|  | @ -846,7 +846,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." | ||||
|  | @ -936,7 +936,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?"), | ||||
|  | @ -946,7 +946,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?"), | ||||
|  | @ -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(); | ||||
| 
 | ||||
|         if (!msg.empty() && !ssl_accept) { | ||||
|             wxRichMessageDialog | ||||
|             RichMessageDialog | ||||
|                 dlg(nullptr, | ||||
|                     wxString::Format(_L("%s\nDo you want to continue?"), msg), | ||||
|                     "PrusaSlicer", wxICON_QUESTION | wxYES_NO); | ||||
|  | @ -1620,6 +1620,7 @@ void GUI_App::update_ui_from_settings() | |||
|         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
 | ||||
|  | @ -2855,7 +2856,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; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ namespace GUI { | |||
| 
 | ||||
| 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) | ||||
| 	, 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,6 +47,7 @@ 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); | ||||
|  | @ -53,8 +55,6 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he | |||
| 		btn_sizer->Add(button); | ||||
| 	} | ||||
| 
 | ||||
| 	rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT); | ||||
| 
 | ||||
| 	if (! bitmap.IsOk()) { | ||||
| 		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(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*/) | ||||
|  | @ -72,7 +76,7 @@ void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/) | |||
|     wxButton* btn = new wxButton(this, btn_id); | ||||
|     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); }); | ||||
| }; | ||||
| 
 | ||||
|  | @ -209,33 +213,38 @@ MessageDialog::MessageDialog(wxWindow* parent, | |||
|     apply_style(style); | ||||
|     finalize(); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| // MessageWithCheckDialog
 | ||||
| // RichMessageDialog
 | ||||
| 
 | ||||
| MessageWithCheckDialog::MessageWithCheckDialog( wxWindow* parent, | ||||
|                                                 const wxString& message, | ||||
|                                                 const wxString& checkbox_label, | ||||
|                                                 const wxString& caption/* = wxEmptyString*/, | ||||
|                                                 long style/* = wxOK*/) | ||||
| 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, 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); | ||||
|     m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText); | ||||
|     m_checkBox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_checkBoxValue = m_checkBox->GetValue(); }); | ||||
| 
 | ||||
|     btn_sizer->Insert(0, m_checkBox, wxALIGN_CENTER_VERTICAL); | ||||
| 
 | ||||
|     apply_style(style); | ||||
|     finalize(); | ||||
| } | ||||
| 
 | ||||
| bool MessageWithCheckDialog::GetCheckVal() | ||||
| int RichMessageDialog::ShowModal() | ||||
| { | ||||
|     if (m_check) | ||||
|         return m_check->GetValue(); | ||||
|     return false; | ||||
|     if (m_checkBoxText.IsEmpty()) | ||||
|         m_checkBox->Hide(); | ||||
|     else | ||||
|         m_checkBox->SetLabelText(m_checkBoxText); | ||||
|     Layout(); | ||||
| 
 | ||||
|     return wxDialog::ShowModal(); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| // InfoDialog
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -87,6 +89,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 +120,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 +283,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 | ||||
|  |  | |||
|  | @ -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", | ||||
|         "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",  | ||||
|  | @ -2482,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); | ||||
|                         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); | ||||
|  | @ -2504,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); | ||||
|                         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); | ||||
|  | @ -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()); | ||||
|         if (opt_key == "printer_technology") { | ||||
|             this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key)); | ||||
|  |  | |||
|  | @ -101,6 +101,8 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr | |||
|         EndDialog(wxID_OK); | ||||
|         }); | ||||
| 
 | ||||
|     wxGetApp().UpdateDlgDarkUI(this); | ||||
| 
 | ||||
|     Fit(); | ||||
|     CenterOnParent(); | ||||
| 
 | ||||
|  | @ -331,6 +333,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"); | ||||
|  |  | |||
|  | @ -72,6 +72,7 @@ public: | |||
|     } | ||||
| protected: | ||||
|     void on_dpi_changed(const wxRect &suggested_rect) override; | ||||
|     void on_sys_color_changed() override; | ||||
| 
 | ||||
| private: | ||||
|     enum Column { | ||||
|  |  | |||
|  | @ -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") { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966