mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into custom_gcodes
This commit is contained in:
		
						commit
						befbd6b0fe
					
				
					 35 changed files with 821 additions and 262 deletions
				
			
		|  | @ -188,6 +188,8 @@ add_library(libslic3r STATIC | |||
|     Time.cpp | ||||
|     Time.hpp | ||||
|     MTUtils.hpp | ||||
|     VoronoiOffset.cpp | ||||
|     VoronoiOffset.hpp | ||||
|     Zipper.hpp | ||||
|     Zipper.cpp | ||||
|     MinAreaBoundingBox.hpp | ||||
|  |  | |||
|  | @ -1298,7 +1298,28 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu | |||
|     m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); | ||||
|     m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); | ||||
|     m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
 | ||||
| 
 | ||||
|     { | ||||
|         BoundingBoxf bbox(print.config().bed_shape.values); | ||||
|         m_placeholder_parser.set("print_bed_min",  new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); | ||||
|         m_placeholder_parser.set("print_bed_max",  new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); | ||||
|         m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); | ||||
|     } | ||||
|     { | ||||
|         // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
 | ||||
|         // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
 | ||||
|         // It does NOT encompass user extrusions generated by custom G-code,
 | ||||
|         // therefore it does NOT encompass the initial purge line.
 | ||||
|         // It does NOT encompass MMU/MMU2 starting (wipe) areas.
 | ||||
|         auto pts = std::make_unique<ConfigOptionPoints>(); | ||||
|         pts->values.reserve(print.first_layer_convex_hull().size()); | ||||
|         for (const Point &pt : print.first_layer_convex_hull().points) | ||||
|             pts->values.emplace_back(unscale(pt)); | ||||
|         BoundingBoxf bbox(pts->values); | ||||
|         m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); | ||||
|         m_placeholder_parser.set("first_layer_print_min",  new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); | ||||
|         m_placeholder_parser.set("first_layer_print_max",  new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); | ||||
|         m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); | ||||
|     } | ||||
|     std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); | ||||
|     // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
 | ||||
|     this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); | ||||
|  |  | |||
|  | @ -355,7 +355,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ | |||
|         max_layer_height = std::min(max_layer_height, mlh); | ||||
|     } | ||||
|     // The Prusa3D Fast (0.35mm layer height) print profile sets a higher layer height than what is normally allowed
 | ||||
|     // by the nozzle. This is a hack and it works by increasing extrusion width.
 | ||||
|     // by the nozzle. This is a hack and it works by increasing extrusion width. See GH #3919.
 | ||||
|     max_layer_height = std::max(max_layer_height, max_object_layer_height); | ||||
| 
 | ||||
|     for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) { | ||||
|  | @ -400,47 +400,21 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ | |||
|     // and maybe other problems. We will therefore go through layer_tools and detect and fix this.
 | ||||
|     // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder),
 | ||||
|     // we'll mark it with has_wipe tower.
 | ||||
|     assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); | ||||
|     if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { | ||||
|         for (size_t i = 0; i + 1 < m_layer_tools.size();) { | ||||
|             const LayerTools < = m_layer_tools[i]; | ||||
|             assert(lt.has_wipe_tower); | ||||
|             assert(! lt.extruders.empty()); | ||||
|             // Find the next layer with wipe tower or mark a layer as such.
 | ||||
|             size_t j = i + 1; | ||||
|             for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j) { | ||||
|                 LayerTools <_next = m_layer_tools[j]; | ||||
|                 if (lt_next.extruders.empty()) { | ||||
|                     //FIXME Vojtech: Lukasi, proc?
 | ||||
|                     j = m_layer_tools.size(); | ||||
|                     break; | ||||
|                 } | ||||
|                 if (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1) { | ||||
|                     // Support only layer, soluble layers? Otherwise the layer should have been already marked as having wipe tower.
 | ||||
|                     assert(lt_next.has_support && ! lt_next.has_object); | ||||
|                     lt_next.has_wipe_tower = true; | ||||
|                     break; | ||||
|                 } | ||||
|     for (unsigned int i=0; i+1<m_layer_tools.size(); ++i) { | ||||
|         LayerTools& lt = m_layer_tools[i]; | ||||
|         LayerTools& lt_next = m_layer_tools[i+1]; | ||||
|         if (lt.extruders.empty() || lt_next.extruders.empty()) | ||||
|             break; | ||||
|         if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1)) | ||||
|             lt_next.has_wipe_tower = true; | ||||
|         // We should also check that the next wipe tower layer is no further than max_layer_height:
 | ||||
|         unsigned int j = i+1; | ||||
|         double last_wipe_tower_print_z = lt_next.print_z; | ||||
|         while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) | ||||
|             if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { | ||||
|                 m_layer_tools[j].has_wipe_tower = true; | ||||
|                 last_wipe_tower_print_z = m_layer_tools[j].print_z; | ||||
|             } | ||||
|             if (j == m_layer_tools.size()) | ||||
|                 // No wipe tower above layer i, therefore no need to add any wipe tower layer above i.
 | ||||
|                 break; | ||||
|             // We should also check that the next wipe tower layer is no further than max_layer_height.
 | ||||
|             // This algorith may in theory create very thin wipe layer j if layer closely below j is marked as wipe tower.
 | ||||
|             // This may happen if printing with non-soluble break away supports.
 | ||||
|             // On the other side it should not hurt as there will be no wipe, just perimeter and sparse infill printed
 | ||||
|             // at that particular wipe tower layer without extruder change.
 | ||||
|             double last_wipe_tower_print_z = lt.print_z; | ||||
|             assert(m_layer_tools[j].has_wipe_tower); | ||||
|             for (size_t k = i + 1; k < j; ++k) { | ||||
|                 assert(! m_layer_tools[k].has_wipe_tower); | ||||
|                 if (m_layer_tools[k + 1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { | ||||
|                     m_layer_tools[k].has_wipe_tower = true; | ||||
|                     last_wipe_tower_print_z = m_layer_tools[k].print_z; | ||||
|                 } | ||||
|             } | ||||
|             i = j; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Calculate the wipe_tower_layer_height values.
 | ||||
|  |  | |||
|  | @ -252,8 +252,16 @@ bool arrange( | |||
|     // output
 | ||||
|     Pointfs &positions); | ||||
| 
 | ||||
| class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> { | ||||
| public: | ||||
|     typedef double                                          coord_type; | ||||
|     typedef boost::polygon::point_data<coordinate_type>     point_type; | ||||
|     typedef boost::polygon::segment_data<coordinate_type>   segment_type; | ||||
|     typedef boost::polygon::rectangle_data<coordinate_type> rect_type; | ||||
| }; | ||||
| 
 | ||||
| class MedialAxis { | ||||
|     public: | ||||
| public: | ||||
|     Lines lines; | ||||
|     const ExPolygon* expolygon; | ||||
|     double max_width; | ||||
|  | @ -263,14 +271,8 @@ class MedialAxis { | |||
|     void build(ThickPolylines* polylines); | ||||
|     void build(Polylines* polylines); | ||||
|      | ||||
|     private: | ||||
|     class VD : public boost::polygon::voronoi_diagram<double> { | ||||
|     public: | ||||
|         typedef double                                          coord_type; | ||||
|         typedef boost::polygon::point_data<coordinate_type>     point_type; | ||||
|         typedef boost::polygon::segment_data<coordinate_type>   segment_type; | ||||
|         typedef boost::polygon::rectangle_data<coordinate_type> rect_type; | ||||
|     }; | ||||
| private: | ||||
|     using VD = VoronoiDiagram; | ||||
|     VD vd; | ||||
|     std::set<const VD::edge_type*> edges, valid_edges; | ||||
|     std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness; | ||||
|  |  | |||
|  | @ -166,6 +166,7 @@ class SupportLayer : public Layer | |||
| { | ||||
| public: | ||||
|     // Polygons covered by the supports: base, interface and contact areas.
 | ||||
|     // Used to suppress retraction if moving for a support extrusion over these support_islands.
 | ||||
|     ExPolygonCollection         support_islands; | ||||
|     // Extrusion paths for the support base and for the support interface and contacts.
 | ||||
|     ExtrusionEntityCollection   support_fills; | ||||
|  |  | |||
|  | @ -1480,9 +1480,6 @@ stl_stats ModelObject::get_object_stl_stats() const | |||
|     // fill full_stats from all objet's meshes
 | ||||
|     for (ModelVolume* volume : this->volumes) | ||||
|     { | ||||
|         if (volume->id() == this->volumes[0]->id()) | ||||
|             continue; | ||||
| 
 | ||||
|         const stl_stats& stats = volume->mesh().stl.stats; | ||||
| 
 | ||||
|         // initialize full_stats (for repaired errors)
 | ||||
|  |  | |||
|  | @ -244,7 +244,6 @@ bool Print::invalidate_step(PrintStep step) | |||
| { | ||||
| 	bool invalidated = Inherited::invalidate_step(step); | ||||
|     // Propagate to dependent steps.
 | ||||
|     //FIXME Why should skirt invalidate brim? Shouldn't it be vice versa?
 | ||||
|     if (step == psSkirt) | ||||
| 		invalidated |= Inherited::invalidate_step(psBrim); | ||||
|     if (step != psGCodeExport) | ||||
|  | @ -1606,6 +1605,8 @@ void Print::process() | |||
|     } | ||||
|     if (this->set_started(psSkirt)) { | ||||
|         m_skirt.clear(); | ||||
|         m_skirt_convex_hull.clear(); | ||||
|         m_first_layer_convex_hull.points.clear(); | ||||
|         if (this->has_skirt()) { | ||||
|             this->set_status(88, L("Generating skirt")); | ||||
|             this->_make_skirt(); | ||||
|  | @ -1614,11 +1615,15 @@ void Print::process() | |||
|     } | ||||
| 	if (this->set_started(psBrim)) { | ||||
|         m_brim.clear(); | ||||
|         m_first_layer_convex_hull.points.clear(); | ||||
|         if (m_config.brim_width > 0) { | ||||
|             this->set_status(88, L("Generating brim")); | ||||
|             this->_make_brim(); | ||||
|         } | ||||
|        this->set_done(psBrim); | ||||
|         // Brim depends on skirt (brim lines are trimmed by the skirt lines), therefore if
 | ||||
|         // the skirt gets invalidated, brim gets invalidated as well and the following line is called.
 | ||||
|         this->finalize_first_layer_convex_hull(); | ||||
|         this->set_done(psBrim); | ||||
|     } | ||||
|     BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); | ||||
| } | ||||
|  | @ -1697,22 +1702,7 @@ void Print::_make_skirt() | |||
|     } | ||||
| 
 | ||||
|     // Include the wipe tower.
 | ||||
|     if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { | ||||
|         double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; | ||||
|         double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; | ||||
|         Vec2d pt = Vec2d(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); | ||||
| 
 | ||||
|         std::vector<Vec2d> pts; | ||||
|         pts.push_back(Vec2d(pt.x(), pt.y())); | ||||
|         pts.push_back(Vec2d(pt.x()+width, pt.y())); | ||||
|         pts.push_back(Vec2d(pt.x()+width, pt.y()+depth)); | ||||
|         pts.push_back(Vec2d(pt.x(), pt.y()+depth)); | ||||
|         for (Vec2d& pt : pts) { | ||||
|             pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; | ||||
|             pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); | ||||
|             points.push_back(Point(scale_(pt.x()), scale_(pt.y()))); | ||||
|         } | ||||
|     } | ||||
|     append(points, this->first_layer_wipe_tower_corners()); | ||||
| 
 | ||||
|     if (points.size() < 3) | ||||
|         // At least three points required for a convex hull.
 | ||||
|  | @ -1796,28 +1786,19 @@ void Print::_make_skirt() | |||
|     } | ||||
|     // Brims were generated inside out, reverse to print the outmost contour first.
 | ||||
|     m_skirt.reverse(); | ||||
| 
 | ||||
|     // Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull.
 | ||||
|     for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1)))) | ||||
|         append(m_skirt_convex_hull, std::move(poly.points)); | ||||
| } | ||||
| 
 | ||||
