mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Fixed conflicts after merge with master
This commit is contained in:
		
						commit
						c3d643ead3
					
				
					 28 changed files with 497 additions and 185 deletions
				
			
		| 
						 | 
				
			
			@ -77,7 +77,7 @@ inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag
 | 
			
		|||
    #define DISABLE_BOOST_OFFSET
 | 
			
		||||
 | 
			
		||||
    using ClipperLib::ClipperOffset;
 | 
			
		||||
    using ClipperLib::jtMiter;
 | 
			
		||||
    using ClipperLib::jtSquare;
 | 
			
		||||
    using ClipperLib::etClosedPolygon;
 | 
			
		||||
    using ClipperLib::Paths;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,8 +85,8 @@ inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag
 | 
			
		|||
    
 | 
			
		||||
    try {
 | 
			
		||||
        ClipperOffset offs;
 | 
			
		||||
        offs.AddPath(sh.Contour, jtMiter, etClosedPolygon);
 | 
			
		||||
        offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon);
 | 
			
		||||
        offs.AddPath(sh.Contour, jtSquare, etClosedPolygon);
 | 
			
		||||
        offs.AddPaths(sh.Holes, jtSquare, etClosedPolygon);
 | 
			
		||||
        offs.Execute(result, static_cast<double>(distance));
 | 
			
		||||
    } catch (ClipperLib::clipperException &) {
 | 
			
		||||
        throw GeometryException(GeomErr::OFFSET);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -528,15 +528,12 @@ public:
 | 
			
		|||
 | 
			
		||||
    static inline double overfit(const Box& bb, const Box& bin)
 | 
			
		||||
    {
 | 
			
		||||
        auto Bw = bin.width();
 | 
			
		||||
        auto Bh = bin.height();
 | 
			
		||||
        auto mBw = -Bw;
 | 
			
		||||
        auto mBh = -Bh;
 | 
			
		||||
        auto wdiff = double(bb.width()) + mBw;
 | 
			
		||||
        auto hdiff = double(bb.height()) + mBh;
 | 
			
		||||
        double diff = 0;
 | 
			
		||||
        if(wdiff > 0) diff += wdiff;
 | 
			
		||||
        if(hdiff > 0) diff += hdiff;
 | 
			
		||||
        auto wdiff = TCompute<RawShape>(bb.width()) - bin.width();
 | 
			
		||||
        auto hdiff = TCompute<RawShape>(bb.height()) - bin.height();
 | 
			
		||||
        double diff = .0;
 | 
			
		||||
        if(wdiff > 0) diff += double(wdiff);
 | 
			
		||||
        if(hdiff > 0) diff += double(hdiff);
 | 
			
		||||
 | 
			
		||||
        return diff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -396,11 +396,14 @@ template<> std::function<double(const Item&)> AutoArranger<Box>::get_objfn()
 | 
			
		|||
        double score = std::get<0>(result);
 | 
			
		||||
        auto& fullbb = std::get<1>(result);
 | 
			
		||||
        
 | 
			
		||||
        double miss = Placer::overfit(fullbb, m_bin);
 | 
			
		||||
        auto bin = m_bin;
 | 
			
		||||
        sl::offset(bin, -EPSILON * (m_bin.width() + m_bin.height()));
 | 
			
		||||
 | 
			
		||||
        double miss = Placer::overfit(fullbb, bin);
 | 
			
		||||
        miss = miss > 0? miss : 0;
 | 
			
		||||
        score += miss*miss;
 | 
			
		||||
        
 | 
			
		||||
        return score;    
 | 
			
		||||
        return score;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1334,7 +1334,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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1709,22 +1714,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.
 | 
			
		||||
| 
						 | 
				
			
			@ -1808,28 +1798,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1840,6 +1821,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -1979,6 +1965,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
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -2003,7 +2041,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();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -411,6 +411,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; }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -446,6 +452,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -459,6 +471,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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,6 +45,9 @@
 | 
			
		|||
// Enable rendering of objects using environment map
 | 
			
		||||
#define ENABLE_ENVIRONMENT_MAP (1 && ENABLE_2_3_0_ALPHA1)
 | 
			
		||||
 | 
			
		||||
// Enable smoothing of objects normals
 | 
			
		||||
#define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1)
 | 
			
		||||
 | 
			
		||||
// Enable G-Code viewer
 | 
			
		||||
#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1)
 | 
			
		||||
#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,11 @@
 | 
			
		|||
#include <GL/glew.h>
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
#include <igl/per_face_normals.h>
 | 
			
		||||
#include <igl/per_corner_normals.h>
 | 
			
		||||
#include <igl/per_vertex_normals.h>
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
 | 
			
		||||
#include "3DScene.hpp"
 | 
			
		||||
#include "GLShader.hpp"
 | 
			
		||||
#include "GUI_App.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -62,23 +68,107 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char
 | 
			
		|||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
 | 
			
		||||
void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh)
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& normals)
 | 
			
		||||
{
 | 
			
		||||
    mesh.repair();
 | 
			
		||||
 | 
			
		||||
    using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
 | 
			
		||||
    using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
 | 
			
		||||
 | 
			
		||||
    std::vector<stl_normal> face_normals(mesh.stl.stats.number_of_facets);
 | 
			
		||||
    for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
 | 
			
		||||
        face_normals[i] = mesh.stl.facet_start[i].normal;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
 | 
			
		||||
        Eigen::Index(mesh.its.vertices.size()), 3).cast<double>();
 | 
			
		||||
    Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(),
 | 
			
		||||
        Eigen::Index(mesh.its.indices.size()), 3);
 | 
			
		||||
    Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(),
 | 
			
		||||
        Eigen::Index(face_normals.size()), 3).cast<double>();
 | 
			
		||||
    Eigen::MatrixXd out_normals;
 | 
			
		||||
 | 
			
		||||
    igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals);
 | 
			
		||||
 | 
			
		||||
    normals = std::vector<stl_normal>(mesh.its.vertices.size());
 | 
			
		||||
    for (size_t i = 0; i < mesh.its.indices.size(); ++i) {
 | 
			
		||||
        for (size_t j = 0; j < 3; ++j) {
 | 
			
		||||
            normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast<float>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void smooth_normals_vertex(TriangleMesh& mesh, std::vector<stl_normal>& normals)
 | 
			
		||||
{
 | 
			
		||||
    mesh.repair();
 | 
			
		||||
 | 
			
		||||
    using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
 | 
			
		||||
    using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
 | 
			
		||||
 | 
			
		||||
    Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
 | 
			
		||||
        Eigen::Index(mesh.its.vertices.size()), 3).cast<double>();
 | 
			
		||||
    Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(),
 | 
			
		||||
        Eigen::Index(mesh.its.indices.size()), 3);
 | 
			
		||||
    Eigen::MatrixXd out_normals;
 | 
			
		||||
 | 
			
		||||
//    igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM, out_normals);
 | 
			
		||||
//    igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA, out_normals);
 | 
			
		||||
    igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE, out_normals);
 | 
			
		||||
