Merge branch 'main' into cad-mainline

This commit is contained in:
SoftFever
2026-08-18 15:33:31 +08:00
committed by GitHub
24 changed files with 1302 additions and 195 deletions
+2
View File
@@ -2982,6 +2982,8 @@ public:
const double & opt_float(const t_config_option_key &opt_key, unsigned int idx) const;
double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloatsNullable>(opt_key)->get_at(idx); }
const double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloatsNullable *>(this->option(opt_key))->get_at(idx); }
FloatOrPercent & opt_float_or_percent_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloatsOrPercentsNullable>(opt_key)->get_at(idx); }
const FloatOrPercent & opt_float_or_percent_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(this->option(opt_key))->get_at(idx); }
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
+1
View File
@@ -6726,6 +6726,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
"farthest_point_timelapse"sv,
"compatible_printers"sv,
"compatible_prints"sv,
"filament_colour_type"sv,
"print_host"sv,
"print_host_webui"sv,
"printhost_apikey"sv,
+127 -21
View File
@@ -19,6 +19,7 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <functional>
#include <limits>
#include <numeric>
#include <unordered_map>
@@ -39,7 +40,11 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
const AABBTreeLines::LinesDistancer<L>& unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
float min_distance = -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 = {})
{
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) {
@@ -120,6 +125,107 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
points.push_back(next_point);
}
// 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) {
// 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.
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; };
std::vector<ExtendedPoint<L::Dim>> sampled_points; // Populated lazily, on the first insertion
std::vector<std::pair<double, float>> interior; // Samples of one segment, keyed by position along it
std::vector<Subspan> pending;
for (size_t point_idx = 0; point_idx + 1 < points.size(); ++point_idx) {
const ExtendedPoint<L::Dim>& curr = points[point_idx];
const ExtendedPoint<L::Dim>& next = points[point_idx + 1];
const Vec step = next.position - curr.position;
const double line_len = step.norm();
interior.clear();
if (line_len >= max_probe_spacing)
pending.push_back({0., 1., max_bisection_depth});
while (!pending.empty()) {
const Subspan subspan = pending.back();
pending.pop_back();
if (subspan.depth <= 0 || (subspan.t1 - subspan.t0) * line_len < max_probe_spacing)
continue;
const double t = 0.5 * (subspan.t0 + subspan.t1);
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
(curr.position + t * step).template cast<AABBScalar>());
const float sampled = float(distance + boundary_offset);
interior.emplace_back(t, sampled);
pending.push_back({subspan.t0, t, subspan.depth - 1});
pending.push_back({t, subspan.t1, subspan.depth - 1});
}
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
// 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) {
const float sample = interior[i].second;
const bool at_start = kept == 0; // Nothing kept yet, so the segment's own start precedes it
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));
if (worth_before || worth_after)
interior[kept++] = interior[i];
}
interior.resize(kept);
}
if (!interior.empty() && sampled_points.empty()) {
sampled_points.reserve(points.size() + 8);
sampled_points.assign(points.begin(), points.begin() + point_idx + 1);
}
if (!sampled_points.empty()) {
// Only a sub-span of max_probe_spacing or more is ever bisected, so these sit at least
// 2 * min_spacing apart, and need none of the filtering the passes either side of this one do.
for (const auto& [t, distance] : interior)
sampled_points.push_back({curr.position + t * step, distance});
sampled_points.push_back(next);
}
}
if (!sampled_points.empty())
points = std::move(sampled_points);
}
// Segmentation handling
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector<ExtendedPoint<L::Dim>> new_points;
@@ -362,9 +468,28 @@ public:
smallest_distance_with_lower_speed=-1.f;
// Orca: Pass to the point properties estimator the smallest ovehang distance that triggers a slowdown (smallest_distance_with_lower_speed)
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
if (distance <= speed_sections.front().first) {
final_speed = original_speed;
} else if (distance >= speed_sections.back().first) {
final_speed = speed_sections.back().second;
} else {
size_t section_idx = 0;
while (distance > speed_sections[section_idx + 1].first) {
section_idx++;
}
float t = (distance - speed_sections[section_idx].first) /
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
t = std::clamp(t, 0.0f, 1.0f);
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
}
return round(final_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);
smallest_distance_with_lower_speed, calculate_speed);
const auto width_inv = 1.0f / path.width;
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
@@ -423,25 +548,6 @@ public:
}
}
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
if (distance <= speed_sections.front().first) {
final_speed = original_speed;
} else if (distance >= speed_sections.back().first) {
final_speed = speed_sections.back().second;
} else {
size_t section_idx = 0;
while (distance > speed_sections[section_idx + 1].first) {
section_idx++;
}
float t = (distance - speed_sections[section_idx].first) /
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
t = std::clamp(t, 0.0f, 1.0f);
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
}
return round(final_speed);
};
float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance));
// ORCA: Clamp resulting speed to lowest of calculated speed based on the overhang values and the current speed
// Fixes bug where resulting overhang speed is higher than the current speed due to (for example) volumetric flow limits.
+2 -2
View File
@@ -3246,9 +3246,9 @@ double Model::findMaxSpeed(const ModelObject* object) {
if (objectKey == "outer_wall_speed")
externalPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
if (objectKey == "small_perimeter_speed")
smallPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
smallPerimeterSpeedObj = object->config.get().opt_float_or_percent_nullable(objectKey, 0).get_abs_value(externalPerimeterSpeedObj);
if (objectKey == "small_support_perimeter_speed")
smallSupportPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
smallSupportPerimeterSpeedObj = object->config.get().opt_float_or_percent_nullable(objectKey, 0).get_abs_value(supportSpeedObj);
}
objMaxSpeed = std::max(perimeterSpeedObj, std::max(externalPerimeterSpeedObj, std::max(infillSpeedObj, std::max(solidInfillSpeedObj, std::max(topSolidInfillSpeedObj, std::max(supportSpeedObj, std::max(smallPerimeterSpeedObj, std::max(smallSupportPerimeterSpeedObj, objMaxSpeed))))))));
if (objMaxSpeed <= 0) objMaxSpeed = 250.;
-1
View File
@@ -49,7 +49,6 @@ static std::vector<std::string> s_project_options {
"filament_multi_colour",
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle",
"curr_bed_type",
"flush_multiplier",
// Fast-purge mode: project-level purge control, inert at Default.
+11 -1
View File
@@ -10426,6 +10426,16 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
}
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride)
{
// A single-value object or region override applies to every nozzle variant.
std::vector<int> indices = variant_index;
if (source.size() == 1 && !source.is_nil(0))
std::fill(indices.begin(), indices.end(), 0);
target.set_to_index(&source, indices, stride);
}
//used for object/region config
//use the smallest of multiple to single
@@ -11503,7 +11513,7 @@ void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPr
else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(opt_src);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(opt_dest);
opt_vec_src->set_to_index(opt_vec_dest, variant_index, stride);
set_variant_override(*opt_vec_src, *opt_vec_dest, variant_index, stride);
}
}
}
+52
View File
@@ -842,6 +842,9 @@ extern std::set<std::string> printer_options_with_variant_1;
extern std::set<std::string> printer_options_with_variant_2;
extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1);
extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
@@ -2394,6 +2397,55 @@ static void set_flush_volumes_matrix(std::vector<T> &out_matrix, const std::vect
}
}
template<class T>
static bool has_zero_flush_volume_for_used_filaments(const std::vector<T> &fv_matrix,
const std::vector<T> &flush_multipliers,
const std::vector<int> &used_filaments)
{
if (used_filaments.size() < 2 || flush_multipliers.empty())
return false;
if (fv_matrix.size() % flush_multipliers.size() != 0)
return false;
const size_t matrix_len = fv_matrix.size() / flush_multipliers.size();
const size_t row_len = size_t(std::sqrt(double(matrix_len)));
if (row_len < 2 || row_len * row_len != matrix_len)
return false;
std::vector<int> filtered_filaments;
filtered_filaments.reserve(used_filaments.size());
for (int filament_id : used_filaments) {
if (filament_id <= 0 || filament_id > int(row_len))
continue;
if (std::find(filtered_filaments.begin(), filtered_filaments.end(), filament_id) == filtered_filaments.end())
filtered_filaments.push_back(filament_id);
}
if (filtered_filaments.size() < 2)
return false;
for (T multiplier : flush_multipliers) {
if (multiplier == 0)
return true;
}
for (size_t nozzle_idx = 0; nozzle_idx < flush_multipliers.size(); nozzle_idx++) {
const size_t block_offset = nozzle_idx * matrix_len;
for (int from_id : filtered_filaments) {
for (int to_id : filtered_filaments) {
if (from_id == to_id)
continue;
const size_t matrix_idx = block_offset + size_t(from_id - 1) * row_len + size_t(to_id - 1);
if (matrix_idx < fv_matrix.size() && fv_matrix[matrix_idx] == 0)
return true;
}
}
}
return false;
}
size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id);
} // namespace Slic3r
+1 -1
View File
@@ -3812,7 +3812,7 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr
else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(my_opt);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(it->second.get());
opt_vec_src->set_to_index(opt_vec_dest, variant_index, 1);
set_variant_override(*opt_vec_src, *opt_vec_dest, variant_index);
}
}
}
+8 -19
View File
@@ -2909,7 +2909,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
float a = dynamic_cast<const ConfigOptionFloat*>(proj_cfg.option("wipe_tower_rotation_angle"))->value;
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
// BBS
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
@@ -10737,9 +10737,8 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
wxString region = L"en";
if (language.find("zh") == 0)
region = L"zh";
// Use the generic dual-nozzle PLA+PETG guide rather than the H2D-specific page
// so the link is relevant for all dual-extrusion printers, not just Bambu H2D. (#12073)
wxGetApp().open_browser_with_warning_dialog(wxString::Format(L"https://wiki.bambulab.com/%s/filament-acc/filament/pla-and-petg-dual-extrusion", region));
// Although this link looks like it's only for the H2D, its guidance is generic.
wxGetApp().open_browser_with_warning_dialog(wxString::Format(L"https://wiki.bambulab.com/%s/filament-acc/filament/h2d-pla-and-petg-mutual-support", region));
return false;
});
}
@@ -10873,24 +10872,14 @@ bool GLCanvas3D::is_flushing_matrix_error() {
if (!Sidebar::should_show_SEMM_buttons())
return false;
std::vector<int> plate_extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_extruders(true);
if (plate_extruders.size() < 2)
return false;
const auto &project_config = wxGetApp().preset_bundle->project_config;
const std::vector<double> &config_matrix = (project_config.option<ConfigOptionFloats>("flush_volumes_matrix"))->values;
const std::vector<double> &config_multiplier = (project_config.option<ConfigOptionFloats>("flush_multiplier"))->values;
for (auto multiplier : config_multiplier) {
if (multiplier == 0) return true;
}
int matrix_len = config_matrix.size() / config_multiplier.size();
int row_len = std::sqrt(matrix_len);
for (int i = 0; i < config_matrix.size(); i++)
{
int relative_id = i % matrix_len;
int row_id = relative_id / row_len;
int col_id = relative_id % row_len;
if (row_id != col_id && config_matrix[i] == 0) return true;
}
return false;
return has_zero_flush_volume_for_used_filaments(config_matrix, config_multiplier, plate_extruders);
}
bool GLCanvas3D::_is_any_volume_outside() const
+3 -3
View File
@@ -3273,15 +3273,12 @@ bool GUI_App::on_init_inner()
}
} */
copy_network_if_available();
if (scrn) {
scrn->SetText(_L("Loading Plugins") + dots, 20);
wxYield();
}
on_init_network();
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
// initialize() also installs the libslic3r hooks (capability resolver,
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
@@ -3310,6 +3307,9 @@ bool GUI_App::on_init_inner()
}
}
copy_network_if_available();
on_init_network();
if (m_agent)
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent()));
+2 -2
View File
@@ -12706,7 +12706,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
ModelWipeTower& tower = model.wipe_tower;
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
}
const GLGizmosManager& gizmos = get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView ? assemble_view->get_canvas3d()->get_gizmos_manager() : view3D->get_canvas3d()->get_gizmos_manager();
@@ -12816,7 +12816,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
ModelWipeTower& tower = model.wipe_tower;
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
}
const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;