| void Print::_make_brim() | ||||
| { | ||||
|     // Brim is only printed on first layer and uses perimeter extruder.
 | ||||
|     Polygons    islands = this->first_layer_islands(); | ||||
|     Polygons    loops; | ||||
|     Flow        flow = this->brim_flow(); | ||||
|     Polygons    islands; | ||||
|     for (PrintObject *object : m_objects) { | ||||
|         Polygons object_islands; | ||||
|         for (ExPolygon &expoly : object->m_layers.front()->lslices) | ||||
|             object_islands.push_back(expoly.contour); | ||||
|         if (! object->support_layers().empty()) | ||||
|             object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); | ||||
|         islands.reserve(islands.size() + object_islands.size() * object->instances().size()); | ||||
|         for (const PrintInstance &instance : object->instances()) | ||||
|             for (Polygon &poly : object_islands) { | ||||
|                 islands.push_back(poly); | ||||
|                 islands.back().translate(instance.shift); | ||||
|             } | ||||
|     } | ||||
|     Polygons loops; | ||||
|     size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing())); | ||||
|     size_t      num_loops = size_t(floor(m_config.brim_width.value / flow.spacing())); | ||||
|     for (size_t i = 0; i < num_loops; ++ i) { | ||||
|         this->throw_if_canceled(); | ||||
|         islands = offset(islands, float(flow.scaled_spacing()), jtSquare); | ||||
|  | @ -1828,6 +1809,11 @@ void Print::_make_brim() | |||
|             p.pop_back(); | ||||
|             poly.points = std::move(p); | ||||
|         } | ||||
|         if (i + 1 == num_loops) { | ||||
|             // Remember the outer edge of the last brim line extruded as m_first_layer_convex_hull.
 | ||||
|             for (Polygon &poly : islands) | ||||
|                 append(m_first_layer_convex_hull.points, poly.points); | ||||
|         } | ||||
|         polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); | ||||
|     } | ||||
|     loops = union_pt_chained(loops, false); | ||||
|  | @ -1967,6 +1953,58 @@ void Print::_make_brim() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| Polygons Print::first_layer_islands() const | ||||
| { | ||||
|     Polygons islands; | ||||
|     for (PrintObject *object : m_objects) { | ||||
|         Polygons object_islands; | ||||
|         for (ExPolygon &expoly : object->m_layers.front()->lslices) | ||||
|             object_islands.push_back(expoly.contour); | ||||
|         if (! object->support_layers().empty()) | ||||
|             object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); | ||||
|         islands.reserve(islands.size() + object_islands.size() * object->instances().size()); | ||||
|         for (const PrintInstance &instance : object->instances()) | ||||
|             for (Polygon &poly : object_islands) { | ||||
|                 islands.push_back(poly); | ||||
|                 islands.back().translate(instance.shift); | ||||
|             } | ||||
|     } | ||||
|     return islands; | ||||
| } | ||||
| 
 | ||||
| std::vector<Point> Print::first_layer_wipe_tower_corners() const | ||||
| { | ||||
|     std::vector<Point> corners; | ||||
|     if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { | ||||
|         double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; | ||||
|         double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; | ||||
|         Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); | ||||
|         for (Vec2d pt : { | ||||
|                 pt0, | ||||
|                 Vec2d(pt0.x()+width, pt0.y()      ), | ||||
|                 Vec2d(pt0.x()+width, pt0.y()+depth), | ||||
|                 Vec2d(pt0.x(),       pt0.y()+depth) | ||||
|             }) { | ||||
|             pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; | ||||
|             pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); | ||||
|             corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); | ||||
|         } | ||||
|     } | ||||
|     return corners; | ||||
| } | ||||
| 
 | ||||
| void Print::finalize_first_layer_convex_hull() | ||||
| { | ||||
|     append(m_first_layer_convex_hull.points, m_skirt_convex_hull); | ||||
|     if (m_first_layer_convex_hull.empty()) { | ||||
|         // Neither skirt nor brim was extruded. Collect points of printed objects from 1st layer.
 | ||||
|         for (Polygon &poly : this->first_layer_islands()) | ||||
|             append(m_first_layer_convex_hull.points, std::move(poly.points)); | ||||
|     } | ||||
|     append(m_first_layer_convex_hull.points, this->first_layer_wipe_tower_corners()); | ||||
|     m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); | ||||
| } | ||||
| 
 | ||||
| // Wipe tower support.
 | ||||
