mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
Merge Main into Belt Printer
Merge origin/main (00429da739) into belt-printer.
Conflicts resolved:
- src/CMakeLists.txt: keep both wxInspector workarounds.
- GCodeProcessor.cpp: keep the belt compare_pos / z_for_height lines.
- PrintObjectSlice.cpp: the belt bbox-Z guard also covers main's
printable_region_ids bookkeeping.
- TreeSupport.cpp: the belt-floor check runs before main's PendingNode
queueing.
- Tab.hpp: keep the belt fields, drop the removed upload description
fields.
- tests/libslic3r/CMakeLists.txt: keep both test files.
Also included:
- eSUN PLA belt presets declare their own filament_id (OFkrxQC4) and
scripts/filament_id_snapshot.json is regenerated, as main's filament_id
check requires.
- Custom.json version bumped to 02.04.00.05 so the belt entries reach
existing installs.
- Fix the ambiguous WithinRel call in the belt apron width test, which
otherwise breaks the fff_print build.
This commit is contained in:
+111
-100
@@ -1,3 +1,8 @@
|
||||
#ifdef _WIN32
|
||||
// Keep this first. A header below reaches boost/regex, whose w32_regex_traits
|
||||
// needs the Win32 types declared already.
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include "Config.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Print.hpp"
|
||||
@@ -18,6 +23,7 @@
|
||||
#include "GCode/MachineFrameTransform.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/WipeTower2.hpp"
|
||||
#include "GCode/WipeTowerEstimate.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "MaterialType.hpp"
|
||||
@@ -1080,20 +1086,21 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
||||
|
||||
//BBS: add the wipe tower check logic
|
||||
const PrintConfig & config = print.config();
|
||||
int filaments_count = print.extruders().size();
|
||||
// Custom G-code tool changes (MultiAsSingle) build a real tower on a plate whose objects
|
||||
// all use one filament, so they have to be counted or the hull below collapses to a point.
|
||||
int filaments_count = print.extruders(true).size();
|
||||
int plate_index = print.get_plate_index();
|
||||
const Vec3d plate_origin = print.get_plate_origin();
|
||||
float x = config.wipe_tower_x.get_at(plate_index) + plate_origin(0);
|
||||
float y = config.wipe_tower_y.get_at(plate_index) + plate_origin(1);
|
||||
float width = config.prime_tower_width.value;
|
||||
float a = config.wipe_tower_rotation_angle.value;
|
||||
//float v = config.wiping_volume.value;
|
||||
|
||||
float depth = print.wipe_tower_data(filaments_count).depth;
|
||||
//float brim_width = print.wipe_tower_data(filaments_count).brim_width;
|
||||
|
||||
if (config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib)
|
||||
width = depth;
|
||||
// The estimate resolves the effective width (a rib wall squares the tower).
|
||||
const WipeTowerData &wipe_tower_estimate = print.wipe_tower_data(filaments_count);
|
||||
float width = wipe_tower_estimate.width;
|
||||
float depth = wipe_tower_estimate.depth;
|
||||
float brim_width = wipe_tower_estimate.brim_width;
|
||||
|
||||
Polygons convex_hulls_temp;
|
||||
if (print.has_wipe_tower()) {
|
||||
@@ -1115,36 +1122,54 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
||||
convex_hulls_temp.push_back(wipe_tower_polygon);
|
||||
}
|
||||
}
|
||||
// Post-generation the mesh bottom already carries the brim. Pre-generation the body grows
|
||||
// by the brim only when its width is explicit; the auto brim and a Type2 cone base depend on
|
||||
// the tower height, exact only once generated, so they only warn here - the exact footprint
|
||||
// is re-checked in _make_wipe_tower.
|
||||
const bool exact_footprint = print.is_step_done(psWipeTower);
|
||||
Polygons tower_polys_checked = (!exact_footprint && config.prime_tower_brim_width.value >= 0) ?
|
||||
offset(convex_hulls_temp, float(scale_(brim_width))) :
|
||||
convex_hulls_temp;
|
||||
Polygons tower_polys_estimated;
|
||||
if (!exact_footprint && !convex_hulls_temp.empty()) {
|
||||
double max_height = 0.;
|
||||
for (const PrintObject *object : print.objects())
|
||||
max_height = std::max(max_height, unscale_(object->size().z()));
|
||||
Polygon base = estimate_wipe_tower_first_layer_outline(config, print.wipe_tower_type(), width, depth, max_height);
|
||||
base.rotate(Geometry::deg2rad(a));
|
||||
base.translate(Point(scale_(x), scale_(y)));
|
||||
tower_polys_estimated = offset(base, float(scale_(brim_width)));
|
||||
}
|
||||
// Object proximity stays a body-only warning: brim near-misses would newly warn on
|
||||
// many setups that print fine.
|
||||
if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) {
|
||||
if (warning) {
|
||||
warning->string += L("Prime Tower") + L(" is too close to others, and collisions may be caused.\n");
|
||||
}
|
||||
}
|
||||
if (!intersection(exclude_polys, convex_hulls_temp).empty()) {
|
||||
/*if (warning) {
|
||||
warning->string += L("Prime Tower is too close to exclusion area, there may be collisions when printing.\n");
|
||||
}*/
|
||||
if (!intersection(exclude_polys, tower_polys_checked).empty()) {
|
||||
return {L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n")};
|
||||
}
|
||||
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
|
||||
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, tower_polys_checked).empty()) {
|
||||
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
||||
}
|
||||
// Skip the containment check for towers that will never be printed (single-filament
|
||||
// prints without smooth timelapse keep the config's tower position but emit nothing).
|
||||
// Pre-generation only the body square is tested — the auto-brim estimate can overshoot
|
||||
// the generated brim by several mm and must not hard-fail a print that physically fits.
|
||||
// Post-generation the mesh bottom already includes the real brim, so the exact
|
||||
// footprint is tested.
|
||||
if (filaments_count > 1 || print.enable_timelapse_print()) {
|
||||
// The shared printable polygon is plate-local, while the tower polygons above are
|
||||
// already shifted by the plate origin.
|
||||
Polygons printable_polys = print.get_extruder_shared_printable_polygon();
|
||||
const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y()));
|
||||
for (Polygon &p : printable_polys)
|
||||
p.translate(plate_shift);
|
||||
if (!diff(convex_hulls_temp, printable_polys).empty())
|
||||
return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")};
|
||||
if (warning && !intersection(exclude_polys, tower_polys_estimated).empty()) {
|
||||
warning->string += L("Prime Tower") + L(" is too close to exclusion area, there may be collisions when printing.") + "\n";
|
||||
}
|
||||
if (warning && print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, tower_polys_estimated).empty()) {
|
||||
warning->string += L("Prime Tower") + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n";
|
||||
}
|
||||
// No gate on "is there a tower": one that is not printed estimates to zero, so the hulls
|
||||
// are degenerate and every check passes. Re-deriving it here missed the wrapping-detection
|
||||
// tower on a single-filament plate.
|
||||
Polygons printable_polys = print.get_extruder_shared_printable_polygon();
|
||||
const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y()));
|
||||
for (Polygon &p : printable_polys)
|
||||
p.translate(plate_shift);
|
||||
if (!diff(tower_polys_checked, printable_polys).empty())
|
||||
return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")};
|
||||
if (warning && !diff(tower_polys_estimated, printable_polys).empty())
|
||||
warning->string += L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -2494,7 +2519,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
obj->clear_shared_object();
|
||||
|
||||
//add the print_object share check logic
|
||||
auto is_print_object_the_same = [this](const PrintObject* object1, const PrintObject* object2) -> bool{
|
||||
auto is_print_object_the_same = [](const PrintObject* object1, const PrintObject* object2) -> bool{
|
||||
if (object1->trafo().matrix() != object2->trafo().matrix())
|
||||
return false;
|
||||
const ModelObject* model_obj1 = object1->model_object();
|
||||
@@ -4249,74 +4274,25 @@ bool Print::has_wipe_tower() const
|
||||
|
||||
const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
|
||||
{
|
||||
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
|
||||
double max_height = 0;
|
||||
for (size_t obj_idx = 0; obj_idx < m_objects.size(); obj_idx++) {
|
||||
double object_z = (double) m_objects[obj_idx]->size().z();
|
||||
max_height = std::max(unscale_(object_z), max_height);
|
||||
// Until the tower is generated, size it with the estimate the GUI/CLI placement uses, so
|
||||
// validation cannot reject a position the clamp just accepted.
|
||||
if (is_step_done(psWipeTower) || filaments_cnt == 0)
|
||||
return m_wipe_tower_data;
|
||||
|
||||
double max_height = 0.;
|
||||
double layer_height = std::numeric_limits<double>::max();
|
||||
for (const PrintObject *object : m_objects) {
|
||||
max_height = std::max(max_height, unscale_(double(object->size().z())));
|
||||
layer_height = std::min(layer_height, object->config().layer_height.value);
|
||||
}
|
||||
if (max_height < EPSILON) return m_wipe_tower_data;
|
||||
if (max_height < EPSILON)
|
||||
return m_wipe_tower_data;
|
||||
|
||||
double layer_height = 0.08f; // hard code layer height
|
||||
layer_height = m_objects.front()->config().layer_height.value;
|
||||
|
||||
auto timelapse_type = config().option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
|
||||
bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib);
|
||||
double extra_spacing = config().option("prime_tower_infill_gap")->getFloat() / 100.;
|
||||
double rib_width = config().option("wipe_tower_rib_width")->getFloat();
|
||||
|
||||
double filament_change_volume = 0.;
|
||||
{
|
||||
std::vector<double> filament_change_lengths;
|
||||
auto filament_change_lengths_opt = config().option<ConfigOptionFloats>("filament_change_length");
|
||||
if (filament_change_lengths_opt) filament_change_lengths = filament_change_lengths_opt->values;
|
||||
double length = filament_change_lengths.empty() ? 0 : *std::max_element(filament_change_lengths.begin(), filament_change_lengths.end());
|
||||
double diameter = 1.75;
|
||||
std::vector<double> diameters;
|
||||
auto filament_diameter_opt = config().option<ConfigOptionFloats>("filament_diameter");
|
||||
if (filament_diameter_opt) diameters = filament_diameter_opt->values;
|
||||
diameter = diameters.empty() ? diameter : *std::max_element(diameters.begin(), diameters.end());
|
||||
filament_change_volume = length * PI * diameter * diameter / 4.;
|
||||
}
|
||||
|
||||
|
||||
if (! is_step_done(psWipeTower) && filaments_cnt !=0) {
|
||||
double wipe_volume = m_config.prime_volume;
|
||||
int filament_depth_count = m_config.nozzle_diameter.values.size() == 2 ? filaments_cnt : filaments_cnt - 1;
|
||||
if (filaments_cnt == 1 && enable_timelapse_print()) filament_depth_count = 1;
|
||||
double volume = wipe_volume * filament_depth_count;
|
||||
if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2);
|
||||
|
||||
// Sizing should take into account currently set wiping volumes.
|
||||
// For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower)
|
||||
// and it worked well enough. Let's try to do slightly better by accounting for the purging volumes.
|
||||
const bool semm_flush = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
|
||||
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(m_config, filaments_cnt);
|
||||
|
||||
if (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib) {
|
||||
double depth = std::sqrt(volume / layer_height * extra_spacing);
|
||||
if (need_wipe_tower || filaments_cnt > 1) {
|
||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||
depth = std::max((double) min_wipe_tower_depth, depth);
|
||||
depth += rib_width / std::sqrt(2) + config().wipe_tower_extra_rib_length.value;
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
||||
}
|
||||
}
|
||||
else {
|
||||
double width = m_config.prime_tower_width;
|
||||
double depth = volume / (layer_height * width);
|
||||
// The flush volumes already hold the spacing between wipes.
|
||||
if (!semm_flush) depth *= extra_spacing;
|
||||
if (need_wipe_tower || depth > EPSILON) {
|
||||
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
|
||||
depth = std::max((double) min_wipe_tower_depth, depth);
|
||||
}
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
|
||||
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
|
||||
}
|
||||
if (m_config.prime_tower_brim_width < 0) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
|
||||
}
|
||||
const WipeTowerFootprint footprint = estimate_wipe_tower_footprint(m_config, this->wipe_tower_type(), this->extruders(true), layer_height, max_height);
|
||||
WipeTowerData &data = const_cast<Print *>(this)->m_wipe_tower_data;
|
||||
data.depth = float(footprint.depth);
|
||||
data.width = float(footprint.width);
|
||||
data.brim_width = float(footprint.brim_width);
|
||||
return m_wipe_tower_data;
|
||||
}
|
||||
|
||||
@@ -4543,6 +4519,7 @@ void Print::_make_wipe_tower()
|
||||
m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
|
||||
wipe_tower.generate_new(m_wipe_tower_data.tool_changes);
|
||||
m_wipe_tower_data.depth = wipe_tower.get_depth();
|
||||
m_wipe_tower_data.width = wipe_tower.width();
|
||||
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
|
||||
m_wipe_tower_data.bbx = wipe_tower.get_bbx();
|
||||
m_wipe_tower_data.rib_offset = wipe_tower.get_rib_offset();
|
||||
@@ -4656,6 +4633,7 @@ void Print::_make_wipe_tower()
|
||||
m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
|
||||
wipe_tower.generate(m_wipe_tower_data.tool_changes);
|
||||
m_wipe_tower_data.depth = wipe_tower.get_depth();
|
||||
m_wipe_tower_data.width = wipe_tower.width();
|
||||
m_wipe_tower_data.z_and_depth_pairs = wipe_tower.get_z_and_depth_pairs();
|
||||
m_wipe_tower_data.brim_width = wipe_tower.get_brim_width();
|
||||
m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height();
|
||||
@@ -4691,7 +4669,9 @@ void Print::_make_wipe_tower()
|
||||
wipe_tower.get_wipe_tower_height(), wipe_tower.get_brim_width(),
|
||||
config().wipe_tower_wall_type.value == WipeTowerWallType::wtwRib,
|
||||
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
||||
config().wipe_tower_fillet_wall.value);
|
||||
config().wipe_tower_fillet_wall.value,
|
||||
config().wipe_tower_wall_type.value == WipeTowerWallType::wtwCone ?
|
||||
(float) config().wipe_tower_cone_angle.value : 0.f);
|
||||
const Vec3d origin = Vec3d::Zero();
|
||||
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
||||
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
||||
@@ -4704,6 +4684,28 @@ void Print::_make_wipe_tower()
|
||||
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
||||
{scale_(origin.x()), scale_(origin.y())});
|
||||
}
|
||||
|
||||
// The clamps and checks above work from estimates; re-test the exact generated footprint
|
||||
// so an off-plate tower fails with a clear error instead of exporting unprintable G-code
|
||||
// (validate() only sees the mesh on its next run).
|
||||
if (m_wipe_tower_data.wipe_tower_mesh_data) {
|
||||
Polygon footprint = m_wipe_tower_data.wipe_tower_mesh_data->bottom; // includes brim and rib offset
|
||||
footprint.rotate(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value));
|
||||
footprint.translate(Point(scale_(m_config.wipe_tower_x.get_at(m_plate_index)),
|
||||
scale_(m_config.wipe_tower_y.get_at(m_plate_index))));
|
||||
const Polygons printable_polys = this->get_extruder_shared_printable_polygon();
|
||||
if (!printable_polys.empty() && !diff(Polygons{footprint}, printable_polys).empty()) {
|
||||
const BoundingBox fp = get_extents(footprint);
|
||||
const BoundingBox pr = get_extents(printable_polys);
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("wipe tower footprint [%1%,%2%]-[%3%,%4%] leaves printable [%5%,%6%]-[%7%,%8%]") %
|
||||
unscaled(fp.min.x()) % unscaled(fp.min.y()) % unscaled(fp.max.x()) % unscaled(fp.max.y()) %
|
||||
unscaled(pr.min.x()) % unscaled(pr.min.y()) % unscaled(pr.max.x()) % unscaled(pr.max.y());
|
||||
throw Slic3r::SlicingError(L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n"));
|
||||
}
|
||||
// The cutter/purge corner is a physical obstacle — the brim must stay out like the body.
|
||||
if (!intersection(get_bed_excluded_area(m_config), Polygons{footprint}).empty())
|
||||
throw Slic3r::SlicingError(L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n"));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
|
||||
@@ -5957,7 +5959,7 @@ int Print::load_cached_data(const std::string& directory)
|
||||
return CLI_IMPORT_CACHE_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto find_region = [this](PrintObject* object, size_t config_hash) -> const PrintRegion* {
|
||||
auto find_region = [](PrintObject* object, size_t config_hash) -> const PrintRegion* {
|
||||
int regions_count = object->num_printing_regions();
|
||||
for (int index = 0; index < regions_count; index++ )
|
||||
{
|
||||
@@ -6252,17 +6254,26 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
|
||||
}
|
||||
return wtels;
|
||||
}
|
||||
void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall)
|
||||
void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall, float cone_angle)
|
||||
{
|
||||
wipe_tower_mesh_data = WipeTowerMeshData{};
|
||||
float first_layer_height=0.08; //brim height
|
||||
if (width < EPSILON || depth < EPSILON || height < EPSILON) return;
|
||||
if (!is_rib_wipe_tower || rib_length < EPSILON) {
|
||||
if (cone_angle > EPSILON && (!is_rib_wipe_tower || rib_length < EPSILON)) {
|
||||
// Cone tower: the base bulges past the body box; this bottom polygon feeds the
|
||||
// containment checks, so it must carry the bulge and the brim (cone not lofted).
|
||||
wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height);
|
||||
wipe_tower_mesh_data->bottom = WipeTower2::cone_base_polygon(width, depth, height, cone_angle);
|
||||
auto brim_bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width));
|
||||
if (!brim_bottom.empty())
|
||||
wipe_tower_mesh_data->bottom = brim_bottom.front();
|
||||
wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height);
|
||||
} else if (!is_rib_wipe_tower || rib_length < EPSILON) {
|
||||
wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height);
|
||||
wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height);
|
||||
wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0});
|
||||
wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, 0}), scaled(Vec2f{width + brim_width, depth + brim_width}),
|
||||
scaled(Vec2f{0, depth})};
|
||||
wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, -brim_width}),
|
||||
scaled(Vec2f{width + brim_width, depth + brim_width}), scaled(Vec2f{-brim_width, depth + brim_width})};
|
||||
} else {
|
||||
wipe_tower_mesh_data->real_wipe_tower_mesh = WipeTower::its_make_rib_tower(width, depth, height, rib_length, rib_width, fillet_wall);
|
||||
wipe_tower_mesh_data->bottom = WipeTower::rib_section(width, depth, rib_length, rib_width, fillet_wall);
|
||||
|
||||
Reference in New Issue
Block a user