Merge branch 'feat/printer-agent-infra' into feat/printer-agent-impl

This commit is contained in:
Ian Chua
2026-09-28 21:15:32 +08:00
committed by GitHub
2933 changed files with 209923 additions and 266930 deletions
+31 -5
View File
@@ -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;
+6
View File
@@ -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
+215
View File
@@ -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 &section)
{
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 &sections, 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;
}
+2 -2
View File
@@ -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) {
+2
View File
@@ -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>
+3
View File
@@ -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 {
+6 -3
View File
@@ -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());
+1
View File
@@ -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) {
+79 -2
View File
@@ -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;
+29 -4
View File
@@ -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);
+572
View File
@@ -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 &params,
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);
+3
View File
@@ -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
+99 -1
View File
@@ -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 &para
Polylines FillCubic::fill_surface(const Surface *surface, const FillParams &params)
{
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,
+1
View File
@@ -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
View File
@@ -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 &region = 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 &region = 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;
+6 -1
View File
@@ -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;
};
+110 -74
View File
@@ -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());
+73 -5
View File
@@ -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);
+67
View File
@@ -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_
+22 -1
View File
@@ -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)
+5
View File
@@ -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;
+37 -12
View File
@@ -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);
}
}
+3
View File
@@ -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:
+1
View File
@@ -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",
+1
View File
@@ -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"
+65 -20
View File
@@ -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);
}
+12 -10
View File
@@ -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();
+2 -2
View File
@@ -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
+8 -6
View File
@@ -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
View File
@@ -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(); };
+4
View File
@@ -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;
+21 -3
View File
@@ -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 = {
+3 -1
View File
@@ -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))
+6 -1
View File
@@ -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"
+11
View File
@@ -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));
+2
View File
@@ -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 };
+8
View File
@@ -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
+6
View File
@@ -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
+4 -4
View File
@@ -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);
+22 -12
View File
@@ -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()
+2 -1
View File
@@ -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};
+1 -1
View File
@@ -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)));
+5 -5
View File
@@ -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; }
+2 -1
View File
@@ -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);
+1 -2
View File
@@ -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");
+1 -1
View File
@@ -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);
+7 -4
View File
@@ -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;
+2 -2
View File
@@ -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 };
+57 -66
View File
@@ -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)
+5 -1
View File
@@ -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;
+3 -1
View File
@@ -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();
}
+1 -1
View File
@@ -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)));
+2
View File
@@ -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;
+1 -1
View File
@@ -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();
+5 -5
View File
@@ -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)));
+7 -4
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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:
+27 -3
View File
@@ -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(); }
+3 -2
View File
@@ -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
View File
@@ -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)
+7
View File
@@ -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); }
+2 -2
View File
@@ -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);
+3 -1
View File
@@ -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
+3 -2
View File
@@ -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);
+105
View File
@@ -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
+41
View File
@@ -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
+14 -5
View File
@@ -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);
+10
View File
@@ -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())
+3
View File
@@ -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;
+27
View File
@@ -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
+198
View File
@@ -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
+14
View File
@@ -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
+77
View File
@@ -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
View File
@@ -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)
+59 -17
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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();
+8 -6
View File
@@ -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);
+4 -3
View File
@@ -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));
+4 -3
View File
@@ -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)));
+2 -1
View File
@@ -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);
+2 -1
View File
@@ -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;
+2 -2
View File
@@ -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)));
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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};
+27 -9
View File
@@ -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()) {
+21
View File
@@ -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
View File
@@ -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)
+21
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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."),
+2 -2
View File
@@ -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
+89
View File
@@ -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
+5 -6
View File
@@ -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);
+1 -1
View File
@@ -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));
+2 -1
View File
@@ -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();
+2
View File
@@ -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