| bool Print::has_wipe_tower() const | ||||
| { | ||||
|  | @ -1991,7 +2029,6 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l | |||
|     return m_wipe_tower_data; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void Print::_make_wipe_tower() | ||||
| { | ||||
|     m_wipe_tower_data.clear(); | ||||
|  |  | |||
|  | @ -402,6 +402,12 @@ public: | |||
| 
 | ||||
|     const ExtrusionEntityCollection& skirt() const { return m_skirt; } | ||||
|     const ExtrusionEntityCollection& brim() const { return m_brim; } | ||||
|     // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
 | ||||
|     // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
 | ||||
|     // It does NOT encompass user extrusions generated by custom G-code,
 | ||||
|     // therefore it does NOT encompass the initial purge line.
 | ||||
|     // It does NOT encompass MMU/MMU2 starting (wipe) areas.
 | ||||
|     const Polygon&                   first_layer_convex_hull() const { return m_first_layer_convex_hull; } | ||||
| 
 | ||||
|     const PrintStatistics&      print_statistics() const { return m_print_statistics; } | ||||
| 
 | ||||
|  | @ -437,6 +443,12 @@ private: | |||
|     void                _make_skirt(); | ||||
|     void                _make_brim(); | ||||
|     void                _make_wipe_tower(); | ||||
|     void                finalize_first_layer_convex_hull(); | ||||
| 
 | ||||
|     // Islands of objects and their supports extruded at the 1st layer.
 | ||||
|     Polygons            first_layer_islands() const; | ||||
|     // Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim.
 | ||||
|     std::vector<Point>  first_layer_wipe_tower_corners() const; | ||||
| 
 | ||||
|     // Declared here to have access to Model / ModelObject / ModelInstance
 | ||||
|     static void         model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src); | ||||
|  | @ -450,6 +462,13 @@ private: | |||
|     // Ordered collections of extrusion paths to build skirt loops and brim.
 | ||||
|     ExtrusionEntityCollection               m_skirt; | ||||
|     ExtrusionEntityCollection               m_brim; | ||||
|     // Convex hull of the 1st layer extrusions.
 | ||||
|     // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
 | ||||
|     // It does NOT encompass user extrusions generated by custom G-code,
 | ||||
|     // therefore it does NOT encompass the initial purge line.
 | ||||
|     // It does NOT encompass MMU/MMU2 starting (wipe) areas.
 | ||||
|     Polygon                                 m_first_layer_convex_hull; | ||||
|     Points                                  m_skirt_convex_hull; | ||||
| 
 | ||||
|     // Following section will be consumed by the GCodeGenerator.
 | ||||
|     ToolOrdering 							m_tool_ordering; | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
| // There is an implementation of a hole-aware raycaster that was eventually
 | ||||
| // not used in production version. It is now hidden under following define
 | ||||
| // for possible future use.
 | ||||
| #define SLIC3R_HOLE_RAYCASTER | ||||
| // #define SLIC3R_HOLE_RAYCASTER
 | ||||
| 
 | ||||
| #ifdef SLIC3R_HOLE_RAYCASTER | ||||
|   #include "libslic3r/SLA/Hollowing.hpp" | ||||
|  |  | |||
|  | @ -48,5 +48,8 @@ | |||
| // Enable smoothing of objects normals
 | ||||
| #define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1) | ||||
| 
 | ||||
| // Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5
 | ||||
| #define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1) | ||||
| 
 | ||||
| 
 | ||||
| #endif // _prusaslicer_technologies_h_
 | ||||
|  |  | |||
							
								
								
									
										393
									
								
								src/libslic3r/VoronoiOffset.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								src/libslic3r/VoronoiOffset.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,393 @@ | |||
| // Polygon offsetting code inspired by OpenVoronoi by Anders Wallin
 | ||||
| // https://github.com/aewallin/openvoronoi
 | ||||
| // This offsetter uses results of boost::polygon Voronoi.
 | ||||
| 
 | ||||
| #include "VoronoiOffset.hpp" | ||||
| 
 | ||||
| #include <cmath> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| using VD = Geometry::VoronoiDiagram; | ||||
| 
 | ||||
| namespace detail { | ||||
| 	// Intersect a circle with a ray, return the two parameters
 | ||||
| 	double first_circle_segment_intersection_parameter( | ||||
| 		const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v) | ||||
| 	{ | ||||
| 		const Vec2d 	d = pt - center; | ||||
| #ifndef NDEBUG | ||||
|         double          d0 = (pt - center).norm(); | ||||
|         double          d1 = (pt + v - center).norm(); | ||||
|         assert(r < std::max(d0, d1) + EPSILON); | ||||
| #endif /* NDEBUG */ | ||||
|         const double	a = v.squaredNorm(); | ||||
| 		const double 	b = 2. * d.dot(v); | ||||
| 		const double    c = d.squaredNorm() - r * r; | ||||
| 		std::pair<int, std::array<double, 2>> out; | ||||
|         double          u = b * b - 4. * a * c; | ||||
| 		assert(u > - EPSILON); | ||||
| 		double          t; | ||||
| 		if (u <= 0) { | ||||
| 			// Degenerate to a single closest point.
 | ||||
| 			t = - b / (2. * a); | ||||
| 			assert(t >= - EPSILON && t <= 1. + EPSILON); | ||||
| 			return Slic3r::clamp(0., 1., t); | ||||
| 		} else { | ||||
| 			u = sqrt(u); | ||||
| 			out.first = 2; | ||||
| 			double t0 = (- b - u) / (2. * a); | ||||
| 			double t1 = (- b + u) / (2. * a); | ||||
| 			// One of the intersections shall be found inside the segment.
 | ||||
| 			assert((t0 >= - EPSILON && t0 <= 1. + EPSILON) || (t1 >= - EPSILON && t1 <= 1. + EPSILON)); | ||||
| 			if (t1 < 0.) | ||||
| 				return 0.; | ||||
| 			if (t0 > 1.) | ||||
| 				return 1.; | ||||
| 			return (t0 > 0.) ? t0 : t1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	Vec2d voronoi_edge_offset_point( | ||||
|         const VD                    &vd, | ||||
|         const Lines                 &lines, | ||||
| 		// Distance of a VD vertex to the closest site (input polygon edge or vertex).
 | ||||
|         const std::vector<double> 	&vertex_dist, | ||||
| 		// Minium distance of a VD edge to the closest site (input polygon edge or vertex).
 | ||||
| 		// For a parabolic segment the distance may be smaller than the distance of the two end points.
 | ||||
|         const std::vector<double> 	&edge_dist, | ||||
| 		// Edge for which to calculate the offset point. If the distance towards the input polygon
 | ||||
| 		// is not monotonical, pick the offset point closer to edge.vertex0().
 | ||||
|         const VD::edge_type         &edge, | ||||
| 		// Distance from the input polygon along the edge.
 | ||||
|         const double 				 offset_distance) | ||||
| 	{ | ||||
| 		const VD::vertex_type *v0    = edge.vertex0(); | ||||
| 		const VD::vertex_type *v1    = edge.vertex1(); | ||||
|         const VD::cell_type   *cell  = edge.cell(); | ||||
|         const VD::cell_type   *cell2 = edge.twin()->cell(); | ||||
| 		const Line  		  &line0 = lines[cell->source_index()]; | ||||
| 		const Line  		  &line1 = lines[cell2->source_index()]; | ||||
| 		if (v0 == nullptr || v1 == nullptr) { | ||||
|             assert(edge.is_infinite()); | ||||
|             assert(v0 != nullptr || v1 != nullptr); | ||||
|             // Offsetting on an unconstrained edge.
 | ||||
|             assert(offset_distance > vertex_dist[(v0 ? v0 : v1) - &vd.vertices().front()] - EPSILON); | ||||
| 			Vec2d 	pt, dir; | ||||
| 			double  t; | ||||
|             if (cell->contains_point() && cell2->contains_point()) { | ||||
|                 const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; | ||||
|                 const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; | ||||
|                 // Direction vector of this unconstrained Voronoi edge.
 | ||||
|                 dir = Vec2d(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); | ||||
|                 if (v0 == nullptr) { | ||||
|                 	v0 = v1; | ||||
|                 	dir = - dir; | ||||
|                 } | ||||
| 				pt = Vec2d(v0->x(), v0->y()); | ||||
|                 t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); | ||||
|             } else { | ||||
|                 // Infinite edges could not be created by two segment sites.
 | ||||
|                 assert(cell->contains_point() != cell2->contains_point()); | ||||
|                 // Linear edge goes through the endpoint of a segment.
 | ||||
|                 assert(edge.is_linear()); | ||||
|                 assert(edge.is_secondary()); | ||||
|                 const Line  &line = cell->contains_segment() ? line0 : line1; | ||||
|                 const Point &ipt  = cell->contains_segment() ? | ||||
|                     ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : | ||||
|                     ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); | ||||
|                 assert(line.a == ipt || line.b == ipt); | ||||
|                 pt  = Vec2d(ipt.x(), ipt.y()); | ||||
|                 dir = Vec2d(line.a.y() - line.b.y(), line.b.x() - line.a.x()); | ||||
|                 assert(dir.norm() > 0.); | ||||
|                 t   = offset_distance / dir.norm(); | ||||
|                 if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) | ||||
|                 	t = - t; | ||||
|             } | ||||
|             return pt + t * dir; | ||||
|         } else { | ||||
|             // Constrained edge.
 | ||||
|             Vec2d p0(v0->x(), v0->y()); | ||||
|             Vec2d p1(v1->x(), v1->y()); | ||||
|             double d0 = vertex_dist[v0 - &vd.vertices().front()]; | ||||
|             double d1 = vertex_dist[v1 - &vd.vertices().front()]; | ||||
|             if (cell->contains_segment() && cell2->contains_segment()) { | ||||
|                 // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically.
 | ||||
|                 double ddif = d1 - d0; | ||||
|                 assert(offset_distance > std::min(d0, d1) - EPSILON && offset_distance < std::max(d0, d1) + EPSILON); | ||||
|                 double t    = (ddif == 0) ? 0. : clamp(0., 1., (offset_distance - d0) / ddif); | ||||
|                 return Slic3r::lerp(p0, p1, t); | ||||
|             } else { | ||||
| 	            // One cell contains a point, the other contains an edge or a point.
 | ||||
|                 assert(cell->contains_point() || cell2->contains_point()); | ||||
|                 const Point &ipt = cell->contains_point() ? | ||||
|                     ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : | ||||
|                     ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); | ||||
| 	            double t = detail::first_circle_segment_intersection_parameter( | ||||
| 	            	Vec2d(ipt.x(), ipt.y()), offset_distance, p0, p1 - p0); | ||||
| 	            return Slic3r::lerp(p0, p1, t); | ||||
| 	        } | ||||
|         } | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error) | ||||
| { | ||||
| 	// Distance of a VD vertex to the closest site (input polygon edge or vertex).
 | ||||
|     std::vector<double> vertex_dist(vd.num_vertices(), std::numeric_limits<double>::max()); | ||||
| 
 | ||||
| 	// Minium distance of a VD edge to the closest site (input polygon edge or vertex).
 | ||||
| 	// For a parabolic segment the distance may be smaller than the distance of the two end points.
 | ||||
|     std::vector<double> edge_dist(vd.num_edges(), std::numeric_limits<double>::max()); | ||||
| 
 | ||||
| 	// Calculate minimum distance of input polygons to voronoi vertices and voronoi edges.
 | ||||
|     for (const VD::edge_type &edge : vd.edges()) { | ||||
| 		const VD::vertex_type *v0    = edge.vertex0(); | ||||
| 		const VD::vertex_type *v1    = edge.vertex1(); | ||||
|         const VD::cell_type   *cell  = edge.cell(); | ||||
|         const VD::cell_type   *cell2 = edge.twin()->cell(); | ||||
|         const Line 			  &line0 = lines[cell->source_index()]; | ||||
|         const Line 			  &line1 = lines[cell2->source_index()]; | ||||
| 		double 				   d0, d1, dmin; | ||||
| 		if (v0 == nullptr || v1 == nullptr) { | ||||
| 			assert(edge.is_infinite()); | ||||
|             if (cell->contains_point() && cell2->contains_point()) { | ||||
| 				const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; | ||||
| 				const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; | ||||
|                 d0 = d1 = std::numeric_limits<double>::max(); | ||||
| 		    	if (v0 == nullptr && v1 == nullptr) { | ||||
|                     dmin = (pt1.cast<double>() - pt0.cast<double>()).norm(); | ||||
| 		    	} else { | ||||
|                     Vec2d pt((pt0 + pt1).cast<double>() * 0.5); | ||||
|                     Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); | ||||
| 					Vec2d pt0d(pt0.x(), pt0.y()); | ||||
| 					if (v0) { | ||||
| 						Vec2d a(v0->x(), v0->y()); | ||||
| 						d0 = (a - pt0d).norm(); | ||||
| 						dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0; | ||||
|                         vertex_dist[v0 - &vd.vertices().front()] = d0; | ||||
|                     } else { | ||||
| 						Vec2d a(v1->x(), v1->y()); | ||||
| 						d1 = (a - pt0d).norm(); | ||||
| 						dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1; | ||||
|                         vertex_dist[v1 - &vd.vertices().front()] = d1; | ||||
|                     } | ||||
| 		    	} | ||||
| 		    } else { | ||||
| 			    // Infinite edges could not be created by two segment sites.
 | ||||
|                 assert(cell->contains_point() != cell2->contains_point()); | ||||
|                 // Linear edge goes through the endpoint of a segment.
 | ||||
|                 assert(edge.is_linear()); | ||||
|                 assert(edge.is_secondary()); | ||||
| #ifndef NDEBUG | ||||
|                 if (cell->contains_segment()) { | ||||
|                     const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; | ||||
|                     assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || | ||||
|                            (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); | ||||
|                 } else { | ||||
|                     const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; | ||||
|                     assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || | ||||
|                            (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); | ||||
|                 } | ||||
|                 const Point &pt = cell->contains_segment() ? | ||||
|                     ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : | ||||
|                     ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); | ||||
| #endif /* NDEBUG */ | ||||
|                 if (v0) { | ||||
|                     assert((Point(v0->x(), v0->y()) - pt).cast<double>().norm() < SCALED_EPSILON); | ||||
|                     d0 = dmin = 0.; | ||||
|                     vertex_dist[v0 - &vd.vertices().front()] = d0; | ||||
|                 } else { | ||||
|                     assert((Point(v1->x(), v1->y()) - pt).cast<double>().norm() < SCALED_EPSILON); | ||||
|                     d1 = dmin = 0.; | ||||
|                     vertex_dist[v1 - &vd.vertices().front()] = d1; | ||||
|                 } | ||||
| 		    } | ||||
|         } else { | ||||
| 			// Finite edge has valid points at both sides.
 | ||||
| 	        if (cell->contains_segment() && cell2->contains_segment()) { | ||||
|                 // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments.
 | ||||
|                 Vec2d  pt(line0.a.cast<double>()); | ||||
|                 Vec2d  dir(line0.b.cast<double>() - pt); | ||||
|                 Vec2d  vec0 = Vec2d(v0->x(), v0->y()) - pt; | ||||
|                 Vec2d  vec1 = Vec2d(v1->x(), v1->y()) - pt; | ||||
|                 double l2   = dir.squaredNorm(); | ||||
|                 assert(l2 > 0.); | ||||
|                 d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm(); | ||||
|                 d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm(); | ||||
| 				dmin = std::min(d0, d1); | ||||
| 			} else { | ||||
|                 assert(cell->contains_point() || cell2->contains_point()); | ||||
|                 const Point &pt0 = cell->contains_point() ? | ||||
|                     ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : | ||||
|                     ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); | ||||
| 				// Project p0 to line segment <v0, v1>.
 | ||||
| 	            Vec2d p0(v0->x(), v0->y()); | ||||
| 	            Vec2d p1(v1->x(), v1->y()); | ||||
| 				Vec2d px(pt0.x(), pt0.y()); | ||||
| 				Vec2d v = p1 - p0; | ||||
|                 d0 = (p0 - px).norm(); | ||||
|                 d1 = (p1 - px).norm(); | ||||
|                 double t = v.dot(px - p0); | ||||
| 				double l2 = v.squaredNorm(); | ||||
| 				if (t > 0. && t < l2) { | ||||
| 					// Foot point on the line segment.
 | ||||
| 					Vec2d foot = p0 + (t / l2) * v; | ||||
| 					dmin = (foot - px).norm(); | ||||
| 				} else | ||||
| 					dmin = std::min(d0, d1); | ||||
| 			} | ||||
|             vertex_dist[v0 - &vd.vertices().front()] = d0; | ||||
|             vertex_dist[v1 - &vd.vertices().front()] = d1; | ||||
|         } | ||||
|         edge_dist[&edge - &vd.edges().front()] = dmin; | ||||
| 	} | ||||
| 
 | ||||