//    igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT, out_normals);
 | 
			
		||||
 | 
			
		||||
    normals = std::vector<stl_normal>(mesh.its.vertices.size());
 | 
			
		||||
    for (size_t i = 0; i < static_cast<size_t>(out_normals.rows()); ++i) {
 | 
			
		||||
        normals[i] = out_normals.row(i).cast<float>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals)
 | 
			
		||||
#else
 | 
			
		||||
void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh& mesh)
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
{
 | 
			
		||||
    assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
 | 
			
		||||
    assert(quad_indices.empty() && triangle_indices_size == 0);
 | 
			
		||||
    assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
 | 
			
		||||
 | 
			
		||||
    this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
    if (smooth_normals) {
 | 
			
		||||
        TriangleMesh new_mesh(mesh);
 | 
			
		||||
        std::vector<stl_normal> normals;
 | 
			
		||||
        smooth_normals_corner(new_mesh, normals);
 | 
			
		||||
//        smooth_normals_vertex(new_mesh, normals);
 | 
			
		||||
 | 
			
		||||
    unsigned int vertices_count = 0;
 | 
			
		||||
    for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) {
 | 
			
		||||
        const stl_facet &facet = mesh.stl.facet_start[i];
 | 
			
		||||
        for (int j = 0; j < 3; ++j)
 | 
			
		||||
            this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
 | 
			
		||||
        this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 2 * new_mesh.its.vertices.size());
 | 
			
		||||
        for (size_t i = 0; i < new_mesh.its.vertices.size(); ++i) {
 | 
			
		||||
            const stl_vertex& v = new_mesh.its.vertices[i];
 | 
			
		||||
            const stl_normal& n = normals[i];
 | 
			
		||||
            this->push_geometry(v(0), v(1), v(2), n(0), n(1), n(2));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
 | 
			
		||||
        vertices_count += 3;
 | 
			
		||||
        for (size_t i = 0; i < new_mesh.its.indices.size(); ++i) {
 | 
			
		||||
            const stl_triangle_vertex_indices& idx = new_mesh.its.indices[i];
 | 
			
		||||
            this->push_triangle(idx(0), idx(1), idx(2));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
        this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
 | 
			
		||||
 | 
			
		||||
        unsigned int vertices_count = 0;
 | 
			
		||||
        for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) {
 | 
			
		||||
            const stl_facet& facet = mesh.stl.facet_start[i];
 | 
			
		||||
            for (int j = 0; j < 3; ++j)
 | 
			
		||||
                this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
 | 
			
		||||
 | 
			
		||||
            this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
 | 
			
		||||
            vertices_count += 3;
 | 
			
		||||
        }
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
    }
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GLIndexedVertexArray::finalize_geometry(bool opengl_initialized)
 | 
			
		||||
| 
						 | 
				
			
			@ -466,7 +556,11 @@ int GLVolumeCollection::load_object_volume(
 | 
			
		|||
    this->volumes.emplace_back(new GLVolume(color));
 | 
			
		||||
    GLVolume& v = *this->volumes.back();
 | 
			
		||||
    v.set_color_from_model_volume(model_volume);
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
    v.indexed_vertex_array.load_mesh(mesh, true);
 | 
			
		||||
#else
 | 
			
		||||
    v.indexed_vertex_array.load_mesh(mesh);
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
    v.indexed_vertex_array.finalize_geometry(opengl_initialized);
 | 
			
		||||
    v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
 | 
			
		||||
    if (model_volume->is_model_part())
 | 
			
		||||
| 
						 | 
				
			
			@ -508,8 +602,12 @@ void GLVolumeCollection::load_object_auxiliary(
 | 
			
		|||
        const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
 | 
			
		||||
        this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
 | 
			
		||||
        GLVolume& v = *this->volumes.back();
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
        v.indexed_vertex_array.load_mesh(mesh, true);
 | 
			
		||||
#else
 | 
			
		||||
        v.indexed_vertex_array.load_mesh(mesh);
 | 
			
		||||
	    v.indexed_vertex_array.finalize_geometry(opengl_initialized);
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
        v.indexed_vertex_array.finalize_geometry(opengl_initialized);
 | 
			
		||||
        v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
 | 
			
		||||
        v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
 | 
			
		||||
        // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -119,8 +119,13 @@ public:
 | 
			
		|||
    unsigned int       triangle_indices_VBO_id{ 0 };
 | 
			
		||||
    unsigned int       quad_indices_VBO_id{ 0 };
 | 
			
		||||
 | 
			
		||||
    void load_mesh_full_shading(const TriangleMesh &mesh);
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
    void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false);
 | 
			
		||||
    void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); }
 | 
			
		||||
