mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-17 14:02:35 +00:00
Merge branch 'main' into feat/printer-agent-infra
This commit is contained in:
+105
-7
@@ -53,6 +53,7 @@ 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"
|
||||
@@ -162,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."}
|
||||
@@ -1385,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;
|
||||
@@ -2008,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);
|
||||
@@ -3700,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++) {
|
||||
@@ -3709,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;
|
||||
@@ -3937,6 +3969,22 @@ int CLI::run(int argc, char **argv)
|
||||
// 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");
|
||||
@@ -3991,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)
|
||||
{
|
||||
@@ -5312,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5901,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) {
|
||||
@@ -6197,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) {
|
||||
@@ -7649,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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2825,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()
|
||||
|
||||
+23
-13
@@ -1595,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()
|
||||
{
|
||||
@@ -1664,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -485,7 +485,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;
|
||||
@@ -494,15 +494,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())
|
||||
@@ -550,30 +550,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";
|
||||
@@ -592,6 +573,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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
+40
-177
@@ -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"));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -2098,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();
|
||||
@@ -4491,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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1717,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;
|
||||
|
||||
@@ -1878,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)) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1037,9 +1037,6 @@ size_t PublishSettingsDialog::section_group_for(Section kind)
|
||||
section.mixed_tabs = new TabCtrl(section.page, wxID_ANY, wxDefaultPosition, wxDefaultSize, s_tab_style);
|
||||
section.mixed_tabs->SetFont(Label::Body_14);
|
||||
section.mixed_tabs->SetBackgroundColour(GetBackgroundColour());
|
||||
// The mixed tabs carry full swatch compositions: give them a touch more room than the
|
||||
// filament tabs so neighbouring compositions stay distinguishable (must precede AppendItem).
|
||||
section.mixed_tabs->SetItemSpace(FromDIP(3));
|
||||
page_sizer->Add(section.mixed_tabs, 0, wxEXPAND | wxTOP, FromDIP(2));
|
||||
section.mixed_tabs->Hide();
|
||||
}
|
||||
|
||||
@@ -311,8 +311,11 @@ void Button::render(wxDC& dc)
|
||||
}
|
||||
}
|
||||
auto szContent = textSize;
|
||||
// Whether the measured content reserved the text/icon gap. macOS measures an empty label
|
||||
// as 0-high, so the gap is skipped there; the dot must not advance past it in that case.
|
||||
const bool gap_reserved = szContent.y > 0;
|
||||
if (icon.bmp().IsOk()) {
|
||||
if (szContent.y > 0) {
|
||||
if (gap_reserved) {
|
||||
//BBS norrow size between text and icon
|
||||
if (vertical)
|
||||
szContent.y += spacing;
|
||||
@@ -357,10 +360,10 @@ void Button::render(wxDC& dc)
|
||||
dc.DrawBitmap(icon.bmp(), pt);
|
||||
//BBS norrow size between text and icon
|
||||
if (vertical) {
|
||||
pt.y += szIcon.y + spacing;
|
||||
pt.y += szIcon.y + (gap_reserved ? spacing : 0);
|
||||
pt.x = rcContent.x;
|
||||
} else {
|
||||
pt.x += szIcon.x + spacing;
|
||||
pt.x += szIcon.x + (gap_reserved ? spacing : 0);
|
||||
pt.y = rcContent.y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ int TabCtrl::AppendItem(const wxString& item, int image, int selImage, void* cli
|
||||
btns.push_back(btn);
|
||||
if (btns.size() > 1)
|
||||
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
|
||||
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, item_space);
|
||||
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
sizer->AddStretchSpacer(1);
|
||||
relayout();
|
||||
return btns.size() - 1;
|
||||
@@ -256,12 +256,12 @@ void TabCtrl::relayout()
|
||||
int item = sel + 1;
|
||||
int first = 0;
|
||||
for (int i = 0; i < item; ++i)
|
||||
offset += btns[i]->GetMinSize().x + item_space * 2;
|
||||
offset += btns[i]->GetMinSize().x;
|
||||
if (item < btns.size())
|
||||
offset += btns[item]->GetMinSize().x + item_space * 2;
|
||||
offset += btns[item]->GetMinSize().x;
|
||||
int width = GetSize().x;
|
||||
for (int i = 0; i < btns.size(); ++i) {
|
||||
auto size = btns[i]->GetMinSize().x + item_space * 2;
|
||||
auto size = btns[i]->GetMinSize().x;
|
||||
if (i < sel && offset > width) {
|
||||
sizer->Show(i * 2 + 1, false);
|
||||
sizer->Show(i * 2 + 2, false);
|
||||
@@ -284,26 +284,17 @@ void TabCtrl::relayout()
|
||||
if (item >= btns.size())
|
||||
--item;
|
||||
// Keep spacing 2 ~ 10 TAB_BUTTON_SPACE
|
||||
int b = GetSize().x - offset - 10 - (item + 1 - first) * item_space * 8;
|
||||
int b = GetSize().x - offset - 10 - (item + 1 - first) * 16;
|
||||
sizer->GetItem(item * 2 + 2)->SetMinSize({b > 0 ? b : 0, 0});
|
||||
Layout();
|
||||
}
|
||||
|
||||
void TabCtrl::SetItemSpace(int space)
|
||||
{
|
||||
if (space < 0 || space == item_space)
|
||||
return;
|
||||
item_space = space;
|
||||
relayout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
int TabCtrl::GetFullSize() const
|
||||
{
|
||||
// Mirrors relayout(): a 10px leading spacer plus every button's min width and spacing.
|
||||
// Mirrors relayout(): a 10px leading spacer plus every button's min width.
|
||||
int width = 10;
|
||||
for (const Button* btn : btns)
|
||||
width += btn->GetMinSize().x + item_space * 2;
|
||||
width += btn->GetMinSize().x;
|
||||
return width;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ class TabCtrl : public StaticBox
|
||||
|
||||
int sel = -1;
|
||||
wxFont bold;
|
||||
int item_space = 2; // space around each button, both sides (SetItemSpace)
|
||||
|
||||
public:
|
||||
TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0);
|
||||
@@ -64,10 +63,6 @@ public:
|
||||
int GetNextVisible(int item) const;
|
||||
bool IsVisible(unsigned int item) const;
|
||||
|
||||
// Extra space around each tab button (in px on both sides). Defaults to the control-wide
|
||||
// standard; call before appending items so every button picks it up.
|
||||
void SetItemSpace(int space);
|
||||
|
||||
int GetFullSize() const;
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user