| 	// Mark cells intersected by the offset curve.
 | ||||
| 	std::vector<unsigned char> seed_cells(vd.num_cells(), false); | ||||
| 	for (const VD::cell_type &cell : vd.cells()) { | ||||
| 		const VD::edge_type *first_edge = cell.incident_edge(); | ||||
| 		const VD::edge_type *edge       = first_edge; | ||||
| 		do { | ||||
|             double dmin = edge_dist[edge - &vd.edges().front()]; | ||||
| 			double dmax = std::numeric_limits<double>::max(); | ||||
|             const VD::vertex_type *v0 = edge->vertex0(); | ||||
|             const VD::vertex_type *v1 = edge->vertex1(); | ||||
| 			if (v0 != nullptr) | ||||
|                 dmax = vertex_dist[v0 - &vd.vertices().front()]; | ||||
| 			if (v1 != nullptr) | ||||
|                 dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]); | ||||
|             if (offset_distance >= dmin && offset_distance <= dmax) { | ||||
| 				// This cell is being intersected by the offset curve.
 | ||||
| 				seed_cells[&cell - &vd.cells().front()] = true; | ||||
| 				break; | ||||
| 			} | ||||
| 			edge = edge->next(); | ||||
| 		} while (edge != first_edge); | ||||
| 	} | ||||
| 
 | ||||
| 	auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) { | ||||
|         const VD::vertex_type *v0 = edge->vertex0(); | ||||
|         const VD::vertex_type *v1 = edge->vertex1(); | ||||
|         double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits<double>::max(); | ||||
|         double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::max(); | ||||
|     	if (d0 < offset_distance && offset_distance < d1) | ||||
|         	return true; | ||||
|     	else if (d1 < offset_distance && offset_distance < d0) | ||||
|         	return false; | ||||
|     	else { | ||||
|         	assert(false); | ||||
|         	return false; | ||||
|     	} | ||||
|     }; | ||||
| 
 | ||||
| 	/// \brief starting at e, find the next edge on the face that brackets t
 | ||||
| 	///
 | ||||
| 	/// we can be in one of two modes.
 | ||||
| 	/// if direction==false then we are looking for an edge where src_t < t < trg_t
 | ||||
| 	/// if direction==true we are looning for an edge where       trg_t < t < src_t
 | ||||
| 	auto next_offset_edge = | ||||
| 		[&vd, &vertex_dist, &edge_dist, offset_distance] | ||||
|             (const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* { | ||||
| 	    const VD::edge_type *edge = start_edge; | ||||
| 	    do { | ||||
|             const VD::vertex_type *v0 = edge->vertex0(); | ||||
|             const VD::vertex_type *v1 = edge->vertex1(); | ||||
|             double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits<double>::max(); | ||||
|             double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::max(); | ||||
| 	        if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1)) | ||||
| 	        	return edge; | ||||
| 	        edge = edge->next(); | ||||
| 	    } while (edge != start_edge); | ||||
| 	    assert(false); | ||||
|         return nullptr; | ||||
| 	}; | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| 	auto dist_to_site = [&lines](const VD::cell_type &cell, const Vec2d &point) { | ||||
|         const Line &line = lines[cell.source_index()]; | ||||
|         return cell.contains_point() ? | ||||
|             (((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast<double>() - point).norm() : | ||||
|             line.distance_to(point.cast<coord_t>()); | ||||
| 	}; | ||||
| #endif /* NDEBUG */ | ||||
| 
 | ||||
| 	// Track the offset curves.
 | ||||
| 	Polygons out; | ||||
| 	double angle_step    = 2. * acos((offset_distance - discretization_error) / offset_distance); | ||||
| 	double sin_threshold = sin(angle_step) + EPSILON; | ||||
| 	for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx) | ||||
| 		if (seed_cells[seed_cell_idx]) { | ||||
| 			seed_cells[seed_cell_idx] = false; | ||||
| 			// Initial direction should not matter, an offset curve shall intersect a cell at least at two points
 | ||||
| 			// (if it is not just touching the cell at a single vertex), and such two intersection points shall have
 | ||||
| 			// opposite direction.
 | ||||
|     		bool direction = false;  | ||||
|     		// the first edge on the start-face
 | ||||
|             const VD::cell_type &cell       = vd.cells()[seed_cell_idx]; | ||||
|             const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction); | ||||
|             assert(start_edge->cell() == &cell); | ||||
|             const VD::edge_type *edge       = start_edge; | ||||
|             Polygon  			 poly; | ||||
| 		    do { | ||||
|                 direction = edge_dir(edge); | ||||
| 		        // find the next edge
 | ||||
|                 const VD::edge_type  *next_edge = next_offset_edge(edge->next(), direction); | ||||
| 		        //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n";
 | ||||
| 		        // Interpolate a circular segment or insert a linear segment between edge and next_edge.
 | ||||
|                 const VD::cell_type  *cell      = edge->cell(); | ||||
|                 Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance); | ||||
|                 Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance); | ||||
| #ifndef NDEBUG | ||||
|                 { | ||||
|                     double err = dist_to_site(*cell, p1) - offset_distance; | ||||
|                     assert(std::abs(err) < SCALED_EPSILON); | ||||
|                     err = dist_to_site(*cell, p2) - offset_distance; | ||||
|                     assert(std::abs(err) < SCALED_EPSILON); | ||||
|                 } | ||||
| #endif /* NDEBUG */ | ||||
| 				if (cell->contains_point()) { | ||||
| 					// Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error.
 | ||||
| 					// The arc should cover angle < PI.
 | ||||
| 					//FIXME we should be able to produce correctly oriented output curves based on the first edge taken!
 | ||||
|                     const Line  &line0  = lines[cell->source_index()]; | ||||
| 					const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast<double>(); | ||||
| 					const Vec2d  v1 	= p1 - center; | ||||
| 					const Vec2d  v2 	= p2 - center; | ||||
| 					double 		 orient = cross2(v1, v2); | ||||
|                     double       orient_norm = v1.norm() * v2.norm(); | ||||
| 					bool 		 ccw    = orient > 0; | ||||
|                     bool         obtuse = v1.dot(v2) < 0.; | ||||
| 					if (! ccw) | ||||
| 						orient = - orient; | ||||
| 					assert(orient != 0.); | ||||
|                     if (obtuse || orient > orient_norm * sin_threshold) { | ||||
| 						// Angle is bigger than the threshold, therefore the arc will be discretized.
 | ||||
|                         double angle = asin(orient / orient_norm); | ||||
|                         if (obtuse) | ||||
| 							angle = M_PI - angle; | ||||
| 						size_t n_steps = size_t(ceil(angle / angle_step)); | ||||
| 						double astep = angle / n_steps; | ||||
| 						if (! ccw) | ||||
| 							astep *= -1.; | ||||
| 						double a = astep; | ||||
| 						for (size_t i = 1; i < n_steps; ++ i, a += astep) { | ||||
| 							double c = cos(a); | ||||
| 							double s = sin(a); | ||||
| 							Vec2d  p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y()); | ||||
|                             poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y()))); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|                 poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); | ||||
| 		        // although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here.
 | ||||
| 		        seed_cells[cell - &vd.cells().front()] = false; | ||||
|                 edge = next_edge->twin(); | ||||
| 		    } while (edge != start_edge); | ||||
| 		    out.emplace_back(std::move(poly)); | ||||
| 		} | ||||
| 
 | ||||
| 	return out; | ||||
| } | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
							
								
								
									
										14
									
								
								src/libslic3r/VoronoiOffset.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/libslic3r/VoronoiOffset.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| #ifndef slic3r_VoronoiOffset_hpp_ | ||||
| #define slic3r_VoronoiOffset_hpp_ | ||||
| 
 | ||||
| #include "libslic3r.h" | ||||
| 
 | ||||
| #include "Geometry.hpp" | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error); | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| #endif // slic3r_VoronoiOffset_hpp_
 | ||||
|  | @ -40,12 +40,19 @@ | |||
| #include <Eigen/Dense> | ||||
| 
 | ||||
