mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-10 02:37:04 +00:00
Merge branch 'main' into feat/printer-agent-impl
This commit is contained in:
@@ -755,6 +755,9 @@ struct SparseInfillShape {
|
||||
size_t sharp_turns { 0 };
|
||||
size_t path_count { 0 };
|
||||
double length { 0. };
|
||||
// Digest of every point in the order it is printed. The counts above all survive the same
|
||||
// extrusions being joined into different polylines, so only this tells two such fills apart.
|
||||
uint64_t sequence { 14695981039346656037ull };
|
||||
};
|
||||
|
||||
static SparseInfillShape sparse_infill_shape(const Print &print)
|
||||
@@ -767,6 +770,9 @@ static SparseInfillShape sparse_infill_shape(const Print &print)
|
||||
const Points3 &pts = path.polyline.points;
|
||||
++shape.path_count;
|
||||
shape.point_count += pts.size();
|
||||
for (const auto &pt : pts)
|
||||
for (const coord_t coordinate : {pt.x(), pt.y(), pt.z()})
|
||||
shape.sequence = (shape.sequence ^ uint64_t(coordinate)) * 1099511628211ull;
|
||||
for (size_t i = 1; i < pts.size(); ++i)
|
||||
shape.length += (pts[i] - pts[i - 1]).head<2>().cast<double>().norm();
|
||||
for (size_t i = 1; i + 1 < pts.size(); ++i) {
|
||||
@@ -793,6 +799,33 @@ static SparseInfillShape sparse_infill_shape(const Print &print)
|
||||
return shape;
|
||||
}
|
||||
|
||||
TEST_CASE("Lightning infill slices the same model the same way twice", "[Fill][Regression]")
|
||||
{
|
||||
// Slicing twice in one process catches a generator that carries state from one slice to the
|
||||
// next, or whose result depends on how the parallel layer fill interleaves.
|
||||
auto shape = [] {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "lightning"},
|
||||
{"sparse_infill_density", "50%"},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape first = shape();
|
||||
const SparseInfillShape second = shape();
|
||||
|
||||
REQUIRE(first.path_count > 0);
|
||||
REQUIRE(second.path_count == first.path_count);
|
||||
REQUIRE(second.point_count == first.point_count);
|
||||
REQUIRE(second.sharp_turns == first.sharp_turns);
|
||||
// No tolerance: the same extrusions in the same order add up to the very same number.
|
||||
REQUIRE_THAT(second.length, Catch::Matchers::WithinAbs(first.length, 0.));
|
||||
// All of the above agree when the same branches are joined into different polylines, so the
|
||||
// point sequence is what actually decides whether the two slices produced the same infill.
|
||||
REQUIRE(second.sequence == first.sequence);
|
||||
}
|
||||
|
||||
TEST_CASE("Lightning infill rounds the turns of its branches with the smooth factor", "[Fill]")
|
||||
{
|
||||
auto shape_for = [](const std::string &smooth_factor) {
|
||||
|
||||
@@ -468,22 +468,27 @@ FullPrintConfig make_junction_config(GCodeFlavor flavor, double corner_velocity,
|
||||
constexpr double junction_x = 60.0;
|
||||
constexpr double junction_y = 60.0;
|
||||
|
||||
// Two 40mm travels meeting at (junction_x, junction_y) with the given turn, rotated by `orientation`.
|
||||
// Two 40mm moves meeting at (junction_x, junction_y) with the given turn, rotated by `orientation`.
|
||||
// 40mm is long enough to reach the commanded 150mm/s and brake back to any corner speed these tests
|
||||
// produce. Travels (no E) keep the junction vector purely geometric, as the formulas below assume.
|
||||
std::string corner_gcode(double turn_deg, double orientation_deg)
|
||||
// produce. `e_per_mm` of zero makes them travels, which keeps the junction vector purely geometric
|
||||
// as the formulas below assume.
|
||||
std::string corner_gcode(double turn_deg, double orientation_deg, double e_per_mm = 0.0)
|
||||
{
|
||||
const double len = 40.0;
|
||||
const double a_in = orientation_deg * M_PI / 180.0;
|
||||
const double a_out = (orientation_deg + turn_deg) * M_PI / 180.0;
|
||||
std::ostringstream extrude;
|
||||
if (e_per_mm > 0.0)
|
||||
extrude << std::fixed << std::setprecision(4) << " E" << len * e_per_mm;
|
||||
|
||||
std::ostringstream os;
|
||||
os << std::fixed << std::setprecision(4)
|
||||
<< "M83\n"
|
||||
<< "G1 Z0.2 F1200\n"
|
||||
<< "G1 X" << junction_x - len * std::cos(a_in) << " Y" << junction_y - len * std::sin(a_in) << " F6000\n"
|
||||
<< "G1 X" << junction_x << " Y" << junction_y << " F9000\n"
|
||||
<< "G1 X" << junction_x + len * std::cos(a_out) << " Y" << junction_y + len * std::sin(a_out) << " F9000\n";
|
||||
<< "G1 X" << junction_x << " Y" << junction_y << extrude.str() << " F9000\n"
|
||||
<< "G1 X" << junction_x + len * std::cos(a_out) << " Y" << junction_y + len * std::sin(a_out)
|
||||
<< extrude.str() << " F9000\n";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
@@ -492,7 +497,7 @@ std::string corner_gcode(double turn_deg, double orientation_deg)
|
||||
double corner_speed(const GCodeProcessorResult& r)
|
||||
{
|
||||
for (const auto& mv : r.moves)
|
||||
if (mv.type == EMoveType::Travel &&
|
||||
if ((mv.type == EMoveType::Travel || mv.type == EMoveType::Extrude) &&
|
||||
std::abs(mv.position.x() - junction_x) < 1e-3 &&
|
||||
std::abs(mv.position.y() - junction_y) < 1e-3)
|
||||
return mv.actual_feedrate;
|
||||
@@ -500,11 +505,11 @@ double corner_speed(const GCodeProcessorResult& r)
|
||||
}
|
||||
|
||||
double planned_corner_speed(GCodeFlavor flavor, double corner_velocity, double junction_deviation,
|
||||
double turn_deg, double orientation_deg = 0.0)
|
||||
double turn_deg, double orientation_deg = 0.0, double e_per_mm = 0.0)
|
||||
{
|
||||
GCodeProcessor proc;
|
||||
run_processor(proc, make_junction_config(flavor, corner_velocity, junction_deviation),
|
||||
corner_gcode(turn_deg, orientation_deg).c_str());
|
||||
corner_gcode(turn_deg, orientation_deg, e_per_mm).c_str());
|
||||
return corner_speed(proc.get_result());
|
||||
}
|
||||
|
||||
@@ -582,3 +587,30 @@ TEST_CASE("Junction deviation is only used where the firmware actually plans wit
|
||||
Catch::Matchers::WithinRel(without, 1e-4));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("How fast a corner is taken does not depend on how much is extruded through it",
|
||||
"[GCodeTiming][JunctionDeviation]")
|
||||
{
|
||||
// The junction cosine is taken over XYZE, so the direction vectors have to be unit length or the
|
||||
// E term makes the two paths look more parallel than they are and the corner comes out too fast,
|
||||
// the more so the higher the flow. Marlin normalizes over XYZE on any extruding move
|
||||
// (planner.cpp, esteps > 0) and Klipper leaves E out of the cosine altogether
|
||||
// (toolhead.py::Move.calc_junction); on both, this corner is planned by its geometry alone.
|
||||
const double scv = 5.0;
|
||||
const double turn = 6.0;
|
||||
const double geometric = planned_corner_speed(gcfKlipper, scv, 0.0, turn);
|
||||
REQUIRE(geometric > 0.0);
|
||||
|
||||
// 0.029mm/mm is an ordinary 0.42 x 0.2 line on 1.75mm filament; 0.1 is a fat large-nozzle one.
|
||||
// Unnormalized these came out at 94.4 and 150.0mm/s against a geometric 86.9.
|
||||
for (double e_per_mm : {0.029, 0.1})
|
||||
REQUIRE_THAT(planned_corner_speed(gcfKlipper, scv, 0.0, turn, 0.0, e_per_mm),
|
||||
Catch::Matchers::WithinRel(geometric, 0.02));
|
||||
|
||||
SECTION("and the same holds on Marlin 2") {
|
||||
const double marlin = planned_corner_speed(gcfMarlinFirmware, scv, 0.05, turn);
|
||||
REQUIRE(marlin > 0.0);
|
||||
REQUIRE_THAT(planned_corner_speed(gcfMarlinFirmware, scv, 0.05, turn, 0.0, 0.029),
|
||||
Catch::Matchers::WithinRel(marlin, 0.02));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_preset_bundle_loading.cpp
|
||||
test_preset_setting_id.cpp
|
||||
test_preset_diff.cpp
|
||||
test_vendor_cache.cpp
|
||||
test_elephant_foot_compensation.cpp
|
||||
test_fill_corner_smoothing.cpp
|
||||
test_fill_plane_path.cpp
|
||||
|
||||
@@ -574,11 +574,6 @@ TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Ge
|
||||
Polygon B = A;
|
||||
B.translate(10 / SCALING_FACTOR, 10 / SCALING_FACTOR);
|
||||
|
||||
SVG svg{std::string("one_vertex_touch") + ".svg"};
|
||||
svg.draw(A, "blue");
|
||||
svg.draw(B, "green");
|
||||
svg.Close();
|
||||
|
||||
bool is_inters = Geometry::convex_polygons_intersect(A, B);
|
||||
|
||||
REQUIRE(is_inters == false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
@@ -132,7 +133,7 @@ TEST_CASE("Current vendor type tolerates missing printer model", "[Preset][Bundl
|
||||
{
|
||||
PresetBundle bundle;
|
||||
|
||||
VendorProfile orca_vendor("ORCA");
|
||||
VendorProfile orca_vendor; orca_vendor.id = "ORCA";
|
||||
VendorProfile::PrinterModel model;
|
||||
model.name = "Orca Test";
|
||||
orca_vendor.models.emplace_back(model);
|
||||
@@ -143,6 +144,31 @@ TEST_CASE("Current vendor type tolerates missing printer model", "[Preset][Bundl
|
||||
CHECK(bundle.get_current_vendor_type() == VendorType::Unknown);
|
||||
}
|
||||
|
||||
TEST_CASE("A malformed entry in a vendor's preset list is counted, not thrown", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
|
||||
// A bare number where the list wants an object. An array element has no key,
|
||||
// so reporting one as if it did throws nlohmann's invalid_iterator - which is
|
||||
// not a parse_error, and escapes the catch around the vendor profile parse.
|
||||
std::ofstream((dir.path() / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme","process_list":[123,)"
|
||||
<< R"({"name":"0.20mm Standard @Acme","sub_path":"process/standard.json"}]})";
|
||||
fs::create_directories(dir.path() / "Acme" / "process");
|
||||
std::ofstream((dir.path() / "Acme" / "process" / "standard.json").string())
|
||||
<< R"({"type":"process","name":"0.20mm Standard @Acme","from":"system",)"
|
||||
<< R"("instantiation":"true","layer_height":"0.2"})";
|
||||
|
||||
PresetBundle bundle;
|
||||
size_t loaded = 0;
|
||||
REQUIRE_NOTHROW(loaded = bundle.load_vendor_configs_from_json(
|
||||
dir.path().string(), "Acme", PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent).second);
|
||||
|
||||
CHECK(bundle.error_count() > 0); // the malformed element was counted
|
||||
CHECK(loaded == 1); // the well-formed one beside it still loaded
|
||||
}
|
||||
|
||||
TEST_CASE("Printer extruder count tolerates missing nozzle diameter", "[Preset][Bundle]")
|
||||
{
|
||||
PresetBundle bundle;
|
||||
|
||||
1620
tests/libslic3r/test_vendor_cache.cpp
Normal file
1620
tests/libslic3r/test_vendor_cache.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user