Merge branch 'main' into feat/ota-opc-ci

This commit is contained in:
Ian Chua
2026-09-15 15:19:43 +08:00
committed by GitHub
224 changed files with 4402 additions and 1827 deletions
+335 -37
View File
@@ -53,6 +53,8 @@ using namespace nlohmann;
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "libslic3r/FilamentMixer.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode.hpp"
#include "libslic3r/Model.hpp"
@@ -77,8 +79,9 @@ using namespace nlohmann;
#include "libslic3r/ObjColorUtils.hpp"
#include "OrcaSlicer.hpp"
//BBS: add exception handler for win32
#include <wx/filename.h>
#include <wx/stdpaths.h>
//BBS: add exception handler for win32
#ifdef WIN32
#include "dev-utils/BaseException.h"
#endif
@@ -160,6 +163,7 @@ std::map<int, std::string> cli_errors = {
{CLI_FILAMENT_CAN_NOT_MAP, "Some filaments cannot be mapped to correct extruders for multi-extruder Printer."},
{CLI_ONLY_ONE_TPU_SUPPORTED, "Not support printing 2 or more TPU filaments."},
{CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER, "Some filaments cannot be printed on the extruder mapped to."},
{CLI_MIXED_FILAMENT_INVALID, "A mixed filament is invalid: its components are different filament types, or it has no filament of its own."},
{CLI_SLICING_ERROR, "Failed slicing the model. Please verify the slicing of all plates on Orca Slicer before uploading."},
{CLI_GCODE_PATH_CONFLICTS, " G-code conflicts detected after slicing. Please make sure the 3mf file can be successfully sliced in the latest Orca Slicer. If the file slices normally in Orca Slicer, try moving the wipe tower further from other models, as we use more conservative parameters for it during upload."},
{CLI_GCODE_PATH_IN_UNPRINTABLE_AREA, "Found G-code in unprintable area of multi-extruder printers after slicing. Please make sure the 3mf file can be successfully sliced in the latest Orca Slicer."}
@@ -1383,6 +1387,25 @@ int CLI::run(int argc, char **argv)
if (downward_check_option)
downward_check = downward_check_option->value;
// --export-settings - writes its JSON to stdout, so reject every action or transform that may write there
// too (--info, --help, --orient, slicing and exporting). The allowed ones do nothing when nothing is
// sliced or exported.
if (std::find(m_actions.begin(), m_actions.end(), "export_settings") != m_actions.end() && m_config.opt_string("export_settings") == "-") {
static const std::set<std::string> stdout_compatible = { "export_settings", "uptodate", "load_defaultfila", "min_save",
"mtcpp", "mstpp", "no_check", "normative_check", "pipe" };
for (const std::vector<std::string> *opt_keys : { &m_actions, &m_transforms }) {
for (const std::string &opt_key : *opt_keys) {
if (stdout_compatible.count(opt_key) == 0) {
std::string flag = opt_key;
std::replace(flag.begin(), flag.end(), '_', '-');
boost::nowide::cerr << "--export-settings - cannot be combined with --" << flag << std::endl;
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
flush_and_exit(CLI_INVALID_PARAMS);
}
}
}
}
bool start_gui = m_actions.empty() && !downward_check;
if (start_gui) {
BOOST_LOG_TRIVIAL(info) << "no action, start gui directly" << std::endl;
@@ -1466,6 +1489,10 @@ int CLI::run(int argc, char **argv)
std::vector<std::string> upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings;
std::vector<std::string> current_filaments_name, current_filaments_system_name, current_inherits_group, current_extruder_variants, new_extruder_variants, current_print_extruder_variants, new_printer_extruder_variants;
DynamicPrintConfig load_process_config, load_machine_config;
//ORCA: full configs of the "current" (3MF-embedded) process/printer presets, kept so that
// compatible_printers_condition can be evaluated for them below. Previously only the
// literal compatible_printers list was extracted.
DynamicPrintConfig current_process_full_config, current_printer_full_config;
bool new_process_config_is_system = true, new_printer_config_is_system = true;
std::string pipe_name, makerlab_name, makerlab_version, different_process_setting;
const std::vector<std::string> &metadata_name = m_config.option<ConfigOptionStrings>("metadata_name", true)->values;
@@ -2002,19 +2029,21 @@ int CLI::run(int argc, char **argv)
}
};
auto resolve_preset = [&ensure_cli_preset_bundle](const std::string &file, DynamicPrintConfig &config,
// 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) {
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
return true;
std::unique_ptr<PresetBundle> source_bundle;
PresetBundle *bundle = nullptr;
bool allow_source_manifest = false;
if (config_from == "system") {
source_bundle = std::make_unique<PresetBundle>();
bundle = source_bundle.get();
if (!system_preset_resolver)
system_preset_resolver = std::make_unique<PresetBundle>();
bundle = system_preset_resolver.get();
allow_source_manifest = true;
} else {
bundle = ensure_cli_preset_bundle(error);
@@ -2046,6 +2075,51 @@ int CLI::run(int argc, char **argv)
error, allow_source_manifest);
};
//ORCA: list the keys a user preset overrides relative to its system parent, for the
// `different_settings_to_system` column of an exported 3MF. Without it the CLI
// writes an empty column, so re-opening a CLI-exported project in the GUI shows
// spurious "unsaved changes" and can revert inherited process/filament/machine
// values to system defaults.
//
// The parent comes from the preset bundle that inherits resolution already builds,
// so this adds no extra loading. Returns "" whenever the parent cannot be resolved,
// which is exactly the previous behaviour.
auto cli_different_settings = [&ensure_cli_preset_bundle](const DynamicPrintConfig &resolved,
const std::string &parent_name,
Preset::Type type) -> std::string {
if (parent_name.empty())
return std::string();
std::string error;
PresetBundle *bundle = ensure_cli_preset_bundle(error);
if (bundle == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "CLI: no preset bundle for different_settings_to_system: " << error;
return std::string();
}
const PresetCollection *collection = nullptr;
switch (type) {
case Preset::TYPE_PRINT: collection = &bundle->prints; break;
case Preset::TYPE_FILAMENT: collection = &bundle->filaments; break;
case Preset::TYPE_PRINTER: collection = &bundle->printers; break;
default: return std::string();
}
const Preset *parent = collection->find_preset2(parent_name, true);
if (parent == nullptr) {
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: parent preset '%1%' not found; leaving different_settings_to_system empty")%parent_name;
return std::string();
}
std::vector<std::string> keys = resolved.diff(parent->config);
//ORCA: preset metadata, not user-tunable settings. compatible_printers /
// compatible_prints have their own tracking columns and would double-count.
keys.erase(std::remove_if(keys.begin(), keys.end(), [](const std::string &k) {
return k == "inherits" || k == "compatible_printers" || k == "compatible_prints"
|| k == "compatible_printers_condition" || k == "compatible_prints_condition"
|| k == "print_settings_id" || k == "filament_settings_id" || k == "printer_settings_id";
}),
keys.end());
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% overrides vs parent '%2%'")%keys.size()%parent_name;
return Slic3r::escape_strings_cstyle(keys);
};
auto load_config_file = [&resolve_preset](const std::string& file, DynamicPrintConfig& config, std::string& config_type,
std::string& config_name, std::string& filament_id, std::string& config_from) {
if (! boost::filesystem::exists(file)) {
@@ -2635,6 +2709,8 @@ int CLI::run(int argc, char **argv)
flush_and_exit(ret);
}
upward_compatible_printers = config.option<ConfigOptionStrings>("upward_compatible_machine", true)->values;
//ORCA: keep the full config so compatible_printers_condition can be evaluated against it below
current_printer_full_config = std::move(config);
}
}
}
@@ -2657,6 +2733,8 @@ int CLI::run(int argc, char **argv)
flush_and_exit(ret);
}
current_print_compatible_printers = config.option<ConfigOptionStrings>("compatible_printers", true)->values;
//ORCA: keep the full config so compatible_printers_condition can be evaluated against it below
current_process_full_config = std::move(config);
}
}
}
@@ -2675,46 +2753,88 @@ int CLI::run(int argc, char **argv)
for (int index = 0; index < upward_compatible_printers.size(); index++) {
BOOST_LOG_TRIVIAL(info) << boost::format("index %1%, upward_compatible_printers %2%")%index %upward_compatible_printers[index];
}
//ORCA: Replace the four manual equality-loop checks below with is_compatible_with_printer(), the
// same helper the GUI uses, which also evaluates compatible_printers_condition. Process
// profiles that declare compatibility via condition only -- leaving compatible_printers
// empty -- were always reported incompatible by the literal-name match, so a CLI slice with
// such a preset exited with CLI_PROCESS_NOT_COMPATIBLE (-17) even though the GUI accepts the
// same pair. Behaviour is unchanged where an explicit list exists: is_compatible_with_printer
// does the same name match, and returns true when both list and condition are empty (which
// matches the "old 3mf, no compatible printers" path below).
auto check_compat = [](const DynamicPrintConfig &process_cfg,
const DynamicPrintConfig &printer_cfg,
const std::string &printer_name) -> bool {
return is_compatible_with_printer(process_cfg, Preset::TYPE_PRINT, printer_cfg, printer_name);
};
//ORCA: a 3MF's project config does not carry compatible_printers / compatible_printers_condition.
// PresetBundle::construct_full_config() erases both and re-emits them as
// print_compatible_printers and compatible_machine_expression_group; they are renamed back
// only on the PresetBundle load path, which the CLI does not take. Feeding the project config
// to the check as-is therefore presents no list and no condition, and
// is_compatible_with_printer() reads that as "no constraint" and accepts every printer.
// Translate the two keys back. Index 0 of the expression group is the print preset -- the
// group is filled print, filaments, printer (PresetBundle.cpp).
// The raw keys win whenever they carry something. A project the CLI exported itself has the
// real compatible_printers_condition AND an all-empty compatible_machine_expression_group,
// so copying the group's first entry unconditionally would overwrite a valid condition with
// "" and accept every printer. The renamed keys are only a fallback, and an empty value is
// never written over a real one.
auto cli_process_compat_config = [](const DynamicPrintConfig &project_cfg) -> DynamicPrintConfig {
DynamicPrintConfig cfg = project_cfg;
const auto *raw_list = project_cfg.option<ConfigOptionStrings>("compatible_printers");
const auto *list = project_cfg.option<ConfigOptionStrings>("print_compatible_printers");
if ((raw_list == nullptr || raw_list->values.empty()) && list != nullptr && !list->values.empty())
cfg.set_key_value("compatible_printers", new ConfigOptionStrings(list->values));
const auto *raw_cond = project_cfg.option<ConfigOptionString>("compatible_printers_condition");
const auto *group = project_cfg.option<ConfigOptionStrings>("compatible_machine_expression_group");
if ((raw_cond == nullptr || raw_cond->value.empty()) && group != nullptr && !group->values.empty() &&
!group->values.front().empty())
cfg.set_key_value("compatible_printers_condition", new ConfigOptionString(group->values.front()));
return cfg;
};
if (!new_printer_name.empty()) {
if (!new_process_name.empty()) {
for (int index = 0; index < new_print_compatible_printers.size(); index++) {
if (new_print_compatible_printers[index] == new_printer_system_name) {
process_compatible = true;
break;
}
}
//new process + new printer: both configs came from --load-settings
process_compatible = check_compat(load_process_config, load_machine_config, new_printer_system_name);
BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, inherited from %2%, new process %3%, inherited from %4% ,compatible %5%")
%new_printer_name %new_printer_system_name %new_process_name %new_process_system_name %process_compatible;
}
else {
for (int index = 0; index < current_print_compatible_printers.size(); index++) {
if (current_print_compatible_printers[index] == new_printer_system_name) {
process_compatible = true;
break;
}
//3MF-embedded process vs new printer. current_process_full_config is only populated from
//profiles/BBL/process_full/, so for every other vendor fall back to the 3MF's own project
//config in m_print_config, with its renamed compatibility keys translated back (see
//cli_process_compat_config above). Without this a 3MF built from a condition-only process
//is rejected when re-sliced with the very printer it was made for.
{
//ORCA: profiles/BBL/{process,machine}_full/ are gitignored and not generated in-tree,
// so current_*_full_config is always empty and this fallback is the only live path.
const DynamicPrintConfig process_cfg = current_process_full_config.empty()
? cli_process_compat_config(m_print_config)
: current_process_full_config;
process_compatible = check_compat(process_cfg, load_machine_config, new_printer_system_name);
}
BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, inherited from %2%, old process %3%, inherited from %4% ,compatible %5%")
%new_printer_name %new_printer_system_name %current_process_name %current_process_system_name %process_compatible;
}
}
else if (!new_process_name.empty()) {
for (int index = 0; index < new_print_compatible_printers.size(); index++) {
if (new_print_compatible_printers[index] == current_printer_system_name) {
process_compatible = true;
break;
}
//new process vs 3MF-embedded printer. As above, current_printer_full_config only resolves for
//BBL profiles; otherwise evaluate against the 3MF's own project config in m_print_config, which
//holds the embedded printer's printer_notes / nozzle_diameter.
{
const DynamicPrintConfig &printer_cfg = current_printer_full_config.empty() ? m_print_config : current_printer_full_config;
process_compatible = check_compat(load_process_config, printer_cfg, current_printer_system_name);
}
BOOST_LOG_TRIVIAL(info) << boost::format("old printer %1%, inherited from %2%, new process %3%, inherited from %4% ,compatible %5%")
%current_printer_name %current_printer_system_name %new_process_name %new_process_system_name %process_compatible;
}
else {
//check the compatible of old printer&&process
for (int index = 0; index < current_print_compatible_printers.size(); index++) {
if (current_print_compatible_printers[index] == current_printer_system_name) {
process_compatible = true;
break;
}
}
//both sides 3MF-embedded (pure reprocess)
if (!current_process_full_config.empty() && !current_printer_full_config.empty())
process_compatible = check_compat(current_process_full_config, current_printer_full_config, current_printer_system_name);
else
process_compatible = std::find(current_print_compatible_printers.begin(), current_print_compatible_printers.end(), current_printer_system_name) != current_print_compatible_printers.end();
if (!process_compatible && current_print_compatible_printers.empty())
{
BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf, no compatible printers, set to compatible");
@@ -2937,8 +3057,10 @@ int CLI::run(int argc, char **argv)
}
}
else {
//todo: support user machine preset's different settings
different_settings[filament_count+1] = "";
//ORCA: was a //todo — compute the user's overrides instead of writing an empty column.
different_settings[filament_count+1] = new_printer_config_is_system
? std::string()
: cli_different_settings(load_machine_config, new_printer_system_name, Preset::TYPE_PRINTER);
if (new_printer_config_is_system)
inherits_group[filament_count+1] = "";
else
@@ -3080,8 +3202,14 @@ int CLI::run(int argc, char **argv)
print_compatible_printers = std::move(current_print_compatible_printers);
}
else {
//todo: support system process preset
different_settings[0] = "";
//ORCA: was a //todo. Prefer a value the loaded JSON already carried, otherwise
// compute the overrides against the system parent.
if (!different_process_setting.empty())
different_settings[0] = different_process_setting;
else
different_settings[0] = new_process_config_is_system
? std::string()
: cli_different_settings(load_process_config, new_process_system_name, Preset::TYPE_PRINT);
if (new_process_config_is_system)
inherits_group[0] = "";
else
@@ -3268,6 +3396,16 @@ int CLI::run(int argc, char **argv)
int filament_index = load_filaments_index[index];
std::vector<std::string> different_keys;
//ORCA: diff before load_default_gcodes_to_config, the way the process and machine
// slots above already do. That call materialises absent gcode keys via
// option(..., true), and DynamicConfig::diff only compares keys present in
// both configs -- so a gcode key the leaf did not carry would go from "not
// compared" to "compared as empty against the parent" and land in the column
// as an override the user never made.
std::string filament_different_settings;
if (load_filament_count > 0)
filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT);
load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT);
if (load_filament_count > 0) {
@@ -3279,8 +3417,8 @@ int CLI::run(int argc, char **argv)
opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0);
config.erase("filament_settings_id");
//todo: update different settings of filaments
different_settings[filament_index] = "";
//ORCA: was a //todo — same treatment as process/machine above.
different_settings[filament_index] = filament_different_settings;
inherits_group[filament_index] = load_filaments_inherit[index];
}
else {
@@ -3585,6 +3723,15 @@ int CLI::run(int argc, char **argv)
}
}
// A mixed slot never reaches a nozzle, so its row and column stay empty, as in the GUI.
// Command line options are not merged into m_print_config yet, so they win here.
const ConfigOptionBools *is_mixed_opt = m_extra_config.option<ConfigOptionBools>("filament_is_mixed");
if (!is_mixed_opt)
is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed");
auto is_mixed_slot = [is_mixed_opt](int idx) {
return is_mixed_opt && idx < static_cast<int>(is_mixed_opt->values.size()) && is_mixed_opt->values[idx];
};
for (size_t nozzle_id = 0; nozzle_id < new_extruder_count; ++nozzle_id) {
std::vector<double> flush_vol_mtx = get_flush_volumes_matrix(flush_vol_matrix, nozzle_id, new_extruder_count);
for (int from_idx = 0; from_idx < project_filament_count; from_idx++) {
@@ -3594,7 +3741,7 @@ int CLI::run(int argc, char **argv)
bool is_from_support = filament_is_support->get_at(from_idx);
for (int to_idx = 0; to_idx < project_filament_count; to_idx++) {
bool is_to_support = filament_is_support->get_at(to_idx);
if (from_idx == to_idx) {
if (from_idx == to_idx || is_mixed_slot(from_idx) || is_mixed_slot(to_idx)) {
flush_vol_mtx[project_filament_count * from_idx + to_idx] = 0.f;
} else {
int flushing_volume = 0;
@@ -3731,12 +3878,113 @@ int CLI::run(int argc, char **argv)
}
}
//ORCA: settings passed on the command line (--sparse-infill-density 25% ...) override the loaded
// presets right here, so they belong in different_settings_to_system just as a preset
// override does. Without them re-opening the exported project in the GUI shows nothing
// modified and reverts those values to the system presets'.
//
// The keys come from m_config, not m_extra_config: read_cli() puts only what the user typed
// into m_config (setup() adds nothing but CLI-own defaults), whereas the CLI writes its own
// values into m_extra_config. Only keys whose value the override actually changed are
// recorded -- a typed value equal to the loaded one modifies nothing -- and each lands in
// the column(s) whose preset type owns it: [0] process, [1..n-2] filaments, [n-1] printer.
//
// "Changed" is judged the way the value is read: a list is compared entry by entry with a
// missing entry read as the first, as get_at() does -- so --nozzle-temperature 245 against
// 245,245,245 is no change, although the two serialize differently.
//
// A key the loaded config does not carry at all is always recorded, even if the typed value
// equals the built-in default. On reopen the GUI restores an unlisted key from the SYSTEM
// preset, which need not match that default: a 3MF written before an option existed leaves
// it absent here, and --sparse-infill-density 20% (the default) against a Prusa system 15%
// would otherwise go unrecorded and be reverted. Over-recording is cosmetic; under-recording
// loses the value.
std::map<std::string, std::unique_ptr<ConfigOption>> cli_override_before;
for (const std::string &key : m_config.keys()) {
if (!m_extra_config.has(key))
continue;
const ConfigOption *loaded = m_print_config.option(key);
cli_override_before[key].reset(loaded != nullptr ? loaded->clone() : nullptr); // null: always recorded
}
// Apply command line options to a more specific DynamicPrintConfig which provides normalize()
// (command line options override --load files)
m_print_config.apply(m_extra_config, true);
if (!cli_override_before.empty()) {
std::vector<std::string> &columns = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
auto owned_by = [](const std::vector<std::string> &options, const std::string &key) {
return std::find(options.begin(), options.end(), key) != options.end();
};
auto add_to_column = [&columns](size_t index, const std::string &key) {
std::vector<std::string> keys;
Slic3r::unescape_strings_cstyle(columns[index], keys);
if (std::find(keys.begin(), keys.end(), key) == keys.end()) {
keys.push_back(key);
columns[index] = Slic3r::escape_strings_cstyle(keys);
}
};
auto same_value = [](const ConfigOption *a, const ConfigOption *b) {
if (a == nullptr || b == nullptr)
return false;
const auto *va = dynamic_cast<const ConfigOptionVectorBase *>(a);
const auto *vb = dynamic_cast<const ConfigOptionVectorBase *>(b);
if (va == nullptr || vb == nullptr)
return va == vb && a->serialize() == b->serialize();
const std::vector<std::string> ea = va->vserialize(), eb = vb->vserialize();
if (ea.empty() || eb.empty())
return ea.empty() && eb.empty();
for (size_t i = 0; i < std::max(ea.size(), eb.size()); ++i)
if (ea[i < ea.size() ? i : 0] != eb[i < eb.size() ? i : 0])
return false;
return true;
};
//ORCA: always true after the resize to filament_count + 2 above, and nothing in between can
// shrink the column vector -- different_settings_to_system is not a CLI option. Kept as
// a check rather than an assert: release builds compile asserts out, so an assert would
// protect nothing, while a build with _GLIBCXX_ASSERTIONS would abort on columns[0].
if (columns.size() >= 2) {
for (const auto &[key, before] : cli_override_before) {
if (same_value(before.get(), m_print_config.option(key)))
continue;
bool recorded = false;
if (owned_by(Preset::print_options(), key)) {
add_to_column(0, key);
recorded = true;
}
if (owned_by(Preset::filament_options(), key)) {
for (size_t i = 1; i + 1 < columns.size(); ++i)
add_to_column(i, key);
recorded = true;
}
if (owned_by(Preset::printer_options(), key)) {
add_to_column(columns.size() - 1, key);
recorded = true;
}
if (recorded)
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: override %1% recorded in different_settings_to_system") % key;
}
}
}
// Normalizing after importing the 3MFs / AMFs
m_print_config.normalize_fdm();
// A mixed slot is virtual but still needs a filament entry of its own. Without one, feature
// filament ids aimed at it fall outside the filament count, are reset to the first filament
// and the model silently prints in a single colour.
if (const auto *is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed")) {
const auto &is_mixed = is_mixed_opt->values;
for (size_t slot = static_cast<size_t>(std::max(filament_count, 0)); slot < is_mixed.size(); ++slot) {
if (!is_mixed[slot])
continue;
BOOST_LOG_TRIVIAL(error) << boost::format("mixed filament slot %1% has no filament of its own, only %2% filaments are loaded; "
"load one filament per slot, including each mixed one")
% (slot + 1) % filament_count;
record_exit_reson(outfile_dir, CLI_MIXED_FILAMENT_INVALID, 0, cli_errors[CLI_MIXED_FILAMENT_INVALID], sliced_info);
flush_and_exit(CLI_MIXED_FILAMENT_INVALID);
}
}
m_print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value = printer_technology;
bool has_wipe_tower_position = m_print_config.option<ConfigOptionFloats>("wipe_tower_x") && m_print_config.option<ConfigOptionFloats>("wipe_tower_y");
@@ -3791,6 +4039,15 @@ int CLI::run(int argc, char **argv)
bool is_smooth_timelapse = false;
if (enable_timelapse && timelapse_type_opt && (timelapse_type_opt->getInt() == TimelapseType::tlSmooth))
is_smooth_timelapse = true;
// A mixed filament swaps between its components every layer, so it needs the tower even when
// every loaded preset is the same.
if (disable_wipe_tower_after_mapping) {
if (const auto *is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed");
is_mixed_opt && has_any_mixed_filament(is_mixed_opt->values)) {
disable_wipe_tower_after_mapping = false;
BOOST_LOG_TRIVIAL(info) << boost::format("%1%, set disable_wipe_tower_after_mapping back to false due to a mixed filament")%__LINE__;
}
}
if (disable_wipe_tower_after_mapping) {
if (is_smooth_timelapse)
{
@@ -5112,7 +5369,7 @@ int CLI::run(int argc, char **argv)
//skip this object due to be locked in plate
ap.itemid = locked_aps.size();
locked_aps.emplace_back(ap);
boost::nowide::cout <<__FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx;
}
}
}
@@ -5701,7 +5958,11 @@ int CLI::run(int argc, char **argv)
//FIXME check for mixing the FFF / SLA parameters.
// or better save fff_print_config vs. sla_print_config
//m_print_config.save(m_config.opt_string("save"));
m_print_config.save_to_json(m_config.opt_string(opt_key), std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION));
const std::string &settings_file = m_config.opt_string(opt_key);
if (settings_file == "-")
m_print_config.save_to_json(boost::nowide::cout, "project_settings", "project", SoftFever_VERSION, /*replace_invalid_utf8=*/true);
else
m_print_config.save_to_json(settings_file, std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION));
} else if (opt_key == "info") {
// --info works on unrepaired model
for (Model &model : m_models) {
@@ -5997,6 +6258,36 @@ int CLI::run(int argc, char **argv)
flush_and_exit(CLI_ONLY_ONE_TPU_SUPPORTED);
}
// Same type gate as the GUI's Sidebar::has_broken_mixed_filament: refuse a plate that uses a
// mixed slot whose components are different filament types. Missing or out-of-range
// components never get here, validate() already rejects them for the whole project.
const auto *is_mixed_opt = m_print_config.option<ConfigOptionBools>("filament_is_mixed");
const auto *components_opt = m_print_config.option<ConfigOptionStrings>("filament_mixed_components");
if (is_mixed_opt && components_opt && has_any_mixed_filament(is_mixed_opt->values)) {
const auto &is_mixed = is_mixed_opt->values;
const auto &components = components_opt->values;
const size_t num_physical = static_cast<size_t>(filament_count) - static_cast<size_t>(std::count(is_mixed.begin(), is_mixed.end(), true));
std::vector<std::string> physical_types(num_physical);
for (size_t f_index = 0; f_index < num_physical; ++f_index) {
std::string displayed_type;
physical_types[f_index] = m_print_config.get_filament_type(displayed_type, static_cast<int>(f_index));
if (physical_types[f_index].empty())
physical_types[f_index] = "PLA";
}
const std::vector<size_t> mismatched_slots = check_mixed_filament_type_consistency(is_mixed, components, physical_types);
// plate_filaments has mixed slots expanded to their components; the gate needs the slots.
const std::vector<int> plate_slots = mismatched_slots.empty() ? std::vector<int>() :
part_plate->get_extruders_under_cli(true, m_print_config, false);
for (size_t slot : mismatched_slots) {
if (std::find(plate_slots.begin(), plate_slots.end(), static_cast<int>(slot) + 1) == plate_slots.end())
continue;
BOOST_LOG_TRIVIAL(error) << boost::format("plate %1%: mixed filament %2% mixes components of different filament types")
% (index + 1) % (slot + 1);
record_exit_reson(outfile_dir, CLI_MIXED_FILAMENT_INVALID, index + 1, cli_errors[CLI_MIXED_FILAMENT_INVALID], sliced_info);
flush_and_exit(CLI_MIXED_FILAMENT_INVALID);
}
}
if (new_extruder_count > 1) {
std::vector<std::vector<int>> unprintable_filament_vec;
for (const std::set<int>& filamnt_ids : unprintable_filament_ids) {
@@ -7449,6 +7740,13 @@ bool CLI::setup(int argc, char **argv)
this->print_help();
return false;
}
// Orca: resolve here, while the process is still in the directory the user invoked it from.
// GUI_App's constructor moves the working directory to <data_dir>/log, long before the GUI
// opens these files in post_init(), and a relative path would then resolve against that.
for (std::string &input_file : m_input_files)
input_file = resolve_cli_input_path(input_file);
// Parse actions and transform options.
for (auto const &opt_key : opt_order) {
if (cli_actions_config_def.has(opt_key))
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include "stackwalker.h"
#include "StackWalker.h"
#include <eh.h>
class CBaseException : public CStackWalker
+1 -1
View File
@@ -425,7 +425,7 @@ LPSTACKINFO CStackWalker::StackWalker(HANDLE hThread, const CONTEXT* context)
else
c = *context;
STACKFRAME64 sf = {0};
STACKFRAME64 sf = {};
DWORD imageType;
//intel X86
+6
View File
@@ -558,6 +558,12 @@ if (_opts)
target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}")
endif()
if (IS_CLANG_CL)
# CGAL passes /fp:strict /fp:except-. clang-cl reports the second as overriding part of
# the first; the settings cc1 receives are the same ones MSVC produces from that pair.
target_compile_options(libslic3r_cgal PRIVATE -Wno-overriding-option)
endif ()
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} admesh libigl mcut boost_libs)
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
+15 -6
View File
@@ -7,6 +7,7 @@
#include <algorithm>
#include <assert.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <regex>
@@ -1515,6 +1516,19 @@ std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value
//BBS: add json support
void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const
{
// Serialize first: if that throws (invalid UTF-8), the existing file stays untouched.
std::ostringstream ss;
this->save_to_json(ss, name, from, version);
boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc);
c << ss.str();
c.close();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file;
}
void ConfigBase::save_to_json(std::ostream &os, const std::string &name, const std::string &from, const std::string &version, bool replace_invalid_utf8) const
{
json j;
//record the headers
@@ -1561,12 +1575,7 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name,
j["plugins"] = unique_refs;
}
boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc);
c << j.dump(1, '\t') << std::endl;
c.close();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file;
os << j.dump(1, '\t', false, replace_invalid_utf8 ? json::error_handler_t::replace : json::error_handler_t::strict) << std::endl;
}
void ConfigBase::save(const std::string &file) const
+15
View File
@@ -1006,6 +1006,7 @@ public:
int getInt() const override { return this->value; }
void setInt(int val) override { this->value = val; }
ConfigOption* clone() const override { return new ConfigOptionInt(*this); }
using ConfigOptionSingle<int>::operator==;
bool operator==(const ConfigOptionInt &rhs) const throw() { return this->value == rhs.value; }
std::string serialize() const override
@@ -1048,6 +1049,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionIntsTempl(*this); }
ConfigOptionIntsTempl& operator= (const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionVector<int>::operator==;
bool operator==(const ConfigOptionIntsTempl &rhs) const throw() { return this->values == rhs.values; }
bool operator< (const ConfigOptionIntsTempl &rhs) const throw() { return this->values < rhs.values; }
// Could a special "nil" value be stored inside the vector, indicating undefined value?
@@ -1137,6 +1139,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionString(*this); }
ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionSingle<std::string>::operator==;
bool operator==(const ConfigOptionString &rhs) const throw() { return this->value == rhs.value; }
bool operator< (const ConfigOptionString &rhs) const throw() { return this->value < rhs.value; }
bool empty() const { return this->value.empty(); }
@@ -1171,6 +1174,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionStrings(*this); }
ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionVector<std::string>::operator==;
bool operator==(const ConfigOptionStrings &rhs) const throw() { return this->values == rhs.values; }
bool operator< (const ConfigOptionStrings &rhs) const throw() { return this->values < rhs.values; }
bool is_nil(size_t) const override { return false; }
@@ -1215,6 +1219,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPercent(*this); }
ConfigOptionPercent& operator= (const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionFloat::operator==;
bool operator==(const ConfigOptionPercent &rhs) const throw() { return this->value == rhs.value; }
bool operator< (const ConfigOptionPercent &rhs) const throw() { return this->value < rhs.value; }
@@ -1257,6 +1262,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPercentsTempl(*this); }
ConfigOptionPercentsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionFloatsTempl<NULLABLE>::operator==;
bool operator==(const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl<NULLABLE>::vectors_equal(this->values, rhs.values); }
bool operator< (const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl<NULLABLE>::vectors_lower(this->values, rhs.values); }
@@ -1502,6 +1508,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPoint(*this); }
ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionSingle<Vec2d>::operator==;
bool operator==(const ConfigOptionPoint &rhs) const throw() { return this->value == rhs.value; }
bool operator< (const ConfigOptionPoint &rhs) const throw() { return this->value < rhs.value; }
@@ -1539,6 +1546,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPoints(*this); }
ConfigOptionPoints& operator= (const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionVector<Vec2d>::operator==;
bool operator==(const ConfigOptionPoints &rhs) const throw() { return this->values == rhs.values; }
bool operator< (const ConfigOptionPoints &rhs) const throw()
{ return std::lexicographical_compare(this->values.begin(), this->values.end(), rhs.values.begin(), rhs.values.end(), [](const auto &l, const auto &r){ return l < r; }); }
@@ -1617,6 +1625,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionPoint3(*this); }
ConfigOptionPoint3& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionSingle<Vec3d>::operator==;
bool operator==(const ConfigOptionPoint3 &rhs) const throw() { return this->value == rhs.value; }
bool operator< (const ConfigOptionPoint3 &rhs) const throw()
{ return this->value.x() < rhs.value.x() || (this->value.x() == rhs.value.x() && (this->value.y() < rhs.value.y() || (this->value.y() == rhs.value.y() && this->value.z() < rhs.value.z()))); }
@@ -1860,6 +1869,7 @@ public:
bool getBool() const override { return this->value; }
ConfigOption* clone() const override { return new ConfigOptionBool(*this); }
ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionSingle<bool>::operator==;
bool operator==(const ConfigOptionBool &rhs) const throw() { return this->value == rhs.value; }
bool operator< (const ConfigOptionBool &rhs) const throw() { return int(this->value) < int(rhs.value); }
@@ -1911,6 +1921,7 @@ public:
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionBoolsTempl(*this); }
ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
using ConfigOptionVector<unsigned char>::operator==;
bool operator==(const ConfigOptionBoolsTempl &rhs) const throw() { return this->values == rhs.values; }
bool operator< (const ConfigOptionBoolsTempl &rhs) const throw() { return this->values < rhs.values; }
// Could a special "nil" value be stored inside the vector, indicating undefined value?
@@ -2163,6 +2174,7 @@ public:
ConfigOptionEnumsGenericTempl& operator= (const ConfigOption* opt) { this->set(opt); return *this; }
bool operator< (const ConfigOptionInts& rhs) const throw() { return this->values < rhs.values; }
using ConfigOptionInts::operator==;
bool operator==(const ConfigOptionInts& rhs) const
{
if (rhs.type() != this->type())
@@ -2813,6 +2825,9 @@ public:
//BBS: add json support
void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const;
// Same document, written to a stream. Invalid UTF-8 in a string value throws nlohmann's type_error unless
// replace_invalid_utf8 is set, which writes U+FFFD instead (for callers such as stdout with no handler).
void save_to_json(std::ostream &os, const std::string &name, const std::string &from, const std::string &version, bool replace_invalid_utf8 = false) const;
// Rebuild the in-memory "plugins" manifest (the "name;uuid;capability" references the plugin
// dispatchers consume) from the plugin-backed options via the registered resolver. save_to_json()
+5 -5
View File
@@ -968,10 +968,10 @@ EmbossStyles Emboss::get_font_list_by_register() {
}
// TODO: Fix global function
bool CALLBACK EnumFamCallBack(LPLOGFONT lplf,
LPNEWTEXTMETRIC lpntm,
DWORD FontType,
LPVOID aFontList)
int CALLBACK EnumFamCallBack(const LOGFONT *lplf,
const TEXTMETRIC *lpntm,
DWORD FontType,
LPARAM aFontList)
{
std::vector<std::wstring> *fontList =
(std::vector<std::wstring> *) (aFontList);
@@ -988,7 +988,7 @@ EmbossStyles Emboss::get_font_list_by_enumeration() {
HDC hDC = GetDC(NULL);
std::vector<std::wstring> font_names;
EnumFontFamilies(hDC, (LPCTSTR) NULL, (FONTENUMPROC) EnumFamCallBack,
EnumFontFamilies(hDC, (LPCTSTR) NULL, EnumFamCallBack,
(LPARAM) &font_names);
EmbossStyles font_list;
+59 -52
View File
@@ -11,7 +11,7 @@
#include "AABBTreeLines.hpp"
#include "ExtrusionEntity.hpp"
#include "FillBase.hpp"
#include "Fill.hpp"
#include "FillRectilinear.hpp"
#include "FillLightning.hpp"
#include "FillConcentricInternal.hpp"
@@ -1234,6 +1234,33 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
return surface_fills;
}
// Orca: Anchors and printed infill must share the same body origin. Keep the choice
// here so per-model surface centering and separated sparse infill cannot drift apart.
static BoundingBox infill_bounding_box(const Layer &layer, const SurfaceFill &fill, const ExPolygon &expoly, BoundingBox bbox)
{
const auto &params = fill.params;
const auto &config = layer.regions()[fill.region_id]->region().config();
const bool external = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
const bool per_model = external && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model &&
(params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral);
const bool separate = !external && params.separated_infills &&
(is_separable_infill_pattern(params.pattern) || !config.solid_infill_rotate_template.value.empty() ||
!config.sparse_infill_rotate_template.value.empty());
if (per_model || separate) {
double best_overlap = 0.;
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_bboxes.size(); ++i) {
const double overlap = area(intersection_ex(layer.lslices[i], expoly));
if (overlap > best_overlap) {
best_overlap = overlap;
const Point center = layer.lslices_separated_component_bboxes[i].center();
bbox = layer.object()->bounding_box();
bbox.translate(center.x(), center.y());
}
}
}
return bbox;
}
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill> &fills)
{
@@ -1353,19 +1380,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// Orca: Checking the filling of a centered surface by drawing for each model parts
bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral;
if (is_top_or_bottom) {
params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern
}
// Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly
// fall through to the default (whole-object) bounding box below.
bool is_per_model_center = is_top_or_bottom && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model && is_centered_infill;
bool is_separate_infill = !is_top_or_bottom && surface_fill.params.separated_infills &&
(
is_separable_infill_pattern(surface_fill.params.pattern) ||
params.config->solid_infill_rotate_template != "" ||
params.config->sparse_infill_rotate_template != "" );
if( surface_fill.params.pattern == ipLockedZag ) {
params.locked_zag = true;
params.infill_lock_depth = surface_fill.params.infill_lock_depth;
@@ -1389,34 +1406,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.can_reverse = false;
for (ExPolygon& expoly : surface_fill.expolygons) {
// Orca: separate infill / per-model pattern centering.
//
// Center the pattern on each connected body of the object independently, so every piece
// is filled exactly as if it were sliced on its own: touching/overlapping parts merge
// into one body sharing a center, while separate parts and disconnected islands (even
// interleaved-but-not-touching ones, e.g. chain links) each get their own. The body each
// island belongs to, and its full bounding box, were resolved in 3D by PrintObject::
// infill() (lslices_separated_component_bboxes, aligned with this layer's lslices). We
// match this fill region to the island it overlaps most, then re-use the whole-object
// bounding box (origin-centered — identical extent to the default, so coverage and cost
// are unchanged) re-centered on that body.
if (is_per_model_center || is_separate_infill) {
double best_overlap = 0.;
BoundingBox best_component;
for (size_t r = 0; r < this->lslices.size() && r < this->lslices_separated_component_bboxes.size(); ++ r) {
const double overlap = area(intersection_ex(this->lslices[r], expoly));
if (overlap > best_overlap) {
best_overlap = overlap;
best_component = this->lslices_separated_component_bboxes[r];
}
}
if (best_component.defined) {
const Point c = best_component.center();
BoundingBox part_bbox = bbox; // origin-centered, whole-object extent (from above)
part_bbox.translate(c.x(), c.y()); // re-center on this body
f->set_bounding_box(part_bbox);
}
} // - End: separate infill / per-model pattern centering
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
if (params.symmetric_infill_y_axis) {
@@ -1583,8 +1574,14 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
params.multiline = surface_fill.params.multiline;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.smooth_factor = surface_fill.params.smooth_factor;
// Orca: Match make_fills() when choosing the origin of plane-path patterns.
// Without the sparse extrusion role, the filler uses each surface's bounds
// instead of the object's bounds, so bridge anchors shift away from printed infill.
params.extrusion_role = surface_fill.params.extrusion_role;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Orca: Match the per-body origin of make_fills() before generating physical anchors.
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing;
surface_fill.surface.expolygon = std::move(expoly);
@@ -1598,6 +1595,25 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
return sparse_infill_polylines;
}
// Returns the filament id (1-based) the region is ironed with, or -1 when the
// region is not ironed. AllSolid always irons. TopSurfaces and TopmostOnly need
// either some top shells or, in spiral mode, more than one bottom shell, and
// TopmostOnly additionally needs the layer to be the topmost one.
int Layer::choose_ironing_extruder(const PrintRegionConfig &cfg,
bool spiral_mode,
bool is_topmost_layer)
{
if (cfg.ironing_type == IroningType::NoIroning)
return -1;
const bool gate = (cfg.ironing_type == IroningType::AllSolid)
|| ((cfg.top_shell_layers > 0 || (spiral_mode && cfg.bottom_shell_layers > 1))
&& (cfg.ironing_type == IroningType::TopSurfaces
|| (cfg.ironing_type == IroningType::TopmostOnly && is_topmost_layer)));
if (!gate)
return -1;
return cfg.top_surface_filament_id;
}
// Create ironing extrusions over top surfaces.
void Layer::make_ironing()
{
@@ -1667,19 +1683,10 @@ void Layer::make_ironing()
if (! layerm->slices.empty()) {
IroningParams ironing_params;
const PrintRegionConfig &config = layerm->region().config();
if (config.ironing_type != IroningType::NoIroning &&
(config.ironing_type == IroningType::AllSolid ||
((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) &&
(config.ironing_type == IroningType::TopSurfaces ||
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) {
// Iron the whole face.
ironing_params.extruder = config.top_surface_filament_id;
} else {
// Iron just the infill.
ironing_params.extruder = config.top_surface_filament_id;
}
}
ironing_params.extruder = Layer::choose_ironing_extruder(
config,
/*spiral_mode=*/this->object()->print()->config().spiral_mode,
/*is_topmost_layer=*/layerm->layer()->upper_layer == nullptr);
if (ironing_params.extruder != -1) {
//TODO just_infill is currently not used.
ironing_params.just_infill = false;
+6
View File
@@ -14,6 +14,12 @@ namespace Slic3r {
class ExtrusionEntityCollection;
class LayerRegion;
class PrintObject;
// Orca: Share the layer rotation calculation between infill generation and internal
// bridge angle selection so both interpret rotation templates in the same way.
double calculate_infill_rotation_angle(const PrintObject *object, size_t layer_id,
const double &fixed_infill_angle, const std::string &template_string);
// An interface class to Perl, aggregating an instance of a Fill and a FillData.
class Filler
+2 -2
View File
@@ -1395,8 +1395,8 @@ void Filler::_fill_surface_single(
}
#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
const auto hook_length = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length)));
const auto hook_length_max = coordf_t(std::min<float>(std::numeric_limits<coord_t>::max(), scale_(params.anchor_length_max)));
const auto hook_length = coordf_t(scale_(params.anchor_length));
const auto hook_length_max = coordf_t(scale_(params.anchor_length_max));
Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length, hook_length_max) : std::move(all_polylines);
+4 -4
View File
@@ -8844,7 +8844,7 @@ public:
auto model = object.get_model();
auto o = m_temp_model.add_object(object);
int backup_id = model->get_object_backup_id(object);
push_task({ AddObject, (size_t) backup_id, object.get_model()->get_backup_path(), o, 1 });
push_task({ AddObject, (size_t) backup_id, object.get_model()->get_backup_path(), o, { 1 } });
}
void remove_object_mesh(ModelObject& object) {
@@ -8854,7 +8854,7 @@ public:
void backup_soon() {
boost::lock_guard lock(m_mutex);
m_other_changes_backup = true;
m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq });
m_tasks.push_back({ Backup, 0, std::string(), nullptr, { ++m_task_seq } });
m_cond.notify_all();
}
@@ -8872,7 +8872,7 @@ public:
m_ui_tasks.clear();
m_tasks.clear();
}
m_tasks.push_back({ RemoveBackup, model.id().id, model.get_backup_path(), nullptr, removeAll });
m_tasks.push_back({ RemoveBackup, model.id().id, model.get_backup_path(), nullptr, { removeAll } });
++m_task_seq;
if (model.is_need_backup()) {
m_other_changes = false;
@@ -9087,7 +9087,7 @@ public:
else
m_cond.wait(lock);
if (m_interval > 0 && boost::get_system_time() > m_next_backup) {
m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq });
m_tasks.push_back({ Backup, 0, std::string(), nullptr, { ++m_task_seq } });
m_next_backup += boost::posix_time::seconds(m_interval);
// Maybe wakeup from power sleep
if (m_next_backup < boost::get_system_time())
+7 -2
View File
@@ -6308,8 +6308,13 @@ LayerResult GCode::process_layer(
all_label_ids.insert(inst.label_object_id);
break;
}
std::vector<size_t> filament_instances_id(all_label_ids.begin(), all_label_ids.end());
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
// Orca: A scheduled extruder may have no object instances on this layer.
// Clear any pending mask so it cannot be emitted for the wrong toolchange.
m_filament_instances_code.clear();
if (!all_label_ids.empty()) {
std::vector<size_t> filament_instances_id(all_label_ids.begin(), all_label_ids.end());
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
}
}
// The inline _extrude hook may already have taken the snapshot mid-extrusion on a
+1 -1
View File
@@ -910,7 +910,7 @@ namespace Slic3r
unsigned int iterations = (1 << all_extruders.size());
unsigned int final_state = iterations - 1;
std::vector<std::vector<float>>cache(iterations, std::vector<float>(all_extruders.size(), 0x7fffffff));
std::vector<std::vector<float>>cache(iterations, std::vector<float>(all_extruders.size(), std::numeric_limits<float>::max()));
std::vector<std::vector<int>>prev(iterations, std::vector<int>(all_extruders.size(), -1));
cache[1][0] = 0.;
for (unsigned int state = 0; state < iterations; ++state) {
+15 -7
View File
@@ -187,6 +187,12 @@ void Layer::make_perimeters()
{
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id();
const auto clear_generated_extrusions = [](LayerRegion *layer_region) {
layer_region->perimeters.clear();
layer_region->fills.clear();
layer_region->thin_fills.clear();
};
// keep track of regions whose perimeters we have already generated
std::vector<unsigned char> done(m_regions.size(), false);
@@ -217,13 +223,11 @@ void Layer::make_perimeters()
if (this_region.gradient_volume_id() != other_region.gradient_volume_id())
continue;
if (is_perimeter_compatible(*m_object->print(), this_region, other_region))
{
other_layerm->perimeters.clear();
other_layerm->fills.clear();
other_layerm->thin_fills.clear();
layerms.push_back(other_layerm);
done[it - m_regions.begin()] = true;
}
{
clear_generated_extrusions(other_layerm);
layerms.push_back(other_layerm);
done[it - m_regions.begin()] = true;
}
}
if (layerms.size() == 1) { // optimization
@@ -231,6 +235,10 @@ void Layer::make_perimeters()
(*layerm)->make_perimeters((*layerm)->slices, {*layerm}, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons);
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
} else {
// Orca: Unlike the compatible regions above, the initiating region has not
// been cleared yet and may contain paths from a previous incompatible run.
clear_generated_extrusions(*layerm);
SurfaceCollection new_slices;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
LayerRegion *layerm_config = layerms.front();
+6
View File
@@ -16,6 +16,7 @@ using LayerPtrs = std::vector<Layer*>;
class LayerRegion;
using LayerRegionPtrs = std::vector<LayerRegion*>;
class PrintRegion;
class PrintRegionConfig;
class PrintObject;
class Print;
@@ -200,6 +201,11 @@ public:
FillAdaptive::Octree *support_fill_octree,
FillLightning::Generator* lightning_generator) const;
void make_ironing();
// Returns the filament id (1-based) the region is ironed with, or -1 when the
// region is not ironed.
static int choose_ironing_extruder(const PrintRegionConfig &cfg,
bool spiral_mode,
bool is_topmost_layer);
void make_contour_z(const sla::IndexedMesh &mesh);
void export_region_slices_to_svg(const char *path) const;
+2 -2
View File
@@ -30,8 +30,8 @@ bool Line::intersection_infinite(const Line &other, Point* point) const
return false;
double t1 = cross2(v12, v2) / denom;
Vec2d result = (a1 + t1 * v1);
if (result.x() > std::numeric_limits<coord_t>::max() || result.x() < std::numeric_limits<coord_t>::lowest() ||
result.y() > std::numeric_limits<coord_t>::max() || result.y() < std::numeric_limits<coord_t>::lowest()) {
if (result.x() > double(std::numeric_limits<coord_t>::max()) || result.x() < double(std::numeric_limits<coord_t>::lowest()) ||
result.y() > double(std::numeric_limits<coord_t>::max()) || result.y() < double(std::numeric_limits<coord_t>::lowest())) {
// Intersection has at least one of the coordinates much bigger (or smaller) than coord_t maximum value (or minimum).
// So it can not be stored into the Point without integer overflows. That could mean that input lines are parallel or near parallel.
return false;
+2
View File
@@ -3,6 +3,8 @@
#ifdef _WIN32
#include <charconv>
#endif
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <fast_float/fast_float.h>
+14
View File
@@ -867,6 +867,20 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre
return is_compatible_with_printer(preset, active_printer, &config);
}
// ORCA: see the header. The CLI resolves --load-settings into bare DynamicPrintConfigs and has no
// Preset objects to hand; without this it would have to reimplement the policy or build the shells
// at every call site.
bool is_compatible_with_printer(const DynamicPrintConfig &preset_config, Preset::Type preset_type,
const DynamicPrintConfig &printer_config, const std::string &printer_name)
{
Preset preset(preset_type, std::string("__compat_check"));
preset.config = preset_config;
Preset printer(Preset::TYPE_PRINTER, printer_name);
printer.config = printer_config;
return is_compatible_with_printer(PresetWithVendorProfile(preset, nullptr),
PresetWithVendorProfile(printer, nullptr));
}
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
{
//BBS: add config related log
+5
View File
@@ -459,6 +459,11 @@ protected:
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer);
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
// ORCA: same check for callers that hold raw configs rather than Presets (the CLI). Wraps them in
// throwaway Preset shells and delegates, so the compatibility policy -- including the fail-open on a
// malformed compatible_printers_condition -- lives in one place for the GUI and the CLI alike.
bool is_compatible_with_printer(const DynamicPrintConfig &preset_config, Preset::Type preset_type,
const DynamicPrintConfig &printer_config, const std::string &printer_name);
// Where a preset is being loaded from. `Auto` lets load_presets() infer from the directory path.
struct PresetOrigin {
+44 -32
View File
@@ -484,7 +484,7 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
auto collection_for_type = [](PresetBundle &bundle, Preset::Type preset_type) -> PresetCollection * {
auto collection_for_type = [](const PresetBundle &bundle, Preset::Type preset_type) -> const PresetCollection * {
switch (preset_type) {
case Preset::TYPE_PRINT: return &bundle.prints;
case Preset::TYPE_FILAMENT: return &bundle.filaments;
@@ -493,15 +493,15 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
}
};
PresetCollection *collection = collection_for_type(*this, type);
const PresetCollection *collection = collection_for_type(*this, type);
if (collection == nullptr) {
error = "Unsupported preset type";
return false;
}
const boost::filesystem::path source_path = boost::filesystem::absolute(source_file).lexically_normal();
auto find_loaded = [&](PresetBundle &bundle) -> const Preset * {
PresetCollection *loaded_collection = collection_for_type(bundle, type);
auto find_loaded = [&](const PresetBundle &bundle) -> const Preset * {
const PresetCollection *loaded_collection = collection_for_type(bundle, type);
const Preset *resolved = nullptr;
for (const Preset &preset : loaded_collection->get_presets()) {
if (preset.file.empty())
@@ -549,30 +549,11 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
continue;
try {
PresetBundle library_bundle;
const PresetBundle *base_bundle = nullptr;
if (vendor_id != ORCA_FILAMENT_LIBRARY &&
boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) {
library_bundle.m_preserve_vendor_source_paths = true;
library_bundle.load_vendor_configs_from_json(root_dir.string(), ORCA_FILAMENT_LIBRARY, LoadSystem,
compatibility_rule, nullptr, false);
if (library_bundle.error_count() != 0) {
error = "OrcaFilamentLibrary contains invalid presets";
return false;
}
base_bundle = &library_bundle;
}
PresetBundle source_bundle;
source_bundle.m_preserve_vendor_source_paths = true;
source_bundle.load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem,
compatibility_rule, base_bundle, false);
if (source_bundle.error_count() != 0) {
error = "Vendor bundle contains invalid presets";
const PresetBundle *loaded = load_source_vendor(root_dir, vendor_id, compatibility_rule, error);
if (loaded == nullptr)
return false;
}
const Preset *resolved = find_loaded(source_bundle);
const Preset *resolved = find_loaded(*loaded);
if (resolved == nullptr) {
if (error.empty())
error = "Source file is not an instantiated preset in its vendor manifest";
@@ -591,6 +572,37 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
return false;
}
const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir,
const std::string &vendor_id,
ForwardCompatibilitySubstitutionRule compatibility_rule,
std::string &error)
{
auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule);
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;
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);
if (library == nullptr) {
error = "OrcaFilamentLibrary contains invalid presets";
return nullptr;
}
}
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);
if (bundle->error_count() != 0) {
error = "Vendor bundle contains invalid presets";
return nullptr;
}
return m_source_vendor_bundles.emplace(std::move(key), std::move(bundle)).first->second.get();
}
bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type,
const std::string &source_file,
ForwardCompatibilitySubstitutionRule compatibility_rule,
@@ -4981,7 +4993,7 @@ static void apply_mixed_config_relocations(DynamicPrintConfig&
case coBools: {
auto* live = static_cast<ConfigOptionBools*>(opt);
const auto* frozen = static_cast<const ConfigOptionBools*>(snapshot.get());
for (const auto [from, to] : moves) {
for (const auto& [from, to] : moves) {
const unsigned char cell = from < frozen->values.size() ? frozen->values[from] : 0;
if (live->values.size() <= to)
live->values.resize(to + 1, 0);
@@ -4992,7 +5004,7 @@ static void apply_mixed_config_relocations(DynamicPrintConfig&
case coStrings: {
auto* live = static_cast<ConfigOptionStrings*>(opt);
const auto* frozen = static_cast<const ConfigOptionStrings*>(snapshot.get());
for (const auto [from, to] : moves) {
for (const auto& [from, to] : moves) {
const std::string cell = from < frozen->values.size() ? frozen->values[from] : std::string();
if (live->values.size() <= to)
live->values.resize(to + 1, std::string{});
@@ -5028,7 +5040,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig&
auto* live = static_cast<ConfigOptionBools*>(opt);
std::unique_ptr<ConfigOption> snapshot(opt->clone());
const auto* frozen = static_cast<const ConfigOptionBools*>(snapshot.get());
for (const auto [from, to] : moves) {
for (const auto& [from, to] : moves) {
const bool cell = from < frozen->values.size() ? frozen->values[from] : false;
if (live->values.size() <= to)
live->values.resize(to + 1, false);
@@ -5044,7 +5056,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig&
auto* live = static_cast<ConfigOptionStrings*>(opt);
std::unique_ptr<ConfigOption> snapshot(opt->clone());
const auto* frozen = static_cast<const ConfigOptionStrings*>(snapshot.get());
for (const auto [from, to] : moves) {
for (const auto& [from, to] : moves) {
const std::string cell = from < frozen->values.size() ? frozen->values[from] : std::string();
if (live->values.size() <= to)
live->values.resize(to + 1, std::string{});
@@ -5060,7 +5072,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig&
auto* live = static_cast<ConfigOptionInts*>(opt);
std::unique_ptr<ConfigOption> snapshot(opt->clone());
const auto* frozen = static_cast<const ConfigOptionInts*>(snapshot.get());
for (const auto [from, to] : moves) {
for (const auto& [from, to] : moves) {
const int cell = from < frozen->values.size() ? frozen->values[from] : 0;
if (live->values.size() <= to)
live->values.resize(to + 1, 0);
@@ -5087,7 +5099,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig&
move_ints("filament_volume_map");
{
const std::vector<std::vector<std::string>> frozen = ams_multi_color_filment;
for (const auto [from, to] : moves) {
for (const auto& [from, to] : moves) {
const std::vector<std::string> cell = from < frozen.size() ? frozen[from] : std::vector<std::string>();
if (ams_multi_color_filment.size() <= to)
ams_multi_color_filment.resize(to + 1, std::vector<std::string>{});
+12
View File
@@ -11,6 +11,7 @@
#include <map>
#include <set>
#include <shared_mutex>
#include <tuple>
#include <unordered_map>
#include <optional>
#include <array>
@@ -652,6 +653,17 @@ private:
bool m_generate_vendor_caches { false };
bool m_preserve_vendor_source_paths { false };
// 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>>
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);
// Orca: validation only - flag any printer with two or more compatible
// filament presets sharing one filament_id (ambiguous AMS subtype match).
bool check_duplicate_filament_subtypes() const;
+41 -178
View File
@@ -5430,7 +5430,7 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->readonly = false;
def->nullable = true;
def->set_default_value(new ConfigOptionFloatsNullable { {0.0} });
def->set_default_value(new ConfigOptionFloatsNullable { 0.0 });
def = this->add("cooling_tube_retraction", coFloat);
def->label = L("Cooling tube position");
@@ -10936,6 +10936,28 @@ std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicP
return variant_index;
}
// Regathers a vector option's values through per-slot source indices (one input index per
// output slot). Out-of-range indices keep the first value, matching get_at's fallback.
template<typename OptType, typename ValueType>
static void gather_option_values(const char *caller, const std::string &key, OptType *opt, const std::vector<int> &slot_param_indices)
{
if (!opt || opt->values.empty()) {
BOOST_LOG_TRIVIAL(warning) << caller << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key;
return;
}
std::vector<ValueType> new_values;
new_values.reserve(slot_param_indices.size());
for (int idx : slot_param_indices) {
if (idx < 0 || static_cast<size_t>(idx) >= opt->values.size()) {
BOOST_LOG_TRIVIAL(warning) << caller << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx;
new_values.emplace_back(opt->values.front());
}
else
new_values.emplace_back(opt->values[idx]);
}
opt->values = std::move(new_values);
}
void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set<std::string>& key_set, std::string id_name, std::string variant_name)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count;
@@ -11013,155 +11035,18 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key;
continue;
}
// An empty option has no first value to fall back on; give it one registered default per filament.
if (auto *vec = dynamic_cast<ConfigOptionVectorBase*>(this->option(key)); vec && vec->empty() && optdef->default_value)
vec->resize(filament_count, optdef->default_value.get());
switch (optdef->type) {
case coStrings:
{
ConfigOptionStrings * opt = this->option<ConfigOptionStrings>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<std::string> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coInts:
{
ConfigOptionInts * opt = this->option<ConfigOptionInts>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<int> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coFloats:
{
ConfigOptionFloats * opt = this->option<ConfigOptionFloats>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<double> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coPercents:
{
ConfigOptionPercents * opt = this->option<ConfigOptionPercents>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<double> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coFloatsOrPercents:
{
ConfigOptionFloatsOrPercents * opt = this->option<ConfigOptionFloatsOrPercents>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<FloatOrPercent> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coBools:
{
ConfigOptionBools * opt = this->option<ConfigOptionBools>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<unsigned char> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coEnums:
{
ConfigOptionEnumsGeneric * opt = this->option<ConfigOptionEnumsGeneric>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<int> new_values;
new_values.resize(filament_count);
for (int f_index = 0; f_index < filament_count; f_index++)
{
if (variant_index[f_index] < 0 || static_cast<size_t>(variant_index[f_index]) >= opt->size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index];
continue;
}
new_values[f_index] = opt->get_at(variant_index[f_index]);
}
opt->values = new_values;
break;
}
case coStrings: gather_option_values<ConfigOptionStrings, std::string>(__FUNCTION__, key, this->option<ConfigOptionStrings>(key), variant_index); break;
case coInts: gather_option_values<ConfigOptionInts, int>(__FUNCTION__, key, this->option<ConfigOptionInts>(key), variant_index); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(__FUNCTION__, key, this->option<ConfigOptionFloats>(key), variant_index); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(__FUNCTION__, key, this->option<ConfigOptionPercents>(key), variant_index); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(__FUNCTION__, key, this->option<ConfigOptionFloatsOrPercents>(key), variant_index); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(__FUNCTION__, key, this->option<ConfigOptionBools>(key), variant_index); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(__FUNCTION__, key, this->option<ConfigOptionEnumsGeneric>(key), variant_index); break;
default:
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key;
break;
@@ -11180,28 +11065,6 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
}
}
// Regathers a vector option's values through per-slot source indices (one input index per
// output slot). Out-of-range indices keep the first value, matching get_at's fallback.
template<typename OptType, typename ValueType>
static void gather_option_values(const std::string &key, OptType *opt, const std::vector<int> &slot_param_indices)
{
if (!opt || opt->values.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key;
return;
}
std::vector<ValueType> new_values;
new_values.reserve(slot_param_indices.size());
for (int idx : slot_param_indices) {
if (idx < 0 || static_cast<size_t>(idx) >= opt->values.size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx;
new_values.emplace_back(opt->values.front());
}
else
new_values.emplace_back(opt->values[idx]);
}
opt->values = std::move(new_values);
}
void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
int extruder_count, int extruder_nozzle_volume_count,
@@ -11296,13 +11159,13 @@ void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(Dy
continue;
}
switch (optdef->type) {
case coStrings: gather_option_values<ConfigOptionStrings, std::string>(key, this->option<ConfigOptionStrings>(key), slot_param_indices); break;
case coInts: gather_option_values<ConfigOptionInts, int>(key, this->option<ConfigOptionInts>(key), slot_param_indices); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(key, this->option<ConfigOptionFloats>(key), slot_param_indices); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(key, this->option<ConfigOptionPercents>(key), slot_param_indices); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(key, this->option<ConfigOptionFloatsOrPercents>(key), slot_param_indices); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(key, this->option<ConfigOptionBools>(key), slot_param_indices); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(key, this->option<ConfigOptionEnumsGeneric>(key), slot_param_indices); break;
case coStrings: gather_option_values<ConfigOptionStrings, std::string>(__FUNCTION__, key, this->option<ConfigOptionStrings>(key), slot_param_indices); break;
case coInts: gather_option_values<ConfigOptionInts, int>(__FUNCTION__, key, this->option<ConfigOptionInts>(key), slot_param_indices); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(__FUNCTION__, key, this->option<ConfigOptionFloats>(key), slot_param_indices); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(__FUNCTION__, key, this->option<ConfigOptionPercents>(key), slot_param_indices); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(__FUNCTION__, key, this->option<ConfigOptionFloatsOrPercents>(key), slot_param_indices); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(__FUNCTION__, key, this->option<ConfigOptionBools>(key), slot_param_indices); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(__FUNCTION__, key, this->option<ConfigOptionEnumsGeneric>(key), slot_param_indices); break;
default:
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key;
break;
@@ -12053,7 +11916,7 @@ CLIActionsConfigDef::CLIActionsConfigDef()
def = this->add("export_settings", coString);
def->label = L("Export Settings");
def->tooltip = L("This exports settings to a file.");
def->tooltip = L("This exports settings to a file. Use - to write them to stdout.");
def->cli_params = "settings.json";
def->set_default_value(new ConfigOptionString("output.json"));
+43 -41
View File
@@ -1011,41 +1011,46 @@ public: \
{ PrintConfigDef::handle_legacy(opt_key, value); }
#define PRINT_CONFIG_CLASS_ELEMENT_DEFINITION(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem);
#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(KEY) cache.opt_add(BOOST_PP_STRINGIZE(KEY), base_ptr, this->KEY);
#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION(r, data, elem) PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(BOOST_PP_TUPLE_ELEM(1, elem))
#define PRINT_CONFIG_CLASS_ELEMENT_HASH(r, data, elem) boost::hash_combine(seed, BOOST_PP_TUPLE_ELEM(1, elem).hash());
#define PRINT_CONFIG_CLASS_ELEMENT_EQUAL(r, data, elem) if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false;
#define PRINT_CONFIG_CLASS_ELEMENT_LOWER(r, data, elem) \
if (BOOST_PP_TUPLE_ELEM(1, elem) < rhs.BOOST_PP_TUPLE_ELEM(1, elem)) return true; \
if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false;
#define PRINT_CONFIG_CLASS_ELEMENT_VISIT(r, data, elem) if (! f(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(1, elem)), this->BOOST_PP_TUPLE_ELEM(1, elem), rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return;
// Each option list is expanded into the members and again into for_each_option_pair(), which calls
// f(key, this->option, rhs.option) in declaration order and stops when f returns false. hash(),
// operator==, operator< and initialize() iterate the options through that visitor.
#define PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \
size_t hash() const throw() \
{ \
size_t seed = 0; \
this->for_each_option_pair(*this, [&seed](const char*, const auto &a, const auto&) { boost::hash_combine(seed, a.hash()); return true; }); \
return seed; \
} \
bool operator==(const CLASS_NAME &rhs) const throw() \
{ \
bool eq = true; \
this->for_each_option_pair(rhs, [&eq](const char*, const auto &a, const auto &b) { eq = (a == b); return eq; }); \
return eq; \
} \
bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \
bool operator<(const CLASS_NAME &rhs) const throw() \
{ \
int c = 0; \
this->for_each_option_pair(rhs, [&c](const char*, const auto &a, const auto &b) { if (a < b) c = -1; else if (! (a == b)) c = 1; return c == 0; }); \
return c < 0; \
} \
protected: \
void initialize(StaticCacheBase &cache, const char *base_ptr) \
{ \
this->for_each_option_pair(*this, [&cache, base_ptr](const char *key, const auto &a, const auto&) { cache.opt_add(key, base_ptr, a); return true; }); \
}
#define PRINT_CONFIG_CLASS_DEFINE(CLASS_NAME, PARAMETER_DEFINITION_SEQ) \
class CLASS_NAME : public StaticPrintConfig { \
STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \
public: \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \
size_t hash() const throw() \
template<typename F> void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const \
{ \
size_t seed = 0; \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ) \
return seed; \
} \
bool operator==(const CLASS_NAME &rhs) const throw() \
{ \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ) \
return true; \
} \
bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \
bool operator<(const CLASS_NAME &rhs) const throw() \
{ \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_LOWER, _, PARAMETER_DEFINITION_SEQ) \
return false; \
} \
protected: \
void initialize(StaticCacheBase &cache, const char *base_ptr) \
{ \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ) \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT, _, PARAMETER_DEFINITION_SEQ) \
} \
PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \
};
#define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM(r, data, i, elem) BOOST_PP_COMMA_IF(i) public elem
@@ -1059,43 +1064,43 @@ protected: \
if (! (*static_cast<const elem*>(this) == static_cast<const elem&>(rhs))) return false;
// Generic version, with or without new parameters. Don't use this directly.
#define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_REGISTRATION, PARAMETER_HASHES, PARAMETER_EQUALS) \
#define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_VISIT) \
class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) { \
STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \
CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) { assert(s_cache_##CLASS_NAME.initialized()); *this = s_cache_##CLASS_NAME.defaults(); } \
public: \
PARAMETER_DEFINITION \
template<typename F> void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const { PARAMETER_VISIT } \
size_t hash() const throw() \
{ \
size_t seed = 0; \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_HASH, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \
PARAMETER_HASHES \
this->for_each_option_pair(*this, [&seed](const char*, const auto &a, const auto&) { boost::hash_combine(seed, a.hash()); return true; }); \
return seed; \
} \
bool operator==(const CLASS_NAME &rhs) const throw() \
{ \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_EQUAL, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \
PARAMETER_EQUALS \
return true; \
bool eq = true; \
this->for_each_option_pair(rhs, [&eq](const char*, const auto &a, const auto &b) { eq = (a == b); return eq; }); \
return eq; \
} \
bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \
protected: \
CLASS_NAME(int) : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 1) {} \
void initialize(StaticCacheBase &cache, const char* base_ptr) { \
PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) \
PARAMETER_REGISTRATION \
this->for_each_option_pair(*this, [&cache, base_ptr](const char *key, const auto &a, const auto&) { cache.opt_add(key, base_ptr, a); return true; }); \
} \
};
// Variant without adding new parameters.
#define PRINT_CONFIG_CLASS_DERIVED_DEFINE0(CLASS_NAME, CLASSES_PARENTS_TUPLE) \
PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY())
PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, BOOST_PP_EMPTY(), BOOST_PP_EMPTY())
// Variant with adding new parameters.
#define PRINT_CONFIG_CLASS_DERIVED_DEFINE(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION_SEQ) \
PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ), \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ), \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ), \
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ))
BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT, _, PARAMETER_DEFINITION_SEQ))
// This object is mapped to Perl as Slic3r::Config::PrintObject.
PRINT_CONFIG_CLASS_DEFINE(
@@ -2148,11 +2153,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0(
#undef STATIC_PRINT_CONFIG_CACHE_BASE
#undef STATIC_PRINT_CONFIG_CACHE_DERIVED
#undef PRINT_CONFIG_CLASS_ELEMENT_DEFINITION
#undef PRINT_CONFIG_CLASS_ELEMENT_EQUAL
#undef PRINT_CONFIG_CLASS_ELEMENT_LOWER
#undef PRINT_CONFIG_CLASS_ELEMENT_HASH
#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION
#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2
#undef PRINT_CONFIG_CLASS_ELEMENT_VISIT
#undef PRINT_CONFIG_CLASS_COMMON_BODY
#undef PRINT_CONFIG_CLASS_DEFINE
#undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST
#undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM
+192 -126
View File
@@ -21,9 +21,11 @@
#include "TriangleMeshSlicer.hpp"
#include "Utils.hpp"
#include "Fill/FillAdaptive.hpp"
#include "Fill/Fill.hpp"
#include "Fill/FillLightning.hpp"
#include "Format/STL.hpp"
#include "format.hpp"
#include "AABBTreeIndirect.hpp"
#include "AABBTreeLines.hpp"
#include <cstddef>
@@ -672,6 +674,98 @@ void PrintObject::prepare_infill()
} // for each region
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Orca: precompute the object's 3D connected bodies for separated infills / per-model
// centering. Two islands belong to the same body when their slices overlap on adjacent
// layers; islands that only overlap in top-down projection but never touch (e.g. interleaved
// chain links) stay separate, matching "split to objects". Each layer island then records
// the full bounding box of its body, so its infill is centered on that body as if it were
// sliced alone. Compute this before bridges so anchors and extrusion share the same origin.
bool needs_separated_components = false;
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
const PrintRegionConfig &rc = this->printing_region(i).config();
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
needs_separated_components = true;
break;
}
}
// Orca: Fast path: the feature only changes anything when the object is made of more than one
// connected body. Detect that cheaply the same way as "Split to objects" — more than one
// model part, or a single part whose mesh is splittable (is_splittable() is cached). A single
// body already shares the object center, i.e. the default, so skip the connectivity pass.
if (needs_separated_components) {
int parts = 0;
const ModelVolume *first_part = nullptr;
for (const ModelVolume *v : this->model_object()->volumes)
if (v->is_model_part()) { ++ parts; first_part = v; }
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
needs_separated_components = false;
}
for (Layer *layer : m_layers)
layer->lslices_separated_component_bboxes.clear();
if (needs_separated_components) {
const size_t nl = m_layers.size();
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
for (size_t i = 0; i < nl; ++ i)
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
const size_t nreg = offset[nl];
// Orca: Union-find over every (layer, island).
std::vector<size_t> parent(nreg);
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
auto find = [&parent](size_t x) {
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
return x;
};
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
// Orca: Index the smaller of two consecutive layers instead of scanning every
// pair of islands. The tree prunes distant boxes on fragmented models; exact
// polygon intersections still decide connectivity for the remaining candidates.
for (size_t i = 0; i + 1 < nl; ++ i) {
m_print->throw_if_canceled();
size_t layer_a = i, layer_b = i + 1;
if (m_layers[layer_a]->lslices.size() < m_layers[layer_b]->lslices.size())
std::swap(layer_a, layer_b);
const Layer *la = m_layers[layer_a], *lb = m_layers[layer_b];
if (lb->lslices.empty())
continue;
using IslandTree = AABBTreeIndirect::Tree<2, coord_t>;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(lb->lslices.size());
for (size_t b = 0; b < lb->lslices.size(); ++ b)
bboxes.emplace_back(b, lb->lslices_bboxes[b]);
IslandTree tree;
tree.build_modify_input(bboxes);
for (size_t a = 0; a < la->lslices.size(); ++ a) {
const IslandTree::BoundingBox query(la->lslices_bboxes[a].min, la->lslices_bboxes[a].max);
AABBTreeIndirect::traverse(tree,
[&query](const IslandTree::Node &node) { return node.bbox.intersects(query); },
[&](const IslandTree::Node &node) {
const size_t b = node.idx;
// Orca: Tree boxes include an epsilon, so retain the original box
// filter. Already-connected islands cannot change the partition
// and need no further polygon intersection.
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
find(offset[layer_a] + a) != find(offset[layer_b] + b) &&
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
unite(offset[layer_a] + a, offset[layer_b] + b);
return true;
});
}
}
// Orca: Full bounding box of each body, indexed by its union-find root.
std::vector<BoundingBox> body_bbox(nreg);
for (size_t i = 0; i < nl; ++ i)
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
// Orca: Store the body bbox for every island.
for (size_t i = 0; i < nl; ++ i) {
Layer *layer = m_layers[i];
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
for (size_t a = 0; a < layer->lslices.size(); ++ a)
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
}
}
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
@@ -706,71 +800,6 @@ void PrintObject::infill()
if (this->set_started(posInfill)) {
m_print->set_status(35, L("Generating infill toolpath"));
// Orca: precompute the object's 3D connected bodies for separated infills / per-model
// centering. Two islands belong to the same body when their slices overlap on adjacent
// layers; islands that only overlap in top-down projection but never touch (e.g. interleaved
// chain links) stay separate, matching "split to objects". Each layer island then records
// the full bounding box of its body, so its infill is centered on that body as if it were
// sliced alone. Done once here, before the parallel fill, and only when a region needs it.
bool needs_separated_components = false;
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
const PrintRegionConfig &rc = this->printing_region(i).config();
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
needs_separated_components = true;
break;
}
}
// Fast path: the feature only changes anything when the object is made of more than one
// connected body. Detect that cheaply the same way as "Split to objects" — more than one
// model part, or a single part whose mesh is splittable (is_splittable() is cached). A single
// body already shares the object center, i.e. the default, so skip the connectivity pass.
if (needs_separated_components) {
int parts = 0;
const ModelVolume *first_part = nullptr;
for (const ModelVolume *v : this->model_object()->volumes)
if (v->is_model_part()) { ++ parts; first_part = v; }
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
needs_separated_components = false;
}
for (Layer *layer : m_layers)
layer->lslices_separated_component_bboxes.clear();
if (needs_separated_components) {
const size_t nl = m_layers.size();
std::vector<size_t> offset(nl + 1, 0); // flat index of the first island of each layer
for (size_t i = 0; i < nl; ++ i)
offset[i + 1] = offset[i] + m_layers[i]->lslices.size();
const size_t nreg = offset[nl];
// Union-find over every (layer, island).
std::vector<size_t> parent(nreg);
for (size_t i = 0; i < nreg; ++ i) parent[i] = i;
auto find = [&parent](size_t x) {
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
return x;
};
auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; };
// Join islands that overlap between two consecutive layers.
for (size_t i = 0; i + 1 < nl; ++ i) {
const Layer *la = m_layers[i], *lb = m_layers[i + 1];
for (size_t a = 0; a < la->lslices.size(); ++ a)
for (size_t b = 0; b < lb->lslices.size(); ++ b)
if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) &&
! intersection_ex(la->lslices[a], lb->lslices[b]).empty())
unite(offset[i] + a, offset[i + 1] + b);
}
// Full bounding box of each body, indexed by its union-find root.
std::vector<BoundingBox> body_bbox(nreg);
for (size_t i = 0; i < nl; ++ i)
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
// Store the body bbox for every island.
for (size_t i = 0; i < nl; ++ i) {
Layer *layer = m_layers[i];
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
for (size_t a = 0; a < layer->lslices.size(); ++ a)
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
}
}
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
@@ -1401,8 +1430,6 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "infill_anchor_max"
|| opt_key == "top_surface_line_width"
|| opt_key == "bottom_surface_density"
|| opt_key == "center_of_surface_pattern"
|| opt_key == "separated_infills"
|| opt_key == "initial_layer_line_width"
|| opt_key == "small_area_infill_flow_compensation"
|| opt_key == "lateral_lattice_angle_1"
@@ -1410,6 +1437,10 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "infill_overhang_angle") {
steps.emplace_back(posInfill);
} else if (opt_key == "sparse_infill_pattern"
// Orca: Body centering now also determines bridge anchors during preparation.
// Invalidating preparation also invalidates infill, including top/bottom surfaces.
|| opt_key == "center_of_surface_pattern"
|| opt_key == "separated_infills"
|| opt_key == "sparse_infill_smooth_factor"
|| opt_key == "symmetric_infill_y_axis"
|| opt_key == "infill_shift_step"
@@ -1480,13 +1511,9 @@ bool PrintObject::invalidate_state_by_config_options(
steps.emplace_back(posPerimeters);
steps.emplace_back(posSupportMaterial);
} else if (opt_key == "bridge_flow" || opt_key == "internal_bridge_flow") {
if (m_config.support_top_z_distance > 0.) {
// Only invalidate due to bridging if bridging is enabled.
// If later "support_top_z_distance" is modified, the complete PrintObject is invalidated anyway.
steps.emplace_back(posPerimeters);
steps.emplace_back(posInfill);
steps.emplace_back(posSupportMaterial);
}
steps.emplace_back(posPerimeters);
steps.emplace_back(posInfill);
steps.emplace_back(posSupportMaterial);
} else if (
opt_key == "wall_generator"
|| opt_key == "wall_transition_length"
@@ -1604,7 +1631,9 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
bool PrintObject::invalidate_all_steps()
{
// First call the "invalidate" functions, which may cancel background processing.
bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
const bool inherited_invalidated = Inherited::invalidate_all_steps();
const bool print_invalidated = m_print->invalidate_all_steps();
bool result = inherited_invalidated || print_invalidated;
// Then reset some of the depending values.
m_slicing_params.valid = false;
return result;
@@ -3009,21 +3038,12 @@ void PrintObject::bridge_over_infill()
return diff(layers_sparse_infill, not_sparse_infill);
};
// LAMBDA do determine optimal bridging angle
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern, double infill_direction) {
// Orca: Derive the fallback bridge direction from the supplied anchor geometry.
// Pattern-specific angle selection belongs at the call site, where the supporting
// layer and region are known; this helper must not override it with a base config angle.
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors) {
AABBTreeLines::LinesDistancer<Line> lines_tree(anchors);
// Orca: since 3D Honeycomb was "fixed" by forcing coordf_t layerHeight = scale_(1.0), this is no longer needed.
// CorssHatch also does not need fixed angle.
//
// Check it the infill that require a fixed infill angle.
//switch (dominant_pattern) {
//case ip3DHoneycomb:
//case ipCrossHatch:
// return (infill_direction + 45.0) * 2.0 * M_PI / 360.;
//default: break;
//}
std::map<double, int> counted_directions;
for (const Polygon &p : bridged_area) {
double acc_distance = 0;
@@ -3089,18 +3109,15 @@ void PrintObject::bridge_over_infill()
if (bridging_angle == 0) {
bridging_angle = 0.001;
}
switch (dominant_pattern) {
case ipHilbertCurve: bridging_angle += 0.25 * PI; break;
case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break;
default: break;
}
return bridging_angle;
};
// LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly
// generated lines
auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) {
// Orca: Extend scan sections to the nearest anchors and reconstruct the bridge area.
// scan_spacing controls boundary sampling independently of the extrusion spacing;
// anchoring overlap and smoothing thresholds still use the physical bridging flow.
auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle,
coord_t scan_spacing, bool restore_anchors = false) {
auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) {
for (Line &l : lines) {
double ax = double(l.a.x());
@@ -3127,12 +3144,12 @@ void PrintObject::bridge_over_infill()
BoundingBox bb_x = get_extents(bridged_area);
BoundingBox bb_y = get_extents(anchors);
const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing();
const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + scan_spacing - 1) / scan_spacing;
std::vector<Line> vertical_lines(n_vlines);
for (size_t i = 0; i < n_vlines; i++) {
// Orca: Make sure the line is placed in the middle of the extrusion
// coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing();
coord_t x = bb_x.min.x() + (i + 0.5) * bridging_flow.scaled_spacing();
// Orca: Sample the center of each reconstructed strip. Its edges lie
// half a scan step away, even when the sampling is finer than extrusion.
coord_t x = bb_x.min.x() + (i + 0.5) * scan_spacing;
coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing();
coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing();
vertical_lines[i].a = Point{x, y_min};
@@ -3155,7 +3172,11 @@ void PrintObject::bridge_over_infill()
auto anchors_intersections = anchors_and_walls_tree.intersections_with_line<true>(vertical_lines[i]);
for (Line &section : polygon_sections[i]) {
auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a,
// Orca: A repaired boundary may already overlap its anchor by one flow width.
// Include that overlap in the search so restoring rounded corners does not
// extend every already anchored section into the next sparse infill cell.
const coord_t overlap = restore_anchors ? bridging_flow.scaled_width() + SCALED_EPSILON : 0;
auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a + Point{0, overlap},
[](const Point &a, const std::pair<Point, size_t> &b) {
return a.y() > b.first.y();
});
@@ -3164,7 +3185,7 @@ void PrintObject::bridge_over_infill()
section.a.y() -= bridging_flow.scaled_width() * (0.5 + 0.5);
}
auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b,
auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b - Point{0, overlap},
[](const Point &a, const std::pair<Point, size_t> &b) {
return a.y() < b.first.y();
});
@@ -3194,7 +3215,9 @@ void PrintObject::bridge_over_infill()
});
}
// reconstruct polygon from polygon sections
// Orca: Reconstruct the polygon from scan sections. At discontinuities and
// strip starts/ends, use half the scan step for the X offsets; using half an
// extrusion spacing would overlap the finer strips and distort curved anchors.
struct TracedPoly
{
Points lows;
@@ -3220,8 +3243,8 @@ void PrintObject::bridge_over_infill()
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
traced_poly.lows.push_back(candidate->a);
} else {
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scan_spacing / 2, 0});
traced_poly.lows.push_back(candidate->a - Point{scan_spacing / 2, 0});
traced_poly.lows.push_back(candidate->a);
}
@@ -3229,8 +3252,8 @@ void PrintObject::bridge_over_infill()
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
traced_poly.highs.push_back(candidate->b);
} else {
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scan_spacing / 2, 0});
traced_poly.highs.push_back(candidate->b - Point{scan_spacing / 2, 0});
traced_poly.highs.push_back(candidate->b);
}
segment_added = true;
@@ -3238,9 +3261,9 @@ void PrintObject::bridge_over_infill()
}
if (!segment_added) {
// Zero overlapping segments, we just close this polygon
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
// Orca: No section continues this strip; close at its right edge.
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scan_spacing / 2, 0});
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scan_spacing / 2, 0});
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
traced_poly.lows.clear();
@@ -3255,9 +3278,9 @@ void PrintObject::bridge_over_infill()
for (const auto &segment : polygon_slice) {
if (used_segments.find(&segment) == used_segments.end()) {
TracedPoly &new_tp = current_traced_polys.emplace_back();
new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0});
new_tp.lows.push_back(segment.a - Point{scan_spacing / 2, 0});
new_tp.lows.push_back(segment.a);
new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0});
new_tp.highs.push_back(segment.b - Point{scan_spacing / 2, 0});
new_tp.highs.push_back(segment.b);
}
}
@@ -3364,7 +3387,10 @@ void PrintObject::bridge_over_infill()
total_fill_area = closing(total_fill_area, float(SCALED_EPSILON));
expansion_area = closing(expansion_area, float(SCALED_EPSILON));
expansion_area = intersection(expansion_area, deep_infill_area);
Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing));
// Orca: Preserve the real lower-layer anchors for every candidate in this
// layer. Replacing this shared set for one pattern also changes later regions,
// and synthetic straight lines can claim support where no infill is printed.
const Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing));
Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5);
#ifdef DEBUG_BRIDGE_OVER_INFILL
@@ -3375,6 +3401,9 @@ void PrintObject::bridge_over_infill()
std::vector<CandidateSurface> expanded_surfaces;
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
const auto &region_config = candidate.region->region().config();
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
region_config.sparse_infill_pattern == ipOctagramSpiral;
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area);
@@ -3403,20 +3432,40 @@ void PrintObject::bridge_over_infill()
to_lines(area_to_be_bridge), to_lines(boundary_plines), to_lines(anchors), to_lines(expansion_area));
#endif
double bridging_angle = 0;
if (!anchors.empty()) {
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors),
candidate.region->region().config().sparse_infill_pattern.value,
candidate.region->region().config().infill_direction.value);
} else {
// use expansion boundaries as anchors.
// Also, use Infill pattern that is neutral for angle determination, since there are no infill lines.
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine, 0);
double bridging_angle = -1.;
if (!anchors.empty() && turning_pattern) {
// Orca: Keep adjacent bridges over Hilbert/Octagram aligned despite
// their many local turning directions. Use the lower layer's rotation,
// since that is the infill supporting the bridge, not the current layer's.
for (const LayerRegion *lower_region : layer->lower_layer->regions()) {
// Orca: Apply the configured direction only if the same region has
// sparse infill below this bridge. A height modifier may put another
// pattern underneath, requiring the geometry-based fallback below.
if (&lower_region->region() != &candidate.region->region() ||
intersection(area_to_be_bridge, to_polygons(lower_region->fill_surfaces.filter_by_type(stInternal))).empty())
continue;
bridging_angle = calculate_infill_rotation_angle(po, layer->lower_layer->id(), region_config.infill_direction.value,
region_config.sparse_infill_rotate_template.value) + 0.5 * PI;
// Orca: Apply model alignment as infill generation does, then normalize
// the undirected bridge angle to [0, PI), including negative rotations.
if (region_config.align_infill_direction_to_model) {
const auto &m = po->trafo().matrix();
bridging_angle += std::atan2(double(m(1, 0)), double(m(0, 0)));
}
bridging_angle = std::fmod(bridging_angle, PI);
if (bridging_angle < 0.)
bridging_angle += PI;
break;
}
}
// Orca: A different region below (e.g. a height modifier) needs the actual anchor
// directions. When there are no sparse anchors, use the expansion boundaries.
if (bridging_angle < 0.)
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors.empty() ? boundary_plines : anchors));
// ORCA: Internal bridge angle override
// Orca: Preserve the user's absolute or relative internal bridge angle
// override after automatic direction selection.
if (candidate.region->region().config().internal_bridge_angle.value > 0) {
const auto &region_config = candidate.region->region().config();
const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value);
if (region_config.relative_bridge_angle.value)
bridging_angle += custom_angle_rad;
@@ -3429,11 +3478,19 @@ void PrintObject::bridge_over_infill()
}
}
// Orca: Changing the bridge direction must not change its physical supports.
// Extend to actual sparse infill or the existing boundary anchors, never to
// a synthetic grid that merely has the same nominal angle and spacing.
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {
boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10)));
}
Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle);
// Orca: Use four samples per extrusion spacing for Hilbert/Octagram so the
// reconstructed boundary follows rounded anchors instead of cutting corners.
// Keep the original step for other patterns and at least one coordinate unit
// after integer division. This changes boundary accuracy, not infill density.
const coord_t scan_spacing = std::max(coord_t(1), flow.scaled_spacing() / (turning_pattern ? 4 : 1));
Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle, scan_spacing);
// Check collision with other expanded surfaces
{
@@ -3447,7 +3504,9 @@ void PrintObject::bridge_over_infill()
}
}
if (reconstruct) {
bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle);
// Orca: Retain the same sampling accuracy when matching a nearby
// bridge's direction; rebuilding must not lose the curved supports.
bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle, scan_spacing);
}
}
@@ -3455,6 +3514,13 @@ void PrintObject::bridge_over_infill()
// bridging_area = opening(bridging_area, flow.scaled_spacing());
bridging_area = opening(bridging_area, flow.scaled_spacing() * 0.75);
bridging_area = closing(bridging_area, flow.scaled_spacing());
// Orca: Opening/closing can pull rounded bridge ends away from their real
// supports. Restore those contacts after smoothing, preserving the cleaned
// area and the selected angle; do not smooth the restored contacts again.
if (turning_pattern && !bridging_area.empty()) {
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
bridging_angle, scan_spacing, true));
}
bridging_area = intersection(bridging_area, limiting_area);
bridging_area = intersection(bridging_area, total_fill_area);
bridging_area = diff(bridging_area, total_top_area);
+3 -1
View File
@@ -1007,7 +1007,9 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
bool SLAPrintObject::invalidate_all_steps()
{
return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
const bool inherited_invalidated = Inherited::invalidate_all_steps();
const bool print_invalidated = m_print->invalidate_all_steps();
return inherited_invalidated || print_invalidated;
}
double SLAPrintObject::get_elevation() const {
+7 -2
View File
@@ -30,6 +30,11 @@ static HMODULE s_hKernel32 = nullptr;
static SetThreadDescriptionType s_fnSetThreadDescription = nullptr;
static GetThreadDescriptionType s_fnGetThreadDescription = nullptr;
// Convert the FARPROC from GetProcAddress to Fn through a generic function pointer.
template<typename Fn> static Fn load_proc(HMODULE module, const char* name) {
return reinterpret_cast<Fn>(reinterpret_cast<void(*)()>(::GetProcAddress(module, name)));
}
static bool WindowsGetSetThreadNameAPIInitialize()
{
if (! s_SetGetThreadDescriptionInitialized) {
@@ -37,8 +42,8 @@ static bool WindowsGetSetThreadNameAPIInitialize()
// to initialize
s_hKernel32 = LoadLibraryW(L"Kernel32.dll");
if (s_hKernel32) {
s_fnSetThreadDescription = (SetThreadDescriptionType)::GetProcAddress(s_hKernel32, "SetThreadDescription");
s_fnGetThreadDescription = (GetThreadDescriptionType)::GetProcAddress(s_hKernel32, "GetThreadDescription");
s_fnSetThreadDescription = load_proc<SetThreadDescriptionType>(s_hKernel32, "SetThreadDescription");
s_fnGetThreadDescription = load_proc<GetThreadDescriptionType>(s_hKernel32, "GetThreadDescription");
}
s_SetGetThreadDescriptionInitialized = true;
}
+4
View File
@@ -70,6 +70,7 @@
#define CLI_FILAMENT_CAN_NOT_MAP -66
#define CLI_ONLY_ONE_TPU_SUPPORTED -67
#define CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER -68
#define CLI_MIXED_FILAMENT_INVALID -69
#define CLI_SLICING_ERROR -100
#define CLI_GCODE_PATH_CONFLICTS -101
@@ -313,6 +314,9 @@ extern unsigned get_current_pid();
std::string per_user_temp_id();
// Per-user temp root under `base`; an empty `user_id` returns `base` unchanged.
std::string per_user_temp_dir(const std::string &base, const std::string &user_id);
// Completes a relative command line input path against the current working directory. Absolute
// paths and custom open protocol URLs are returned unchanged.
std::string resolve_cli_input_path(const std::string &path);
// BBS: backup & restore
std::string get_process_name(int pid);
+13
View File
@@ -1339,6 +1339,19 @@ std::string per_user_temp_dir(const std::string &base, const std::string &user_i
return base + "/orcaslicer_" + user_id;
}
std::string resolve_cli_input_path(const std::string &path)
{
const boost::filesystem::path input(path);
if (path.empty() || is_supported_open_protocol(path) || input.is_absolute())
return path;
boost::system::error_code ec;
const boost::filesystem::path resolved = boost::filesystem::system_complete(input, ec);
if (ec)
return path;
return resolved.lexically_normal().make_preferred().string();
}
// BBS: backup & restore
std::string get_process_name(int pid)
{
+1
View File
@@ -1,6 +1,7 @@
#include "AMSDryControl.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "GUI_App.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "I18N.hpp"
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
+1
View File
@@ -14,6 +14,7 @@
//Previous defintions
class wxGrid;
class ProgressBar;
namespace Slic3r {
+2
View File
@@ -2,6 +2,8 @@
#include "ExtrusionCalibration.hpp"
#include "MsgDialog.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "libslic3r/Preset.hpp"
#include "I18N.hpp"
#include <algorithm>
+1
View File
@@ -11,6 +11,7 @@
#include "MainFrame.hpp"
#include "format.hpp"
#include "Widgets/ProgressDialog.hpp"
#include <wx/tooltip.h>
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/StaticBox.hpp"
@@ -5,8 +5,10 @@
#include <thread>
#include "GUI_App.hpp"
#include "GUI_Utils.hpp"
#include <wx/timer.h>
class Button;
class Label;
class CheckBox;
namespace Slic3r { namespace GUI {
class CapsuleButton;
+1
View File
@@ -1,6 +1,7 @@
#include "CalibrationWizard.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "MsgDialog.hpp"
#include "CalibrationWizardPage.hpp"
#include "../../libslic3r/calib.hpp"
@@ -1,5 +1,7 @@
#include <regex>
#include "CalibrationWizardPresetPage.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
@@ -1,4 +1,5 @@
#include "CalibrationWizardSavePage.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
+1
View File
@@ -1,5 +1,6 @@
#include "GUI_App.hpp"
#include "CapsuleButton.hpp"
#include "Widgets/StateColor.hpp"
#include <wx/dcbuffer.h>
#include "wx/graphics.h"
#include "Widgets/Label.hpp"
+1
View File
@@ -1,4 +1,5 @@
#include "ColorDecomposeSupport.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "MixedFilamentDialog.hpp"
#include "GUI_App.hpp"
#include "MsgDialog.hpp"
+1
View File
@@ -2,6 +2,7 @@
#include "ConfigManipulation.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "format.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/Model.hpp"
+1
View File
@@ -1,4 +1,5 @@
#include "DailyTips.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
+2
View File
@@ -1,5 +1,7 @@
#include <boost/log/trivial.hpp>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/UserNotification.hpp"
#include "libslic3r/PrintConfig.hpp"
@@ -4,6 +4,8 @@
#include <set>
#include "DevFilaBlackList.h"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "DevFilaSystem.h"
#include "DevManager.h"
#include "DevConfigUtil.h"
@@ -1,5 +1,6 @@
#include <nlohmann/json.hpp>
#include "DevFilaSystem.h"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
// TODO: remove this include
+3 -1
View File
@@ -1,3 +1,5 @@
#include <limits>
#include <nlohmann/json.hpp>
#include "DevMapping.h"
#include "DevFilaSystem.h"
@@ -270,7 +272,7 @@ namespace Slic3r
std::set<int> picked_tar;
for (int k = 0; k < distance_map.size(); k++)
{
float min_val = INT_MAX;
float min_val = std::numeric_limits<float>::max();
int picked_src_idx = -1;
int picked_tar_idx = -1;
for (int i = 0; i < distance_map.size(); i++)
+2
View File
@@ -1,5 +1,7 @@
#include "libslic3r/libslic3r.h"
#include "DeviceManager.hpp"
#include "HMS.hpp"
#include "I18N.hpp"
#include "libslic3r/Time.hpp"
#include "libslic3r/Thread.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
+1
View File
@@ -1,6 +1,7 @@
#include "wgtMsgPanel.h"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/StateColor.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
+1
View File
@@ -1,6 +1,7 @@
#include "DragCanvas.hpp"
#include "wxExtensions.hpp"
#include "GUI_App.hpp"
#include "Widgets/StateColor.hpp"
namespace Slic3r { namespace GUI {
+3
View File
@@ -1,7 +1,10 @@
#include "EncodedFilament.hpp"
#include <nlohmann/json.hpp>
#include "GUI_App.hpp"
using json = nlohmann::json;
namespace Slic3r
{
@@ -1,4 +1,5 @@
#include "ExportPresetBundleDialog.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "GUI_App.hpp"
#include "ConfigWizard.hpp"
#include "I18N.hpp"
@@ -12,7 +13,11 @@
#include <libslic3r/PresetBundle.hpp>
#include <wx/string.h>
#include <miniz.h>
#include <nlohmann/json.hpp>
#include <slic3r/GUI/MsgDialog.hpp>
using json = nlohmann::json;
namespace Slic3r { namespace GUI {
ExportPresetBundleDialog::ExportPresetBundleDialog(
+1
View File
@@ -1,6 +1,7 @@
#include "ExtraRenderers.hpp"
#include "wxExtensions.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "BitmapComboBox.hpp"
#include "Plater.hpp"
#include "Widgets/ComboBox.hpp"
+1
View File
@@ -1,5 +1,6 @@
#include "ExtrusionCalibration.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/Preset.hpp"
#include <algorithm>
+109 -122
View File
@@ -11,6 +11,7 @@
#include "libslic3r/PrintConfig.hpp"
#include <algorithm>
#include <cmath>
#include <regex>
#include <utility>
#include <cstdint>
@@ -540,51 +541,95 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
case coStrings:
case coFloatOrPercent:
case coFloatsOrPercents: {
if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && str.Last() != '%')
{
if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() &&
!(m_opt.nullable && str == m_na_value)) {
bool update_control = false;
wxString numeric_str = str;
double val = 0.;
const char dec_sep = is_decimal_separator_point() ? '.' : ',';
const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
// Replace the first incorrect separator in decimal number.
if (str.Replace(dec_sep_alt, dec_sep, false) != 0)
set_value(str, false);
// Orca: normalize the decimal separator and optional unit before
// detecting the percentage suffix and parsing the numeric part.
update_control |= numeric_str.Replace(dec_sep_alt, dec_sep, false) != 0;
update_control |= numeric_str.Replace(" ", "", true) != 0;
const bool has_literal_unit = numeric_str.EndsWith("mm");
if (has_literal_unit) {
numeric_str.RemoveLast(2);
update_control = true;
}
bool is_percent = !numeric_str.IsEmpty() && numeric_str.Last() == '%';
if (is_percent)
numeric_str.RemoveLast();
// remove space and "mm" substring, if any exists
str.Replace(" ", "", true);
str.Replace("m", "", true);
if (!str.ToDouble(&val))
{
if ((has_literal_unit && is_percent) || !numeric_str.ToDouble(&val) || !std::isfinite(val)) {
if (!check_value) {
m_value.clear();
break;
}
show_error(m_parent, _L("Invalid numeric."));
set_value(double_to_string(val), true);
}
else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) ||
(m_opt.sidetext.rfind("mm ") != std::string::npos && val > /*1*/m_opt.max_literal)) &&
(m_value.empty() || into_u8(str) != boost::any_cast<std::string>(m_value)))
{
if (!check_value) {
m_value.clear();
break;
numeric_str = double_to_string(std::clamp(0., double(m_opt.min), double(m_opt.max)));
is_percent = false;
update_control = true;
} else {
const bool looks_like_missing_percent = !is_percent && !has_literal_unit &&
((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) ||
(m_opt.sidetext.rfind("mm ") != std::string::npos && val > m_opt.max_literal));
// Orca: validate explicit percentages and literal values before
// asking whether an otherwise valid literal was meant as a percentage.
const bool out_of_range = !m_opt.is_value_valid(val);
if (out_of_range) {
if (!check_value) {
m_value.clear();
break;
}
show_error(m_parent, _L("Value is out of range."));
val = std::clamp(val, double(m_opt.min), double(m_opt.max));
// Orca: retain the inferred percent unit when clamping a
// suspicious unitless value, so 2000 becomes 100%, not 100 mm.
is_percent |= looks_like_missing_percent;
numeric_str = double_to_string(val);
update_control = true;
} else {
const bool value_changed = m_value.empty() || into_u8(str) != boost::any_cast<std::string>(m_value);
if (looks_like_missing_percent && value_changed) {
if (!check_value) {
m_value.clear();
break;
}
const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm";
const wxString stVal = numeric_str;
const wxString msg_text = from_u8((boost::format(_utf8(L("Is it %s%% or %s %s?"))) %
stVal % stVal % sidetext).str());
WarningDialog dialog(m_parent, msg_text, _L("Parameter validation") + ": " + m_opt_id, wxYES | wxNO);
dialog.SetButtonLabel(wxID_YES, stVal + _L("%"));
dialog.SetButtonLabel(wxID_NO, stVal + " " + _L(sidetext));
dialog.GetSizer()->SetSizeHints(&dialog);
dialog.Fit();
dialog.CenterOnParent();
is_percent = dialog.ShowModal() == wxID_YES;
update_control = true;
}
}
const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm";
const wxString stVal = double_to_string(val, 2);
const wxString msg_text = from_u8((boost::format(_utf8(L("Is it %s%% or %s %s?\n"
"YES for %s%%, \n"
"NO for %s %s."))) %
stVal % stVal % sidetext % stVal % stVal % sidetext)
.str());
WarningDialog dialog(m_parent, msg_text, _L("Parameter validation") + ": " + m_opt_id, wxYES | wxNO);
if ((val > 100) && dialog.ShowModal() == wxID_YES) {
set_value(from_u8((boost::format("%s%%") % stVal).str()), false /*true*/);
str += "%%";
} else
set_value(stVal, false); // it's no needed but can be helpful, when inputted value contained "," instead of "."
// Orca: also enforce the literal limit after clamping an explicit mm input.
if (!is_percent && m_opt.sidetext.rfind("mm ") != std::string::npos && val > m_opt.max_literal) {
if (!check_value) {
m_value.clear();
break;
}
if (!out_of_range)
show_error(m_parent, _L("Value is out of range."));
val = m_opt.max_literal;
numeric_str = double_to_string(val);
update_control = true;
}
}
if (update_control) {
str = numeric_str + (is_percent ? "%" : "");
set_value(str, true);
}
}
if (m_opt.opt_key == "thumbnails") {
@@ -2154,6 +2199,7 @@ void PrinterAgentChoice::msw_rescale()
void PluginField::BUILD()
{
auto* panel = new wxPanel(m_parent, wxID_ANY);
panel->SetBackgroundColour(*wxWHITE);
wxGetApp().UpdateDarkUI(panel);
window = panel;
@@ -2196,9 +2242,8 @@ void PluginField::rebuild_ui()
m_rows.clear();
m_standalone_add_btn = nullptr;
if (m_values.empty()) {
add_empty_state_row();
} else {
add_empty_state_row();
if (!m_values.empty()) {
for (size_t i = 0; i < m_values.size(); ++i)
add_plugin_row(display_name_for_value(m_values[i]), i == m_values.size() - 1);
}
@@ -2215,94 +2260,43 @@ void PluginField::rebuild_ui()
void PluginField::add_empty_state_row()
{
const auto button_size = wxSize(def_width_thinner() * m_em_unit, -1);
auto row_sizer = new wxBoxSizer(wxHORIZONTAL);
wxTextCtrl* display = new wxTextCtrl(window, wxID_ANY, _L("No plugin selected"),
wxDefaultPosition, wxSize(def_width_wider() * m_em_unit, wxDefaultCoord),
wxTE_READONLY);
display->SetEditable(false);
wxGetApp().UpdateDarkUI(display);
display->SetToolTip(_L("No plugin selected"));
auto add_btn = new ScalableButton(window, wxID_ANY, "param_add", wxEmptyString,
button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16);
wxGetApp().UpdateDarkUI(add_btn);
add_btn->SetToolTip(_L("Add plugin"));
auto add_btn = new Button(window, _L("Add plugin"), "param_add", 0, 16);
add_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_add_clicked(); });
row_sizer->Add(display, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
row_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL);
m_main_sizer->Add(row_sizer, 0, wxEXPAND);
PluginRow row;
row.display = display;
row.add_btn = add_btn;
row.sizer = row_sizer;
m_rows.push_back(row);
m_main_sizer->Add(add_btn, 0, wxEXPAND | wxBOTTOM, window->FromDIP(SidebarProps::ContentMarginV()));
m_standalone_add_btn = add_btn;
}
void PluginField::add_plugin_row(const wxString& value, bool is_last)
{
const auto button_size = wxSize(def_width_thinner() * m_em_unit, -1);
auto row_sizer = new wxBoxSizer(wxHORIZONTAL);
ScalableButton* select_btn = new ScalableButton(window, wxID_ANY, "search", wxEmptyString,
button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16);
wxGetApp().UpdateDarkUI(select_btn);
select_btn->SetToolTip(_L("Select plugin"));
wxTextCtrl* display = new wxTextCtrl(window, wxID_ANY, value,
wxDefaultPosition, wxSize(def_width_wider() * m_em_unit, wxDefaultCoord),
wxTE_READONLY);
display->SetEditable(false);
wxGetApp().UpdateDarkUI(display);
ComboBox* display = new ComboBox(window, wxID_ANY, value, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY | CB_NO_DROP_ICON);
display->SetIcon("edit");
display->SetToolTip(get_tooltip_text(value));
ScalableButton* remove_btn = nullptr;
if (!m_opt.readonly) {
remove_btn = new ScalableButton(window, wxID_ANY, "cross", wxEmptyString,
button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16);
wxGetApp().UpdateDarkUI(remove_btn);
remove_btn->SetToolTip(_L("Remove plugin"));
}
ScalableButton* remove_btn = new ScalableButton(window, wxID_ANY, "cross", wxEmptyString,
wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16);
remove_btn->SetToolTip(_L("Remove plugin"));
ScalableButton* add_btn = nullptr;
if (is_last && !m_opt.readonly) {
add_btn = new ScalableButton(window, wxID_ANY, "param_add", wxEmptyString,
button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16);
wxGetApp().UpdateDarkUI(add_btn);
add_btn->SetToolTip(_L("Add plugin"));
add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_add_clicked(); });
}
if (m_opt.readonly)
remove_btn->Disable();
const size_t row_index = m_rows.size();
select_btn->Bind(wxEVT_BUTTON, [this, row_index](wxCommandEvent&) { on_select_clicked(row_index); });
if (remove_btn)
remove_btn->Bind(wxEVT_BUTTON, [this, row_index](wxCommandEvent&) { on_remove_clicked(row_index); });
display->Bind(wxEVT_LEFT_DOWN, [this, row_index](wxMouseEvent& ) { on_select_clicked(row_index); });
remove_btn->Bind(wxEVT_BUTTON, [this, row_index](wxCommandEvent&) { on_remove_clicked(row_index); });
row_sizer->Add(select_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
row_sizer->Add(display, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
if (remove_btn)
row_sizer->Add(remove_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
if (add_btn)
row_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL);
else if (!m_opt.readonly) {
// Reserve space equal to the add button so all rows align.
row_sizer->Add(button_size.GetWidth(), button_size.GetHeight(), 0, wxALIGN_CENTER_VERTICAL);
}
row_sizer->Add(display , 1, wxALIGN_CENTER_VERTICAL);
row_sizer->Add(remove_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, window->FromDIP(SidebarProps::ElementSpacing()));
const int bottom_gap = is_last ? 0 : 4;
m_main_sizer->Add(row_sizer, 0, wxEXPAND | (bottom_gap > 0 ? wxBOTTOM : 0), bottom_gap);
m_main_sizer->Add(row_sizer, 0, wxEXPAND | wxBOTTOM, window->FromDIP(is_last ? SidebarProps::ContentMarginV() : 4));
PluginRow row;
row.select_btn = select_btn;
row.display = display;
row.remove_btn = remove_btn;
row.add_btn = add_btn;
row.sizer = row_sizer;
m_rows.push_back(row);
}
@@ -2354,9 +2348,9 @@ void PluginField::on_add_clicked()
m_values.push_back(selected);
m_value = m_values;
rebuild_ui();
on_change_field();
// Defer: don't destroy the clicked button from inside its own handler.
if(window)
window->CallAfter([this]() {rebuild_ui(); on_change_field();});
}
void PluginField::on_remove_clicked(size_t index)
@@ -2367,8 +2361,9 @@ void PluginField::on_remove_clicked(size_t index)
m_values.erase(m_values.begin() + index);
m_value = m_values;
rebuild_ui();
on_change_field();
// Defer: don't destroy the clicked button from inside its own handler.
if(window)
window->CallAfter([this]() {rebuild_ui(); on_change_field();});
}
wxString PluginField::get_row_value(size_t index) const
@@ -2382,7 +2377,7 @@ void PluginField::set_row_value(size_t index, const wxString& value)
{
if (index >= m_rows.size() || !m_rows[index].display)
return;
m_rows[index].display->ChangeValue(value);
m_rows[index].display->SetValue(value);
m_rows[index].display->SetToolTip(get_tooltip_text(value));
}
@@ -2425,14 +2420,10 @@ boost::any& PluginField::get_value()
void PluginField::enable()
{
for (auto& row : m_rows) {
if (row.select_btn)
row.select_btn->Enable();
if (row.display)
row.display->Enable();
if (row.remove_btn)
row.remove_btn->Enable();
if (row.add_btn)
row.add_btn->Enable();
}
if (m_standalone_add_btn)
m_standalone_add_btn->Enable();
@@ -2441,14 +2432,10 @@ void PluginField::enable()
void PluginField::disable()
{
for (auto& row : m_rows) {
if (row.select_btn)
row.select_btn->Disable();
if (row.display)
row.display->Disable();
if (row.remove_btn)
row.remove_btn->Disable();
if (row.add_btn)
row.add_btn->Disable();
}
if (m_standalone_add_btn)
m_standalone_add_btn->Disable();
@@ -2863,11 +2850,11 @@ void PointCtrl::BUILD()
//temp->Add(static_text_y, 0, wxALIGN_CENTER_VERTICAL, 0);
temp->Add(y_input);
x_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(x_textctrl); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(y_textctrl); }), y_textctrl->GetId());
x_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_input_value(x_textctrl); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_input_value(y_textctrl); }), y_textctrl->GetId());
x_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_value(x_textctrl); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_value(y_textctrl); }), y_textctrl->GetId());
x_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_input_value(x_textctrl); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_input_value(y_textctrl); }), y_textctrl->GetId());
// // recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(x_input);
@@ -2916,7 +2903,7 @@ bool PointCtrl::value_was_changed(wxTextCtrl* win)
return boost::any_cast<Vec2d>(m_value) != boost::any_cast<Vec2d>(val);
}
void PointCtrl::propagate_value(wxTextCtrl* win)
void PointCtrl::propagate_input_value(wxTextCtrl* win)
{
if (win->GetValue().empty())
on_kill_focus();
+6 -5
View File
@@ -25,6 +25,7 @@
#include "wxExtensions.hpp"
#include "Widgets/SpinInput.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/ComboBox.hpp"
#ifdef __WXMSW__
#define wxMSW true
@@ -532,10 +533,8 @@ public:
private:
struct PluginRow {
ScalableButton* select_btn { nullptr };
wxTextCtrl* display { nullptr };
ComboBox* display { nullptr };
ScalableButton* remove_btn { nullptr };
ScalableButton* add_btn { nullptr };
wxBoxSizer* sizer { nullptr };
};
@@ -553,7 +552,7 @@ private:
wxBoxSizer* m_main_sizer { nullptr };
std::vector<PluginRow> m_rows;
std::vector<std::string> m_values;
ScalableButton* m_standalone_add_btn { nullptr };
Button* m_standalone_add_btn { nullptr };
std::function<std::string()> m_selector;
};
@@ -628,8 +627,10 @@ private:
void on_button_click(wxCommandEvent &WXUNUSED(ev));
void save_colors_to_config();
private:
#if !defined(__linux__) && !defined(__LINUX__)
wxColourData* m_clrData{nullptr};
wxColourPickerWidget* m_picker_widget{nullptr};
#endif
};
class PointCtrl : public Field {
@@ -649,7 +650,7 @@ public:
void BUILD() override;
bool value_was_changed(wxTextCtrl* win);
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value(wxTextCtrl* win);
void propagate_input_value(wxTextCtrl* win);
void set_value(const Vec2d& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false) override;
boost::any& get_value() override;
+1
View File
@@ -1,5 +1,6 @@
#include "FilamentMapPanel.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "Widgets/MultiNozzleSync.hpp" // manuallySetNozzleCount producer for extruder_nozzle_stats
#include <algorithm>
+62 -103
View File
@@ -1078,56 +1078,36 @@ const double GLCanvas3D::DefaultCameraZoomToPlateMarginFactor = 1.25;
void GLCanvas3D::load_arrange_settings()
{
std::string dist_fff_str =
wxGetApp().app_config->get("arrange", "min_object_distance_fff");
// Each key must match what _render_arrange_menu writes, which appends a per-mode
// postfix to the base name.
auto load_float = [](const char *key, float &out) {
// The menu writes these with float_to_string_decimal_point, so parse them back
// the same way rather than with anything locale-dependent.
std::string value = wxGetApp().app_config->get("arrange", key);
size_t parsed = 0;
double number = string_to_double_decimal_point(value, &parsed);
if (parsed > 0)
out = float(number);
};
auto load_bool = [](const char *key, bool &out) {
std::string value = wxGetApp().app_config->get("arrange", key);
if (!value.empty())
out = (value == "1" || value == "true");
};
std::string dist_fff_seq_print_str =
wxGetApp().app_config->get("arrange", "min_object_distance_seq_print_fff");
load_float("min_object_distance_fff", m_arrange_settings_fff.distance);
load_float("min_object_distance_fff_seq_print", m_arrange_settings_fff_seq_print.distance);
load_float("min_object_distance_sla", m_arrange_settings_sla.distance);
std::string dist_sla_str =
wxGetApp().app_config->get("arrange", "min_object_distance_sla");
load_bool("enable_rotation_fff", m_arrange_settings_fff.enable_rotation);
load_bool("enable_rotation_fff_seq_print", m_arrange_settings_fff_seq_print.enable_rotation);
load_bool("enable_rotation_sla", m_arrange_settings_sla.enable_rotation);
std::string en_rot_fff_str =
wxGetApp().app_config->get("arrange", "enable_rotation_fff");
std::string en_rot_fff_seqp_str =
wxGetApp().app_config->get("arrange", "enable_rotation_seq_print");
std::string en_rot_sla_str =
wxGetApp().app_config->get("arrange", "enable_rotation_sla");
std::string en_allow_multiple_materials_str =
wxGetApp().app_config->get("arrange", "allow_multi_materials_on_same_plate");
std::string en_avoid_region_str =
wxGetApp().app_config->get("arrange", "avoid_extrusion_cali_region");
if (!dist_fff_str.empty())
m_arrange_settings_fff.distance = std::stof(dist_fff_str);
if (!dist_fff_seq_print_str.empty())
m_arrange_settings_fff_seq_print.distance = std::stof(dist_fff_seq_print_str);
if (!dist_sla_str.empty())
m_arrange_settings_sla.distance = std::stof(dist_sla_str);
if (!en_rot_fff_str.empty())
m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "true");
if (!en_allow_multiple_materials_str.empty())
m_arrange_settings_fff.allow_multi_materials_on_same_plate = (en_allow_multiple_materials_str == "1" || en_allow_multiple_materials_str == "true");
if (!en_rot_fff_seqp_str.empty())
m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "true");
if(!en_avoid_region_str.empty())
m_arrange_settings_fff.avoid_extrusion_cali_region = (en_avoid_region_str == "1" || en_avoid_region_str == "true");
if (!en_rot_sla_str.empty())
m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "true");
// These two keys carry no postfix, so the one stored value covers both FFF modes.
load_bool("allow_multi_materials_on_same_plate", m_arrange_settings_fff.allow_multi_materials_on_same_plate);
load_bool("allow_multi_materials_on_same_plate", m_arrange_settings_fff_seq_print.allow_multi_materials_on_same_plate);
load_bool("avoid_extrusion_cali_region", m_arrange_settings_fff.avoid_extrusion_cali_region);
load_bool("avoid_extrusion_cali_region", m_arrange_settings_fff_seq_print.avoid_extrusion_cali_region);
//BBS: add specific arrange settings
m_arrange_settings_fff_seq_print.is_seq_print = true;
@@ -2118,12 +2098,6 @@ void GLCanvas3D::render(bool only_init)
_render_selection_center();
#endif // ENABLE_RENDER_SELECTION_CENTER
// we need to set the mouse's scene position here because the depth buffer
// could be invalidated by the following gizmo render methods
// this position is used later into on_mouse() to drag the objects
if (m_picking_enabled)
m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<coord_t>());
// sidebar hints need to be rendered before the gizmos because the depth buffer
// could be invalidated by the following gizmo render methods
_render_selection_sidebar_hints();
@@ -4511,12 +4485,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
volume_bbox.offset(1.0);
const bool is_cut_connector_selected = m_selection.is_any_connector();
if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) {
const Vec3d scene_position = _mouse_to_3d(pos);
if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(scene_position) && !is_cut_connector_selected) {
m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None;
// The dragging operation is initiated.
m_mouse.drag.move_volume_idx = volume_idx;
m_selection.setup_cache();
m_mouse.drag.start_position_3D = m_mouse.scene_position;
m_mouse.drag.start_position_3D = scene_position;
m_sequential_print_clearance_first_displacement = true;
m_moving = true;
@@ -5059,7 +5034,21 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
}
//BBS: notify instance updates to part plater list
m_selection.notify_instance_update(-1, 0);
// Only what moved: the selected instances, or every instance of an object one of whose
// parts moved. Notifying a plate about an instance that stayed put invalidates its slice
// result, and notifying instance 0 alone left a moved copy unregistered on its new plate.
{
std::set<std::pair<int, int>> notified;
for (unsigned int i : m_selection.get_volume_idxs()) {
const GLVolume* v = m_volumes.volumes[i];
const int object_idx = v->object_idx();
if (object_idx < 0 || object_idx >= static_cast<int>(m_model->objects.size()))
continue;
const std::pair<int, int> key(object_idx, selection_mode == Selection::Volume ? -1 : v->instance_idx());
if (notified.insert(key).second)
m_selection.notify_instance_update(key.first, key.second);
}
}
// Fixes sinking/flying instances (snaps object to buildplate)
for (const std::pair<int, int>& i : done) {
@@ -5945,7 +5934,7 @@ bool GLCanvas3D::_render_orient_menu(float left, float right, float bottom, floa
}
//BBS: GUI refactor: adjust main toolbar position
bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, float top)
void GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, float top)
{
ImGuiWrapper *imgui = wxGetApp().imgui();
@@ -5970,7 +5959,6 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo
imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
ArrangeSettings settings = get_arrange_settings();
ArrangeSettings &settings_out = get_arrange_settings();
const float slider_icon_width = imgui->get_slider_icon_size().x;
const float cursor_slider_left = imgui->calc_text_size(_L("Spacing")).x + imgui->scaled(1.5f);
@@ -5979,13 +5967,9 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo
auto &appcfg = wxGetApp().app_config;
PrinterTechnology ptech = current_printer_technology();
bool settings_changed = false;
float dist_min = 0.f; // 0 means auto
std::string dist_key = "min_object_distance", rot_key = "enable_rotation";
std::string bed_shrink_x_key = "bed_shrink_x", bed_shrink_y_key = "bed_shrink_y";
std::string multi_material_key = "allow_multi_materials_on_same_plate";
std::string avoid_extrusion_key = "avoid_extrusion_cali_region";
std::string align_to_y_axis_key = "align_to_y_axis";
std::string postfix;
//BBS:
bool seq_print = false;
@@ -5993,59 +5977,41 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo
if (ptech == ptSLA) {
postfix = "_sla";
} else if (ptech == ptFFF) {
seq_print = &settings == &m_arrange_settings_fff_seq_print;
if (seq_print) {
postfix = "_fff_seq_print";
} else {
postfix = "_fff";
}
seq_print = wxGetApp().global_print_sequence() == PrintSequence::ByObject;
postfix = seq_print ? "_fff_seq_print" : "_fff";
}
dist_key += postfix;
rot_key += postfix;
bed_shrink_x_key += postfix;
bed_shrink_y_key += postfix;
ImGui::AlignTextToFramePadding();
imgui->text(_L("Spacing"));
ImGui::SameLine(1.2 * cursor_slider_left);
ImGui::PushItemWidth(window_width - slider_icon_width);
bool b_Spacing = imgui->bbl_slider_float_style("##Spacing", &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance;
bool b_Spacing = imgui->bbl_slider_float_style("##Spacing", &settings_out.distance, 0.f, 100.0f, "%5.2f", 1.0f, /*clamp=*/false);
ImGui::SameLine(window_width - slider_icon_width + 1.3 * cursor_slider_left);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_spacing_input = ImGui::BBLDragFloat("##spacing_input", &settings.distance, 0.05f, 0.0f, 0.0f, "%.2f");
if (b_Spacing || b_spacing_input)
{
settings.distance = std::max(dist_min, settings.distance);
settings_out.distance = settings.distance;
bool b_spacing_input = ImGui::BBLDragFloat("##spacing_input", &settings_out.distance, 0.05f, 0.0f, 0.0f, "%.2f");
if (b_Spacing || b_spacing_input) {
settings_out.distance = std::max(0.f, settings_out.distance);
appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance));
settings_changed = true;
}
imgui->text(_L("0 means auto spacing."));
ImGui::Separator();
if (imgui->bbl_checkbox(_L("Auto rotate for arrangement"), settings.enable_rotation)) {
settings_out.enable_rotation = settings.enable_rotation;
if (imgui->bbl_checkbox(_L("Auto rotate for arrangement"), settings_out.enable_rotation))
appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation);
settings_changed = true;
}
if (imgui->bbl_checkbox(_L("Allow multiple materials on same plate"), settings.allow_multi_materials_on_same_plate)) {
settings_out.allow_multi_materials_on_same_plate = settings.allow_multi_materials_on_same_plate;
appcfg->set("arrange", multi_material_key.c_str(), settings_out.allow_multi_materials_on_same_plate );
settings_changed = true;
}
if (imgui->bbl_checkbox(_L("Allow multiple materials on same plate"), settings_out.allow_multi_materials_on_same_plate))
appcfg->set("arrange", multi_material_key.c_str(), settings_out.allow_multi_materials_on_same_plate);
// only show this option if the printer has micro Lidar and can do first layer scan
DynamicPrintConfig &current_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
const bool has_lidar = wxGetApp().preset_bundle->is_bbl_vendor();
auto op = current_config.option("scan_first_layer");
if (has_lidar && op && op->getBool()) {
if (imgui->bbl_checkbox(_L("Avoid extrusion calibration region"), settings.avoid_extrusion_cali_region)) {
settings_out.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region;
appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region ? "1" : "0");
settings_changed = true;
}
if (imgui->bbl_checkbox(_L("Avoid extrusion calibration region"), settings_out.avoid_extrusion_cali_region))
appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region);
} else {
settings_out.avoid_extrusion_cali_region = false;
}
@@ -6057,11 +6023,7 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo
settings_out.align_to_y_axis = false;
}
if (imgui->bbl_checkbox(_L("Align to Y axis"), settings.align_to_y_axis)) {
settings_out.align_to_y_axis = settings.align_to_y_axis;
appcfg->set("arrange", align_to_y_axis_key, settings_out.align_to_y_axis ? "1" : "0");
settings_changed = true;
}
imgui->bbl_checkbox(_L("Align to Y axis"), settings_out.align_to_y_axis);
if (settings_out.enable_rotation == true) { imgui->disabled_end(); }
}
@@ -6077,7 +6039,6 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo
if (imgui->button(_L("Reset"))) {
settings_out = ArrangeSettings{};
settings_out.distance = std::max(dist_min, settings_out.distance);
//BBS: add specific arrange settings
if (seq_print) settings_out.is_seq_print = true;
@@ -6087,18 +6048,16 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo
else
settings_out.align_to_y_axis = false;
appcfg->set("arrange", dist_key, float_to_string_decimal_point(settings_out.distance));
appcfg->set("arrange", rot_key, settings_out.enable_rotation ? "1" : "0");
appcfg->set("arrange", align_to_y_axis_key, settings_out.align_to_y_axis ? "1" : "0");
settings_changed = true;
appcfg->erase("arrange", dist_key);
appcfg->erase("arrange", rot_key);
appcfg->erase("arrange", multi_material_key);
appcfg->erase("arrange", avoid_extrusion_key);
}
ImGui::PopStyleVar(1);
imgui->end();
//BBS
ImGuiWrapper::pop_toolbar_style();
return settings_changed;
}
static const float cameraProjection[16] = {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f};
+2 -18
View File
@@ -337,7 +337,6 @@ class GLCanvas3D
bool dragging{ false };
Vec2d position{ DBL_MAX, DBL_MAX };
Vec3d scene_position{ DBL_MAX, DBL_MAX, DBL_MAX };
bool ignore_left_up{ false };
Drag drag;
bool ignore_right_up;
@@ -656,11 +655,7 @@ public:
}
void load_arrange_settings();
ArrangeSettings& get_arrange_settings();// { return get_arrange_settings(this); }
ArrangeSettings& get_arrange_settings(PrintSequence print_seq) {
return (print_seq == PrintSequence::ByObject) ? m_arrange_settings_fff_seq_print
: m_arrange_settings_fff;
}
ArrangeSettings& get_arrange_settings();
class SequentialPrintClearance
{
@@ -1163,17 +1158,6 @@ public:
void highlight_toolbar_item(const std::string& item_name);
void highlight_gizmo(const std::string& gizmo_name);
ArrangeSettings get_arrange_settings() const {
const ArrangeSettings &settings = get_arrange_settings();
ArrangeSettings ret = settings;
if (&settings == &m_arrange_settings_fff_seq_print) {
ret.distance = std::max(ret.distance,
float(min_object_distance(*m_config)));
}
return ret;
}
// Timestamp for FPS calculation and notification fade-outs.
static int64_t timestamp_now() {
#ifdef _WIN32
@@ -1308,7 +1292,7 @@ private:
void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field, m_gizmos.get_uniform_scaling()); }
//BBS: GUI refactor: adjust main toolbar position
bool _render_orient_menu(float left, float right, float bottom, float top);
bool _render_arrange_menu(float left, float right, float bottom, float top);
void _render_arrange_menu(float left, float right, float bottom, float top);
void _render_3d_navigator();
void _update_volumes_hover_state();
+2
View File
@@ -9,6 +9,7 @@
#include "3DScene.hpp"
#include "OpenGLManager.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "GLModel.hpp"
#include <glad/gl.h>
@@ -31,6 +32,7 @@
#include "GUI_App.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcgraph.h>
#include <wx/dcmemory.h>
namespace Slic3r {
namespace GUI {
+10 -223
View File
@@ -3,6 +3,14 @@
#include "libslic3r/Technologies.hpp"
#include "libslic3r/Platform.hpp"
#include "GUI_App.hpp"
#include "BindDialog.hpp"
#include "DeviceManager.hpp"
#include "HMS.hpp"
#include "PresetBundleDialog.hpp"
#include "WebUserLoginDialog.hpp"
#include "WebViewDialog.hpp"
#include "slic3r/Utils/BBLCloudServiceAgent.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "GUI_Init.hpp"
#include "GUI_ObjectList.hpp"
#include "slic3r/GUI/UserManager.hpp"
@@ -598,7 +606,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
#ifdef WIN32
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
static void register_win32_device_notification_event()
{
@@ -7769,21 +7777,6 @@ void GUI_App::stop_http_server()
m_http_server.stop();
}
void GUI_App::switch_staff_pick(bool on)
{
mainframe->m_webview->SendDesignStaffpick(on);
}
bool GUI_App::switch_language()
{
if (select_language()) {
recreate_GUI(_L("Switching application language") + dots);
return true;
} else {
return false;
}
}
#ifdef __linux__
static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language,
const wxLanguageInfo* system_language)
@@ -7878,72 +7871,6 @@ int GUI_App::GetSingleChoiceIndex(const wxString& message,
#endif
}
// select language from the list of installed languages
bool GUI_App::select_language()
{
wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY);
std::vector<const wxLanguageInfo*> language_infos;
language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
for (size_t i = 0; i < translations.GetCount(); ++ i) {
const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]);
if (langinfo != nullptr)
language_infos.emplace_back(langinfo);
}
sort_remove_duplicates(language_infos);
std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; });
wxArrayString names;
names.Alloc(language_infos.size());
// Some valid language should be selected since the application start up.
const wxString active_language_code = current_language_code();
const wxLanguageInfo* active_language_info = wxLocale::FindLanguageInfo(active_language_code);
const wxLanguage current_language = active_language_info != nullptr ? wxLanguage(active_language_info->Language) : wxLanguage(m_wxLocale->GetLanguage());
const wxString active_lang_prefix = active_language_code.BeforeFirst('_');
int init_selection = -1;
int init_selection_alt = -1;
int init_selection_default = -1;
for (size_t i = 0; i < language_infos.size(); ++ i) {
if (wxLanguage(language_infos[i]->Language) == current_language)
// The dictionary matches the active language and country.
init_selection = i;
else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == active_lang_prefix) ||
// if the active language is Slovak, mark the Czech language as active.
(language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && active_lang_prefix == "sk"))
// The dictionary matches the active language, it does not necessarily match the country.
init_selection_alt = i;
if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en")
// This will be the default selection if the active language does not match any dictionary.
init_selection_default = i;
names.Add(language_infos[i]->Description);
}
if (init_selection == -1)
// This is the dictionary matching the active language.
init_selection = init_selection_alt;
if (init_selection != -1)
// This is the language to highlight in the choice dialog initially.
init_selection_default = init_selection;
const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default);
// Try to load a new language.
if (index != -1 && (init_selection == -1 || init_selection != index)) {
const wxLanguageInfo *new_language_info = language_infos[index];
if (this->load_language(new_language_info->CanonicalName, false)) {
// Save language at application config.
// Which language to save as the selected dictionary language?
// 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its
// stability in the future:
// wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH);
// 2) Current locale language may not match the dictionary name, see GH issue #3901
// m_wxLocale->GetCanonicalName()
// 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name.
app_config->set("language", new_language_info->CanonicalName.ToUTF8().data());
return true;
}
}
return false;
}
// Load gettext translation files and activate them at the start of the application,
// based on the "language" key stored in the application config.
@@ -8330,146 +8257,6 @@ void GUI_App::show_ip_address_enter_dialog_handler(wxCommandEvent& evt)
show_modal_ip_address_enter_dialog(mode == -1?false:true, title);
}
//void GUI_App::add_config_menu(wxMenuBar *menu)
//void GUI_App::add_config_menu(wxMenu *menu)
//{
// auto local_menu = new wxMenu();
// wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt));
//
// const auto config_wizard_name = _(ConfigWizard::name(true));
// const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Open %s"))) % config_wizard_name).str());
// // Cmd+, is standard on OS X - what about other operating systems?
// if (is_editor()) {
// local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
// local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
// local_menu->AppendSeparator();
// }
// local_menu->Append(config_id_base + ConfigMenuPreferences, _L("Preferences") + dots +
//#ifdef __APPLE__
// "\tCtrl+,",
//#else
// "\tCtrl+P",
//#endif
// _L("Application preferences"));
// wxMenu* mode_menu = nullptr;
// if (is_editor()) {
// local_menu->AppendSeparator();
// mode_menu = new wxMenu();
// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple Mode"));
// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced Mode"));
// Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple);
// Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced);
//
// local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s Mode"), SLIC3R_APP_NAME));
// }
// local_menu->AppendSeparator();
// local_menu->Append(config_id_base + ConfigMenuLanguage, _L("Language"));
// if (is_editor()) {
// local_menu->AppendSeparator();
// }
//
// local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) {
// switch (event.GetId() - config_id_base) {
// case ConfigMenuWizard:
// run_wizard(ConfigWizard::RR_USER);
// break;
// case ConfigMenuUpdate:
// check_updates(true);
// break;
//#ifdef __linux__
// case ConfigMenuDesktopIntegration:
// show_desktop_integration_dialog();
// break;
//#endif
// case ConfigMenuSnapshots:
// //BBS do not support task snapshot
// break;
// case ConfigMenuPreferences:
// {
// //BBS GUI refactor: remove unuse layout logic
// //bool app_layout_changed = false;
// {
// // the dialog needs to be destroyed before the call to recreate_GUI()
// // or sometimes the application crashes into wxDialogBase() destructor
// // so we put it into an inner scope
// PreferencesDialog dlg(mainframe);
// dlg.ShowModal();
// //BBS GUI refactor: remove unuse layout logic
// //app_layout_changed = dlg.settings_layout_changed();
// if (dlg.seq_top_layer_only_changed())
// this->plater_->refresh_print();
//
// if (dlg.recreate_GUI()) {
// recreate_GUI(_L("Restart application") + dots);
// return;
// }
//#ifdef _WIN32
// if (is_editor()) {
// if (app_config->get("associate_3mf") == "true")
// associate_3mf_files();
// if (app_config->get("associate_stl") == "true")
// associate_stl_files();
// }
// else {
// if (app_config->get("associate_gcode") == "true")
// associate_gcode_files();
// }
//#endif // _WIN32
// }
// //BBS GUI refactor: remove unuse layout logic
// /*if (app_layout_changed) {
// // hide full main_sizer for mainFrame
// mainframe->GetSizer()->Show(false);
// mainframe->update_layout();
// mainframe->select_tab(size_t(0));
// }*/
// break;
// }
// case ConfigMenuLanguage:
// {
// /* Before change application language, let's check unsaved changes on 3D-Scene
// * and draw user's attention to the application restarting after a language change
// */
// {
// // the dialog needs to be destroyed before the call to switch_language()
// // or sometimes the application crashes into wxDialogBase() destructor
// // so we put it into an inner scope
// wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
// title += " - " + _L("Choose language");
// //wxMessageDialog dialog(nullptr,
// MessageDialog dialog(nullptr,
// _L("Switching the language requires application restart.\n") + "\n\n" +
// _L("Do you want to continue?"),
// title,
// wxICON_QUESTION | wxOK | wxCANCEL);
// if (dialog.ShowModal() == wxID_CANCEL)
// return;
// }
//
// switch_language();
// break;
// }
// case ConfigMenuFlashFirmware:
// //BBS FirmwareDialog::run(mainframe);
// break;
// default:
// break;
// }
// });
//
// using std::placeholders::_1;
//
// if (mode_menu != nullptr) {
// auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); };
// mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple);
// mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced);
// }
//
// // BBS
// //menu->Append(local_menu, _L("Configuration"));
// menu->AppendSubMenu(local_menu, _L("Configuration"));
//}
void GUI_App::open_presetbundledialog(size_t open_on_tab, const std::string& highlight_option)
{
bool app_layout_changed = false;
@@ -9418,7 +9205,7 @@ int GUI_App::filaments_cnt() const
PrintSequence GUI_App::global_print_sequence() const
{
PrintSequence global_print_seq = PrintSequence::ByDefault;
auto curr_preset_config = preset_bundle->prints.get_edited_preset().config;
const auto &curr_preset_config = preset_bundle->prints.get_edited_preset().config;
if (curr_preset_config.has("print_sequence"))
global_print_seq = curr_preset_config.option<ConfigOptionEnum<PrintSequence>>("print_sequence")->value;
return global_print_seq;
+10 -12
View File
@@ -1,23 +1,17 @@
#ifndef slic3r_GUI_App_hpp_
#define slic3r_GUI_App_hpp_
#include <functional>
#include <memory>
#include <string>
#include "ActionRegistry.hpp"
#include "ImGuiWrapper.hpp"
#include "ConfigWizard.hpp"
#include "OpenGLManager.hpp"
#include "PresetBundleDialog.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/UserNotification.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/BBLCloudServiceAgent.hpp"
#include "slic3r/GUI/WebViewDialog.hpp"
#include "slic3r/GUI/WebUserLoginDialog.hpp"
#include "slic3r/GUI/BindDialog.hpp"
#include "slic3r/GUI/HMS.hpp"
#include "slic3r/Utils/CloudProvider.hpp"
#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp"
#include "slic3r/GUI/HttpServer.hpp"
#include "../Utils/PrintHost.hpp"
@@ -64,9 +58,14 @@ class ModelObject;
class Model;
class UserManager;
class DeviceManager;
class MachineObject;
class NetworkAgent;
class IPrinterAgent;
class TaskManager;
// Same typedef as in bambu_networking.hpp, so this header need not include it.
typedef std::function<bool()> WasCancelledFn;
namespace GUI{
class RemovableDriveManager;
@@ -85,6 +84,8 @@ class ParamsDialog;
class HMSQuery;
class ModelMallDialog;
class PingCodeBindDialog;
class PresetBundleDialog;
class ZUserLogin;
class NetworkErrorDialog;
class PluginsDialog;
class SpeedDialWebDialog;
@@ -569,7 +570,6 @@ public:
void start_http_server(const std::string& provider = ORCA_CLOUD_PROVIDER);
void start_http_server(int port, const std::string& provider = ORCA_CLOUD_PROVIDER);
void stop_http_server();
void switch_staff_pick(bool on);
void on_show_check_privacy_dlg(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER);
void show_check_privacy_dlg(wxCommandEvent& evt);
@@ -583,7 +583,6 @@ public:
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
void update_ui_from_settings();
bool switch_language();
bool load_language(wxString language, bool initial);
Tab* get_tab(Preset::Type type);
@@ -801,7 +800,6 @@ private:
bool window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
void window_pos_sanitize(wxTopLevelWindow* window);
void window_pos_center(wxTopLevelWindow *window);
bool select_language();
// Dynamic printer agent selection - internal helpers for switch_printer_agent
// and the plugin load/unload callbacks (init_plugin_gui_wiring).
@@ -832,7 +830,7 @@ wxDECLARE_EVENT(EVT_UPDATE_BUNDLE_COMPLETE, wxCommandEvent);
bool is_support_filament(int extruder_id, bool strict_check = true);
bool is_soluble_filament(int extruder_id);
// check if the filament for model is in the list
bool has_filaments(const std::vector<string>& model_filaments);
bool has_filaments(const std::vector<std::string>& model_filaments);
} // namespace GUI
} // Slic3r
+2 -20
View File
@@ -1392,7 +1392,7 @@ void MenuFactory::create_default_menu()
{
wxMenu* sub_menu_primitives = append_submenu_add_generic(&m_default_menu, ModelVolumeType::INVALID);
wxMenu* sub_menu_handy = append_submenu_add_handy_model(&m_default_menu, ModelVolumeType::INVALID);
#ifdef __WINDOWS__
append_submenu(&m_default_menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "menu_add_part",
[]() {return true; }, m_parent);
append_submenu(&m_default_menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "menu_add_part",
@@ -1400,15 +1400,6 @@ void MenuFactory::create_default_menu()
append_menu_item(&m_default_menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models
[](wxCommandEvent&) { plater()->add_file(); }, "menu_add_part", &m_default_menu,
[]() {return wxGetApp().plater()->can_add_model(); }, m_parent);
#else
append_submenu(&m_default_menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "",
[]() {return true; }, m_parent);
append_submenu(&m_default_menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "",
[]() {return true; }, m_parent);
append_menu_item(&m_default_menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models
[](wxCommandEvent&) { plater()->add_file(); }, "", &m_default_menu,
[]() {return wxGetApp().plater()->can_add_model(); }, m_parent);
#endif
m_default_menu.AppendSeparator();
@@ -1789,7 +1780,6 @@ void MenuFactory::create_plate_menu()
wxMenu* sub_menu_primitives = append_submenu_add_generic(menu, ModelVolumeType::INVALID);
wxMenu* sub_menu_handy = append_submenu_add_handy_model(menu, ModelVolumeType::INVALID);
#ifdef __WINDOWS__
append_submenu(menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "menu_add_part",
[]() {return true; }, m_parent);
append_submenu(menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "menu_add_part",
@@ -1797,15 +1787,7 @@ void MenuFactory::create_plate_menu()
append_menu_item(menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models
[](wxCommandEvent&) { plater()->add_file(); }, "menu_add_part", menu,
[]() {return wxGetApp().plater()->can_add_model(); }, m_parent);
#else
append_submenu(menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "",
[]() {return true; }, m_parent);
append_submenu(menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "",
[]() {return true; }, m_parent);
append_menu_item(menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models
[](wxCommandEvent&) { plater()->add_file(); }, "", menu,
[]() {return wxGetApp().plater()->can_add_model(); }, m_parent);
#endif
append_menu_item_replace_all_with_stl(menu);
+2 -2
View File
@@ -2578,7 +2578,7 @@ void ObjectGridTable::OnSelectCell(int row, int col)
return;
m_panel->m_side_window->Freeze();
if (row == 0 || col == col_filaments) {
m_panel->m_object_settings->UpdateAndShow(row, false, false, false, nullptr, nullptr, std::string());
m_panel->m_object_settings->UpdateAndShowRow(row, false, false, false, nullptr, nullptr, std::string());
}
else {
ObjectGridRow* grid_row = m_grid_data[row - 1];
@@ -2588,7 +2588,7 @@ void ObjectGridTable::OnSelectCell(int row, int col)
//m_panel->m_object_settings->get_og()->set_name(GUI::from_u8(grid_row->name.value));
//m_panel->m_page_text->SetLabel(GUI::from_u8(grid_row->name.value));
m_panel->m_object_settings->UpdateAndShow(row, true, is_object, false, object, grid_row->config, grid_col->category);
m_panel->m_object_settings->UpdateAndShowRow(row, true, is_object, false, object, grid_row->config, grid_col->category);
std::vector<ObjectVolumeID> object_volume_ids;
ObjectVolumeID object_volume_id;
+1 -1
View File
@@ -463,7 +463,7 @@ void ObjectTableSettings::update_config_values(bool is_object, ModelObject* obje
m_table->reload_cell_data(m_current_row, category);
}
void ObjectTableSettings::UpdateAndShow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category)
void ObjectTableSettings::UpdateAndShowRow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category)
{
m_current_row = row;
m_current_category = category;
+1 -1
View File
@@ -71,7 +71,7 @@ public:
//return visible count
int update_extra_column_visible_status(ConfigOptionsGroup* option_group, const std::vector<SimpleSettingData>& option_keys, ModelConfig* config);
void update_config_values(bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& changed_opt_key = "");
void UpdateAndShow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category);
void UpdateAndShowRow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category);
void ValueChanged(int row, bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& key);
void resetAllValues(int row, bool is_object, ModelObject* object, ModelConfig* config, const std::string& category);
void msw_rescale();
+3 -2
View File
@@ -69,6 +69,8 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std
HANDLE handlesrc = nullptr;
HANDLE handledst = nullptr;
CopyFileResult ret = SUCCESS;
DWORD size = 0;
DWORD dwRead = 0, dwWrite = 0;
handlesrc = CreateFile(src.wc_str(),
GENERIC_READ,
@@ -96,9 +98,8 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std
goto __finished;
}
DWORD size=GetFileSize(handlesrc,NULL);
size = GetFileSize(handlesrc,NULL);
buff = new char[size+1];
DWORD dwRead=0,dwWrite;
result = ReadFile(handlesrc, buff, size, &dwRead, NULL);
if (!result) {
DWORD errCode = GetLastError();
@@ -1,5 +1,7 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoAdvancedCut.hpp"
#include "slic3r/GUI/Widgets/ProgressDialog.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <glad/gl.h>
@@ -1,6 +1,7 @@
#include "GLGizmoBrimEars.hpp"
#include <glad/gl.h>
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "slic3r/GUI/GUI_App.hpp"
@@ -4,6 +4,7 @@
#include "libslic3r/Print.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
+1
View File
@@ -1,4 +1,5 @@
#include "GLGizmoMeasure.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
+2
View File
@@ -32,6 +32,8 @@
*/
using namespace std::string_view_literals;
namespace Slic3r::GUI::GLGizmoUtils {
void render_tooltip_button(
@@ -702,7 +702,7 @@ bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, bo
for (int i = 0; i < number; i++)
{
char buf[3][64] = {0};
char buf[3][64] = {};
float buf_size[3] = {0};
for (int j = 0; j < 3; j++) {
ImGui::DataTypeFormatString(buf[j], IM_ARRAYSIZE(buf[j]), ImGuiDataType_Double, (void *) &vec[i][j], "%.2f");
+4
View File
@@ -4,6 +4,10 @@
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
#include "libslic3r/Thread.hpp"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace Slic3r {
namespace GUI {
+2 -1
View File
@@ -1,6 +1,7 @@
#include "IMSlider.hpp"
#include "libslic3r/GCode.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "NotificationManager.hpp"
#include "Widgets/StateColor.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
@@ -790,7 +791,7 @@ void IMSlider::draw_ticks(const ImRect& slideable_region) {
void IMSlider::show_tooltip(const std::string tooltip) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 6 * m_scale, 3 * m_scale });
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, { 3 * m_scale });
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * m_scale);
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 });
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));
+2
View File
@@ -3,6 +3,8 @@
#include "GUI_App.hpp"
#include "GUI_Utils.hpp"
#include <wx/stattext.h>
#include <wx/timer.h>
class wxStaticBitmap;
namespace Slic3r { namespace GUI {
+1 -1
View File
@@ -521,7 +521,7 @@ void ImageGrid::render(wxDC& dc)
if (!m_status_msg.IsEmpty()) {
auto si = m_status_icon.GetBmpSize();
auto st = dc.GetMultiLineTextExtent(m_status_msg);
auto rect = wxRect{0, 0, max(st.x, si.x), si.y + 26 + st.y}.CenterIn(wxRect({0, 0}, size));
auto rect = wxRect{0, 0, std::max(st.x, si.x), si.y + 26 + st.y}.CenterIn(wxRect({0, 0}, size));
dc.DrawBitmap(m_status_icon.bmp(), rect.x + (rect.width - si.x) / 2, rect.y);
dc.SetTextForeground(wxColor(0x909090));
dc.DrawText(m_status_msg, rect.x + (rect.width - st.x) / 2, rect.GetBottom() - st.y);
+4 -1
View File
@@ -114,7 +114,10 @@ namespace instance_check_internal
if (my_instance_hash == other_instance_hash) {
BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance";
orca_slicer_hwnd = hwnd;
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
// Do not alter the window state when opening a file in the existing instance.
// A minimized window still needs restoring before it can receive focus.
if (IsIconic(hwnd))
ShowWindow(hwnd, SW_RESTORE);
SetForegroundWindow(hwnd);
return false;
}
-1
View File
@@ -87,7 +87,6 @@ private:
std::condition_variable m_thread_stop_condition;
mutable std::mutex m_thread_stop_mutex;
bool m_stop{ false };
bool m_start{ true };
// background thread method
void listen();
+4
View File
@@ -3,6 +3,10 @@
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/HMS.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/GUI/DeviceCore/DevManager.h"
+2
View File
@@ -1,4 +1,6 @@
#include "SendJob.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -2,6 +2,7 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Utils/Http.hpp"
namespace Slic3r {
+2 -91
View File
@@ -1591,7 +1591,7 @@ void MainFrame::register_win32_callbacks()
//static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED };
//static GUID GUID_DEVINTERFACE_DISK = { 0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b };
//static GUID GUID_DEVINTERFACE_VOLUME = { 0x71a27cdd, 0x812a, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2, 0x09, 0x2f };
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
// Register USB HID (Human Interface Devices) notifications to trigger the 3DConnexion enumeration.
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = { 0 };
@@ -1631,7 +1631,7 @@ void MainFrame::register_win32_callbacks()
{
static constexpr int device_count = 1;
RAWINPUTDEVICE devices[device_count] = { 0 };
RAWINPUTDEVICE devices[device_count] = {};
// multi-axis mouse (SpaceNavigator, etc.)
devices[0].usUsagePage = 0x01;
devices[0].usUsage = 0x08;
@@ -3275,98 +3275,9 @@ void MainFrame::init_menubar_as_editor()
auto preference_item = new wxMenuItem(parent_menu, ConfigMenuPreferences + config_id_base, _L("Preferences") + "\t" + ctrl + "P", "");
#endif
//auto printer_item = new wxMenuItem(parent_menu, ConfigMenuPrinter + config_id_base, _L("Printer"), "");
//auto language_item = new wxMenuItem(parent_menu, ConfigMenuLanguage + config_id_base, _L("Switch Language"), "");
// parent_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) {
// switch (event.GetId() - config_id_base) {
// //case ConfigMenuLanguage:
// //{
// // /* Before change application language, let's check unsaved changes on 3D-Scene
// // * and draw user's attention to the application restarting after a language change
// // */
// // {
// // // the dialog needs to be destroyed before the call to switch_language()
// // // or sometimes the application crashes into wxDialogBase() destructor
// // // so we put it into an inner scope
// // wxString title = _L("Language selection");
// // wxMessageDialog dialog(nullptr,
// // _L("Switching the language requires application restart.\n") + "\n\n" +
// // _L("Do you want to continue?"),
// // title,
// // wxICON_QUESTION | wxOK | wxCANCEL);
// // if (dialog.ShowModal() == wxID_CANCEL)
// // return;
// // }
//
// // wxGetApp().switch_language();
// // break;
// //}
// //case ConfigMenuWizard:
// //{
// // wxGetApp().run_wizard(ConfigWizard::RR_USER);
// // break;
// //}
// case ConfigMenuPrinter:
// {
// wxGetApp().params_dialog()->Popup();
// wxGetApp().get_tab(Preset::TYPE_PRINTER)->restore_last_select_item();
// break;
// }
// case ConfigMenuPreferences:
// {
// CallAfter([this] {
// PreferencesDialog dlg(this);
// dlg.ShowModal();
//#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
//#else
// if (dlg.seq_top_layer_only_changed())
//#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// plater()->refresh_print();
//#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
//#ifdef _WIN32
// /*
// if (wxGetApp().app_config()->get("associate_3mf") == "true")
// wxGetApp().associate_3mf_files();
// if (wxGetApp().app_config()->get("associate_stl") == "true")
// wxGetApp().associate_stl_files();
// /*if (wxGetApp().app_config()->get("associate_step") == "true")
// wxGetApp().associate_step_files();*/
//#endif // _WIN32
//#endif
// });
// break;
// }
// default:
// break;
// }
// });
#ifdef __APPLE__
wxString about_title = wxString::Format(_L("&About %s"), SLIC3R_APP_FULL_NAME);
//auto about_item = new wxMenuItem(parent_menu, OrcaSlicerMenuAbout + bambu_studio_id_base, about_title, "");
//parent_menu->Bind(wxEVT_MENU, [this, bambu_studio_id_base](wxEvent& event) {
// switch (event.GetId() - bambu_studio_id_base) {
// case OrcaSlicerMenuAbout:
// Slic3r::GUI::about();
// break;
// case OrcaSlicerMenuPreferences:
// CallAfter([this] {
// PreferencesDialog dlg(this);
// dlg.ShowModal();
//#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
//#else
// if (dlg.seq_top_layer_only_changed())
//#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
// plater()->refresh_print();
// });
// break;
// default:
// break;
// }
//});
//parent_menu->Insert(0, about_item);
append_menu_item(
parent_menu, wxID_ANY, _L(about_title), "",
[](wxCommandEvent &) { Slic3r::GUI::about();},
+1
View File
@@ -66,6 +66,7 @@ class Tab;
class PrintHostQueueDialog;
class Plater;
class MainFrame;
class WebViewPanel;
class ParamsDialog;
#ifdef __WXGTK__
class ResizeEdgePanel;
+3
View File
@@ -2,6 +2,9 @@
#include "ImageGrid.h"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "DeviceManager.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "Plater.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/SwitchButton.hpp"
+6
View File
@@ -3,6 +3,11 @@
#include "Widgets/CheckBox.hpp"
#include "Widgets/Label.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "DeviceManager.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/AppConfig.hpp"
#include "I18N.hpp"
#include "MsgDialog.hpp"
@@ -13,6 +18,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/utf8_codecvt.hpp>
#undef pid_t
#include <boost/process.hpp>
+2 -2
View File
@@ -297,7 +297,7 @@ void MeshClipper::recalculate_triangles()
// it so it lies on our line. This will be the figure to subtract
// from the cut. The coordinates must not overflow after the transform,
// make the rectangle a bit smaller.
const coord_t size = (std::numeric_limits<coord_t>::max()/2 - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4;
const coord_t size = (double(std::numeric_limits<coord_t>::max()/2) - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4;
Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
ep.front().rotate(angle);
ep.front().translate(scale_(-e * a), scale_(-e * b));
@@ -352,7 +352,7 @@ void MeshClipper::recalculate_triangles()
// To prevent overflow after scaling, downscale the input if needed:
double extra_scale = 1.;
coord_t limit = coord_t(std::min(std::numeric_limits<coord_t>::max() / (2. * std::max(1., scale_x)), std::numeric_limits<coord_t>::max() / (2. * std::max(1., scale_y))));
coord_t limit = coord_t(std::min(double(std::numeric_limits<coord_t>::max()) / (2. * std::max(1., scale_x)), double(std::numeric_limits<coord_t>::max()) / (2. * std::max(1., scale_y))));
coord_t max_coord = 0;
for (const Point& pt : exp.contour)
max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y())));
+1
View File
@@ -1,6 +1,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/PresetBundle.hpp"
#include "Mouse3DController.hpp"
#include "GUI.hpp"
#include "Camera.hpp"
#include "GUI_App.hpp"
+19 -10
View File
@@ -698,10 +698,15 @@ std::string OptionsGroup::pick_plugin(const ConfigOptionDef& opt)
Slic3r::PluginManager& manager = Slic3r::PluginManager::instance();
const Slic3r::PluginCapabilityType plugin_type = Slic3r::plugin_capability_type_from_string(opt.plugin_type);
if (plugin_type == Slic3r::PluginCapabilityType::Unknown) {
const std::string message = opt.plugin_type.empty()
? "This setting does not specify a plugin capability type."
: "This setting specifies an unrecognized plugin capability type: '" + opt.plugin_type + "'.";
wxMessageBox(from_u8(message), _L("Plugin Selection"), wxOK | wxICON_WARNING, m_parent);
MessageDialog dlg(m_parent,
opt.plugin_type.empty() ? _L("This setting does not specify a plugin capability type.")
: _L("This setting specifies an unrecognized plugin capability type: ") + "'" + opt.plugin_type + "'.",
_L("Plugin Selection"),
wxOK | wxICON_WARNING
);
dlg.CenterOnParent();
dlg.ShowModal();
return {};
}
@@ -714,7 +719,13 @@ std::string OptionsGroup::pick_plugin(const ConfigOptionDef& opt)
});
if (caps.empty()) {
wxMessageBox(_L("No plugins capabilities available for this type.\nEnable or install some to use."), _L("Plugin Selection"), wxOK | wxICON_INFORMATION, m_parent);
MessageDialog dlg(m_parent,
_L("No plugins capabilities available for this type.\nEnable or install some to use."),
_L("Plugin Selection"),
wxOK | wxICON_INFORMATION
);
dlg.CenterOnParent();
dlg.ShowModal();
return {};
}
@@ -787,11 +798,9 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
#endif
else if (opt_key == "printer_agent")
{
// why: printer_agent is a coString kept out of m_opt_map. The generic non-opt_map revert
// below restores the edited config from get_value(), but a deregistered/"(missing)" saved
// id has no selectable row, so the field yields no value and the edited config keeps the
// user's interim pick -> stuck dirty. Restore the SAVED id straight into the edited config
// (displayable or not; config is the saved or system baseline), then repaint and notify.
// A deregistered/"(missing)" saved id has no selectable row, so the field yields no
// value. Restore the saved id directly instead of letting the generic revert path read
// the field value back into the edited config.
const std::string saved_id = config.opt_string("printer_agent");
set_value(opt_key, saved_id);
this->change_opt_value(opt_key, saved_id);
-1
View File
@@ -66,7 +66,6 @@ class ParamsPanel : public wxPanel
{
#if __WXOSX__
wxWindow* m_tmp_panel;
int m_size_move = -1;
#endif // __WXOSX__
private:
+7 -6
View File
@@ -17,6 +17,7 @@
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include "libslic3r/libslic3r.h"
@@ -1112,7 +1113,7 @@ void PartPlate::show_tooltip(const std::string tooltip)
{
const auto scale = m_plater->get_current_canvas3D()->get_scale();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {6 * scale, 3 * scale});
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, {3 * scale});
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * scale);
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
ImGui::PushStyleColor(ImGuiCol_Border, {0, 0, 0, 0});
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));
@@ -1716,7 +1717,7 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode, const Dynam
return plate_extruders;
}
std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const
std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config, bool expand_mixed_slots) const
{
std::vector<int> plate_extruders;
@@ -1877,7 +1878,7 @@ std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D
// Expand mixed filament slots to their physical components. A mixed slot is virtual and
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
// physical filaments it resolves to instead.
{
if (expand_mixed_slots) {
auto* is_mixed_opt = full_config.option<ConfigOptionBools>("filament_is_mixed");
auto* comp_strs_opt = full_config.option<ConfigOptionStrings>("filament_mixed_components");
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
@@ -1917,7 +1918,7 @@ std::vector<int> PartPlate::get_extruders_without_support(bool conside_custom_gc
const DynamicPrintConfig& glb_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
if (!contain_instance_totally(obj_idx, 0))
if (!contain_any_instance_totally(obj_idx))
continue;
ModelObject* mo = m_model->objects[obj_idx];
@@ -2088,7 +2089,7 @@ bool PartPlate::check_single_extruder_mixed_filament_risk(const DynamicPrintConf
"which may significantly increase waste and the risk of nozzle / waste-chute clogging.");
for (int obj_idx = 0; obj_idx < (int)m_model->objects.size(); ++obj_idx) {
if (!contain_instance_totally(obj_idx, 0))
if (!contain_any_instance_totally(obj_idx))
continue;
ModelObject *mo = m_model->objects[obj_idx];
int obj_ext = mo->config.has("extruder") ? mo->config.extruder() : 1;
@@ -2307,7 +2308,7 @@ bool PartPlate::check_compatible_of_nozzle_and_filament(const DynamicPrintConfig
return wipe_tower_size;
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
if (!use_global_objects && !contain_instance_totally(obj_idx, 0))
if (!use_global_objects && !contain_any_instance_totally(obj_idx))
continue;
BoundingBoxf3 bbox = m_model->objects[obj_idx]->bounding_box();
+2 -1
View File
@@ -350,7 +350,8 @@ public:
// get used filaments from config, 1 based idx
std::vector<int> get_extruders(bool conside_custom_gcode = false) const;
std::vector<int> get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const;
std::vector<int> get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const;
// expand_mixed_slots = false keeps mixed filament slots as slots instead of their components.
std::vector<int> get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config, bool expand_mixed_slots = true) const;
std::vector<int> get_extruders_without_support(bool conside_custom_gcode = false) const;
// get used filaments from gcode result, 1 based idx
std::vector<int> get_used_filaments();
+1
View File
@@ -1,5 +1,6 @@
#include "GUI_Utils.hpp"
#include "GUI_App.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
+1
View File
@@ -9,6 +9,7 @@
#include <boost/regex.hpp>
#include <wx/sizer.h>
#include <wx/tooltip.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/button.h>
+7 -1
View File
@@ -17839,6 +17839,10 @@ void Plater::increase_instances(size_t num)
model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror());
// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec));
}
// Register the copies with the plate they land on before the scene reloads: the plate's
// filament list and wipe tower preview are read from that registry.
for (size_t i = model_object->instances.size() - num; i < model_object->instances.size(); ++i)
p->partplate_list.notify_instance_update(obj_idx, static_cast<int>(i));
#ifdef SUPPORT_AUTO_CENTER
if (p->get_config("autocenter") == "true")
@@ -17869,8 +17873,10 @@ void Plater::decrease_instances(size_t num)
ModelObject* model_object = p->model.objects[obj_idx];
if (model_object->instances.size() > num) {
for (size_t i = 0; i < num; ++ i)
for (size_t i = 0; i < num; ++ i) {
p->partplate_list.notify_instance_removed(obj_idx, static_cast<int>(model_object->instances.size()) - 1);
model_object->delete_last_instance();
}
p->update();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
sidebar().obj_list()->decrease_object_instances(obj_idx, num);
+1
View File
@@ -42,6 +42,7 @@ class Button;
namespace Slic3r {
class BuildVolume;
class MachineObject;
enum class BuildVolume_Type : char;
class Model;
class ModelObject;
+57 -26
View File
@@ -9,12 +9,16 @@
#include "GUI.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "Widgets/DialogButtons.hpp"
namespace Slic3r { namespace GUI {
PluginPickerDialog::PluginPickerDialog(wxWindow* parent,
const wxString& plugin_type_label,
const std::vector<Slic3r::PluginDescriptor>& plugins)
: wxDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label))
: DPIDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label))
, m_plugins(plugins)
{
build_ui(plugin_type_label);
@@ -24,7 +28,7 @@ PluginPickerDialog::PluginPickerDialog(wxWindow* parent,
PluginPickerDialog::PluginPickerDialog(wxWindow* parent,
const wxString& plugin_type_label,
std::vector<CapabilityEntry> capabilities)
: wxDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label))
: DPIDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label))
, m_capabilities(std::move(capabilities))
{
build_capability_ui(plugin_type_label);
@@ -33,12 +37,18 @@ PluginPickerDialog::PluginPickerDialog(wxWindow* parent,
void PluginPickerDialog::build_ui(const wxString& plugin_type_label)
{
SetBackgroundColour(*wxWHITE);
const bool has_plugins = !m_plugins.empty();
auto* top_sizer = new wxBoxSizer(wxVERTICAL);
auto* info_text = new wxStaticText(this, wxID_ANY,
wxString::Format(_L("Choose a %s plugin from the list below."), plugin_type_label));
top_sizer->Add(info_text, 0, wxALL | wxEXPAND, 10);
info_text->SetFont(Label::Body_14);
info_text->SetForegroundColour(wxColour("#363636"));
top_sizer->Add(info_text, 0, wxALL | wxEXPAND, FromDIP(10));
top_sizer->AddSpacer(FromDIP(5));
wxArrayString choices;
choices.reserve(m_plugins.size());
@@ -49,84 +59,103 @@ void PluginPickerDialog::build_ui(const wxString& plugin_type_label)
choices.Add(label);
}
m_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
m_choice = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY);
for (const wxString &opt : choices) { m_choice->Append(opt); }
if (has_plugins) {
m_choice->SetSelection(0);
m_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent& evt) {
m_choice->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
update_description(evt.GetSelection());
});
} else {
m_choice->Enable(false);
}
top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, 10);
top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(10));
m_description = new wxStaticText(this, wxID_ANY, wxEmptyString);
m_description->SetFont(Label::Body_14);
m_description->SetForegroundColour(wxColour("#363636"));
m_description->Wrap(400);
top_sizer->Add(m_description, 0, wxALL | wxEXPAND, 10);
top_sizer->Add(m_description, 0, wxALL | wxEXPAND, FromDIP(10));
if (has_plugins)
update_description(0);
else
m_description->SetLabel(_L("No plugins found for this type."));
auto* button_sizer = new wxStdDialogButtonSizer();
auto* ok_button = new wxButton(this, wxID_OK);
ok_button->Enable(has_plugins);
button_sizer->AddButton(ok_button);
button_sizer->AddButton(new wxButton(this, wxID_CANCEL));
button_sizer->Realize();
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
top_sizer->Add(button_sizer, 0, wxALL | wxALIGN_RIGHT, 10);
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); });
dlg_btns->GetOK()->Enable(has_plugins);
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
top_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizerAndFit(top_sizer);
wxGetApp().UpdateDlgDarkUI(this);
}
void PluginPickerDialog::build_capability_ui(const wxString& plugin_type_label)
{
SetBackgroundColour(*wxWHITE);
const bool has_capabilities = !m_capabilities.empty();
auto* top_sizer = new wxBoxSizer(wxVERTICAL);
auto* info_text = new wxStaticText(this, wxID_ANY,
wxString::Format(_L("Choose a %s plugin from the list below."), plugin_type_label));
top_sizer->Add(info_text, 0, wxALL | wxEXPAND, 10);
info_text->SetFont(Label::Body_14);
info_text->SetForegroundColour(wxColour("#363636"));
top_sizer->Add(info_text, 0, wxALL | wxEXPAND, FromDIP(10));
top_sizer->AddSpacer(FromDIP(5));
wxArrayString choices;
choices.reserve(m_capabilities.size());
for (const auto& cap : m_capabilities)
choices.Add(cap.label);
m_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
m_choice = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY);
for (const wxString &opt : choices) { m_choice->Append(opt); }
if (has_capabilities) {
m_choice->SetSelection(0);
m_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent& evt) {
m_choice->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
update_capability_description(evt.GetSelection());
});
} else {
m_choice->Enable(false);
}
top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, 10);
top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(10));
m_description = new wxStaticText(this, wxID_ANY, wxEmptyString);
m_description->SetFont(Label::Body_14);
m_description->SetForegroundColour(wxColour("#363636"));
m_description->Wrap(400);
top_sizer->Add(m_description, 0, wxALL | wxEXPAND, 10);
top_sizer->Add(m_description, 0, wxALL | wxEXPAND, FromDIP(10));
if (has_capabilities)
update_capability_description(0);
else
m_description->SetLabel(_L("No plugins found for this type."));
auto* button_sizer = new wxStdDialogButtonSizer();
auto* ok_button = new wxButton(this, wxID_OK);
ok_button->Enable(has_capabilities);
button_sizer->AddButton(ok_button);
button_sizer->AddButton(new wxButton(this, wxID_CANCEL));
button_sizer->Realize();
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
top_sizer->Add(button_sizer, 0, wxALL | wxALIGN_RIGHT, 10);
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); });
dlg_btns->GetOK()->Enable(has_capabilities);
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
top_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizerAndFit(top_sizer);
wxGetApp().UpdateDlgDarkUI(this);
}
PluginPickerDialog::CapabilityEntry PluginPickerDialog::selected_capability() const
@@ -185,4 +214,6 @@ void PluginPickerDialog::update_description(int selection)
Layout();
}
void PluginPickerDialog::on_dpi_changed(const wxRect &suggested_rect) {}
}} // namespace Slic3r::GUI
+7 -2
View File
@@ -11,9 +11,12 @@
#include "slic3r/plugin/PluginManager.hpp"
#include "GUI_Utils.hpp"
#include "Widgets/ComboBox.hpp"
namespace Slic3r { namespace GUI {
class PluginPickerDialog : public wxDialog
class PluginPickerDialog : public DPIDialog
{
public:
// Entry for capability-level selection (plugin_type non-empty path).
@@ -40,13 +43,15 @@ public:
// Returns the {plugin_key, name} of the selected capability (capability path).
CapabilityEntry selected_capability() const;
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
void build_ui(const wxString& plugin_type_label);
void build_capability_ui(const wxString& plugin_type_label);
void update_description(int selection);
void update_capability_description(int selection);
wxChoice* m_choice { nullptr };
ComboBox* m_choice { nullptr };
wxStaticText* m_description { nullptr };
std::vector<Slic3r::PluginDescriptor> m_plugins;
std::vector<CapabilityEntry> m_capabilities;
+1
View File
@@ -1,6 +1,7 @@
#include "PluginsConfigDialog.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "format.hpp"
+1
View File
@@ -2,6 +2,7 @@
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "I18N.hpp"
#include "OrcaCloudServiceAgent.hpp"
#include "slic3r/plugin/PluginConfig.hpp"
+4 -3
View File
@@ -2,6 +2,7 @@
#define slic3r_PluginsDialog_hpp_
#include "Widgets/WebViewHostDialog.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "PluginSource.hpp"
#include "PluginStatus.hpp"
#include "PluginSort.hpp"
@@ -107,12 +108,12 @@ private:
const wxString& title,
const wxString& message,
int maximum = 100,
int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE,
int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE, // | wxPD_CAN_ABORT for cancel button
bool finish_after_dialog_destroyed = false)
{
const auto alive = m_alive;
wxProgressDialog* progress = new wxProgressDialog(title, message, maximum, this, style);
wxTimer* timer = new wxTimer();
ProgressDialog* progress = new ProgressDialog(title, message, maximum, this, style);
wxTimer* timer = new wxTimer();
timer->Bind(wxEVT_TIMER, [alive, progress, message](wxTimerEvent&) {
if (alive->load(std::memory_order_acquire) && progress)
+4 -20
View File
@@ -2,6 +2,7 @@
#include "OptionsGroup.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "WebViewDialog.hpp"
#include "Plater.hpp"
#include "GLCanvas3D.hpp" // ORCA: for live preview refresh when toggling "Dim lower layers"
#include "MsgDialog.hpp"
@@ -507,26 +508,14 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS
}
}
// the dialog needs to be destroyed before the call to switch_language()
// or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope
MessageDialog msg_wingow(nullptr, _L("Switching languages requires the application to restart.\n") + "\n" + _L("Do you want to continue?"),
L("Language selection"), wxICON_QUESTION | wxOK | wxCANCEL);
if (msg_wingow.ShowModal() == wxID_CANCEL) {
MessageDialog msg_window(nullptr, _L("Switching languages requires the application to restart.\n") + "\n" + _L("Do you want to continue?"),
_L("Language selection"), wxICON_QUESTION | wxOK | wxCANCEL);
if (msg_window.ShowModal() == wxID_CANCEL) {
combobox->SetSelection(m_current_language_selected);
return;
}
}
auto check = [](bool yes_or_no) {
// if (yes_or_no)
// return true;
int act_btns = ActionButtons::SAVE;
return wxGetApp().check_and_keep_current_preset_changes(_L("Switching application language"),
_L("Switching application language while some presets are modified."), act_btns);
};
m_current_language_selected = combobox->GetSelection();
if (m_current_language_selected >= 0 && m_current_language_selected < vlist.size()) {
m_pending_language = vlist[m_current_language_selected]->CanonicalName.ToUTF8().data();
@@ -1031,11 +1020,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
app_config->set_bool(param, checkbox->GetValue());
app_config->save();
// if (param == "staff_pick_switch") {
// bool pbool = app_config->get("staff_pick_switch") == "true";
// wxGetApp().switch_staff_pick(pbool);
// }
if (param == "sync_user_preset") {
bool sync = app_config->get("sync_user_preset") == "true" ? true : false;
if (sync) {
+1 -1
View File
@@ -1803,7 +1803,7 @@ static void* get_function(const char* name)
return function;
#if defined(_MSC_VER) || defined(_WIN32)
function = GetProcAddress(module, name);
function = reinterpret_cast<void*>(GetProcAddress(module, name));
#else
function = dlsym(module, name);
#endif

Some files were not shown because too many files have changed in this diff Show More