| #ifdef HAS_GLSAFE | ||||
| void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name) | ||||
| void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) | ||||
| { | ||||
| #if defined(NDEBUG) && ENABLE_OPENGL_ERROR_LOGGING | ||||
|     // In release mode, if OpenGL debugging was forced by ENABLE_OPENGL_ERROR_LOGGING, only show
 | ||||
|     // OpenGL errors if sufficiently high loglevel.
 | ||||
|     if (Slic3r::get_logging_level() < 5) | ||||
|         return; | ||||
| #endif // ENABLE_OPENGL_ERROR_LOGGING
 | ||||
| 
 | ||||
|     GLenum err = glGetError(); | ||||
|     if (err == GL_NO_ERROR) | ||||
|         return; | ||||
|     const char *sErr = 0; | ||||
|     const char* sErr = 0; | ||||
|     switch (err) { | ||||
|     case GL_INVALID_ENUM:       sErr = "Invalid Enum";      break; | ||||
|     case GL_INVALID_VALUE:      sErr = "Invalid Value";     break; | ||||
|  | @ -56,10 +63,10 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char | |||
|     case GL_OUT_OF_MEMORY:      sErr = "Out Of Memory";     break; | ||||
|     default:                    sErr = "Unknown";           break; | ||||
|     } | ||||
| 	BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; | ||||
|     BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; | ||||
|     assert(false); | ||||
| } | ||||
| #endif | ||||
| #endif // HAS_GLSAFE
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,20 +10,20 @@ | |||
| 
 | ||||
| #include <functional> | ||||
| 
 | ||||
| #ifndef NDEBUG | ||||
| #define HAS_GLSAFE | ||||
| #if ENABLE_OPENGL_ERROR_LOGGING || ! defined(NDEBUG) | ||||
|     #define HAS_GLSAFE | ||||
| #endif | ||||
| 
 | ||||
| #ifdef HAS_GLSAFE | ||||
| extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); | ||||
| inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } | ||||
| #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) | ||||
| #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) | ||||
| #else | ||||
| inline void glAssertRecentCall() { } | ||||
| #define glsafe(cmd) cmd | ||||
| #define glcheck() | ||||
| #endif | ||||
|     extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); | ||||
|     inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } | ||||
|     #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) | ||||
|     #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) | ||||
| #else // HAS_GLSAFE
 | ||||
|     inline void glAssertRecentCall() { } | ||||
|     #define glsafe(cmd) cmd | ||||
|     #define glcheck() | ||||
| #endif // HAS_GLSAFE
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
|  |  | |||
|  | @ -1534,9 +1534,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) | |||
|     , m_retina_helper(nullptr) | ||||
| #endif | ||||
|     , m_in_render(false) | ||||
|     , m_main_toolbar(GLToolbar::Normal, "Top") | ||||
|     , m_undoredo_toolbar(GLToolbar::Normal, "Top") | ||||
|     , m_collapse_toolbar(GLToolbar::Normal, "Top") | ||||
|     , m_main_toolbar(GLToolbar::Normal, "Main") | ||||
|     , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") | ||||
|     , m_gizmos(*this) | ||||
|     , m_use_clipping_planes(false) | ||||
|     , m_sidebar_field("") | ||||
|  | @ -1914,11 +1913,6 @@ void GLCanvas3D::enable_undoredo_toolbar(bool enable) | |||
|     m_undoredo_toolbar.set_enabled(enable); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::enable_collapse_toolbar(bool enable) | ||||
| { | ||||
|     m_collapse_toolbar.set_enabled(enable); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::enable_dynamic_background(bool enable) | ||||
| { | ||||
|     m_dynamic_background_enabled = enable; | ||||
|  | @ -2112,7 +2106,7 @@ void GLCanvas3D::render() | |||
| 	        tooltip = m_undoredo_toolbar.get_tooltip(); | ||||
| 
 | ||||
| 	    if (tooltip.empty()) | ||||
| 	        tooltip = m_collapse_toolbar.get_tooltip(); | ||||
|             tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); | ||||
| 
 | ||||
| 	    if (tooltip.empty()) | ||||
|             tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); | ||||
|  | @ -2854,8 +2848,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) | |||
| 
 | ||||
|     m_dirty |= m_main_toolbar.update_items_state(); | ||||
|     m_dirty |= m_undoredo_toolbar.update_items_state(); | ||||
|     m_dirty |= m_collapse_toolbar.update_items_state(); | ||||
|     m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); | ||||
|     m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); | ||||
|     bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); | ||||
|     m_dirty |= mouse3d_controller_applied; | ||||
| 
 | ||||
|  | @ -3473,7 +3467,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (m_collapse_toolbar.on_mouse(evt, *this)) | ||||
|     if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) | ||||
|     { | ||||
|         if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) | ||||
|             mouse_up_cleanup(); | ||||
|  | @ -4187,7 +4181,7 @@ void GLCanvas3D::update_ui_from_settings() | |||
| #endif // ENABLE_RETINA_GL
 | ||||
| 
 | ||||
|     bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; | ||||
|     enable_collapse_toolbar(enable_collapse); | ||||
|     wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -5055,51 +5049,7 @@ bool GLCanvas3D::_init_view_toolbar() | |||
| 
 | ||||
| bool GLCanvas3D::_init_collapse_toolbar() | ||||
| { | ||||
|     if (!m_collapse_toolbar.is_enabled() && m_collapse_toolbar.get_items_count() > 0) | ||||
|         return true; | ||||
| 
 | ||||
|     BackgroundTexture::Metadata background_data; | ||||
|     background_data.filename = "toolbar_background.png"; | ||||
|     background_data.left = 16; | ||||
|     background_data.top = 16; | ||||
|     background_data.right = 16; | ||||
|     background_data.bottom = 16; | ||||
| 
 | ||||
|     if (!m_collapse_toolbar.init(background_data)) | ||||
|     { | ||||
|         // unable to init the toolbar texture, disable it
 | ||||
|         m_collapse_toolbar.set_enabled(false); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     m_collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); | ||||
|     m_collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); | ||||
|     m_collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); | ||||
|     m_collapse_toolbar.set_border(5.0f); | ||||
|     m_collapse_toolbar.set_separator_size(5); | ||||
|     m_collapse_toolbar.set_gap_size(2); | ||||
| 
 | ||||
|     GLToolbarItem::Data item; | ||||
| 
 | ||||
|     item.name = "collapse_sidebar"; | ||||
|     item.icon_filename = "collapse.svg"; | ||||
|     item.tooltip =  wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); | ||||
|     item.sprite_id = 0; | ||||
|     item.left.action_callback = [this, item]() { | ||||
|         std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? | ||||
|             _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); | ||||
| 
 | ||||
|         int id = m_collapse_toolbar.get_item_id("collapse_sidebar"); | ||||
|         m_collapse_toolbar.set_tooltip(id, new_tooltip); | ||||
|         set_tooltip(""); | ||||
| 
 | ||||
|         wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); | ||||
|     }; | ||||
| 
 | ||||
|     if (!m_collapse_toolbar.add_item(item)) | ||||
|         return false; | ||||
| 
 | ||||
|     return true; | ||||
|     return wxGetApp().plater()->init_collapse_toolbar(); | ||||
| } | ||||
| 
 | ||||
| bool GLCanvas3D::_set_current() | ||||
|  | @ -5427,20 +5377,21 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const | |||
|     float size = GLToolbar::Default_Icons_Size * scale; | ||||
| 
 | ||||
|     // Set current size for all top toolbars. It will be used for next calculations
 | ||||
|     GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); | ||||
| #if ENABLE_RETINA_GL | ||||
|     const float sc = m_retina_helper->get_scale_factor() * scale; | ||||
|     m_main_toolbar.set_scale(sc); | ||||
|     m_undoredo_toolbar.set_scale(sc); | ||||
|     m_collapse_toolbar.set_scale(sc); | ||||
|     collapse_toolbar.set_scale(sc); | ||||
|     size *= m_retina_helper->get_scale_factor(); | ||||
| #else | ||||
|     m_main_toolbar.set_icons_size(size); | ||||
|     m_undoredo_toolbar.set_icons_size(size); | ||||
|     m_collapse_toolbar.set_icons_size(size); | ||||
|     collapse_toolbar.set_icons_size(size); | ||||
| #endif // ENABLE_RETINA_GL
 | ||||
| 
 | ||||
|     float top_tb_width  = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + m_collapse_toolbar.get_width(); | ||||
|     int   items_cnt     = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + m_collapse_toolbar.get_visible_items_cnt(); | ||||
|     float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); | ||||
|     int   items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); | ||||
|     float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars 
 | ||||
| 
 | ||||
|     // calculate scale needed for items in all top toolbars
 | ||||
|  | @ -5460,7 +5411,6 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const | |||
|         wxGetApp().set_auto_toolbar_icon_scale(new_scale); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLCanvas3D::_render_overlays() const | ||||
| { | ||||
|     glsafe(::glDisable(GL_DEPTH_TEST)); | ||||
|  | @ -5485,12 +5435,12 @@ void GLCanvas3D::_render_overlays() const | |||
|     const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); | ||||
|     m_main_toolbar.set_scale(scale); | ||||
|     m_undoredo_toolbar.set_scale(scale); | ||||
|     m_collapse_toolbar.set_scale(scale); | ||||
|     wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); | ||||
| #else | ||||
|     const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); | ||||
|     m_main_toolbar.set_icons_size(size); | ||||
|     m_undoredo_toolbar.set_icons_size(size); | ||||
|     m_collapse_toolbar.set_icons_size(size); | ||||
|     wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); | ||||
| #endif // ENABLE_RETINA_GL
 | ||||
| 
 | ||||
|     _render_main_toolbar(); | ||||
|  | @ -5594,7 +5544,8 @@ void GLCanvas3D::_render_main_toolbar() const | |||
|     float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); | ||||
| 
 | ||||
|     float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; | ||||
|     float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; | ||||
|     const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); | ||||
|     float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; | ||||
|     float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; | ||||
| 
 | ||||
|     m_main_toolbar.set_position(top, left); | ||||
|  | @ -5610,7 +5561,8 @@ void GLCanvas3D::_render_undoredo_toolbar() const | |||
|     float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); | ||||
| 
 | ||||
|     float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; | ||||
|     float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; | ||||
|     const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); | ||||
|     float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; | ||||
|     float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; | ||||
|     m_undoredo_toolbar.set_position(top, left); | ||||
|     m_undoredo_toolbar.render(*this); | ||||
|  | @ -5618,8 +5570,7 @@ void GLCanvas3D::_render_undoredo_toolbar() const | |||
| 
 | ||||
