mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 11:21:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
536ef75894 | ||
|
|
cda1588578 | ||
|
|
ec0d8c225f | ||
|
|
3040ebaac1 | ||
|
|
d347a80ef8 | ||
|
|
e340a13c18 | ||
|
|
9e16cdb23b | ||
|
|
58842bab05 | ||
|
|
04204ec0d3 | ||
|
|
9ed459e132 |
@@ -44,14 +44,6 @@ jobs:
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.artifact }}
|
||||
# run_unit_tests.sh installs the plugin tests' numpy with the uv the build stages
|
||||
# beside them; the Windows arm64 build bundles none, so put one on PATH there.
|
||||
- name: Install uv
|
||||
if: runner.os == 'Windows' && runner.arch == 'ARM64'
|
||||
uses: astral-sh/setup-uv@v10.2.0
|
||||
with:
|
||||
version: "0.11.21" # ORCA_UV_VERSION in CMakeLists.txt
|
||||
enable-cache: false
|
||||
- uses: lukka/get-cmake@latest
|
||||
with:
|
||||
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
|
||||
|
||||
+1
-3
@@ -52,6 +52,4 @@ internal_docs/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.opc
|
||||
/.test/
|
||||
docs/superpowers/
|
||||
ctest_results.xml
|
||||
docs/superpowers/
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
# This file is made to support the unit tests workflow.
|
||||
# It should only require the directories build/tests, scripts/, and tests/ to function,
|
||||
# and cmake (with ctest) installed -- plus network access to PyPI whenever numpy has to
|
||||
# be installed into a freshly built test tree (see below).
|
||||
# and cmake (with ctest) installed.
|
||||
# (otherwise, update the workflow too, but try to avoid to keep things self-contained)
|
||||
#
|
||||
# Usage: run_unit_tests.sh [TEST_DIR] [BUILD_CONFIG]
|
||||
@@ -19,52 +18,6 @@ cd "${ROOT_DIR}" || exit 1
|
||||
TEST_DIR="${1:-build/tests}"
|
||||
BUILD_CONFIG="${2:-}"
|
||||
|
||||
# The slic3rutils plugin-host tests build numpy arrays through the CPython copied next
|
||||
# to the test binary (see tests/slic3rutils/CMakeLists.txt), which ships no numpy.
|
||||
# Install it with the uv staged beside that runtime -- the tool the app installs plugin
|
||||
# dependencies with -- straight into the interpreter's own site-packages: no pip needed
|
||||
# in the runtime, no PYTHONPATH. Re-checked every run because a rebuild of the test
|
||||
# target re-copies the runtime; needs network whenever it installs. Pinned so a numpy
|
||||
# release cannot change results on its own.
|
||||
NUMPY_VERSION="2.5.3"
|
||||
|
||||
# Without numpy those tests assert the numpy-absent error path instead, so a local run
|
||||
# only warns. Under CI it fails the run, which would otherwise stay green while silently
|
||||
# dropping the array coverage. (The Flatpak leg runs this inside `flatpak build`, whose
|
||||
# minimal environment has no CI, and its offline build stages no uv, so numpy stays
|
||||
# best-effort there.)
|
||||
numpy_unavailable() {
|
||||
if [ -n "${CI:-}" ]; then
|
||||
echo "error: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "warning: $1; the numpy-backed binding tests will cover only the numpy-absent path."
|
||||
}
|
||||
|
||||
has_pinned_numpy() {
|
||||
"${python_exe}" -c "import sys, numpy; sys.exit(numpy.__version__ != '${NUMPY_VERSION}')" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
find_args=("${TEST_DIR}" \( -path '*/python/bin/python3' -o -path '*/python/python.exe' \))
|
||||
# Multi-config trees hold one copy per configuration; only bootstrap the one being run.
|
||||
[ -n "${BUILD_CONFIG}" ] && find_args+=(-path "*/${BUILD_CONFIG}/*")
|
||||
python_exe="$(find "${find_args[@]}" -print -quit 2>/dev/null)"
|
||||
|
||||
if [ -z "${python_exe}" ]; then
|
||||
numpy_unavailable "no bundled Python under ${TEST_DIR}"
|
||||
elif ! has_pinned_numpy; then
|
||||
uv_exe="${python_exe%/python/*}/tools/uv/uv"
|
||||
# Builds that bundle no uv (Windows arm64) fall back to one on PATH.
|
||||
[ -x "${uv_exe}" ] || uv_exe="$(command -v uv)"
|
||||
echo "Installing numpy ${NUMPY_VERSION} into the embedded test interpreter (${python_exe})..."
|
||||
if [ -z "${uv_exe}" ]; then
|
||||
numpy_unavailable "no uv staged beside the tests or on PATH"
|
||||
elif ! "${uv_exe}" pip install --python "${python_exe}" --only-binary :all: "numpy==${NUMPY_VERSION}" \
|
||||
|| ! has_pinned_numpy; then
|
||||
numpy_unavailable "could not install numpy ${NUMPY_VERSION} into ${python_exe}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the whole suite, excluding tests tagged [NotWorking] and tests labelled RequiresApp,
|
||||
# which run the built orca-slicer binary that this directory does not contain.
|
||||
# --no-tests=error fails the job if the filter matches nothing (instead of passing green).
|
||||
|
||||
+52
-6
@@ -2116,9 +2116,14 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
// One resolver for the whole run, so presets from the same vendor tree share its load.
|
||||
std::unique_ptr<PresetBundle> system_preset_resolver;
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
return *system_preset_resolver;
|
||||
};
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
||||
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
||||
return true;
|
||||
@@ -2126,9 +2131,7 @@ int CLI::run(int argc, char **argv)
|
||||
PresetBundle *bundle = nullptr;
|
||||
bool allow_source_manifest = false;
|
||||
if (config_from == "system") {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
bundle = system_preset_resolver.get();
|
||||
bundle = &ensure_system_preset_resolver();
|
||||
allow_source_manifest = true;
|
||||
} else {
|
||||
bundle = ensure_cli_preset_bundle(error);
|
||||
@@ -3120,6 +3123,49 @@ int CLI::run(int argc, char **argv)
|
||||
return 0;
|
||||
};
|
||||
|
||||
// A project saved before a printer or process option existed has no value for it. The GUI takes such
|
||||
// keys from the project's system preset (load_external_preset refreshes every key the project did not
|
||||
// override), so fill them from there too instead of leaving them to the option default.
|
||||
// The extruder variant keys describe the project's variant layout and are kept as they are, so an
|
||||
// older project is not left with a variant list from one layout and ids from another.
|
||||
auto fill_missing_project_keys = [this, &ensure_system_preset_resolver](const std::string &system_name, Preset::Type type) {
|
||||
if (system_name.empty())
|
||||
return;
|
||||
static const std::set<std::string> skip_keys = {
|
||||
"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition", "compatible_prints_condition",
|
||||
"print_settings_id", "filament_settings_id", "printer_settings_id",
|
||||
"print_host", "print_host_webui", "printhost_apikey", "printhost_cafile", "printhost_user", "printhost_password", "printhost_port",
|
||||
"printer_extruder_id", "printer_extruder_variant", "print_extruder_id", "print_extruder_variant", "extruder_variant_list"};
|
||||
const std::vector<std::string> &options = type == Preset::TYPE_PRINTER ? Preset::printer_options() : Preset::print_options();
|
||||
// Keys the legacy handler drops on load can never be in a project, so they do not count as missing.
|
||||
auto dropped_on_load = [](std::string key) {
|
||||
std::string value;
|
||||
PrintConfigDef::handle_legacy(key, value);
|
||||
return key.empty();
|
||||
};
|
||||
std::vector<std::string> missing;
|
||||
for (const std::string &key : options)
|
||||
if (m_print_config.option(key) == nullptr && skip_keys.count(key) == 0 && !dropped_on_load(key))
|
||||
missing.push_back(key);
|
||||
if (missing.empty())
|
||||
return;
|
||||
DynamicPrintConfig system_config;
|
||||
std::string error;
|
||||
if (!ensure_system_preset_resolver().resolve_system_preset(system_config, type, system_name, config_substitution_rule, error)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); keys missing from the project keep their defaults") % system_name % error;
|
||||
return;
|
||||
}
|
||||
for (const std::string &key : missing)
|
||||
if (const ConfigOption *opt = system_config.option(key)) {
|
||||
m_print_config.set_key_value(key, opt->clone());
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% missing from the project, taken from '%2%': %3%") % key % system_name % opt->serialize();
|
||||
}
|
||||
};
|
||||
if (new_printer_name.empty())
|
||||
fill_missing_project_keys(current_printer_system_name, Preset::TYPE_PRINTER);
|
||||
if (new_process_name.empty())
|
||||
fill_missing_project_keys(current_process_system_name, Preset::TYPE_PRINT);
|
||||
|
||||
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
|
||||
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
|
||||
inherits_group.resize(filament_count + 2, std::string());
|
||||
|
||||
+10
-5
@@ -2504,7 +2504,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = path;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2556,7 +2557,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
}
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\n" + err_msg;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2580,7 +2582,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\n" + ex.what();
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2693,7 +2696,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
if (ret) {
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2712,7 +2716,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = path;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
|
||||
@@ -575,18 +575,20 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
|
||||
const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir,
|
||||
const std::string &vendor_id,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error)
|
||||
std::string &error, bool allow_cache)
|
||||
{
|
||||
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule);
|
||||
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule, allow_cache);
|
||||
if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end())
|
||||
return it->second.get();
|
||||
|
||||
// The library loads with no base of its own, so the tree a vendor inherits from
|
||||
// is the same one that resolves the library's own presets.
|
||||
const PresetBundle *library = nullptr;
|
||||
const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY);
|
||||
const PresetBundle *library = nullptr;
|
||||
if (vendor_id != ORCA_FILAMENT_LIBRARY &&
|
||||
boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) {
|
||||
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error);
|
||||
(boost::filesystem::is_regular_file(root_dir / (library_file + ".json")) ||
|
||||
(allow_cache && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"))))) {
|
||||
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache);
|
||||
if (library == nullptr) {
|
||||
error = "OrcaFilamentLibrary contains invalid presets";
|
||||
return nullptr;
|
||||
@@ -595,7 +597,7 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa
|
||||
|
||||
auto bundle = std::make_unique<PresetBundle>();
|
||||
bundle->m_preserve_vendor_source_paths = true;
|
||||
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false);
|
||||
bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, allow_cache);
|
||||
if (bundle->error_count() != 0) {
|
||||
error = "Vendor bundle contains invalid presets";
|
||||
return nullptr;
|
||||
@@ -638,6 +640,43 @@ bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PresetBundle::resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error)
|
||||
{
|
||||
const std::string vendor_id = find_preset_vendor(name, type);
|
||||
if (vendor_id.empty()) {
|
||||
error = "No vendor lists the preset";
|
||||
return false;
|
||||
}
|
||||
// Release builds ship a vendor as its preset cache alone, without the profile JSONs.
|
||||
auto installed = [&vendor_id](const fs::path &root) {
|
||||
return fs::is_regular_file(root / (vendor_id + ".json")) || fs::is_regular_file(root / (vendor_id + ".opc"));
|
||||
};
|
||||
fs::path root_dir = fs::path(data_dir()) / PRESET_SYSTEM_DIR;
|
||||
if (!installed(root_dir))
|
||||
root_dir = fs::path(resources_dir()) / PRESET_PROFILES_DIR;
|
||||
const bool cache_only = !fs::is_regular_file(root_dir / (vendor_id + ".json"));
|
||||
|
||||
try {
|
||||
const PresetBundle *vendor = load_source_vendor(root_dir, vendor_id, compatibility_rule, error, cache_only);
|
||||
if (vendor == nullptr)
|
||||
return false;
|
||||
const PresetCollection &collection = type == Preset::TYPE_PRINTER ? vendor->printers :
|
||||
type == Preset::TYPE_PRINT ? vendor->prints : vendor->filaments;
|
||||
const Preset *preset = collection.find_preset(name, false);
|
||||
if (preset == nullptr) {
|
||||
error = "Preset was not found in its vendor bundle";
|
||||
return false;
|
||||
}
|
||||
config = preset->config;
|
||||
} catch (const std::exception &ex) {
|
||||
error = ex.what();
|
||||
return false;
|
||||
}
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
PresetBundle::PresetBundle(const PresetBundle &rhs)
|
||||
{
|
||||
*this = rhs;
|
||||
|
||||
@@ -273,6 +273,10 @@ public:
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest = true);
|
||||
// Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed
|
||||
// there, as the GUI reads it, and from the bundled profiles otherwise.
|
||||
bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error);
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
@@ -663,13 +667,14 @@ private:
|
||||
// Vendor trees loaded by resolve_preset_config's manifest path, so every preset
|
||||
// resolved through this bundle shares one load per source root and vendor. The
|
||||
// filament library is one such tree, shared by every vendor under its root.
|
||||
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>>
|
||||
// A tree read from its preset cache is kept apart: its presets carry no source file.
|
||||
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule, bool>, std::unique_ptr<PresetBundle>>
|
||||
m_source_vendor_bundles;
|
||||
|
||||
const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir,
|
||||
const std::string &vendor_id,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error);
|
||||
std::string &error, bool allow_cache = false);
|
||||
|
||||
// Orca: validation only - flag any printer with two or more compatible
|
||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||
|
||||
+10
-5
@@ -2717,7 +2717,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
|
||||
@@ -3344,7 +3345,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
|
||||
@@ -4950,7 +4952,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
{
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = file;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
@@ -4980,7 +4983,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = file + "\n" + ex.what();
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
@@ -4994,7 +4998,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = file;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
|
||||
@@ -34,6 +34,10 @@ static const char* Segments_Vertex_Shader =
|
||||
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
|
||||
// world_position, so the caster would write a depth the receiver never looks up.
|
||||
"uniform float bias_scale;\n"
|
||||
// draw the instances last to first, top layers before the ones they hide, so that early depth
|
||||
// rejection discards most of the hidden fragments; set when the camera looks down on the print
|
||||
"uniform int reverse_order;\n"
|
||||
"uniform int instance_count;\n"
|
||||
"in int vertex_id;\n"
|
||||
"out vec3 color;\n"
|
||||
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
|
||||
@@ -59,7 +63,8 @@ static const char* Segments_Vertex_Shader =
|
||||
" return top_diffuse + front_diffuse + top_specular;\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n"
|
||||
" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
|
||||
" int id_b = id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
|
||||
" vec3 pos_b = texelFetch(position_tex, id_b).xyz;\n"
|
||||
|
||||
@@ -763,6 +763,8 @@ void ViewerImpl::init(const std::string& opengl_context_version)
|
||||
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
|
||||
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
|
||||
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
|
||||
m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
|
||||
m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
|
||||
// ORCA: realistic view
|
||||
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
|
||||
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
|
||||
@@ -2090,6 +2092,15 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
|
||||
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
|
||||
// The segments come in print order, bottom layer first. Seen from above, that is back to front,
|
||||
// and every hidden fragment is shaded before the one that covers it. Drawing them last to first
|
||||
// lets the depth test reject the hidden ones instead. The camera looks down when the world's
|
||||
// up axis points towards it, which is the view matrix's (2, 2) entry being positive.
|
||||
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
|
||||
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
|
||||
// with the lookup off.
|
||||
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
|
||||
|
||||
@@ -362,6 +362,8 @@ private:
|
||||
int m_uni_segments_height_width_angle_tex_id{ -1 };
|
||||
int m_uni_segments_colors_tex_id{ -1 };
|
||||
int m_uni_segments_segment_index_tex_id{ -1 };
|
||||
int m_uni_segments_reverse_order_id{ -1 };
|
||||
int m_uni_segments_instance_count_id{ -1 };
|
||||
int m_uni_segments_shadow_map_id{ -1 };
|
||||
int m_uni_segments_shadow_light_vp_id{ -1 };
|
||||
int m_uni_segments_shadow_intensity_id{ -1 };
|
||||
|
||||
@@ -12800,8 +12800,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
notification_manager->set_slicing_progress_export_possible();
|
||||
|
||||
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
|
||||
const std::string lifecycle_job_name = this->background_process.fff_print() ?
|
||||
this->background_process.fff_print()->output_filename() : std::string();
|
||||
this->background_process.reset_export();
|
||||
// This bool stops showing export finished notification even when process_completed_with_error is false
|
||||
bool has_error = false;
|
||||
@@ -12848,8 +12846,18 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
|
||||
{
|
||||
Slic3r::LifecycleEventContext ctx;
|
||||
ctx.name = lifecycle_job_name;
|
||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||
if (const PrintBase* print = this->background_process.current_print()) {
|
||||
const Model& model = print->model();
|
||||
ctx.id = std::to_string(model.id().id);
|
||||
if (model.model_info)
|
||||
ctx.name = model.model_info->model_name;
|
||||
} else {
|
||||
// Realistically Printbase* print will never be null because select_technology already asserts an active print
|
||||
// and the worker thread asserts it before processing.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": slicing completed without an active print; lifecycle event has no model ID";
|
||||
}
|
||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn :
|
||||
(has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
|
||||
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
|
||||
}
|
||||
|
||||
@@ -345,21 +345,16 @@ boost::filesystem::path find_bundled_python_home()
|
||||
fs::path bundle_python = fs::path(resources_dir()).parent_path() / "MacOS" / "python";
|
||||
if (valid_python_home(bundle_python))
|
||||
return bundle_python;
|
||||
#elif !defined(_WIN32)
|
||||
#elif defined(_WIN32)
|
||||
fs::path exe_python = boost::dll::program_location().parent_path() / "python";
|
||||
if (valid_python_home(exe_python))
|
||||
return exe_python;
|
||||
#else
|
||||
fs::path linux_python = fs::path(resources_dir()).parent_path() / "lib" / "python";
|
||||
if (valid_python_home(linux_python))
|
||||
return linux_python;
|
||||
#endif
|
||||
|
||||
// Next to the executable: the Windows install layout, and the runtime copied
|
||||
// beside every platform's unit-test binary (tests/slic3rutils/CMakeLists.txt).
|
||||
// The CI test runner only receives the build/tests tree, so the candidates
|
||||
// below -- all of which point into the deps or install trees -- never resolve
|
||||
// there.
|
||||
fs::path exe_python = boost::dll::program_location().parent_path() / "python";
|
||||
if (valid_python_home(exe_python))
|
||||
return exe_python;
|
||||
|
||||
fs::path configured_python = ORCA_BUNDLED_PYTHON_ROOT;
|
||||
if (!configured_python.empty() && valid_python_home(configured_python))
|
||||
return configured_python;
|
||||
|
||||
@@ -15,3 +15,11 @@ set_tests_properties(cli_strict_mode PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
TIMEOUT 900)
|
||||
|
||||
add_test(NAME cli_project_missing_keys
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_project_missing_keys.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON}
|
||||
${CMAKE_SOURCE_DIR}/resources/profiles/BBL)
|
||||
set_tests_properties(cli_project_missing_keys PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
TIMEOUT 900)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end check that the CLI fills settings missing from a project from the project's system presets.
|
||||
#
|
||||
# A project saved before an option existed has no value for it. The GUI takes such keys from the
|
||||
# project's system printer and process presets, not from the option defaults, and the CLI must slice
|
||||
# the project with the same values. A project is exported from the shipped Bambu Lab P1S presets, one
|
||||
# printer key and one process key are removed from it, one kept key is changed, and it is sliced again.
|
||||
#
|
||||
# usage: test_cli_project_missing_keys.sh <orca-slicer binary> <python3> <resources/profiles/BBL>
|
||||
set -u
|
||||
|
||||
BIN="${1:-}"
|
||||
PY="${2:-python3}"
|
||||
PROFILES="${3:-}"
|
||||
# 77 is the test's SKIP_RETURN_CODE.
|
||||
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
|
||||
[ -d "$PROFILES" ] || { echo "FAIL: profiles directory not found: $PROFILES"; exit 1; }
|
||||
|
||||
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-missing-keys.XXXXXX")"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
"$PY" - "$WORK/cube.stl" <<'EOF'
|
||||
import sys
|
||||
|
||||
v = [(x, y, z) for z in (0, 10) for y in (0, 10) for x in (0, 10)]
|
||||
with open(sys.argv[1], "w") as f:
|
||||
f.write("solid cube\n")
|
||||
for a, b, c, d in ((0, 2, 3, 1), (4, 5, 7, 6), (0, 1, 5, 4), (2, 6, 7, 3), (0, 4, 6, 2), (1, 3, 7, 5)):
|
||||
for tri in ((v[a], v[b], v[c]), (v[a], v[c], v[d])):
|
||||
f.write("facet normal 0 0 0\nouter loop\n")
|
||||
for p in tri:
|
||||
f.write("vertex %g %g %g\n" % p)
|
||||
f.write("endloop\nendfacet\n")
|
||||
f.write("endsolid cube\n")
|
||||
EOF
|
||||
|
||||
# slice <tag> <input> [option...]: slice into $WORK/<tag>/out.3mf with a fresh data directory.
|
||||
slice() {
|
||||
local out="$WORK/$1" input="$2"; shift 2
|
||||
mkdir -p "$out"
|
||||
timeout 300 "$BIN" --datadir "$out/datadir" "$@" --slice 0 --outputdir "$out" --export-3mf out.3mf "$input" \
|
||||
> "$out/log" 2>&1 || { echo "FAIL: $1: orca-slicer exited $?"; tail -n 40 "$out/log"; exit 1; }
|
||||
}
|
||||
|
||||
slice base "$WORK/cube.stl" \
|
||||
--load-settings "$PROFILES/machine/Bambu Lab P1S 0.4 nozzle.json;$PROFILES/process/0.20mm Standard @BBL X1C.json" \
|
||||
--load-filaments "$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json"
|
||||
|
||||
# The removed keys, with their option defaults from PrintConfig.cpp, and a kept key with a new value.
|
||||
"$PY" - "$WORK/base/out.3mf" "$WORK/old.3mf" <<'EOF' || exit $?
|
||||
import json, sys, zipfile
|
||||
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
missing = {"extruder_clearance_dist_to_rod": "40", "sparse_infill_density": "20%"}
|
||||
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
|
||||
for item in zin.infolist():
|
||||
data = zin.read(item.filename)
|
||||
if item.filename == "Metadata/project_settings.config":
|
||||
config = json.loads(data)
|
||||
for key, default in missing.items():
|
||||
if config[key] == default:
|
||||
print("SKIP: %s is %s in the system preset, the option default, so the test cannot tell them apart" % (key, default))
|
||||
sys.exit(77)
|
||||
expected = {key: config.pop(key) for key in missing}
|
||||
expected["wall_loops"] = str(int(config["wall_loops"]) + 1)
|
||||
config["wall_loops"] = expected["wall_loops"]
|
||||
data = json.dumps(config, indent=4)
|
||||
zout.writestr(item, data)
|
||||
with open(dst + ".expected.json", "w") as f:
|
||||
json.dump(expected, f)
|
||||
EOF
|
||||
|
||||
slice project "$WORK/old.3mf"
|
||||
|
||||
"$PY" - "$WORK/project/out.3mf" "$WORK/old.3mf.expected.json" <<'EOF'
|
||||
import json, sys, zipfile
|
||||
|
||||
with zipfile.ZipFile(sys.argv[1]) as z:
|
||||
config = json.loads(z.read("Metadata/project_settings.config"))
|
||||
with open(sys.argv[2]) as f:
|
||||
expected = json.load(f)
|
||||
errors = ["%s is %r, want %r" % (key, config.get(key), want) for key, want in expected.items() if config.get(key) != want]
|
||||
for e in errors:
|
||||
print("FAIL: " + e)
|
||||
sys.exit(1 if errors else 0)
|
||||
EOF
|
||||
status=$?
|
||||
[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; }
|
||||
echo "PASS"
|
||||
@@ -15,6 +15,9 @@
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/LifecycleEvents.hpp"
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
#include "test_utils.hpp"
|
||||
@@ -22,7 +25,10 @@
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
@@ -224,8 +230,88 @@ std::string resolved_output_name(Model& model, const std::string& format, const
|
||||
return print.output_filename(filename_base);
|
||||
}
|
||||
|
||||
struct ScopedLifecycleHook
|
||||
{
|
||||
explicit ScopedLifecycleHook(LifecycleHookFn hook) { set_lifecycle_hook_fn(std::move(hook)); }
|
||||
~ScopedLifecycleHook() { set_lifecycle_hook_fn(nullptr); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Slicing lifecycle events identify the model", "[Print][LifecycleEvents]")
|
||||
{
|
||||
struct ObservedEvent {
|
||||
LifecycleEvent event;
|
||||
std::string id;
|
||||
std::string name;
|
||||
};
|
||||
std::vector<ObservedEvent> events;
|
||||
ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||
events.push_back({ event, ctx.id, ctx.name });
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
ModelInfo info;
|
||||
info.model_name = "Lifecycle test model";
|
||||
model.model_info = std::make_shared<ModelInfo>(std::move(info));
|
||||
init_print({cube(20)}, print, model);
|
||||
|
||||
print.process();
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
print.export_gcode(temp.string(), nullptr, nullptr);
|
||||
GCodeProcessorResult result;
|
||||
print.export_gcode_from_previous_file(temp.string(), &result);
|
||||
|
||||
const std::string expected_id = std::to_string(print.model().id().id);
|
||||
const std::vector<LifecycleEvent> expected_events = {
|
||||
LifecycleEvent::SliceStarted,
|
||||
LifecycleEvent::SliceGeometryFinished,
|
||||
LifecycleEvent::GCodeExportStarted,
|
||||
LifecycleEvent::GCodeExportFinished,
|
||||
LifecycleEvent::GCodeExportStarted,
|
||||
LifecycleEvent::GCodeExportFinished,
|
||||
};
|
||||
REQUIRE(events.size() == expected_events.size());
|
||||
for (size_t i = 0; i < expected_events.size(); ++i) {
|
||||
CHECK(events[i].event == expected_events[i]);
|
||||
CHECK(events[i].id == expected_id);
|
||||
CHECK(events[i].name == "Lifecycle test model");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Slicing lifecycle event name is empty without model metadata", "[Print][LifecycleEvents]")
|
||||
{
|
||||
std::string event_id;
|
||||
std::string event_name = "unset";
|
||||
ScopedLifecycleHook hook([&](LifecycleEvent event, const LifecycleEventContext& ctx) {
|
||||
if (event == LifecycleEvent::SliceStarted) {
|
||||
event_id = ctx.id;
|
||||
event_name = ctx.name;
|
||||
}
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model);
|
||||
print.process();
|
||||
|
||||
CHECK(event_id == std::to_string(print.model().id().id));
|
||||
CHECK(event_name.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Output filenames with numeric statistics fail before slicing finishes", "[Print][Regression]")
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_key_value("filename_format", new ConfigOptionString("{int(total_weight*10) / 10.0}"));
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
init_print({cube(20)}, print, model, config);
|
||||
|
||||
CHECK_THROWS_AS(print.output_filename(), PlaceholderParserError);
|
||||
}
|
||||
|
||||
TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]")
|
||||
{
|
||||
Model model;
|
||||
|
||||
@@ -5405,6 +5405,35 @@ struct ScopedDataDir
|
||||
~ScopedDataDir() { set_data_dir(previous); }
|
||||
};
|
||||
|
||||
// resources_dir() is process-wide too; system preset lookups scan its profiles directory.
|
||||
struct ScopedResourcesDir
|
||||
{
|
||||
std::string previous = resources_dir();
|
||||
explicit ScopedResourcesDir(const fs::path &dir) { set_resources_dir(dir.string()); }
|
||||
~ScopedResourcesDir() { set_resources_dir(previous); }
|
||||
};
|
||||
|
||||
// An "Acme" vendor under root whose "Acme Printer" inherits extruder_clearance_dist_to_rod from an
|
||||
// abstract base, with the printer in a nested sub_path so the name cannot be derived from the file.
|
||||
void write_acme_printer_vendor(const fs::path &root, double dist_to_rod)
|
||||
{
|
||||
const fs::path machine_dir = root / "Acme" / "machine";
|
||||
fs::create_directories(machine_dir / "nested");
|
||||
std::ofstream((root / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme",)"
|
||||
<< R"("machine_model_list":[{"name":"Acme One","sub_path":"machine/model.json"}],"machine_list":[)"
|
||||
<< R"({"name":"fdm_acme_common","sub_path":"machine/base.json"},)"
|
||||
<< R"({"name":"Acme Printer","sub_path":"machine/nested/printer.json"}]})";
|
||||
std::ofstream((machine_dir / "model.json").string())
|
||||
<< R"({"type":"machine_model","name":"Acme One","nozzle_diameter":"0.4"})";
|
||||
std::ofstream((machine_dir / "base.json").string())
|
||||
<< R"({"type":"machine","name":"fdm_acme_common","from":"system","instantiation":"false",)"
|
||||
<< R"("extruder_clearance_dist_to_rod":")" << dist_to_rod << R"("})";
|
||||
std::ofstream((machine_dir / "nested" / "printer.json").string())
|
||||
<< R"({"type":"machine","name":"Acme Printer","from":"system","instantiation":"true","inherits":"fdm_acme_common",)"
|
||||
<< R"("printer_model":"Acme One","printer_variant":"0.4"})";
|
||||
}
|
||||
|
||||
std::string read_file(const fs::path &file)
|
||||
{
|
||||
std::ifstream in(file.string(), std::ios::binary);
|
||||
@@ -5482,3 +5511,75 @@ TEST_CASE("Config import confines zip entries, preset names and bundle ids to th
|
||||
CHECK_FALSE(any_filename_contains(temp_dir.path(), "bundle-escape"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset resolves by name from the bundled profiles", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error.empty());
|
||||
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset resolves from the data directory copy of its vendor", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
|
||||
write_acme_printer_vendor(temp_dir.path() / "data" / PRESET_SYSTEM_DIR, 35.);
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(35., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset resolves from a vendor shipped as its preset cache alone", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
const fs::path profiles = temp_dir.path() / "resources" / "profiles";
|
||||
write_acme_printer_vendor(profiles, 33.);
|
||||
|
||||
PresetBundle writer;
|
||||
writer.set_generate_vendor_caches(true);
|
||||
writer.load_vendor_configs_from_json(profiles.string(), "Acme", PresetBundle::LoadSystem,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
REQUIRE(fs::exists(profiles / "Acme.opc"));
|
||||
// Release builds ship the cache and drop the profile JSONs, manifest included.
|
||||
fs::remove(profiles / "Acme.json");
|
||||
fs::remove_all(profiles / "Acme");
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("A system preset no vendor lists is not resolved", "[Preset][Bundle]")
|
||||
{
|
||||
ScopedTemporaryDir temp_dir;
|
||||
ScopedDataDir data(temp_dir.path() / "data");
|
||||
ScopedResourcesDir resources(temp_dir.path() / "resources");
|
||||
write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.);
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Unknown Printer",
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_FALSE(error.empty());
|
||||
}
|
||||
|
||||
@@ -49,16 +49,9 @@ if (WIN32)
|
||||
COMMENT "Copying Python runtime for slic3rutils plugin host API tests"
|
||||
VERBATIM
|
||||
)
|
||||
elseif (NOT FLATPAK)
|
||||
# The CI unit-test runner only receives the build/tests tree, so both the
|
||||
# interpreter and the libpython the test binary links have to travel next to
|
||||
# the executable; find_bundled_python_home() picks the copy up from there.
|
||||
if (APPLE)
|
||||
target_link_options(${_TEST_NAME}_tests PRIVATE
|
||||
"LINKER:-rpath,@executable_path/python/lib")
|
||||
else ()
|
||||
set_property(TARGET ${_TEST_NAME}_tests APPEND PROPERTY BUILD_RPATH "$ORIGIN/python/lib")
|
||||
endif ()
|
||||
elseif (APPLE)
|
||||
target_link_options(${_TEST_NAME}_tests PRIVATE
|
||||
"LINKER:-rpath,@executable_path/python/lib")
|
||||
|
||||
add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf
|
||||
@@ -66,7 +59,7 @@ elseif (NOT FLATPAK)
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${CMAKE_PREFIX_PATH}/libpython"
|
||||
"$<TARGET_FILE_DIR:${_TEST_NAME}_tests>/python"
|
||||
COMMENT "Copying Python runtime for the plugin host API tests"
|
||||
COMMENT "Copying Python runtime for macOS plugin host API tests"
|
||||
VERBATIM
|
||||
)
|
||||
elseif (FLATPAK)
|
||||
@@ -83,16 +76,4 @@ elseif (FLATPAK)
|
||||
)
|
||||
endif()
|
||||
|
||||
# scripts/run_unit_tests.sh installs the tests' numpy into that runtime with this uv,
|
||||
# staged where the app build tree keeps it (<exe dir>/tools/uv, see src/CMakeLists.txt).
|
||||
if (ORCA_BUNDLED_UV_EXECUTABLE)
|
||||
add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:${_TEST_NAME}_tests>/tools/uv"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORCA_BUNDLED_UV_EXECUTABLE}"
|
||||
"$<TARGET_FILE_DIR:${_TEST_NAME}_tests>/tools/uv/${ORCA_BUNDLED_UV_FILENAME}"
|
||||
COMMENT "Copying uv for the plugin host API tests"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
orcaslicer_discover_tests(${_TEST_NAME}_tests)
|
||||
|
||||
Reference in New Issue
Block a user