#else
 | 
			
		||||
    void load_mesh_full_shading(const TriangleMesh& mesh);
 | 
			
		||||
    void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); }
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
 | 
			
		||||
    inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2522,11 +2522,19 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
 | 
			
		|||
                                TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles);
 | 
			
		||||
	                            assert(! mesh.empty());
 | 
			
		||||
                                mesh.transform(sla_print->sla_trafo(*m_model->objects[volume.object_idx()]).inverse());
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
                                volume.indexed_vertex_array.load_mesh(mesh, true);
 | 
			
		||||
#else
 | 
			
		||||
                                volume.indexed_vertex_array.load_mesh(mesh);
 | 
			
		||||
	                        } else {
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
                            } else {
 | 
			
		||||
	                        	// Reload the original volume.
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
                                volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true);
 | 
			
		||||
#else
 | 
			
		||||
                                volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh());
 | 
			
		||||
	                        }
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
                            }
 | 
			
		||||
                            volume.finalize_geometry(true);
 | 
			
		||||
	                    }
 | 
			
		||||
                    	//FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable
 | 
			
		||||
| 
						 | 
				
			
			@ -6960,7 +6968,11 @@ void GLCanvas3D::_load_sla_shells()
 | 
			
		|||
        const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) {
 | 
			
		||||
        m_volumes.volumes.emplace_back(new GLVolume(color));
 | 
			
		||||
        GLVolume& v = *m_volumes.volumes.back();
 | 
			
		||||
#if ENABLE_SMOOTH_NORMALS
 | 
			
		||||
        v.indexed_vertex_array.load_mesh(mesh, true);
 | 
			
		||||
#else
 | 
			
		||||
        v.indexed_vertex_array.load_mesh(mesh);
 | 
			
		||||
#endif // ENABLE_SMOOTH_NORMALS
 | 
			
		||||
        v.indexed_vertex_array.finalize_geometry(this->m_initialized);
 | 
			
		||||
        v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled;
 | 
			
		||||
        v.composite_id.volume_id = volume_id;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -695,33 +695,41 @@ void ObjectList::selection_changed()
 | 
			
		|||
    part_selection_changed();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::fill_layer_config_ranges_cache()
 | 
			
		||||
void ObjectList::copy_layers_to_clipboard()
 | 
			
		||||
{
 | 
			
		||||
    wxDataViewItemArray sel_layers;
 | 
			
		||||
    GetSelections(sel_layers);
 | 
			
		||||
 | 
			
		||||
    const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]);
 | 
			
		||||
    const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers.front());
 | 
			
		||||
    if (obj_idx < 0 || (int)m_objects->size() <= obj_idx)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
 | 
			
		||||
    m_layer_config_ranges_cache.clear();
 | 
			
		||||
    t_layer_config_ranges& cache_ranges = m_clipboard.get_ranges_cache();
 | 
			
		||||
 | 
			
		||||
    if (sel_layers.Count() == 1 && m_objects_model->GetItemType(sel_layers.front()) & itLayerRoot)
 | 
			
		||||
    {
 | 
			
		||||
        cache_ranges.clear();
 | 
			
		||||
        cache_ranges = ranges;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const auto layer_item : sel_layers)
 | 
			
		||||
        if (m_objects_model->GetItemType(layer_item) & itLayer) {
 | 
			
		||||
            auto range = m_objects_model->GetLayerRangeByItem(layer_item);
 | 
			
		||||
            auto it = ranges.find(range);
 | 
			
		||||
            if (it != ranges.end())
 | 
			
		||||
                m_layer_config_ranges_cache[it->first] = it->second;
 | 
			
		||||
                cache_ranges[it->first] = it->second;
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::paste_layers_into_list()
 | 
			
		||||
{
 | 
			
		||||
    const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection());
 | 
			
		||||
    t_layer_config_ranges& cache_ranges = m_clipboard.get_ranges_cache();
 | 
			
		||||
 | 
			
		||||
    if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || 
 | 
			
		||||
        m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA)
 | 
			
		||||
        cache_ranges.empty() || printer_technology() == ptSLA)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx);
 | 
			
		||||