| void GLCanvas3D::_render_collapse_toolbar() const | ||||
| { | ||||
|     if (!m_collapse_toolbar.is_enabled()) | ||||
|         return; | ||||
|     GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); | ||||
| 
 | ||||
|     Size cnv_size = get_canvas_size(); | ||||
|     float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); | ||||
|  | @ -5627,10 +5578,10 @@ void GLCanvas3D::_render_collapse_toolbar() const | |||
|     float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; | ||||
| 
 | ||||
|     float top  = 0.5f * (float)cnv_size.get_height() * inv_zoom; | ||||
|     float left = (0.5f * (float)cnv_size.get_width() - (float)m_collapse_toolbar.get_width() - band) * inv_zoom; | ||||
|     float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; | ||||
| 
 | ||||
|     m_collapse_toolbar.set_position(top, left); | ||||
|     m_collapse_toolbar.render(*this); | ||||
|     collapse_toolbar.set_position(top, left); | ||||
|     collapse_toolbar.render(*this); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_render_view_toolbar() const | ||||
|  | @ -7164,9 +7115,10 @@ bool GLCanvas3D::_activate_search_toolbar_item() | |||
| 
 | ||||
| bool GLCanvas3D::_deactivate_collapse_toolbar_items() | ||||
| { | ||||
|     if (m_collapse_toolbar.is_item_pressed("print")) | ||||
|     GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); | ||||
|     if (collapse_toolbar.is_item_pressed("print")) | ||||
|     { | ||||
|         m_collapse_toolbar.force_left_action(m_collapse_toolbar.get_item_id("print"), *this); | ||||
|         collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -452,7 +452,6 @@ private: | |||
|     mutable GLGizmosManager m_gizmos; | ||||
|     mutable GLToolbar m_main_toolbar; | ||||
|     mutable GLToolbar m_undoredo_toolbar; | ||||
|     mutable GLToolbar m_collapse_toolbar; | ||||
|     ClippingPlane m_clipping_planes[2]; | ||||
|     mutable ClippingPlane m_camera_clipping_plane; | ||||
|     bool m_use_clipping_planes; | ||||
|  | @ -588,7 +587,6 @@ public: | |||
|     void enable_selection(bool enable); | ||||
|     void enable_main_toolbar(bool enable); | ||||
|     void enable_undoredo_toolbar(bool enable); | ||||
|     void enable_collapse_toolbar(bool enable); | ||||
|     void enable_dynamic_background(bool enable); | ||||
|     void enable_labels(bool enable) { m_labels.enable(enable); } | ||||
| #if ENABLE_SLOPE_RENDERING | ||||
|  |  | |||
|  | @ -1238,7 +1238,7 @@ bool GLToolbar::generate_icons_texture() const | |||
|     } | ||||
| 
 | ||||
|     std::vector<std::pair<int, bool>> states; | ||||
|     if (m_name == "Top") | ||||
|     if (m_type == Normal) | ||||
|     { | ||||
|         states.push_back({ 1, false }); // Normal
 | ||||
|         states.push_back({ 0, false }); // Pressed
 | ||||
|  | @ -1247,7 +1247,7 @@ bool GLToolbar::generate_icons_texture() const | |||
|         states.push_back({ 0, false }); // HoverPressed
 | ||||
|         states.push_back({ 2, false }); // HoverDisabled
 | ||||
|     } | ||||
|     else if (m_name == "View") | ||||
|     else | ||||
|     { | ||||
|         states.push_back({ 1, false }); // Normal
 | ||||
|         states.push_back({ 1, true });  // Pressed
 | ||||
|  |  | |||
|  | @ -2506,7 +2506,6 @@ 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
 | ||||
| //            std::vector<int> obj_idxs;
 | ||||
| 
 | ||||
|             for (auto map_item : sel_map) | ||||
|             { | ||||
|  | @ -2569,22 +2568,45 @@ void ObjectList::merge(bool to_multipart_object) | |||
|         new_object->name = _u8L("Merged"); | ||||
|         DynamicPrintConfig* config = &new_object->config; | ||||
| 
 | ||||
|         int frst_obj_idx = obj_idxs.front(); | ||||
|         const Vec3d& main_offset  = (*m_objects)[frst_obj_idx]->instances[0]->get_offset(); | ||||
| 
 | ||||
|         for (int obj_idx : obj_idxs) | ||||
|         { | ||||
|             ModelObject* object = (*m_objects)[obj_idx]; | ||||
|             Vec3d offset = object->instances[0]->get_offset(); | ||||
| 
 | ||||
|             if (object->id() == (*m_objects)[frst_obj_idx]->id()) | ||||
|                 new_object->add_instance(*object->instances[0]); | ||||
|             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(); | ||||
| 
 | ||||
|             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(); | ||||
| 
 | ||||
|             // 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; | ||||
|                 new_volume->set_rotation(vol_rot); | ||||
| 
 | ||||
|                 // set scale
 | ||||
|                 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); | ||||
|                 new_volume->set_mirror(vol_mirror); | ||||
| 
 | ||||
|                 // set offset
 | ||||
|                 Vec3d vol_offset = volume_offset_correction* new_volume->get_offset(); | ||||
|                 new_volume->set_offset(vol_offset); | ||||
|             } | ||||
| 
 | ||||
|             // merge settings
 | ||||
|             auto new_opt_keys = config->keys(); | ||||
| 
 | ||||
|             const DynamicPrintConfig& from_config = object->config; | ||||
|             auto opt_keys = from_config.keys(); | ||||
| 
 | ||||
|             // merge settings
 | ||||
|             for (auto& opt_key : opt_keys) { | ||||
|                 if (find(new_opt_keys.begin(), new_opt_keys.end(), opt_key) == new_opt_keys.end()) { | ||||
|                     const ConfigOption* option = from_config.option(opt_key); | ||||
|  | @ -2596,18 +2618,11 @@ void ObjectList::merge(bool to_multipart_object) | |||
|                     config->set_key_value(opt_key, option->clone()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // merge volumes
 | ||||
|             for (const ModelVolume* volume : object->volumes) { | ||||
|                 ModelVolume* new_volume = new_object->add_volume(*volume); | ||||
|                 Vec3d vol_offset = offset - main_offset + new_volume->get_offset(); | ||||
|                 new_volume->set_offset(vol_offset); | ||||
|             } | ||||
|             // save extruder value if it was set
 | ||||
|             if (object->volumes.size() == 1 && find(opt_keys.begin(), opt_keys.end(), "extruder") != opt_keys.end()) { | ||||
|                 ModelVolume* volume = new_object->volumes.back(); | ||||
|                 const ConfigOption* option = from_config.option("extruder"); | ||||
|                 if (option)  | ||||
|                 if (option) | ||||
|                     volume->config.set_key_value("extruder", option->clone()); | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,8 @@ | |||
| #include "wxExtensions.hpp" | ||||
| #include "ObjectDataViewModel.hpp" | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| 
 | ||||
| class wxBoxSizer; | ||||
| class wxBitmapComboBox; | ||||
| class wxMenuItem; | ||||
|  |  | |||
|  | @ -68,7 +68,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba | |||
|     m_canvas->enable_selection(true); | ||||
|     m_canvas->enable_main_toolbar(true); | ||||
|     m_canvas->enable_undoredo_toolbar(true); | ||||
|     m_canvas->enable_collapse_toolbar(true); | ||||
|     m_canvas->enable_labels(true); | ||||
| #if ENABLE_SLOPE_RENDERING | ||||
|     m_canvas->enable_slope(true); | ||||
|  | @ -222,7 +221,6 @@ bool Preview::init(wxWindow* parent, Model* model) | |||
|     m_canvas->set_process(m_process); | ||||
|     m_canvas->enable_legend_texture(true); | ||||
|     m_canvas->enable_dynamic_background(true); | ||||
|     m_canvas->enable_collapse_toolbar(true); | ||||
| 
 | ||||
|     m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     create_double_slider(); | ||||
|  |  | |||
|  | @ -54,18 +54,35 @@ bool GLGizmoFdmSupports::on_init() | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate) | ||||
| { | ||||
|     if (activate && ! m_internal_stack_active) { | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); | ||||
|         wxGetApp().plater()->enter_gizmos_stack(); | ||||
|         m_internal_stack_active = true; | ||||
|     } | ||||
|     if (! activate && m_internal_stack_active) { | ||||
|         wxGetApp().plater()->leave_gizmos_stack(); | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); | ||||
|         m_internal_stack_active = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection) | ||||
| { | ||||
|     const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; | ||||
|     if (! mo) | ||||
|     if (m_state != On) | ||||
|         return; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; | ||||
| 
 | ||||
|     if (mo && selection.is_from_single_instance() | ||||
|      && (mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) | ||||
|      && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) | ||||
|     { | ||||
|         update_from_model_object(); | ||||
|         m_old_mo_id = mo->id(); | ||||
|         m_old_volumes_size = mo->volumes.size(); | ||||
|         m_schedule_update = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -131,8 +148,10 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const | |||
|         // Now render both enforcers and blockers.
 | ||||
|         for (int i=0; i<2; ++i) { | ||||
|             glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); | ||||
|             for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) | ||||
|                 iva.render(); | ||||
|             for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { | ||||
|                 if (iva.has_VBOs()) | ||||
|                     iva.render(); | ||||
|             } | ||||
|         } | ||||
|         glsafe(::glPopMatrix()); | ||||
|         if (is_left_handed) | ||||
|  | @ -493,6 +512,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous | |||
|                 : (m_button_down == Button::Left | ||||
|                    ? _L("Add supports") | ||||
|                    : _L("Block supports")); | ||||
|         activate_internal_undo_redo_stack(true); | ||||
|         Plater::TakeSnapshot(wxGetApp().plater(), action_name); | ||||
|         update_model_object(); | ||||
| 
 | ||||
|  | @ -588,6 +608,8 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr | |||
|         update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER); | ||||
|     } | ||||
| 
 | ||||
|     activate_internal_undo_redo_stack(true); | ||||
| 
 | ||||
|     Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") | ||||
|                                                     : _L("Add supports by angle")); | ||||
|     update_model_object(); | ||||
|  | @ -778,12 +800,9 @@ void GLGizmoFdmSupports::on_set_state() | |||
|         return; | ||||
| 
 | ||||
