mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 00:31:19 +00:00
Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl
This commit is contained in:
+31
-5
@@ -5527,12 +5527,28 @@ int CLI::run(int argc, char **argv)
|
||||
//add the virtual object into unselect list if has
|
||||
partplate_list.preprocess_exclude_areas(unselected, enable_wrapping_detect);
|
||||
|
||||
if (used_filament_set.size() > 0)
|
||||
// Filament ids given on the command line size the tower for STL input. A project
|
||||
// records its filament use per plate, so count there and keep its tower positions.
|
||||
const int plate_count = partplate_list.get_plate_count();
|
||||
const bool from_project = used_filament_set.empty();
|
||||
std::vector<int> plate_filament_counts(plate_count, static_cast<int>(used_filament_set.size()));
|
||||
if (from_project)
|
||||
for (int plate_index = 0; plate_index < plate_count; ++plate_index)
|
||||
plate_filament_counts[plate_index] = static_cast<int>(partplate_list.get_plate(plate_index)->get_extruders_under_cli(true, m_print_config).size());
|
||||
// A project only gets a tower the slicer will print: the prime tower enabled, and not
|
||||
// a by-object print unless a smooth timelapse needs it, as the per-plate arrange decides.
|
||||
const bool project_tower_allowed = m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value &&
|
||||
(is_smooth_timelapse || !arrange_cfg.is_seq_print);
|
||||
const auto plate_needs_wipe_tower = [from_project, project_tower_allowed, is_smooth_timelapse](int filament_count) {
|
||||
if (!from_project)
|
||||
return filament_count > 0;
|
||||
return project_tower_allowed && (filament_count > 1 || (filament_count > 0 && is_smooth_timelapse));
|
||||
};
|
||||
const int max_filament_count = plate_count > 0 ? *std::max_element(plate_filament_counts.begin(), plate_filament_counts.end()) : 0;
|
||||
|
||||
if (plate_needs_wipe_tower(max_filament_count))
|
||||
{
|
||||
//prepare the wipe tower
|
||||
int plate_count = partplate_list.get_plate_count();
|
||||
int extruder_size = used_filament_set.size();
|
||||
|
||||
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
|
||||
// This margin only pre-adjusts the default away from the near edges;
|
||||
// estimate_wipe_tower_polygon below computes the real clamped position.
|
||||
@@ -5568,7 +5584,11 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
for (int bedid = 0; bedid < MAX_PLATE_COUNT; bedid++) {
|
||||
int plate_index_valid = std::min(bedid, plate_count - 1);
|
||||
if (bedid < plate_count) {
|
||||
// Overflow beds may receive objects from any plate, so size them for the busiest one.
|
||||
const int extruder_size = bedid < plate_count ? plate_filament_counts[bedid] : max_filament_count;
|
||||
if (!plate_needs_wipe_tower(extruder_size))
|
||||
continue;
|
||||
if (bedid < plate_count && !from_project) {
|
||||
wipe_x_option->set_at(&wt_x_opt, plate_index_valid, 0);
|
||||
wipe_y_option->set_at(&wt_y_opt, plate_index_valid, 0);
|
||||
}
|
||||
@@ -7024,6 +7044,12 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
sliced_info.sliced_plates.push_back(sliced_plate_info);
|
||||
} catch (const Slic3r::SlicingErrors &exs) {
|
||||
const std::string message = print_fff ? print_fff->slicing_errors_message(exs) : std::string(exs.what());
|
||||
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate " << index+1 << ": " << message;
|
||||
boost::nowide::cerr << message << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_SLICING_ERROR, index+1, message, sliced_info);
|
||||
flush_and_exit(CLI_SLICING_ERROR);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate "<<index+1 << std::endl;
|
||||
boost::nowide::cerr << ex.what() << std::endl;
|
||||
|
||||
@@ -28,6 +28,12 @@ if (ORCA_TOOLS)
|
||||
target_link_libraries(generate_system_cache libslic3r boost_headeronly)
|
||||
target_compile_definitions(generate_system_cache PRIVATE ${_DEV_DEFS})
|
||||
|
||||
# profile_include_dump: prints what included templates contribute to a vendor's presets,
|
||||
# to diff against the same tool built in BambuStudio. Built only on request.
|
||||
add_executable(profile_include_dump EXCLUDE_FROM_ALL profile_include_dump.cpp)
|
||||
target_link_libraries(profile_include_dump libslic3r boost_headeronly)
|
||||
target_compile_definitions(profile_include_dump PRIVATE ${_DEV_DEFS})
|
||||
|
||||
endif()
|
||||
|
||||
# Function that adds source file encoding check to a target
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
// Prints what the profile `include` key contributes to one vendor's system
|
||||
// presets, so that the dump from OrcaSlicer and the one from BambuStudio can be
|
||||
// diffed: the two copies of this file differ only in how they load the vendor.
|
||||
// For every preset whose values pass through an included template, from its
|
||||
// own file or an ancestor's, it prints the templates those files name, in the
|
||||
// order they apply, then the loaded value of each key the templates set. Every
|
||||
// line starts with the preset, so a plain diff names the preset of each
|
||||
// difference.
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace fs = boost::filesystem;
|
||||
using nlohmann::json;
|
||||
|
||||
namespace {
|
||||
|
||||
// What a sub-file states about where its values come from, and what it sets.
|
||||
struct Entry
|
||||
{
|
||||
std::string inherits;
|
||||
std::vector<std::string> includes;
|
||||
std::vector<std::string> keys;
|
||||
};
|
||||
|
||||
// Sub-files by name, per section of the vendor index: `inherits` and `include`
|
||||
// both resolve within a section.
|
||||
using Sections = std::map<std::string, std::map<std::string, Entry>>;
|
||||
|
||||
json read_json(const fs::path &path)
|
||||
{
|
||||
boost::nowide::ifstream in(path.string());
|
||||
if (!in)
|
||||
throw std::runtime_error("Cannot read " + path.string());
|
||||
return json::parse(in);
|
||||
}
|
||||
|
||||
Sections read_vendor(const fs::path &dir, const std::string &vendor)
|
||||
{
|
||||
// Keys that name and place a sub-file rather than set a value.
|
||||
static const std::set<std::string> metadata = {"name", "type", "from", "instantiation", "inherits", "include"};
|
||||
Sections sections;
|
||||
const json index = read_json(dir / (vendor + ".json"));
|
||||
for (const char *section : {"process_list", "filament_list", "machine_list"}) {
|
||||
const auto list = index.find(section);
|
||||
if (list == index.end())
|
||||
continue;
|
||||
for (const json &item : *list) {
|
||||
const json file = read_json(dir / vendor / item.at("sub_path").get<std::string>());
|
||||
// A file without a name goes by its name in the index.
|
||||
Entry &entry = sections[section][file.value("name", item.at("name").get<std::string>())];
|
||||
entry.inherits = file.value("inherits", "");
|
||||
if (const auto include = file.find("include"); include != file.end()) {
|
||||
if (include->is_string())
|
||||
entry.includes.push_back(include->get<std::string>());
|
||||
else
|
||||
for (const json &name : *include)
|
||||
entry.includes.push_back(name.get<std::string>());
|
||||
}
|
||||
for (auto it = file.begin(); it != file.end(); ++it)
|
||||
if (metadata.count(it.key()) == 0)
|
||||
entry.keys.push_back(it.key());
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
const PresetCollection &presets_of(const PresetBundle &bundle, const std::string §ion)
|
||||
{
|
||||
if (section == "process_list")
|
||||
return bundle.prints;
|
||||
if (section == "filament_list")
|
||||
return bundle.filaments;
|
||||
return bundle.printers;
|
||||
}
|
||||
|
||||
size_t dump(const PresetBundle &bundle, const Sections §ions, std::ostream &out)
|
||||
{
|
||||
size_t dumped = 0;
|
||||
for (const auto &[section, entries] : sections) {
|
||||
std::vector<const Preset *> presets;
|
||||
for (const Preset &preset : presets_of(bundle, section).get_presets())
|
||||
if (preset.is_system)
|
||||
presets.push_back(&preset);
|
||||
std::sort(presets.begin(), presets.end(), [](const Preset *a, const Preset *b) { return a->name < b->name; });
|
||||
for (const Preset *preset : presets) {
|
||||
// The preset and its ancestors, root first: the order their includes apply in.
|
||||
std::vector<const Entry *> lineage;
|
||||
for (auto it = entries.find(preset->name); it != entries.end() && lineage.size() <= entries.size();
|
||||
it = entries.find(it->second.inherits))
|
||||
lineage.insert(lineage.begin(), &it->second);
|
||||
std::string templates;
|
||||
std::set<std::string> keys;
|
||||
for (const Entry *entry : lineage)
|
||||
for (const std::string &name : entry->includes) {
|
||||
templates += (templates.empty() ? "" : "; ") + name;
|
||||
if (const auto it = entries.find(name); it != entries.end())
|
||||
keys.insert(it->second.keys.begin(), it->second.keys.end());
|
||||
}
|
||||
if (templates.empty())
|
||||
continue;
|
||||
const std::string prefix = section.substr(0, section.find('_')) + " | " + preset->name + " | ";
|
||||
out << prefix << "include = " << templates << "\n";
|
||||
for (const std::string &key : keys)
|
||||
out << prefix << key << " = " << (preset->config.has(key) ? preset->config.opt_serialize(key) : "<absent>") << "\n";
|
||||
++dumped;
|
||||
}
|
||||
}
|
||||
return dumped;
|
||||
}
|
||||
|
||||
// Orca: load the vendor as the app does, against the filament library when the
|
||||
// directory has one: parsed from its JSON files or, with from_cache, from the
|
||||
// preset cache the app reads on every launch after the first.
|
||||
void load_vendor(PresetBundle &bundle, const std::string &dir, const std::string &vendor, bool from_cache)
|
||||
{
|
||||
const auto rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
|
||||
PresetBundle library;
|
||||
const PresetBundle *base = nullptr;
|
||||
if (fs::is_regular_file(fs::path(dir) / (std::string(PresetBundle::ORCA_FILAMENT_LIBRARY) + ".json"))) {
|
||||
library.load_vendor_configs_from_json(dir, PresetBundle::ORCA_FILAMENT_LIBRARY, PresetBundle::LoadSystem, rule, nullptr, false);
|
||||
base = &library;
|
||||
}
|
||||
if (!from_cache) {
|
||||
bundle.load_vendor_configs_from_json(dir, vendor, PresetBundle::LoadSystem, rule, base, false);
|
||||
return;
|
||||
}
|
||||
// Parse once to write <vendor>.opc beside the profile, then load that
|
||||
// cache into a clean bundle. Any cache already there goes first, so that a
|
||||
// failed write cannot leave it to be loaded; one that was not there before
|
||||
// goes again afterwards.
|
||||
const fs::path cache = fs::path(dir) / (vendor + ".opc");
|
||||
const bool had_cache = fs::exists(cache);
|
||||
fs::remove(cache);
|
||||
PresetBundle parsed;
|
||||
parsed.set_is_validation_mode(true); // parse the JSON: validation never serves a cache
|
||||
parsed.set_generate_vendor_caches(true);
|
||||
parsed.load_vendor_configs_from_json(dir, vendor, PresetBundle::LoadSystem, rule, base);
|
||||
const bool loaded = bundle.load_vendor_cache(cache.string(), vendor, parsed.vendors.at(vendor).config_version, base);
|
||||
if (!had_cache)
|
||||
fs::remove(cache);
|
||||
if (!loaded)
|
||||
throw std::runtime_error("The preset cache " + cache.string() + " was not written or was rejected");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: profile_include_dump -p <profiles dir> -o <dump file> [-v <vendor>] [-l <log level>] [-c]\n"
|
||||
" -v vendor to dump, BBL if omitted\n"
|
||||
" -l log level, 0 (fatal) to 5 (trace); 1 if omitted\n"
|
||||
" -c load the presets from the vendor's preset cache, generated first, not the JSON\n";
|
||||
std::string dir, output, vendor = "BBL";
|
||||
unsigned log_level = 1;
|
||||
bool from_cache = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "-c") {
|
||||
from_cache = true;
|
||||
continue;
|
||||
}
|
||||
if (i + 1 == argc) {
|
||||
std::cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
const std::string value = argv[++i];
|
||||
if (arg == "-p")
|
||||
dir = value;
|
||||
else if (arg == "-o")
|
||||
output = value;
|
||||
else if (arg == "-v")
|
||||
vendor = value;
|
||||
else if (arg == "-l" && value.size() == 1 && value[0] >= '0' && value[0] <= '5')
|
||||
log_level = unsigned(value[0] - '0');
|
||||
else {
|
||||
std::cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (dir.empty() || output.empty() || !fs::is_directory(dir)) {
|
||||
std::cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
set_logging_level(log_level);
|
||||
|
||||
try {
|
||||
PresetBundle bundle;
|
||||
load_vendor(bundle, dir, vendor, from_cache);
|
||||
boost::nowide::ofstream out(output);
|
||||
if (!out)
|
||||
throw std::runtime_error("Cannot write " + output);
|
||||
const size_t dumped = dump(bundle, read_vendor(dir, vendor), out);
|
||||
out.close();
|
||||
if (!out)
|
||||
throw std::runtime_error("Failed writing " + output);
|
||||
std::cerr << "Dumped " << dumped << " " << vendor << " presets that include a template\n";
|
||||
} catch (const std::exception &ex) {
|
||||
std::cerr << ex.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -2673,10 +2673,10 @@ void CadDocument::apply_feature(TopoDS_Shape& result, bool& have_body,
|
||||
ridge = pipe.Shape();
|
||||
have_ridge = !ridge.IsNull();
|
||||
}
|
||||
} catch (const Standard_Failure&) {
|
||||
have_ridge = false; // OCCT failure — on OCCT >= 8 Standard_Failure derives from std::exception, so this handler must come first
|
||||
} catch (const std::exception&) {
|
||||
have_ridge = false; // fall back to the bare cylinder/bore below
|
||||
} catch (const Standard_Failure&) {
|
||||
have_ridge = false; // OCCT failure (not a std::exception) — must be caught here too
|
||||
}
|
||||
|
||||
if (f.thread_internal) {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <TopExp.hxx>
|
||||
#include <TopTools.hxx>
|
||||
#include <TopTools_IndexedMapOfShape.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
|
||||
#include <Poly_Triangulation.hxx>
|
||||
#include <gp_Ax2.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
|
||||
@@ -950,6 +950,9 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
|
||||
}
|
||||
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INHERITS)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INHERITS, it.value());
|
||||
}
|
||||
else if (!load_inherits_to_config && boost::iequals(it.key(), BBL_JSON_KEY_INCLUDES)) {
|
||||
key_values.emplace(BBL_JSON_KEY_INCLUDES, it.value().dump());
|
||||
} else if (boost::iequals(it.key(), ORCA_JSON_KEY_RENAMED_FROM)) {
|
||||
key_values.emplace(ORCA_JSON_KEY_RENAMED_FROM, it.value());
|
||||
} else {
|
||||
|
||||
@@ -697,7 +697,8 @@ public:
|
||||
}
|
||||
} else {
|
||||
// Resize by duplicating the last value.
|
||||
this->values.resize(n, this->values./*back*/front());
|
||||
T v = this->values./*back*/front();
|
||||
this->values.resize(n, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,8 +773,10 @@ public:
|
||||
|
||||
if (this->values.empty())
|
||||
this->values.resize(rhs_vec->size());
|
||||
else
|
||||
this->values.resize(rhs_vec->size(), this->values.front());
|
||||
else {
|
||||
T v = this->values.front();
|
||||
this->values.resize(rhs_vec->size(), v);
|
||||
}
|
||||
|
||||
assert(default_index.size() == rhs_vec->size());
|
||||
|
||||
|
||||
@@ -990,6 +990,7 @@ EmbossStyles Emboss::get_font_list_by_enumeration() {
|
||||
std::vector<std::wstring> font_names;
|
||||
EnumFontFamilies(hDC, (LPCTSTR) NULL, EnumFamCallBack,
|
||||
(LPARAM) &font_names);
|
||||
ReleaseDC(NULL, hDC);
|
||||
|
||||
EmbossStyles font_list;
|
||||
for (const std::wstring &font_name : font_names) {
|
||||
|
||||
@@ -423,6 +423,74 @@ bool ExtrusionLoop::is_smooth(double angle_threshold, double min_arm_length) con
|
||||
return true;
|
||||
}
|
||||
|
||||
// The seam is inserted into the loop unless a vertex lies within the G-code resolution of it, so a
|
||||
// loop can begin and end with a segment of a few micrometres. A plain loop stops there anyway; a
|
||||
// scarf extrudes through both ends, and the planner nearly halts on a block that short. Drop the
|
||||
// vertex next to the seam point instead, so the loop still starts and ends at the seam. A trimmed
|
||||
// path loses its arc fitting; its geometry is unchanged, it just prints as line segments.
|
||||
static void trim_seam_ends(ExtrusionPaths &paths, double tolerance)
|
||||
{
|
||||
const auto shorter = [tolerance](const Point3 &a, const Point3 &b) { return (b - a).cast<double>().norm() < tolerance; };
|
||||
|
||||
while (!paths.empty()) {
|
||||
Points3 &points = paths.front().polyline.points;
|
||||
if (points.size() < 2 || !shorter(points[0], points[1]))
|
||||
break;
|
||||
if (points.size() > 2) {
|
||||
points.erase(points.begin() + 1);
|
||||
paths.front().polyline.fitting_result.clear();
|
||||
} else if (paths.size() > 1) {
|
||||
const Point3 seam = points.front();
|
||||
paths.erase(paths.begin());
|
||||
paths.front().polyline.points.front() = seam;
|
||||
paths.front().polyline.fitting_result.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (!paths.empty()) {
|
||||
Points3 &points = paths.back().polyline.points;
|
||||
if (points.size() < 2 || !shorter(points[points.size() - 2], points.back()))
|
||||
break;
|
||||
if (points.size() > 2) {
|
||||
points.erase(points.end() - 2);
|
||||
paths.back().polyline.fitting_result.clear();
|
||||
} else if (paths.size() > 1) {
|
||||
const Point3 seam = points.back();
|
||||
paths.pop_back();
|
||||
paths.back().polyline.points.back() = seam;
|
||||
paths.back().polyline.fitting_result.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Split `polyline` where the scarf ramp ends. When the split would leave a remainder shorter
|
||||
// than half a slope step before the next vertex, the ramp is extended to that vertex instead:
|
||||
// a stub that short makes the motion planner slow down at the end of the ramp. Planners treat
|
||||
// moves of a millimetre and more as ordinary, so the ramp never grows by more than that.
|
||||
static void split_at_slope_end(const Polyline3 &polyline, double length, double slope_max_segment_length, Polyline3 &slope, Polyline3 &flat)
|
||||
{
|
||||
const double snap_distance = std::min(0.5 * slope_max_segment_length, scale_(1.));
|
||||
double acc_length = 0.;
|
||||
size_t line_idx = 0;
|
||||
for (const Line3 &line : polyline.lines()) {
|
||||
const double end_length = acc_length + line.length();
|
||||
if (end_length >= length) {
|
||||
if (end_length - length < snap_distance) {
|
||||
polyline.split_at_index(line_idx + 1, &slope, &flat);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
acc_length = end_length;
|
||||
++line_idx;
|
||||
}
|
||||
polyline.split_at_length(length, &slope, &flat);
|
||||
}
|
||||
|
||||
ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
|
||||
double seam_gap,
|
||||
double slope_min_length,
|
||||
@@ -431,6 +499,14 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
|
||||
ExtrusionLoopRole role)
|
||||
: ExtrusionLoop(role)
|
||||
{
|
||||
// An eighth of a common line width: the path moves by less than that at the seam.
|
||||
trim_seam_ends(original_paths, scale_(0.05));
|
||||
// The caller measured the loop before the trim; a scarf that covers the whole loop must still end at 1.
|
||||
double trimmed_length = 0.;
|
||||
for (const ExtrusionPath &path : original_paths)
|
||||
trimmed_length += unscale_(path.length());
|
||||
slope_min_length = std::min(slope_min_length, trimmed_length);
|
||||
|
||||
// create slopes
|
||||
const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline3 &poly, double ratio_begin, double ratio_end) {
|
||||
if (poly.empty()) { return; }
|
||||
@@ -487,12 +563,13 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
|
||||
// Split current path into slope and non-slope part
|
||||
Polyline3 slope_path;
|
||||
Polyline3 flat_path;
|
||||
path->polyline.split_at_length(scale_(remaining_length), &slope_path, &flat_path);
|
||||
split_at_slope_end(path->polyline, scale_(remaining_length), slope_max_segment_length, slope_path, flat_path);
|
||||
|
||||
add_slop(*path, slope_path, start_ratio, 1);
|
||||
start_ratio = 1;
|
||||
|
||||
paths.emplace_back(std::move(flat_path), *path);
|
||||
if (flat_path.size() > 1)
|
||||
paths.emplace_back(std::move(flat_path), *path);
|
||||
remaining_length = 0;
|
||||
} else {
|
||||
remaining_length -= path_len;
|
||||
|
||||
@@ -464,6 +464,16 @@ void group_region_by_fuzzify(PerimeterGenerator& g)
|
||||
}
|
||||
}
|
||||
|
||||
g.fuzzy_supported_area.reset();
|
||||
if ((g.has_fuzzy_skin || g.has_fuzzy_hole) && g.lower_slices != nullptr) {
|
||||
coord_t max_thickness = 0;
|
||||
for (const auto& region : regions)
|
||||
if (should_fuzzify(region.config, g.layer_id, 0, true) || should_fuzzify(region.config, g.layer_id, 0, false))
|
||||
max_thickness = std::max(max_thickness, region.config.thickness);
|
||||
// Walls farther than a line width plus the noise amplitude from the layer below are bridging; keep them smooth.
|
||||
g.fuzzy_supported_area = offset_ex(*g.lower_slices, float(g.ext_perimeter_flow.scaled_width() + max_thickness));
|
||||
}
|
||||
|
||||
if (regions.size() == 1) { // optimization
|
||||
g.regions_by_fuzzify.push_back({regions.front().config, {}});
|
||||
return;
|
||||
@@ -560,13 +570,23 @@ static std::vector<MergedFuzzyRegion> collect_merged_fuzzy_regions(const std::ve
|
||||
return merged_regions;
|
||||
}
|
||||
|
||||
// Afterwards an empty region means nothing to fuzzify, no longer full coverage.
|
||||
static void restrict_to_supported(std::vector<MergedFuzzyRegion>& merged_regions, const std::optional<ExPolygons>& supported)
|
||||
{
|
||||
if (!supported)
|
||||
return;
|
||||
for (auto& merged_region : merged_regions)
|
||||
merged_region.expolygons = merged_region.expolygons.empty() ? *supported : intersection_ex(merged_region.expolygons, *supported);
|
||||
}
|
||||
|
||||
Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, const size_t loop_idx, const bool is_contour)
|
||||
{
|
||||
Polygon fuzzified;
|
||||
|
||||
const auto slice_z = perimeter_generator.slice_z;
|
||||
const auto& regions = perimeter_generator.regions_by_fuzzify;
|
||||
if (regions.size() == 1) { // optimization
|
||||
const auto& supported = perimeter_generator.fuzzy_supported_area;
|
||||
if (regions.size() == 1 && !supported) { // optimization
|
||||
const auto& config = regions.begin()->first;
|
||||
const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop_idx, is_contour);
|
||||
if (!fuzzify) {
|
||||
@@ -590,7 +610,7 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
|
||||
// Fast path: single merged region — apply directly without splitting
|
||||
if (merged_regions.size() == 1) {
|
||||
const auto& mr = merged_regions.front();
|
||||
if (mr.expolygons.empty()) {
|
||||
if (mr.expolygons.empty() && !supported) {
|
||||
fuzzified = polygon;
|
||||
fuzzy_polyline(fuzzified.points, true, slice_z, *mr.config);
|
||||
return fuzzified;
|
||||
@@ -626,6 +646,8 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
|
||||
if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty())
|
||||
merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons);
|
||||
|
||||
restrict_to_supported(merged_regions, supported);
|
||||
|
||||
// Split the loops into lines with different config, and fuzzy them separately
|
||||
fuzzified = polygon;
|
||||
for (const auto& r : merged_regions) {
|
||||
@@ -689,7 +711,8 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
|
||||
const auto slice_z = perimeter_generator.slice_z;
|
||||
const auto layer_height = perimeter_generator.layer_height;
|
||||
const auto& regions = perimeter_generator.regions_by_fuzzify;
|
||||
if (regions.size() == 1) { // optimization
|
||||
const auto& supported = perimeter_generator.fuzzy_supported_area;
|
||||
if (regions.size() == 1 && !supported) { // optimization
|
||||
const auto& config = regions.begin()->first;
|
||||
const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour);
|
||||
if (fuzzify)
|
||||
@@ -703,7 +726,7 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
|
||||
if (!merged_regions.empty()) {
|
||||
|
||||
// Fast path: single merged region — apply directly without splitting
|
||||
if (merged_regions.size() == 1 && merged_regions.front().expolygons.empty()) {
|
||||
if (merged_regions.size() == 1 && merged_regions.front().expolygons.empty() && !supported) {
|
||||
fuzzy_extrusion_line(extrusion->junctions, slice_z, perimeter_generator.layer_height, *merged_regions.front().config, closed);
|
||||
return;
|
||||
}
|
||||
@@ -753,6 +776,8 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
|
||||
if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty())
|
||||
merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons);
|
||||
|
||||
restrict_to_supported(merged_regions, supported);
|
||||
|
||||
// Split the loops into lines with different config, and fuzzy them separately
|
||||
for (const auto& r : merged_regions) {
|
||||
const auto splitted = Algorithm::split_line(*extrusion, r.expolygons, false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "../AABBTreeLines.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
@@ -14,7 +15,9 @@
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <tuple>
|
||||
|
||||
// Boost pool: Don't use mutexes to synchronize memory allocation.
|
||||
#define BOOST_POOL_NO_MT
|
||||
@@ -1318,6 +1321,564 @@ bool has_no_collinear_lines(const Polylines &polylines)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Non-crossing centerlines for multiline adaptive cubic, see docs/HLSD/multiline-infill.md.
|
||||
namespace noncrossing {
|
||||
|
||||
// y = x() * x + y() in the frame where the sweep family is horizontal.
|
||||
using Lin = Vec2d;
|
||||
|
||||
static const Vec2d family_dir[3] { Vec2d(1., 0.), Vec2d(0.5, 0.5 * sqrt(3.)), Vec2d(0.5, -0.5 * sqrt(3.)) };
|
||||
|
||||
struct SweepLine
|
||||
{
|
||||
Vec2d a, b; // a.x() < b.x()
|
||||
int family;
|
||||
Lin lin;
|
||||
std::vector<std::pair<double, int>> junctions; // (x, junction)
|
||||
};
|
||||
|
||||
struct Junction
|
||||
{
|
||||
Vec2d p;
|
||||
std::vector<int> lines;
|
||||
std::vector<std::pair<int, int>> pairs; // (left, right) line of each path through, bottom-up
|
||||
std::vector<std::pair<int, int>> bends; // (path, bend) of each pair, -1 where it runs straight
|
||||
};
|
||||
|
||||
struct LevelPath
|
||||
{
|
||||
std::vector<Vec2d> verts; // start, bends, end
|
||||
std::vector<int> lines; // line of each piece
|
||||
std::vector<int> junctions; // junction of each bend
|
||||
std::vector<int> turn; // 1 turning up, -1 turning down
|
||||
std::vector<std::vector<Lin>> cuts;
|
||||
std::vector<std::pair<Lin, int>> pushes; // (line, bend) keeping a wall away from a neighbour's cut
|
||||
int start_term { -1 }; // junction where the path stops on another line, or -1
|
||||
int end_term { -1 };
|
||||
};
|
||||
|
||||
// Moves f onto line c (side 1: from below) wherever c lies beyond it, over the stretches overlapping [w0, w1].
|
||||
static void clip_profile(std::vector<Vec2d> &f, const Lin &c, int side, double w0, double w1, double lim0, double lim1, bool cut_at_window, bool drop_past_limits)
|
||||
{
|
||||
const double r0 = std::max(lim0, f.front().x()), r1 = std::min(lim1, f.back().x());
|
||||
if (r1 <= r0)
|
||||
return;
|
||||
const size_t ia = std::upper_bound(f.begin(), f.end(), r0, [](double x, const Vec2d &p) { return x < p.x(); }) - f.begin();
|
||||
const size_t ib = std::lower_bound(f.begin() + ia, f.end(), r1, [](const Vec2d &p, double x) { return p.x() < x; }) - f.begin();
|
||||
auto interpolate = [](const Vec2d &a, const Vec2d &b, double x) { return b.x() > a.x() ? a.y() + (x - a.x()) / (b.x() - a.x()) * (b.y() - a.y()) : b.y(); };
|
||||
std::vector<Vec2d> local{ Vec2d(r0, interpolate(f[ia - 1], f[ia], r0)) };
|
||||
local.insert(local.end(), f.begin() + ia, f.begin() + ib);
|
||||
local.emplace_back(r1, interpolate(f[ib - 1], f[ib], r1));
|
||||
|
||||
const double tol = 1.;
|
||||
auto beyond = [&c, side, tol](const Vec2d &p) { return side * (c.x() * p.x() + c.y() - p.y()) - tol; };
|
||||
std::vector<std::pair<double, double>> stretches;
|
||||
auto add = [&stretches](double x0, double x1) {
|
||||
if (!stretches.empty() && stretches.back().second >= x0)
|
||||
stretches.back().second = x1;
|
||||
else
|
||||
stretches.emplace_back(x0, x1);
|
||||
};
|
||||
for (size_t i = 1; i < local.size(); ++i) {
|
||||
const Vec2d &p = local[i - 1], &q = local[i];
|
||||
if (q.x() <= p.x())
|
||||
continue;
|
||||
const double bp = beyond(p), bq = beyond(q);
|
||||
if (bp > 0. && bq > 0.)
|
||||
add(p.x(), q.x());
|
||||
else if (bp > 0. || bq > 0.) {
|
||||
const double x = p.x() + bp / (bp - bq) * (q.x() - p.x());
|
||||
if (bp > 0.)
|
||||
add(p.x(), x);
|
||||
else
|
||||
add(x, q.x());
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<double, double>> keep;
|
||||
for (auto [x0, x1] : stretches) {
|
||||
if (x1 < w0 || x0 > w1)
|
||||
continue;
|
||||
if (drop_past_limits && ((x0 <= r0 && r0 == lim0) || (x1 >= r1 && r1 == lim1)))
|
||||
continue;
|
||||
keep.emplace_back(cut_at_window ? std::max(x0, w0) : x0, cut_at_window ? std::min(x1, w1) : x1);
|
||||
}
|
||||
if (keep.empty())
|
||||
return;
|
||||
|
||||
auto y_local = [&](double x) {
|
||||
size_t i = 1;
|
||||
while (i + 1 < local.size() && local[i].x() < x)
|
||||
++i;
|
||||
return interpolate(local[i - 1], local[i], x);
|
||||
};
|
||||
std::vector<Vec2d> out(f.begin(), f.begin() + ia);
|
||||
auto push = [&out, tol](double x, double y) {
|
||||
if (out.empty() || x > out.back().x() || std::abs(y - out.back().y()) > 2. * tol)
|
||||
out.emplace_back(x, y);
|
||||
};
|
||||
size_t k = 0;
|
||||
for (const Vec2d &p : local) {
|
||||
for (; k < keep.size() && keep[k].second < p.x(); ++k) {
|
||||
const auto [x0, x1] = keep[k];
|
||||
push(x0, y_local(x0));
|
||||
push(x0, c.x() * x0 + c.y());
|
||||
push(x1, c.x() * x1 + c.y());
|
||||
push(x1, y_local(x1));
|
||||
}
|
||||
if (k < keep.size() && keep[k].first <= p.x() && p.x() <= keep[k].second)
|
||||
continue;
|
||||
push(p.x(), p.y());
|
||||
}
|
||||
for (; k < keep.size(); ++k) {
|
||||
const auto [x0, x1] = keep[k];
|
||||
push(x0, y_local(x0));
|
||||
push(x0, c.x() * x0 + c.y());
|
||||
push(x1, c.x() * x1 + c.y());
|
||||
push(x1, y_local(x1));
|
||||
}
|
||||
for (size_t i = ib; i < f.size(); ++i)
|
||||
push(f[i].x(), f[i].y());
|
||||
f = std::move(out);
|
||||
}
|
||||
|
||||
static std::vector<Vec2d> path_points(const LevelPath &path, const std::vector<SweepLine> &lines, double reach)
|
||||
{
|
||||
std::vector<Vec2d> f = path.verts;
|
||||
const int nb = int(path.junctions.size());
|
||||
auto x_of = [&path](int b) { return path.verts[b + 1].x(); };
|
||||
auto sharp = [&](int b) { return lines[path.lines[b]].family != 0 && lines[path.lines[b + 1]].family != 0; };
|
||||
// The run of bends turning the same way as bend b, up to the neighbouring bends turning the other way.
|
||||
auto window = [&](int b) {
|
||||
int l = b - 1, r = b + 1;
|
||||
while (l >= 0 && path.turn[l] == path.turn[b])
|
||||
--l;
|
||||
while (r < nb && path.turn[r] == path.turn[b])
|
||||
++r;
|
||||
return std::make_pair(l >= 0 ? x_of(l) : f.front().x(), r < nb ? x_of(r) : f.back().x());
|
||||
};
|
||||
// Sharp bends between the slanted lines go last, so they win at the tip of a small triangle.
|
||||
for (int b = 0; b < nb; ++b)
|
||||
if (!sharp(b)) {
|
||||
const auto [w0, w1] = window(b);
|
||||
for (const Lin &c : path.cuts[b])
|
||||
clip_profile(f, c, path.turn[b], w0, w1, x_of(b) - reach, x_of(b) + reach, true, false);
|
||||
}
|
||||
for (int b = 0; b < nb; ++b)
|
||||
if (sharp(b))
|
||||
for (const Lin &c : path.cuts[b])
|
||||
clip_profile(f, c, path.turn[b], x_of(b), x_of(b), x_of(b) - reach, x_of(b) + reach, false, true);
|
||||
for (const auto &[c, b] : path.pushes) {
|
||||
const auto [w0, w1] = window(b);
|
||||
clip_profile(f, c, path.turn[b], w0, w1, x_of(b) - reach, x_of(b) + reach, true, true);
|
||||
}
|
||||
std::vector<Vec2d> pts;
|
||||
for (const Vec2d &p : f) {
|
||||
while (pts.size() >= 2 && std::abs(cross2(Vec2d(pts.back() - pts[pts.size() - 2]), Vec2d(p - pts.back()))) <=
|
||||
1e-9 * (pts.back() - pts[pts.size() - 2]).norm() * (p - pts.back()).norm())
|
||||
pts.pop_back();
|
||||
pts.push_back(p);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
static double polyline_length(const std::vector<Vec2d> &pts)
|
||||
{
|
||||
double len = 0.;
|
||||
for (size_t i = 1; i < pts.size(); ++i)
|
||||
len += (pts[i] - pts[i - 1]).norm();
|
||||
return len;
|
||||
}
|
||||
|
||||
// Point at the given distance along pts, and the index of the segment it lies on.
|
||||
static std::pair<Vec2d, size_t> point_along(const std::vector<Vec2d> &pts, double t)
|
||||
{
|
||||
for (size_t i = 1; i < pts.size(); ++i) {
|
||||
const double len = (pts[i] - pts[i - 1]).norm();
|
||||
if (t <= len)
|
||||
return { pts[i - 1] + (len > 0. ? t / len : 0.) * (pts[i] - pts[i - 1]), i };
|
||||
t -= len;
|
||||
}
|
||||
return { pts.back(), pts.size() - 1 };
|
||||
}
|
||||
|
||||
} // namespace noncrossing
|
||||
|
||||
Polylines multiline_paths(const Lines &lines_in, double d1, double end_overlap, int sweep, const BoundingBox &cover)
|
||||
{
|
||||
using namespace noncrossing;
|
||||
const double eps = scale_(0.002);
|
||||
const Eigen::Rotation2Dd to_sweep(-sweep * M_PI / 3.);
|
||||
const BoundingBoxf box(cover.min.cast<double>(), cover.max.cast<double>());
|
||||
|
||||
// Lines in the sweep frame, collinear pieces merged.
|
||||
struct Piece { double c, s0, s1; };
|
||||
std::array<std::vector<Piece>, 3> pieces;
|
||||
for (const Line &line : lines_in) {
|
||||
Vec2d a = line.a.cast<double>(), b = line.b.cast<double>();
|
||||
if (!Geometry::liang_barsky_line_clipping(a, b, box) || (b - a).norm() < 10. * eps)
|
||||
continue;
|
||||
a = to_sweep * a;
|
||||
b = to_sweep * b;
|
||||
const double angle = std::atan2(b.y() - a.y(), b.x() - a.x()) / (M_PI / 3.);
|
||||
if (std::abs(angle - std::round(angle)) > 0.01)
|
||||
// Not one of the three families.
|
||||
return {};
|
||||
const int f = (int(std::round(angle)) % 3 + 3) % 3;
|
||||
const Vec2d &d = family_dir[f];
|
||||
const Vec2d n(-d.y(), d.x());
|
||||
pieces[f].push_back({ n.dot(a), std::min(d.dot(a), d.dot(b)), std::max(d.dot(a), d.dot(b)) });
|
||||
}
|
||||
std::vector<SweepLine> lines;
|
||||
for (int f = 0; f < 3; ++f) {
|
||||
std::vector<Piece> &ps = pieces[f];
|
||||
const Vec2d &d = family_dir[f];
|
||||
const Vec2d n(-d.y(), d.x());
|
||||
const double slope = d.y() / d.x();
|
||||
std::sort(ps.begin(), ps.end(), [](const Piece &l, const Piece &r) { return l.c < r.c; });
|
||||
for (size_t i = 0; i < ps.size();) {
|
||||
size_t j = i + 1;
|
||||
while (j < ps.size() && ps[j].c - ps[i].c < eps)
|
||||
++j;
|
||||
std::sort(ps.begin() + i, ps.begin() + j, [](const Piece &l, const Piece &r) { return l.s0 < r.s0; });
|
||||
double c = 0.;
|
||||
for (size_t k = i; k < j; ++k)
|
||||
c += ps[k].c / double(j - i);
|
||||
double s0 = ps[i].s0, s1 = ps[i].s1;
|
||||
for (size_t k = i + 1; k <= j; ++k) {
|
||||
if (k < j && ps[k].s0 <= s1 + eps) {
|
||||
s1 = std::max(s1, ps[k].s1);
|
||||
continue;
|
||||
}
|
||||
const Vec2d a = s0 * d + c * n;
|
||||
lines.push_back({ a, s1 * d + c * n, f, Lin(slope, a.y() - slope * a.x()), {} });
|
||||
if (k < j) {
|
||||
s0 = ps[k].s0;
|
||||
s1 = ps[k].s1;
|
||||
}
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
auto along = [](const SweepLine &l, const Vec2d &p) { return family_dir[l.family].dot(p - l.a); };
|
||||
auto length = [](const SweepLine &l) { return (l.b - l.a).norm(); };
|
||||
|
||||
// Crossings, including the ends of lines stopping on another line.
|
||||
std::vector<Junction> junctions;
|
||||
auto detect_junctions = [&]() {
|
||||
struct Hit { Vec2d p; int i, j; };
|
||||
std::vector<Hit> hits;
|
||||
std::vector<int> order(lines.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
std::sort(order.begin(), order.end(), [&lines](int l, int r) { return lines[l].a.x() < lines[r].a.x(); });
|
||||
for (size_t oi = 0; oi < order.size(); ++oi) {
|
||||
const SweepLine &li = lines[order[oi]];
|
||||
for (size_t oj = oi + 1; oj < order.size() && lines[order[oj]].a.x() <= li.b.x() + eps; ++oj) {
|
||||
const SweepLine &lj = lines[order[oj]];
|
||||
if (li.family == lj.family)
|
||||
continue;
|
||||
const double x = (lj.lin.y() - li.lin.y()) / (li.lin.x() - lj.lin.x());
|
||||
const Vec2d p(x, li.lin.x() * x + li.lin.y());
|
||||
const double ti = along(li, p), tj = along(lj, p);
|
||||
if (ti > -eps && ti < length(li) + eps && tj > -eps && tj < length(lj) + eps)
|
||||
hits.push_back({ p, order[oi], order[oj] });
|
||||
}
|
||||
}
|
||||
std::sort(hits.begin(), hits.end(), [](const Hit &l, const Hit &r) { return l.p.x() < r.p.x(); });
|
||||
junctions.clear();
|
||||
for (const Hit &hit : hits) {
|
||||
int found = -1;
|
||||
for (int k = int(junctions.size()) - 1; k >= 0 && junctions[k].p.x() > hit.p.x() - eps; --k)
|
||||
if (std::abs(junctions[k].p.y() - hit.p.y()) < eps) {
|
||||
found = k;
|
||||
break;
|
||||
}
|
||||
if (found < 0) {
|
||||
found = int(junctions.size());
|
||||
junctions.push_back({ hit.p, {}, {}, {} });
|
||||
}
|
||||
std::vector<int> &jl = junctions[found].lines;
|
||||
for (int li : { hit.i, hit.j })
|
||||
if (std::find(jl.begin(), jl.end(), li) == jl.end())
|
||||
jl.push_back(li);
|
||||
}
|
||||
for (SweepLine &l : lines)
|
||||
l.junctions.clear();
|
||||
for (int ji = 0; ji < int(junctions.size()); ++ji)
|
||||
for (int li : junctions[ji].lines)
|
||||
lines[li].junctions.emplace_back(junctions[ji].p.x(), ji);
|
||||
for (SweepLine &l : lines)
|
||||
std::sort(l.junctions.begin(), l.junctions.end());
|
||||
};
|
||||
detect_junctions();
|
||||
auto has_arm = [&](int ji, int li, bool right) {
|
||||
const double t = along(lines[li], junctions[ji].p);
|
||||
return right ? t < length(lines[li]) - eps : t > eps;
|
||||
};
|
||||
|
||||
// A line ending on another just past a crossing stops at the crossing, where its stub would leave a hole.
|
||||
auto crosses = [&](int ji, int li) { return has_arm(ji, li, false) && has_arm(ji, li, true); };
|
||||
for (int pass = 0; pass < 3; ++pass) {
|
||||
std::vector<bool> touched(lines.size(), false);
|
||||
bool changed = false;
|
||||
for (int ji = 0; ji < int(junctions.size()); ++ji) {
|
||||
const Junction &J = junctions[ji];
|
||||
if (J.lines.size() != 2 || touched[J.lines[0]] || touched[J.lines[1]] || !crosses(ji, J.lines[0]) || !crosses(ji, J.lines[1]))
|
||||
continue;
|
||||
// Shortest arm of each line from J to the junction where it ends on another line.
|
||||
struct DeadArm { double length; bool at_b; int end; };
|
||||
std::array<DeadArm, 2> dead;
|
||||
dead.fill({ std::numeric_limits<double>::max(), false, -1 });
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
const SweepLine &l = lines[J.lines[k]];
|
||||
const size_t at = std::find_if(l.junctions.begin(), l.junctions.end(), [ji](const std::pair<double, int> &j) { return j.second == ji; }) - l.junctions.begin();
|
||||
const double t = along(l, J.p);
|
||||
if (at + 1 < l.junctions.size() && length(l) - along(l, junctions[l.junctions[at + 1].second].p) < eps)
|
||||
dead[k] = { length(l) - t, true, l.junctions[at + 1].second };
|
||||
if (at > 0 && along(l, junctions[l.junctions[at - 1].second].p) < eps && t < dead[k].length)
|
||||
dead[k] = { t, false, l.junctions[at - 1].second };
|
||||
}
|
||||
const int k = dead[0].length <= dead[1].length ? 0 : 1;
|
||||
if (dead[k].length >= 2. * d1)
|
||||
continue;
|
||||
SweepLine &l = lines[J.lines[k]];
|
||||
(dead[k].at_b ? l.b : l.a) = J.p;
|
||||
// Only the shortened line and those it ended on have stale junctions until the next pass.
|
||||
for (int li : junctions[dead[k].end].lines)
|
||||
touched[li] = true;
|
||||
changed = true;
|
||||
}
|
||||
if (!changed)
|
||||
break;
|
||||
detect_junctions();
|
||||
}
|
||||
|
||||
// At every crossing the lines bounce off each other, so that every path keeps running left to right.
|
||||
for (int ji = 0; ji < int(junctions.size()); ++ji) {
|
||||
Junction &J = junctions[ji];
|
||||
std::vector<int> left, right;
|
||||
for (int li : J.lines)
|
||||
if (has_arm(ji, li, false) && has_arm(ji, li, true))
|
||||
left.push_back(li);
|
||||
right = left;
|
||||
std::sort(left.begin(), left.end(), [&lines](int l, int r) { return lines[l].lin.x() > lines[r].lin.x(); });
|
||||
std::sort(right.begin(), right.end(), [&lines](int l, int r) { return lines[l].lin.x() < lines[r].lin.x(); });
|
||||
for (size_t k = 0; k < left.size(); ++k)
|
||||
J.pairs.emplace_back(left[k], right[k]);
|
||||
J.bends.assign(J.pairs.size(), { -1, -1 });
|
||||
}
|
||||
|
||||
std::vector<LevelPath> paths;
|
||||
for (int li = 0; li < int(lines.size()); ++li) {
|
||||
const SweepLine &l = lines[li];
|
||||
const int start = !l.junctions.empty() && !has_arm(l.junctions.front().second, li, false) ? l.junctions.front().second : -1;
|
||||
LevelPath path;
|
||||
path.start_term = start;
|
||||
path.verts.push_back(start >= 0 ? junctions[start].p : l.a);
|
||||
path.lines.push_back(li);
|
||||
int cur = li;
|
||||
double x = path.verts.front().x();
|
||||
for (;;) {
|
||||
const auto &js = lines[cur].junctions;
|
||||
const auto it = std::find_if(js.begin(), js.end(), [x, eps](const std::pair<double, int> &j) { return j.first > x + 0.25 * eps; });
|
||||
if (it == js.end()) {
|
||||
path.verts.push_back(lines[cur].b);
|
||||
break;
|
||||
}
|
||||
Junction &J = junctions[it->second];
|
||||
const size_t k = std::find_if(J.pairs.begin(), J.pairs.end(), [cur](const std::pair<int, int> &p) { return p.first == cur; }) - J.pairs.begin();
|
||||
if (k == J.pairs.size()) {
|
||||
path.verts.push_back(J.p);
|
||||
path.end_term = it->second;
|
||||
break;
|
||||
}
|
||||
if (const int next = J.pairs[k].second; next != cur) {
|
||||
J.bends[k] = { int(paths.size()), int(path.junctions.size()) };
|
||||
path.verts.push_back(J.p);
|
||||
path.lines.push_back(next);
|
||||
path.junctions.push_back(it->second);
|
||||
path.turn.push_back(lines[next].lin.x() > lines[cur].lin.x() ? 1 : -1);
|
||||
cur = next;
|
||||
}
|
||||
x = it->first;
|
||||
}
|
||||
path.cuts.resize(path.junctions.size());
|
||||
paths.push_back(std::move(path));
|
||||
}
|
||||
|
||||
// Cut the two bends of a crossing d1 apart, no further than the neighbouring bends turning the other way.
|
||||
for (const Junction &J : junctions) {
|
||||
if (J.pairs.size() < 2 || J.bends.front().first < 0 || J.bends.back().first < 0)
|
||||
continue;
|
||||
const Vec2d n = (family_dir[lines[J.pairs.back().second].family] - family_dir[lines[J.pairs.back().first].family]).normalized();
|
||||
auto cut = [&J, &n](double offset) {
|
||||
const Vec2d q = J.p + offset * n;
|
||||
const double s = -n.x() / n.y();
|
||||
return Lin(s, q.y() - s * q.x());
|
||||
};
|
||||
LevelPath &lo = paths[J.bends.front().first], &hi = paths[J.bends.back().first];
|
||||
const int lb = J.bends.front().second, hb = J.bends.back().second;
|
||||
if (J.pairs.size() == 3) {
|
||||
lo.cuts[lb].push_back(cut(-d1));
|
||||
hi.cuts[hb].push_back(cut(d1));
|
||||
continue;
|
||||
}
|
||||
// Only the tip of a small triangle, between the two slanted families, stops at its neighbouring bends.
|
||||
const bool sharp = lines[J.pairs.front().first].family != 0 && lines[J.pairs.front().second].family != 0;
|
||||
auto room = [&](const LevelPath &P, int b, int away, double sign) {
|
||||
double c = std::numeric_limits<double>::max();
|
||||
for (int nb : { b - 1, b + 1 })
|
||||
if (sharp && nb >= 0 && nb < int(P.junctions.size()) && P.turn[nb] == away)
|
||||
c = std::min(c, sign * n.dot(junctions[P.junctions[nb]].p - J.p));
|
||||
if (b == 0 && P.start_term >= 0)
|
||||
c = std::min(c, sign * n.dot(P.verts.front() - J.p));
|
||||
if (b + 1 == int(P.junctions.size()) && P.end_term >= 0)
|
||||
c = std::min(c, sign * n.dot(P.verts.back() - J.p));
|
||||
return std::max(c, 0.);
|
||||
};
|
||||
const double c_lo = room(lo, lb, 1, -1.), c_hi = room(hi, hb, -1, 1.);
|
||||
double d_lo = 0.5 * d1;
|
||||
if (d1 - c_hi <= c_lo)
|
||||
d_lo = std::clamp(d_lo, d1 - c_hi, c_lo);
|
||||
else if (c_lo + c_hi > 0.)
|
||||
d_lo = d1 * c_lo / (c_lo + c_hi);
|
||||
const double d_hi = d1 - d_lo;
|
||||
lo.cuts[lb].push_back(cut(-d_lo));
|
||||
hi.cuts[hb].push_back(cut(d_hi));
|
||||
auto propagate = [&](const LevelPath &P, int b, int away, int step, double offset) {
|
||||
for (int nb : { b - 1, b + 1 })
|
||||
if (nb >= 0 && nb < int(P.junctions.size()) && P.turn[nb] == away) {
|
||||
const Junction &W = junctions[P.junctions[nb]];
|
||||
const int k = int(std::find_if(W.pairs.begin(), W.pairs.end(), [&](const std::pair<int, int> &p) { return p.first == P.lines[nb]; }) - W.pairs.begin()) + step;
|
||||
if (k >= 0 && k < int(W.bends.size()) && W.bends[k].first >= 0)
|
||||
paths[W.bends[k].first].pushes.emplace_back(cut(offset), W.bends[k].second);
|
||||
}
|
||||
};
|
||||
if (sharp) {
|
||||
propagate(lo, lb, 1, -1, -d_lo - d1);
|
||||
propagate(hi, hb, -1, 1, d_hi + d1);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<Vec2d>> geometry;
|
||||
Linesf segments;
|
||||
std::vector<std::pair<int, double>> segment_start; // path, distance along it
|
||||
std::vector<std::pair<double, double>> kept; // stretch of each path left by the trimming
|
||||
for (const LevelPath &path : paths) {
|
||||
geometry.push_back(path.junctions.empty() ? std::vector<Vec2d>{ path.verts.front(), path.verts.back() } : path_points(path, lines, 4. * d1));
|
||||
double along_path = 0.;
|
||||
for (size_t i = 1; i < geometry.back().size(); ++i) {
|
||||
segments.emplace_back(geometry.back()[i - 1], geometry.back()[i]);
|
||||
segment_start.emplace_back(int(geometry.size()) - 1, along_path);
|
||||
along_path += (geometry.back()[i] - geometry.back()[i - 1]).norm();
|
||||
}
|
||||
kept.emplace_back(0., along_path);
|
||||
}
|
||||
|
||||
// A path ending on another line stops end_overlap inside the walls of the others, as trimmed so far.
|
||||
const double end_clearance = d1 - end_overlap;
|
||||
AABBTreeLines::LinesDistancer<Linef> tree(segments);
|
||||
auto clearance = [&](int pi, const Vec2d &q) {
|
||||
double dist = std::numeric_limits<double>::max();
|
||||
for (size_t s : tree.all_lines_in_radius(q, d1)) {
|
||||
const auto [pj, start] = segment_start[s];
|
||||
const Vec2d a = segments[s].a, d = segments[s].b - a;
|
||||
const double len = d.norm(), t0 = std::max(0., kept[pj].first - start), t1 = std::min(len, kept[pj].second - start);
|
||||
if (pj != pi && len > 0. && t0 <= t1)
|
||||
dist = std::min(dist, line_alg::distance_to(Linef(a + t0 / len * d, a + t1 / len * d), q));
|
||||
}
|
||||
return dist;
|
||||
};
|
||||
// Returns the length trimmed off.
|
||||
auto trim_front = [&](int pi, std::vector<Vec2d> &pts) {
|
||||
const double total = polyline_length(pts), step = d1 / 32.;
|
||||
double t = 0.;
|
||||
while (t <= total && clearance(pi, point_along(pts, t).first) < end_clearance)
|
||||
t += step;
|
||||
if (t > total) {
|
||||
pts.clear();
|
||||
return total;
|
||||
}
|
||||
if (t == 0.)
|
||||
return 0.;
|
||||
for (double lo = std::max(0., t - step); t - lo > step / 256.;)
|
||||
if (const double mid = 0.5 * (lo + t); clearance(pi, point_along(pts, mid).first) < end_clearance)
|
||||
lo = mid;
|
||||
else
|
||||
t = mid;
|
||||
const auto [q, seg] = point_along(pts, t);
|
||||
pts.erase(pts.begin(), pts.begin() + (seg - 1));
|
||||
pts.front() = q;
|
||||
return t;
|
||||
};
|
||||
|
||||
// A path stopping on the line of another path is trimmed first, so that it gives way to that path.
|
||||
std::vector<std::vector<std::tuple<double, double, int>>> carried(lines.size()); // x range and path of each piece
|
||||
for (int pi = 0; pi < int(paths.size()); ++pi)
|
||||
for (size_t i = 0; i < paths[pi].lines.size(); ++i)
|
||||
carried[paths[pi].lines[i]].emplace_back(paths[pi].verts[i].x(), paths[pi].verts[i + 1].x(), pi);
|
||||
std::vector<std::vector<int>> stopping_on(paths.size());
|
||||
for (int pi = 0; pi < int(paths.size()); ++pi)
|
||||
for (const auto &[ji, own] : { std::make_pair(paths[pi].start_term, paths[pi].lines.front()), std::make_pair(paths[pi].end_term, paths[pi].lines.back()) })
|
||||
if (ji >= 0)
|
||||
for (int li : junctions[ji].lines)
|
||||
if (li != own)
|
||||
for (const auto &[x0, x1, pj] : carried[li])
|
||||
if (pj != pi && x0 - eps <= junctions[ji].p.x() && junctions[ji].p.x() <= x1 + eps)
|
||||
stopping_on[pj].push_back(pi);
|
||||
std::vector<int> order;
|
||||
std::vector<bool> visited(paths.size(), false);
|
||||
std::function<void(int)> visit = [&](int pi) {
|
||||
if (visited[pi])
|
||||
return;
|
||||
visited[pi] = true;
|
||||
for (int child : stopping_on[pi])
|
||||
visit(child);
|
||||
order.push_back(pi);
|
||||
};
|
||||
for (int pi = 0; pi < int(paths.size()); ++pi)
|
||||
visit(pi);
|
||||
std::vector<std::vector<Vec2d>> trimmed(paths.size());
|
||||
auto trim = [&](int pi) {
|
||||
std::vector<Vec2d> &pts = trimmed[pi];
|
||||
if (paths[pi].start_term >= 0)
|
||||
kept[pi].first += trim_front(pi, pts);
|
||||
if (paths[pi].end_term >= 0 && !pts.empty()) {
|
||||
std::reverse(pts.begin(), pts.end());
|
||||
kept[pi].second -= trim_front(pi, pts);
|
||||
std::reverse(pts.begin(), pts.end());
|
||||
}
|
||||
if (pts.size() < 2 || polyline_length(pts) < d1) {
|
||||
pts.clear();
|
||||
kept[pi] = { 0., -1. };
|
||||
}
|
||||
};
|
||||
// Ends grow back where the ends they gave way to were trimmed later; the last pass only shortens them.
|
||||
for (int pass = 0; pass < 3; ++pass)
|
||||
for (int pi : order) {
|
||||
if (pass < 2) {
|
||||
trimmed[pi] = geometry[pi];
|
||||
kept[pi] = { 0., polyline_length(geometry[pi]) };
|
||||
} else if (trimmed[pi].empty())
|
||||
continue;
|
||||
trim(pi);
|
||||
}
|
||||
|
||||
Polylines out;
|
||||
const Eigen::Rotation2Dd to_world = to_sweep.inverse();
|
||||
for (const std::vector<Vec2d> &pts : trimmed) {
|
||||
if (pts.empty())
|
||||
continue;
|
||||
Polyline pl;
|
||||
for (const Vec2d &p : pts) {
|
||||
const Vec2d w = to_world * p;
|
||||
pl.points.emplace_back(coord_t(std::round(w.x())), coord_t(std::round(w.y())));
|
||||
}
|
||||
out.emplace_back(std::move(pl));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void Filler::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
@@ -1371,6 +1932,17 @@ void Filler::_fill_surface_single(
|
||||
all_polylines.reserve(lines.size());
|
||||
std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; });
|
||||
|
||||
if (params.multiline > 1) {
|
||||
const double d1 = scale_(this->spacing) * params.multiline;
|
||||
BoundingBox cover = get_extents(expolygon);
|
||||
cover.offset(coord_t(4. * d1));
|
||||
// Rotate the family the paths run along with the layer, like the other multiline patterns.
|
||||
const int sweep = int((this->layer_id / std::max(thickness_layers, 1u)) % 3);
|
||||
// Line ends overlap the walls they stop on by half a line, so that they bond.
|
||||
if (Polylines paths = multiline_paths(lines, d1, 0.5 * scale_(this->spacing), sweep, cover); !paths.empty())
|
||||
all_polylines = std::move(paths);
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(all_polylines, params, spacing);
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ FillAdaptive::OctreePtr build_octree(
|
||||
// If true, octree is densified below internal overhangs only.
|
||||
bool support_overhangs_only);
|
||||
|
||||
// Multiline infill: lines of the three families to non-crossing paths d1 apart, ends reaching end_overlap into walls.
|
||||
Polylines multiline_paths(const Lines &lines, double d1, double end_overlap, int sweep, const BoundingBox &cover);
|
||||
|
||||
//
|
||||
// Some of the algorithms used by class FillAdaptive were inspired by
|
||||
// Cura Engine's class SubDivCube
|
||||
|
||||
@@ -3047,12 +3047,56 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upper level of a cubic band [0, h] over one period, from the crossing at (0, tau) to the one at (period, tau).
|
||||
// See docs/HLSD/multiline-infill.md.
|
||||
static std::vector<Vec2d> cubic_upper_level(double tau, double h, double period, double d1)
|
||||
{
|
||||
const double s3 = std::sqrt(3.);
|
||||
const double y_cut = std::clamp(tau - 0.5 * d1, 0., h - d1) + d1;
|
||||
const double y_flat = std::min(h, h + y_cut - 2. * d1);
|
||||
const double x2 = (h - tau) / s3 + d1;
|
||||
const double x3 = (h + tau) / s3 - d1;
|
||||
// (slope, intercept) of the rising line, its chamfer, the horizontal line, the falling chamfer and line.
|
||||
const std::array<Vec2d, 5> lines{ Vec2d(s3, tau), Vec2d(1. / s3, h - x2 / s3), Vec2d(0., y_flat),
|
||||
Vec2d(-1. / s3, h + x3 / s3), Vec2d(-s3, tau + s3 * period) };
|
||||
auto y_at = [&lines, y_cut](double x) {
|
||||
double y = std::numeric_limits<double>::max();
|
||||
for (const Vec2d &l : lines)
|
||||
y = std::min(y, l.x() * x + l.y());
|
||||
return std::max(y, y_cut);
|
||||
};
|
||||
|
||||
std::vector<double> xs;
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
if (lines[i].x() != 0.)
|
||||
xs.emplace_back((y_cut - lines[i].y()) / lines[i].x());
|
||||
for (size_t j = i + 1; j < lines.size(); ++j)
|
||||
xs.emplace_back((lines[j].y() - lines[i].y()) / (lines[i].x() - lines[j].x()));
|
||||
}
|
||||
xs.erase(std::remove_if(xs.begin(), xs.end(), [period](double x) { return x <= 1. || x >= period - 1.; }), xs.end());
|
||||
xs.insert(xs.end(), { 0., period });
|
||||
std::sort(xs.begin(), xs.end());
|
||||
xs.erase(std::unique(xs.begin(), xs.end(), [](double a, double b) { return b - a < 1.; }), xs.end());
|
||||
|
||||
std::vector<Vec2d> pts;
|
||||
for (double x : xs) {
|
||||
const Vec2d p(x, y_at(x));
|
||||
if (pts.size() >= 2) {
|
||||
const Vec2d &a = pts[pts.size() - 2], &b = pts.back();
|
||||
if (std::abs((b.y() - a.y()) / (b.x() - a.x()) - (p.y() - b.y()) / (p.x() - b.x())) < EPSILON)
|
||||
pts.pop_back();
|
||||
}
|
||||
pts.emplace_back(p);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
bool FillRectilinear::fill_surface_trapezoidal(
|
||||
const Surface* surface,
|
||||
FillParams params,
|
||||
const std::initializer_list<SweepParams>& sweep_params,
|
||||
Polylines& polylines_out,
|
||||
int Pattern_type) // 0=grid, 1=triangular, 2=stars
|
||||
int Pattern_type) // 0=grid, 1=triangular, 2=stars, 3=cubic
|
||||
{
|
||||
assert(params.multiline > 1);
|
||||
|
||||
@@ -3350,6 +3394,55 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: // Cubic
|
||||
{
|
||||
// Same z shifted lines as the single-line cubic; the slanted ones cross tau above the horizontal ones.
|
||||
auto pos_mod = [](double a, double m) { const double r = std::fmod(a, m); return r < 0. ? r + m : r; };
|
||||
const double h = 0.5 * std::sqrt(3.0) * period;
|
||||
const double shift = scale_(std::sqrt(0.5) * this->z);
|
||||
const double tau = pos_mod(-3. * shift, h);
|
||||
const double y0 = pos_mod(-2. * shift, 2. * h);
|
||||
|
||||
std::array<std::vector<Vec2d>, 2> levels{ cubic_upper_level(h - tau, h, period, d1), cubic_upper_level(tau, h, period, d1) };
|
||||
for (Vec2d &p : levels.front())
|
||||
p.y() = h - p.y();
|
||||
|
||||
const size_t layer_mod = infill_layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
|
||||
// Only cover the surface, seen in the frame the pattern is built in.
|
||||
ExPolygon local = expolygon;
|
||||
local.translate(-rotate_vector.second.x(), -rotate_vector.second.y());
|
||||
if (layer_mod)
|
||||
local.rotate(-angle);
|
||||
BoundingBox cover = get_extents(local);
|
||||
cover.offset(period);
|
||||
|
||||
const int64_t n_min = int64_t(std::floor((cover.min.y() - y0) / h)) - 1;
|
||||
const int64_t n_max = int64_t(std::ceil((cover.max.y() - y0) / h)) + 1;
|
||||
for (int64_t n = n_min; n <= n_max; ++n) {
|
||||
const double x_off = (n & 1) ? 0.5 * period : 0.;
|
||||
const double base = y0 + double(n) * h - tau;
|
||||
const int64_t j_min = int64_t(std::floor((cover.min.x() - x_off) / period)) - 1;
|
||||
const int64_t j_max = int64_t(std::ceil((cover.max.x() - x_off) / period));
|
||||
for (const std::vector<Vec2d> &level : levels) {
|
||||
Polyline row;
|
||||
row.points.reserve(size_t(j_max - j_min + 1) * level.size());
|
||||
for (int64_t j = j_min; j <= j_max; ++j)
|
||||
for (size_t i = (j == j_min) ? 0 : 1; i < level.size(); ++i)
|
||||
row.points.emplace_back(coord_t(std::round(x_off + double(j * period) + level[i].x())),
|
||||
coord_t(std::round(base + level[i].y())));
|
||||
polylines.emplace_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
if (layer_mod)
|
||||
for (Polyline &pl : polylines)
|
||||
pl.rotate(angle, Point(0, 0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle unknown pattern type
|
||||
break;
|
||||
@@ -3532,6 +3625,11 @@ Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶
|
||||
Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
Polylines polylines_out;
|
||||
if (params.multiline > 1) {
|
||||
if (!this->fill_surface_trapezoidal(surface, params, {}, polylines_out, 3))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillCubic::fill_surface_trapezoidal() failed.";
|
||||
return polylines_out;
|
||||
}
|
||||
coordf_t dx = sqrt(0.5) * z;
|
||||
if (! this->fill_surface_by_multilines(
|
||||
surface, params,
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "XCAFDoc_DocumentTool.hxx"
|
||||
#include "XCAFDoc_ShapeTool.hxx"
|
||||
#include "XCAFApp_Application.hxx"
|
||||
#include "TDF_LabelSequence.hxx"
|
||||
#include "TopoDS_Solid.hxx"
|
||||
#include "TopoDS_Compound.hxx"
|
||||
#include "TopoDS_Builder.hxx"
|
||||
|
||||
+218
-198
@@ -970,6 +970,27 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// A folded tower layer is thicker than the object layer it sits on, so the height process_layer
|
||||
// emitted is not the tower's. Both writers declare one, but each hardcodes a tag dialect - Type 1
|
||||
// forces s_IsBBLPrinter and writes "; LAYER_HEIGHT:", Type 2 writes ";HEIGHT:" - and the processor
|
||||
// reads only its printer's, so a Type 1 tower on a non-BBL printer loses it and the merged layer
|
||||
// is drawn and costed as a thin one. Declare it here, where the printer is known, unless the tower
|
||||
// already wrote the right tag. _extrude puts the object's height back on the next object path,
|
||||
// since process_layer forces the role to erWipeTower on any layer with a tower.
|
||||
std::string WipeTowerIntegration::tower_height_tag(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr,
|
||||
const std::string &tcr_gcode) const
|
||||
{
|
||||
const std::string tag = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height);
|
||||
if (! m_sparse_layers_combined || std::abs(gcodegen.m_last_height - tcr.layer_height) <= EPSILON ||
|
||||
tcr_gcode.find(tag) != std::string::npos)
|
||||
return {};
|
||||
// Keep m_last_height what the G-code last declared, so a second visit does not repeat it.
|
||||
gcodegen.m_last_height = tcr.layer_height;
|
||||
char buf[64];
|
||||
sprintf(buf, "%s%g\n", tag.c_str(), tcr.layer_height);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
||||
{
|
||||
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
||||
@@ -1467,6 +1488,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str));
|
||||
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_filament_id, &config);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tower_height_tag(gcodegen, tcr, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
// Count the toolchange only when the emitted block really changed the tool —
|
||||
// tower visits without a filament change must not advance the ordinal.
|
||||
@@ -1799,6 +1821,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
std::string tcr_gcode,
|
||||
tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tower_height_tag(gcodegen, tcr, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
check_add_eol(toolchange_gcode_str);
|
||||
|
||||
@@ -1946,7 +1969,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
// Folded into a later, thicker layer that prints at its own z: nothing to emit.
|
||||
bool ignore_sparse = wipe_tower_layer_is_combined_away(m_tool_changes[m_layer_idx]);
|
||||
if (m_sparse_layers_skipped) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]) && m_layer_idx != 0;
|
||||
@@ -1964,7 +1988,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
// Folded into a later, thicker layer that prints at its own z: nothing to emit.
|
||||
bool ignore_sparse = wipe_tower_layer_is_combined_away(m_tool_changes[m_layer_idx]);
|
||||
if (m_sparse_layers_skipped) {
|
||||
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]);
|
||||
wipe_tower_z = m_compacted_tower_z[m_layer_idx];
|
||||
@@ -1994,7 +2019,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
if (m_layer_idx >= (int) m_tool_changes.size())
|
||||
return true;
|
||||
|
||||
bool ignore_sparse = false;
|
||||
bool ignore_sparse = wipe_tower_layer_is_combined_away(m_tool_changes[m_layer_idx]);
|
||||
if (m_sparse_layers_skipped)
|
||||
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]);
|
||||
|
||||
@@ -2479,7 +2504,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = path;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2531,7 +2557,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
}
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\n" + err_msg;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2555,7 +2582,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
boost::nowide::remove(path_tmp.c_str());
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\n" + ex.what();
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2668,7 +2696,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
if (ret) {
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message();
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -2687,7 +2716,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(print->model().id().id);
|
||||
ctx.id = std::to_string(print->model().id().id);
|
||||
ctx.name = print->get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = path;
|
||||
ctx.cancellation_check = [print]() { return print->canceled(); };
|
||||
@@ -3070,162 +3100,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (m_config.small_area_infill_flow_compensation.value && !m_config.small_area_infill_flow_compensation_model.empty())
|
||||
m_small_area_infill_flow_compensator = make_unique<SmallAreaInfillFlowCompensator>(print.config());
|
||||
|
||||
// Process file_start_gcode - written at the very top of the file, before any header
|
||||
{
|
||||
std::string top_gcode_template = print.config().file_start_gcode.value;
|
||||
if (!top_gcode_template.empty()) {
|
||||
DynamicConfig top_config;
|
||||
// file_start_gcode runs before the parser copy that normally restores these, so set them here.
|
||||
PlaceholderParser::update_timestamp(top_config);
|
||||
PlaceholderParser::update_user_name(top_config);
|
||||
top_config.set_key_value("print_time_total_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder)));
|
||||
top_config.set_key_value("print_time_day", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder)));
|
||||
top_config.set_key_value("print_time_hour", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder)));
|
||||
top_config.set_key_value("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
||||
top_config.set_key_value("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||
top_config.set_key_value("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||
std::string top_gcode = print.placeholder_parser().process(top_gcode_template, 0, &top_config);
|
||||
if (!top_gcode.empty())
|
||||
file.writeln(top_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Don't output Header block if BTT thumbnail is identified in the list
|
||||
// Get the thumbnails value as a string
|
||||
std::string thumbnails_value = print.config().option<ConfigOptionString>("thumbnails")->value;
|
||||
// search string for the BTT_TFT label
|
||||
bool has_BTT_thumbnail = (thumbnails_value.find("BTT_TFT") != std::string::npos);
|
||||
|
||||
if(!has_BTT_thumbnail){
|
||||
file.write_format("; HEADER_BLOCK_START\n");
|
||||
// Write information on the generator.
|
||||
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
|
||||
if (is_bbl_printers)
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
//BBS: total layer number
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str());
|
||||
//Orca: extra check for bbl printer
|
||||
if (is_bbl_printers) {
|
||||
if (print.calib_params().mode == CalibMode::Calib_None) { // Don't support skipping in cali mode
|
||||
// list all label_object_id with sorted order here
|
||||
m_enable_exclude_object = true;
|
||||
m_label_objects_ids.clear();
|
||||
m_label_objects_ids.reserve(print.num_object_instances());
|
||||
for (const PrintObject *print_object : print.objects())
|
||||
for (const PrintInstance &print_instance : print_object->instances())
|
||||
m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id());
|
||||
|
||||
std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end());
|
||||
|
||||
std::string objects_id_list = "; model label id: ";
|
||||
for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++)
|
||||
objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n"));
|
||||
file.writeln(objects_id_list);
|
||||
} else {
|
||||
m_enable_exclude_object = false;
|
||||
m_label_objects_ids.clear();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::string filament_density_list = "; filament_density: ";
|
||||
(filament_density_list+=m_config.filament_density.serialize()) +='\n';
|
||||
file.writeln(filament_density_list);
|
||||
|
||||
std::string filament_diameter_list = "; filament_diameter: ";
|
||||
(filament_diameter_list += m_config.filament_diameter.serialize()) += '\n';
|
||||
file.writeln(filament_diameter_list);
|
||||
|
||||
coordf_t max_height_z = -1;
|
||||
for (const auto& object : print.objects())
|
||||
max_height_z = std::max(object->layers().back()->print_z, max_height_z);
|
||||
|
||||
std::ostringstream max_height_z_tip;
|
||||
max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n';
|
||||
file.writeln(max_height_z_tip.str());
|
||||
}
|
||||
|
||||
{
|
||||
auto used_filaments = print.get_slice_used_filaments(false);
|
||||
std::ostringstream out;
|
||||
out << "; filament: ";
|
||||
for (size_t idx = 0; idx < used_filaments.size(); ++idx) {
|
||||
if (idx != 0)
|
||||
out << ',';
|
||||
out << used_filaments[idx] + 1;
|
||||
}
|
||||
file.writeln(out.str());
|
||||
}
|
||||
|
||||
file.write_format("; HEADER_BLOCK_END\n\n");
|
||||
}
|
||||
|
||||
// BBS: write global config at the beginning of gcode file because printer
|
||||
// need these config information
|
||||
// Append full config, delimited by two 'phony' configuration keys
|
||||
// CONFIG_BLOCK_START and CONFIG_BLOCK_END. The delimiters are structured
|
||||
// as configuration key / value pairs to be parsable by older versions of
|
||||
// PrusaSlicer G-code viewer.
|
||||
{
|
||||
if (is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple image
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n",
|
||||
first_layer_bed_temperature);
|
||||
file.write_format(
|
||||
"; first_layer_temperature = %d\n",
|
||||
print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} else if (thumbnail_cb != nullptr) {
|
||||
// generate the thumbnails
|
||||
auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
|
||||
|
||||
if (errors != enum_bitmask<ThumbnailError>()) {
|
||||
std::string error_str = format("Invalid thumbnails value:");
|
||||
error_str += GCodeThumbnails::get_error_string(errors);
|
||||
throw Slic3r::ExportError(error_str);
|
||||
}
|
||||
|
||||
if (!thumbnails.empty())
|
||||
GCodeThumbnails::export_thumbnails_to_file(
|
||||
thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write some terse information on the slicing parameters.
|
||||
const PrintObject *first_object = print.objects().front();
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
const double initial_layer_print_height = print.config().initial_layer_print_height.value;
|
||||
for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = print.get_print_region(region_id);
|
||||
file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
|
||||
file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
|
||||
file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
|
||||
file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
|
||||
file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
|
||||
if (print.has_support_material())
|
||||
file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
|
||||
if (print.config().initial_layer_line_width.value > 0)
|
||||
file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width());
|
||||
file.write_format("\n");
|
||||
}
|
||||
|
||||
file.write_format("; EXECUTABLE_BLOCK_START\n");
|
||||
|
||||
// SoftFever
|
||||
if( m_enable_exclude_object)
|
||||
file.write(set_object_info(&print));
|
||||
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
|
||||
|
||||
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
|
||||
m_placeholder_parser_integration.parser = print.placeholder_parser();
|
||||
m_placeholder_parser_integration.parser.update_timestamp();
|
||||
@@ -3360,16 +3234,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// Orca: Initialise AdaptivePA processor filter
|
||||
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
|
||||
|
||||
// Emit machine envelope limits for the Marlin firmware.
|
||||
this->print_machine_envelope(file, print);
|
||||
|
||||
// Disable fan.
|
||||
if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) {
|
||||
file.write(m_writer.set_fan(0));
|
||||
//BBS: disable additional fan
|
||||
file.write(m_writer.set_additional_fan(0));
|
||||
}
|
||||
|
||||
// Update output variables after the extruders were initialized.
|
||||
m_placeholder_parser_integration.init(m_writer);
|
||||
|
||||
@@ -3721,6 +3585,157 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
||||
update_placeholder_parser_with_variant_params();
|
||||
|
||||
// Expand the file header only after the start-up placeholders and writer state are ready.
|
||||
// Use the regular custom G-code path so invalid placeholders report the template name.
|
||||
if (!print.config().file_start_gcode.value.empty())
|
||||
file.writeln(this->placeholder_parser_process("file_start_gcode", print.config().file_start_gcode.value, initial_extruder_id));
|
||||
|
||||
// Orca: Don't output Header block if BTT thumbnail is identified in the list
|
||||
// Get the thumbnails value as a string
|
||||
std::string thumbnails_value = print.config().option<ConfigOptionString>("thumbnails")->value;
|
||||
// search string for the BTT_TFT label
|
||||
bool has_BTT_thumbnail = (thumbnails_value.find("BTT_TFT") != std::string::npos);
|
||||
|
||||
if(!has_BTT_thumbnail){
|
||||
file.write_format("; HEADER_BLOCK_START\n");
|
||||
// Write information on the generator.
|
||||
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
|
||||
if (is_bbl_printers)
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
//BBS: total layer number
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str());
|
||||
//Orca: extra check for bbl printer
|
||||
if (is_bbl_printers) {
|
||||
if (print.calib_params().mode == CalibMode::Calib_None) { // Don't support skipping in cali mode
|
||||
// list all label_object_id with sorted order here
|
||||
m_enable_exclude_object = true;
|
||||
m_label_objects_ids.clear();
|
||||
m_label_objects_ids.reserve(print.num_object_instances());
|
||||
for (const PrintObject *print_object : print.objects())
|
||||
for (const PrintInstance &print_instance : print_object->instances())
|
||||
m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id());
|
||||
|
||||
std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end());
|
||||
|
||||
std::string objects_id_list = "; model label id: ";
|
||||
for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++)
|
||||
objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n"));
|
||||
file.writeln(objects_id_list);
|
||||
} else {
|
||||
m_enable_exclude_object = false;
|
||||
m_label_objects_ids.clear();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::string filament_density_list = "; filament_density: ";
|
||||
(filament_density_list+=m_config.filament_density.serialize()) +='\n';
|
||||
file.writeln(filament_density_list);
|
||||
|
||||
std::string filament_diameter_list = "; filament_diameter: ";
|
||||
(filament_diameter_list += m_config.filament_diameter.serialize()) += '\n';
|
||||
file.writeln(filament_diameter_list);
|
||||
|
||||
coordf_t max_height_z = -1;
|
||||
for (const auto& object : print.objects())
|
||||
max_height_z = std::max(object->layers().back()->print_z, max_height_z);
|
||||
|
||||
std::ostringstream max_height_z_tip;
|
||||
max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n';
|
||||
file.writeln(max_height_z_tip.str());
|
||||
}
|
||||
|
||||
{
|
||||
auto used_filaments = print.get_slice_used_filaments(false);
|
||||
std::ostringstream out;
|
||||
out << "; filament: ";
|
||||
for (size_t idx = 0; idx < used_filaments.size(); ++idx) {
|
||||
if (idx != 0)
|
||||
out << ',';
|
||||
out << used_filaments[idx] + 1;
|
||||
}
|
||||
file.writeln(out.str());
|
||||
}
|
||||
|
||||
file.write_format("; HEADER_BLOCK_END\n\n");
|
||||
}
|
||||
|
||||
// BBS: write global config at the beginning of gcode file because printer
|
||||
// need these config information
|
||||
// Append full config, delimited by two 'phony' configuration keys
|
||||
// CONFIG_BLOCK_START and CONFIG_BLOCK_END. The delimiters are structured
|
||||
// as configuration key / value pairs to be parsable by older versions of
|
||||
// PrusaSlicer G-code viewer.
|
||||
{
|
||||
if (is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple image
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n",
|
||||
first_layer_bed_temperature);
|
||||
file.write_format(
|
||||
"; first_layer_temperature = %d\n",
|
||||
print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} else if (thumbnail_cb != nullptr) {
|
||||
// generate the thumbnails
|
||||
auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
|
||||
|
||||
if (errors != enum_bitmask<ThumbnailError>()) {
|
||||
std::string error_str = format("Invalid thumbnails value:");
|
||||
error_str += GCodeThumbnails::get_error_string(errors);
|
||||
throw Slic3r::ExportError(error_str);
|
||||
}
|
||||
|
||||
if (!thumbnails.empty())
|
||||
GCodeThumbnails::export_thumbnails_to_file(
|
||||
thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write some terse information on the slicing parameters.
|
||||
const PrintObject *first_object = print.objects().front();
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
const double initial_layer_print_height = print.config().initial_layer_print_height.value;
|
||||
for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = print.get_print_region(region_id);
|
||||
file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
|
||||
file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
|
||||
file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
|
||||
file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
|
||||
file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
|
||||
if (print.has_support_material())
|
||||
file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
|
||||
if (print.config().initial_layer_line_width.value > 0)
|
||||
file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width());
|
||||
file.write_format("\n");
|
||||
}
|
||||
|
||||
file.write_format("; EXECUTABLE_BLOCK_START\n");
|
||||
|
||||
// SoftFever
|
||||
if( m_enable_exclude_object)
|
||||
file.write(set_object_info(&print));
|
||||
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
|
||||
|
||||
// Emit machine envelope limits for the Marlin firmware.
|
||||
this->print_machine_envelope(file, print);
|
||||
|
||||
// Disable fan.
|
||||
if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) {
|
||||
file.write(m_writer.set_fan(0));
|
||||
//BBS: disable additional fan
|
||||
file.write(m_writer.set_additional_fan(0));
|
||||
}
|
||||
|
||||
std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id);
|
||||
if (print.config().gcode_flavor != gcfKlipper) {
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
@@ -7954,6 +7969,20 @@ double GCode::calc_max_volumetric_speed(const double layer_height, const double
|
||||
return res;
|
||||
}
|
||||
|
||||
// ORCA: Overlap at or below which the overhang fan switches on; negative when it does not depend on overlap
|
||||
// (Overhang_threshold_none cools every external perimeter).
|
||||
static float overhang_fan_overlap_threshold(int overhang_fan_threshold)
|
||||
{
|
||||
switch (overhang_fan_threshold) {
|
||||
case (int) Overhang_threshold_1_4: return 0.9f;
|
||||
case (int) Overhang_threshold_2_4: return 0.75f;
|
||||
case (int) Overhang_threshold_3_4: return 0.5f;
|
||||
case (int) Overhang_threshold_4_4: return 0.25f;
|
||||
case (int) Overhang_threshold_bridge: return 0.05f;
|
||||
default: return -1.f;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed)
|
||||
{
|
||||
std::string gcode;
|
||||
@@ -8084,7 +8113,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
_mm3_per_mm *= filament_flow_ratio;
|
||||
|
||||
if (path.role() == erTopSolidInfill) {
|
||||
_mm3_per_mm *= m_config.top_solid_infill_flow_ratio;
|
||||
_mm3_per_mm *= NOZZLE_CONFIG(top_solid_infill_flow_ratio);
|
||||
} else if (path.role() == erBottomSurface) {
|
||||
_mm3_per_mm *= m_config.bottom_solid_infill_flow_ratio;
|
||||
} else if (path.role() == erInternalBridgeInfill) {
|
||||
@@ -8296,6 +8325,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
|
||||
ConfigOptionPercents overhang_overlap_levels({90, 75, 50, 25, 13, 0});
|
||||
|
||||
// ORCA: Lets the path be split where the overhang fan switches, not only where the speed changes.
|
||||
// Bridges and overhang perimeters are cooled regardless of overlap.
|
||||
float fan_overlap_threshold = -1.f;
|
||||
if (FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers && path.role() != erBridgeInfill &&
|
||||
path.role() != erOverhangPerimeter)
|
||||
fan_overlap_threshold = overhang_fan_overlap_threshold(FILAMENT_CONFIG(overhang_fan_threshold));
|
||||
|
||||
if (NOZZLE_CONFIG(slowdown_for_curled_perimeters)){
|
||||
ConfigOptionFloatsOrPercents dynamic_overhang_speeds(
|
||||
{FloatOrPercent{100, true},
|
||||
@@ -8316,7 +8352,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
FloatOrPercent{NOZZLE_CONFIG(overhang_4_4_speed).get_abs_value(ref_speed) * 100 / ref_speed, true}});
|
||||
|
||||
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds,
|
||||
ref_speed, speed, NOZZLE_CONFIG(slowdown_for_curled_perimeters));
|
||||
ref_speed, speed, NOZZLE_CONFIG(slowdown_for_curled_perimeters),
|
||||
fan_overlap_threshold);
|
||||
}else{
|
||||
ConfigOptionFloatsOrPercents dynamic_overhang_speeds(
|
||||
{FloatOrPercent{100, true},
|
||||
@@ -8335,7 +8372,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
FloatOrPercent{NOZZLE_CONFIG(bridge_speed) * 100 / ref_speed, true}});
|
||||
|
||||
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhang_overlap_levels, dynamic_overhang_speeds,
|
||||
ref_speed, speed, NOZZLE_CONFIG(slowdown_for_curled_perimeters));
|
||||
ref_speed, speed, NOZZLE_CONFIG(slowdown_for_curled_perimeters),
|
||||
fan_overlap_threshold);
|
||||
}
|
||||
variable_speed = std::any_of(new_points.begin(), new_points.end(),
|
||||
[speed](const ProcessedPoint &p) { return fabs(double(p.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec)
|
||||
@@ -8487,28 +8525,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
if (role == erBridgeInfill || role == erOverhangPerimeter) { // ORCA: Split out bridge infill to internal and external to apply separate fan settings
|
||||
return true;
|
||||
}
|
||||
switch (overhang_fan_threshold) {
|
||||
case (int)Overhang_threshold_1_4:
|
||||
return overlap <= 0.9f;
|
||||
break;
|
||||
case (int)Overhang_threshold_2_4:
|
||||
return overlap <= 0.75f;
|
||||
break;
|
||||
case (int)Overhang_threshold_3_4:
|
||||
return overlap <= 0.5f;
|
||||
break;
|
||||
case (int)Overhang_threshold_4_4:
|
||||
return overlap <= 0.25f;
|
||||
break;
|
||||
case (int)Overhang_threshold_bridge:
|
||||
return overlap <= 0.05f;
|
||||
break;
|
||||
case (int)Overhang_threshold_none:
|
||||
if (overhang_fan_threshold == Overhang_threshold_none)
|
||||
return is_external_perimeter(role);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
const float overlap_threshold = overhang_fan_overlap_threshold(overhang_fan_threshold);
|
||||
return overlap_threshold >= 0.f && overlap <= overlap_threshold;
|
||||
};
|
||||
|
||||
std::string comment;
|
||||
|
||||
@@ -107,7 +107,8 @@ public:
|
||||
m_is_first_print(true),
|
||||
m_print_config(&print_config),
|
||||
m_last_wipe_tower_print_z(print_config.z_offset.value),
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(print_config))
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(print_config)),
|
||||
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(print_config))
|
||||
{
|
||||
// Precomputed rather than accumulated while emitting, so that the clearance validator and
|
||||
// the emitter cannot disagree about where the compacted tower sits on any given layer.
|
||||
@@ -138,6 +139,7 @@ public:
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string tower_height_tag(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, const std::string &tcr_gcode) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const Polygons &bed_polygons) const;
|
||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||
@@ -175,6 +177,9 @@ private:
|
||||
// wipe_tower_no_sparse_layers, as answered by the shared compaction rule rather than by the raw
|
||||
// option: smooth timelapse and wrapping detection keep a tower on every layer regardless.
|
||||
const bool m_sparse_layers_skipped;
|
||||
// Combined tower layers are thicker than the object layer they sit on, the only case where the
|
||||
// tower's height is not the one process_layer already declared.
|
||||
const bool m_sparse_layers_combined;
|
||||
// Print z of the compacted tower per planned layer. Empty when the tower is not compacted.
|
||||
std::vector<float> m_compacted_tower_z;
|
||||
};
|
||||
|
||||
@@ -41,10 +41,12 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
|
||||
float flow_width,
|
||||
float max_line_length = -1.0f,
|
||||
float min_distance = -1.0f,
|
||||
// Maps an overhang distance onto the speed it will be printed at. Interior sampling
|
||||
// needs it to tell which of the points it could add would change the G-code, and is
|
||||
// skipped without it.
|
||||
const std::function<float(float)>& distance_to_speed = {})
|
||||
// Speed an overhang distance prints at. Without it, interior sampling
|
||||
// is skipped and every line over 4mm is split.
|
||||
const std::function<float(float)>& distance_to_speed = {},
|
||||
// Overlap (1 - distance / flow_width) at or below which the overhang
|
||||
// fan switches on; negative when the fan does not depend on overlap.
|
||||
float fan_overlap_threshold = -1.0f)
|
||||
{
|
||||
bool looped = input_points.front() == input_points.back();
|
||||
std::function<size_t(size_t,size_t)> get_prev_index = [](size_t idx, size_t count) {
|
||||
@@ -125,29 +127,43 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
|
||||
points.push_back(next_point);
|
||||
}
|
||||
|
||||
// ORCA: How an overhang distance prints, which is what the passes below compare. A segment is printed at the lower
|
||||
// of the speeds at its two ends, and with the overhang fan on if the overlap at either end turns it on. A point
|
||||
// added to a path can therefore only change the G-code where it prints at a different speed or fan state from the
|
||||
// points either side of it, and the passes below add points there and nowhere else.
|
||||
const float width_inv = 1.f / flow_width;
|
||||
// Whether an overhang distance turns the overhang fan on. The overlap test check_overhang_fan applies in GCode.cpp.
|
||||
auto fan_on = [fan_overlap_threshold, width_inv](float distance) {
|
||||
return fan_overlap_threshold >= 0.f && 1.f - distance * width_inv <= fan_overlap_threshold;
|
||||
};
|
||||
// Whether two overhang distances are interchangeable ie have the same speed (beyond a 1mm/sec threshold that gcode.cpp filters out on)
|
||||
// and the same fan state.
|
||||
auto same_speed_and_fan = [&distance_to_speed, &fan_on](float a, float b) {
|
||||
return std::abs(distance_to_speed(a) - distance_to_speed(b)) <= 1.f && fan_on(a) == fan_on(b);
|
||||
};
|
||||
// Whether the first overhang distance prints slower than the second, beyond the 1mm/sec gcode.cpp tolerance, or turns the overhang
|
||||
// fan on where the second does not. Against a supported point (overhang distance 0) it tells whether an end is
|
||||
// affected by the overhang.
|
||||
auto slower_or_cooled = [&distance_to_speed, &fan_on](float a, float b) {
|
||||
return distance_to_speed(a) < distance_to_speed(b) - 1.f || (fan_on(a) && !fan_on(b));
|
||||
};
|
||||
|
||||
// ORCA: Interior sampling
|
||||
// The passes below infer the support under a span from its endpoints alone, so an interior that is supported
|
||||
// differently from both ends is invisible to them: the outer perimeter of an overhang whose ends are caged by
|
||||
// full height walls reads as supported along its whole length. Probe the interior, keep the samples the
|
||||
// endpoint interpolation fails to predict, and bisect either side of each one, so a span that is only partly
|
||||
// unsupported gets points where its support actually changes instead of one reading spread across all of it.
|
||||
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS && min_distance > 0 && distance_to_speed) {
|
||||
// The passes below infer the support under a span from its endpoints alone, so a part that is supported
|
||||
// differently from both ends is invisible to them. The outer perimeter of an overhang whose ends are supported by
|
||||
// reads as supported along its whole length. Probe the interior, keep the samples the endpoint interpolation fails to predict,
|
||||
// and bisect either side of each one, so a span that is only partly unsupported gets points where its support actually changes
|
||||
// instead of one reading being spread across all of it.
|
||||
// Skipped where there is nothing to find: min_distance <= 0 when no overhang can slow this path down, and
|
||||
// fan_overlap_threshold < 0 when no overhang switches the fan on. It also needs distance_to_speed to tell which of
|
||||
// the points it could add would change the G-code.
|
||||
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS && distance_to_speed && (min_distance > 0 || fan_overlap_threshold >= 0.f)) {
|
||||
// Probe at least this densely before treating matching samples as evidence that a span is uniform. The
|
||||
// segmentation pass below only splits lines of 2mm or more, and every pass here drops points closer
|
||||
// together than min_spacing, so finer discovery would not produce a more precise speed transition.
|
||||
// together than min_spacing, so finer discovery would not produce a more precise transition.
|
||||
const double max_probe_spacing = std::max(2., 4. * min_spacing);
|
||||
// A backstop for that length test, which on a non-finite length would never be met.
|
||||
constexpr int max_bisection_depth = 10;
|
||||
// Whether two readings are interchangeable. A segment is printed at the lower of the speeds its ends
|
||||
// read, so a sample that agrees on speed with what is already known cannot change the G-code, whatever
|
||||
// its distance says. The distances themselves are far too coarse a stand-in for this: the speed sections
|
||||
// interpolate, so readings a small fraction of min_distance apart can still be tens of mm/s apart.
|
||||
// The tolerance matches the one GCode.cpp applies when it decides a path has a variable speed at all.
|
||||
auto same_speed = [&distance_to_speed](float a, float b) {
|
||||
return std::abs(distance_to_speed(a) - distance_to_speed(b)) <= 1.f;
|
||||
};
|
||||
// Whether the first reading is printed slower than the second, once they are known to differ.
|
||||
auto prints_slower = [&distance_to_speed](float a, float b) { return distance_to_speed(a) < distance_to_speed(b); };
|
||||
|
||||
// Part of a segment still to bisect: its positions along the segment and bisections left.
|
||||
struct Subspan { double t0, t1; int depth; };
|
||||
@@ -185,8 +201,8 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
|
||||
if (!interior.empty()) {
|
||||
std::sort(interior.begin(), interior.end(),
|
||||
[](const std::pair<double, float>& l, const std::pair<double, float>& r) { return l.first < r.first; });
|
||||
// Coarse probing keeps every sample it took until this pass can see which ones bracket a speed
|
||||
// transition. Matching samples cannot be discarded during discovery: one may be the last
|
||||
// Coarse probing keeps every sample it took until this pass can see which ones bracket a speed or
|
||||
// fan transition. Matching samples cannot be discarded during discovery: one may be the last
|
||||
// supported point before a narrow unsupported pocket found by a later probe.
|
||||
size_t kept = 0;
|
||||
for (size_t i = 0; i < interior.size(); ++i) {
|
||||
@@ -195,15 +211,15 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
|
||||
const bool at_end = i + 1 == interior.size(); // And nothing follows the last sample but the segment's end
|
||||
const float before = at_start ? curr.distance : interior[kept - 1].second;
|
||||
const float after = at_end ? next.distance : interior[i + 1].second;
|
||||
// A sample is worth a point in the path only where it prints at a different speed from the
|
||||
// readings either side of it. Differing from one of the segment's own ends is not enough on
|
||||
// its own where the sample is the faster of the two: the segmentation pass below already
|
||||
// ends the slowdown an end reads, at a distance taken from how far out that end is rather
|
||||
// than from wherever bisection happened to stop, and a point here would leave the span
|
||||
// beside the end too short for that pass to run at all. Support an end cannot account for,
|
||||
// where the interior is the slower reading, is exactly what this pass is here to find.
|
||||
const bool worth_before = !same_speed(sample, before) && (!at_start || prints_slower(sample, before));
|
||||
const bool worth_after = !same_speed(sample, after) && (!at_end || prints_slower(sample, after));
|
||||
// A sample is worth a point in the path only where it prints differently, in speed or fan state,
|
||||
// from the points either side of it. Differing from one of the segment's own ends is not enough on
|
||||
// its own where the sample is not the slower or cooled of the two: the segmentation pass below
|
||||
// already confines the slowdown and cooling at an end, at a distance taken from how far out that
|
||||
// end is rather than from wherever bisection happened to stop, and a point here would leave the
|
||||
// span beside the end too short for that pass to run at all. Support an end cannot account for,
|
||||
// where the interior is the slower or cooled of the two, is exactly what this pass is here to find.
|
||||
const bool worth_before = !same_speed_and_fan(sample, before) && (!at_start || slower_or_cooled(sample, before));
|
||||
const bool worth_after = !same_speed_and_fan(sample, after) && (!at_end || slower_or_cooled(sample, after));
|
||||
if (worth_before || worth_after)
|
||||
interior[kept++] = interior[i];
|
||||
}
|
||||
@@ -238,52 +254,60 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
|
||||
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
|
||||
double line_len = (next.position - curr.position).norm();
|
||||
|
||||
// ORCA: Segment path to smaller lines by adding additional points only if the path has an overhang that
|
||||
// will trigger a slowdown and the path is also reasonably large, i.e. 2mm in length or more
|
||||
// If there is no overhang in the start/end point, dont segment it.
|
||||
// Ignore this check if the control of segmentation for overhangs is disabled (min_distance=-1)
|
||||
if ((min_distance > 0 && ((std::abs(curr.distance) > min_distance) || (std::abs(next.distance) > min_distance)) && line_len >= 2.f) ||
|
||||
(min_distance <= 0 && line_len > 4.0f)) {
|
||||
|
||||
// ORCA: A line prints as slow as its slower end and is cooled if either end is, so an overhang at one
|
||||
// end would otherwise slow down or cool the whole line. Split the line so that only the part beside
|
||||
// that end prints that way, if the line is reasonably long (2mm or more) and at least one end prints
|
||||
// slower or cooled compared with a supported point (overhang distance 0). Deciding on how the end
|
||||
// prints, rather than on its overhang distance against min_distance, also catches an end whose overhang
|
||||
// distance is exactly where the slowdown begins, such as an outline crossing at half a line width.
|
||||
// Without distance_to_speed, split every line over 4mm.
|
||||
const bool split_line = distance_to_speed ?
|
||||
line_len >= 2.f && (slower_or_cooled(curr.distance, 0.f) || slower_or_cooled(next.distance, 0.f)) :
|
||||
line_len > 4.0f;
|
||||
if (split_line) {
|
||||
// Each end's piece is that end's overhang distance plus 1.5 line widths (3 * boundary_offset) long:
|
||||
// a0 ends the piece beside curr, a1 starts the piece beside next.
|
||||
double a0 = std::clamp((curr.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double t0 = std::min(a0, a1);
|
||||
double t1 = std::max(a0, a1);
|
||||
|
||||
if (t0 < 1.0) {
|
||||
Vec p0 = curr.position + t0 * (next.position - curr.position);
|
||||
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
|
||||
p0.template cast<AABBScalar>());
|
||||
ExtendedPoint<L::Dim> new_p{};
|
||||
new_p.position = p0;
|
||||
new_p.distance = float(p0_dist + boundary_offset);
|
||||
// ORCA: only create a new point in the path if the new point overhang distance will be used to generate a speed change
|
||||
// or if this option is disabled (min_distance<=0)
|
||||
if( (std::abs(p0_dist) > min_distance) || (min_distance<=0)){
|
||||
// ORCA: also filter out points that are introduced to the start of the path when their distance from the start point is
|
||||
// not meaningful
|
||||
if ((p0 - curr.position).norm() > min_spacing && (next.position - p0).norm() > min_spacing) {
|
||||
new_points.push_back(new_p);
|
||||
}
|
||||
}
|
||||
// Up to two cut points, in order along the line. Each takes its own overhang distance, so every
|
||||
// piece prints by the overhang distances at its own two ends. t0 >= 1 or t1 <= 0 falls on the
|
||||
// line's own end, so there is no cut. A cut closer than min_spacing to either end of the line is
|
||||
// not meaningful and is filtered out (#6714).
|
||||
ExtendedPoint<L::Dim> cut[2]{};
|
||||
bool keep[2] = {false, false};
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
const double t = k == 0 ? t0 : t1;
|
||||
if (k == 0 ? t >= 1.0 : t <= 0.0)
|
||||
continue;
|
||||
const Vec p = curr.position + t * (next.position - curr.position);
|
||||
auto [p_dist, p_near_l, p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
|
||||
p.template cast<AABBScalar>());
|
||||
cut[k].position = p;
|
||||
cut[k].distance = float(p_dist + boundary_offset);
|
||||
keep[k] = (p - curr.position).norm() > min_spacing && (next.position - p).norm() > min_spacing;
|
||||
}
|
||||
if (t1 > 0.0) {
|
||||
Vec p1 = curr.position + t1 * (next.position - curr.position);
|
||||
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
|
||||
p1.template cast<AABBScalar>());
|
||||
ExtendedPoint<L::Dim> new_p{};
|
||||
new_p.position = p1;
|
||||
new_p.distance = float(p1_dist + boundary_offset);
|
||||
// ORCA: only create a new point in the path if the new point overhang distance will be used to generate a speed change
|
||||
// or if this option is disabled (min_distance<=0)
|
||||
if( (std::abs(p1_dist) > min_distance) || (min_distance<=0)){
|
||||
// ORCA: filter out points that are introduced to the end of the path when their distance from the end point is
|
||||
// not meaningful
|
||||
if ((p1 - curr.position).norm() > min_spacing && (next.position - p1).norm() > min_spacing) {
|
||||
new_points.push_back(new_p);
|
||||
}
|
||||
}
|
||||
if (distance_to_speed) {
|
||||
// Only keep a cut that changes the G-code: one that prints differently from at least one of the
|
||||
// points either side of it, which are the line's ends or the other cut where that is kept. A cut
|
||||
// that prints like both would only split a move into two identical ones.
|
||||
if (keep[0])
|
||||
keep[0] = !same_speed_and_fan(cut[0].distance, curr.distance) ||
|
||||
!same_speed_and_fan(cut[0].distance, keep[1] ? cut[1].distance : next.distance);
|
||||
if (keep[1])
|
||||
keep[1] = !same_speed_and_fan(cut[1].distance, keep[0] ? cut[0].distance : curr.distance) ||
|
||||
!same_speed_and_fan(cut[1].distance, next.distance);
|
||||
// Two cuts closer together than min_spacing would leave a micro segment between them, so only the
|
||||
// first is kept.
|
||||
if (keep[0] && keep[1] && (cut[1].position - cut[0].position).norm() <= min_spacing)
|
||||
keep[1] = false;
|
||||
}
|
||||
for (int k = 0; k < 2; ++k)
|
||||
if (keep[k])
|
||||
new_points.push_back(cut[k]);
|
||||
}
|
||||
}
|
||||
new_points.push_back(next);
|
||||
@@ -423,7 +447,10 @@ public:
|
||||
const ConfigOptionFloatsOrPercents &speeds,
|
||||
float ext_perimeter_speed,
|
||||
float original_speed,
|
||||
bool slowdown_for_curled_edges)
|
||||
bool slowdown_for_curled_edges,
|
||||
// Overlap at or below which the overhang fan switches on; negative when the fan
|
||||
// does not depend on overlap.
|
||||
float fan_overlap_threshold = -1.0f)
|
||||
{
|
||||
size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size());
|
||||
std::vector<std::pair<float, float>> speed_sections;
|
||||
@@ -463,7 +490,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// If a meaningful (i.e. needing slowdown) overhang distance was not found, then we shouldn't split the lines
|
||||
// If no overhang distance slows this path down, -1 turns interior sampling off unless the overhang fan can switch.
|
||||
// Lines are only split where an end prints slower or cooled, so here only a fan switch splits them.
|
||||
if (!found)
|
||||
smallest_distance_with_lower_speed=-1.f;
|
||||
|
||||
@@ -487,9 +515,17 @@ public:
|
||||
return round(final_speed);
|
||||
};
|
||||
|
||||
// ORCA: The speed sections are built from ext_perimeter_speed, which can be above the speed this path prints at
|
||||
// (original_speed, e.g. held down by resonance avoidance). Every segment is capped at original_speed below, so
|
||||
// overhang distances whose speeds differ only above it print the same and must not count as a speed change when
|
||||
// the path is split.
|
||||
auto effective_speed = [&calculate_speed, original_speed](float distance) {
|
||||
return std::min(calculate_speed(distance), original_speed);
|
||||
};
|
||||
|
||||
std::vector<ExtendedPoint<3>> extended_points =
|
||||
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width, -1,
|
||||
smallest_distance_with_lower_speed, calculate_speed);
|
||||
smallest_distance_with_lower_speed, effective_speed, fan_overlap_threshold);
|
||||
const auto width_inv = 1.0f / path.width;
|
||||
std::vector<ProcessedPoint> processed_points;
|
||||
processed_points.reserve(extended_points.size());
|
||||
|
||||
@@ -49,6 +49,48 @@ std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<
|
||||
return tower_z;
|
||||
}
|
||||
|
||||
bool wipe_tower_sparse_layers_combined(const PrintConfig &config)
|
||||
{
|
||||
return config.wipe_tower_sparse_layers_combination.value && ! wipe_tower_sparse_layers_skipped(config) &&
|
||||
config.timelapse_type.value != TimelapseType::tlSmooth && ! config.enable_wrapping_detection.value;
|
||||
}
|
||||
|
||||
bool wipe_tower_layer_is_combined_away(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes)
|
||||
{
|
||||
return ! layer_tool_changes.empty() && layer_tool_changes.front().combined_away;
|
||||
}
|
||||
|
||||
std::vector<char> combine_sparse_wipe_tower_layers(std::vector<float> &layer_height,
|
||||
const std::vector<char> &layer_is_sparse,
|
||||
const std::vector<float> &max_layer_height,
|
||||
size_t first_layer_idx)
|
||||
{
|
||||
assert(layer_is_sparse.size() == layer_height.size() && max_layer_height.size() == layer_height.size());
|
||||
std::vector<char> combined_away(layer_height.size(), 0);
|
||||
float pending_height = 0.f; // what the layers folded away so far add up to
|
||||
for (size_t i = 0; i < layer_height.size(); ++i) {
|
||||
// A toolchange has to purge at its own z, so it neither folds away nor takes over the run
|
||||
// below it - and a run always flushes on its own last layer, so nothing is ever pending here.
|
||||
if (! layer_is_sparse[i] || i <= first_layer_idx) {
|
||||
pending_height = 0.f;
|
||||
continue;
|
||||
}
|
||||
const float merged = pending_height + layer_height[i];
|
||||
// Hand the run on only if the next layer can swallow the whole thing; a layer already past
|
||||
// the cap is left alone rather than shrunk.
|
||||
const bool next_takes_it = i + 1 < layer_height.size() && layer_is_sparse[i + 1] &&
|
||||
merged + layer_height[i + 1] <= max_layer_height[i + 1] + float(EPSILON);
|
||||
if (next_takes_it) {
|
||||
combined_away[i] = 1;
|
||||
pending_height = merged;
|
||||
} else {
|
||||
layer_height[i] = merged;
|
||||
pending_height = 0.f;
|
||||
}
|
||||
}
|
||||
return combined_away;
|
||||
}
|
||||
|
||||
inline float align_round(float value, float base)
|
||||
{
|
||||
return std::round(value / base) * base;
|
||||
@@ -1904,6 +1946,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
|
||||
//m_bridging(float(config.wipe_tower_bridging)),
|
||||
m_bridging(10.f),
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
|
||||
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(config)),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_current_tool(initial_tool),
|
||||
@@ -2028,6 +2071,16 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
|
||||
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
|
||||
|
||||
// Orca: max_layer_height is per nozzle, so read it through the filament->nozzle map rather than
|
||||
// by filament id. Zero means three quarters of the nozzle diameter, as in Slicing.cpp.
|
||||
{
|
||||
const std::vector<int> &filament_map = config.filament_map.values; // 1 based nozzle indices
|
||||
const size_t nozzle_idx = idx < filament_map.size() && filament_map[idx] > 0 ? size_t(filament_map[idx] - 1) : 0;
|
||||
const float max_layer_height = float(config.max_layer_height.get_at(nozzle_idx));
|
||||
m_filpar[idx].max_layer_height = max_layer_height > 0.f ? max_layer_height
|
||||
: 0.75f * float(config.nozzle_diameter.get_at(nozzle_idx));
|
||||
}
|
||||
|
||||
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
|
||||
if (max_vol_speed!= 0.f)
|
||||
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
||||
@@ -3001,7 +3054,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (! m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -3898,7 +3951,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter,
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -4008,7 +4061,7 @@ WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block,
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (filament_id < m_used_filament_length.size())
|
||||
m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -4125,7 +4178,7 @@ WipeTower::ToolChangeResult WipeTower::finish_block_solid(const WipeTowerBlock &
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (filament_id < m_used_filament_length.size())
|
||||
m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -4668,6 +4721,15 @@ void WipeTower::calc_block_infill_gap()
|
||||
m_extra_spacing = 1.f;
|
||||
}
|
||||
|
||||
// A folded layer is generated like any other but thrown away by the emitter, so its extrusions must
|
||||
// not be charged to the filament used.
|
||||
bool WipeTower::layer_is_printed(bool toolchanges_on_layer) const
|
||||
{
|
||||
if (m_layer_info != m_plan.end() && m_layer_info->combined_away)
|
||||
return false;
|
||||
return ! m_sparse_layers_skipped || toolchanges_on_layer;
|
||||
}
|
||||
|
||||
void WipeTower::plan_tower_new()
|
||||
{
|
||||
if (m_wipe_tower_brim_width < 0) m_wipe_tower_brim_width = get_auto_brim_by_height(m_wipe_tower_height);
|
||||
@@ -4820,6 +4882,9 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
if (m_plan.empty())
|
||||
return;
|
||||
//m_extra_spacing = 1.f;
|
||||
// Before planning: the layer heights this rewrites feed the extrusion flow of every later pass.
|
||||
if (m_sparse_layers_combined)
|
||||
combine_sparse_wipe_tower_plan(m_plan, m_filpar, m_first_layer_idx, m_current_tool);
|
||||
m_wipe_tower_height = m_plan.back().z;//real wipe_tower_height
|
||||
plan_tower_new();
|
||||
m_layer_info = m_plan.begin();
|
||||
@@ -5014,6 +5079,9 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
if (only_generate_wall && !timelapse_wall.gcode.empty()) {
|
||||
layer_result.insert(layer_result.begin(), std::move(timelapse_wall));
|
||||
}
|
||||
if (layer.combined_away)
|
||||
for (WipeTower::ToolChangeResult &tcr : layer_result)
|
||||
tcr.combined_away = true;
|
||||
result.emplace_back(std::move(layer_result));
|
||||
}
|
||||
assert(m_outer_wall.size() == m_plan.size());
|
||||
@@ -5179,7 +5247,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode)
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
return construct_tcr(writer, false, old_tool, true, false, 0.f, false);
|
||||
|
||||
@@ -152,6 +152,10 @@ public:
|
||||
bool is_contact = false;
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Orca: folded into a later, thicker layer, so the emitter drops it. Set by the tower, so
|
||||
// the two cannot disagree about which layers print.
|
||||
bool combined_away = false;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
float total_extrusion_length_in_plane() {
|
||||
float e_length = 0.f;
|
||||
@@ -391,6 +395,8 @@ public:
|
||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
||||
float filament_tower_interface_pre_extrusion_length = 0;
|
||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
||||
// Tallest layer this filament's nozzle can lay down; caps the sparse layer combination.
|
||||
float max_layer_height = 0.f;
|
||||
};
|
||||
|
||||
|
||||
@@ -522,6 +528,7 @@ private:
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_sparse_layers_skipped = false;
|
||||
bool m_sparse_layers_combined = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
@@ -595,6 +602,8 @@ private:
|
||||
}
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
// Whether the layer reaches the G-code, and so whether its extrusions count as filament used.
|
||||
bool layer_is_printed(bool toolchanges_on_layer) const;
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
@@ -634,6 +643,8 @@ private:
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
bool extruder_fill{true};
|
||||
// Folded into a later, thicker layer, so this one prints nothing at all.
|
||||
bool combined_away{false};
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
@@ -700,6 +711,62 @@ std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<
|
||||
float base_z = 0.f);
|
||||
|
||||
|
||||
// Combination rule for wipe_tower_sparse_layers_combination. Nothing is compacted - the tower keeps
|
||||
// following the object - but a run of consecutive toolchange-free layers prints as one thicker layer,
|
||||
// the way infill combination merges sparse infill. Shared so that neither tower generator nor the
|
||||
// G-code emitter can combine on its own.
|
||||
|
||||
// Whether sparse layers are really combined. Skipping them outright is the stronger answer to the
|
||||
// same problem and wins over this; smooth timelapse and wrapping detection need a tower on every
|
||||
// layer, so they rule it out too.
|
||||
bool wipe_tower_sparse_layers_combined(const PrintConfig &config);
|
||||
|
||||
// A planned layer folded into a later, thicker one prints nothing at all.
|
||||
bool wipe_tower_layer_is_combined_away(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes);
|
||||
|
||||
// Folds runs of sparse layers into one. layer_height is raised in place on the layer that prints a
|
||||
// run - always its last, so the merged extrusion lands on top of what it covers - and the returned
|
||||
// mask marks the layers that now print nothing. A run stops growing once one more layer would pass
|
||||
// max_layer_height of the nozzle that prints it. first_layer_idx and below never combine: the
|
||||
// tower's first layer carries the brim.
|
||||
std::vector<char> combine_sparse_wipe_tower_layers(std::vector<float> &layer_height,
|
||||
const std::vector<char> &layer_is_sparse,
|
||||
const std::vector<float> &max_layer_height,
|
||||
size_t first_layer_idx);
|
||||
|
||||
// Applies the rule above to a planned tower. Either generator's plan fits: both carry height,
|
||||
// tool_changes and combined_away per layer, and index their filament parameters by tool.
|
||||
template<class PlanLayers, class FilamentParams>
|
||||
void combine_sparse_wipe_tower_plan(PlanLayers &plan, const FilamentParams &filpar, size_t first_layer_idx, size_t initial_tool)
|
||||
{
|
||||
const size_t n = plan.size();
|
||||
std::vector<float> heights(n);
|
||||
std::vector<char> sparse(n);
|
||||
std::vector<float> caps(n);
|
||||
|
||||
// A layer with no toolchange prints with the filament the layer below left loaded.
|
||||
size_t tool = initial_tool;
|
||||
for (const auto &layer : plan)
|
||||
if (! layer.tool_changes.empty()) {
|
||||
tool = layer.tool_changes.front().old_tool;
|
||||
break;
|
||||
}
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
heights[i] = plan[i].height;
|
||||
sparse[i] = plan[i].tool_changes.empty() ? 1 : 0;
|
||||
caps[i] = tool < filpar.size() ? filpar[tool].max_layer_height : 0.f;
|
||||
if (! plan[i].tool_changes.empty())
|
||||
tool = plan[i].tool_changes.back().new_tool;
|
||||
}
|
||||
|
||||
const std::vector<char> combined_away = combine_sparse_wipe_tower_layers(heights, sparse, caps, first_layer_idx);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
plan[i].height = heights[i];
|
||||
plan[i].combined_away = combined_away[i] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
|
||||
@@ -1033,6 +1033,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_z_pos(0.f),
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
|
||||
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(config)),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_infill_speed(default_region_config.sparse_infill_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
@@ -1150,6 +1151,16 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
|
||||
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
|
||||
|
||||
// Orca: max_layer_height is per nozzle, so read it through the filament->nozzle map rather than
|
||||
// by filament id. Zero means three quarters of the nozzle diameter, as in Slicing.cpp.
|
||||
{
|
||||
const std::vector<int> &filament_map = config.filament_map.values; // 1 based nozzle indices
|
||||
const size_t nozzle_idx = idx < filament_map.size() && filament_map[idx] > 0 ? size_t(filament_map[idx] - 1) : 0;
|
||||
const float max_layer_height = float(config.max_layer_height.get_at(nozzle_idx));
|
||||
m_filpar[idx].max_layer_height = max_layer_height > 0.f ? max_layer_height
|
||||
: 0.75f * float(config.nozzle_diameter.get_at(nozzle_idx));
|
||||
}
|
||||
|
||||
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
|
||||
if (max_vol_speed!= 0.f)
|
||||
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
||||
@@ -2103,7 +2114,9 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (! m_sparse_layers_skipped || toolchanges_on_layer || first_layer) {
|
||||
// A folded layer prints nothing, so it consumes nothing and adds no height of its own.
|
||||
const bool combined_away = m_layer_info != m_plan.end() && m_layer_info->combined_away;
|
||||
if ((! m_sparse_layers_skipped || toolchanges_on_layer || first_layer) && ! combined_away) {
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
m_current_height += m_layer_info->height;
|
||||
@@ -2435,6 +2448,10 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
if (m_plan.empty())
|
||||
return;
|
||||
|
||||
// Before planning: the layer heights this rewrites feed the extrusion flow of every later pass.
|
||||
if (m_sparse_layers_combined)
|
||||
combine_sparse_wipe_tower_plan(m_plan, m_filpar, m_first_layer_idx, m_current_tool);
|
||||
|
||||
plan_tower();
|
||||
#if 1
|
||||
for (int i=0;i<5;++i) {
|
||||
@@ -2533,6 +2550,10 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
|
||||
}
|
||||
|
||||
if (layer.combined_away)
|
||||
for (WipeTower::ToolChangeResult &tcr : layer_result)
|
||||
tcr.combined_away = true;
|
||||
|
||||
result.emplace_back(std::move(layer_result));
|
||||
|
||||
if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z)
|
||||
|
||||
@@ -200,6 +200,8 @@ public:
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Tallest layer this filament's nozzle can lay down; caps the sparse layer combination.
|
||||
float max_layer_height = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -268,6 +270,7 @@ private:
|
||||
float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_sparse_layers_skipped = false;
|
||||
bool m_sparse_layers_combined = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
@@ -368,6 +371,8 @@ private:
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
// Folded into a later, thicker layer, so this one prints nothing at all.
|
||||
bool combined_away{false};
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
@@ -2256,13 +2256,45 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
|
||||
// ORCA:
|
||||
// Inner Outer Inner wall ordering mode perimeter order optimisation functions
|
||||
|
||||
// Whether two Arachne lines touch: somewhere the gap between their centrelines is no more than the
|
||||
// touching distance there. Each junction of one line is measured against the segments of the other,
|
||||
// both ways, and the search stops at the first spot that touches.
|
||||
// Arachne varies line width to fill the region (e.g. the odd centre line of a narrow wall is wider
|
||||
// than nominal), so the touching distance is half the combined width at the closest points, not the
|
||||
// nominal spacing. Widths are taken locally so a line widened in one place (a wedge tip, a wall
|
||||
// transition) does not count as touching where it passes close to other perimeters. min_threshold keeps
|
||||
// the nominal spacing threshold as the lower bound.
|
||||
static bool arachne_lines_touch(const Arachne::ExtrusionLine &a, const Arachne::ExtrusionLine &b, double min_threshold)
|
||||
{
|
||||
auto one_way = [min_threshold](const Arachne::ExtrusionLine &from, const Arachne::ExtrusionLine &to) {
|
||||
for (const Arachne::ExtrusionJunction &j : from.junctions) {
|
||||
const Vec2d p = j.p.cast<double>();
|
||||
for (size_t k = 0; k + 1 < to.junctions.size(); ++k) {
|
||||
const Arachne::ExtrusionJunction &j0 = to.junctions[k];
|
||||
const Arachne::ExtrusionJunction &j1 = to.junctions[k + 1];
|
||||
const Vec2d s0 = j0.p.cast<double>();
|
||||
const Vec2d seg = j1.p.cast<double>() - s0;
|
||||
const double l2 = seg.squaredNorm();
|
||||
const double t = l2 > 0. ? std::clamp((p - s0).dot(seg) / l2, 0., 1.) : 0.;
|
||||
const double w = double(j0.w) + t * double(j1.w - j0.w); // width of `to` at the closest point
|
||||
const double touch_distance = std::max(min_threshold, 0.5 * (double(j.w) + w));
|
||||
if ((s0 + t * seg - p).norm() <= touch_distance)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return one_way(a, b) || one_way(b, a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds all perimeters touching a given set of reference lines, given as indexes.
|
||||
*
|
||||
* @param entities The list of PerimeterGeneratorArachneExtrusion entities.
|
||||
* @param referenceIndices A set of indices representing the reference points.
|
||||
* @param threshold_external The distance threshold to consider for proximity for a reference perimeter with inset index 0
|
||||
* @param threshold_internal The distance threshold to consider for proximity for a reference perimeter with inset index 1+
|
||||
* @param threshold_external The minimum touching distance for a reference perimeter with inset index 0
|
||||
* @param threshold_internal The minimum touching distance for a reference perimeter with inset index 1+
|
||||
* @param considered_inset_idx What perimeter inset index are we searching for (eg. if we are searching for first internal perimeters proximate to the current reference perimeter, this value should be set to 1 etc).
|
||||
* @return std::vector<int> A vector of indices representing the touching perimeters.
|
||||
*/
|
||||
@@ -2271,7 +2303,6 @@ std::vector<int> findAllTouchingPerimeters(const std::vector<PerimeterGeneratorA
|
||||
|
||||
for (const int refIdx : referenceIndices) {
|
||||
const auto& referenceEntity = entities[refIdx];
|
||||
Points referencePoints = Arachne::to_points(*referenceEntity.extrusion);
|
||||
for (size_t i = 0; i < entities.size(); ++i) {
|
||||
// Skip already considered references and the reference entity
|
||||
if (referenceIndices.count(i) > 0) continue;
|
||||
@@ -2282,15 +2313,9 @@ std::vector<int> findAllTouchingPerimeters(const std::vector<PerimeterGeneratorA
|
||||
continue; // skip if they dont match
|
||||
}
|
||||
|
||||
Points points = Arachne::to_points(*entity.extrusion);
|
||||
double distance = MultiPoint::minimumDistanceBetweenLinesDefinedByPoints(referencePoints, points);
|
||||
// Add to touchingIndices if within threshold distance
|
||||
size_t threshold=0;
|
||||
if(referenceEntity.extrusion->inset_idx == 0)
|
||||
threshold = threshold_external;
|
||||
else
|
||||
threshold = threshold_internal;
|
||||
if (distance <= threshold) {
|
||||
// Add to touchingIndices if the lines touch.
|
||||
const double threshold = double(referenceEntity.extrusion->inset_idx == 0 ? threshold_external : threshold_internal);
|
||||
if (arachne_lines_touch(*referenceEntity.extrusion, *entity.extrusion, threshold)) {
|
||||
touchingIndices.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_PerimeterGenerator_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "Layer.hpp"
|
||||
#include "Flow.hpp"
|
||||
@@ -105,6 +106,8 @@ public:
|
||||
bool has_fuzzy_hole = false;
|
||||
// Preserve construction order so overlap precedence remains deterministic.
|
||||
std::vector<std::pair<FuzzySkinConfig, ExPolygons>> regions_by_fuzzify;
|
||||
// Area resting on the layer below, where fuzzy skin is allowed. Unset means no restriction.
|
||||
std::optional<ExPolygons> fuzzy_supported_area;
|
||||
|
||||
PerimeterGenerator(
|
||||
// Input:
|
||||
|
||||
@@ -1199,6 +1199,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"enable_tower_interface_features",
|
||||
"enable_tower_interface_cooldown_during_tower",
|
||||
"wipe_tower_no_sparse_layers",
|
||||
"wipe_tower_sparse_layers_combination",
|
||||
"compatible_printers",
|
||||
"compatible_printers_condition",
|
||||
"inherits",
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#define ORCA_JSON_KEY_UPDATE_TIME "updated_time"
|
||||
#define ORCA_JSON_KEY_CREATED_TIME "created_time"
|
||||
#define BBL_JSON_KEY_INHERITS "inherits"
|
||||
#define BBL_JSON_KEY_INCLUDES "include"
|
||||
#define BBL_JSON_KEY_INSTANTIATION "instantiation"
|
||||
#define BBL_JSON_KEY_NOZZLE_DIAMETER "nozzle_diameter"
|
||||
#define BBL_JSON_KEY_PRINTER_TECH "machine_tech"
|
||||
|
||||
@@ -6597,7 +6597,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
// now, or deserialized from the vendor's cache; the code is shared so a
|
||||
// cache-loaded bundle cannot come out different from a JSON-loaded one.
|
||||
// Resolves `inherits` against the presets loaded before this one
|
||||
// (config_maps) or against base_bundle's filament library, flattens, validates
|
||||
// (config_maps) or against base_bundle's filament library, layers each
|
||||
// `include` (include_maps) under the preset's own keys, flattens, validates
|
||||
// and registers the preset. Returns the reason loading failed, empty on
|
||||
// success.
|
||||
std::string PresetBundle::load_vendor_preset(
|
||||
@@ -6606,9 +6607,10 @@ std::string PresetBundle::load_vendor_preset(
|
||||
const PresetBundle* base_bundle,
|
||||
LoadConfigBundleAttributes flags,
|
||||
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, std::string>& filament_id_maps,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
|
||||
std::map<std::string, std::string>& filament_id_maps,
|
||||
PresetCollection* presets_collection, size_t& count, bool is_from_lib,
|
||||
const std::set<std::string>* retain_configs)
|
||||
const std::set<std::string>* retain_configs, const std::set<std::string>* retain_includes)
|
||||
{
|
||||
const VendorProfile* current_vendor_profile = &this->vendors.at(vendor_name);
|
||||
const std::string subfile = path + "/" + vendor_name + "/" + entry.sub_path;
|
||||
@@ -6651,14 +6653,30 @@ std::string PresetBundle::load_vendor_preset(
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (presets_collection->type() == Preset::TYPE_PRINTER)
|
||||
default_config = &presets_collection->default_preset_for(entry.config_src).config;
|
||||
else
|
||||
default_config = &presets_collection->default_preset().config;
|
||||
}
|
||||
else
|
||||
default_config = &presets_collection->default_preset_for(entry.config_src).config;
|
||||
config = *default_config;
|
||||
// Layer each included preset's own keys over the parent, in the order listed;
|
||||
// this preset's own keys go on top.
|
||||
for (const std::string& name : entry.includes) {
|
||||
auto it = include_maps.find(name);
|
||||
if (it == include_maps.end()) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not find include " << name << " for " << preset_name;
|
||||
continue;
|
||||
}
|
||||
config.apply(it->second);
|
||||
}
|
||||
config.apply(entry.config_src);
|
||||
// Record what a base states, its diff against the default, for the presets
|
||||
// that include it. It is taken before extend_default_config_length pads every
|
||||
// per-variant key to the base's variant count: the padded defaults would
|
||||
// otherwise override the values each includer inherits.
|
||||
if (entry.instantiation == "false" && (retain_includes == nullptr || retain_includes->count(preset_name) != 0)) {
|
||||
DynamicPrintConfig included;
|
||||
included.apply_only(config, config.diff(presets_collection->default_preset_for(config).config));
|
||||
include_maps.emplace(preset_name, std::move(included));
|
||||
}
|
||||
extend_default_config_length(config, true, *default_config);
|
||||
if (entry.instantiation == "false" && "Template" != vendor_name) {
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
@@ -7117,6 +7135,15 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
}
|
||||
entry.name = key_values[BBL_JSON_KEY_NAME];
|
||||
entry.description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
||||
// A file that states no instantiation and is named as G-code, or has no
|
||||
// name, is a template that is only there to be included. A nameless one
|
||||
// goes by its name in the vendor index.
|
||||
if (auto it = key_values.find(BBL_JSON_KEY_INSTANTIATION);
|
||||
(it == key_values.end() || it->second.empty()) && (entry.name.empty() || entry.name.find("gcode") != std::string::npos)) {
|
||||
key_values[BBL_JSON_KEY_INSTANTIATION] = "false";
|
||||
if (entry.name.empty())
|
||||
entry.name = subfile_iter.first;
|
||||
}
|
||||
if(key_values.find(BBL_JSON_KEY_INSTANTIATION) == key_values.end())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Missing instantiation attribute for " << entry.name;
|
||||
@@ -7145,6 +7172,20 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
if (auto it = key_values.find(BBL_JSON_KEY_INCLUDES); it != key_values.end()) {
|
||||
// An array of names, or one bare name; load_from_json kept the JSON text.
|
||||
nlohmann::json includes = nlohmann::json::parse(it->second);
|
||||
if (!includes.is_array())
|
||||
includes = nlohmann::json::array({std::move(includes)});
|
||||
for (const auto& name : includes) {
|
||||
if (name.is_string())
|
||||
entry.includes.push_back(name.get<std::string>());
|
||||
else {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid include " << name.dump() << " for " << entry.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key_values.find(ORCA_JSON_KEY_RENAMED_FROM) != key_values.end()) {
|
||||
if (!unescape_strings_cstyle(key_values[ORCA_JSON_KEY_RENAMED_FROM], entry.renamed_from)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Config \"" << dir << "\": The preset \"" << entry.name
|
||||
@@ -7161,7 +7202,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
return reason;
|
||||
};
|
||||
|
||||
std::map<std::string, DynamicPrintConfig> configs;
|
||||
std::map<std::string, DynamicPrintConfig> configs, include_maps;
|
||||
std::map<std::string, std::string> filament_id_maps;
|
||||
// Orca: whether to (re)write the vendor's cache after this parse, leaving it
|
||||
// in step with the profile so the next run reads it instead. It is written
|
||||
@@ -7178,6 +7219,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
auto load_subfiles = [&](std::vector<std::pair<std::string, std::string>>& subfiles,
|
||||
std::vector<CachedPreset>& entries, const char* kind, bool is_from_lib = false) {
|
||||
configs.clear();
|
||||
include_maps.clear();
|
||||
filament_id_maps.clear();
|
||||
for (auto& subfile : subfiles) {
|
||||
CachedPreset entry;
|
||||
@@ -7185,8 +7227,8 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
if (reason.empty()) {
|
||||
const int errors_before_install = m_errors;
|
||||
reason = load_vendor_preset(entry, dir, vendor_name, base_bundle, flags,
|
||||
substitution_context, substitutions, configs, filament_id_maps, presets,
|
||||
presets_loaded, is_from_lib);
|
||||
substitution_context, substitutions, configs, include_maps, filament_id_maps,
|
||||
presets, presets_loaded, is_from_lib);
|
||||
install_errors += m_errors - errors_before_install;
|
||||
}
|
||||
if (!reason.empty()) {
|
||||
@@ -7981,26 +8023,29 @@ bool PresetBundle::load_vendor_cache(const std::string& cache_path, const std::s
|
||||
// parsed), so no substitutions are reported, as before.
|
||||
ConfigSubstitutionContext substitution_context { ForwardCompatibilitySubstitutionRule::EnableSilent };
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::map<std::string, DynamicPrintConfig> configs;
|
||||
std::map<std::string, DynamicPrintConfig> configs, include_maps;
|
||||
std::map<std::string, std::string> filament_id_maps;
|
||||
const std::string path = boost::filesystem::path(cache_path).parent_path().string();
|
||||
size_t count = 0;
|
||||
auto install_entries = [&](const std::vector<CachedPreset>& entries, PresetCollection* presets, bool is_from_lib) {
|
||||
configs.clear();
|
||||
include_maps.clear();
|
||||
filament_id_maps.clear();
|
||||
// Only configs of presets that other entries inherit are ever looked
|
||||
// up again; registering just those skips one full config copy for
|
||||
// every leaf preset. The library's filaments are all retained — they
|
||||
// become the m_config_maps other vendors resolve against.
|
||||
std::set<std::string> inherited;
|
||||
for (const CachedPreset& entry : entries)
|
||||
// Only configs of presets that other entries inherit or include are
|
||||
// ever looked up again; registering just those skips one full config
|
||||
// copy for every leaf preset. The library's filaments are all retained
|
||||
// — they become the m_config_maps other vendors resolve against.
|
||||
std::set<std::string> inherited, included;
|
||||
for (const CachedPreset& entry : entries) {
|
||||
if (! entry.inherits.empty())
|
||||
inherited.insert(entry.inherits);
|
||||
included.insert(entry.includes.begin(), entry.includes.end());
|
||||
}
|
||||
const std::set<std::string>* retain_configs = is_from_lib ? nullptr : &inherited;
|
||||
for (const CachedPreset& entry : entries) {
|
||||
const std::string reason = load_vendor_preset(entry, path, vendor_name,
|
||||
base_bundle, LoadConfigBundleAttribute::LoadSystem, substitution_context, substitutions,
|
||||
configs, filament_id_maps, presets, count, is_from_lib, retain_configs);
|
||||
configs, include_maps, filament_id_maps, presets, count, is_from_lib, retain_configs, &included);
|
||||
if (! reason.empty())
|
||||
throw std::runtime_error("entry " + entry.name + " failed to install: " + reason);
|
||||
}
|
||||
|
||||
@@ -634,22 +634,24 @@ private:
|
||||
// load_vendor_configs_from_json reads a cache.
|
||||
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const PresetBundle* base_bundle);
|
||||
|
||||
// Load one source-form preset entry into this bundle: resolve `inherits`,
|
||||
// flatten, validate and register the preset. Returns the reason loading
|
||||
// failed, empty on success. See the definition for the sharing contract
|
||||
// between the JSON parse and the cache load.
|
||||
// retain_configs, when non-null, names the only presets registered into
|
||||
// config_maps (a full config copy each). The cache load passes the names its
|
||||
// entries inherit — the only ones ever looked up again; the JSON parse
|
||||
// retains all, not knowing what later subfiles inherit.
|
||||
// Load one source-form preset entry into this bundle: resolve `inherits`
|
||||
// and `include`, flatten, validate and register the preset. Returns the
|
||||
// reason loading failed, empty on success. See the definition for the
|
||||
// sharing contract between the JSON parse and the cache load.
|
||||
// retain_configs / retain_includes, when non-null, name the only presets
|
||||
// registered into config_maps / include_maps (a config copy each). The
|
||||
// cache load passes the names its entries inherit / include — the only
|
||||
// ones ever looked up again; the JSON parse retains all, not knowing what
|
||||
// later subfiles name.
|
||||
std::string load_vendor_preset(const CachedPreset& entry,
|
||||
const std::string& path, const std::string& vendor_name,
|
||||
const PresetBundle* base_bundle,
|
||||
LoadConfigBundleAttributes flags,
|
||||
ConfigSubstitutionContext& substitution_context, PresetsConfigSubstitutions& substitutions,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, std::string>& filament_id_maps,
|
||||
std::map<std::string, DynamicPrintConfig>& config_maps, std::map<std::string, DynamicPrintConfig>& include_maps,
|
||||
std::map<std::string, std::string>& filament_id_maps,
|
||||
PresetCollection* presets_collection, size_t& count, bool is_from_lib,
|
||||
const std::set<std::string>* retain_configs = nullptr);
|
||||
const std::set<std::string>* retain_configs = nullptr, const std::set<std::string>* retain_includes = nullptr);
|
||||
|
||||
// Clear every collection's m_printer_hold_alias, which reset() leaves alone.
|
||||
void clear_printer_hold_aliases();
|
||||
|
||||
@@ -254,7 +254,7 @@ constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ"
|
||||
// save_entries below), or a change to the cache's own layout or the
|
||||
// meaning of its stamps. Option-schema drift is NOT such a change — the
|
||||
// dictionary handles it, which is why this no longer moves every release.
|
||||
constexpr uint32_t CACHE_VERSION = 1;
|
||||
constexpr uint32_t CACHE_VERSION = 2;
|
||||
|
||||
// A stamp-string read that refuses an absurd length before allocating anything.
|
||||
// The stamps are read from files named from the outside (peek_version is
|
||||
@@ -325,7 +325,7 @@ void visit_entry(Archive& ar, Entry& e, ConfigFn&& config)
|
||||
{
|
||||
ar(e.name, e.sub_path);
|
||||
config();
|
||||
ar(e.inherits, e.description, e.instantiation, e.setting_id, e.filament_id, e.renamed_from);
|
||||
ar(e.inherits, e.includes, e.description, e.instantiation, e.setting_id, e.filament_id, e.renamed_from);
|
||||
}
|
||||
|
||||
// The count comes from a file that has already passed magic and CRC, but a
|
||||
|
||||
@@ -107,12 +107,13 @@ void load_config(cereal::BinaryInputArchive& ar, DynamicPrintConfig& config, con
|
||||
// comes after.
|
||||
void skip_config(cereal::BinaryInputArchive& ar, const CacheDictionary& dict);
|
||||
|
||||
// One preset as its JSON subfile states it: the config diff, the name of the
|
||||
// preset it inherits, and the parse metadata — everything the parse phase of
|
||||
// load_vendor_configs_from_json extracts and nothing it derives. Inheritance
|
||||
// is resolved when the entry is installed, against whatever filament library
|
||||
// is loaded then, so a cache carries no other vendor's values and no other
|
||||
// vendor's update can make it stale.
|
||||
// One preset as its JSON subfile states it: the config diff, the names of the
|
||||
// preset it inherits and the presets it includes, and the parse metadata —
|
||||
// everything the parse phase of load_vendor_configs_from_json extracts and
|
||||
// nothing it derives. Inheritance and includes are resolved when the entry is
|
||||
// installed, against whatever filament library is loaded then, so a cache
|
||||
// carries no other vendor's values and no other vendor's update can make it
|
||||
// stale.
|
||||
// Written and read by visit_entry in PresetCacheFormat.cpp, which lists every
|
||||
// field below in this order — once, for the save, the load and the name peek alike.
|
||||
struct CachedPreset
|
||||
@@ -121,6 +122,7 @@ struct CachedPreset
|
||||
std::string sub_path; // path under the vendor's directory
|
||||
DynamicPrintConfig config_src; // the preset's own diff, nothing inherited
|
||||
std::string inherits;
|
||||
std::vector<std::string> includes; // layered under config_src, in this order
|
||||
std::string description;
|
||||
std::string instantiation; // "true"/"false" as stated; anything else was already counted as a parse error
|
||||
std::string setting_id;
|
||||
|
||||
+30
-5
@@ -378,6 +378,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "wipe_tower_bridging"
|
||||
|| opt_key == "wipe_tower_extra_flow"
|
||||
|| opt_key == "wipe_tower_no_sparse_layers"
|
||||
|| opt_key == "wipe_tower_sparse_layers_combination"
|
||||
|| opt_key == "flush_volumes_matrix"
|
||||
|| opt_key == "prime_volume"
|
||||
|| opt_key == "flush_into_infill"
|
||||
@@ -1705,6 +1706,25 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
|
||||
|
||||
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
|
||||
//BBS: refine seq-print validation logic
|
||||
// The exception's own message is just "Errors"; the detail is in the per-object errors,
|
||||
// whose object id is the PrintObject's.
|
||||
std::string Print::slicing_errors_message(const SlicingErrors &errors) const
|
||||
{
|
||||
std::string message;
|
||||
for (const SlicingError &error : errors.errors_) {
|
||||
std::string object_name;
|
||||
for (const PrintObject *object : m_objects)
|
||||
if (object->id().id == error.objectId()) {
|
||||
object_name = object->model_object()->name;
|
||||
break;
|
||||
}
|
||||
if (!message.empty())
|
||||
message += "\n";
|
||||
message += object_name.empty() ? std::string(error.what()) : object_name + ": " + error.what();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
|
||||
{
|
||||
auto add_warning = [warnings](StringObjectException w) {
|
||||
@@ -2697,7 +2717,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx);
|
||||
@@ -3324,7 +3345,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx);
|
||||
@@ -4930,7 +4952,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
{
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = file;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
@@ -4960,7 +4983,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str();
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Error;
|
||||
ctx.msg = file + "\n" + ex.what();
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
@@ -4974,7 +4998,8 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
|
||||
|
||||
{
|
||||
LifecycleEventContext ctx;
|
||||
ctx.name = std::to_string(m_model.id().id);
|
||||
ctx.id = std::to_string(m_model.id().id);
|
||||
ctx.name = get_model_name();
|
||||
ctx.code = LifecycleEvtCode::Ok;
|
||||
ctx.msg = file;
|
||||
ctx.cancellation_check = [this]() { return canceled(); };
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SlicingErrors;
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
class ModelObject;
|
||||
@@ -967,6 +969,8 @@ public:
|
||||
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
|
||||
// The per-object messages of a SlicingErrors, each prefixed with its object's name.
|
||||
std::string slicing_errors_message(const SlicingErrors &errors) const;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
|
||||
@@ -1522,7 +1522,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
|
||||
def = this->add("top_solid_infill_flow_ratio", coFloat);
|
||||
def = this->add("top_solid_infill_flow_ratio", coFloats);
|
||||
def->label = L("Top surface flow ratio");
|
||||
def->category = L("Advanced");
|
||||
def->tooltip = L("This factor affects the amount of material for top solid infill. "
|
||||
@@ -1531,7 +1531,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def->min = 0;
|
||||
def->max = 2;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
def->nullable = true;
|
||||
def->set_default_value(new ConfigOptionFloatsNullable{1});
|
||||
|
||||
def = this->add("bottom_solid_infill_flow_ratio", coFloat);
|
||||
def->label = L("Bottom surface flow ratio");
|
||||
@@ -6704,6 +6705,20 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("wipe_tower_sparse_layers_combination", coBool);
|
||||
def->label = L("Combine sparse layers");
|
||||
def->tooltip = L("If enabled, consecutive layers on which the prime tower has no filament change are printed as a single "
|
||||
"thicker tower layer instead of one thin layer each, the same way infill combination merges sparse infill. "
|
||||
"The merged layer is printed at the top of the run, at the height of everything it covers.\n\n"
|
||||
"Only whole layers are merged, and never past the maximum layer height of the nozzle printing the tower "
|
||||
"(three quarters of the nozzle diameter when that is left at 0). Two or more layers therefore have to fit "
|
||||
"under that limit before anything changes at all: at a 0.2 mm layer height under a 0.3 mm maximum nothing "
|
||||
"is merged, while at 0.1 mm three layers become one.\n\n"
|
||||
"Unlike \"No sparse layers\" the tower keeps following the model, so the toolhead never has to reach down to it. "
|
||||
"Has no effect with \"No sparse layers\", smooth timelapse or clumping detection, which need a tower on every layer.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("single_extruder_multi_material_priming", coBool);
|
||||
def->label = L("Prime all printing extruders");
|
||||
def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print.");
|
||||
@@ -9127,6 +9142,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
||||
value = "tree(auto)";
|
||||
} else if (opt_key == "support_base_pattern" && value == "none") {
|
||||
value = "hollow";
|
||||
} else if (opt_key == "tree_support_wall_count" && value == "-1") {
|
||||
value = "0";
|
||||
} else if (opt_key == "different_settings_to_system") {
|
||||
std::string copy_value = value;
|
||||
copy_value.erase(std::remove(copy_value.begin(), copy_value.end(), '\"'), copy_value.end()); // remove '"' in string
|
||||
@@ -9395,7 +9412,8 @@ std::set<std::string> print_options_with_variant = {
|
||||
"initial_layer_travel_jerk",
|
||||
"default_junction_deviation",
|
||||
"print_extruder_id", //coInts
|
||||
"print_extruder_variant" //coStrings
|
||||
"print_extruder_variant", //coStrings
|
||||
"top_solid_infill_flow_ratio"
|
||||
};
|
||||
|
||||
std::set<std::string> filament_options_with_variant = {
|
||||
|
||||
@@ -163,6 +163,7 @@ inline bool is_smoothable_infill_pattern(InfillPattern pattern, int multiline =
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars:
|
||||
case ipCubic:
|
||||
return multiline > 1;
|
||||
default:
|
||||
return false;
|
||||
@@ -1403,7 +1404,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloatsNullable, small_perimeter_threshold))
|
||||
((ConfigOptionFloatsOrPercentsNullable, small_support_perimeter_speed))
|
||||
((ConfigOptionFloatsNullable, small_support_perimeter_threshold))
|
||||
((ConfigOptionFloat, top_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloatsNullable, top_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloat, bottom_solid_infill_flow_ratio))
|
||||
((ConfigOptionFloatOrPercent, infill_anchor))
|
||||
((ConfigOptionFloatOrPercent, infill_anchor_max))
|
||||
@@ -1631,6 +1632,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionString, toolchange_cyclic_order))
|
||||
((ConfigOptionBool, toolchange_cyclic_first_layer))
|
||||
((ConfigOptionBool, wipe_tower_no_sparse_layers))
|
||||
((ConfigOptionBool, wipe_tower_sparse_layers_combination))
|
||||
((ConfigOptionString, change_filament_gcode))
|
||||
((ConfigOptionString, change_extrusion_role_gcode))
|
||||
((ConfigOptionString, process_change_extrusion_role_gcode))
|
||||
|
||||
@@ -34,6 +34,10 @@ static const char* Segments_Vertex_Shader =
|
||||
// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not
|
||||
// world_position, so the caster would write a depth the receiver never looks up.
|
||||
"uniform float bias_scale;\n"
|
||||
// draw the instances last to first, top layers before the ones they hide, so that early depth
|
||||
// rejection discards most of the hidden fragments; set when the camera looks down on the print
|
||||
"uniform int reverse_order;\n"
|
||||
"uniform int instance_count;\n"
|
||||
"in int vertex_id;\n"
|
||||
"out vec3 color;\n"
|
||||
"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n"
|
||||
@@ -59,7 +63,8 @@ static const char* Segments_Vertex_Shader =
|
||||
" return top_diffuse + front_diffuse + top_specular;\n"
|
||||
"}\n"
|
||||
"void main() {\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n"
|
||||
" int instance = (reverse_order != 0) ? instance_count - 1 - gl_InstanceID : gl_InstanceID;\n"
|
||||
" int id_a = int(texelFetch(segment_index_tex, instance).r);\n"
|
||||
" int id_b = id_a + 1;\n"
|
||||
" vec3 pos_a = texelFetch(position_tex, id_a).xyz;\n"
|
||||
" vec3 pos_b = texelFetch(position_tex, id_b).xyz;\n"
|
||||
|
||||
@@ -763,6 +763,8 @@ void ViewerImpl::init(const std::string& opengl_context_version)
|
||||
m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex");
|
||||
m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex");
|
||||
m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex");
|
||||
m_uni_segments_reverse_order_id = glGetUniformLocation(m_segments_shader_id, "reverse_order");
|
||||
m_uni_segments_instance_count_id = glGetUniformLocation(m_segments_shader_id, "instance_count");
|
||||
// ORCA: realistic view
|
||||
m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map");
|
||||
m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp");
|
||||
@@ -2090,6 +2092,15 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data()));
|
||||
glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data()));
|
||||
glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data()));
|
||||
// The segments come in print order, bottom layer first. Seen from above, that is back to front,
|
||||
// and every hidden fragment is shaded before the one that covers it. Drawing them last to first
|
||||
// lets the depth test reject the hidden ones instead. The camera looks down when the world's
|
||||
// up axis points towards it, which is the view matrix's (2, 2) entry being positive.
|
||||
const bool top_down = !m_rendering_shadow_casters && view_matrix[10] > 0.0f;
|
||||
glsafe(glUniform1i(m_uni_segments_reverse_order_id, top_down ? 1 : 0));
|
||||
#ifndef ENABLE_OPENGL_ES
|
||||
glsafe(glUniform1i(m_uni_segments_instance_count_id, static_cast<int>(m_enabled_segments_count)));
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
// ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades
|
||||
// with the lookup off.
|
||||
glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit));
|
||||
|
||||
@@ -362,6 +362,8 @@ private:
|
||||
int m_uni_segments_height_width_angle_tex_id{ -1 };
|
||||
int m_uni_segments_colors_tex_id{ -1 };
|
||||
int m_uni_segments_segment_index_tex_id{ -1 };
|
||||
int m_uni_segments_reverse_order_id{ -1 };
|
||||
int m_uni_segments_instance_count_id{ -1 };
|
||||
int m_uni_segments_shadow_map_id{ -1 };
|
||||
int m_uni_segments_shadow_light_vp_id{ -1 };
|
||||
int m_uni_segments_shadow_intensity_id{ -1 };
|
||||
|
||||
@@ -287,6 +287,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/FilamentMapDialog.hpp
|
||||
GUI/IconManager.cpp
|
||||
GUI/IconManager.hpp
|
||||
GUI/IdleScheduler.cpp
|
||||
GUI/IdleScheduler.hpp
|
||||
GUI/ImageGrid.cpp
|
||||
GUI/ImageGrid.h
|
||||
GUI/ImGuiWrapper.cpp
|
||||
@@ -348,6 +350,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/KBShortcutsDialog.hpp
|
||||
GUI/KeyChord.cpp
|
||||
GUI/KeyChord.hpp
|
||||
GUI/Lazy.cpp
|
||||
GUI/Lazy.hpp
|
||||
GUI/LazyPage.cpp
|
||||
GUI/LazyPage.hpp
|
||||
GUI/LibVGCode/LibVGCodeWrapper.hpp
|
||||
GUI/LibVGCode/LibVGCodeWrapper.cpp
|
||||
GUI/LinuxDisplayBackend.cpp
|
||||
@@ -436,6 +442,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Plater.hpp
|
||||
GUI/PlateSettingsDialog.cpp
|
||||
GUI/PlateSettingsDialog.hpp
|
||||
GUI/PrebuildQueue.hpp
|
||||
GUI/Preferences.cpp
|
||||
GUI/Preferences.hpp
|
||||
GUI/PresetBundleDialog.cpp
|
||||
@@ -515,6 +522,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/SliceInfoPanel.hpp
|
||||
GUI/SlicingProgressNotification.cpp
|
||||
GUI/SlicingProgressNotification.hpp
|
||||
GUI/StagedBuild.hpp
|
||||
GUI/StatusPanel.cpp
|
||||
GUI/StatusPanel.hpp
|
||||
GUI/StepMeshDialog.cpp
|
||||
|
||||
@@ -625,6 +625,12 @@ void Bed3D::update_model_offset()
|
||||
shift(2) = -0.03;
|
||||
Vec3d* model_offset_ptr = const_cast<Vec3d*>(&m_model_offset);
|
||||
*model_offset_ptr = shift;
|
||||
//BBS: TODO: hack for current stl for BBL printer
|
||||
if (std::string::npos != m_model_filename.find("bbl-3dp-"))
|
||||
{
|
||||
(*model_offset_ptr)(0) -= m_bed_shape[2].x() / 2.0f;
|
||||
(*model_offset_ptr)(1) -= m_bed_shape[2].y() / 2.0f;
|
||||
}
|
||||
(*model_offset_ptr)(2) = -0.41 + GROUND_Z;
|
||||
|
||||
// update extended bounding box
|
||||
|
||||
@@ -819,7 +819,7 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
|
||||
m_scrolled_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL);
|
||||
m_scrolled_window->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(20));
|
||||
|
||||
wxBoxSizer *title_sizer_h= new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *title_sizer_v = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -1000,13 +1000,13 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
show_pos.x = screen_size.GetLeft();
|
||||
m_scrolled_window->SetMaxSize(wxSize(screen_size.GetWidth(), popup_height));
|
||||
m_scrolled_window->SetMinSize(wxSize(screen_size.GetWidth(), popup_height));
|
||||
m_scrolled_window->SetScrollRate(FromDIP(10), FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(FromDIP(10), FromDIP(20));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_scrolled_window->SetMaxSize(wxSize(popup_width, popup_height));
|
||||
m_scrolled_window->SetMinSize(wxSize(popup_width, popup_height));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(20));
|
||||
}
|
||||
|
||||
//Vertical Direction Processing
|
||||
@@ -2117,7 +2117,7 @@ void AmsReplaceMaterialDialog::create()
|
||||
identical_filament->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#009688"))); // Orca: accent teal (not brand green)
|
||||
|
||||
m_scrollview_groups = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrollview_groups->SetScrollRate(5, 5);
|
||||
m_scrollview_groups->SetScrollRate(5, FromDIP(20));
|
||||
//m_scrollview_groups->SetMinSize(wxSize(400, 400));
|
||||
//m_scrollview_groups->SetMaxSize(wxSize(400, 400));
|
||||
m_scrollview_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -606,7 +606,7 @@ AuFolderPanel::AuFolderPanel(wxWindow *parent, AuxiliaryFolderType type, wxWindo
|
||||
wxBoxSizer *sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
wxBoxSizer *sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer *sizer_top = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
@@ -863,17 +863,27 @@ void AuxiliaryPanel::init_tabpanel()
|
||||
m_tabpanel->SetBackgroundColour(wxColour("#FEFFFF"));
|
||||
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [](wxBookCtrlEvent &e) { /* Event handling */ });
|
||||
|
||||
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
|
||||
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
|
||||
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
|
||||
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
|
||||
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
|
||||
|
||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
|
||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
|
||||
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
|
||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
|
||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
|
||||
add_build_step([this] {
|
||||
m_designer_panel = new DesignerPanel(m_tabpanel, AuxiliaryFolderType::DESIGNER);
|
||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
|
||||
});
|
||||
add_build_step([this] {
|
||||
m_pictures_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::MODEL_PICTURE);
|
||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
|
||||
});
|
||||
add_build_step([this] {
|
||||
m_bill_of_materials_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::BILL_OF_MATERIALS);
|
||||
m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
|
||||
});
|
||||
add_build_step([this] {
|
||||
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
|
||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
|
||||
});
|
||||
add_build_step([this] {
|
||||
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
|
||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
|
||||
Layout();
|
||||
});
|
||||
}
|
||||
|
||||
wxWindow *AuxiliaryPanel::create_side_tools()
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "slic3r/GUI/UpgradePanel.hpp"
|
||||
#include "slic3r/GUI/AmsWidgets.hpp"
|
||||
#include "Widgets/SideTools.hpp"
|
||||
#include "StagedBuild.hpp"
|
||||
|
||||
#define AUFILE_GREY700 wxColour(107, 107, 107)
|
||||
#define AUFILE_GREY500 wxColour(158, 158, 158)
|
||||
@@ -194,7 +195,7 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class AuxiliaryPanel : public wxPanel
|
||||
class AuxiliaryPanel : public wxPanel, public StagedBuild
|
||||
{
|
||||
private:
|
||||
Tabbook *m_tabpanel = {nullptr};
|
||||
|
||||
@@ -558,7 +558,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
//show bind failed info
|
||||
m_sw_bind_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(450), FromDIP(300)), wxVSCROLL);
|
||||
m_sw_bind_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_bind_failed_info->SetScrollRate(5, 5);
|
||||
m_sw_bind_failed_info->SetScrollRate(5, FromDIP(20));
|
||||
m_sw_bind_failed_info->SetMinSize(wxSize(FromDIP(450), FromDIP(90)));
|
||||
m_sw_bind_failed_info->SetMaxSize(wxSize(FromDIP(450), FromDIP(90)));
|
||||
|
||||
|
||||
@@ -3521,7 +3521,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
cards->Show(m_box_mate, false, true);
|
||||
|
||||
m_form->FitInside();
|
||||
m_form->SetScrollRate(0, 10); // vertical only, like Prepare's sidebar: never scroll labels out
|
||||
m_form->SetScrollRate(0, FromDIP(20)); // vertical only, like Prepare's sidebar: never scroll labels out
|
||||
m_form->SetMinSize(wxSize(264, -1));
|
||||
|
||||
// Right column: a small view toolbar over the live 3D viewport that mirrors
|
||||
@@ -4862,13 +4862,13 @@ void DesignPanel::on_import_mesh()
|
||||
try {
|
||||
shape = GeometryEngine::mesh_to_brep(mesh.its, MESH_IMPORT_TOLERANCE,
|
||||
MESH_IMPORT_MERGE_ANGLE_DEG, stats);
|
||||
} catch (const std::exception& e) {
|
||||
fail(_L("Mesh conversion failed: ") + wxString::FromUTF8(e.what()));
|
||||
return;
|
||||
} catch (const Standard_Failure& e) { // OCCT throws outside std::exception
|
||||
} catch (const Standard_Failure& e) { // on OCCT >= 8 Standard_Failure derives from std::exception — must precede that handler
|
||||
fail(_L("Mesh conversion failed: ") + wxString::FromUTF8(
|
||||
e.GetMessageString() ? e.GetMessageString() : "OCCT error"));
|
||||
return;
|
||||
} catch (const std::exception& e) {
|
||||
fail(_L("Mesh conversion failed: ") + wxString::FromUTF8(e.what()));
|
||||
return;
|
||||
}
|
||||
if (shape.IsNull()) { fail(_L("Mesh conversion produced no geometry")); return; }
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "libslic3r/CAD/CadDocument.hpp"
|
||||
#include "slic3r/GUI/CAD/DesignInteraction.hpp" // CadLevel: what one Esc press means
|
||||
#include "slic3r/GUI/Lazy.hpp"
|
||||
|
||||
class ComboBox; // Orca dropdown (Widgets/ComboBox.hpp) — replaces wxChoice everywhere here
|
||||
class StaticBox; // Orca rounded card frame (Widgets/StaticBox.hpp)
|
||||
@@ -46,7 +47,7 @@ class DesignCanvas;
|
||||
// Design (CAD) tab: a sketch-first, Onshape-style form-driven CAD panel.
|
||||
// Sketch and Extrude are independent tools: the user creates a Sketch first,
|
||||
// then selects it and Extrudes to produce a solid.
|
||||
class DesignPanel : public wxPanel
|
||||
class DesignPanel : public wxPanel, public LazyInstance<DesignPanel>
|
||||
{
|
||||
public:
|
||||
explicit DesignPanel(wxWindow* parent);
|
||||
|
||||
@@ -1989,11 +1989,10 @@ json action_set_feature_expr(DesignPanel* panel, const json& params)
|
||||
// Dispatch one parsed request ON THE MAIN THREAD. Returns a JSON-RPC reply string.
|
||||
std::string handle_on_main(const std::string& method, const json& params, const json& id)
|
||||
{
|
||||
MainFrame* mf = wxGetApp().mainframe;
|
||||
// The panel is built on first use, and in a headless session nobody clicks the tab that
|
||||
// would build it -- so build it here rather than refusing. Safe: this runs on the main
|
||||
// thread (see the CallAfter that dispatches us).
|
||||
DesignPanel* panel = mf ? mf->ensure_design_panel() : nullptr;
|
||||
DesignPanel* panel = DesignPanel::ensure();
|
||||
if (!panel)
|
||||
return rpc_error(id, -32001, "Design panel not ready");
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ HistoryWindow::HistoryWindow(wxWindow* parent, const std::vector<PACalibResult>&
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto scroll_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
scroll_window->SetScrollRate(5, 5);
|
||||
scroll_window->SetScrollRate(5, FromDIP(20));
|
||||
scroll_window->SetBackgroundColour(*wxWHITE);
|
||||
scroll_window->SetMinSize(HISTORY_WINDOW_SIZE);
|
||||
scroll_window->SetSize(HISTORY_WINDOW_SIZE);
|
||||
|
||||
@@ -207,7 +207,7 @@ SelectMObjectPopup::SelectMObjectPopup(wxWindow* parent)
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_LIST_SIZE, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetMinSize(SELECT_MACHINE_LIST_SIZE);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, SELECT_MACHINE_ITEM_SIZE.y);
|
||||
auto m_sizxer_scrolledWindow = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow->SetSizer(m_sizxer_scrolledWindow);
|
||||
m_scrolledWindow->Layout();
|
||||
@@ -491,14 +491,15 @@ void CalibrationPanel::init_tabpanel() {
|
||||
selected);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
|
||||
add_build_steps_of(*m_cali_panels[i]);
|
||||
|
||||
// ORCA use standard paddings and keep arrow icon for consistent look between sidebars
|
||||
//for (int i = 0; i < (int)CALI_MODE_COUNT; i++)
|
||||
// m_tabpanel->SetPageImage(i, "");
|
||||
|
||||
//auto padding_size = m_tabpanel->GetBtnsListCtrl()->GetPaddingSize(0);
|
||||
//m_tabpanel->GetBtnsListCtrl()->SetPaddingSize({ FromDIP(15), padding_size.y });
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void CalibrationPanel::init_timer()
|
||||
@@ -534,6 +535,8 @@ void CalibrationPanel::update_print_error_info(int code, std::string msg, std::s
|
||||
}
|
||||
|
||||
void CalibrationPanel::update_all() {
|
||||
// Every wizard's pages exist once the last build step has run.
|
||||
if (!built()) return;
|
||||
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
@@ -597,7 +600,7 @@ void CalibrationPanel::update_all() {
|
||||
|
||||
void CalibrationPanel::show_status(int status)
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
if (!built()) return;
|
||||
if (last_status == status)return;
|
||||
last_status = status;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "CalibrationWizard.hpp"
|
||||
#include "Tabbook.hpp"
|
||||
#include "Lazy.hpp"
|
||||
//#include "Widgets/SideTools.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
@@ -88,7 +89,7 @@ private:
|
||||
};
|
||||
|
||||
|
||||
class CalibrationPanel : public wxPanel
|
||||
class CalibrationPanel : public wxPanel, public StagedBuild, public LazyInstance<CalibrationPanel>
|
||||
{
|
||||
public:
|
||||
CalibrationPanel(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
|
||||
@@ -109,7 +110,6 @@ protected:
|
||||
|
||||
|
||||
int last_status;
|
||||
bool m_initialized { false };
|
||||
std::string last_conn_type = "undedefined";
|
||||
MachineObject* obj{ nullptr };
|
||||
MachineObject* last_obj { nullptr };
|
||||
|
||||
@@ -81,7 +81,7 @@ CalibrationWizard::CalibrationWizard(wxWindow* parent, CalibMode mode, wxWindowI
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* padding_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
@@ -130,6 +130,15 @@ CalibrationWizard::~CalibrationWizard()
|
||||
;
|
||||
}
|
||||
|
||||
void CalibrationWizard::add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make)
|
||||
{
|
||||
add_build_step([this, &step, make = std::move(make)] {
|
||||
step = new CalibrationWizardPageStep(make());
|
||||
m_all_pages_sizer->Add(step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
step->page->Hide();
|
||||
});
|
||||
}
|
||||
|
||||
void CalibrationWizard::on_cali_job_finished(wxCommandEvent& event)
|
||||
{
|
||||
this->on_cali_job_finished(event.GetString());
|
||||
@@ -520,33 +529,28 @@ void PressureAdvanceWizard::on_cali_job_finished(wxString evt_data)
|
||||
|
||||
void PressureAdvanceWizard::create_pages()
|
||||
{
|
||||
start_step = new CalibrationWizardPageStep(new CalibrationPAStartPage(m_scrolledWindow));
|
||||
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
|
||||
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
|
||||
save_step = new CalibrationWizardPageStep(new CalibrationPASavePage(m_scrolledWindow));
|
||||
add_page_step(start_step, [this] { return new CalibrationPAStartPage(m_scrolledWindow); });
|
||||
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
|
||||
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
|
||||
add_page_step(save_step, [this] { return new CalibrationPASavePage(m_scrolledWindow); });
|
||||
|
||||
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
add_build_step([this] {
|
||||
m_page_steps.push_back(start_step);
|
||||
m_page_steps.push_back(preset_step);
|
||||
m_page_steps.push_back(cali_step);
|
||||
m_page_steps.push_back(save_step);
|
||||
|
||||
for (int i = 0; i < m_page_steps.size() -1; i++) {
|
||||
m_page_steps[i]->chain(m_page_steps[i+1]);
|
||||
}
|
||||
|
||||
m_page_steps.push_back(start_step);
|
||||
m_page_steps.push_back(preset_step);
|
||||
m_page_steps.push_back(cali_step);
|
||||
m_page_steps.push_back(save_step);
|
||||
for (int i = 0; i < m_page_steps.size(); i++) {
|
||||
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_page_steps.size() -1; i++) {
|
||||
m_page_steps[i]->chain(m_page_steps[i+1]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_page_steps.size(); i++) {
|
||||
m_page_steps[i]->page->Hide();
|
||||
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &PressureAdvanceWizard::on_cali_action, this);
|
||||
}
|
||||
|
||||
if (!m_page_steps.empty())
|
||||
show_step(m_page_steps.front());
|
||||
if (!m_page_steps.empty())
|
||||
show_step(m_page_steps.front());
|
||||
});
|
||||
}
|
||||
|
||||
void PressureAdvanceWizard::on_cali_action(wxCommandEvent& evt)
|
||||
@@ -1053,59 +1057,46 @@ FlowRateWizard::FlowRateWizard(wxWindow* parent, wxWindowID id, const wxPoint& p
|
||||
|
||||
void FlowRateWizard::create_pages()
|
||||
{
|
||||
start_step = new CalibrationWizardPageStep(new CalibrationFlowRateStartPage(m_scrolledWindow));
|
||||
preset_step = new CalibrationWizardPageStep(new CalibrationPresetPage(m_scrolledWindow, m_mode, false));
|
||||
add_page_step(start_step, [this] { return new CalibrationFlowRateStartPage(m_scrolledWindow); });
|
||||
add_page_step(preset_step, [this] { return new CalibrationPresetPage(m_scrolledWindow, m_mode, false); });
|
||||
|
||||
// manual
|
||||
cali_coarse_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI));
|
||||
coarse_save_step = new CalibrationWizardPageStep(new CalibrationFlowCoarseSavePage(m_scrolledWindow));
|
||||
cali_fine_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI));
|
||||
fine_save_step = new CalibrationWizardPageStep(new CalibrationFlowFineSavePage(m_scrolledWindow));
|
||||
add_page_step(cali_coarse_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_CALI); });
|
||||
add_page_step(coarse_save_step, [this] { return new CalibrationFlowCoarseSavePage(m_scrolledWindow); });
|
||||
add_page_step(cali_fine_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode, CaliPageType::CALI_PAGE_FINE_CALI); });
|
||||
add_page_step(fine_save_step, [this] { return new CalibrationFlowFineSavePage(m_scrolledWindow); });
|
||||
|
||||
// auto
|
||||
cali_step = new CalibrationWizardPageStep(new CalibrationCaliPage(m_scrolledWindow, m_mode));
|
||||
save_step = new CalibrationWizardPageStep(new CalibrationFlowX1SavePage(m_scrolledWindow));
|
||||
add_page_step(cali_step, [this] { return new CalibrationCaliPage(m_scrolledWindow, m_mode); });
|
||||
add_page_step(save_step, [this] { return new CalibrationFlowX1SavePage(m_scrolledWindow); });
|
||||
|
||||
m_all_pages_sizer->Add(start_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(preset_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(cali_coarse_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(coarse_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(cali_fine_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(fine_save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
add_build_step([this] {
|
||||
m_page_steps.push_back(start_step);
|
||||
m_page_steps.push_back(preset_step);
|
||||
m_page_steps.push_back(cali_coarse_step);
|
||||
m_page_steps.push_back(coarse_save_step);
|
||||
m_page_steps.push_back(cali_fine_step);
|
||||
m_page_steps.push_back(fine_save_step);
|
||||
|
||||
m_all_pages_sizer->Add(cali_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
m_all_pages_sizer->Add(save_step->page, 1, wxEXPAND | wxALL, FromDIP(25));
|
||||
//m_page_steps.push_back(cali_step);
|
||||
//m_page_steps.push_back(save_step);
|
||||
|
||||
m_page_steps.push_back(start_step);
|
||||
m_page_steps.push_back(preset_step);
|
||||
m_page_steps.push_back(cali_coarse_step);
|
||||
m_page_steps.push_back(coarse_save_step);
|
||||
m_page_steps.push_back(cali_fine_step);
|
||||
m_page_steps.push_back(fine_save_step);
|
||||
for (int i = 0; i < m_page_steps.size() - 1; i++) {
|
||||
m_page_steps[i]->chain(m_page_steps[i + 1]);
|
||||
}
|
||||
|
||||
//m_page_steps.push_back(cali_step);
|
||||
//m_page_steps.push_back(save_step);
|
||||
for (int i = 0; i < m_page_steps.size(); i++) {
|
||||
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_page_steps.size() - 1; i++) {
|
||||
m_page_steps[i]->chain(m_page_steps[i + 1]);
|
||||
}
|
||||
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
|
||||
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
|
||||
|
||||
// hide all pages
|
||||
cali_step->page->Hide();
|
||||
save_step->page->Hide();
|
||||
for (int i = 0; i < m_page_steps.size(); i++) {
|
||||
m_page_steps[i]->page->Hide();
|
||||
m_page_steps[i]->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
|
||||
}
|
||||
if (!m_page_steps.empty())
|
||||
show_step(m_page_steps.front());
|
||||
|
||||
|
||||
cali_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
|
||||
save_step->page->Bind(EVT_CALI_ACTION, &FlowRateWizard::on_cali_action, this);
|
||||
|
||||
if (!m_page_steps.empty())
|
||||
show_step(m_page_steps.front());
|
||||
|
||||
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
|
||||
set_cali_method(CalibrationMethod::CALI_METHOD_MANUAL);
|
||||
});
|
||||
}
|
||||
|
||||
void FlowRateWizard::on_cali_action(wxCommandEvent& evt)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "CalibrationWizardPresetPage.hpp"
|
||||
#include "CalibrationWizardCaliPage.hpp"
|
||||
#include "CalibrationWizardSavePage.hpp"
|
||||
#include "StagedBuild.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
@@ -36,7 +37,7 @@ struct ConfigIndexValue
|
||||
int index{0};
|
||||
};
|
||||
|
||||
class CalibrationWizard : public wxPanel {
|
||||
class CalibrationWizard : public wxPanel, public StagedBuild {
|
||||
public:
|
||||
CalibrationWizard(wxWindow* parent, CalibMode mode,
|
||||
wxWindowID id = wxID_ANY,
|
||||
@@ -79,6 +80,9 @@ public:
|
||||
protected:
|
||||
void on_cali_go_home();
|
||||
|
||||
// Queues a page as a build step, created hidden and added to the pages sizer.
|
||||
void add_page_step(CalibrationWizardPageStep*& step, std::function<CalibrationWizardPage*()> make);
|
||||
|
||||
protected:
|
||||
/* wx widgets*/
|
||||
wxScrolledWindow* m_scrolledWindow;
|
||||
|
||||
@@ -192,8 +192,10 @@ void CalibrationCaliPage::update(MachineObject* obj)
|
||||
set_cali_img();
|
||||
}
|
||||
|
||||
// A calibration can run before the Device tab is ever opened, and only its status
|
||||
// panel shows a print error.
|
||||
if (obj->print_error > 0) {
|
||||
StatusPanel* status_panel = Slic3r::GUI::wxGetApp().mainframe->m_monitor->get_status_panel();
|
||||
StatusPanel* status_panel = MonitorPanel::ensure()->get_status_panel();
|
||||
status_panel->obj = obj;
|
||||
status_panel->update_error_message();
|
||||
}
|
||||
|
||||
@@ -834,7 +834,7 @@ void CaliPageSendingPanel::create(wxWindow* parent)
|
||||
|
||||
m_sw_print_failed_info = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(380), FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
|
||||
|
||||
@@ -1046,6 +1046,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
|
||||
// Orca: both tower generators skip sparse layers, so this is not a wipe tower 2 exclusive.
|
||||
toggle_line("wipe_tower_no_sparse_layers", have_prime_tower);
|
||||
// Dropping the sparse layers outright leaves nothing to combine, so the two are exclusive.
|
||||
toggle_line("wipe_tower_sparse_layers_combination", have_prime_tower && !config->opt_bool("wipe_tower_no_sparse_layers"));
|
||||
|
||||
WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type");
|
||||
bool have_rib_wall = (wipe_tower_wall_type == WipeTowerWallType::wtwRib)&&have_prime_tower;
|
||||
|
||||
@@ -2710,7 +2710,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
// We can now enable scrolling on hscroll
|
||||
p->hscroll->SetScrollRate(30, 30);
|
||||
p->hscroll->SetScrollRate(30, FromDIP(20));
|
||||
|
||||
on_window_geometry(this, [this]() {
|
||||
p->init_dialog_size();
|
||||
|
||||
@@ -699,7 +699,7 @@ CreateFilamentPresetDialog::CreateFilamentPresetDialog(wxWindow *parent)
|
||||
m_scrolled_preset_panel = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrolled_preset_panel->SetMaxSize(wxSize(-1, FromDIP(350)));
|
||||
m_scrolled_preset_panel->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_preset_panel->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_panel->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolled_sizer->Add(create_item(FilamentOptionType::PRESET_FOR_PRINTER), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5));
|
||||
m_scrolled_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
|
||||
@@ -1593,7 +1593,7 @@ CreatePrinterPresetDialog::CreatePrinterPresetDialog(wxWindow *parent)
|
||||
|
||||
m_page1 = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_page1->SetBackgroundColour(*wxWHITE);
|
||||
m_page1->SetScrollRate(5, 5);
|
||||
m_page1->SetScrollRate(5, FromDIP(20));
|
||||
m_page2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_page2->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
create_printer_page1(m_page1);
|
||||
@@ -2652,7 +2652,7 @@ wxBoxSizer *CreatePrinterPresetDialog::create_presets_template_item(wxWindow *pa
|
||||
wxBoxSizer *vertical_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolled_preset_window = new wxScrolledWindow(parent);
|
||||
m_scrolled_preset_window->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_window->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_preset_window->SetBackgroundColour(*wxWHITE);
|
||||
//m_scrolled_preset_window->SetMinSize(wxSize(FromDIP(1500), FromDIP(-1)));
|
||||
m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(1500), FromDIP(-1)));
|
||||
@@ -4297,7 +4297,7 @@ wxBoxSizer *ExportConfigsDialog::create_select_printer(wxWindow *parent)
|
||||
optionSizer->SetMinSize(OPTION_SIZE);
|
||||
horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL, FromDIP(10));
|
||||
m_scrolled_preset_window = new wxScrolledWindow(parent);
|
||||
m_scrolled_preset_window->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_window->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_preset_window->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(660), FromDIP(400)));
|
||||
m_scrolled_preset_window->SetSize(wxSize(FromDIP(660), FromDIP(400)));
|
||||
@@ -4736,7 +4736,7 @@ wxBoxSizer *EditFilamentPresetDialog::create_preset_tree_sizer()
|
||||
{
|
||||
wxBoxSizer *filament_preset_tree_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_preset_tree_window = new wxScrolledWindow(this);
|
||||
m_preset_tree_window->SetScrollRate(5, 5);
|
||||
m_preset_tree_window->SetScrollRate(5, FromDIP(20));
|
||||
m_preset_tree_window->SetBackgroundColour(PRINTER_LIST_COLOUR);
|
||||
m_preset_tree_window->SetMinSize(wxSize(-1, FromDIP(400)));
|
||||
m_preset_tree_window->SetMaxSize(wxSize(-1, FromDIP(300)));
|
||||
|
||||
@@ -39,7 +39,7 @@ DeviceErrorDialog::DeviceErrorDialog(MachineObject* obj, wxWindow* parent, wxWin
|
||||
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
|
||||
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scroll_area->SetScrollRate(0, 5);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetBackgroundColour(*wxWHITE);
|
||||
m_scroll_area->SetMinSize(wxSize(FromDIP(320), FromDIP(250)));
|
||||
|
||||
@@ -538,7 +538,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
|
||||
break;
|
||||
}
|
||||
case DeviceErrorDialog::CHECK_ASSISTANT: {
|
||||
wxGetApp().mainframe->m_monitor->jump_to_HMS(); // go to assistant page
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built())
|
||||
monitor->jump_to_HMS(); // go to assistant page
|
||||
break;
|
||||
}
|
||||
case DeviceErrorDialog::FILAMENT_EXTRUDED: {
|
||||
@@ -568,7 +569,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
|
||||
}
|
||||
case DeviceErrorDialog::JUMP_TO_LIVEVIEW: {
|
||||
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
|
||||
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_LiveView();
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built())
|
||||
monitor->jump_to_LiveView();
|
||||
break;
|
||||
}
|
||||
case DeviceErrorDialog::NO_REMINDER_NEXT_TIME: {
|
||||
@@ -618,7 +620,8 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
|
||||
}
|
||||
case DeviceErrorDialog::OK_JUMP_RACK: {
|
||||
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
|
||||
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built())
|
||||
monitor->jump_to_Rack();
|
||||
break;
|
||||
}
|
||||
case DeviceErrorDialog::ABORT: {
|
||||
|
||||
@@ -934,7 +934,8 @@ void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
|
||||
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built())
|
||||
monitor->jump_to_Upgrade();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
{
|
||||
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built())
|
||||
monitor->jump_to_Upgrade();
|
||||
|
||||
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
|
||||
evt.SetEventObject(this);
|
||||
|
||||
@@ -1443,6 +1443,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
libvgcode::EGCodeExtrusionRole::SupportTransition, libvgcode::EGCodeExtrusionRole::Mixed
|
||||
});
|
||||
m_paths_bounding_box = BoundingBoxf3(libvgcode::convert(bbox[0]).cast<double>(), libvgcode::convert(bbox[1]).cast<double>());
|
||||
m_max_bounding_box = m_paths_bounding_box;
|
||||
|
||||
if (wxGetApp().is_editor())
|
||||
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
|
||||
|
||||
+235
-60
@@ -20,6 +20,7 @@
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "CameraUtils.hpp"
|
||||
#include "GLShader.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "Tab.hpp"
|
||||
@@ -117,6 +118,36 @@ void GLCanvas3D::load_render_colors()
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static void pan_camera(Camera& camera, const Vec2d& screen_delta, const Vec3d& anchor)
|
||||
{
|
||||
// Orca: Derive world-units-per-pixel from the projection which produced the visible frame.
|
||||
// Perspective additionally scales with the eye-space depth of the point being dragged.
|
||||
const auto& viewport = camera.get_viewport();
|
||||
const auto& projection = camera.get_projection_matrix().matrix();
|
||||
const double depth_scale = camera.get_type() == Camera::EType::Perspective ?
|
||||
(anchor - camera.get_position()).dot(camera.get_dir_forward()) : 1.0;
|
||||
const double projection_x = projection(0, 0) * viewport[2];
|
||||
const double projection_y = projection(1, 1) * viewport[3];
|
||||
|
||||
// Orca: X/Y projection coefficients already include zoom. Using them directly avoids a
|
||||
// project/unproject round-trip through window depth, whose precision depends on the scene frustum.
|
||||
if (viewport[2] > 0 && viewport[3] > 0 && anchor.allFinite() && depth_scale > EPSILON &&
|
||||
std::abs(projection_x) > EPSILON && std::abs(projection_y) > EPSILON) {
|
||||
const Vec3d displacement = 2.0 * depth_scale *
|
||||
(screen_delta.y() / projection_y * camera.get_dir_up() -
|
||||
screen_delta.x() / projection_x * camera.get_dir_right());
|
||||
if (displacement.allFinite()) {
|
||||
camera.translate(displacement);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Preserve the former target-plane behavior if the projection or anchor is invalid.
|
||||
// Screen Y grows downward, and the camera moves opposite to the drag.
|
||||
camera.translate(camera.get_inv_zoom() *
|
||||
(screen_delta.y() * camera.get_dir_up() - screen_delta.x() * camera.get_dir_right()));
|
||||
}
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
|
||||
RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {}
|
||||
@@ -1057,8 +1088,6 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent);
|
||||
@@ -2206,15 +2235,14 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
|
||||
_render_background();
|
||||
|
||||
//BBS add partplater rendering logic
|
||||
bool only_current = false, only_body = false, no_partplate = false;
|
||||
bool only_current = false, only_body = false;
|
||||
const bool show_bed = is_bed_visible();
|
||||
bool show_grid = true;
|
||||
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
||||
if (!m_main_toolbar.is_enabled()) {
|
||||
//only_body = true;
|
||||
only_current = true;
|
||||
}
|
||||
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
|
||||
no_partplate = true;
|
||||
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
|
||||
show_grid = false;
|
||||
if (m_axes_at_bed_center)
|
||||
@@ -2228,11 +2256,11 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
||||
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
|
||||
// floating would read as a rendering fault rather than a deliberate view option.
|
||||
if (!no_partplate && m_show_bed)
|
||||
if (show_bed)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
|
||||
if (!no_partplate && m_show_bed) //BBS: add outline logic
|
||||
if (show_bed) //BBS: add outline logic
|
||||
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
|
||||
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
|
||||
if (m_axes_at_bed_center && show_bed)
|
||||
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
|
||||
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
|
||||
@@ -2324,6 +2352,13 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
|
||||
render_thumbnail(thumbnail_data, w, h, thumbnail_params, model_objects, m_volumes, camera_type, camera_view_angle_type, for_picking, ban_light);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_set_shown_canvas_current()
|
||||
{
|
||||
// Thumbnails also render outside render(), where another library's GL context (e.g. WebKitGTK's) can be current.
|
||||
// Inside render(), the shown canvas is the one already bound.
|
||||
return wxGetApp().plater()->get_current_canvas3D()->_set_current();
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
|
||||
unsigned int w,
|
||||
unsigned int h,
|
||||
@@ -2335,6 +2370,9 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
|
||||
bool for_picking,
|
||||
bool ban_light)
|
||||
{
|
||||
if (!_set_shown_canvas_current())
|
||||
return;
|
||||
|
||||
GLShaderProgram* shader = nullptr;
|
||||
if (for_picking)
|
||||
shader = wxGetApp().get_shader("flat");
|
||||
@@ -2373,6 +2411,9 @@ void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_d
|
||||
bool for_picking,
|
||||
bool ban_light)
|
||||
{
|
||||
if (!_set_shown_canvas_current())
|
||||
return;
|
||||
|
||||
GLShaderProgram *shader = wxGetApp().get_shader("thumbnail");
|
||||
switch (OpenGLManager::get_framebuffers_type()) {
|
||||
case OpenGLManager::EFramebufferType::Arb: {
|
||||
@@ -3281,6 +3322,9 @@ void GLCanvas3D::unbind_event_handlers()
|
||||
m_canvas->Unbind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
|
||||
m_canvas->Unbind(wxEVT_GESTURE_ZOOM, &GLCanvas3D::on_gesture, this);
|
||||
m_canvas->Unbind(wxEVT_GESTURE_ROTATE, &GLCanvas3D::on_gesture, this);
|
||||
#if __WXOSX__
|
||||
initGestures(m_canvas->GetHandle(), nullptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4029,27 +4073,38 @@ void GLCanvas3D::on_gesture(wxGestureEvent &evt)
|
||||
|
||||
auto & camera = wxGetApp().plater()->get_camera();
|
||||
if (evt.GetEventType() == wxEVT_GESTURE_PAN) {
|
||||
auto p = evt.GetPosition();
|
||||
// Orca: Gesture coordinates must use framebuffer pixels, and one stable world-space
|
||||
// anchor must be retained for the complete gesture to prevent perspective drift.
|
||||
const auto p = evt.GetPosition();
|
||||
auto d = static_cast<wxPanGestureEvent&>(evt).GetDelta();
|
||||
float z = 0;
|
||||
const Vec3d &p2 = _mouse_to_3d({p.x, p.y}, &z);
|
||||
const Vec3d &p1 = _mouse_to_3d({p.x - d.x, p.y - d.y}, &z);
|
||||
camera.set_target(camera.get_target() + p1 - p2);
|
||||
Vec2d screen_position(p.x, p.y);
|
||||
Vec2d screen_delta(d.x, d.y);
|
||||
apply_retina_scale(screen_position);
|
||||
apply_retina_scale(screen_delta);
|
||||
if (evt.IsGestureStart() || !m_gesture_pan_anchor.has_value())
|
||||
m_gesture_pan_anchor = get_camera_pan_anchor(camera, ECameraNavigationType::Gesture,
|
||||
screen_position - screen_delta);
|
||||
pan_camera(camera, screen_delta, *m_gesture_pan_anchor);
|
||||
if (evt.IsGestureEnd())
|
||||
m_gesture_pan_anchor.reset();
|
||||
} else if (evt.GetEventType() == wxEVT_GESTURE_ZOOM) {
|
||||
static float zoom_start = 1;
|
||||
if (evt.IsGestureStart())
|
||||
zoom_start = camera.get_zoom();
|
||||
camera.set_zoom(zoom_start * static_cast<wxZoomGestureEvent&>(evt).GetZoomFactor());
|
||||
} else if (evt.GetEventType() == wxEVT_GESTURE_ROTATE) {
|
||||
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
// Orca: Rotation starts a different navigation operation, so a previous pan anchor
|
||||
// must not be reused; rotation and pan fallbacks share the same navigation pivot.
|
||||
m_gesture_pan_anchor.reset();
|
||||
bool rotate_limit = current_printer_technology() != ptSLA;
|
||||
static double last_rotate = 0;
|
||||
if (evt.IsGestureStart())
|
||||
last_rotate = 0;
|
||||
auto rotate = static_cast<wxRotateGestureEvent&>(evt).GetRotationAngle() - last_rotate;
|
||||
last_rotate += rotate;
|
||||
if (plate)
|
||||
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, plate->get_bounding_box().center());
|
||||
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Gesture);
|
||||
if (rotate_target.has_value())
|
||||
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, *rotate_target);
|
||||
else
|
||||
camera.rotate_on_sphere(-rotate, 0, rotate_limit);
|
||||
camera.auto_type(Camera::EType::Perspective);
|
||||
@@ -4339,6 +4394,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_SWITCH_TO_GLOBAL));
|
||||
}
|
||||
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
|
||||
// Orca: Retain the click position even if the first motion event crosses a surface edge.
|
||||
m_mouse.set_start_position_2D_as_invalid();
|
||||
m_mouse.drag.start_position_2D = pos;
|
||||
|
||||
//BBS: add orient deactivate logic
|
||||
if (!m_gizmos.on_mouse(evt)) {
|
||||
if (_deactivate_arrange_menu() || _deactivate_orient_menu())
|
||||
@@ -4532,6 +4591,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
}
|
||||
// do not process the dragging if the left mouse was set down in another canvas
|
||||
else if (is_camera_rotate(evt, button_mappings)) {
|
||||
// Orca: Rotation and panning use different drag coordinates and cached anchors.
|
||||
// Clear the pan state before processing rotation or switching buttons mid-drag.
|
||||
m_mouse.set_start_position_2D_as_invalid();
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
@@ -4549,12 +4611,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
if (this->m_canvas_type == ECanvasType::CanvasAssembleView || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports ||
|
||||
m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmSegmentation ||
|
||||
m_gizmos.get_current_type() == GLGizmosManager::FuzzySkin) {
|
||||
Vec3d rotate_target = Vec3d::Zero();
|
||||
if (!m_selection.is_empty())
|
||||
rotate_target = m_selection.get_bounding_box().center();
|
||||
// Orca: Reuse the centralized pivot policy for scene-oriented tools.
|
||||
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Mouse);
|
||||
if (rotate_target.has_value())
|
||||
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, *rotate_target);
|
||||
else
|
||||
rotate_target = volumes_bounding_box().center();
|
||||
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, rotate_target);
|
||||
camera.rotate_on_sphere(rot.x(), rot.y(), false);
|
||||
}
|
||||
else {
|
||||
if (wxGetApp().app_config->get_bool("use_free_camera"))
|
||||
@@ -4578,28 +4640,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
}
|
||||
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, m_rotation_center);
|
||||
} else {
|
||||
Vec3d rotate_target = Vec3d::Zero();
|
||||
if (m_canvas_type == ECanvasType::CanvasPreview) {
|
||||
PartPlate *plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
if (plate)
|
||||
rotate_target = plate->get_bounding_box().center();
|
||||
}
|
||||
else {
|
||||
if (!m_selection.is_empty())
|
||||
rotate_target = m_selection.get_bounding_box().center();
|
||||
else {
|
||||
// Rotate around the center of objects on current plate
|
||||
auto bbox = volumes_bounding_box(true);
|
||||
if (!bbox.defined) {
|
||||
// Rotate around current plate center if current plate is empty
|
||||
bbox = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box();
|
||||
}
|
||||
rotate_target = bbox.center();
|
||||
}
|
||||
}
|
||||
|
||||
if (!rotate_target.isZero())
|
||||
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, rotate_target);
|
||||
// Orca: Keep regular mouse orbit and perspective-pan fallback centered
|
||||
// on the same selection, active-plate, or scene reference.
|
||||
const std::optional<Vec3d> rotate_target = get_camera_orbit_target(ECameraNavigationType::Mouse);
|
||||
if (rotate_target.has_value())
|
||||
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, *rotate_target);
|
||||
else
|
||||
camera.rotate_on_sphere(rot.x(), rot.y(), rotate_limit);
|
||||
}
|
||||
@@ -4615,16 +4660,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
|
||||
}
|
||||
else if (is_camera_pan(evt, button_mappings)) {
|
||||
// Orca: Pan uses screen coordinates and must not inherit the rotation start point.
|
||||
m_mouse.set_start_position_3D_as_invalid();
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
|
||||
if (m_mouse.is_start_position_2D_defined()) {
|
||||
// get point in model space at Z = 0
|
||||
float z = 0.0f;
|
||||
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
|
||||
Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
|
||||
Camera& camera = wxGetApp().plater()->get_camera();
|
||||
if (this->m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
// Orca: Use a constrained camera when navigating the 3D scene with a regular mouse, if the free camera is not selected
|
||||
@@ -4636,7 +4679,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
camera.recover_from_free_camera();
|
||||
}
|
||||
|
||||
camera.set_target(camera.get_target() + orig - cur_pos);
|
||||
// Orca: Cache the surface under the initial click and apply every incremental
|
||||
// cursor delta at that depth, so perspective zoom and camera angle stay exact.
|
||||
const Vec2d screen_delta =
|
||||
pos.cast<double>() - m_mouse.drag.start_position_2D.cast<double>();
|
||||
if (!m_mouse.drag.camera_pan_anchor.has_value())
|
||||
m_mouse.drag.camera_pan_anchor = get_camera_pan_anchor(camera, ECameraNavigationType::Mouse,
|
||||
m_mouse.drag.start_position_2D.cast<double>());
|
||||
pan_camera(camera, screen_delta, *m_mouse.drag.camera_pan_anchor);
|
||||
m_dirty = true;
|
||||
m_mouse.ignore_right_up = true; // will be reset on button up event even if not right button is pressed
|
||||
}
|
||||
@@ -7231,6 +7281,16 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
||||
m_last_w = w;
|
||||
m_last_h = h;
|
||||
|
||||
set_imgui_scaling();
|
||||
|
||||
this->request_extra_frame();
|
||||
|
||||
// ensures that this canvas is current
|
||||
_set_current();
|
||||
}
|
||||
|
||||
void GLCanvas3D::set_imgui_scaling()
|
||||
{
|
||||
float font_size = wxGetApp().em_unit();
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -7243,15 +7303,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
||||
#endif
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
|
||||
wxGetApp().imgui()->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
|
||||
#else
|
||||
imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
|
||||
wxGetApp().imgui()->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
|
||||
#endif
|
||||
|
||||
this->request_extra_frame();
|
||||
|
||||
// ensures that this canvas is current
|
||||
_set_current();
|
||||
}
|
||||
|
||||
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model, bool include_plates) const
|
||||
@@ -7329,11 +7384,8 @@ void GLCanvas3D::_picking_pass()
|
||||
m_hover_volume_idxs.clear();
|
||||
m_hover_plate_idxs.clear();
|
||||
|
||||
// Orca: ignore clipping plane if not applying
|
||||
GLGizmoBase *current_gizmo = m_gizmos.get_current();
|
||||
const ClippingPlane clipping_plane = ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
|
||||
ClippingPlane::ClipsNothing())
|
||||
.inverted_normal();
|
||||
// Orca: Picking and camera navigation must interpret the active gizmo clipping plane identically.
|
||||
const ClippingPlane clipping_plane = get_raycaster_clipping_plane();
|
||||
const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane);
|
||||
if (hit.is_valid()) {
|
||||
switch (hit.type)
|
||||
@@ -10627,6 +10679,129 @@ Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
|
||||
return mouse_ray(mouse_pos).intersect_plane(0.0);
|
||||
}
|
||||
|
||||
ClippingPlane GLCanvas3D::get_raycaster_clipping_plane() const
|
||||
{
|
||||
// Orca: Ignore the gizmo clipping plane when the active tool does not apply it, and
|
||||
// invert the result into the convention expected by SceneRaycaster.
|
||||
GLGizmoBase* current_gizmo = m_gizmos.get_current();
|
||||
return ((!current_gizmo || current_gizmo->apply_clipping_plane()) ? m_gizmos.get_clipping_plane() :
|
||||
ClippingPlane::ClipsNothing())
|
||||
.inverted_normal();
|
||||
}
|
||||
|
||||
std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType navigation_type) const
|
||||
{
|
||||
// Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot
|
||||
// choose different reference depths for the same canvas and active tool.
|
||||
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
if (navigation_type == ECameraNavigationType::Gesture)
|
||||
return current_plate == nullptr ? std::nullopt :
|
||||
std::make_optional(current_plate->get_bounding_box().center());
|
||||
|
||||
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
||||
const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView ||
|
||||
gizmo_type == GLGizmosManager::FdmSupports || gizmo_type == GLGizmosManager::Seam ||
|
||||
gizmo_type == GLGizmosManager::MmSegmentation || gizmo_type == GLGizmosManager::FuzzySkin;
|
||||
if (use_scene_target) {
|
||||
if (!m_selection.is_empty())
|
||||
return m_selection.get_bounding_box().center();
|
||||
|
||||
// Orca: Preserve the world-origin fallback used by orbit in an empty scene.
|
||||
return volumes_bounding_box().center();
|
||||
}
|
||||
|
||||
// Orca: Free-camera rotation uses Camera::m_target rather than a plate or selection pivot.
|
||||
if (wxGetApp().app_config->get_bool("use_free_camera"))
|
||||
return std::nullopt;
|
||||
|
||||
Vec3d target = Vec3d::Zero();
|
||||
if (m_canvas_type == ECanvasType::CanvasPreview) {
|
||||
if (current_plate != nullptr)
|
||||
target = current_plate->get_bounding_box().center();
|
||||
} else if (!m_selection.is_empty()) {
|
||||
target = m_selection.get_bounding_box().center();
|
||||
} else {
|
||||
// Orca: Match regular mouse orbit: objects on the active plate, then the plate itself.
|
||||
BoundingBoxf3 bbox = volumes_bounding_box(true);
|
||||
if (!bbox.defined && current_plate != nullptr)
|
||||
bbox = current_plate->get_bounding_box();
|
||||
if (bbox.defined)
|
||||
target = bbox.center();
|
||||
}
|
||||
|
||||
// Orca: Preserve the existing zero sentinel used by regular mouse orbit.
|
||||
return target.isZero() ? std::nullopt : std::make_optional(target);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::is_bed_visible() const
|
||||
{
|
||||
if (m_canvas_type == ECanvasType::CanvasPreview)
|
||||
return m_render_preview;
|
||||
if (m_canvas_type != ECanvasType::CanvasView3D || !m_show_bed)
|
||||
return false;
|
||||
if (!m_main_toolbar.is_enabled())
|
||||
return true;
|
||||
|
||||
const auto type = m_gizmos.get_current_type();
|
||||
return type != GLGizmosManager::FdmSupports && type != GLGizmosManager::Seam &&
|
||||
type != GLGizmosManager::MmSegmentation && type != GLGizmosManager::FuzzySkin;
|
||||
}
|
||||
|
||||
Vec3d GLCanvas3D::get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
|
||||
const Vec2d& screen_position) const
|
||||
{
|
||||
// Orthographic panning has the same scale at every depth, so no raycast is needed.
|
||||
if (camera.get_type() != Camera::EType::Perspective)
|
||||
return camera.get_target();
|
||||
|
||||
// Orca: Reject non-finite anchors and points behind the camera before their depth is
|
||||
// allowed to scale a perspective pan.
|
||||
const Vec3d camera_position = camera.get_position();
|
||||
const Vec3d camera_forward = camera.get_dir_forward();
|
||||
const auto is_valid_anchor = [&camera_position, &camera_forward](const Vec3d& anchor) {
|
||||
return anchor.allFinite() && (anchor - camera_position).dot(camera_forward) > EPSILON;
|
||||
};
|
||||
|
||||
// Orca: Prefer the nearest visible bed or volume surface and exclude gizmos and
|
||||
// selected-volume picking priority from navigation depth selection.
|
||||
const ClippingPlane clipping_plane = get_raycaster_clipping_plane();
|
||||
const bool bed_visible = is_bed_visible();
|
||||
const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(screen_position, camera, &clipping_plane,
|
||||
bed_visible ? SceneRaycaster::EHitMode::SceneOnly : SceneRaycaster::EHitMode::VolumesOnly);
|
||||
if (hit.is_valid()) {
|
||||
const Vec3d hit_position = hit.position.cast<double>();
|
||||
if (is_valid_anchor(hit_position))
|
||||
return hit_position;
|
||||
}
|
||||
|
||||
// Orca: When the cursor is just outside the visible plate, use the point under it on the active
|
||||
// plate plane. Using the plate center here would give it a different perspective depth.
|
||||
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
// Orca: An almost edge-on perspective makes intersection depth extremely sensitive to
|
||||
// the cursor's vertical position. Use the stable orbit depth around horizontal views.
|
||||
static constexpr double min_plate_plane_forward_z = 0.05;
|
||||
if (bed_visible && std::abs(camera_forward.z()) >= min_plate_plane_forward_z &&
|
||||
current_plate != nullptr && current_plate->get_bounding_box().defined) {
|
||||
Vec3d ray_origin;
|
||||
Vec3d ray_direction;
|
||||
CameraUtils::ray_from_screen_pos(camera, screen_position, ray_origin, ray_direction);
|
||||
const double z_direction = ray_direction.z();
|
||||
if (ray_origin.allFinite() && ray_direction.allFinite() && std::abs(z_direction) > EPSILON) {
|
||||
const double plate_z = current_plate->get_bounding_box().center().z();
|
||||
const double distance = (plate_z - ray_origin.z()) / z_direction;
|
||||
const Vec3d plate_position = ray_origin + distance * ray_direction;
|
||||
const double eye_depth = (plate_position - camera_position).dot(camera_forward);
|
||||
if (distance >= 0.0 && is_valid_anchor(plate_position) &&
|
||||
eye_depth >= camera.get_near_z() && eye_depth <= camera.get_far_z())
|
||||
return plate_position;
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Near-horizontal rays and points outside the scene depth use the orbit reference point.
|
||||
const std::optional<Vec3d> orbit_target = get_camera_orbit_target(navigation_type);
|
||||
return orbit_target.has_value() && is_valid_anchor(*orbit_target) ? *orbit_target : camera.get_target();
|
||||
}
|
||||
|
||||
// While it looks like we can call
|
||||
// this->reload_scene(true, true)
|
||||
// the two functions are quite different:
|
||||
|
||||
@@ -185,8 +185,6 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent);
|
||||
@@ -338,6 +336,8 @@ class GLCanvas3D
|
||||
int move_volume_idx{ -1 };
|
||||
bool move_requires_threshold{ false };
|
||||
Point move_start_threshold_position_2D{ Invalid_2D_Point };
|
||||
// Orca: Keep the world-space point selected at the start of a mouse pan.
|
||||
std::optional<Vec3d> camera_pan_anchor;
|
||||
};
|
||||
|
||||
bool dragging{ false };
|
||||
@@ -346,7 +346,12 @@ class GLCanvas3D
|
||||
Drag drag;
|
||||
bool ignore_right_up;
|
||||
|
||||
void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; }
|
||||
// Orca: The screen-space start and world-space anchor describe the same pan session.
|
||||
// Invalidating one must invalidate the other so a new drag cannot reuse stale depth.
|
||||
void set_start_position_2D_as_invalid() {
|
||||
drag.start_position_2D = Drag::Invalid_2D_Point;
|
||||
drag.camera_pan_anchor.reset();
|
||||
}
|
||||
void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; }
|
||||
void set_move_start_threshold_position_2D_as_invalid() { drag.move_start_threshold_position_2D = Drag::Invalid_2D_Point; }
|
||||
|
||||
@@ -549,6 +554,8 @@ private:
|
||||
bool m_fps_overlay_tick{ false };
|
||||
LayersEditing m_layers_editing;
|
||||
Mouse m_mouse;
|
||||
// Orca: Gesture pans have their own lifecycle and stable world-space anchor.
|
||||
std::optional<Vec3d> m_gesture_pan_anchor;
|
||||
GLGizmosManager m_gizmos;
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
mutable GLToolbar m_main_toolbar;
|
||||
@@ -1292,6 +1299,8 @@ public:
|
||||
Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
|
||||
|
||||
bool make_current_for_postinit();
|
||||
// Sizes ImGui's fonts and style for this canvas; the fonts are rebuilt when the size changes.
|
||||
void set_imgui_scaling();
|
||||
|
||||
private:
|
||||
bool _is_shown_on_screen() const;
|
||||
@@ -1311,6 +1320,7 @@ private:
|
||||
bool _init_collapse_toolbar();
|
||||
|
||||
bool _set_current();
|
||||
bool _set_shown_canvas_current();
|
||||
void _resize(unsigned int w, unsigned int h);
|
||||
|
||||
//BBS: add part plate related logic
|
||||
@@ -1406,6 +1416,20 @@ private:
|
||||
// Convert the screen space coordinate to world coordinate on the bed.
|
||||
Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
|
||||
|
||||
// Orca: Navigation type selects the legacy pivot policy used when no visible surface is hit.
|
||||
enum class ECameraNavigationType : unsigned char
|
||||
{
|
||||
Mouse,
|
||||
Gesture
|
||||
};
|
||||
|
||||
// Orca: These helpers keep clipping, orbit pivots, and perspective-pan depth selection consistent.
|
||||
ClippingPlane get_raycaster_clipping_plane() const;
|
||||
bool is_bed_visible() const;
|
||||
std::optional<Vec3d> get_camera_orbit_target(ECameraNavigationType navigation_type) const;
|
||||
Vec3d get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
|
||||
const Vec2d& screen_position) const;
|
||||
|
||||
void _start_timer() { m_timer.Start(100, wxTIMER_CONTINUOUS); }
|
||||
void _stop_timer() { m_timer.Stop(); }
|
||||
|
||||
|
||||
@@ -456,8 +456,9 @@ void GLModel::init_from(const indexed_triangle_set& its)
|
||||
data.reserve_vertices(3 * its.indices.size());
|
||||
data.reserve_indices(3 * its.indices.size());
|
||||
|
||||
// Read user preference: smooth normals enabled
|
||||
const bool smooth_normals_enabled = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
// CLI thumbnail generation can initialize models without a wx application.
|
||||
const bool smooth_normals_enabled = wxApp::GetInstance() != nullptr && wxGetApp().app_config != nullptr &&
|
||||
wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
|
||||
if (smooth_normals_enabled) {
|
||||
// Use per-corner smooth normals (via IGL)
|
||||
|
||||
+67
-33
@@ -854,13 +854,18 @@ void GUI_App::post_init()
|
||||
slow_bootup = true;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", slow bootup, won't render gl here.";
|
||||
}
|
||||
if (!switch_to_3d) {
|
||||
// Starting on Home, the GL resources load at idle so Home paints first and Prepare is never
|
||||
// shown.
|
||||
const bool gl_at_idle = !starts_on_prepare() && is_editor();
|
||||
if (!switch_to_3d && gl_at_idle) {
|
||||
plater_->select_view_3D("3D");
|
||||
} else if (!switch_to_3d) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", begin load_gl_resources";
|
||||
#ifndef __linux__
|
||||
mainframe->Freeze();
|
||||
#endif
|
||||
plater_->canvas3D()->enable_render(false);
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
mainframe->select_prepare_for_gl_init();
|
||||
plater_->select_view_3D("3D");
|
||||
//BBS init the opengl resource here
|
||||
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
|
||||
@@ -897,9 +902,7 @@ void GUI_App::post_init()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished rendering a first frame for test";
|
||||
}
|
||||
}
|
||||
if (is_editor())
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
if (app_config->get("default_page") == "1")
|
||||
if (starts_on_prepare())
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
#ifndef __linux__
|
||||
mainframe->Thaw();
|
||||
@@ -909,6 +912,7 @@ void GUI_App::post_init()
|
||||
|
||||
plater_->trigger_restore_project(1);
|
||||
//#endif
|
||||
mainframe->prebuild_pages_when_idle();
|
||||
|
||||
//BBS: remove GCodeViewer as seperate APP logic
|
||||
/*if (this->init_params->start_as_gcodeviewer) {
|
||||
@@ -1923,9 +1927,9 @@ bool GUI_App::hot_reload_network_plugin()
|
||||
m_device_manager->add_user_subscribe();
|
||||
}
|
||||
|
||||
if (mainframe && mainframe->m_monitor) {
|
||||
mainframe->m_monitor->update_network_version_footer();
|
||||
mainframe->m_monitor->set_default();
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
|
||||
monitor->update_network_version_footer();
|
||||
monitor->set_default();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": reset monitor panel";
|
||||
}
|
||||
|
||||
@@ -3405,14 +3409,20 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "create the main window";
|
||||
mainframe = new MainFrame();
|
||||
// hide settings tabs after first Layout
|
||||
// The first render can happen as soon as the frame is shown, before the queued
|
||||
// new_project() sets the same view.
|
||||
plater_->get_camera().select_view("topfront");
|
||||
plater_->get_camera().requires_zoom_to_bed = true;
|
||||
if (is_editor()) {
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
if (starts_on_prepare()) {
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
} else {
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
}
|
||||
}
|
||||
|
||||
sidebar().obj_list()->init();
|
||||
//sidebar().aux_list()->init_auxiliary();
|
||||
mainframe->m_project->init_auxiliary();
|
||||
|
||||
// update_mode(); // !!! do that later
|
||||
SetTopWindow(mainframe);
|
||||
@@ -4122,13 +4132,13 @@ void GUI_App::select_machine(const std::string& agent_id)
|
||||
|
||||
// Use MonitorPanel::select_machine() to trigger full selection flow
|
||||
// This reuses existing logic for machine switching (UI updates, callbacks, etc.)
|
||||
if (mainframe && mainframe->m_monitor) {
|
||||
mainframe->m_monitor->select_machine(dev_id);
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built()) {
|
||||
monitor->select_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": triggered select_machine for dev_id=" << dev_id;
|
||||
} else {
|
||||
// Fallback if MonitorPanel not available
|
||||
m_device_manager->set_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": fallback set_selected_machine dev_id=" << dev_id;
|
||||
} else if (m_device_manager->set_selected_machine(dev_id)) {
|
||||
// The Device tab's own state is set when the tab is built.
|
||||
MonitorPanel::on_machine_selected(m_device_manager->get_selected_machine());
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": set_selected_machine dev_id=" << dev_id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4652,6 +4662,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
||||
|
||||
//BBS: trigger restore project logic here, and skip confirm
|
||||
plater_->trigger_restore_project(1);
|
||||
mainframe->prebuild_pages_when_idle();
|
||||
|
||||
// #ys_FIXME_delete_after_testing Do we still need this ?
|
||||
// CallAfter([]() {
|
||||
@@ -4993,7 +5004,8 @@ void GUI_App::get_login_info(const std::string& provider/* = ORCA_CLOUD_PROVIDER
|
||||
wxString strJS = wxString::Format("window.postMessage(%s)", from_u8(logout_cmd));
|
||||
GUI::wxGetApp().run_script(strJS);
|
||||
}
|
||||
mainframe->m_webview->SetLoginPanelVisibility(true);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SetLoginPanelVisibility(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5127,9 +5139,9 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
"homepage_bambu_login_or_register",
|
||||
};
|
||||
if (app_config->get_stealth_mode() && stealth_blocked_info_commands.count(command_str)) {
|
||||
CallAfter([this] {
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
CallAfter([] {
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendCloudProvidersInfo();
|
||||
});
|
||||
return "";
|
||||
}
|
||||
@@ -5143,8 +5155,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
app_config->set_bool("stealth_mode", false);
|
||||
app_config->save();
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendCloudProvidersInfo();
|
||||
// Continue with login
|
||||
if (command_str == "homepage_login_or_register")
|
||||
this->request_login(true);
|
||||
@@ -5225,8 +5237,8 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
}
|
||||
else if (command_str.compare("get_recent_projects") == 0) {
|
||||
if (mainframe) {
|
||||
if (mainframe->m_webview) {
|
||||
mainframe->m_webview->SendRecentList(INT_MAX);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built()) {
|
||||
home->SendRecentList(INT_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7759,8 +7771,8 @@ void GUI_App::on_stealth_mode_enter()
|
||||
BOOST_LOG_TRIVIAL(info) << "logout: on_stealth_mode_enter";
|
||||
request_user_logout(ORCA_CLOUD_PROVIDER);
|
||||
request_user_logout(BBL_CLOUD_PROVIDER);
|
||||
if (mainframe && mainframe->m_webview) {
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built()) {
|
||||
home->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8170,6 +8182,26 @@ ConfigOptionMode GUI_App::get_saved_mode()
|
||||
return saved_mode_from_string(app_config->get("user_mode"));
|
||||
}
|
||||
|
||||
bool GUI_App::starts_on_prepare() const
|
||||
{
|
||||
return app_config->get("default_page") == "1";
|
||||
}
|
||||
|
||||
int GUI_App::input_idle_ms() const
|
||||
{
|
||||
return int(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_last_input).count());
|
||||
}
|
||||
|
||||
// Every wxCommandEvent claims the user-input category, so only real mouse and key events count,
|
||||
// plus main window resizes, since a border drag produces no mouse events.
|
||||
int GUI_App::FilterEvent(wxEvent& event)
|
||||
{
|
||||
if ((!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT)) ||
|
||||
(event.GetEventType() == wxEVT_SIZE && event.GetEventObject() == mainframe))
|
||||
m_last_input = std::chrono::steady_clock::now();
|
||||
return Event_Skip;
|
||||
}
|
||||
|
||||
ConfigOptionMode GUI_App::get_mode()
|
||||
{
|
||||
return app_config->get_bool("developer_mode") ? comDevelop : get_saved_mode();
|
||||
@@ -8221,9 +8253,10 @@ void GUI_App::update_mode()
|
||||
mainframe->m_param_panel->update_mode();
|
||||
if (mainframe->m_param_dialog)
|
||||
mainframe->m_param_dialog->panel()->update_mode();
|
||||
if (mainframe->m_printer_view)
|
||||
mainframe->m_printer_view->update_mode();
|
||||
mainframe->m_webview->update_mode();
|
||||
if (PrinterWebView* view = PrinterWebView::if_built())
|
||||
view->update_mode();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->update_mode();
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
@@ -8241,9 +8274,10 @@ void GUI_App::update_mode()
|
||||
}
|
||||
|
||||
void GUI_App::update_internal_development() {
|
||||
mainframe->m_webview->update_mode();
|
||||
if (mainframe->m_printer_view)
|
||||
mainframe->m_printer_view->update_mode();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->update_mode();
|
||||
if (PrinterWebView* view = PrinterWebView::if_built())
|
||||
view->update_mode();
|
||||
}
|
||||
|
||||
void GUI_App::show_ip_address_enter_dialog(wxString title)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef slic3r_GUI_App_hpp_
|
||||
#define slic3r_GUI_App_hpp_
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -245,6 +246,7 @@ private:
|
||||
bool m_app_conf_exists{ false };
|
||||
EAppMode m_app_mode{ EAppMode::Editor };
|
||||
bool m_is_recreating_gui{ false };
|
||||
std::chrono::steady_clock::time_point m_last_input{ std::chrono::steady_clock::now() };
|
||||
#ifdef __linux__
|
||||
bool m_opengl_initialized{ false };
|
||||
#endif
|
||||
@@ -385,6 +387,11 @@ public:
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
bool is_recreating_gui() const { return m_is_recreating_gui; }
|
||||
// Milliseconds since the last mouse or keyboard event the app processed, or main window resize.
|
||||
int input_idle_ms() const;
|
||||
int FilterEvent(wxEvent& event) override;
|
||||
// The Preferences "Default page" choice, stored as its index: 0 Home, 1 Prepare.
|
||||
bool starts_on_prepare() const;
|
||||
std::string logo_name() const { return is_editor() ? "OrcaSlicer" : "OrcaSlicer-gcodeviewer"; }
|
||||
|
||||
bool is_closing() const { return m_is_closing.load(std::memory_order_acquire); }
|
||||
|
||||
@@ -2764,12 +2764,12 @@ ObjectTablePanel::ObjectTablePanel( wxWindow* parent, wxWindowID id, const wxPoi
|
||||
//m_object_grid->AssignTable(m_object_grid_table);
|
||||
|
||||
m_side_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(440),FromDIP(480)), wxVSCROLL);
|
||||
m_side_window->SetScrollRate( 0, 5 );
|
||||
m_side_window->SetScrollRate(0, FromDIP(20));
|
||||
m_page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
//m_page_top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_side_window->SetBackgroundColour(wxColour(0xff, 0xff, 0xff));
|
||||
m_side_window->SetSizer(m_page_sizer);
|
||||
m_side_window->SetScrollbars(1, 20, 1, 2);
|
||||
m_side_window->SetScrollbars(1, FromDIP(20), 1, 2);
|
||||
//m_side_window->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
|
||||
|
||||
//m_side_window->EnableScrolling(false, true);
|
||||
|
||||
@@ -247,7 +247,9 @@ int get_dpi_for_window(const wxWindow *window)
|
||||
|
||||
const HDC hdc = GetDC(hwnd);
|
||||
if (hdc == NULL) { return DPI_DEFAULT; }
|
||||
return GetDeviceCaps(hdc, LOGPIXELSX);
|
||||
const int dpi = GetDeviceCaps(hdc, LOGPIXELSX);
|
||||
ReleaseDC(hwnd, hdc); // GetDC's handle must be released, unlike GetWindowDC's
|
||||
return dpi;
|
||||
}
|
||||
#elif defined __linux__
|
||||
// TODO
|
||||
|
||||
@@ -122,7 +122,8 @@ HMSNotifyItem::HMSNotifyItem(const std::string& dev_id, wxWindow *parent, DevHMS
|
||||
m_hms_content->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
|
||||
wxCommandEvent evt(EVT_ALREADY_READ_HMS);
|
||||
evt.SetString(long_error_code);
|
||||
wxPostEvent(wxGetApp().mainframe->m_monitor, evt);
|
||||
if (MonitorPanel* monitor = MonitorPanel::if_built())
|
||||
wxPostEvent(monitor, evt);
|
||||
|
||||
if (!m_url.empty()) wxLaunchDefaultBrowser(m_url);
|
||||
});
|
||||
@@ -170,7 +171,7 @@ HMSPanel::HMSPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wx
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_top_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "IdleScheduler.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/evtloop.h>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
// The timer's period while waiting for the user to go quiet.
|
||||
constexpr int tick_ms = 250;
|
||||
// Input-free time before a slice may start, about a double-click interval, so a user
|
||||
// mid-gesture is left alone.
|
||||
constexpr int quiet_ms = 500;
|
||||
// Budget of one slice. The next slice is a timer message, so paint, timers and input
|
||||
// queued meanwhile are handled first; a click waits at most a slice plus the unit that
|
||||
// overran it.
|
||||
constexpr int slice_ms = 40;
|
||||
// Delay before the next slice; on GTK a due timer runs ahead of repaints and posted events,
|
||||
// and wxOSX rejects a 0 ms timer.
|
||||
constexpr int next_slice_ms = 5;
|
||||
|
||||
// True when unhandled keyboard, button, touch or pen input is queued; only Windows can ask.
|
||||
bool input_pending()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Windows synthesizes a mouse move whenever a window appears under the cursor, so those
|
||||
// do not count.
|
||||
return (GetQueueStatus(QS_INPUT & ~QS_MOUSEMOVE) >> 16) != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// True inside a wxYield(), where a slice would build pages in the middle of the code that yielded.
|
||||
bool yielding()
|
||||
{
|
||||
const wxEventLoopBase* loop = wxEventLoopBase::GetActive();
|
||||
return loop != nullptr && loop->IsYielding();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IdleScheduler::IdleScheduler(std::function<int()> input_idle_ms) : m_input_idle_ms(std::move(input_idle_ms))
|
||||
{
|
||||
m_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent&) { tick(); });
|
||||
}
|
||||
|
||||
void IdleScheduler::start()
|
||||
{
|
||||
if (!m_timer.IsRunning())
|
||||
m_timer.Start(tick_ms);
|
||||
}
|
||||
|
||||
void IdleScheduler::stop()
|
||||
{
|
||||
m_timer.Stop();
|
||||
}
|
||||
|
||||
void IdleScheduler::tick()
|
||||
{
|
||||
// A unit that pumps the event loop lets the timer fire inside its own slice.
|
||||
if (m_in_slice)
|
||||
return;
|
||||
if (!m_queue.pending()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (m_input_idle_ms() < quiet_ms || input_pending() || yielding()) {
|
||||
start();
|
||||
return;
|
||||
}
|
||||
const auto now_ms = [] {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
};
|
||||
const char* const fn = __FUNCTION__;
|
||||
m_in_slice = true;
|
||||
PrebuildQueue::Slice slice;
|
||||
{
|
||||
ScopeGuard in_slice([this] { m_in_slice = false; });
|
||||
slice = m_queue.run_slice(slice_ms, now_ms, input_pending, [fn](const std::string& name, long long ms) {
|
||||
BOOST_LOG_TRIVIAL(debug) << fn << ": " << name << ": unit took " << ms << " ms";
|
||||
});
|
||||
}
|
||||
if (slice.completed)
|
||||
BOOST_LOG_TRIVIAL(info) << fn << ": task complete: " << slice.name << ", " << slice.units << " unit(s) in " << slice.ms << " ms this slice";
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(debug) << fn << ": " << slice.name << ": " << slice.units << " unit(s) in " << slice.ms << " ms, yielding";
|
||||
if (!slice.remaining)
|
||||
stop();
|
||||
else if (input_pending())
|
||||
m_timer.Start(tick_ms);
|
||||
else
|
||||
m_timer.StartOnce(next_slice_ms);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
#include "PrebuildQueue.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Runs the queue's units while the user is idle. Once the user has been idle long enough,
|
||||
// slices run one per timer message until the work is done or input arrives, so the event
|
||||
// loop handles what it has between slices. Main thread only.
|
||||
class IdleScheduler
|
||||
{
|
||||
public:
|
||||
// input_idle_ms reports how long ago the user last touched the mouse or keyboard.
|
||||
explicit IdleScheduler(std::function<int()> input_idle_ms);
|
||||
|
||||
void add(LazyBase& task) { m_queue.add(task); }
|
||||
void clear() { m_queue.clear(); }
|
||||
// The task names in queue order, for a log line.
|
||||
std::string names() const { return m_queue.names(); }
|
||||
|
||||
// Arms the timer; it stops itself once no task is pending, so call again when a task
|
||||
// appears.
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void tick();
|
||||
|
||||
PrebuildQueue m_queue;
|
||||
wxTimer m_timer;
|
||||
std::function<int()> m_input_idle_ms;
|
||||
bool m_in_slice{ false };
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -159,8 +159,12 @@ void KBShortcutsDialog::fill_pages()
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
page(_L("Global"), _L("Available anywhere in the window, even while typing in a text field."), ShortcutContext::Global, {
|
||||
fixed(Section::Application, { alt, "1-9, 0" }, L("Run a speed dial favorite while the dial is open")),
|
||||
fixed(Section::SpeedDial, { alt, "1-9, 0" }, L("Run favorite 1 to 10")),
|
||||
fixed(Section::SpeedDial, { ctrl, "B" }, L("Pin or unpin the selected action")),
|
||||
// wx cycles notebook pages on Ctrl+Tab, which is Cmd+Tab on macOS and never arrives there.
|
||||
#ifndef __APPLE__
|
||||
fixed(Section::Application, { ctrl, key(L_CONTEXT("Tab", "Keyboard Shortcut")) }, L("Switch to the next main tab")),
|
||||
#endif
|
||||
});
|
||||
|
||||
page(_L("Prepare"), _L("Available while the 3D view on the Prepare tab has focus."), ShortcutContext::Plater, {
|
||||
@@ -207,7 +211,7 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const Page& page)
|
||||
wxGetApp().UpdateDarkUI(scrollable_panel);
|
||||
const wxColour page_colour = StateColor::darkModeColorFor(*wxWHITE);
|
||||
scrollable_panel->SetBackgroundColour(page_colour);
|
||||
scrollable_panel->SetScrollRate(0, 20);
|
||||
scrollable_panel->SetScrollRate(0, FromDIP(20));
|
||||
const int page_width = FromDIP(PAGE_WIDTH);
|
||||
scrollable_panel->SetInitialSize(wxSize(page_width, FromDIP(450)));
|
||||
|
||||
@@ -454,7 +458,10 @@ ShortcutCaptureDialog::ShortcutCaptureDialog(wxWindow* parent, Shortcut shortcut
|
||||
capture_sizer->Add(m_chord_label, 0, wxALIGN_CENTER);
|
||||
capture_sizer->AddStretchSpacer();
|
||||
capture->SetSizer(capture_sizer);
|
||||
capture->Bind(wxEVT_KEY_DOWN, &ShortcutCaptureDialog::on_key, this);
|
||||
capture->Layout(); // the box is created at its final size, so nothing resizes it into laying the sizer out
|
||||
// The hook runs before the window procedure, so Windows does not open its window menu
|
||||
// over the dialog on Alt+Space.
|
||||
Bind(wxEVT_CHAR_HOOK, &ShortcutCaptureDialog::on_key, this);
|
||||
capture->Bind(wxEVT_CHAR, &ShortcutCaptureDialog::on_char, this);
|
||||
capture->Bind(wxEVT_LEFT_DOWN, [capture](wxMouseEvent&) { capture->SetFocus(); });
|
||||
sizer->Add(capture, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(20));
|
||||
@@ -531,10 +538,12 @@ void ShortcutCaptureDialog::record(const KeyChord& chord)
|
||||
m_ok->Enable(false);
|
||||
};
|
||||
const bool global = (shortcut_info(m_shortcut).contexts & context_bit(ShortcutContext::Global)) != 0;
|
||||
if (global && !chord.is_menu_accelerator()) {
|
||||
if (chord.is_system_shortcut()) {
|
||||
reject(_L("The system uses this shortcut, so it cannot be assigned."));
|
||||
} else if (global && !chord.is_menu_accelerator()) {
|
||||
reject(m_rejection);
|
||||
} else if (const std::optional<Shortcut> owner = wxGetApp().shortcuts().step_owner(m_shortcut, chord); owner.has_value()) {
|
||||
reject(wxString::Format(_L("Already used as a step of %s."), _(shortcut_info(*owner).name)));
|
||||
reject(wxString::Format(_L("Shift and Ctrl with this key belong to %s and cannot be assigned."), _(shortcut_info(*owner).name)));
|
||||
} else {
|
||||
m_conflicts = wxGetApp().shortcuts().conflicts(m_shortcut, chord);
|
||||
m_status->SetForegroundColour(m_status_colour);
|
||||
|
||||
@@ -182,6 +182,16 @@ bool KeyChord::is_menu_accelerator() const
|
||||
return valid() && ((modifiers & (wxMOD_CONTROL | wxMOD_ALT | wxMOD_RAW_CONTROL)) != 0 || (!is_printable(key) && key != WXK_SPACE));
|
||||
}
|
||||
|
||||
// Only a chord the desktop acts on while still delivering it to the app belongs here.
|
||||
bool KeyChord::is_system_shortcut() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return modifiers == wxMOD_ALT && (key == WXK_F4 || key == WXK_SPACE);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string KeyChord::to_string() const
|
||||
{
|
||||
if (!valid())
|
||||
|
||||
@@ -34,6 +34,9 @@ struct KeyChord
|
||||
// True when Ctrl or Alt is held or the key is non-printable, the chords a menu can own without
|
||||
// swallowing typing in text fields.
|
||||
bool is_menu_accelerator() const;
|
||||
// True for a chord the desktop acts on although the app receives it, so a binding would
|
||||
// take it from the system.
|
||||
bool is_system_shortcut() const;
|
||||
|
||||
// Platform-neutral text ("Ctrl+Shift+S") for persistence and wx accelerator strings.
|
||||
std::string to_string() const;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "Lazy.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/app.h>
|
||||
#include <wx/utils.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
LazyBase::OnDemandBuild::OnDemandBuild(const LazyBase& lazy) : m_lazy(lazy), m_started(std::chrono::steady_clock::now())
|
||||
{
|
||||
// The unit tests have no app.
|
||||
if (wxTheApp != nullptr)
|
||||
m_busy = std::make_unique<wxBusyCursor>();
|
||||
}
|
||||
|
||||
LazyBase::OnDemandBuild::~OnDemandBuild()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Lazy::ensure: built " << m_lazy.name() << " on demand, " << m_units << " unit(s) in "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_started).count() << " ms";
|
||||
}
|
||||
|
||||
void LazyBase::log_null_factory(const std::string& name)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Lazy::build_step: the factory for " << name << " returned null";
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,198 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "StagedBuild.hpp"
|
||||
|
||||
class wxBusyCursor;
|
||||
|
||||
// Deferred construction of one object. Lazy<T> holds the factory and builds the object on
|
||||
// demand or, unit by unit, from an IdleScheduler; LazyBase is what the scheduler sees of
|
||||
// it; LazyInstance<Self> gives a type with one such object app-wide static access to it.
|
||||
// docs/HLSD/deferred-page-construction.md covers the design.
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
template <class Self> class LazyInstance;
|
||||
|
||||
// A prebuild task: what PrebuildQueue sees of a Lazy<T>. A type with its own unit sequence
|
||||
// implements it directly.
|
||||
class LazyBase
|
||||
{
|
||||
public:
|
||||
virtual ~LazyBase() = default;
|
||||
|
||||
// Labels the object in logs.
|
||||
virtual const std::string& name() const = 0;
|
||||
virtual bool built() const = 0;
|
||||
// Whether the idle prebuild has work here.
|
||||
virtual bool pending() const { return !built(); }
|
||||
// Runs one unit and returns true while more remain.
|
||||
virtual bool build_step() = 0;
|
||||
// Position in the idle queue: lower builds first, negative is never prebuilt.
|
||||
virtual int prebuild_order() const = 0;
|
||||
|
||||
protected:
|
||||
// Busy cursor around a build the user is waiting on, and a log line after it.
|
||||
class OnDemandBuild
|
||||
{
|
||||
public:
|
||||
explicit OnDemandBuild(const LazyBase& lazy);
|
||||
~OnDemandBuild();
|
||||
void unit() { ++m_units; }
|
||||
|
||||
private:
|
||||
const LazyBase& m_lazy;
|
||||
std::unique_ptr<wxBusyCursor> m_busy;
|
||||
std::chrono::steady_clock::time_point m_started;
|
||||
int m_units{ 0 };
|
||||
};
|
||||
|
||||
static void log_null_factory(const std::string& name);
|
||||
};
|
||||
|
||||
// Holds an object a factory makes on the first build_step() or ensure(), followed by one
|
||||
// StagedBuild step per unit if the type has them, and keeps callbacks until the object is
|
||||
// complete. The object's parent owns it, not the holder. A LazyInstance type is registered
|
||||
// for its statics; any other type is reached only through the holder.
|
||||
template <class T>
|
||||
class Lazy : public LazyBase
|
||||
{
|
||||
public:
|
||||
using Factory = std::function<T*()>;
|
||||
|
||||
Lazy(std::string name, int order, Factory make) : m_name(std::move(name)), m_order(order), m_make(std::move(make))
|
||||
{
|
||||
if constexpr (registered())
|
||||
LazyInstance<T>::s_lazy = this;
|
||||
}
|
||||
~Lazy() override
|
||||
{
|
||||
if constexpr (registered())
|
||||
if (LazyInstance<T>::s_lazy == this)
|
||||
LazyInstance<T>::s_lazy = nullptr;
|
||||
}
|
||||
Lazy(const Lazy&) = delete;
|
||||
Lazy& operator=(const Lazy&) = delete;
|
||||
|
||||
// Null until completely built.
|
||||
T* get() const { return built() ? m_object : nullptr; }
|
||||
|
||||
// Builds whatever is left now and returns the object, null if the factory returned null
|
||||
// or a nested call finds the object mid-build.
|
||||
T* ensure()
|
||||
{
|
||||
if (!built() && !m_building) {
|
||||
OnDemandBuild build(*this);
|
||||
do
|
||||
build.unit();
|
||||
while (build_step());
|
||||
}
|
||||
return get();
|
||||
}
|
||||
|
||||
// Runs fn on the object now if it is built, otherwise once it is.
|
||||
void when_built(std::function<void(T&)> fn)
|
||||
{
|
||||
if (built())
|
||||
fn(*m_object);
|
||||
else
|
||||
m_deferred.push_back(std::move(fn));
|
||||
}
|
||||
|
||||
const std::string& name() const override { return m_name; }
|
||||
// Read by a worker thread through the statics; the last write in build_step() releases it.
|
||||
bool built() const override { return m_complete.load(std::memory_order_acquire); }
|
||||
bool pending() const override { return !built() && !m_failed; }
|
||||
int prebuild_order() const override { return m_order; }
|
||||
|
||||
// The factory is the first unit, then one StagedBuild step each. A unit that pumps the
|
||||
// event loop cannot re-enter; a nested call does nothing.
|
||||
bool build_step() override
|
||||
{
|
||||
if (built() || m_building || m_failed)
|
||||
return false;
|
||||
m_building = true;
|
||||
try {
|
||||
if (m_object == nullptr)
|
||||
m_object = m_make();
|
||||
else
|
||||
step();
|
||||
} catch (...) {
|
||||
m_building = false;
|
||||
throw;
|
||||
}
|
||||
m_building = false;
|
||||
if (m_object == nullptr) {
|
||||
m_failed = true;
|
||||
log_null_factory(m_name);
|
||||
return false;
|
||||
}
|
||||
if (steps_remain())
|
||||
return true;
|
||||
m_complete.store(true, std::memory_order_release);
|
||||
for (auto& fn : m_deferred)
|
||||
fn(*m_object);
|
||||
m_deferred.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr bool registered() { return std::is_base_of_v<LazyInstance<T>, T>; }
|
||||
static constexpr bool staged() { return std::is_base_of_v<StagedBuild, T>; }
|
||||
|
||||
bool steps_remain() const
|
||||
{
|
||||
if constexpr (staged())
|
||||
return !m_object->built();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
void step()
|
||||
{
|
||||
if constexpr (staged())
|
||||
m_object->build_step();
|
||||
}
|
||||
|
||||
std::string m_name;
|
||||
int m_order;
|
||||
Factory m_make;
|
||||
T* m_object{ nullptr };
|
||||
std::atomic<bool> m_complete{ false };
|
||||
bool m_building{ false };
|
||||
bool m_failed{ false };
|
||||
std::vector<std::function<void(T&)>> m_deferred;
|
||||
};
|
||||
|
||||
// Mixin for a type with one lazily built instance in the app. The statics reach that
|
||||
// instance through its Lazy holder (a recreated MainFrame's holder replaces the old
|
||||
// frame's).
|
||||
template <class Self>
|
||||
class LazyInstance
|
||||
{
|
||||
public:
|
||||
// Null until completely built.
|
||||
static Self* if_built() { return s_lazy ? s_lazy->get() : nullptr; }
|
||||
// Builds the object if needed; null while no holder exists or the holder has no
|
||||
// object.
|
||||
static Self* ensure() { return s_lazy ? s_lazy->ensure() : nullptr; }
|
||||
// Runs fn on the object now if it is built, otherwise once it is.
|
||||
static void when_built(std::function<void(Self&)> fn)
|
||||
{
|
||||
if (s_lazy)
|
||||
s_lazy->when_built(std::move(fn));
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Lazy<Self>;
|
||||
inline static Lazy<Self>* s_lazy{ nullptr };
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "LazyPage.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
void apply_dark_ui_to_lazy_panel(wxWindow* panel)
|
||||
{
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxGetApp().UpdateDarkUIWin(panel);
|
||||
#endif
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <wx/bookctrl.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
#include "Lazy.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Applies the app's dark-mode pass to a panel built after the frame's own pass ran.
|
||||
void apply_dark_ui_to_lazy_panel(wxWindow* panel);
|
||||
|
||||
// A page whose panel is built the first time the page is shown, or earlier by an
|
||||
// IdleScheduler; nothing builds while the frame is still hidden. The Lazy side holds the
|
||||
// panel and, for a LazyInstance panel type, registers it for the type's statics.
|
||||
template <class Panel>
|
||||
class LazyPage : public wxPanel, public Lazy<Panel>
|
||||
{
|
||||
public:
|
||||
using Factory = std::function<Panel*(wxWindow* parent)>;
|
||||
|
||||
LazyPage(wxWindow* parent, std::string name, int order, Factory make = [](wxWindow* parent) { return new Panel(parent); })
|
||||
: wxPanel(parent), Lazy<Panel>(std::move(name), order, [this, make = std::move(make)] {
|
||||
Panel* panel = make(this);
|
||||
GetSizer()->Add(panel, 1, wxEXPAND);
|
||||
// Hidden while its page is, as an inserted page would be.
|
||||
if (!IsShown())
|
||||
panel->Hide();
|
||||
return panel;
|
||||
})
|
||||
{
|
||||
SetSizer(new wxBoxSizer(wxVERTICAL));
|
||||
// Shown by the book when its tab is selected.
|
||||
Hide();
|
||||
this->when_built([this](Panel& panel) {
|
||||
apply_dark_ui_to_lazy_panel(&panel);
|
||||
Layout();
|
||||
});
|
||||
}
|
||||
|
||||
// The parent book currently lists this page.
|
||||
bool in_book() const
|
||||
{
|
||||
auto* book = dynamic_cast<wxBookCtrlBase*>(GetParent());
|
||||
return book != nullptr && book->FindPage(this) != wxNOT_FOUND;
|
||||
}
|
||||
|
||||
// Pending only while the tab is in the book.
|
||||
bool pending() const override { return in_book() && Lazy<Panel>::pending(); }
|
||||
|
||||
// Forwarded so the panel's own Show() override stays its activation hook; wx calls this
|
||||
// virtual from the book's ShowWithEffect() only for wxSHOW_EFFECT_NONE, the default.
|
||||
bool Show(bool show = true) override
|
||||
{
|
||||
const bool changed = wxPanel::Show(show);
|
||||
if (show) {
|
||||
// The book shows its first page as it is inserted, before startup has chosen the
|
||||
// start page, so a hidden frame builds nothing; MainFrame::Show() completes it.
|
||||
if (this->built() || wxGetTopLevelParent(this)->IsShown()) {
|
||||
if (Panel* panel = this->ensure())
|
||||
panel->Show(true);
|
||||
// The sizer skipped the panel while the book kept it hidden.
|
||||
Layout();
|
||||
}
|
||||
} else if (Panel* panel = this->get()) {
|
||||
panel->Show(false);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
+229
-170
@@ -81,6 +81,7 @@
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gtk/gtk.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#endif // __WXGTK__
|
||||
#include <slic3r/GUI/CreatePresetsDialog.hpp>
|
||||
|
||||
@@ -317,7 +318,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
// BBS
|
||||
, m_recent_projects(18)
|
||||
, m_settings_dialog(this)
|
||||
, diff_dialog(this)
|
||||
, m_idle([] { return wxGetApp().input_idle_ms(); })
|
||||
, m_diff_dialog("compare_presets", 100, [this] { return make_diff_dialog(); })
|
||||
{
|
||||
#ifdef __WXOSX__
|
||||
set_miniaturizable(GetHandle());
|
||||
@@ -509,6 +511,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
wxQueueEvent(wxGetApp().plater(), new SimpleEvent(EVT_NOTICE_CHILDE_SIZE_CHANGED));
|
||||
|
||||
fit_tab_labels(); // ORCA on resize
|
||||
// Restarts the idle build so a hidden Prepare page is laid out at the new size.
|
||||
if (m_prebuild_started)
|
||||
m_idle.start();
|
||||
});
|
||||
|
||||
//BBS
|
||||
@@ -737,9 +742,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
|
||||
wxGetApp().persist_window_geometry(this, true);
|
||||
wxGetApp().persist_window_geometry(&m_settings_dialog, true);
|
||||
// bind events from DiffDlg
|
||||
|
||||
bind_diff_dialog();
|
||||
}
|
||||
|
||||
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
|
||||
@@ -832,8 +834,10 @@ bool MainFrame::handle_global_shortcut(const KeyChord& chord)
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainFrame::bind_diff_dialog()
|
||||
DiffPresetDialog* MainFrame::make_diff_dialog()
|
||||
{
|
||||
auto* dialog = new DiffPresetDialog(this);
|
||||
|
||||
auto get_tab = [](Preset::Type type) {
|
||||
Tab* null_tab = nullptr;
|
||||
for (Tab* tab : wxGetApp().tabs_list)
|
||||
@@ -842,23 +846,24 @@ void MainFrame::bind_diff_dialog()
|
||||
return null_tab;
|
||||
};
|
||||
|
||||
auto transfer = [this, get_tab](Preset::Type type) {
|
||||
get_tab(type)->transfer_options(diff_dialog.get_left_preset_name(type),
|
||||
diff_dialog.get_right_preset_name(type),
|
||||
diff_dialog.get_selected_options(type));
|
||||
auto transfer = [dialog, get_tab](Preset::Type type) {
|
||||
get_tab(type)->transfer_options(dialog->get_left_preset_name(type),
|
||||
dialog->get_right_preset_name(type),
|
||||
dialog->get_selected_options(type));
|
||||
};
|
||||
|
||||
auto process_options = [this](std::function<void(Preset::Type)> process) {
|
||||
const Preset::Type diff_dlg_type = diff_dialog.view_type();
|
||||
auto process_options = [dialog](std::function<void(Preset::Type)> process) {
|
||||
const Preset::Type diff_dlg_type = dialog->view_type();
|
||||
if (diff_dlg_type == Preset::TYPE_INVALID) {
|
||||
for (const Preset::Type& type : diff_dialog.types_list() )
|
||||
for (const Preset::Type& type : dialog->types_list() )
|
||||
process(type);
|
||||
}
|
||||
else
|
||||
process(diff_dlg_type);
|
||||
};
|
||||
|
||||
diff_dialog.Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
|
||||
dialog->Bind(EVT_DIFF_DIALOG_TRANSFER, [process_options, transfer](SimpleEvent&) { process_options(transfer); });
|
||||
return dialog;
|
||||
}
|
||||
|
||||
|
||||
@@ -1186,8 +1191,9 @@ void MainFrame::update_edge_panels()
|
||||
void MainFrame::shutdown()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
|
||||
if (m_project != nullptr)
|
||||
m_project->shutdown();
|
||||
m_idle.stop();
|
||||
if (ProjectPanel* project = ProjectPanel::if_built())
|
||||
project->shutdown();
|
||||
m_plugin_pages.shutdown();
|
||||
if (m_plater != nullptr)
|
||||
m_plater->remove_dock_panes();
|
||||
@@ -1314,19 +1320,6 @@ void MainFrame::show_option(bool show)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_CAD
|
||||
DesignPanel* MainFrame::ensure_design_panel()
|
||||
{
|
||||
if (m_design_panel == nullptr && m_design_page != nullptr) {
|
||||
wxBusyCursor busy;
|
||||
m_design_panel = new DesignPanel(m_design_page);
|
||||
m_design_page->GetSizer()->Add(m_design_panel, 1, wxEXPAND);
|
||||
m_design_page->Layout();
|
||||
}
|
||||
return m_design_panel;
|
||||
}
|
||||
#endif
|
||||
|
||||
void MainFrame::init_tabpanel() {
|
||||
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on
|
||||
// Windows 10 with multiple high resolution displays connected.
|
||||
@@ -1369,23 +1362,20 @@ void MainFrame::init_tabpanel() {
|
||||
// m_param_panel->OnActivate();
|
||||
#ifdef SLIC3R_CAD
|
||||
else if (m_design_page != nullptr && panel == m_design_page) {
|
||||
// Built on first activation, never at startup: the panel creates several hundred
|
||||
// controls and its own GL canvas, which a user who does not open the tab should
|
||||
// not pay for.
|
||||
ensure_design_panel();
|
||||
// Re-sync the Design bed to the active printer: the panel is built before the
|
||||
// printer profile is fully applied, so its bed must refresh on activation or the
|
||||
// grid (true bed) spills past the stale default bed quad.
|
||||
m_design_panel->on_tab_shown();
|
||||
DesignPanel::ensure()->on_tab_shown();
|
||||
}
|
||||
#endif
|
||||
else if (panel == m_monitor) {
|
||||
else if (panel == m_monitor_page) {
|
||||
//monitor
|
||||
}
|
||||
#ifdef SLIC3R_CAD
|
||||
// Any page that is not Design takes the Design status line down with it — see
|
||||
// DesignPanel::on_tab_hidden for why the popup does not follow the page on its own.
|
||||
if (m_design_panel != nullptr && panel != m_design_page) m_design_panel->on_tab_hidden();
|
||||
if (DesignPanel* design = DesignPanel::if_built(); design != nullptr && panel != m_design_page)
|
||||
design->on_tab_hidden();
|
||||
#endif
|
||||
#ifndef __APPLE__
|
||||
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||
@@ -1401,13 +1391,14 @@ void MainFrame::init_tabpanel() {
|
||||
});
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
m_webview = new WebViewPanel(m_tabpanel);
|
||||
m_home_page = new LazyPage<WebViewPanel>(m_tabpanel, TAB_ID_HOME, 10);
|
||||
m_lazy_pages.push_back(m_home_page);
|
||||
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
|
||||
wxString url = evt.GetString();
|
||||
select_tab(TAB_ID_HOME);
|
||||
m_webview->load_url(url);
|
||||
WebViewPanel::ensure()->load_url(url);
|
||||
});
|
||||
m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
|
||||
m_tabpanel->AddPage(TAB_ID_HOME, m_home_page, "", "tab_home_active");
|
||||
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||
}
|
||||
|
||||
@@ -1418,15 +1409,13 @@ void MainFrame::init_tabpanel() {
|
||||
wxGetApp().plater_ = m_plater;
|
||||
|
||||
#ifdef SLIC3R_CAD
|
||||
// Stand-in page for the Design tab. The real DesignPanel is built into it the first time
|
||||
// the tab is selected (see the page-changed handler above), so nothing it constructs sits
|
||||
// on the startup path. The experimental feature is off by default, and when it is off the
|
||||
// page is never created, so the tab does not appear at all (the preference takes effect on
|
||||
// the next start, like the other feature toggles).
|
||||
// The experimental feature is off by default, and when it is off the page is never
|
||||
// created, so the tab does not appear at all (the preference takes effect on the next
|
||||
// start, like the other feature toggles).
|
||||
if (wxGetApp().is_enable_cad_feature()) {
|
||||
m_design_page = new wxPanel(this);
|
||||
m_design_page->SetSizer(new wxBoxSizer(wxVERTICAL));
|
||||
m_design_page->Hide();
|
||||
// Experimental and heavy enough that building it unasked would cost more than it saves.
|
||||
m_design_page = new LazyPage<DesignPanel>(this, TAB_ID_DESIGN, -1);
|
||||
m_lazy_pages.push_back(m_design_page);
|
||||
start_mcp_control_if_enabled(); // opens the MCP socket iff ORCA_CAD_MCP is set
|
||||
}
|
||||
#endif
|
||||
@@ -1434,33 +1423,41 @@ void MainFrame::init_tabpanel() {
|
||||
create_preset_tabs();
|
||||
|
||||
//BBS add pages
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
|
||||
m_monitor_page = new LazyPage<MonitorPanel>(m_tabpanel, TAB_ID_MONITOR, 20);
|
||||
m_lazy_pages.push_back(m_monitor_page);
|
||||
m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor_page, _L("Device"), "tab_monitor_active");
|
||||
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
m_printer_view_page = new LazyPage<PrinterWebView>(m_tabpanel, TAB_ID_MONITOR_WEB, 50, [this](wxWindow* parent) {
|
||||
auto* view = new PrinterWebView(parent);
|
||||
if (!m_printer_url.empty())
|
||||
view->load_url(m_printer_url, m_printer_api_key);
|
||||
return view;
|
||||
});
|
||||
m_lazy_pages.push_back(m_printer_view_page);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
|
||||
wxString url = evt.GetString();
|
||||
wxString key = evt.GetAPIkey();
|
||||
//select_tab(MainFrame::tpMonitor);
|
||||
m_printer_view->load_url(url, key);
|
||||
m_printer_url = evt.GetString();
|
||||
m_printer_api_key = evt.GetAPIkey();
|
||||
if (PrinterWebView* view = PrinterWebView::if_built())
|
||||
view->load_url(m_printer_url, m_printer_api_key);
|
||||
});
|
||||
m_printer_view->Hide();
|
||||
|
||||
m_multi_machine_page = new LazyPage<MultiMachinePage>(m_tabpanel, TAB_ID_MULTI_DEVICE, 40);
|
||||
m_lazy_pages.push_back(m_multi_machine_page);
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
// TODO: change the bitmap
|
||||
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
|
||||
m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
|
||||
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_project->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
|
||||
m_project_page = new LazyPage<ProjectPanel>(m_tabpanel, TAB_ID_PROJECT, 60);
|
||||
m_lazy_pages.push_back(m_project_page);
|
||||
m_tabpanel->AddPage(TAB_ID_PROJECT, m_project_page, _L("Project"), "tab_auxiliary_active");
|
||||
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
|
||||
// show_device() removes this tab for printers without the Bambu device tab, and the page
|
||||
// then never builds its panel.
|
||||
m_calibration_page = new LazyPage<CalibrationPanel>(m_tabpanel, TAB_ID_CALIBRATION, 30);
|
||||
m_lazy_pages.push_back(m_calibration_page);
|
||||
m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration_page, _L("Calibration"), "tab_calibration_active");
|
||||
|
||||
// Plugin pages are appended after the built-in tabs; their ids are namespaced
|
||||
// (plugin.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
|
||||
@@ -1495,67 +1492,44 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
// Remove the extra page before switching to any layout that shouldn't have it.
|
||||
if (!want_web_device_tab) {
|
||||
if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_printer_view_page->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_printer_agents) {
|
||||
if (!m_monitor) {
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
|
||||
if (m_tabpanel->FindPage(m_monitor) == wxNOT_FOUND) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
if (!m_monitor_page->in_book()) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
|
||||
m_printer_view_page->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||
m_monitor_page->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
}
|
||||
|
||||
if (m_printer_view == nullptr) {
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
|
||||
wxString url = evt.GetString();
|
||||
wxString key = evt.GetAPIkey();
|
||||
// select_tab(MainFrame::tpMonitor);
|
||||
m_printer_view->load_url(url, key);
|
||||
});
|
||||
}
|
||||
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
if (!m_multi_machine) {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
// TODO: change the bitmap
|
||||
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
|
||||
m_multi_machine->Show(false);
|
||||
if (!m_multi_machine_page->in_book()) {
|
||||
m_multi_machine_page->Show(false);
|
||||
// Past the web Device tab when it is already there, so enabling multi-machine
|
||||
// later can't wedge this page between the two Device tabs.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
|
||||
TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
|
||||
TAB_ID_MULTI_DEVICE, m_multi_machine_page, _L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
|
||||
m_calibration->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||
if (!m_calibration_page->in_book()) {
|
||||
m_calibration_page->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
|
||||
_L("Calibration"), "tab_calibration_active");
|
||||
}
|
||||
|
||||
if (want_web_device_tab) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) == wxNOT_FOUND) {
|
||||
m_printer_view_page->Show(false);
|
||||
// Immediately right of the native Device tab, not at the end of the tab bar.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
|
||||
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
|
||||
m_printer_view_page, _L("Device (Web)"), "tab_monitor_active");
|
||||
} else {
|
||||
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
||||
}
|
||||
@@ -1567,48 +1541,37 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||
|
||||
if (m_prebuild_started)
|
||||
m_idle.start();
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_use_native) {
|
||||
if (m_tabpanel->FindPage(m_monitor) != wxNOT_FOUND) {
|
||||
if (m_monitor_page->in_book()) {
|
||||
fit_tab_labels(); // ORCA on printer change - same button layout
|
||||
return;
|
||||
}
|
||||
// Remove printer view
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view_page)) != wxNOT_FOUND) {
|
||||
m_printer_view_page->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
|
||||
// Create/insert monitor page
|
||||
if (!m_monitor) {
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||
// Insert monitor page
|
||||
m_monitor_page->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor_page,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
if (!m_multi_machine) {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
// TODO: change the bitmap
|
||||
m_multi_machine->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
|
||||
m_multi_machine_page->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine_page,
|
||||
_L("Multi-device"), "tab_multi_active");
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
m_calibration->Show(false);
|
||||
m_calibration_page->Show(false);
|
||||
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
|
||||
// append, so its position doesn't depend on the relayout() below running afterwards.
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration_page,
|
||||
_L("Calibration"), "tab_calibration_active");
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
@@ -1616,37 +1579,30 @@ void MainFrame::show_device(bool should_use_native) {
|
||||
#endif // _MSW_DARK_MODE
|
||||
|
||||
} else {
|
||||
if (m_tabpanel->FindPage(m_printer_view) != wxNOT_FOUND) {
|
||||
if (m_printer_view_page->in_book()) {
|
||||
fit_tab_labels(); // ORCA on printer change - same button layout
|
||||
return;
|
||||
}
|
||||
if ((idx = m_tabpanel->FindPage(m_calibration)) != wxNOT_FOUND) {
|
||||
m_calibration->Show(false);
|
||||
if ((idx = m_tabpanel->FindPage(m_calibration_page)) != wxNOT_FOUND) {
|
||||
m_calibration_page->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
if ((idx = m_tabpanel->FindPage(m_multi_machine)) != wxNOT_FOUND) {
|
||||
m_multi_machine->Show(false);
|
||||
if ((idx = m_tabpanel->FindPage(m_multi_machine_page)) != wxNOT_FOUND) {
|
||||
m_multi_machine_page->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
if ((idx = m_tabpanel->FindPage(m_monitor)) != wxNOT_FOUND) {
|
||||
m_monitor->Show(false);
|
||||
if ((idx = m_tabpanel->FindPage(m_monitor_page)) != wxNOT_FOUND) {
|
||||
m_monitor_page->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
if (m_printer_view == nullptr) {
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
|
||||
wxString url = evt.GetString();
|
||||
wxString key = evt.GetAPIkey();
|
||||
// select_tab(MainFrame::tpMonitor);
|
||||
m_printer_view->load_url(url, key);
|
||||
});
|
||||
}
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
|
||||
m_printer_view_page->Show(false);
|
||||
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view_page,
|
||||
_L("Device"), "tab_monitor_active");
|
||||
}
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||
if (m_prebuild_started)
|
||||
m_idle.start();
|
||||
}
|
||||
|
||||
bool MainFrame::is_prepare_or_preview_tab() const
|
||||
@@ -2692,13 +2648,11 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
|
||||
//BBS GUI refactor: remove unused layout new/dlg
|
||||
//if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
|
||||
m_param_panel->msw_rescale();
|
||||
m_project->msw_rescale();
|
||||
if(m_monitor)
|
||||
m_monitor->msw_rescale();
|
||||
if(m_multi_machine)
|
||||
m_multi_machine->msw_rescale();
|
||||
if(m_calibration)
|
||||
m_calibration->msw_rescale();
|
||||
// A panel mid-build gets the pass once it is complete.
|
||||
ProjectPanel::when_built([](ProjectPanel& project) { project.msw_rescale(); });
|
||||
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.msw_rescale(); });
|
||||
MultiMachinePage::when_built([](MultiMachinePage& multi_machine) { multi_machine.msw_rescale(); });
|
||||
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.msw_rescale(); });
|
||||
|
||||
// BBS
|
||||
#if 0
|
||||
@@ -2752,7 +2706,8 @@ void MainFrame::on_sys_color_changed()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
diff_dialog.on_sys_color_changed();
|
||||
if (DiffPresetDialog* dialog = DiffPresetDialog::if_built())
|
||||
dialog->on_sys_color_changed();
|
||||
|
||||
// BBS
|
||||
m_tabpanel->Rescale();
|
||||
@@ -2760,10 +2715,8 @@ void MainFrame::on_sys_color_changed()
|
||||
|
||||
// update Plater
|
||||
wxGetApp().plater()->sys_color_changed();
|
||||
if(m_monitor)
|
||||
m_monitor->on_sys_color_changed();
|
||||
if(m_calibration)
|
||||
m_calibration->on_sys_color_changed();
|
||||
MonitorPanel::when_built([](MonitorPanel& monitor) { monitor.on_sys_color_changed(); });
|
||||
CalibrationPanel::when_built([](CalibrationPanel& calibration) { calibration.on_sys_color_changed(); });
|
||||
// update Tabs
|
||||
for (auto tab : wxGetApp().tabs_list)
|
||||
tab->sys_color_changed();
|
||||
@@ -3696,7 +3649,8 @@ void MainFrame::set_max_recent_count(int max)
|
||||
}
|
||||
wxGetApp().app_config->set_recent_projects(recent_projects);
|
||||
wxGetApp().app_config->save();
|
||||
m_webview->SendRecentList(-1);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendRecentList(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4040,23 +3994,124 @@ void MainFrame::select_tab(wxPanel* panel)
|
||||
select_tab(page_name);
|
||||
}
|
||||
|
||||
// Selects the Prepare page without the page-changed event, so the GL canvas is on screen
|
||||
// and nothing else is built for the pass.
|
||||
void MainFrame::select_prepare_for_gl_init()
|
||||
{
|
||||
m_tabpanel->ChangeSelection(m_tabpanel->FindPageByName(TAB_ID_PREPARE));
|
||||
}
|
||||
|
||||
// The book shows its first page as it is inserted, while the frame is hidden and nothing
|
||||
// may build; the first show completes that page.
|
||||
bool MainFrame::Show(bool show)
|
||||
{
|
||||
const bool changed = DPIFrame::Show(show);
|
||||
if (show && changed && m_tabpanel != nullptr)
|
||||
if (wxWindow* page = m_tabpanel->GetCurrentPage())
|
||||
page->Show(true);
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MainFrame::GLResourcesPrebuild::built() const
|
||||
{
|
||||
return m_frame.m_plater != nullptr && m_frame.m_plater->canvas3D()->is_initialized() &&
|
||||
m_frame.m_plater->get_partplate_list().icon_textures_loaded();
|
||||
}
|
||||
|
||||
bool MainFrame::GLResourcesPrebuild::build_step()
|
||||
{
|
||||
GLCanvas3D* canvas = m_frame.m_plater->canvas3D();
|
||||
#ifdef __WXGTK__
|
||||
// wx creates a GTK canvas's GL surface when the widget is realized, so the context can be
|
||||
// made current on it while hidden.
|
||||
gtk_widget_realize(canvas->get_wxglcanvas()->GetHandle());
|
||||
#endif
|
||||
if (!canvas->make_current_for_postinit()) {
|
||||
// The first render of the canvas loads everything instead.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": cannot make the GL context current on the hidden canvas";
|
||||
m_failed = true;
|
||||
return false;
|
||||
}
|
||||
switch (m_step) {
|
||||
case 0:
|
||||
m_failed = !wxGetApp().init_opengl();
|
||||
break;
|
||||
case 1: {
|
||||
const Size size = canvas->get_canvas_size();
|
||||
wxGetApp().imgui()->set_display_size(float(std::max(1, size.get_width())), float(std::max(1, size.get_height())));
|
||||
canvas->set_imgui_scaling();
|
||||
// Builds the font atlas without leaving a frame open at the hidden canvas's size.
|
||||
wxGetApp().imgui()->new_frame();
|
||||
wxGetApp().imgui()->end_frame();
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
// One texture per unit until none remain.
|
||||
if (m_frame.m_plater->get_partplate_list().load_next_plate_texture())
|
||||
return true;
|
||||
break;
|
||||
case 3:
|
||||
m_failed = !canvas->init();
|
||||
break;
|
||||
default:
|
||||
// Runs after init(), which sets the color mode the icons are drawn for.
|
||||
m_frame.m_plater->get_partplate_list().load_icon_textures();
|
||||
return false;
|
||||
}
|
||||
++m_step;
|
||||
return !m_failed;
|
||||
}
|
||||
|
||||
bool MainFrame::PrepareLayoutPrebuild::built() const
|
||||
{
|
||||
// The book lays out the page it shows.
|
||||
const wxWindow* page = m_frame.m_tabpanel != nullptr ? m_frame.m_tabpanel->GetCurrentPage() : nullptr;
|
||||
return page == nullptr || page == m_frame.m_plater || page->GetSize() == m_laid_out_size;
|
||||
}
|
||||
|
||||
bool MainFrame::PrepareLayoutPrebuild::build_step()
|
||||
{
|
||||
// Sized as the book sizes the page it selects, so the selection finds nothing to lay out.
|
||||
const wxWindow* page = m_frame.m_tabpanel->GetCurrentPage();
|
||||
m_laid_out_size = page->GetSize();
|
||||
m_frame.m_plater->SetSize(page->GetRect());
|
||||
return false;
|
||||
}
|
||||
|
||||
// A page out of the book stays registered and is passed over; a negative order is never
|
||||
// registered.
|
||||
void MainFrame::prebuild_pages_when_idle()
|
||||
{
|
||||
m_idle.clear();
|
||||
m_idle.add(m_gl_prebuild);
|
||||
if (m_param_panel)
|
||||
m_idle.add(m_param_panel->settings_page_prebuild());
|
||||
m_idle.add(m_prepare_layout_prebuild);
|
||||
for (LazyBase* page : m_lazy_pages)
|
||||
if (page->prebuild_order() >= 0)
|
||||
m_idle.add(*page);
|
||||
m_idle.add(m_diff_dialog);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": prebuild queue: " << m_idle.names();
|
||||
m_idle.start();
|
||||
m_prebuild_started = true;
|
||||
}
|
||||
|
||||
//BBS
|
||||
void MainFrame::jump_to_monitor(std::string dev_id)
|
||||
{
|
||||
if(!m_monitor)
|
||||
return;
|
||||
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
|
||||
if (!dev_id.empty()) {
|
||||
((MonitorPanel*)m_monitor)->select_machine(dev_id);
|
||||
MonitorPanel::ensure()->select_machine(dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
void MainFrame::jump_to_multipage()
|
||||
{
|
||||
if(!m_multi_machine)
|
||||
if (!m_multi_machine_page->in_book())
|
||||
return;
|
||||
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
|
||||
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
|
||||
if (MultiMachinePage* page = m_multi_machine_page->ensure())
|
||||
page->jump_to_send_page();
|
||||
}
|
||||
|
||||
|
||||
@@ -4105,8 +4160,8 @@ void MainFrame::request_select_tab(const wxString& id)
|
||||
}
|
||||
|
||||
int MainFrame::get_calibration_curr_tab() {
|
||||
if (m_calibration)
|
||||
return m_calibration->get_tabpanel()->GetSelection();
|
||||
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
|
||||
return calibration->get_tabpanel()->GetSelection();
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -4215,7 +4270,8 @@ void MainFrame::add_to_recent_projects(const wxString& filename)
|
||||
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
|
||||
}
|
||||
wxGetApp().app_config->set_recent_projects(recent_projects);
|
||||
m_webview->SendRecentList(0);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendRecentList(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4331,7 +4387,8 @@ void MainFrame::open_recent_project(size_t file_id, wxString const & filename)
|
||||
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
|
||||
}
|
||||
wxGetApp().app_config->set_recent_projects(recent_projects);
|
||||
m_webview->SendRecentList(-1);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendRecentList(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4354,7 +4411,8 @@ void MainFrame::remove_recent_project(size_t file_id, wxString const &filename)
|
||||
recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i)));
|
||||
}
|
||||
wxGetApp().app_config->set_recent_projects(recent_projects);
|
||||
m_webview->SendRecentList(-1);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendRecentList(-1);
|
||||
}
|
||||
|
||||
void MainFrame::load_url(wxString url)
|
||||
@@ -4408,14 +4466,14 @@ bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName
|
||||
|
||||
void MainFrame::refresh_plugin_tips()
|
||||
{
|
||||
if (m_webview != nullptr)
|
||||
m_webview->ShowNetpluginTip();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->ShowNetpluginTip();
|
||||
}
|
||||
|
||||
void MainFrame::RunScript(wxString js)
|
||||
{
|
||||
if (m_webview != nullptr)
|
||||
m_webview->RunScript(js);
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->RunScript(js);
|
||||
}
|
||||
|
||||
void MainFrame::technology_changed()
|
||||
@@ -4524,7 +4582,8 @@ void MainFrame::update_side_preset_ui()
|
||||
|
||||
|
||||
//take off multi machine
|
||||
if(m_multi_machine){m_multi_machine->clear_page();}
|
||||
if (MultiMachinePage* multi_machine = MultiMachinePage::if_built())
|
||||
multi_machine->clear_page();
|
||||
}
|
||||
|
||||
void MainFrame::on_select_default_preset(SimpleEvent& evt)
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "Widgets/SideButton.hpp"
|
||||
#include "Widgets/SideMenuPopup.hpp"
|
||||
#include "FilamentGroupPopup.hpp"
|
||||
#include "LazyPage.hpp"
|
||||
#include "IdleScheduler.hpp"
|
||||
|
||||
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
@@ -136,6 +138,45 @@ class MainFrame : public DPIFrame
|
||||
#endif
|
||||
bool m_loaded {false};
|
||||
wxTimer* m_reset_title_text_colour_timer{ nullptr };
|
||||
IdleScheduler m_idle;
|
||||
bool m_prebuild_started{ false };
|
||||
// Loads the Prepare canvas's GL resources while its page is hidden.
|
||||
class GLResourcesPrebuild : public LazyBase
|
||||
{
|
||||
public:
|
||||
explicit GLResourcesPrebuild(MainFrame& frame) : m_frame(frame) {}
|
||||
const std::string& name() const override { return m_name; }
|
||||
bool built() const override;
|
||||
bool pending() const override { return !m_failed && !built(); }
|
||||
bool build_step() override;
|
||||
int prebuild_order() const override { return 0; }
|
||||
|
||||
private:
|
||||
MainFrame& m_frame;
|
||||
std::string m_name{ "gl_resources" };
|
||||
int m_step{ 0 };
|
||||
bool m_failed{ false };
|
||||
} m_gl_prebuild{ *this };
|
||||
// Lays out the hidden Prepare page at the size the book gives its pages.
|
||||
class PrepareLayoutPrebuild : public LazyBase
|
||||
{
|
||||
public:
|
||||
explicit PrepareLayoutPrebuild(MainFrame& frame) : m_frame(frame) {}
|
||||
const std::string& name() const override { return m_name; }
|
||||
bool built() const override;
|
||||
bool build_step() override;
|
||||
int prebuild_order() const override { return 0; }
|
||||
|
||||
private:
|
||||
MainFrame& m_frame;
|
||||
std::string m_name{ "prepare_layout" };
|
||||
wxSize m_laid_out_size;
|
||||
} m_prepare_layout_prebuild{ *this };
|
||||
// Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
|
||||
std::vector<LazyBase*> m_lazy_pages;
|
||||
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
|
||||
wxString m_printer_url;
|
||||
wxString m_printer_api_key;
|
||||
|
||||
wxString m_qs_last_input_file = wxEmptyString;
|
||||
wxString m_qs_last_output_file = wxEmptyString;
|
||||
@@ -180,7 +221,7 @@ class MainFrame : public DPIFrame
|
||||
bool can_delete() const;
|
||||
bool can_delete_all() const;
|
||||
bool can_reslice() const;
|
||||
void bind_diff_dialog();
|
||||
DiffPresetDialog* make_diff_dialog();
|
||||
|
||||
// BBS
|
||||
wxBoxSizer* create_side_tools();
|
||||
@@ -377,6 +418,12 @@ public:
|
||||
void select_tab(wxPanel* panel);
|
||||
void select_tab(const wxString& id = wxString());
|
||||
void request_select_tab(const wxString& id);
|
||||
// post_init() needs the plater's canvas on screen to initialize OpenGL; this pass does not
|
||||
// build the settings page.
|
||||
void select_prepare_for_gl_init();
|
||||
// Builds the lazy tab pages while the user is idle; post_init() calls it once.
|
||||
void prebuild_pages_when_idle();
|
||||
bool Show(bool show = true) override;
|
||||
int get_calibration_curr_tab();
|
||||
void select_view(const std::string& direction);
|
||||
void update_shortcut_labels();
|
||||
@@ -435,27 +482,21 @@ public:
|
||||
BBLTopbar* m_topbar{ nullptr };
|
||||
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
|
||||
Plater* m_plater { nullptr };
|
||||
// Lazy pages, created once and kept for the frame's life; their panels are reached
|
||||
// through LazyInstance's statics, and show_device() only moves pages in and out of the book.
|
||||
#ifdef SLIC3R_CAD
|
||||
// The tab page is the placeholder; m_design_panel stays null until the tab is first
|
||||
// selected, so everything the Design panel builds stays off the startup path.
|
||||
wxPanel* m_design_page { nullptr };
|
||||
DesignPanel* m_design_panel { nullptr };
|
||||
// Builds the Design panel if it does not exist yet and returns it (null only before the
|
||||
// placeholder page itself exists). Main thread only -- it creates wx controls. Both the
|
||||
// tab activation and the MCP socket go through this: the socket is driven headlessly,
|
||||
// with nobody to click the tab, and without this every verb would answer "not ready".
|
||||
DesignPanel* ensure_design_panel();
|
||||
LazyPage<DesignPanel>* m_design_page { nullptr };
|
||||
#endif
|
||||
//BBS: GUI refactor
|
||||
MonitorPanel* m_monitor{ nullptr };
|
||||
LazyPage<MonitorPanel>* m_monitor_page{ nullptr };
|
||||
|
||||
//AuxiliaryPanel* m_auxiliary{ nullptr };
|
||||
MultiMachinePage* m_multi_machine{ nullptr };
|
||||
ProjectPanel* m_project{ nullptr };
|
||||
LazyPage<MultiMachinePage>* m_multi_machine_page{ nullptr };
|
||||
LazyPage<ProjectPanel>* m_project_page{ nullptr };
|
||||
|
||||
CalibrationPanel* m_calibration{ nullptr };
|
||||
WebViewPanel* m_webview { nullptr };
|
||||
PrinterWebView* m_printer_view{nullptr};
|
||||
LazyPage<CalibrationPanel>* m_calibration_page{ nullptr };
|
||||
LazyPage<WebViewPanel>* m_home_page { nullptr };
|
||||
LazyPage<PrinterWebView>* m_printer_view_page{nullptr};
|
||||
PluginPages m_plugin_pages;
|
||||
wxLogWindow* m_log_window { nullptr };
|
||||
// BBS
|
||||
@@ -466,7 +507,8 @@ public:
|
||||
ParamsDialog* m_param_dialog{ nullptr };
|
||||
//BBS
|
||||
SettingsDialog m_settings_dialog;
|
||||
DiffPresetDialog diff_dialog;
|
||||
// The Compare presets dialog, built on first use or at idle through its holder.
|
||||
Lazy<DiffPresetDialog> m_diff_dialog;
|
||||
wxWindow* m_plater_page{ nullptr };
|
||||
PrintHostQueueDialog* m_printhost_queue_dlg;
|
||||
|
||||
|
||||
@@ -1125,7 +1125,7 @@ wxBoxSizer* MixedFilamentDialog::create_recommendation_grid()
|
||||
outer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, FromDIP(4));
|
||||
|
||||
m_recommendation_scroll = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(116)));
|
||||
m_recommendation_scroll->SetScrollRate(0, 5);
|
||||
m_recommendation_scroll->SetScrollRate(0, FromDIP(20));
|
||||
m_recommendation_scroll->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F8F8F8")));
|
||||
|
||||
m_recommendation_grid = new wxWrapSizer(wxHORIZONTAL, wxREMOVE_LEADING_SPACES);
|
||||
|
||||
+44
-37
@@ -105,6 +105,7 @@ MonitorPanel::MonitorPanel(wxWindow* parent, wxWindowID id, const wxPoint& pos,
|
||||
: wxPanel(parent, id, pos, size, style),
|
||||
m_select_machine(SelectMachinePopup(this))
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
#ifdef __WINDOWS__
|
||||
SetDoubleBuffered(true);
|
||||
#endif //__WINDOWS__
|
||||
@@ -182,31 +183,36 @@ void MonitorPanel::init_tabpanel()
|
||||
auto title = m_tabpanel->GetPageText(m_tabpanel->GetSelection());
|
||||
m_media_file_panel->SwitchStorage(title == _L("Storage"));
|
||||
}
|
||||
page->SetFocus();
|
||||
// The first page is selected while the panel is built off screen.
|
||||
if (page->IsShownOnScreen())
|
||||
page->SetFocus();
|
||||
update_all();
|
||||
}, m_tabpanel->GetId());
|
||||
|
||||
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
|
||||
m_status_info_panel = new StatusPanel(m_tabpanel);
|
||||
m_status_info_panel = new StatusPanel(m_tabpanel);
|
||||
// Queued before the page is added, since adding it selects it and runs the handler above,
|
||||
// where built() must already be false.
|
||||
add_build_steps_of(*m_status_info_panel);
|
||||
m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
|
||||
add_build_step([this] {
|
||||
m_media_file_panel = new MediaFilePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
|
||||
});
|
||||
add_build_step([this] {
|
||||
m_upgrade_panel = new UpgradePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
|
||||
});
|
||||
add_build_step([this] {
|
||||
m_hms_panel = new HMSPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
|
||||
|
||||
m_media_file_panel = new MediaFilePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
|
||||
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
|
||||
std::string network_ver = Slic3r::NetworkAgent::get_version();
|
||||
if (!network_ver.empty()) {
|
||||
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
|
||||
}
|
||||
|
||||
m_upgrade_panel = new UpgradePanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
|
||||
|
||||
m_hms_panel = new HMSPanel(m_tabpanel);
|
||||
m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
|
||||
|
||||
std::string network_ver = Slic3r::NetworkAgent::get_version();
|
||||
if (!network_ver.empty()) {
|
||||
m_tabpanel->SetFooterText(wxString::Format(_L("Network plug-in v%s"), network_ver));
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
|
||||
show_status((int)MonitorStatus::MONITOR_NO_PRINTER);
|
||||
});
|
||||
}
|
||||
|
||||
void MonitorPanel::set_default()
|
||||
@@ -298,19 +304,23 @@ void MonitorPanel::on_select_printer(wxCommandEvent& event)
|
||||
set_default();
|
||||
update_all();
|
||||
|
||||
MachineObject *obj_ = dev->get_selected_machine();
|
||||
if (obj_) {
|
||||
obj_->last_cali_version = -1;
|
||||
obj_->reset_pa_cali_history_result();
|
||||
obj_->reset_pa_cali_result();
|
||||
Sidebar &sidebar = GUI::wxGetApp().sidebar();
|
||||
sidebar.update_sync_status(obj_);
|
||||
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj_));
|
||||
}
|
||||
on_machine_selected(dev->get_selected_machine());
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
void MonitorPanel::on_machine_selected(MachineObject* obj)
|
||||
{
|
||||
if (obj == nullptr)
|
||||
return;
|
||||
obj->last_cali_version = -1;
|
||||
obj->reset_pa_cali_history_result();
|
||||
obj->reset_pa_cali_result();
|
||||
Sidebar& sidebar = GUI::wxGetApp().sidebar();
|
||||
sidebar.update_sync_status(obj);
|
||||
sidebar.set_need_auto_sync_after_connect_printer(sidebar.need_auto_sync_extruder_list_after_connect_priner(obj));
|
||||
}
|
||||
|
||||
void MonitorPanel::on_printer_clicked(wxMouseEvent &event)
|
||||
{
|
||||
auto mouse_pos = ClientToScreen(event.GetPosition());
|
||||
@@ -341,7 +351,8 @@ void MonitorPanel::on_size(wxSizeEvent &event)
|
||||
|
||||
void MonitorPanel::update_all()
|
||||
{
|
||||
if (!m_initialized)
|
||||
// Every page exists once the last build step has run.
|
||||
if (!built())
|
||||
return;
|
||||
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
@@ -422,16 +433,12 @@ void MonitorPanel::update_hms_tag()
|
||||
|
||||
bool MonitorPanel::Show(bool show)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
// Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
|
||||
// still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
|
||||
if (wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
|
||||
#endif
|
||||
|
||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||
DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (show) {
|
||||
#ifdef __APPLE__
|
||||
wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
|
||||
#endif
|
||||
start_update();
|
||||
update_network_version_footer();
|
||||
|
||||
@@ -458,7 +465,7 @@ bool MonitorPanel::Show(bool show)
|
||||
|
||||
void MonitorPanel::show_status(int status)
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
if (!built()) return;
|
||||
if (last_status == status)return;
|
||||
if ((last_status & (int)MonitorStatus::MONITOR_CONNECTING) != 0) {
|
||||
NetworkAgent* agent = wxGetApp().getAgent();
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "slic3r/GUI/AmsWidgets.hpp"
|
||||
#include "Widgets/SideTools.hpp"
|
||||
#include "SelectMachinePop.hpp"
|
||||
#include "Lazy.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@@ -72,16 +73,16 @@ public:
|
||||
void msw_rescale();
|
||||
};
|
||||
|
||||
class MonitorPanel : public wxPanel
|
||||
class MonitorPanel : public wxPanel, public StagedBuild, public LazyInstance<MonitorPanel>
|
||||
{
|
||||
private:
|
||||
Tabbook* m_tabpanel{ nullptr };
|
||||
wxSizer* m_main_sizer{ nullptr };
|
||||
|
||||
StatusPanel* m_status_info_panel;
|
||||
MediaFilePanel* m_media_file_panel;
|
||||
UpgradePanel* m_upgrade_panel;
|
||||
HMSPanel* m_hms_panel;
|
||||
StatusPanel* m_status_info_panel{ nullptr };
|
||||
MediaFilePanel* m_media_file_panel{ nullptr };
|
||||
UpgradePanel* m_upgrade_panel{ nullptr };
|
||||
HMSPanel* m_hms_panel{ nullptr };
|
||||
|
||||
/* side tools */
|
||||
SideTools* m_side_tools{nullptr};
|
||||
@@ -96,7 +97,6 @@ private:
|
||||
wxBitmap m_arrow_img;
|
||||
|
||||
int last_status;
|
||||
bool m_initialized { false };
|
||||
bool update_flag{false};
|
||||
wxTimer* m_refresh_timer = nullptr;
|
||||
|
||||
@@ -125,6 +125,8 @@ public:
|
||||
|
||||
StatusPanel* get_status_panel() {return m_status_info_panel;};
|
||||
void select_machine(std::string machine_sn);
|
||||
// Resets the selected printer's calibration results and syncs the sidebar, without the Device tab.
|
||||
static void on_machine_selected(MachineObject* obj);
|
||||
void on_timer(wxTimerEvent& event);
|
||||
void on_select_printer(wxCommandEvent& event);
|
||||
void on_printer_clicked(wxMouseEvent &event);
|
||||
|
||||
@@ -194,9 +194,10 @@ Button* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, con
|
||||
bd->button = btn;
|
||||
//bd->type = type;
|
||||
|
||||
mb->id = wxString::Format("%d", m_buttons.size());
|
||||
// m_buttons.size() is a size_t, which does not match the %d conversion
|
||||
mb->id = wxString(std::to_string(m_buttons.size()));
|
||||
mb->buttondata = bd;
|
||||
m_buttons[ wxString::Format("%d", m_buttons.size())] = mb;
|
||||
m_buttons[ wxString(std::to_string(m_buttons.size()))] = mb;
|
||||
return btn;
|
||||
};
|
||||
|
||||
@@ -398,7 +399,7 @@ static void add_msg_content(wxWindow *parent,
|
||||
}
|
||||
wxScrolledWindow *scrolledWindow = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
scrolledWindow->SetScrollRate(0, 20);
|
||||
scrolledWindow->SetScrollRate(0, scrolledWindow->FromDIP(20));
|
||||
scrolledWindow->EnableScrolling(false, true);
|
||||
wxBoxSizer *sizer_scrolled = new wxBoxSizer(wxHORIZONTAL);
|
||||
Label *wrapped_text = new Label(scrolledWindow, font, msg, LB_AUTO_WRAP, wxSize(info_width, -1));
|
||||
|
||||
@@ -21,8 +21,9 @@ MultiMachineItem::MultiMachineItem(wxWindow* parent, MachineObject* obj)
|
||||
Bind(wxEVT_MOTION, &MultiMachineItem::OnMove, this);
|
||||
Bind(EVT_MULTI_DEVICE_VIEW, [obj](auto& e) {
|
||||
wxGetApp().mainframe->jump_to_monitor(obj->get_dev_id());
|
||||
if (wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()) {
|
||||
wxGetApp().mainframe->m_monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
|
||||
MonitorPanel* monitor = MonitorPanel::if_built();
|
||||
if (monitor && monitor->get_status_panel()->get_media_play_ctrl()) {
|
||||
monitor->get_status_panel()->get_media_play_ctrl()->jump_to_play();
|
||||
}
|
||||
});
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
@@ -397,7 +398,7 @@ MultiMachineManagerPage::MultiMachineManagerPage(wxWindow* parent)
|
||||
|
||||
m_machine_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_machine_list->SetBackgroundColour(*wxWHITE);
|
||||
m_machine_list->SetScrollRate(0, 5);
|
||||
m_machine_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_machine_list->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_machine_list->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace GUI {
|
||||
MultiMachinePage::MultiMachinePage(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: wxPanel(parent, id, pos, size, style)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
init_tabpanel();
|
||||
m_main_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxLEFT, 0);
|
||||
@@ -322,7 +323,7 @@ MultiMachinePickPage::MultiMachinePickPage(Plater* plater /*= nullptr*/)
|
||||
scroll_macine_list->SetMinSize(wxSize(FromDIP(400), FromDIP(10 * 30)));
|
||||
scroll_macine_list->SetMaxSize(wxSize(FromDIP(400), FromDIP(10 * 30)));
|
||||
scroll_macine_list->SetBackgroundColour(*wxWHITE);
|
||||
scroll_macine_list->SetScrollRate(0, 5);
|
||||
scroll_macine_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
|
||||
sizer_machine_list = new wxBoxSizer(wxVERTICAL);
|
||||
scroll_macine_list->SetSizer(sizer_machine_list);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "MultiTaskManagerPage.hpp"
|
||||
#include "MultiMachineManagerPage.hpp"
|
||||
#include "Tabbook.hpp"
|
||||
#include "Lazy.hpp"
|
||||
|
||||
#include "wx/button.h"
|
||||
|
||||
@@ -19,7 +20,7 @@ namespace GUI {
|
||||
#define PICK_LEFT_DEV_STATUS 250
|
||||
#define PICK_DEVICE_MAX 6
|
||||
|
||||
class MultiMachinePage : public wxPanel
|
||||
class MultiMachinePage : public wxPanel, public LazyInstance<MultiMachinePage>
|
||||
{
|
||||
private:
|
||||
wxTimer* m_refresh_timer = nullptr;
|
||||
|
||||
@@ -675,7 +675,7 @@ LocalTaskManagerPage::LocalTaskManagerPage(wxWindow* parent)
|
||||
|
||||
m_task_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_task_list->SetBackgroundColour(*wxWHITE);
|
||||
m_task_list->SetScrollRate(0, 5);
|
||||
m_task_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_task_list->SetMinSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_task_list->SetMaxSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
@@ -1056,7 +1056,7 @@ CloudTaskManagerPage::CloudTaskManagerPage(wxWindow* parent)
|
||||
|
||||
m_task_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_task_list->SetBackgroundColour(*wxWHITE);
|
||||
m_task_list->SetScrollRate(0, 5);
|
||||
m_task_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_task_list->SetMinSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_task_list->SetMaxSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ ObjColorPanel::ObjColorPanel(wxWindow *parent, Slic3r::ObjDialogInOut &in_out, c
|
||||
//new color table
|
||||
m_scrolledWindow = new wxScrolledWindow(m_page_simple, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(0, 20);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->EnableScrolling(false, true);
|
||||
m_scrolledWindow->ShowScrollbars(wxScrollbarVisibility::wxSHOW_SB_NEVER, wxScrollbarVisibility::wxSHOW_SB_DEFAULT);
|
||||
draw_new_table();
|
||||
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
bool split_multi_line{false};
|
||||
bool option_label_at_right{false};
|
||||
// BBS: new layout
|
||||
wxWindow * stb;
|
||||
wxWindow * stb{ nullptr };
|
||||
const wxString icon;
|
||||
const wxString title;
|
||||
bool m_labels_hidden{false};
|
||||
|
||||
@@ -290,7 +290,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
|
||||
|
||||
m_compare_btn = new ScalableButton(m_top_panel, wxID_ANY, "compare", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
|
||||
m_compare_btn->SetToolTip(_L("Compare presets"));
|
||||
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { wxGetApp().mainframe->diff_dialog.show(); }));
|
||||
m_compare_btn->Bind(wxEVT_BUTTON, ([](wxCommandEvent e) { DiffPresetDialog::ensure()->show(); }));
|
||||
|
||||
m_setting_btn = new ScalableButton(m_top_panel, wxID_ANY, "table", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true);
|
||||
m_setting_btn->SetToolTip(_L("View all object's settings"));
|
||||
@@ -391,7 +391,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
|
||||
m_page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_page_view->SetSizer(m_page_sizer);
|
||||
m_page_view->SetScrollbars(1, 20, 1, 2);
|
||||
m_page_view->SetScrollbars(1, FromDIP(20), 1, 2);
|
||||
//m_page_view->SetScrollRate( 5, 5 );
|
||||
|
||||
if (m_mode_region)
|
||||
@@ -550,18 +550,36 @@ void ParamsPanel::clear_page()
|
||||
void ParamsPanel::OnActivate()
|
||||
{
|
||||
if (m_current_tab == NULL)
|
||||
{
|
||||
//the first time
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
|
||||
// BBS: open/close tab
|
||||
//m_current_tab = m_tab_print;
|
||||
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
|
||||
}
|
||||
select_default_tab();
|
||||
Tab* cur_tab = dynamic_cast<Tab *> (m_current_tab);
|
||||
if (cur_tab)
|
||||
cur_tab->OnActivate();
|
||||
}
|
||||
|
||||
void ParamsPanel::select_default_tab()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": first time opened, set current tab to print");
|
||||
// BBS: open/close tab
|
||||
//m_current_tab = m_tab_print;
|
||||
set_active_tab(m_tab_print ? m_tab_print : m_tab_filament);
|
||||
}
|
||||
|
||||
bool ParamsPanel::SettingsPagePrebuild::built() const
|
||||
{
|
||||
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
|
||||
return tab != nullptr && !tab->page_build_pending();
|
||||
}
|
||||
|
||||
bool ParamsPanel::SettingsPagePrebuild::build_step()
|
||||
{
|
||||
if (m_panel.m_current_tab == nullptr) {
|
||||
m_panel.select_default_tab();
|
||||
return !built();
|
||||
}
|
||||
Tab* tab = dynamic_cast<Tab*>(m_panel.m_current_tab);
|
||||
return tab != nullptr && tab->page_build_step();
|
||||
}
|
||||
|
||||
void ParamsPanel::OnToggled(wxCommandEvent& event)
|
||||
{
|
||||
if (m_mode_region && m_mode_region->GetId() == event.GetId()) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "wxExtensions.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Lazy.hpp"
|
||||
|
||||
class ModeSwitchButton;
|
||||
class SwitchButton;
|
||||
@@ -121,6 +122,22 @@ class ParamsPanel : public wxPanel
|
||||
|
||||
wxPanel* m_current_tab { nullptr };
|
||||
|
||||
// Builds the selected page's option groups at idle and then shows them for the mode;
|
||||
// while no tab is selected yet, its first unit selects the default one.
|
||||
class SettingsPagePrebuild : public LazyBase
|
||||
{
|
||||
public:
|
||||
explicit SettingsPagePrebuild(ParamsPanel& panel) : m_panel(panel) {}
|
||||
const std::string& name() const override { return m_name; }
|
||||
bool built() const override;
|
||||
bool build_step() override;
|
||||
int prebuild_order() const override { return 0; }
|
||||
|
||||
private:
|
||||
ParamsPanel& m_panel;
|
||||
std::string m_name{ "settings_page" };
|
||||
} m_settings_page_prebuild{ *this };
|
||||
|
||||
bool m_has_object_config { false };
|
||||
|
||||
struct Highlighter
|
||||
@@ -149,6 +166,10 @@ class ParamsPanel : public wxPanel
|
||||
//clear the right page
|
||||
void clear_page();
|
||||
void OnActivate();
|
||||
// The print tab, or the filament tab without one.
|
||||
void select_default_tab();
|
||||
// The settings page's prebuild task.
|
||||
LazyBase& settings_page_prebuild() { return m_settings_page_prebuild; }
|
||||
void set_active_tab(wxPanel*tab);
|
||||
bool is_active_and_shown_tab(wxPanel*tab);
|
||||
void update_mode();
|
||||
|
||||
+130
-125
@@ -799,67 +799,8 @@ void PartPlate::render_logo(bool bottom, bool render_cali)
|
||||
{
|
||||
if (!m_partplate_list->render_bedtype_logo) {
|
||||
// render third-party printer texture logo
|
||||
if (m_partplate_list->m_logo_texture_filename.empty()) {
|
||||
m_partplate_list->m_logo_texture.reset();
|
||||
if (!m_partplate_list->load_logo_texture())
|
||||
return;
|
||||
}
|
||||
|
||||
//GLTexture* temp_texture = const_cast<GLTexture*>(&m_temp_texture);
|
||||
|
||||
if (m_partplate_list->m_logo_texture.get_id() == 0 || m_partplate_list->m_logo_texture.get_source() != m_partplate_list->m_logo_texture_filename) {
|
||||
m_partplate_list->m_logo_texture.reset();
|
||||
|
||||
if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".svg")) {
|
||||
/*// use higher resolution images if graphic card and opengl version allow
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
|
||||
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
|
||||
if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
|
||||
render_default(bottom, false);
|
||||
return;
|
||||
}
|
||||
canvas.request_extra_frame();
|
||||
}*/
|
||||
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
if (!m_partplate_list->m_logo_texture.load_from_svg_file(m_partplate_list->m_logo_texture_filename, true, true, true, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".png")) {
|
||||
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
|
||||
/* if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_logo_texture_filename) {
|
||||
if (!temp_texture->load_from_file(m_logo_texture_filename, false, GLTexture::None, false)) {
|
||||
render_default(bottom, false);
|
||||
return;
|
||||
}
|
||||
canvas.request_extra_frame();
|
||||
}*/
|
||||
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
if (!m_partplate_list->m_logo_texture.load_from_file(m_partplate_list->m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_partplate_list->m_logo_texture_filename;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (m_partplate_list->m_logo_texture.unsent_compressed_data_available()) {
|
||||
// sends to gpu the already available compressed levels of the main texture
|
||||
m_partplate_list->m_logo_texture.send_compressed_data_to_gpu();
|
||||
|
||||
// the temporary texture is not needed anymore, reset it
|
||||
//if (temp_texture->get_id() != 0)
|
||||
// temp_texture->reset();
|
||||
|
||||
//canvas.request_extra_frame();
|
||||
}
|
||||
|
||||
if (m_logo_triangles.is_initialized())
|
||||
render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom);
|
||||
@@ -3555,8 +3496,11 @@ void PartPlate::render(const Transform3d& view_matrix, const Transform3d& projec
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
if (wxGetApp().show_plate_gridlines() && show_grid)
|
||||
if (wxGetApp().show_plate_gridlines() && show_grid) {
|
||||
glsafe(::glDepthMask(bottom ? GL_TRUE : GL_FALSE));
|
||||
render_grid(bottom);
|
||||
glsafe(::glDepthMask(GL_TRUE));
|
||||
}
|
||||
|
||||
if (!hide_chrome && !bottom && m_selected && !force_background_color) {
|
||||
if (m_partplate_list)
|
||||
@@ -4229,6 +4173,7 @@ Vec2d PartPlateList::compute_shape_position(int index, int cols)
|
||||
//generate icon textures
|
||||
void PartPlateList::generate_icon_textures()
|
||||
{
|
||||
m_icon_textures_dark = m_is_dark;
|
||||
// use higher resolution images if graphic card and opengl version allow
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(), icon_size = max_tex_size / 8;
|
||||
std::string path = resources_dir() + "/images/";
|
||||
@@ -4444,6 +4389,9 @@ void PartPlateList::release_icon_textures()
|
||||
PartPlateList::is_load_bedtype_textures = false;
|
||||
PartPlateList::is_load_extruder_only_area_textures = false;
|
||||
PartPlateList::is_load_cali_texture = false;
|
||||
m_next_bedtype_texture = 0;
|
||||
m_next_extruder_only_area_texture = 0;
|
||||
m_next_cali_texture = 0;
|
||||
for (int i = 0; i < btCount; i++) {
|
||||
for (auto& part: bed_texture_info[i].parts) {
|
||||
if (part.texture) {
|
||||
@@ -5999,12 +5947,7 @@ void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& pr
|
||||
plate_hover_action = hover_id % PartPlate::GRABBER_COUNT;
|
||||
}
|
||||
|
||||
static bool last_dark_mode_status = m_is_dark;
|
||||
if (m_is_dark != last_dark_mode_status) {
|
||||
last_dark_mode_status = m_is_dark;
|
||||
generate_icon_textures();
|
||||
} else if(m_del_texture.get_id() == 0)
|
||||
generate_icon_textures();
|
||||
load_icon_textures();
|
||||
for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) {
|
||||
int current_index = (*it)->get_index();
|
||||
if (only_current && (current_index != m_current_plate))
|
||||
@@ -6146,6 +6089,8 @@ bool PartPlateList::set_shapes(const Pointfs &shape,
|
||||
}
|
||||
is_load_bedtype_textures = false; //reload textures
|
||||
is_load_extruder_only_area_textures = false; // reload textures
|
||||
m_next_bedtype_texture = 0;
|
||||
m_next_extruder_only_area_texture = 0;
|
||||
calc_bounding_boxes();
|
||||
|
||||
update_logo_texture_filename(texture_filename);
|
||||
@@ -7086,53 +7031,120 @@ bool PartPlateList::init_extruder_only_area_info()
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartPlateList::load_bedtype_textures()
|
||||
static GLint logo_texture_size()
|
||||
{
|
||||
if (PartPlateList::is_load_bedtype_textures) return;
|
||||
|
||||
init_bed_type_info();
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
for (int i = 0; i < (unsigned int)btCount; ++i) {
|
||||
for (int j = 0; j < bed_texture_info[i].parts.size(); j++) {
|
||||
std::string filename = resources_dir() + "/images/" + bed_texture_info[i].parts[j].filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
PartPlateList::bed_texture_info[i].parts[j].texture = new GLTexture();
|
||||
if (!PartPlateList::bed_texture_info[i].parts[j].texture->load_from_svg_file(filename, true, true, true, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
PartPlateList::is_load_bedtype_textures = true;
|
||||
return std::min<GLint>(OpenGLManager::get_gl_info().get_max_tex_size(), 2048);
|
||||
}
|
||||
|
||||
void PartPlateList::load_extruder_only_area_textures() {
|
||||
if (PartPlateList::is_load_extruder_only_area_textures) return;
|
||||
// Loads the texture of the next untried part across the parts of `infos`, in order, advancing
|
||||
// `next`; false once every part has been tried.
|
||||
static bool load_next_part_texture(PartPlateList::BedTextureInfo* infos, size_t count, size_t& next, bool compress_and_filter)
|
||||
{
|
||||
size_t k = next;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (k >= infos[i].parts.size()) {
|
||||
k -= infos[i].parts.size();
|
||||
continue;
|
||||
}
|
||||
++next;
|
||||
PartPlateList::BedTextureInfo::TexturePart& part = infos[i].parts[k];
|
||||
const std::string filename = resources_dir() + "/images/" + part.filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
part.texture = new GLTexture();
|
||||
if (!part.texture->load_from_svg_file(filename, true, compress_and_filter, compress_and_filter, logo_texture_size()))
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load texture from %1% failed!") % filename;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load texture from %1% failed!") % filename;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ok = init_extruder_only_area_info();
|
||||
if (!ok) {
|
||||
void PartPlateList::load_bedtype_textures()
|
||||
{
|
||||
while (load_next_bedtype_texture()) {}
|
||||
}
|
||||
|
||||
bool PartPlateList::load_next_bedtype_texture()
|
||||
{
|
||||
if (PartPlateList::is_load_bedtype_textures)
|
||||
return false;
|
||||
if (m_next_bedtype_texture == 0)
|
||||
init_bed_type_info();
|
||||
if (load_next_part_texture(bed_texture_info, btCount, m_next_bedtype_texture, true))
|
||||
return true;
|
||||
PartPlateList::is_load_bedtype_textures = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartPlateList::load_logo_texture()
|
||||
{
|
||||
if (m_logo_texture_filename.empty()) {
|
||||
m_logo_texture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_logo_texture.get_id() != 0 && m_logo_texture.get_source() == m_logo_texture_filename) {
|
||||
if (m_logo_texture.unsent_compressed_data_available())
|
||||
// sends to gpu the already available compressed levels of the main texture
|
||||
m_logo_texture.send_compressed_data_to_gpu();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_logo_texture.reset();
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
if (boost::algorithm::iends_with(m_logo_texture_filename, ".svg")) {
|
||||
if (!m_logo_texture.load_from_svg_file(m_logo_texture_filename, true, true, true, logo_texture_size())) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_logo_texture_filename;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(m_logo_texture_filename, ".png")) {
|
||||
if (!m_logo_texture.load_from_file(m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_logo_texture_filename;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_logo_texture_filename;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartPlateList::load_icon_textures()
|
||||
{
|
||||
if (!icon_textures_loaded())
|
||||
generate_icon_textures();
|
||||
}
|
||||
|
||||
bool PartPlateList::load_next_plate_texture()
|
||||
{
|
||||
if (!render_bedtype_logo) {
|
||||
load_logo_texture();
|
||||
return false;
|
||||
}
|
||||
return load_next_bedtype_texture() || load_next_cali_texture() || load_next_extruder_only_area_texture();
|
||||
}
|
||||
|
||||
void PartPlateList::load_extruder_only_area_textures()
|
||||
{
|
||||
while (load_next_extruder_only_area_texture()) {}
|
||||
}
|
||||
|
||||
bool PartPlateList::load_next_extruder_only_area_texture()
|
||||
{
|
||||
if (PartPlateList::is_load_extruder_only_area_textures)
|
||||
return false;
|
||||
if (m_next_extruder_only_area_texture == 0 && !init_extruder_only_area_info()) {
|
||||
PartPlateList::is_load_extruder_only_area_textures = true;
|
||||
return;
|
||||
}
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
for (int i = 0; i < (unsigned int) ExtruderOnlyAreaType::btAreaCount; ++i) {
|
||||
for (int j = 0; j < extruder_only_area_info[i].parts.size(); j++) {
|
||||
std::string filename = resources_dir() + "/images/" + extruder_only_area_info[i].parts[j].filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
PartPlateList::extruder_only_area_info[i].parts[j].texture = new GLTexture();
|
||||
if (!PartPlateList::extruder_only_area_info[i].parts[j].texture->load_from_svg_file(filename, true, false, false, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (load_next_part_texture(extruder_only_area_info, (size_t) ExtruderOnlyAreaType::btAreaCount, m_next_extruder_only_area_texture, false))
|
||||
return true;
|
||||
PartPlateList::is_load_extruder_only_area_textures = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PartPlateList::init_cali_texture_info()
|
||||
@@ -7147,26 +7159,19 @@ void PartPlateList::init_cali_texture_info()
|
||||
|
||||
void PartPlateList::load_cali_textures()
|
||||
{
|
||||
if (PartPlateList::is_load_cali_texture) return;
|
||||
while (load_next_cali_texture()) {}
|
||||
}
|
||||
|
||||
init_cali_texture_info();
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
for (int i = 0; i < (unsigned int)btCount; ++i) {
|
||||
for (int j = 0; j < cali_texture_info.parts.size(); j++) {
|
||||
std::string filename = resources_dir() + "/images/" + cali_texture_info.parts[j].filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
PartPlateList::cali_texture_info.parts[j].texture = new GLTexture();
|
||||
if (!PartPlateList::cali_texture_info.parts[j].texture->load_from_svg_file(filename, true, true, true, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load cali texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load cali texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool PartPlateList::load_next_cali_texture()
|
||||
{
|
||||
if (PartPlateList::is_load_cali_texture)
|
||||
return false;
|
||||
if (m_next_cali_texture == 0)
|
||||
init_cali_texture_info();
|
||||
if (load_next_part_texture(&cali_texture_info, 1, m_next_cali_texture, true))
|
||||
return true;
|
||||
PartPlateList::is_load_cali_texture = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PartPlateList::on_extruder_count_changed(int extruder_count)
|
||||
|
||||
@@ -644,6 +644,7 @@ class PartPlateList : public ObjectBase
|
||||
std::string m_hover_tooltip;
|
||||
|
||||
bool m_is_dark = false;
|
||||
bool m_icon_textures_dark = false;
|
||||
|
||||
int m_filament_count = 1;
|
||||
|
||||
@@ -943,12 +944,25 @@ public:
|
||||
bool calc_extruder_only_area(Rect &left_only_rect, Rect &right_only_rect);
|
||||
void init_bed_type_info();
|
||||
bool init_extruder_only_area_info();
|
||||
// Each load_*_textures() loads whatever of its set is not loaded yet; each load_next_*()
|
||||
// loads one texture and returns false once none remain.
|
||||
void load_bedtype_textures();
|
||||
bool load_next_bedtype_texture();
|
||||
void load_extruder_only_area_textures();
|
||||
bool load_next_extruder_only_area_texture();
|
||||
// Starts loading the printer's logo texture, or sends the levels compressed since; false when
|
||||
// there is no logo to draw.
|
||||
bool load_logo_texture();
|
||||
|
||||
void show_cali_texture(bool show = true);
|
||||
void init_cali_texture_info();
|
||||
void load_cali_textures();
|
||||
bool load_next_cali_texture();
|
||||
bool icon_textures_loaded() const { return m_del_texture.get_id() != 0 && m_icon_textures_dark == m_is_dark; }
|
||||
void load_icon_textures();
|
||||
// Loads the next bed-type, calibration or extruder-area texture, or the logo, which rendering
|
||||
// otherwise loads on first use; false once none remain.
|
||||
bool load_next_plate_texture();
|
||||
|
||||
void on_extruder_count_changed(int extruder_count);
|
||||
|
||||
@@ -960,6 +974,13 @@ public:
|
||||
BedTextureInfo bed_texture_info[btCount];
|
||||
BedTextureInfo cali_texture_info;
|
||||
BedTextureInfo extruder_only_area_info[(unsigned char) Slic3r::ExtruderOnlyAreaType::btAreaCount];
|
||||
|
||||
private:
|
||||
// The next part to load in each texture set, counted across the set's parts in order; reset
|
||||
// with the set's is_load_* flag.
|
||||
size_t m_next_bedtype_texture{ 0 };
|
||||
size_t m_next_cali_texture{ 0 };
|
||||
size_t m_next_extruder_only_area_texture{ 0 };
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
||||
@@ -148,7 +148,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_line->SetBackgroundColour(wxColor(238, 238, 238));
|
||||
|
||||
m_list_view = new wxScrolledWindow(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(267, -1), wxHSCROLL | wxVSCROLL);
|
||||
m_list_view->SetScrollRate(5, 5);
|
||||
m_list_view->SetScrollRate(5, FromDIP(30));
|
||||
m_list_view->SetMinSize(wxSize(FromDIP(267), FromDIP(378)));
|
||||
m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(378)));
|
||||
m_list_view->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
+42
-32
@@ -3056,8 +3056,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
|
||||
// add filament content
|
||||
p->m_panel_filament_content = new wxScrolledWindow(p->m_filament_area_wrapper, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
p->m_panel_filament_content->SetScrollbars(0, 100, 1, 2);
|
||||
p->m_panel_filament_content->SetScrollRate(0, 5);
|
||||
p->m_panel_filament_content->SetScrollRate(0, FromDIP(20));
|
||||
//p->m_panel_filament_content->SetMaxSize(wxSize{-1, FromDIP(174)});
|
||||
p->m_panel_filament_content->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
|
||||
@@ -3149,8 +3148,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
// 3) Mixed filament rows, in their own scroll area so a long mixed list does not
|
||||
// push the physical filament list off screen.
|
||||
p->m_mixed_scroll_area = new wxScrolledWindow(p->m_filament_area_wrapper, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
p->m_mixed_scroll_area->SetScrollbars(0, 100, 1, 2);
|
||||
p->m_mixed_scroll_area->SetScrollRate(0, 5);
|
||||
p->m_mixed_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
p->m_mixed_scroll_area->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
{
|
||||
auto* mix_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -7093,7 +7091,7 @@ struct Plater::priv
|
||||
void remove(size_t obj_idx);
|
||||
bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); //BBS
|
||||
void delete_all_objects_from_model();
|
||||
void reset(bool apply_presets_change = false);
|
||||
void reset(bool apply_presets_change = false, bool reload_presets = true);
|
||||
void center_selection();
|
||||
void drop_selection();
|
||||
void mirror(Axis axis);
|
||||
@@ -7951,7 +7949,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
|
||||
if (this->q->get_project_filename().IsEmpty() && this->q->is_empty_project()) {
|
||||
int skip_confirm = e.GetInt();
|
||||
this->q->new_project(skip_confirm, true);
|
||||
// Skips the preset reload; trigger_restore_project()'s callers load the presets first.
|
||||
this->q->new_project(skip_confirm, true, wxString(), false);
|
||||
}
|
||||
});
|
||||
//wxPostEvent(this->q, wxCommandEvent{EVT_RESTORE_PROJECT});
|
||||
@@ -10416,7 +10415,7 @@ void Plater::priv::delete_all_objects_from_model()
|
||||
model.plates_custom_gcodes.clear();
|
||||
}
|
||||
|
||||
void Plater::priv::reset(bool apply_presets_change)
|
||||
void Plater::priv::reset(bool apply_presets_change, bool reload_presets)
|
||||
{
|
||||
// TakeSnapshot below and load_current_presets() further down each re-evaluate the
|
||||
// aggregate dirty flag against a baseline that hasn't been reset yet, so they can toggle
|
||||
@@ -10471,8 +10470,8 @@ void Plater::priv::reset(bool apply_presets_change)
|
||||
// Same reason, one level up: the Design tab keeps the editable document, not the Model, so
|
||||
// clearing the recipe alone leaves the tab showing the previous project's feature tree —
|
||||
// and its next edit syncs that tree straight back into the new project.
|
||||
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
|
||||
wxGetApp().mainframe->m_design_panel->clear_document();
|
||||
if (DesignPanel* design = DesignPanel::if_built())
|
||||
design->clear_document();
|
||||
#endif
|
||||
assemble_view->get_canvas3d()->reset_explosion_ratio();
|
||||
update();
|
||||
@@ -10491,7 +10490,7 @@ void Plater::priv::reset(bool apply_presets_change)
|
||||
wxGetApp().preset_bundle->reset_project_embedded_presets();
|
||||
if (apply_presets_change)
|
||||
wxGetApp().apply_keeped_preset_modifications();
|
||||
else
|
||||
else if (reload_presets)
|
||||
wxGetApp().load_current_presets(false, false);
|
||||
|
||||
//BBS
|
||||
@@ -12861,8 +12860,6 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
notification_manager->set_slicing_progress_export_possible();
|
||||
|
||||
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
|
||||
const std::string lifecycle_job_name = this->background_process.fff_print() ?
|
||||
this->background_process.fff_print()->output_filename() : std::string();
|
||||
this->background_process.reset_export();
|
||||
// This bool stops showing export finished notification even when process_completed_with_error is false
|
||||
bool has_error = false;
|
||||
@@ -12909,8 +12906,18 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
|
||||
{
|
||||
Slic3r::LifecycleEventContext ctx;
|
||||
ctx.name = lifecycle_job_name;
|
||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||
if (const PrintBase* print = this->background_process.current_print()) {
|
||||
const Model& model = print->model();
|
||||
ctx.id = std::to_string(model.id().id);
|
||||
if (model.model_info)
|
||||
ctx.name = model.model_info->model_name;
|
||||
} else {
|
||||
// Realistically Printbase* print will never be null because select_technology already asserts an active print
|
||||
// and the worker thread asserts it before processing.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": slicing completed without an active print; lifecycle event has no model ID";
|
||||
}
|
||||
ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn :
|
||||
(has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok);
|
||||
ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string());
|
||||
Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx);
|
||||
}
|
||||
@@ -13243,17 +13250,17 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
// Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
|
||||
// while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
|
||||
// TAB_ID_MONITOR itself.
|
||||
const bool selecting_web_device_tab = main_frame->m_printer_view &&
|
||||
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
|
||||
const bool selecting_web_device_tab = main_frame->m_printer_view_page &&
|
||||
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view_page;
|
||||
if (selecting_web_device_tab) {
|
||||
// Use the selected discovered machine when the preset has no host.
|
||||
main_frame->load_printer_url();
|
||||
} else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
|
||||
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
if (main_frame->m_printer_view && url.empty()) {
|
||||
if (PrinterWebView* view = PrinterWebView::if_built(); view != nullptr && url.empty()) {
|
||||
// It's missing_connection page, reload so that we can replay the gif image
|
||||
main_frame->m_printer_view->reload();
|
||||
view->reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13984,8 +13991,8 @@ void Plater::priv::unbind_canvas_event_handlers()
|
||||
// The Design tab's viewport is a fourth GLCanvas3D on the same shared GL context, owned by
|
||||
// MainFrame rather than by us — same reach as reset() uses for clear_document(). Null until
|
||||
// the tab has been opened once, so most sessions skip it.
|
||||
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
|
||||
wxGetApp().mainframe->m_design_panel->unbind_canvas_event_handlers();
|
||||
if (DesignPanel* design = DesignPanel::if_built())
|
||||
design->unbind_canvas_event_handlers();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -13998,8 +14005,8 @@ void Plater::priv::reset_canvas_volumes()
|
||||
preview->get_canvas3d()->reset_volumes();
|
||||
|
||||
#ifdef SLIC3R_CAD
|
||||
if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->m_design_panel != nullptr)
|
||||
wxGetApp().mainframe->m_design_panel->reset_canvas_volumes();
|
||||
if (DesignPanel* design = DesignPanel::if_built())
|
||||
design->reset_canvas_volumes();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -15411,7 +15418,7 @@ Print& Plater::fff_print() { return p->fff_print; }
|
||||
const SLAPrint& Plater::sla_print() const { return p->sla_print; }
|
||||
SLAPrint& Plater::sla_print() { return p->sla_print; }
|
||||
|
||||
int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_name)
|
||||
int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_name, bool reload_presets)
|
||||
{
|
||||
model().calib_pa_pattern.reset(nullptr);
|
||||
model().plates_custom_gcodes.clear();
|
||||
@@ -15459,7 +15466,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
|
||||
// completes, so hold notifications until then to avoid firing on transient flips.
|
||||
ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(p->dirty_state);
|
||||
|
||||
reset(transfer_preset_changes);
|
||||
reset(transfer_preset_changes, reload_presets);
|
||||
reset_project_dirty_after_save();
|
||||
reset_project_dirty_initial_presets();
|
||||
wxGetApp().update_saved_preset_from_current_preset();
|
||||
@@ -16469,7 +16476,9 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
|
||||
_obj->config.set_key_value("internal_solid_infill_line_width", new ConfigOptionFloatOrPercent(nozzle_diameter * 1.2f, false));
|
||||
// ORCA: use the pattern parameter
|
||||
_obj->config.set_key_value("top_surface_pattern", new ConfigOptionEnum<InfillPattern>(pattern));
|
||||
_obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f));
|
||||
const auto *top_solid_flow = dynamic_cast<const ConfigOptionFloatsNullable *>(_obj->config.option("top_solid_infill_flow_ratio"));
|
||||
_obj->config.set_key_value("top_solid_infill_flow_ratio",
|
||||
new ConfigOptionFloatsNullable(top_solid_flow ? top_solid_flow->size() : 1, 1.0f));
|
||||
_obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45));
|
||||
_obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135));
|
||||
_obj->config.set_key_value("center_of_surface_pattern", new ConfigOptionEnum<CenterOfSurfacePattern>(CenterOfSurfacePattern::Each_Surface));
|
||||
@@ -18044,7 +18053,7 @@ void Plater::deselect_all() { p->deselect_all(); }
|
||||
void Plater::exit_gizmo() { p->exit_gizmo(); }
|
||||
|
||||
void Plater::remove(size_t obj_idx) { p->remove(obj_idx); }
|
||||
void Plater::reset(bool apply_presets_change) { p->reset(apply_presets_change); }
|
||||
void Plater::reset(bool apply_presets_change, bool reload_presets) { p->reset(apply_presets_change, reload_presets); }
|
||||
void Plater::reset_with_confirm()
|
||||
{
|
||||
if (p->model.objects.empty() || MessageDialog(static_cast<wxWindow *>(this), _L("All objects will be removed, continue?"),
|
||||
@@ -20061,7 +20070,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
|
||||
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
|
||||
{
|
||||
p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
|
||||
auto calibration_panel = p->main_frame->m_calibration;
|
||||
CalibrationPanel* calibration_panel = CalibrationPanel::ensure();
|
||||
if (calibration_panel) {
|
||||
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
|
||||
wxCommandEvent event(EVT_CALIBRATION_JOB_FINISHED);
|
||||
@@ -20093,8 +20102,8 @@ void Plater::print_job_finished(wxCommandEvent &evt)
|
||||
|
||||
dev->set_selected_machine(evt.GetString().ToStdString());
|
||||
p->main_frame->request_select_tab(TAB_ID_MONITOR);
|
||||
//jump to monitor and select device status panel
|
||||
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||
// Selects the status page on a built Device tab; one built by the switch starts there.
|
||||
MonitorPanel* curr_monitor = MonitorPanel::if_built();
|
||||
if(curr_monitor)
|
||||
curr_monitor->get_tabpanel()->ChangeSelection(MonitorPanel::PrinterTab::PT_STATUS);
|
||||
}
|
||||
@@ -20834,8 +20843,8 @@ void Plater::update_print_error_info(int code, std::string msg, std::string extr
|
||||
if (p->m_send_to_sdcard_dlg) {
|
||||
p->m_send_to_sdcard_dlg->update_print_error_info(code, msg, extra);
|
||||
}
|
||||
if (p->main_frame->m_calibration)
|
||||
p->main_frame->m_calibration->update_print_error_info(code, msg, extra);
|
||||
if (CalibrationPanel* calibration = CalibrationPanel::if_built())
|
||||
calibration->update_print_error_info(code, msg, extra);
|
||||
}
|
||||
|
||||
wxString Plater::get_project_filename(const wxString& extension) const
|
||||
@@ -21128,7 +21137,8 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
|
||||
{
|
||||
printer_name.Replace("Bambu Lab", "", false);
|
||||
wxString content;
|
||||
bool device_page = (wxGetApp().mainframe == nullptr) && (wxGetApp().mainframe->m_monitor->IsShown());
|
||||
MainFrame* frame = wxGetApp().mainframe;
|
||||
bool device_page = frame != nullptr && frame->m_monitor_page->in_book();
|
||||
if (type == PrinterWarningType::NOT_CONNECTED) {
|
||||
if (device_page) {
|
||||
content = wxString::Format(_L("Printer not connected. Please go to the device page to connect %s before syncing."),
|
||||
|
||||
@@ -339,7 +339,7 @@ public:
|
||||
// Helper: returns config indices where filament_is_mixed == false
|
||||
std::vector<size_t> physical_filament_config_indices() const;
|
||||
|
||||
int new_project(bool skip_confirm = false, bool silent = false, const wxString& project_name = wxString());
|
||||
int new_project(bool skip_confirm = false, bool silent = false, const wxString& project_name = wxString(), bool reload_presets = true);
|
||||
// BBS: save & backup
|
||||
void load_project(wxString const & filename = "", wxString const & originfile = "-");
|
||||
int save_project(bool saveAs = false);
|
||||
@@ -499,7 +499,7 @@ public:
|
||||
void deselect_all();
|
||||
void exit_gizmo();
|
||||
void remove(size_t obj_idx);
|
||||
void reset(bool apply_presets_change = false);
|
||||
void reset(bool apply_presets_change = false, bool reload_presets = true);
|
||||
void reset_with_confirm();
|
||||
//BBS: return int for various result
|
||||
int close_with_confirm(std::function<bool(bool yes_or_no)> second_check = nullptr); // BBS close project
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Lazy.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Holders to build at idle, ordered by prebuild_order(), run one slice at a time. The
|
||||
// holders outlive the queue. Main thread only.
|
||||
class PrebuildQueue
|
||||
{
|
||||
public:
|
||||
struct Slice
|
||||
{
|
||||
std::string name; // the task that ran; empty when nothing was pending
|
||||
int units{ 0 }; // units run
|
||||
long long ms{ 0 }; // time spent
|
||||
bool completed{ false }; // the task has no more units
|
||||
bool remaining{ false }; // some task is still pending
|
||||
};
|
||||
|
||||
// Lower order runs first, equal order in the order added.
|
||||
void add(LazyBase& task)
|
||||
{
|
||||
auto after = std::find_if(m_tasks.begin(), m_tasks.end(), [&](const LazyBase* t) { return t->prebuild_order() > task.prebuild_order(); });
|
||||
m_tasks.insert(after, &task);
|
||||
}
|
||||
|
||||
void clear() { m_tasks.clear(); }
|
||||
|
||||
bool pending() const
|
||||
{
|
||||
return std::any_of(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
|
||||
}
|
||||
|
||||
// The task names in queue order, comma separated.
|
||||
std::string names() const
|
||||
{
|
||||
std::string out;
|
||||
for (const LazyBase* t : m_tasks)
|
||||
out += (out.empty() ? "" : ", ") + t->name();
|
||||
return out;
|
||||
}
|
||||
|
||||
// Runs units of the first pending task until it completes, budget_ms of now_ms() have
|
||||
// passed, or interrupt() is true after a unit; on_unit gets the task name and each
|
||||
// unit's duration. A task with no work is passed over and stays in the queue, so one
|
||||
// whose work returns is pending again.
|
||||
Slice run_slice(int budget_ms,
|
||||
const std::function<long long()>& now_ms,
|
||||
const std::function<bool()>& interrupt,
|
||||
const std::function<void(const std::string&, long long)>& on_unit = {})
|
||||
{
|
||||
Slice slice;
|
||||
auto next = std::find_if(m_tasks.begin(), m_tasks.end(), [](const LazyBase* t) { return t->pending(); });
|
||||
if (next == m_tasks.end())
|
||||
return slice;
|
||||
// The pointer is copied out, since a unit may add tasks and reallocate m_tasks.
|
||||
LazyBase* const task = *next;
|
||||
slice.name = task->name();
|
||||
const long long started = now_ms();
|
||||
for (;;) {
|
||||
const long long unit_started = now_ms();
|
||||
const bool more = task->build_step();
|
||||
const long long now = now_ms();
|
||||
++slice.units;
|
||||
if (on_unit)
|
||||
on_unit(slice.name, now - unit_started);
|
||||
slice.ms = now - started;
|
||||
if (!more) {
|
||||
slice.completed = true;
|
||||
break;
|
||||
}
|
||||
if (slice.ms >= budget_ms || interrupt())
|
||||
break;
|
||||
}
|
||||
slice.remaining = pending();
|
||||
return slice;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<LazyBase*> m_tasks;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
@@ -1044,8 +1044,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
|
||||
}
|
||||
else if (param == "hide_login_side_panel") {
|
||||
if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) {
|
||||
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built()) {
|
||||
home->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview
|
||||
@@ -1254,9 +1254,8 @@ wxBoxSizer *PreferencesDialog::create_item_bambu_cloud(wxString title, wxString
|
||||
app_config->save();
|
||||
|
||||
// Update homepage visibility immediately
|
||||
auto *mainframe = wxGetApp().mainframe;
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
if (WebViewPanel* home = WebViewPanel::if_built())
|
||||
home->SendCloudProvidersInfo();
|
||||
});
|
||||
|
||||
m_sizer->Add(cb, 0, wxALIGN_CENTER);
|
||||
@@ -1476,7 +1475,7 @@ void PreferencesDialog::create()
|
||||
app_config = get_app_config();
|
||||
|
||||
m_parent = new MyscrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_parent->SetScrollRate(5, 5);
|
||||
m_parent->SetScrollRate(0, FromDIP(20));
|
||||
m_parent->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -32,7 +32,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
||||
|
||||
|
||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(10));
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrollwindow->SetMinSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
m_scrollwindow->SetMaxSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "wx/textctrl.h"
|
||||
#include <wx/timer.h>
|
||||
#include <memory>
|
||||
#include "Lazy.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -34,7 +35,7 @@ namespace GUI {
|
||||
class PrinterWebViewHandler;
|
||||
|
||||
|
||||
class PrinterWebView : public wxPanel {
|
||||
class PrinterWebView : public wxPanel, public LazyInstance<PrinterWebView> {
|
||||
public:
|
||||
PrinterWebView(wxWindow *parent);
|
||||
virtual ~PrinterWebView();
|
||||
|
||||
@@ -44,6 +44,7 @@ const std::vector<std::string> license_list = {
|
||||
|
||||
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "")
|
||||
@@ -67,6 +68,7 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
||||
m_auxiliary = new AuxiliaryPanel(this);
|
||||
m_auxiliary->Hide();
|
||||
main_sizer->Add(m_auxiliary, wxSizerFlags().Expand().Proportion(1));
|
||||
add_build_steps_of(*m_auxiliary);
|
||||
Bind(EVT_AUXILIARY_DONE, [this](wxCommandEvent& e) { update_model_data();});
|
||||
|
||||
SetSizer(main_sizer);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user