| 
						 | 
				
			
			@ -732,7 +740,7 @@ void ObjectList::paste_layers_into_list()
 | 
			
		|||
    t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
 | 
			
		||||
 | 
			
		||||
    // and create Layer item(s) according to the layer_config_ranges
 | 
			
		||||
    for (const auto range : m_layer_config_ranges_cache)
 | 
			
		||||
    for (const auto range : cache_ranges)
 | 
			
		||||
        ranges.emplace(range);
 | 
			
		||||
 | 
			
		||||
    layers_item = add_layer_root_item(object_item);
 | 
			
		||||
| 
						 | 
				
			
			@ -745,6 +753,48 @@ void ObjectList::paste_layers_into_list()
 | 
			
		|||
#endif //no __WXOSX__
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::copy_settings_to_clipboard()
 | 
			
		||||
{
 | 
			
		||||
    wxDataViewItem item = GetSelection();
 | 
			
		||||
    assert(item.IsOk());
 | 
			
		||||
    if (m_objects_model->GetItemType(item) & itSettings)
 | 
			
		||||
        item = m_objects_model->GetParent(item);
 | 
			
		||||
 | 
			
		||||
    DynamicPrintConfig& config_cache = m_clipboard.get_config_cache();
 | 
			
		||||
    config_cache = get_item_config(item);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::paste_settings_into_list()
 | 
			
		||||
{
 | 
			
		||||
    wxDataViewItem item = GetSelection();
 | 
			
		||||
    assert(item.IsOk());
 | 
			
		||||
    if (m_objects_model->GetItemType(item) & itSettings)
 | 
			
		||||
        item = m_objects_model->GetParent(item);
 | 
			
		||||
 | 
			
		||||
    ItemType item_type = m_objects_model->GetItemType(item);
 | 
			
		||||
    if(!(item_type & (itObject | itVolume |itLayer)))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    DynamicPrintConfig& config_cache = m_clipboard.get_config_cache();
 | 
			
		||||
    assert(!config_cache.empty());
 | 
			
		||||
 | 
			
		||||
    auto keys = config_cache.keys();
 | 
			
		||||
    auto part_options = get_options(true);
 | 
			
		||||
 | 
			
		||||
    for (const std::string& opt_key: keys) {
 | 
			
		||||
        if (item_type & (itVolume | itLayer) &&
 | 
			
		||||
            std::find(part_options.begin(), part_options.end(), opt_key) == part_options.end())
 | 
			
		||||
            continue; // we can't to add object specific options for the part's(itVolume | itLayer) config 
 | 
			
		||||
 | 
			
		||||
        const ConfigOption* option = config_cache.option(opt_key);
 | 
			
		||||
        if (option)
 | 
			
		||||
            m_config->set_key_value(opt_key, option->clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add settings item for object/sub-object and show them 
 | 
			
		||||
    show_settings(add_settings_item(item, m_config));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes)
 | 
			
		||||
{
 | 
			
		||||
    if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx))
 | 
			
		||||
| 
						 | 
				
			
			@ -984,20 +1034,46 @@ void ObjectList::extruder_editing()
 | 
			
		|||
 | 
			
		||||
void ObjectList::copy()
 | 
			
		||||
{
 | 
			
		||||
    // if (m_selection_mode & smLayer)
 | 
			
		||||
    //     fill_layer_config_ranges_cache();
 | 
			
		||||
    // else {
 | 
			
		||||
    //     m_layer_config_ranges_cache.clear();
 | 
			
		||||
        wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY));
 | 
			
		||||
    // }
 | 
			
		||||
    wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::paste()
 | 
			
		||||
{
 | 
			
		||||
    // if (!m_layer_config_ranges_cache.empty())
 | 
			
		||||
    //     paste_layers_into_list();
 | 
			
		||||
    // else
 | 
			
		||||
        wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE));
 | 
			
		||||
    wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ObjectList::copy_to_clipboard()
 | 
			
		||||
{
 | 
			
		||||
    wxDataViewItemArray sels;
 | 
			
		||||
    GetSelections(sels);
 | 
			
		||||
    ItemType type = m_objects_model->GetItemType(sels.front());
 | 
			
		||||
    if (!(type & (itSettings | itLayer | itLayerRoot))) {
 | 
			
		||||
        m_clipboard.reset();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (type & itSettings)
 | 
			
		||||
        copy_settings_to_clipboard();
 | 
			
		||||
    if (type & (itLayer | itLayerRoot))
 | 
			
		||||
        copy_layers_to_clipboard();
 | 
			
		||||
 | 
			
		||||
    m_clipboard.set_type(type);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ObjectList::paste_from_clipboard()
 | 
			
		||||
{
 | 
			
		||||
    if (!(m_clipboard.get_type() & (itSettings | itLayer | itLayerRoot))) {
 | 
			
		||||
        m_clipboard.reset();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_clipboard.get_type() & itSettings)
 | 
			
		||||
        paste_settings_into_list();
 | 
			
		||||
    if (m_clipboard.get_type() & (itLayer | itLayerRoot))
 | 
			
		||||
        paste_layers_into_list();
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectList::undo()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,8 @@
 | 
			
		|||
#include "wxExtensions.hpp"
 | 
			
		||||
#include "ObjectDataViewModel.hpp"
 | 
			
		||||
 | 
			
		||||
#include "libslic3r/PrintConfig.hpp"
 | 
			
		||||
 | 
			
		||||
class wxBoxSizer;
 | 
			
		||||
class wxBitmapComboBox;
 | 
			
		||||
class wxMenuItem;
 | 
			
		||||
| 
						 | 
				
			
			@ -81,10 +83,32 @@ public:
 | 
			
		|||
        smLayerRoot = 16, // used for undo/redo
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct Clipboard
 | 
			
		||||
    {
 | 
			
		||||
        void reset() {
 | 
			
		||||
            m_type = itUndef; 
 | 
			
		||||
            m_layer_config_ranges_cache .clear();
 | 
			
		||||
            m_config_cache.clear();
 | 
			
		||||
        }
 | 
			
		||||
        bool        empty()    const { return m_type == itUndef; }
 | 
			
		||||
        ItemType    get_type() const { return m_type; }
 | 
			
		||||
        void        set_type(ItemType type) { m_type = type; }
 | 
			
		||||
 | 
			
		||||
        t_layer_config_ranges&  get_ranges_cache() { return m_layer_config_ranges_cache; }
 | 
			
		||||
        DynamicPrintConfig&     get_config_cache() { return m_config_cache; }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        ItemType                m_type {itUndef};
 | 
			
		||||
        t_layer_config_ranges   m_layer_config_ranges_cache;
 | 
			
		||||
        DynamicPrintConfig      m_config_cache;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    SELECTION_MODE  m_selection_mode {smUndef};
 | 
			
		||||
    int             m_selected_layers_range_idx;
 | 
			
		||||
 | 
			
		||||
    Clipboard       m_clipboard;
 | 
			
		||||
 | 
			
		||||
    struct dragged_item_data
 | 
			
		||||
    {
 | 
			
		||||
        void init(const int obj_idx, const int subobj_idx, const ItemType type) {
 | 
			
		||||
| 
						 | 
				
			
			@ -148,8 +172,6 @@ private:
 | 
			
		|||
 | 
			
		||||
    std::vector<wxBitmap*>      m_bmp_vector;
 | 
			
		||||
 | 
			
		||||
    t_layer_config_ranges       m_layer_config_ranges_cache;
 | 
			
		||||
 | 
			
		||||
    int			m_selected_object_id = -1;
 | 
			
		||||
    bool		m_prevent_list_events = false;		// We use this flag to avoid circular event handling Select() 
 | 
			
		||||
                                                    // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler 
 | 
			
		||||
| 
						 | 
				
			
			@ -230,6 +252,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    void                copy();
 | 
			
		||||
    void                paste();
 | 
			
		||||
    bool                copy_to_clipboard();
 | 
			
		||||
    bool                paste_from_clipboard();
 | 
			
		||||
    void                undo();
 | 
			
		||||
    void                redo();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -385,8 +409,11 @@ public:
 | 
			
		|||
    void fix_through_netfabb();
 | 
			
		||||
    void update_item_error_icon(const int obj_idx, int vol_idx) const ;
 | 
			
		||||
 | 
			
		||||
    void fill_layer_config_ranges_cache();
 | 
			
		||||
    void copy_layers_to_clipboard();
 | 
			
		||||
    void paste_layers_into_list();
 | 
			
		||||
    void copy_settings_to_clipboard();
 | 
			
		||||
    void paste_settings_into_list();
 | 
			
		||||
    bool clipboard_is_empty() const { return m_clipboard.empty(); } 
 | 
			
		||||
    void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes);
 | 
			
		||||
    void paste_objects_into_list(const std::vector<size_t>& object_idxs);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -325,21 +325,22 @@ void MainFrame::init_tabpanel()
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) {
 | 
			
		||||
        auto panel = m_tabpanel->GetCurrentPage();
 | 
			
		||||
        wxWindow* panel = m_tabpanel->GetCurrentPage();
 | 
			
		||||
        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 || !static_cast<Tab*>(panel)->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;
 | 
			
		||||
        if (find(tabs_list.begin(), tabs_list.end(), panel) != tabs_list.end()) {
 | 
			
		||||
        if (tab && std::find(tabs_list.begin(), tabs_list.end(), tab) != tabs_list.end()) {
 | 
			
		||||
            // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered
 | 
			
		||||
            // before the MainFrame is fully set up.
 | 
			
		||||
            static_cast<Tab*>(panel)->OnActivate();
 | 
			
		||||
            tab->OnActivate();
 | 
			
		||||
            m_last_selected_tab = m_tabpanel->GetSelection();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            select_tab(0);
 | 
			
		||||
            select_tab(0); // select Plater
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (m_layout == slOld) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5495,7 +5495,10 @@ void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_acti
 | 
			
		|||
 | 
			
		||||
void Plater::copy_selection_to_clipboard()
 | 
			
		||||
{
 | 
			
		||||
    if (can_copy_to_clipboard())
 | 
			
		||||
    // At first try to copy selected values to the ObjectList's clipboard
 | 
			
		||||
    // to check if Settings or Layers are selected in the list
 | 
			
		||||
    // and then copy to 3DCanvas's clipboard if not
 | 
			
		||||
    if (can_copy_to_clipboard() && !p->sidebar->obj_list()->copy_to_clipboard())
 | 
			
		||||
        p->view3D->get_canvas3d()->get_selection().copy_to_clipboard();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5505,7 +5508,12 @@ void Plater::paste_from_clipboard()
 | 
			
		|||
        return;
 | 
			
		||||
 | 
			
		||||
    Plater::TakeSnapshot snapshot(this, _L("Paste From Clipboard"));
 | 
			
		||||
    p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
 | 
			
		||||
 | 
			
		||||
    // At first try to paste values from the ObjectList's clipboard
 | 
			
		||||
    // to check if Settings or Layers were copied
 | 
			
		||||
    // and then paste from the 3DCanvas's clipboard if not
 | 
			
		||||
    if (!p->sidebar->obj_list()->paste_from_clipboard())
 | 
			
		||||
        p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::search(bool plater_is_active)
 | 
			
		||||
| 
						 | 
				
			
			@ -5642,7 +5650,7 @@ bool Plater::can_paste_from_clipboard() const
 | 
			
		|||
    const Selection& selection = p->view3D->get_canvas3d()->get_selection();
 | 
			
		||||
    const Selection::Clipboard& clipboard = selection.get_clipboard();
 | 
			
		||||
 | 
			
		||||
    if (clipboard.is_empty())
 | 
			
		||||
    if (clipboard.is_empty() && p->sidebar->obj_list()->clipboard_is_empty())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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