|     if (m_state == On && m_old_state != On) { // the gizmo was just turned on
 | ||||
|         { | ||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned on"))); | ||||
|         } | ||||
|         if (! m_parent.get_gizmos_manager().is_serializing()) { | ||||
|             wxGetApp().CallAfter([]() { | ||||
|                 wxGetApp().plater()->enter_gizmos_stack(); | ||||
|             wxGetApp().CallAfter([this]() { | ||||
|                 activate_internal_undo_redo_stack(true); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | @ -793,11 +812,7 @@ void GLGizmoFdmSupports::on_set_state() | |||
|             m_setting_angle = false; | ||||
|             m_parent.use_slope(false); | ||||
|         } | ||||
| 
 | ||||
|         wxGetApp().plater()->leave_gizmos_stack(); | ||||
|         { | ||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned off"))); | ||||
|         } | ||||
|         activate_internal_undo_redo_stack(false); | ||||
|         m_old_mo_id = -1; | ||||
|         m_ivas.clear(); | ||||
|         m_selected_facets.clear(); | ||||
|  | @ -820,14 +835,19 @@ void GLGizmoFdmSupports::on_stop_dragging() | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive& ar) | ||||
| void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&) | ||||
| { | ||||
|     update_from_model_object(); | ||||
|     // We should update the gizmo from current ModelObject, but it is not
 | ||||
|     // possible at this point. That would require having updated selection and
 | ||||
|     // common gizmos data, which is not done at this point. Instead, save
 | ||||
|     // a flag to do the update in set_fdm_support_data, which will be called
 | ||||
|     // soon after.
 | ||||
|     m_schedule_update = true; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive& ar) const | ||||
| void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const | ||||
| { | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -64,6 +64,7 @@ private: | |||
| 
 | ||||
|     void update_model_object() const; | ||||
|     void update_from_model_object(); | ||||
|     void activate_internal_undo_redo_stack(bool activate); | ||||
| 
 | ||||
|     void select_facets_by_angle(float threshold, bool overwrite, bool block); | ||||
|     bool m_overwrite_selected = false; | ||||
|  | @ -74,6 +75,8 @@ private: | |||
|     float m_clipping_plane_distance = 0.f; | ||||
|     std::unique_ptr<ClippingPlane> m_clipping_plane; | ||||
|     bool m_setting_angle = false; | ||||
|     bool m_internal_stack_active = false; | ||||
|     bool m_schedule_update = false; | ||||
| 
 | ||||
|     // This map holds all translated description texts, so they can be easily referenced during layout calculations
 | ||||
|     // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
 | ||||
|  |  | |||
|  | @ -28,8 +28,7 @@ bool GLGizmoFlatten::on_init() | |||
| 
 | ||||
| void GLGizmoFlatten::on_set_state() | ||||
| { | ||||
|     if (m_state == On && is_plane_update_necessary()) | ||||
|         update_planes(); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const | ||||
|  | @ -81,7 +80,8 @@ void GLGizmoFlatten::on_render() const | |||
|             else | ||||
|                 glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.5f)); | ||||
| 
 | ||||
|             m_planes[i].vbo.render(); | ||||
|             if (m_planes[i].vbo.has_VBOs()) | ||||
|                 m_planes[i].vbo.render(); | ||||
|         } | ||||
|         glsafe(::glPopMatrix()); | ||||
|     } | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) | |||
|         return; | ||||
| 
 | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
