mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-23 17:02:39 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a80d0b49f0 |
@@ -5,9 +5,8 @@
|
||||
# re-sliced on its own to see whether it changes the G-code
|
||||
# harness - the GUI-vs-CLI parity harness (metrics only, never fails)
|
||||
# Both test the latest successful build_all.yml Linux AppImage from main, with
|
||||
# sources checked out at the commit that build was made from; a manual run can
|
||||
# name another branch, or pin one build by its run id. Nothing here gates a
|
||||
# build or a PR.
|
||||
# sources checked out at the commit that build was made from. Nothing here
|
||||
# gates a build or a PR.
|
||||
name: Parity Nightly
|
||||
|
||||
on:
|
||||
@@ -21,13 +20,9 @@ on:
|
||||
required: false
|
||||
default: "main"
|
||||
build_branch:
|
||||
description: "branch whose newest successful build_all artifact to test (a PR build is the PR merged into its base; sources are checked out at the PR head)"
|
||||
description: "branch whose latest successful build_all artifact to test"
|
||||
required: false
|
||||
default: "main"
|
||||
build_run_id:
|
||||
description: "build_all run id to test instead of build_branch's newest (same PR caveat)"
|
||||
required: false
|
||||
default: ""
|
||||
fixtures:
|
||||
description: "harness fixture ids, space-separated (empty = all)"
|
||||
required: false
|
||||
@@ -55,53 +50,14 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ inputs.build_branch || 'main' }}
|
||||
RUN_ID: ${{ inputs.build_run_id }}
|
||||
SCHEDULED: ${{ github.event_name == 'schedule' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "$RUN_ID" ]; then
|
||||
[[ $RUN_ID =~ ^[0-9]+$ ]] || { echo "build_run_id must be a numeric run id, got '$RUN_ID'" >&2; exit 1; }
|
||||
# a pinned build is read directly, not through a search; it must come
|
||||
# from this repository, because the later jobs check out its commit here
|
||||
found=$(gh api "repos/$GH_REPO/actions/runs/$RUN_ID" --jq \
|
||||
'select(.path == ".github/workflows/build_all.yml" and .conclusion == "success"
|
||||
and .head_repository.full_name == env.GH_REPO)
|
||||
| "\(.id) \(.head_sha) \(.created_at)"')
|
||||
[ -n "$found" ] || { echo "run $RUN_ID is not a successful build_all run of $GH_REPO" >&2; exit 1; }
|
||||
else
|
||||
# GitHub serves filtered run listings (branch=, status=, head_sha=, ...)
|
||||
# from a search index that has returned weeks-old results, while the
|
||||
# unfiltered listing stays current, so list unfiltered and filter here.
|
||||
# The repository check keeps out fork PRs whose branch has the same
|
||||
# name. A feature branch is normally built only for its PR, and a PR
|
||||
# build compiles the PR merged into its base rather than head_sha, so
|
||||
# a build of the branch itself (push or dispatch) is preferred when
|
||||
# the same page has one.
|
||||
pick='([.workflow_runs[] | select(.head_branch == env.BRANCH and .conclusion == "success"
|
||||
and .head_repository.full_name == env.GH_REPO)]
|
||||
| map(select(.event != "pull_request"))[0] // .[0])
|
||||
| select(.) | "\(.id) \(.head_sha) \(.created_at)"'
|
||||
# a page of 100 runs spans about a day and a half; a manual run may
|
||||
# target a branch that last built weeks ago
|
||||
pages=3
|
||||
if [ "$SCHEDULED" != true ]; then pages=20; fi
|
||||
found=""
|
||||
for page in $(seq "$pages"); do
|
||||
found=$(gh api "repos/$GH_REPO/actions/workflows/build_all.yml/runs?per_page=100&page=$page" --jq "$pick")
|
||||
if [ -n "$found" ]; then break; fi
|
||||
done
|
||||
[ -n "$found" ] || { echo "no successful $BRANCH build among the last $((pages * 100)) build_all runs; pass build_run_id to test an older one" >&2; exit 1; }
|
||||
fi
|
||||
read -r run_id head_sha created <<< "$found"
|
||||
# the nightly fails rather than report on a stale build
|
||||
if [ "$SCHEDULED" = true ] && [ $(( $(date +%s) - $(date -d "$created" +%s) )) -gt 172800 ]; then
|
||||
echo "newest $BRANCH build $run_id is from $created, over 48 hours old" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'run_id=%s\nhead_sha=%s\n' "$run_id" "$head_sha" >> "$GITHUB_OUTPUT"
|
||||
gh run list --workflow build_all.yml \
|
||||
--branch "${{ inputs.build_branch || 'main' }}" \
|
||||
--status success --limit 1 --json databaseId,headSha \
|
||||
--jq '"run_id=\(.[0].databaseId)\nhead_sha=\(.[0].headSha)"' \
|
||||
>> "$GITHUB_OUTPUT"
|
||||
cat "$GITHUB_OUTPUT"
|
||||
echo "Testing build [$run_id](https://github.com/$GH_REPO/actions/runs/$run_id) of \`$head_sha\`, built $created" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
effect:
|
||||
name: Override sweep effect stage (shard ${{ matrix.shard }})
|
||||
|
||||
@@ -7015,6 +7015,12 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
sliced_info.sliced_plates.push_back(sliced_plate_info);
|
||||
} catch (const Slic3r::SlicingErrors &exs) {
|
||||
const std::string message = print_fff ? print_fff->slicing_errors_message(exs) : std::string(exs.what());
|
||||
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate " << index+1 << ": " << message;
|
||||
boost::nowide::cerr << message << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_SLICING_ERROR, index+1, message, sliced_info);
|
||||
flush_and_exit(CLI_SLICING_ERROR);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate "<<index+1 << std::endl;
|
||||
boost::nowide::cerr << ex.what() << std::endl;
|
||||
|
||||
@@ -1704,6 +1704,25 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
|
||||
|
||||
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
|
||||
//BBS: refine seq-print validation logic
|
||||
// The exception's own message is just "Errors"; the detail is in the per-object errors,
|
||||
// whose object id is the PrintObject's.
|
||||
std::string Print::slicing_errors_message(const SlicingErrors &errors) const
|
||||
{
|
||||
std::string message;
|
||||
for (const SlicingError &error : errors.errors_) {
|
||||
std::string object_name;
|
||||
for (const PrintObject *object : m_objects)
|
||||
if (object->id().id == error.objectId()) {
|
||||
object_name = object->model_object()->name;
|
||||
break;
|
||||
}
|
||||
if (!message.empty())
|
||||
message += "\n";
|
||||
message += object_name.empty() ? std::string(error.what()) : object_name + ": " + error.what();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
|
||||
{
|
||||
auto add_warning = [warnings](StringObjectException w) {
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SlicingErrors;
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
class ModelObject;
|
||||
@@ -967,6 +969,8 @@ public:
|
||||
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
|
||||
// The per-object messages of a SlicingErrors, each prefixed with its object's name.
|
||||
std::string slicing_errors_message(const SlicingErrors &errors) const;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
|
||||
@@ -505,3 +505,29 @@ TEST_CASE("Sequential printing publishes the nozzle group result", "[Print][Mult
|
||||
CHECK(gcode.find("; SEQ-ND-OK") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Slicing errors are reported per object with the object's name", "[Print]")
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({Slic3r::Test::cube(20.)}, print, model);
|
||||
// Lift the cube off the bed: its first layer is empty, which G-code export reports per object.
|
||||
ModelObject *object = model.objects.front();
|
||||
object->name = "floating cube";
|
||||
object->instances.front()->set_offset(object->instances.front()->get_offset() + Vec3d(0., 0., 2.));
|
||||
print.apply(model, DynamicPrintConfig::full_print_config());
|
||||
print.set_status_silent();
|
||||
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
std::string message;
|
||||
try {
|
||||
print.process();
|
||||
print.export_gcode(temp.string(), nullptr, nullptr);
|
||||
FAIL("slicing did not report the empty first layer");
|
||||
} catch (const SlicingErrors &errors) {
|
||||
REQUIRE(errors.errors_.size() == 1);
|
||||
message = print.slicing_errors_message(errors);
|
||||
}
|
||||
CHECK(message.rfind("floating cube: ", 0) == 0);
|
||||
CHECK(message.find("empty first layer") != std::string::npos);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user