mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-16 04:11:13 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
109d09e0b8 | ||
|
|
b00237807d | ||
|
|
f6cf18fb96 |
Vendored
+2
-17
@@ -1,22 +1,7 @@
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (JPEG_VERSION STREQUAL "6")
|
||||
message("Using Jpeg Lib 62")
|
||||
set(jpeg_flag "")
|
||||
elseif (JPEG_VERSION STREQUAL "7")
|
||||
message("Using Jpeg Lib 70")
|
||||
set(jpeg_flag "-DWITH_JPEG7=ON")
|
||||
else ()
|
||||
message("Using Jpeg Lib 80")
|
||||
set(jpeg_flag "-DWITH_JPEG8=ON")
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
orcaslicer_add_cmake_project(JPEG
|
||||
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
|
||||
URL_HASH SHA256=d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
|
||||
DEPENDS ${ZLIB_PKG}
|
||||
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
|
||||
URL_HASH SHA256=1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
|
||||
CMAKE_ARGS
|
||||
-DENABLE_SHARED=OFF
|
||||
-DENABLE_STATIC=ON
|
||||
${jpeg_flag}
|
||||
)
|
||||
|
||||
@@ -336,10 +336,10 @@ modules:
|
||||
sha256: 3d22d46c566b1761a0e15ea397589b3a5f36ac09b7c785382e6470156c04247f
|
||||
dest: external-packages/PNG
|
||||
|
||||
# libjpeg-turbo 3.0.1
|
||||
# libjpeg-turbo 3.2.0
|
||||
- type: file
|
||||
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
|
||||
sha256: d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
|
||||
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
|
||||
sha256: 1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
|
||||
dest: external-packages/JPEG
|
||||
|
||||
# Freetype 2.12.1
|
||||
|
||||
+27
-18
@@ -432,27 +432,36 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
|
||||
std::string OozePrevention::pre_toolchange(GCode& gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
unsigned int extruder_id = gcodegen.writer().filament()->id();
|
||||
const auto& filament_idle_temp = gcodegen.config().idle_temperature;
|
||||
if (filament_idle_temp.get_at(extruder_id) == 0) {
|
||||
// There is no idle temperature defined in filament settings.
|
||||
// Use the delta value from print config.
|
||||
if (gcodegen.config().standby_temperature_delta.value != 0) {
|
||||
// we assume that heating is always slower than cooling, so no need to block
|
||||
gcode += gcodegen.writer().set_temperature
|
||||
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
|
||||
gcode.pop_back();
|
||||
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
|
||||
}
|
||||
} else {
|
||||
// Use the value from filament settings. That one is absolute, not delta.
|
||||
gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
|
||||
gcode.pop_back();
|
||||
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
|
||||
|
||||
// Check if this tool will ever be used again in this print.
|
||||
bool is_last_use = false;
|
||||
if (gcodegen.m_print != nullptr && !gcodegen.m_print->tool_ordering().empty()) {
|
||||
coordf_t current_z = gcodegen.layer() ? gcodegen.layer()->print_z : gcodegen.writer().get_position().z();
|
||||
const auto& tool_ordering = gcodegen.m_print->tool_ordering();
|
||||
const auto& cfg = gcodegen.config();
|
||||
const int heater = imex_physical_heater_for(cfg.is_imex.value, cfg.physical_extruder_map, int(extruder_id));
|
||||
is_last_use = tool_ordering.is_last_extrusion_layer(current_z, extruder_id);
|
||||
for (unsigned int f : tool_ordering.all_extruders())
|
||||
if (is_last_use && imex_physical_heater_for(cfg.is_imex.value, cfg.physical_extruder_map, int(f)) == heater)
|
||||
is_last_use = tool_ordering.is_last_extrusion_layer(current_z, f);
|
||||
}
|
||||
|
||||
int temp = 0;
|
||||
if (!is_last_use) {
|
||||
const auto& filament_idle_temp = gcodegen.config().idle_temperature;
|
||||
if (filament_idle_temp.get_at(extruder_id) == 0) {
|
||||
if (gcodegen.config().standby_temperature_delta.value == 0)
|
||||
return std::string();
|
||||
temp = this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value;
|
||||
} else {
|
||||
temp = filament_idle_temp.get_at(extruder_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string gcode = gcodegen.writer().set_temperature(temp, false, extruder_id);
|
||||
gcode.pop_back();
|
||||
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
|
||||
return gcode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1004,6 +1004,7 @@ protected:
|
||||
size_t num_objects,
|
||||
size_t num_islands);
|
||||
|
||||
friend class OozePrevention;
|
||||
friend class Wipe;
|
||||
friend class WipeTowerIntegration;
|
||||
friend class PressureEqualizer;
|
||||
|
||||
@@ -1276,6 +1276,13 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
|
||||
sort_remove_duplicates(m_all_printing_extruders);
|
||||
}
|
||||
|
||||
m_last_layer_per_extruder.clear();
|
||||
for (const LayerTools& lt : m_layer_tools) {
|
||||
for (unsigned int ext : lt.extruders) {
|
||||
m_last_layer_per_extruder[ext] = lt.print_z;
|
||||
}
|
||||
}
|
||||
|
||||
if (prime_multi_material && ! m_all_printing_extruders.empty()) {
|
||||
// Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
|
||||
// Then set m_first_printing_extruder to the 1st extruder primed.
|
||||
@@ -3664,5 +3671,11 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
bool ToolOrdering::is_last_extrusion_layer(coordf_t print_z, unsigned int extruder_id) const
|
||||
{
|
||||
auto it = m_last_layer_per_extruder.find(extruder_id);
|
||||
if (it == m_last_layer_per_extruder.end())
|
||||
return true;
|
||||
return print_z >= it->second - EPSILON;
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -370,6 +370,7 @@ public:
|
||||
std::vector<int> & initial_filaments);
|
||||
|
||||
bool has_non_support_filament(const PrintConfig &config);
|
||||
bool is_last_extrusion_layer(coordf_t print_z, unsigned int extruder_id) const;
|
||||
|
||||
private:
|
||||
void initialize_layers(std::vector<coordf_t> &zs);
|
||||
@@ -413,6 +414,8 @@ private:
|
||||
// which keeps every legacy per-object code path bit-identical (loops over an empty map are
|
||||
// no-ops; downstream emission falls through to the per-object branch).
|
||||
std::map<unsigned int, std::map<LayerTools::MixedSubLayerGroup::VolumeKey, std::vector<size_t>>> m_gradient_volume_layers;
|
||||
// Map: extruder_id -> print_z (z coordinate) of the last layer it is used on
|
||||
std::map<unsigned int, coordf_t> m_last_layer_per_extruder;
|
||||
const PrintObject* m_print_object_ptr = nullptr;
|
||||
Print* m_print;
|
||||
bool m_sorted = false;
|
||||
|
||||
@@ -222,6 +222,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmoUtils.hpp
|
||||
GUI/GLCanvas3D.cpp
|
||||
GUI/GLCanvas3D.hpp
|
||||
GUI/SequentialPrintClearance.hpp
|
||||
GUI/GLModel.cpp
|
||||
GUI/GLModel.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include <string_view>
|
||||
#include "slic3r/GUI/Selection.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "SequentialPrintClearance.hpp"
|
||||
#ifdef SLIC3R_CAD
|
||||
#include "slic3r/GUI/CAD/DesignSketchTool.hpp" // Design tab: interactive 2D sketch tool
|
||||
#endif
|
||||
@@ -6397,17 +6398,16 @@ void GLCanvas3D::update_sequential_clearance()
|
||||
//BBS: add the height logic
|
||||
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
Polygons polygons;
|
||||
std::vector<std::pair<Polygon, float>> height_polygons;
|
||||
polygons.reserve(instances_count);
|
||||
height_polygons.reserve(instances_count);
|
||||
std::vector<struct height_info> convex_and_bounding_boxes;
|
||||
struct height_info
|
||||
{
|
||||
double instance_height;
|
||||
BoundingBox bounding_box;
|
||||
Polygon hull_polygon;
|
||||
};
|
||||
std::vector<SequentialClearanceInstance> convex_and_bounding_boxes;
|
||||
convex_and_bounding_boxes.reserve(instances_count);
|
||||
std::map<ObjectID, int> print_order;
|
||||
for (const PrintObject* print_object : fff_print()->objects())
|
||||
for (const PrintInstance& instance : print_object->instances())
|
||||
print_order.emplace(instance.model_instance->id(), instance.model_instance->arrange_order);
|
||||
|
||||
for (size_t i = 0; i < instance_transforms.size(); ++i) {
|
||||
const size_t first_instance = convex_and_bounding_boxes.size();
|
||||
const auto& instances = instance_transforms[i];
|
||||
double rotation_z0 = instances.front()->get_rotation().z();
|
||||
int index = 0;
|
||||
@@ -6427,96 +6427,20 @@ void GLCanvas3D::update_sequential_clearance()
|
||||
Polygon convex_hull(std::move(inst_pts));
|
||||
BoundingBox bouding_box = convex_hull.bounding_box();
|
||||
BoundingBox plate_bb = plate->get_bounding_box_crd();
|
||||
const ObjectID instance_id = m_model->objects[i]->instances[index]->id();
|
||||
double instance_height = m_model->objects[i]->get_instance_max_z(index++);
|
||||
//skip the object for not current plate
|
||||
if (!plate_bb.overlap(bouding_box))
|
||||
continue;
|
||||
convex_and_bounding_boxes.push_back({instance_height, bouding_box, convex_hull});
|
||||
convex_and_bounding_boxes.push_back({instance_height, bouding_box, convex_hull, instance_id});
|
||||
polygons.emplace_back(std::move(convex_hull));
|
||||
}
|
||||
sort_sequential_clearance_instances(convex_and_bounding_boxes.begin() + first_instance, convex_and_bounding_boxes.end(), print_order);
|
||||
}
|
||||
|
||||
//sort the print instance
|
||||
std::sort(convex_and_bounding_boxes.begin(), convex_and_bounding_boxes.end(),
|
||||
[](auto &l, auto &r) {
|
||||
auto ly1 = l.bounding_box.min.y();
|
||||
auto ly2 = l.bounding_box.max.y();
|
||||
auto ry1 = r.bounding_box.min.y();
|
||||
auto ry2 = r.bounding_box.max.y();
|
||||
auto inter_min = std::max(ly1, ry1);
|
||||
auto inter_max = std::min(ly2, ry2);
|
||||
auto lx = l.bounding_box.min.x();
|
||||
auto rx = r.bounding_box.min.x();
|
||||
if (inter_max - inter_min > 0)
|
||||
return (lx < rx) || ((lx == rx)&&(ly1 < ry1));
|
||||
else
|
||||
return (ly1 < ry1);
|
||||
});
|
||||
|
||||
/*bool has_interlaced_objects = false;
|
||||
for (int k = 0; k < bounding_box_count; k++)
|
||||
{
|
||||
Polygon& convex = convex_and_bounding_boxes[k].hull_polygon;
|
||||
BoundingBox& bbox = convex_and_bounding_boxes[k].bounding_box;
|
||||
auto iy1 = bbox.min.y();
|
||||
auto iy2 = bbox.max.y();
|
||||
|
||||
for (int i = k+1; i < bounding_box_count; i++)
|
||||
{
|
||||
Polygon& next_convex = convex_and_bounding_boxes[i].hull_polygon;
|
||||
BoundingBox& next_bbox = convex_and_bounding_boxes[i].bounding_box;
|
||||
auto py1 = next_bbox.min.y();
|
||||
auto py2 = next_bbox.max.y();
|
||||
auto inter_min = std::max(iy1, py1); // min y of intersection
|
||||
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
|
||||
if (inter_max - inter_min > 0) {
|
||||
has_interlaced_objects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_interlaced_objects)
|
||||
break;
|
||||
}*/
|
||||
|
||||
int bounding_box_count = convex_and_bounding_boxes.size();
|
||||
double printable_height = fff_print()->config().printable_height;
|
||||
double hc1 = fff_print()->config().extruder_clearance_height_to_lid;
|
||||
double hc2 = fff_print()->config().extruder_clearance_height_to_rod;
|
||||
for (int k = 0; k < bounding_box_count; k++)
|
||||
{
|
||||
Polygon& convex = convex_and_bounding_boxes[k].hull_polygon;
|
||||
BoundingBox& bbox = convex_and_bounding_boxes[k].bounding_box;
|
||||
auto iy1 = bbox.min.y();
|
||||
auto iy2 = bbox.max.y();
|
||||
double height = (k == (bounding_box_count - 1))?printable_height:hc1;
|
||||
|
||||
/*if (has_interlaced_objects) {
|
||||
if ((k < (bounding_box_count - 1)) && (convex_and_bounding_boxes[k].instance_height > hc2)) {
|
||||
height_polygons.emplace_back(std::make_pair(convex, hc2));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((k < (bounding_box_count - 1)) && (convex_and_bounding_boxes[k].instance_height > hc1)) {
|
||||
height_polygons.emplace_back(std::make_pair(convex, hc1));
|
||||
}
|
||||
}*/
|
||||
|
||||
for (int i = k+1; i < bounding_box_count; i++)
|
||||
{
|
||||
Polygon& next_convex = convex_and_bounding_boxes[i].hull_polygon;
|
||||
BoundingBox& next_bbox = convex_and_bounding_boxes[i].bounding_box;
|
||||
auto py1 = next_bbox.min.y();
|
||||
auto py2 = next_bbox.max.y();
|
||||
auto inter_min = std::max(iy1, py1); // min y of intersection
|
||||
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
|
||||
if (inter_max - inter_min > 0) {
|
||||
height = hc2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (height < convex_and_bounding_boxes[k].instance_height)
|
||||
height_polygons.emplace_back(std::make_pair(convex, height));
|
||||
}
|
||||
const auto& config = fff_print()->config();
|
||||
const auto height_polygons = sequential_clearance_height_polygons(convex_and_bounding_boxes, config.printable_height,
|
||||
config.extruder_clearance_height_to_lid, config.extruder_clearance_height_to_rod);
|
||||
|
||||
// sends instances 2d hulls to be rendered
|
||||
set_sequential_print_clearance_visible(true);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
struct SequentialClearanceInstance
|
||||
{
|
||||
double instance_height;
|
||||
BoundingBox bounding_box;
|
||||
Polygon hull_polygon;
|
||||
ObjectID instance_id;
|
||||
};
|
||||
|
||||
// PrintApply may group rotated copies in a different order from ModelObject::instances.
|
||||
// Reorder one object's instances only when validation has assigned all of them an order.
|
||||
inline void sort_sequential_clearance_instances(
|
||||
std::vector<SequentialClearanceInstance>::iterator first, std::vector<SequentialClearanceInstance>::iterator last,
|
||||
const std::map<ObjectID, int>& print_order)
|
||||
{
|
||||
if (!std::all_of(first, last, [&print_order](const SequentialClearanceInstance& instance) {
|
||||
const auto order = print_order.find(instance.instance_id);
|
||||
return order != print_order.end() && order->second > 0;
|
||||
}))
|
||||
return;
|
||||
|
||||
std::stable_sort(first, last, [&print_order](const SequentialClearanceInstance& lhs, const SequentialClearanceInstance& rhs) {
|
||||
return print_order.at(lhs.instance_id) < print_order.at(rhs.instance_id);
|
||||
});
|
||||
}
|
||||
|
||||
// Input follows object-list order with validated print order within each object.
|
||||
inline std::vector<std::pair<Polygon, float>> sequential_clearance_height_polygons(
|
||||
const std::vector<SequentialClearanceInstance>& instances, double printable_height, double height_to_lid, double height_to_rod)
|
||||
{
|
||||
std::vector<std::pair<Polygon, float>> height_polygons;
|
||||
height_polygons.reserve(instances.size());
|
||||
for (size_t k = 0; k < instances.size(); ++k) {
|
||||
const auto& instance = instances[k];
|
||||
double height = k + 1 == instances.size() ? printable_height : height_to_lid;
|
||||
for (size_t i = k + 1; i < instances.size(); ++i) {
|
||||
const auto& next_bbox = instances[i].bounding_box;
|
||||
if (std::min(instance.bounding_box.max.y(), next_bbox.max.y()) >
|
||||
std::max(instance.bounding_box.min.y(), next_bbox.min.y())) {
|
||||
height = height_to_rod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (height < instance.instance_height)
|
||||
height_polygons.emplace_back(instance.hull_polygon, float(height));
|
||||
}
|
||||
return height_polygons;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
@@ -163,7 +163,7 @@ M109 S240 T0 ; set nozzle temperature and wait for it to be reached
|
||||
; CP TOOLCHANGE END
|
||||
M104 S240 T1 ; preheat T1 time: 30s lead 30.0s
|
||||
; CP TOOLCHANGE START
|
||||
M104 S200 T0 ; set nozzle temperature ;cooldown
|
||||
M104 S0 T0 ; set nozzle temperature ;cooldown
|
||||
T1 ; change extruder
|
||||
M109 S240 T1 ; set nozzle temperature and wait for it to be reached
|
||||
; CP TOOLCHANGE END
|
||||
|
||||
@@ -1742,3 +1742,112 @@ TEST_CASE("Each filament cools with the fan speeds of its extruder variant", "[M
|
||||
CHECK(gcode.find("; start range high " + std::to_string(range_high) + "\n") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Shared heater on IMEX printer stays hot until all filaments on it are done", "[MultiFilament]")
|
||||
{
|
||||
DynamicPrintConfig cfg = multifilament_config(7);
|
||||
cfg.set_deserialize_strict({
|
||||
{"ooze_prevention", 1},
|
||||
{"preheat_time", 0},
|
||||
{"single_extruder_multi_material", 0},
|
||||
});
|
||||
imex_7x4_printer(cfg);
|
||||
|
||||
const std::string gcode = slice_with_object_overrides({make_cube(20., 20., 10.), make_cube(20., 20., 4.), make_cube(20., 20., 10.)},
|
||||
cfg,
|
||||
{{{"extruder", 1}, {"layer_height", 0.30}},
|
||||
{{"extruder", 2}, {"layer_height", 0.15}},
|
||||
{{"extruder", 5}}});
|
||||
|
||||
bool premature_s0_t0 = false;
|
||||
double current_z = 0.0;
|
||||
|
||||
GCodeReader reader;
|
||||
reader.parse_buffer(gcode, [&](GCodeReader& /*self*/, const GCodeReader::GCodeLine& line) {
|
||||
if (line.has_z())
|
||||
current_z = line.z();
|
||||
|
||||
const std::string raw = line.raw();
|
||||
if (raw.find("M104 S0 T0") != std::string::npos && raw.find(";cooldown") != std::string::npos) {
|
||||
if (current_z < 9.0)
|
||||
premature_s0_t0 = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Filaments 1 and 2 share head 0, and filament 1 prints up to 10mm.
|
||||
// T0 must not shut down at ~4mm when filament 2 finishes.
|
||||
CHECK_FALSE(premature_s0_t0);
|
||||
}
|
||||
|
||||
TEST_CASE("Unused extruder turns off after its final layer on multi-object print", "[MultiFilament]")
|
||||
{
|
||||
const std::string gcode = slice_with_object_overrides(
|
||||
{ make_cube(20., 20., 10.), make_cube(20., 20., 4.) },
|
||||
multifilament_config(2, {
|
||||
{ "single_extruder_multi_material", 0 },
|
||||
{ "ooze_prevention", 1 },
|
||||
{ "standby_temperature_delta", -40 },
|
||||
{ "nozzle_temperature", "240,240" },
|
||||
}),
|
||||
{
|
||||
{ { "extruder", 1 }, { "layer_height", 0.20 } },
|
||||
{ { "extruder", 2 }, { "layer_height", 0.15 } }
|
||||
});
|
||||
|
||||
int t1_s0_cooldown_count = 0;
|
||||
int t0_s0_cooldown_count = 0;
|
||||
|
||||
std::istringstream stream(gcode);
|
||||
for (std::string line; std::getline(stream, line);) {
|
||||
if (line.find(";cooldown") != std::string::npos) {
|
||||
if (line.find("M104 S0 T1") != std::string::npos)
|
||||
++t1_s0_cooldown_count;
|
||||
if (line.find("M104 S0 T0") != std::string::npos)
|
||||
++t0_s0_cooldown_count;
|
||||
}
|
||||
}
|
||||
|
||||
// T1 (extruder 2, 4mm cube) gets exactly one S0 after its last layer.
|
||||
// T0 (extruder 1, 10mm cube) never gets S0 mid-print.
|
||||
CHECK(t1_s0_cooldown_count == 1);
|
||||
CHECK(t0_s0_cooldown_count == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Unused extruder turns off on non-IMEX three-tool print", "[MultiFilament]")
|
||||
{
|
||||
const std::string gcode = slice_with_object_overrides(
|
||||
{ make_cube(20., 20., 4.), make_cube(20., 20., 10.), make_cube(20., 20., 10.) },
|
||||
multifilament_config(3, {
|
||||
{ "single_extruder_multi_material", 0 },
|
||||
{ "ooze_prevention", 1 },
|
||||
{ "standby_temperature_delta", -40 },
|
||||
{ "nozzle_temperature", "240,240,240" },
|
||||
}),
|
||||
{
|
||||
{ { "extruder", 1 } },
|
||||
{ { "extruder", 2 } },
|
||||
{ { "extruder", 3 } }
|
||||
});
|
||||
|
||||
bool premature_s0_t1 = false;
|
||||
bool premature_s0_t2 = false;
|
||||
double current_z = 0.0;
|
||||
|
||||
GCodeReader reader;
|
||||
reader.parse_buffer(gcode, [&](GCodeReader& /*self*/, const GCodeReader::GCodeLine& line) {
|
||||
if (line.has_z())
|
||||
current_z = line.z();
|
||||
|
||||
const std::string raw = line.raw();
|
||||
if (raw.find(";cooldown") != std::string::npos && current_z < 9.0) {
|
||||
if (raw.find("M104 S0 T1") != std::string::npos)
|
||||
premature_s0_t1 = true;
|
||||
if (raw.find("M104 S0 T2") != std::string::npos)
|
||||
premature_s0_t2 = true;
|
||||
}
|
||||
});
|
||||
|
||||
// T1 and T2 print up to 10mm — neither should get S0 before that.
|
||||
CHECK_FALSE(premature_s0_t1);
|
||||
CHECK_FALSE(premature_s0_t2);
|
||||
}
|
||||
@@ -47,6 +47,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_file_url.cpp
|
||||
test_imgui_wrapper.cpp
|
||||
test_user_manager.cpp
|
||||
test_sequential_clearance.cpp
|
||||
../fff_print/test_helpers.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include "catch2/matchers/catch_matchers.hpp"
|
||||
#include "fff_print/test_helpers.hpp"
|
||||
#include "slic3r/GUI/SequentialPrintClearance.hpp"
|
||||
|
||||
#include <libslic3r/libslic3r.h>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/PrintConfig.hpp>
|
||||
#include <libslic3r/Print.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
namespace {
|
||||
|
||||
SequentialClearanceInstance instance(coord_t x, coord_t y, double height)
|
||||
{
|
||||
return {height, BoundingBox(Point(x, y), Point(x + 5, y + 10)),
|
||||
Polygon{Point(x, y), Point(x + 5, y), Point(x + 5, y + 10), Point(x, y + 10)}};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Sequential clearance preserves model print order when objects move", "[SequentialClearance][Regression]")
|
||||
{
|
||||
const coord_t last_y = GENERATE(-20, 20);
|
||||
std::vector<SequentialClearanceInstance> instances{instance(20, 0, 50.), instance(0, last_y, 50.)};
|
||||
const auto warnings = sequential_clearance_height_polygons(instances, 200., 30., 10.);
|
||||
|
||||
REQUIRE(warnings.size() == 1);
|
||||
CHECK(warnings.front().first == instance(20, 0, 50.).hull_polygon);
|
||||
CHECK_THAT(warnings.front().second, WithinAbs(30., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential clearance handles chains of overlapping Y ranges", "[SequentialClearance][Regression]")
|
||||
{
|
||||
// A overlaps B, B overlaps C, but A does not overlap C. Combining X order
|
||||
// for overlaps with Y order otherwise formed the cycle B < A < C < B.
|
||||
std::vector<SequentialClearanceInstance> instances{
|
||||
instance(20, 0, 50.), instance(10, 8, 50.), instance(0, 16, 50.)};
|
||||
const auto warnings = sequential_clearance_height_polygons(instances, 200., 30., 10.);
|
||||
|
||||
REQUIRE(warnings.size() == 2);
|
||||
CHECK(warnings[0].first == instance(20, 0, 50.).hull_polygon);
|
||||
CHECK(warnings[1].first == instance(10, 8, 50.).hull_polygon);
|
||||
CHECK_THAT(warnings[0].second, WithinAbs(10., 1e-6));
|
||||
CHECK_THAT(warnings[1].second, WithinAbs(10., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential clearance applies rod height only to positive Y overlap", "[SequentialClearance]")
|
||||
{
|
||||
const coord_t next_y = GENERATE(9, 10, 11);
|
||||
std::vector<SequentialClearanceInstance> instances{instance(0, 0, 50.), instance(20, next_y, 50.)};
|
||||
const auto warnings = sequential_clearance_height_polygons(instances, 200., 30., 10.);
|
||||
|
||||
REQUIRE(warnings.size() == 1);
|
||||
CHECK(warnings.front().first == instances.front().hull_polygon);
|
||||
CHECK_THAT(warnings.front().second, WithinAbs(next_y < 10 ? 10. : 30., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential clearance allows the last object up to printable height", "[SequentialClearance]")
|
||||
{
|
||||
std::vector<SequentialClearanceInstance> instances;
|
||||
CHECK(sequential_clearance_height_polygons(instances, 200., 30., 10.).empty());
|
||||
|
||||
instances.push_back(instance(0, 0, 200.));
|
||||
CHECK(sequential_clearance_height_polygons(instances, 200., 30., 10.).empty());
|
||||
|
||||
instances.front().instance_height = 201.;
|
||||
const auto warnings = sequential_clearance_height_polygons(instances, 200., 30., 10.);
|
||||
REQUIRE(warnings.size() == 1);
|
||||
CHECK_THAT(warnings.front().second, WithinAbs(200., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential clearance warns the same rotated copy as print validation", "[SequentialClearance][Regression]")
|
||||
{
|
||||
constexpr double rod_height = 10.;
|
||||
constexpr double lid_height = 30.;
|
||||
constexpr double printable_height = 200.;
|
||||
Model model = Test::model("rotated copies", make_cube(10., 10., 50.));
|
||||
ModelObject* object = model.objects.front();
|
||||
object->instances.front()->set_offset(Vec3d(150., 80., 0.));
|
||||
ModelInstance* rotated = object->add_instance();
|
||||
rotated->set_offset(Vec3d(50., 80., 0.));
|
||||
rotated->set_rotation(Z, PI / 2.);
|
||||
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({{"print_sequence", "by object"}, {"extruder_clearance_radius", 20.},
|
||||
{"extruder_clearance_height_to_lid", lid_height}, {"extruder_clearance_height_to_rod", rod_height},
|
||||
{"printable_height", printable_height}});
|
||||
Print print;
|
||||
print.auto_assign_extruders(object);
|
||||
print.apply(model, config);
|
||||
Polygons polygons;
|
||||
std::vector<std::pair<Polygon, float>> validation_warnings;
|
||||
Print::sequential_print_clearance_valid(print, &polygons, &validation_warnings);
|
||||
|
||||
std::map<ObjectID, int> print_order;
|
||||
for (const PrintObject* print_object : print.objects())
|
||||
for (const PrintInstance& print_instance : print_object->instances())
|
||||
print_order.emplace(print_instance.model_instance->id(), print_instance.model_instance->arrange_order);
|
||||
|
||||
std::vector<SequentialClearanceInstance> instances;
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
Polygon hull = object->convex_hull_2d(object->instances[i]->get_matrix());
|
||||
instances.push_back({object->get_instance_max_z(i), hull.bounding_box(), hull, object->instances[i]->id()});
|
||||
}
|
||||
const Polygon expected_warning = instances[1].hull_polygon;
|
||||
REQUIRE(validation_warnings.size() == 1);
|
||||
CHECK(validation_warnings.front().first.bounding_box().contains(instances[1].bounding_box.center()));
|
||||
CHECK_FALSE(validation_warnings.front().first.bounding_box().contains(instances[0].bounding_box.center()));
|
||||
|
||||
sort_sequential_clearance_instances(instances.begin(), instances.end(), print_order);
|
||||
const auto warnings = sequential_clearance_height_polygons(instances, printable_height, lid_height, rod_height);
|
||||
REQUIRE(warnings.size() == 1);
|
||||
CHECK(warnings.front().first == expected_warning);
|
||||
CHECK_THAT(warnings.front().second, WithinAbs(rod_height, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential clearance keeps copy order when print order is unavailable", "[SequentialClearance]")
|
||||
{
|
||||
std::vector<SequentialClearanceInstance> instances{instance(20, 0, 50.), instance(0, 0, 50.)};
|
||||
instances[0].instance_id = ObjectID(1);
|
||||
instances[1].instance_id = ObjectID(2);
|
||||
std::map<ObjectID, int> print_order{{ObjectID(1), 2}, {ObjectID(2), 1}};
|
||||
|
||||
SECTION("No validation order") { print_order.clear(); }
|
||||
SECTION("New copy missing from print") { print_order.erase(ObjectID(2)); }
|
||||
SECTION("Copy has not been ordered") { print_order[ObjectID(2)] = 0; }
|
||||
SECTION("Copy has an invalid order") { print_order[ObjectID(2)] = -1; }
|
||||
|
||||
sort_sequential_clearance_instances(instances.begin(), instances.end(), print_order);
|
||||
CHECK(instances[0].instance_id == ObjectID(1));
|
||||
CHECK(instances[1].instance_id == ObjectID(2));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential clearance keeps object order when sorting copies", "[SequentialClearance]")
|
||||
{
|
||||
std::vector<SequentialClearanceInstance> instances(4, instance(0, 0, 50.));
|
||||
for (size_t i = 0; i < instances.size(); ++i)
|
||||
instances[i].instance_id = ObjectID(instances.size() - i);
|
||||
const std::map<ObjectID, int> print_order{{ObjectID(1), 1}, {ObjectID(2), 2}, {ObjectID(3), 3}, {ObjectID(4), 4}};
|
||||
|
||||
// The two objects were reordered in the model since the last validation.
|
||||
sort_sequential_clearance_instances(instances.begin(), instances.begin() + 2, print_order);
|
||||
sort_sequential_clearance_instances(instances.begin() + 2, instances.end(), print_order);
|
||||
CHECK(instances[0].instance_id == ObjectID(3));
|
||||
CHECK(instances[1].instance_id == ObjectID(4));
|
||||
CHECK(instances[2].instance_id == ObjectID(1));
|
||||
CHECK(instances[3].instance_id == ObjectID(2));
|
||||
}
|
||||
Reference in New Issue
Block a user