|     if (mo) { | ||||
|     if (m_state == On && mo) { | ||||
|         if (m_old_mo_id != mo->id()) { | ||||
|             reload_cache(); | ||||
|             m_old_mo_id = mo->id(); | ||||
|  | @ -810,11 +810,6 @@ void GLGizmoHollow::on_set_state() | |||
|     if (m_state == m_old_state) | ||||
|         return; | ||||
| 
 | ||||
|     if (m_state == On && m_old_state != On) { // the gizmo was just turned on
 | ||||
|         // we'll now reload support points:
 | ||||
|         if (m_c->selection_info()->model_object()) | ||||
|             reload_cache(); | ||||
|     } | ||||
|     if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off
 | ||||
|         m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); | ||||
|     m_old_state = m_state; | ||||
|  |  | |||
|  | @ -67,10 +67,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S | |||
| 
 | ||||
|     ModelObject* mo = m_c->selection_info()->model_object(); | ||||
| 
 | ||||
|     if (mo && mo->id() != m_old_mo_id) { | ||||
|     if (m_state == On && mo && mo->id() != m_old_mo_id) { | ||||
|         disable_editing_mode(); | ||||
|         reload_cache(); | ||||
|         m_old_mo_id = mo->id(); | ||||
|         m_c->instances_hider()->show_supports(true); | ||||
|     } | ||||
| 
 | ||||
|     // If we triggered autogeneration before, check backend and fetch results if they are there
 | ||||
|  | @ -884,25 +885,23 @@ CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const | |||
| 
 | ||||
| void GLGizmoSlaSupports::on_set_state() | ||||
| { | ||||
|     const ModelObject* mo = m_c->selection_info()->model_object(); | ||||
| 
 | ||||
|     if (m_state == m_old_state) | ||||
|         return; | ||||
| 
 | ||||
|     if (m_state == On && m_old_state != On) { // the gizmo was just turned on
 | ||||
|         Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); | ||||
|         // This function can be called from undo/redo, when selection (and hence
 | ||||
|         // common gizmos data are not yet deserialized. The CallAfter should put
 | ||||
|         // this off until after the update is done.
 | ||||
|         wxGetApp().CallAfter([this]() { | ||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); | ||||
| 
 | ||||
|         // we'll now reload support points:
 | ||||
|         if (mo) | ||||
|             reload_cache(); | ||||
| 
 | ||||
|         // Set default head diameter from config.
 | ||||
|         const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; | ||||
|         m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value; | ||||
|         m_c->instances_hider()->show_supports(true); | ||||
|             // Set default head diameter from config.
 | ||||
|             const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; | ||||
|             m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value; | ||||
|         }); | ||||
|     } | ||||
|     if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
 | ||||
|         bool will_ask = mo && m_editing_mode && unsaved_changes(); | ||||
|         bool will_ask = m_editing_mode && unsaved_changes(); | ||||
|         if (will_ask) { | ||||
|             wxGetApp().CallAfter([this]() { | ||||
|                 // Following is called through CallAfter, because otherwise there was a problem
 | ||||
|  | @ -922,7 +921,7 @@ void GLGizmoSlaSupports::on_set_state() | |||
|             disable_editing_mode(); // so it is not active next time the gizmo opens
 | ||||
|             Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); | ||||
|             m_normal_cache.clear(); | ||||
| 
 | ||||
|             m_old_mo_id = -1; | ||||
|         } | ||||
|     } | ||||
|     m_old_state = m_state; | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ | |||
| #include "slic3r/GUI/Camera.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| 
 | ||||
| #include "slic3r/GUI/PresetBundle.hpp" | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
|  | @ -170,7 +172,8 @@ void InstancesHider::show_supports(bool show) { | |||
| void HollowedMesh::on_update() | ||||
| { | ||||
|     const ModelObject* mo = get_pool()->selection_info()->model_object(); | ||||
|     if (! mo) | ||||
|     bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; | ||||
|     if (! mo || ! is_sla) | ||||
|         return; | ||||
| 
 | ||||
|     const GLCanvas3D* canvas = get_pool()->get_canvas(); | ||||
|  | @ -376,7 +379,8 @@ void ObjectClipper::set_position(double pos, bool keep_normal) | |||
| void SupportsClipper::on_update() | ||||
| { | ||||
|     const ModelObject* mo = get_pool()->selection_info()->model_object(); | ||||
|     if (! mo) | ||||
|     bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; | ||||
|     if (! mo || ! is_sla) | ||||
|         return; | ||||
| 
 | ||||
|     const GLCanvas3D* canvas = get_pool()->get_canvas(); | ||||
|  |  | |||
|  | @ -1104,9 +1104,16 @@ void GLGizmosManager::activate_gizmo(EType type) | |||
|     } | ||||
| 
 | ||||
|     m_current = type; | ||||
|     m_common_gizmos_data->update(get_current() | ||||
|                            ? get_current()->get_requirements() | ||||
|                            : CommonGizmosDataID(0)); | ||||
| 
 | ||||
|     // Updating common data should be left to the update_data function, which
 | ||||
|     // is always called after this one. activate_gizmo can be called by undo/redo,
 | ||||
|     // when selection is not yet deserialized, so the common data would update
 | ||||
|     // incorrectly (or crash if relying on unempty selection). Undo/redo stack
 | ||||
|     // will also call update_data, after selection is restored.
 | ||||
| 
 | ||||
|     //m_common_gizmos_data->update(get_current()
 | ||||
|     //                       ? get_current()->get_requirements()
 | ||||
|     //                       : CommonGizmosDataID(0));
 | ||||
| 
 | ||||
|     if (type != Undefined) | ||||
|         m_gizmos[type]->set_state(GLGizmoBase::On); | ||||
|  |  | |||
|  | @ -141,11 +141,6 @@ public: | |||
|         EType new_current = m_current; | ||||
|         m_current = old_current; | ||||
| 
 | ||||
|         // Update common data. They should be updated when activate_gizmo is
 | ||||
|         // called, so it can be used in on_set_state which is called from there.
 | ||||
|         if (new_current != Undefined) | ||||
|             m_common_gizmos_data->update(m_gizmos[new_current]->get_requirements()); | ||||
| 
 | ||||
|         // activate_gizmo call sets m_current and calls set_state for the gizmo
 | ||||
|         // it does nothing in case the gizmo is already activated
 | ||||
|         // it can safely be called for Undefined gizmo
 | ||||
|  |  | |||
|  | @ -329,7 +329,7 @@ void MainFrame::init_tabpanel() | |||
|         Tab* tab = dynamic_cast<Tab*>(panel); | ||||
| 
 | ||||
|         // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology
 | ||||
|         if (panel == nullptr || (tab && tab->supports_printer_technology(m_plater->printer_technology()))) | ||||
|         if (panel == nullptr || (tab && ! tab->supports_printer_technology(m_plater->printer_technology()))) | ||||
|             return; | ||||
| 
 | ||||
|         auto& tabs_list = wxGetApp().tabs_list; | ||||
|  |  | |||
|  | @ -49,7 +49,8 @@ void MeshClipper::render_cut() | |||
|     if (! m_triangles_valid) | ||||
|         recalculate_triangles(); | ||||
| 
 | ||||
|     m_vertex_array.render(); | ||||
|     if (m_vertex_array.has_VBOs()) | ||||
|         m_vertex_array.render(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1589,6 +1589,7 @@ struct Plater::priv | |||
|     Mouse3DController mouse3d_controller; | ||||
|     View3D* view3D; | ||||
|     GLToolbar view_toolbar; | ||||
|     GLToolbar collapse_toolbar; | ||||
|     Preview *preview; | ||||
| 
 | ||||
|     BackgroundSlicingProcess    background_process; | ||||
|  | @ -1683,6 +1684,7 @@ struct Plater::priv | |||
|     void reset_canvas_volumes(); | ||||
| 
 | ||||
|     bool init_view_toolbar(); | ||||
|     bool init_collapse_toolbar(); | ||||
| 
 | ||||
|     void reset_all_gizmos(); | ||||
|     void update_ui_from_settings(); | ||||
|  | @ -1878,6 +1880,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     , m_ui_jobs(this) | ||||
|     , delayed_scene_refresh(false) | ||||
|     , view_toolbar(GLToolbar::Radio, "View") | ||||
|     , collapse_toolbar(GLToolbar::Normal, "Collapse") | ||||
|     , m_project_filename(wxEmptyString) | ||||
| { | ||||
|     this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
|  | @ -3922,6 +3925,51 @@ bool Plater::priv::init_view_toolbar() | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::init_collapse_toolbar() | ||||
| { | ||||
|     if (collapse_toolbar.get_items_count() > 0) | ||||
|         // already initialized
 | ||||
|         return true; | ||||
| 
 | ||||
|     BackgroundTexture::Metadata background_data; | ||||
|     background_data.filename = "toolbar_background.png"; | ||||
|     background_data.left = 16; | ||||
|     background_data.top = 16; | ||||
|     background_data.right = 16; | ||||
|     background_data.bottom = 16; | ||||
| 
 | ||||
|     if (!collapse_toolbar.init(background_data)) | ||||
|         return false; | ||||
| 
 | ||||
|     collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); | ||||
|     collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); | ||||
|     collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); | ||||
|     collapse_toolbar.set_border(5.0f); | ||||
|     collapse_toolbar.set_separator_size(5); | ||||
|     collapse_toolbar.set_gap_size(2); | ||||
| 
 | ||||
|     GLToolbarItem::Data item; | ||||
| 
 | ||||
|     item.name = "collapse_sidebar"; | ||||
|     item.icon_filename = "collapse.svg"; | ||||
|     item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); | ||||
|     item.sprite_id = 0; | ||||
|     item.left.action_callback = [this, item]() { | ||||
|         std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? | ||||
|             _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); | ||||
| 
 | ||||
|         int id = collapse_toolbar.get_item_id("collapse_sidebar"); | ||||
|         collapse_toolbar.set_tooltip(id, new_tooltip); | ||||
| 
 | ||||
|         wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); | ||||
|     }; | ||||
| 
 | ||||
|     if (!collapse_toolbar.add_item(item)) | ||||
|         return false; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::can_set_instance_to_object() const | ||||
| { | ||||
|     const int obj_idx = get_selected_object_idx(); | ||||
|  | @ -5531,6 +5579,11 @@ bool Plater::init_view_toolbar() | |||
|     return p->init_view_toolbar(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::init_collapse_toolbar() | ||||
| { | ||||
|     return p->init_collapse_toolbar(); | ||||
| } | ||||
| 
 | ||||
| const Camera& Plater::get_camera() const | ||||
| { | ||||
|     return p->camera; | ||||
|  | @ -5574,6 +5627,16 @@ GLToolbar& Plater::get_view_toolbar() | |||
|     return p->view_toolbar; | ||||
| } | ||||
| 
 | ||||
| const GLToolbar& Plater::get_collapse_toolbar() const | ||||
| { | ||||
|     return p->collapse_toolbar; | ||||
| } | ||||
| 
 | ||||
| GLToolbar& Plater::get_collapse_toolbar() | ||||
| { | ||||
|     return p->collapse_toolbar; | ||||
| } | ||||
| 
 | ||||
| const Mouse3DController& Plater::get_mouse3d_controller() const | ||||
| { | ||||
|     return p->mouse3d_controller; | ||||
|  |  | |||
|  | @ -315,6 +315,7 @@ public: | |||
|     void sys_color_changed(); | ||||
| 
 | ||||
|     bool init_view_toolbar(); | ||||
|     bool init_collapse_toolbar(); | ||||
| 
 | ||||
|     const Camera& get_camera() const; | ||||
|     Camera& get_camera(); | ||||
|  | @ -330,6 +331,9 @@ public: | |||
|     const GLToolbar& get_view_toolbar() const; | ||||
|     GLToolbar& get_view_toolbar(); | ||||
| 
 | ||||
|     const GLToolbar& get_collapse_toolbar() const; | ||||
|     GLToolbar& get_collapse_toolbar(); | ||||
| 
 | ||||
|     const Mouse3DController& get_mouse3d_controller() const; | ||||
|     Mouse3DController& get_mouse3d_controller(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include <libslic3r/Polyline.hpp> | ||||
| #include <libslic3r/EdgeGrid.hpp> | ||||
| #include <libslic3r/Geometry.hpp> | ||||
| #include <libslic3r/VoronoiOffset.hpp> | ||||
| 
 | ||||
| #define BOOST_VORONOI_USE_GMP 1 | ||||
| #include "boost/polygon/voronoi.hpp" | ||||
|  | @ -16,12 +17,7 @@ using boost::polygon::voronoi_diagram; | |||
| 
 | ||||
| using namespace Slic3r; | ||||
| 
 | ||||
| struct VD : public boost::polygon::voronoi_diagram<double> { | ||||
|     typedef double                                          coord_type; | ||||
|     typedef boost::polygon::point_data<coordinate_type>     point_type; | ||||
|     typedef boost::polygon::segment_data<coordinate_type>   segment_type; | ||||
|     typedef boost::polygon::rectangle_data<coordinate_type> rect_type; | ||||
| }; | ||||
| using VD = Geometry::VoronoiDiagram; | ||||
| 
 | ||||
| // #define VORONOI_DEBUG_OUT
 | ||||
| 
 | ||||
|  | @ -322,6 +318,7 @@ static inline void dump_voronoi_to_svg( | |||
|     /* const */ VD      &vd, | ||||
|     const Points        &points, | ||||
|     const Lines         &lines, | ||||
|     const Polygons      &offset_curves = Polygons(), | ||||
|     const double         scale = 0.7) // 0.2?
 | ||||
| { | ||||
|     const std::string   inputSegmentPointColor      = "lightseagreen"; | ||||
|  | @ -336,6 +333,9 @@ static inline void dump_voronoi_to_svg( | |||
|     const std::string   voronoiArcColor             = "red"; | ||||
|     const coord_t       voronoiLineWidth            = coord_t(0.02 * scale / SCALING_FACTOR); | ||||
| 
 | ||||
|     const std::string   offsetCurveColor            = "magenta"; | ||||
|     const coord_t       offsetCurveLineWidth        = coord_t(0.09 * scale / SCALING_FACTOR); | ||||
| 
 | ||||
|     const bool          internalEdgesOnly           = false; | ||||
|     const bool          primaryEdgesOnly            = false; | ||||
| 
 | ||||
|  | @ -408,6 +408,7 @@ static inline void dump_voronoi_to_svg( | |||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); | ||||
|     svg.Close(); | ||||
| } | ||||
| #endif | ||||
|  | @ -1585,6 +1586,32 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") | |||
| 
 | ||||
| #ifdef VORONOI_DEBUG_OUT | ||||
|     dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), | ||||
|         vd, Points(), lines, 0.015); | ||||
|         vd, Points(), lines, Polygons(), 0.015); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("Voronoi offset", "[VoronoiOffset]") | ||||
| { | ||||
|   Polygons poly_with_hole = { Polygon { | ||||
|         {       0, 10000000}, | ||||
|         {  700000,        0}, | ||||
|         {  700000,  9000000}, | ||||
|         { 9100000,  9000000}, | ||||
|         { 9100000,        0}, | ||||
|         {10000000, 10000000} | ||||
|         } | ||||
|   }; | ||||
| 
 | ||||
|   VD vd; | ||||
|   Lines lines = to_lines(poly_with_hole); | ||||
|   construct_voronoi(lines.begin(), lines.end(), &vd); | ||||
| 
 | ||||
|   Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); | ||||
| 
 | ||||
| #ifdef VORONOI_DEBUG_OUT | ||||
|   dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(), | ||||
|       vd, Points(), lines, offsetted_polygons); | ||||
| #endif | ||||
| 
 | ||||
|   REQUIRE(offsetted_polygons.size() == 2); | ||||
| } | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ TEST_CASE("Raycaster - find intersections of a line and cylinder") | |||
|     REQUIRE(std::abs(out[1].first - std::sqrt(72.f)) < 0.001f); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #ifdef SLIC3R_HOLE_RAYCASTER | ||||
| // Create a simple scene with a 20mm cube and a big hole in the front wall 
 | ||||
| // with 5mm radius. Then shoot rays from interesting positions and see where
 | ||||
| // they land.
 | ||||
|  | @ -94,3 +94,4 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]") | |||
|     // Check for support tree correctness
 | ||||
|     test_support_model_collision("20mm_cube.obj", {}, hcfg, holes); | ||||
| } | ||||
| #endif | ||||
|  |  | |||
|  | @ -105,8 +105,13 @@ void test_supports(const std::string          &obj_filename, | |||
|     // Create the special index-triangle mesh with spatial indexing which
 | ||||
|     // is the input of the support point and support mesh generators
 | ||||
|     sla::EigenMesh3D emesh{mesh}; | ||||
| 
 | ||||
| #ifdef SLIC3R_HOLE_RAYCASTER | ||||
|     if (hollowingcfg.enabled)  | ||||
|         emesh.load_holes(drainholes); | ||||
| #endif | ||||
| 
 | ||||
|     // TODO: do the cgal hole cutting...
 | ||||
|      | ||||
|     // Create the support point generator
 | ||||
|     sla::SupportPointGenerator::Config autogencfg; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 YuSanka
						YuSanka